vue3 集成 tinymce 及自定义皮肤

【Vue 集成 TinyMCE 官方指南】

一、安装 tinymce-vue 包

npm install --save "@tinymce/tinymce-vue"

二、 使用 Tiny Cloud 或通过自托管 TinyMCE 来提供对 TinyMCE 的访问。

2.1 通过 Tiny Cloud 访问 TinyMCE(非最优解)

在 editor 元素中用 api-key 选项赋上你的 Tiny Cloud API key

比如: 下面是我们自定义的一个公共组件src/components/TinymceEditor/index.vue

<template>
  <editor api-key='your-api-key' :init="{ /* your other settings */ }" />
</template>

<script lang="ts">
import Editor from '@tinymce/tinymce-vue'
export default {
   name: 'TinymceEditor',
   components: { Editor }
 }
</script>
2.2 TinyMCE 自托管

TinyMCE 可以通过以下方式自托管:

  • 2.2.1 独立于 Vue.js 应用部署 TinyMCE
    (我们重点讲这个,请移步第三部分。)

要在创建 Vue.js 应用时对 TinyMCE 进行独立部署,可以将 tinymce 脚本添加到 HTML 文件(/项目跟目录/public/index.html)的 <head><body> 的末尾,like:

<script src="/path/to/tinymce.min.js"></script>

更多有关自托管 TinyMCE 的信息,请参阅:【Installing TinyMCE】

三、独立部署 TinyMCE

3.1 下载自托管版本(离线版的) TinyMCE

https://www.tiny.cloud/get-tiny/self-hosted/

可以下载 Dev 版的

把下载完后的js目录下的tinymce文件夹📁整个复制到项目的public目录下

3.2 下载中文语言包

https://www.tiny.cloud/get-tiny/language-packages/

下载完把zh_CN.js文件放在项目/public/tinymce/langs/下面

3.3 使用@tinymce/tinymce-vue组件

public下的文件在开发过程中可以直接用/访问到,我们这边用tinymceScriptSrc去指定加载我们本地的tinymce.min.js

<template>
    <editor
      :id="tinymceId"
      initialValue="<p>Initial editor content</p>"
      v-model="contentValue"
      :init="initOptions"
      tinymceScriptSrc="/tinymce/tinymce.min.js"
    />
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
import Editor from '@tinymce/tinymce-vue'
import {
  filePickerCallback,
  uploadHandler,
} from './uploadHandler'
import { menubar, plugins, toolbar } from './config'

export default defineComponent({
  name: 'Tinymce',
  components: { TinymceEditor, EditorImage },
  props: {
    tinymceId: {
      type: String,
      default: function () {
        return (
          'vue-tinymce-' +
          +new Date() +
          ((Math.random() * 1000).toFixed(0) + '')
        )
      },
    },
    modelValue: {
      type: String,
      default: '',
    },
  },
  emits: ['update:modelValue'],
  setup(props, { emit }) {
    const hasChange = ref(false)
    const hasInit = ref(false)
    const fullscreen = ref(false)

    const initOptions = ref({
      language: 'zh_CN',
      skin: 'animal',
      content_css: 'animal',
      body_class: 'panel-body',
      height: props.height, // 注:引入autoresize插件时,此属性失效
      min_height: props.height,
      plugins: plugins,
      toolbar: toolbar,
      toolbar_sticky: true,
      toolbar_mode: 'wrap', // 'sliding'
      content_style:
        'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }, image: {max-width: 100%;}',
      branding: false, // tiny技术支持信息是否显示
      link_title: false,
      default_link_target: '_blank',
      convert_urls: false,
      end_container_on_empty_block: true,
      imagetools_cors_hosts: ['106.54.168.208:1016'],
      init_instance_callback: (editor: any) => {
        // tinymce 实例初始化时的回调
        if (props.moduleValue) {
          editor.setContent(props.modelValue)
        }
        hasInit.value = true
        editor.on('NodeChange Change KeyUp SetContent', () => {
          hasChange.value = true
          emit('update:modelValue', editor.getContent())
        })
      },
      image_title: true,
      // #参考 https://www.tiny.cloud/docs/configure/file-image-upload/#file_picker_callback
      file_picker_callback: filePickerCallback, // 选择文件后的处理
      images_upload_handler: uploadHandler, // 自定义文件上传函数
      setup: (editor: any) => {
        // tinymce 实例渲染之前的回调
        editor.on('FullscreenStateChanged', (e: any) => {
          fullscreen.value = e.state
        })
      },
    })

    return {
      initOptions,
      hasInit,
      fullscreen,
      contentValue: computed({
        get: () => props.modelValue,
        set: val => {
          emit('update:modelValue', val)
        },
      }),
    }
  },
})
导入 plugins, toolbar 的 config 文件,一些插件和工作栏配置

太具体的代码为了篇幅不贴了,可以看下去我的项目仓库看 ➡️ https://github.com/aizawasayo/animal-admin-vue3.git

3.4 自定义主题

首先,访问 http://skin.tiny.cloud/t5/

下载下来的目录以你定义的皮肤名称为文件名,包含 skintool.json 配置文件和 skins 文件夹(新创建的皮肤)

我们用这个新的 skins 文件夹替换原来的 /public/tinymce/skins 目录(如果不再需要tinymce的默认主题的话),将skintool.json放到/public/tinymce/下。

