Skip to content

Commit

Permalink
HtmlEditor: Implement converter option
Browse files Browse the repository at this point in the history
  • Loading branch information
marker-dao authored Oct 18, 2024
1 parent 3a0826e commit e56a9d3
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 7 deletions.
27 changes: 22 additions & 5 deletions packages/devextreme/js/__internal/ui/html_editor/m_html_editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,11 @@ const HtmlEditor = Editor.inherit({
customizeModules: null,
tableContextMenu: null,
allowSoftLineBreak: false,

formDialogOptions: null,

imageUpload: null,

// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
stylingMode: config().editorStylingMode || 'outlined',
converter: null,
});
},

Expand Down Expand Up @@ -264,6 +262,12 @@ const HtmlEditor = Editor.inherit({
this._deltaConverter = new DeltaConverter();
}
}

const { converter } = this.option();

if (converter) {
this._htmlConverter = converter;
}
},

_renderContentImpl() {
Expand Down Expand Up @@ -428,7 +432,12 @@ const HtmlEditor = Editor.inherit({

_textChangeHandler() {
const { value: currentValue } = this.option();
const convertedValue = this._deltaConverter.toHtml();

const htmlMarkup = this._deltaConverter.toHtml();

const convertedValue = isFunction(this._htmlConverter?.fromHtml)
? String(this._htmlConverter.fromHtml(htmlMarkup))
: htmlMarkup;

if (
currentValue !== convertedValue
Expand Down Expand Up @@ -505,13 +514,21 @@ const HtmlEditor = Editor.inherit({

_optionChanged(args) {
switch (args.name) {
case 'converter': {
this._htmlConverter = args.value;
break;
}
case 'value': {
if (this._quillInstance) {
if (this._isEditorUpdating) {
this._isEditorUpdating = false;
} else {
const updatedValue = isFunction(this._htmlConverter?.toHtml)
? String(this._htmlConverter.toHtml(args.value))
: args.value;

this._suppressValueChangeAction();
this._updateHtmlContent(args.value);
this._updateHtmlContent(updatedValue);
this._resumeValueChangeAction();
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import $ from 'jquery';
import 'ui/html_editor';
import { prepareEmbedValue, prepareTableValue } from './utils.js';
import { isObject } from 'core/utils/type';
import keyboardMock from '../../../helpers/keyboardMock.js';

const { test, module: testModule } = QUnit;

const TOOLBAR_FORMAT_WIDGET_CLASS = 'dx-htmleditor-toolbar-format';
const DISABLED_STATE_CLASS = 'dx-state-disabled';
const BUTTON_CLASS = 'dx-button';
const HTML_EDITOR_CONTENT_CLASS = 'dx-htmleditor-content';

const moduleConfig = {
beforeEach: function() {
Expand Down Expand Up @@ -494,6 +496,218 @@ testModule('API', moduleConfig, () => {
this.clock.tick(10);
assert.ok(this.options.onContentReady.calledOnce, 'onContentReady has been called once');
});

testModule('converter option', () => {
test('toHtml and fromHtml should be called once after value option changed', function(assert) {
const converter = {
toHtml: sinon.stub(),
fromHtml: sinon.stub(),
};
this.options = { converter };

this.createEditor();

this.instance.option('value', 'new value');

assert.strictEqual(this.options.converter.toHtml.callCount, 1);
assert.strictEqual(this.options.converter.fromHtml.callCount, 1);
});

test('toHtml and fromHtml must be called the correct number of times after the character has been entered', function(assert) {
assert.expect(2);

const done = assert.async();

const converter = {
toHtml: sinon.stub(),
fromHtml: sinon.stub(),
};

this.options = {
converter,
onValueChanged: () => {
assert.strictEqual(converter.toHtml.callCount, 0);
assert.strictEqual(converter.fromHtml.callCount, 1);

done();
},
};

this.createEditor();

this.instance.focus();

const input = this.instance.$element().find(`.${HTML_EDITOR_CONTENT_CLASS}`).get(0);

keyboardMock(input).type('t').change();
input.textContent = 't';
});

[
'',
'string',
true,
false,
null,
undefined,
NaN,
4,
Infinity,
-Infinity,
{},
].forEach(value => {
test(`There is no error here if the toHtml return value is ${value}`, function(assert) {
this.options = {
converter: {
toHtml: () => value,
fromHtml: (e) => e,
},
};

this.createEditor();

try {
this.instance.option('value', '');
} catch(e) {
assert.ok(false, `error: ${e.message}`);
} finally {
assert.ok(true, 'there is no error');
}
});

test(`There is no error here if the fromHtml return value is ${value}`, function(assert) {
this.options = {
converter: {
toHtml: (e) => e,
fromHtml: () => value,
},
};

this.createEditor();

try {
this.instance.option('value', '');
} catch(e) {
assert.ok(false, `error: ${e.message}`);
} finally {
assert.ok(true, 'there is no error');
}
});

test(`There is no error here if toHtml is ${value}`, function(assert) {
this.options = {
converter: {
toHtml: value,
fromHtml: (val) => val,
}
};

this.createEditor();

try {
this.instance.option('value', '');
} catch(e) {
assert.ok(false, `error: ${e.message}`);
} finally {
assert.ok(true, 'there is no error');
}
});

test(`There is no error here if fromHtml is ${value}`, function(assert) {
this.options = {
converter: {
toHtml: (val) => val,
fromHtml: value,
},
};

this.createEditor();

try {
this.instance.option('value', '');
} catch(e) {
assert.ok(false, `error: ${e.message}`);
} finally {
assert.ok(true, 'there is no error');
}
});
});

test('converter option runtime change should update html converter', function(assert) {
const firstConverter = {
toHtml: sinon.stub(),
fromHtml: sinon.stub(),
};

const secondConverter = {
toHtml: sinon.stub(),
fromHtml: sinon.stub(),
};

const instance = $('#htmlEditor').dxHtmlEditor({
converter: firstConverter,
}).dxHtmlEditor('instance');

instance.option('converter', secondConverter);
instance.option('value', 'new value');

assert.strictEqual(firstConverter.toHtml.callCount, 0);
assert.strictEqual(firstConverter.fromHtml.callCount, 0);
assert.strictEqual(secondConverter.toHtml.callCount, 1);
assert.strictEqual(secondConverter.fromHtml.callCount, 1);
});

test('The converter methods get the correct parameters', function(assert) {
const converter = {
toHtml: (value) => {
assert.strictEqual(value, 'new value');

return value;
},
fromHtml: (value) => {
assert.strictEqual(value, '<p>new value</p>');

return value;
},
};

this.options = { converter };

this.createEditor();

this.instance.option('value', 'new value');
});

test('toHtml changes value correctly', function(assert) {
const converter = {
toHtml: () => {
return '<p>NEW VALUE</p>';
},
};
this.options = { converter };

this.createEditor();

this.instance.option('value', 'new value');

assert.strictEqual(this.instance.option('value'), '<p>NEW VALUE</p>');
});

test('fromHtml changes value correctly', function(assert) {
const converter = {
fromHtml: () => {
return '**NEW VALUE**';
},
};
this.options = { converter };

this.createEditor();

this.instance.option('value', 'new value');

assert.strictEqual(this.instance.option('value'), '**NEW VALUE**');
});
});
});

testModule('Private API', moduleConfig, () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -649,4 +649,20 @@ export default function() {
});
});
});

testModule('converter option', () => {
test('value from toHtml must match the markup value', function(assert) {
const instance = $('#htmlEditor').dxHtmlEditor({
converter: {
toHtml: () => '<h1>Hi!</h1><p>Test</p>',
},
}).dxHtmlEditor('instance');

instance.option('value', 'new value');

const markup = instance.$element().find(`.${CONTENT_CLASS}`).html();

assert.strictEqual(markup, '<h1>Hi!</h1><p>Test</p>');
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -706,5 +706,3 @@ QUnit.module('Chat', moduleConfig, () => {
});
});
});


0 comments on commit e56a9d3

Please sign in to comment.