pull/5416/merge
Achord Chan 5 days ago committed by GitHub
commit 6f6f9d157f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -197,6 +197,7 @@ const sidebar: DefaultTheme.Config['sidebar'] = {
export default defineConfig({ export default defineConfig({
title: 'Example', title: 'Example',
description: 'An example app using VitePress.', description: 'An example app using VitePress.',
dir: false,
srcExclude: ['**/parts/**'], srcExclude: ['**/parts/**'],
markdown: { markdown: {
image: { lazyLoad: true } image: { lazyLoad: true }

@ -42,6 +42,25 @@ describe('navigation accessibility', () => {
expect(await sectionLink.getAttribute('aria-current')).toBeNull() expect(await sectionLink.getAttribute('aria-current')).toBeNull()
}) })
test('preserves a runtime-managed document direction', async () => {
await goto('/')
const html = page.locator('html')
expect(await html.getAttribute('dir')).toBeNull()
await page.evaluate(() => {
document.documentElement.dir = 'rtl'
})
await page
.locator('.VPNavBarMenuLink[href="/markdown-extensions/"]')
.click()
await page.waitForFunction(() =>
location.pathname.startsWith('/markdown-extensions')
)
expect(await html.getAttribute('dir')).toBe('rtl')
})
test('marks only exact sidebar links, including fragments', async () => { test('marks only exact sidebar links, including fragments', async () => {
const overview = '.VPSidebarItem .link[href="/sidebar-hash/"]' const overview = '.VPSidebarItem .link[href="/sidebar-hash/"]'
const sectionOne = '.VPSidebarItem .link[href="/sidebar-hash/#section-one"]' const sectionOne = '.VPSidebarItem .link[href="/sidebar-hash/#section-one"]'

@ -3,10 +3,16 @@ import {
mergeConfig, mergeConfig,
normalizeAssetsBase, normalizeAssetsBase,
normalizeSiteBase, normalizeSiteBase,
resolveSiteData,
type UserConfig type UserConfig
} from 'node/config' } from 'node/config'
describe('node/config', () => { describe('node/config', () => {
test('preserves disabled automatic direction handling', async () => {
expect((await resolveSiteData('', { dir: false })).dir).toBe(false)
expect((await resolveSiteData('', {})).dir).toBe('ltr')
})
test('merges markdown hooks from extended configs', async () => { test('merges markdown hooks from extended configs', async () => {
const calls: string[] = [] const calls: string[] = []
const md = {} as MarkdownItAsync const md = {} as MarkdownItAsync

@ -44,7 +44,7 @@ The following properties can be overridden for each locale (including root):
```ts ```ts
interface LocaleSpecificConfig<ThemeConfig = any> { interface LocaleSpecificConfig<ThemeConfig = any> {
lang?: string lang?: string
dir?: string dir?: string | false
title?: string title?: string
titleTemplate?: string | boolean titleTemplate?: string | boolean
description?: string description?: string
@ -53,6 +53,8 @@ interface LocaleSpecificConfig<ThemeConfig = any> {
} }
``` ```
Set `dir: false` at the site or locale level when application code manages the `<html dir>` attribute at runtime. VitePress will omit the attribute from generated HTML and will not overwrite it during client-side navigation.
Refer [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) interface for details on customizing the placeholder texts of the default theme. Don't override `themeConfig.algolia` or `themeConfig.carbonAds` at locale-level. Refer [Algolia docs](../reference/default-theme-search#i18n) for using multilingual search. Refer [`DefaultTheme.Config`](https://github.com/vuejs/vitepress/blob/main/types/default-theme.d.ts) interface for details on customizing the placeholder texts of the default theme. Don't override `themeConfig.algolia` or `themeConfig.carbonAds` at locale-level. Refer [Algolia docs](../reference/default-theme-search#i18n) for using multilingual search.
**Pro tip:** Config file can be stored at `docs/.vitepress/config/index.ts` too. It might help you organize stuff by creating a configuration file per locale and then merge and export them from `index.ts`. **Pro tip:** Config file can be stored at `docs/.vitepress/config/index.ts` too. It might help you organize stuff by creating a configuration file per locale and then merge and export them from `index.ts`.
@ -143,4 +145,4 @@ watchEffect(() => {
## RTL Support (Experimental) ## RTL Support (Experimental)
For RTL support, specify `dir: 'rtl'` in config and use some RTLCSS PostCSS plugin like <https://github.com/MohammadYounes/rtlcss>, <https://github.com/vkalinichev/postcss-rtl> or <https://github.com/elchininet/postcss-rtlcss>. You'll need to configure your PostCSS plugin to use `:where([dir="ltr"])` and `:where([dir="rtl"])` as prefixes to prevent CSS specificity issues. For RTL support, specify `dir: 'rtl'` in config and use some RTLCSS PostCSS plugin like <https://github.com/MohammadYounes/rtlcss>, <https://github.com/vkalinichev/postcss-rtl> or <https://github.com/elchininet/postcss-rtlcss>. You'll need to configure your PostCSS plugin to use `:where([dir="ltr"])` and `:where([dir="rtl"])` as prefixes to prevent CSS specificity issues. If users can switch direction at runtime, set `dir: false` and update `document.documentElement.dir` in your application code.

@ -40,7 +40,7 @@ interface VitePressData<T = any> {
description: Ref<string> description: Ref<string>
lang: Ref<string> lang: Ref<string>
isDark: Ref<boolean> isDark: Ref<boolean>
dir: Ref<string> dir: Ref<string | false>
localeIndex: Ref<string> localeIndex: Ref<string>
/** /**
* Current location hash * Current location hash

@ -58,7 +58,10 @@ export function initData(route: Route): VitePressData {
frontmatter: computed(() => route.data.frontmatter), frontmatter: computed(() => route.data.frontmatter),
params: computed(() => route.data.params), params: computed(() => route.data.params),
lang: computed(() => site.value.lang), lang: computed(() => site.value.lang),
dir: computed(() => route.data.frontmatter.dir || site.value.dir), dir: computed(() => {
const dir = route.data.frontmatter.dir
return dir === false ? false : dir || site.value.dir
}),
localeIndex: computed(() => site.value.localeIndex || 'root'), localeIndex: computed(() => site.value.localeIndex || 'root'),
title: computed(() => createTitle(site.value, route.data)), title: computed(() => createTitle(site.value, route.data)),
description: computed( description: computed(

@ -49,7 +49,9 @@ const VitePressApp = defineComponent({
onMounted(() => { onMounted(() => {
watchEffect(() => { watchEffect(() => {
document.documentElement.lang = lang.value document.documentElement.lang = lang.value
document.documentElement.dir = dir.value if (dir.value !== false) {
document.documentElement.dir = dir.value
}
}) })
}) })

@ -41,7 +41,7 @@ export function useLangs({
linkToCorrespondingPage linkToCorrespondingPage
}), }),
lang: value.lang, lang: value.lang,
dir: value.dir dir: value.dir === false ? undefined : value.dir
} }
) )
) )

@ -99,7 +99,9 @@ export async function renderPage(
const title = createTitle(siteData, pageData) const title = createTitle(siteData, pageData)
const description = pageData.description || siteData.description const description = pageData.description || siteData.description
const dir = pageData.frontmatter.dir || siteData.dir || 'ltr' const frontmatterDir = pageData.frontmatter.dir
const dir = frontmatterDir === false ? false : frontmatterDir || siteData.dir
const dirAttr = dir === false ? '' : ` dir="${dir || 'ltr'}"`
const isDefault404 = page === '404.md' && !hasCustom404 const isDefault404 = page === '404.md' && !hasCustom404
// the initial load only needs the lean page js — the static content is // the initial load only needs the lean page js — the static content is
@ -200,7 +202,7 @@ export async function renderPage(
} }
const html = `<!DOCTYPE html> const html = `<!DOCTYPE html>
<html lang="${siteData.lang}" dir="${dir}"> <html lang="${siteData.lang}"${dirAttr}>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
${ ${

@ -409,7 +409,7 @@ export async function resolveSiteData(
return { return {
lang: userConfig.lang || 'en-US', lang: userConfig.lang || 'en-US',
dir: userConfig.dir || 'ltr', dir: userConfig.dir === false ? false : userConfig.dir || 'ltr',
title: userConfig.title || 'VitePress', title: userConfig.title || 'VitePress',
titleTemplate: userConfig.titleTemplate, titleTemplate: userConfig.titleTemplate,
description: userConfig.description || 'A VitePress site', description: userConfig.description || 'A VitePress site',

12
types/shared.d.ts vendored

@ -184,10 +184,11 @@ export interface SiteData<ThemeConfig = any> {
*/ */
lang: string lang: string
/** /**
* The text direction (`dir` attribute) of the site. * The text direction (`dir` attribute) of the site. Set to `false` to let
* application code manage the attribute.
* @default 'ltr' * @default 'ltr'
*/ */
dir: string dir: string | false
/** /**
* The title of the site. * The title of the site.
* @default 'VitePress' * @default 'VitePress'
@ -295,7 +296,7 @@ export interface VitePressData<T = any> {
/** /**
* The text direction of the active locale. * The text direction of the active locale.
*/ */
dir: Ref<string> dir: Ref<string | false>
/** /**
* The key of the active locale. * The key of the active locale.
*/ */
@ -379,9 +380,10 @@ export interface LocaleSpecificConfig<ThemeConfig = any> {
*/ */
lang?: string lang?: string
/** /**
* The text direction of the locale. * The text direction of the locale. Set to `false` to let application code
* manage the `dir` attribute.
*/ */
dir?: string dir?: string | false
/** /**
* The title of the site in the locale. * The title of the site in the locale.
*/ */

Loading…
Cancel
Save