1. 程式人生 > >CKeditor 編輯器使用記錄 (CKeditor 4 and 5 )

CKeditor 編輯器使用記錄 (CKeditor 4 and 5 )

文章目錄

CKeditor 編輯器

CKeditor 是一款可定製的適合開發人員使用的富文字編輯器

編輯器官網

CKeditor 4 使用

官網

自定義構建

構建頁面

常用外掛

Code tag
Easy Image
Find / Replace
Font Size and Family
Letter-spacing
Line Height
Page Break
Paragraph Indentation
Statistics of the number of characters
Stored automatically in the browser storage
Text Transform

CKeditor 5 使用

CDN 使用

div id="editor">
	<h2>Sample</h2>

	<p>This is an instance of the <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#classic-editor">classic editor build</a>.</p>

	<figure class="image"><img src="../tests/manual/sample.jpg" alt="Autumn fields" /></figure>

	<p>You can use this sample to validate whether your <a href="https://ckeditor.com/docs/ckeditor5/latest/builds/guides/development/custom-builds.html">custom build</a> works fine.</p>
</div>

<script src="../build/ckeditor.js"></script>
<script>
	ClassicEditor.create(document.querySelector("#editor"), {
		// config
	})
		.then(editor => {
			window.editor = editor;
		})
		.catch(err => {
			console.error(err.stack);
		});

	
</script>

npm 使用

官方文件

安裝

npm install --save @ckeditor/ckeditor5-vue @ckeditor/ckeditor5-build-classic

main.js引入

import Vue from 'vue';
import CKEditor from '@ckeditor/ckeditor5-vue';

Vue.use(CKEditor);

單檔案元件使用

<template>
    <div id="app">
        <ckeditor :editor="editor" v-model="editorData" :config="editorConfig"></ckeditor>
    </div>
</template>

<script>
    import ClassicEditor from '@ckeditor/ckeditor5-build-classic';

    export default {
        name: 'app',
        data() {
            return {
                editor: ClassicEditor,
                editorData: '<p>Content of the editor.</p>',
                editorConfig: {
                    // The configuration of the editor.
                }
            };
        }
    }
</script>

自定義圖片上傳

自定義圖片上傳外掛必需架構

// 自定義檔案上傳
	class MyUploadAdapter {
		constructor(loader, url) {
			// 上傳期間要使用的 FileLoader 例項。 
			this.loader = loader;

			// 伺服器後端的上傳URL。 這 是XMLHttpRequest 將影象資料傳送到的地址。
			this.url = url;
		}

		// 上傳
		upload() {
			return new Promise((resolve, reject) => {
			    // 自定義上傳邏輯。。。 假設 response 為響應資料
			
			    // 上傳失敗時必須呼叫 reject() 函式
				if (!response || response.error) {
				   return reject(errorMessage);
				}
				
				// 如果上傳成功,請呼叫 resolve() 並傳入至少包含 {default: URL} 的物件,該 URL 為上傳影象在伺服器上的地址。
				resolve({
				   default: response.url // // 此 URL 將用於在內容中顯示影象。 
				});
			});
		}

		// 中止上傳
		abort() {
			if (this.xhr) {
				this.xhr.abort();
			}
		}
	}
	

