Merge branch 'main' into feat/custom-social-icons

pull/953/head
Divyansh Singh 3 years ago committed by GitHub
commit 489ba7151a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -46,7 +46,7 @@ Pages and internal links get generated with the `.html` suffix by default.
### External Links ### External Links
Outbound links automatically get `target="_blank" rel="noopener noreferrer"`: Outbound links automatically get `target="_blank" rel="noreferrer"`:
- [vuejs.org](https://vuejs.org) - [vuejs.org](https://vuejs.org)
- [VitePress on GitHub](https://github.com/vuejs/vitepress) - [VitePress on GitHub](https://github.com/vuejs/vitepress)

@ -1,5 +1,5 @@
import { watchEffect, Ref } from 'vue' import { watchEffect, Ref } from 'vue'
import { HeadConfig, SiteData, createTitle } from '../../shared' import { HeadConfig, SiteData, createTitle, mergeHead } from '../../shared'
import { Route } from '../router' import { Route } from '../router'
export function useUpdateHead(route: Route, siteDataByRouteRef: Ref<SiteData>) { export function useUpdateHead(route: Route, siteDataByRouteRef: Ref<SiteData>) {
@ -14,50 +14,20 @@ export function useUpdateHead(route: Route, siteDataByRouteRef: Ref<SiteData>) {
return return
} }
const newEls: HTMLElement[] = [] managedHeadTags.forEach((el) => document.head.removeChild(el))
const commonLength = Math.min(managedHeadTags.length, newTags.length) managedHeadTags = []
for (let i = 0; i < commonLength; i++) { newTags.forEach((headConfig) => {
let el = managedHeadTags[i]
const [tag, attrs, innerHTML = ''] = newTags[i]
if (el.tagName.toLocaleLowerCase() === tag) {
for (const key in attrs) {
if (el.getAttribute(key) !== attrs[key]) {
el.setAttribute(key, attrs[key])
}
}
for (let i = 0; i < el.attributes.length; i++) {
const name = el.attributes[i].name
if (!(name in attrs)) {
el.removeAttribute(name)
}
}
if (el.innerHTML !== innerHTML) {
el.innerHTML = innerHTML
}
} else {
document.head.removeChild(el)
el = createHeadElement(newTags[i])
document.head.append(el)
}
newEls.push(el)
}
managedHeadTags
.slice(commonLength)
.forEach((el) => document.head.removeChild(el))
newTags.slice(commonLength).forEach((headConfig) => {
const el = createHeadElement(headConfig) const el = createHeadElement(headConfig)
document.head.appendChild(el) document.head.appendChild(el)
newEls.push(el) managedHeadTags.push(el)
}) })
managedHeadTags = newEls
} }
watchEffect(() => { watchEffect(() => {
const pageData = route.data const pageData = route.data
const siteData = siteDataByRouteRef.value const siteData = siteDataByRouteRef.value
const pageDescription = pageData && pageData.description const pageDescription = pageData && pageData.description
const frontmatterHead = pageData && pageData.frontmatter.head const frontmatterHead = (pageData && pageData.frontmatter.head) || []
// update title and description // update title and description
document.title = createTitle(siteData, pageData) document.title = createTitle(siteData, pageData)
@ -66,11 +36,9 @@ export function useUpdateHead(route: Route, siteDataByRouteRef: Ref<SiteData>) {
.querySelector(`meta[name=description]`)! .querySelector(`meta[name=description]`)!
.setAttribute('content', pageDescription || siteData.description) .setAttribute('content', pageDescription || siteData.description)
updateHeadTags([ updateHeadTags(
// site head can only change during dev mergeHead(siteData.head, filterOutHeadDescription(frontmatterHead))
...(import.meta.env.DEV ? siteData.head : []), )
...(frontmatterHead ? filterOutHeadDescription(frontmatterHead) : [])
])
}) })
} }

