docs: rework docs site to have different sections (#2686)

This PR starts putting in place the high-level changes we want to make
to the docs site.
- It makes separate sections for Reference and Examples and Community.
- Gets rid of the secondary sidebar and integrates it into the main
sidebar.
- Groups the reference articles by type.
- Pulls in the examples alongside code and a live playground so people
don't have to visit examples.tldraw.com separately.

<img width="1458" alt="Screenshot 2024-01-30 at 09 43 46"
src="https://github.com/tldraw/tldraw/assets/469604/4f5aa339-3a69-4d9b-9b9f-dfdddea623e8">

Again, this is the top-level changes and there's more to be done for the
next PR(s):
  - create quick start page
  - clean up installation page
  - add accordion to Examples page prbly
  - put fun stuff in header (from footer)
  - landing page
  - something for landing page of API
  - search cmd-k and border
  - cleanup _sidebarReferenceContentLinks
  - external links _blank
  - address potential skew issue with code examples
  - have a link to other examples (next.js, etc.)

### Change Type

- [x] `documentation` — Changes to the documentation only[^2]

### Test Plan

1. Make sure examples work!

### Release Notes

- Rework our docs site to pull together the examples app and reference
section more cohesively.

---------

Co-authored-by: Taha <98838967+Taha-Hassan-Git@users.noreply.github.com>
Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
Co-authored-by: Mitja Bezenšek <mitja.bezensek@gmail.com>
Co-authored-by: alex <alex@dytry.ch>
Co-authored-by: Lu Wilson <l2wilson94@gmail.com>
Co-authored-by: Dan Groshev <git@dgroshev.com>
This commit is contained in:
Mime Čuvalo 2024-01-30 14:19:25 +00:00 committed by GitHub
parent a43b172b64
commit 3ae48af67c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
68 changed files with 94415 additions and 92577 deletions

View file

@ -65,6 +65,8 @@ export async function connect(opts = {} as { reset?: boolean }) {
status TEXT NOT NULL,
date TEXT,
sourceUrl TEXT,
componentCode TEXT,
componentCodeFiles TEXT,
keywords TEXT,
content TEXT NOT NULL,
path TEXT,

View file

@ -8,17 +8,17 @@ import { getApiMarkdown } from './getApiMarkdown'
export async function createApiMarkdown() {
const apiInputSection: InputSection = {
id: 'gen' as string,
id: 'reference' as string,
title: 'API Reference',
description: "Reference for the tldraw package's APIs (generated).",
categories: [],
sidebar_behavior: 'show-title',
sidebar_behavior: 'reference',
}
const addedCategories = new Set<string>()
const INPUT_DIR = path.join(process.cwd(), 'api')
const OUTPUT_DIR = path.join(CONTENT_DIR, 'gen')
const OUTPUT_DIR = path.join(CONTENT_DIR, 'reference')
if (fs.existsSync(OUTPUT_DIR)) {
fs.rmSync(OUTPUT_DIR, { recursive: true })
@ -85,7 +85,7 @@ export async function createApiMarkdown() {
const sectionsJsonPath = path.join(CONTENT_DIR, 'sections.json')
const sectionsJson = JSON.parse(fs.readFileSync(sectionsJsonPath, 'utf8')) as InputSection[]
sectionsJson.splice(
sectionsJson.findIndex((s) => s.id === 'gen'),
sectionsJson.findIndex((s) => s.id === 'reference'),
1
)
sectionsJson.push(apiInputSection)

View file

@ -11,8 +11,8 @@ export async function generateApiContent(): Promise<GeneratedContent> {
const sections = require(path.join(CONTENT_DIRECTORY, 'sections.json')) as InputSection[]
try {
const inputApiSection = sections.find((s) => s.id === 'gen')
if (!inputApiSection) throw new Error(`Could not find section with id 'gen'`)
const inputApiSection = sections.find((s) => s.id === 'reference')
if (!inputApiSection) throw new Error(`Could not find section with id 'reference'`)
const outputApiSection = generateSection(inputApiSection, articles, 999999) // always at the end!
const contentComplete = { sections: [outputApiSection], articles }

View file

@ -13,7 +13,7 @@ export async function generateContent() {
}
for (let i = 0; i < sections.length; i++) {
if (sections[i].id === 'gen') continue
if (sections[i].id === 'reference') continue
result.sections.push(generateSection(sections[i], result.articles, i))
}

View file

@ -0,0 +1,34 @@
import { Articles, GeneratedContent, InputSection } from '../../types/content-types'
import { generateSection } from './generateSection'
const { log: nicelog } = console
const section: InputSection = {
id: 'examples',
title: 'Examples',
description: 'Code recipes for bending tldraw to your will.',
categories: [
{ id: 'basic', title: 'Getting Started', description: '', groups: [] },
{ id: 'ui', title: 'UI/Theming', description: '', groups: [] },
{ id: 'shapes/tools', title: 'Shapes & Tools', description: '', groups: [] },
{ id: 'data/assets', title: 'Data & Assets', description: '', groups: [] },
{ id: 'editor-api', title: 'Editor API', description: '', groups: [] },
{ id: 'collaboration', title: 'Collaboration', description: '', groups: [] },
],
sidebar_behavior: 'show-links',
}
export async function generateExamplesContent(): Promise<GeneratedContent> {
const articles: Articles = {}
try {
const outputExamplesSection = generateSection(section, articles, 0)
const contentComplete = { sections: [outputExamplesSection], articles }
return contentComplete
} catch (error) {
nicelog(`x Could not generate Examples content`)
throw error
}
}

View file

@ -21,21 +21,29 @@ export function generateSection(section: InputSection, articles: Articles, index
)
// The file directory for this section
const dir = path.join(CONTENT_DIR, section.id)
const isExamples = section.id === 'examples'
const dir = isExamples
? path.join(process.cwd(), '..', 'examples', 'src', 'examples')
: path.join(CONTENT_DIR, section.id)
const files = fs.readdirSync(dir, { withFileTypes: false })
const isGenerated = section.id === 'gen'
const isGenerated = section.id === 'reference'
for (const file of files) {
const filename = file.toString()
const fileContent = fs.readFileSync(path.join(dir, filename)).toString()
const pathname = isExamples ? path.join(dir, filename, 'README.md') : path.join(dir, filename)
const fileContent = fs.readFileSync(pathname).toString()
const extension = path.extname(filename)
const articleId = filename.replace(extension, '')
const parsed = matter({ content: fileContent }, {})
// If we're in prod and the article isn't published, skip it
if (process.env.NODE_ENV !== 'development' && parsed.data.status !== 'published') {
if (
process.env.NODE_ENV !== 'development' &&
!isExamples &&
parsed.data.status !== 'published'
) {
continue
}
@ -48,6 +56,38 @@ export function generateSection(section: InputSection, articles: Articles, index
const isUncategorized = categoryId === section.id + '_ucg'
const isIndex = articleId === section.id
const componentCode = parsed.data.component
? fs
.readFileSync(
path.join(
dir,
filename,
`${parsed.data.component}${parsed.data.component.endsWith('.tsx') ? '' : '.tsx'}`
)
)
.toString()
: null
const componentCodeFiles: { [key: string]: string } = {}
if (parsed.data.component) {
const exampleParentDirectory = path.join(dir, filename)
const codeFilenames = fs.readdirSync(exampleParentDirectory, {
withFileTypes: true,
recursive: true,
})
codeFilenames
.filter(
(file) =>
!file.isDirectory() &&
file.name !== 'README.md' &&
file.name.replace('.tsx', '') !==
parsed.data.component.replace('./', '').replace('.tsx', '')
)
.forEach((file) => {
componentCodeFiles[file.name] = fs
.readFileSync(path.join(file.path, file.name))
.toString()
})
}
const article: Article = {
id: articleId,
@ -55,7 +95,7 @@ export function generateSection(section: InputSection, articles: Articles, index
sectionIndex: 0,
groupIndex: -1,
groupId: parsed.data.group ?? null,
categoryIndex: parsed.data.order ?? -1,
categoryIndex: parsed.data.order ?? parsed.data.priority ?? -1,
sectionId: section.id,
author,
categoryId,
@ -68,6 +108,8 @@ export function generateSection(section: InputSection, articles: Articles, index
sourceUrl: isGenerated // if it's a generated API doc, then we don't have a link
? parsed.data.sourceUrl ?? null
: `${section.id}/${articleId}${extension}`,
componentCode,
componentCodeFiles: componentCode ? JSON.stringify(componentCodeFiles) : null,
content: parsed.content,
path:
section.id === 'getting-started'
@ -166,14 +208,10 @@ export function generateSection(section: InputSection, articles: Articles, index
}
const sortArticles = (articleA: Article, articleB: Article) => {
const { categoryIndex: categoryIndexA, date: dateA = '01/01/1970' } = articleA
const { categoryIndex: categoryIndexB, date: dateB = '01/01/1970' } = articleB
const { categoryIndex: categoryIndexA, title: titleA } = articleA
const { categoryIndex: categoryIndexB, title: titleB } = articleB
return categoryIndexA === categoryIndexB
? new Date(dateB!).getTime() > new Date(dateA!).getTime()
? 1
: -1
: categoryIndexA < categoryIndexB
? -1
: 1
? titleA.localeCompare(titleB)
: categoryIndexA - categoryIndexB
}

View file

@ -5,6 +5,7 @@ import { nicelog } from '@/utils/nicelog'
import { connect } from './connect'
import { generateApiContent } from './generateApiContent'
import { generateContent } from './generateContent'
import { generateExamplesContent } from './generateExamplesContent'
export async function refreshContent(opts = {} as { silent: boolean }) {
if (!opts.silent) nicelog('◦ Resetting database...')
@ -16,6 +17,9 @@ export async function refreshContent(opts = {} as { silent: boolean }) {
if (!opts.silent) nicelog('◦ Generating / adding regular content to db...')
await addContentToDb(db, await generateContent())
if (!opts.silent) nicelog('◦ Generating / adding Examples content to db...')
await addContentToDb(db, await generateExamplesContent())
if (!opts.silent) nicelog('◦ Generating / adding API content to db...')
await addContentToDb(db, await generateApiContent())