然后在初始化选项中将 skincontent_css 选项都定义为新皮肤名。

tinymce.init({
  // ...
  skin: "animal",
  content_css: "animal"
});

现在我的 tinymce 主题色就变成绿色系了。

将应用部署到 HTTP 服务器

官方文档还有关于部署到生产环境的配置。鉴于本篇是独立部署,直接放到了 public 目录下,不需要做更多处理。

vite build打包完成后 public 下的文件都被原封不动地复制到 dist 目录下,那么把这些静态资源悉数传到 CDN 加速然后在生产环境使用 cdn 地址即可。具体步骤可见 ➡️ 【通过 jsDelivr + Github 对静态资源实现免费 CDN 加速】

📃.env.production

# .env.production

# cdn api
VITE_APP_CDN_API = 'https://cdn.jsdelivr.net/gh/xxxxxxxx/cdn-for-tinymce@1.0/'

📃src/components/Tinymce.vue

<template>
    <tinymce-editor
      :id="tinymceId"
      initialValue="<p>Initial editor content</p>"
      v-model="contentValue"
      :init="initOptions"
      :tinymceScriptSrc="tinymceSrc"
    />
</template>

export default defineComponent({
  setup(props, { emit }) {
    return {
      tinymceSrc: computed(() =>
        import.meta.env.VITE_APP_CDN_API
          ? import.meta.env.VITE_APP_CDN_API + 'tinymce/tinymce.min.js'
          : '/tinymce/tinymce.min.js'
      ),
    }
  },
})

顺便提下,如果要把打包出来的dist/assets下的资源(图片、js、css)也放到另外的 cdn 引入,可以在vite.config.ts配置base(公共路径前缀)来实现:

// https://cn.vitejs.dev/config/
export default ({ mode }) => ({
  // base: 'https://cdn.example.com/assets/', // CDN(总是 HTTPS 协议)
  // base: '//cdn.example.com/assets/', // CDN(协议相同)
  base: // 相当于 webpack 的 publicPath,如果 build.assetsDir 的资源需要放到 cdn,则需要改成 cdn 地址
    mode === 'production'
      ? 'https://cdn.jsdelivr.net/gh/aizawasayo/xxxxxx@1.0/'
      : '/',
})

想了解更多,我们可以 Vite 为例,参阅:【静态资源处理】【构建生产版本】

TinyMCE 的 init 配置参数补充:

base_url<String>

指定 TinyMCE 主目录的基本 URL。
默认情况下,base_url 是包含 TinyMCE javascript 文件(如 tinymce.min.js)的目录,其他要引入的文件诸如主题和插件等内容会默认在这个目录下去查找。即都是在一处的,指定了tinymce.min.js地址的情况通常不用再配置。

如果另外指定了这个选项,那么需注意其它所有本应和 TinyMCE javascript 放在同一目录的文件,如皮肤、主题、插件等,都会基于这个配置路径去查找。

api-key<String>

Tiny Cloud API key。用 Tiny Cloud 进行部署时需要提供 给TinyMCE editor。

注册 Tiny Cloud API key,请访问 Tiny Account 注册页。要查找现有 Tiny 帐户的 Tiny Cloud API key,请登录并访问 Tiny Account Dashboard

默认值: no-api-key

tinymce-script-src<String>

可使用 tinymce-script-src 属性来指定一个外部版本的 TinyMCE 以延迟加载。

skin_url <String>

默认情况下,会在引入的 tinymce.min.js 的根目录中(比如按照我们的部署就是/public/tinymce/下)查找 skins 文件夹。使用 skin_url 选项则可以自行指定皮肤目录的位置。
base_url场景类似,当从不同的基础位置(例如 cdn)加载 tinymce.min.jsskin(比如项目本地服务)时,这将非常有用。
当用到自定义主题时,通常我们还是会将皮肤相关规范地放在/public/tinymce/目录下,这种情况指定skin_url为: '/tinymce/skins/ui/animal'即可,也比较方便维护管理。

举个tinymce.min.js用 cdn 地址的🌰:
此时我们的/public/tinymce/目录下只有这些文件了,其他诸如plugins、themes、icons都是和tinymce.min.js相同的 cdn 源引入:

此时我们需要调整的就是本地皮肤和语言的路径,还有别忘了加上apiKey

<template>
    <editor
      :id="tinymceId"
      v-model="contentValue"
      :init="initOptions"
      apiKey="dkb23i61in9c5mdpxxxxxxxxxxxxxxxxxxdnt8k8hpogn2n"
      tinymceScriptSrc="https://cdn.jsdelivr.net/npm/tinymce@5.9.2/tinymce.min.js"
    />
</template>
export default defineComponent({
  name: 'Tinymce',
  setup(props, { emit }) {
    const initOptions = ref({
      language: 'zh_CN',
      language_url: '/tinymce/langs/zh_CN.js',
      skin_url: '/tinymce/skins/ui/animal',
      skin: 'animal',
      content_css: '/tinymce/skins/content/animal/content.min.css',
  },
  return {
    initOptions
  }
})
</script>

总结:反正最后静态资源也是要用 cdn 加速,还不如一整个自托管而不是部分去用官方的 cdn,不易维护、配置麻烦不说,体验也一般。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容