Skip to content

Commit

Permalink
feat: add maxLength and minLength validators
Browse files Browse the repository at this point in the history
  • Loading branch information
sibiraj-s committed Jan 1, 2021
1 parent f555608 commit d59051c
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 66 deletions.
22 changes: 2 additions & 20 deletions src/lib/editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

import { EditorState, Plugin, PluginKey, Transaction } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view';
import { Node as ProsemirrorNode } from 'prosemirror-model';

import { NgxEditorService, NgxEditorServiceConfig } from './editor.service';
import { SharedService } from './services/shared/shared.service';
import { Toolbar } from './types';
import { editable as editablePlugin, placeholder as placeholderPlugin } from 'ngx-editor/plugins';
import { toDoc, toHTML } from './html';
import { parseValue, toHTML } from './parsers';

@Component({
selector: 'ngx-editor',
Expand Down Expand Up @@ -77,29 +76,12 @@ export class NgxEditorComponent implements ControlValueAccessor, OnInit, OnDestr
this.onTouched = fn;
}

private parse(value: Record<string, any> | string): ProsemirrorNode {
if (!value) {
return null;
}

let contentJson = null;

if (typeof value === 'string') {
contentJson = toDoc(value, this.config.schema);
} else {
contentJson = value;
}

const { schema } = this.config;
return schema.nodeFromJSON(contentJson);
}

private updateContent(value: Record<string, any> | string): void {
try {
const { state } = this.view;
const { tr, doc } = state;

const newDoc = this.parse(value);
const newDoc = parseValue(value, this.config.schema);
tr.replaceWith(0, state.doc.content.size, newDoc)
.setMeta('PREVENT_ONCHANGE', true);

Expand Down
24 changes: 0 additions & 24 deletions src/lib/html.ts

This file was deleted.

38 changes: 38 additions & 0 deletions src/lib/parsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { DOMSerializer, Schema, DOMParser, Node as ProsemirrorNode } from 'prosemirror-model';

import defaultSchema from './schema';

// https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
export const toHTML = (json: Record<string, any>, inputSchema?: Schema): string => {

const schema = inputSchema ?? defaultSchema;

const contentNode = schema.nodeFromJSON(json);
const html = DOMSerializer.fromSchema(schema).serializeFragment(contentNode.content);

const div = document.createElement('div');
div.appendChild(html);
return div.innerHTML;
};

export const toDoc = (html: string, inputSchema?: Schema): Record<string, any> => {
const schema = inputSchema ?? defaultSchema;

const el = document.createElement('div');
el.innerHTML = html;

return DOMParser.fromSchema(schema).parse(el).toJSON();
};

export const parseValue = (value: string | Record<string, any> | null, schema: Schema): ProsemirrorNode => {
if (!value) {
return null;
}

if (typeof value !== 'string') {
return schema.nodeFromJSON(value);
}

const docJson = toDoc(value, schema);
return schema.nodeFromJSON(docJson);
};
88 changes: 67 additions & 21 deletions src/lib/validators.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,88 @@
import { AbstractControl, ValidatorFn } from '@angular/forms';
import { Schema } from 'prosemirror-model';
import { Schema} from 'prosemirror-model';

import { parseValue } from './parsers';
import defaultSchema from './schema';

type ValidationErrors = Record<string, any>;

function isEmptyInputValue(value: any): boolean {
// we don't check for string here so it also works with arrays
return value == null || value.length === 0;
}

function hasValidLength(value: any): boolean {
// non-strict comparison is intentional, to check for both `null` and `undefined` values
return value != null && typeof value.length === 'number';
}

import { toDoc } from './html';
import schema from './schema';

// @dynamic
export class Validators {

static required(customSchema?: Schema): ValidatorFn {
return (c: AbstractControl) => {
static required(userSchema?: Schema): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {

const schema = userSchema || defaultSchema;
const doc = parseValue(control.value, schema);

const userSchema = customSchema || schema;
const isEmpty = doc.childCount === 1
&& doc?.firstChild?.isTextblock
&& doc.firstChild.content.size === 0;

const value = c.value;
if (!value) {
if (!isEmpty) {
return null;
}

let doc = null;
if (typeof value === 'string') {
doc = toDoc(value, userSchema);
} else {
doc = value;
return {
required: true
};
};
}

static maxLength(maxLength: number, userSchema?: Schema): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const schema = userSchema || defaultSchema;
const doc = parseValue(control.value, schema);

const value = doc.textContent;

if (hasValidLength(value) && value.length > maxLength) {
return {
maxlength: {
requiredLength: maxLength,
actualLength: value.length
}
};
}

const node = userSchema.nodeFromJSON(doc);
return null;
};
}

const isEmpty = node.childCount === 1
&& node?.firstChild?.isTextblock
&& node.firstChild.content.size === 0;
static minLength(minLength: number, userSchema?: Schema): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {

if (!isEmpty) {
const schema = userSchema || defaultSchema;
const doc = parseValue(control.value, schema);

const value = doc.textContent;

if (isEmptyInputValue(value) || !hasValidLength(value)) {
// don't validate empty values to allow optional controls
// don't validate values without `length` property
return null;
}

return {
required: true
};
if (value.length < minLength) {
return {
minlength: {
requiredLength: minLength, actualLength: value.length
}
};
}

return null;
};
}
}
2 changes: 1 addition & 1 deletion src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export * from './lib/schema';
export * from './lib/validators';

export * from './lib/types';
export * from './lib/html';
export * from './lib/parsers';

0 comments on commit d59051c

Please sign in to comment.