alinea 2.0.0-preview.13 → 2.0.0-preview.14
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/dist/.init/content/pages/welcome.json +1 -1
- package/dist/.mcp/later/dev.json +1 -1
- package/dist/.mcp/online/dev.json +1 -1
- package/dist/adapter/next/cms.js +6 -2
- package/dist/adapter/next/context.js +1 -4
- package/dist/adapter/next/handler.js +12 -1
- package/dist/backend/CommitEntries.d.ts +17 -0
- package/dist/backend/CommitEntries.js +39 -0
- package/dist/backend/Handler.d.ts +9 -0
- package/dist/backend/Handler.js +80 -20
- package/dist/backend/api/BasicAuth.js +5 -1
- package/dist/backend/api/CreateBackend.js +2 -2
- package/dist/backend/api/DatabaseApi.js +18 -5
- package/dist/backend/api/GithubApi.d.ts +13 -0
- package/dist/backend/api/GithubApi.js +170 -30
- package/dist/backend/api/database.js +2 -2
- package/dist/backend/util/Syncable.d.ts +6 -0
- package/dist/backend/util/Syncable.js +3 -1
- package/dist/bundled.d.ts +911 -151
- package/dist/chunks/{chunk-5H4D5JQL.js → chunk-2PP7WJUJ.js} +1 -11
- package/dist/chunks/{chunk-QHHMKJUO.js → chunk-7DDPSWRY.js} +465 -2
- package/dist/chunks/{chunk-HOETF6HN.js → chunk-PMGIIYDD.js} +1 -0
- package/dist/chunks/{chunk-3QFSGTGF.js → chunk-SCV3TEXM.js} +1 -1
- package/dist/chunks/{chunk-UPY2RUZF.js → chunk-TWBREZWC.js} +30 -0
- package/dist/cli/Generate.js +20 -7
- package/dist/cli/Mcp.js +1 -1
- package/dist/cli/Serve.js +1 -1
- package/dist/cli/bin.js +1 -1
- package/dist/cli/build/BuildEmitter.d.ts +5 -1
- package/dist/cli/build/BuildEmitter.js +8 -3
- package/dist/cli/generate/CompileConfig.js +10 -6
- package/dist/cli/generate/DevDB.d.ts +4 -1
- package/dist/cli/generate/DevDB.js +15 -7
- package/dist/cli/generate/GenerateDashboard.js +1 -0
- package/dist/cli/generate/MediaLocations.d.ts +18 -0
- package/dist/cli/generate/MediaLocations.js +69 -0
- package/dist/cli/serve/CreateLocalServer.js +1 -0
- package/dist/cli/serve/mcp/DevMcp.js +1 -1
- package/dist/cloud/CloudRemote.js +21 -7
- package/dist/cms.d.ts +4 -2
- package/dist/components/Alert.js +2 -2
- package/dist/components/AppShell.js +2 -2
- package/dist/components/AsyncList.d.ts +52 -0
- package/dist/components/AsyncList.js +69 -0
- package/dist/components/Breadcrumbs.js +6 -6
- package/dist/components/Button.js +4 -4
- package/dist/components/Calendar.js +18 -18
- package/dist/components/Checkbox.js +10 -10
- package/dist/components/CheckboxGroup.js +2 -2
- package/dist/components/Collapsible.js +5 -5
- package/dist/components/ColorSwatch.js +2 -2
- package/dist/components/ColorSwatchPicker.js +4 -4
- package/dist/components/ComboBox.d.ts +27 -9
- package/dist/components/ComboBox.js +40 -9
- package/dist/components/Command.js +13 -13
- package/dist/components/ContentCard.js +28 -28
- package/dist/components/ContentGrid.js +11 -11
- package/dist/components/DataList.js +1 -1
- package/dist/components/DateField.d.ts +4 -0
- package/dist/components/DateField.js +2 -2
- package/dist/components/DatePicker.js +2 -2
- package/dist/components/DateRangePicker.js +2 -2
- package/dist/components/Dialog.d.ts +14 -3
- package/dist/components/Dialog.js +38 -11
- package/dist/components/DropZone.js +6 -6
- package/dist/components/DropdownMenu.js +8 -8
- package/dist/components/Empty.js +4 -4
- package/dist/components/Field.d.ts +1 -1
- package/dist/components/Field.js +18 -12
- package/dist/components/Form.d.ts +23 -0
- package/dist/components/Form.js +64 -0
- package/dist/components/Link.d.ts +8 -1
- package/dist/components/List.js +27 -27
- package/dist/components/MediaPreview.js +5 -5
- package/dist/components/MultipleSelect.d.ts +13 -8
- package/dist/components/MultipleSelect.js +25 -25
- package/dist/components/NavRail.js +7 -7
- package/dist/components/NumberField.d.ts +5 -1
- package/dist/components/NumberField.js +6 -4
- package/dist/components/Page.js +6 -6
- package/dist/components/Popover.js +2 -2
- package/dist/components/PreviewFrame.js +8 -8
- package/dist/components/RadioGroup.js +10 -10
- package/dist/components/Resizable.js +1 -1
- package/dist/components/SearchField.js +8 -8
- package/dist/components/Select.d.ts +13 -10
- package/dist/components/Select.js +18 -32
- package/dist/components/Sheet.js +6 -6
- package/dist/components/Sidebar.js +5 -5
- package/dist/components/SortableList.js +12 -12
- package/dist/components/Spinner.js +4 -4
- package/dist/components/Stack.d.ts +20 -0
- package/dist/components/Stack.js +45 -0
- package/dist/components/Surface.js +1 -1
- package/dist/components/Switch.js +5 -5
- package/dist/components/Table.d.ts +11 -1
- package/dist/components/Table.js +81 -53
- package/dist/components/Tabs.js +6 -6
- package/dist/components/TagGroup.js +10 -10
- package/dist/components/TextField.d.ts +7 -1
- package/dist/components/TextField.js +20 -11
- package/dist/components/TimeField.d.ts +6 -1
- package/dist/components/TimeField.js +51 -26
- package/dist/components/Timestamp.js +1 -1
- package/dist/components/ToggleGroup.js +2 -2
- package/dist/components/Toolbar.js +3 -3
- package/dist/components/Tooltip.js +5 -4
- package/dist/components/Tree.js +27 -27
- package/dist/components/internal/DateControl.js +11 -11
- package/dist/components/internal/FormScope.d.ts +2 -0
- package/dist/components/internal/FormScope.js +8 -0
- package/dist/components/internal/ListBoxGroup.d.ts +12 -0
- package/dist/components/internal/ListBoxGroup.js +46 -0
- package/dist/components/internal/ListBoxOption.d.ts +2 -2
- package/dist/components/internal/ListBoxOption.js +5 -5
- package/dist/components/internal/Locale.js +2 -2
- package/dist/components/internal/PopoverSurface.js +5 -4
- package/dist/components/internal/SelectionCheckbox.js +5 -5
- package/dist/components/internal/Slot.js +1 -1
- package/dist/components/internal/ToggleButton.js +4 -4
- package/dist/components/internal/Trigger.js +2 -2
- package/dist/components/internal/useDragDrop.js +2 -2
- package/dist/components/types.d.ts +4 -0
- package/dist/components.d.ts +3 -0
- package/dist/components.js +3 -0
- package/dist/core/Client.js +12 -1
- package/dist/core/Config.d.ts +6 -0
- package/dist/core/Config.js +12 -0
- package/dist/core/DashboardEntry.d.ts +19 -0
- package/dist/core/DashboardEntry.js +0 -0
- package/dist/core/Document.d.ts +2 -2
- package/dist/core/Document.js +10 -3
- package/dist/core/EntryFields.d.ts +8 -2
- package/dist/core/Field.d.ts +5 -0
- package/dist/core/Filter.d.ts +19 -2
- package/dist/core/Filter.js +5 -0
- package/dist/core/Graph.d.ts +10 -2
- package/dist/core/Overview.d.ts +74 -5
- package/dist/core/Overview.js +1 -0
- package/dist/core/Root.js +1 -1
- package/dist/core/Scope.d.ts +5 -0
- package/dist/core/Scope.js +22 -5
- package/dist/core/Type.d.ts +25 -6
- package/dist/core/Type.js +25 -7
- package/dist/core/db/LocalStore.d.ts +7 -0
- package/dist/core/field/CreateField.d.ts +1 -2
- package/dist/core/source/Source.d.ts +2 -0
- package/dist/core/source/Source.js +11 -0
- package/dist/core/util/Async.d.ts +2 -0
- package/dist/core/util/Async.js +12 -1
- package/dist/core/util/Checks.js +1 -1
- package/dist/core/util/Slugs.js +4 -3
- package/dist/dashboard/App.css +13 -11
- package/dist/dashboard/App.d.ts +1 -0
- package/dist/dashboard/App.js +22 -6
- package/dist/dashboard/Href.d.ts +37 -0
- package/dist/dashboard/Href.js +46 -0
- package/dist/dashboard/app/ActivityStatus.js +34 -12
- package/dist/dashboard/app/AuthView.js +2 -2
- package/dist/dashboard/app/BlockSettings.d.ts +9 -0
- package/dist/dashboard/app/BlockSettings.js +16 -0
- package/dist/dashboard/app/BlockSheet.js +3 -3
- package/dist/dashboard/app/BlockSheetStoryFrame.js +2 -2
- package/dist/dashboard/app/CompactField.js +15 -15
- package/dist/dashboard/app/CroppedImage.js +2 -2
- package/dist/dashboard/app/DashboardLayout.d.ts +1 -1
- package/dist/dashboard/app/DashboardLayout.js +12 -0
- package/dist/dashboard/app/EntryActions.d.ts +11 -0
- package/dist/dashboard/app/EntryActions.js +30 -0
- package/dist/dashboard/app/EntryFields.d.ts +15 -2
- package/dist/dashboard/app/EntryFields.js +26 -5
- package/dist/dashboard/app/EntryHeader.js +17 -7
- package/dist/dashboard/app/EntryReferences.js +2 -2
- package/dist/dashboard/app/EntrySidebar.js +25 -23
- package/dist/dashboard/app/EntrySidebarPreview.js +2 -2
- package/dist/dashboard/app/EntryValidationModal.js +3 -3
- package/dist/dashboard/app/Explorer.js +84 -45
- package/dist/dashboard/app/ExplorerBatchActions.js +4 -4
- package/dist/dashboard/app/ExplorerControls.js +5 -5
- package/dist/dashboard/app/ExplorerList.js +2 -2
- package/dist/dashboard/app/ExplorerModal.js +3 -3
- package/dist/dashboard/app/ExplorerOverviewHeader.d.ts +6 -0
- package/dist/dashboard/app/ExplorerOverviewHeader.js +28 -0
- package/dist/dashboard/app/ExplorerTable.js +7 -7
- package/dist/dashboard/app/ExplorerTabs.d.ts +12 -0
- package/dist/dashboard/app/ExplorerTabs.js +42 -0
- package/dist/dashboard/app/ImageEditor.js +31 -31
- package/dist/dashboard/app/LinkPicker.js +3 -3
- package/dist/dashboard/app/LocaleMenu.js +9 -9
- package/dist/dashboard/app/MediaPreviewCell.js +5 -5
- package/dist/dashboard/app/OverviewCell.js +12 -10
- package/dist/dashboard/app/RemoveDialog.js +5 -0
- package/dist/dashboard/app/SearchBar.js +4 -4
- package/dist/dashboard/app/SettingsTab.d.ts +6 -0
- package/dist/dashboard/app/SettingsTab.js +27 -0
- package/dist/dashboard/app/SidebarTree.js +19 -19
- package/dist/dashboard/app/WorkspaceMenu.js +6 -6
- package/dist/dashboard/app/WorkspaceRoots.js +102 -98
- package/dist/dashboard/app/editor/FileEditor.js +18 -18
- package/dist/dashboard/app/pages/EntryPage.js +42 -39
- package/dist/dashboard/app/pages/RootPage.js +3 -3
- package/dist/dashboard/app/pages/SplashPage.js +94 -79
- package/dist/dashboard/app/pages/UsersPage.js +9 -9
- package/dist/dashboard/app/ui/DashboardModal.js +14 -14
- package/dist/dashboard/atoms/activity.d.ts +1 -0
- package/dist/dashboard/atoms/activity.js +8 -5
- package/dist/dashboard/atoms/config.d.ts +2 -0
- package/dist/dashboard/atoms/dashboard.d.ts +11 -0
- package/dist/dashboard/atoms/dashboard.js +8 -0
- package/dist/dashboard/atoms/editor.d.ts +7 -1
- package/dist/dashboard/atoms/editor.js +14 -2
- package/dist/dashboard/atoms/explorer.d.ts +15 -0
- package/dist/dashboard/atoms/explorer.js +43 -14
- package/dist/dashboard/atoms/graph.d.ts +5 -0
- package/dist/dashboard/atoms/graph.js +26 -1
- package/dist/dashboard/atoms/nav.d.ts +16 -10
- package/dist/dashboard/atoms/nav.js +96 -36
- package/dist/dashboard/atoms/overview.d.ts +26 -4
- package/dist/dashboard/atoms/overview.js +41 -12
- package/dist/dashboard/atoms/root.js +7 -1
- package/dist/dashboard/boot/ActivityEvent.d.ts +1 -1
- package/dist/dashboard/boot/Boot.js +6 -0
- package/dist/dashboard/boot/DashboardWorker.d.ts +6 -0
- package/dist/dashboard/boot/DashboardWorker.js +89 -8
- package/dist/dashboard/boot/LoadWorker.js +3 -0
- package/dist/dashboard/boot/SyncEvent.d.ts +7 -0
- package/dist/dashboard/boot/SyncEvent.js +14 -0
- package/dist/dashboard/boot/WorkerDB.d.ts +1 -0
- package/dist/dashboard/boot/WorkerDB.js +3 -0
- package/dist/dashboard/cms/EntryTable.d.ts +4 -3
- package/dist/dashboard/cms/EntryTable.js +2 -2
- package/dist/dashboard/cms/ViewProps.d.ts +3 -2
- package/dist/dashboard/cms/hooks.d.ts +34 -18
- package/dist/dashboard/cms/hooks.js +43 -18
- package/dist/dashboard/contexts.d.ts +3 -2
- package/dist/dashboard/contexts.js +3 -2
- package/dist/dashboard/hook/UseEntryEditor.d.ts +1 -1
- package/dist/dashboard/hook/UseEntryEditor.js +7 -3
- package/dist/dashboard/icons.d.ts +1 -0
- package/dist/dashboard/icons.js +15 -0
- package/dist/database/BrowserEntryStore.js +35 -13
- package/dist/database/DatabaseTables.js +1 -1
- package/dist/database/EntryDatabase.d.ts +10 -1
- package/dist/database/EntryDatabase.js +31 -10
- package/dist/database/EntryStore.js +1 -11
- package/dist/database/entry/EntryTable.d.ts +5 -3
- package/dist/database/entry/EntryTable.js +25 -8
- package/dist/database/query/Condition.d.ts +4 -1
- package/dist/database/query/Condition.js +16 -5
- package/dist/database/query/EntryQuery.js +59 -7
- package/dist/database/query/Relation.d.ts +13 -1
- package/dist/database/query/Relation.js +19 -1
- package/dist/database/query/Search.d.ts +7 -1
- package/dist/database/query/Search.js +54 -9
- package/dist/database/sync/EntrySyncer.d.ts +2 -0
- package/dist/database/sync/EntrySyncer.js +4 -2
- package/dist/database/sync/Ingest.d.ts +2 -1
- package/dist/database/sync/Ingest.js +20 -6
- package/dist/field/code/CodeField.view.js +12 -12
- package/dist/field/link/EntryLink.d.ts +4 -5
- package/dist/field/link/FileLink.d.ts +2 -2
- package/dist/field/link/ImageLink.d.ts +2 -5
- package/dist/field/link/ImageLink.js +0 -1
- package/dist/field/link/LinkField.view.js +18 -15
- package/dist/field/list/ListField.view.js +50 -35
- package/dist/field/metadata/MetadataField.view.js +14 -14
- package/dist/field/richtext/RichTextBlock.js +39 -35
- package/dist/field/richtext/RichTextExtensions.js +23 -23
- package/dist/field/richtext/RichTextField.view.js +6 -6
- package/dist/field/richtext/RichTextInsertMenu.js +5 -5
- package/dist/field/richtext/RichTextToolbar.js +26 -1
- package/dist/field/richtext/Toolbar.d.ts +9 -1
- package/dist/field/richtext/Toolbar.js +12 -2
- package/dist/field/select/SelectField.view.js +1 -0
- package/dist/index.css +174 -23
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/next.d.ts +2 -0
- package/dist/next.edge.d.ts +2 -0
- package/dist/picker/entry/EntryPicker.d.ts +3 -1
- package/dist/picker/entry/EntryPicker.js +15 -1
- package/dist/preview/react.js +3 -0
- package/dist/query.d.ts +26 -14
- package/dist/query.js +4 -0
- package/dist/types.d.ts +8 -5
- package/dist/ui/Stack.d.ts +4 -2
- package/docs/ai-setup.md +3 -3
- package/docs/cli.md +4 -2
- package/docs/components/combo-box.md +105 -16
- package/docs/components/date-field.md +1 -1
- package/docs/components/dialog.md +51 -0
- package/docs/components/form.md +67 -0
- package/docs/components/link.md +10 -0
- package/docs/components/multiple-select.md +9 -17
- package/docs/components/number-field.md +5 -1
- package/docs/components/select.md +9 -20
- package/docs/components/stack.md +42 -0
- package/docs/components/table.md +83 -0
- package/docs/components/text-field.md +9 -1
- package/docs/components/time-field.md +5 -1
- package/docs/components.md +39 -1
- package/docs/configuration.md +3 -1
- package/docs/custom-fields.md +135 -7
- package/docs/deploy/self-host.md +2 -0
- package/docs/deploy.md +7 -4
- package/docs/fields/image.md +0 -1
- package/docs/fields/list.md +19 -0
- package/docs/fields/object.md +5 -3
- package/docs/fields/rich-text.md +31 -0
- package/docs/index.md +8 -6
- package/docs/instant-publishing.md +23 -2
- package/docs/overviews.md +128 -4
- package/docs/query/filtering.md +2 -0
- package/docs/query/related.md +58 -9
- package/docs/query.md +52 -1
- package/docs/quickstart.md +5 -5
- package/docs/schema/document.md +2 -0
- package/docs/schema/type.md +3 -1
- package/docs/upgrading.md +100 -19
- package/llms-full.txt +1022 -125
- package/package.json +1 -1
- package/readme.md +2 -2
- package/dist/chunks/{chunk-4ZA6KZHS.js → chunk-6FQ5IMNA.js} +3 -3
package/docs/overviews.md
CHANGED
|
@@ -64,16 +64,18 @@ export const products = Config.root('Products', {
|
|
|
64
64
|
- `sort`: the default order of the children, in the overview and in the sidebar tree. See [Sorting](#sorting).
|
|
65
65
|
- `sorts`: the orders editors can pick in the filter and sort menu, keyed by name. See [Sort options](#sort-options).
|
|
66
66
|
- `filters`: filters editors can apply in the filter and sort menu, keyed by name. See [Filters](#filters).
|
|
67
|
+
- `tabs`: tabs above the listed entries, keyed by a name kept in the url. See [Tabs](#tabs).
|
|
67
68
|
- `layout`: `'table'` or `'cards'`, the layout the overview opens in. Editors can still switch.
|
|
68
69
|
- `thumbnail`: the image shown on cards: an image field, or one per type (`{BlogPost: BlogPost.cover, Event: Event.poster}`). Defaults to the first image found in the fields of the entry.
|
|
69
70
|
- `actions`: components rendered in the toolbar of the overview, such as an export button. See [Actions](#actions).
|
|
71
|
+
- `header`: a component rendered between the toolbar and the listed entries, such as totals. See [Header](#header).
|
|
70
72
|
|
|
71
73
|
## Columns
|
|
72
74
|
|
|
73
75
|
`Config.column(options)` takes:
|
|
74
76
|
|
|
75
77
|
- `header` (required): the column header.
|
|
76
|
-
- `select`: what the column shows. Anything a [query](query.md) can select: a field (`Product.price`), an expression (`Query.url`), a query of linked entries, or an object of those.
|
|
78
|
+
- `select`: what the column shows. Anything a [query](query.md) can select: a field (`Product.price`), an expression (`Query.url`), a query of linked entries, a relation such as `Query.referencedBy`, or an object of those.
|
|
77
79
|
- `format`: a function that turns the selected value into text: `(value, {locale}) => string`.
|
|
78
80
|
- `view`: a React component that renders the cell, see [Custom cells](#custom-cells).
|
|
79
81
|
- `sortBy`, `sortable`: how the column sorts, see [Sorting](#sorting).
|
|
@@ -186,7 +188,34 @@ Force them on or off with `builtins`, for example `{type: true}` to always show
|
|
|
186
188
|
|
|
187
189
|
## Sorting
|
|
188
190
|
|
|
189
|
-
Editors sort an overview by clicking a column header, or from the filter and sort menu. The title, the built-in columns, expressions of the entry (such as `Query.title`) and fields that hold a single value (text, number, date, select, check, path, ...) are sortable. Columns that select links, lists, multiple selects, rich text or JSON need `sortBy`: an expression, or a query of a linked entry that selects a single expression, such as `Product.brand.first({select: Query.title})`. Set `sortable: false` to turn sorting off for a column.
|
|
191
|
+
Editors sort an overview by clicking a column header, or from the filter and sort menu. The title, the built-in columns, expressions of the entry (such as `Query.title`) and fields that hold a single value (text, number, date, select, check, path, ...) are sortable, and so are columns that count related entries, such as `Query.referencedBy({type: Product, count: true})`. Columns that select links, lists, multiple selects, rich text or JSON need `sortBy`: an expression, or a query of a linked entry that selects a single expression, such as `Product.brand.first({select: Query.title})`. Set `sortable: false` to turn sorting off for a column.
|
|
192
|
+
|
|
193
|
+
A column that counts the products of each brand, which editors can sort by:
|
|
194
|
+
|
|
195
|
+
File: schema/Brands.ts
|
|
196
|
+
```tsx
|
|
197
|
+
import {Config, Query} from 'alinea'
|
|
198
|
+
import {Product} from './Product'
|
|
199
|
+
|
|
200
|
+
export const Brands = Config.document('Brands', {
|
|
201
|
+
contains: ['Brand'],
|
|
202
|
+
fields: {},
|
|
203
|
+
overview: {
|
|
204
|
+
columns: {
|
|
205
|
+
products: Config.column({
|
|
206
|
+
header: 'Products',
|
|
207
|
+
width: 100,
|
|
208
|
+
align: 'end',
|
|
209
|
+
select: Query.referencedBy({
|
|
210
|
+
type: Product,
|
|
211
|
+
field: Product.brand,
|
|
212
|
+
count: true
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
```
|
|
190
219
|
|
|
191
220
|
Sorting runs in the database query, and entries without a value come last. It only changes what the editor sees, never the stored order of the entries. While a list is sorted, "Sorted by Price · Reset" shows in the toolbar and rows can't be reordered; Reset goes back to the default order. The sort is kept in the url (`?sort=price`, or `?sort=-price` for descending), so it survives a reload and can be shared, and it is restored when the editor comes back to the overview in the same session. Search results stay ordered by relevance.
|
|
192
221
|
|
|
@@ -257,6 +286,74 @@ export const Products = Config.document('Products', {
|
|
|
257
286
|
})
|
|
258
287
|
```
|
|
259
288
|
|
|
289
|
+
### Options from the content
|
|
290
|
+
|
|
291
|
+
`options` can also be a function that loads them, for example an option per category entry. It receives the `graph` to query and the `locale` of the overview, and runs when the overview opens. Filters can match on the entries that link to a listed entry too, with `_referenced` (see [Entries that link here](query/related.md#entries-that-link-here)):
|
|
292
|
+
|
|
293
|
+
File: schema/Products.ts
|
|
294
|
+
```tsx
|
|
295
|
+
import {Config, Query} from 'alinea'
|
|
296
|
+
import {Category, Product} from './Product'
|
|
297
|
+
|
|
298
|
+
export const Products = Config.document('Products', {
|
|
299
|
+
contains: ['Product'],
|
|
300
|
+
overview: {
|
|
301
|
+
filters: {
|
|
302
|
+
category: {
|
|
303
|
+
label: 'Category',
|
|
304
|
+
multiple: true,
|
|
305
|
+
async options({graph, locale}) {
|
|
306
|
+
const categories = await graph.find({
|
|
307
|
+
type: Category,
|
|
308
|
+
locale,
|
|
309
|
+
select: {id: Query.id, title: Query.title},
|
|
310
|
+
orderBy: {asc: Query.title}
|
|
311
|
+
})
|
|
312
|
+
return Object.fromEntries(
|
|
313
|
+
categories.map(category => [
|
|
314
|
+
category.id,
|
|
315
|
+
{
|
|
316
|
+
label: category.title,
|
|
317
|
+
filter: {categories: {includes: {_entry: category.id}}}
|
|
318
|
+
}
|
|
319
|
+
])
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
usage: {
|
|
324
|
+
label: 'Usage',
|
|
325
|
+
options: {
|
|
326
|
+
linked: {label: 'Linked from other entries', filter: {_referenced: true}},
|
|
327
|
+
unused: {label: 'Not linked anywhere', filter: {_referenced: false}}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
fields: {}
|
|
333
|
+
})
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Tabs
|
|
337
|
+
|
|
338
|
+
`tabs` shows tabs above the listed entries, apart from the filter and sort menu. Each tab has a `label` and an optional `filter`, like the options of a filter. A tab without a filter lists every child. The first tab is selected when the overview opens. The key of the selected tab is kept in the url (`?tab=active`) and the overview remembers it when the editor returns to it. Entries match the filter of the tab, the applied filters and the search terms.
|
|
339
|
+
|
|
340
|
+
File: schema/Products.ts
|
|
341
|
+
```tsx
|
|
342
|
+
import {Config} from 'alinea'
|
|
343
|
+
|
|
344
|
+
export const Products = Config.document('Products', {
|
|
345
|
+
contains: ['Product'],
|
|
346
|
+
overview: {
|
|
347
|
+
tabs: {
|
|
348
|
+
all: {label: 'All'},
|
|
349
|
+
active: {label: 'Active', filter: {status: 'active'}},
|
|
350
|
+
archived: {label: 'Archived', filter: {status: 'archived'}}
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
fields: {}
|
|
354
|
+
})
|
|
355
|
+
```
|
|
356
|
+
|
|
260
357
|
## Mixed lists
|
|
261
358
|
|
|
262
359
|
When a parent contains several types, a column can select a different value per type. Key `select` and `sortBy` by the type names of your schema:
|
|
@@ -342,6 +439,33 @@ export function ExportProducts({query}: OverviewActionProps) {
|
|
|
342
439
|
}
|
|
343
440
|
```
|
|
344
441
|
|
|
442
|
+
## Header
|
|
443
|
+
|
|
444
|
+
`header` renders a component between the toolbar (and the tabs) and the listed entries, for example totals or a chart of what is listed. It receives `OverviewHeaderProps`: the `workspace`, `root` and `parentId` of the list, the `locale`, the `search` terms, the `filter` of the selected tab and the applied filters (undefined when none applies) and a `query` for the listed entries, without paging. Like `actions`, it takes a component or a path to one: `header: '@/views/ProductTotals'`.
|
|
445
|
+
|
|
446
|
+
File: views/ProductTotals.tsx
|
|
447
|
+
```tsx
|
|
448
|
+
import {type OverviewHeaderProps, useGraph} from 'alinea/cms'
|
|
449
|
+
import {Stack, Text} from 'alinea/components'
|
|
450
|
+
import {useEffect, useState} from 'react'
|
|
451
|
+
import {Product} from '@/schema/Product'
|
|
452
|
+
|
|
453
|
+
export function ProductTotals({query}: OverviewHeaderProps) {
|
|
454
|
+
const graph = useGraph()
|
|
455
|
+
const [prices, setPrices] = useState<Array<number | null>>([])
|
|
456
|
+
useEffect(() => {
|
|
457
|
+
graph.find({...query, select: Product.price}).then(setPrices)
|
|
458
|
+
}, [graph, query])
|
|
459
|
+
const total = prices.reduce<number>((sum, price) => sum + (price ?? 0), 0)
|
|
460
|
+
return (
|
|
461
|
+
<Stack direction="row" gap={24}>
|
|
462
|
+
<Text>{prices.length} products</Text>
|
|
463
|
+
<Text>€{total} in total</Text>
|
|
464
|
+
</Stack>
|
|
465
|
+
)
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
345
469
|
## Entry tables in your own views
|
|
346
470
|
|
|
347
471
|
`EntryTable` from `alinea/cms` renders a table of entries with the columns of an overview: the headers sort and a row opens its entry. Use it in a `Field.view` section, or any other custom view, to show related entries. Here a brand lists its products:
|
|
@@ -384,7 +508,7 @@ export function BrandProducts() {
|
|
|
384
508
|
`EntryTable` takes:
|
|
385
509
|
|
|
386
510
|
- `type`: list entries of this type, or of an array of types.
|
|
387
|
-
- `filter`: a query [filter](query/filtering.md), such as `{brand: {has: {_entry: entry.id}}}`.
|
|
511
|
+
- `filter`: a query [filter](query/filtering.md), such as `{brand: {has: {_entry: entry.id}}}`. Conditions on related entries work too: `{_referenced: Query.referencedBy({type: Product, filter: {stock: {gt: 0}}})}` lists the entries that a product in stock links to.
|
|
388
512
|
- `workspace`, `root`, `parentId`: only list entries in this workspace or root, or the children of this entry (`null` for the top level).
|
|
389
513
|
- `overview`: the columns and default order to use: an overview object, or the type or root that configures one. Defaults to the overview of the root or type that contains `type`.
|
|
390
514
|
- `columns`: show these columns instead of those of the overview.
|
|
@@ -445,7 +569,7 @@ Prefer this over a type with a hidden title and a single `Field.view`: that crea
|
|
|
445
569
|
|
|
446
570
|
Two other options replace parts of the dashboard:
|
|
447
571
|
|
|
448
|
-
- A type's `view` replaces the
|
|
572
|
+
- A type's `view` replaces the form of entries of that type, below the entry header. It receives `TypeViewProps`: `{type}`.
|
|
449
573
|
- A type's `defaultView: 'overview'` opens the overview of an entry's children first, instead of its form.
|
|
450
574
|
|
|
451
575
|
## Upgrading from Alinea 1.x
|
package/docs/query/filtering.md
CHANGED
|
@@ -21,6 +21,7 @@ A filter is an object with a condition per key. The keys are:
|
|
|
21
21
|
|
|
22
22
|
- the fields of the queried `type`, such as `featured` or `publishDate`. TypeScript checks them when you pass a `type`.
|
|
23
23
|
- the entry's own properties with an underscore: `_id`, `_type`, `_parentId`, `_path`, `_url`, `_status`, `_locale`, `_workspace`, `_root`, `_index`, `_createdAt` and `_updatedAt`.
|
|
24
|
+
- `_referenced`: whether another entry links to the entry. `true` or `false`, or a `Query.referencedBy(...)` query that the linking entry has to match, see [Entries that link here](related.md#entries-that-link-here).
|
|
24
25
|
|
|
25
26
|
Every key has to match, so several keys in one filter mean "and".
|
|
26
27
|
|
|
@@ -152,6 +153,7 @@ export async function searchSite(terms: string, locale: string) {
|
|
|
152
153
|
How it matches:
|
|
153
154
|
|
|
154
155
|
- The input is split into words, and an entry must match every word.
|
|
156
|
+
- Hyphenated, spaced and joined spellings find each other: `multi-modal`, `multi modal` and `multimodal` all match "multi-modal", "multi modal" and "multimodal".
|
|
155
157
|
- Words match as prefixes, so `alin` finds "Alinea".
|
|
156
158
|
- Small typos are tolerated: one wrong letter for words of 5 to 14 characters, more for longer words. Case and accents don't matter.
|
|
157
159
|
- Entry titles are always searched. Other fields only when you mark them `searchable: true`: [text](../fields/text.md) and [rich text](../fields/rich-text.md) fields support it, also inside lists, objects and rich text blocks.
|
package/docs/query/related.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Related
|
|
2
2
|
|
|
3
|
-
A single query can also fetch entries related to the ones it finds: their parents, children and siblings, their translations, and the entries
|
|
3
|
+
A single query can also fetch entries related to the ones it finds: their parents, children and siblings, their translations, the entries they link to and the entries that link to them. Put a relation in `select` (or in `include` when you don't select), and it's resolved in the same query.
|
|
4
4
|
|
|
5
5
|
```tsx
|
|
6
6
|
import {cms} from '@/cms'
|
|
@@ -100,9 +100,9 @@ const post = await cms.get({
|
|
|
100
100
|
|
|
101
101
|
Linked entries are looked up in the locale of the entry you queried, falling back to entries without a locale, so a link to an untranslated author keeps working from every translation of a post.
|
|
102
102
|
|
|
103
|
-
##
|
|
103
|
+
## Entries that link here
|
|
104
104
|
|
|
105
|
-
Links are stored on the entry that links
|
|
105
|
+
Links are stored on the entry that links. `Query.referencedBy` finds the entries that link to the entry, through any of their fields, including links in rich text. Pass `type` to only find entries of that type, and `field` to only follow one link field of it. Like the other relations it takes the query options, `count: true` and `first: true`:
|
|
106
106
|
|
|
107
107
|
```tsx
|
|
108
108
|
import {cms} from '@/cms'
|
|
@@ -110,19 +110,68 @@ import {Author, BlogPost} from '@/schema'
|
|
|
110
110
|
import {Query} from 'alinea'
|
|
111
111
|
|
|
112
112
|
export async function authorPage(path: string) {
|
|
113
|
-
|
|
113
|
+
return cms.get({
|
|
114
114
|
type: Author,
|
|
115
115
|
path,
|
|
116
|
-
select: {
|
|
116
|
+
select: {
|
|
117
|
+
name: Query.title,
|
|
118
|
+
bio: Author.bio,
|
|
119
|
+
// The posts whose author field links to this author
|
|
120
|
+
posts: Query.referencedBy({
|
|
121
|
+
type: BlogPost,
|
|
122
|
+
field: BlogPost.author,
|
|
123
|
+
select: {title: Query.title, url: Query.url},
|
|
124
|
+
orderBy: {desc: BlogPost.publishDate}
|
|
125
|
+
}),
|
|
126
|
+
// Every entry linking to the author, in any field
|
|
127
|
+
mentions: Query.referencedBy({count: true})
|
|
128
|
+
}
|
|
117
129
|
})
|
|
118
|
-
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The entries that link to a translated entry are looked up in its locale. For an entry without a locale, such as a shared author, the linking entries of every locale are returned: pass `locale` to only get those of one locale.
|
|
134
|
+
|
|
135
|
+
The same relation is a filter condition: `_referenced` holds for entries that an entry of the query links to. `{_referenced: true}` matches entries that anything links to and `{_referenced: false}` entries nothing links to, such as unused media files:
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
import {cms} from '@/cms'
|
|
139
|
+
import {Author, BlogPost} from '@/schema'
|
|
140
|
+
import {Query} from 'alinea'
|
|
141
|
+
|
|
142
|
+
// Authors of at least one featured post
|
|
143
|
+
const featuredAuthors = await cms.find({
|
|
144
|
+
type: Author,
|
|
145
|
+
filter: {
|
|
146
|
+
_referenced: Query.referencedBy({
|
|
147
|
+
type: BlogPost,
|
|
148
|
+
field: BlogPost.author,
|
|
149
|
+
filter: {featured: true}
|
|
150
|
+
})
|
|
151
|
+
},
|
|
152
|
+
select: Query.title
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Filtering on the link
|
|
157
|
+
|
|
158
|
+
The other way around, "the posts by this author" is a filter on the posts: links store the id of their target as `_entry`. This is the way to go when you start from the posts, for example to page through them:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import {cms} from '@/cms'
|
|
162
|
+
import {BlogPost} from '@/schema'
|
|
163
|
+
import {Query} from 'alinea'
|
|
164
|
+
|
|
165
|
+
export async function postsBy(authorId: string, page: number) {
|
|
166
|
+
return cms.find({
|
|
119
167
|
type: BlogPost,
|
|
120
168
|
// Single link: has, multiple links: includes
|
|
121
|
-
filter: {author: {has: {_entry:
|
|
169
|
+
filter: {author: {has: {_entry: authorId}}},
|
|
122
170
|
select: {title: Query.title, url: Query.url},
|
|
123
|
-
orderBy: {desc: BlogPost.publishDate}
|
|
171
|
+
orderBy: {desc: BlogPost.publishDate},
|
|
172
|
+
skip: page * 10,
|
|
173
|
+
take: 10
|
|
124
174
|
})
|
|
125
|
-
return {author, posts}
|
|
126
175
|
}
|
|
127
176
|
```
|
|
128
177
|
|
package/docs/query.md
CHANGED
|
@@ -79,7 +79,58 @@ const urls = await cms.find({type: BlogPost, select: Query.url})
|
|
|
79
79
|
// Array<string>
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
`Query` has `id`, `title`, `type`, `index`, `workspace`, `root`, `status`, `parentId`, `locale`, `path`, `url`, `aliases`, `createdAt` and `updatedAt`, plus the relation helpers such as `Query.children` described in [Related content](query/related.md) and `Query.snippet` for [search results](query/filtering.md).
|
|
82
|
+
`Query` has `id`, `title`, `type`, `index`, `workspace`, `root`, `status`, `parentId`, `locale`, `path`, `url`, `aliases`, `createdAt` and `updatedAt`, plus the relation helpers such as `Query.children` and `Query.referencedBy` described in [Related content](query/related.md) and `Query.snippet` for [search results](query/filtering.md).
|
|
83
|
+
|
|
84
|
+
### Query fields and type fields
|
|
85
|
+
|
|
86
|
+
There are two kinds of references you can select. `Query.*` are the built-in properties every entry has, whatever its type: `Query.id`, `Query.title`, `Query.url` and the others above. The fields you define are selected through their type: `BlogPost.intro` is the `intro` field of `BlogPost`, `BlogPost.cover` its cover image. Type fields are typed from their definition and resolved like they are without a select, so `BlogPost.cover` returns an `ImageLink`. Combine both in one `select`:
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
import {cms} from '@/cms'
|
|
90
|
+
import {BlogPost} from '@/schema'
|
|
91
|
+
import {Query} from 'alinea'
|
|
92
|
+
|
|
93
|
+
const posts = await cms.find({
|
|
94
|
+
type: BlogPost,
|
|
95
|
+
select: {
|
|
96
|
+
// Built-in properties of the entry
|
|
97
|
+
id: Query.id,
|
|
98
|
+
title: Query.title,
|
|
99
|
+
url: Query.url,
|
|
100
|
+
// Fields of the BlogPost type
|
|
101
|
+
intro: BlogPost.intro,
|
|
102
|
+
cover: BlogPost.cover
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
// Array<{id: string, title: string, url: string, intro: string, cover: ImageLink | null}>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
A type field only has a value on entries of that type, so select it in a query for that `type` (or one filtered on `_type`). `Query` fields work in any query, also one without a `type`, such as a search across all entries. Fields that `Config.document` adds, like `title` and `path`, are available both ways: `Query.title` and `BlogPost.title` return the same value.
|
|
109
|
+
|
|
110
|
+
Fields inside an [object field](fields/object.md) are selected through the object: `Page.seo.title` returns the `title` of the `seo` object, typed like the field. That works wherever a field can be selected, also inside relations such as `Query.children`, and in `orderBy`:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
import {cms} from '@/cms'
|
|
114
|
+
import {Config, Field, Query} from 'alinea'
|
|
115
|
+
|
|
116
|
+
export const Page = Config.document('Page', {
|
|
117
|
+
fields: {
|
|
118
|
+
seo: Field.object('SEO', {
|
|
119
|
+
fields: {
|
|
120
|
+
title: Field.text('SEO title'),
|
|
121
|
+
description: Field.text('SEO description')
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const pages = await cms.find({
|
|
128
|
+
type: Page,
|
|
129
|
+
select: {url: Query.url, title: Page.seo.title},
|
|
130
|
+
orderBy: {asc: Page.seo.title}
|
|
131
|
+
})
|
|
132
|
+
// Array<{url: string, title: string}>
|
|
133
|
+
```
|
|
83
134
|
|
|
84
135
|
Selecting only what a page needs keeps the data you pass to client components small, and TypeScript infers the result either way. To name the type of a result, use `Awaited<ReturnType<typeof yourQueryFunction>>`.
|
|
85
136
|
|
package/docs/quickstart.md
CHANGED
|
@@ -20,22 +20,22 @@ Navigate to the newly created project directory and install the package with you
|
|
|
20
20
|
|
|
21
21
|
Variant: npm
|
|
22
22
|
```shellscript
|
|
23
|
-
npm install alinea@
|
|
23
|
+
npm install alinea@rc
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Variant: yarn
|
|
27
27
|
```shellscript
|
|
28
|
-
yarn add alinea@
|
|
28
|
+
yarn add alinea@rc
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Variant: pnpm
|
|
32
32
|
```shellscript
|
|
33
|
-
pnpm add alinea@
|
|
33
|
+
pnpm add alinea@rc
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Variant: bun
|
|
37
37
|
```shellscript
|
|
38
|
-
bun add alinea@
|
|
38
|
+
bun add alinea@rc
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
## 3. Initialize the project
|
|
@@ -44,7 +44,7 @@ Alinea requires a [config file](configuration.md) which can be auto-generated by
|
|
|
44
44
|
|
|
45
45
|
Variant: npm
|
|
46
46
|
```shellscript
|
|
47
|
-
npx alinea@
|
|
47
|
+
npx alinea@rc init
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
Variant: yarn
|
package/docs/schema/document.md
CHANGED
|
@@ -57,6 +57,8 @@ export const Home = Config.document('Home', {
|
|
|
57
57
|
})
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
Next to `fields`, a document takes `seo` and `details`: fields added to the SEO and Details tabs. `settings` adds a Settings tab with its fields.
|
|
61
|
+
|
|
60
62
|
## Using the metadata
|
|
61
63
|
|
|
62
64
|
Select the metadata field in the query for a page and map it to your framework's head tags. With the Next.js App Router:
|
package/docs/schema/type.md
CHANGED
|
@@ -19,6 +19,7 @@ The label ("Author") is what editors see. The name the type is stored under is i
|
|
|
19
19
|
## Options
|
|
20
20
|
|
|
21
21
|
- `fields` (required): the [fields](../fields.md) of the type, keyed by name. Field names must start with a letter and contain only letters, digits and underscores. Spread [tabs](../fields/tabs.md) or `Field.view(...)` sections in between to lay out the form.
|
|
22
|
+
- `settings`: fields that editors set in the settings sheet of a block, below its label and anchor, instead of in the block's form. Entries of the type show them in a Settings tab. They are stored and queried like the other fields, so their names can't repeat a field name.
|
|
22
23
|
- `contains`: the types that can be created as children of this entry, by schema name or by reference. Without it, entries of this type can't have children.
|
|
23
24
|
- `overview`: how the dashboard lists the children: columns, the default order (`sort`), the layout and actions. See [Overviews](../overviews.md).
|
|
24
25
|
- `insertOrder`: where new children are added: `'first'`, `'last'` or `'free'` (default), which lets the editor choose in the create dialog.
|
|
@@ -27,7 +28,8 @@ The label ("Author") is what editors see. The name the type is stored under is i
|
|
|
27
28
|
- `hidden`: set to `true` to leave entries of this type out of the sidebar tree. They can still be linked to and queried.
|
|
28
29
|
- `defaultView`: `'edit'` opens the form when an entry is selected, `'overview'` shows a table of its children first. The default is `'overview'` for entries that have children and `'edit'` otherwise.
|
|
29
30
|
- `preview`: turn [live previews](../live-previews.md) on or off for this type (`true`/`false`), or pass a React component that renders the preview. It overrides the setting of the root, workspace and config.
|
|
30
|
-
- `view`: a React component, or a path to one, that replaces the
|
|
31
|
+
- `view`: a React component, or a path to one, that replaces the form of entries of this type. It renders below the entry header and edits the entry with the hooks of `alinea/cms`. It receives `{type}`. See [Replace the form of a type](../custom-fields.md#replace-the-form-of-a-type).
|
|
32
|
+
- `actions`: components rendered in the header of the entry editor, next to the publish button, such as an export button. Each is a component or a path to one and receives `EntryActionProps`: the `entry` and the `locale` it is edited in. See [Add actions to the entry header](../custom-fields.md#add-actions-to-the-entry-header).
|
|
31
33
|
- `orderChildrenBy` (deprecated): sort children by one or more fields. Use `overview.sort`, which still accepts the same value.
|
|
32
34
|
- `summaryRow`, `summaryThumb` (deprecated): not used by the dashboard. Configure the columns and card image in the `overview` of the parent instead.
|
|
33
35
|
|
package/docs/upgrading.md
CHANGED
|
@@ -4,23 +4,25 @@ This guide walks through moving an existing Alinea 1.7 project to 2.0. Your cont
|
|
|
4
4
|
|
|
5
5
|
## Checklist
|
|
6
6
|
|
|
7
|
-
1. Run Node.js 24 or higher, locally, in CI and wherever your site runs, and use React 19.
|
|
8
|
-
2. Update the package: `npm install alinea@
|
|
9
|
-
3. Add `handlerUrl` to your config, it is now required. Replace `dashboardFile` with `adminPath`.
|
|
7
|
+
1. Run Node.js 24 or higher, locally, in CI (scheduled jobs that run the alinea CLI too) and wherever your site runs, and use React 19.
|
|
8
|
+
2. Update the package: `npm install alinea@rc` (or `pnpm add`, `yarn add`, `bun add`).
|
|
9
|
+
3. Add `handlerUrl` to your config, it is now required, and set `baseUrl.production` (or make `handlerUrl` absolute). Replace `dashboardFile` with `adminPath`.
|
|
10
10
|
4. Make sure your Next.js config is wrapped in `withAlinea` and your scripts run `alinea dev -- next dev` and `alinea build -- next build`: media URLs are now served through your site.
|
|
11
|
-
5.
|
|
12
|
-
6.
|
|
13
|
-
7.
|
|
14
|
-
8.
|
|
15
|
-
9.
|
|
16
|
-
10.
|
|
17
|
-
11.
|
|
11
|
+
5. Media: replace `image.url` with `image.src` and use the URLs Alinea returns instead of building them from `location`. `alinea build` warns about media files that store a url as their location, `alinea build --fix` repairs them.
|
|
12
|
+
6. Move children of `Config.media(...)` into its `children` option.
|
|
13
|
+
7. Remove the options of `Field.metadata(...)` and the `step` option of `Field.time`.
|
|
14
|
+
8. Rich text: pass `extensions` as a function, upgrade custom extensions to Tiptap 3 and render custom attributes as `data-*` attributes.
|
|
15
|
+
9. Handler: replace `remote` with `backend`, and move entry hooks to `beforeCommit` / `afterCommit`.
|
|
16
|
+
10. Self-hosted OAuth2: provide `validateClaims`.
|
|
17
|
+
11. Custom field views and dashboard code: import hooks from `alinea/cms` and components from `alinea/components` instead of `alinea/dashboard` and `alinea/ui`, replace `useEntryEditor` with `useEntry` and rename the theme tokens your CSS reads.
|
|
18
|
+
12. Run `alinea build`, open the dashboard and save an entry to review the diff.
|
|
19
|
+
13. Upgrading from a 2.0 preview instead? See [From a 2.0 preview](#from-a-2-0-preview).
|
|
18
20
|
|
|
19
21
|
## Runtime requirements
|
|
20
22
|
|
|
21
|
-
Node.js 24 or higher. The CLI exits with "Alinea requires Node version 24 or higher" on older versions. The bundled content database is opened with Node's built-in `node:sqlite` (or `bun:sqlite` under Bun), so the runtime that serves your site needs Node 24 too, not only the machine that builds it.
|
|
23
|
+
Node.js 24 or higher. The CLI exits with "Alinea requires Node version 24 or higher" on older versions. The bundled content database is opened with Node's built-in `node:sqlite` (or `bun:sqlite` under Bun), so the runtime that serves your site needs Node 24 too, not only the machine that builds it. The same goes for scheduled CI jobs that run the alinea CLI, such as a nightly `alinea build` or a script that imports content: update the Node version in those workflows as well.
|
|
22
24
|
|
|
23
|
-
React 19. `react` and `react-dom` 19 are now peer dependencies and the CLI checks for them. In practice that means Next.js 15 or newer.
|
|
25
|
+
React 19. `react` and `react-dom` 19 are now peer dependencies and the CLI checks for them. In practice that means Next.js 15 or newer. Check third-party React renderers too: libraries built on React 18 internals, such as older versions of react-three-fiber or react-pixi, break under React 19. Upgrade them to a version that supports React 19.
|
|
24
26
|
|
|
25
27
|
## Configuration
|
|
26
28
|
|
|
@@ -50,6 +52,23 @@ export const cms = createCMS({
|
|
|
50
52
|
|
|
51
53
|
The handler now matches its path exactly, so requests have to use the configured URL (a trailing slash no longer matches).
|
|
52
54
|
|
|
55
|
+
### baseUrl is required for production
|
|
56
|
+
|
|
57
|
+
Your site reaches the handler at `handlerUrl` resolved against `baseUrl`, also in production, to check for new content. Without a production `baseUrl` (and with a relative `handlerUrl`), `alinea build` exits and queries throw `Missing baseUrl in Alinea config: set baseUrl.production to the url of your site`. Alinea doesn't fall back to the request origin or to hosting provider variables: set the production URL of your site yourself, for example from an environment variable you define:
|
|
58
|
+
|
|
59
|
+
File: cms.ts
|
|
60
|
+
```tsx
|
|
61
|
+
export const cms = createCMS({
|
|
62
|
+
schema,
|
|
63
|
+
workspaces,
|
|
64
|
+
baseUrl: {
|
|
65
|
+
development: 'http://localhost:3000',
|
|
66
|
+
production: process.env.SITE_URL ?? 'https://example.com'
|
|
67
|
+
},
|
|
68
|
+
handlerUrl: '/api/cms'
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
53
72
|
### dashboardFile is replaced by adminPath
|
|
54
73
|
|
|
55
74
|
`adminPath` is the path the dashboard is served on, `/admin` by default. `alinea build` writes the dashboard to `public/admin.html` and `withAlinea` serves it at `/admin`. `dashboardFile` still works when `adminPath` isn't set, but it is deprecated. See [Configuration](configuration.md) for the new `maxUploadSize` and `tracer` options.
|
|
@@ -70,6 +89,28 @@ What to check:
|
|
|
70
89
|
- Media requests now reach your handler route. A static export without the handler can't serve them.
|
|
71
90
|
- If you defined `images.localPatterns` or `remotePatterns` yourself, check that the admin file route is still allowed. The `uploads.alinea.cloud` remote pattern is no longer added.
|
|
72
91
|
- Don't change the `mediaDir` of an existing workspace: stored locations are relative to it. New projects default to `public/media`.
|
|
92
|
+
- A media file whose `location` is a url (some 1.x saves wrote the upload url there) can't be served. `alinea build` warns about these files; `alinea build --fix` points them at the file of the same name in the `mediaDir` when there is one.
|
|
93
|
+
|
|
94
|
+
Image links only have `src`: the `url` property, which held the same value, was removed so that spreading an image into `<img>` or `next/image` (`<Image {...image} />`) doesn't put a `url` attribute on the element. Replace `image.url` with `image.src`. File links keep `url` and `href`.
|
|
95
|
+
|
|
96
|
+
Don't derive media URLs from the stored file. Code that reads `location`, or parses the 1.x file names (`name.<id>.ext`) to build a URL or find an id, breaks on `/admin/file/...` URLs. Use the URL Alinea returns instead: `src` for images, `url` (or `href`) for files, and `_url` when you query media files directly:
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
import {cms} from '@/cms'
|
|
100
|
+
import {Query} from 'alinea'
|
|
101
|
+
import {MediaFile} from 'alinea/core'
|
|
102
|
+
|
|
103
|
+
// 1.7: built the url from the stored location
|
|
104
|
+
// const src = file.location
|
|
105
|
+
|
|
106
|
+
const files = await cms.find({
|
|
107
|
+
type: MediaFile,
|
|
108
|
+
select: {title: Query.title, url: Query.url}
|
|
109
|
+
})
|
|
110
|
+
// Without a select each result has its url as `_url`
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`ImageLink` no longer needs a type parameter when the image field has no extra fields: replace `ImageLink<undefined>` with `ImageLink`.
|
|
73
114
|
|
|
74
115
|
A workspace can set `mediaUrl` to add a prefix to its media URLs, which helps when several workspaces have files with the same name.
|
|
75
116
|
|
|
@@ -123,6 +164,8 @@ Field.richText('Body', {
|
|
|
123
164
|
|
|
124
165
|
The `RichText` renderer from `alinea/ui` always renders tables with a `<tbody>`, and the `tbody` view override is gone.
|
|
125
166
|
|
|
167
|
+
Attributes that a custom extension adds to a node only reach your views and tag overrides of `RichText` as `data-*` attributes: render them as `data-*` in the extension's `renderHTML`. Content saved by 1.7 gets these attributes when it is saved again, see [Custom attributes](fields/rich-text.md#custom-attributes).
|
|
168
|
+
|
|
126
169
|
### Edit.move
|
|
127
170
|
|
|
128
171
|
```tsx
|
|
@@ -157,9 +200,12 @@ const handler = createHandler({
|
|
|
157
200
|
// Inspect or rewrite the changes, return the mutations to commit
|
|
158
201
|
return mutations
|
|
159
202
|
},
|
|
160
|
-
afterCommit({
|
|
161
|
-
// Pages that query content refresh on their own, revalidate your own
|
|
162
|
-
|
|
203
|
+
afterCommit({entries, removed}) {
|
|
204
|
+
// Pages that query content refresh on their own, revalidate your own
|
|
205
|
+
// caches, eg. a feed that lists products
|
|
206
|
+
const changed = [...entries, ...removed]
|
|
207
|
+
if (changed.some(entry => entry.type === 'Product'))
|
|
208
|
+
revalidateTag('products', 'max')
|
|
163
209
|
}
|
|
164
210
|
})
|
|
165
211
|
|
|
@@ -167,7 +213,7 @@ export const GET = handler
|
|
|
167
213
|
export const POST = handler
|
|
168
214
|
```
|
|
169
215
|
|
|
170
|
-
Errors thrown in
|
|
216
|
+
`afterCommit` receives the `mutations`, the `sha` of the new commit, the `user`, the changed `entries` as they are after the commit and the `removed` entries with the url they had. Errors thrown in it are logged and don't fail the commit. See [Instant publishing](instant-publishing.md).
|
|
171
217
|
|
|
172
218
|
### remote is replaced by backend
|
|
173
219
|
|
|
@@ -203,7 +249,7 @@ The dashboard was rebuilt on React Aria Components. Nothing changes for editors
|
|
|
203
249
|
|
|
204
250
|
### Components
|
|
205
251
|
|
|
206
|
-
`alinea/ui` now only exports the `RichText` renderer and the `imageBlurUrl` helper (and the deprecated `HStack`/`VStack`). Its `Button`, `Chip`, `Icon`, `Loader`, `Typo` and other components were removed, as were the files behind deep imports like `alinea/ui/Button`. Use the public component library instead, it is what the dashboard itself is built from:
|
|
252
|
+
`alinea/ui` now only exports the `RichText` renderer and the `imageBlurUrl` helper (and the deprecated `HStack`/`VStack`, use `Stack` from `alinea/components`). Its `Button`, `Chip`, `Icon`, `Loader`, `Typo` and other components were removed, as were the files behind deep imports like `alinea/ui/Button`. Use the public component library instead, it is what the dashboard itself is built from:
|
|
207
253
|
|
|
208
254
|
```tsx
|
|
209
255
|
// 1.7
|
|
@@ -215,6 +261,23 @@ import {Button, Spinner} from 'alinea/components'
|
|
|
215
261
|
|
|
216
262
|
See [Components](components.md). Dashboard icons are available from `alinea/dashboard/icons`.
|
|
217
263
|
|
|
264
|
+
### Theme tokens
|
|
265
|
+
|
|
266
|
+
The CSS custom properties of 1.7 were renamed and many were removed. Styles that read an old token silently fall back to the property's default. The closest replacements:
|
|
267
|
+
|
|
268
|
+
- `--alinea-background` → `--alinea-bg`, `--alinea-content` → `--alinea-backdrop`, `--alinea-lift`, `--alinea-inner` and `--alinea-toolbar` → `--alinea-bg-muted`.
|
|
269
|
+
- `--alinea-foreground` → `--alinea-fg`, `--alinea-foreground-faded` → `--alinea-fg-muted`, `--alinea-title-foreground` → `--alinea-fg-strong`.
|
|
270
|
+
- `--alinea-outline` and `--alinea-inner-outline` → `--alinea-border`, `--alinea-highlight` → `--alinea-muted`.
|
|
271
|
+
- `--alinea-fields` → `--alinea-input`, `--alinea-fields-foreground` → `--alinea-field-label`, `--alinea-fields-focus` and `--alinea-focus-foreground` → `--alinea-ring`.
|
|
272
|
+
- `--alinea-selected` stays, `--alinea-selected-foreground` → `--alinea-selected-fg`, `--alinea-selected-faded` → `--alinea-primary-subtle`.
|
|
273
|
+
- `--alinea-button-background` → `--alinea-primary`, `--alinea-button-foreground` → `--alinea-primary-fg`, `--alinea-error` → `--alinea-danger`.
|
|
274
|
+
- `--alinea-accent` (the workspace color) → `--alinea-workspace-color`.
|
|
275
|
+
- `--alinea-variant-info-*` → `--alinea-primary-subtle` and `--alinea-primary-subtle-fg`, `--alinea-variant-success-*` → `--alinea-success-subtle(-fg)`, `--alinea-variant-progress-*` → `--alinea-warning-subtle(-fg)`.
|
|
276
|
+
- `--alinea-modal-background` → `--alinea-scrim`, `--alinea-shadow` → `--alinea-shadow-raised`, `--alinea-border-radius` → `--alinea-radius`.
|
|
277
|
+
- Removed without a replacement: `--alinea-hue`, `--alinea-saturation`, `--alinea-positive`, `--alinea-negative`, `--alinea-button-hover`, `--alinea-accent-foreground`, `--alinea-divider-top`, `--alinea-divider-bottom`, `--alinea-tabs-trigger-*`, `--alinea-fields-table`, `--alinea-fields-header`, `--alinea-fields-shadow`, `--alinea-fields-selected` and the `create` and `disabled` variants. Derive hover and disabled shades with `color-mix()` and `opacity`.
|
|
278
|
+
|
|
279
|
+
The tokens of `alinea/css`, and its resets, now apply inside elements with a `data-alinea-theme` attribute only. The dashboard sets it on `<html>`, so the CSS of your dashboard views reads the tokens as before. A page of your site that imports `alinea/css` to render `alinea/components` needs an element with `data-alinea-theme` around them. See [Theme tokens](components.md#theme-tokens) for the full list.
|
|
280
|
+
|
|
218
281
|
### Custom field views
|
|
219
282
|
|
|
220
283
|
Field views are still referenced by path (`view: '@/fields/Range.view'`) or passed as a component. The hooks and helpers for custom views now come from one entry point, `alinea/cms`. From `alinea/dashboard`, `useField` and its related hooks still work but are deprecated: the new `useField` returns a `[value, setValue]` tuple, and the options and error have their own hooks:
|
|
@@ -231,11 +294,11 @@ const options = useFieldOptions(field)
|
|
|
231
294
|
const error = useFieldError(field)
|
|
232
295
|
```
|
|
233
296
|
|
|
234
|
-
`useFieldValue`, `useFieldOptions`, `useFieldError` and `useFieldKey` move to `alinea/cms` too and `useFieldMutator` becomes `useFieldSetter`. Deep imports of dashboard hooks move as well: `useLocale` and `useGraph` from `alinea/dashboard/hook/*` are exported from `alinea/cms`, and `useEntryEditor` is replaced by `useEntry`. `alinea/dashboard/hooks` is internal now. `InputLabel` is now an alias of the `Field` component: it takes `label`, `description`, `error`, `required`, `disabled`, `readOnly`, `icon` and `shared`. Its 1.7 props `help`, `width`, `inline`, `size` and the fold props are gone, so help text passed through `{...options}` is no longer shown: pass it as `description`, or replace it with `FieldChrome` from `alinea/cms`, which reads the label, help text and error from the field. The form atoms (`useForm`, `FormProvider`, `FormRow` and so on) were removed from `alinea/dashboard`. See [Custom fields](custom-fields.md) for a complete example.
|
|
297
|
+
`useFieldValue`, `useFieldOptions`, `useFieldError` and `useFieldKey` move to `alinea/cms` too and `useFieldMutator` becomes `useFieldSetter`. Deep imports of dashboard hooks move as well: `useLocale` and `useGraph` from `alinea/dashboard/hook/*` are exported from `alinea/cms`, and `useEntryEditor` is replaced by `useEntry`. `useEntryEditor` still works as a shim for views that only read the entry: its `activeVersion` has the `id`, `locale`, `path`, `status`, `title` and `type` of the entry, and `activeVersion.data` only has `path`. Reading another property throws, read field values with `useFieldValue(field)` instead. `alinea/dashboard/hooks` is internal now. `InputLabel` is now an alias of the `Field` component: it takes `label`, `description`, `error`, `required`, `disabled`, `readOnly`, `icon` and `shared`. Its 1.7 props `help`, `width`, `inline`, `size` and the fold props are gone, so help text passed through `{...options}` is no longer shown: pass it as `description`, or replace it with `FieldChrome` from `alinea/cms`, which reads the label, help text and error from the field. The form atoms (`useForm`, `FormProvider`, `FormRow` and so on) were removed from `alinea/dashboard`. See [Custom fields](custom-fields.md) for a complete example.
|
|
235
298
|
|
|
236
299
|
### Custom type views
|
|
237
300
|
|
|
238
|
-
A type's `view` now only receives `{type}`, the `editor` prop of 1.7 is gone. A custom `auth` view in the config is not used: the dashboard always renders its own sign-in.
|
|
301
|
+
A type's `view` now only receives `{type}`, the `editor` prop of 1.7 is gone. The view no longer replaces the whole editor: it renders below the entry header, which publishes and saves drafts, and reads and writes the fields of the entry with the hooks of `alinea/cms`. A custom `auth` view in the config is not used: the dashboard always renders its own sign-in.
|
|
239
302
|
|
|
240
303
|
### List overviews
|
|
241
304
|
|
|
@@ -282,6 +345,24 @@ See [Overviews](overviews.md) for sorting, custom cells and toolbar actions.
|
|
|
282
345
|
|
|
283
346
|
`alinea/yjs` and `alinea/cloud/view/CloudAuth` no longer exist. `alinea/picker/entry/EntryPicker` and `alinea/picker/url/UrlPicker` no longer ship the picker UI: `EntryPickerModal`, `UrlPickerForm` and `UrlPickerModal` were removed without a public replacement. Import the picker option types (`EditorInfo`, `EditorLocation`, `EditorLimitLocation` and `EntryPickerConditions`) from `alinea/field/link`. Code that subclassed fields relied on the removed shape system (`alinea/core/Shape`, `Field.shape`, `Type.shape`): the field constructors no longer take a shape, and `postProcess` became `queryValue`.
|
|
284
347
|
|
|
348
|
+
## From a 2.0 preview
|
|
349
|
+
|
|
350
|
+
Projects on a 2.0 preview have most of the above already. These changes landed in the release candidate:
|
|
351
|
+
|
|
352
|
+
- Image links no longer have `url`, use `src`. File links keep `url` and `href`.
|
|
353
|
+
- `useEntry()` from `alinea/cms` returns a `DashboardEntry`: the `id`, `type`, `title`, `path`, `url`, `locale`, `status`, `workspace`, `root` and `parentId` of the entry and its saved field values in `data`. The other properties of the internal entry row are gone.
|
|
354
|
+
- `useEntryEditor()`: the publish status moved from `activeVersion.data.status` to `activeVersion.status`. Reading another property of `activeVersion.data` than `path` throws.
|
|
355
|
+
- `useSiblingFieldValue(key)` is deprecated: pass the field to `useFieldValue(field)`, which is typed and also reads fields of a parent row or the entry, or its key: `useFieldValue('path')`. `EditFields` takes an object field to render its fields in its scope.
|
|
356
|
+
- Theme tokens apply inside `data-alinea-theme` only, see [Theme tokens](#theme-tokens) above. The workspace color is `--alinea-workspace-color`.
|
|
357
|
+
- `Select` from `alinea/components` only shows its clear button with `clearable`. `Field.select` still lets editors clear fields that aren't required.
|
|
358
|
+
- `Select`, `ComboBox` and `MultipleSelect` values are strings or numbers, and `onValueChange` is typed like the `value` you pass. Without a `value`, it receives `string | number`.
|
|
359
|
+
- `TimeField` with a `name` submits `14:30` (or `14:30:15` with `granularity="second"`) like a native time input, it used to submit seconds always.
|
|
360
|
+
- `NumberField` no longer groups digits: pass `formatOptions={{useGrouping: true}}` to show `1,250`.
|
|
361
|
+
- `afterCommit` also receives the changed `entries` and the `removed` entries, so hooks no longer have to look them up from the mutations.
|
|
362
|
+
- Every field accepts `width`, custom fields don't need to declare it in their options.
|
|
363
|
+
- `alinea build` accepts an absolute `handlerUrl` without a production `baseUrl`, and its error names the missing `baseUrl` setting.
|
|
364
|
+
- A type's `view` renders below the entry header, in the editor of the entry: the field hooks of `alinea/cms` and `EditField` edit its fields and the header publishes them. It used to replace the whole editor without access to the fields.
|
|
365
|
+
|
|
285
366
|
## After upgrading
|
|
286
367
|
|
|
287
368
|
Run your build and open the dashboard. Things to try: an image renders on your site through `/admin/file/...`, publishing an entry updates the site, and custom fields still render. Then have a look at what's new in 2.0: [Field.localiser](internationalization.md) for per-field translations, URL aliases and backlinks, [instant publishing](instant-publishing.md) and the [MCP server](mcp.md) for coding agents.
|