Skip to content

Commit

Permalink
Merge branch 'main' into feat/anchorLinkOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
farnabaz committed Oct 5, 2022
2 parents c77d502 + cb7679e commit fc73fb1
Show file tree
Hide file tree
Showing 19 changed files with 2,588 additions and 1,522 deletions.
12 changes: 3 additions & 9 deletions docs/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,12 @@ export default defineAppConfig({
alt: 'Content made easy for Vue developers'
},
aside: {
level: 1,
filter: [
'/v1',
'/content-v1',
'/fr',
'/ja',
'/ru'
]
level: 1
},
header: {
title: false,
logo: true
logo: true,
exclude: ['/v1', '/content-v1', '/fr', '/ja', '/ru']
},
footer: {
credits: {
Expand Down
13 changes: 12 additions & 1 deletion docs/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { resolve } from 'pathe'
import { defineNuxtConfig } from 'nuxt'
import consola from 'consola'

const alias = {}
Expand Down Expand Up @@ -70,5 +69,17 @@ export default defineNuxtConfig({
},
colorMode: {
preference: 'dark'
},
runtimeConfig: {
public: {
algolia: {
applicationId: 'Q46Q8609QS',
apiKey: '1661c72375532f074aedd9452a1270be',
langAttribute: 'lang',
docSearch: {
indexName: 'content-nuxtjs'
}
}
}
}
})
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@nuxt-themes/docus": "npm:@nuxt-themes/docus-edge@latest",
"@nuxtlabs/github-module": "npm:@nuxtlabs/github-module-edge@latest",
"monaco-editor-core": "^0.34.0",
"nuxt": "^3.0.0-rc.10",
"nuxt": "^3.0.0-rc.11",
"vue-plausible": "^1.3.2"
},
"dependencies": {
Expand Down
4 changes: 2 additions & 2 deletions docs/tokens.config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { defineTokens } from '@nuxtjs/design-tokens'
import { defineTheme } from 'pinceau'

export default defineTokens({
export default defineTheme({
colors: {
primary: {
50: { value: '#ecfdf5' },
Expand Down
2,187 changes: 1,490 additions & 697 deletions docs/yarn.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"test:unit": "nuxi prepare test/fixtures/basic && nuxi prepare test/fixtures/document-driven && vitest run"
},
"dependencies": {
"@nuxt/kit": "^3.0.0-rc.10",
"@nuxt/kit": "^3.0.0-rc.11",
"consola": "^2.15.3",
"defu": "^6.1.0",
"destr": "^1.1.1",
Expand Down Expand Up @@ -80,8 +80,8 @@
},
"devDependencies": {
"@nuxt/module-builder": "^0.1.7",
"@nuxt/schema": "^3.0.0-rc.10",
"@nuxt/test-utils": "^3.0.0-rc.10",
"@nuxt/schema": "^3.0.0-rc.11",
"@nuxt/test-utils": "^3.0.0-rc.11",
"@nuxthq/admin": "npm:@nuxthq/admin-edge@latest",
"@nuxtjs/eslint-config-typescript": "latest",
"@types/ws": "^8.5.3",
Expand All @@ -92,7 +92,7 @@
"husky": "^8.0.1",
"jiti": "^1.15.0",
"lint-staged": "^13.0.3",
"nuxt": "^3.0.0-rc.10",
"nuxt": "npm:nuxt3@latest",
"rehype-figure": "^1.0.1",
"remark-oembed": "^1.2.2",
"vitest": "^0.23.2",
Expand Down
59 changes: 56 additions & 3 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
createResolver,
addImports,
addComponentsDir,
addTemplate
addTemplate,
addComponent
} from '@nuxt/kit'
import { genImport, genSafeVariableName } from 'knitwork'
import type { ListenOptions } from 'listhen'
Expand Down Expand Up @@ -203,6 +204,9 @@ export interface ModuleOptions {
* @default [1]
*/
exclude?: number[]
},
experimental: {
clientDB: boolean
}
}

Expand Down Expand Up @@ -252,6 +256,9 @@ export default defineNuxtModule<ModuleOptions>({
anchorLinks: {
depth: 4,
exclude: [1]
},
experimental: {
clientDB: false
}
},
async setup (options, nuxt) {
Expand Down Expand Up @@ -297,13 +304,13 @@ export default defineNuxtModule<ModuleOptions>({
},
{
method: 'get',
route: `/api/${options.base}/cache`,
route: `/api/${options.base}/cache.json`,
handler: resolveRuntimeModule('./server/api/cache')
}
)

if (!nuxt.options.dev) {
nitroConfig.prerender.routes.unshift('/api/_content/cache')
nitroConfig.prerender.routes.unshift(`/api/${options.base}/cache.json`)
}

// Register source storages
Expand Down Expand Up @@ -559,6 +566,12 @@ export default defineNuxtModule<ModuleOptions>({
contentContext.markdown = processMarkdownOptions(contentContext.markdown)

nuxt.options.runtimeConfig.public.content = defu(nuxt.options.runtimeConfig.public.content, {
clientDB: {
isSPA: options.experimental.clientDB && nuxt.options.ssr === false,
// Disable cache in dev mode
integrity: nuxt.options.dev ? undefined : Date.now()
},
navigation: contentContext.navigation,
base: options.base,
// Tags will use in markdown renderer for component replacement
tags: contentContext.markdown.tags as any,
Expand All @@ -584,6 +597,34 @@ export default defineNuxtModule<ModuleOptions>({
tailwindConfig.content.push(resolve(nuxt.options.buildDir, 'content-cache', 'parsed/**/*.md'))
})

// Experimental preview mode
if (process.env.NUXT_PREVIEW_API) {
// Add preview plugin
addPlugin(resolveRuntimeModule('./preview/preview-plugin'))

// Add preview components
addComponent({
name: 'ContentPreviewMode',
filePath: resolveRuntimeModule('./preview/components/ContentPreviewMode.vue')
})

// @ts-ignore
nuxt.options.runtimeConfig.public.content.previewAPI = process.env.NUXT_PREVIEW_API
// @ts-ignore
nuxt.options.runtimeConfig.content.previewAPI = process.env.NUXT_PREVIEW_API

if (nuxt.options.vite !== false) {
nuxt.options.vite = defu(
nuxt.options.vite === true ? {} : nuxt.options.vite,
{
optimizeDeps: {
include: ['socket.io-client', 'slugify']
}
}
)
}
}

// Setup content dev module
if (!nuxt.options.dev) {
nuxt.hook('build:before', async () => {
Expand Down Expand Up @@ -659,6 +700,14 @@ export default defineNuxtModule<ModuleOptions>({
})

interface ModulePublicRuntimeConfig {
/**
* @experimental
*/
clientDB: {
isSPA: boolean
integrity: number
}

tags: Record<string, string>

base: string;
Expand All @@ -668,6 +717,8 @@ interface ModulePublicRuntimeConfig {

// Shiki config
highlight: ModuleOptions['highlight']

navigation: ModuleOptions['navigation']
}

interface ModulePrivateRuntimeConfig {
Expand All @@ -677,6 +728,8 @@ interface ModulePrivateRuntimeConfig {
*/
cacheVersion: string;
cacheIntegrity: string;

previewAPI?: string
}

declare module '@nuxt/schema' {
Expand Down
106 changes: 106 additions & 0 deletions src/runtime/composables/client-db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { Storage } from 'unstorage'
// @ts-ignore
import LSDriver from 'unstorage/drivers/localstorage'
import { createStorage, prefixStorage } from 'unstorage'
import { useRuntimeConfig, useCookie } from '#app'
import { withBase } from 'ufo'
import { createPipelineFetcher } from '../query/match/pipeline'
import { createQuery } from '../query/query'
import type { NavItem, ParsedContent, ParsedContentMeta, QueryBuilderParams } from '../types'
import { createNav } from '../server/navigation'

const withContentBase = url => withBase(url, '/api/' + useRuntimeConfig().public.content.base)

export const contentStorage = prefixStorage(createStorage({ driver: LSDriver() }), '@content')

export const getPreview = () => {
return useCookie('previewToken').value
}

export function createDB (storage: Storage) {
async function getItems () {
const keys = new Set(await storage.getKeys('cache:'))

// Merge preview items
const previewToken = getPreview()
if (previewToken) {
const previewKeys = await storage.getKeys(`${previewToken}:`)
const previewContents = await Promise.all(previewKeys.map(key => storage.getItem(key) as Promise<ParsedContent>))
for (const pItem of previewContents) {
keys.delete(`cache:${pItem._id}`)
if (!pItem.__deleted) {
keys.add(`${previewToken}:${pItem._id}`)
}
}
}

return Promise.all(Array.from(keys).map(key => storage.getItem(key) as Promise<ParsedContent>))
}
return {
storage,
fetch: createPipelineFetcher(getItems),
query: (query?: QueryBuilderParams) => createQuery(createPipelineFetcher(getItems), query)
}
}

let contentDatabase
export async function useContentDatabase () {
if (!contentDatabase) {
const { clientDB } = useRuntimeConfig().public.content
contentDatabase = createDB(contentStorage)
const integrity = await contentDatabase.storage.getItem('integrity')
if (clientDB.integrity !== +integrity) {
const { contents, navigation } = await $fetch(withContentBase('cache.json'))

for (const content of contents) {
await contentDatabase.storage.setItem(`cache:${content._id}`, content)
}

await contentDatabase.storage.setItem('navigation', navigation)

await contentDatabase.storage.setItem('integrity', clientDB.integrity)
}
}
return contentDatabase
}

export async function generateNavigation (query): Promise<Array<NavItem>> {
const db = await useContentDatabase()

if (!getPreview() && Object.keys(query || {}).length === 0) {
return db.storage.getItem('navigation')
}

const contents = await db.query(query)
.where({
/**
* Partial contents are not included in the navigation
* A partial content is a content that has `_` prefix in its path
*/
_partial: false,
/**
* Exclude any pages which have opted out of navigation via frontmatter.
*/
navigation: {
$ne: false
}
})
.find()

const dirConfigs = await db.query().where({ _path: /\/_dir$/i, _partial: true }).find()

const configs = dirConfigs.reduce((configs, conf) => {
if (conf.title.toLowerCase() === 'dir') {
conf.title = undefined
}
const key = conf._path.split('/').slice(0, -1).join('/') || '/'
configs[key] = {
...conf,
// Extract meta from body. (non MD files)
...conf.body
}
return configs
}, {} as Record<string, ParsedContentMeta>)

return createNav(contents as ParsedContentMeta[], configs)
}
9 changes: 7 additions & 2 deletions src/runtime/composables/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { hash } from 'ohash'
import { useCookie } from '#app'
import type { NavItem, QueryBuilder, QueryBuilderParams } from '../types'
import { jsonStringify } from '../utils/json'
import { addPrerenderPath, withContentBase } from './utils'
import { addPrerenderPath, shouldUseClientDB, withContentBase } from './utils'

export const fetchContentNavigation = (queryBuilder?: QueryBuilder | QueryBuilderParams): Promise<Array<NavItem>> => {
export const fetchContentNavigation = async (queryBuilder?: QueryBuilder | QueryBuilderParams): Promise<Array<NavItem>> => {
let params = queryBuilder

// When params is an instance of QueryBuilder then we need to pick the params explicitly
Expand All @@ -17,6 +17,11 @@ export const fetchContentNavigation = (queryBuilder?: QueryBuilder | QueryBuilde
addPrerenderPath(apiPath)
}

if (shouldUseClientDB()) {
const generateNavigation = await import('./client-db').then(m => m.generateNavigation)
return generateNavigation(params || {})
}

return $fetch(apiPath, {
method: 'GET',
responseType: 'json',
Expand Down
9 changes: 7 additions & 2 deletions src/runtime/composables/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { useCookie } from '#app'
import { createQuery } from '../query/query'
import type { ParsedContent, QueryBuilder, QueryBuilderParams } from '../types'
import { jsonStringify } from '../utils/json'
import { addPrerenderPath, withContentBase } from './utils'
import { addPrerenderPath, shouldUseClientDB, withContentBase } from './utils'

/**
* Query fetcher
*/
export const createQueryFetch = <T = ParsedContent>(path?: string) => (query: QueryBuilder<T>) => {
export const createQueryFetch = <T = ParsedContent>(path?: string) => async (query: QueryBuilder<T>) => {
if (path) {
if (query.params().first) {
query.where({ _path: withoutTrailingSlash(path) })
Expand All @@ -31,6 +31,11 @@ export const createQueryFetch = <T = ParsedContent>(path?: string) => (query: Qu
addPrerenderPath(apiPath)
}

if (shouldUseClientDB()) {
const db = await import('./client-db').then(m => m.useContentDatabase())
return db.fetch(query)
}

return $fetch(apiPath as any, {
method: 'GET',
responseType: 'json',
Expand Down

0 comments on commit fc73fb1

Please sign in to comment.