Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: theme settings & addons #273

Merged
merged 23 commits into from
May 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
bed24de
:rotating_light: (utils) update utils format
Tahul Apr 30, 2021
447f7fb
:fire: (docus) begin the cleanup of docus
Tahul Apr 30, 2021
84fee36
:pencil: (css) comments css utils (preparing for refact)
Tahul Apr 30, 2021
68d4525
:fire: (types) cleanup aliases from types
Tahul Apr 30, 2021
cbd80da
:building_construction: (structure) update core runtime structure
Tahul Apr 30, 2021
1d43b22
:sparkles: (plugin) update runtime plugin
Tahul Apr 30, 2021
7ad4999
:sparkles: (database) update querybuilder import
Tahul Apr 30, 2021
10c625b
:sparkles: (useDocusApi) create useDocusApi composable
Tahul Apr 30, 2021
525dbe4
:sparkles: (useDocusGithub) create useDocusGithub composable
Tahul Apr 30, 2021
e335517
:sparkles: (helpers) create createDocus helpers file
Tahul Apr 30, 2021
f6bc652
:sparkles: (useDocusNavigation) create useDocusNavigation composable
Tahul Apr 30, 2021
ecb8750
:sparkles: (useDocusReleases) create useDocusReleases composable
Tahul Apr 30, 2021
af1fef2
:label: (types) update types with DocusAddon & import from refactored…
Tahul Apr 30, 2021
aad9f99
:sparkles: (createDocus) update createDocus using composables
Tahul Apr 30, 2021
f122276
:sparkles: (components) update components settings references
Tahul Apr 30, 2021
77b7acc
:lipstick: (..) ..
Tahul Apr 30, 2021
dfd0834
:sparkles: (docus) move docus to runtime, update to use addons manager
Tahul Apr 30, 2021
b2b9312
:twisted_rightwards_arrows: (merge) re-import Ahad flattening from la…
Tahul Apr 30, 2021
a783455
:sparkles: (useDocusAddons) create simple addons manager
Tahul Apr 30, 2021
5350cdb
:sparkles: (useDocusStyle) re-implement style vars injection
Tahul Apr 30, 2021
6aa6679
:sparkles: (defaultTheme) update settings key
Tahul Apr 30, 2021
665b84b
:sparkles: (useDocusReleases) add init hook to docus releases
Tahul Apr 30, 2021
9581503
:sparkles: (docus) export docus from runtime index
Tahul Apr 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/core/database.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Loki from '@lokidb/loki'
import { QueryBuilder } from './runtime/QueryBuilder'
import { useHooks } from '.'
import { QueryBuilder } from './runtime/api/QueryBuilder'
import { useHooks } from './hooks'

let _db
let _items
Expand Down
9 changes: 6 additions & 3 deletions src/core/plugin.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { joinURL, withLeadingSlash } from 'ufo'
import settings from '~docus-cache/docus-settings.json'
import { createDocus, <%= options.isSSG ? "QueryBuilder" : "RemoteQueryBuilder" %> } from '~docus'
/* <% if (options.watch) { %> */ import { useWebSocket } from '~docus/websocket' /* <% } %> */
/* <% if (options.watch) { %> */ import { useWebSocket } from '~docus/api/websocket' /* <% } %> */


/* <% if (options.isSSG) { %> */
Expand Down Expand Up @@ -33,9 +33,12 @@ export default async function (ctx, inject) {
items = db.getCollection('items')
}
/* <% } %> */


const $docus = await createDocus(ctx, settings, process.server ? ctx.ssrContext.docus.createQuery : createQuery)
const $docus = await createDocus(
ctx,
settings,
process.server ? ctx.ssrContext.docus.createQuery : createQuery
)

inject('docus', $docus)

Expand Down
File renamed without changes.
File renamed without changes.
38 changes: 38 additions & 0 deletions src/core/runtime/composables/addons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { DocusAddonContext } from 'src/types'

