Merge branch 'develop'
This commit is contained in:
		
						commit
						05d0620491
					
				
					 86 changed files with 2364 additions and 872 deletions
				
			
		
							
								
								
									
										19
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -1,11 +1,23 @@
 | 
				
			||||||
 | 
					# Visual Studio Code
 | 
				
			||||||
 | 
					/.vscode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Intelij-IDEA
 | 
				
			||||||
 | 
					/.idea
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Node.js
 | 
				
			||||||
 | 
					/node_modules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# yarn
 | 
				
			||||||
 | 
					yarn.lock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# config
 | 
				
			||||||
/.config/*
 | 
					/.config/*
 | 
				
			||||||
!/.config/example.yml
 | 
					!/.config/example.yml
 | 
				
			||||||
!/.config/mongo_initdb_example.js
 | 
					!/.config/mongo_initdb_example.js
 | 
				
			||||||
/.vscode
 | 
					
 | 
				
			||||||
/node_modules
 | 
					# misskey
 | 
				
			||||||
/build
 | 
					/build
 | 
				
			||||||
/built
 | 
					/built
 | 
				
			||||||
built
 | 
					 | 
				
			||||||
/data
 | 
					/data
 | 
				
			||||||
/.cache-loader
 | 
					/.cache-loader
 | 
				
			||||||
/db
 | 
					/db
 | 
				
			||||||
| 
						 | 
					@ -17,7 +29,6 @@ api-docs.json
 | 
				
			||||||
*.log
 | 
					*.log
 | 
				
			||||||
/redis
 | 
					/redis
 | 
				
			||||||
*.code-workspace
 | 
					*.code-workspace
 | 
				
			||||||
yarn.lock
 | 
					 | 
				
			||||||
.DS_Store
 | 
					.DS_Store
 | 
				
			||||||
/files
 | 
					/files
 | 
				
			||||||
ormconfig.json
 | 
					ormconfig.json
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										13
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										13
									
								
								CHANGELOG.md
									
										
									
									
									
								
							| 
						 | 
					@ -54,6 +54,19 @@ mongodb:
 | 
				
			||||||
8. master ブランチに戻す
 | 
					8. master ブランチに戻す
 | 
				
			||||||
9. enjoy
 | 
					9. enjoy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					11.16.0 (2019/05/19)
 | 
				
			||||||
 | 
					--------------------
 | 
				
			||||||
 | 
					### 注意
 | 
				
			||||||
 | 
					このアップデートを適用した後、プロセスを起動(もしくは再起動)する前に[マイグレーション](#migration)の手順を実行してください
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### ✨Improvements
 | 
				
			||||||
 | 
					* ユーザーグループ機能を追加
 | 
				
			||||||
 | 
					* ページに「いいね」できるように
 | 
				
			||||||
 | 
					* UIの改善
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🐛Fixes
 | 
				
			||||||
 | 
					* トークを読み込むときに最大数指定できなかった問題を修正
 | 
				
			||||||
 | 
					
 | 
				
			||||||
11.15.0 (2019/05/16)
 | 
					11.15.0 (2019/05/16)
 | 
				
			||||||
--------------------
 | 
					--------------------
 | 
				
			||||||
### ✨Improvements
 | 
					### ✨Improvements
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -265,6 +265,7 @@ common:
 | 
				
			||||||
  my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。"
 | 
					  my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。"
 | 
				
			||||||
  hide-password: "パスワードを隠す"
 | 
					  hide-password: "パスワードを隠す"
 | 
				
			||||||
  show-password: "パスワードを表示する"
 | 
					  show-password: "パスワードを表示する"
 | 
				
			||||||
 | 
					  enter-username: "ユーザー名を入力してください"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  do-not-use-in-production: "これは開発ビルドです。本番環境で使用しないでください。"
 | 
					  do-not-use-in-production: "これは開発ビルドです。本番環境で使用しないでください。"
 | 
				
			||||||
  user-suspended: "このユーザーは凍結されています。"
 | 
					  user-suspended: "このユーザーは凍結されています。"
 | 
				
			||||||
| 
						 | 
					@ -480,20 +481,24 @@ common/views/components/messaging.vue:
 | 
				
			||||||
  search-user: "ユーザーを探す"
 | 
					  search-user: "ユーザーを探す"
 | 
				
			||||||
  you: "あなた"
 | 
					  you: "あなた"
 | 
				
			||||||
  no-history: "履歴はありません"
 | 
					  no-history: "履歴はありません"
 | 
				
			||||||
 | 
					  user: "ユーザー"
 | 
				
			||||||
 | 
					  group: "グループ"
 | 
				
			||||||
 | 
					  start-with-user: "ユーザーとトークを開始"
 | 
				
			||||||
 | 
					  start-with-group: "グループとトークを開始"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
common/views/components/messaging-room.vue:
 | 
					common/views/components/messaging-room.vue:
 | 
				
			||||||
  empty: "このユーザーと話したことはありません"
 | 
					  not-talked-user: "このユーザーとの会話はありません"
 | 
				
			||||||
 | 
					  not-talked-group: "このグループでの会話はありません"
 | 
				
			||||||
  no-history: "これより過去の履歴はありません"
 | 
					  no-history: "これより過去の履歴はありません"
 | 
				
			||||||
  resize-form: "ドラッグしてフォームの広さを調整"
 | 
					 | 
				
			||||||
  new-message: "新しいメッセージがあります"
 | 
					  new-message: "新しいメッセージがあります"
 | 
				
			||||||
  only-one-file-attached: "メッセージに添付できるのはひとつのファイルのみです"
 | 
					  only-one-file-attached: "メッセージに添付できるファイルはひとつです"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
common/views/components/messaging-room.form.vue:
 | 
					common/views/components/messaging-room.form.vue:
 | 
				
			||||||
  input-message-here: "ここにメッセージを入力"
 | 
					  input-message-here: "ここにメッセージを入力"
 | 
				
			||||||
  send: "送信"
 | 
					  send: "送信"
 | 
				
			||||||
  attach-from-local: "PCからファイルを添付する"
 | 
					  attach-from-local: "PCからファイルを添付する"
 | 
				
			||||||
  attach-from-drive: "ドライブからファイルを添付する"
 | 
					  attach-from-drive: "ドライブからファイルを添付する"
 | 
				
			||||||
  only-one-file-attached: "メッセージに添付できるのはひとつのファイルのみです"
 | 
					  only-one-file-attached: "メッセージに添付できるファイルはひとつです"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
common/views/components/messaging-room.message.vue:
 | 
					common/views/components/messaging-room.message.vue:
 | 
				
			||||||
  is-read: "既読"
 | 
					  is-read: "既読"
 | 
				
			||||||
| 
						 | 
					@ -750,11 +755,27 @@ common/views/components/user-list-editor.vue:
 | 
				
			||||||
  remove-user: "このリストから削除"
 | 
					  remove-user: "このリストから削除"
 | 
				
			||||||
  delete-are-you-sure: "リスト「$1」を削除しますか?"
 | 
					  delete-are-you-sure: "リスト「$1」を削除しますか?"
 | 
				
			||||||
  deleted: "削除しました"
 | 
					  deleted: "削除しました"
 | 
				
			||||||
 | 
					  add-user: "ユーザーを追加"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					common/views/components/user-group-editor.vue:
 | 
				
			||||||
 | 
					  users: "メンバー"
 | 
				
			||||||
 | 
					  rename: "グループ名を変更"
 | 
				
			||||||
 | 
					  delete: "グループを削除"
 | 
				
			||||||
 | 
					  remove-user: "このグループから削除"
 | 
				
			||||||
 | 
					  delete-are-you-sure: "グループ「$1」を削除しますか?"
 | 
				
			||||||
 | 
					  deleted: "削除しました"
 | 
				
			||||||
 | 
					  add-user: "メンバーを追加"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
common/views/components/user-lists.vue:
 | 
					common/views/components/user-lists.vue:
 | 
				
			||||||
 | 
					  user-lists: "リスト"
 | 
				
			||||||
  create-list: "リストを作成"
 | 
					  create-list: "リストを作成"
 | 
				
			||||||
  list-name: "リスト名"
 | 
					  list-name: "リスト名"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					common/views/components/user-groups.vue:
 | 
				
			||||||
 | 
					  user-groups: "グループ"
 | 
				
			||||||
 | 
					  create-group: "グループを作成"
 | 
				
			||||||
 | 
					  group-name: "グループ名"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
common/views/widgets/broadcast.vue:
 | 
					common/views/widgets/broadcast.vue:
 | 
				
			||||||
  fetching: "確認中"
 | 
					  fetching: "確認中"
 | 
				
			||||||
  no-broadcasts: "お知らせはありません"
 | 
					  no-broadcasts: "お知らせはありません"
 | 
				
			||||||
| 
						 | 
					@ -827,6 +848,11 @@ common/views/pages/follow.vue:
 | 
				
			||||||
  follow-processing: "フォロー処理中"
 | 
					  follow-processing: "フォロー処理中"
 | 
				
			||||||
  follow-request: "フォロー申請"
 | 
					  follow-request: "フォロー申請"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					common/views/pages/follow-requests.vue:
 | 
				
			||||||
 | 
					  received-follow-requests: "フォロー申請"
 | 
				
			||||||
 | 
					  accept: "承認"
 | 
				
			||||||
 | 
					  reject: "拒否"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
desktop:
 | 
					desktop:
 | 
				
			||||||
  banner-crop-title: "バナーとして表示する部分を選択"
 | 
					  banner-crop-title: "バナーとして表示する部分を選択"
 | 
				
			||||||
  banner: "バナー"
 | 
					  banner: "バナー"
 | 
				
			||||||
| 
						 | 
					@ -1139,6 +1165,7 @@ desktop/views/components/ui.header.vue:
 | 
				
			||||||
desktop/views/components/ui.header.account.vue:
 | 
					desktop/views/components/ui.header.account.vue:
 | 
				
			||||||
  profile: "プロフィール"
 | 
					  profile: "プロフィール"
 | 
				
			||||||
  lists: "リスト"
 | 
					  lists: "リスト"
 | 
				
			||||||
 | 
					  groups: "グループ"
 | 
				
			||||||
  follow-requests: "フォロー申請"
 | 
					  follow-requests: "フォロー申請"
 | 
				
			||||||
  admin: "管理"
 | 
					  admin: "管理"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1154,14 +1181,6 @@ desktop/views/components/ui.header.post.vue:
 | 
				
			||||||
desktop/views/components/ui.header.search.vue:
 | 
					desktop/views/components/ui.header.search.vue:
 | 
				
			||||||
  placeholder: "検索"
 | 
					  placeholder: "検索"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
desktop/views/components/received-follow-requests-window.vue:
 | 
					 | 
				
			||||||
  title: "フォロー申請"
 | 
					 | 
				
			||||||
  accept: "承認"
 | 
					 | 
				
			||||||
  reject: "拒否"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
desktop/views/components/user-lists-window.vue:
 | 
					 | 
				
			||||||
  title: "リスト"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
desktop/views/components/user-preview.vue:
 | 
					desktop/views/components/user-preview.vue:
 | 
				
			||||||
  notes: "投稿"
 | 
					  notes: "投稿"
 | 
				
			||||||
  following: "フォロー"
 | 
					  following: "フォロー"
 | 
				
			||||||
| 
						 | 
					@ -1700,6 +1719,7 @@ mobile/views/components/ui.nav.vue:
 | 
				
			||||||
  follow-requests: "フォロー申請"
 | 
					  follow-requests: "フォロー申請"
 | 
				
			||||||
  search: "検索"
 | 
					  search: "検索"
 | 
				
			||||||
  user-lists: "リスト"
 | 
					  user-lists: "リスト"
 | 
				
			||||||
 | 
					  user-groups: "グループ"
 | 
				
			||||||
  widgets: "ウィジェット"
 | 
					  widgets: "ウィジェット"
 | 
				
			||||||
  game: "ゲーム"
 | 
					  game: "ゲーム"
 | 
				
			||||||
  admin: "管理"
 | 
					  admin: "管理"
 | 
				
			||||||
| 
						 | 
					@ -1714,9 +1734,6 @@ mobile/views/pages/drive.vue:
 | 
				
			||||||
    move-folder: "このフォルダを移動"
 | 
					    move-folder: "このフォルダを移動"
 | 
				
			||||||
    delete-folder: "このフォルダを削除"
 | 
					    delete-folder: "このフォルダを削除"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mobile/views/pages/user-lists.vue:
 | 
					 | 
				
			||||||
  title: "リスト"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
mobile/views/pages/signup.vue:
 | 
					mobile/views/pages/signup.vue:
 | 
				
			||||||
  lets-start: "📦 始めましょう"
 | 
					  lets-start: "📦 始めましょう"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1749,11 +1766,6 @@ mobile/views/pages/widgets/activity.vue:
 | 
				
			||||||
mobile/views/pages/share.vue:
 | 
					mobile/views/pages/share.vue:
 | 
				
			||||||
  share-with: "{name}で共有"
 | 
					  share-with: "{name}で共有"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mobile/views/pages/received-follow-requests.vue:
 | 
					 | 
				
			||||||
  title: "フォロー申請"
 | 
					 | 
				
			||||||
  accept: "承認"
 | 
					 | 
				
			||||||
  reject: "拒否"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
mobile/views/pages/note.vue:
 | 
					mobile/views/pages/note.vue:
 | 
				
			||||||
  title: "投稿"
 | 
					  title: "投稿"
 | 
				
			||||||
  prev: "前の投稿"
 | 
					  prev: "前の投稿"
 | 
				
			||||||
| 
						 | 
					@ -1874,6 +1886,10 @@ pages:
 | 
				
			||||||
  edit-this-page: "このページを編集"
 | 
					  edit-this-page: "このページを編集"
 | 
				
			||||||
  view-source: "ソースを表示"
 | 
					  view-source: "ソースを表示"
 | 
				
			||||||
  view-page: "ページを見る"
 | 
					  view-page: "ページを見る"
 | 
				
			||||||
 | 
					  like: "いいね"
 | 
				
			||||||
 | 
					  unlike: "いいね解除"
 | 
				
			||||||
 | 
					  liked-pages: "いいねしたページ"
 | 
				
			||||||
 | 
					  my-pages: "自分のページ"
 | 
				
			||||||
  inspector: "インスペクター"
 | 
					  inspector: "インスペクター"
 | 
				
			||||||
  content: "ページブロック"
 | 
					  content: "ページブロック"
 | 
				
			||||||
  variables: "変数"
 | 
					  variables: "変数"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										23
									
								
								migration/1558072954435-PageLike.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								migration/1558072954435-PageLike.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,23 @@
 | 
				
			||||||
 | 
					import {MigrationInterface, QueryRunner} from "typeorm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class PageLike1558072954435 implements MigrationInterface {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async up(queryRunner: QueryRunner): Promise<any> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`CREATE TABLE "page_like" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "pageId" character varying(32) NOT NULL, CONSTRAINT "PK_813f034843af992d3ae0f43c64c" PRIMARY KEY ("id"))`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`CREATE INDEX "IDX_0e61efab7f88dbb79c9166dbb4" ON "page_like" ("userId") `);
 | 
				
			||||||
 | 
					        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_4ce6fb9c70529b4c8ac46c9bfa" ON "page_like" ("userId", "pageId") `);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "page" ADD "likedCount" integer NOT NULL DEFAULT 0`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "page_like" ADD CONSTRAINT "FK_0e61efab7f88dbb79c9166dbb48" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "page_like" ADD CONSTRAINT "FK_cf8782626dced3176038176a847" FOREIGN KEY ("pageId") REFERENCES "page"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async down(queryRunner: QueryRunner): Promise<any> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "page_like" DROP CONSTRAINT "FK_cf8782626dced3176038176a847"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "page_like" DROP CONSTRAINT "FK_0e61efab7f88dbb79c9166dbb48"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "page" DROP COLUMN "likedCount"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`DROP INDEX "IDX_4ce6fb9c70529b4c8ac46c9bfa"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`DROP INDEX "IDX_0e61efab7f88dbb79c9166dbb4"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`DROP TABLE "page_like"`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								migration/1558103093633-UserGroup.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								migration/1558103093633-UserGroup.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					import {MigrationInterface, QueryRunner} from "typeorm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class UserGroup1558103093633 implements MigrationInterface {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async up(queryRunner: QueryRunner): Promise<any> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`CREATE TABLE "user_group" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "name" character varying(256) NOT NULL, "userId" character varying(32) NOT NULL, "isPrivate" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_3c29fba6fe013ec8724378ce7c9" PRIMARY KEY ("id"))`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`CREATE INDEX "IDX_20e30aa35180e317e133d75316" ON "user_group" ("createdAt") `);
 | 
				
			||||||
 | 
					        await queryRunner.query(`CREATE INDEX "IDX_3d6b372788ab01be58853003c9" ON "user_group" ("userId") `);
 | 
				
			||||||
 | 
					        await queryRunner.query(`CREATE TABLE "user_group_joining" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userGroupId" character varying(32) NOT NULL, CONSTRAINT "PK_15f2425885253c5507e1599cfe7" PRIMARY KEY ("id"))`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`CREATE INDEX "IDX_f3a1b4bd0c7cabba958a0c0b23" ON "user_group_joining" ("userId") `);
 | 
				
			||||||
 | 
					        await queryRunner.query(`CREATE INDEX "IDX_67dc758bc0566985d1b3d39986" ON "user_group_joining" ("userGroupId") `);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "messaging_message" ADD "groupId" character varying(32)`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "messaging_message" ADD "reads" character varying(32) array NOT NULL DEFAULT '{}'::varchar[]`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "messaging_message" ALTER COLUMN "recipientId" DROP NOT NULL`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."recipientId" IS 'The recipient user ID.'`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`CREATE INDEX "IDX_2c4be03b446884f9e9c502135b" ON "messaging_message" ("groupId") `);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "messaging_message" ADD CONSTRAINT "FK_2c4be03b446884f9e9c502135be" FOREIGN KEY ("groupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user_group" ADD CONSTRAINT "FK_3d6b372788ab01be58853003c93" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user_group_joining" ADD CONSTRAINT "FK_f3a1b4bd0c7cabba958a0c0b231" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user_group_joining" ADD CONSTRAINT "FK_67dc758bc0566985d1b3d399865" FOREIGN KEY ("userGroupId") REFERENCES "user_group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public async down(queryRunner: QueryRunner): Promise<any> {
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user_group_joining" DROP CONSTRAINT "FK_67dc758bc0566985d1b3d399865"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user_group_joining" DROP CONSTRAINT "FK_f3a1b4bd0c7cabba958a0c0b231"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "user_group" DROP CONSTRAINT "FK_3d6b372788ab01be58853003c93"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "messaging_message" DROP CONSTRAINT "FK_2c4be03b446884f9e9c502135be"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`DROP INDEX "IDX_2c4be03b446884f9e9c502135b"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`COMMENT ON COLUMN "messaging_message"."recipientId" IS ''`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "messaging_message" ALTER COLUMN "recipientId" SET NOT NULL`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "messaging_message" DROP COLUMN "reads"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`ALTER TABLE "messaging_message" DROP COLUMN "groupId"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`DROP INDEX "IDX_67dc758bc0566985d1b3d39986"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`DROP INDEX "IDX_f3a1b4bd0c7cabba958a0c0b23"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`DROP TABLE "user_group_joining"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`DROP INDEX "IDX_3d6b372788ab01be58853003c9"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`DROP INDEX "IDX_20e30aa35180e317e133d75316"`);
 | 
				
			||||||
 | 
					        await queryRunner.query(`DROP TABLE "user_group"`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"name": "misskey",
 | 
						"name": "misskey",
 | 
				
			||||||
	"author": "syuilo <i@syuilo.com>",
 | 
						"author": "syuilo <i@syuilo.com>",
 | 
				
			||||||
	"version": "11.15.0",
 | 
						"version": "11.16.0",
 | 
				
			||||||
	"codename": "daybreak",
 | 
						"codename": "daybreak",
 | 
				
			||||||
	"repository": {
 | 
						"repository": {
 | 
				
			||||||
		"type": "git",
 | 
							"type": "git",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
	<ui-card>
 | 
						<ui-card>
 | 
				
			||||||
		<template #title><fa icon="broadcast-tower"/> {{ $t('announcements') }}</template>
 | 
							<template #title><fa :icon="faBroadcastTower"/> {{ $t('announcements') }}</template>
 | 
				
			||||||
		<section v-for="(announcement, i) in announcements" class="fit-top">
 | 
							<section v-for="(announcement, i) in announcements" class="fit-top">
 | 
				
			||||||
			<ui-input v-model="announcement.title" @change="save">
 | 
								<ui-input v-model="announcement.title" @change="save">
 | 
				
			||||||
				<span>{{ $t('title') }}</span>
 | 
									<span>{{ $t('title') }}</span>
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@
 | 
				
			||||||
			</ui-horizon-group>
 | 
								</ui-horizon-group>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
		<section>
 | 
							<section>
 | 
				
			||||||
			<ui-button @click="add"><fa icon="plus"/> {{ $t('add') }}</ui-button>
 | 
								<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('add') }}</ui-button>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
	</ui-card>
 | 
						</ui-card>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
| 
						 | 
					@ -27,12 +27,14 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../i18n';
 | 
					import i18n from '../../i18n';
 | 
				
			||||||
 | 
					import { faBroadcastTower, faPlus } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('admin/views/announcements.vue'),
 | 
						i18n: i18n('admin/views/announcements.vue'),
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			announcements: [],
 | 
								announcements: [],
 | 
				
			||||||
 | 
								faBroadcastTower, faPlus
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,7 @@
 | 
				
			||||||
				<fa icon="spinner" pulse v-if="type === 'waiting'"/>
 | 
									<fa icon="spinner" pulse v-if="type === 'waiting'"/>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<header v-if="title" v-html="title"></header>
 | 
								<header v-if="title" v-html="title"></header>
 | 
				
			||||||
 | 
								<header v-if="title == null && user">{{ $t('@.enter-username') }}</header>
 | 
				
			||||||
			<div class="body" v-if="text" v-html="text"></div>
 | 
								<div class="body" v-if="text" v-html="text"></div>
 | 
				
			||||||
			<ui-input v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></ui-input>
 | 
								<ui-input v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></ui-input>
 | 
				
			||||||
			<ui-input v-if="user" v-model="userInputValue" autofocus @keydown="onInputKeydown"><template #prefix>@</template></ui-input>
 | 
								<ui-input v-if="user" v-model="userInputValue" autofocus @keydown="onInputKeydown"><template #prefix>@</template></ui-input>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,6 +44,8 @@ import uiSwitch from './ui/switch.vue';
 | 
				
			||||||
import uiRadio from './ui/radio.vue';
 | 
					import uiRadio from './ui/radio.vue';
 | 
				
			||||||
import uiSelect from './ui/select.vue';
 | 
					import uiSelect from './ui/select.vue';
 | 
				
			||||||
import uiInfo from './ui/info.vue';
 | 
					import uiInfo from './ui/info.vue';
 | 
				
			||||||
 | 
					import uiMargin from './ui/margin.vue';
 | 
				
			||||||
 | 
					import uiHr from './ui/hr.vue';
 | 
				
			||||||
import formButton from './ui/form/button.vue';
 | 
					import formButton from './ui/form/button.vue';
 | 
				
			||||||
import formRadio from './ui/form/radio.vue';
 | 
					import formRadio from './ui/form/radio.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -91,5 +93,7 @@ Vue.component('ui-switch', uiSwitch);
 | 
				
			||||||
Vue.component('ui-radio', uiRadio);
 | 
					Vue.component('ui-radio', uiRadio);
 | 
				
			||||||
Vue.component('ui-select', uiSelect);
 | 
					Vue.component('ui-select', uiSelect);
 | 
				
			||||||
Vue.component('ui-info', uiInfo);
 | 
					Vue.component('ui-info', uiInfo);
 | 
				
			||||||
 | 
					Vue.component('ui-margin', uiMargin);
 | 
				
			||||||
 | 
					Vue.component('ui-hr', uiHr);
 | 
				
			||||||
Vue.component('form-button', formButton);
 | 
					Vue.component('form-button', formButton);
 | 
				
			||||||
Vue.component('form-radio', formRadio);
 | 
					Vue.component('form-radio', formRadio);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,16 @@ import * as autosize from 'autosize';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('common/views/components/messaging-room.form.vue'),
 | 
						i18n: i18n('common/views/components/messaging-room.form.vue'),
 | 
				
			||||||
	props: ['user'],
 | 
						props: {
 | 
				
			||||||
 | 
							user: {
 | 
				
			||||||
 | 
								type: Object,
 | 
				
			||||||
 | 
								requird: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							group: {
 | 
				
			||||||
 | 
								type: Object,
 | 
				
			||||||
 | 
								requird: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			text: null,
 | 
								text: null,
 | 
				
			||||||
| 
						 | 
					@ -43,7 +52,7 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		draftId(): string {
 | 
							draftId(): string {
 | 
				
			||||||
			return this.user.id;
 | 
								return this.user ? 'user:' + this.user.id : 'group:' + this.group.id;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		canSend(): boolean {
 | 
							canSend(): boolean {
 | 
				
			||||||
			return (this.text != null && this.text != '') || this.file != null;
 | 
								return (this.text != null && this.text != '') || this.file != null;
 | 
				
			||||||
| 
						 | 
					@ -159,7 +168,8 @@ export default Vue.extend({
 | 
				
			||||||
		send() {
 | 
							send() {
 | 
				
			||||||
			this.sending = true;
 | 
								this.sending = true;
 | 
				
			||||||
			this.$root.api('messaging/messages/create', {
 | 
								this.$root.api('messaging/messages/create', {
 | 
				
			||||||
				userId: this.user.id,
 | 
									userId: this.user ? this.user.id : undefined,
 | 
				
			||||||
 | 
									groupId: this.group ? this.group.id : undefined,
 | 
				
			||||||
				text: this.text ? this.text : undefined,
 | 
									text: this.text ? this.text : undefined,
 | 
				
			||||||
				fileId: this.file ? this.file.id : undefined
 | 
									fileId: this.file ? this.file.id : undefined
 | 
				
			||||||
			}).then(message => {
 | 
								}).then(message => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,12 @@
 | 
				
			||||||
		<div></div>
 | 
							<div></div>
 | 
				
			||||||
		<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
 | 
							<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
 | 
				
			||||||
		<footer>
 | 
							<footer>
 | 
				
			||||||
 | 
								<template v-if="isGroup">
 | 
				
			||||||
 | 
									<span class="read" v-if="message.reads.length > 0">{{ $t('is-read') }} {{ message.reads.length }}</span>
 | 
				
			||||||
 | 
								</template>
 | 
				
			||||||
 | 
								<template v-else>
 | 
				
			||||||
				<span class="read" v-if="isMe && message.isRead">{{ $t('is-read') }}</span>
 | 
									<span class="read" v-if="isMe && message.isRead">{{ $t('is-read') }}</span>
 | 
				
			||||||
 | 
								</template>
 | 
				
			||||||
			<mk-time :time="message.createdAt"/>
 | 
								<mk-time :time="message.createdAt"/>
 | 
				
			||||||
			<template v-if="message.is_edited"><fa icon="pencil-alt"/></template>
 | 
								<template v-if="message.is_edited"><fa icon="pencil-alt"/></template>
 | 
				
			||||||
		</footer>
 | 
							</footer>
 | 
				
			||||||
| 
						 | 
					@ -42,6 +47,9 @@ export default Vue.extend({
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		message: {
 | 
							message: {
 | 
				
			||||||
			required: true
 | 
								required: true
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							isGroup: {
 | 
				
			||||||
 | 
								required: false
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,14 +4,14 @@
 | 
				
			||||||
	@drop.prevent.stop="onDrop"
 | 
						@drop.prevent.stop="onDrop"
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
	<div class="body">
 | 
						<div class="body">
 | 
				
			||||||
		<p class="init" v-if="init"><fa icon="spinner .spin"/>{{ $t('@.loading') }}</p>
 | 
							<p class="init" v-if="init"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}</p>
 | 
				
			||||||
		<p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ $t('empty') }}</p>
 | 
							<p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ user ? $t('not-talked-user') : $t('not-talked-group') }}</p>
 | 
				
			||||||
		<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa :icon="faFlag"/>{{ $t('no-history') }}</p>
 | 
							<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa :icon="faFlag"/>{{ $t('no-history') }}</p>
 | 
				
			||||||
		<button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
 | 
							<button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
 | 
				
			||||||
			<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
 | 
								<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
 | 
				
			||||||
		</button>
 | 
							</button>
 | 
				
			||||||
		<template v-for="(message, i) in _messages">
 | 
							<template v-for="(message, i) in _messages">
 | 
				
			||||||
			<x-message :message="message" :key="message.id"/>
 | 
								<x-message :message="message" :key="message.id" :is-group="group != null"/>
 | 
				
			||||||
			<p class="date" v-if="i != messages.length - 1 && message._date != _messages[i + 1]._date">
 | 
								<p class="date" v-if="i != messages.length - 1 && message._date != _messages[i + 1]._date">
 | 
				
			||||||
				<span>{{ _messages[i + 1]._datetext }}</span>
 | 
									<span>{{ _messages[i + 1]._datetext }}</span>
 | 
				
			||||||
			</p>
 | 
								</p>
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,7 @@
 | 
				
			||||||
				<button @click="onIndicatorClick"><i><fa :icon="faArrowCircleDown"/></i>{{ $t('new-message') }}</button>
 | 
									<button @click="onIndicatorClick"><i><fa :icon="faArrowCircleDown"/></i>{{ $t('new-message') }}</button>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</transition>
 | 
							</transition>
 | 
				
			||||||
		<x-form :user="user" ref="form"/>
 | 
							<x-form :user="user" :group="group" ref="form"/>
 | 
				
			||||||
	</footer>
 | 
						</footer>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -34,17 +34,30 @@ import i18n from '../../../i18n';
 | 
				
			||||||
import XMessage from './messaging-room.message.vue';
 | 
					import XMessage from './messaging-room.message.vue';
 | 
				
			||||||
import XForm from './messaging-room.form.vue';
 | 
					import XForm from './messaging-room.form.vue';
 | 
				
			||||||
import { url } from '../../../config';
 | 
					import { url } from '../../../config';
 | 
				
			||||||
import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faArrowCircleDown, faFlag } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faFlag } from '@fortawesome/free-regular-svg-icons';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('common/views/components/messaging-room.vue'),
 | 
						i18n: i18n('common/views/components/messaging-room.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XMessage,
 | 
							XMessage,
 | 
				
			||||||
		XForm
 | 
							XForm
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: ['user', 'isNaked'],
 | 
						props: {
 | 
				
			||||||
 | 
							user: {
 | 
				
			||||||
 | 
								type: Object,
 | 
				
			||||||
 | 
								requird: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							group: {
 | 
				
			||||||
 | 
								type: Object,
 | 
				
			||||||
 | 
								requird: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							isNaked: {
 | 
				
			||||||
 | 
								type: Boolean,
 | 
				
			||||||
 | 
								requird: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
| 
						 | 
					@ -76,7 +89,10 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		this.connection = this.$root.stream.connectToChannel('messaging', { otherparty: this.user.id });
 | 
							this.connection = this.$root.stream.connectToChannel('messaging', {
 | 
				
			||||||
 | 
								otherparty: this.user ? this.user.id : undefined,
 | 
				
			||||||
 | 
								group: this.group ? this.group.id : undefined,
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.connection.on('message', this.onMessage);
 | 
							this.connection.on('message', this.onMessage);
 | 
				
			||||||
		this.connection.on('read', this.onRead);
 | 
							this.connection.on('read', this.onRead);
 | 
				
			||||||
| 
						 | 
					@ -147,7 +163,8 @@ export default Vue.extend({
 | 
				
			||||||
				const max = this.existMoreMessages ? 20 : 10;
 | 
									const max = this.existMoreMessages ? 20 : 10;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.$root.api('messaging/messages', {
 | 
									this.$root.api('messaging/messages', {
 | 
				
			||||||
					userId: this.user.id,
 | 
										userId: this.user ? this.user.id : undefined,
 | 
				
			||||||
 | 
										groupId: this.group ? this.group.id : undefined,
 | 
				
			||||||
					limit: max + 1,
 | 
										limit: max + 1,
 | 
				
			||||||
					untilId: this.existMoreMessages ? this.messages[0].id : undefined
 | 
										untilId: this.existMoreMessages ? this.messages[0].id : undefined
 | 
				
			||||||
				}).then(messages => {
 | 
									}).then(messages => {
 | 
				
			||||||
| 
						 | 
					@ -199,14 +216,23 @@ export default Vue.extend({
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onRead(ids) {
 | 
							onRead(x) {
 | 
				
			||||||
			if (!Array.isArray(ids)) ids = [ids];
 | 
								if (this.user) {
 | 
				
			||||||
			for (const id of ids) {
 | 
									if (!Array.isArray(x)) x = [x];
 | 
				
			||||||
 | 
									for (const id of x) {
 | 
				
			||||||
					if (this.messages.some(x => x.id == id)) {
 | 
										if (this.messages.some(x => x.id == id)) {
 | 
				
			||||||
						const exist = this.messages.map(x => x.id).indexOf(id);
 | 
											const exist = this.messages.map(x => x.id).indexOf(id);
 | 
				
			||||||
						this.messages[exist].isRead = true;
 | 
											this.messages[exist].isRead = true;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
								} else if (this.group) {
 | 
				
			||||||
 | 
									for (const id of x.ids) {
 | 
				
			||||||
 | 
										if (this.messages.some(x => x.id == id)) {
 | 
				
			||||||
 | 
											const exist = this.messages.map(x => x.id).indexOf(id);
 | 
				
			||||||
 | 
											this.messages[exist].reads.push(x.userId);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		onDeleted(id) {
 | 
							onDeleted(id) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,7 @@
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<div class="history" v-if="messages.length > 0">
 | 
						<div class="history" v-if="messages.length > 0">
 | 
				
			||||||
		<template>
 | 
							<div class="title">{{ $t('user') }}</div>
 | 
				
			||||||
		<a v-for="message in messages"
 | 
							<a v-for="message in messages"
 | 
				
			||||||
			class="user"
 | 
								class="user"
 | 
				
			||||||
			:href="`/i/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
 | 
								:href="`/i/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
 | 
				
			||||||
| 
						 | 
					@ -42,15 +42,41 @@
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</a>
 | 
							</a>
 | 
				
			||||||
		</template>
 | 
					 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<p class="no-history" v-if="!fetching && messages.length == 0">{{ $t('no-history') }}</p>
 | 
						<div class="history" v-if="groupMessages.length > 0">
 | 
				
			||||||
 | 
							<div class="title">{{ $t('group') }}</div>
 | 
				
			||||||
 | 
							<a v-for="message in groupMessages"
 | 
				
			||||||
 | 
								class="user"
 | 
				
			||||||
 | 
								:href="`/i/messaging/group/${message.groupId}`"
 | 
				
			||||||
 | 
								:data-is-me="isMe(message)"
 | 
				
			||||||
 | 
								:data-is-read="message.reads.includes($store.state.i.id)"
 | 
				
			||||||
 | 
								@click.prevent="navigateGroup(message.group)"
 | 
				
			||||||
 | 
								:key="message.id"
 | 
				
			||||||
 | 
							>
 | 
				
			||||||
 | 
								<div>
 | 
				
			||||||
 | 
									<mk-avatar class="avatar" :user="message.user"/>
 | 
				
			||||||
 | 
									<header>
 | 
				
			||||||
 | 
										<span class="name">{{ message.group.name }}</span>
 | 
				
			||||||
 | 
										<mk-time :time="message.createdAt"/>
 | 
				
			||||||
 | 
									</header>
 | 
				
			||||||
 | 
									<div class="body">
 | 
				
			||||||
 | 
										<p class="text"><span class="me" v-if="isMe(message)">{{ $t('you') }}:</span>{{ message.text }}</p>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
							</a>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						<p class="no-history" v-if="!fetching && (messages.length == 0 && groupMessages.length == 0)">{{ $t('no-history') }}</p>
 | 
				
			||||||
	<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
 | 
						<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
 | 
				
			||||||
 | 
						<ui-margin>
 | 
				
			||||||
 | 
							<ui-button @click="startUser()"><fa :icon="faUser"/> {{ $t('start-with-user') }}</ui-button>
 | 
				
			||||||
 | 
							<ui-button @click="startGroup()"><fa :icon="faUsers"/> {{ $t('start-with-group') }}</ui-button>
 | 
				
			||||||
 | 
						</ui-margin>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import { faUser, faUsers } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import getAcct from '../../../../../misc/acct/render';
 | 
					import getAcct from '../../../../../misc/acct/render';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -71,9 +97,11 @@ export default Vue.extend({
 | 
				
			||||||
			fetching: true,
 | 
								fetching: true,
 | 
				
			||||||
			moreFetching: false,
 | 
								moreFetching: false,
 | 
				
			||||||
			messages: [],
 | 
								messages: [],
 | 
				
			||||||
 | 
								groupMessages: [],
 | 
				
			||||||
			q: null,
 | 
								q: null,
 | 
				
			||||||
			result: [],
 | 
								result: [],
 | 
				
			||||||
			connection: null
 | 
								connection: null,
 | 
				
			||||||
 | 
								faUser, faUsers
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
| 
						 | 
					@ -82,10 +110,13 @@ export default Vue.extend({
 | 
				
			||||||
		this.connection.on('message', this.onMessage);
 | 
							this.connection.on('message', this.onMessage);
 | 
				
			||||||
		this.connection.on('read', this.onRead);
 | 
							this.connection.on('read', this.onRead);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.$root.api('messaging/history').then(messages => {
 | 
							this.$root.api('messaging/history', { group: false }).then(messages => {
 | 
				
			||||||
 | 
								this.$root.api('messaging/history', { group: true }).then(groupMessages => {
 | 
				
			||||||
				this.messages = messages;
 | 
									this.messages = messages;
 | 
				
			||||||
 | 
									this.groupMessages = groupMessages;
 | 
				
			||||||
				this.fetching = false;
 | 
									this.fetching = false;
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	beforeDestroy() {
 | 
						beforeDestroy() {
 | 
				
			||||||
		this.connection.dispose();
 | 
							this.connection.dispose();
 | 
				
			||||||
| 
						 | 
					@ -96,16 +127,27 @@ export default Vue.extend({
 | 
				
			||||||
			return message.userId == this.$store.state.i.id;
 | 
								return message.userId == this.$store.state.i.id;
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		onMessage(message) {
 | 
							onMessage(message) {
 | 
				
			||||||
 | 
								if (message.recipientId) {
 | 
				
			||||||
				this.messages = this.messages.filter(m => !(
 | 
									this.messages = this.messages.filter(m => !(
 | 
				
			||||||
					(m.recipientId == message.recipientId && m.userId == message.userId) ||
 | 
										(m.recipientId == message.recipientId && m.userId == message.userId) ||
 | 
				
			||||||
					(m.recipientId == message.userId && m.userId == message.recipientId)));
 | 
										(m.recipientId == message.userId && m.userId == message.recipientId)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				this.messages.unshift(message);
 | 
									this.messages.unshift(message);
 | 
				
			||||||
 | 
								} else if (message.groupId) {
 | 
				
			||||||
 | 
									this.groupMessages = this.groupMessages.filter(m => m.groupId !== message.groupId);
 | 
				
			||||||
 | 
									this.groupMessages.unshift(message);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		onRead(ids) {
 | 
							onRead(ids) {
 | 
				
			||||||
			for (const id of ids) {
 | 
								for (const id of ids) {
 | 
				
			||||||
				const found = this.messages.find(m => m.id == id);
 | 
									const found = this.messages.find(m => m.id == id);
 | 
				
			||||||
				if (found) found.isRead = true;
 | 
									if (found) {
 | 
				
			||||||
 | 
										if (found.recipientId) {
 | 
				
			||||||
 | 
											found.isRead = true;
 | 
				
			||||||
 | 
										} else if (found.groupId) {
 | 
				
			||||||
 | 
											found.reads.push(this.$store.state.i.id);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		search() {
 | 
							search() {
 | 
				
			||||||
| 
						 | 
					@ -125,6 +167,9 @@ export default Vue.extend({
 | 
				
			||||||
		navigate(user) {
 | 
							navigate(user) {
 | 
				
			||||||
			this.$emit('navigate', user);
 | 
								this.$emit('navigate', user);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							navigateGroup(group) {
 | 
				
			||||||
 | 
								this.$emit('navigateGroup', group);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		onSearchKeydown(e) {
 | 
							onSearchKeydown(e) {
 | 
				
			||||||
			switch (e.which) {
 | 
								switch (e.which) {
 | 
				
			||||||
				case 9: // [TAB]
 | 
									case 9: // [TAB]
 | 
				
			||||||
| 
						 | 
					@ -161,6 +206,30 @@ export default Vue.extend({
 | 
				
			||||||
					(list.childNodes[i].nextElementSibling || list.childNodes[0]).focus();
 | 
										(list.childNodes[i].nextElementSibling || list.childNodes[0]).focus();
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							async startUser() {
 | 
				
			||||||
 | 
								const { result: user } = await this.$root.dialog({
 | 
				
			||||||
 | 
									user: {
 | 
				
			||||||
 | 
										local: true
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								if (user == null) return;
 | 
				
			||||||
 | 
								this.navigate(user);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							async startGroup() {
 | 
				
			||||||
 | 
								const groups = await this.$root.api('users/groups/joined');
 | 
				
			||||||
 | 
								const { canceled, result: group } = await this.$root.dialog({
 | 
				
			||||||
 | 
									type: null,
 | 
				
			||||||
 | 
									title: this.$t('select-group'),
 | 
				
			||||||
 | 
									select: {
 | 
				
			||||||
 | 
										items: groups.map(group => ({
 | 
				
			||||||
 | 
											value: group, text: group.name
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									showCancelButton: true
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								if (canceled) return;
 | 
				
			||||||
 | 
								this.navigateGroup(group);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -173,6 +242,9 @@ export default Vue.extend({
 | 
				
			||||||
		font-size 0.8em
 | 
							font-size 0.8em
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> .history
 | 
							> .history
 | 
				
			||||||
 | 
								> .title
 | 
				
			||||||
 | 
									padding 8px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			> a
 | 
								> a
 | 
				
			||||||
				&:last-child
 | 
									&:last-child
 | 
				
			||||||
					border-bottom none
 | 
										border-bottom none
 | 
				
			||||||
| 
						 | 
					@ -311,6 +383,13 @@ export default Vue.extend({
 | 
				
			||||||
						color rgba(#000, 0.3)
 | 
											color rgba(#000, 0.3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> .history
 | 
						> .history
 | 
				
			||||||
 | 
							> .title
 | 
				
			||||||
 | 
								padding 6px 16px
 | 
				
			||||||
 | 
								margin 0 auto
 | 
				
			||||||
 | 
								max-width 500px
 | 
				
			||||||
 | 
								background rgba(0, 0, 0, 0.05)
 | 
				
			||||||
 | 
								color var(--text)
 | 
				
			||||||
 | 
								font-size 85%
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> a
 | 
							> a
 | 
				
			||||||
			display block
 | 
								display block
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<router-link :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
 | 
					<router-link :to="`/@${page.user.username}/pages/${page.name}`" class="vhpxefrj" tabindex="-1">
 | 
				
			||||||
	<div class="thumbnail" v-if="page.eyeCatchingImage" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div>
 | 
						<div class="thumbnail" v-if="page.eyeCatchingImage" :style="`background-image: url('${page.eyeCatchingImage.thumbnailUrl}')`"></div>
 | 
				
			||||||
	<article>
 | 
						<article>
 | 
				
			||||||
		<header>
 | 
							<header>
 | 
				
			||||||
| 
						 | 
					@ -32,16 +32,13 @@ export default Vue.extend({
 | 
				
			||||||
	display block
 | 
						display block
 | 
				
			||||||
	overflow hidden
 | 
						overflow hidden
 | 
				
			||||||
	width 100%
 | 
						width 100%
 | 
				
			||||||
	background var(--face)
 | 
						border solid var(--lineWidth) var(--urlPreviewBorder)
 | 
				
			||||||
 | 
						border-radius 4px
 | 
				
			||||||
 | 
						overflow hidden
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	&.round
 | 
						&:hover
 | 
				
			||||||
		border-radius 8px
 | 
							text-decoration none
 | 
				
			||||||
 | 
							border-color var(--urlPreviewBorderHover)
 | 
				
			||||||
	&.shadow
 | 
					 | 
				
			||||||
		box-shadow 0 4px 16px rgba(#000, 0.1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		@media (min-width 500px)
 | 
					 | 
				
			||||||
			box-shadow 0 8px 32px rgba(#000, 0.1)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	> .thumbnail
 | 
						> .thumbnail
 | 
				
			||||||
		position absolute
 | 
							position absolute
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -113,7 +113,7 @@ export default Vue.extend({
 | 
				
			||||||
	padding 8px 10px
 | 
						padding 8px 10px
 | 
				
			||||||
	text-align center
 | 
						text-align center
 | 
				
			||||||
	font-weight normal
 | 
						font-weight normal
 | 
				
			||||||
	font-size 16px
 | 
						font-size 14px
 | 
				
			||||||
	line-height 24px
 | 
						line-height 24px
 | 
				
			||||||
	border none
 | 
						border none
 | 
				
			||||||
	outline none
 | 
						outline none
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										15
									
								
								src/client/app/common/views/components/ui/hr.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/client/app/common/views/components/ui/hr.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div class="evrzpitu"></div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					export default Vue.extend({});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					.evrzpitu
 | 
				
			||||||
 | 
						margin 16px 0
 | 
				
			||||||
 | 
						border-bottom solid var(--lineWidth) var(--faceDivider)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										16
									
								
								src/client/app/common/views/components/ui/margin.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/client/app/common/views/components/ui/margin.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div class="zdcrxcne">
 | 
				
			||||||
 | 
						<slot></slot>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					export default Vue.extend({});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					.zdcrxcne
 | 
				
			||||||
 | 
						margin 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -1,95 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
 | 
					 | 
				
			||||||
	<button class="ui" @click="add">{{ $t('create-list') }}</button>
 | 
					 | 
				
			||||||
	<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.name }}</a>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n('common/views/components/user-lists.vue'),
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			lists: []
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	mounted() {
 | 
					 | 
				
			||||||
		this.$root.api('users/lists/list').then(lists => {
 | 
					 | 
				
			||||||
			this.fetching = false;
 | 
					 | 
				
			||||||
			this.lists = lists;
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		add() {
 | 
					 | 
				
			||||||
			this.$root.dialog({
 | 
					 | 
				
			||||||
				title: this.$t('list-name'),
 | 
					 | 
				
			||||||
				input: true
 | 
					 | 
				
			||||||
			}).then(async ({ canceled, result: name }) => {
 | 
					 | 
				
			||||||
				if (canceled) return;
 | 
					 | 
				
			||||||
				const list = await this.$root.api('users/lists/create', {
 | 
					 | 
				
			||||||
					name
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.lists.push(list)
 | 
					 | 
				
			||||||
				this.$emit('choosen', list);
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		choice(list) {
 | 
					 | 
				
			||||||
			this.$emit('choosen', list);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
.xkxvokkjlptzyewouewmceqcxhpgzprp
 | 
					 | 
				
			||||||
	padding 16px
 | 
					 | 
				
			||||||
	background: var(--bg)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> button
 | 
					 | 
				
			||||||
		display block
 | 
					 | 
				
			||||||
		margin-bottom 16px
 | 
					 | 
				
			||||||
		color var(--primaryForeground)
 | 
					 | 
				
			||||||
		background var(--primary)
 | 
					 | 
				
			||||||
		width 100%
 | 
					 | 
				
			||||||
		border-radius 38px
 | 
					 | 
				
			||||||
		user-select none
 | 
					 | 
				
			||||||
		cursor pointer
 | 
					 | 
				
			||||||
		padding 0 16px
 | 
					 | 
				
			||||||
		min-width 100px
 | 
					 | 
				
			||||||
		line-height 38px
 | 
					 | 
				
			||||||
		font-size 14px
 | 
					 | 
				
			||||||
		font-weight 700
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:hover
 | 
					 | 
				
			||||||
			background var(--primaryLighten10)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:active
 | 
					 | 
				
			||||||
			background var(--primaryDarken10)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	a
 | 
					 | 
				
			||||||
		display block
 | 
					 | 
				
			||||||
		margin 8px 0
 | 
					 | 
				
			||||||
		padding 8px
 | 
					 | 
				
			||||||
		color var(--text)
 | 
					 | 
				
			||||||
		background var(--face)
 | 
					 | 
				
			||||||
		box-shadow 0 2px 16px var(--reversiListItemShadow)
 | 
					 | 
				
			||||||
		border-radius 6px
 | 
					 | 
				
			||||||
		cursor pointer
 | 
					 | 
				
			||||||
		line-height 32px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		*
 | 
					 | 
				
			||||||
			pointer-events none
 | 
					 | 
				
			||||||
			user-select none
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:hover
 | 
					 | 
				
			||||||
			box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.05)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		&:active
 | 
					 | 
				
			||||||
			box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
							
								
								
									
										45
									
								
								src/client/app/common/views/deck/deck.column-template.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/client/app/common/views/deck/deck.column-template.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,45 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<x-column>
 | 
				
			||||||
 | 
						<template #header>
 | 
				
			||||||
 | 
							<fa v-if="icon" :icon="icon"/>{{ title }}
 | 
				
			||||||
 | 
						</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<div>
 | 
				
			||||||
 | 
							<component :is="component" @init="init" v-bind="$attrs"/>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</x-column>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import XColumn from './deck.column.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						components: {
 | 
				
			||||||
 | 
							XColumn,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							component: {
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								title: null,
 | 
				
			||||||
 | 
								icon: null,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mounted() {
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							init(v) {
 | 
				
			||||||
 | 
								this.title = v.title;
 | 
				
			||||||
 | 
								this.icon = v.icon;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -1,34 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<x-column>
 | 
					 | 
				
			||||||
	<template #header>
 | 
					 | 
				
			||||||
		<fa :icon="faHashtag"/>{{ $t('@.explore') }}
 | 
					 | 
				
			||||||
	</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<div>
 | 
					 | 
				
			||||||
		<x-explore v-bind="$attrs"/>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
</x-column>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
import XColumn from './deck.column.vue';
 | 
					 | 
				
			||||||
import XExplore from '../../../common/views/pages/explore.vue';
 | 
					 | 
				
			||||||
import { faHashtag } from '@fortawesome/free-solid-svg-icons';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n(),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	components: {
 | 
					 | 
				
			||||||
		XColumn,
 | 
					 | 
				
			||||||
		XExplore,
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			faHashtag
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -116,6 +116,10 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	created() {
 | 
						created() {
 | 
				
			||||||
 | 
							this.$emit('init', {
 | 
				
			||||||
 | 
								title: this.$t('@.explore'),
 | 
				
			||||||
 | 
								icon: faHashtag
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
		this.$root.api('hashtags/list', {
 | 
							this.$root.api('hashtags/list', {
 | 
				
			||||||
			sort: '+attachedLocalUsers',
 | 
								sort: '+attachedLocalUsers',
 | 
				
			||||||
			attachedToLocalUserOnly: true,
 | 
								attachedToLocalUserOnly: true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,9 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<mk-ui>
 | 
					<div>
 | 
				
			||||||
	<template #header><fa :icon="['far', 'envelope']"/>{{ $t('title') }}</template>
 | 
						<ui-container :body-togglable="true">
 | 
				
			||||||
 | 
							<template #header>{{ $t('received-follow-requests') }}</template>
 | 
				
			||||||
	<main>
 | 
							<div v-if="!fetching">
 | 
				
			||||||
 | 
								<sequential-entrance animation="entranceFromTop" delay="25" tag="div" class="mcbzkkaw">
 | 
				
			||||||
				<div v-for="req in requests">
 | 
									<div v-for="req in requests">
 | 
				
			||||||
					<router-link :key="req.id" :to="req.follower | userPage">
 | 
										<router-link :key="req.id" :to="req.follower | userPage">
 | 
				
			||||||
						<mk-user-name :user="req.follower"/>
 | 
											<mk-user-name :user="req.follower"/>
 | 
				
			||||||
| 
						 | 
					@ -11,17 +12,19 @@
 | 
				
			||||||
						<a @click="accept(req.follower)">{{ $t('accept') }}</a>|<a @click="reject(req.follower)">{{ $t('reject') }}</a>
 | 
											<a @click="accept(req.follower)">{{ $t('accept') }}</a>|<a @click="reject(req.follower)">{{ $t('reject') }}</a>
 | 
				
			||||||
					</span>
 | 
										</span>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
	</main>
 | 
								</sequential-entrance>
 | 
				
			||||||
</mk-ui>
 | 
							</div>
 | 
				
			||||||
 | 
						</ui-container>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					import Progress from '../../scripts/loading';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('mobile/views/pages/received-follow-requests.vue'),
 | 
						i18n: i18n('common/views/pages/follow-requests.vue'),
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			fetching: true,
 | 
								fetching: true,
 | 
				
			||||||
| 
						 | 
					@ -29,14 +32,10 @@ export default Vue.extend({
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	mounted() {
 | 
						mounted() {
 | 
				
			||||||
		document.title = this.$t('title');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Progress.start();
 | 
							Progress.start();
 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.$root.api('following/requests/list').then(requests => {
 | 
							this.$root.api('following/requests/list').then(requests => {
 | 
				
			||||||
			this.fetching = false;
 | 
								this.fetching = false;
 | 
				
			||||||
			this.requests = requests;
 | 
								this.requests = requests;
 | 
				
			||||||
 | 
					 | 
				
			||||||
			Progress.done();
 | 
								Progress.done();
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -56,7 +55,7 @@ export default Vue.extend({
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
main
 | 
					.mcbzkkaw
 | 
				
			||||||
	> div
 | 
						> div
 | 
				
			||||||
		display flex
 | 
							display flex
 | 
				
			||||||
		padding 16px
 | 
							padding 16px
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,11 @@
 | 
				
			||||||
		<small>@{{ page.user.username }}</small>
 | 
							<small>@{{ page.user.username }}</small>
 | 
				
			||||||
		<router-link v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId" :to="`/i/pages/edit/${page.id}`">{{ $t('edit-this-page') }}</router-link>
 | 
							<router-link v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId" :to="`/i/pages/edit/${page.id}`">{{ $t('edit-this-page') }}</router-link>
 | 
				
			||||||
		<router-link :to="`./${page.name}/view-source`">{{ $t('view-source') }}</router-link>
 | 
							<router-link :to="`./${page.name}/view-source`">{{ $t('view-source') }}</router-link>
 | 
				
			||||||
 | 
							<div class="like">
 | 
				
			||||||
 | 
								<button @click="unlike()" v-if="page.isLiked" :title="$t('unlike')"><fa :icon="faHeartS"/></button>
 | 
				
			||||||
 | 
								<button @click="like()" v-else :title="$t('like')"><fa :icon="faHeart"/></button>
 | 
				
			||||||
 | 
								<span class="count" v-if="page.likedCount > 0">{{ page.likedCount }}</span>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
	</footer>
 | 
						</footer>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -19,8 +24,8 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../../i18n';
 | 
					import i18n from '../../../../i18n';
 | 
				
			||||||
import { faICursor, faPlus } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faHeart as faHeartS } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faSave, faStickyNote } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faHeart } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
import XBlock from './page.block.vue';
 | 
					import XBlock from './page.block.vue';
 | 
				
			||||||
import { ASEvaluator } from '../../../../../../misc/aiscript/evaluator';
 | 
					import { ASEvaluator } from '../../../../../../misc/aiscript/evaluator';
 | 
				
			||||||
import { collectPageVars } from '../../../scripts/collect-page-vars';
 | 
					import { collectPageVars } from '../../../scripts/collect-page-vars';
 | 
				
			||||||
| 
						 | 
					@ -76,7 +81,7 @@ export default Vue.extend({
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			page: null,
 | 
								page: null,
 | 
				
			||||||
			script: null,
 | 
								script: null,
 | 
				
			||||||
			faPlus, faICursor, faSave, faStickyNote
 | 
								faHeartS, faHeart
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -103,6 +108,24 @@ export default Vue.extend({
 | 
				
			||||||
		getPageVars() {
 | 
							getPageVars() {
 | 
				
			||||||
			return collectPageVars(this.page.content);
 | 
								return collectPageVars(this.page.content);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							like() {
 | 
				
			||||||
 | 
								this.$root.api('pages/like', {
 | 
				
			||||||
 | 
									pageId: this.page.id,
 | 
				
			||||||
 | 
								}).then(() => {
 | 
				
			||||||
 | 
									this.page.isLiked = true;
 | 
				
			||||||
 | 
									this.page.likedCount++;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							unlike() {
 | 
				
			||||||
 | 
								this.$root.api('pages/unlike', {
 | 
				
			||||||
 | 
									pageId: this.page.id,
 | 
				
			||||||
 | 
								}).then(() => {
 | 
				
			||||||
 | 
									this.page.isLiked = false;
 | 
				
			||||||
 | 
									this.page.likedCount--;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -161,4 +184,7 @@ export default Vue.extend({
 | 
				
			||||||
		> a + a
 | 
							> a + a
 | 
				
			||||||
			margin-left 8px
 | 
								margin-left 8px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> .like
 | 
				
			||||||
 | 
								margin-top 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</style>
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										143
									
								
								src/client/app/common/views/pages/pages.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/client/app/common/views/pages/pages.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,143 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
						<ui-container :body-togglable="true">
 | 
				
			||||||
 | 
							<template #header><fa :icon="faEdit" fixed-width/>{{ $t('my-pages') }}</template>
 | 
				
			||||||
 | 
							<div class="rknalgpo" v-if="!fetching">
 | 
				
			||||||
 | 
								<ui-button class="new" @click="create()"><fa :icon="faPlus"/></ui-button>
 | 
				
			||||||
 | 
								<sequential-entrance animation="entranceFromTop" delay="25" tag="div" class="pages">
 | 
				
			||||||
 | 
									<x-page-preview v-for="page in pages" class="page" :page="page" :key="page.id"/>
 | 
				
			||||||
 | 
								</sequential-entrance>
 | 
				
			||||||
 | 
								<ui-button v-if="existMore" @click="fetchMore()" style="margin-top:16px;">{{ $t('@.load-more') }}</ui-button>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</ui-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<ui-container :body-togglable="true">
 | 
				
			||||||
 | 
							<template #header><fa :icon="faHeart" fixed-width/>{{ $t('liked-pages') }}</template>
 | 
				
			||||||
 | 
							<div class="rknalgpo" v-if="!fetching">
 | 
				
			||||||
 | 
								<sequential-entrance animation="entranceFromTop" delay="25" tag="div" class="pages">
 | 
				
			||||||
 | 
									<x-page-preview v-for="like in likes" class="page" :page="like.page" :key="like.page.id"/>
 | 
				
			||||||
 | 
								</sequential-entrance>
 | 
				
			||||||
 | 
								<ui-button v-if="existMoreLikes" @click="fetchMoreLiked()">{{ $t('@.load-more') }}</ui-button>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</ui-container>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import { faPlus, faEdit } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					import { faStickyNote, faHeart } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					import Progress from '../../scripts/loading';
 | 
				
			||||||
 | 
					import XPagePreview from '../../views/components/page-preview.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						i18n: i18n('pages'),
 | 
				
			||||||
 | 
						components: {
 | 
				
			||||||
 | 
							XPagePreview
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								fetching: true,
 | 
				
			||||||
 | 
								pages: [],
 | 
				
			||||||
 | 
								existMore: false,
 | 
				
			||||||
 | 
								moreFetching: false,
 | 
				
			||||||
 | 
								likes: [],
 | 
				
			||||||
 | 
								existMoreLikes: false,
 | 
				
			||||||
 | 
								moreLikesFetching: false,
 | 
				
			||||||
 | 
								faStickyNote, faPlus, faEdit, faHeart
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						created() {
 | 
				
			||||||
 | 
							this.fetch();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.$emit('init', {
 | 
				
			||||||
 | 
								title: this.$t('@.pages'),
 | 
				
			||||||
 | 
								icon: faStickyNote
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							async fetch() {
 | 
				
			||||||
 | 
								Progress.start();
 | 
				
			||||||
 | 
								this.fetching = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const pages = await this.$root.api('i/pages', {
 | 
				
			||||||
 | 
									limit: 11
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (pages.length == 11) {
 | 
				
			||||||
 | 
									this.existMore = true;
 | 
				
			||||||
 | 
									pages.pop();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const likes = await this.$root.api('i/page-likes', {
 | 
				
			||||||
 | 
									limit: 11
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (likes.length == 11) {
 | 
				
			||||||
 | 
									this.existMoreLikes = true;
 | 
				
			||||||
 | 
									likes.pop();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								this.pages = pages;
 | 
				
			||||||
 | 
								this.likes = likes;
 | 
				
			||||||
 | 
								this.fetching = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Progress.done();
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							fetchMore() {
 | 
				
			||||||
 | 
								this.moreFetching = true;
 | 
				
			||||||
 | 
								this.$root.api('i/pages', {
 | 
				
			||||||
 | 
									limit: 11,
 | 
				
			||||||
 | 
									untilId: this.pages[this.pages.length - 1].id
 | 
				
			||||||
 | 
								}).then(pages => {
 | 
				
			||||||
 | 
									if (pages.length == 11) {
 | 
				
			||||||
 | 
										this.existMore = true;
 | 
				
			||||||
 | 
										pages.pop();
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										this.existMore = false;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									this.pages = this.pages.concat(pages);
 | 
				
			||||||
 | 
									this.moreFetching = false;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							fetchMoreLiked() {
 | 
				
			||||||
 | 
								this.moreLikesFetching = true;
 | 
				
			||||||
 | 
								this.$root.api('i/page-likes', {
 | 
				
			||||||
 | 
									limit: 11,
 | 
				
			||||||
 | 
									untilId: this.likes[this.likes.length - 1].id
 | 
				
			||||||
 | 
								}).then(pages => {
 | 
				
			||||||
 | 
									if (pages.length == 11) {
 | 
				
			||||||
 | 
										this.existMoreLikes = true;
 | 
				
			||||||
 | 
										pages.pop();
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										this.existMoreLikes = false;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									this.likes = this.likes.concat(pages);
 | 
				
			||||||
 | 
									this.moreLikesFetching = false;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							create() {
 | 
				
			||||||
 | 
								this.$router.push(`/i/pages/new`);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					.rknalgpo
 | 
				
			||||||
 | 
						padding 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> .new
 | 
				
			||||||
 | 
							margin-bottom 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> * > .page:not(:last-child)
 | 
				
			||||||
 | 
							margin-bottom 8px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@media (min-width 500px)
 | 
				
			||||||
 | 
							> * > .page:not(:last-child)
 | 
				
			||||||
 | 
								margin-bottom 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										182
									
								
								src/client/app/common/views/pages/user-group-editor.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								src/client/app/common/views/pages/user-group-editor.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,182 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div class="ivrbakop">
 | 
				
			||||||
 | 
						<ui-container v-if="group">
 | 
				
			||||||
 | 
							<template #header><fa :icon="faUsers"/> {{ group.name }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-margin>
 | 
				
			||||||
 | 
									<ui-button @click="rename"><fa :icon="faICursor"/> {{ $t('rename') }}</ui-button>
 | 
				
			||||||
 | 
									<ui-button @click="del"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button>
 | 
				
			||||||
 | 
								</ui-margin>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
						</ui-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<ui-container>
 | 
				
			||||||
 | 
							<template #header><fa :icon="faUsers"/> {{ $t('users') }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-margin>
 | 
				
			||||||
 | 
									<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('add-user') }}</ui-button>
 | 
				
			||||||
 | 
								</ui-margin>
 | 
				
			||||||
 | 
								<sequential-entrance animation="entranceFromTop" delay="25">
 | 
				
			||||||
 | 
									<div class="kjlrfbes" v-for="user in users">
 | 
				
			||||||
 | 
										<div>
 | 
				
			||||||
 | 
											<a :href="user | userPage">
 | 
				
			||||||
 | 
												<mk-avatar class="avatar" :user="user" :disable-link="true"/>
 | 
				
			||||||
 | 
											</a>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
										<div>
 | 
				
			||||||
 | 
											<header>
 | 
				
			||||||
 | 
												<b><mk-user-name :user="user"/></b>
 | 
				
			||||||
 | 
												<span class="username">@{{ user | acct }}</span>
 | 
				
			||||||
 | 
											</header>
 | 
				
			||||||
 | 
											<div>
 | 
				
			||||||
 | 
												<a @click="remove(user)">{{ $t('remove-user') }}</a>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									</div>
 | 
				
			||||||
 | 
								</sequential-entrance>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
						</ui-container>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					import { faICursor, faUsers, faPlus } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						i18n: i18n('common/views/components/user-group-editor.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							groupId: {
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								group: null,
 | 
				
			||||||
 | 
								users: [],
 | 
				
			||||||
 | 
								faICursor, faTrashAlt, faUsers, faPlus
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						created() {
 | 
				
			||||||
 | 
							this.$root.api('users/groups/show', {
 | 
				
			||||||
 | 
								groupId: this.groupId
 | 
				
			||||||
 | 
							}).then(group => {
 | 
				
			||||||
 | 
								this.group = group;
 | 
				
			||||||
 | 
								this.fetchUsers();
 | 
				
			||||||
 | 
								this.$emit('init', {
 | 
				
			||||||
 | 
									title: this.group.name,
 | 
				
			||||||
 | 
									icon: faUsers
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							fetchUsers() {
 | 
				
			||||||
 | 
								this.$root.api('users/show', {
 | 
				
			||||||
 | 
									userIds: this.group.userIds
 | 
				
			||||||
 | 
								}).then(users => {
 | 
				
			||||||
 | 
									this.users = users;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							rename() {
 | 
				
			||||||
 | 
								this.$root.dialog({
 | 
				
			||||||
 | 
									title: this.$t('rename'),
 | 
				
			||||||
 | 
									input: {
 | 
				
			||||||
 | 
										default: this.group.name
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}).then(({ canceled, result: name }) => {
 | 
				
			||||||
 | 
									if (canceled) return;
 | 
				
			||||||
 | 
									this.$root.api('users/groups/update', {
 | 
				
			||||||
 | 
										groupId: this.group.id,
 | 
				
			||||||
 | 
										name: name
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							del() {
 | 
				
			||||||
 | 
								this.$root.dialog({
 | 
				
			||||||
 | 
									type: 'warning',
 | 
				
			||||||
 | 
									text: this.$t('delete-are-you-sure').replace('$1', this.group.name),
 | 
				
			||||||
 | 
									showCancelButton: true
 | 
				
			||||||
 | 
								}).then(({ canceled }) => {
 | 
				
			||||||
 | 
									if (canceled) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									this.$root.api('users/groups/delete', {
 | 
				
			||||||
 | 
										groupId: this.group.id
 | 
				
			||||||
 | 
									}).then(() => {
 | 
				
			||||||
 | 
										this.$root.dialog({
 | 
				
			||||||
 | 
											type: 'success',
 | 
				
			||||||
 | 
											text: this.$t('deleted')
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
									}).catch(e => {
 | 
				
			||||||
 | 
										this.$root.dialog({
 | 
				
			||||||
 | 
											type: 'error',
 | 
				
			||||||
 | 
											text: e
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							remove(user: any) {
 | 
				
			||||||
 | 
								this.$root.api('users/groups/pull', {
 | 
				
			||||||
 | 
									groupId: this.group.id,
 | 
				
			||||||
 | 
									userId: user.id
 | 
				
			||||||
 | 
								}).then(() => {
 | 
				
			||||||
 | 
									this.fetchUsers();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							async add() {
 | 
				
			||||||
 | 
								const { result: user } = await this.$root.dialog({
 | 
				
			||||||
 | 
									user: {
 | 
				
			||||||
 | 
										local: true
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								if (user == null) return;
 | 
				
			||||||
 | 
								this.$root.api('users/groups/push', {
 | 
				
			||||||
 | 
									groupId: this.group.id,
 | 
				
			||||||
 | 
									userId: user.id
 | 
				
			||||||
 | 
								}).then(() => {
 | 
				
			||||||
 | 
									this.fetchUsers();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					.ivrbakop
 | 
				
			||||||
 | 
						.kjlrfbes
 | 
				
			||||||
 | 
							display flex
 | 
				
			||||||
 | 
							padding 16px
 | 
				
			||||||
 | 
							border-top solid 1px var(--faceDivider)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> div:first-child
 | 
				
			||||||
 | 
								> a
 | 
				
			||||||
 | 
									> .avatar
 | 
				
			||||||
 | 
										width 64px
 | 
				
			||||||
 | 
										height 64px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> div:last-child
 | 
				
			||||||
 | 
								flex 1
 | 
				
			||||||
 | 
								padding-left 16px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@media (max-width 500px)
 | 
				
			||||||
 | 
									font-size 14px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								> header
 | 
				
			||||||
 | 
									color var(--text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									> .username
 | 
				
			||||||
 | 
										margin-left 8px
 | 
				
			||||||
 | 
										opacity 0.7
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										63
									
								
								src/client/app/common/views/pages/user-groups.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/client/app/common/views/pages/user-groups.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,63 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<ui-container>
 | 
				
			||||||
 | 
						<template #header><fa :icon="faUsers"/> {{ $t('user-groups') }}</template>
 | 
				
			||||||
 | 
						<ui-margin>
 | 
				
			||||||
 | 
							<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('create-group') }}</ui-button>
 | 
				
			||||||
 | 
						</ui-margin>
 | 
				
			||||||
 | 
						<div class="hwgkdrbl" v-for="group in groups" :key="group.id">
 | 
				
			||||||
 | 
							<ui-hr/>
 | 
				
			||||||
 | 
							<ui-margin>
 | 
				
			||||||
 | 
								<router-link :to="`/i/groups/${group.id}`">{{ group.name }}</router-link>
 | 
				
			||||||
 | 
							</ui-margin>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</ui-container>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					import { faUsers, faPlus } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						i18n: i18n('common/views/components/user-groups.vue'),
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								fetching: true,
 | 
				
			||||||
 | 
								groups: [],
 | 
				
			||||||
 | 
								faUsers, faPlus
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						mounted() {
 | 
				
			||||||
 | 
							this.$root.api('users/groups/owned').then(groups => {
 | 
				
			||||||
 | 
								this.fetching = false;
 | 
				
			||||||
 | 
								this.groups = groups;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.$emit('init', {
 | 
				
			||||||
 | 
								title: this.$t('user-groups'),
 | 
				
			||||||
 | 
								icon: faUsers
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							add() {
 | 
				
			||||||
 | 
								this.$root.dialog({
 | 
				
			||||||
 | 
									title: this.$t('group-name'),
 | 
				
			||||||
 | 
									input: true
 | 
				
			||||||
 | 
								}).then(async ({ canceled, result: name }) => {
 | 
				
			||||||
 | 
									if (canceled) return;
 | 
				
			||||||
 | 
									const list = await this.$root.api('users/groups/create', {
 | 
				
			||||||
 | 
										name
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									this.groups.push(list)
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					.hwgkdrbl
 | 
				
			||||||
 | 
						display block
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -1,18 +1,23 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="cudqjmnl">
 | 
					<div class="cudqjmnl">
 | 
				
			||||||
	<ui-card>
 | 
						<ui-container v-if="list">
 | 
				
			||||||
		<template #title><fa :icon="faList"/> {{ list.name }}</template>
 | 
							<template #header><fa :icon="faListUl"/> {{ list.name }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<section>
 | 
							<section class="fwvevrks">
 | 
				
			||||||
 | 
								<ui-margin>
 | 
				
			||||||
				<ui-button @click="rename"><fa :icon="faICursor"/> {{ $t('rename') }}</ui-button>
 | 
									<ui-button @click="rename"><fa :icon="faICursor"/> {{ $t('rename') }}</ui-button>
 | 
				
			||||||
				<ui-button @click="del"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button>
 | 
									<ui-button @click="del"><fa :icon="faTrashAlt"/> {{ $t('delete') }}</ui-button>
 | 
				
			||||||
 | 
								</ui-margin>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
	</ui-card>
 | 
						</ui-container>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<ui-card>
 | 
						<ui-container>
 | 
				
			||||||
		<template #title><fa :icon="faUsers"/> {{ $t('users') }}</template>
 | 
							<template #header><fa :icon="faUsers"/> {{ $t('users') }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<section>
 | 
							<section>
 | 
				
			||||||
 | 
								<ui-margin>
 | 
				
			||||||
 | 
									<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('add-user') }}</ui-button>
 | 
				
			||||||
 | 
								</ui-margin>
 | 
				
			||||||
			<sequential-entrance animation="entranceFromTop" delay="25">
 | 
								<sequential-entrance animation="entranceFromTop" delay="25">
 | 
				
			||||||
				<div class="phcqulfl" v-for="user in users">
 | 
									<div class="phcqulfl" v-for="user in users">
 | 
				
			||||||
					<div>
 | 
										<div>
 | 
				
			||||||
| 
						 | 
					@ -32,34 +37,44 @@
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
			</sequential-entrance>
 | 
								</sequential-entrance>
 | 
				
			||||||
		</section>
 | 
							</section>
 | 
				
			||||||
	</ui-card>
 | 
						</ui-container>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import { faList, faICursor, faUsers } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faListUl, faICursor, faUsers, faPlus } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('common/views/components/user-list-editor.vue'),
 | 
						i18n: i18n('common/views/components/user-list-editor.vue'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	props: {
 | 
						props: {
 | 
				
			||||||
		list: {
 | 
							listId: {
 | 
				
			||||||
			required: true
 | 
								required: true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
 | 
								list: null,
 | 
				
			||||||
			users: [],
 | 
								users: [],
 | 
				
			||||||
			faList, faICursor, faTrashAlt, faUsers
 | 
								faListUl, faICursor, faTrashAlt, faUsers, faPlus
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mounted() {
 | 
						created() {
 | 
				
			||||||
 | 
							this.$root.api('users/lists/show', {
 | 
				
			||||||
 | 
								listId: this.listId
 | 
				
			||||||
 | 
							}).then(list => {
 | 
				
			||||||
 | 
								this.list = list;
 | 
				
			||||||
			this.fetchUsers();
 | 
								this.fetchUsers();
 | 
				
			||||||
 | 
								this.$emit('init', {
 | 
				
			||||||
 | 
									title: this.list.name,
 | 
				
			||||||
 | 
									icon: faListUl
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
| 
						 | 
					@ -117,6 +132,21 @@ export default Vue.extend({
 | 
				
			||||||
			}).then(() => {
 | 
								}).then(() => {
 | 
				
			||||||
				this.fetchUsers();
 | 
									this.fetchUsers();
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							async add() {
 | 
				
			||||||
 | 
								const { result: user } = await this.$root.dialog({
 | 
				
			||||||
 | 
									user: {
 | 
				
			||||||
 | 
										local: true
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								if (user == null) return;
 | 
				
			||||||
 | 
								this.$root.api('users/lists/push', {
 | 
				
			||||||
 | 
									listId: this.list.id,
 | 
				
			||||||
 | 
									userId: user.id
 | 
				
			||||||
 | 
								}).then(() => {
 | 
				
			||||||
 | 
									this.fetchUsers();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -126,7 +156,7 @@ export default Vue.extend({
 | 
				
			||||||
.cudqjmnl
 | 
					.cudqjmnl
 | 
				
			||||||
	.phcqulfl
 | 
						.phcqulfl
 | 
				
			||||||
		display flex
 | 
							display flex
 | 
				
			||||||
		padding 16px 0
 | 
							padding 16px
 | 
				
			||||||
		border-top solid 1px var(--faceDivider)
 | 
							border-top solid 1px var(--faceDivider)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		> div:first-child
 | 
							> div:first-child
 | 
				
			||||||
| 
						 | 
					@ -143,6 +173,8 @@ export default Vue.extend({
 | 
				
			||||||
				font-size 14px
 | 
									font-size 14px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			> header
 | 
								> header
 | 
				
			||||||
 | 
									color var(--text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				> .username
 | 
									> .username
 | 
				
			||||||
					margin-left 8px
 | 
										margin-left 8px
 | 
				
			||||||
					opacity 0.7
 | 
										opacity 0.7
 | 
				
			||||||
							
								
								
									
										63
									
								
								src/client/app/common/views/pages/user-lists.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/client/app/common/views/pages/user-lists.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,63 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<ui-container>
 | 
				
			||||||
 | 
						<template #header><fa :icon="faListUl"/> {{ $t('user-lists') }}</template>
 | 
				
			||||||
 | 
						<ui-margin>
 | 
				
			||||||
 | 
							<ui-button @click="add"><fa :icon="faPlus"/> {{ $t('create-list') }}</ui-button>
 | 
				
			||||||
 | 
						</ui-margin>
 | 
				
			||||||
 | 
						<div class="cpqqyrst" v-for="list in lists" :key="list.id">
 | 
				
			||||||
 | 
							<ui-hr/>
 | 
				
			||||||
 | 
							<ui-margin>
 | 
				
			||||||
 | 
								<router-link :to="`/i/lists/${list.id}`">{{ list.name }}</router-link>
 | 
				
			||||||
 | 
							</ui-margin>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
					</ui-container>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
 | 
					import { faListUl, faPlus } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						i18n: i18n('common/views/components/user-lists.vue'),
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								fetching: true,
 | 
				
			||||||
 | 
								lists: [],
 | 
				
			||||||
 | 
								faListUl, faPlus
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						mounted() {
 | 
				
			||||||
 | 
							this.$root.api('users/lists/list').then(lists => {
 | 
				
			||||||
 | 
								this.fetching = false;
 | 
				
			||||||
 | 
								this.lists = lists;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.$emit('init', {
 | 
				
			||||||
 | 
								title: this.$t('user-lists'),
 | 
				
			||||||
 | 
								icon: faListUl
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							add() {
 | 
				
			||||||
 | 
								this.$root.dialog({
 | 
				
			||||||
 | 
									title: this.$t('list-name'),
 | 
				
			||||||
 | 
									input: true
 | 
				
			||||||
 | 
								}).then(async ({ canceled, result: name }) => {
 | 
				
			||||||
 | 
									if (canceled) return;
 | 
				
			||||||
 | 
									const list = await this.$root.api('users/lists/create', {
 | 
				
			||||||
 | 
										name
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									this.lists.push(list)
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					.cpqqyrst
 | 
				
			||||||
 | 
						display block
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -22,6 +22,7 @@ import MkShare from '../common/views/pages/share.vue';
 | 
				
			||||||
import MkFollow from '../common/views/pages/follow.vue';
 | 
					import MkFollow from '../common/views/pages/follow.vue';
 | 
				
			||||||
import MkNotFound from '../common/views/pages/not-found.vue';
 | 
					import MkNotFound from '../common/views/pages/not-found.vue';
 | 
				
			||||||
import MkSettings from './views/pages/settings.vue';
 | 
					import MkSettings from './views/pages/settings.vue';
 | 
				
			||||||
 | 
					import DeckColumn from '../common/views/deck/deck.column-template.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Ctx from './views/components/context-menu.vue';
 | 
					import Ctx from './views/components/context-menu.vue';
 | 
				
			||||||
import PostFormWindow from './views/components/post-form-window.vue';
 | 
					import PostFormWindow from './views/components/post-form-window.vue';
 | 
				
			||||||
| 
						 | 
					@ -138,9 +139,14 @@ init(async (launch, os) => {
 | 
				
			||||||
					{ path: '/search', component: () => import('../common/views/deck/deck.search-column.vue').then(m => m.default) },
 | 
										{ path: '/search', component: () => import('../common/views/deck/deck.search-column.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/tags/:tag', name: 'tag', component: () => import('../common/views/deck/deck.hashtag-column.vue').then(m => m.default) },
 | 
										{ path: '/tags/:tag', name: 'tag', component: () => import('../common/views/deck/deck.hashtag-column.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/featured', name: 'featured', component: () => import('../common/views/deck/deck.featured-column.vue').then(m => m.default) },
 | 
										{ path: '/featured', name: 'featured', component: () => import('../common/views/deck/deck.featured-column.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/explore', name: 'explore', component: () => import('../common/views/deck/deck.explore-column.vue').then(m => m.default) },
 | 
										{ path: '/explore', name: 'explore', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default) }) },
 | 
				
			||||||
					{ path: '/explore/tags/:tag', name: 'explore-tag', props: true, component: () => import('../common/views/deck/deck.explore-column.vue').then(m => m.default) },
 | 
										{ path: '/explore/tags/:tag', name: 'explore-tag', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default), tag: route.params.tag }) },
 | 
				
			||||||
					{ path: '/i/favorites', component: () => import('../common/views/deck/deck.favorites-column.vue').then(m => m.default) }
 | 
										{ path: '/i/favorites', component: () => import('../common/views/deck/deck.favorites-column.vue').then(m => m.default) },
 | 
				
			||||||
 | 
										{ path: '/i/pages', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) },
 | 
				
			||||||
 | 
										{ path: '/i/lists', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) },
 | 
				
			||||||
 | 
										{ path: '/i/lists/:listId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.listId }) },
 | 
				
			||||||
 | 
										{ path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
 | 
				
			||||||
 | 
										{ path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) },
 | 
				
			||||||
				]}
 | 
									]}
 | 
				
			||||||
				: { path: '/', component: MkHome, children: [
 | 
									: { path: '/', component: MkHome, children: [
 | 
				
			||||||
					{ path: '', name: 'index', component: MkHomeTimeline },
 | 
										{ path: '', name: 'index', component: MkHomeTimeline },
 | 
				
			||||||
| 
						 | 
					@ -156,12 +162,18 @@ init(async (launch, os) => {
 | 
				
			||||||
					{ path: '/explore', name: 'explore', component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
 | 
										{ path: '/explore', name: 'explore', component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/explore/tags/:tag', name: 'explore-tag', props: true, component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
 | 
										{ path: '/explore/tags/:tag', name: 'explore-tag', props: true, component: () => import('../common/views/pages/explore.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/i/favorites', component: () => import('./views/home/favorites.vue').then(m => m.default) },
 | 
										{ path: '/i/favorites', component: () => import('./views/home/favorites.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/i/pages', component: () => import('./views/home/pages.vue').then(m => m.default) },
 | 
										{ path: '/i/pages', component: () => import('../common/views/pages/pages.vue').then(m => m.default) },
 | 
				
			||||||
 | 
										{ path: '/i/lists', component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) },
 | 
				
			||||||
 | 
										{ path: '/i/lists/:listId', props: true, component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default) },
 | 
				
			||||||
 | 
										{ path: '/i/groups', component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) },
 | 
				
			||||||
 | 
										{ path: '/i/groups/:groupId', props: true, component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default) },
 | 
				
			||||||
 | 
										{ path: '/i/follow-requests', component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) },
 | 
				
			||||||
				]},
 | 
									]},
 | 
				
			||||||
			{ path: '/@:user/pages/:page', props: true, component: () => import('./views/pages/page.vue').then(m => m.default) },
 | 
								{ path: '/@:user/pages/:page', props: true, component: () => import('./views/pages/page.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/@:user/pages/:pageName/view-source', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
								{ path: '/@:user/pages/:pageName/view-source', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/i/pages/new', component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
								{ path: '/i/pages/new', component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/i/pages/edit/:pageId', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
								{ path: '/i/pages/edit/:pageId', props: true, component: () => import('./views/pages/page-editor.vue').then(m => m.default) },
 | 
				
			||||||
 | 
								{ path: '/i/messaging/group/:group', component: MkMessagingRoom },
 | 
				
			||||||
			{ path: '/i/messaging/:user', component: MkMessagingRoom },
 | 
								{ path: '/i/messaging/:user', component: MkMessagingRoom },
 | 
				
			||||||
			{ path: '/i/drive', component: MkDrive },
 | 
								{ path: '/i/drive', component: MkDrive },
 | 
				
			||||||
			{ path: '/i/drive/folder/:folder', component: MkDrive },
 | 
								{ path: '/i/drive/folder/:folder', component: MkDrive },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
 | 
					<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
 | 
				
			||||||
	<template #header><fa icon="comments"/> {{ $t('@.messaging') }}: <mk-user-name :user="user"/></template>
 | 
						<template #header><fa icon="comments"/> {{ $t('@.messaging') }}: <mk-user-name v-if="user" :user="user"/><span v-else>{{ group.name }}</span></template>
 | 
				
			||||||
	<x-messaging-room :user="user" :class="$style.content"/>
 | 
						<x-messaging-room :user="user" :group="group" :class="$style.content"/>
 | 
				
			||||||
</mk-window>
 | 
					</mk-window>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,10 +16,14 @@ export default Vue.extend({
 | 
				
			||||||
	components: {
 | 
						components: {
 | 
				
			||||||
		XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
 | 
							XMessagingRoom: () => import('../../../common/views/components/messaging-room.vue').then(m => m.default)
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	props: ['user'],
 | 
						props: ['user', 'group'],
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
		popout(): string {
 | 
							popout(): string {
 | 
				
			||||||
 | 
								if (this.user) {
 | 
				
			||||||
				return `${url}/i/messaging/${getAcct(this.user)}`;
 | 
									return `${url}/i/messaging/${getAcct(this.user)}`;
 | 
				
			||||||
 | 
								} else if (this.group) {
 | 
				
			||||||
 | 
									return `${url}/i/messaging/group/${this.group.id}`;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<mk-window ref="window" width="500px" height="560px" @closed="destroyDom">
 | 
					<mk-window ref="window" width="500px" height="560px" @closed="destroyDom">
 | 
				
			||||||
	<template #header :class="$style.header"><fa icon="comments"/>{{ $t('@.messaging') }}</template>
 | 
						<template #header :class="$style.header"><fa icon="comments"/>{{ $t('@.messaging') }}</template>
 | 
				
			||||||
	<x-messaging :class="$style.content" @navigate="navigate"/>
 | 
						<x-messaging :class="$style.content" @navigate="navigate" @navigateGroup="navigateGroup"/>
 | 
				
			||||||
</mk-window>
 | 
					</mk-window>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,11 @@ export default Vue.extend({
 | 
				
			||||||
			this.$root.new(MkMessagingRoomWindow, {
 | 
								this.$root.new(MkMessagingRoomWindow, {
 | 
				
			||||||
				user: user
 | 
									user: user
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							navigateGroup(group) {
 | 
				
			||||||
 | 
								this.$root.new(MkMessagingRoomWindow, {
 | 
				
			||||||
 | 
									group: group
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,70 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<mk-window ref="window" is-modal width="450px" height="500px" @closed="destroyDom">
 | 
					 | 
				
			||||||
	<template #header><fa :icon="['far', 'envelope']"/> {{ $t('title') }}</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<div class="slpqaxdoxhvglersgjukmvizkqbmbokc">
 | 
					 | 
				
			||||||
		<div v-for="req in requests">
 | 
					 | 
				
			||||||
			<router-link :key="req.id" :to="req.follower | userPage">
 | 
					 | 
				
			||||||
				<mk-user-name :user="req.follower"/>
 | 
					 | 
				
			||||||
			</router-link>
 | 
					 | 
				
			||||||
			<span>
 | 
					 | 
				
			||||||
				<a @click="accept(req.follower)">{{ $t('accept') }}</a>|<a @click="reject(req.follower)">{{ $t('reject') }}</a>
 | 
					 | 
				
			||||||
			</span>
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
</mk-window>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n('desktop/views/components/received-follow-requests-window.vue'),
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			requests: []
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	mounted() {
 | 
					 | 
				
			||||||
		this.$root.api('following/requests/list').then(requests => {
 | 
					 | 
				
			||||||
			this.fetching = false;
 | 
					 | 
				
			||||||
			this.requests = requests;
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		accept(user) {
 | 
					 | 
				
			||||||
			this.$root.api('following/requests/accept', { userId: user.id }).then(() => {
 | 
					 | 
				
			||||||
				this.requests = this.requests.filter(r => r.follower.id != user.id);
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		reject(user) {
 | 
					 | 
				
			||||||
			this.$root.api('following/requests/reject', { userId: user.id }).then(() => {
 | 
					 | 
				
			||||||
				this.requests = this.requests.filter(r => r.follower.id != user.id);
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		close() {
 | 
					 | 
				
			||||||
			(this as any).$refs.window.close();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
.slpqaxdoxhvglersgjukmvizkqbmbokc
 | 
					 | 
				
			||||||
	padding 16px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> button
 | 
					 | 
				
			||||||
		margin-bottom 16px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> div
 | 
					 | 
				
			||||||
		display flex
 | 
					 | 
				
			||||||
		padding 16px
 | 
					 | 
				
			||||||
		border solid 1px var(--faceDivider)
 | 
					 | 
				
			||||||
		border-radius 4px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		> span
 | 
					 | 
				
			||||||
			margin 0 0 0 auto
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -28,12 +28,19 @@
 | 
				
			||||||
						<i><fa icon="angle-right"/></i>
 | 
											<i><fa icon="angle-right"/></i>
 | 
				
			||||||
					</router-link>
 | 
										</router-link>
 | 
				
			||||||
				</li>
 | 
									</li>
 | 
				
			||||||
				<li @click="list">
 | 
									<li>
 | 
				
			||||||
					<p>
 | 
										<router-link to="/i/lists">
 | 
				
			||||||
						<i><fa icon="list" fixed-width/></i>
 | 
											<i><fa icon="list" fixed-width/></i>
 | 
				
			||||||
						<span>{{ $t('lists') }}</span>
 | 
											<span>{{ $t('lists') }}</span>
 | 
				
			||||||
						<i><fa icon="angle-right"/></i>
 | 
											<i><fa icon="angle-right"/></i>
 | 
				
			||||||
					</p>
 | 
										</router-link>
 | 
				
			||||||
 | 
									</li>
 | 
				
			||||||
 | 
									<li>
 | 
				
			||||||
 | 
										<router-link to="/i/groups">
 | 
				
			||||||
 | 
											<i><fa :icon="faUsers" fixed-width/></i>
 | 
				
			||||||
 | 
											<span>{{ $t('groups') }}</span>
 | 
				
			||||||
 | 
											<i><fa icon="angle-right"/></i>
 | 
				
			||||||
 | 
										</router-link>
 | 
				
			||||||
				</li>
 | 
									</li>
 | 
				
			||||||
				<li>
 | 
									<li>
 | 
				
			||||||
					<router-link to="/i/pages">
 | 
										<router-link to="/i/pages">
 | 
				
			||||||
| 
						 | 
					@ -42,12 +49,12 @@
 | 
				
			||||||
						<i><fa icon="angle-right"/></i>
 | 
											<i><fa icon="angle-right"/></i>
 | 
				
			||||||
					</router-link>
 | 
										</router-link>
 | 
				
			||||||
				</li>
 | 
									</li>
 | 
				
			||||||
				<li @click="followRequests" v-if="($store.state.i.isLocked || $store.state.i.carefulBot)">
 | 
									<li v-if="($store.state.i.isLocked || $store.state.i.carefulBot)">
 | 
				
			||||||
					<p>
 | 
										<router-link to="/i/follow-requests">
 | 
				
			||||||
						<i><fa :icon="['far', 'envelope']" fixed-width/></i>
 | 
											<i><fa :icon="['far', 'envelope']" fixed-width/></i>
 | 
				
			||||||
						<span>{{ $t('follow-requests') }}<i v-if="$store.state.i.pendingReceivedFollowRequestsCount">{{ $store.state.i.pendingReceivedFollowRequestsCount }}</i></span>
 | 
											<span>{{ $t('follow-requests') }}<i v-if="$store.state.i.pendingReceivedFollowRequestsCount">{{ $store.state.i.pendingReceivedFollowRequestsCount }}</i></span>
 | 
				
			||||||
						<i><fa icon="angle-right"/></i>
 | 
											<i><fa icon="angle-right"/></i>
 | 
				
			||||||
					</p>
 | 
										</router-link>
 | 
				
			||||||
				</li>
 | 
									</li>
 | 
				
			||||||
			</ul>
 | 
								</ul>
 | 
				
			||||||
			<ul>
 | 
								<ul>
 | 
				
			||||||
| 
						 | 
					@ -96,12 +103,10 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import MkUserListsWindow from './user-lists-window.vue';
 | 
					 | 
				
			||||||
import MkFollowRequestsWindow from './received-follow-requests-window.vue';
 | 
					 | 
				
			||||||
// import MkSettingsWindow from './settings-window.vue';
 | 
					// import MkSettingsWindow from './settings-window.vue';
 | 
				
			||||||
import MkDriveWindow from './drive-window.vue';
 | 
					import MkDriveWindow from './drive-window.vue';
 | 
				
			||||||
import contains from '../../../common/scripts/contains';
 | 
					import contains from '../../../common/scripts/contains';
 | 
				
			||||||
import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faHome, faColumns, faUsers } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faMoon, faSun, faStickyNote } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faMoon, faSun, faStickyNote } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default Vue.extend({
 | 
					export default Vue.extend({
 | 
				
			||||||
| 
						 | 
					@ -109,7 +114,7 @@ export default Vue.extend({
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			isOpen: false,
 | 
								isOpen: false,
 | 
				
			||||||
			faHome, faColumns, faMoon, faSun, faStickyNote
 | 
								faHome, faColumns, faMoon, faSun, faStickyNote, faUsers
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	computed: {
 | 
						computed: {
 | 
				
			||||||
| 
						 | 
					@ -147,14 +152,6 @@ export default Vue.extend({
 | 
				
			||||||
			this.close();
 | 
								this.close();
 | 
				
			||||||
			this.$root.new(MkDriveWindow);
 | 
								this.$root.new(MkDriveWindow);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		list() {
 | 
					 | 
				
			||||||
			this.close();
 | 
					 | 
				
			||||||
			this.$root.new(MkUserListsWindow);
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		followRequests() {
 | 
					 | 
				
			||||||
			this.close();
 | 
					 | 
				
			||||||
			this.$root.new(MkFollowRequestsWindow);
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		signout() {
 | 
							signout() {
 | 
				
			||||||
			this.$root.signout();
 | 
								this.$root.signout();
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -72,8 +72,6 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import MkUserListsWindow from './user-lists-window.vue';
 | 
					 | 
				
			||||||
import MkFollowRequestsWindow from './received-follow-requests-window.vue';
 | 
					 | 
				
			||||||
import MkSettingsWindow from './settings-window.vue';
 | 
					import MkSettingsWindow from './settings-window.vue';
 | 
				
			||||||
import MkDriveWindow from './drive-window.vue';
 | 
					import MkDriveWindow from './drive-window.vue';
 | 
				
			||||||
import MkMessagingWindow from './messaging-window.vue';
 | 
					import MkMessagingWindow from './messaging-window.vue';
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,24 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
 | 
					 | 
				
			||||||
	<template #header><fa icon="list"/> {{ list.name }}</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<x-editor :list="list"/>
 | 
					 | 
				
			||||||
</mk-window>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import XEditor from '../../../common/views/components/user-list-editor.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	components: {
 | 
					 | 
				
			||||||
		XEditor
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	props: {
 | 
					 | 
				
			||||||
		list: {
 | 
					 | 
				
			||||||
			required: true
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,36 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
 | 
					 | 
				
			||||||
	<template #header><fa icon="list"/> {{ $t('title') }}</template>
 | 
					 | 
				
			||||||
	<x-lists :class="$style.content" @choosen="choosen"/>
 | 
					 | 
				
			||||||
</mk-window>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
import MkUserListWindow from './user-list-window.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n('desktop/views/components/user-lists-window.vue'),
 | 
					 | 
				
			||||||
	components: {
 | 
					 | 
				
			||||||
		XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		close() {
 | 
					 | 
				
			||||||
			(this as any).$refs.window.close();
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		choosen(list) {
 | 
					 | 
				
			||||||
			this.$root.new(MkUserListWindow, {
 | 
					 | 
				
			||||||
				list
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="stylus" module>
 | 
					 | 
				
			||||||
.content
 | 
					 | 
				
			||||||
	height 100%
 | 
					 | 
				
			||||||
	overflow auto
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,92 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<div class="rknalgpo" v-if="!fetching">
 | 
					 | 
				
			||||||
	<ui-button @click="create()"><fa :icon="faPlus"/></ui-button>
 | 
					 | 
				
			||||||
	<sequential-entrance animation="entranceFromTop" delay="25">
 | 
					 | 
				
			||||||
		<template v-for="page in pages">
 | 
					 | 
				
			||||||
			<x-page-preview class="page" :page="page" :key="page.id"/>
 | 
					 | 
				
			||||||
		</template>
 | 
					 | 
				
			||||||
	</sequential-entrance>
 | 
					 | 
				
			||||||
	<ui-button v-if="existMore" @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					 | 
				
			||||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
 | 
					 | 
				
			||||||
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
 | 
					 | 
				
			||||||
import XPagePreview from '../../../common/views/components/page-preview.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n(),
 | 
					 | 
				
			||||||
	components: {
 | 
					 | 
				
			||||||
		XPagePreview
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			pages: [],
 | 
					 | 
				
			||||||
			existMore: false,
 | 
					 | 
				
			||||||
			moreFetching: false,
 | 
					 | 
				
			||||||
			faStickyNote, faPlus
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	created() {
 | 
					 | 
				
			||||||
		this.fetch();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		fetch() {
 | 
					 | 
				
			||||||
			Progress.start();
 | 
					 | 
				
			||||||
			this.fetching = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$root.api('i/pages', {
 | 
					 | 
				
			||||||
				limit: 11
 | 
					 | 
				
			||||||
			}).then(pages => {
 | 
					 | 
				
			||||||
				if (pages.length == 11) {
 | 
					 | 
				
			||||||
					this.existMore = true;
 | 
					 | 
				
			||||||
					pages.pop();
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.pages = pages;
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				Progress.done();
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		fetchMore() {
 | 
					 | 
				
			||||||
			this.moreFetching = true;
 | 
					 | 
				
			||||||
			this.$root.api('i/pages', {
 | 
					 | 
				
			||||||
				limit: 11,
 | 
					 | 
				
			||||||
				untilId: this.pages[this.pages.length - 1].id
 | 
					 | 
				
			||||||
			}).then(pages => {
 | 
					 | 
				
			||||||
				if (pages.length == 11) {
 | 
					 | 
				
			||||||
					this.existMore = true;
 | 
					 | 
				
			||||||
					pages.pop();
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.existMore = false;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.pages = this.pages.concat(pages);
 | 
					 | 
				
			||||||
				this.moreFetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		create() {
 | 
					 | 
				
			||||||
			this.$router.push(`/i/pages/new`);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
.rknalgpo
 | 
					 | 
				
			||||||
	margin 0 auto
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	> * > .page
 | 
					 | 
				
			||||||
		margin-bottom 8px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@media (min-width 500px)
 | 
					 | 
				
			||||||
		> * > .page
 | 
					 | 
				
			||||||
			margin-bottom 16px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<div class="mk-messaging-room-page">
 | 
					<div class="mk-messaging-room-page">
 | 
				
			||||||
	<x-messaging-room v-if="user" :user="user" :is-naked="true"/>
 | 
						<x-messaging-room v-if="user || group" :user="user" :group="group" :is-naked="true"/>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,8 @@ export default Vue.extend({
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			fetching: true,
 | 
								fetching: true,
 | 
				
			||||||
			user: null
 | 
								user: null,
 | 
				
			||||||
 | 
								group: null
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	watch: {
 | 
						watch: {
 | 
				
			||||||
| 
						 | 
					@ -47,6 +48,7 @@ export default Vue.extend({
 | 
				
			||||||
			Progress.start();
 | 
								Progress.start();
 | 
				
			||||||
			this.fetching = true;
 | 
								this.fetching = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (this.$route.params.user) {
 | 
				
			||||||
				this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
 | 
									this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
 | 
				
			||||||
					this.user = user;
 | 
										this.user = user;
 | 
				
			||||||
					this.fetching = false;
 | 
										this.fetching = false;
 | 
				
			||||||
| 
						 | 
					@ -55,6 +57,16 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					Progress.done();
 | 
										Progress.done();
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									this.$root.api('users/groups/show', { groupId: this.$route.params.group }).then(group => {
 | 
				
			||||||
 | 
										this.group = group;
 | 
				
			||||||
 | 
										this.fetching = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										document.title = this.$t('@.messaging') + ': ' + this.group.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										Progress.done();
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@
 | 
				
			||||||
		<template #header><fa icon="comments"/>{{ $t('@.messaging') }}</template>
 | 
							<template #header><fa icon="comments"/>{{ $t('@.messaging') }}</template>
 | 
				
			||||||
		<template #func><button @click="add"><fa icon="plus"/></button></template>
 | 
							<template #func><button @click="add"><fa icon="plus"/></button></template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		<x-messaging ref="index" compact @navigate="navigate"/>
 | 
							<x-messaging ref="index" compact @navigate="navigate" @navigateGroup="navigateGroup"/>
 | 
				
			||||||
	</ui-container>
 | 
						</ui-container>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,11 @@ export default define({
 | 
				
			||||||
				user: user
 | 
									user: user
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							navigateGroup(group) {
 | 
				
			||||||
 | 
								this.$root.new(MkMessagingRoomWindow, {
 | 
				
			||||||
 | 
									group: group
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		add() {
 | 
							add() {
 | 
				
			||||||
			this.$root.new(MkMessagingWindow);
 | 
								this.$root.new(MkMessagingWindow);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,17 +18,16 @@ import MkDrive from './views/pages/drive.vue';
 | 
				
			||||||
import MkWidgets from './views/pages/widgets.vue';
 | 
					import MkWidgets from './views/pages/widgets.vue';
 | 
				
			||||||
import MkMessaging from './views/pages/messaging.vue';
 | 
					import MkMessaging from './views/pages/messaging.vue';
 | 
				
			||||||
import MkMessagingRoom from './views/pages/messaging-room.vue';
 | 
					import MkMessagingRoom from './views/pages/messaging-room.vue';
 | 
				
			||||||
import MkReceivedFollowRequests from './views/pages/received-follow-requests.vue';
 | 
					 | 
				
			||||||
import MkNote from './views/pages/note.vue';
 | 
					import MkNote from './views/pages/note.vue';
 | 
				
			||||||
import MkSearch from './views/pages/search.vue';
 | 
					import MkSearch from './views/pages/search.vue';
 | 
				
			||||||
import MkFavorites from './views/pages/favorites.vue';
 | 
					import MkFavorites from './views/pages/favorites.vue';
 | 
				
			||||||
import MkUserLists from './views/pages/user-lists.vue';
 | 
					import UI from './views/pages/ui.vue';
 | 
				
			||||||
import MkUserList from './views/pages/user-list.vue';
 | 
					 | 
				
			||||||
import MkReversi from './views/pages/games/reversi.vue';
 | 
					import MkReversi from './views/pages/games/reversi.vue';
 | 
				
			||||||
import MkTag from './views/pages/tag.vue';
 | 
					import MkTag from './views/pages/tag.vue';
 | 
				
			||||||
import MkShare from '../common/views/pages/share.vue';
 | 
					import MkShare from '../common/views/pages/share.vue';
 | 
				
			||||||
import MkFollow from '../common/views/pages/follow.vue';
 | 
					import MkFollow from '../common/views/pages/follow.vue';
 | 
				
			||||||
import MkNotFound from '../common/views/pages/not-found.vue';
 | 
					import MkNotFound from '../common/views/pages/not-found.vue';
 | 
				
			||||||
 | 
					import DeckColumn from '../common/views/deck/deck.column-template.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PostForm from './views/components/post-form-dialog.vue';
 | 
					import PostForm from './views/components/post-form-dialog.vue';
 | 
				
			||||||
import FileChooser from './views/components/drive-file-chooser.vue';
 | 
					import FileChooser from './views/components/drive-file-chooser.vue';
 | 
				
			||||||
| 
						 | 
					@ -125,9 +124,14 @@ init((launch, os) => {
 | 
				
			||||||
					{ path: '/search', component: () => import('../common/views/deck/deck.search-column.vue').then(m => m.default) },
 | 
										{ path: '/search', component: () => import('../common/views/deck/deck.search-column.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/tags/:tag', name: 'tag', component: () => import('../common/views/deck/deck.hashtag-column.vue').then(m => m.default) },
 | 
										{ path: '/tags/:tag', name: 'tag', component: () => import('../common/views/deck/deck.hashtag-column.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/featured', name: 'featured', component: () => import('../common/views/deck/deck.featured-column.vue').then(m => m.default) },
 | 
										{ path: '/featured', name: 'featured', component: () => import('../common/views/deck/deck.featured-column.vue').then(m => m.default) },
 | 
				
			||||||
					{ path: '/explore', name: 'explore', component: () => import('../common/views/deck/deck.explore-column.vue').then(m => m.default) },
 | 
										{ path: '/explore', name: 'explore', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default) }) },
 | 
				
			||||||
					{ path: '/explore/tags/:tag', name: 'explore-tag', props: true, component: () => import('../common/views/deck/deck.explore-column.vue').then(m => m.default) },
 | 
										{ path: '/explore/tags/:tag', name: 'explore-tag', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default), tag: route.params.tag }) },
 | 
				
			||||||
					{ path: '/i/favorites', component: () => import('../common/views/deck/deck.favorites-column.vue').then(m => m.default) }
 | 
										{ path: '/i/favorites', component: () => import('../common/views/deck/deck.favorites-column.vue').then(m => m.default) },
 | 
				
			||||||
 | 
										{ path: '/i/pages', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) },
 | 
				
			||||||
 | 
										{ path: '/i/lists', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) },
 | 
				
			||||||
 | 
										{ path: '/i/lists/:listId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.listId }) },
 | 
				
			||||||
 | 
										{ path: '/i/groups', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
 | 
				
			||||||
 | 
										{ path: '/i/groups/:groupId', component: DeckColumn, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.groupId }) },
 | 
				
			||||||
				]}]
 | 
									]}]
 | 
				
			||||||
			: [
 | 
								: [
 | 
				
			||||||
				{ path: '/', name: 'index', component: MkIndex },
 | 
									{ path: '/', name: 'index', component: MkIndex },
 | 
				
			||||||
| 
						 | 
					@ -135,12 +139,15 @@ init((launch, os) => {
 | 
				
			||||||
			{ path: '/signup', name: 'signup', component: MkSignup },
 | 
								{ path: '/signup', name: 'signup', component: MkSignup },
 | 
				
			||||||
			{ path: '/i/settings', name: 'settings', component: () => import('./views/pages/settings.vue').then(m => m.default) },
 | 
								{ path: '/i/settings', name: 'settings', component: () => import('./views/pages/settings.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/i/favorites', name: 'favorites', component: MkFavorites },
 | 
								{ path: '/i/favorites', name: 'favorites', component: MkFavorites },
 | 
				
			||||||
			{ path: '/i/pages', name: 'pages', component: () => import('./views/pages/pages.vue').then(m => m.default) },
 | 
								{ path: '/i/pages', name: 'pages', component: UI, props: route => ({ component: () => import('../common/views/pages/pages.vue').then(m => m.default) }) },
 | 
				
			||||||
			{ path: '/i/lists', name: 'user-lists', component: MkUserLists },
 | 
								{ path: '/i/lists', name: 'user-lists', component: UI, props: route => ({ component: () => import('../common/views/pages/user-lists.vue').then(m => m.default) }) },
 | 
				
			||||||
			{ path: '/i/lists/:list', name: 'user-list', component: MkUserList },
 | 
								{ path: '/i/lists/:list', component: UI, props: route => ({ component: () => import('../common/views/pages/user-list-editor.vue').then(m => m.default), listId: route.params.list }) },
 | 
				
			||||||
			{ path: '/i/received-follow-requests', name: 'received-follow-requests', component: MkReceivedFollowRequests },
 | 
								{ path: '/i/groups', name: 'user-groups', component: UI, props: route => ({ component: () => import('../common/views/pages/user-groups.vue').then(m => m.default) }) },
 | 
				
			||||||
 | 
								{ path: '/i/groups/:group', component: UI, props: route => ({ component: () => import('../common/views/pages/user-group-editor.vue').then(m => m.default), groupId: route.params.group }) },
 | 
				
			||||||
 | 
								{ path: '/i/follow-requests', name: 'follow-requests', component: UI, props: route => ({ component: () => import('../common/views/pages/follow-requests.vue').then(m => m.default) }) },
 | 
				
			||||||
			{ path: '/i/widgets', name: 'widgets', component: MkWidgets },
 | 
								{ path: '/i/widgets', name: 'widgets', component: MkWidgets },
 | 
				
			||||||
			{ path: '/i/messaging', name: 'messaging', component: MkMessaging },
 | 
								{ path: '/i/messaging', name: 'messaging', component: MkMessaging },
 | 
				
			||||||
 | 
								{ path: '/i/messaging/group/:group', component: MkMessagingRoom },
 | 
				
			||||||
			{ path: '/i/messaging/:user', component: MkMessagingRoom },
 | 
								{ path: '/i/messaging/:user', component: MkMessagingRoom },
 | 
				
			||||||
			{ path: '/i/drive', name: 'drive', component: MkDrive },
 | 
								{ path: '/i/drive', name: 'drive', component: MkDrive },
 | 
				
			||||||
			{ path: '/i/drive/folder/:folder', component: MkDrive },
 | 
								{ path: '/i/drive/folder/:folder', component: MkDrive },
 | 
				
			||||||
| 
						 | 
					@ -151,8 +158,8 @@ init((launch, os) => {
 | 
				
			||||||
			{ path: '/search', component: MkSearch },
 | 
								{ path: '/search', component: MkSearch },
 | 
				
			||||||
			{ path: '/tags/:tag', component: MkTag },
 | 
								{ path: '/tags/:tag', component: MkTag },
 | 
				
			||||||
			{ path: '/featured', name: 'featured', component: () => import('./views/pages/featured.vue').then(m => m.default) },
 | 
								{ path: '/featured', name: 'featured', component: () => import('./views/pages/featured.vue').then(m => m.default) },
 | 
				
			||||||
			{ path: '/explore', name: 'explore', component: () => import('./views/pages/explore.vue').then(m => m.default) },
 | 
								{ path: '/explore', name: 'explore', component: UI, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default) }) },
 | 
				
			||||||
			{ path: '/explore/tags/:tag', name: 'explore-tag', props: true, component: () => import('./views/pages/explore.vue').then(m => m.default) },
 | 
								{ path: '/explore/tags/:tag', name: 'explore-tag', component: UI, props: route => ({ component: () => import('../common/views/pages/explore.vue').then(m => m.default), tag: route.params.tag }) },
 | 
				
			||||||
			{ path: '/share', component: MkShare },
 | 
								{ path: '/share', component: MkShare },
 | 
				
			||||||
			{ path: '/games/reversi/:game?', name: 'reversi', component: MkReversi },
 | 
								{ path: '/games/reversi/:game?', name: 'reversi', component: MkReversi },
 | 
				
			||||||
			{ path: '/@:user', name: 'user', component: () => import('./views/pages/user/index.vue').then(m => m.default), children: [
 | 
								{ path: '/@:user', name: 'user', component: () => import('./views/pages/user/index.vue').then(m => m.default), children: [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@
 | 
				
			||||||
						<li><router-link to="/" :data-active="$route.name == 'index'"><i><fa icon="home" fixed-width/></i>{{ $t('timeline') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
											<li><router-link to="/" :data-active="$route.name == 'index'"><i><fa icon="home" fixed-width/></i>{{ $t('timeline') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
				
			||||||
						<li><p @click="showNotifications = true"><i><fa :icon="['far', 'bell']" fixed-width/></i>{{ $t('notifications') }}<i v-if="hasUnreadNotification" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></p></li>
 | 
											<li><p @click="showNotifications = true"><i><fa :icon="['far', 'bell']" fixed-width/></i>{{ $t('notifications') }}<i v-if="hasUnreadNotification" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></p></li>
 | 
				
			||||||
						<li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'"><i><fa :icon="['far', 'comments']" fixed-width/></i>{{ $t('@.messaging') }}<i v-if="hasUnreadMessagingMessage" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
 | 
											<li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'"><i><fa :icon="['far', 'comments']" fixed-width/></i>{{ $t('@.messaging') }}<i v-if="hasUnreadMessagingMessage" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
 | 
				
			||||||
						<li v-if="$store.getters.isSignedIn && ($store.state.i.isLocked || $store.state.i.carefulBot)"><router-link to="/i/received-follow-requests" :data-active="$route.name == 'received-follow-requests'"><i><fa :icon="['far', 'envelope']" fixed-width/></i>{{ $t('follow-requests') }}<i v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
 | 
											<li v-if="$store.getters.isSignedIn && ($store.state.i.isLocked || $store.state.i.carefulBot)"><router-link to="/i/follow-requests" :data-active="$route.name == 'follow-requests'"><i><fa :icon="['far', 'envelope']" fixed-width/></i>{{ $t('follow-requests') }}<i v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
 | 
				
			||||||
						<li><router-link to="/featured" :data-active="$route.name == 'featured'"><i><fa :icon="faNewspaper" fixed-width/></i>{{ $t('@.featured-notes') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
											<li><router-link to="/featured" :data-active="$route.name == 'featured'"><i><fa :icon="faNewspaper" fixed-width/></i>{{ $t('@.featured-notes') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
				
			||||||
						<li><router-link to="/explore" :data-active="$route.name == 'explore' || $route.name == 'explore-tag'"><i><fa :icon="faHashtag" fixed-width/></i>{{ $t('@.explore') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
											<li><router-link to="/explore" :data-active="$route.name == 'explore' || $route.name == 'explore-tag'"><i><fa :icon="faHashtag" fixed-width/></i>{{ $t('@.explore') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
				
			||||||
						<li><router-link to="/games/reversi" :data-active="$route.name == 'reversi'"><i><fa icon="gamepad" fixed-width/></i>{{ $t('game') }}<i v-if="hasGameInvitation" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
 | 
											<li><router-link to="/games/reversi" :data-active="$route.name == 'reversi'"><i><fa icon="gamepad" fixed-width/></i>{{ $t('game') }}<i v-if="hasGameInvitation" class="circle"><fa icon="circle"/></i><i><fa icon="angle-right"/></i></router-link></li>
 | 
				
			||||||
| 
						 | 
					@ -28,6 +28,7 @@
 | 
				
			||||||
						<li><router-link to="/i/widgets" :data-active="$route.name == 'widgets'"><i><fa :icon="['far', 'calendar-alt']" fixed-width/></i>{{ $t('widgets') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
											<li><router-link to="/i/widgets" :data-active="$route.name == 'widgets'"><i><fa :icon="['far', 'calendar-alt']" fixed-width/></i>{{ $t('widgets') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
				
			||||||
						<li><router-link to="/i/favorites" :data-active="$route.name == 'favorites'"><i><fa icon="star" fixed-width/></i>{{ $t('@.favorites') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
											<li><router-link to="/i/favorites" :data-active="$route.name == 'favorites'"><i><fa icon="star" fixed-width/></i>{{ $t('@.favorites') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
				
			||||||
						<li><router-link to="/i/lists" :data-active="$route.name == 'user-lists'"><i><fa icon="list" fixed-width/></i>{{ $t('user-lists') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
											<li><router-link to="/i/lists" :data-active="$route.name == 'user-lists'"><i><fa icon="list" fixed-width/></i>{{ $t('user-lists') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
				
			||||||
 | 
											<li><router-link to="/i/groups" :data-active="$route.name == 'user-groups'"><i><fa :icon="faUsers" fixed-width/></i>{{ $t('user-groups') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
				
			||||||
						<li><router-link to="/i/drive" :data-active="$route.name == 'drive'"><i><fa icon="cloud" fixed-width/></i>{{ $t('@.drive') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
											<li><router-link to="/i/drive" :data-active="$route.name == 'drive'"><i><fa icon="cloud" fixed-width/></i>{{ $t('@.drive') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
				
			||||||
						<li><router-link to="/i/pages" :data-active="$route.name == 'pages'"><i><fa :icon="faStickyNote" fixed-width/></i>{{ $t('@.pages') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
											<li><router-link to="/i/pages" :data-active="$route.name == 'pages'"><i><fa :icon="faStickyNote" fixed-width/></i>{{ $t('@.pages') }}<i><fa icon="angle-right"/></i></router-link></li>
 | 
				
			||||||
					</ul>
 | 
										</ul>
 | 
				
			||||||
| 
						 | 
					@ -66,7 +67,7 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import { lang } from '../../../config';
 | 
					import { lang } from '../../../config';
 | 
				
			||||||
import { faNewspaper, faHashtag, faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
 | 
					import { faNewspaper, faHashtag, faHome, faColumns, faUsers } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
import { faMoon, faSun, faStickyNote } from '@fortawesome/free-regular-svg-icons';
 | 
					import { faMoon, faSun, faStickyNote } from '@fortawesome/free-regular-svg-icons';
 | 
				
			||||||
import { search } from '../../../common/scripts/search';
 | 
					import { search } from '../../../common/scripts/search';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -87,7 +88,7 @@ export default Vue.extend({
 | 
				
			||||||
			announcements: [],
 | 
								announcements: [],
 | 
				
			||||||
			searching: false,
 | 
								searching: false,
 | 
				
			||||||
			showNotifications: false,
 | 
								showNotifications: false,
 | 
				
			||||||
			faNewspaper, faHashtag, faMoon, faSun, faHome, faColumns, faStickyNote
 | 
								faNewspaper, faHashtag, faMoon, faSun, faHome, faColumns, faStickyNote, faUsers
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,28 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<mk-ui>
 | 
					 | 
				
			||||||
	<template #header><span style="margin-right:4px;"><fa :icon="faHashtag"/></span>{{ $t('@.explore') }}</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<main>
 | 
					 | 
				
			||||||
		<x-explore v-bind="$attrs"/>
 | 
					 | 
				
			||||||
	</main>
 | 
					 | 
				
			||||||
</mk-ui>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
import { faHashtag } from '@fortawesome/free-solid-svg-icons';
 | 
					 | 
				
			||||||
import XExplore from '../../../common/views/pages/explore.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n(''),
 | 
					 | 
				
			||||||
	components: {
 | 
					 | 
				
			||||||
		XExplore
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			faHashtag
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -2,9 +2,10 @@
 | 
				
			||||||
<mk-ui>
 | 
					<mk-ui>
 | 
				
			||||||
	<template #header>
 | 
						<template #header>
 | 
				
			||||||
		<template v-if="user"><span style="margin-right:4px;"><fa :icon="['far', 'comments']"/></span><mk-user-name :user="user"/></template>
 | 
							<template v-if="user"><span style="margin-right:4px;"><fa :icon="['far', 'comments']"/></span><mk-user-name :user="user"/></template>
 | 
				
			||||||
 | 
							<template v-else-if="group"><span style="margin-right:4px;"><fa :icon="['far', 'comments']"/></span>{{ group.name }}</template>
 | 
				
			||||||
		<template v-else><mk-ellipsis/></template>
 | 
							<template v-else><mk-ellipsis/></template>
 | 
				
			||||||
	</template>
 | 
						</template>
 | 
				
			||||||
	<x-messaging-room v-if="!fetching" :user="user" :is-naked="true"/>
 | 
						<x-messaging-room v-if="!fetching" :user="user" :group="group" :is-naked="true"/>
 | 
				
			||||||
</mk-ui>
 | 
					</mk-ui>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,6 +23,7 @@ export default Vue.extend({
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			fetching: true,
 | 
								fetching: true,
 | 
				
			||||||
			user: null,
 | 
								user: null,
 | 
				
			||||||
 | 
								group: null,
 | 
				
			||||||
			unwatchDarkmode: null
 | 
								unwatchDarkmode: null
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -48,12 +50,21 @@ export default Vue.extend({
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		fetch() {
 | 
							fetch() {
 | 
				
			||||||
			this.fetching = true;
 | 
								this.fetching = true;
 | 
				
			||||||
 | 
								if (this.$route.params.user) {
 | 
				
			||||||
				this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
 | 
									this.$root.api('users/show', parseAcct(this.$route.params.user)).then(user => {
 | 
				
			||||||
					this.user = user;
 | 
										this.user = user;
 | 
				
			||||||
					this.fetching = false;
 | 
										this.fetching = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					document.title = `${this.$t('@.messaging')}: ${Vue.filter('userName')(this.user)} | ${this.$root.instanceName}`;
 | 
										document.title = `${this.$t('@.messaging')}: ${Vue.filter('userName')(this.user)} | ${this.$root.instanceName}`;
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									this.$root.api('users/groups/show', { groupId: this.$route.params.group }).then(group => {
 | 
				
			||||||
 | 
										this.group = group;
 | 
				
			||||||
 | 
										this.fetching = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										document.title = this.$t('@.messaging') + ': ' + this.group.name;
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
<mk-ui>
 | 
					<mk-ui>
 | 
				
			||||||
	<template #header><span style="margin-right:4px;"><fa :icon="['far', 'comments']"/></span>{{ $t('@.messaging') }}</template>
 | 
						<template #header><span style="margin-right:4px;"><fa :icon="['far', 'comments']"/></span>{{ $t('@.messaging') }}</template>
 | 
				
			||||||
	<x-messaging @navigate="navigate" :header-top="48"/>
 | 
						<x-messaging @navigate="navigate" @navigateGroup="navigateGroup" :header-top="48"/>
 | 
				
			||||||
</mk-ui>
 | 
					</mk-ui>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,9 @@ export default Vue.extend({
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		navigate(user) {
 | 
							navigate(user) {
 | 
				
			||||||
			(this as any).$router.push(`/i/messaging/${getAcct(user)}`);
 | 
								(this as any).$router.push(`/i/messaging/${getAcct(user)}`);
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							navigateGroup(group) {
 | 
				
			||||||
 | 
								(this as any).$router.push(`/i/messaging/group/${group.id}`);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,94 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<mk-ui>
 | 
					 | 
				
			||||||
	<template #header><span style="margin-right:4px;"><fa :icon="faStickyNote"/></span>{{ $t('@.pages') }}</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<main>
 | 
					 | 
				
			||||||
		<ui-button @click="create()"><fa :icon="faPlus"/></ui-button>
 | 
					 | 
				
			||||||
		<sequential-entrance animation="entranceFromTop" delay="25">
 | 
					 | 
				
			||||||
			<template v-for="page in pages">
 | 
					 | 
				
			||||||
				<x-page-preview class="page" :page="page" :key="page.id"/>
 | 
					 | 
				
			||||||
			</template>
 | 
					 | 
				
			||||||
		</sequential-entrance>
 | 
					 | 
				
			||||||
		<ui-button v-if="existMore" @click="fetchMore()">{{ $t('@.load-more') }}</ui-button>
 | 
					 | 
				
			||||||
	</main>
 | 
					 | 
				
			||||||
</mk-ui>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					 | 
				
			||||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
 | 
					 | 
				
			||||||
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
 | 
					 | 
				
			||||||
import XPagePreview from '../../../common/views/components/page-preview.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n(),
 | 
					 | 
				
			||||||
	components: {
 | 
					 | 
				
			||||||
		XPagePreview
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			pages: [],
 | 
					 | 
				
			||||||
			existMore: false,
 | 
					 | 
				
			||||||
			moreFetching: false,
 | 
					 | 
				
			||||||
			faStickyNote, faPlus
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	created() {
 | 
					 | 
				
			||||||
		this.fetch();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		fetch() {
 | 
					 | 
				
			||||||
			Progress.start();
 | 
					 | 
				
			||||||
			this.fetching = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$root.api('i/pages', {
 | 
					 | 
				
			||||||
				limit: 11
 | 
					 | 
				
			||||||
			}).then(pages => {
 | 
					 | 
				
			||||||
				if (pages.length == 11) {
 | 
					 | 
				
			||||||
					this.existMore = true;
 | 
					 | 
				
			||||||
					pages.pop();
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.pages = pages;
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				Progress.done();
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		fetchMore() {
 | 
					 | 
				
			||||||
			this.moreFetching = true;
 | 
					 | 
				
			||||||
			this.$root.api('i/pages', {
 | 
					 | 
				
			||||||
				limit: 11,
 | 
					 | 
				
			||||||
				untilId: this.pages[this.pages.length - 1].id
 | 
					 | 
				
			||||||
			}).then(pages => {
 | 
					 | 
				
			||||||
				if (pages.length == 11) {
 | 
					 | 
				
			||||||
					this.existMore = true;
 | 
					 | 
				
			||||||
					pages.pop();
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					this.existMore = false;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				this.pages = this.pages.concat(pages);
 | 
					 | 
				
			||||||
				this.moreFetching = false;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		create() {
 | 
					 | 
				
			||||||
			this.$router.push(`/i/pages/new`);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<style lang="stylus" scoped>
 | 
					 | 
				
			||||||
main
 | 
					 | 
				
			||||||
	> * > .page
 | 
					 | 
				
			||||||
		margin-bottom 8px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@media (min-width 500px)
 | 
					 | 
				
			||||||
		> * > .page
 | 
					 | 
				
			||||||
			margin-bottom 16px
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</style>
 | 
					 | 
				
			||||||
							
								
								
									
										38
									
								
								src/client/app/mobile/views/pages/ui.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/client/app/mobile/views/pages/ui.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,38 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<mk-ui>
 | 
				
			||||||
 | 
						<template #header><span style="margin-right:4px;" v-if="icon"><fa :icon="icon"/></span>{{ title }}</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<main>
 | 
				
			||||||
 | 
							<component :is="component" @init="init" v-bind="$attrs"/>
 | 
				
			||||||
 | 
						</main>
 | 
				
			||||||
 | 
					</mk-ui>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						props: {
 | 
				
			||||||
 | 
							component: {
 | 
				
			||||||
 | 
								required: true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								title: null,
 | 
				
			||||||
 | 
								icon: null,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mounted() {
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							init(v) {
 | 
				
			||||||
 | 
								this.title = v.title;
 | 
				
			||||||
 | 
								this.icon = v.icon;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -1,48 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<mk-ui>
 | 
					 | 
				
			||||||
	<template #header v-if="!fetching"><fa icon="list"/>{{ list.name }}</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<main v-if="!fetching">
 | 
					 | 
				
			||||||
		<x-editor :list="list"/>
 | 
					 | 
				
			||||||
	</main>
 | 
					 | 
				
			||||||
</mk-ui>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					 | 
				
			||||||
import XEditor from '../../../common/views/components/user-list-editor.vue';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	components: {
 | 
					 | 
				
			||||||
		XEditor
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			list: null
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	watch: {
 | 
					 | 
				
			||||||
		$route: 'fetch'
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	created() {
 | 
					 | 
				
			||||||
		this.fetch();
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		fetch() {
 | 
					 | 
				
			||||||
			Progress.start();
 | 
					 | 
				
			||||||
			this.fetching = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			this.$root.api('users/lists/show', {
 | 
					 | 
				
			||||||
				listId: this.$route.params.list
 | 
					 | 
				
			||||||
			}).then(list => {
 | 
					 | 
				
			||||||
				this.list = list;
 | 
					 | 
				
			||||||
				this.fetching = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				Progress.done();
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,35 +0,0 @@
 | 
				
			||||||
<template>
 | 
					 | 
				
			||||||
<mk-ui>
 | 
					 | 
				
			||||||
	<template #header><fa icon="list"/>{{ $t('title') }}</template>
 | 
					 | 
				
			||||||
	<template #func><button @click="$refs.lists.add()"><fa icon="plus"/></button></template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	<x-lists ref="lists" @choosen="choosen"/>
 | 
					 | 
				
			||||||
</mk-ui>
 | 
					 | 
				
			||||||
</template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
import Vue from 'vue';
 | 
					 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default Vue.extend({
 | 
					 | 
				
			||||||
	i18n: i18n('mobile/views/pages/user-lists.vue'),
 | 
					 | 
				
			||||||
	data() {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			fetching: true,
 | 
					 | 
				
			||||||
			lists: []
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	components: {
 | 
					 | 
				
			||||||
		XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	mounted() {
 | 
					 | 
				
			||||||
		document.title = this.$t('title');
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	methods: {
 | 
					 | 
				
			||||||
		choosen(list) {
 | 
					 | 
				
			||||||
			if (!list) return;
 | 
					 | 
				
			||||||
			this.$router.push(`/i/lists/${list.id}`);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,8 @@ import { SwSubscription } from '../models/entities/sw-subscription';
 | 
				
			||||||
import { Blocking } from '../models/entities/blocking';
 | 
					import { Blocking } from '../models/entities/blocking';
 | 
				
			||||||
import { UserList } from '../models/entities/user-list';
 | 
					import { UserList } from '../models/entities/user-list';
 | 
				
			||||||
import { UserListJoining } from '../models/entities/user-list-joining';
 | 
					import { UserListJoining } from '../models/entities/user-list-joining';
 | 
				
			||||||
 | 
					import { UserGroup } from '../models/entities/user-group';
 | 
				
			||||||
 | 
					import { UserGroupJoining } from '../models/entities/user-group-joining';
 | 
				
			||||||
import { Hashtag } from '../models/entities/hashtag';
 | 
					import { Hashtag } from '../models/entities/hashtag';
 | 
				
			||||||
import { NoteFavorite } from '../models/entities/note-favorite';
 | 
					import { NoteFavorite } from '../models/entities/note-favorite';
 | 
				
			||||||
import { AbuseUserReport } from '../models/entities/abuse-user-report';
 | 
					import { AbuseUserReport } from '../models/entities/abuse-user-report';
 | 
				
			||||||
| 
						 | 
					@ -41,6 +43,7 @@ import { UserKeypair } from '../models/entities/user-keypair';
 | 
				
			||||||
import { UserPublickey } from '../models/entities/user-publickey';
 | 
					import { UserPublickey } from '../models/entities/user-publickey';
 | 
				
			||||||
import { UserProfile } from '../models/entities/user-profile';
 | 
					import { UserProfile } from '../models/entities/user-profile';
 | 
				
			||||||
import { Page } from '../models/entities/page';
 | 
					import { Page } from '../models/entities/page';
 | 
				
			||||||
 | 
					import { PageLike } from '../models/entities/page-like';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
 | 
					const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -105,6 +108,8 @@ export function initDb(justBorrow = false, sync = false, log = false) {
 | 
				
			||||||
			UserPublickey,
 | 
								UserPublickey,
 | 
				
			||||||
			UserList,
 | 
								UserList,
 | 
				
			||||||
			UserListJoining,
 | 
								UserListJoining,
 | 
				
			||||||
 | 
								UserGroup,
 | 
				
			||||||
 | 
								UserGroupJoining,
 | 
				
			||||||
			UserNotePining,
 | 
								UserNotePining,
 | 
				
			||||||
			Following,
 | 
								Following,
 | 
				
			||||||
			FollowRequest,
 | 
								FollowRequest,
 | 
				
			||||||
| 
						 | 
					@ -116,6 +121,7 @@ export function initDb(justBorrow = false, sync = false, log = false) {
 | 
				
			||||||
			NoteWatching,
 | 
								NoteWatching,
 | 
				
			||||||
			NoteUnread,
 | 
								NoteUnread,
 | 
				
			||||||
			Page,
 | 
								Page,
 | 
				
			||||||
 | 
								PageLike,
 | 
				
			||||||
			Log,
 | 
								Log,
 | 
				
			||||||
			DriveFile,
 | 
								DriveFile,
 | 
				
			||||||
			DriveFolder,
 | 
								DriveFolder,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,7 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typ
 | 
				
			||||||
import { User } from './user';
 | 
					import { User } from './user';
 | 
				
			||||||
import { DriveFile } from './drive-file';
 | 
					import { DriveFile } from './drive-file';
 | 
				
			||||||
import { id } from '../id';
 | 
					import { id } from '../id';
 | 
				
			||||||
 | 
					import { UserGroup } from './user-group';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Entity()
 | 
					@Entity()
 | 
				
			||||||
export class MessagingMessage {
 | 
					export class MessagingMessage {
 | 
				
			||||||
| 
						 | 
					@ -29,10 +30,10 @@ export class MessagingMessage {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Index()
 | 
						@Index()
 | 
				
			||||||
	@Column({
 | 
						@Column({
 | 
				
			||||||
		...id(),
 | 
							...id(), nullable: true,
 | 
				
			||||||
		comment: 'The recipient user ID.'
 | 
							comment: 'The recipient user ID.'
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public recipientId: User['id'];
 | 
						public recipientId: User['id'] | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@ManyToOne(type => User, {
 | 
						@ManyToOne(type => User, {
 | 
				
			||||||
		onDelete: 'CASCADE'
 | 
							onDelete: 'CASCADE'
 | 
				
			||||||
| 
						 | 
					@ -40,6 +41,19 @@ export class MessagingMessage {
 | 
				
			||||||
	@JoinColumn()
 | 
						@JoinColumn()
 | 
				
			||||||
	public recipient: User | null;
 | 
						public recipient: User | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Index()
 | 
				
			||||||
 | 
						@Column({
 | 
				
			||||||
 | 
							...id(), nullable: true,
 | 
				
			||||||
 | 
							comment: 'The recipient group ID.'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public groupId: UserGroup['id'] | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ManyToOne(type => UserGroup, {
 | 
				
			||||||
 | 
							onDelete: 'CASCADE'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						@JoinColumn()
 | 
				
			||||||
 | 
						public group: UserGroup | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Column('varchar', {
 | 
						@Column('varchar', {
 | 
				
			||||||
		length: 4096, nullable: true
 | 
							length: 4096, nullable: true
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
| 
						 | 
					@ -50,6 +64,12 @@ export class MessagingMessage {
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public isRead: boolean;
 | 
						public isRead: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column({
 | 
				
			||||||
 | 
							...id(),
 | 
				
			||||||
 | 
							array: true, default: '{}'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public reads: User['id'][];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Column({
 | 
						@Column({
 | 
				
			||||||
		...id(),
 | 
							...id(),
 | 
				
			||||||
		nullable: true,
 | 
							nullable: true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										33
									
								
								src/models/entities/page-like.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/models/entities/page-like.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
 | 
				
			||||||
 | 
					import { User } from './user';
 | 
				
			||||||
 | 
					import { id } from '../id';
 | 
				
			||||||
 | 
					import { Page } from './page';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Entity()
 | 
				
			||||||
 | 
					@Index(['userId', 'pageId'], { unique: true })
 | 
				
			||||||
 | 
					export class PageLike {
 | 
				
			||||||
 | 
						@PrimaryColumn(id())
 | 
				
			||||||
 | 
						public id: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('timestamp with time zone')
 | 
				
			||||||
 | 
						public createdAt: Date;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Index()
 | 
				
			||||||
 | 
						@Column(id())
 | 
				
			||||||
 | 
						public userId: User['id'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ManyToOne(type => User, {
 | 
				
			||||||
 | 
							onDelete: 'CASCADE'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						@JoinColumn()
 | 
				
			||||||
 | 
						public user: User | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column(id())
 | 
				
			||||||
 | 
						public pageId: Page['id'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ManyToOne(type => Page, {
 | 
				
			||||||
 | 
							onDelete: 'CASCADE'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						@JoinColumn()
 | 
				
			||||||
 | 
						public page: Page | null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -95,6 +95,11 @@ export class Page {
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	public visibleUserIds: User['id'][];
 | 
						public visibleUserIds: User['id'][];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('integer', {
 | 
				
			||||||
 | 
							default: 0
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public likedCount: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(data: Partial<Page>) {
 | 
						constructor(data: Partial<Page>) {
 | 
				
			||||||
		if (data == null) return;
 | 
							if (data == null) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										41
									
								
								src/models/entities/user-group-joining.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/models/entities/user-group-joining.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
 | 
				
			||||||
 | 
					import { User } from './user';
 | 
				
			||||||
 | 
					import { UserGroup } from './user-group';
 | 
				
			||||||
 | 
					import { id } from '../id';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Entity()
 | 
				
			||||||
 | 
					export class UserGroupJoining {
 | 
				
			||||||
 | 
						@PrimaryColumn(id())
 | 
				
			||||||
 | 
						public id: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('timestamp with time zone', {
 | 
				
			||||||
 | 
							comment: 'The created date of the UserGroupJoining.'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public createdAt: Date;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Index()
 | 
				
			||||||
 | 
						@Column({
 | 
				
			||||||
 | 
							...id(),
 | 
				
			||||||
 | 
							comment: 'The user ID.'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public userId: User['id'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ManyToOne(type => User, {
 | 
				
			||||||
 | 
							onDelete: 'CASCADE'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						@JoinColumn()
 | 
				
			||||||
 | 
						public user: User | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Index()
 | 
				
			||||||
 | 
						@Column({
 | 
				
			||||||
 | 
							...id(),
 | 
				
			||||||
 | 
							comment: 'The group ID.'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public userGroupId: UserGroup['id'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ManyToOne(type => UserGroup, {
 | 
				
			||||||
 | 
							onDelete: 'CASCADE'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						@JoinColumn()
 | 
				
			||||||
 | 
						public userGroup: UserGroup | null;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/models/entities/user-group.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/models/entities/user-group.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
 | 
				
			||||||
 | 
					import { User } from './user';
 | 
				
			||||||
 | 
					import { id } from '../id';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Entity()
 | 
				
			||||||
 | 
					export class UserGroup {
 | 
				
			||||||
 | 
						@PrimaryColumn(id())
 | 
				
			||||||
 | 
						public id: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Index()
 | 
				
			||||||
 | 
						@Column('timestamp with time zone', {
 | 
				
			||||||
 | 
							comment: 'The created date of the UserGroup.'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public createdAt: Date;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('varchar', {
 | 
				
			||||||
 | 
							length: 256,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public name: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Index()
 | 
				
			||||||
 | 
						@Column({
 | 
				
			||||||
 | 
							...id(),
 | 
				
			||||||
 | 
							comment: 'The ID of owner.'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public userId: User['id'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@ManyToOne(type => User, {
 | 
				
			||||||
 | 
							onDelete: 'CASCADE'
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						@JoinColumn()
 | 
				
			||||||
 | 
						public user: User | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Column('boolean', {
 | 
				
			||||||
 | 
							default: false,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						public isPrivate: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constructor(data: Partial<UserGroup>) {
 | 
				
			||||||
 | 
							if (data == null) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (const [k, v] of Object.entries(data)) {
 | 
				
			||||||
 | 
								(this as any)[k] = v;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,6 @@ import { PollVote } from './entities/poll-vote';
 | 
				
			||||||
import { Meta } from './entities/meta';
 | 
					import { Meta } from './entities/meta';
 | 
				
			||||||
import { SwSubscription } from './entities/sw-subscription';
 | 
					import { SwSubscription } from './entities/sw-subscription';
 | 
				
			||||||
import { NoteWatching } from './entities/note-watching';
 | 
					import { NoteWatching } from './entities/note-watching';
 | 
				
			||||||
import { UserListJoining } from './entities/user-list-joining';
 | 
					 | 
				
			||||||
import { NoteUnread } from './entities/note-unread';
 | 
					import { NoteUnread } from './entities/note-unread';
 | 
				
			||||||
import { RegistrationTicket } from './entities/registration-tickets';
 | 
					import { RegistrationTicket } from './entities/registration-tickets';
 | 
				
			||||||
import { UserRepository } from './repositories/user';
 | 
					import { UserRepository } from './repositories/user';
 | 
				
			||||||
| 
						 | 
					@ -20,6 +19,9 @@ import { SigninRepository } from './repositories/signin';
 | 
				
			||||||
import { MessagingMessageRepository } from './repositories/messaging-message';
 | 
					import { MessagingMessageRepository } from './repositories/messaging-message';
 | 
				
			||||||
import { ReversiGameRepository } from './repositories/games/reversi/game';
 | 
					import { ReversiGameRepository } from './repositories/games/reversi/game';
 | 
				
			||||||
import { UserListRepository } from './repositories/user-list';
 | 
					import { UserListRepository } from './repositories/user-list';
 | 
				
			||||||
 | 
					import { UserListJoining } from './entities/user-list-joining';
 | 
				
			||||||
 | 
					import { UserGroupRepository } from './repositories/user-group';
 | 
				
			||||||
 | 
					import { UserGroupJoining } from './entities/user-group-joining';
 | 
				
			||||||
import { FollowRequestRepository } from './repositories/follow-request';
 | 
					import { FollowRequestRepository } from './repositories/follow-request';
 | 
				
			||||||
import { MutingRepository } from './repositories/muting';
 | 
					import { MutingRepository } from './repositories/muting';
 | 
				
			||||||
import { BlockingRepository } from './repositories/blocking';
 | 
					import { BlockingRepository } from './repositories/blocking';
 | 
				
			||||||
| 
						 | 
					@ -36,6 +38,7 @@ import { AuthSessionRepository } from './repositories/auth-session';
 | 
				
			||||||
import { UserProfile } from './entities/user-profile';
 | 
					import { UserProfile } from './entities/user-profile';
 | 
				
			||||||
import { HashtagRepository } from './repositories/hashtag';
 | 
					import { HashtagRepository } from './repositories/hashtag';
 | 
				
			||||||
import { PageRepository } from './repositories/page';
 | 
					import { PageRepository } from './repositories/page';
 | 
				
			||||||
 | 
					import { PageLikeRepository } from './repositories/page-like';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const Apps = getCustomRepository(AppRepository);
 | 
					export const Apps = getCustomRepository(AppRepository);
 | 
				
			||||||
export const Notes = getCustomRepository(NoteRepository);
 | 
					export const Notes = getCustomRepository(NoteRepository);
 | 
				
			||||||
| 
						 | 
					@ -51,6 +54,8 @@ export const UserKeypairs = getRepository(UserKeypair);
 | 
				
			||||||
export const UserPublickeys = getRepository(UserPublickey);
 | 
					export const UserPublickeys = getRepository(UserPublickey);
 | 
				
			||||||
export const UserLists = getCustomRepository(UserListRepository);
 | 
					export const UserLists = getCustomRepository(UserListRepository);
 | 
				
			||||||
export const UserListJoinings = getRepository(UserListJoining);
 | 
					export const UserListJoinings = getRepository(UserListJoining);
 | 
				
			||||||
 | 
					export const UserGroups = getCustomRepository(UserGroupRepository);
 | 
				
			||||||
 | 
					export const UserGroupJoinings = getRepository(UserGroupJoining);
 | 
				
			||||||
export const UserNotePinings = getRepository(UserNotePining);
 | 
					export const UserNotePinings = getRepository(UserNotePining);
 | 
				
			||||||
export const Followings = getCustomRepository(FollowingRepository);
 | 
					export const Followings = getCustomRepository(FollowingRepository);
 | 
				
			||||||
export const FollowRequests = getCustomRepository(FollowRequestRepository);
 | 
					export const FollowRequests = getCustomRepository(FollowRequestRepository);
 | 
				
			||||||
| 
						 | 
					@ -74,3 +79,4 @@ export const ReversiGames = getCustomRepository(ReversiGameRepository);
 | 
				
			||||||
export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository);
 | 
					export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository);
 | 
				
			||||||
export const Logs = getRepository(Log);
 | 
					export const Logs = getRepository(Log);
 | 
				
			||||||
export const Pages = getCustomRepository(PageRepository);
 | 
					export const Pages = getCustomRepository(PageRepository);
 | 
				
			||||||
 | 
					export const PageLikes = getCustomRepository(PageLikeRepository);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import { EntityRepository, Repository } from 'typeorm';
 | 
					import { EntityRepository, Repository } from 'typeorm';
 | 
				
			||||||
import { MessagingMessage } from '../entities/messaging-message';
 | 
					import { MessagingMessage } from '../entities/messaging-message';
 | 
				
			||||||
import { Users, DriveFiles } from '..';
 | 
					import { Users, DriveFiles, UserGroups } from '..';
 | 
				
			||||||
import { ensure } from '../../prelude/ensure';
 | 
					import { ensure } from '../../prelude/ensure';
 | 
				
			||||||
import { types, bool, SchemaType } from '../../misc/schema';
 | 
					import { types, bool, SchemaType } from '../../misc/schema';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,11 +16,13 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
 | 
				
			||||||
		src: MessagingMessage['id'] | MessagingMessage,
 | 
							src: MessagingMessage['id'] | MessagingMessage,
 | 
				
			||||||
		me?: any,
 | 
							me?: any,
 | 
				
			||||||
		options?: {
 | 
							options?: {
 | 
				
			||||||
			populateRecipient: boolean
 | 
								populateRecipient?: boolean,
 | 
				
			||||||
 | 
								populateGroup?: boolean,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	): Promise<PackedMessagingMessage> {
 | 
						): Promise<PackedMessagingMessage> {
 | 
				
			||||||
		const opts = options || {
 | 
							const opts = options || {
 | 
				
			||||||
			populateRecipient: true
 | 
								populateRecipient: true,
 | 
				
			||||||
 | 
								populateGroup: true,
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const message = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
 | 
							const message = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
 | 
				
			||||||
| 
						 | 
					@ -32,10 +34,13 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
 | 
				
			||||||
			userId: message.userId,
 | 
								userId: message.userId,
 | 
				
			||||||
			user: await Users.pack(message.user || message.userId, me),
 | 
								user: await Users.pack(message.user || message.userId, me),
 | 
				
			||||||
			recipientId: message.recipientId,
 | 
								recipientId: message.recipientId,
 | 
				
			||||||
			recipient: opts.populateRecipient ? await Users.pack(message.recipient || message.recipientId, me) : undefined,
 | 
								recipient: message.recipientId && opts.populateRecipient ? await Users.pack(message.recipient || message.recipientId, me) : undefined,
 | 
				
			||||||
 | 
								groupId: message.recipientId,
 | 
				
			||||||
 | 
								group: message.groupId && opts.populateGroup ? await UserGroups.pack(message.group || message.groupId) : undefined,
 | 
				
			||||||
			fileId: message.fileId,
 | 
								fileId: message.fileId,
 | 
				
			||||||
			file: message.fileId ? await DriveFiles.pack(message.fileId) : null,
 | 
								file: message.fileId ? await DriveFiles.pack(message.fileId) : null,
 | 
				
			||||||
			isRead: message.isRead
 | 
								isRead: message.isRead,
 | 
				
			||||||
 | 
								reads: message.reads,
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -83,17 +88,36 @@ export const packedMessagingMessageSchema = {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		recipientId: {
 | 
							recipientId: {
 | 
				
			||||||
			type: types.string,
 | 
								type: types.string,
 | 
				
			||||||
			optional: bool.false, nullable: bool.false,
 | 
								optional: bool.false, nullable: bool.true,
 | 
				
			||||||
			format: 'id',
 | 
								format: 'id',
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		recipient: {
 | 
							recipient: {
 | 
				
			||||||
			type: types.object,
 | 
								type: types.object,
 | 
				
			||||||
			optional: bool.true, nullable: bool.false,
 | 
								optional: bool.true, nullable: bool.true,
 | 
				
			||||||
			ref: 'User'
 | 
								ref: 'User'
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							groupId: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.true,
 | 
				
			||||||
 | 
								format: 'id',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							group: {
 | 
				
			||||||
 | 
								type: types.object,
 | 
				
			||||||
 | 
								optional: bool.true, nullable: bool.true,
 | 
				
			||||||
 | 
								ref: 'UserGroup'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
		isRead: {
 | 
							isRead: {
 | 
				
			||||||
			type: types.boolean,
 | 
								type: types.boolean,
 | 
				
			||||||
			optional: bool.true, nullable: bool.false,
 | 
								optional: bool.true, nullable: bool.false,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							reads: {
 | 
				
			||||||
 | 
								type: types.array,
 | 
				
			||||||
 | 
								optional: bool.true, nullable: bool.false,
 | 
				
			||||||
 | 
								items: {
 | 
				
			||||||
 | 
									type: types.string,
 | 
				
			||||||
 | 
									optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
									format: 'id'
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										26
									
								
								src/models/repositories/page-like.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/models/repositories/page-like.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					import { EntityRepository, Repository } from 'typeorm';
 | 
				
			||||||
 | 
					import { PageLike } from '../entities/page-like';
 | 
				
			||||||
 | 
					import { Pages } from '..';
 | 
				
			||||||
 | 
					import { ensure } from '../../prelude/ensure';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@EntityRepository(PageLike)
 | 
				
			||||||
 | 
					export class PageLikeRepository extends Repository<PageLike> {
 | 
				
			||||||
 | 
						public async pack(
 | 
				
			||||||
 | 
							src: PageLike['id'] | PageLike,
 | 
				
			||||||
 | 
							me?: any
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							const like = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								id: like.id,
 | 
				
			||||||
 | 
								page: await Pages.pack(like.page || like.pageId, me),
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public packMany(
 | 
				
			||||||
 | 
							likes: any[],
 | 
				
			||||||
 | 
							me: any
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							return Promise.all(likes.map(x => this.pack(x, me)));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,24 +1,30 @@
 | 
				
			||||||
import { EntityRepository, Repository } from 'typeorm';
 | 
					import { EntityRepository, Repository } from 'typeorm';
 | 
				
			||||||
import { Page } from '../entities/page';
 | 
					import { Page } from '../entities/page';
 | 
				
			||||||
import { SchemaType, types, bool } from '../../misc/schema';
 | 
					import { SchemaType, types, bool } from '../../misc/schema';
 | 
				
			||||||
import { Users, DriveFiles } from '..';
 | 
					import { Users, DriveFiles, PageLikes } from '..';
 | 
				
			||||||
import { awaitAll } from '../../prelude/await-all';
 | 
					import { awaitAll } from '../../prelude/await-all';
 | 
				
			||||||
import { DriveFile } from '../entities/drive-file';
 | 
					import { DriveFile } from '../entities/drive-file';
 | 
				
			||||||
 | 
					import { User } from '../entities/user';
 | 
				
			||||||
 | 
					import { ensure } from '../../prelude/ensure';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type PackedPage = SchemaType<typeof packedPageSchema>;
 | 
					export type PackedPage = SchemaType<typeof packedPageSchema>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@EntityRepository(Page)
 | 
					@EntityRepository(Page)
 | 
				
			||||||
export class PageRepository extends Repository<Page> {
 | 
					export class PageRepository extends Repository<Page> {
 | 
				
			||||||
	public async pack(
 | 
						public async pack(
 | 
				
			||||||
		src: Page,
 | 
							src: Page['id'] | Page,
 | 
				
			||||||
 | 
							me?: User['id'] | User | null | undefined,
 | 
				
			||||||
	): Promise<PackedPage> {
 | 
						): Promise<PackedPage> {
 | 
				
			||||||
 | 
							const meId = me ? typeof me === 'string' ? me : me.id : null;
 | 
				
			||||||
 | 
							const page = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const attachedFiles: Promise<DriveFile | undefined>[] = [];
 | 
							const attachedFiles: Promise<DriveFile | undefined>[] = [];
 | 
				
			||||||
		const collectFile = (xs: any[]) => {
 | 
							const collectFile = (xs: any[]) => {
 | 
				
			||||||
			for (const x of xs) {
 | 
								for (const x of xs) {
 | 
				
			||||||
				if (x.type === 'image') {
 | 
									if (x.type === 'image') {
 | 
				
			||||||
					attachedFiles.push(DriveFiles.findOne({
 | 
										attachedFiles.push(DriveFiles.findOne({
 | 
				
			||||||
						id: x.fileId,
 | 
											id: x.fileId,
 | 
				
			||||||
						userId: src.userId
 | 
											userId: page.userId
 | 
				
			||||||
					}));
 | 
										}));
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				if (x.children) {
 | 
									if (x.children) {
 | 
				
			||||||
| 
						 | 
					@ -26,7 +32,7 @@ export class PageRepository extends Repository<Page> {
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		collectFile(src.content);
 | 
							collectFile(page.content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 後方互換性のため
 | 
							// 後方互換性のため
 | 
				
			||||||
		let migrated = false;
 | 
							let migrated = false;
 | 
				
			||||||
| 
						 | 
					@ -47,29 +53,31 @@ export class PageRepository extends Repository<Page> {
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
		migrate(src.content);
 | 
							migrate(page.content);
 | 
				
			||||||
		if (migrated) {
 | 
							if (migrated) {
 | 
				
			||||||
			this.update(src.id, {
 | 
								this.update(page.id, {
 | 
				
			||||||
				content: src.content
 | 
									content: page.content
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return await awaitAll({
 | 
							return await awaitAll({
 | 
				
			||||||
			id: src.id,
 | 
								id: page.id,
 | 
				
			||||||
			createdAt: src.createdAt.toISOString(),
 | 
								createdAt: page.createdAt.toISOString(),
 | 
				
			||||||
			updatedAt: src.updatedAt.toISOString(),
 | 
								updatedAt: page.updatedAt.toISOString(),
 | 
				
			||||||
			userId: src.userId,
 | 
								userId: page.userId,
 | 
				
			||||||
			user: Users.pack(src.user || src.userId),
 | 
								user: Users.pack(page.user || page.userId),
 | 
				
			||||||
			content: src.content,
 | 
								content: page.content,
 | 
				
			||||||
			variables: src.variables,
 | 
								variables: page.variables,
 | 
				
			||||||
			title: src.title,
 | 
								title: page.title,
 | 
				
			||||||
			name: src.name,
 | 
								name: page.name,
 | 
				
			||||||
			summary: src.summary,
 | 
								summary: page.summary,
 | 
				
			||||||
			alignCenter: src.alignCenter,
 | 
								alignCenter: page.alignCenter,
 | 
				
			||||||
			font: src.font,
 | 
								font: page.font,
 | 
				
			||||||
			eyeCatchingImageId: src.eyeCatchingImageId,
 | 
								eyeCatchingImageId: page.eyeCatchingImageId,
 | 
				
			||||||
			eyeCatchingImage: src.eyeCatchingImageId ? await DriveFiles.pack(src.eyeCatchingImageId) : null,
 | 
								eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null,
 | 
				
			||||||
			attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles))
 | 
								attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)),
 | 
				
			||||||
 | 
								likedCount: page.likedCount,
 | 
				
			||||||
 | 
								isLiked: meId ? await PageLikes.findOne({ pageId: page.id, userId: meId }).then(x => x != null) : undefined,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										61
									
								
								src/models/repositories/user-group.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/models/repositories/user-group.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					import { EntityRepository, Repository } from 'typeorm';
 | 
				
			||||||
 | 
					import { UserGroup } from '../entities/user-group';
 | 
				
			||||||
 | 
					import { ensure } from '../../prelude/ensure';
 | 
				
			||||||
 | 
					import { UserGroupJoinings } from '..';
 | 
				
			||||||
 | 
					import { bool, types, SchemaType } from '../../misc/schema';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type PackedUserGroup = SchemaType<typeof packedUserGroupSchema>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@EntityRepository(UserGroup)
 | 
				
			||||||
 | 
					export class UserGroupRepository extends Repository<UserGroup> {
 | 
				
			||||||
 | 
						public async pack(
 | 
				
			||||||
 | 
							src: UserGroup['id'] | UserGroup,
 | 
				
			||||||
 | 
						): Promise<PackedUserGroup> {
 | 
				
			||||||
 | 
							const userGroup = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const users = await UserGroupJoinings.find({
 | 
				
			||||||
 | 
								userGroupId: userGroup.id
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								id: userGroup.id,
 | 
				
			||||||
 | 
								createdAt: userGroup.createdAt.toISOString(),
 | 
				
			||||||
 | 
								name: userGroup.name,
 | 
				
			||||||
 | 
								userIds: users.map(x => x.userId)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const packedUserGroupSchema = {
 | 
				
			||||||
 | 
						type: types.object,
 | 
				
			||||||
 | 
						optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
						properties: {
 | 
				
			||||||
 | 
							id: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								format: 'id',
 | 
				
			||||||
 | 
								description: 'The unique identifier for this UserGroup.',
 | 
				
			||||||
 | 
								example: 'xxxxxxxxxx',
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							createdAt: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								format: 'date-time',
 | 
				
			||||||
 | 
								description: 'The date that the UserGroup was created.'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							name: {
 | 
				
			||||||
 | 
								type: types.string,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								description: 'The name of the UserGroup.'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							userIds: {
 | 
				
			||||||
 | 
								type: types.array,
 | 
				
			||||||
 | 
								nullable: bool.false, optional: bool.true,
 | 
				
			||||||
 | 
								items: {
 | 
				
			||||||
 | 
									type: types.string,
 | 
				
			||||||
 | 
									nullable: bool.false, optional: bool.false,
 | 
				
			||||||
 | 
									format: 'id',
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import { EntityRepository, Repository, In } from 'typeorm';
 | 
					import { EntityRepository, Repository, In } from 'typeorm';
 | 
				
			||||||
import { User, ILocalUser, IRemoteUser } from '../entities/user';
 | 
					import { User, ILocalUser, IRemoteUser } from '../entities/user';
 | 
				
			||||||
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..';
 | 
					import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserGroupJoinings } from '..';
 | 
				
			||||||
import { ensure } from '../../prelude/ensure';
 | 
					import { ensure } from '../../prelude/ensure';
 | 
				
			||||||
import config from '../../config';
 | 
					import config from '../../config';
 | 
				
			||||||
import { SchemaType, bool, types } from '../../misc/schema';
 | 
					import { SchemaType, bool, types } from '../../misc/schema';
 | 
				
			||||||
| 
						 | 
					@ -54,6 +54,31 @@ export class UserRepository extends Repository<User> {
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
 | 
				
			||||||
 | 
							const joinings = await UserGroupJoinings.find({ userId: userId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message')
 | 
				
			||||||
 | 
								.where(`message.groupId = :groupId`, { groupId: j.userGroupId })
 | 
				
			||||||
 | 
								.andWhere('message.userId != :userId', { userId: userId })
 | 
				
			||||||
 | 
								.andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
 | 
				
			||||||
 | 
								.andWhere('message.createdAt > :joinedAt', { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない
 | 
				
			||||||
 | 
								.getOne().then(x => x != null)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const [withUser, withGroups] = await Promise.all([
 | 
				
			||||||
 | 
								// TODO: ミュートを考慮
 | 
				
			||||||
 | 
								MessagingMessages.count({
 | 
				
			||||||
 | 
									where: {
 | 
				
			||||||
 | 
										recipientId: userId,
 | 
				
			||||||
 | 
										isRead: false
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									take: 1
 | 
				
			||||||
 | 
								}).then(count => count > 0),
 | 
				
			||||||
 | 
								groupQs
 | 
				
			||||||
 | 
							]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return withUser || withGroups.some(x => x);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public async pack(
 | 
						public async pack(
 | 
				
			||||||
		src: User['id'] | User,
 | 
							src: User['id'] | User,
 | 
				
			||||||
		me?: User['id'] | User | null | undefined,
 | 
							me?: User['id'] | User | null | undefined,
 | 
				
			||||||
| 
						 | 
					@ -151,13 +176,7 @@ export class UserRepository extends Repository<User> {
 | 
				
			||||||
				autoWatch: profile!.autoWatch,
 | 
									autoWatch: profile!.autoWatch,
 | 
				
			||||||
				alwaysMarkNsfw: profile!.alwaysMarkNsfw,
 | 
									alwaysMarkNsfw: profile!.alwaysMarkNsfw,
 | 
				
			||||||
				carefulBot: profile!.carefulBot,
 | 
									carefulBot: profile!.carefulBot,
 | 
				
			||||||
				hasUnreadMessagingMessage: MessagingMessages.count({
 | 
									hasUnreadMessagingMessage: this.getHasUnreadMessagingMessage(user.id),
 | 
				
			||||||
					where: {
 | 
					 | 
				
			||||||
						recipientId: user.id,
 | 
					 | 
				
			||||||
						isRead: false
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
					take: 1
 | 
					 | 
				
			||||||
				}).then(count => count > 0),
 | 
					 | 
				
			||||||
				hasUnreadNotification: Notifications.count({
 | 
									hasUnreadNotification: Notifications.count({
 | 
				
			||||||
					where: {
 | 
										where: {
 | 
				
			||||||
						notifieeId: user.id,
 | 
											notifieeId: user.id,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +1,33 @@
 | 
				
			||||||
import { publishMainStream } from '../../../services/stream';
 | 
					import { publishMainStream, publishGroupMessagingStream } from '../../../services/stream';
 | 
				
			||||||
import { publishMessagingStream } from '../../../services/stream';
 | 
					import { publishMessagingStream } from '../../../services/stream';
 | 
				
			||||||
import { publishMessagingIndexStream } from '../../../services/stream';
 | 
					import { publishMessagingIndexStream } from '../../../services/stream';
 | 
				
			||||||
import { User } from '../../../models/entities/user';
 | 
					import { User } from '../../../models/entities/user';
 | 
				
			||||||
import { MessagingMessage } from '../../../models/entities/messaging-message';
 | 
					import { MessagingMessage } from '../../../models/entities/messaging-message';
 | 
				
			||||||
import { MessagingMessages } from '../../../models';
 | 
					import { MessagingMessages, UserGroupJoinings, Users } from '../../../models';
 | 
				
			||||||
import { In } from 'typeorm';
 | 
					import { In } from 'typeorm';
 | 
				
			||||||
 | 
					import { IdentifiableError } from '../../../misc/identifiable-error';
 | 
				
			||||||
 | 
					import { UserGroup } from '../../../models/entities/user-group';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Mark messages as read
 | 
					 * Mark messages as read
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export default async (
 | 
					export async function readUserMessagingMessage(
 | 
				
			||||||
	userId: User['id'],
 | 
						userId: User['id'],
 | 
				
			||||||
	otherpartyId: User['id'],
 | 
						otherpartyId: User['id'],
 | 
				
			||||||
	messageIds: MessagingMessage['id'][]
 | 
						messageIds: MessagingMessage['id'][]
 | 
				
			||||||
) => {
 | 
					) {
 | 
				
			||||||
	if (messageIds.length === 0) return;
 | 
						if (messageIds.length === 0) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const messages = await MessagingMessages.find({
 | 
				
			||||||
 | 
							id: In(messageIds)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (const message of messages) {
 | 
				
			||||||
 | 
							if (message.recipientId !== userId) {
 | 
				
			||||||
 | 
								throw new IdentifiableError('e140a4bf-49ce-4fb6-b67c-b78dadf6b52f', 'Access denied (user).');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Update documents
 | 
						// Update documents
 | 
				
			||||||
	await MessagingMessages.update({
 | 
						await MessagingMessages.update({
 | 
				
			||||||
		id: In(messageIds),
 | 
							id: In(messageIds),
 | 
				
			||||||
| 
						 | 
					@ -30,14 +42,62 @@ export default async (
 | 
				
			||||||
	publishMessagingStream(otherpartyId, userId, 'read', messageIds);
 | 
						publishMessagingStream(otherpartyId, userId, 'read', messageIds);
 | 
				
			||||||
	publishMessagingIndexStream(userId, 'read', messageIds);
 | 
						publishMessagingIndexStream(userId, 'read', messageIds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Calc count of my unread messages
 | 
						if (!Users.getHasUnreadMessagingMessage(userId)) {
 | 
				
			||||||
	const count = await MessagingMessages.count({
 | 
					 | 
				
			||||||
		recipientId: userId,
 | 
					 | 
				
			||||||
		isRead: false
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (count == 0) {
 | 
					 | 
				
			||||||
		// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
 | 
							// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
 | 
				
			||||||
		publishMainStream(userId, 'readAllMessagingMessages');
 | 
							publishMainStream(userId, 'readAllMessagingMessages');
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Mark messages as read
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function readGroupMessagingMessage(
 | 
				
			||||||
 | 
						userId: User['id'],
 | 
				
			||||||
 | 
						groupId: UserGroup['id'],
 | 
				
			||||||
 | 
						messageIds: MessagingMessage['id'][]
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						if (messageIds.length === 0) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check joined
 | 
				
			||||||
 | 
						const joining = await UserGroupJoinings.findOne({
 | 
				
			||||||
 | 
							userId: userId,
 | 
				
			||||||
 | 
							userGroupId: groupId
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (joining == null) {
 | 
				
			||||||
 | 
							throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (group).');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const messages = await MessagingMessages.find({
 | 
				
			||||||
 | 
							id: In(messageIds)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const reads = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (const message of messages) {
 | 
				
			||||||
 | 
							if (message.userId === userId) continue;
 | 
				
			||||||
 | 
							if (message.reads.includes(userId)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Update document
 | 
				
			||||||
 | 
							await MessagingMessages.createQueryBuilder().update()
 | 
				
			||||||
 | 
								.set({
 | 
				
			||||||
 | 
									reads: (() => `array_append("reads", '${joining.userId}')`) as any
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								.where('id = :id', { id: message.id })
 | 
				
			||||||
 | 
								.execute();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							reads.push(message.id);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Publish event
 | 
				
			||||||
 | 
						publishGroupMessagingStream(groupId, 'read', {
 | 
				
			||||||
 | 
							ids: reads,
 | 
				
			||||||
 | 
							userId: userId
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						publishMessagingIndexStream(userId, 'read', reads);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!Users.getHasUnreadMessagingMessage(userId)) {
 | 
				
			||||||
 | 
							// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
 | 
				
			||||||
 | 
							publishMainStream(userId, 'readAllMessagingMessages');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										45
									
								
								src/server/api/endpoints/i/page-likes.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/server/api/endpoints/i/page-likes.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,45 @@
 | 
				
			||||||
 | 
					import $ from 'cafy';
 | 
				
			||||||
 | 
					import { ID } from '../../../../misc/cafy-id';
 | 
				
			||||||
 | 
					import define from '../../define';
 | 
				
			||||||
 | 
					import { PageLikes } from '../../../../models';
 | 
				
			||||||
 | 
					import { makePaginationQuery } from '../../common/make-pagination-query';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						desc: {
 | 
				
			||||||
 | 
							'ja-JP': '「いいね」したページ一覧を取得します。',
 | 
				
			||||||
 | 
							'en-US': 'Get liked pages'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags: ['account', 'pages'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kind: 'read:page-likes',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params: {
 | 
				
			||||||
 | 
							limit: {
 | 
				
			||||||
 | 
								validator: $.optional.num.range(1, 100),
 | 
				
			||||||
 | 
								default: 10
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sinceId: {
 | 
				
			||||||
 | 
								validator: $.optional.type(ID),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							untilId: {
 | 
				
			||||||
 | 
								validator: $.optional.type(ID),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, async (ps, user) => {
 | 
				
			||||||
 | 
						const query = makePaginationQuery(PageLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId)
 | 
				
			||||||
 | 
							.andWhere(`like.userId = :meId`, { meId: user.id })
 | 
				
			||||||
 | 
							.leftJoinAndSelect('like.page', 'page');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const likes = await query
 | 
				
			||||||
 | 
							.take(ps.limit!)
 | 
				
			||||||
 | 
							.getMany();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return await PageLikes.packMany(likes, user);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,13 @@
 | 
				
			||||||
import $ from 'cafy';
 | 
					import $ from 'cafy';
 | 
				
			||||||
import define from '../../define';
 | 
					import define from '../../define';
 | 
				
			||||||
import { MessagingMessage } from '../../../../models/entities/messaging-message';
 | 
					import { MessagingMessage } from '../../../../models/entities/messaging-message';
 | 
				
			||||||
import { MessagingMessages, Mutings } from '../../../../models';
 | 
					import { MessagingMessages, Mutings, UserGroupJoinings } from '../../../../models';
 | 
				
			||||||
import { Brackets } from 'typeorm';
 | 
					import { Brackets } from 'typeorm';
 | 
				
			||||||
import { types, bool } from '../../../../misc/schema';
 | 
					import { types, bool } from '../../../../misc/schema';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const meta = {
 | 
					export const meta = {
 | 
				
			||||||
	desc: {
 | 
						desc: {
 | 
				
			||||||
		'ja-JP': 'Messagingの履歴を取得します。',
 | 
							'ja-JP': 'トークの履歴を取得します。',
 | 
				
			||||||
		'en-US': 'Show messaging history.'
 | 
							'en-US': 'Show messaging history.'
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,11 @@ export const meta = {
 | 
				
			||||||
		limit: {
 | 
							limit: {
 | 
				
			||||||
			validator: $.optional.num.range(1, 100),
 | 
								validator: $.optional.num.range(1, 100),
 | 
				
			||||||
			default: 10
 | 
								default: 10
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							group: {
 | 
				
			||||||
 | 
								validator: $.optional.bool,
 | 
				
			||||||
 | 
								default: false
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,17 +45,36 @@ export default define(meta, async (ps, user) => {
 | 
				
			||||||
		muterId: user.id,
 | 
							muterId: user.id,
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const groups = ps.group ? await UserGroupJoinings.find({
 | 
				
			||||||
 | 
							userId: user.id,
 | 
				
			||||||
 | 
						}).then(xs => xs.map(x => x.userGroupId)) : [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ps.group && groups.length === 0) {
 | 
				
			||||||
 | 
							return [];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const history: MessagingMessage[] = [];
 | 
						const history: MessagingMessage[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (let i = 0; i < ps.limit!; i++) {
 | 
						for (let i = 0; i < ps.limit!; i++) {
 | 
				
			||||||
		const found = history.map(m => (m.userId === user.id) ? m.recipientId : m.userId);
 | 
							const found = ps.group
 | 
				
			||||||
 | 
								? history.map(m => m.groupId!)
 | 
				
			||||||
 | 
								: history.map(m => (m.userId === user.id) ? m.recipientId! : m.userId!);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const query = MessagingMessages.createQueryBuilder('message')
 | 
							const query = MessagingMessages.createQueryBuilder('message')
 | 
				
			||||||
			.where(new Brackets(qb => { qb
 | 
								.orderBy('message.createdAt', 'DESC');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (ps.group) {
 | 
				
			||||||
 | 
								query.where(`message.groupId IN (:...groups)`, { groups: groups });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (found.length > 0) {
 | 
				
			||||||
 | 
									query.andWhere(`message.groupId NOT IN (:...found)`, { found: found });
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								query.where(new Brackets(qb => { qb
 | 
				
			||||||
				.where(`message.userId = :userId`, { userId: user.id })
 | 
									.where(`message.userId = :userId`, { userId: user.id })
 | 
				
			||||||
				.orWhere(`message.recipientId = :userId`, { userId: user.id });
 | 
									.orWhere(`message.recipientId = :userId`, { userId: user.id });
 | 
				
			||||||
			}))
 | 
								}));
 | 
				
			||||||
			.orderBy('message.createdAt', 'DESC');
 | 
								query.andWhere(`message.groupId IS NULL`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (found.length > 0) {
 | 
								if (found.length > 0) {
 | 
				
			||||||
				query.andWhere(`message.userId NOT IN (:...found)`, { found: found });
 | 
									query.andWhere(`message.userId NOT IN (:...found)`, { found: found });
 | 
				
			||||||
| 
						 | 
					@ -61,6 +85,7 @@ export default define(meta, async (ps, user) => {
 | 
				
			||||||
				query.andWhere(`message.userId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) });
 | 
									query.andWhere(`message.userId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) });
 | 
				
			||||||
				query.andWhere(`message.recipientId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) });
 | 
									query.andWhere(`message.recipientId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) });
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const message = await query.getOne();
 | 
							const message = await query.getOne();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,17 @@
 | 
				
			||||||
import $ from 'cafy';
 | 
					import $ from 'cafy';
 | 
				
			||||||
import { ID } from '../../../../misc/cafy-id';
 | 
					import { ID } from '../../../../misc/cafy-id';
 | 
				
			||||||
import read from '../../common/read-messaging-message';
 | 
					 | 
				
			||||||
import define from '../../define';
 | 
					import define from '../../define';
 | 
				
			||||||
import { ApiError } from '../../error';
 | 
					import { ApiError } from '../../error';
 | 
				
			||||||
import { getUser } from '../../common/getters';
 | 
					import { getUser } from '../../common/getters';
 | 
				
			||||||
import { MessagingMessages } from '../../../../models';
 | 
					import { MessagingMessages, UserGroups, UserGroupJoinings } from '../../../../models';
 | 
				
			||||||
import { makePaginationQuery } from '../../common/make-pagination-query';
 | 
					import { makePaginationQuery } from '../../common/make-pagination-query';
 | 
				
			||||||
import { types, bool } from '../../../../misc/schema';
 | 
					import { types, bool } from '../../../../misc/schema';
 | 
				
			||||||
 | 
					import { Brackets } from 'typeorm';
 | 
				
			||||||
 | 
					import { readUserMessagingMessage, readGroupMessagingMessage } from '../../common/read-messaging-message';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const meta = {
 | 
					export const meta = {
 | 
				
			||||||
	desc: {
 | 
						desc: {
 | 
				
			||||||
		'ja-JP': '指定したユーザーとのMessagingのメッセージ一覧を取得します。',
 | 
							'ja-JP': 'トークメッセージ一覧を取得します。',
 | 
				
			||||||
		'en-US': 'Get messages of messaging.'
 | 
							'en-US': 'Get messages of messaging.'
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,13 +23,21 @@ export const meta = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	params: {
 | 
						params: {
 | 
				
			||||||
		userId: {
 | 
							userId: {
 | 
				
			||||||
			validator: $.type(ID),
 | 
								validator: $.optional.type(ID),
 | 
				
			||||||
			desc: {
 | 
								desc: {
 | 
				
			||||||
				'ja-JP': '対象のユーザーのID',
 | 
									'ja-JP': '対象のユーザーのID',
 | 
				
			||||||
				'en-US': 'Target user ID'
 | 
									'en-US': 'Target user ID'
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							groupId: {
 | 
				
			||||||
 | 
								validator: $.optional.type(ID),
 | 
				
			||||||
 | 
								desc: {
 | 
				
			||||||
 | 
									'ja-JP': '対象のグループのID',
 | 
				
			||||||
 | 
									'en-US': 'Target group ID'
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		limit: {
 | 
							limit: {
 | 
				
			||||||
			validator: $.optional.num.range(1, 100),
 | 
								validator: $.optional.num.range(1, 100),
 | 
				
			||||||
			default: 10
 | 
								default: 10
 | 
				
			||||||
| 
						 | 
					@ -64,27 +73,85 @@ export const meta = {
 | 
				
			||||||
			code: 'NO_SUCH_USER',
 | 
								code: 'NO_SUCH_USER',
 | 
				
			||||||
			id: '11795c64-40ea-4198-b06e-3c873ed9039d'
 | 
								id: '11795c64-40ea-4198-b06e-3c873ed9039d'
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							noSuchGroup: {
 | 
				
			||||||
 | 
								message: 'No such group.',
 | 
				
			||||||
 | 
								code: 'NO_SUCH_GROUP',
 | 
				
			||||||
 | 
								id: 'c4d9f88c-9270-4632-b032-6ed8cee36f7f'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							groupAccessDenied: {
 | 
				
			||||||
 | 
								message: 'You can not read messages of groups that you have not joined.',
 | 
				
			||||||
 | 
								code: 'GROUP_ACCESS_DENIED',
 | 
				
			||||||
 | 
								id: 'a053a8dd-a491-4718-8f87-50775aad9284'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps, user) => {
 | 
					export default define(meta, async (ps, user) => {
 | 
				
			||||||
	// Fetch recipient
 | 
						if (ps.userId != null) {
 | 
				
			||||||
 | 
							// Fetch recipient (user)
 | 
				
			||||||
		const recipient = await getUser(ps.userId).catch(e => {
 | 
							const recipient = await getUser(ps.userId).catch(e => {
 | 
				
			||||||
			if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
 | 
								if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
 | 
				
			||||||
			throw e;
 | 
								throw e;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId)
 | 
							const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId)
 | 
				
			||||||
		.andWhere(`(message.userId = :meId AND message.recipientId = :recipientId) OR (message.userId = :recipientId AND message.recipientId = :meId)`, { meId: user.id, recipientId: recipient.id });
 | 
								.andWhere(new Brackets(qb => { qb
 | 
				
			||||||
 | 
									.where(new Brackets(qb => { qb
 | 
				
			||||||
 | 
										.where('message.userId = :meId')
 | 
				
			||||||
 | 
										.andWhere('message.recipientId = :recipientId');
 | 
				
			||||||
 | 
									}))
 | 
				
			||||||
 | 
									.orWhere(new Brackets(qb => { qb
 | 
				
			||||||
 | 
										.where('message.userId = :recipientId')
 | 
				
			||||||
 | 
										.andWhere('message.recipientId = :meId');
 | 
				
			||||||
 | 
									}));
 | 
				
			||||||
 | 
								}))
 | 
				
			||||||
 | 
								.setParameter('meId', user.id)
 | 
				
			||||||
 | 
								.setParameter('recipientId', recipient.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const messages = await query.getMany();
 | 
							const messages = await query.take(ps.limit!).getMany();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Mark all as read
 | 
							// Mark all as read
 | 
				
			||||||
		if (ps.markAsRead) {
 | 
							if (ps.markAsRead) {
 | 
				
			||||||
		read(user.id, recipient.id, messages.map(x => x.id));
 | 
								readUserMessagingMessage(user.id, recipient.id, messages.map(x => x.id));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, {
 | 
							return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, {
 | 
				
			||||||
			populateRecipient: false
 | 
								populateRecipient: false
 | 
				
			||||||
		})));
 | 
							})));
 | 
				
			||||||
 | 
						} else if (ps.groupId != null) {
 | 
				
			||||||
 | 
							// Fetch recipient (group)
 | 
				
			||||||
 | 
							const recipientGroup = await UserGroups.findOne(ps.groupId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (recipientGroup == null) {
 | 
				
			||||||
 | 
								throw new ApiError(meta.errors.noSuchGroup);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// check joined
 | 
				
			||||||
 | 
							const joining = await UserGroupJoinings.findOne({
 | 
				
			||||||
 | 
								userId: user.id,
 | 
				
			||||||
 | 
								userGroupId: recipientGroup.id
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (joining == null) {
 | 
				
			||||||
 | 
								throw new ApiError(meta.errors.groupAccessDenied);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId)
 | 
				
			||||||
 | 
								.andWhere(`message.groupId = :groupId`, { groupId: recipientGroup.id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const messages = await query.take(ps.limit!).getMany();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Mark all as read
 | 
				
			||||||
 | 
							if (ps.markAsRead) {
 | 
				
			||||||
 | 
								readGroupMessagingMessage(user.id, recipientGroup.id, messages.map(x => x.id));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, {
 | 
				
			||||||
 | 
								populateGroup: false
 | 
				
			||||||
 | 
							})));
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							throw new Error();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,19 +1,22 @@
 | 
				
			||||||
import $ from 'cafy';
 | 
					import $ from 'cafy';
 | 
				
			||||||
import { ID } from '../../../../../misc/cafy-id';
 | 
					import { ID } from '../../../../../misc/cafy-id';
 | 
				
			||||||
import { publishMainStream } from '../../../../../services/stream';
 | 
					import { publishMainStream, publishGroupMessagingStream } from '../../../../../services/stream';
 | 
				
			||||||
import { publishMessagingStream, publishMessagingIndexStream } from '../../../../../services/stream';
 | 
					import { publishMessagingStream, publishMessagingIndexStream } from '../../../../../services/stream';
 | 
				
			||||||
import pushSw from '../../../../../services/push-notification';
 | 
					import pushSw from '../../../../../services/push-notification';
 | 
				
			||||||
import define from '../../../define';
 | 
					import define from '../../../define';
 | 
				
			||||||
import { ApiError } from '../../../error';
 | 
					import { ApiError } from '../../../error';
 | 
				
			||||||
import { getUser } from '../../../common/getters';
 | 
					import { getUser } from '../../../common/getters';
 | 
				
			||||||
import { MessagingMessages, DriveFiles, Mutings } from '../../../../../models';
 | 
					import { MessagingMessages, DriveFiles, Mutings, UserGroups, UserGroupJoinings } from '../../../../../models';
 | 
				
			||||||
import { MessagingMessage } from '../../../../../models/entities/messaging-message';
 | 
					import { MessagingMessage } from '../../../../../models/entities/messaging-message';
 | 
				
			||||||
import { genId } from '../../../../../misc/gen-id';
 | 
					import { genId } from '../../../../../misc/gen-id';
 | 
				
			||||||
import { types, bool } from '../../../../../misc/schema';
 | 
					import { types, bool } from '../../../../../misc/schema';
 | 
				
			||||||
 | 
					import { User } from '../../../../../models/entities/user';
 | 
				
			||||||
 | 
					import { UserGroup } from '../../../../../models/entities/user-group';
 | 
				
			||||||
 | 
					import { Not } from 'typeorm';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const meta = {
 | 
					export const meta = {
 | 
				
			||||||
	desc: {
 | 
						desc: {
 | 
				
			||||||
		'ja-JP': '指定したユーザーへMessagingのメッセージを送信します。',
 | 
							'ja-JP': 'トークメッセージを送信します。',
 | 
				
			||||||
		'en-US': 'Create a message of messaging.'
 | 
							'en-US': 'Create a message of messaging.'
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,13 +28,21 @@ export const meta = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	params: {
 | 
						params: {
 | 
				
			||||||
		userId: {
 | 
							userId: {
 | 
				
			||||||
			validator: $.type(ID),
 | 
								validator: $.optional.type(ID),
 | 
				
			||||||
			desc: {
 | 
								desc: {
 | 
				
			||||||
				'ja-JP': '対象のユーザーのID',
 | 
									'ja-JP': '対象のユーザーのID',
 | 
				
			||||||
				'en-US': 'Target user ID'
 | 
									'en-US': 'Target user ID'
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							groupId: {
 | 
				
			||||||
 | 
								validator: $.optional.type(ID),
 | 
				
			||||||
 | 
								desc: {
 | 
				
			||||||
 | 
									'ja-JP': '対象のグループのID',
 | 
				
			||||||
 | 
									'en-US': 'Target group ID'
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		text: {
 | 
							text: {
 | 
				
			||||||
			validator: $.optional.str.pipe(MessagingMessages.isValidText)
 | 
								validator: $.optional.str.pipe(MessagingMessages.isValidText)
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
| 
						 | 
					@ -60,6 +71,18 @@ export const meta = {
 | 
				
			||||||
			id: '11795c64-40ea-4198-b06e-3c873ed9039d'
 | 
								id: '11795c64-40ea-4198-b06e-3c873ed9039d'
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							noSuchGroup: {
 | 
				
			||||||
 | 
								message: 'No such group.',
 | 
				
			||||||
 | 
								code: 'NO_SUCH_GROUP',
 | 
				
			||||||
 | 
								id: 'c94e2a5d-06aa-4914-8fa6-6a42e73d6537'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							groupAccessDenied: {
 | 
				
			||||||
 | 
								message: 'You can not send messages to groups that you have not joined.',
 | 
				
			||||||
 | 
								code: 'GROUP_ACCESS_DENIED',
 | 
				
			||||||
 | 
								id: 'd96b3cca-5ad1-438b-ad8b-02f931308fbd'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		noSuchFile: {
 | 
							noSuchFile: {
 | 
				
			||||||
			message: 'No such file.',
 | 
								message: 'No such file.',
 | 
				
			||||||
			code: 'NO_SUCH_FILE',
 | 
								code: 'NO_SUCH_FILE',
 | 
				
			||||||
| 
						 | 
					@ -75,16 +98,38 @@ export const meta = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default define(meta, async (ps, user) => {
 | 
					export default define(meta, async (ps, user) => {
 | 
				
			||||||
 | 
						let recipientUser: User | undefined;
 | 
				
			||||||
 | 
						let recipientGroup: UserGroup | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ps.userId != null) {
 | 
				
			||||||
		// Myself
 | 
							// Myself
 | 
				
			||||||
		if (ps.userId === user.id) {
 | 
							if (ps.userId === user.id) {
 | 
				
			||||||
			throw new ApiError(meta.errors.recipientIsYourself);
 | 
								throw new ApiError(meta.errors.recipientIsYourself);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Fetch recipient
 | 
							// Fetch recipient (user)
 | 
				
			||||||
	const recipient = await getUser(ps.userId).catch(e => {
 | 
							recipientUser = await getUser(ps.userId).catch(e => {
 | 
				
			||||||
			if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
 | 
								if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
 | 
				
			||||||
			throw e;
 | 
								throw e;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
						} else if (ps.groupId != null) {
 | 
				
			||||||
 | 
							// Fetch recipient (group)
 | 
				
			||||||
 | 
							recipientGroup = await UserGroups.findOne(ps.groupId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (recipientGroup == null) {
 | 
				
			||||||
 | 
								throw new ApiError(meta.errors.noSuchGroup);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// check joined
 | 
				
			||||||
 | 
							const joining = await UserGroupJoinings.findOne({
 | 
				
			||||||
 | 
								userId: user.id,
 | 
				
			||||||
 | 
								userGroupId: recipientGroup.id
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (joining == null) {
 | 
				
			||||||
 | 
								throw new ApiError(meta.errors.groupAccessDenied);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	let file = null;
 | 
						let file = null;
 | 
				
			||||||
	if (ps.fileId != null) {
 | 
						if (ps.fileId != null) {
 | 
				
			||||||
| 
						 | 
					@ -107,32 +152,49 @@ export default define(meta, async (ps, user) => {
 | 
				
			||||||
		id: genId(),
 | 
							id: genId(),
 | 
				
			||||||
		createdAt: new Date(),
 | 
							createdAt: new Date(),
 | 
				
			||||||
		fileId: file ? file.id : null,
 | 
							fileId: file ? file.id : null,
 | 
				
			||||||
		recipientId: recipient.id,
 | 
							recipientId: recipientUser ? recipientUser.id : null,
 | 
				
			||||||
 | 
							groupId: recipientGroup ? recipientGroup.id : null,
 | 
				
			||||||
		text: ps.text ? ps.text.trim() : null,
 | 
							text: ps.text ? ps.text.trim() : null,
 | 
				
			||||||
		userId: user.id,
 | 
							userId: user.id,
 | 
				
			||||||
		isRead: false
 | 
							isRead: false,
 | 
				
			||||||
 | 
							reads: [] as any[]
 | 
				
			||||||
	} as MessagingMessage);
 | 
						} as MessagingMessage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const messageObj = await MessagingMessages.pack(message);
 | 
						const messageObj = await MessagingMessages.pack(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (recipientUser) {
 | 
				
			||||||
		// 自分のストリーム
 | 
							// 自分のストリーム
 | 
				
			||||||
	publishMessagingStream(message.userId, message.recipientId, 'message', messageObj);
 | 
							publishMessagingStream(message.userId, recipientUser.id, 'message', messageObj);
 | 
				
			||||||
		publishMessagingIndexStream(message.userId, 'message', messageObj);
 | 
							publishMessagingIndexStream(message.userId, 'message', messageObj);
 | 
				
			||||||
		publishMainStream(message.userId, 'messagingMessage', messageObj);
 | 
							publishMainStream(message.userId, 'messagingMessage', messageObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 相手のストリーム
 | 
							// 相手のストリーム
 | 
				
			||||||
	publishMessagingStream(message.recipientId, message.userId, 'message', messageObj);
 | 
							publishMessagingStream(recipientUser.id, message.userId, 'message', messageObj);
 | 
				
			||||||
	publishMessagingIndexStream(message.recipientId, 'message', messageObj);
 | 
							publishMessagingIndexStream(recipientUser.id, 'message', messageObj);
 | 
				
			||||||
	publishMainStream(message.recipientId, 'messagingMessage', messageObj);
 | 
							publishMainStream(recipientUser.id, 'messagingMessage', messageObj);
 | 
				
			||||||
 | 
						} else if (recipientGroup) {
 | 
				
			||||||
 | 
							// グループのストリーム
 | 
				
			||||||
 | 
							publishGroupMessagingStream(recipientGroup.id, 'message', messageObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// メンバーのストリーム
 | 
				
			||||||
 | 
							const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id });
 | 
				
			||||||
 | 
							for (const joining of joinings) {
 | 
				
			||||||
 | 
								publishMessagingIndexStream(joining.userId, 'message', messageObj);
 | 
				
			||||||
 | 
								publishMainStream(joining.userId, 'messagingMessage', messageObj);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
 | 
						// 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
 | 
				
			||||||
	setTimeout(async () => {
 | 
						setTimeout(async () => {
 | 
				
			||||||
		const freshMessage = await MessagingMessages.findOne({ id: message.id });
 | 
							const freshMessage = await MessagingMessages.findOne(message.id);
 | 
				
			||||||
		if (freshMessage == null) return; // メッセージが削除されている場合もある
 | 
							if (freshMessage == null) return; // メッセージが削除されている場合もある
 | 
				
			||||||
		if (!freshMessage.isRead) {
 | 
					
 | 
				
			||||||
 | 
							if (recipientUser) {
 | 
				
			||||||
 | 
								if (freshMessage.isRead) return; // 既読
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			//#region ただしミュートされているなら発行しない
 | 
								//#region ただしミュートされているなら発行しない
 | 
				
			||||||
			const mute = await Mutings.find({
 | 
								const mute = await Mutings.find({
 | 
				
			||||||
				muterId: recipient.id,
 | 
									muterId: recipientUser.id,
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
			const mutedUserIds = mute.map(m => m.muteeId.toString());
 | 
								const mutedUserIds = mute.map(m => m.muteeId.toString());
 | 
				
			||||||
			if (mutedUserIds.indexOf(user.id) != -1) {
 | 
								if (mutedUserIds.indexOf(user.id) != -1) {
 | 
				
			||||||
| 
						 | 
					@ -140,8 +202,15 @@ export default define(meta, async (ps, user) => {
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			//#endregion
 | 
								//#endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			publishMainStream(message.recipientId, 'unreadMessagingMessage', messageObj);
 | 
								publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj);
 | 
				
			||||||
			pushSw(message.recipientId, 'unreadMessagingMessage', messageObj);
 | 
								pushSw(recipientUser.id, 'unreadMessagingMessage', messageObj);
 | 
				
			||||||
 | 
							} else if (recipientGroup) {
 | 
				
			||||||
 | 
								const joinings = await UserGroupJoinings.find({ userGroupId: recipientGroup.id, userId: Not(user.id) });
 | 
				
			||||||
 | 
								for (const joining of joinings) {
 | 
				
			||||||
 | 
									if (freshMessage.reads.includes(joining.userId)) return; // 既読
 | 
				
			||||||
 | 
									publishMainStream(joining.userId, 'unreadMessagingMessage', messageObj);
 | 
				
			||||||
 | 
									pushSw(joining.userId, 'unreadMessagingMessage', messageObj);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}, 2000);
 | 
						}, 2000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
import $ from 'cafy';
 | 
					import $ from 'cafy';
 | 
				
			||||||
import { ID } from '../../../../../misc/cafy-id';
 | 
					import { ID } from '../../../../../misc/cafy-id';
 | 
				
			||||||
import define from '../../../define';
 | 
					import define from '../../../define';
 | 
				
			||||||
import { publishMessagingStream } from '../../../../../services/stream';
 | 
					import { publishMessagingStream, publishGroupMessagingStream } from '../../../../../services/stream';
 | 
				
			||||||
import * as ms from 'ms';
 | 
					import * as ms from 'ms';
 | 
				
			||||||
import { ApiError } from '../../../error';
 | 
					import { ApiError } from '../../../error';
 | 
				
			||||||
import { MessagingMessages } from '../../../../../models';
 | 
					import { MessagingMessages } from '../../../../../models';
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ export const meta = {
 | 
				
			||||||
	stability: 'stable',
 | 
						stability: 'stable',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	desc: {
 | 
						desc: {
 | 
				
			||||||
		'ja-JP': '指定したメッセージを削除します。',
 | 
							'ja-JP': '指定したトークメッセージを削除します。',
 | 
				
			||||||
		'en-US': 'Delete a message.'
 | 
							'en-US': 'Delete a message.'
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,6 +57,10 @@ export default define(meta, async (ps, user) => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	await MessagingMessages.delete(message.id);
 | 
						await MessagingMessages.delete(message.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (message.recipientId) {
 | 
				
			||||||
		publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id);
 | 
							publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id);
 | 
				
			||||||
		publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id);
 | 
							publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id);
 | 
				
			||||||
 | 
						} else if (message.groupId) {
 | 
				
			||||||
 | 
							publishGroupMessagingStream(message.groupId, 'deleted', message.id);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,13 @@
 | 
				
			||||||
import $ from 'cafy';
 | 
					import $ from 'cafy';
 | 
				
			||||||
import { ID } from '../../../../../misc/cafy-id';
 | 
					import { ID } from '../../../../../misc/cafy-id';
 | 
				
			||||||
import read from '../../../common/read-messaging-message';
 | 
					 | 
				
			||||||
import define from '../../../define';
 | 
					import define from '../../../define';
 | 
				
			||||||
import { ApiError } from '../../../error';
 | 
					import { ApiError } from '../../../error';
 | 
				
			||||||
import { MessagingMessages } from '../../../../../models';
 | 
					import { MessagingMessages } from '../../../../../models';
 | 
				
			||||||
 | 
					import { readUserMessagingMessage, readGroupMessagingMessage } from '../../../common/read-messaging-message';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const meta = {
 | 
					export const meta = {
 | 
				
			||||||
	desc: {
 | 
						desc: {
 | 
				
			||||||
		'ja-JP': '指定した自分宛てのメッセージを既読にします。',
 | 
							'ja-JP': '指定した自分宛てのトークメッセージを既読にします。',
 | 
				
			||||||
		'en-US': 'Mark as read a message of messaging.'
 | 
							'en-US': 'Mark as read a message of messaging.'
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -39,12 +39,21 @@ export const meta = {
 | 
				
			||||||
export default define(meta, async (ps, user) => {
 | 
					export default define(meta, async (ps, user) => {
 | 
				
			||||||
	const message = await MessagingMessages.findOne({
 | 
						const message = await MessagingMessages.findOne({
 | 
				
			||||||
		id: ps.messageId,
 | 
							id: ps.messageId,
 | 
				
			||||||
		recipientId: user.id
 | 
					 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (message == null) {
 | 
						if (message == null) {
 | 
				
			||||||
		throw new ApiError(meta.errors.noSuchMessage);
 | 
							throw new ApiError(meta.errors.noSuchMessage);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	read(user.id, message.userId, [message.id]);
 | 
						if (message.recipientId) {
 | 
				
			||||||
 | 
							await readUserMessagingMessage(user.id, message.recipientId, [message.id]).catch(e => {
 | 
				
			||||||
 | 
								if (e.id === 'e140a4bf-49ce-4fb6-b67c-b78dadf6b52f') throw new ApiError(meta.errors.noSuchMessage);
 | 
				
			||||||
 | 
								throw e;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						} else if (message.groupId) {
 | 
				
			||||||
 | 
							await readGroupMessagingMessage(user.id, message.groupId, [message.id]).catch(e => {
 | 
				
			||||||
 | 
								if (e.id === '930a270c-714a-46b2-b776-ad27276dc569') throw new ApiError(meta.errors.noSuchMessage);
 | 
				
			||||||
 | 
								throw e;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ export const meta = {
 | 
				
			||||||
		'en-US': 'Favorite a note.'
 | 
							'en-US': 'Favorite a note.'
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tags: ['favorites'],
 | 
						tags: ['notes', 'favorites'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	requireCredential: true,
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ export const meta = {
 | 
				
			||||||
		'en-US': 'Unfavorite a note.'
 | 
							'en-US': 'Unfavorite a note.'
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tags: ['favorites'],
 | 
						tags: ['notes', 'favorites'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	requireCredential: true,
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/server/api/endpoints/pages/like.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/server/api/endpoints/pages/like.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,79 @@
 | 
				
			||||||
 | 
					import $ from 'cafy';
 | 
				
			||||||
 | 
					import { ID } from '../../../../misc/cafy-id';
 | 
				
			||||||
 | 
					import define from '../../define';
 | 
				
			||||||
 | 
					import { ApiError } from '../../error';
 | 
				
			||||||
 | 
					import { Pages, PageLikes } from '../../../../models';
 | 
				
			||||||
 | 
					import { genId } from '../../../../misc/gen-id';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						desc: {
 | 
				
			||||||
 | 
							'ja-JP': '指定したページを「いいね」します。',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags: ['pages'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kind: 'write:page-likes',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params: {
 | 
				
			||||||
 | 
							pageId: {
 | 
				
			||||||
 | 
								validator: $.type(ID),
 | 
				
			||||||
 | 
								desc: {
 | 
				
			||||||
 | 
									'ja-JP': '対象のページのID',
 | 
				
			||||||
 | 
									'en-US': 'Target page ID.'
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errors: {
 | 
				
			||||||
 | 
							noSuchPage: {
 | 
				
			||||||
 | 
								message: 'No such page.',
 | 
				
			||||||
 | 
								code: 'NO_SUCH_PAGE',
 | 
				
			||||||
 | 
								id: 'cc98a8a2-0dc3-4123-b198-62c71df18ed3'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							yourPage: {
 | 
				
			||||||
 | 
								message: 'You cannot like your page.',
 | 
				
			||||||
 | 
								code: 'YOUR_PAGE',
 | 
				
			||||||
 | 
								id: '28800466-e6db-40f2-8fae-bf9e82aa92b8'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							alreadyLiked: {
 | 
				
			||||||
 | 
								message: 'The page has already been liked.',
 | 
				
			||||||
 | 
								code: 'ALREADY_LIKED',
 | 
				
			||||||
 | 
								id: 'cc98a8a2-0dc3-4123-b198-62c71df18ed3'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, async (ps, user) => {
 | 
				
			||||||
 | 
						const page = await Pages.findOne(ps.pageId);
 | 
				
			||||||
 | 
						if (page == null) {
 | 
				
			||||||
 | 
							throw new ApiError(meta.errors.noSuchPage);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (page.userId === user.id) {
 | 
				
			||||||
 | 
							throw new ApiError(meta.errors.yourPage);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// if already liked
 | 
				
			||||||
 | 
						const exist = await PageLikes.findOne({
 | 
				
			||||||
 | 
							pageId: page.id,
 | 
				
			||||||
 | 
							userId: user.id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (exist != null) {
 | 
				
			||||||
 | 
							throw new ApiError(meta.errors.alreadyLiked);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create like
 | 
				
			||||||
 | 
						await PageLikes.save({
 | 
				
			||||||
 | 
							id: genId(),
 | 
				
			||||||
 | 
							createdAt: new Date(),
 | 
				
			||||||
 | 
							pageId: page.id,
 | 
				
			||||||
 | 
							userId: user.id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Pages.increment({ id: page.id }, 'likedCount', 1);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -70,5 +70,5 @@ export default define(meta, async (ps, user) => {
 | 
				
			||||||
		throw new ApiError(meta.errors.noSuchPage);
 | 
							throw new ApiError(meta.errors.noSuchPage);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return await Pages.pack(page);
 | 
						return await Pages.pack(page, user);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										62
									
								
								src/server/api/endpoints/pages/unlike.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/server/api/endpoints/pages/unlike.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,62 @@
 | 
				
			||||||
 | 
					import $ from 'cafy';
 | 
				
			||||||
 | 
					import { ID } from '../../../../misc/cafy-id';
 | 
				
			||||||
 | 
					import define from '../../define';
 | 
				
			||||||
 | 
					import { ApiError } from '../../error';
 | 
				
			||||||
 | 
					import { Pages, PageLikes } from '../../../../models';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						desc: {
 | 
				
			||||||
 | 
							'ja-JP': '指定したページの「いいね」を解除します。',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags: ['pages'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kind: 'write:page-likes',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params: {
 | 
				
			||||||
 | 
							pageId: {
 | 
				
			||||||
 | 
								validator: $.type(ID),
 | 
				
			||||||
 | 
								desc: {
 | 
				
			||||||
 | 
									'ja-JP': '対象のページのID',
 | 
				
			||||||
 | 
									'en-US': 'Target page ID.'
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errors: {
 | 
				
			||||||
 | 
							noSuchPage: {
 | 
				
			||||||
 | 
								message: 'No such page.',
 | 
				
			||||||
 | 
								code: 'NO_SUCH_PAGE',
 | 
				
			||||||
 | 
								id: 'a0d41e20-1993-40bd-890e-f6e560ae648e'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							notLiked: {
 | 
				
			||||||
 | 
								message: 'You have not liked that page.',
 | 
				
			||||||
 | 
								code: 'NOT_LIKED',
 | 
				
			||||||
 | 
								id: 'f5e586b0-ce93-4050-b0e3-7f31af5259ee'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, async (ps, user) => {
 | 
				
			||||||
 | 
						const page = await Pages.findOne(ps.pageId);
 | 
				
			||||||
 | 
						if (page == null) {
 | 
				
			||||||
 | 
							throw new ApiError(meta.errors.noSuchPage);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const exist = await PageLikes.findOne({
 | 
				
			||||||
 | 
							pageId: page.id,
 | 
				
			||||||
 | 
							userId: user.id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (exist == null) {
 | 
				
			||||||
 | 
							throw new ApiError(meta.errors.notLiked);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Delete like
 | 
				
			||||||
 | 
						await PageLikes.delete(exist.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Pages.decrement({ id: page.id }, 'likedCount', 1);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/server/api/endpoints/users/groups/create.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/server/api/endpoints/users/groups/create.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,51 @@
 | 
				
			||||||
 | 
					import $ from 'cafy';
 | 
				
			||||||
 | 
					import define from '../../../define';
 | 
				
			||||||
 | 
					import { UserGroups, UserGroupJoinings } from '../../../../../models';
 | 
				
			||||||
 | 
					import { genId } from '../../../../../misc/gen-id';
 | 
				
			||||||
 | 
					import { UserGroup } from '../../../../../models/entities/user-group';
 | 
				
			||||||
 | 
					import { types, bool } from '../../../../../misc/schema';
 | 
				
			||||||
 | 
					import { UserGroupJoining } from '../../../../../models/entities/user-group-joining';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						desc: {
 | 
				
			||||||
 | 
							'ja-JP': 'ユーザーグループを作成します。',
 | 
				
			||||||
 | 
							'en-US': 'Create a user group.'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags: ['groups'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kind: 'write:user-groups',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params: {
 | 
				
			||||||
 | 
							name: {
 | 
				
			||||||
 | 
								validator: $.str.range(1, 100)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res: {
 | 
				
			||||||
 | 
							type: types.object,
 | 
				
			||||||
 | 
							optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
							ref: 'UserGroup',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, async (ps, user) => {
 | 
				
			||||||
 | 
						const userGroup = await UserGroups.save({
 | 
				
			||||||
 | 
							id: genId(),
 | 
				
			||||||
 | 
							createdAt: new Date(),
 | 
				
			||||||
 | 
							userId: user.id,
 | 
				
			||||||
 | 
							name: ps.name,
 | 
				
			||||||
 | 
						} as UserGroup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Push the owner
 | 
				
			||||||
 | 
						await UserGroupJoinings.save({
 | 
				
			||||||
 | 
							id: genId(),
 | 
				
			||||||
 | 
							createdAt: new Date(),
 | 
				
			||||||
 | 
							userId: user.id,
 | 
				
			||||||
 | 
							userGroupId: userGroup.id
 | 
				
			||||||
 | 
						} as UserGroupJoining);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return await UserGroups.pack(userGroup);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										49
									
								
								src/server/api/endpoints/users/groups/delete.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/server/api/endpoints/users/groups/delete.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					import $ from 'cafy';
 | 
				
			||||||
 | 
					import { ID } from '../../../../../misc/cafy-id';
 | 
				
			||||||
 | 
					import define from '../../../define';
 | 
				
			||||||
 | 
					import { ApiError } from '../../../error';
 | 
				
			||||||
 | 
					import { UserGroups } from '../../../../../models';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						desc: {
 | 
				
			||||||
 | 
							'ja-JP': '指定したユーザーグループを削除します。',
 | 
				
			||||||
 | 
							'en-US': 'Delete a user group'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags: ['groups'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kind: 'write:user-groups',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params: {
 | 
				
			||||||
 | 
							groupId: {
 | 
				
			||||||
 | 
								validator: $.type(ID),
 | 
				
			||||||
 | 
								desc: {
 | 
				
			||||||
 | 
									'ja-JP': '対象となるユーザーグループのID',
 | 
				
			||||||
 | 
									'en-US': 'ID of target user group'
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errors: {
 | 
				
			||||||
 | 
							noSuchGroup: {
 | 
				
			||||||
 | 
								message: 'No such group.',
 | 
				
			||||||
 | 
								code: 'NO_SUCH_GROUP',
 | 
				
			||||||
 | 
								id: '63dbd64c-cd77-413f-8e08-61781e210b38'
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, async (ps, user) => {
 | 
				
			||||||
 | 
						const userGroup = await UserGroups.findOne({
 | 
				
			||||||
 | 
							id: ps.groupId,
 | 
				
			||||||
 | 
							userId: user.id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (userGroup == null) {
 | 
				
			||||||
 | 
							throw new ApiError(meta.errors.noSuchGroup);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						await UserGroups.delete(userGroup.id);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										33
									
								
								src/server/api/endpoints/users/groups/joined.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/server/api/endpoints/users/groups/joined.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					import define from '../../../define';
 | 
				
			||||||
 | 
					import { UserGroups, UserGroupJoinings } from '../../../../../models';
 | 
				
			||||||
 | 
					import { types, bool } from '../../../../../misc/schema';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						desc: {
 | 
				
			||||||
 | 
							'ja-JP': '自分の所属するユーザーグループ一覧を取得します。'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags: ['groups', 'account'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kind: 'read:user-groups',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res: {
 | 
				
			||||||
 | 
							type: types.array,
 | 
				
			||||||
 | 
							optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
							items: {
 | 
				
			||||||
 | 
								type: types.object,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								ref: 'UserGroup',
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, async (ps, me) => {
 | 
				
			||||||
 | 
						const joinings = await UserGroupJoinings.find({
 | 
				
			||||||
 | 
							userId: me.id,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return await Promise.all(joinings.map(x => UserGroups.pack(x.userGroupId)));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										33
									
								
								src/server/api/endpoints/users/groups/owned.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/server/api/endpoints/users/groups/owned.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					import define from '../../../define';
 | 
				
			||||||
 | 
					import { UserGroups } from '../../../../../models';
 | 
				
			||||||
 | 
					import { types, bool } from '../../../../../misc/schema';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						desc: {
 | 
				
			||||||
 | 
							'ja-JP': '自分の作成したユーザーグループ一覧を取得します。'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags: ['groups', 'account'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kind: 'read:user-groups',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res: {
 | 
				
			||||||
 | 
							type: types.array,
 | 
				
			||||||
 | 
							optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
							items: {
 | 
				
			||||||
 | 
								type: types.object,
 | 
				
			||||||
 | 
								optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
								ref: 'UserGroup',
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, async (ps, me) => {
 | 
				
			||||||
 | 
						const userGroups = await UserGroups.find({
 | 
				
			||||||
 | 
							userId: me.id,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return await Promise.all(userGroups.map(x => UserGroups.pack(x)));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										68
									
								
								src/server/api/endpoints/users/groups/pull.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/server/api/endpoints/users/groups/pull.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,68 @@
 | 
				
			||||||
 | 
					import $ from 'cafy';
 | 
				
			||||||
 | 
					import { ID } from '../../../../../misc/cafy-id';
 | 
				
			||||||
 | 
					import define from '../../../define';
 | 
				
			||||||
 | 
					import { ApiError } from '../../../error';
 | 
				
			||||||
 | 
					import { getUser } from '../../../common/getters';
 | 
				
			||||||
 | 
					import { UserGroups, UserGroupJoinings } from '../../../../../models';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						desc: {
 | 
				
			||||||
 | 
							'ja-JP': '指定したユーザーグループから指定したユーザーを削除します。',
 | 
				
			||||||
 | 
							'en-US': 'Remove a user to a user group.'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags: ['groups', 'users'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kind: 'write:user-groups',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params: {
 | 
				
			||||||
 | 
							groupId: {
 | 
				
			||||||
 | 
								validator: $.type(ID),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							userId: {
 | 
				
			||||||
 | 
								validator: $.type(ID),
 | 
				
			||||||
 | 
								desc: {
 | 
				
			||||||
 | 
									'ja-JP': '対象のユーザーのID',
 | 
				
			||||||
 | 
									'en-US': 'Target user ID'
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errors: {
 | 
				
			||||||
 | 
							noSuchGroup: {
 | 
				
			||||||
 | 
								message: 'No such group.',
 | 
				
			||||||
 | 
								code: 'NO_SUCH_GROUP',
 | 
				
			||||||
 | 
								id: '4662487c-05b1-4b78-86e5-fd46998aba74'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							noSuchUser: {
 | 
				
			||||||
 | 
								message: 'No such user.',
 | 
				
			||||||
 | 
								code: 'NO_SUCH_USER',
 | 
				
			||||||
 | 
								id: '0b5cc374-3681-41da-861e-8bc1146f7a55'
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, async (ps, me) => {
 | 
				
			||||||
 | 
						// Fetch the group
 | 
				
			||||||
 | 
						const userGroup = await UserGroups.findOne({
 | 
				
			||||||
 | 
							id: ps.groupId,
 | 
				
			||||||
 | 
							userId: me.id,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (userGroup == null) {
 | 
				
			||||||
 | 
							throw new ApiError(meta.errors.noSuchGroup);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Fetch the user
 | 
				
			||||||
 | 
						const user = await getUser(ps.userId).catch(e => {
 | 
				
			||||||
 | 
							if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
 | 
				
			||||||
 | 
							throw e;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Pull the user
 | 
				
			||||||
 | 
						await UserGroupJoinings.delete({ userId: user.id });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										90
									
								
								src/server/api/endpoints/users/groups/push.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/server/api/endpoints/users/groups/push.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,90 @@
 | 
				
			||||||
 | 
					import $ from 'cafy';
 | 
				
			||||||
 | 
					import { ID } from '../../../../../misc/cafy-id';
 | 
				
			||||||
 | 
					import define from '../../../define';
 | 
				
			||||||
 | 
					import { ApiError } from '../../../error';
 | 
				
			||||||
 | 
					import { getUser } from '../../../common/getters';
 | 
				
			||||||
 | 
					import { UserGroups, UserGroupJoinings } from '../../../../../models';
 | 
				
			||||||
 | 
					import { genId } from '../../../../../misc/gen-id';
 | 
				
			||||||
 | 
					import { UserGroupJoining } from '../../../../../models/entities/user-group-joining';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						desc: {
 | 
				
			||||||
 | 
							'ja-JP': '指定したユーザーグループに指定したユーザーを追加します。',
 | 
				
			||||||
 | 
							'en-US': 'Add a user to a user group.'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags: ['groups', 'users'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kind: 'write:user-groups',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params: {
 | 
				
			||||||
 | 
							groupId: {
 | 
				
			||||||
 | 
								validator: $.type(ID),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							userId: {
 | 
				
			||||||
 | 
								validator: $.type(ID),
 | 
				
			||||||
 | 
								desc: {
 | 
				
			||||||
 | 
									'ja-JP': '対象のユーザーのID',
 | 
				
			||||||
 | 
									'en-US': 'Target user ID'
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errors: {
 | 
				
			||||||
 | 
							noSuchGroup: {
 | 
				
			||||||
 | 
								message: 'No such group.',
 | 
				
			||||||
 | 
								code: 'NO_SUCH_GROUP',
 | 
				
			||||||
 | 
								id: '583f8bc0-8eee-4b78-9299-1e14fc91e409'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							noSuchUser: {
 | 
				
			||||||
 | 
								message: 'No such user.',
 | 
				
			||||||
 | 
								code: 'NO_SUCH_USER',
 | 
				
			||||||
 | 
								id: 'da52de61-002c-475b-90e1-ba64f9cf13a8'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							alreadyAdded: {
 | 
				
			||||||
 | 
								message: 'That user has already been added to that group.',
 | 
				
			||||||
 | 
								code: 'ALREADY_ADDED',
 | 
				
			||||||
 | 
								id: '7e35c6a0-39b2-4488-aea6-6ee20bd5da2c'
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, async (ps, me) => {
 | 
				
			||||||
 | 
						// Fetch the group
 | 
				
			||||||
 | 
						const userGroup = await UserGroups.findOne({
 | 
				
			||||||
 | 
							id: ps.groupId,
 | 
				
			||||||
 | 
							userId: me.id,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (userGroup == null) {
 | 
				
			||||||
 | 
							throw new ApiError(meta.errors.noSuchGroup);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Fetch the user
 | 
				
			||||||
 | 
						const user = await getUser(ps.userId).catch(e => {
 | 
				
			||||||
 | 
							if (e.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
 | 
				
			||||||
 | 
							throw e;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const exist = await UserGroupJoinings.findOne({
 | 
				
			||||||
 | 
							userGroupId: userGroup.id,
 | 
				
			||||||
 | 
							userId: user.id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (exist) {
 | 
				
			||||||
 | 
							throw new ApiError(meta.errors.alreadyAdded);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Push the user
 | 
				
			||||||
 | 
						await UserGroupJoinings.save({
 | 
				
			||||||
 | 
							id: genId(),
 | 
				
			||||||
 | 
							createdAt: new Date(),
 | 
				
			||||||
 | 
							userId: user.id,
 | 
				
			||||||
 | 
							userGroupId: userGroup.id
 | 
				
			||||||
 | 
						} as UserGroupJoining);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										61
									
								
								src/server/api/endpoints/users/groups/show.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/server/api/endpoints/users/groups/show.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					import $ from 'cafy';
 | 
				
			||||||
 | 
					import { ID } from '../../../../../misc/cafy-id';
 | 
				
			||||||
 | 
					import define from '../../../define';
 | 
				
			||||||
 | 
					import { ApiError } from '../../../error';
 | 
				
			||||||
 | 
					import { UserGroups, UserGroupJoinings } from '../../../../../models';
 | 
				
			||||||
 | 
					import { types, bool } from '../../../../../misc/schema';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
						desc: {
 | 
				
			||||||
 | 
							'ja-JP': '指定したユーザーグループの情報を取得します。',
 | 
				
			||||||
 | 
							'en-US': 'Show a user group.'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tags: ['groups', 'account'],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						requireCredential: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						kind: 'read:user-groups',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						params: {
 | 
				
			||||||
 | 
							groupId: {
 | 
				
			||||||
 | 
								validator: $.type(ID),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res: {
 | 
				
			||||||
 | 
							type: types.object,
 | 
				
			||||||
 | 
							optional: bool.false, nullable: bool.false,
 | 
				
			||||||
 | 
							ref: 'UserGroup',
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errors: {
 | 
				
			||||||
 | 
							noSuchGroup: {
 | 
				
			||||||
 | 
								message: 'No such group.',
 | 
				
			||||||
 | 
								code: 'NO_SUCH_GROUP',
 | 
				
			||||||
 | 
								id: 'ea04751e-9b7e-487b-a509-330fb6bd6b9b'
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default define(meta, async (ps, me) => {
 | 
				
			||||||
 | 
						// Fetch the group
 | 
				
			||||||
 | 
						const userGroup = await UserGroups.findOne({
 | 
				
			||||||
 | 
							id: ps.groupId,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (userGroup == null) {
 | 
				
			||||||
 | 
							throw new ApiError(meta.errors.noSuchGroup);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const joining = await UserGroupJoinings.findOne({
 | 
				
			||||||
 | 
							userId: me.id,
 | 
				
			||||||
 | 
							userGroupId: userGroup.id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (joining == null && userGroup.userId !== me.id) {
 | 
				
			||||||
 | 
							throw new ApiError(meta.errors.noSuchGroup);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return await UserGroups.pack(userGroup);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -80,5 +80,5 @@ export default define(meta, async (ps, me) => {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Push the user
 | 
						// Push the user
 | 
				
			||||||
	pushUserToUserList(user, userList);
 | 
						await pushUserToUserList(user, userList);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,4 +21,8 @@ export const kinds = [
 | 
				
			||||||
	'write:votes',
 | 
						'write:votes',
 | 
				
			||||||
	'read:pages',
 | 
						'read:pages',
 | 
				
			||||||
	'write:pages',
 | 
						'write:pages',
 | 
				
			||||||
 | 
						'write:page-likes',
 | 
				
			||||||
 | 
						'read:page-likes',
 | 
				
			||||||
 | 
						'read:user-groups',
 | 
				
			||||||
 | 
						'write:user-groups',
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@ import { packedBlockingSchema } from '../../../models/repositories/blocking';
 | 
				
			||||||
import { packedNoteReactionSchema } from '../../../models/repositories/note-reaction';
 | 
					import { packedNoteReactionSchema } from '../../../models/repositories/note-reaction';
 | 
				
			||||||
import { packedHashtagSchema } from '../../../models/repositories/hashtag';
 | 
					import { packedHashtagSchema } from '../../../models/repositories/hashtag';
 | 
				
			||||||
import { packedPageSchema } from '../../../models/repositories/page';
 | 
					import { packedPageSchema } from '../../../models/repositories/page';
 | 
				
			||||||
 | 
					import { packedUserGroupSchema } from '../../../models/repositories/user-group';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function convertSchemaToOpenApiSchema(schema: Schema) {
 | 
					export function convertSchemaToOpenApiSchema(schema: Schema) {
 | 
				
			||||||
	const res: any = schema;
 | 
						const res: any = schema;
 | 
				
			||||||
| 
						 | 
					@ -66,6 +67,7 @@ export const schemas = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	User: convertSchemaToOpenApiSchema(packedUserSchema),
 | 
						User: convertSchemaToOpenApiSchema(packedUserSchema),
 | 
				
			||||||
	UserList: convertSchemaToOpenApiSchema(packedUserListSchema),
 | 
						UserList: convertSchemaToOpenApiSchema(packedUserListSchema),
 | 
				
			||||||
 | 
						UserGroup: convertSchemaToOpenApiSchema(packedUserGroupSchema),
 | 
				
			||||||
	App: convertSchemaToOpenApiSchema(packedAppSchema),
 | 
						App: convertSchemaToOpenApiSchema(packedAppSchema),
 | 
				
			||||||
	MessagingMessage: convertSchemaToOpenApiSchema(packedMessagingMessageSchema),
 | 
						MessagingMessage: convertSchemaToOpenApiSchema(packedMessagingMessageSchema),
 | 
				
			||||||
	Note: convertSchemaToOpenApiSchema(packedNoteSchema),
 | 
						Note: convertSchemaToOpenApiSchema(packedNoteSchema),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,20 +1,39 @@
 | 
				
			||||||
import autobind from 'autobind-decorator';
 | 
					import autobind from 'autobind-decorator';
 | 
				
			||||||
import read from '../../common/read-messaging-message';
 | 
					import { readUserMessagingMessage, readGroupMessagingMessage } from '../../common/read-messaging-message';
 | 
				
			||||||
import Channel from '../channel';
 | 
					import Channel from '../channel';
 | 
				
			||||||
 | 
					import { UserGroupJoinings } from '../../../../models';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class extends Channel {
 | 
					export default class extends Channel {
 | 
				
			||||||
	public readonly chName = 'messaging';
 | 
						public readonly chName = 'messaging';
 | 
				
			||||||
	public static shouldShare = false;
 | 
						public static shouldShare = false;
 | 
				
			||||||
	public static requireCredential = true;
 | 
						public static requireCredential = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private otherpartyId: string;
 | 
						private otherpartyId: string | null;
 | 
				
			||||||
 | 
						private groupId: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@autobind
 | 
						@autobind
 | 
				
			||||||
	public async init(params: any) {
 | 
						public async init(params: any) {
 | 
				
			||||||
		this.otherpartyId = params.otherparty as string;
 | 
							this.otherpartyId = params.otherparty as string;
 | 
				
			||||||
 | 
							this.groupId = params.group as string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check joining
 | 
				
			||||||
 | 
							if (this.groupId) {
 | 
				
			||||||
 | 
								const joining = await UserGroupJoinings.findOne({
 | 
				
			||||||
 | 
									userId: this.user!.id,
 | 
				
			||||||
 | 
									userGroupId: this.groupId
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (joining == null) {
 | 
				
			||||||
 | 
									return;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const subCh = this.otherpartyId
 | 
				
			||||||
 | 
								? `messagingStream:${this.user!.id}-${this.otherpartyId}`
 | 
				
			||||||
 | 
								: `messagingStream:${this.groupId}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Subscribe messaging stream
 | 
							// Subscribe messaging stream
 | 
				
			||||||
		this.subscriber.on(`messagingStream:${this.user!.id}-${this.otherpartyId}`, data => {
 | 
							this.subscriber.on(subCh, data => {
 | 
				
			||||||
			this.send(data);
 | 
								this.send(data);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -23,7 +42,11 @@ export default class extends Channel {
 | 
				
			||||||
	public onMessage(type: string, body: any) {
 | 
						public onMessage(type: string, body: any) {
 | 
				
			||||||
		switch (type) {
 | 
							switch (type) {
 | 
				
			||||||
			case 'read':
 | 
								case 'read':
 | 
				
			||||||
				read(this.user!.id, this.otherpartyId, [body.id]);
 | 
									if (this.otherpartyId) {
 | 
				
			||||||
 | 
										readUserMessagingMessage(this.user!.id, this.otherpartyId, [body.id]);
 | 
				
			||||||
 | 
									} else if (this.groupId) {
 | 
				
			||||||
 | 
										readGroupMessagingMessage(this.user!.id, this.groupId, [body.id]);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@ import { User } from '../models/entities/user';
 | 
				
			||||||
import { Note } from '../models/entities/note';
 | 
					import { Note } from '../models/entities/note';
 | 
				
			||||||
import { UserList } from '../models/entities/user-list';
 | 
					import { UserList } from '../models/entities/user-list';
 | 
				
			||||||
import { ReversiGame } from '../models/entities/games/reversi/game';
 | 
					import { ReversiGame } from '../models/entities/games/reversi/game';
 | 
				
			||||||
 | 
					import { UserGroup } from '../models/entities/user-group';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Publisher {
 | 
					class Publisher {
 | 
				
			||||||
	private publish = (channel: string, type: string | null, value?: any): void => {
 | 
						private publish = (channel: string, type: string | null, value?: any): void => {
 | 
				
			||||||
| 
						 | 
					@ -39,6 +40,10 @@ class Publisher {
 | 
				
			||||||
		this.publish(`messagingStream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value);
 | 
							this.publish(`messagingStream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public publishGroupMessagingStream = (groupId: UserGroup['id'], type: string, value?: any): void => {
 | 
				
			||||||
 | 
							this.publish(`messagingStream:${groupId}`, type, typeof value === 'undefined' ? null : value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public publishMessagingIndexStream = (userId: User['id'], type: string, value?: any): void => {
 | 
						public publishMessagingIndexStream = (userId: User['id'], type: string, value?: any): void => {
 | 
				
			||||||
		this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value);
 | 
							this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -74,6 +79,7 @@ export const publishNoteStream = publisher.publishNoteStream;
 | 
				
			||||||
export const publishNotesStream = publisher.publishNotesStream;
 | 
					export const publishNotesStream = publisher.publishNotesStream;
 | 
				
			||||||
export const publishUserListStream = publisher.publishUserListStream;
 | 
					export const publishUserListStream = publisher.publishUserListStream;
 | 
				
			||||||
export const publishMessagingStream = publisher.publishMessagingStream;
 | 
					export const publishMessagingStream = publisher.publishMessagingStream;
 | 
				
			||||||
 | 
					export const publishGroupMessagingStream = publisher.publishGroupMessagingStream;
 | 
				
			||||||
export const publishMessagingIndexStream = publisher.publishMessagingIndexStream;
 | 
					export const publishMessagingIndexStream = publisher.publishMessagingIndexStream;
 | 
				
			||||||
export const publishReversiStream = publisher.publishReversiStream;
 | 
					export const publishReversiStream = publisher.publishReversiStream;
 | 
				
			||||||
export const publishReversiGameStream = publisher.publishReversiGameStream;
 | 
					export const publishReversiGameStream = publisher.publishReversiGameStream;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,6 +64,7 @@ export async function updateHashtag(user: User, tag: string, isUserAttached = fa
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (Object.keys(set).length > 0) {
 | 
							if (Object.keys(set).length > 0) {
 | 
				
			||||||
 | 
								q.set(set);
 | 
				
			||||||
			q.execute();
 | 
								q.execute();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue