alinea 2.0.0-preview.11 → 2.0.0-preview.13

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 (268) hide show
  1. package/dist/.init/content/pages/welcome.json +1 -1
  2. package/dist/.mcp/later/dev.json +1 -1
  3. package/dist/.mcp/online/dev.json +1 -1
  4. package/dist/adapter/next/cms.js +16 -8
  5. package/dist/adapter/next/dashboardUrl.d.ts +6 -0
  6. package/dist/adapter/next/dashboardUrl.js +13 -0
  7. package/dist/adapter/next/syncCheck.d.ts +15 -2
  8. package/dist/adapter/next/syncCheck.js +26 -9
  9. package/dist/backend/Handler.js +5 -1
  10. package/dist/backend/api/BlobStore.d.ts +21 -0
  11. package/dist/backend/api/BlobStore.js +63 -0
  12. package/dist/backend/api/CreateBackend.js +10 -8
  13. package/dist/backend/api/DatabaseApi.d.ts +4 -1
  14. package/dist/backend/api/DatabaseApi.js +88 -4
  15. package/dist/backend/api/GithubApi.js +47 -24
  16. package/dist/backend/api/database.js +6 -6
  17. package/dist/bundled.d.ts +687 -248
  18. package/dist/chunks/{bun-sqlite-5SHXN3AT.js → bun-sqlite-RI6BYU5S.js} +3 -3
  19. package/dist/chunks/{chunk-YCQR5GRT.js → chunk-3QFSGTGF.js} +2 -3
  20. package/dist/chunks/{chunk-QJGGYF6E.js → chunk-4ZA6KZHS.js} +5 -5
  21. package/dist/chunks/{chunk-ASYKC6IE.js → chunk-5H4D5JQL.js} +1 -1
  22. package/dist/chunks/{chunk-ETWOY4AL.js → chunk-A2I6MCCS.js} +1 -1
  23. package/dist/chunks/{chunk-W5AXN7GH.js → chunk-CYVOGRSD.js} +1 -2
  24. package/dist/chunks/{chunk-TIJTT5ZI.js → chunk-G43W7AME.js} +1 -0
  25. package/dist/chunks/{chunk-RE523BQ7.js → chunk-HOETF6HN.js} +5829 -5830
  26. package/dist/chunks/{chunk-AIU6CMVT.js → chunk-PTYZMV5G.js} +1 -1
  27. package/dist/chunks/{chunk-JOF663EG.js → chunk-QHHMKJUO.js} +1 -1
  28. package/dist/chunks/{chunk-66YZ47TY.js → chunk-RAR44T5O.js} +1 -1
  29. package/dist/chunks/{chunk-QSN6RH6X.js → chunk-VA6NC3MI.js} +1 -1
  30. package/dist/chunks/{node-sqlite-XVNTNMCY.js → node-sqlite-C7VJ2NSN.js} +3 -3
  31. package/dist/cli/Mcp.js +1 -1
  32. package/dist/cli/Serve.js +1 -1
  33. package/dist/cli/bin.js +2 -3
  34. package/dist/cli/generate/GenerateDashboard.js +17 -5
  35. package/dist/cli/serve/mcp/DevMcp.js +1 -1
  36. package/dist/cli/serve/mcp/McpTools.js +29 -1
  37. package/dist/cli/static/dashboard/entry.js +2 -1
  38. package/dist/cloud/CloudRemote.js +1 -1
  39. package/dist/components/Alert.js +3 -3
  40. package/dist/components/AppShell.js +2 -2
  41. package/dist/components/Badge.d.ts +3 -1
  42. package/dist/components/Badge.js +5 -3
  43. package/dist/components/Breadcrumbs.js +6 -6
  44. package/dist/components/Button.d.ts +6 -1
  45. package/dist/components/Button.js +14 -5
  46. package/dist/components/Calendar.js +20 -20
  47. package/dist/components/Checkbox.js +9 -9
  48. package/dist/components/CheckboxGroup.js +4 -4
  49. package/dist/components/Collapsible.js +3 -3
  50. package/dist/components/ColorSwatch.js +2 -2
  51. package/dist/components/ColorSwatchPicker.js +2 -2
  52. package/dist/components/ComboBox.js +7 -7
  53. package/dist/components/Command.js +10 -9
  54. package/dist/components/ContentCard.d.ts +3 -1
  55. package/dist/components/ContentCard.js +39 -17
  56. package/dist/components/ContentGrid.js +15 -15
  57. package/dist/components/DataList.js +2 -2
  58. package/dist/components/DateField.js +2 -2
  59. package/dist/components/DatePicker.js +2 -2
  60. package/dist/components/DateRangePicker.js +2 -2
  61. package/dist/components/Dialog.js +6 -6
  62. package/dist/components/DropZone.js +4 -4
  63. package/dist/components/DropdownMenu.js +10 -10
  64. package/dist/components/Empty.js +4 -4
  65. package/dist/components/Field.js +8 -8
  66. package/dist/components/List.js +23 -23
  67. package/dist/components/MediaPreview.js +5 -5
  68. package/dist/components/MultipleSelect.js +20 -20
  69. package/dist/components/NavRail.js +4 -4
  70. package/dist/components/NumberField.js +5 -5
  71. package/dist/components/Page.js +5 -5
  72. package/dist/components/Popover.js +2 -2
  73. package/dist/components/PreviewFrame.js +5 -5
  74. package/dist/components/RadioGroup.js +6 -6
  75. package/dist/components/Resizable.js +5 -5
  76. package/dist/components/SearchField.d.ts +6 -1
  77. package/dist/components/SearchField.js +7 -5
  78. package/dist/components/Select.js +14 -14
  79. package/dist/components/Sheet.d.ts +50 -0
  80. package/dist/components/Sheet.js +202 -0
  81. package/dist/components/Sidebar.js +4 -4
  82. package/dist/components/SortableList.d.ts +34 -30
  83. package/dist/components/SortableList.js +162 -265
  84. package/dist/components/Spinner.js +1 -1
  85. package/dist/components/Surface.js +2 -2
  86. package/dist/components/Switch.js +7 -7
  87. package/dist/components/Table.d.ts +3 -1
  88. package/dist/components/Table.js +53 -42
  89. package/dist/components/Tabs.js +5 -5
  90. package/dist/components/TagGroup.js +9 -9
  91. package/dist/components/TextField.js +10 -10
  92. package/dist/components/TimeField.js +8 -8
  93. package/dist/components/Timestamp.js +1 -1
  94. package/dist/components/ToggleGroup.js +2 -2
  95. package/dist/components/Toolbar.js +4 -4
  96. package/dist/components/Tooltip.js +5 -5
  97. package/dist/components/Tree.js +29 -29
  98. package/dist/components/internal/DateControl.js +10 -10
  99. package/dist/components/internal/ListBoxOption.js +13 -13
  100. package/dist/components/internal/Locale.js +2 -2
  101. package/dist/components/internal/PopoverSurface.js +2 -2
  102. package/dist/components/internal/SelectionCheckbox.js +6 -6
  103. package/dist/components/internal/Slot.js +1 -1
  104. package/dist/components/internal/ToggleButton.js +3 -3
  105. package/dist/components/internal/Trigger.js +2 -2
  106. package/dist/components/internal/sortableDrag.d.ts +19 -0
  107. package/dist/components/internal/sortableDrag.js +175 -0
  108. package/dist/components/internal/useDragDrop.js +2 -2
  109. package/dist/components.d.ts +1 -0
  110. package/dist/components.js +1 -0
  111. package/dist/core/Config.d.ts +6 -1
  112. package/dist/core/Document.d.ts +9 -1
  113. package/dist/core/Document.js +31 -6
  114. package/dist/core/Overview.d.ts +2 -4
  115. package/dist/core/TextDoc.d.ts +4 -0
  116. package/dist/core/Validation.d.ts +0 -1
  117. package/dist/core/Validation.js +0 -6
  118. package/dist/core/field/RichTextField.js +23 -4
  119. package/dist/core/media/MediaTypes.js +1 -1
  120. package/dist/core/source/GithubSource.d.ts +4 -0
  121. package/dist/core/source/GithubSource.js +127 -25
  122. package/dist/core/source/GithubUsage.d.ts +21 -0
  123. package/dist/core/source/GithubUsage.js +47 -0
  124. package/dist/dashboard/App.d.ts +2 -2
  125. package/dist/dashboard/App.js +5 -3
  126. package/dist/dashboard/ViewKeys.d.ts +1 -0
  127. package/dist/dashboard/ViewKeys.js +1 -0
  128. package/dist/dashboard/app/ActivityStatus.js +11 -11
  129. package/dist/dashboard/app/AuthView.js +5 -5
  130. package/dist/dashboard/app/BlockSheet.d.ts +50 -0
  131. package/dist/dashboard/app/BlockSheet.js +207 -0
  132. package/dist/dashboard/app/BlockSheetStoryFrame.d.ts +6 -0
  133. package/dist/dashboard/app/BlockSheetStoryFrame.js +29 -0
  134. package/dist/dashboard/app/CompactField.js +15 -15
  135. package/dist/dashboard/app/CroppedImage.js +2 -2
  136. package/dist/dashboard/app/DashboardErrorBoundary.js +3 -3
  137. package/dist/dashboard/app/DashboardLayout.js +3 -1
  138. package/dist/dashboard/app/EntryHeader.js +32 -33
  139. package/dist/dashboard/app/EntryReferences.js +5 -5
  140. package/dist/dashboard/app/EntrySidebar.js +8 -8
  141. package/dist/dashboard/app/EntrySidebarPreview.js +4 -4
  142. package/dist/dashboard/app/EntryStatusIcon.d.ts +23 -0
  143. package/dist/dashboard/app/EntryStatusIcon.js +58 -0
  144. package/dist/dashboard/app/EntryValidationModal.d.ts +6 -5
  145. package/dist/dashboard/app/EntryValidationModal.js +18 -18
  146. package/dist/dashboard/app/Explorer.d.ts +3 -1
  147. package/dist/dashboard/app/Explorer.js +51 -47
  148. package/dist/dashboard/app/ExplorerBatchActions.d.ts +3 -1
  149. package/dist/dashboard/app/ExplorerBatchActions.js +68 -43
  150. package/dist/dashboard/app/ExplorerCards.js +7 -1
  151. package/dist/dashboard/app/ExplorerControls.js +6 -6
  152. package/dist/dashboard/app/ExplorerList.js +5 -5
  153. package/dist/dashboard/app/ExplorerModal.js +1 -1
  154. package/dist/dashboard/app/ExplorerTable.js +14 -11
  155. package/dist/dashboard/app/ImageEditor.js +25 -25
  156. package/dist/dashboard/app/ImagePicker.js +3 -13
  157. package/dist/dashboard/app/LinkPicker.js +29 -27
  158. package/dist/dashboard/app/LocaleMenu.js +7 -7
  159. package/dist/dashboard/app/MediaPreviewCell.js +2 -2
  160. package/dist/dashboard/app/MoveConfirmDialog.d.ts +5 -0
  161. package/dist/dashboard/app/MoveConfirmDialog.js +79 -0
  162. package/dist/dashboard/app/OverviewCell.js +8 -13
  163. package/dist/dashboard/app/ReadOnlyBadge.d.ts +1 -1
  164. package/dist/dashboard/app/RemoveDialog.d.ts +17 -0
  165. package/dist/dashboard/app/{DeleteDialog.js → RemoveDialog.js} +67 -27
  166. package/dist/dashboard/app/RemoveRedirect.d.ts +7 -0
  167. package/dist/dashboard/app/RemoveRedirect.js +123 -0
  168. package/dist/dashboard/app/SearchBar.d.ts +9 -0
  169. package/dist/dashboard/app/SearchBar.js +31 -0
  170. package/dist/dashboard/app/SidebarTree.js +29 -74
  171. package/dist/dashboard/app/WorkspaceMenu.js +8 -8
  172. package/dist/dashboard/app/WorkspaceRoots.js +11 -11
  173. package/dist/dashboard/app/editor/FileEditor.js +16 -16
  174. package/dist/dashboard/app/modals/CreateEntry.js +5 -5
  175. package/dist/dashboard/app/pages/EntryPage.d.ts +2 -1
  176. package/dist/dashboard/app/pages/EntryPage.js +35 -16
  177. package/dist/dashboard/app/pages/SplashPage.js +35 -35
  178. package/dist/dashboard/app/pages/UsersPage.js +14 -14
  179. package/dist/dashboard/app/ui/DashboardModal.js +17 -16
  180. package/dist/dashboard/atoms/dashboard.js +1 -1
  181. package/dist/dashboard/atoms/entry.js +1 -14
  182. package/dist/dashboard/atoms/entryTable.js +1 -0
  183. package/dist/dashboard/atoms/explorer.d.ts +4 -0
  184. package/dist/dashboard/atoms/explorer.js +19 -7
  185. package/dist/dashboard/atoms/graph.js +1 -1
  186. package/dist/dashboard/atoms/link.js +5 -2
  187. package/dist/dashboard/atoms/move.d.ts +5 -0
  188. package/dist/dashboard/atoms/move.js +29 -3
  189. package/dist/dashboard/atoms/moveConfirmation.d.ts +34 -0
  190. package/dist/dashboard/atoms/moveConfirmation.js +95 -0
  191. package/dist/dashboard/atoms/nav.d.ts +9 -5
  192. package/dist/dashboard/atoms/nav.js +120 -45
  193. package/dist/dashboard/atoms/overview.d.ts +6 -5
  194. package/dist/dashboard/atoms/overview.js +2 -16
  195. package/dist/dashboard/atoms/remove.d.ts +112 -0
  196. package/dist/dashboard/atoms/remove.js +268 -0
  197. package/dist/dashboard/atoms/root.d.ts +2 -2
  198. package/dist/dashboard/atoms/root.js +41 -26
  199. package/dist/dashboard/atoms/user.js +1 -1
  200. package/dist/dashboard/atoms/utils.js +1 -1
  201. package/dist/dashboard/boot/Boot.js +25 -17
  202. package/dist/dashboard/boot/BootProd.d.ts +6 -1
  203. package/dist/dashboard/boot/BootProd.js +2 -2
  204. package/dist/dashboard/boot/DashboardWorker.d.ts +1 -0
  205. package/dist/dashboard/boot/DashboardWorker.js +18 -18
  206. package/dist/dashboard/boot/LoadWorker.js +17 -10
  207. package/dist/dashboard/cms/EntryTable.js +6 -3
  208. package/dist/database/BrowserEntryStore.d.ts +17 -29
  209. package/dist/database/BrowserEntryStore.js +116 -144
  210. package/dist/database/DatabaseTables.js +2 -2
  211. package/dist/database/EntryDatabase.js +21 -1
  212. package/dist/database/EntryTransaction.js +0 -35
  213. package/dist/database/EntryUrlAliases.d.ts +4 -0
  214. package/dist/database/EntryUrlAliases.js +16 -10
  215. package/dist/database/driver/RuntimeDatabase.js +3 -3
  216. package/dist/database/driver/WasmDatabase.d.ts +5 -18
  217. package/dist/database/driver/WasmDatabase.js +15 -16
  218. package/dist/database/entry/EntryTable.js +2 -2
  219. package/dist/database/query/EntryQuery.js +1 -1
  220. package/dist/database/query/Relation.js +1 -1
  221. package/dist/database/query/Search.js +2 -2
  222. package/dist/field/check/CheckField.view.js +2 -2
  223. package/dist/field/code/CodeField.view.js +14 -14
  224. package/dist/field/link/EntryLink.d.ts +4 -4
  225. package/dist/field/link/FileLink.d.ts +4 -4
  226. package/dist/field/link/ImageLink.d.ts +4 -4
  227. package/dist/field/link/Link.d.ts +1 -1
  228. package/dist/field/link/LinkField.d.ts +5 -0
  229. package/dist/field/link/LinkField.view.js +246 -372
  230. package/dist/field/link/UrlLink.d.ts +4 -4
  231. package/dist/field/list/ListField.d.ts +2 -0
  232. package/dist/field/list/ListField.view.d.ts +15 -0
  233. package/dist/field/list/ListField.view.js +343 -301
  234. package/dist/field/metadata/MetadataField.d.ts +17 -7
  235. package/dist/field/metadata/MetadataField.js +14 -3
  236. package/dist/field/metadata/MetadataField.view.d.ts +1 -0
  237. package/dist/field/metadata/MetadataField.view.js +27 -23
  238. package/dist/field/richtext/RichTextBlock.d.ts +3 -1
  239. package/dist/field/richtext/RichTextBlock.js +178 -75
  240. package/dist/field/richtext/RichTextBlockHost.js +1 -1
  241. package/dist/field/richtext/RichTextExtensions.js +35 -35
  242. package/dist/field/richtext/RichTextField.view.js +14 -8
  243. package/dist/field/richtext/RichTextInsertMenu.js +43 -27
  244. package/dist/field/views.js +2 -0
  245. package/dist/index.css +490 -309
  246. package/dist/picker/url/UrlPicker.js +1 -0
  247. package/docs/components/badge.md +1 -0
  248. package/docs/components/button.md +1 -0
  249. package/docs/components/content-card.md +1 -0
  250. package/docs/components/search-field.md +1 -0
  251. package/docs/components/sortable-list.md +5 -20
  252. package/docs/components/table.md +1 -0
  253. package/docs/configuration.md +1 -1
  254. package/docs/deploy/self-host.md +1 -1
  255. package/docs/deploy.md +5 -4
  256. package/docs/fields.md +1 -1
  257. package/docs/index.md +1 -1
  258. package/docs/instant-publishing.md +7 -22
  259. package/docs/overviews.md +3 -4
  260. package/docs/upgrading.md +3 -2
  261. package/llms-full.txt +31 -55
  262. package/package.json +2 -3
  263. package/dist/core/db/EntryValidationError.d.ts +0 -13
  264. package/dist/core/db/EntryValidationError.js +0 -23
  265. package/dist/dashboard/app/DeleteDialog.d.ts +0 -11
  266. package/dist/dashboard/atoms/delete.d.ts +0 -68
  267. package/dist/dashboard/atoms/delete.js +0 -143
  268. package/dist/chunks/{chunk-MOWR65LM.js → chunk-PYDOMKJZ.js} +71 -71
@@ -0,0 +1,112 @@
1
+ import type { Config } from 'alinea/core/Config';
2
+ import type { EntryReference } from 'alinea/core/db/EntryReference';
3
+ import type { EntryFields } from 'alinea/core/EntryFields';
4
+ import type { Filter } from 'alinea/core/Filter';
5
+ import type { Policy } from 'alinea/core/Role';
6
+ import { type Atom, type PrimitiveAtom } from 'jotai';
7
+ import { type EntryReferenceWithSource } from './entry.js';
8
+ import type { ExplorerItemData, ExplorerLimitLocation } from './explorer.js';
9
+ /** Deleting removes the entries, archiving unpublishes them until restored */
10
+ export type RemoveAction = 'delete' | 'archive';
11
+ /** An entry that is about to be removed, in the locale it is shown in */
12
+ export interface RemoveSubject {
13
+ id: string;
14
+ title: string;
15
+ type: string;
16
+ workspace: string;
17
+ root: string;
18
+ /** Null for entries without languages */
19
+ locale: string | null;
20
+ parents: Array<string>;
21
+ hasChildren: boolean;
22
+ }
23
+ /** A translation that is removed, a null locale removes every version */
24
+ export interface Removal {
25
+ id: string;
26
+ locale: string | null;
27
+ }
28
+ /** An entry that is removed with the translation of its parent */
29
+ export interface RemovedDescendant extends Removal {
30
+ subjectId: string;
31
+ }
32
+ /** The public URL of a removed translation of a page */
33
+ export interface RemovedUrl extends Removal {
34
+ url: string;
35
+ }
36
+ /** The page the URLs of the removed pages redirect to */
37
+ export interface RedirectTarget {
38
+ id: string;
39
+ title: string;
40
+ url: string;
41
+ /** The languages of its versions, null for an entry without languages */
42
+ locales: Array<string | null>;
43
+ }
44
+ /** Which languages of the entries are removed and the links that will break */
45
+ export interface RemovePlan {
46
+ subjects: Array<RemoveSubject>;
47
+ /** The languages of a single entry the user can pick to delete, undefined
48
+ * for a batch */
49
+ locales?: Array<string>;
50
+ /** The picked languages, the shown language at first */
51
+ selectedLocales: PrimitiveAtom<Array<string>>;
52
+ removals: Atom<Array<Removal>>;
53
+ /** The versions linking to the removed translations and their contents,
54
+ * that are not removed themselves */
55
+ references: Atom<Array<EntryReferenceWithSource>>;
56
+ /** How many entries the user can not read link to them, their titles stay
57
+ * hidden */
58
+ hiddenSources: Atom<number>;
59
+ /** Every removed translation is published and can be archived instead */
60
+ archivable: Atom<boolean>;
61
+ /** The public URLs of the removed translations of pages */
62
+ urls: Atom<Array<RemovedUrl>>;
63
+ /** The page the editor picked to redirect the URLs to */
64
+ redirect: PrimitiveAtom<RedirectTarget | undefined>;
65
+ /** The URLs the redirect target has no version for, they don't redirect */
66
+ unredirected: Atom<Array<RemovedUrl>>;
67
+ }
68
+ export interface RemovePlanOptions {
69
+ subjects: Array<RemoveSubject>;
70
+ locales?: Array<string>;
71
+ references?: Array<EntryReferenceWithSource>;
72
+ /** Links from versions the user can not read */
73
+ hidden?: Array<EntryReference>;
74
+ descendants?: Array<RemovedDescendant>;
75
+ /** The translations that can be archived */
76
+ archivable?: Array<Removal>;
77
+ /** The public URLs of the subjects, in each of their languages */
78
+ urls?: Array<RemovedUrl>;
79
+ }
80
+ /**
81
+ * Entries without languages are removed entirely, the others in the picked
82
+ * languages or, in a batch, the language they are shown in.
83
+ */
84
+ export declare function createRemovePlan({ subjects, locales, references, hidden, descendants, archivable, urls }: RemovePlanOptions): RemovePlan;
85
+ export interface RedirectPicker {
86
+ /** The entries of the types that hold URL aliases */
87
+ condition: Filter<EntryFields>;
88
+ /** The roots of the workspace of the removed entries */
89
+ locations: Array<ExplorerLimitLocation>;
90
+ /** Entries that are not removed and the user may add an alias to */
91
+ canSelect(item: ExplorerItemData): boolean;
92
+ }
93
+ /** Which pages the URLs of the removed entries can redirect to */
94
+ export declare function redirectPicker(config: Config, policy: Policy, subjects: Array<RemoveSubject>): RedirectPicker;
95
+ /** Loads the page the editor picked to redirect the URLs to */
96
+ export declare const loadRedirectTargetAtom: import("jotai").WritableAtom<null, [id: string, locale: string | null], Promise<RedirectTarget | undefined>> & {
97
+ init: null;
98
+ };
99
+ /** Loads the references to the entries before the remove dialog opens */
100
+ export declare const loadRemovePlanAtom: import("jotai").WritableAtom<null, [subjects: RemoveSubject[], locales?: string[] | undefined], Promise<RemovePlan>> & {
101
+ init: null;
102
+ };
103
+ /** Removes the translations and redirects their URLs in one commit */
104
+ export declare const deleteEntriesAtom: import("jotai").WritableAtom<null, [plan: RemovePlan], Promise<void>> & {
105
+ init: null;
106
+ };
107
+ /** Archives the translations the plan would delete */
108
+ export declare const archiveEntriesAtom: import("jotai").WritableAtom<null, [plan: RemovePlan], Promise<void>> & {
109
+ init: null;
110
+ };
111
+ /** Archive without asking first, until the browser session ends */
112
+ export declare const archiveDirectlyAtom: import("jotai").WritableAtom<boolean, [boolean | typeof import("jotai/utils").RESET | ((prev: boolean) => boolean | typeof import("jotai/utils").RESET)], void>;
@@ -0,0 +1,268 @@
1
+ import {
2
+ atomWithStorage,
3
+ createJSONStorage
4
+ } from "../../chunks/chunk-G43W7AME.js";
5
+ import {
6
+ atom
7
+ } from "../../chunks/chunk-CFNRVI3Y.js";
8
+ import "../../chunks/chunk-CQ52GEHY.js";
9
+
10
+ // src/dashboard/atoms/remove.ts
11
+ import { Entry } from "alinea/core/Entry";
12
+ import { getWorkspace } from "alinea/core/Internal";
13
+ import { MediaFile, MediaLibrary } from "alinea/core/media/MediaTypes";
14
+ import { Root } from "alinea/core/Root";
15
+ import { entries } from "alinea/core/util/Objects";
16
+ import { dataWithUrlAliases, hasUrlAliases } from "alinea/database/EntryUrlAliases";
17
+ import { configAtom, graphAtom } from "./core.js";
18
+ import { loadIncomingReferences } from "./entry.js";
19
+ import { policyAtom } from "./user.js";
20
+ function createRemovePlan({
21
+ subjects,
22
+ locales,
23
+ references = [],
24
+ hidden = [],
25
+ descendants = [],
26
+ archivable = [],
27
+ urls = []
28
+ }) {
29
+ const shown = subjects[0]?.locale;
30
+ const selectedLocales = atom(
31
+ shown && locales?.includes(shown) ? [shown] : Array()
32
+ );
33
+ const removals = atom((get) => {
34
+ const selected = get(selectedLocales);
35
+ return subjects.flatMap(({ id, locale }) => {
36
+ if (locale === null || !locales) return [{ id, locale }];
37
+ return selected.map((locale2) => ({ id, locale: locale2 }));
38
+ });
39
+ });
40
+ const removed = atom(
41
+ (get) => get(removals).flatMap((removal) => [
42
+ removal,
43
+ ...descendants.filter(
44
+ ({ subjectId, locale }) => subjectId === removal.id && locale === removal.locale
45
+ )
46
+ ])
47
+ );
48
+ const breaks = (reference, removed2) => !removed2.some(
49
+ ({ id, locale }) => id === reference.sourceId && locale === reference.sourceLocale
50
+ ) && // References hold the language of the linking entry: links from an
51
+ // entry without languages can point to any language
52
+ removed2.some(
53
+ ({ id, locale }) => id === reference.targetId && (locale === null || reference.sourceLocale === null || locale === reference.sourceLocale)
54
+ );
55
+ const removedUrls = atom(
56
+ (get) => get(removals).flatMap(
57
+ ({ id, locale }) => urls.filter((url) => url.id === id && url.locale === locale)
58
+ )
59
+ );
60
+ const redirect = atom(void 0);
61
+ return {
62
+ subjects,
63
+ locales,
64
+ selectedLocales,
65
+ removals,
66
+ references: atom((get) => {
67
+ const current = get(removed);
68
+ return references.filter(({ reference }) => breaks(reference, current));
69
+ }),
70
+ hiddenSources: atom((get) => {
71
+ const current = get(removed);
72
+ const sources = hidden.filter((reference) => breaks(reference, current)).map((reference) => reference.sourceId);
73
+ return new Set(sources).size;
74
+ }),
75
+ archivable: atom((get) => {
76
+ const current = get(removals);
77
+ return current.length > 0 && current.every(
78
+ ({ id, locale }) => archivable.some((item) => item.id === id && item.locale === locale)
79
+ );
80
+ }),
81
+ urls: removedUrls,
82
+ redirect,
83
+ unredirected: atom((get) => {
84
+ const target = get(redirect);
85
+ if (!target) return [];
86
+ return get(removedUrls).filter(
87
+ (url) => redirectLocale(target.locales, url.locale) === void 0
88
+ );
89
+ })
90
+ };
91
+ }
92
+ function redirectLocale(locales, locale) {
93
+ if (locales.includes(locale)) return locale;
94
+ if (locales.includes(null)) return null;
95
+ return void 0;
96
+ }
97
+ function hasUrl(type) {
98
+ if (!type || type === MediaFile || type === MediaLibrary) return false;
99
+ return hasUrlAliases(type);
100
+ }
101
+ var redirectStatuses = ["published", "draft"];
102
+ function redirectPicker(config, policy, subjects) {
103
+ const [first] = subjects;
104
+ const removed = new Set(subjects.map((subject) => subject.id));
105
+ const types = entries(config.schema).filter(([, type]) => hasUrl(type)).map(([name]) => name);
106
+ const workspace = first && config.workspaces[first.workspace];
107
+ const roots = workspace ? entries(getWorkspace(workspace).roots) : [];
108
+ const locations = roots.filter(([, root]) => !Root.isMediaRoot(root)).map(([root]) => ({ workspace: first.workspace, root }));
109
+ return {
110
+ condition: { _type: { in: types } },
111
+ locations,
112
+ canSelect(item) {
113
+ if (removed.has(item.id)) return false;
114
+ if (item.parents.some((parent) => removed.has(parent))) return false;
115
+ return policy.canUpdate({ ...item, field: "metadata.aliases" }) && policy.canPublish(item);
116
+ }
117
+ };
118
+ }
119
+ var loadRedirectTargetAtom = atom(
120
+ null,
121
+ async (get, _set, id, locale) => {
122
+ const versions = await get(graphAtom).find({
123
+ id,
124
+ status: "all",
125
+ versionStatus: { in: redirectStatuses },
126
+ select: { title: Entry.title, locale: Entry.locale, url: Entry.url }
127
+ });
128
+ const shown = versions.find((version) => version.locale === locale) ?? versions[0];
129
+ if (!shown) return void 0;
130
+ const locales = new Set(versions.map((version) => version.locale));
131
+ return { id, title: shown.title, url: shown.url, locales: [...locales] };
132
+ }
133
+ );
134
+ var loadRemovePlanAtom = atom(
135
+ null,
136
+ async (get, _set, subjects, locales) => {
137
+ const config = get(configAtom);
138
+ const policy = get(policyAtom);
139
+ const [first] = subjects;
140
+ const versions = await get(graphAtom).find({
141
+ id: { in: subjects.map((subject) => subject.id) },
142
+ status: "all",
143
+ select: {
144
+ id: Entry.id,
145
+ locale: Entry.locale,
146
+ status: Entry.status,
147
+ active: Entry.active,
148
+ url: Entry.url,
149
+ descendants: {
150
+ edge: "children",
151
+ // The deepest level queried, queries are sent as JSON
152
+ depth: 999,
153
+ status: "all",
154
+ select: Entry.id
155
+ }
156
+ }
157
+ });
158
+ const descendants = versions.flatMap(
159
+ (version) => version.descendants.map((id) => ({
160
+ id,
161
+ locale: version.locale,
162
+ subjectId: version.id
163
+ }))
164
+ );
165
+ const subjectOf = (id) => subjects.find((subject) => subject.id === id);
166
+ const archivable = versions.flatMap(({ id, locale, status, active }) => {
167
+ const subject = subjectOf(id);
168
+ if (!subject || !active || status !== "published") return [];
169
+ if (config.schema[subject.type] === MediaFile) return [];
170
+ if (!policy.canArchive({ ...subject, locale })) return [];
171
+ return [{ id, locale }];
172
+ });
173
+ const urls = versions.flatMap(({ id, locale, status, url }) => {
174
+ const subject = subjectOf(id);
175
+ if (!subject || status !== "published" || !url) return [];
176
+ if (!hasUrl(config.schema[subject.type])) return [];
177
+ return [{ id, locale, url }];
178
+ });
179
+ const targetIds = new Set(
180
+ subjects.map((subject) => subject.id).concat(descendants.map((d) => d.id))
181
+ );
182
+ const { references, hidden } = await loadIncomingReferences(
183
+ get,
184
+ Array.from(targetIds)
185
+ );
186
+ return createRemovePlan({
187
+ subjects,
188
+ locales: locales?.filter((locale) => policy.canDelete({ ...first, locale })),
189
+ references,
190
+ hidden,
191
+ descendants,
192
+ archivable,
193
+ urls: urls.filter(
194
+ (url, index) => urls.findIndex(
195
+ (other) => other.id === url.id && other.locale === url.locale
196
+ ) === index
197
+ )
198
+ });
199
+ }
200
+ );
201
+ async function redirects(get, plan) {
202
+ const target = get(plan.redirect);
203
+ const urls = get(plan.urls);
204
+ if (!target || urls.length === 0) return [];
205
+ const versions = await get(graphAtom).find({
206
+ id: target.id,
207
+ status: "all",
208
+ versionStatus: { in: redirectStatuses },
209
+ select: {
210
+ locale: Entry.locale,
211
+ status: Entry.versionStatus,
212
+ data: Entry.data
213
+ }
214
+ });
215
+ const locales = versions.map((version) => version.locale);
216
+ return versions.flatMap(({ locale, status, data }) => {
217
+ const redirected = urls.filter((url) => redirectLocale(locales, url.locale) === locale).map((url) => url.url);
218
+ const next = dataWithUrlAliases(data, redirected);
219
+ if (next === data) return [];
220
+ const set = { metadata: next.metadata };
221
+ return [{ op: "update", id: target.id, locale, status, set }];
222
+ });
223
+ }
224
+ var deleteEntriesAtom = atom(
225
+ null,
226
+ async (get, _set, plan) => {
227
+ const removals = get(plan.removals).map(
228
+ (removal) => ({ op: "remove", ...removal })
229
+ );
230
+ const updates = await redirects(get, plan);
231
+ await get(graphAtom).mutate(removals.concat(updates));
232
+ }
233
+ );
234
+ var archiveEntriesAtom = atom(
235
+ null,
236
+ async (get, _set, plan) => {
237
+ const removals = get(plan.removals).map(
238
+ (removal) => ({ op: "archive", ...removal })
239
+ );
240
+ const updates = await redirects(get, plan);
241
+ await get(graphAtom).mutate(removals.concat(updates));
242
+ }
243
+ );
244
+ var noStorage = { getItem: () => null, setItem() {
245
+ }, removeItem() {
246
+ } };
247
+ function session() {
248
+ try {
249
+ return globalThis.sessionStorage ?? noStorage;
250
+ } catch {
251
+ return noStorage;
252
+ }
253
+ }
254
+ var archiveDirectlyAtom = atomWithStorage(
255
+ "alinea-dashboard-archive-directly",
256
+ false,
257
+ createJSONStorage(session),
258
+ { getOnInit: true }
259
+ );
260
+ export {
261
+ archiveDirectlyAtom,
262
+ archiveEntriesAtom,
263
+ createRemovePlan,
264
+ deleteEntriesAtom,
265
+ loadRedirectTargetAtom,
266
+ loadRemovePlanAtom,
267
+ redirectPicker
268
+ };
@@ -1,7 +1,7 @@
1
+ import { Config } from 'alinea/core/Config';
1
2
  import type { EntryStatus } from 'alinea/core/Entry';