自定義圖片上傳外掛

    // 自定義檔案上傳
	class MyUploadAdapter {
		constructor(loader, url) {
			// 上傳期間要使用的 FileLoader 例項。 
			this.loader = loader;

			// 伺服器後端的上傳URL。 這 是XMLHttpRequest 將影象資料傳送到的地址。
			this.url = url;
		}

		// 開始上傳
		upload() {
			return new Promise((resolve, reject) => {
				this._initRequest();
				this._initListeners(resolve, reject);
				this._sendRequest();
			});
		}

		// 中止上傳
		abort() {
			if (this.xhr) {
				this.xhr.abort();
			}
		}

		// 使用傳遞給建構函式的 URL 初始化 XMLHttpRequest 物件。
		_initRequest() {
			const xhr = (this.xhr = new XMLHttpRequest());

			// 請注意,您的請求可能會有所不同。 由您和您的編輯器整合來選擇正確的通訊渠道。 此示例使用帶有JSON的POST請求作為資料結構,但您的配置可能不同。
			xhr.open("POST", this.url, true);
			xhr.responseType = "json";
		}

		// 初始化 XMLHttpRequest 偵聽器。
		_initListeners(resolve, reject) {
			const xhr = this.xhr;
			const loader = this.loader;
			const genericErrorText = "Couldn't upload file:" + ` ${loader.file.name}.`;

			xhr.addEventListener("error", () => reject(genericErrorText));
			xhr.addEventListener("abort", () => reject());
			xhr.addEventListener("load", () => {
				const response = xhr.response;

			    // 上傳失敗時必須呼叫 reject() 函式。
				if (!response || response.error) {
				   return reject(response && response.error ? response.error.message : genericErrorText);
				}

				// 如果上傳成功,請使用至少包含 {default: URL} 的物件,該 URL 為上傳影象在伺服器上的地址。
				resolve({
				   default: response.url // // 此URL將用於在內容中顯示影象。 
				});
			
			});

			// 支援上傳進度。 FileLoader 具有 uploadTotal 和 uploaded 屬性,如果使用它們,在編輯器的介面中將會顯示上傳的進度條。
			if (xhr.upload) {
				xhr.upload.addEventListener("progress", evt => {
					if (evt.lengthComputable) {
						loader.uploadTotal = evt.total;
						loader.uploaded = evt.loaded;
					}
				});
			}
		}
		// 準備資料併發送請求。
		_sendRequest() {
			// 準備表單資料。
			const data = new FormData();
			data.append("upload", this.loader.file);

			// 傳送請求。
			this.xhr.send(data);
		}
	}

對接外掛,以便在初始化編輯器時使用

	// 對接外掛
	function MyCustomUploadAdapterPlugin(editor) {
		editor.plugins.get("FileRepository").createUploadAdapter = loader => {
			// Configure the URL to the upload script in your back-end here!
			return new MyUploadAdapter(loader, "https://img.sqydt.darongshutech.com/");
		};
	}

初始化編輯器,並載入自定義圖片上傳功能

	// 建立編輯器例項,並載入自定義圖片上傳功能
	ClassicEditor.create(document.querySelector("#editor"), {
		extraPlugins: [MyCustomUploadAdapterPlugin]
	})
	.then(editor => {
		window.editor = editor;
	})
	.catch(err => {
		console.error(err.stack);
	});

自定義圖片上傳(七牛雲)

import axios from "axios";
window.axios = axios;
function MyCustomUploadAdapterPlugin(editor) {
  editor.plugins.get("FileRepository").createUploadAdapter = loader => {
    // Configure the URL to the upload script in your back-end here!
    return new MyUploadAdapter(loader, 10, "api/public/getUploadToken");
  };
}
// 自定義七妞雲上傳
class MyUploadAdapter {
  constructor(loader, maxSize, getToken) {
    // 上傳期間要使用的 FileLoader 例項。
    this.loader = loader;

    // 圖片大小限制
    this.maxSize = maxSize;

    // 獲取上傳七牛雲token,介面地址
    this.upTokenUrl = getToken;

    // 允許上傳的圖片格式
    this.acceptString = ".jpg,.jpeg,.png,.gif,.JPG,.JPEG,.PNG,.GIF";

    // 七牛雲上傳地址
    this.qiniuUrl = "http://up.qiniu.com";

    // 檔案存放地址
    this.baseUrl = "https://img.rmsq.com/";
  }

  // 上傳
  upload() {
    return new Promise((resolve, reject) => {
      this._uploadFile(resolve, reject);
    });
  }

  // 中止上傳
  abort() {
    if (this.xhr) {
      this.xhr.abort();
    }
  }
  
  _uploadFile(resolveCK, rejectCK) {
    const file = this.loader.file;
    const isLt = file.size / 1024 / 1024 < this.maxSize;

    // const fileExt = _file.type.split("/")[1]; // .mp3 在火狐和ie 讀取的格式為 mpeg
    const lastIndex = file.name.lastIndexOf(".");
    const fileExt = file.name.substring(+lastIndex + 1); // 上傳的檔案的格式
    const allAcceptArr = this.acceptString.split(","); // 允許上傳的格式

    if (!this._isInArr("." + fileExt, allAcceptArr)) {
      console.error("請上傳格式為" + this.acceptString + "的檔案!");
      return false;
    }

    if (this.maxSize && !isLt) {
      console.error("請上傳小於" + this.maxSize + "MB的檔案!");
      return false;
    }

    // 使用FormData物件上傳檔案
    var formData = new FormData();
    formData.append("file", file);

    // 獲取七牛雲TOKEN
    this._getQiniuToken(fileExt).then(resData => {
      formData.append("token", resData.data.token);
      formData.append("key", resData.data.key);

      // 上傳檔案至七牛雲
      this._requestQiniu(formData)
        .then(response => {
          // 如果上傳成功,請呼叫 resolve() 並傳入至少包含 {default: URL} 的物件,該 URL 為上傳影象在伺服器上的地址。
          resolveCK({
            default: this.baseUrl + response.key // // 此 URL 將用於在內容中顯示影象。
          });
        })
        .catch(err => {
          // 上傳失敗
          rejectCK(err);
        });
    });
  }

