Skip to content

Commit

Permalink
Standalone editor: Remove more dependencies (#2139)
Browse files Browse the repository at this point in the history
* Standalone editor: Refactor list format handler 1

* Standalone editor: List refactor 2

* Standalone editor: remove dependencies

---------

Co-authored-by: Bryan Valverde U <[email protected]>
  • Loading branch information
JiuqingSong and BryanValverdeU authored Oct 16, 2023
1 parent d0924ae commit 573f78a
Show file tree
Hide file tree
Showing 27 changed files with 776 additions and 69 deletions.
40 changes: 20 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,27 +132,27 @@ In order to run the code below, you may also need to install [webpack](https://w

```html
<html>
<body>
<div style="width: 500px; height: 400px; border: solid 1px black" id="contentDiv"></div>
<button id="buttonB">B</button> <button id="buttonI">I</button>
<body>
<div style="width: 500px; height: 400px; border: solid 1px black" id="contentDiv"></div>
<button id="buttonB">B</button> <button id="buttonI">I</button>
<button id="buttonU">U</button>
<script src="rooster.js"></script>
<script>
var contentDiv = document.getElementById("contentDiv");
var editor = roosterjs.createEditor(contentDiv);
editor.setContent('Welcome to <b>RoosterJs</b>!');
document.getElementById('buttonB').addEventListener('click', function () {
roosterjs.toggleBold(editor);
});
document.getElementById('buttonI').addEventListener('click', function () {
roosterjs.toggleItalic(editor);
});
document.getElementById('buttonU').addEventListener('click', function () {
roosterjs.toggleUnderline(editor);
});
</script>
</body>
<script src="rooster.js"></script>
<script>
var contentDiv = document.getElementById('contentDiv');
var editor = roosterjs.createEditor(contentDiv);
editor.setContent('Welcome to <b>RoosterJs</b>!');
document.getElementById('buttonB').addEventListener('click', function () {
roosterjs.toggleBold(editor);
});
document.getElementById('buttonI').addEventListener('click', function () {
roosterjs.toggleItalic(editor);
});
document.getElementById('buttonU').addEventListener('click', function () {
roosterjs.toggleUnderline(editor);
});
</script>
</body>
</html>
```

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { getDelimiterFromElement } from 'roosterjs-editor-dom';
import { isEntityElement } from '../../domUtils/entityUtils';
import { isEntityDelimiter, isEntityElement } from '../../domUtils/entityUtils';
import type {
DomToModelContext,
ElementProcessor,
Expand Down Expand Up @@ -28,6 +27,6 @@ function tryGetProcessorForEntity(element: HTMLElement, context: DomToModelConte
: null;
}

function tryGetProcessorForDelimiter(element: Node, context: DomToModelContext) {
return getDelimiterFromElement(element) ? context.elementProcessors.delimiter : null;
function tryGetProcessorForDelimiter(element: HTMLElement, context: DomToModelContext) {
return isEntityDelimiter(element) ? context.elementProcessors.delimiter : null;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { isElementOfType } from './isElementOfType';
import { isNodeOfType } from './isNodeOfType';
import type { ContentModelEntityFormat } from 'roosterjs-content-model-types';

const ENTITY_INFO_NAME = '_Entity';
const ENTITY_TYPE_PREFIX = '_EType_';
const ENTITY_ID_PREFIX = '_EId_';
const ENTITY_READONLY_PREFIX = '_EReadonly_';
const ZERO_WIDTH_SPACE = '\u200B';
const DELIMITER_BEFORE = 'entityDelimiterBefore';
const DELIMITER_AFTER = 'entityDelimiterAfter';

/**
* @internal
Expand Down Expand Up @@ -41,3 +45,37 @@ export function generateEntityClassNames(format: ContentModelEntityFormat): stri
format.id ? `${ENTITY_ID_PREFIX}${format.id} ` : ''
}${ENTITY_READONLY_PREFIX}${format.isReadonly ? '1' : '0'}`;
}

/**
* @internal
*/
export function isEntityDelimiter(element: HTMLElement): boolean {
return (
isElementOfType(element, 'span') &&
(element.classList.contains(DELIMITER_AFTER) ||
element.classList.contains(DELIMITER_BEFORE)) &&
element.textContent === ZERO_WIDTH_SPACE
);
}

/**
* @internal
* Adds delimiters to the element provided. If the delimiters already exists, will not be added
* @param element the node to add the delimiters
*/
export function addDelimiters(doc: Document, element: HTMLElement): HTMLElement[] {
return [
insertDelimiter(doc, element, true /*isAfter*/),
insertDelimiter(doc, element, false /*isAfter*/),
];
}

function insertDelimiter(doc: Document, element: Element, isAfter: boolean) {
const span = doc.createElement('span');

span.className = isAfter ? DELIMITER_AFTER : DELIMITER_BEFORE;
span.appendChild(doc.createTextNode(ZERO_WIDTH_SPACE));
element.parentNode?.insertBefore(span, isAfter ? element.nextSibling : element);

return span;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { validate } from 'roosterjs-editor-dom';
import type { ContentModelWithDataset } from 'roosterjs-content-model-types';
import type { Definition } from 'roosterjs-editor-types';
import { validate } from './validate';
import type { ContentModelWithDataset, Definition } from 'roosterjs-content-model-types';

const EditingInfoDatasetName = 'editingInfo';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { getObjectKeys } from '../getObjectKeys';
import type { Definition } from 'roosterjs-content-model-types';

/**
* @internal
* Validate the given object with a type definition object
* @param input The object to validate
* @param def The type definition object used for validation
* @returns True if the object passed the validation, otherwise false
*/
export function validate<T>(input: any, def: Definition<T>): input is T {
let result = false;
if ((def.isOptional && typeof input === 'undefined') || (def.allowNull && input === null)) {
result = true;
} else if (
(!def.isOptional && typeof input === 'undefined') ||
(!def.allowNull && input === null)
) {
return false;
} else {
switch (def.type) {
case 'string':
result =
typeof input === 'string' &&
(typeof def.value === 'undefined' || input === def.value);
break;

case 'number':
result =
typeof input === 'number' &&
(typeof def.value === 'undefined' || areSameNumbers(def.value, input)) &&
(typeof def.minValue === 'undefined' || input >= def.minValue) &&
(typeof def.maxValue === 'undefined' || input <= def.maxValue);
break;

case 'boolean':
result =
typeof input === 'boolean' &&
(typeof def.value === 'undefined' || input === def.value);
break;

case 'array':
result =
Array.isArray(input) &&
(typeof def.minLength === 'undefined' || input.length >= def.minLength) &&
(typeof def.maxLength === 'undefined' || input.length <= def.maxLength) &&
input.every(x => validate(x, def.itemDef));
break;

case 'object':
result =
typeof input === 'object' &&
getObjectKeys(def.propertyDef).every(x =>
validate(input[x], def.propertyDef[x])
);
break;
}
}

return result;
}

function areSameNumbers(n1: number, n2: number) {
return Math.abs(n1 - n2) < 1e-3;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @internal
* Removes the node and keep all children in place, return the parentNode where the children are attached
* @param node the node to remove
*/
export function unwrap(node: Node): Node | null {
// Unwrap requires a parentNode
const parentNode = node ? node.parentNode : null;

if (!parentNode) {
return null;
}

while (node.firstChild) {
parentNode.insertBefore(node.firstChild, node);
}

parentNode.removeChild(node);
return parentNode;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Wrap the given node with a new element, put the wrapper node under the parent of the first node
* and return the wrapper element.
* @param doc Parent document object
* @param node The node to wrap
* @param wrapperTag The tag of wrapper HTML element
* @returns The wrapper element
*/
export function wrap<T extends keyof HTMLElementTagNameMap>(
doc: Document,
node: Node,
wrapperTag: T
): HTMLElementTagNameMap[T] {
const wrapper = doc.createElement(wrapperTag);
node.parentNode?.insertBefore(wrapper, node);
wrapper.appendChild(node);

return wrapper;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export { isElementOfType } from './domUtils/isElementOfType';
export { getObjectKeys } from './domUtils/getObjectKeys';
export { default as toArray } from './domUtils/toArray';
export { moveChildNodes, wrapAllChildNodes } from './domUtils/moveChildNodes';
export { wrap } from './domUtils/wrap';

export { createBr } from './modelApi/creators/createBr';
export { createListItem } from './modelApi/creators/createListItem';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { addDelimiters, wrap } from 'roosterjs-editor-dom';
import { addDelimiters } from '../../domUtils/entityUtils';
import { applyFormat } from '../utils/applyFormat';
import { getObjectKeys } from '../../domUtils/getObjectKeys';
import { reuseCachedElement } from '../utils/reuseCachedElement';
import { wrap } from '../../domUtils/wrap';
import type {
ContentModelBlockHandler,
ContentModelEntity,
Expand Down Expand Up @@ -32,7 +33,7 @@ export const handleEntityBlock: ContentModelBlockHandler<ContentModelEntity> = (
* @internal
*/
export const handleEntitySegment: ContentModelSegmentHandler<ContentModelEntity> = (
_,
doc,
parent,
entityModel,
context,
Expand All @@ -44,15 +45,15 @@ export const handleEntitySegment: ContentModelSegmentHandler<ContentModelEntity>
newSegments?.push(wrapper);

if (getObjectKeys(format).length > 0) {
const span = wrap(wrapper, 'span');
const span = wrap(doc, wrapper, 'span');

applyFormat(span, context.formatAppliers.segment, format, context);
}

applyFormat(wrapper, context.formatAppliers.entity, entityFormat, context);

if (context.addDelimiterForEntity && entityFormat.isReadonly) {
const [after, before] = addDelimiters(wrapper);
const [after, before] = addDelimiters(doc, wrapper);

newSegments?.push(after, before);
context.regularSelection.current.segment = after;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { handleSegmentCommon } from '../utils/handleSegmentCommon';
import { isNodeOfType } from '../../domUtils/isNodeOfType';
import { reuseCachedElement } from '../utils/reuseCachedElement';
import { wrap } from 'roosterjs-editor-dom';
import { wrap } from '../../domUtils/wrap';
import type {
ContentModelBlockHandler,
ContentModelGeneralBlock,
Expand Down Expand Up @@ -51,7 +51,7 @@ export const handleGeneralSegment: ContentModelSegmentHandler<ContentModelGenera
parent.appendChild(node);

if (isNodeOfType(node, 'ELEMENT_NODE')) {
const element = wrap(node, 'span');
const element = wrap(doc, node, 'span');

handleSegmentCommon(doc, node, element, group, context, segmentNodes);
context.onNodeCreated?.(group, node);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { applyFormat } from '../utils/applyFormat';
import { applyMetadata } from '../utils/applyMetadata';
import { setParagraphNotImplicit } from '../../modelApi/block/setParagraphNotImplicit';
import { unwrap } from 'roosterjs-editor-dom';
import { unwrap } from '../../domUtils/unwrap';
import type {
ContentModelBlockHandler,
ContentModelListItem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getObjectKeys } from '../../domUtils/getObjectKeys';
import { optimize } from '../optimizers/optimize';
import { reuseCachedElement } from '../utils/reuseCachedElement';
import { stackFormat } from '../utils/stackFormat';
import { unwrap } from 'roosterjs-editor-dom';
import { unwrap } from '../../domUtils/unwrap';
import type {
ContentModelBlockHandler,
ContentModelParagraph,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as getDelimiterFromElement from 'roosterjs-editor-dom/lib/delimiter/getDelimiterFromElement';
import * as entityUtils from '../../../lib/domUtils/entityUtils';
import { createContentModelDocument } from '../../../lib/modelApi/creators/createContentModelDocument';
import { createDomToModelContext } from '../../../lib/domToModel/context/createDomToModelContext';
import { elementProcessor } from '../../../lib/domToModel/processors/elementProcessor';
Expand Down Expand Up @@ -117,7 +117,7 @@ describe('elementProcessor', () => {

it('delimiter', () => {
const element = document.createElement('span') as HTMLElement;
spyOn(getDelimiterFromElement, 'default').and.returnValue(element);
spyOn(entityUtils, 'isEntityDelimiter').and.returnValue(true);

elementProcessor(group, element, context);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as validate from 'roosterjs-editor-dom/lib/metadata/validate';
import { ContentModelWithDataset } from 'roosterjs-content-model-types';
import { Definition } from 'roosterjs-editor-types';
import * as validate from 'roosterjs-content-model-dom/lib/domUtils/metadata/validate';
import { ContentModelWithDataset, Definition } from 'roosterjs-content-model-types';
import { hasMetadata, updateMetadata } from '../../../lib/domUtils/metadata/updateMetadata';

describe('updateMetadata', () => {
Expand Down Expand Up @@ -101,7 +100,7 @@ describe('updateMetadata', () => {
};
const callback = jasmine.createSpy('callback').and.callFake(() => null);

spyOn(validate, 'default').and.returnValue(true);
spyOn(validate, 'validate').and.returnValue(true);

const result = updateMetadata(model, callback, {} as Definition<void>);

Expand All @@ -124,7 +123,7 @@ describe('updateMetadata', () => {
return !!input.c;
}

spyOn(validate, 'default').and.callFake(fakeValidation);
spyOn(validate, 'validate').and.callFake(fakeValidation);

const result = updateMetadata(model, callback, {} as Definition<void>);

Expand All @@ -149,7 +148,7 @@ describe('updateMetadata', () => {
return !!input.a;
}

spyOn(validate, 'default').and.callFake(fakeValidation);
spyOn(validate, 'validate').and.callFake(fakeValidation);

const result = updateMetadata(model, callback, {} as Definition<void>);

Expand Down
Loading

0 comments on commit 573f78a

Please sign in to comment.