Skip to content

Instantly share code, notes, and snippets.

@alexeckermann
Last active April 27, 2020 05:49
Show Gist options
  • Save alexeckermann/4a97ffe90f933a10e7cb7fa3b8cfa241 to your computer and use it in GitHub Desktop.
Save alexeckermann/4a97ffe90f933a10e7cb7fa3b8cfa241 to your computer and use it in GitHub Desktop.
Experimental Typesetting Plugin for CKEditor 5

Experimental Typesetting in CK5

Context

Some of our content comes from a time before we had a stringent editor with a strict data normalisation model. So in some cases we have to deal with legacy content normalisation. In addition, there are some tools that will import content into the application that have yet to have the same strict data normalisation.

Problems

This experiment focusses on spacing with two distinct problems.

  1. Trailing non-breaking space characters in block elements. Whilst it sounds harmless it has caused printed documents (created by our application outside of CK) to unnecesarily overflow a line with a clear space causing a blank line.

  2. Incorrect usage of non-breaking spaces (NBSP, \u00A0) in content. An NBSP that is not used inconjunction with a space character (\u0020) should be changed from a \u00A0 to a normal \u0020 character.

Examples

Note: Examples given in entity encoded HTML.

<p>This is a trailing space. &nbsp;</p>

<p>This&nbsp;is&nbsp;an&nbsp;annoying&nbsp;use&nbsp;of&nbsp;NBSP. But this &nbsp;was an ok usage.</p>

import Range from '@ckeditor/ckeditor5-engine/src/model/range';
import Node from '@ckeditor/ckeditor5-engine/src/view/node';
export function textUpcastHelper( dispatcher ) : void {
dispatcher.on( 'text', ( evt, data, conversionApi ) => {
const { viewItem } = data;
let text = removeTrailingSpacing( viewItem.data, viewItem );
text = replaceUnnecesaryNonBreakingSpaces( text );
if ( text != viewItem.data ) {
if ( conversionApi.consumable.consume( viewItem ) ) {
const cleanedText = conversionApi.writer.createText( text );
conversionApi.writer.insert( cleanedText, data.modelCursor );
data.modelRange = Range._createFromPositionAndShift( data.modelCursor, cleanedText.offsetSize );
data.modelCursor = data.modelRange.end;
data.viewItem = cleanedText;
}
}
}, { priority: 'high' } );
};
export function removeTrailingSpacing( text : string, node : Node ) : string {
if ( text.endsWith( '\u0020' ) || text.endsWith( '\u00A0' ) ) {
if ( !node.nextSibling && node.parent.parent == node.root ) {
text = text.trimRight();
}
}
return text;
};
const unnecesaryNBSPExpression = /(\S)(\u00A0)(\S)/g;
export function replaceUnnecesaryNonBreakingSpaces( text : string ) : string {
while ( text.includes( '\u00A0' ) && unnecesaryNBSPExpression.test( text ) ) {
text = text.replace( unnecesaryNBSPExpression, '$1\u0020$3' );
}
return text;
}
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
import { textUpcastHelper } from './typesetting/text_upcast_helper';
export default class Typesetting extends Plugin {
public editor : Editor
static get pluginName() {
return 'Typesetting';
}
async init() : Promise<void> {
this.editor.conversion.for( 'upcast' ).add( textUpcastHelper );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment