Skip to content

Commit 4bdeeee

Browse files
Migrate documentation to vitepress (knex#400)
Co-authored-by: Benicio Cardozo <[email protected]>
1 parent 8100b64 commit 4bdeeee

30 files changed

+9634
-7
lines changed

.gitignore

+1-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,2 @@
11
node_modules
2-
npm-debug.log
3-
.idea
4-
.DS_Store
5-
.vagrant
6-
build/CHANGELOG.md
7-
build/version.js
8-
package-lock.json
2+
.vitepress/dist

.vitepress/config.ts

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { defineConfig } from 'vitepress'
2+
import KnexDialectsPlugins from './knexDialects'
3+
4+
export default defineConfig({
5+
title: 'Knex.js',
6+
description: 'Beta knex.js documentation.',
7+
base: '/',
8+
srcDir: 'src',
9+
head: [
10+
["link", { rel: "icon", type: "image/png", href: "/knex-logo.png" }],
11+
],
12+
themeConfig: {
13+
logo: '/knex-logo.png',
14+
repo: 'knex/knex',
15+
docsRepo: 'knex/documentation',
16+
docsDir: 'src',
17+
docsBranch: 'main',
18+
editLinks: true,
19+
editLinkText: 'Edit this page on GitHub',
20+
lastUpdated: 'Last Updated',
21+
nav: [
22+
{ text: 'Guide', link: '/guide/', activeMatch: '^/guide/' },
23+
{
24+
text: 'F.A.Q.',
25+
link: '/faq/',
26+
},
27+
{
28+
text: 'Changelog',
29+
link: '/changelog.html',
30+
}
31+
],
32+
sidebar: {
33+
'/guide/': getGuideSidebar(),
34+
'/faq/': getFaqSidebar(),
35+
},
36+
algolia: {
37+
appId: 'V7E3EHUPD6',
38+
apiKey: '44b5077836c1c8fba0f364383dde7fb4',
39+
indexName: 'knex',
40+
initialQuery: 'Installation',
41+
}
42+
},
43+
vite: {
44+
plugins: [
45+
KnexDialectsPlugins()
46+
]
47+
}
48+
})
49+
50+
function getGuideSidebar() {
51+
return [
52+
{
53+
text: 'Installation',
54+
link: '/guide/'
55+
},
56+
{
57+
text: 'Query Builder',
58+
link: '/guide/query-builder'
59+
},
60+
{
61+
text: 'Transactions',
62+
link: '/guide/transactions'
63+
},
64+
{
65+
text: 'Schema Builder',
66+
link: '/guide/schema-builder'
67+
},
68+
{
69+
text: 'Raw',
70+
link: '/guide/raw'
71+
},
72+
{
73+
text: 'Ref',
74+
link: '/guide/ref'
75+
},
76+
{
77+
text: 'Utility',
78+
link: '/guide/utility'
79+
},
80+
{
81+
text: 'Interfaces',
82+
link: '/guide/interfaces'
83+
},
84+
{
85+
text: 'Migrations',
86+
link: '/guide/migrations'
87+
},
88+
]
89+
}
90+
function getFaqSidebar() {
91+
return [
92+
{
93+
text: 'F.A.Q.',
94+
link: '/faq/'
95+
},
96+
{
97+
text: 'Recipes',
98+
link: '/faq/recipes'
99+
},
100+
{
101+
text: 'Support',
102+
link: '/faq/support'
103+
},
104+
]
105+
}

.vitepress/knexDialects.ts

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
2+
import Knex from 'knex'
3+
import type { PluginOption } from 'vite'
4+
5+
const dialects = {
6+
'better-sqlite3': Knex({ client: 'better-sqlite3' }),
7+
cockroachdb: Knex({ client: 'cockroachdb' }),
8+
mssql: Knex({ client: 'mssql' }),
9+
mysql: Knex({ client: 'mysql' }),
10+
mysql2: Knex({ client: 'mysql2' }),
11+
oracledb: Knex({ client: 'oracledb' }),
12+
pgnative: Knex({ client: 'pgnative' }),
13+
postgres: Knex({ client: 'postgres' }),
14+
redshift: Knex({ client: 'redshift' }),
15+
sqlite3: Knex({ client: 'sqlite3' }),
16+
}
17+
18+
export default function knexDialects (): PluginOption {
19+
const regex = /<SqlOutput[\s]*code="([^"]+)"[\s]*\/>/ig
20+
21+
return {
22+
name: 'transform-file',
23+
enforce: 'pre',
24+
25+
transform(src, id) {
26+
if (id.endsWith('.md')) {
27+
const matches = src.matchAll(regex)
28+
for (const match of matches) {
29+
let markdown = ''
30+
const getCode = Function("knex", `return knex.raw(${match[1]});`);
31+
32+
for (const dialect in dialects) {
33+
const knex = dialects[dialect]
34+
const { sql } = getCode(knex)
35+
const output = sql.toString()
36+
37+
markdown += `<div data-dialect="${dialect}">\n\n\`\`\`sql\n${output}\n\`\`\`\n\n</div>\n`
38+
}
39+
40+
src = src.replace(match[0], markdown)
41+
}
42+
}
43+
44+
return src
45+
}
46+
}
47+
}