  _isInArr(_val, _arr) {
    return _arr.some(item => {
      if (item === _val) {
        return true;
      }
    });
  }

  _getQiniuToken(fileExt) {
    return new Promise((resolve, reject) => {
      axios.get(this.upTokenUrl, { params: { file_ext: fileExt } }).then(res => {
        if (res && res.data && res.data.code === 2000) {
          resolve(res.data);
        } else {
          console.error(res.data.msg);
        }
      });
    });
  }

  _requestQiniu(formData) {
    const config = {
      headers: { "Content-Type": "multipart/form-data" },
      onUploadProgress: progressEvent => {
        // 上傳進度
        this.loader.uploadTotal = progressEvent.total;
        this.loader.uploaded = progressEvent.loaded;
      }
    };
    return new Promise((resolve, reject) => {
      axios.post(this.qiniuUrl, formData, config).then(res => {
        if (res && res.status == 200) {
          resolve(res.data);
        } else {
          console.error(res.data.msg);
        }
      });
    });
  }
}

使用

    initEditor() {
      ClassicEditor.create(document.querySelector("#sqbCkEditor"), {
        // The configuration of the editor.
        language: "zh-cn",
        toolbar: ["undo", "redo", "|", "heading", "|", "fontSize", "bold", "italic", "link", "bulletedList", "numberedList", "blockQuote", "imageUpload", "imageStyle:full", "imageStyle:side"],
        extraPlugins: [MyCustomUploadAdapterPlugin] // 使用自定義上傳圖片外掛
        // ckfinder: {
        //   uploadUrl: "http://up.qiniu.com"
        // }
      })
        .then(editor => {
          editor.setData("<p>預設內容</p>");
          this.ckEditor = editor;
        })
        .catch(msg => {
          console.log("msg", msg);
        });
    },

自定義構建

文件描述

基本操作

漢化

  import "@ckeditor/ckeditor5-build-classic/build/translations/zh-cn.js";

  ClassicEditor.create(document.querySelector("#sqbCkEditor"), {
    // The configuration of the editor.
    language: "zh-cn",
    // ckfinder: {
    //   uploadUrl: "http://up.qiniu.com"
    // }
  })
    .then(editor => {
      console.log("asd", editor);
    })
    .catch(msg => {
      console.log("msg", msg);
    });

設定內容

this.ckEditor.setData("<p>內容</p>");

獲取編輯器的內容,HTML

const editorContent = this.ckEditor.getData();

獲取編輯器的內容,純文字

this.ckEditor.getData().replace(/<[^>]*>/gi, "");

獲取編輯器的內容,過濾圖片

this.ckEditor.getData().replace(/<img.*?(?:>|\/>)/gi, "");

獲取編輯器的內容,過濾上傳的圖片

this.ckEditor.getData().replace(/<figure[^>]*.*?>.*?<\/figure>/gi, "");

獲取可用工具欄配置

Array.from(this.formControl.ckEditor.ui.componentFactory.names())

還原編輯器樣式

  1. 獲取編輯器內的DOM(未經轉換),在載入了編輯器 css 的專案中可還原
const _editorDom = this.ckEditor.editing.view.domRoots.get("main").outerHTML;
  1. 通過編輯器還原

與 CKEditor 4 不同,CKEditor 5 實現了自定義資料模型。這意味著載入到編輯器中的每個內容都需要轉換為該模型,然後再渲染回檢視。
每種內容都必須由某些功能處理。該功能定義了HTML(檢視)和編輯器模型之間的雙向轉換。

    <div id="showContent"></div>

    ClassicEditor.create(document.querySelector('#showContent')).then(editor => {
      editor.isReadOnly = true; // 將編輯器設為只讀
      editor.setData(editorContent) // 顯示內容 editorContent
    }).catch(msg => {
      console.error(msg);
    });