2
3
  import { Permission } from 'alinea/core/Role';
3
4
  import { type RootData, type RootI18n } from 'alinea/core/Root';
4
- import { Schema } from 'alinea/core/Schema';
5
5
  import { ExplorerAtoms } from 'alinea/dashboard/atoms/explorer';
6
6
  import { type Atom, type PrimitiveAtom } from 'jotai';
7
7
  import type { ComponentType } from 'react';
@@ -113,7 +113,7 @@ export declare const rootAtoms: (workspace: string, root: string) => RootAtoms;
113
113
  * Entries can be dropped on a container, but only placed before or after the
114
114
  * children of a parent that does not order them, and never inside themselves
115
115
  */
116
- export declare function treeAcceptsDrop(schema: Schema, entries: Map<string, RootTreeItem>, target: DropTarget, keys?: ReadonlySet<Key>): boolean;
116
+ export declare function treeAcceptsDrop(config: Config, rootData: RootData, entries: Map<string, RootTreeItem>, target: DropTarget, keys?: ReadonlySet<Key>): boolean;
117
117
  /** The selected entry, or the location of a selected entry that is not listed */
118
118
  export declare function locatedItem({ entries, snapshot }: TreeView): RootTreeItem | undefined;
119
119
  export {};
@@ -1,13 +1,14 @@
1
1
  import {
2
2
  selectAtom,
3
3
  unwrap
4
- } from "../../chunks/chunk-TIJTT5ZI.js";
4
+ } from "../../chunks/chunk-G43W7AME.js";
5
5
  import {
6
6
  atom
7
7
  } from "../../chunks/chunk-CFNRVI3Y.js";
