Skip to content

Commit

Permalink
feat(plugin-redirect): add redirect plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Mister-Hope committed Feb 2, 2024
1 parent af2e351 commit 62d0582
Show file tree
Hide file tree
Showing 34 changed files with 1,491 additions and 0 deletions.
57 changes: 57 additions & 0 deletions plugins/plugin-redirect/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"name": "@vuepress/plugin-redirect",
"version": "2.0.0-rc.0",
"description": "VuePress plugin - redirect",
"keywords": [
"vuepress-plugin",
"vuepress",
"plugin",
"redirect"
],
"homepage": "https://ecosystem.vuejs.press/plugins/redirect.html",
"bugs": {
"url": "https://github.com/vuepress/ecosystem/issues"
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuepress/ecosystem.git",
"directory": "plugins/plugin-redirect"
},
"license": "MIT",
"author": {
"name": "Mr.Hope",
"email": "[email protected]",
"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",
"bin": {
"vp-redirect": "./lib/cli/index.js"
},
"files": [
"lib"
],
"scripts": {
"build": "tsc -b tsconfig.build.json",
"clean": "rimraf --glob ./lib ./*.tsbuildinfo",
"style": "sass src:lib --style=compressed --no-source-map"
},
"dependencies": {
"@vuepress/helper": "workspace:*",
"@vueuse/core": "^10.7.2",
"cac": "^6.7.14",
"vue": "^3.4.15",
"vue-router": "^4.2.5"
},
"peerDependencies": {
"vuepress": "2.0.0-rc.2"
},
"publishConfig": {
"access": "public"
}
}
132 changes: 132 additions & 0 deletions plugins/plugin-redirect/src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#!/usr/bin/env node
import { createRequire } from 'node:module'
import { removeEndingSlash, removeLeadingSlash } from '@vuepress/helper'
import { cac } from 'cac'
import {
loadUserConfig,
resolveAppConfig,
resolveCliAppConfig,
resolveUserConfigConventionalPath,
transformUserConfigToPlugin,
} from 'vuepress/cli'
import { createBuildApp } from 'vuepress/core'
import { fs, logger, path } from 'vuepress/utils'
import { getRedirectHTML } from '../node/utils/index.js'

interface RedirectCommandOptions {
hostname: string
output?: string
config?: string
cache: string
temp?: string
cleanCache?: boolean
cleanTemp?: boolean
}

const require = createRequire(import.meta.url)

const cli = cac('vp-redirect')
const { version } = require('@vuepress/plugin-redirect/package.json') as {
version: string
}

cli
.command(
'generate [source-dir]',
'Generate redirect site using VuePress project under source folder',
)
.option(
'--hostname <hostname>',
'Hostname to redirect to (E.g.: https://new.example.com/)',
{ default: '/' },
)
.option('-c, --config <config>', 'Set path to config file')
.option(
'-o, --output <output>',
'Set the output directory (default: .vuepress/redirect)',
)
.option('--cache <cache>', 'Set the directory of the cache files')
.option('-t, --temp <temp>', 'Set the directory of the temporary files')
.option('--clean-cache', 'Clean the cache files before generation')
.option('--clean-temp', 'Clean the temporary files before generation')
.action(async (sourceDir: string, commandOptions: RedirectCommandOptions) => {
if (!sourceDir) return cli.outputHelp()

// ensure NODE_ENV is set
process.env.NODE_ENV ??= 'production'

// resolve app config from cli options
const cliAppConfig = resolveCliAppConfig(sourceDir, {})

// resolve user config file
const userConfigPath = resolveUserConfigConventionalPath(
cliAppConfig.source,
)

const { userConfig } = await loadUserConfig(userConfigPath)

// resolve the final app config to use
const appConfig = resolveAppConfig({
defaultAppConfig: {},
cliAppConfig,
userConfig,
})

if (appConfig === null) return

// create vuepress app
const app = createBuildApp(appConfig)

// use user-config plugin
app.use(transformUserConfigToPlugin(userConfig, cliAppConfig.source))

// clean temp and cache
if (commandOptions.cleanTemp === true) {
logger.info('Cleaning temp...')
await fs.remove(app.dir.temp())
}
if (commandOptions.cleanCache === true) {
logger.info('Cleaning cache...')
await fs.remove(app.dir.cache())
}

const outputFolder = commandOptions.output
? path.join(process.cwd(), commandOptions.output)
: path.join(app.dir.source(), '.vuepress', 'redirect')

// empty output directory
await fs.emptyDir(outputFolder)

// initialize vuepress app to get pages
logger.info('Initializing VuePress and preparing data...')

await app.init()

// redirect all pages

// initialize vuepress app to get pages
logger.info('Generating redirect pages...')

await Promise.all(
app.pages.map((page) => {
const redirectUrl = `${removeEndingSlash(commandOptions.hostname)}${
app.options.base
}${removeLeadingSlash(page.path)}`
const destLocation = path.join(
outputFolder,
removeLeadingSlash(page.path.replace(/\/$/, '/index.html')),
)

return fs
.ensureDir(path.dirname(destLocation))
.then(() => fs.writeFile(destLocation, getRedirectHTML(redirectUrl)))
}),
)
})

