From f4cd0aeb6919bbb5a3ca27b7d6f295427e75998c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=83=BD=E5=AE=81?= <994718917@qq.com> Date: Tue, 9 May 2023 00:23:23 +0800 Subject: [PATCH 01/33] refactor: resolve duplicate function definitions (#2350) --- src/node/markdown/plugins/link.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/node/markdown/plugins/link.ts b/src/node/markdown/plugins/link.ts index cab9a6ea..d742f678 100644 --- a/src/node/markdown/plugins/link.ts +++ b/src/node/markdown/plugins/link.ts @@ -5,7 +5,7 @@ import type MarkdownIt from 'markdown-it' import type { MarkdownEnv } from '../env' import { URL } from 'url' -import { EXTERNAL_URL_RE, PATHNAME_PROTOCOL_RE } from '../../shared' +import { EXTERNAL_URL_RE, PATHNAME_PROTOCOL_RE, isExternal } from '../../shared' const indexRE = /(^|.*\/)index.md(#?.*)$/i @@ -26,8 +26,7 @@ export const linkPlugin = ( if (hrefIndex >= 0) { const hrefAttr = token.attrs![hrefIndex] const url = hrefAttr[1] - const isExternal = EXTERNAL_URL_RE.test(url) - if (isExternal) { + if (isExternal(url)) { Object.entries(externalAttrs).forEach(([key, val]) => { token.attrSet(key, val) }) From 58795d2f938d39989a48dd04df27c43fa2cb62a9 Mon Sep 17 00:00:00 2001 From: engvuchen <31369318+engvuchen@users.noreply.github.com> Date: Tue, 9 May 2023 00:32:21 +0800 Subject: [PATCH 02/33] docs: add warning about using block elements in footer config (#2341) Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> --- docs/reference/default-theme-footer.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/reference/default-theme-footer.md b/docs/reference/default-theme-footer.md index e363537f..6457798e 100644 --- a/docs/reference/default-theme-footer.md +++ b/docs/reference/default-theme-footer.md @@ -36,4 +36,8 @@ export default { } ``` +::: warning +Only inline elements can be used in `message` and `copyright` as they are rendered inside a `

` element. If you want to add block elements, consider using [`layout-bottom`](../guide/extending-default-theme#layout-slots) slot instead. +::: + Note that footer will not be displayed when the [SideBar](./default-theme-sidebar) is visible. From c20bd283319158135e2d850485970dfc5fe82812 Mon Sep 17 00:00:00 2001 From: Vinicius Teixeira Dias <69281620+viniciusteixeiradias@users.noreply.github.com> Date: Tue, 9 May 2023 02:55:32 +1000 Subject: [PATCH 03/33] feat(theme): open search box on pressing slash too (#2328) Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> --- .../components/VPNavBarSearch.vue | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/client/theme-default/components/VPNavBarSearch.vue b/src/client/theme-default/components/VPNavBarSearch.vue index dfa5e588..12c3929e 100644 --- a/src/client/theme-default/components/VPNavBarSearch.vue +++ b/src/client/theme-default/components/VPNavBarSearch.vue @@ -61,9 +61,12 @@ onMounted(() => { preconnect() - const handleSearchHotKey = (e: KeyboardEvent) => { - if (e.key === 'k' && (e.ctrlKey || e.metaKey)) { - e.preventDefault() + const handleSearchHotKey = (event: KeyboardEvent) => { + if ( + (event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) || + (!isEditingContent(event) && event.key === '/') + ) { + event.preventDefault() load() remove() } @@ -101,6 +104,18 @@ function poll() { }, 16) } +function isEditingContent(event: KeyboardEvent): boolean { + const element = event.target as HTMLElement + const tagName = element.tagName + + return ( + element.isContentEditable || + tagName === 'INPUT' || + tagName === 'SELECT' || + tagName === 'TEXTAREA' + ) +} + // Local search const showSearch = ref(false) @@ -112,6 +127,13 @@ if (__VP_LOCAL_SEARCH__) { showSearch.value = true } }) + + onKeyStroke('/', (event) => { + if (!isEditingContent(event)) { + event.preventDefault() + showSearch.value = true + } + }) } const metaKey = ref(`'Meta'`) From 35f8b896372e75e62882df613a49e8945e7bc832 Mon Sep 17 00:00:00 2001 From: JD Solanki Date: Mon, 8 May 2023 22:31:10 +0530 Subject: [PATCH 04/33] fix(theme): don't update opacity on hover (#2326) --- src/client/theme-default/components/VPNavBarTitle.vue | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/client/theme-default/components/VPNavBarTitle.vue b/src/client/theme-default/components/VPNavBarTitle.vue index 0da14c90..805bc7fa 100644 --- a/src/client/theme-default/components/VPNavBarTitle.vue +++ b/src/client/theme-default/components/VPNavBarTitle.vue @@ -35,10 +35,6 @@ const { currentLang } = useLangs() transition: opacity 0.25s; } -.title:hover { - opacity: 0.6; -} - @media (min-width: 960px) { .title { flex-shrink: 0; From 2f482afaabdb4206b87e2453d0099257693c4653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20S=C3=A1nchez?= Date: Mon, 8 May 2023 19:22:47 +0200 Subject: [PATCH 05/33] feat(theme): add focus trap to local search dialog (#2324) Co-authored-by: Divyansh Singh <40380293+brc-dd@users.noreply.github.com> --- package.json | 2 + pnpm-lock.yaml | 66 +++++++++++++++++++ .../components/VPLocalSearchBox.vue | 22 +++++-- 3 files changed, 84 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 9eba1f2e..cf967e5d 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,9 @@ "@vitejs/plugin-vue": "^4.2.1", "@vue/devtools-api": "^6.5.0", "@vueuse/core": "^10.1.0", + "@vueuse/integrations": "^10.1.0", "body-scroll-lock": "4.0.0-beta.0", + "focus-trap": "^7.4.0", "mark.js": "8.11.1", "minisearch": "^6.0.1", "shiki": "^0.14.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75561cd3..98a9864f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,9 +19,15 @@ importers: '@vueuse/core': specifier: ^10.1.0 version: 10.1.0(vue@3.2.47) + '@vueuse/integrations': + specifier: ^10.1.0 + version: 10.1.0(focus-trap@7.4.0)(vue@3.2.47) body-scroll-lock: specifier: 4.0.0-beta.0 version: 4.0.0-beta.0 + focus-trap: + specifier: ^7.4.0 + version: 7.4.0 mark.js: specifier: 8.11.1 version: 8.11.1 @@ -1276,6 +1282,56 @@ packages: - vue dev: false + /@vueuse/integrations@10.1.0(focus-trap@7.4.0)(vue@3.2.47): + resolution: {integrity: sha512-eSGdRYSFIspSYQIC0hzivi95Jv/BEH9Z47BrpNNKZi6k/LcHDAANmiNeiPN1BxlmO2PovG8dN9Et/AZC4WZ2YQ==} + peerDependencies: + async-validator: '*' + axios: '*' + change-case: '*' + drauu: '*' + focus-trap: '*' + fuse.js: '*' + idb-keyval: '*' + jwt-decode: '*' + nprogress: '*' + qrcode: '*' + sortablejs: '*' + universal-cookie: '*' + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + dependencies: + '@vueuse/core': 10.1.0(vue@3.2.47) + '@vueuse/shared': 10.1.0(vue@3.2.47) + focus-trap: 7.4.0 + vue-demi: 0.14.0(vue@3.2.47) + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + /@vueuse/metadata@10.1.0: resolution: {integrity: sha512-cM28HjDEw5FIrPE9rgSPFZvQ0ZYnOLAOr8hl1XM6tFl80U3WAR5ROdnAqiYybniwP5gt9MKKAJAqd/ab2aHkqg==} dev: false @@ -2249,6 +2305,12 @@ packages: path-exists: 5.0.0 dev: true + /focus-trap@7.4.0: + resolution: {integrity: sha512-yI7FwUqU4TVb+7t6PaQ3spT/42r/KLEi8mtdGoQo2li/kFzmu9URmalTvw7xCCJtSOyhBxscvEAmvjeN9iHARg==} + dependencies: + tabbable: 6.1.1 + dev: false + /follow-redirects@1.15.2(debug@4.3.4): resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} @@ -4156,6 +4218,10 @@ packages: engines: {node: '>= 0.4'} dev: true + /tabbable@6.1.1: + resolution: {integrity: sha512-4kl5w+nCB44EVRdO0g/UGoOp3vlwgycUVtkk/7DPyeLZUCuNFFKCFG6/t/DgHLrUPHjrZg6s5tNm+56Q2B0xyg==} + dev: false + /temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} diff --git a/src/client/theme-default/components/VPLocalSearchBox.vue b/src/client/theme-default/components/VPLocalSearchBox.vue index 8f630248..0230d141 100644 --- a/src/client/theme-default/components/VPLocalSearchBox.vue +++ b/src/client/theme-default/components/VPLocalSearchBox.vue @@ -9,6 +9,7 @@ import { useScrollLock, useSessionStorage } from '@vueuse/core' +import { useFocusTrap } from '@vueuse/integrations/useFocusTrap' import Mark from 'mark.js/src/vanilla.js' import MiniSearch, { type SearchResult } from 'minisearch' import { useRouter } from 'vitepress' @@ -26,11 +27,11 @@ import { type Ref } from 'vue' import type { ModalTranslations } from '../../../../types/local-search' +import { dataSymbol } from '../../app/data' import { pathToFile } from '../../app/utils' import { slash } from '../../shared' import { useData } from '../composables/data' import { createTranslate } from '../support/translation' -import { dataSymbol } from '../../app/data' defineProps<{ placeholder: string @@ -64,6 +65,12 @@ interface Result { } const vitePressData = useData() +const { activate } = useFocusTrap(el, { + immediate: true, + allowOutsideClick: true, + clickOutsideDeactivates: true, + escapeDeactivates: true +}) const { localeIndex, theme } = vitePressData const searchIndex = computedAsync(async () => markRaw( @@ -84,14 +91,14 @@ const searchIndex = computedAsync(async () => const disableQueryPersistence = computed(() => { return ( - theme.value.search?.provider === 'local' && - theme.value.search.options?.disableQueryPersistence === true + theme.value.search?.provider === 'local' && + theme.value.search.options?.disableQueryPersistence === true ) }) const filterText = disableQueryPersistence.value - ? ref('') - : useSessionStorage('vitepress:local-search-filter', '') + ? ref('') + : useSessionStorage('vitepress:local-search-filter', '') const showDetailedList = useLocalStorage( 'vitepress:local-search-detailed-list', @@ -334,6 +341,7 @@ onMounted(() => { body.value = document.body nextTick(() => { isLocked.value = true + nextTick().then(() => activate()) }) }) @@ -474,6 +482,7 @@ function formMarkRegex(terms: Set) { }" :aria-label="[...p.titles, p.title].join(' > ')" @mouseenter="!disableMouseOver && (selectedIndex = index)" + @focusin="selectedIndex = index" @click="$emit('close')" >

@@ -498,7 +507,7 @@ function formMarkRegex(terms: Set) {
-
+
@@ -727,6 +736,7 @@ function formMarkRegex(terms: Set) { transition: none; line-height: 1rem; border: solid 2px var(--vp-local-search-result-border); + outline: none; } .result > div { From b31933fbdd7aabfe080234407153aefa8f6a3f30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=83=BD=E5=AE=81?= Date: Wed, 10 May 2023 23:28:59 +0800 Subject: [PATCH 06/33] fix(build): uniform handling of windows slash in localSearchPlugin (#2358) --- src/client/theme-default/components/VPLocalSearchBox.vue | 3 +-- src/node/plugins/localSearchPlugin.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/client/theme-default/components/VPLocalSearchBox.vue b/src/client/theme-default/components/VPLocalSearchBox.vue index 0230d141..0f7349b3 100644 --- a/src/client/theme-default/components/VPLocalSearchBox.vue +++ b/src/client/theme-default/components/VPLocalSearchBox.vue @@ -29,7 +29,6 @@ import { import type { ModalTranslations } from '../../../../types/local-search' import { dataSymbol } from '../../app/data' import { pathToFile } from '../../app/utils' -import { slash } from '../../shared' import { useData } from '../composables/data' import { createTranslate } from '../support/translation' @@ -218,7 +217,7 @@ debouncedWatch( ) async function fetchExcerpt(id: string) { - const file = pathToFile(slash(id.slice(0, id.indexOf('#')))) + const file = pathToFile(id.slice(0, id.indexOf('#'))) try { return { id, mod: await import(/*@vite-ignore*/ file) } } catch (e) { diff --git a/src/node/plugins/localSearchPlugin.ts b/src/node/plugins/localSearchPlugin.ts index 0afa1adb..080a8483 100644 --- a/src/node/plugins/localSearchPlugin.ts +++ b/src/node/plugins/localSearchPlugin.ts @@ -60,7 +60,7 @@ export async function localSearchPlugin( } function getLocaleForPath(file: string) { - const relativePath = path.relative(siteConfig.srcDir, file) + const relativePath = slash(path.relative(siteConfig.srcDir, file)) const siteData = resolveSiteDataByRoute(siteConfig.site, relativePath) return siteData?.localeIndex ?? 'root' } @@ -97,7 +97,7 @@ export async function localSearchPlugin( function getDocId(file: string) { let relFile = slash(path.relative(siteConfig.srcDir, file)) relFile = siteConfig.rewrites.map[relFile] || relFile - let id = path.join(siteConfig.site.base, relFile) + let id = slash(path.join(siteConfig.site.base, relFile)) id = id.replace(/\/index\.md$/, '/') id = id.replace(/\.md$/, siteConfig.cleanUrls ? '' : '.html') return id From af4bb52947d9454e71ce63c06c5efe814410209b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=83=BD=E5=AE=81?= Date: Wed, 10 May 2023 23:30:26 +0800 Subject: [PATCH 07/33] refactor: simplify `hasAside` computed property (#2356) --- src/client/theme-default/composables/sidebar.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/client/theme-default/composables/sidebar.ts b/src/client/theme-default/composables/sidebar.ts index df307d3c..a99d317e 100644 --- a/src/client/theme-default/composables/sidebar.ts +++ b/src/client/theme-default/composables/sidebar.ts @@ -60,8 +60,7 @@ export function useSidebar() { const hasAside = computed(() => { if (frontmatter.value.layout === 'home') return false if (frontmatter.value.aside != null) return !!frontmatter.value.aside - if (theme.value.aside === false) return false - return true + return theme.value.aside !== false }) const isSidebarEnabled = computed(() => hasSidebar.value && is960.value) From 97065cefc22e4772c0295c5ad23a87eea286f46b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Wed, 10 May 2023 23:36:15 +0800 Subject: [PATCH 08/33] feat(cli): add shortcuts (#2353) --- src/node/cli.ts | 2 + src/node/shortcuts.ts | 95 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/node/shortcuts.ts diff --git a/src/node/cli.ts b/src/node/cli.ts index 39be1190..4aef9747 100644 --- a/src/node/cli.ts +++ b/src/node/cli.ts @@ -4,6 +4,7 @@ import { createLogger } from 'vite' import { build, createServer, serve } from '.' import { init } from './init/init' import { version } from '../../package.json' +import { bindShortcuts } from './shortcuts' const argv: any = minimist(process.argv.slice(2)) @@ -33,6 +34,7 @@ if (!command || command === 'dev') { await server.listen() logVersion(server.config.logger) server.printUrls() + bindShortcuts(server) } createDevServer().catch((err) => { createLogger().error( diff --git a/src/node/shortcuts.ts b/src/node/shortcuts.ts new file mode 100644 index 00000000..7e27fd65 --- /dev/null +++ b/src/node/shortcuts.ts @@ -0,0 +1,95 @@ +import colors from 'picocolors' +import type { ViteDevServer } from 'vite' + +export type CLIShortcut = { + key: string + description: string + action(server: ViteDevServer): void | Promise +} + +export function bindShortcuts(server: ViteDevServer): void { + if (!server.httpServer || !process.stdin.isTTY || process.env.CI) { + return + } + + server.config.logger.info( + colors.dim(colors.green(' ➜')) + + colors.dim(' press ') + + colors.bold('h') + + colors.dim(' to show help') + ) + + let actionRunning = false + + const onInput = async (input: string) => { + // ctrl+c or ctrl+d + if (input === '\x03' || input === '\x04') { + await server.close().finally(() => process.exit(1)) + return + } + + if (actionRunning) return + + if (input === 'h') { + server.config.logger.info( + [ + '', + colors.bold(' Shortcuts'), + ...SHORTCUTS.map( + (shortcut) => + colors.dim(' press ') + + colors.bold(shortcut.key) + + colors.dim(` to ${shortcut.description}`) + ) + ].join('\n') + ) + } + + const shortcut = SHORTCUTS.find((shortcut) => shortcut.key === input) + if (!shortcut) return + + actionRunning = true + await shortcut.action(server) + actionRunning = false + } + + process.stdin.setRawMode(true) + + process.stdin.on('data', onInput).setEncoding('utf8').resume() + + server.httpServer.on('close', () => { + process.stdin.off('data', onInput).pause() + }) +} + +const SHORTCUTS: CLIShortcut[] = [ + { + key: 'u', + description: 'show server url', + action(server) { + server.config.logger.info('') + server.printUrls() + } + }, + { + key: 'o', + description: 'open in browser', + action(server) { + server.openBrowser() + } + }, + { + key: 'c', + description: 'clear console', + action(server) { + server.config.logger.clearScreen('error') + } + }, + { + key: 'q', + description: 'quit', + async action(server) { + await server.close().finally(() => process.exit()) + } + } +] From d6c0985002ee792b1e8e052f71cdd6bd72c315ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaqu=C3=ADn=20S=C3=A1nchez?= Date: Wed, 10 May 2023 17:38:45 +0200 Subject: [PATCH 09/33] fix(a11y): mobile and theme switcher (#2354) --- .../theme-default/components/VPNavBar.vue | 6 ++++++ .../components/VPNavBarSearchButton.vue | 2 +- .../components/VPSwitchAppearance.vue | 19 +++++++++---------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/client/theme-default/components/VPNavBar.vue b/src/client/theme-default/components/VPNavBar.vue index bccf5c68..bcd7d0b4 100644 --- a/src/client/theme-default/components/VPNavBar.vue +++ b/src/client/theme-default/components/VPNavBar.vue @@ -175,6 +175,12 @@ const classes = computed(() => ({ } } +@media (max-width: 768px) { + .content-body { + column-gap: 0.5rem; + } +} + .menu + .translations::before, .menu + .appearance::before, .menu + .social-links::before, diff --git a/src/client/theme-default/components/VPNavBarSearchButton.vue b/src/client/theme-default/components/VPNavBarSearchButton.vue index f3436a42..886bce29 100644 --- a/src/client/theme-default/components/VPNavBarSearchButton.vue +++ b/src/client/theme-default/components/VPNavBarSearchButton.vue @@ -61,7 +61,7 @@ defineProps<{ align-items: center; margin: 0; padding: 0; - width: 32px; + width: 48px; height: 55px; background: transparent; transition: border-color 0.25s; diff --git a/src/client/theme-default/components/VPSwitchAppearance.vue b/src/client/theme-default/components/VPSwitchAppearance.vue index f4da3e9e..3a283545 100644 --- a/src/client/theme-default/components/VPSwitchAppearance.vue +++ b/src/client/theme-default/components/VPSwitchAppearance.vue @@ -75,16 +75,15 @@ watch(checked, (newIsDark) => {