tldraw/apps/docs/app/api/search/route.ts

130 lines
3.5 KiB
TypeScript
Raw Normal View History

import { SearchResult } from '@/types/search-types'
import { getDb } from '@/utils/ContentDatabase'
import { SEARCH_RESULTS, searchBucket, sectionTypeBucket } from '@/utils/search-api'
use native structuredClone on node, cloudflare workers, and in tests (#3166) Currently, we only use native `structuredClone` in the browser, falling back to `JSON.parse(JSON.stringify(...))` elsewhere, despite Node supporting `structuredClone` [since v17](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) and Cloudflare Workers supporting it [since 2022](https://blog.cloudflare.com/standards-compliant-workers-api/). This PR adjusts our shim to use the native `structuredClone` on all platforms, if available. Additionally, `jsdom` doesn't implement `structuredClone`, a bug [open since 2022](https://github.com/jsdom/jsdom/issues/3363). This PR patches `jsdom` environment in all packages/apps that use it for tests. Also includes a driveby removal of `deepCopy`, a function that is strictly inferior to `structuredClone`. ### Change Type <!-- ❗ Please select a 'Scope' label ❗️ --> - [x] `sdk` — Changes the tldraw SDK - [x] `dotcom` — Changes the tldraw.com web app - [ ] `docs` — Changes to the documentation, examples, or templates. - [ ] `vs code` — Changes to the vscode plugin - [ ] `internal` — Does not affect user-facing stuff <!-- ❗ Please select a 'Type' label ❗️ --> - [ ] `bugfix` — Bug fix - [ ] `feature` — New feature - [x] `improvement` — Improving existing features - [x] `chore` — Updating dependencies, other boring stuff - [ ] `galaxy brain` — Architectural changes - [ ] `tests` — Changes to any test code - [ ] `tools` — Changes to infrastructure, CI, internal scripts, debugging tools, etc. - [ ] `dunno` — I don't know ### Test Plan 1. A smoke test would be enough - [ ] Unit Tests - [x] End to end tests
2024-03-18 17:16:09 +00:00
import { structuredClone } from '@tldraw/utils'
import { NextRequest } from 'next/server'
type Data = {
results: {
articles: SearchResult[]
apiDocs: SearchResult[]
examples: SearchResult[]
}
status: 'success' | 'error' | 'no-query'
}
const BANNED_HEADINGS = ['new', 'constructor', 'properties', 'example', 'methods']
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url)
const query = searchParams.get('q')?.toLowerCase()
if (!query) {
return new Response(
JSON.stringify({
results: structuredClone(SEARCH_RESULTS),
status: 'no-query',
}),
{
status: 400,
}
)
}
try {
const results: Data['results'] = structuredClone(SEARCH_RESULTS)
const db = await getDb()
const searchForArticle = await db.db.prepare(
`
SELECT id, title, sectionId, categoryId, content
FROM ftsArticles
WHERE ftsArticles MATCH ?
ORDER BY bm25(ftsArticles, 1000.0)
`,
query
)
await searchForArticle.all().then(async (queryResults) => {
for (const article of queryResults) {
const section = await db.getSection(article.sectionId)
const category = await db.getCategory(article.categoryId)
const isUncategorized = category.id === section.id + '_ucg'
results[searchBucket(article.sectionId)].push({
id: article.id,
type: 'article',
subtitle: isUncategorized ? section.title : `${section.title} / ${category.title}`,
title: article.title,
sectionType: sectionTypeBucket(section.id),
url: isUncategorized
? `${section.id}/${article.id}`
: `${section.id}/${category.id}/${article.id}`,
score: 0,
})
}
})
const searchForArticleHeadings = await db.db.prepare(
`
SELECT id, title, articleId, slug
FROM ftsHeadings
WHERE ftsHeadings MATCH ?
ORDER BY bm25(ftsHeadings, 1000.0)
`,
query
)
await searchForArticleHeadings.all().then(async (queryResults) => {
for (const heading of queryResults) {
if (BANNED_HEADINGS.some((h) => heading.slug.endsWith(h))) continue
const article = await db.getArticle(heading.articleId)
const section = await db.getSection(article.sectionId)
const category = await db.getCategory(article.categoryId)
const isUncategorized = category.id === section.id + '_ucg'
results[searchBucket(article.sectionId)].push({
id: article.id + '#' + heading.slug,
type: 'heading',
subtitle: isUncategorized ? section.title : `${section.title} / ${category.title}`,
sectionType: sectionTypeBucket(section.id),
title:
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>
2024-01-30 14:19:25 +00:00
section.id === 'reference'
? article.title + '.' + heading.title
: article.title + ': ' + heading.title,
url: isUncategorized
? `${section.id}/${article.id}#${heading.slug}`
: `${section.id}/${category.id}/${article.id}#${heading.slug}`,
score: 0,
})
}
})
Object.keys(results).forEach((section: string) => {
results[section as keyof Data['results']] = results[section as keyof Data['results']].slice(
0,
20
)
})
results.articles.sort(
(a, b) => (b.type === 'heading' ? -1 : 1) - (a.type === 'heading' ? -1 : 1)
)
return new Response(JSON.stringify({ results, status: 'success' }), {
status: 200,
})
} catch (e: any) {
return new Response(
JSON.stringify({
results: structuredClone(SEARCH_RESULTS),
status: 'error',
error: e.message,
}),
{
status: 500,
}
)
}
}