From 4a50404feae553e0f212c11b7a8373dc5773d0dc Mon Sep 17 00:00:00 2001 From: "Mr.Hope" Date: Thu, 1 Feb 2024 23:26:40 +0800 Subject: [PATCH] feat(plugin-rtl): add rtl plugin (#49) --- docs/.vuepress/configs/navbar/en.ts | 1 + docs/.vuepress/configs/navbar/zh.ts | 1 + docs/.vuepress/configs/sidebar/en.ts | 1 + docs/.vuepress/configs/sidebar/zh.ts | 1 + docs/plugins/rtl.md | 53 ++++++++++++++++++++++++ docs/zh/plugins/copy-code.md | 20 ++++----- docs/zh/plugins/feed/channel.md | 34 +++++++-------- docs/zh/plugins/rtl.md | 53 ++++++++++++++++++++++++ docs/zh/plugins/seo/config.md | 8 ++-- docs/zh/plugins/sitemap/config.md | 2 +- plugins/plugin-rtl/package.json | 50 ++++++++++++++++++++++ plugins/plugin-rtl/src/client/config.ts | 52 +++++++++++++++++++++++ plugins/plugin-rtl/src/node/index.ts | 1 + plugins/plugin-rtl/src/node/rtlPlugin.ts | 37 +++++++++++++++++ plugins/plugin-rtl/tsconfig.build.json | 8 ++++ pnpm-lock.yaml | 9 ++++ tsconfig.build.json | 1 + 17 files changed, 300 insertions(+), 32 deletions(-) create mode 100644 docs/plugins/rtl.md create mode 100644 docs/zh/plugins/rtl.md create mode 100644 plugins/plugin-rtl/package.json create mode 100644 plugins/plugin-rtl/src/client/config.ts create mode 100644 plugins/plugin-rtl/src/node/index.ts create mode 100644 plugins/plugin-rtl/src/node/rtlPlugin.ts create mode 100644 plugins/plugin-rtl/tsconfig.build.json diff --git a/docs/.vuepress/configs/navbar/en.ts b/docs/.vuepress/configs/navbar/en.ts index 0940054a08..c148cbdedd 100644 --- a/docs/.vuepress/configs/navbar/en.ts +++ b/docs/.vuepress/configs/navbar/en.ts @@ -52,6 +52,7 @@ export const navbarEn: NavbarConfig = [ '/plugins/active-header-links', '/plugins/git', '/plugins/palette', + '/plugins/rtl', '/plugins/theme-data', '/plugins/toc', ], diff --git a/docs/.vuepress/configs/navbar/zh.ts b/docs/.vuepress/configs/navbar/zh.ts index 1398cec50e..f34a634910 100644 --- a/docs/.vuepress/configs/navbar/zh.ts +++ b/docs/.vuepress/configs/navbar/zh.ts @@ -52,6 +52,7 @@ export const navbarZh: NavbarConfig = [ '/zh/plugins/active-header-links', '/zh/plugins/git', '/zh/plugins/palette', + '/zh/plugins/rtl', '/zh/plugins/theme-data', '/zh/plugins/toc', ], diff --git a/docs/.vuepress/configs/sidebar/en.ts b/docs/.vuepress/configs/sidebar/en.ts index e063e6aa6e..4403b69176 100644 --- a/docs/.vuepress/configs/sidebar/en.ts +++ b/docs/.vuepress/configs/sidebar/en.ts @@ -68,6 +68,7 @@ export const sidebarEn: SidebarConfig = { '/plugins/active-header-links', '/plugins/git', '/plugins/palette', + '/plugins/rtl', '/plugins/theme-data', '/plugins/toc', ], diff --git a/docs/.vuepress/configs/sidebar/zh.ts b/docs/.vuepress/configs/sidebar/zh.ts index c1a7e5d952..0e705529f5 100644 --- a/docs/.vuepress/configs/sidebar/zh.ts +++ b/docs/.vuepress/configs/sidebar/zh.ts @@ -69,6 +69,7 @@ export const sidebarZh: SidebarConfig = { '/zh/plugins/active-header-links', '/zh/plugins/git', '/zh/plugins/palette', + '/zh/plugins/rtl', '/zh/plugins/theme-data', '/zh/plugins/toc', ], diff --git a/docs/plugins/rtl.md b/docs/plugins/rtl.md new file mode 100644 index 0000000000..a4e89cec06 --- /dev/null +++ b/docs/plugins/rtl.md @@ -0,0 +1,53 @@ +# rtl + + + +This plugin will set direction to rtl on configured locales. + +## Usage + +```bash +npm i -D @vuepress/plugin-rtl@next +``` + +```ts +import { rtlPlugin } from '@vuepress/plugin-rtl' + +export default { + plugins: [ + rtlPlugin({ + // options + locales: ['/ar/'], + }), + ], +} +``` + +## Options + +### locales + +- Type: `string[]` +- Default: `['/']` +- Details: + Locale path to enable rtl. + +### selector + +- Type: `SelectorOptions` + + ```ts + interface SelectorOptions { + [element: string]: { + [attrs: string]: string + } + } + ``` + +- Default: `{ 'html': { dir: 'rtl' } }` + +- Details: + + Selector to enable rtl. + + The default settings mean that the `dir` attribute of the `html` element will be set to `rtl` in rtl locales. diff --git a/docs/zh/plugins/copy-code.md b/docs/zh/plugins/copy-code.md index 47dbd5655b..2da24bbb5b 100644 --- a/docs/zh/plugins/copy-code.md +++ b/docs/zh/plugins/copy-code.md @@ -28,32 +28,32 @@ export default { ### selector -- 类型: `string | string[]` -- 默认值: `'.theme-default-content div[class*="language-"] pre'` +- 类型:`string | string[]` +- 默认值:`'.theme-default-content div[class*="language-"] pre'` - 详情: 代码块选择器 ### showInMobile -- 类型: `boolean` -- 默认值: `false` +- 类型:`boolean` +- 默认值:`false` - 详情: 是否展示在移动端 ### duration -- 类型: `number` -- 默认值: `2000` +- 类型:`number` +- 默认值:`2000` - 详情: 提示消息显示时间,设置为 `0` 会禁用提示。 ### delay -- 类型: `number` -- 默认值: `800` +- 类型:`number` +- 默认值:`800` - 详情: 注册复制按钮的延时,单位 ms。 @@ -62,7 +62,7 @@ export default { ### locales -- 类型: `CopyCodeLocaleConfig` +- 类型:`CopyCodeLocaleConfig` ```ts interface CopyCodeLocaleData { @@ -82,7 +82,7 @@ export default { } ``` -- 必填: 否 +- 必填:否 - 详情: 复制按钮插件的国际化配置。 diff --git a/docs/zh/plugins/feed/channel.md b/docs/zh/plugins/feed/channel.md index 621026736f..09497d62f7 100644 --- a/docs/zh/plugins/feed/channel.md +++ b/docs/zh/plugins/feed/channel.md @@ -4,28 +4,28 @@ ## channel.title -- 类型: `string` -- 默认值: `SiteConfig.title` +- 类型:`string` +- 默认值:`SiteConfig.title` 频道的标题 ## channel.link -- 类型: `string` -- 默认值: 部署的网址 (通过 `options.hostname` 和 `context.base` 生成) +- 类型:`string` +- 默认值:部署的网址 (通过 `options.hostname` 和 `context.base` 生成) 频道地址 ## channel.description -- 类型: `string` -- 默认值: `SiteConfig.description` +- 类型:`string` +- 默认值:`SiteConfig.description` 频道描述信息 ## channel.language -- 类型: `string` +- 类型:`string` - 默认值: - `siteConfig.locales['/'].locales` - 如果上述未提供,回退到 `"en-US"` @@ -34,7 +34,7 @@ ## channel.copyright -- 类型: `string` +- 类型:`string` - 默认值: - 尝试读取 channel 选项中的 `author.name` 生成 `Copyright by $author` - 建议自行设置: **是** @@ -43,43 +43,43 @@ ## channel.pubDate -- 类型: `string` (需是合法的 Date ISOString) -- 默认值: 每次插件构建时刻 +- 类型:`string` (需是合法的 Date ISOString) +- 默认值:每次插件构建时刻 - 建议自行设置: **是** 频道内容的发布时间 ## channel.lastUpdated -- 类型: `string` (需是合法的 Date ISOString) -- 默认值: 每次插件构建时刻 +- 类型:`string` (需是合法的 Date ISOString) +- 默认值:每次插件构建时刻 频道内容的上次更新时间 ## channel.ttl -- 类型: `number` +- 类型:`number` - 建议自行设置: **是** 内容有效时间,即获取后保持缓存而不进行新获取的时间 ## channel.image -- 类型: `string` +- 类型:`string` - 建议自行设置: **是** 这是一个会在频道中使用的图片,建议设置正方形图片、尺寸最好不小于 512×512。 ## channel.icon -- 类型: `string` +- 类型:`string` - 建议自行设置: **是** 一个代表频道的图标,建议设置正方形图片、尺寸最好不小于 128×128,背景色透明。 ## channel.author -- 类型: `FeedAuthor` +- 类型:`FeedAuthor` - 建议自行设置: **是** 频道的作者。 @@ -107,7 +107,7 @@ interface FeedAuthor { ## channel.hub -- 类型: `string` +- 类型:`string` Websub 的链接。Websub 需要服务器后端,与 VuePress 主旨不符,如无特殊需要忽略即可。 diff --git a/docs/zh/plugins/rtl.md b/docs/zh/plugins/rtl.md new file mode 100644 index 0000000000..cdc2029da2 --- /dev/null +++ b/docs/zh/plugins/rtl.md @@ -0,0 +1,53 @@ +# rtl + + + +此插件会在配置的语言上设置 rtl 方向。 + +## 使用方法 + +```bash +npm i -D @vuepress/plugin-rtl@next +``` + +```ts +import { rtlPlugin } from '@vuepress/plugin-rtl' + +export default { + plugins: [ + rtlPlugin({ + // 配置项 + locales: ['/ar/'], + }), + ], +} +``` + +## 选项 + +### locales + +- 类型:`string[]` +- 默认值:`['/']` +- 详情: + 开启 RTL 布局的多语言路径。 + +### selector + +- 类型:`SelectorOptions` + + ```ts + interface SelectorOptions { + [element: string]: { + [attrs: string]: string + } + } + ``` + +- 默认值:`{ 'html': { dir: 'rtl' } }` + +- 详情: + + 开启 RTL 的选择器。 + + 默认设置意味着在 RTL 多语言中,`html` 元素的 `dir` 属性将被设置为 `rtl`。 diff --git a/docs/zh/plugins/seo/config.md b/docs/zh/plugins/seo/config.md index eaed5baca8..a8753c0d86 100644 --- a/docs/zh/plugins/seo/config.md +++ b/docs/zh/plugins/seo/config.md @@ -43,7 +43,7 @@ - 类型:`boolean` -- 默认值: `true` +- 默认值:`true` - 详情: @@ -91,7 +91,7 @@ ## ogp -- 类型: +- 类型: ```ts function ogp( @@ -112,7 +112,7 @@ ## jsonLd -- 类型: +- 类型: ```ts function jsonLd( @@ -133,7 +133,7 @@ ## customHead -- 类型: +- 类型: ```ts function customHead( diff --git a/docs/zh/plugins/sitemap/config.md b/docs/zh/plugins/sitemap/config.md index 4a544c077d..ab09332125 100644 --- a/docs/zh/plugins/sitemap/config.md +++ b/docs/zh/plugins/sitemap/config.md @@ -3,7 +3,7 @@ ## hostname - 类型:`string` -- 必填: 是 +- 必填:是 - 详情: 当前网站部署到的域名,插件需要此选项才能工作。 diff --git a/plugins/plugin-rtl/package.json b/plugins/plugin-rtl/package.json new file mode 100644 index 0000000000..47810c7e52 --- /dev/null +++ b/plugins/plugin-rtl/package.json @@ -0,0 +1,50 @@ +{ + "name": "vuepress-plugin-rtl", + "version": "2.0.0-rc.0", + "description": "VuePress plugin - rtl", + "keywords": [ + "vuepress-plugin", + "vuepress", + "plugin", + "rtl" + ], + "homepage": "https://ecosystem.vuejs.press/plugins/rtl.html", + "bugs": { + "url": "https://github.com/vuepress/ecosystem/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuepress/ecosystem.git", + "directory": "plugins/plugin-rtl" + }, + "license": "MIT", + "author": { + "name": "Mr.Hope", + "email": "mister-hope@outlook.com", + "url": "https://mister-hope.com" + }, + "type": "module", + "exports": { + ".": "./lib/node/index.js", + "./package.json": "./package.json" + }, + "main": "./lib/node/index.js", + "types": "./lib/node/index.d.ts", + "files": [ + "lib" + ], + "scripts": { + "build": "tsc -b tsconfig.build.json", + "clean": "rimraf --glob ./lib ./*.tsbuildinfo", + "copy": "cpx \"src/**/*.{d.ts,css,svg}\" lib" + }, + "dependencies": { + "vue": "^3.4.15" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.2" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/plugins/plugin-rtl/src/client/config.ts b/plugins/plugin-rtl/src/client/config.ts new file mode 100644 index 0000000000..fdfe0ad5fa --- /dev/null +++ b/plugins/plugin-rtl/src/client/config.ts @@ -0,0 +1,52 @@ +import { onMounted, watch } from 'vue' +import type { ClientConfig } from 'vuepress/client' +import { defineClientConfig, useRouteLocale } from 'vuepress/client' + +declare const __RTL_LOCALES__: string[] +declare const __RTL_SELECTOR__: Record> + +const entries = Object.entries + +const getElement = (selector: string): HTMLElement | null => + selector === 'html' + ? document.documentElement + : selector === 'body' + ? document.body + : document.querySelector(selector) + +export default defineClientConfig({ + setup() { + const routeLocale = useRouteLocale() + + const toggleRTL = (routeLocale: string): void => { + if (__RTL_LOCALES__.includes(routeLocale)) { + entries(__RTL_SELECTOR__).forEach(([selector, attrs = {}]) => { + const element = getElement(selector) + + if (element) + entries(attrs).forEach(([attr, value]) => { + if (attr === 'class') element.classList.add(value) + else element.setAttribute(attr, value) + }) + }) + document.documentElement.style.setProperty('direction', 'rtl') + } else { + entries(__RTL_LOCALES__).forEach(([selector, attrs = {}]) => { + const element = getElement(selector) + + if (element) + entries(attrs).forEach(([attr, value]) => { + if (attr === 'class') element.classList.remove(value) + else element.removeAttribute(attr) + }) + }) + + document.documentElement.style.removeProperty('direction') + } + } + + onMounted(() => { + watch(routeLocale, toggleRTL, { immediate: true }) + }) + }, +}) as ClientConfig diff --git a/plugins/plugin-rtl/src/node/index.ts b/plugins/plugin-rtl/src/node/index.ts new file mode 100644 index 0000000000..279190c03f --- /dev/null +++ b/plugins/plugin-rtl/src/node/index.ts @@ -0,0 +1 @@ +export * from './rtlPlugin.js' diff --git a/plugins/plugin-rtl/src/node/rtlPlugin.ts b/plugins/plugin-rtl/src/node/rtlPlugin.ts new file mode 100644 index 0000000000..16122c8ec1 --- /dev/null +++ b/plugins/plugin-rtl/src/node/rtlPlugin.ts @@ -0,0 +1,37 @@ +import type { PluginObject } from 'vuepress/core' +import { getDirname, path } from 'vuepress/utils' + +export interface RTLPluginOptions { + /** + * RTL locales + * + * @default ['/'] + */ + locales?: string[] + + /** + * RTL selector + * + * @default { 'html': { dir: 'rtl' } } + */ + // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style + selector?: { + // eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style + [element: string]: { + [attr: string]: string + } + } +} + +const __dirname = getDirname(import.meta.url) + +export const rltPlugin = (options: RTLPluginOptions = {}): PluginObject => ({ + name: 'vuepress-plugin-rtl', + + define: { + __RTL_LOCALES__: Array.isArray(options.locales) ? options.locales : ['/'], + __RTL_SELECTOR__: options.selector || { html: { dir: 'rtl' } }, + }, + + clientConfigFile: path.join(__dirname, '../client/config.js'), +}) diff --git a/plugins/plugin-rtl/tsconfig.build.json b/plugins/plugin-rtl/tsconfig.build.json new file mode 100644 index 0000000000..458814cb0c --- /dev/null +++ b/plugins/plugin-rtl/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib" + }, + "include": ["./src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af1ad3e262..802147c584 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -393,6 +393,15 @@ importers: specifier: 2.0.0-rc.2 version: 2.0.0-rc.2(@vuepress/bundler-vite@2.0.0-rc.2)(@vuepress/bundler-webpack@2.0.0-rc.2)(typescript@5.3.3)(vue@3.4.15) + plugins/plugin-rtl: + dependencies: + vue: + specifier: ^3.4.15 + version: 3.4.15(typescript@5.3.3) + vuepress: + specifier: 2.0.0-rc.2 + version: 2.0.0-rc.2(@vuepress/bundler-vite@2.0.0-rc.2)(@vuepress/bundler-webpack@2.0.0-rc.2)(typescript@5.3.3)(vue@3.4.15) + plugins/plugin-search: dependencies: chokidar: diff --git a/tsconfig.build.json b/tsconfig.build.json index 894866e8ba..8e4449c543 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -31,6 +31,7 @@ { "path": "./plugins/plugin-register-components/tsconfig.build.json" }, + { "path": "./plugins/plugin-rtl/tsconfig.build.json" }, { "path": "./plugins/plugin-search/tsconfig.build.json" }, { "path": "./plugins/plugin-seo/tsconfig.build.json" }, { "path": "./plugins/plugin-shiki/tsconfig.build.json" },