8
8
  import "../../chunks/chunk-CQ52GEHY.js";
9
9
 
10
10
  // src/dashboard/atoms/root.ts
11
+ import { Config } from "alinea/core/Config";
11
12
  import { Permission } from "alinea/core/Role";
12
13
  import { Root } from "alinea/core/Root";
13
14
  import { Schema } from "alinea/core/Schema";
@@ -18,6 +19,8 @@ import {
18
19
  } from "alinea/dashboard/atoms/explorer";
19
20
  import { IcOutlineDescription } from "../icons.js";
20
21
  import { viewAtoms } from "./config.js";
22
+ import { confirmMoveAtom, draggedEntriesAtom } from "./moveConfirmation.js";
23
+ import { rootAcceptsType } from "./move.js";
21
24
  import { configAtom, graphAtom } from "./core.js";
22
25
  import {
23
26
  loadTreeChildren,
@@ -381,11 +384,6 @@ var RootAtoms = class {
381
384
  * scroll offset when the editor returns to it through either of them.
382
385
  */
383
386
  explorerScrollOffset = dispense((_key) => atom(0));
384
- /**
385
- * The filters picked in the explorers of this root, kept while the editor
386
- * opens entries and folders. Each overview applies the ones it declares.
387
- */
388
- #explorerFilters = atom({});
389
387
  children = dispense((parentId) => {
390
388
  const location = {
391
389
  workspace: this.workspace,
@@ -412,7 +410,9 @@ var RootAtoms = class {
412
410
  enableNavigation: true,
413
411
  sortState: overviewSortAtom(this.workspace, this.key, parentId),
414
412
  rootData: this.data,
415
- filterState: this.#explorerFilters,
413
+ // The filters picked in this list, kept when the editor comes back to
414
+ // it, like its sort, but not applied to the lists of other entries
415
+ filterState: atom({}),
416
416
  scrollOffset: this.explorerScrollOffset,
417
417
  selectedLocaleAtom: this.#explorerLocale,
418
418
  selectionBehavior: "toggle",
@@ -443,7 +443,8 @@ var RootAtoms = class {
443
443
  acceptedDragTypes = [...dashboardEntryDragTypes];
444
444
  getItems = atom(
445
445
  null,
446
- (_get, _set, keys) => {
446
+ (_get, set, keys) => {
447
+ set(draggedEntriesAtom, keys);
447
448
  return [...keys].map(dashboardEntryDragItem);
448
449
  }
449
450
  );
@@ -454,12 +455,18 @@ var RootAtoms = class {
454
455
  });
455
456
  onMove = atom(
456
457
  null,
457
- async (get, _set, event, tree) => {
458
+ async (get, set, event, tree) => {
458
459
  const policy = get(policyAtom);
459
460
  const permission = event.target.position === "on" ? Permission.Move : Permission.Reorder;
460
- const { schema } = get(configAtom);
461
461
  const { entries } = get(tree.view);
462
- if (!treeAcceptsDrop(schema, entries, event.target, event.keys)) return;
462
+ const accepted = treeAcceptsDrop(
463
+ get(configAtom),
464
+ get(this.data),
465
+ entries,
466
+ event.target,
467
+ event.keys
468
+ );
469
+ if (!accepted) return;
463
470
  const moving = get(tree.items).filter((item) => event.keys.has(item.id));
464
471
  for (const item of moving)
465
472
  policy.assert(permission, {
@@ -470,30 +477,38 @@ var RootAtoms = class {
470
477
  locale: item.locale,
471
478
  parents: item.parents
472
479
  });
473
- await moveEntries(
474
- get(graphAtom),
475
- moving.map((item) => item.id),
476
- event.target
477
- );
480
+ const ids = moving.map((item) => item.id);
481
+ if (!await set(confirmMoveAtom, ids, event.target)) return;
482
+ await moveEntries(get(graphAtom), ids, event.target);
478
483
  }
479
484
  );
480
- onDrop = atom(null, async (get, _set, event) => {
481
- await moveEntries(
482
- get(graphAtom),
483
- dashboardEntryDropIds(event.items),
484
- event.target
485
- );
485
+ onDrop = atom(null, async (get, set, event) => {
486
+ const ids = dashboardEntryDropIds(event.items);
487
+ if (!await set(confirmMoveAtom, ids, event.target)) return;
488
+ await moveEntries(get(graphAtom), ids, event.target);
486
489
  });
487
490
  };
488
491
  var rootAtoms = dispense(
489
492
  (workspace, root) => new RootAtoms(workspace, root)
490
493
  );
491
- function treeAcceptsDrop(schema, entries, target, keys = /* @__PURE__ */ new Set()) {
494
+ function treeAcceptsDrop(config, rootData, entries, target, keys = /* @__PURE__ */ new Set()) {
492
495
  const entry = entries.get(String(target.key));
493
496
  if (!entry || entry.parents.some((id) => keys.has(id))) return false;
494
- if (target.position !== "on") return !entry.ordered;
495
- const type = schema[entry.type];
496
- return !keys.has(entry.id) && Boolean(type && Type.isContainer(type));
497
+ const into = target.position === "on";
498
+ if (into ? keys.has(entry.id) : entry.ordered) return false;
499
+ const parentId = into ? entry.id : entry.parentId;
500
+ const parent = parentId ? entries.get(parentId) : void 0;
501
+ const parentType = parent && config.schema[parent.type];
502
+ if (into && !(parentType && Type.isContainer(parentType))) return false;
503
+ for (const key of keys) {
504
+ const dragged = entries.get(String(key));
505
+ if (!dragged || !config.schema[dragged.type]) continue;
506
+ if (dragged.parentId === parentId) continue;
507
+ if (parentId && !parentType) continue;
508
+ const accepted = parentType ? Config.typeContains(config, parentType, config.schema[dragged.type]) : rootAcceptsType(config, rootData, dragged.type);
509
+ if (!accepted) return false;
510
+ }
511
+ return true;
497
512
  }
498
513
  function locatedItem({
499
514
  entries,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  selectAtom,
3
3
  unwrap
4
- } from "../../chunks/chunk-TIJTT5ZI.js";
4
+ } from "../../chunks/chunk-G43W7AME.js";
5
5
  import {
6
6
  atom
7
7
  } from "../../chunks/chunk-CFNRVI3Y.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  unwrap
3
- } from "../../chunks/chunk-TIJTT5ZI.js";
3
+ } from "../../chunks/chunk-G43W7AME.js";
4
4
  import {
5
5
  atom
6
6
  } from "../../chunks/chunk-CFNRVI3Y.js";
@@ -5,7 +5,6 @@ import "../../chunks/chunk-CQ52GEHY.js";
5
5
 
6
6
  // src/dashboard/boot/Boot.tsx
7
7
  import { IndexEvent } from "alinea/core/db/IndexEvent";
8
- import { MemorySource } from "alinea/core/source/MemorySource";
9
8
  import { createRoot } from "react-dom/client";
10
9
  import { App } from "../App.js";
11
10
  import { ActivityEvent } from "./ActivityEvent.js";
@@ -18,15 +17,7 @@ async function boot(gen) {
18
17
  if (inWorker) {
19
18
  loadWorker(gen);
20
19
  } else {
21
- let events;
22
- let worker;
23
- try {
24
- ;
25
- [events, worker] = createSharedWorker();
26
- } catch {
27
- console.warn("Shared worker not supported, falling back to local worker.");
28
- events = worker = new DashboardWorker(new MemorySource());
29
- }
20
+ const [events, worker] = connect();
30
21
  const scripts = document.getElementsByTagName("script");
31
22
  const element = scripts[scripts.length - 1];
32
23
  const into = document.createElement("div");
@@ -56,20 +47,37 @@ async function boot(gen) {
56
47
  }
57
48
  }
58
49
  }
59
- function createSharedWorker() {
50
+ function connect() {
51
+ const options = { type: "module", name: "Alinea dashboard" };
52
+ try {
53
+ const worker = new SharedWorker(import.meta.url, options);
54
+ return [listen(worker.port), wrap2(worker.port)];
55
+ } catch {
56
+ console.warn("Shared worker not supported, falling back to a worker.");
57
+ }
58
+ try {
59
+ const worker = new Worker(import.meta.url, options);
60
+ return [listen(worker), wrap2(worker)];
61
+ } catch {
62
+ console.warn("Worker not supported, running on the main thread.");
63
+ const worker = new DashboardWorker();
64
+ return [worker, worker];
65
+ }
66
+ }
67
+ function wrap2(endpoint) {
68
+ return wrap(endpoint);
69
+ }
70
+ function listen(source) {
60
71
  const events = new EventTarget();
61
- const worker = new SharedWorker(import.meta.url, {
62
- type: "module",
63
- name: "Alinea dashboard"
64
- });
65
- worker.port.addEventListener("message", ({ data }) => {
72
+ source.addEventListener("message", (event) => {
73
+ const { data } = event;
66
74
  if (data.type === IndexEvent.type) {
67
75
  events.dispatchEvent(new IndexEvent(data.data));
68
76
  } else if (data.type === ActivityEvent.type) {
69
77
  events.dispatchEvent(new ActivityEvent(data.activities));
70
78
  }
71
79
  });
72
- return [events, wrap(worker.port)];
80
+ return events;
73
81
  }
74
82
  function isWorkerScope() {
75
83
  return typeof WorkerGlobalScope !== "undefined" && globalThis instanceof WorkerGlobalScope;
@@ -1,3 +1,8 @@
1
1
  import type { CMS } from 'alinea/core/CMS';
2
2
  import type { ComponentType } from 'react';
3
- export declare function bootProd(handlerUrl: string, cms: CMS, views: Record<string, ComponentType>): Promise<void>;
3
+ /**
4
+ * Boot a built dashboard. Its build id is a hash of the built files, which
5
+ * the page passes in the script URL, so that a deploy that leaves the
6
+ * dashboard unchanged keeps its shared worker and the tabs it serves.
7
+ */
8
+ export declare function bootProd(handlerUrl: string, cms: CMS, views: Record<string, ComponentType>, buildId?: string): Promise<void>;
@@ -3,11 +3,11 @@ import "../../chunks/chunk-CQ52GEHY.js";
3
3
  // src/dashboard/boot/BootProd.ts
4
4
  import { Client } from "alinea/core/Client";
5
5
  import { boot } from "./Boot.js";
6
- function bootProd(handlerUrl, cms, views) {
6
+ function bootProd(handlerUrl, cms, views, buildId = process.env.ALINEA_BUILD_ID) {
7
7
  async function* getConfig() {
8
8
  yield {
9
9
  local: false,
10
- revision: process.env.ALINEA_BUILD_ID,
10
+ revision: buildId,
11
11
  configFingerprint: process.env.ALINEA_CONFIG_FINGERPRINT,
12
12
  config: cms.config,
13
13
  views,
@@ -7,6 +7,7 @@ import { EntryStore } from 'alinea/database/EntryStore';
7
7
  import { type Activity } from './ActivityEvent.js';
8
8
  export declare class DashboardWorker extends EventTarget {
9
9
  #private;
10
+ /** Keep the content in memory with `source`, instead of in the browser. */
10
11
  constructor(source?: Source);
11
12
  get db(): EntryStore | Promise<EntryStore>;
12
13
  sync(): Promise<string>;