camox 0.74.1 → 0.76.0

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.
Files changed (32) hide show
  1. package/dist/core/lib/artifactRuntime.js +4 -2
  2. package/dist/features/provider/AuthenticatedCamoxProvider.js +75 -63
  3. package/dist/features/renderer/RendererFrames.js +47 -41
  4. package/dist/features/routes/pageRuntime.d.ts +6 -4
  5. package/dist/features/routes/pageRuntime.js +5 -1
  6. package/dist/features/studio/components/ChatWidget.js +137 -121
  7. package/dist/features/studio/components/ClearThread.js +121 -0
  8. package/dist/features/studio/components/DeleteDraftDialog.js +67 -65
  9. package/dist/features/studio/components/DraftBar.js +60 -49
  10. package/dist/features/studio/components/DraftNotices.js +149 -0
  11. package/dist/features/studio/components/DraftsControl.js +217 -180
  12. package/dist/features/studio/components/DraftsSheet.js +387 -0
  13. package/dist/features/studio/components/PublishedTab.js +511 -0
  14. package/dist/features/studio/components/ReviewDraftDialog.js +104 -89
  15. package/dist/features/studio/components/SheetTable.js +46 -0
  16. package/dist/features/studio/components/Specialists.js +1 -1
  17. package/dist/features/studio/components/ThreadMessages.js +376 -171
  18. package/dist/features/studio/draftLabel.js +15 -1
  19. package/dist/features/studio/draftNotice.js +1 -0
  20. package/dist/features/studio/studioStore.js +5 -5
  21. package/dist/features/studio/useArchiveDraft.js +60 -0
  22. package/dist/features/studio/useRestoreDraft.js +70 -0
  23. package/dist/features/studio/viewSwitch.js +19 -11
  24. package/dist/lib/draft-scope.d.ts +1 -1
  25. package/dist/lib/draft-scope.js +6 -3
  26. package/dist/lib/queries.js +12 -1
  27. package/dist/lib/studio-reads.js +22 -0
  28. package/dist/lib/use-project-room.js +19 -13
  29. package/dist/lib/use-thread.js +1 -1
  30. package/dist/package.js +1 -1
  31. package/dist/studio.css +1 -1
  32. package/package.json +4 -4
@@ -11,6 +11,14 @@ function draftLabel(draft) {
11
11
  if (draft.name) return draft.name;
12
12
  return `${draft.createdBy.name}, ${dayFormat.format(draft.createdAt)}`;
13
13
  }
14
+ /**
15
+ * A Publication's name: its draft's, or for a draft published before it was named, who published
16
+ * it and when, e.g. "Rémi, Oct 13".
17
+ */
18
+ function publicationLabel(publication) {
19
+ if (publication.name) return publication.name;
20
+ return `${publication.publishedBy.name}, ${dayFormat.format(publication.publishedAt)}`;
21
+ }
14
22
  const relative = new Intl.RelativeTimeFormat(void 0, { numeric: "auto" });
15
23
  const units = [
16
24
  ["year", 31536e6],
@@ -26,7 +34,13 @@ function timeAgo(at, now = Date.now()) {
26
34
  for (const [unit, size] of units) if (elapsed >= size) return relative.format(-Math.round(elapsed / size), unit);
27
35
  return relative.format(0, "minute");
28
36
  }
37
+ const DAY_MS = 864e5;
38
+ /** How long until a moment, in whole days: "12 days", or "Less than a day" on its last day. */
39
+ function daysLeft(until, now = Date.now()) {
40
+ const days = Math.floor((until - now) / DAY_MS);
41
+ return days < 1 ? "Less than a day" : plural(days, "day");
42
+ }
29
43
  const plural = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
30
44
 
31
45
  //#endregion
32
- export { draftLabel, plural, timeAgo };
46
+ export { daysLeft, draftLabel, plural, publicationLabel, timeAgo };
@@ -5,6 +5,7 @@ function draftNoticeMessage(notice) {
5
5
  case "missing": return "This draft doesn't exist, so you're viewing Live.";
6
6
  case "forbidden": return "You can't open this draft, so you're viewing Live.";
7
7
  case "ended": return `${notice.endedBy.name} ${notice.outcome} this draft, so you're viewing Live.`;
8
+ case "archived": return `${notice.archivedBy.name} archived this draft, so you're viewing Live.`;
8
9
  }
9
10
  }