.vitepress/theme/AlgoliaSearchBox.vue

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<script setup lang="ts">
2+
import '@docsearch/css'
3+
import docsearch from '@docsearch/js'
4+
import { useRoute, useRouter, useData } from 'vitepress'
5+
import { getCurrentInstance, onMounted, watch } from 'vue'
6+
import type { DefaultTheme } from '../config'
7+
import type { DocSearchHit } from '@docsearch/react/dist/esm/types'
8+
const props = defineProps<{
9+
options: DefaultTheme.AlgoliaSearchOptions
10+
multilang?: boolean
11+
}>()
12+
const vm = getCurrentInstance()
13+
const route = useRoute()
14+
const router = useRouter()
15+
watch(
16+
() => props.options,
17+
(value) => {
18+
update(value)
19+
}
20+
)
21+
onMounted(() => {
22+
initialize(props.options)
23+
})
24+
function isSpecialClick(event: MouseEvent) {
25+
return (
26+
event.button === 1 ||
27+
event.altKey ||
28+
event.ctrlKey ||
29+
event.metaKey ||
30+
event.shiftKey
31+
)
32+
}
33+
function getRelativePath(absoluteUrl: string) {
34+
const { pathname, hash } = new URL(absoluteUrl)
35+
return pathname + hash
36+
}
37+
function update(options: any) {
38+
if (vm && vm.vnode.el) {
39+
vm.vnode.el.innerHTML =
40+
'<div class="algolia-search-box" id="docsearch"></div>'
41+
initialize(options)
42+
}
43+
}
44+
const { lang } = useData()
45+
// if the user has multiple locales, the search results should be filtered
46+
// based on the language
47+
const facetFilters: string[] = props.multilang ? ['lang:' + lang.value] : []
48+
if (props.options.searchParameters?.facetFilters) {
49+
facetFilters.push(...props.options.searchParameters.facetFilters)
50+
}
51+
watch(lang, (newLang, oldLang) => {
52+
const index = facetFilters.findIndex((filter) => filter === 'lang:' + oldLang)
53+
if (index > -1) {
54+
facetFilters.splice(index, 1, 'lang:' + newLang)
55+
}
56+
})
57+
function initialize(userOptions: any) {
58+
docsearch(
59+
Object.assign({}, userOptions, {
60+
container: '#docsearch',
61+
searchParameters: Object.assign({}, userOptions.searchParameters, {
62+
// pass a custom lang facetFilter to allow multiple language search
63+
// https://github.com/algolia/docsearch-configs/pull/3942
64+
facetFilters
65+
}),
66+
navigator: {
67+
navigate: ({ itemUrl }: { itemUrl: string }) => {
68+
const { pathname: hitPathname } = new URL(
69+
window.location.origin + itemUrl
70+
)
71+
// Router doesn't handle same-page navigation so we use the native
72+
// browser location API for anchor navigation
73+
if (route.path === hitPathname) {
74+
window.location.assign(window.location.origin + itemUrl)
75+
} else {
76+
router.go(itemUrl)
77+
}
78+
}
79+
},
80+
transformItems: (items: DocSearchHit[]) => {
81+
return items.map((item) => {
82+
return Object.assign({}, item, {
83+
url: getRelativePath(item.url)
84+
})
85+
})
86+
},
87+
hitComponent: ({
88+
hit,
89+
children
90+
}: {
91+
hit: DocSearchHit
92+
children: any
93+
}) => {
94+
const relativeHit = hit.url.startsWith('http')
95+
? getRelativePath(hit.url as string)
96+
: hit.url
97+
return {
98+
type: 'a',
99+
ref: undefined,
100+
constructor: undefined,
101+
key: undefined,
102+
props: {
103+
href: hit.url,
104+
onClick: (event: MouseEvent) => {
105+
if (isSpecialClick(event)) {
106+
return
107+
}
108+
// we rely on the native link scrolling when user is already on
109+
// the right anchor because Router doesn't support duplicated
110+
// history entries
111+
if (route.path === relativeHit) {
112+
return
113+
}
114+
// if the hits goes to another page, we prevent the native link
115+
// behavior to leverage the Router loading feature
116+
if (route.path !== relativeHit) {
117+
event.preventDefault()
118+
}
119+
router.go(relativeHit)
120+
},
121+
children
122+
},
123+
__v: null
124+
}
125+
}
126+
})
127+
)
128+
}
129+
</script>
130+
131+
<template>
132+
<div class="algolia-search-box" id="docsearch" />
133+
</template>
134+
135+
<style>
136+
.algolia-search-box {
137+
padding-top: 1px;
138+
}
139+
@media (min-width: 720px) {
140+
.algolia-search-box {
141+
padding-left: 8px;
142+
}
143+
}
144+
@media (min-width: 751px) {
145+
.algolia-search-box {
146+
min-width: 176.3px; /* avoid layout shift */
147+
}
148+
.algolia-search-box .DocSearch-Button-Placeholder {
149+
padding-left: 8px;
150+
font-size: 0.9rem;
151+
font-weight: 500;
152+
}
153+
}
154+
body .DocSearch {
155+
--docsearch-primary-color: var(--c-brand);
156+
--docsearch-modal-background: var(--c-white);
157+
--docsearch-hit-background: var(--c-white-dark);
158+
--docsearch-searchbox-focus-background: var(--c-white-dark);
159+
--docsearch-highlight-color: var(--docsearch-primary-color);
160+
--docsearch-searchbox-shadow: inset 0 0 0 2px var(--c-text-lighter);
161+
--docsearch-hit-shadow: 0 1px 3px 0 var(--c-white);
162+
--docsearch-text-color: var(--c-text);
163+
--docsearch-muted-color: var(--c-text-dark-1);
164+
--docsearch-searchbox-background: var(--c-white);
165+
--docsearch-footer-background: var(--c-white);
166+
--docsearch-modal-shadow: 0 3px 8px 0 var(--c-white-dark);
167+
--docsearch-hit-color: var(--c-text);
168+
--docsearch-footer-shadow: 0 -1px 0 0 var(--c-white-dark);
169+
--docsearch-key-gradient: linear-gradient(-225deg,var(--c-white-dark),var(--c-white));
170+
--docsearch-key-shadow: inset 0 -2px 0 0 var(--c-white-dark),inset 0 0 1px 1px var(--c-white),0 1px 2px 1px var(--c-white-darker);
171+
}
172+
</style>

.vitepress/theme/Layout.vue

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script setup>
2+
import DefaultTheme from 'vitepress/theme'
3+
import { useData } from 'vitepress'
4+
import { computed, defineAsyncComponent } from 'vue'
5+
import SqlDialectSelector from "./SqlDialectSelector.vue";
6+
import ToggleDark from './ToggleDark.vue'
7+
8+
const { Layout } = DefaultTheme
9+
const { site, theme } = useData()
10+
11+
const AlgoliaSearchBox = defineAsyncComponent(() => import('./AlgoliaSearchBox.vue'))
12+
13+
// automatic multilang check for AlgoliaSearchBox
14+
const isMultiLang = computed(() => Object.keys(site.value.langs).length > 1)
15+
</script>
16+
17+
<template>
18+
<Layout>
19+
<template #navbar-search>
20+
<SqlDialectSelector />
21+
<AlgoliaSearchBox
22+
v-if="theme.algolia"
23+
:options="theme.algolia"
24+
:multilang="isMultiLang"
25+
/>
26+
<ToggleDark />
27+
</template>
28+
</Layout>
29+
</template>

0 commit comments

Comments
 (0)