diff --git a/packages/@vuepress/plugin-pwa-popup/package.json b/packages/@vuepress/plugin-pwa-popup/package.json new file mode 100644 index 0000000000..d59ab7b570 --- /dev/null +++ b/packages/@vuepress/plugin-pwa-popup/package.json @@ -0,0 +1,42 @@ +{ + "name": "@vuepress/plugin-pwa-popup", + "version": "2.0.0-alpha.7", + "description": "VuePress plugin - PWA popup", + "keywords": [ + "vuepress", + "plugin", + "pwa", + "popup" + ], + "homepage": "https://github.com/vuepress", + "bugs": { + "url": "https://github.com/vuepress/vuepress-next/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuepress/vuepress-next.git" + }, + "license": "MIT", + "author": "meteorlxy", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib", + "styles" + ], + "scripts": { + "build": "tsc -b tsconfig.build.json", + "clean": "rimraf lib *.tsbuildinfo", + "copy": "cpx \"src/**/*.css\" lib" + }, + "dependencies": { + "@vuepress/client": "2.0.0-alpha.7", + "@vuepress/core": "2.0.0-alpha.7", + "@vuepress/plugin-pwa": "2.0.0-alpha.7", + "@vuepress/shared": "2.0.0-alpha.7", + "@vuepress/utils": "2.0.0-alpha.7" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/@vuepress/plugin-pwa-popup/src/components/PwaPopup.css b/packages/@vuepress/plugin-pwa-popup/src/components/PwaPopup.css new file mode 100644 index 0000000000..ee2b00dc12 --- /dev/null +++ b/packages/@vuepress/plugin-pwa-popup/src/components/PwaPopup.css @@ -0,0 +1,32 @@ +:root { + --pwa-popup-color: #3eaf7c; +} + +.pwa-popup { + position: fixed; + right: 1em; + bottom: 1em; + padding: 1em; + border: 1px solid #3eaf7c; + border-radius: 3px; + background: #fff; + box-shadow: 0 4px 16px var(--pwa-popup-color); + text-align: center; + z-index: 3; +} + +.pwa-popup > button { + margin-top: 0.5em; + padding: 0.25em 2em; +} + +.pwa-popup-enter-active, +.pwa-popup-leave-active { + transition: opacity 0.3s, transform 0.3s; +} + +.pwa-popup-enter-from, +.pwa-popup-leave-to { + opacity: 0; + transform: translate(0, 50%) scale(0.5); +} diff --git a/packages/@vuepress/plugin-pwa-popup/src/components/PwaPopup.ts b/packages/@vuepress/plugin-pwa-popup/src/components/PwaPopup.ts new file mode 100644 index 0000000000..c07df874d5 --- /dev/null +++ b/packages/@vuepress/plugin-pwa-popup/src/components/PwaPopup.ts @@ -0,0 +1,89 @@ +import { computed, defineComponent, h, ref, Transition } from 'vue' +import type { PropType } from 'vue' +import { useRouteLocale } from '@vuepress/client' +import { + usePwaEvent, + useSkipWaiting, +} from '@vuepress/plugin-pwa/lib/composables' +import type { LocaleConfig } from '@vuepress/shared' + +import './PwaPopup.css' + +export type PwaPopupLocales = LocaleConfig<{ + message: string + buttonText: string +}> + +export const PwaPopup = defineComponent({ + name: 'PwaPopup', + + props: { + locales: { + type: Object as PropType, + required: true, + default: () => ({}), + }, + }, + + setup(props) { + const event = usePwaEvent() + const routeLocale = useRouteLocale() + + const locale = computed( + () => + props.locales[routeLocale.value] ?? { + message: 'New content is available.', + buttonText: 'Refresh', + } + ) + + const show = ref(false) + const registration = ref(null) + const onClick = () => { + show.value = false + if (registration.value) { + useSkipWaiting(registration.value) + location.reload(true) + } + } + + event.on('updated', (reg) => { + if (reg) { + registration.value = reg + show.value = true + } + }) + + return () => + h( + Transition, + { + name: 'pwa-popup', + }, + { + default: () => + show.value + ? h( + 'div', + { + class: 'pwa-popup', + }, + [ + locale.value.message, + h('br'), + h( + 'button', + { + onClick, + }, + locale.value.buttonText + ), + ] + ) + : null, + } + ) + }, +}) + +export default PwaPopup diff --git a/packages/@vuepress/plugin-pwa-popup/src/components/PwaPopupWrapper.ts b/packages/@vuepress/plugin-pwa-popup/src/components/PwaPopupWrapper.ts new file mode 100644 index 0000000000..f54e9e8d91 --- /dev/null +++ b/packages/@vuepress/plugin-pwa-popup/src/components/PwaPopupWrapper.ts @@ -0,0 +1,22 @@ +import { h } from 'vue' +import type { FunctionalComponent } from 'vue' +import { PwaPopup } from './PwaPopup' +import type { PwaPopupLocales } from './PwaPopup' + +declare const __DEV__: boolean +declare const __SSR__: boolean +declare const __PWA_POPUP_LOCALES__: PwaPopupLocales + +const locales = __PWA_POPUP_LOCALES__ + +export const PwaPopupWrapper: FunctionalComponent = () => { + if (__DEV__ || __SSR__) return null + + return h(PwaPopup, { + locales, + }) +} + +PwaPopupWrapper.displayName = 'PwaPopupWrapper' + +export default PwaPopupWrapper diff --git a/packages/@vuepress/plugin-pwa-popup/src/index.ts b/packages/@vuepress/plugin-pwa-popup/src/index.ts new file mode 100644 index 0000000000..2e2f89fe19 --- /dev/null +++ b/packages/@vuepress/plugin-pwa-popup/src/index.ts @@ -0,0 +1,28 @@ +import type { Plugin } from '@vuepress/core' +import type { LocaleConfig } from '@vuepress/shared' +import { path } from '@vuepress/utils' + +/** + * Options for @vuepress/plugin-pwa-popup + */ +export interface PwaPopupPluginOptions { + locales?: LocaleConfig<{ + message: string + buttonText: string + }> +} + +export const pwaPopupPlugin: Plugin = ({ locales }) => ({ + name: '@vuepress/plugin-pwa-popup', + + clientAppRootComponentFiles: path.resolve( + __dirname, + './components/PwaPopupWrapper.js' + ), + + define: { + __PWA_POPUP_LOCALES__: locales, + }, +}) + +export default pwaPopupPlugin diff --git a/packages/@vuepress/plugin-pwa-popup/tsconfig.build.json b/packages/@vuepress/plugin-pwa-popup/tsconfig.build.json new file mode 100644 index 0000000000..0adc8e05c1 --- /dev/null +++ b/packages/@vuepress/plugin-pwa-popup/tsconfig.build.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../tsconfig.base.json", + "references": [ + { "path": "../client/tsconfig.build.json" }, + { "path": "../core/tsconfig.build.json" }, + { "path": "../plugin-pwa/tsconfig.build.json" }, + { "path": "../shared/tsconfig.build.json" }, + { "path": "../utils/tsconfig.build.json" }, + { "path": "./tsconfig.esm.json" }, + { "path": "./tsconfig.cjs.json" } + ], + "files": [] +} diff --git a/packages/@vuepress/plugin-pwa-popup/tsconfig.cjs.json b/packages/@vuepress/plugin-pwa-popup/tsconfig.cjs.json new file mode 100644 index 0000000000..7559624d9e --- /dev/null +++ b/packages/@vuepress/plugin-pwa-popup/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "CommonJS", + "rootDir": "./src", + "outDir": "./lib" + }, + "include": ["./src/index.ts"] +} diff --git a/packages/@vuepress/plugin-pwa-popup/tsconfig.esm.json b/packages/@vuepress/plugin-pwa-popup/tsconfig.esm.json new file mode 100644 index 0000000000..917ce7e1e2 --- /dev/null +++ b/packages/@vuepress/plugin-pwa-popup/tsconfig.esm.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "ES2020", + "rootDir": "./src", + "outDir": "./lib" + }, + "include": ["./src"], + "exclude": ["./src/index.ts"] +} diff --git a/packages/@vuepress/plugin-pwa-popup/tsconfig.json b/packages/@vuepress/plugin-pwa-popup/tsconfig.json new file mode 100644 index 0000000000..3114e03c34 --- /dev/null +++ b/packages/@vuepress/plugin-pwa-popup/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": ["./src", "./__tests__"] +} diff --git a/tsconfig.json b/tsconfig.json index 96f1dd38c0..c81d4920f5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,7 @@ "path": "./packages/@vuepress/plugin-palette-stylus/tsconfig.build.json" }, { "path": "./packages/@vuepress/plugin-pwa/tsconfig.build.json" }, + { "path": "./packages/@vuepress/plugin-pwa-popup/tsconfig.build.json" }, { "path": "./packages/@vuepress/shared/tsconfig.build.json" }, { "path": "./packages/@vuepress/theme-default/tsconfig.build.json" }, { "path": "./packages/@vuepress/theme-vue/tsconfig.build.json" },