10
11
 
@@ -6,7 +6,7 @@ const studioStore = createStore({
6
6
  isCommandPaletteOpen: false,
7
7
  commandPalettePages: [],
8
8
  isReviewDraftDialogOpen: false,
9
- isDeleteDraftDialogOpen: false,
9
+ isDraftsSheetOpen: false,
10
10
  localeDialog: null,
11
11
  websiteLocation: null,
12
12
  agentMessages: [],
@@ -58,16 +58,16 @@ const studioStore = createStore({
58
58
  isReviewDraftDialogOpen: false
59
59
  };
60
60
  },
61
- openDeleteDraftDialog: (context) => {
61
+ openDraftsSheet: (context) => {
62
62
  return {
63
63
  ...context,
64
- isDeleteDraftDialogOpen: true
64
+ isDraftsSheetOpen: true
65
65
  };
66
66
  },
67
- closeDeleteDraftDialog: (context) => {
67
+ closeDraftsSheet: (context) => {
68
68
  return {
69
69
  ...context,
70
- isDeleteDraftDialogOpen: false
70
+ isDraftsSheetOpen: false
71
71
  };
72
72
  },
73
73
  openLocaleDialog: (context, event) => {
@@ -0,0 +1,60 @@
1
+ import { draftMutations } from "../../lib/queries.js";
2
+ import { useLeaveDraft } from "./viewSwitch.js";
3
+ import { c } from "react/compiler-runtime";
4
+ import { useMutation } from "@tanstack/react-query";
5
+ import { toast } from "@camox/ui/toaster";
6
+
7
+ //#region src/features/studio/useArchiveDraft.ts
8
+ /**
9
+ * Archives the draft the studio views, then switches to Live (ADR 0046). Archiving loses nothing
10
+ * and any member can restore the draft, so it asks for no confirmation.
11
+ */
12
+ function useArchiveDraft(projectId) {
13
+ const $ = c(9);
14
+ const leaveDraft = useLeaveDraft();
15
+ let t0;
16
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
17
+ t0 = draftMutations.archive();
18
+ $[0] = t0;
19
+ } else t0 = $[0];
20
+ let t1;
21
+ if ($[1] !== leaveDraft) {
22
+ t1 = {
23
+ ...t0,
24
+ onSuccess: (t2) => {
25
+ const { archived } = t2;
26
+ return leaveDraft(archived);
27
+ },
28
+ onError: _temp
29
+ };
30
+ $[1] = leaveDraft;
31
+ $[2] = t1;
32
+ } else t1 = $[2];
33
+ const archive = useMutation(t1);
34
+ let t2;
35
+ if ($[3] !== archive || $[4] !== projectId) {
36
+ t2 = () => {
37
+ if (projectId) archive.mutate({ projectId });
38
+ };
39
+ $[3] = archive;
40
+ $[4] = projectId;
41
+ $[5] = t2;
42
+ } else t2 = $[5];
43
+ let t3;
44
+ if ($[6] !== archive.isPending || $[7] !== t2) {
45
+ t3 = {
46
+ archive: t2,
47
+ archiving: archive.isPending
48
+ };
49
+ $[6] = archive.isPending;
50
+ $[7] = t2;
51
+ $[8] = t3;
52
+ } else t3 = $[8];
53
+ return t3;
54
+ }
55
+ function _temp(error) {
56
+ return toast.error(error.message || "Could not archive the draft");
57
+ }
58
+
59
+ //#endregion
60
+ export { useArchiveDraft };
@@ -0,0 +1,70 @@
1
+ import { useNavigate } from "../navigation/navigation.js";
2
+ import { draftMutations } from "../../lib/queries.js";
3
+ import { useSwitchView } from "./viewSwitch.js";
4
+ import { c } from "react/compiler-runtime";
5
+ import { useMutation } from "@tanstack/react-query";
6
+ import { toast } from "@camox/ui/toaster";
7
+
8
+ //#region src/features/studio/useRestoreDraft.ts
9
+ /**
10
+ * Restores an archived draft, then takes the member into it on the same page (ADR 0046): the
11
+ * draft is back as it was, so they go on where it stopped. `label` names it in the toast.
12
+ */
13
+ function useRestoreDraft(projectId, options) {
14
+ const $ = c(10);
15
+ const navigate = useNavigate();
16
+ const switchView = useSwitchView();
17
+ let t0;
18
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
19
+ t0 = {
20
+ ...draftMutations.restore(),
21
+ onError: _temp
22
+ };
23
+ $[0] = t0;
24
+ } else t0 = $[0];
25
+ const restore = useMutation(t0);
26
+ let t1;
27
+ if ($[1] !== navigate || $[2] !== options || $[3] !== projectId || $[4] !== restore || $[5] !== switchView) {
28
+ t1 = (draftId, t2) => {
29
+ const label = t2 === void 0 ? "the draft" : t2;
30
+ if (!projectId) return;
31
+ restore.mutate({
32
+ projectId,
33
+ draftId
34
+ }, { onSuccess: () => {
35
+ options?.onRestored?.();
36
+ switchView(() => navigate({
37
+ to: window.location.href,
38
+ draft: draftId
39
+ }), {
40
+ loading: `Restoring ${label}…`,
41
+ success: `Restored ${label}. You're now viewing it.`
42
+ });
43
+ } });
44
+ };
45
+ $[1] = navigate;
46
+ $[2] = options;
47
+ $[3] = projectId;
48
+ $[4] = restore;
49
+ $[5] = switchView;
50
+ $[6] = t1;
51
+ } else t1 = $[6];
52
+ const t2 = restore.isPending ? restore.variables?.draftId : void 0;
53
+ let t3;
54
+ if ($[7] !== t1 || $[8] !== t2) {
55
+ t3 = {
56
+ restore: t1,
57
+ restoring: t2
58
+ };
59
+ $[7] = t1;
60
+ $[8] = t2;
61
+ $[9] = t3;
62
+ } else t3 = $[9];
63
+ return t3;
64
+ }
65
+ function _temp(error) {
66
+ return toast.error(error.message || "Could not restore the draft");
67
+ }
68
+
69
+ //#endregion
70
+ export { useRestoreDraft };
@@ -4,6 +4,7 @@ import { c } from "react/compiler-runtime";
4
4
  import { toast } from "@camox/ui/toaster";
5
5
 
6
6
  //#region src/features/studio/viewSwitch.ts
7
+ const SWITCH_FAILED = "Something went wrong while switching. Reload the page to try again.";
7
8
  /**
8
9
  * Tells the editor the studio heard them while it switches to another draft or locale: the
9
10
  * studio keeps showing what it showed until the next view is ready, then shows it at once.
@@ -12,7 +13,7 @@ import { toast } from "@camox/ui/toaster";
12
13
  function toastSwitch(switching, messages) {
13
14
  toast.promise(switching, {
14
15
  ...messages,
15
- error: "Something went wrong while switching. Reload the page to try again."
16
+ error: SWITCH_FAILED
16
17
  });
17
18
  }
18
19
  /**
@@ -31,9 +32,10 @@ function useSwitchView() {
31
32
  return t0;
32
33
  }
33
34
  /**
34
- * Switches the studio to Live once the draft it views ended, by this member, here or in another
35
- * tab, replacing the draft's URL entry. Called once the studio already left the draft, it does
36
- * nothing.
35
+ * Switches the studio to Live once the draft it views ended or was archived, by this member, here
36
+ * or in another tab, replacing the draft's URL entry. An ended draft's dialog already showed it
37
+ * working, so its toast only says so once Live shows; an archived draft's toast shows meanwhile
38
+ * too. Called once the studio already left the draft, it does nothing.
37
39
  */
38
40
  function useLeaveDraft() {
39
41
  const $ = c(3);
@@ -41,14 +43,17 @@ function useLeaveDraft() {
41
43
  const shown = useViewShown();
42
44
  let t0;
43
45
  if ($[0] !== draftScope || $[1] !== shown) {
44
- t0 = (ended) => {
45
- if (ended && draftScope.current() !== ended.draftId) return;
46
- draftScope.leave(ended);
47
- if (!ended) return;
48
- const what = `Draft ${ended.outcome}`;
46
+ t0 = (left) => {
47
+ if (left && draftScope.current() !== left.draftId) return;
48
+ draftScope.leave(left);
49
+ if (!left) return;
50
+ if ("outcome" in left) {
51
+ shown().then(() => toast.success(`Draft ${left.outcome}. You're now viewing Live.`), _temp);
52
+ return;
53
+ }
49
54
  toastSwitch(shown(), {
50
- loading: `${what}. Switching to Live…`,
51
- success: `${what}. You're now viewing Live.`
55
+ loading: "Draft archived. Switching to Live…",
56
+ success: "Draft archived. You're now viewing Live."
52
57
  });
53
58
  };
54
59
  $[0] = draftScope;
@@ -57,6 +62,9 @@ function useLeaveDraft() {
57
62
  } else t0 = $[2];
58
63
  return t0;
59
64
  }
65
+ function _temp() {
66
+ return toast.error(SWITCH_FAILED);
67
+ }
60
68
 
61
69
  //#endregion
62
70
  export { useLeaveDraft, useSwitchView };
@@ -1,5 +1,5 @@
1
1
  import "./locale-path.js";
2
2
  import "../features/routes/pageRuntime.js";
3
3
  import "react";
4
- import { EndedDraft } from "@camox/api-contract/drafts";
4
+ import { ArchivedDraft, EndedDraft } from "@camox/api-contract/drafts";
5
5
  import { QueryClient } from "@tanstack/react-query";
@@ -136,14 +136,17 @@ function createDraftScope({ queryClient, browser }) {
136
136
  }
137
137
  notify("adopted");
138
138
  },
139
- leave(ended, { notice = false } = {}) {
139
+ leave(left, { notice = false } = {}) {
140
140
  queryClient.invalidateQueries({ queryKey: ["camox"] });
141
141
  pending = null;
142
142
  current = null;
143
143
  replaceUrl(null);
144
- if (notice && ended) report({
144
+ if (notice && left) report("outcome" in left ? {
145
145
  reason: "ended",
146
- ...ended
146
+ ...left
147
+ } : {
148
+ reason: "archived",
149
+ ...left
147
150
  });
148
151
  notify("left");
149
152
  },
@@ -42,8 +42,19 @@ const projectMutations = {
42
42
  await sendFaviconChange(String(projectId), { method: "DELETE" });
43
43
  } })
44
44
  };
45
+ const publicationMutations = {
46
+ /** Starts a draft reverting a Publication (ADR 0045), named in the input. */
47
+ revert: () => getOrpc().publications.revert.mutationOptions(),
48
+ /** Clears a Publication's thread for good, named in the input (ADR 0045). */
49
+ clearThread: () => getOrpc().publications.clearThread.mutationOptions()
50
+ };
45
51
  const draftMutations = {
46
52
  publish: () => getOrpc().drafts.publish.mutationOptions(),
53
+ /** Archives the draft the studio views (ADR 0046). */
54
+ archive: () => getOrpc().drafts.archive.mutationOptions(),
55
+ /** Restores an archived draft, named in the input. */
56
+ restore: () => getOrpc().drafts.restore.mutationOptions(),
57
+ /** Deletes an archived draft for good, named in the input. */
47
58
  delete: () => getOrpc().drafts.delete.mutationOptions(),
48
59
  /** Requests the draft's overview again right away, once it failed. */
49
60
  retryOverview: () => getOrpc().drafts.retryOverview.mutationOptions(),
@@ -138,4 +149,4 @@ const fileMutations = {
138
149
  };
139
150
 
140
151
  //#endregion
141
- export { blockMutations, collectionMutations, draftMutations, fileMutations, localeMutations, pageMutations, projectMutations, repeatableItemMutations, specialistMutations };
152
+ export { blockMutations, collectionMutations, draftMutations, fileMutations, localeMutations, pageMutations, projectMutations, publicationMutations, repeatableItemMutations, specialistMutations };
@@ -197,6 +197,28 @@ const reads = {
197
197
  fetch: ({ api, signal }, { projectId }) => api.drafts.list({ projectId }, { signal }),
198
198
  staleTime: Infinity
199
199
  }),
200
+ archivedDrafts: entry({
201
+ key: ({ projectId }) => [...queryKeys.drafts.archived, projectId],
202
+ shared: true,
203
+ fetch: async ({ api, signal }, { projectId }) => (await api.drafts.listArchived({ projectId }, { signal })).drafts,
204
+ staleTime: Infinity
205
+ }),
206
+ publications: entry({
207
+ key: ({ projectId }) => [...queryKeys.publications.list, projectId],
208
+ shared: true,
209
+ fetch: async ({ api, signal }, { projectId }) => (await api.publications.list({ projectId }, { signal })).publications,
210
+ staleTime: Infinity
211
+ }),
212
+ publication: entry({
213
+ key: ({ projectId, id }) => [
214
+ ...queryKeys.publications.get,
215
+ projectId,
216
+ id
217
+ ],
218
+ shared: true,
219
+ fetch: async ({ api, signal }, input) => (await api.publications.get(input, { signal })).publication,
220
+ staleTime: Infinity
221
+ }),
200
222
  draft: entry({
201
223
  key: ({ draftId }) => [...queryKeys.drafts.get, draftId],
202
224
  shared: true,
@@ -11,14 +11,14 @@ const DEBOUNCE_MS = 300;
11
11
  * wherever they change nothing. The studio cache applies each one to the reads it reaches.
12
12
  */
13
13
  function useProjectRoom(apiUrl, projectId, studioCache, t0) {
14
- const $ = c(13);
14
+ const $ = c(14);
15
15
  let t1;
16
16
  if ($[0] !== t0) {
17
17
  t1 = t0 === void 0 ? {} : t0;
18
18
  $[0] = t0;
19
19
  $[1] = t1;
20
20
  } else t1 = $[1];
21
- const { onDraftEnded, onRevision } = t1;
21
+ const { onDraftEnded, onDraftArchived, onRevision } = t1;
22
22
  const timerRef = useRef(void 0);
23
23
  let t2;
24
24
  if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
@@ -35,7 +35,7 @@ function useProjectRoom(apiUrl, projectId, studioCache, t0) {
35
35
  const t4 = String(projectId ?? "");
36
36
  const t5 = !!projectId;
37
37
  let t6;
38
- if ($[5] !== onDraftEnded || $[6] !== onRevision || $[7] !== projectId || $[8] !== studioCache || $[9] !== t3 || $[10] !== t4 || $[11] !== t5) {
38
+ if ($[5] !== onDraftArchived || $[6] !== onDraftEnded || $[7] !== onRevision || $[8] !== projectId || $[9] !== studioCache || $[10] !== t3 || $[11] !== t4 || $[12] !== t5) {
39
39
  t6 = {
40
40
  ...t3,
41
41
  party: "project-room",
@@ -63,8 +63,13 @@ function useProjectRoom(apiUrl, projectId, studioCache, t0) {
63
63
  onDraftEnded?.(ended);
64
64
  return;
65
65
  }
66
+ if (data.type === "draftArchived") {
67
+ const { type: _type_0, ...archived } = data;
68
+ onDraftArchived?.(archived);
69
+ return;
70
+ }
66
71
  if (data.type === "revision") {
67
- const { type: _type_0, ...revision } = data;
72
+ const { type: _type_1, ...revision } = data;
68
73
  onRevision?.(revision);
69
74
  return;
70
75
  }
@@ -78,15 +83,16 @@ function useProjectRoom(apiUrl, projectId, studioCache, t0) {
78
83
  }, DEBOUNCE_MS);
79
84
  }
80
85
  };
81
- $[5] = onDraftEnded;
82
- $[6] = onRevision;
83
- $[7] = projectId;
84
- $[8] = studioCache;
85
- $[9] = t3;
86
- $[10] = t4;
87
- $[11] = t5;
88
- $[12] = t6;
89
- } else t6 = $[12];
86
+ $[5] = onDraftArchived;
87
+ $[6] = onDraftEnded;
88
+ $[7] = onRevision;
89
+ $[8] = projectId;
90
+ $[9] = studioCache;
91
+ $[10] = t3;
92
+ $[11] = t4;
93
+ $[12] = t5;
94
+ $[13] = t6;
95
+ } else t6 = $[13];
90
96
  usePartySocket(t6);
91
97
  }
92
98
 
@@ -147,7 +147,7 @@ function useThread(apiUrl, projectId, draftId, t0) {
147
147
  if (asked(frame_1.startedDraft)) {
148
148
  handedOff.set(threadKey(apiUrl, projectId, frame_1.startedDraft.draftId), {
149
149
  sent,
150
- view: runOf(threadViewOf(latest.current))
150
+ ...!frame_1.startedDraft.restored && { view: runOf(threadViewOf(latest.current)) }
151
151
  });
152
152
  onEnterDraft?.(frame_1.startedDraft.draftId);
153
153
  }
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.74.1";
2
+ var version = "0.76.0";
3
3
 
4
4
  //#endregion
5
5
  export { version };