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

Docs: Move to Sanity for Content Editong #3864

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ playwright/test-results

# Local Netlify folder
.netlify
docs/.env.local
2 changes: 2 additions & 0 deletions docs/docs-components/DocsSideNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { useNavigationContext } from './navigationContext';
import newSidebarIndex, { siteIndexType } from './siteIndex';
import useGetSideNavItems from './useGetSideNavItems';

// to-do: pull the site index from Sanity CMS

export const MIN_NAV_WIDTH_PX = 280;

export function convertNamesForURL(name: string): string {
Expand Down
2 changes: 2 additions & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
},
"dependencies": {
"@mdx-js/react": "^2.1.1",
"@sanity/client": "^6.22.3",
"date-fns": "^3.6.0",
"gestalt": ">0.0.0",
"gestalt-charts": ">0.0.0",
"gestalt-datepicker": ">0.0.0",
"gestalt-design-tokens": ">0.0.0",
"gray-matter": "^4.0.3",
"groq": "^3.63.0",
"highlight.js": "^10.4.1",
"history": "^5.0.0",
"lz-string": "^1.4.5",
Expand Down
35 changes: 30 additions & 5 deletions docs/pages/[...id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import { MDXRemote } from 'next-mdx-remote';
import { serialize } from 'next-mdx-remote/serialize';
import remarkBreaks from 'remark-breaks';
import remarkGfm from 'remark-gfm';
import { Text } from 'gestalt';
import ErrorBoundary from '../docs-components/ErrorBoundary';
import MarkdownPage from '../docs-components/MarkdownPage';
import { getAllMarkdownPosts, getDocByRoute } from '../utils/mdHelper';
import { getPostBySlug, getSanityRoutes } from '../utils/sanity';

function getPlatform(pathName: string): 'android' | 'ios' | 'web' {
if (pathName.startsWith('android')) return 'android';
Expand Down Expand Up @@ -54,20 +56,39 @@ export async function getStaticProps(context: {
params: {
id: ReadonlyArray<string>;
};
preview?: boolean;
previewData?: {
[key: string]: string;
};
}): Promise<{
props: {
props?: {
sanity?: boolean;
meta: {
[key: string]: string;
};
content: Record<any, any>;
pageSourceUrl: string;
platform: 'android' | 'ios' | 'web';
};
notFound?: boolean;
}> {
const { id } = context.params;

if (context.preview) {
console.log('In Preview Mode.. Fetching the latest draft from Sanity....');
}

const pathName = id.join('/');
const { meta, content } = await getDocByRoute(pathName);

const { meta, content, isMDX } = await getDocByRoute(pathName, context.preview);

if (!isMDX) {
return {
notFound: true,
};
}

console.log(content);

// @ts-expect-error - TS2345 - Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
const mdxSource = await serialize(content, {
Expand All @@ -77,6 +98,7 @@ export async function getStaticProps(context: {
return {
props: {
meta,
// if it's preview mode, (pass in the raw data from GetserverSideProps...) also pass the raw content to be rendered.. the client side will handle the rendering
content: mdxSource,
pageSourceUrl: `https://github.com/pinterest/gestalt/tree/master/docs/markdown/${pathName}.md`,
platform: getPlatform(pathName),
Expand All @@ -90,18 +112,21 @@ export async function getStaticPaths(): Promise<{
id: string | ReadonlyArray<string>;
};
}>;
fallback: boolean;
fallback: boolean | 'blocking';
}> {
const sanityRoutes = await getSanityRoutes();
console.log('paths', sanityRoutes);

// get all the possible paths that exist within ./markdown folder
const paths = await getAllMarkdownPosts();

return {
paths: paths.map((name) => ({
paths: sanityRoutes.map((name) => ({
params: {
id: name,
},
})),

fallback: false, // show 404 if not a valid path }
fallback: 'blocking', // show 404 if not a valid path }
};
}
24 changes: 24 additions & 0 deletions docs/pages/api/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { NextApiRequest, NextApiResponse } from 'next/types';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { slug, token } = req.query;

// to-do: replace with a more secure token
if (token !== 'hello') {
res.status(401).json({ message: 'Invalid Viewer Token' });
return;
}

if (!slug) {
res.status(400).json({ message: 'Missing params', arguments: req.query });
return;
}

res.setPreviewData({
maxAge: 1 * 60, // The preview mode cookies expires in 1 minute
});
res.writeHead(307, {
Location: `/${slug}?time=${Date.now()}`,
});
res.end('preview mode');
}
2 changes: 2 additions & 0 deletions docs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"isolatedModules": true,
"jsx": "preserve",
"paths": {
"sanity-gestalt-docs": ["../sanity"],
"gestalt": ["../packages/gestalt"],
"gestalt-charts": ["../packages/gestalt-charts"],
"gestalt-datepicker": ["../packages/gestalt-datepicker"]
Expand All @@ -24,6 +25,7 @@
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"],
"references": [
{ "path": "../sanity" },
{ "path": "../packages/gestalt" },
{ "path": "../packages/gestalt-charts" },
{ "path": "../packages/gestalt-datepicker" }
Expand Down
32 changes: 29 additions & 3 deletions docs/utils/mdHelper.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { promises as fs } from 'fs';
import path from 'path';
import matter from 'gray-matter';
import logGAEvent from './gAnalytics';
import { getPostBySlug } from './sanity';
import siteIndex, { siteIndexType } from '../docs-components/siteIndex';

export async function getDocByRoute(route: string): Promise<{
export async function getMarkdownFileContent(route: string): Promise<{
content?: string;
meta: {
[key: string]: string;
Expand All @@ -25,11 +25,37 @@ export async function getDocByRoute(route: string): Promise<{

return { route, meta: data, content, isMDX: true };
} catch (ex: any) {
logGAEvent('md-page-not-found', { route, error: ex.message });
return { route, isMDX: false, meta: {} };
}
}

export async function getDocByRoute(
route: string,
isDraftMode?: boolean,
): Promise<{
content?: string;
meta: {
[key: string]: string;
};
route: string;
isMDX: boolean;
}> {
// check if the the route is available locally
const localMarkdownData = await getMarkdownFileContent(route);

if (localMarkdownData.isMDX) {
return localMarkdownData;
}

const sanityPost = await getPostBySlug(route, isDraftMode);
if (sanityPost && sanityPost.markdown) {
console.log('sanityPost', sanityPost);
// to-do: meta/frontmatter {title, description, fullWidth} here should align with the meta data from the markdown files
return { route, meta: sanityPost, content: sanityPost.markdown, isMDX: true };
}
return { route, meta: {}, isMDX: false };
}

export async function getAllMarkdownPosts(): Promise<ReadonlyArray<ReadonlyArray<string>>> {
function convertNamesForURL(name: string): string {
return name.replace(/ /g, '_').replace(/'/g, '').toLowerCase();
Expand Down
56 changes: 56 additions & 0 deletions docs/utils/sanity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { createClient } from '@sanity/client';
import { Post, Slug } from 'sanity-gestalt-docs';

export const client = createClient({
projectId: 'k05lbr97',
dataset: 'docs',
apiVersion: new Date().toISOString().slice(0, 10),
token: '', // or leave blank for unauthenticated usage
useCdn: false, // `false` if you want to ensure fresh data
perspective: 'published',
});

function sanityFetch<T>(query: string, isDraftMode = false): Promise<T> {
const token = process.env.NEXT_PUBLIC_SANITY_DRAFT_AUTHORIZATION_TOKEN;

if (isDraftMode && !token) {
throw new Error(
'The `NEXT_PUBLIC_SANITY_DRAFT_AUTHORIZATION_TOKEN` environment variable is required.',
);
}

return client.fetch<T>(
query,
{},
{
token,
perspective: isDraftMode ? 'previewDrafts' : 'published',
},
);
}

// Fetch Sanity Draft Posts when in Preview Mode and populate the post

export async function getPostBySlug(slug: string, isDraftMode = false): Promise<Post | null> {
const query = `*[_type == "post" && slug.current == "${slug}"][0]`;
const post = await sanityFetch<Post | null>(query, isDraftMode);
return post;
}

// uses GROQ to query content: https://www.sanity.io/docs/groq
export async function getSanityRoutes(): Promise<ReadonlyArray<ReadonlyArray<string>>> {
const slugs = await client.fetch<{ slug: Slug | null }[]>('*[_type == "post"] {slug{current}}');
const validSlugs = slugs.filter((slug) => slug.slug !== null);
const routes = validSlugs.map((s) => s.slug!.current?.split('/') || []);
return routes;
}

export async function createPost(post: Post) {
const result = client.create(post);
return result;
}

export async function updateDocumentTitle(_id: string, title: string) {
const result = client.patch(_id).set({ title });
return result;
}
3 changes: 3 additions & 0 deletions sanity/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "@sanity/eslint-config-studio"
}
29 changes: 29 additions & 0 deletions sanity/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# Dependencies
/node_modules
/.pnp
.pnp.js

# Compiled Sanity Studio
/dist

# Temporary Sanity runtime, generated by the CLI on every dev server start
/.sanity

# Logs
/logs
*.log

# Coverage directory used by testing tools
/coverage

# Misc
.DS_Store
*.pem

# Typescript
*.tsbuildinfo

# Dotenv and similar local-only files
*.local
11 changes: 11 additions & 0 deletions sanity/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Sanity Blogging Content Studio

Congratulations, you have now installed the Sanity Content Studio, an open-source real-time content editing environment connected to the Sanity backend.

Now you can do the following things:

- [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme)
- Check out the example frontend: [React/Next.js](https://github.com/sanity-io/tutorial-sanity-blog-react-next)
- [Read the blog post about this template](https://www.sanity.io/blog/build-your-own-blog-with-sanity-and-next-js?utm_source=readme)
- [Join the community Slack](https://slack.sanity.io/?utm_source=readme)
- [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme)
30 changes: 30 additions & 0 deletions sanity/defaultDocumentNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {type DefaultDocumentNodeResolver} from 'sanity/structure'
import {Iframe} from 'sanity-plugin-iframe-pane'
import {type Post} from './sanity.types'

// Customise this function to show the correct URL based on the current document
const preview_url = 'http://localhost:8888/api/preview' //'https://deploy-preview-3864--gestalt.netlify.app'
function getPreviewUrl(doc: Post) {
return doc?.slug?.current
? `${preview_url}/?slug=${doc.slug.current}&token=hello`
: `${preview_url}`
}

// Import this into the deskTool() plugin
export const defaultDocumentNode: DefaultDocumentNodeResolver = (S, {schemaType}) => {
// Only show preview pane on `movie` schema type documents
switch (schemaType) {
case `postaa`:
return S.document().views([S.view.form()])
default:
return S.document().views([
S.view.form(),
S.view
.component(Iframe)
.options({
url: (doc: Post) => getPreviewUrl(doc),
})
.title('Preview'),
])
}
}
37 changes: 37 additions & 0 deletions sanity/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "sanity-docs",
"private": true,
"version": "1.0.0",
"license": "UNLICENSED",
"types": "./sanity.types.ts",
"scripts": {
"dev": "sanity dev",
"start": "sanity start",
"build": "sanity build",
"deploy": "sanity deploy",
"deploy-graphql": "sanity graphql deploy"
},
"keywords": [
"sanity"
],
"dependencies": {
"@sanity/vision": "^3.63.0",
"easymde": "^2.18",
"react": "^18.3",
"react-dom": "^18.3",
"sanity": "^3.63.0",
"sanity-plugin-iframe-pane": "^3.1.6",
"sanity-plugin-markdown": "^5.0.0",
"styled-components": "^6.1.8"
},
"devDependencies": {
"@sanity/eslint-config-studio": "^4.0.0",
"@types/react": "^18.0.25"
},
"prettier": {
"semi": false,
"printWidth": 100,
"bracketSpacing": false,
"singleQuote": true
}
}
13 changes: 13 additions & 0 deletions sanity/sanity.cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {defineCliConfig} from 'sanity/cli'

export default defineCliConfig({
api: {
projectId: 'k05lbr97',
dataset: 'docs'
},
/**
* Enable auto-updates for studios.
* Learn more at https://www.sanity.io/docs/cli#auto-updates
*/
autoUpdates: true,
})
Loading
Loading