cli.command('').action(() => cli.outputHelp())

cli.help()
cli.version(version)

cli.parse()
167 changes: 167 additions & 0 deletions plugins/plugin-redirect/src/client/components/LanguageSwitch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import {
usePreferredLanguages,
useScrollLock,
useSessionStorage,
} from '@vueuse/core'
import type { VNode } from 'vue'
import {
computed,
defineComponent,
h,
onMounted,
onUnmounted,
ref,
TransitionGroup,
watch,
} from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRouteLocale } from 'vuepress/client'
import {
redirectLocaleConfig,
redirectLocaleEntries,
redirectLocales,
} from '../define.js'

import '../styles/language-switch.scss'

const { switchLocale } = redirectLocaleConfig

interface LocaleInfo {
lang: string
localePath: string
}

const REDIRECT_LOCALE_STORAGE = useSessionStorage<Record<string, boolean>>(
'VUEPRESS_REDIRECT_LOCALES',
{},
)

export default defineComponent({
name: 'LanguageSwitch',

setup() {
const languages = usePreferredLanguages()
const route = useRoute()
const router = useRouter()
const routeLocale = useRouteLocale()

const showModal = ref(false)

const info = computed<LocaleInfo | null>(() => {
if (redirectLocaleEntries.some(([key]) => routeLocale.value === key))
for (const language of languages.value)
for (const [localePath, langs] of redirectLocaleEntries)
if (langs.includes(language)) {
if (localePath === routeLocale.value) return null

return {
lang: language,
localePath,
}
}

return null
})

const locale = computed(() => {
if (info.value) {
const { lang, localePath } = info.value
const locales = [
redirectLocales[routeLocale.value],
redirectLocales[localePath],
]

return {
hint: locales.map(({ hint }) => hint.replace('$1', lang)),
switch: locales
.map(({ switch: switchText }) => switchText.replace('$1', lang))
.join(' / '),
cancel: locales.map(({ cancel }) => cancel).join(' / '),
}
}

return null
})

const targetRoute = computed(() =>
info.value
? route.path.replace(routeLocale.value, info.value.localePath)
: null,
)

const updateStatus = (): void => {
REDIRECT_LOCALE_STORAGE.value[routeLocale.value] = true
showModal.value = false
}

onMounted(() => {
const isLocked = useScrollLock(document.body)

if (!REDIRECT_LOCALE_STORAGE.value[routeLocale.value])
if (info.value)
if (switchLocale === 'direct') router.replace(targetRoute.value!)
else if (switchLocale === 'modal') showModal.value = true
else showModal.value = false
else showModal.value = false

watch(
showModal,
(value) => {
isLocked.value = value
},
{ immediate: true },
)

onUnmounted(() => {
isLocked.value = false
})
})

return (): VNode | null =>
showModal.value
? h(TransitionGroup, { name: 'lang-modal-fade' }, () =>
showModal.value
? h(
'div',
{ key: 'mask', class: 'lang-modal-mask' },
h(
'div',
{
key: 'popup',
class: 'lang-modal-wrapper',
},
[
h(
'div',
{ class: 'lang-modal-content' },
locale.value?.hint.map((text) => h('p', text)),
),
h(
'button',
{
type: 'button',
class: 'lang-modal-action primary',
onClick: () => {
updateStatus()
router.replace(targetRoute.value!)
},
},
locale.value?.switch,
),
h(
'button',
{
type: 'button',
class: 'lang-modal-action',
onClick: () => updateStatus(),
},
locale.value?.cancel,
),
],
),
)
: null,
)
: null
},
})
1 change: 1 addition & 0 deletions plugins/plugin-redirect/src/client/composables/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './redirect.js'
Loading

0 comments on commit 62d0582

Please sign in to comment.