diff --git a/change.ts b/change.ts index 4bf19bb..2875b43 100644 --- a/change.ts +++ b/change.ts @@ -1,4 +1,17 @@ -import type { BasePage, LineId, StringLc } from "./base.ts"; +import type { LineId, StringLc } from "./base.ts"; +import type { + ChangeLine, + CharsCountChange, + DeleteChange, + DescriptionsChange, + FilesChange, + HelpFeelsChange, + ImageChange, + InfoboxDefinitionChange, + InsertChange, + LinesCountChange, + PinChange, +} from "./websocket/change.ts"; /** ページの変更内容 */ export type Change = @@ -6,31 +19,18 @@ export type Change = | UpdateChange | DeleteChange | LinksChange + | ProjectLinksChange + | IconsChange | DescriptionsChange | ImageChange + | FilesChange + | HelpFeelsChange + | InfoboxDefinitionChange | TitleChange + | LinesCountChange + | CharsCountChange | PinChange; -/** 行を新規作成する変更 */ -export interface InsertChange { - /** このIDが示す行の上に挿入する - * - * 末尾に挿入するときは`"_end"`を指定する - */ - _insert: LineId; - - /** 挿入する行のデータ */ - lines: NewLine; -} - -export interface NewLine { - /** 新しく挿入する行のID */ - id: LineId; - - /** 行のテキスト */ - text: string; -} - /** 既存の行を書き換える変更 */ export interface UpdateChange { /** 書き換える行のID */ @@ -40,23 +40,6 @@ export interface UpdateChange { lines: ChangeLine; } -export interface ChangeLine { - /**変更前の文字列*/ - origText: string; - - /**変更後の文字列*/ - text: string; -} - -/** 既存の行を削除する変更 */ -export interface DeleteChange { - /** 削除する行のID */ - _delete: LineId; - - /** 常に `-1` */ - lines: -1; -} - /** ページ中のリンクが変更されると発生する */ export interface LinksChange { /** 新しいリンク */ @@ -66,19 +49,22 @@ export interface LinksChange { linksLc: StringLc[]; } -/** ページのサムネイル本文が変更されると発生する */ -export interface DescriptionsChange { - /** 新しいサムネイル本文 */ - descriptions: string[]; +/** ページ中のproject linksが変更されると発生する */ +export interface ProjectLinksChange { + /** 新しいリンク */ + projectLinks: string[]; + + /** 新しいリンク */ + projectLinksLc: StringLc[]; } -/** ページのサムネイルが変更されると発生する */ -export interface ImageChange { - /** 新しいサムネイルのURL - * - * サムネイルがなくなったときは`null`になる - */ - image: string | null; +/** ページ中のiconsが変更されると発生する */ +export interface IconsChange { + /** 新しいicons */ + icons: string[]; + + /** 新しいicons */ + iconsLc: StringLc[]; } /** ページのタイトルが変更されると発生する */ @@ -89,8 +75,3 @@ export interface TitleChange { /** 新しいタイトル */ titleLc: StringLc; } - -/** ページのピンの状態が変更されると発生する */ -export interface PinChange { - pin: BasePage["pin"]; -} diff --git a/deno.jsonc b/deno.jsonc index 22f3763..86071f1 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -5,14 +5,15 @@ "fix": "deno fmt && deno lint --fix && deno test --allow-read --doc --parallel --shuffle && deno publish --dry-run --allow-dirty", "check": "deno fmt --check && deno lint && deno test --allow-read --doc --parallel --shuffle && deno publish --dry-run", "coverage": "deno test --allow-read=./ --parallel --shuffle --coverage && deno coverage --html", - "doc": "deno doc --html rest.ts userscript.ts" + "doc": "deno doc --html rest.ts userscript.ts websocket.ts" }, "imports": { "@std/testing/types": "jsr:@std/testing@0/types" }, "exports": { "./rest": "./rest.ts", - "./userscript": "./userscript.ts" + "./userscript": "./userscript.ts", + "./websocket": "./websocket.ts" }, "compilerOptions": { "lib": [ diff --git a/deno.lock b/deno.lock index d3d5329..693553c 100644 --- a/deno.lock +++ b/deno.lock @@ -1,17 +1,14 @@ { - "version": "3", - "packages": { - "specifiers": { - "jsr:@std/testing": "jsr:@std/testing@0.225.3", - "jsr:@std/testing@0": "jsr:@std/testing@0.225.3" - }, - "jsr": { - "@std/testing@0.225.3": { - "integrity": "348c24d0479d44ab3dbb4f26170f242e19f24051b45935d4a9e7ca0ab7e37780" - } + "version": "4", + "specifiers": { + "jsr:@std/testing@*": "0.225.3", + "jsr:@std/testing@0": "0.225.3" + }, + "jsr": { + "@std/testing@0.225.3": { + "integrity": "348c24d0479d44ab3dbb4f26170f242e19f24051b45935d4a9e7ca0ab7e37780" } }, - "remote": {}, "workspace": { "dependencies": [ "jsr:@std/testing@0" diff --git a/websocket.ts b/websocket.ts new file mode 100644 index 0000000..3353cf1 --- /dev/null +++ b/websocket.ts @@ -0,0 +1,2 @@ +export * from "./websocket/change.ts"; +export * from "./websocket/event.ts"; diff --git a/websocket/change.ts b/websocket/change.ts new file mode 100644 index 0000000..dd23c6b --- /dev/null +++ b/websocket/change.ts @@ -0,0 +1,136 @@ +import type { BasePage, LineId } from "../base.ts"; + +/** Changes to push to the cosense server */ +export type ChangeToPush = + | InsertChange + | UpdateChange + | DeleteChange + | LinksChange + | ProjectLinksChange + | IconsChange + | DescriptionsChange + | ImageChange + | FilesChange + | HelpFeelsChange + | InfoboxDefinitionChange + | TitleChange + | LinesCountChange + | CharsCountChange + | PinChange; + +/** 行を新規作成する変更 */ +export interface InsertChange { + /** このIDが示す行の上に挿入する + * + * 末尾に挿入するときは`"_end"`を指定する + */ + _insert: LineId; + + /** 挿入する行のデータ */ + lines: NewLine; +} +export interface NewLine { + /** 新しく挿入する行のID */ + id: LineId; + + /** 行のテキスト */ + text: string; +} + +export interface UpdateChange { + _update: string; + lines: Pick; + noTimestampUpdate?: unknown; +} + +export interface ChangeLine { + /**変更前の文字列*/ + origText: string; + + /**変更後の文字列*/ + text: string; +} + +/** 既存の行を削除する変更 */ +export interface DeleteChange { + /** 削除する行のID */ + _delete: LineId; + + /** 常に `-1` */ + lines: -1; +} + +export interface LinksChange { + links: string[]; +} + +export interface ProjectLinksChange { + projectLinks: string[]; +} + +export interface IconsChange { + icons: string[]; +} + +/** ページのサムネイル本文が変更されると発生する */ +export interface DescriptionsChange { + /** 新しいサムネイル本文 */ + descriptions: string[]; +} + +/** ページのサムネイルが変更されると発生する */ +export interface ImageChange { + /** 新しいサムネイルのURL + * + * サムネイルがなくなったときは`null`になる + */ + image: string | null; +} + +export interface TitleChange { + title: string; +} + +export interface FilesChange { + /** Array of file IDs + * + * These IDs reference files that have been uploaded to the page. + * Files can include images, documents, or other attachments. + */ + files: string[]; +} +export interface HelpFeelsChange { + /** Array of Helpfeel entries without the leading "? " prefix + * + * Helpfeel is a Scrapbox notation for creating help/documentation entries. + * Example: "? How to use" becomes "How to use" in this array. + * These entries are used to build the page's help documentation. + */ + helpfeels: string[]; +} + +export interface InfoboxDefinitionChange { + /** Array of trimmed lines from infobox tables + * + * Contains lines from tables marked with either `table:infobox` or `table:cosense` + */ + infoboxDefinition: string[]; +} + +export interface LinesCountChange { + linesCount: number; +} + +export interface CharsCountChange { + charsCount: number; +} + +/** ページのピンの状態が変更されると発生する */ +export interface PinChange { + pin: BasePage["pin"]; +} + +export interface DeletePageChange { + deleted: true; + merged?: true; +} diff --git a/websocket/event.ts b/websocket/event.ts new file mode 100644 index 0000000..6a00217 --- /dev/null +++ b/websocket/event.ts @@ -0,0 +1,178 @@ +import type { CommitId, PageId, ProjectId, UserId } from "../base.ts"; +import type { User } from "../response.ts"; +import type { + ChangeToPush, + DeleteChange, + DeletePageChange, + DescriptionsChange, + IconsChange, + ImageChange, + InsertChange, + LinksChange, + TitleChange, + UpdateChange, +} from "./change.ts"; + +export interface EmitEventMap { + "socket.io-request": ( + req: { method: "commit"; data: PageCommit } | { + method: "room:join"; + data: JoinRoomRequest; + }, + callback: ( + res: + | { data: PageCommitResponse | JoinRoomResponse } + | { error: { name: string; message?: string } }, + ) => void, + ) => void; + cursor: (req: Omit) => void; +} + +export interface PageCommit { + kind: "page"; + parentId: CommitId; + projectId: ProjectId; + pageId: PageId; + userId: UserId; + changes: ChangeToPush[] | [DeletePageChange]; + cursor?: null; + freeze: true; +} + +export interface PageCommitResponse { + commitId: CommitId; +} + +export type JoinRoomRequest = + | JoinPageRoomRequest + | JoinProjectRoomRequest + | JoinStreamRoomRequest; + +export interface JoinProjectRoomRequest { + pageId: null; + projectId: ProjectId; + projectUpdatesStream: false; +} + +export interface JoinPageRoomRequest { + pageId: PageId; + projectId: ProjectId; + projectUpdatesStream: false; +} + +export interface JoinStreamRoomRequest { + pageId: null; + projectId: ProjectId; + projectUpdatesStream: true; +} + +export interface JoinRoomResponse { + success: true; + pageId: PageId | null; + projectId: ProjectId; +} + +export interface MoveCursorData { + user: Omit; + pageId: PageId; + position: { + line: number; + char: number; + }; + visible: boolean; + socketId: string; +} + +export interface ListenEventMap { + "projectUpdatesStream:commit": (event: ProjectUpdatesStreamCommit) => void; + "projectUpdatesStream:event": (event: ProjectUpdatesStreamEvent) => void; + commit: (event: CommitNotification) => void; + cursor: (event: MoveCursorData) => void; + "quick-search:commit": (event: QuickSearchCommit) => void; + "quick-search:replace-link": QuickSearchReplaceLink; + "infobox:updating": boolean; + "infobox:reload": void; + "literal-database:reload": void; +} + +export interface ProjectUpdatesStreamCommit { + kind: "page"; + id: CommitId; + parentId: CommitId; + projectId: ProjectId; + pageId: PageId; + userId: UserId; + changes: + | ( + | InsertChange + | UpdateChange + | DeleteChange + | TitleChange + | LinksChange + | IconsChange + )[] + | [DeletePageChange]; + cursor: null; + freeze: true; +} + +export type ProjectUpdatesStreamEvent = + | PageDeleteEvent + | MemberJoinEvent + | MemberAddEvent + | AdminAddEvent + | AdminDeleteEvent + | OwnerSetEvent + | InvitationResetEvent; + +export interface ProjectEvent { + id: string; + pageId: string; + userId: string; + projectId: string; + created: number; + updated: number; +} + +export interface PageDeleteEvent extends ProjectEvent { + type: "page.delete"; + data: { + titleLc: string; + }; +} +export interface MemberJoinEvent extends ProjectEvent { + type: "member.join"; +} +export interface MemberAddEvent extends ProjectEvent { + type: "member.add"; +} +export interface InvitationResetEvent extends ProjectEvent { + type: "invitation.reset"; +} +export interface AdminAddEvent extends ProjectEvent { + type: "admin.add"; + targetUserId: UserId; +} +export interface AdminDeleteEvent extends ProjectEvent { + type: "admin.delete"; + targetUserId: UserId; +} +export interface OwnerSetEvent extends ProjectEvent { + type: "owner.set"; + targetUserId: UserId; +} + +export interface CommitNotification extends PageCommit { + id: string; +} + +export interface QuickSearchCommit extends Omit { + changes: + | (TitleChange | LinksChange | DescriptionsChange | ImageChange)[] + | [DeletePageChange]; +} + +export interface QuickSearchReplaceLink { + from: string; + to: string; +}