export const useDocusAddons = (context: DocusAddonContext, addons: any[]) => {
/**
* Addons context to be spread into Docus injection
*/
const addonsContext = {}

/**
* Setup all addons
*/
const setupAddons = async () =>
await Promise.all(
addons.map(async addon => {
const addonKeys = addon(context)

Object.entries(addonKeys).forEach(([key, value]) => {
if (key === 'init') return

const contextKeys = [Object.keys(addonsContext), ...Object.keys(context.state)]

// eslint-disable-next-line no-console
if (contextKeys.includes(key)) console.warn(`You duplicated the key ${key} in Docus context.`)

addonsContext[key] = value
})

if ((addonKeys as any)?.init) {
return await (addonKeys as any)?.init?.()
}
})
)

return {
addonsContext,
setupAddons
}
}
31 changes: 31 additions & 0 deletions src/core/runtime/composables/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { joinURL } from 'ufo'

export const useDocusApi = createQuery => {
function data(path: string) {
return createQuery(joinURL('/data', path), {}).fetch()
}

function search(path: string | any, options?) {
if (typeof path !== 'string') {
options = path
path = ''
}

return createQuery(joinURL('/pages', path), options)
}

function page(path: string) {
return this.search(path).fetch()
}

function findLinkBySlug(links: any[], slug: string) {
return links.find(link => link.slug === slug)
}

return {
data,
search,
page,
findLinkBySlug
}
}
14 changes: 14 additions & 0 deletions src/core/runtime/composables/github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { computed } from '@nuxtjs/composition-api'
import { DocusAddonContext } from 'src/types'
import { joinURL, withoutTrailingSlash } from 'ufo'

export const useDocusGithub = ({ state }: DocusAddonContext) => {
Copy link
Contributor

@atinux atinux Apr 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not useGitHub?

const previewUrl = computed(() => withoutTrailingSlash(state.settings.url) + '/preview.png')

const repoUrl = computed(() => joinURL(state.settings.github.url, state.settings.github.repo))

return {
previewUrl,
repoUrl
}
}
40 changes: 40 additions & 0 deletions src/core/runtime/composables/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { DocusAddonContext } from 'src/types'
import Vue from 'vue'

export const docusInit = async ({ context, state }: DocusAddonContext, fetch: any) => {
// HotReload on development
if (process.client && process.dev) window.onNuxtReady(() => window.$nuxt.$on('content:update', fetch))

// Fetch on server
if (process.server) {
await fetch()

context.beforeNuxtRender(({ nuxtState }) => (nuxtState.docus = state))
}

// SPA Fallback
if (process.client && !state.settings) await fetch()
}

export const clientAsyncData = (app, $nuxt: any) => {
if (process.client) {
window.onNuxtReady((nuxt: any) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
$nuxt = nuxt

// Workaround since in full static mode, asyncData is not called anymore
app.router.beforeEach((_: any, __: any, next: any) => {
const payload = nuxt._pagePayload || {}

payload.data = payload.data || []

if (payload.data[0]?.page?.template && typeof Vue.component(payload.data[0].page.template) === 'function') {
// Preload the component on client-side navigation
Vue.component(payload.data[0].page.template)
}

next()
})
})
}
}
219 changes: 219 additions & 0 deletions src/core/runtime/composables/navigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import { DocusAddonContext } from 'src/types'
import { pascalCase } from 'scule'
import { withoutTrailingSlash, withTrailingSlash } from 'ufo'
import { computed } from '@nuxtjs/composition-api'
import Vue from 'vue'

