diff --git a/zero.Web.UI/App/components/forms/rte.vue b/zero.Web.UI/App/components/forms/rte.vue index 02c533b6..e24039e7 100644 --- a/zero.Web.UI/App/components/forms/rte.vue +++ b/zero.Web.UI/App/components/forms/rte.vue @@ -41,6 +41,7 @@ import { debounce as _debounce } from 'underscore'; import { Editor, EditorContent, EditorMenuBubble } from 'tiptap'; import EditorMenuBar from './rte.menubar.js'; + import { MaxSize } from 'zero/config/rte.extensions.js'; import { Placeholder } from 'tiptap-extensions'; import createConfig from 'zero/config/rte.config.js'; @@ -58,6 +59,10 @@ type: Boolean, default: false }, + maxLength: { + type: Number, + default: null + }, placeholder: { type: String, default: null @@ -139,6 +144,11 @@ })); } + if (this.maxLength > 0) + { + this.extensions.push(new MaxSize({ maxSize: this.maxLength })); + } + this.editor = new Editor({ editable: !this.disabled, extensions: this.extensions, diff --git a/zero.Web.UI/App/config/rte.extensions.js b/zero.Web.UI/App/config/rte.extensions.js index d99fb1ac..9da11220 100644 --- a/zero.Web.UI/App/config/rte.extensions.js +++ b/zero.Web.UI/App/config/rte.extensions.js @@ -1,5 +1,5 @@  -import { Node } from 'tiptap'; +import { Node, Extension, Plugin } from 'tiptap'; import { chainCommands, exitCode } from 'prosemirror-commands'; export class HardBreak extends Node @@ -33,4 +33,41 @@ export class HardBreak extends Node 'Enter': command } } +} + + +export class MaxSize extends Extension +{ + get name() + { + return 'maxSize' + } + + get defaultOptions() + { + return { + maxSize: null + } + } + + get plugins() + { + return [ + new Plugin({ + appendTransaction: (transactions, oldState, newState) => + { + const max = this.options.maxSize; + const oldLength = oldState.doc.content.size; + const newLength = newState.doc.content.size; + + if (max && newLength > max && newLength > oldLength) + { + let newTr = newState.tr; + newTr.insertText('', max + 1, newLength); + return newTr; + } + } + }) + ] + } } \ No newline at end of file diff --git a/zero.Web.UI/App/editor/fields/rte.vue b/zero.Web.UI/App/editor/fields/rte.vue index 1c5dfa4f..78bb7f01 100644 --- a/zero.Web.UI/App/editor/fields/rte.vue +++ b/zero.Web.UI/App/editor/fields/rte.vue @@ -1,5 +1,5 @@  @@ -14,6 +14,18 @@ type: Boolean, default: false }, + maxLength: { + type: Number, + default: null + }, + placeholder: { + type: String, + default: null + }, + setup: { + type: Function, + default: () => { } + }, config: Object } } diff --git a/zero.Web.UI/app/core/editor-field.ts b/zero.Web.UI/app/core/editor-field.ts index 5d00e466..337bbab6 100644 --- a/zero.Web.UI/app/core/editor-field.ts +++ b/zero.Web.UI/app/core/editor-field.ts @@ -187,11 +187,15 @@ class EditorField /** * Render a rich-text editor field + * @param {object} [options] - Custom options + * @param {number} [options.maxLength=null] - Maximum characters + * @param {string} [options.placeholder=null] - Placeholder text (can be a translation) or function + * @param {function} [options.setup=value] - Called on RTE setup * @returns {EditorField} */ - rte() + rte(options) { - return this._setComponent(() => import('../editor/fields/rte.vue')); + return this._setComponent(() => import('../editor/fields/rte.vue'), { ...options }); }