-
Notifications
You must be signed in to change notification settings - Fork 0
feature: preview links in CMS #233
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e82e68e
2c0f7e3
1c907d8
a2391ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import type { Client } from '@datocms/cli/lib/cma-client-node'; | ||
|
||
export const createPreviewToken = async (client: Client) => { | ||
const roles = await client.roles.list(); | ||
const editorRole = roles.find((role) => role.name === 'Editor'); | ||
|
||
return client.accessTokens.create({ | ||
name: 'Preview', | ||
can_access_cda: true, | ||
can_access_cda_preview: false, | ||
can_access_cma: true, | ||
role: { | ||
type: 'role', | ||
id: editorRole!.id, | ||
}, | ||
}); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import type { Client } from '@datocms/cli/lib/cma-client-node'; | ||
import { createPreviewToken } from '../lib/createPreviewToken'; | ||
|
||
export default async function (client: Client) { | ||
console.log('Manage upload filters'); | ||
|
||
console.log('Install plugin "Model Deployment Links"'); | ||
const previewApiToken = await createPreviewToken(client); | ||
await client.plugins.create({ | ||
id: 'MKba9NT5QBKZaeI4HcERwA', | ||
package_name: 'datocms-plugin-model-deployment-links', | ||
}); | ||
await client.plugins.update('MKba9NT5QBKZaeI4HcERwA', { | ||
parameters: { datoApiToken: previewApiToken.token }, | ||
}); | ||
|
||
console.log('Creating new fields/fieldsets'); | ||
|
||
console.log( | ||
'Create JSON field "Preview" (`preview`) in model "\uD83D\uDCD1 Page" (`page`)' | ||
); | ||
await client.fields.create('LjXdkuCdQxCFT4hv8_ayew', { | ||
id: 'XF2LuFVWSrmu7Lle8xlhTg', | ||
label: 'Preview', | ||
field_type: 'json', | ||
api_key: 'preview', | ||
localized: true, | ||
appearance: { | ||
addons: [], | ||
editor: 'MKba9NT5QBKZaeI4HcERwA', | ||
parameters: { urlPattern: '/{ locale }/{ slug }/' }, | ||
}, | ||
}); | ||
|
||
console.log( | ||
'Create JSON field "Preview" (`preview`) in model "\uD83C\uDFE0 Home" (`home_page`)' | ||
); | ||
await client.fields.create('X_tZn3TxQY28ltSyjZUGHQ', { | ||
id: 'YPXZOMoWRdKTHLUkN9ytfw', | ||
label: 'Preview', | ||
field_type: 'json', | ||
api_key: 'preview', | ||
localized: true, | ||
appearance: { | ||
addons: [], | ||
editor: 'MKba9NT5QBKZaeI4HcERwA', | ||
parameters: { urlPattern: '/{ locale }/' }, | ||
}, | ||
}); | ||
|
||
console.log( | ||
'Create JSON field "Preview" (`preview`) in model "\uD83E\uDD37 Not found" (`not_found_page`)' | ||
); | ||
await client.fields.create('d_AvMVoMSqmNbMqx-NdqIw', { | ||
id: 'O_DCfpaDSzq1MX4DaRlMpQ', | ||
label: 'Preview', | ||
field_type: 'json', | ||
api_key: 'preview', | ||
localized: true, | ||
appearance: { | ||
addons: [], | ||
editor: 'MKba9NT5QBKZaeI4HcERwA', | ||
parameters: { urlPattern: '/{ locale }/404' }, | ||
}, | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -66,6 +66,18 @@ You can now run your project locally: | |||||
npm run dev | ||||||
``` | ||||||
|
||||||
### Configure DatoCMS plugins | ||||||
|
||||||
Head Start comes with a few DatoCMS plugins pre-installed. Some plugins require additional project-specific configuration: | ||||||
|
||||||
#### Model Deployments Links plugin | ||||||
|
||||||
The [Model Deployment Links plugin](https://www.datocms.com/marketplace/plugins/i/datocms-plugin-model-deployment-links) requires an API key: | ||||||
|
||||||
- In your DatoCMS instance go to Project Settings > API Tokens (`/project_settings/access_tokens`) and "Add a new access token". Name it "Preview" (or whatever you prefer), for the "Role associated with this API token | ||||||
" select "Editor" and keep the other settings as is. | ||||||
- Go to Enviroment Configuration > Plugins > Model Deployment Links and enter the newly created access token in the plugin settings under "DatoCMS API Token". | ||||||
|
||||||
Comment on lines
+79
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The word 'Enviroment' appears to be misspelled. Consider correcting it to 'Environment'.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
### Add DatoCMS secrets to repository | ||||||
|
||||||
Head Start provides GitHub Actions which include linting code and validating HTML on PR changes. These Actions require the DatoCMS tokens to be available. | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,6 @@ | ||
--- | ||
import type { SiteLocale } from '@lib/i18n/types'; | ||
import type { | ||
NotFoundPageQuery, | ||
NotFoundPageRecord, | ||
} from '@lib/datocms/types'; | ||
import type { NotFoundPageQuery, NotFoundPageRecord } from '@lib/datocms/types'; | ||
import { datocmsRequest } from '@lib/datocms'; | ||
import { noIndexTag, titleTag } from '@lib/seo'; | ||
import Layout from '@layouts/Default.astro'; | ||
|
@@ -15,14 +12,16 @@ export const prerender = false; | |
|
||
Astro.response.status = 404; | ||
|
||
const { locale } = Astro.params as { locale: SiteLocale }; | ||
const localeFromPath = Astro.params.locale as SiteLocale; | ||
const localeFromQuery = Astro.url.searchParams.get('locale') as SiteLocale; | ||
const locale = localeFromQuery || localeFromPath; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is now needed to have the |
||
const { page } = (await datocmsRequest<NotFoundPageQuery>({ | ||
query, | ||
variables: { locale }, | ||
})) as { page: NotFoundPageRecord }; | ||
--- | ||
|
||
<Layout pageUrls={[]} seoMetaTags={[noIndexTag, titleTag(page.title)]}> | ||
<h1>{page.title} {Astro.params.locale}</h1> | ||
<h1>{page.title}</h1> | ||
<Blocks blocks={page.bodyBlocks as AnyBlock[]} /> | ||
</Layout> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,19 +53,30 @@ type Params = { | |
|
||
const { locale, path } = Astro.params as Params; | ||
const variables = { locale, slug: getPageSlugFromPath(path) }; | ||
const { page } = (await datocmsRequest<PageQuery>({ query, variables })) as { | ||
page: NonNullable<PageQuery['page']>; // Only NonNullable when statically generated. Handle as a 404 when this is a server route! | ||
}; | ||
const breadcrumbs = [...getParentPages(page), page].map((page) => | ||
const { page } = await datocmsRequest<PageQuery>({ query, variables }); | ||
|
||
if (!page) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check ensures this type casting is no longer needed: as {
page: NonNullable<PageQuery['page']>; // Only NonNullable when statically generated. Handle as a 404 when this is a server route!
}; But it makes the |
||
return Astro.rewrite(`/404/?locale=${locale}`); | ||
} | ||
|
||
const canonicalUrl = getPageHref({ locale, record: page }); | ||
if (Astro.url.pathname !== canonicalUrl) { | ||
return Astro.redirect(canonicalUrl); | ||
} | ||
|
||
const breadcrumbs = [...getParentPages(page), page].map((record) => | ||
formatBreadcrumb({ | ||
text: page.title, | ||
href: getPageHref({ locale, record: page }), | ||
text: record.title, | ||
href: getPageHref({ locale, record }), | ||
}) | ||
); | ||
const pageUrls = (page._allSlugLocales || []).map(({ locale }) => ({ | ||
locale: locale as SiteLocale, | ||
pathname: getPageHref({ locale: locale as SiteLocale, record: page }), | ||
})) as PageUrl[]; | ||
|
||
const pageLocales = (page._allSlugLocales?.map(({ locale }) => locale) ?? | ||
[]) as SiteLocale[]; | ||
const pageUrls = pageLocales.map((locale) => ({ | ||
locale, | ||
pathname: getPageHref({ locale, record: page }), | ||
})) satisfies PageUrl[]; | ||
--- | ||
|
||
<Layout | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
#import '@lib/routing/PageRoute.fragment.graphql' | ||
|
||
query ReroutePage($locale: SiteLocale!, $slug: String!) { | ||
page(locale: $locale, filter: { slug: { eq: $slug } }) { | ||
...PageRoute | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import type { APIRoute } from 'astro'; | ||
import { datocmsRequest } from '@lib/datocms'; | ||
import type { ReroutePageQuery, SiteLocale } from '@lib/datocms/types'; | ||
import { getPageHref } from '@lib/routing'; | ||
import query from './_page.query.graphql'; | ||
|
||
export const prerender = false; | ||
|
||
const jsonResponse = (data: object, status: number = 200) => { | ||
return new Response(JSON.stringify(data), { | ||
status, | ||
headers: { | ||
'Content-Type': 'application/json', | ||
}, | ||
}); | ||
}; | ||
|
||
export const GET: APIRoute = async ({ request }) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a generic alternate approach that I used in the nododos-website. But I think the canonical url check in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmm. I guess the canonical check works on localhost and preview as the generic page route is then always handled dynamically on the server. However the production environment has prerendered pages and will throw a 404 I suppose. So I guess we do need this reroute API handler. In that case the config of the Preview field of the generic page will need to use that one instead. |
||
const locale = new URL(request.url).searchParams.get('locale') as SiteLocale; | ||
if (!locale) { | ||
return jsonResponse({ error: 'Missing \'locale\' parameter' }, 400); | ||
} | ||
|
||
const slug = new URL(request.url).searchParams.get('slug'); | ||
if (!slug) { | ||
return jsonResponse({ error: 'Missing \'slug\' parameter' }, 400); | ||
} | ||
|
||
const { page } = (await datocmsRequest<ReroutePageQuery>({ query, variables: { slug, locale } })); | ||
if (!page) { | ||
return jsonResponse({ error: 'Page not found' }, 404); | ||
} | ||
|
||
return new Response('',{ | ||
status: 307, | ||
headers: { 'Location': getPageHref({ locale, record: page }) }, | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: this could also be achieved within a migration script using https://www.datocms.com/docs/content-management-api/resources/access-token/create . However it's difficult to reliably get the Editor role ID. Since this is a one time config step, I'm happy to have it as a manual step, rather than scripting the generation of access token.