UNPKG

faim

Version:

Element Plus & Element UI isomorphic UI component library, more than Element.

218 lines (217 loc) 7.96 kB
import { debounce } from "lodash-es"; import tinymce from "tinymce/tinymce"; import { v4 as uuidv4 } from "uuid"; import { computed, defineComponent, h, isVue3, onMounted, onUnmounted, ref, unref, watch } from "vue-demi"; import { conclude, resolveConfig } from "vue-global-config"; import "tinymce/models/dom"; import "tinymce/plugins/accordion"; import "tinymce/plugins/advlist"; import "tinymce/plugins/anchor"; import "tinymce/plugins/autolink"; import "tinymce/plugins/autosave"; import "tinymce/plugins/charmap"; import "tinymce/plugins/directionality"; import "tinymce/plugins/emoticons"; import "tinymce/plugins/emoticons/js/emojis.min"; import "tinymce/plugins/fullscreen"; import "tinymce/plugins/help"; import "tinymce/plugins/image"; import "tinymce/plugins/importcss"; import "tinymce/plugins/insertdatetime"; import "tinymce/plugins/link"; import "tinymce/plugins/lists"; import "tinymce/plugins/media"; import "tinymce/plugins/nonbreaking"; import "tinymce/plugins/pagebreak"; import "tinymce/plugins/preview"; import "tinymce/plugins/quickbars"; import "tinymce/plugins/save"; import "tinymce/plugins/searchreplace"; import "tinymce/plugins/table"; import "tinymce/plugins/visualblocks"; import "tinymce/plugins/visualchars"; import "tinymce/plugins/wordcount"; const model = { prop: isVue3 ? "modelValue" : "value", event: isVue3 ? "update:modelValue" : "input" }; const globalProps = {}; const globalAttrs = {}; const globalListeners = {}; const globalSlots = {}; export default defineComponent({ name: "FaRichText", install(app, options = {}) { const { props, attrs, listeners, slots } = resolveConfig(options, { props: this.props, camelizePropNames: true }); Object.assign(globalProps, props); Object.assign(globalAttrs, attrs); Object.assign(globalListeners, listeners); Object.assign(globalSlots, slots); app.component(this.name, this); }, props: { [model.prop]: String, disabled: { type: Boolean, default: void 0 }, outputFormat: {} }, emits: [model.event, "init"], setup(props, { emit, expose, attrs }) { const id = ref(`minimce-${uuidv4()}`); const preventSettingContent = ref(false); const preventUpdatingModelValue = ref(false); const Disabled = computed( () => conclude([props.disabled, globalProps.disabled], { type: Boolean }) ); const OutputFormat = computed(() => conclude([props.outputFormat, globalProps.outputFormat], { type: String })); const Options = computed(() => conclude( [ attrs, globalAttrs, { selector: `#${id.value}`, /** * 默认开启所有免费插件 * https://www.tiny.cloud/docs/tinymce/6/full-featured-open-source-demo/ */ plugins: "preview importcss searchreplace autolink autosave save directionality visualblocks visualchars fullscreen image link media table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount help charmap quickbars emoticons accordion", menubar: "file edit view insert format tools table help", toolbar: "undo redo | accordion accordionremove | bold italic underline strikethrough | fontfamily fontsize blocks | alignleft aligncenter alignright alignjustify | outdent indent | numlist bullist | forecolor backcolor removeformat | pagebreak | charmap emoticons | fullscreen preview save print | insertfile image media template link anchor codesample | ltr rtl", quickbars_selection_toolbar: "bold italic | quicklink h2 h3 blockquote quickimage quicktable", contextmenu: "link image table", branding: false, promotion: false, quickbars_insert_toolbar: false, // 默认屏蔽 iframe 原因: // - 允许用户引入未知的 iframe 存在执行未知脚本等安全隐患 // - 小程序侧不支持 iframe // - 小程序侧 web-view 中使用 iframe 需要配置业务域名 // - 给微信公众号 H5 侧带来授权问题 invalid_elements: "iframe,frame", // note that skin and content_css is disabled to avoid the normal // loading process and is instead loaded as a string via content_style skin: false, content_css: false, // skin: useDarkMode ? 'oxide-dark' : 'oxide', // content_css: useDarkMode ? 'dark' : 'default', autosave_ask_before_unload: false, // 改动后刷新,不再弹 alert autosave_interval: "30s", autosave_prefix: "{path}{query}-{id}-", autosave_restore_when_empty: false, autosave_retention: "2m", // importcss_append: true, // height: 500, relative_urls: false, convert_urls: false, image_advtab: true, image_caption: true, // 开启时,出现两个 bug:1. 部分菜单项失效;2. 拖拉拽调整视频大小会错位(该问题在 v6.0 仍在存在) media_live_embeds: false, toolbar_mode: "sliding", // toolbar_sticky: true, // toolbar_sticky_offset: isSmallScreen ? 102 : 108, // extended_valid_elements: 'img[class|src|border=0|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name|referrerpolicy=no-referrer]', init_instance_callback: (editor) => { watch( Disabled, (n) => { editor.mode.set(n ? "readonly" : "design"); }, { immediate: true } ); const onContentChange = debounce(() => { if (preventUpdatingModelValue.value) { preventUpdatingModelValue.value = false; return; } const newContent = editor.getContent({ format: OutputFormat.value }); if (newContent !== props[model.prop]) { preventSettingContent.value = true; emit(model.event, newContent); } }, 100); editor.on("Change input Redo Undo SetContent", onContentChange); watch( () => props[model.prop], (newModelValue) => { if (preventSettingContent.value) { preventSettingContent.value = false; return; } preventUpdatingModelValue.value = true; editor.setContent(newModelValue || ""); } ); setTimeout(() => { editor.setContent(props[model.prop] || ""); }); const wordcountButton = editor.getContainer().querySelector("button.tox-statusbar__wordcount"); wordcountButton?.click(); } } ], { mergeFunction: (previousValue, currentValue) => (...args) => { previousValue(...args); currentValue(...args); }, type: Object } )); onUnmounted(() => { tinymce.get(id.value)?.destroy(); }); onMounted(() => { const el = document.querySelector(`#${id.value}`); const intersectionObserver = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { intersectionObserver.unobserve(el); tinymce.init(Options.value); } else { } }); if (el) { intersectionObserver.observe(el); } }); expose?.({ id }); return { id // loading, // height: (Options.value.height ?? '400') + 'px', }; }, render() { return isVue3 ? h("textarea", { id: this.id, class: "fa-rich-text" }) : h("textarea", { attrs: { id: unref(this.id), class: "fa-rich-text" }, on: { input: (value) => { this.$emit(model.event, value); } } }); } });