Skip to content

Commit

Permalink
docs: searchable api (#1253)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shinigami92 authored Aug 22, 2022
1 parent f684a14 commit 0866ee9
Show file tree
Hide file tree
Showing 8 changed files with 359 additions and 2 deletions.
5 changes: 5 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ export default defineConfig({

nav: [
{ text: 'Guide', link: '/guide/' },
{
text: 'API',
activeMatch: `^/api/`,
link: '/api/',
},
{
text: 'Ecosystem',
items: [{ text: 'StackBlitz ', link: 'https://fakerjs.dev/new' }],
Expand Down
8 changes: 8 additions & 0 deletions docs/api/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# Markdown
*.md
!index.md
!localization.md

# TypeScript
*.ts
!api-types.ts

# JSON
*.json
240 changes: 240 additions & 0 deletions docs/api/ApiIndex.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
<!-- This content is mostly copied over from https://github.com/vuejs/docs/blob/main/src/api/ApiIndex.vue -->

<script setup lang="ts">
import { computed, ref } from 'vue';
import apiSearchIndex from './api-search-index.json';
import { APIGroup } from './api-types';

const query = ref('');
const normalize = (s: string) => s.toLowerCase().replace(/-/g, ' ');

const filtered = computed(() => {
const q = normalize(query.value);
const matches = (text: string) => normalize(text).includes(q);

return (apiSearchIndex as APIGroup[])
.map((section) => {
// section title match
if (matches(section.text)) {
return section;
}

// filter groups
const matchedGroups = section.items
.map((item) => {
// group title match
if (matches(item.text)) {
return item;
}
// filter headers
const matchedHeaders = item.headers.filter(
({ text, anchor }) => matches(text) || matches(anchor)
);
return matchedHeaders.length
? { text: item.text, link: item.link, headers: matchedHeaders }
: null;
})
.filter((i) => i);

return matchedGroups.length
? { text: section.text, items: matchedGroups }
: null;
})
.filter((i) => i) as APIGroup[];
});

// same as vitepress' slugify logic
function slugify(text: string): string {
return (
text
// Replace special characters
.replace(/[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'<>,.?/]+/g, '-')
// Remove continuous separators
.replace(/\-{2,}/g, '-')
// Remove prefixing and trailing separators
.replace(/^\-+|\-+$/g, '')
// ensure it doesn't start with a number (like #123)
.replace(/^(\d)/, '_$1')
// // lowercase
// .toLowerCase()
);
}
</script>

<template>
<div id="api-index">
<div class="header">
<h1>API Reference</h1>
<div class="api-filter">
<label for="api-filter">Filter</label>
<input
type="search"
placeholder="Enter keyword"
id="api-filter"
v-model="query"
/>
</div>
</div>

<div v-for="section of filtered" :key="section.text" class="api-section">
<h2 :id="slugify(section.text)">{{ section.text }}</h2>
<div class="api-groups">
<div v-for="item of section.items" :key="item.text" class="api-group">
<h3>{{ item.text }}</h3>
<ul>
<li v-for="h of item.headers" :key="h.anchor">
<a :href="item.link + '.html#' + slugify(h.anchor)">{{
h.anchor
}}</a>
</li>
</ul>
</div>
</div>
</div>

<div v-if="!filtered.length" class="no-match">
No API matching "{{ query }}" found.
</div>
</div>
</template>

<style scoped>
#api-index {
max-width: 1024px;
margin: 0px auto;
padding: 64px 32px;
}

h1,
h2,
h3 {
font-weight: 600;
line-height: 1;
}

h1,
h2 {
letter-spacing: -0.02em;
}

h1 {
font-size: 38px;
}

h2 {
font-size: 24px;
color: var(--vp-c-text-1);
margin: 36px 0;
transition: color 0.5s;
padding-top: 36px;
border-top: 1px solid var(--vp-c-divider-light);
}

h3 {
letter-spacing: -0.01em;
color: var(--vp-c-green);
font-size: 18px;
margin-bottom: 1em;
transition: color 0.5s;
}

.api-section {
margin-bottom: 64px;
}

.api-groups a {
font-size: 15px;
font-weight: 500;
line-height: 2;
color: var(--vp-c-text-code);
transition: color 0.5s;
}

.dark .api-groups a {
font-weight: 400;
}

.api-groups a:hover {
color: var(--vp-c-green);
transition: none;
}

.api-group {
break-inside: avoid;
overflow: auto;
margin-bottom: 20px;
background-color: var(--vp-c-bg-soft);
border-radius: 8px;
padding: 28px 32px;
transition: background-color 0.5s;
}

.header {
display: flex;
align-items: center;
justify-content: space-between;
}

.api-filter {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 1rem;
}

.api-filter input {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 6px 12px;
}

.api-filter:focus {
border-color: var(--vp-c-green-light);
}

.no-match {
font-size: 1.2em;
color: var(--vp-c-text-3);
text-align: center;
margin-top: 36px;
padding-top: 36px;
border-top: 1px solid var(--vp-c-divider-light);
}

@media (max-width: 768px) {
#api-index {
padding: 42px 24px;
}

h1 {
font-size: 32px;
margin-bottom: 24px;
}

h2 {
font-size: 22px;
margin: 42px 0 32px;
padding-top: 32px;
}

.api-groups a {
font-size: 14px;
}

.header {
display: block;
}
}

@media (min-width: 768px) {
.api-groups {
columns: 2;
}
}

@media (min-width: 1024px) {
.api-groups {
columns: 3;
}
}
</style>
26 changes: 26 additions & 0 deletions docs/api/api-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// These interfaces are copied over from https://github.com/vuejs/docs/blob/main/src/api/api.data.ts

/**
* Represents a link to e.g. a Faker-Module method within the API search index page.
*/
export interface APIHeader {
anchor: string;
text: string;
}

/**
* Represents a container for e.g. a Faker-Module within the API search index page.
*/
export interface APIItem {
text: string;
link: string;
headers: APIHeader[];
}

/**
* Represents a whole section within the API search index page.
*/
export interface APIGroup {
text: string;
items: APIItem[];
}
12 changes: 12 additions & 0 deletions docs/api/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
layout: page
title: API Reference
sidebar: false
footer: false
---

<script setup>
import ApiIndex from './ApiIndex.vue'
</script>

<ApiIndex />
11 changes: 10 additions & 1 deletion scripts/apidoc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { resolve } from 'path';
import { writeApiPagesIndex } from './apidoc/apiDocsWriter';
import {
writeApiPagesIndex,
writeApiSearchIndex,
} from './apidoc/apiDocsWriter';
import { processDirectMethods } from './apidoc/directMethods';
import { processModuleMethods } from './apidoc/moduleMethods';
import { initMarkdownRenderer } from './apidoc/signature';
Expand All @@ -21,6 +24,10 @@ async function build(): Promise<void> {

const project = app.convert();

if (!project) {
throw new Error('Failed to convert project');
}

// Useful for manually analyzing the content
await app.generateJson(project, pathOutputJson);

Expand All @@ -31,6 +38,8 @@ async function build(): Promise<void> {
modulesPages.push(...processModuleMethods(project));
modulesPages.push(...processDirectMethods(project));
writeApiPagesIndex(modulesPages);

writeApiSearchIndex(project);
}

build().catch(console.error);
Loading

0 comments on commit 0866ee9

Please sign in to comment.