【原创】CKEditor5 富文本插件 - 首行缩进

Freeder
阅读 1,129

前言:CKEditor是一个经典的富文本编辑器了,08年小编初入行时就已用过了,对它还存在一些敬畏之心。奈何一代新人胜旧人,互联网的快速发展,出现了很多的富文本编辑器,比如百度的UEditor,后来者居上,很快成为了主流的编辑器,但其不思进取,很多年没有更新了。后来又试了几个不错的编辑器,如Quill、Froala、Simditor、Summernote、TinyMCE等,都是很不错的编辑器,各有优缺。无意间,又发现了CKEditor5,它是完全重新设计的编辑器,和4及以下版本已经是不同的技术路线了,做得也很美观,就试了下,很是喜欢。

CKEditor编辑器是西方国家开发,照顾的是拉丁文本,对中文的排版还未完全支持,比如中文排版中常用的首行缩进功能。本文是小编使用过程中开发的一个首行缩进插件,仅供大家参考。

一、首行缩进说明(中文排版为何要首行缩进

二、插件原码

1、textindent.js


/**
 * @module text-indent/textindent
 */
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';

import TextIndentEditing from './textindentediting';
import TextIndentUI from './textindentui';

export default class TextIndent extends Plugin {
	/**
	 * @inheritDoc
	 */
	static get requires() {
		return [TextIndentEditing, TextIndentUI];
	}

	/**
	 * @inheritDoc
	 */
	static get pluginName() {
		return 'TextIndent';
	}
}

2、textindentcommand.js

/**
 * @module text-indent/textindentcommand
 */
import Command from '@ckeditor/ckeditor5-core/src/command';
import first from '@ckeditor/ckeditor5-utils/src/first';

const TEXTINDENT = 'textindent';

/**
 * The textindent command plugin.
 *
 * @extends module:core/command~Command
 */
export default class TextIndentCommand extends Command {
	/**
	 * @inheritDoc
	 */
	refresh() {
		const firstBlock = first(this.editor.model.document.selection.getSelectedBlocks());
		this.isEnabled = !!firstBlock && this._canBeAligned(firstBlock);

		// 设置按钮状态
		if (this.isEnabled && firstBlock.hasAttribute(TEXTINDENT)) {
			this.value = firstBlock.getAttribute(TEXTINDENT);
		} else {
			this.value = null;
		}
	}

	/**
	 * @inheritDoc
	 */
	execute() {
		//execute(options = {}) {
		const editor = this.editor;
		const model = editor.model;
		const doc = model.document;

		model.change(writer => {
			const blocks = Array.from(doc.selection.getSelectedBlocks()).filter(block => this._canBeAligned(block));
			const currentTextIndent = blocks[0].getAttribute(TEXTINDENT);
			const removeTextIndent = currentTextIndent === TEXTINDENT || !TEXTINDENT;

			if (removeTextIndent) {
				removeTextIndentFromSelection(blocks, writer);
			} else {
				setTextIndentOnSelection(blocks, writer, TEXTINDENT);
			}
		});
	}

	_canBeAligned(block) {
		return this.editor.model.schema.checkAttribute(block, TEXTINDENT);
	}
}

// Removes the textindent attribute from blocks.
// @private
function removeTextIndentFromSelection(blocks, writer) {
	for (const block of blocks) {
		writer.removeAttribute(TEXTINDENT, block);
	}
}

// Sets the textindent attribute on blocks.
// @private
function setTextIndentOnSelection(blocks, writer, textindent) {
	for (const block of blocks) {
		writer.setAttribute(TEXTINDENT, textindent, block);
	}
}

3、textindentediting.js

/**
 * @module text-indent/textindentediting
 */

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import TextIndentCommand from './textindentcommand';


export default class TextIndentEditing extends Plugin {
	/**
	 * @inheritDoc
	 */
	static get pluginName() {
		return 'TextIndentEditing';
	}

	/**
	 * @inheritDoc
	 */
	constructor(editor) {
		super(editor);

		editor.config.define('textIndentValue', '2em');
	}

	/**
	 * @inheritDoc
	 */
	init() {
		const editor = this.editor;
		const schema = editor.model.schema;

		const indentValue = editor.config.get('textIndentValue');

		// Allow textindent attribute on all blocks.
		schema.extend('$block', { allowAttributes: 'textindent' });
		editor.model.schema.setAttributeProperties('textindent', { isFormatting: true });

		const definition = {
			model: {
				key: 'textindent',
				values: ['textindent']
			},
			view: {
				textindent: {
					key: 'style',
					value: {
						'text-indent': indentValue
						// , width: '50%'
						// , margin: '5px'
					}
				}
			}
		};

		editor.conversion.attributeToAttribute(definition);

		editor.commands.add('textindent', new TextIndentCommand(editor));
	}
}

4、textindentui.js

/**
 * @module text-indent/textindentui
 */

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
import alignRightIcon from './icons/textindent.svg';

export default class TextIndentUI extends Plugin {

	/**
	 * @inheritDoc
	 */
	static get pluginName() {
		return 'TextIndentUI';
	}

	/**
	 * @inheritDoc
	 */
	init() {
		const editor = this.editor;

		editor.ui.componentFactory.add(`textindent`, locale => {
			const command = editor.commands.get('textindent');
			const buttonView = new ButtonView(locale);

			buttonView.set({
				label: '首行缩进',
				icon: alignRightIcon,
				tooltip: true
				, isToggleable: true
			});

			buttonView.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled');

			// Execute command.
			this.listenTo(buttonView, 'execute', () => {
				editor.execute('textindent');
				editor.editing.view.focus();
			});

			return buttonView;
		});
	}

}

三、用法及效果

默认配置是首行缩进2em,也可在外部配置

ClassicEditor.create(document.querySelector('.editor'),
	{
		textIndentValue: '40px'
	});

选中需要缩行的段落

图 首行缩进按钮
图 首行缩进效果
回到顶部