tldraw/apps/docs/app/api/search/route.ts
Mime Čuvalo 3ae48af67c
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

143 lines
3.8 KiB
TypeScript

import { SearchResult } from '@/types/search-types'
import { getDb } from '@/utils/ContentDatabase'
import { NextRequest } from 'next/server'
type Data = {
results: {
articles: SearchResult[]
apiDocs: SearchResult[]
}
status: 'success' | 'error' | 'no-query'
}
const BANNED_HEADINGS = ['new', 'constructor', 'properties', 'example', 'methods']
function scoreResultBasedOnLengthSimilarity(title: string, query: string) {
return 1 - Math.min(1, Math.max(0, Math.abs(title.length - query.length) / 12))
}
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: {
articles: [],
apiDocs: [],
},
status: 'no-query',
}),
{
status: 400,
}
)
}
try {
const results: Data['results'] = {
articles: [],
apiDocs: [],
}
const db = await getDb()
const queryWithoutSpaces = query.replace(/\s/g, '')
const searchForArticle = await db.db.prepare(
`
SELECT id, title, sectionId, categoryId, content
FROM articles
WHERE title LIKE '%' || ? || '%'
OR description LIKE '%' || ? || '%'
OR content LIKE '%' || ? || '%'
OR keywords LIKE '%' || ? || '%';
`,
queryWithoutSpaces,
queryWithoutSpaces,
queryWithoutSpaces
)
await searchForArticle.all(query).then(async (queryResults) => {
for (const article of queryResults) {
const isApiDoc = article.sectionId === 'reference'
const section = await db.getSection(article.sectionId)
const category = await db.getCategory(article.categoryId)
const isUncategorized = category.id === section.id + '_ucg'
results[isApiDoc ? 'apiDocs' : 'articles'].push({
id: article.id,
type: 'article',
subtitle: isUncategorized ? section.title : `${section.title} / ${category.title}`,
title: article.title,
url: isUncategorized
? `${section.id}/${article.id}`
: `${section.id}/${category.id}/${article.id}`,
score: scoreResultBasedOnLengthSimilarity(article.title, query),
})
}
})
const searchForArticleHeadings = await db.db.prepare(
`
SELECT id, title, articleId, slug
FROM headings
WHERE title LIKE '%' || ? || '%'
OR slug LIKE '%' || ? || '%'
`,
queryWithoutSpaces,
queryWithoutSpaces
)
await searchForArticleHeadings.all(queryWithoutSpaces).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 isApiDoc = article.sectionId === 'reference'
const section = await db.getSection(article.sectionId)
const category = await db.getCategory(article.categoryId)
const isUncategorized = category.id === section.id + '_ucg'
results[isApiDoc ? 'apiDocs' : 'articles'].push({
id: article.id + '#' + heading.slug,
type: 'heading',
subtitle: isUncategorized ? section.title : `${section.title} / ${category.title}`,
title:
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: scoreResultBasedOnLengthSimilarity(article.title, query),
})
}
})
results.apiDocs.sort((a, b) => b.score - a.score)
results.articles.sort((a, b) => b.score - a.score)
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: {
articles: [],
apiDocs: [],
},
status: 'error',
error: e.message,
}),
{
status: 500,
}
)
}
}