@ -34,7 +34,7 @@ const component = computed(() => {
:class="classes" :class="classes"
:href="href ? normalizeLink(href) : undefined" :href="href ? normalizeLink(href) : undefined"
:target="isExternal ? '_blank' : undefined" :target="isExternal ? '_blank' : undefined"
:rel="isExternal ? 'noopener noreferrer' : undefined" :rel="isExternal ? 'noreferrer' : undefined"
> >
{{ text }} {{ text }}
</component> </component>

@ -19,7 +19,7 @@ const isExternal = computed(() => props.href && EXTERNAL_URL_RE.test(props.href)
:class="{ link: href }" :class="{ link: href }"
:href="href ? normalizeLink(href) : undefined" :href="href ? normalizeLink(href) : undefined"
:target="isExternal ? '_blank' : undefined" :target="isExternal ? '_blank' : undefined"
:rel="isExternal ? 'noopener noreferrer' : undefined" :rel="isExternal ? 'noreferrer' : undefined"
> >
<slot /> <slot />
<VPIconExternalLink v-if="isExternal && !noIcon" class="icon" /> <VPIconExternalLink v-if="isExternal && !noIcon" class="icon" />

@ -83,7 +83,6 @@ watchPostEffect(async () => {
padding-bottom: 128px; padding-bottom: 128px;
width: var(--vp-sidebar-width); width: var(--vp-sidebar-width);
max-width: 100%; max-width: 100%;
width: var(--vp-sidebar-width);
background-color: var(--vp-c-bg-alt); background-color: var(--vp-c-bg-alt);
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
@ -94,7 +93,7 @@ watchPostEffect(async () => {
@media (min-width: 1440px) { @media (min-width: 1440px) {
.VPSidebar { .VPSidebar {
padding-left: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2); padding-left: max(32px, calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));
width: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px); width: calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px);
} }
} }

@ -19,7 +19,7 @@ const svg = computed(() => {
class="VPSocialLink" class="VPSocialLink"
:href="link" :href="link"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener"
v-html="svg" v-html="svg"
> >
</a> </a>

@ -105,7 +105,7 @@ export function useActiveAnchor(
const scrollY = window.scrollY const scrollY = window.scrollY
const innerHeight = window.innerHeight const innerHeight = window.innerHeight
const offsetHeight = document.body.offsetHeight const offsetHeight = document.body.offsetHeight
const isBottom = scrollY + innerHeight === offsetHeight const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1
// page bottom - highlight last one // page bottom - highlight last one
if (anchors.length && isBottom) { if (anchors.length && isBottom) {

@ -10,7 +10,8 @@ export function useSidebar() {
const sidebar = computed(() => { const sidebar = computed(() => {
const sidebarConfig = theme.value.sidebar const sidebarConfig = theme.value.sidebar
return sidebarConfig ? getSidebar(sidebarConfig, route.path) : [] const relativePath = route.data.relativePath
return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : []
}) })
const hasSidebar = computed(() => { const hasSidebar = computed(() => {

@ -222,7 +222,7 @@
.vp-doc .custom-block div[class*='language-'] code { .vp-doc .custom-block div[class*='language-'] code {
font-weight: 400; font-weight: 400;
background-color: var(--vp-code-block-bg); background-color: transparent;
} }
/** /**

@ -87,5 +87,7 @@ export async function build(
await fs.remove(siteConfig.tempDir) await fs.remove(siteConfig.tempDir)
} }
await siteConfig.buildEnd?.(siteConfig)
console.log(`build complete in ${((Date.now() - start) / 1000).toFixed(2)}s.`) console.log(`build complete in ${((Date.now() - start) / 1000).toFixed(2)}s.`)
} }

@ -5,7 +5,13 @@ import { pathToFileURL } from 'url'
import escape from 'escape-html' import escape from 'escape-html'
import { normalizePath, transformWithEsbuild } from 'vite' import { normalizePath, transformWithEsbuild } from 'vite'
import { RollupOutput, OutputChunk, OutputAsset } from 'rollup' import { RollupOutput, OutputChunk, OutputAsset } from 'rollup'
import { HeadConfig, PageData, createTitle, notFoundPageData } from '../shared' import {
HeadConfig,
PageData,
createTitle,
notFoundPageData,
mergeHead
} from '../shared'
import { slash } from '../utils/slash' import { slash } from '../utils/slash'
import { SiteConfig, resolveSiteDataByRoute } from '../config' import { SiteConfig, resolveSiteDataByRoute } from '../config'
@ -115,10 +121,10 @@ export async function renderPage(
const title: string = createTitle(siteData, pageData) const title: string = createTitle(siteData, pageData)
const description: string = pageData.description || siteData.description const description: string = pageData.description || siteData.description
const head = [ const head = mergeHead(
...siteData.head, siteData.head,
...filterOutHeadDescription(pageData.frontmatter.head) filterOutHeadDescription(pageData.frontmatter.head)
] )
let inlinedScript = '' let inlinedScript = ''
if (config.mpa && result) { if (config.mpa && result) {

@ -71,6 +71,12 @@ export interface UserConfig<ThemeConfig = any> {
* @default false * @default false
*/ */
ignoreDeadLinks?: boolean ignoreDeadLinks?: boolean
/**
* Build end hook: called when SSG finish.
* @param siteConfig The resolved configuration.
*/
buildEnd?: (siteConfig: SiteConfig) => Promise<void>
} }
export type RawConfigExports<ThemeConfig = any> = export type RawConfigExports<ThemeConfig = any> =
@ -88,6 +94,7 @@ export interface SiteConfig<ThemeConfig = any>
| 'mpa' | 'mpa'
| 'lastUpdated' | 'lastUpdated'
| 'ignoreDeadLinks' | 'ignoreDeadLinks'
| 'buildEnd'
> { > {
root: string root: string
srcDir: string srcDir: string
@ -166,7 +173,8 @@ export async function resolveConfig(
vite: userConfig.vite, vite: userConfig.vite,
shouldPreload: userConfig.shouldPreload, shouldPreload: userConfig.shouldPreload,
mpa: !!userConfig.mpa, mpa: !!userConfig.mpa,
ignoreDeadLinks: userConfig.ignoreDeadLinks ignoreDeadLinks: userConfig.ignoreDeadLinks,
buildEnd: userConfig.buildEnd
} }
return config return config
@ -277,7 +285,7 @@ function resolveSiteDataHead(userConfig?: UserConfig): HeadConfig[] {
if (userConfig?.appearance ?? true) { if (userConfig?.appearance ?? true) {
head.push([ head.push([
'script', 'script',
{}, { id: 'check-dark-light' },
` `
;(() => { ;(() => {
const saved = localStorage.getItem('${APPEARANCE_KEY}') const saved = localStorage.getItem('${APPEARANCE_KEY}')

@ -78,7 +78,7 @@ export const createMarkdownRenderer = async (
linkPlugin, linkPlugin,
{ {
target: '_blank', target: '_blank',
rel: 'noopener noreferrer', rel: 'noreferrer',
...options.externalLinks ...options.externalLinks
}, },
base base

@ -83,7 +83,7 @@ export async function createMarkdownToVueRenderFn(
`\n(!) Found dead link ${c.cyan(url)} in file ${c.white( `\n(!) Found dead link ${c.cyan(url)} in file ${c.white(
c.dim(file) c.dim(file)
)}\nIf it is intended, you can use:\n ${c.cyan( )}\nIf it is intended, you can use:\n ${c.cyan(
`<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>` `<a href="${url}" target="_blank" rel="noreferrer">${url}</a>`
)}` )}`
) )
) )

@ -1,3 +1,5 @@
import fs from 'fs'
import path from 'path'
import sirv from 'sirv' import sirv from 'sirv'
import compression from 'compression' import compression from 'compression'
import polka from 'polka' import polka from 'polka'
@ -26,14 +28,21 @@ export async function serve(options: ServeOptions = {}) {
const site = await resolveConfig(options.root, 'serve', 'production') const site = await resolveConfig(options.root, 'serve', 'production')
const base = trimChar(options?.base ?? site?.site?.base ?? '', '/') const base = trimChar(options?.base ?? site?.site?.base ?? '', '/')
const notAnAsset = (pathname: string) => !pathname.includes('/assets/')
const notFound = fs.readFileSync(path.resolve(site.outDir, './404.html'))
const onNoMatch: polka.Options['onNoMatch'] = (req, res) => {
res.statusCode = 404
if (notAnAsset(req.path)) res.write(notFound.toString())
res.end()
}
const compress = compression() const compress = compression()
const serve = sirv(site.outDir, { const serve = sirv(site.outDir, {
etag: true, etag: true,
single: true,
maxAge: 31536000, maxAge: 31536000,
immutable: true, immutable: true,
setHeaders(res, pathname) { setHeaders(res, pathname) {
if (!pathname.includes('/assets/')) { if (notAnAsset(pathname)) {
// force server validation for non-asset files since they // force server validation for non-asset files since they
// are not fingerprinted // are not fingerprinted
res.setHeader('cache-control', 'no-cache') res.setHeader('cache-control', 'no-cache')
@ -42,14 +51,14 @@ export async function serve(options: ServeOptions = {}) {
}) })
if (base) { if (base) {
polka() polka({ onNoMatch })
.use(base, compress, serve) .use(base, compress, serve)
.listen(port, (err: any) => { .listen(port, (err: any) => {
if (err) throw err if (err) throw err
console.log(`Built site served at http://localhost:${port}/${base}/\n`) console.log(`Built site served at http://localhost:${port}/${base}/\n`)
}) })
} else { } else {
polka() polka({ onNoMatch })
.use(compress, serve) .use(compress, serve)
.listen(port, (err: any) => { .listen(port, (err: any) => {
if (err) throw err if (err) throw err

@ -1,4 +1,9 @@
import { SiteData, PageData, LocaleConfig } from '../../types/shared' import {
SiteData,
PageData,
LocaleConfig,
HeadConfig
} from '../../types/shared'
export type { export type {
SiteData, SiteData,
@ -21,7 +26,7 @@ export const notFoundPageData: PageData = {
title: '404', title: '404',
description: 'Not Found', description: 'Not Found',
headers: [], headers: [],
frontmatter: {}, frontmatter: { sidebar: false, layout: 'page' },
lastUpdated: 0 lastUpdated: 0
} }
@ -136,3 +141,17 @@ function cleanRoute(siteData: SiteData, route: string): string {
return route.slice(baseWithoutSuffix.length) return route.slice(baseWithoutSuffix.length)
} }
function hasTag(head: HeadConfig[], tag: HeadConfig) {
const [tagType, tagAttrs] = tag
if (tagType !== 'meta') return false
const keyAttr = Object.entries(tagAttrs)[0] // First key
if (keyAttr == null) return false
return head.some(
([type, attrs]) => type === tagType && attrs[keyAttr[0]] === keyAttr[1]
)
}
export function mergeHead(prev: HeadConfig[], curr: HeadConfig[]) {
return [...prev.filter((tagAttrs) => !hasTag(curr, tagAttrs)), ...curr]
}

Loading…
Cancel
Save