import { Plugin, Widget, toWidget, viewToModelPositionOutsideModelElement, } from "ckeditor5"; import FieldCommand from "./fieldcommand"; // Helper functions to avoid extending String prototype const rtrim = (str, s) => { if (s === undefined) s = "\\s"; return str.replace(new RegExp("[" + s + "]*$"), ""); }; const ltrim = (str, s) => { if (s === undefined) s = "\\s"; return str.replace(new RegExp("^[" + s + "]*"), ""); }; export class FieldConfig { fields = []; } export default class FieldEditing extends Plugin { static get requires() { return [Widget]; } init() { this._defineSchema(); this._defineConverters(); this.editor.commands.add("field", new FieldCommand(this.editor)); this.editor.editing.mapper.on( "viewToModelPosition", viewToModelPositionOutsideModelElement(this.editor.model, (viewElement) => viewElement.hasClass("field"), ), ); this.editor.config.define("fieldConfig", FieldConfig); } _defineSchema() { const schema = this.editor.model.schema; schema.register("field", { // Behaves like a self-contained inline object (e.g. an inline image) // allowed in places where $text is allowed (e.g. in paragraphs). // The inline widget can have the same attributes as text (for example linkHref, bold). inheritAllFrom: "$inlineObject", // The field can have many types, like date, name, surname, etc: allowAttributes: ["field"], }); } _defineConverters() { const conversion = this.editor.conversion; conversion.for("upcast").elementToElement({ view: { name: "span", classes: ["field"], }, model: (viewElement, conversionApi) => { // Extract the "name" from "{name}". if (viewElement === undefined) { return null; } const { writer } = conversionApi; const name = viewElement.getChild(0)?._textData; const fieldtype = viewElement.getAttribute("fieldtype"); const guid = viewElement.getAttribute("guid"); const fieldid = viewElement.getAttribute("fieldid"); const field = { name, type: fieldtype, guid, fieldid, }; return writer.createElement("field", { field }); }, }); conversion.for("editingDowncast").elementToElement({ model: "field", view: (modelItem, { writer: viewWriter }) => { const widgetElement = createfieldView(modelItem, viewWriter); // Enable widget handling on a field element inside the editing view. return toWidget(widgetElement, viewWriter); }, }); conversion.for("dataDowncast").elementToElement({ model: "field", view: (modelItem, { writer: viewWriter }) => createfieldView(modelItem, viewWriter), }); // Helper method for both downcast converters. // function createfieldView( modelItem : Element, viewWriter : DowncastWriter ) { function createfieldView(modelItem, viewWriter) { const field = modelItem.getAttribute("field"); //todo any is a cop out. const fieldView = viewWriter.createContainerElement("span", { class: "field", fieldType: field.type, guid: field.guid, fieldid: field.guid !== undefined ? null : field.id, }); // Insert the field name (as a text). const innerText = viewWriter.createText( "{" + rtrim(ltrim(field.name, "{"), "}") + "}", ); viewWriter.insert(viewWriter.createPositionAt(fieldView, 0), innerText); return fieldView; } } }