export const useDocusNavigation = ({ $nuxt, context, state, api }: DocusAddonContext) => {
const app = context.app

if (!state.navigation) state.navigation = {}

// Map locales to nav
app.i18n.locales.forEach((locale: any) => (state.navigation[locale.code] = {}))

const currentNav = computed(() => state.navigation[app.i18n.locale])

async function fetchNavigation() {
// TODO: Maybe remove this
// Avoid re-fetching in production
if (process.dev === false && state.navigation[app.i18n.locale].links) return

// Get fields
const fields = [
'title',
'menu',
'menuTitle',
'dir',
'nav',
'category',
'slug',
'version',
'to',
'icon',
'description',
'template'
]

// Handle draft fields if in development and enabled from UI
const draft = state.ui?.draft ? undefined : false
if (process.dev) fields.push('draft')

// Query pages
const pages = await api
.search({ deep: true })
.where({ language: app.i18n.locale, draft, nav: { $ne: false } })
.only(fields)
.sortBy('position', 'asc')
.fetch()

let depth = 0

const links = []

const getPageLink = (page: any) => ({
slug: page.slug,
to: withoutTrailingSlash(page.to || page.slug),
menu: page.menu,
menuTitle: page.menuTitle,
template: page.template,
title: page.title,
icon: page.icon,
description: page.description,
...page.nav
})

// Add each page to navigation
pages.forEach((page: any) => {
page.nav = page.nav || {}

if (typeof page.nav === 'string') page.nav = { slot: page.nav }

// TODO: Ignore files directly from @nuxt/content
if (page.slug.startsWith('_')) return

// To: '/docs/guide/hello.md' -> dirs: ['docs', 'guide']
page.dirs = withoutTrailingSlash(page.to)
.split('/')
.filter(_ => _)

// Remove the file part (except if index.md)
if (page.slug !== '') page.dirs = page.dirs.slice(0, -1)

if (!page.dirs.length) {
page.nav.slot = page.nav.slot || 'header'
return links.push(getPageLink(page))
}

let currentLinks = links

let link = null

page.dirs.forEach((dir: string, index: number) => {
// If children has been disabled (nav.children = false)
if (!currentLinks) return
if (index > depth) depth = index

link = api.findLinkBySlug(currentLinks, dir)

if (link) {
currentLinks = link.children
} else {
link = {
slug: dir,
children: []
}
currentLinks.push(link)
currentLinks = currentLinks[currentLinks.length - 1].children
}
})

if (!currentLinks) return

// If index page, merge also with parent for metadata
if (!page.slug) {
if (page.dirs.length === 1) page.nav.slot = page.nav.slot || 'header'

Object.assign(link, getPageLink(page))
} else {
// Push page
currentLinks.push(getPageLink(page))
}
})

// Increment navDepth for files
depth++

// Assign to $docus
state.navigation[app.i18n.locale] = {
depth,
links
}

// calculate categories based on nav
const slugToTitle = title => title && title.replace(/-/g, ' ').split(' ').map(pascalCase).join(' ')
const danglingLinks = []
const categories = state.navigation[app.i18n.locale].links
.filter(link => link.menu !== false)
.reduce((acc, link) => {
link = { ...link }
// clean up children from menu
if (link.children) {
link.children = link.children
.filter(l => l.menu !== false)
// Flatten sub-categories
.flatMap(child => (child.to ? child : child.children))
.flatMap(child => (child.to ? child : child.children))
.filter(l => l.to)
}
// ensure link has proper `menuTitle`
if (!link.menuTitle) {
link.menuTitle = link.title || slugToTitle(link.slug) || ''
}

if (link.children && link.children.length) {
acc.push(link)
} else if (link.to) {
danglingLinks.push(link)
}
return acc
}, [])

// Push other links to end of list
if (danglingLinks.length) categories.push({ to: '', children: danglingLinks })

state.categories[app.i18n.locale] = categories
}

function getPageTemplate(page: any) {
let template = page.template?.self || page.template

if (!template) {
// Fetch from nav (root to link) and fallback to settings.template
const slugs = page.to.split('/').filter(Boolean).slice(0, -1) // no need to get latest slug since it is current page

let links = currentNav.value.links || []

slugs.forEach((slug: string) => {
const link = api.findLinkBySlug(links, slug)

if (link?.template) {
template = typeof link.template === 'string' ? `${link.template}-post` : link.template?.nested
}

if (!link?.children) {
return
}

links = link.children
})

template = template || state.settings.template
}

template = pascalCase(template)

if (!Vue.component(template)) {
// eslint-disable-next-line no-console
console.error(`Template ${template} does not exists, fallback to Page template.`)

template = 'Page'
}

return template
}

function isLinkActive(to: string) {
const path = $nuxt?.$route.path || context.route.path

return withTrailingSlash(path) === withTrailingSlash(context.$contentLocalePath(to))
}

return {
getPageTemplate,
fetchNavigation,
currentNav,
isLinkActive,
init: fetchNavigation
}
}
Loading