From eb3888f2676ece9549bafe35d28bf345e84b51e5 Mon Sep 17 00:00:00 2001 From: motea927 Date: Sat, 17 Jun 2023 10:52:46 +0800 Subject: [PATCH 1/3] feat!: :boom: replace directive to plugin BREAKING CHANGE: remove all directive --- packages/core/directive.ts | 57 ------------------------------- packages/core/main.ts | 2 +- packages/core/plugins.d.ts | 7 ++++ packages/core/plugins.ts | 70 ++++++++++++++++++++++++++++++++++++++ packages/core/shared.ts | 16 ++++----- playgrounds/App.vue | 6 ++-- playgrounds/main.ts | 7 ++-- plugins/i18n-html.ts | 11 ++++++ 8 files changed, 101 insertions(+), 75 deletions(-) delete mode 100644 packages/core/directive.ts create mode 100644 packages/core/plugins.d.ts create mode 100644 packages/core/plugins.ts create mode 100644 plugins/i18n-html.ts diff --git a/packages/core/directive.ts b/packages/core/directive.ts deleted file mode 100644 index 014cc29..0000000 --- a/packages/core/directive.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { Directive } from 'vue' -import { watchEffect } from 'vue' -import type { BindingObj, Options } from './shared' -import { - getDefaultString, - sanitizeHtml, - hasGlobalOptions, - setGlobalOptions, - handleDefaultString, - getBindingValue -} from './shared' - -export type SafeHtmlDirective = Directive< - HTMLElement, - string | (() => string) | BindingObj -> - -const stopWatchMap = new WeakMap void>() - -export const vSafeHtml: SafeHtmlDirective = { - beforeMount(el, binding) { - const stopWatch = watchEffect(() => { - if (!binding.value) return - - const bindingValue = getBindingValue(binding.value) - const defaultString = - typeof binding.value === 'string' || typeof binding.value === 'function' - ? getDefaultString() - : getDefaultString(binding.value.defaultString) - const sanitizeConfig = - typeof binding.value === 'string' || typeof binding.value === 'function' - ? undefined - : binding.value.sanitizeConfig - - if ( - handleDefaultString(el, bindingValue, defaultString, sanitizeConfig) - ) { - return - } - - const sanitizeResult = sanitizeHtml(bindingValue, sanitizeConfig) - el.innerHTML = sanitizeResult - }) - stopWatchMap.set(el, stopWatch) - }, - beforeUnmount(el) { - stopWatchMap.get(el)?.() - stopWatchMap.delete(el) - } -} - -export const createDirective = (options?: Options) => { - if (!hasGlobalOptions() && options) { - setGlobalOptions(options) - } - return vSafeHtml -} diff --git a/packages/core/main.ts b/packages/core/main.ts index 19c3b43..f3dd176 100644 --- a/packages/core/main.ts +++ b/packages/core/main.ts @@ -1,3 +1,3 @@ export { setGlobalOptions } from './shared' -export { vSafeHtml, createDirective } from './directive' export { UseSafeHtml } from './components' +export { createI18nHtml } from './plugins' diff --git a/packages/core/plugins.d.ts b/packages/core/plugins.d.ts new file mode 100644 index 0000000..e806d93 --- /dev/null +++ b/packages/core/plugins.d.ts @@ -0,0 +1,7 @@ +import type { $i18nHtml } from './plugins' + +declare module 'vue' { + interface ComponentCustomProperties { + $i18nHtml: typeof $i18nHtml + } +} diff --git a/packages/core/plugins.ts b/packages/core/plugins.ts new file mode 100644 index 0000000..e7b88b7 --- /dev/null +++ b/packages/core/plugins.ts @@ -0,0 +1,70 @@ +import type { App } from 'vue' +import { useI18n } from 'vue-i18n' +import type { Options, BindingObj } from './shared' +import { + setGlobalOptions, + getBindingValue, + getDefaultString, + handleDefaultString, + sanitizeHtml +} from './shared' + +const generateHtml = + (getContentText: (binding: string | BindingObj) => string) => + (binding: string | BindingObj): string => { + if (!binding) return '' + + const contentText = getContentText(binding) + const defaultString = + typeof binding === 'string' + ? getDefaultString() + : getDefaultString(binding.defaultString) + const sanitizeConfig = + typeof binding === 'string' ? undefined : binding.sanitizeConfig + + const defaultStringResult = handleDefaultString( + contentText, + defaultString, + sanitizeConfig + ) + + if (defaultStringResult !== false) { + return defaultStringResult + } + + const sanitizeResult = sanitizeHtml(contentText, sanitizeConfig) + + return sanitizeResult + } + +export const $safeHtml = generateHtml(getBindingValue) + +export const $i18nHtml = generateHtml((binding) => { + if (!useI18n) { + console.warn(`[mt-v-safe-html]: can not find vue-i18n`) + return '' + } + const { t } = useI18n() + const i18nContentKey = getBindingValue(binding) + return getBindingValue(() => t(i18nContentKey)) +}) + +export const createSafeHtml = { + install: (app: App, options?: Options) => { + if (options) { + setGlobalOptions(options) + } + + app.config.globalProperties.$safeHtml = $safeHtml + } +} + +export const createI18nHtml = { + install: (app: App, options?: Options) => { + if (options) { + setGlobalOptions(options) + } + + app.config.globalProperties.$i18nHtml = $i18nHtml + } +} diff --git a/packages/core/shared.ts b/packages/core/shared.ts index 37d4aa9..3fbfcb3 100644 --- a/packages/core/shared.ts +++ b/packages/core/shared.ts @@ -1,5 +1,6 @@ import DOMPurify from 'dompurify' import type { Config } from 'dompurify' +import { ref } from 'vue' import { isValidate } from './validate' type SanitizeConfig = Config & { @@ -16,41 +17,40 @@ export type BindingObj = { htmlString: string | (() => string) } & Options -let globalOptions: Options | undefined +const globalOptions = ref() export function getDefaultString( componentDefaultString?: string ): string | undefined { - return componentDefaultString ?? globalOptions?.defaultString + return componentDefaultString ?? globalOptions.value?.defaultString } const { sanitize } = DOMPurify export function sanitizeHtml(html: string, sanitizeConfig?: SanitizeConfig) { - const overrideSanitizeConfig = globalOptions?.sanitizeConfig || sanitizeConfig + const overrideSanitizeConfig = + globalOptions.value?.sanitizeConfig || sanitizeConfig return overrideSanitizeConfig ? sanitize(html, overrideSanitizeConfig) : sanitize(html) } export function setGlobalOptions(options: Options | undefined) { - globalOptions = options + globalOptions.value = options } export function hasGlobalOptions(): boolean { - return globalOptions !== undefined + return globalOptions.value !== undefined } // Handle default string export function handleDefaultString( - el: HTMLElement, bindingValue: string, defaultString: string | undefined, sanitizeConfig?: Options['sanitizeConfig'] ) { if (defaultString && !isValidate(bindingValue)) { const sanitizeDefaultResult = sanitizeHtml(defaultString, sanitizeConfig) - el.innerHTML = sanitizeDefaultResult - return true + return sanitizeDefaultResult } return false } diff --git a/playgrounds/App.vue b/playgrounds/App.vue index 502a5ce..47e252c 100644 --- a/playgrounds/App.vue +++ b/playgrounds/App.vue @@ -35,13 +35,11 @@

components

-

i18n

-

-

+

@@ -50,7 +48,7 @@