@supernova-studio/client 0.48.13 → 0.48.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supernova-studio/client",
3
- "version": "0.48.13",
3
+ "version": "0.48.15",
4
4
  "description": "Supernova Data Models",
5
5
  "source": "src/index.ts",
6
6
  "main": "dist/index.js",
@@ -18,6 +18,8 @@ export const DTODocumentationDraftStateUpdated = z.object({
18
18
 
19
19
  export const DTODocumentationDraftStateDeleted = z.object({
20
20
  changeType: z.literal(DTODocumentationDraftChangeType.enum.Deleted),
21
+ deletedAt: z.coerce.date(),
22
+ deletedByUserId: z.string(),
21
23
  });
22
24
 
23
25
  export const DTODocumentationDraftState = z.discriminatedUnion("changeType", [
@@ -2,6 +2,7 @@ import { DocumentationGroupBehavior, ElementGroup } from "@supernova-studio/mode
2
2
  import { z } from "zod";
3
3
  import { DTODocumentationDraftState } from "./draft-state";
4
4
  import { DTODocumentationItemConfigurationV2 } from "./item-configuration-v2";
5
+ import { DTODocumentationPublishMetadata } from "./metadata";
5
6
 
6
7
  //
7
8
  // Read
@@ -26,6 +27,9 @@ export const DTODocumentationGroupV2 = ElementGroup.omit({
26
27
 
27
28
  /** Defined when a group has changed since last publish and can be included into a partial publish */
28
29
  draftState: DTODocumentationDraftState.optional(),
30
+
31
+ /** Defined if a group was published at least once and contains metadata about last publish */
32
+ publishMetadata: DTODocumentationPublishMetadata.optional(),
29
33
  });
30
34
 
31
35
  export type DTODocumentationGroupV2 = z.infer<typeof DTODocumentationGroupV2>;
@@ -17,20 +17,6 @@ export const DTODocumentationHierarchyV2 = z.object({
17
17
  draftState: DTODocumentationDraftState.optional(),
18
18
  })
19
19
  ),
20
-
21
- deletedPages: z.array(
22
- DTODocumentationPageV2.extend({
23
- /** Deleted page is always a draft change */
24
- draftState: DTODocumentationDraftState,
25
- })
26
- ),
27
-
28
- deletedGroups: z.array(
29
- DTODocumentationGroupV2.extend({
30
- /** Deleted page is always a draft change */
31
- draftState: DTODocumentationDraftState,
32
- })
33
- ),
34
20
  });
35
21
 
36
22
  export type DTODocumentationHierarchyV2 = z.infer<typeof DTODocumentationHierarchyV2>;
@@ -5,6 +5,7 @@ export * from "./group-v2";
5
5
  export * from "./hierarchy";
6
6
  export * from "./item-configuration-v1";
7
7
  export * from "./item-configuration-v2";
8
+ export * from "./metadata";
8
9
  export * from "./page-actions-v2";
9
10
  export * from "./page-content";
10
11
  export * from "./page-v1";
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+
3
+ export const DTODocumentationPublishMetadata = z.object({
4
+ lastPublishedByUserId: z.string(),
5
+ lastPublishedAt: z.coerce.date(),
6
+ });
7
+
8
+ export type DTODocumentationPublishMetadata = z.infer<typeof DTODocumentationPublishMetadata>;
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { DTODocumentationDraftState } from "./draft-state";
3
3
  import { DTODocumentationItemConfigurationV2 } from "./item-configuration-v2";
4
+ import { DTODocumentationPublishMetadata } from "./metadata";
4
5
 
5
6
  //
6
7
  // Read
@@ -28,6 +29,9 @@ export const DTODocumentationPageV2 = z.object({
28
29
  /** Defined when a page has changed since last publish and can be included into a partial publish */
29
30
  draftState: DTODocumentationDraftState.optional(),
30
31
 
32
+ /** Defined if a page was published at least once and contains metadata about last publish */
33
+ publishMetadata: DTODocumentationPublishMetadata.optional(),
34
+
31
35
  // Backward compatibility
32
36
  type: z.literal("Page"),
33
37
  });
@@ -6,6 +6,8 @@ import {
6
6
  ElementGroupSnapshot,
7
7
  defaultDocumentationItemConfigurationV2,
8
8
  mapByUnique,
9
+ pickLatestGroupSnapshots,
10
+ pickLatestPageSnapshots,
9
11
  } from "@supernova-studio/model";
10
12
  import * as Y from "yjs";
11
13
  import {
@@ -16,6 +18,7 @@ import {
16
18
  documentationPagesToDTOV2,
17
19
  elementGroupsToDocumentationGroupDTOV2,
18
20
  } from "../../api";
21
+ import { DTODocumentationPublishMetadata } from "../../api/dto/elements/documentation/metadata";
19
22
  import { generateHash } from "../../utils";
20
23
  import { DocumentationPageEditorModel } from "../docs-editor";
21
24
  import { VersionRoomBaseYDoc } from "./base";
@@ -38,7 +41,9 @@ export class FrontendVersionRoomYDoc {
38
41
  // Hierarchy
39
42
  //
40
43
 
41
- getDocumentationHierarchy(): DTODocumentationHierarchyV2 {
44
+ getDocumentationHierarchy(options: { includeDeletedContent?: boolean } = {}): DTODocumentationHierarchyV2 {
45
+ const includeDeletedContent = options.includeDeletedContent ?? false;
46
+
42
47
  const doc = new VersionRoomBaseYDoc(this.yDoc);
43
48
 
44
49
  // Read current room data
@@ -47,6 +52,11 @@ export class FrontendVersionRoomYDoc {
47
52
  const pageSnapshots = doc.getPageSnapshots();
48
53
  const groupSnapshots = doc.getGroupSnapshots();
49
54
 
55
+ if (includeDeletedContent) {
56
+ pages.push(...this.getDeletedPages(pages, pageSnapshots));
57
+ groups.push(...this.getDeletedGroups(groups, groupSnapshots));
58
+ }
59
+
50
60
  const settings = doc.getDocumentationInternalSettings();
51
61
 
52
62
  // Convert pages to DTOs
@@ -58,59 +68,40 @@ export class FrontendVersionRoomYDoc {
58
68
  return {
59
69
  pages: pageDTOs,
60
70
  groups: groupDTOs,
61
- deletedGroups: [],
62
- deletedPages: [],
63
71
  };
64
72
  }
65
73
 
66
- const pageDraftStates = this.buildPageDraftStates(pages, pageSnapshots);
74
+ const pageDraftStates = this.buildPageDraftCreatedAndUpdatedStates(pages, pageSnapshots);
75
+ const pageDraftDeletedStates = this.buildPageDraftDeletedStates(pages, pageSnapshots);
76
+ const pagePublishedMetadata = this.buildPagePublishedMetadata(pages, pageSnapshots);
77
+
67
78
  pageDTOs.forEach(p => {
68
- const draftState = pageDraftStates.get(p.id);
79
+ // Assign draft state
80
+ const draftState = pageDraftDeletedStates.get(p.id) ?? pageDraftStates.get(p.id);
69
81
  draftState && (p.draftState = draftState);
70
- });
71
82
 
72
- const groupDraftStates = this.buildGroupDraftStates(groups, groupSnapshots);
73
- groupDTOs.forEach(g => {
74
- const draftState = groupDraftStates.get(g.id);
75
- draftState && (g.draftState = draftState);
83
+ // Assign publish metadata
84
+ const publishMetadata = pagePublishedMetadata.get(p.id);
85
+ publishMetadata && (p.publishMetadata = publishMetadata);
76
86
  });
77
87
 
78
- // Read deleted pages
79
- const pageIds = new Set(pages.map(p => p.id));
80
- const deletedPagesMap = mapByUnique(
81
- pageSnapshots.filter(s => !pageIds.has(s.page.id)).map(s => s.page),
82
- p => p.id
83
- );
84
- const deletedPages = Array.from(deletedPagesMap.values());
88
+ const groupDraftStates = this.buildGroupDraftCreatedAndUpdatedStates(groups, groupSnapshots);
89
+ const groupDraftDeletedStates = this.buildGroupDraftDeletedStates(groups, groupSnapshots);
90
+ const groupPublishedMetadata = this.buildGroupPublishedMetadata(groups, groupSnapshots);
85
91
 
86
- // Read deleted groups
87
- const groupIds = new Set(groups.map(p => p.id));
88
- const deletedGroupsMap = mapByUnique(
89
- groupSnapshots.filter(s => !groupIds.has(s.group.id)).map(s => s.group),
90
- g => g.id
91
- );
92
- const deletedGroups = Array.from(deletedGroupsMap.values());
93
-
94
- // Convert deleted pages to DTOs with draft states
95
- const deletedPageDTOs = documentationPagesToDTOV2(
96
- deletedPages,
97
- [...groups, ...deletedGroups],
98
- settings.routingVersion
99
- ).map(p => {
100
- return { ...p, draftState: { changeType: "Deleted" } } as const;
101
- });
92
+ groupDTOs.forEach(g => {
93
+ // Assign draft state
94
+ const draftState = groupDraftDeletedStates.get(g.id) ?? groupDraftStates.get(g.id);
95
+ draftState && (g.draftState = draftState);
102
96
 
103
- // Convert deleted groups to DTOs with draft states
104
- const deletedGroupDTOs = elementGroupsToDocumentationGroupDTOV2(deletedGroups, deletedPages).map(g => {
105
- return { ...g, draftState: { changeType: "Deleted" } } as const;
97
+ // Assign publish metadata
98
+ const publishMetadata = groupPublishedMetadata.get(g.id);
99
+ publishMetadata && (g.publishMetadata = publishMetadata);
106
100
  });
107
101
 
108
102
  return {
109
103
  pages: pageDTOs,
110
104
  groups: groupDTOs,
111
-
112
- deletedPages: deletedPageDTOs,
113
- deletedGroups: deletedGroupDTOs,
114
105
  };
115
106
  }
116
107
 
@@ -118,7 +109,32 @@ export class FrontendVersionRoomYDoc {
118
109
  // Drafts - Pages
119
110
  //
120
111
 
121
- private buildPageDraftStates(
112
+ private buildPageDraftDeletedStates(
113
+ pages: DocumentationPageV2[],
114
+ pageSnapshots: DocumentationPageSnapshot[]
115
+ ): Map<string, DTODocumentationDraftState> {
116
+ const deletedSnapshotsByPageId = mapByUnique(
117
+ pickLatestPageSnapshots(pageSnapshots.filter(s => s.reason === "Deletion")),
118
+ s => s.page.id
119
+ );
120
+
121
+ const result = new Map<string, DTODocumentationDraftState>();
122
+
123
+ pages.forEach(page => {
124
+ const deletedSnapshot = deletedSnapshotsByPageId.get(page.id);
125
+ if (!deletedSnapshot) return;
126
+
127
+ result.set(page.id, {
128
+ changeType: "Deleted",
129
+ deletedAt: deletedSnapshot.createdAt,
130
+ deletedByUserId: deletedSnapshot.createdByUserId,
131
+ });
132
+ });
133
+
134
+ return result;
135
+ }
136
+
137
+ private buildPageDraftCreatedAndUpdatedStates(
122
138
  pages: DocumentationPageV2[],
123
139
  pageSnapshots: DocumentationPageSnapshot[]
124
140
  ): Map<string, DTODocumentationDraftState> {
@@ -126,13 +142,9 @@ export class FrontendVersionRoomYDoc {
126
142
 
127
143
  // Read room data
128
144
  const pageHashes = doc.getDocumentationPageContentHashes();
129
- const publishedSnapshots = pageSnapshots.filter(s => s.reason === "Publish");
145
+ const publishedSnapshots = pickLatestPageSnapshots(pageSnapshots.filter(s => s.reason === "Publish"));
130
146
 
131
147
  const publishedSnapshotsByPageId = mapByUnique(publishedSnapshots, s => s.page.id);
132
- const publishedPagesById = mapByUnique(
133
- publishedSnapshots.map(s => s.page),
134
- p => p.id
135
- );
136
148
 
137
149
  const result = new Map<string, DTODocumentationDraftState>();
138
150
 
@@ -141,8 +153,7 @@ export class FrontendVersionRoomYDoc {
141
153
  const snapshot = publishedSnapshotsByPageId.get(page.id);
142
154
  let publishedState: ItemState | undefined;
143
155
  if (snapshot) {
144
- const publishedPage = publishedPagesById.get(snapshot.page.id)!;
145
- publishedState = this.itemStateFromPage(publishedPage, snapshot.pageContentHash);
156
+ publishedState = this.itemStateFromPage(snapshot.page, snapshot.pageContentHash);
146
157
  }
147
158
 
148
159
  // Current state - if content hash is undefined, it means that the page hasn't changed
@@ -165,28 +176,84 @@ export class FrontendVersionRoomYDoc {
165
176
  };
166
177
  }
167
178
 
179
+ private getDeletedPages(
180
+ pages: DocumentationPageV2[],
181
+ pageSnapshots: DocumentationPageSnapshot[]
182
+ ): DocumentationPageV2[] {
183
+ const existingPageIds = new Set(pages.map(p => p.id));
184
+ const latestSnapshots = pickLatestPageSnapshots(pageSnapshots);
185
+ return latestSnapshots.filter(s => !existingPageIds.has(s.page.id)).map(s => s.page);
186
+ }
187
+
188
+ private buildPagePublishedMetadata(
189
+ pages: DocumentationPageV2[],
190
+ pageSnapshots: DocumentationPageSnapshot[]
191
+ ): Map<string, DTODocumentationPublishMetadata> {
192
+ const publishedPageSnapshotsById = mapByUnique(
193
+ pickLatestPageSnapshots(pageSnapshots.filter(s => s.reason === "Publish")),
194
+ s => s.page.id
195
+ );
196
+
197
+ const result = new Map<string, DTODocumentationPublishMetadata>();
198
+
199
+ pages.forEach(p => {
200
+ const publishedSnapshot = publishedPageSnapshotsById.get(p.id);
201
+ if (!publishedSnapshot) return;
202
+
203
+ result.set(p.id, {
204
+ lastPublishedAt: publishedSnapshot.createdAt,
205
+ lastPublishedByUserId: publishedSnapshot.createdByUserId,
206
+ });
207
+ });
208
+
209
+ return result;
210
+ }
211
+
168
212
  //
169
213
  // Drafts - Groups
170
214
  //
171
215
 
172
- private buildGroupDraftStates(
216
+ private buildGroupDraftDeletedStates(
173
217
  groups: ElementGroup[],
174
218
  groupSnapshots: ElementGroupSnapshot[]
175
219
  ): Map<string, DTODocumentationDraftState> {
176
- const doc = new VersionRoomBaseYDoc(this.yDoc);
220
+ const deletedSnapshotsByGroupId = mapByUnique(
221
+ pickLatestGroupSnapshots(groupSnapshots.filter(s => s.reason === "Deletion")),
222
+ s => s.group.id
223
+ );
224
+
225
+ const result = new Map<string, DTODocumentationDraftState>();
226
+
227
+ groups.forEach(group => {
228
+ if (!group.parentPersistentId) return;
229
+
230
+ const deletedSnapshot = deletedSnapshotsByGroupId.get(group.id);
231
+ if (!deletedSnapshot) return;
177
232
 
233
+ result.set(group.id, {
234
+ changeType: "Deleted",
235
+ deletedAt: deletedSnapshot.createdAt,
236
+ deletedByUserId: deletedSnapshot.createdByUserId,
237
+ });
238
+ });
239
+
240
+ return result;
241
+ }
242
+
243
+ private buildGroupDraftCreatedAndUpdatedStates(
244
+ groups: ElementGroup[],
245
+ groupSnapshots: ElementGroupSnapshot[]
246
+ ): Map<string, DTODocumentationDraftState> {
178
247
  // Read room data
179
- const publishedSnapshots = groupSnapshots.filter(s => s.reason === "Publish");
248
+ const publishedSnapshots = pickLatestGroupSnapshots(groupSnapshots.filter(s => s.reason === "Publish"));
180
249
 
181
250
  const publishedSnapshotsByGroupId = mapByUnique(publishedSnapshots, s => s.group.id);
182
- const publishedGroupsById = mapByUnique(
183
- publishedSnapshots.map(s => s.group),
184
- g => g.id
185
- );
186
251
 
187
252
  const result = new Map<string, DTODocumentationDraftState>();
188
253
 
189
254
  groups.forEach(group => {
255
+ if (!group.parentPersistentId) return;
256
+
190
257
  // Current state
191
258
  const currentState: ItemState = this.itemStateFromGroup(group);
192
259
 
@@ -194,8 +261,7 @@ export class FrontendVersionRoomYDoc {
194
261
  const snapshot = publishedSnapshotsByGroupId.get(group.id);
195
262
  let publishedState: ItemState | undefined;
196
263
  if (snapshot) {
197
- const publishedGroup = publishedGroupsById.get(snapshot.group.id)!;
198
- publishedState = this.itemStateFromGroup(publishedGroup);
264
+ publishedState = this.itemStateFromGroup(snapshot.group);
199
265
  }
200
266
 
201
267
  // Calculate draft
@@ -214,6 +280,38 @@ export class FrontendVersionRoomYDoc {
214
280
  };
215
281
  }
216
282
 
283
+ private getDeletedGroups(groups: ElementGroup[], groupSnapshots: ElementGroupSnapshot[]): ElementGroup[] {
284
+ const existingGroupIds = new Set(groups.map(p => p.id));
285
+ const latestSnapshots = pickLatestGroupSnapshots(groupSnapshots);
286
+ return latestSnapshots.filter(s => !existingGroupIds.has(s.group.id)).map(s => s.group);
287
+ }
288
+
289
+ private buildGroupPublishedMetadata(
290
+ groups: ElementGroup[],
291
+ groupSnapshots: ElementGroupSnapshot[]
292
+ ): Map<string, DTODocumentationPublishMetadata> {
293
+ const publishedGroupSnapshotsById = mapByUnique(
294
+ pickLatestGroupSnapshots(groupSnapshots.filter(s => s.reason === "Publish")),
295
+ s => s.group.id
296
+ );
297
+
298
+ const result = new Map<string, DTODocumentationPublishMetadata>();
299
+
300
+ groups.forEach(g => {
301
+ if (!g.parentPersistentId) return;
302
+
303
+ const publishedSnapshot = publishedGroupSnapshotsById.get(g.id);
304
+ if (!publishedSnapshot) return;
305
+
306
+ result.set(g.id, {
307
+ lastPublishedAt: publishedSnapshot.createdAt,
308
+ lastPublishedByUserId: publishedSnapshot.createdByUserId,
309
+ });
310
+ });
311
+
312
+ return result;
313
+ }
314
+
217
315
  //
218
316
  // Drafts - Shared
219
317
  //
@@ -295,4 +393,13 @@ export class FrontendVersionRoomYDoc {
295
393
  const settings = doc.getDocumentationInternalSettings();
296
394
  return settings.isDraftFeatureAdopted;
297
395
  }
396
+
397
+ hasPublishedDocumentationContent() {
398
+ const doc = new VersionRoomBaseYDoc(this.yDoc);
399
+
400
+ return (
401
+ doc.getPageSnapshots().filter(s => s.reason === "Publish").length > 0 ||
402
+ doc.getGroupSnapshots().filter(s => s.reason === "Publish").length > 0
403
+ );
404
+ }
298
405
  }