faim
Version:
Element Plus & Element UI isomorphic UI component library, more than Element.
218 lines (217 loc) • 7.96 kB
JavaScript
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);
}
}
});
}
});