Fix removing field contents in Edit.update
. Pass an undefined value to remove
field contents:
await cms.commit(
Edit.update({
id: '...',
set: {removeMe: undefined}
})
)
Fix processing link data correctly even it contains legacy data
Add the Infer.Entry and Infer.ListItem types which can be used to infer the type of an entry or list item from a query.
type Entry = Infer.Entry<typeof EntryType>
const entry: Entry = await cms.get({type: MyType})
type ListItem = Infer.ListItem<typeof ListType>
const list: Array<ListItem> = await cms.get({select: MyType.list})
withAlinea
export found in
'alinea/next'.cms.find/get
is rewritten to take a single query object.
Have a look at the docs to see how to use the new query api.Field.create
.npx alinea build --fix
.The cms handler has been rewritten to handle both backend and previews. This
requires updating your handler route. In the case of Next.js you can replace
both app/api/cms/[...slug]/route.ts
and /app/api/preview.ts
with the
following:
// app/api/cms/route.ts
import {cms} from '@/cms'
import {createHandler} from 'alinea/next'
const handler = createHandler(cms)
export const GET = handler
export const POST = handler
This release also requires you to restructure your Alinea config file.
The dashboard property is replaced by the baseUrl
, handlerUrl
and
dashboardFile
properties.
// cms.tsx
// Previously:
const cms = createCMS({
// ... schema and workspaces
dashboard: {
dashboardUrl: '/admin.html',
handlerUrl: '/api/cms',
staticFile: 'public/admin.html'
},
preview:
process.env.NODE_ENV === 'development'
? 'http://localhost:3000/api/preview'
: '/api/preview'
})
// Becomes:
const cms = createCMS({
// ... schema and workspaces
baseUrl: {
// Point this to your local frontend
development: 'http://localhost:3000'
// If hosting on vercel you can use: process.env.VERCEL_URL
production: 'http://example.com'
},
handlerUrl: '/api/cms',
dashboardFile: 'admin.html',
// Optionally supply the public folder with
publicDir: 'public',
// Enable previews which are handled via handlerUrl
preview: true
})
createNextCMS
and createCMS
from the alinea
package
root. Instead use the createCMS
function from alinea/core
or
alinea/next
.enableTables
option.Add Edit.link
and Edit.links
to create link values in the Edit api. These
are currently not optimally typed and will be improved in the future.
const imageField = Field.link('Image', {
fields: {
alt: Field.text('Alt text')
}
})
const imageValue = Edit.link(imageField)
.addImage(imageId, {alt: 'An image'})
.value()
Entries in the dashboard sidebar can now be ordered by a field value.
Use the orderChildrenBy
configuration option to set which field to order by.
const Type = Config.type('Type', {
orderChildrenBy: Query.title.asc() // Order by Entry title
// ...
})
Add subscript and superscript options to rich text Fields.
Stop using the porter stemming FTS5 tokenizer in search queries. The algorithm does not work well with non-english languages and so is not a good default.
Fix select box not showing a checkmark in the explorer while replacing file or image links.
Unset validation errors when fields are no longer used.
Reserve Alinea generated properties (#378)
Until now Alinea generated properties that define the content structure as normal identifiers. This means that in a list a row would contain a "type", "id" and "index" property while also containing user defined fields. This had a lot of potential for name clashes: you could choose to name a field "type" for example. It is also not future-proof as Alinea might want to add more properties later on. To solve this issue Alinea now reserves all properties starting with an underscore as internal. It's not needed to upgrade content files manually. The old format will automatically be picked up for some time. It's possible to upgrade all files in one go by running the following, which will write any changes in content back to disk.
npx alinea build --fix
When querying content please pay mind that those internal properties are now accessed by the underscored property and will need to be updated. This becomes most apparent in getting results out of the Link field.
const MyType = Config.type('MyType', {
link: Field.link('Link', {
fields: {
label: Field.text('Label')
}
})
})
const result = await cms.get(Query(MyType).select(MyType.link))
// before:
// EntryReference & {label: string}
// ^? {id: string, type: string, ..., url: string, label: string}
// after:
// EntryLink<{label: string}>
// ^? {_id: string, _type: string, ..., url: string, fields: {label: string}}
Use of Type.isContainer is now deprecated, use a list of types in contains instead.
Use of Type.isHidden is now deprecated, use hidden
instead.
Entry
from alinea/core
which was missing in the previous release.includeSelf
option when querying translations.Two changes that impact how to write config files:
Bundle configuration function in Config
, and fields in a Field
namespace
which is exported from the alinea
package (this is optional).
Deprecate alinea.meta
style configuration.
This change is applied to Schema
(types
), Workspace
(roots
),
Root
(entries
), Type
(fields
), Document
(fields
), Select
fields (options
), Tab
fields (fields
).
To upgrade your config files:
// Before
import alinea from 'alinea'
const Type = alinea.type('Name', {
field: alinea.text('Field'),
[alinea.meta]: {
contains: ['Page']
}
})
// After
import {Config, Field} from 'alinea'
const Type = Config.type('Name', {
contains: ['Page'],
fields: {
field: Field.text('Field')
}
})
Add the mutation API (#374)
This introduces a commit method on CMS instances that can be used to mutate cms content. It can be used to create, update and delete Entries.
Add the Query namespace
alinea
exports this should not be a breaking change.createNextCMS
function is now deprecated and it is recommended to
import it as {createCMS} from 'alinea/next'
instead.alinea
version changes. This means breaking changes to the schema will not cause
errors in the browser.Add a preview widget which enables editors to easily switch from previewing to editing. Enable by setting widget to true:
<cms.previews widget />
Add a function to retrieve the current logged in user:
console.log(await cms.user())
.css
files used for custom fields and views..css
and .module.css
in custom fields and views.isContainer
optional if contains
is used on Types.Field validation (#369)
Introduces two new Field options available for every Field: required
and
validate
. The required
option will make sure the field value is not empty
when saving. The validate
option can be used to validate the field value
using a custom function. The function should return true
if the value is
valid, false
if it is not valid and a string if it is not valid and a
message should be shown to the user.
This is a breaking change, removing the optional
property from Fields.
It was never functional.
alinea.text('Hello field', {
help: 'This field only accepts "hello" as a value',
validate(value) {
if (value !== 'hello') return 'Only "hello" is allowed!'
}
})
condition
option are now constrained with their localeFix storing extra fields
on the Link field correctly for multiple links.
In conditional configuration functions it's now possible to access fields from
parent contexts. For example field options of a nested field inside a List
field can depend on the value of a field in the entry root.
const innerField = alinea.text('Read-only if status is published')
const Type = alinea.type('Conditional example', {
status: alinea.select('Status', {
draft: 'Draft',
published: 'Published'
}),
list: alinea.list('List', {
schema: {ListRow: alinea.type({innerField})}
})
})
alinea.track.options(innerField, get => {
return {readOnly: get(Type.status) === 'published'}
})
Entry
fields showing up as type unkown
in TypeScript.readOnly
option that is included in all fields will now show a lock item
next to the field label. The option is passed down in nested fields such as
the List
and Rich text
fields.The interval at which Alinea polls the backend for content updates is now configurable. It can be set in config.syncInterval and overwritten per query.
// Poll for updates every 60 seconds
const results = await cms.syncInterval(60).find(Entry())
// Disable syncing all for this query in case you want results fast,
// but not necessarily up to date
const matches = await cms.disableSync().find(Entry().search('fast'))
Pages can be queried with search terms.
Any (rich) text field with the searchable
option set to true
is indexed.
const results = await cms.find(Page().search('search', 'terms'))
Shared fields (#365)
Introduce the shared option for Fields. Fields can be persisted over all
locales if your content is localised by setting the shared
option to true
.
When the entry is published the field data is copied to other locales.
This is currently only supported on the root level, not on nested fields.
const Type = alinea.type('Persist', {
// Persist field data over all locales
sharedField: alinea.text('Shared text', {shared: true})
})
Conditional configuration (#364)
All field configuration can be adjusted based on the value of other fields. After defining fields in a Type a tracker function can be set up. The tracker function takes a reference to a field and a subscription function. In the subscription function field values can be retrieved and new options returned.
const Example = alinea.type('Conditional example', {
textField: alinea.text('Text field'),
readOnly: alinea.check('Make read-only'),
hidden: alinea.check('Hide field')
})
alinea.track.options(Example.textField, get => {
const textField = get(Example.textField)
const readOnly = get(Example.readOnly)
const hidden = get(Example.hidden)
return {
readOnly,
hidden,
help: `Text has ${textField.length} characters`
}
})
Preview
and BrowserPreview
exports from the alinea
package
to @alinea/preview
. This should help import the alinea config within in
restrictive environments such as Next 13 which will throw a compile error if
any browser code is imported. This will likely be tweaked in future releases.@alinea/preview/remix
preview hookGenerate types (#271)
Up until now the TypeScript definitions that were available for the content schema were fully inferred using the TypeScript compiler. While this had some advantages it also came with stability issues and overall did not prove to be the best solution. TypeScript definitions are now generated from the schema at build time. The runtime type information should prove useful for the upcoming GraphQL support as well. Since GrapQL does not come with namespacing we've introduced a few breaking changes:
The config file now supports a single schema at the root level, meaning the schema is used for every workspace.
The generated package structure in turn became simpler because the workspace distinction is no longer needed:
// Init pages now available from /pages
import {initPages} from '@alinea/generated/pages'
// The Page type describes every content type of the schema
// type Page = Page.TypeA | Page.TypeB
import {Page} from '@alinea/generated'
Remix run support (#273)
A few changes were necessary to get started with Remix. These changes should make it easier to work with other frameworks as well.
serve
instance if it is running. Since
Remix does not watch file changes in node modules this should make sure
you're always viewing the latest changes.serve
and generate
commands by avoiding race
conditions while publishingNEXT_PUBLIC_
, PUBLIC_
, VITE_
or GATSBY_
.alinea serve
command will apply publish actions directly to the memory
store instead of relying on the file watcher. This should result in better
performance.Pages.whereRoot
method which did not use the new alinea.root
location--fix
option to the generate command, which will write back any
missing or incorrect properties to disk.@alinea/css
package. Build outputs are now cached in the ci step using wireit but this
file was not included.The url
property of entries can now be controlled using the entryUrl
function in the type options. Urls are computed during generation and this can
help to keep them constant if you're using a web framework that does file
system routing. The available paramters are path
, parentPaths
and
locale
.
For example: making sure a doc page always has an url in
the form of /doc/$path
you can specify entryUrl
as the following:
type('Doc', {...fields}).configure({
entryUrl({path}) {
return `/doc/${path}`
}
})
The iframe used in the BrowserPreview
component now supports top level
navigation so it becomes possible to link the user to a cms route from within.
(#246)
The index of newly created entries will be based on the first child of parent. This makes them consistently sortable when published. (#241)
The exports of the alinea package are restructured. This is a breaking change because the input fields are no longer exposed directly but bundled in the "alinea" namespace. A few less used exports were removed and can be found in the @alinea packages.
Client code is shielded from being included server side when compiling with the "worker" condition enabled.
Initial support for selecting external links in the link field. The RichText ui component is adjusted to correctly render links. A custom component or tag can be passed to render links.
<RichText a={<a className="custom-link" />} doc={doc} />
<RichText a={CustomLinkComponent} doc={doc} />
typeNamespace
. It did not take the namespace into account, but
does now.The alinea cli will now forward commands placed after the serve or generate commands. It will wait until the alinea package is generated before doing so to make sure userland code can always depend on the package being available. It also simplifies running the dashboard and development server without requiring tools like npm-run-all. In practice, for a next.js website, this means one can configure scripts like so:
{
"scripts": {
"dev": "alinea serve -- next dev",
"build": "alinea generate -- next build"
}
}
Added a button to mark text as small within the rich text editor
New UI buttons to insert rows in any position in a list field
User preferences get a dedicated popover
Previews can register a listener and implement their own refetch mechanism. The communication happens via messages which work cross-origin.
// For any environment
import {registerPreview} from 'alinea/preview'
registerPreview({
refetch() {
// Reload server data
}
})
// A react hook is available
import {usePreview} from 'alinea/preview/react'
const {isPreviewing} = usePreview({
refetch() {
// Reload server data & redraw
}
})
// A hook specifically for next.js, which refetches static/server props
import {useNextPreview} from 'alinea/preview/next'
const {isPreviewing} = useNextPreview()
alinea
object that bundles the previously
exported config functions.A RichText react component is exposed from @alinea/ui to render rich text values
Auto-close navigation sidebar only for small screens (< 768px)
Update number field styles to use updated css variable names
Select fields configuration can now be set using the configure
method. This
helps type inference for the initial value.
select('Level', {
info: 'Info',
warning: 'Warning'
}).configure({
initialValue: 'info'
})