react使用CKEditor5,两种使用方式(在线构建器和从源代码构建)。附富文本国际化

react使用CKEditor5,两种使用方式(在线构建器和从源代码构建)。附国际化


在这里插入图片描述

介绍

介绍:功能丰富,是最好用的富文本编辑器。
特点:CKEditor5是基于Nodejs的编译器,很好地支持了React等主流web框架,它把菜单的每一项功能都进行了拆分,通过插件的方式来引入,最后配置好。
两种使用方式:

  1. CKEditor5在线构建器构建。构建好的版本使用,直接使用,无需配置;但是功能简单,不可配置
  2. 从源代码构建CKEditor5。CKEditor5 源码自行打包构建,可以配置丰富的功能,配置折腾!

ps:以下详解采用 从源代码构建CKEditor5 编辑器

一、CKEditor5在线构建器构建

说明:该部分内容即官方文档中的 快速开始。(可从文档直接查看 快速开始
优点:CKEditor5在线构建器 快速上手,直接使用,无需配置;
缺点:无需配置,不能配置工具栏。
快速上手

  1. 安装CKEditor5
pnpm install --save @ckeditor/ckeditor5-react @ckeditor/ckeditor5-build-classic
  1. 在React组件中使用CKEditor5组件
// App.jsx / App.tsx

import React, { Component } from 'react';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';

class App extends Component {
    render() {
        return (
            <div className="App">
                <h2>Using CKEditor&nbsp;5 build in React</h2>
                <CKEditor
                    editor={ ClassicEditor }
                    data="<p>Hello from CKEditor&nbsp;5!</p>"
                    onReady={ editor => {
                        // You can store the "editor" and use when it is needed.
                        console.log( 'Editor is ready to use!', editor );
                    } }
                    onChange={ ( event ) => {
                        console.log( event );
                    } }
                    onBlur={ ( event, editor ) => {
                        console.log( 'Blur.', editor );
                    } }
                    onFocus={ ( event, editor ) => {
                        console.log( 'Focus.', editor );
                    } }
                />
            </div>
        );
    }
}

export default App;

二、从源代码构建CKEditor5(使用)

说明:这里不能直接使用@ckeditor/ckeditor5-build-classic这个包了,因为这个包已经包含了很多插件,加入代码编辑器插件会与其冲突,所以只能用@ckeditor/ckeditor5-editor-classic。
优点:从源代码构建CKEditor5 可以单独配置工具栏,每项功能可以单独配置,功能丰富;
缺点-配置难受:①工具栏每项均需安装对应插件;②根据不同的项目脚手架,需要采取不同的环境配置。

1. 安装CKEditor5 依赖项

相对比较全的一份依赖名单如下:(注:如需配置其他功能再单独安装对应依赖即可)


pnpm i @ckeditor/ckeditor5-react @ckeditor/ckeditor5-editor-classic
// 安装插件,用于配置工具栏功能
@ckeditor/ckeditor5-alignment 
@ckeditor/ckeditor5-block-quote 
@ckeditor/ckeditor5-cloud-services 
@ckeditor/ckeditor5-code-block 
@ckeditor/ckeditor5-essentials 
@ckeditor/ckeditor5-export-pdf 
@ckeditor/ckeditor5-export-word 
@ckeditor/ckeditor5-find-and-replace 
@ckeditor/ckeditor5-font 
@ckeditor/ckeditor5-html-support 
@ckeditor/ckeditor5-heading 
@ckeditor/ckeditor5-highlight 
@ckeditor/ckeditor5-horizontal-line 
@ckeditor/ckeditor5-html-embed 
@ckeditor/ckeditor5-image 
@ckeditor/ckeditor5-indent 
@ckeditor/ckeditor5-link
@ckeditor/ckeditor5-list 
@ckeditor/ckeditor5-media-embed 
@ckeditor/ckeditor5-mention 
@ckeditor/ckeditor5-page-break 
@ckeditor/ckeditor5-paste-from-office 
@ckeditor/ckeditor5-remove-format 
@ckeditor/ckeditor5-show-blocks 
@ckeditor/ckeditor5-source-editing 
@ckeditor/ckeditor5-special-characters 
@ckeditor/ckeditor5-style 
@ckeditor/ckeditor5-typing
@ckeditor/ckeditor5-word-count 
@ckeditor/ckeditor5-upload

2. 环境配置(vite/webpack)

根据创建项目的方式选择其中对应的环境配置,以下介绍三种创建项目配置,本人均搭建demo使用成功

react+vite项目

使用 React 和 Vite 配置 CKEditor 5 很简单。通过导入ckeditor5并将其添加到插件列表来修改现有配置。
安装依赖:pnpm i @ckeditor/ckeditor5-theme-lark
配置vite.config.ts

// vite.config.js / vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import ckeditor5 from '@ckeditor/vite-plugin-ckeditor5';

export default defineConfig( {
  plugins: [
    react(),
    ckeditor5( { theme: require.resolve( '@ckeditor/ckeditor5-theme-lark' ) } )
  ],
} )

create-react-app项目

修改webpack配置,配置直接参考官方文档 Create React App

umi4项目(复杂)

安装依赖

pnpm i -D postcss-loader style-loader css-loader

修改.umirc.ts配置

// .umirc.ts  / config/config.ts
  chainWebpack(config) {
    const { styles } = require('@ckeditor/ckeditor5-dev-utils');
    const svgReg = /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/;
    const cssReg = /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/;
    config.module.rule('cke5-svg').test(svgReg).type(
      // raw-loader
      'asset/source',
    );
    // svg exclude
    ['svg', 'svgr'].forEach((rule) => {
      config.module.rule(rule).exclude.add(svgReg);
    });
    // css rule
    config.module
      .rule('cke5-css')
      .test(cssReg)
      .use('style-loader')
      .loader(require.resolve('style-loader'))
      .end()
      .use('css-loader')
      .loader(require.resolve('css-loader'))
      .end()
      .use('postcss-loader')
      .loader(require.resolve('postcss-loader'))
      .options({
        postcssOptions: styles.getPostCssConfig({
          themeImporter: {
            themePath: require.resolve('@ckeditor/ckeditor5-theme-lark'),
          },
          minify: true,
        }),
      });
    // css exclude
    config.module.rule('css').exclude.add(cssReg);
    // assets exclude
    config.module
      .rule('asset')
      .oneOf('fallback')
      .exclude.add(svgReg)
      .add(cssReg);
  },

3. 编辑器配置(组件)

Editor组件,编辑器配置(config:toolbar配置工具栏项,plugins使用对应toolbar项。可以增改尝试)
需另外使用其他细节的配置请查阅官方文档进行配置

import { CKEditor } from '@ckeditor/ckeditor5-react';
import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic';
// NOTE: Use the editor from source (not a build)!
// 引入配置工具栏插件
import { Alignment } from '@ckeditor/ckeditor5-alignment';
import {
  Bold,
  Code,
  Italic,
  Strikethrough,
  Subscript,
  Superscript,
  Underline,
} from '@ckeditor/ckeditor5-basic-styles';
import { BlockQuote } from '@ckeditor/ckeditor5-block-quote';
import { CloudServices } from '@ckeditor/ckeditor5-cloud-services';
import { CodeBlock } from '@ckeditor/ckeditor5-code-block';
import { Essentials } from '@ckeditor/ckeditor5-essentials';
import { ExportPdf } from '@ckeditor/ckeditor5-export-pdf';
import { ExportWord } from '@ckeditor/ckeditor5-export-word';
import { FindAndReplace } from '@ckeditor/ckeditor5-find-and-replace';
import { Font } from '@ckeditor/ckeditor5-font';
import { Heading } from '@ckeditor/ckeditor5-heading';
import { Highlight } from '@ckeditor/ckeditor5-highlight';
import { HorizontalLine } from '@ckeditor/ckeditor5-horizontal-line';
import { HtmlEmbed } from '@ckeditor/ckeditor5-html-embed';
import { GeneralHtmlSupport } from '@ckeditor/ckeditor5-html-support';
import {
  AutoImage,
  Image,
  ImageCaption,
  ImageInsert,
  ImageResize,
  ImageStyle,
  ImageToolbar,
  ImageUpload,
  PictureEditing,
} from '@ckeditor/ckeditor5-image';
import { Indent, IndentBlock } from '@ckeditor/ckeditor5-indent';
import { AutoLink, Link, LinkImage } from '@ckeditor/ckeditor5-link';
import { List, ListProperties, TodoList } from '@ckeditor/ckeditor5-list';
import { MediaEmbed } from '@ckeditor/ckeditor5-media-embed';
import { Mention } from '@ckeditor/ckeditor5-mention';
import { PageBreak } from '@ckeditor/ckeditor5-page-break';
import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
import { PasteFromOffice } from '@ckeditor/ckeditor5-paste-from-office';
import { RemoveFormat } from '@ckeditor/ckeditor5-remove-format';
import { ShowBlocks } from '@ckeditor/ckeditor5-show-blocks';
import { SourceEditing } from '@ckeditor/ckeditor5-source-editing';
import {
  SpecialCharacters,
  SpecialCharactersEssentials,
} from '@ckeditor/ckeditor5-special-characters';
import { Style } from '@ckeditor/ckeditor5-style';
import {
  Table,
  TableCaption,
  TableCellProperties,
  TableColumnResize,
  TableProperties,
  TableToolbar,
} from '@ckeditor/ckeditor5-table';
import { TextTransformation } from '@ckeditor/ckeditor5-typing';
import { SimpleUploadAdapter } from '@ckeditor/ckeditor5-upload';
import { WordCount } from '@ckeditor/ckeditor5-word-count';

// 工具栏功能项配置
const editorConfig: any = {
  plugins: [
    SimpleUploadAdapter,
    BlockQuote,
    Bold,
    Heading,
    Image,
    ImageCaption,
    ImageStyle,
    ImageToolbar,
    Indent,
    Italic,
    Link,
    List,
    MediaEmbed,
    Paragraph,
    Table,
    TableToolbar,
    Alignment,
    AutoImage,
    AutoLink,
    CloudServices,
    Code,
    CodeBlock,
    Essentials,
    ExportPdf,
    ExportWord,
    FindAndReplace,
    Font,
    Highlight,
    HorizontalLine,
    HtmlEmbed,
    ImageInsert,
    ImageResize,
    ImageUpload,
    IndentBlock,
    GeneralHtmlSupport,
    LinkImage,
    ListProperties,
    TodoList,
    Mention,
    PageBreak,
    PasteFromOffice,
    PictureEditing,
    RemoveFormat,
    ShowBlocks,
    SourceEditing,
    SpecialCharacters,
    SpecialCharactersEssentials,
    Style,
    Strikethrough,
    Subscript,
    Superscript,
    TableCaption,
    TableCellProperties,
    TableColumnResize,
    TableProperties,
    TextTransformation,
    Underline,
    WordCount,
  ],
  toolbar: {
    items: [
      'undo',
      'redo',
      'heading',
      'fontSize',
      'fontColor',
      'fontBackgroundColor',
      'bold',
      'underline',
      'italic',
      {
        label: 'Formatting',
        icon: '',
        items: [
          'strikethrough',
          'subscript',
          'superscript',
          'code',
          'horizontalLine',
          '|',
          'removeFormat',
        ],
      },
      'alignment',
      'bulletedList',
      'numberedList',
      'outdent',
      'indent',
      'link',
      'insertImage',
      'insertTable',
      // '-', // break point
      {
        label: 'Others',
        icon: '',
        items: [
          'todoList',
          'specialCharacters',
          'pageBreak',
          {
            label: 'Insert',
            icon: '',
            items: ['highlight', 'blockQuote', 'mediaEmbed', 'codeBlock', 'htmlEmbed'],
          },
          'fontFamily',
          'exportPdf',
          'exportWord',
          'showBlocks',
          'findAndReplace',
          'selectAll',
          '|',
          'sourceEditing',
        ],
      },
    ],
    // shouldNotGroupWhenFull: true,
  },
  // language: 'zh-cn', // 需引入语言包,进行设置。
  htmlSupport: {
    allow: [
      {
        name: /^.*$/,
        styles: true,
        attributes: true,
        classes: true,
      },
    ],
  },
  heading: {
    options: [
      {
        model: 'paragraph',
        title: 'Paragraph',
        class: 'ck-heading_paragraph',
      },
      {
        model: 'heading1',
        view: 'h1',
        title: 'Heading 1',
        class: 'ck-heading_heading1',
      },
      {
        model: 'heading2',
        view: 'h2',
        title: 'Heading 2',
        class: 'ck-heading_heading2',
      },
      {
        model: 'heading3',
        view: 'h3',
        title: 'Heading 3',
        class: 'ck-heading_heading3',
      },
      {
        model: 'heading4',
        view: 'h4',
        title: 'Heading 4',
        class: 'ck-heading_heading4',
      },
      {
        model: 'heading5',
        view: 'h5',
        title: 'Heading 5',
        class: 'ck-heading_heading5',
      },
      {
        model: 'heading6',
        view: 'h6',
        title: 'Heading 6',
        class: 'ck-heading_heading6',
      },
    ],
  },
  exportPdf: {
    stylesheets: ['EDITOR_STYLES'],
    fileName: 'export-pdf-demo.pdf',
    converterOptions: {
      format: 'Tabloid',
      margin_top: '20mm',
      margin_bottom: '20mm',
      margin_right: '24mm',
      margin_left: '24mm',
      page_orientation: 'portrait',
    },
  },
  exportWord: {
    stylesheets: ['EDITOR_STYLES'],
    fileName: 'export-word-demo.docx',
    converterOptions: {
      format: 'B4',
      margin_top: '20mm',
      margin_bottom: '20mm',
      margin_right: '12mm',
      margin_left: '12mm',
      page_orientation: 'portrait',
    },
  },
  // fontFamily: {
  //   options: [
  //     'default',
  //     'Blackoak Std',
  //     '宋体,SimSun',
  //     '新宋体,NSimSun',
  //     '黑体,SimHei',
  //     '微软雅黑,Microsoft YaHei',
  //     '楷体_GB2312,KaiTi_GB2312',
  //     '隶书,LiSu',
  //     '幼园,YouYuan',
  //     '华文细黑,STXihei',
  //     '细明体,MingLiU',
  //     '新细明体,PMingLiU',
  //   ],
  // },
  fontSize: {
    options: [10, 12, 14, 'default', 18, 20, 22, 28],
    supportAllValues: true,
  },
  htmlEmbed: {
    showPreviews: true,
  },
  image: {
    styles: ['alignCenter', 'alignLeft', 'alignRight'],
    resizeOptions: [
      {
        name: 'resizeImage:original',
        label: 'Original',
        value: null,
      },
      {
        name: 'resizeImage:50',
        label: '50%',
        value: '50',
      },
      {
        name: 'resizeImage:75',
        label: '75%',
        value: '75',
      },
    ],
    toolbar: [
      'imageTextAlternative',
      'toggleImageCaption',
      '|',
      'imageStyle:inline',
      'imageStyle:wrapText',
      'imageStyle:breakText',
      '|',
      'resizeImage',
      '|',
    ],
  },
  list: {
    properties: {
      styles: true,
      startIndex: true,
      reversed: true,
    },
  },
  link: {
    decorators: {
      addTargetToExternalLinks: true,
      defaultProtocol: 'https://',
      toggleDownloadable: {
        mode: 'manual',
        label: 'Downloadable',
        attributes: {
          download: 'file',
        },
      },
    },
  },
  mention: {
    feeds: [
      {
        marker: '@',
        feed: [
          '@apple',
          '@bears',
          '@brownie',
          '@cake',
          '@cake',
          '@candy',
          '@canes',
          '@chocolate',
          '@cookie',
          '@cotton',
          '@cream',
          '@cupcake',
          '@danish',
          '@donut',
          '@dragée',
          '@fruitcake',
          '@gingerbread',
          '@gummi',
          '@ice',
          '@jelly-o',
          '@liquorice',
          '@macaroon',
          '@marzipan',
          '@oat',
          '@pie',
          '@plum',
          '@pudding',
          '@sesame',
          '@snaps',
          '@soufflé',
          '@sugar',
          '@sweet',
          '@topping',
          '@wafer',
        ],
        minimumCharacters: 1,
      },
    ],
  },
  // 图片上传.简单的上传适配器 功能配置
  simpleUpload: {
    // The URL that the images are uploaded to.
    uploadUrl: 'https://your-organization-id.cke-cs.com/easyimage/upload/',
    // https://ckeditor.com/docs/ckeditor5/latest/assets/img/malta.jpg
    // Enable the XMLHttpRequest.withCredentials property.
    withCredentials: true,
    // Headers sent along with the XMLHttpRequest to the upload server.
    headers: {
      'X-CSRF-TOKEN': 'CSRF-Token',
      Authorization: 'Bearer <JSON Web Token>',
    },
  },
  table: {
    contentToolbar: [
      'tableColumn',
      'tableRow',
      'mergeTableCells',
      'tableProperties',
      'tableCellProperties',
      'toggleTableCaption',
    ],
  },
  // licenseKey: "your-license-key",
};

interface Props {
  html: string;
  handleSetHtml: (editor: any) => void;
}

export default function App({ html, handleSetHtml }: Props) {
  return (
    <div className="ckeditor">
      <CKEditor
        editor={ClassicEditor}
        config={editorConfig}
        data={html}
        // onReady={(editor) => {
        //   editor.setData(html);
        // }}
        onChange={(_event, editor) => {
          const data = editor.getData();
          handleSetHtml(data);
        }}
      />
    </div>
  );
}

4. 在React组件中使用CKEditor5组件

在React组件中使用

// 引入组件
import Editor from "xxx"
// 使用组件
<Editor html={html} handleSetHtml={handleSetHtml} />

// 编辑器内容,和设置内容
const [html, setHtml] = useState('');
const handleSetHtml = (editor: string) => {
  setHtml(editor);
};

5. 样式修改

global.less

// 富文本
.ck-content {
  min-height: 300px;
  ul,
  ol {
    margin-left: 40px;
    list-style-position: outside;
  }
}
.ck-powered-by {
  display: none;
}
.ckeditor {
  width: 100%;
}

总结&问题

参考:对 UmiJS 和 ckeditor 的折腾CKEditor5的入门使用(react)-国家化
依赖问题

  1. 推荐使用pnpm安装依赖,yarn安装可能出现问题
  2. 单独安装依赖,如出现问题,本人使用的方式是删除依赖,重新pnpm i
  3. 大部分依赖插件需要安装同一版本,否则可能出现意外的问题

附:项目中package.js使用的依赖版本

// dependencies
    "@ckeditor/ckeditor5-alignment": "^41.3.1",
    "@ckeditor/ckeditor5-basic-styles": "^41.0.1",
    "@ckeditor/ckeditor5-block-quote": "^41.3.1",
    "@ckeditor/ckeditor5-cloud-services": "^41.3.1",
    "@ckeditor/ckeditor5-code-block": "^41.3.1",
    "@ckeditor/ckeditor5-dev-translations": "^39.6.3",
    "@ckeditor/ckeditor5-dev-utils": "^38.3.1",
    "@ckeditor/ckeditor5-editor-classic": "^41.0.1",
    "@ckeditor/ckeditor5-essentials": "^41.3.1",
    "@ckeditor/ckeditor5-export-pdf": "^41.3.1",
    "@ckeditor/ckeditor5-export-word": "^41.3.1",
    "@ckeditor/ckeditor5-find-and-replace": "^41.3.1",
    "@ckeditor/ckeditor5-font": "^41.3.1",
    "@ckeditor/ckeditor5-heading": "^41.3.1",
    "@ckeditor/ckeditor5-highlight": "^41.3.1",
    "@ckeditor/ckeditor5-horizontal-line": "^41.3.1",
    "@ckeditor/ckeditor5-html-embed": "^41.3.1",
    "@ckeditor/ckeditor5-html-support": "^41.3.1",
    "@ckeditor/ckeditor5-image": "^41.3.1",
    "@ckeditor/ckeditor5-indent": "^41.3.1",
    "@ckeditor/ckeditor5-link": "^41.3.1",
    "@ckeditor/ckeditor5-list": "^41.3.1",
    "@ckeditor/ckeditor5-media-embed": "^41.3.1",
    "@ckeditor/ckeditor5-mention": "^41.3.1",
    "@ckeditor/ckeditor5-page-break": "^41.3.1",
    "@ckeditor/ckeditor5-paragraph": "^41.0.1",
    "@ckeditor/ckeditor5-paste-from-office": "^41.3.1",
    "@ckeditor/ckeditor5-react": "^6.1.0",
    "@ckeditor/ckeditor5-remove-format": "^41.3.1",
    "@ckeditor/ckeditor5-show-blocks": "^41.3.1",
    "@ckeditor/ckeditor5-source-editing": "^41.3.1",
    "@ckeditor/ckeditor5-special-characters": "^41.3.1",
    "@ckeditor/ckeditor5-style": "^41.3.1",
    "@ckeditor/ckeditor5-table": "^41.0.1",
    "@ckeditor/ckeditor5-theme-lark": "^41.0.1",
    "@ckeditor/ckeditor5-typing": "^41.3.1",
    "@ckeditor/ckeditor5-upload": "^41.3.1",
    "@ckeditor/ckeditor5-word-count": "^41.3.1",

国际化

文档:本地化
注:以下两种方式翻译,自己配置toolbar项的label的(即自己配置的项-下拉选) 需要单独手动翻译,可使用如开发admin/email项目中的i18n国际化方式($t(),这里就不作介绍)。

两种国际化方式

方式1、设置UI语言

说明:这种方式设置语言,一些toolbar项会存在漏翻译的情况。部分需要自定义翻译(根据导入的语言)
当使用预定义版本之一或在线构建器构建的编辑器,需要先导入翻译

// Import translations for the German language.
import '@ckeditor/ckeditor5-build-classic/build/translations/zh-cn';
import '@ckeditor/ckeditor5-build-classic/build/translations/zh';

在组件中配置编辑器的语言

<CKEditor
    config={ {
        // Use the zh-cn language for this editor.
        language: 'zh-cn',
        // ...
    } }
    editor={ ClassicEditor }
    data="<p>Hello from CKEditor&nbsp;5!</p>"
/>

方式2、使用特定语言构建编辑器

说明:这种方式设置语言,翻译全面,默认的toolbar项都可以翻译,不可重新更改。
注:如通过导入翻译重新更改语言则如方法一,此种方式即没意义了。
安装依赖

pnpm install --save @ckeditor/ckeditor5-dev-translations

添加到 webpack 配置中

// webpack.config.js
const { CKEditorTranslationsPlugin } = require( '@ckeditor/ckeditor5-dev-translations' );

module.exports = {
    // ...
    plugins: [
        // ....
        new CKEditorTranslationsPlugin( {
            // The UI language. Language codes follow the https://en.wikipedia.org/wiki/ISO_639-1 format.
            language: 'zh-cn',
            addMainLanguageTranslationsToAllAssets: true
        } ),
        // ....
    ],
    // ...
};

项目中使用

包括 **部分需要自定义翻译 **如下

编辑器配置(组件)

编辑器组件中添加以下内容即可设置ckeditor5语言

// ...
// 引入语言包,获取项目设置的语言
import '@/locales/editor/zh';
import '@/locales/editor/zh-cn';
import { useAppSelector } from '@/hooks/useAppHooks';
import { selectLanguage } from '@/store/reducer/langSlice';

const langConfig = {
  en: 'en',
  hk: 'zh',
  cn: 'zh-cn',
};

// 获取设置的语言
  const lang = useAppSelector(selectLanguage);
// map语言并设置
  editorConfig.language = langConfig[lang as keyof typeof langConfig];

抽离语言包

locales/editor/zh-cn.ts 中文简体

import '@ckeditor/ckeditor5-build-classic/build/translations/zh-cn';

Object.assign(window.CKEDITOR_TRANSLATIONS['zh-cn'].dictionary, {
  'Font Size': '字号',
  'Font Color': '文字颜色',
  'Font Background Color': '背景色',
  'Horizontal line': '分割线',
  'Remove Format': '清除格式',
  'Text alignment': '文本对齐',
  'Align left': '左对齐',
  'Align right': '右对齐',
  'Align center': '居中对齐',
  Justify: '两端对齐',
});

locales/editor/zh.ts 中文繁体

import '@ckeditor/ckeditor5-build-classic/build/translations/zh';

Object.assign(window.CKEDITOR_TRANSLATIONS.zh.dictionary, {
  'Font Size': '字號',
  'Font Color': '文字顔色',
  'Font Background Color': '背景色',
  'Horizontal line': '分割綫',
  'Remove Format': '清除格式',
  'Text alignment': '文本對齊',
  'Align left': '左對齊',
  'Align right': '左對齊',
  'Align center': '居中對齊',
  Justify: '兩端對齊',
});
  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值