Skip to content
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

fix: Preview mode updates #282

Merged
merged 3 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions website/src/lib/wordpress/graphql/queryContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { flexWysiwygContent } from './flexWysiwygContent';
import pageOptions from './fragmentPageOptions';
import seo from './fragmentSeo';

export function queryContent(draft) {
export function queryContent(draft, options) {
const query = /* GraphQL */ `
query GetContent($slug: ID!, $preview: Boolean) {
contentNode(id: $slug, idType: URI, asPreview: $preview) {
contentNode(id: $slug, idType: ${
options?.uriType ?? 'URI'
}, asPreview: $preview) {
__typename
id
databaseId
Expand Down
85 changes: 25 additions & 60 deletions website/src/lib/wordpress/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fetch from 'isomorphic-unfetch';
import { WORDPRESS_API_URL } from 'lib/constants';
import { trimLeadingSlash } from 'lib/utils';
import { queryContent } from './graphql/queryContent';
import { queryGlobals } from './graphql/queryGlobals';
import { queryPosts } from './graphql/queryPosts';
Expand Down Expand Up @@ -36,32 +37,6 @@ async function fetchAPI(query, { variables } = {}, token) {
return json.data;
}

/**
* Get post types and paths for preview mode redirects.
*/
export async function getContentTypes(token) {
const query = /* GraphQL */ `
query ContentTypes {
contentTypes {
nodes {
name
restBase
}
}
}
`;

const data = await fetchAPI(
query,
{
variables: { preview: true },
},
token,
);

return data?.contentTypes?.nodes;
}

/**
* To assist with Preview Mode, this will grab status for content by DB id
* (needed for revisions, unpublished content)
Expand All @@ -77,6 +52,7 @@ export async function getPreviewContent(
uri
status
databaseId
previewRevisionDatabaseId
contentType {
node {
name
Expand Down Expand Up @@ -156,45 +132,34 @@ export async function getNodeType(slug) {
/**
* Get fields for single page regardless of post type.
*/
export async function getContent(slug, preview, previewData) {
export async function getContent(
slug,
preview,
previewData,
options = {},
) {
let draft = false;
if (preview) {
// Get the content types to help build preview URLs
const contentTypesArray = await getContentTypes(
previewData?.token,
);
const contentTypes = {};

for (const contentType of contentTypesArray) {
contentTypes[contentType.restBase] = contentType.name;
}

// Because static pages can't support query strings and those
// make preview mode much easier across published statuses and post types
// this coerces any numeric slug in preview mode to an ID and assumes post type
const segments = slug.split('/');
const lastSegment = segments[segments.length - 1];
const secondLastSegment =
segments.length > 2 ? segments[segments.length - 2] : null;
// Get post type URL segment
const postType = contentTypes[secondLastSegment];

// wordpress requires a different slug structure for various post types
if (slug !== '/' && !isNaN(Number(lastSegment))) {
draft = true;
if (postType === 'post') {
slug = `/?p=${lastSegment}`;
} else if (secondLastSegment) {
slug = `/?id=${lastSegment}&post_type=${postType}`;
} else {
slug = `/?page_id=${lastSegment}`;
}
} else if (slug !== '/') {
slug += '?preview=true';
// This is based on Next.js wordpress example: https://github.com/vercel/next.js/blob/canary/examples/cms-wordpress/lib/api.ts#L105-L112
// If the preview is for a draft or a revision, we need to query by ID.
// We check to see if the preview data is for the same post as the slug
// and if it is, we set `slug` to the ID of the post and the `uriType` to `DATABASE_ID`.
const postPreview = previewData?.post;
const trimSlug = trimLeadingSlash(slug);
const isId = Number.isInteger(Number(trimSlug));
const isSamePost = isId && Number(trimSlug) === postPreview?.id;
const isDraft = isSamePost && postPreview?.status === 'draft';
const isRevision =
isSamePost && postPreview?.status === 'publish';

if ((isDraft || isRevision) && postPreview?.id) {
draft = isDraft;
slug = postPreview.id;
options.uriType = 'DATABASE_ID';
}
}

let query = queryContent(draft);
let query = queryContent(draft, options);

const data = await fetchAPI(
query,
Expand Down
18 changes: 10 additions & 8 deletions website/src/pages/[[...slug]].js
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,16 @@ export async function getStaticProps({
return { redirect: redirect };
}

// Check nodeType before assuming it's a contentNode. We 404 on nonsupported types, but you could handle.
const { nodeByUri } = await getNodeType(slug);
if (!nodeByUri?.isContentNode) {
return {
notFound: true,
revalidate: 60,
props: {},
};
if (!preview) {
// Check nodeType before assuming it's a contentNode. We 404 on nonsupported types, but you could handle.
const { nodeByUri } = await getNodeType(slug);
if (!nodeByUri?.isContentNode) {
return {
notFound: true,
revalidate: 60,
props: {},
};
}
}

const data = await getContent(slug, preview, previewData);
Expand Down
35 changes: 7 additions & 28 deletions website/src/pages/api/preview.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { authorize } from 'lib/auth';
import { getContentTypes, getPreviewContent } from 'lib/wordpress';
import { getPreviewContent } from 'lib/wordpress';

const COOKIE_MAX_AGE = 86400;

export default async function preview(req, res) {
let accessToken;
const { code, id, preview_id, path, slug } = req.query;
const { code, id, path, slug } = req.query;

// Get Auth Token
if (code) {
Expand Down Expand Up @@ -40,7 +40,7 @@ export default async function preview(req, res) {

// Fetch WordPress to check if the provided `id` exists
const post = await getPreviewContent(
preview_id || id,
id,
'DATABASE_ID',
accessToken,
);
Expand All @@ -50,20 +50,14 @@ export default async function preview(req, res) {
return res.status(401).json({ message: 'Post not found' });
}

// Get the content types to help build preview URLs
const contentTypesArray = await getContentTypes(accessToken);
const contentTypes = {};

for (const contentType of contentTypesArray) {
contentTypes[contentType.name] = contentType.restBase;
}
const postId = post?.previewRevisionDatabaseId || post?.databaseId;

// Enable Preview Mode by setting the cookies
res.setPreviewData(
{
post: {
uri: post.uri,
id: post.databaseId,
id: postId,
status: post.status,
type: post.contentType.node.name,
},
Expand All @@ -74,24 +68,9 @@ export default async function preview(req, res) {
},
);

let Location = post.uri;
const typePath = contentTypes[post.contentType.node.name];

// Draft posts need a little help pointing towards the correct Next.js page template
if (
post.status === 'draft' &&
post.contentType.node.name !== 'page'
) {
Location = `/${typePath}/${post.databaseId}`;
} else if (post.status === 'draft') {
Location = `/${post.databaseId}`;
} else if (preview_id && post.contentType.node.name !== 'page') {
Location = `/${typePath}/${preview_id}`;
} else if (preview_id || slug || path) {
Location = `/${preview_id || slug || path}`;
}
const location = `/${postId}/`;

// Redirect to the path from the fetched post
res.writeHead(307, { Location });
res.writeHead(307, { location });
return res.end();
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,33 @@ function headless_redirect() {
return $redirect;
}

// check if preview and user has edit ability.
// if so, redirect to preview path
if (is_preview()) {
if (current_user_can('edit_posts')) {
$auth_code = wpe_headless_generate_authentication_code(wp_get_current_user());

$redirect = $headless_domain . '/api/preview/?code=' . rawurlencode($auth_code) . '&id=' . $id;

return $redirect;
}
}

// else do standard redirect tree
if ($slug) {
$redirect = $headless_domain . $slug;
} else {
$path = $_SERVER['REQUEST_URI'];
$redirect = $headless_domain . $path;
}

// if a 404 quickly fail and send to headless domain to handle
// if we don't, WP will return info on a post we don't want (e.g. the most recent post)
if (is_404()) {
$redirect = $headless_domain . $path;
return $redirect;
}

// check if preview and user has edit ability.
// if so, redirect to preview path
if (is_preview()) {
Expand Down