test: fix tests
This commit is contained in:
parent
9ff091bd39
commit
a51aad42ee
|
@ -6,13 +6,13 @@ describe('@schlechtenburg/core', () => {
|
||||||
const a = 'a';
|
const a = 'a';
|
||||||
const b = 'b';
|
const b = 'b';
|
||||||
|
|
||||||
it('Should activate', () => {
|
it('Should activate', async () => {
|
||||||
const {
|
const {
|
||||||
activeBlockId,
|
activeBlockId,
|
||||||
isActive,
|
isActive,
|
||||||
activate,
|
activate,
|
||||||
deactivate,
|
deactivate,
|
||||||
} = withSetup(() => useActivation(a));
|
} = await withSetup(() => useActivation(a));
|
||||||
|
|
||||||
activate(a);
|
activate(a);
|
||||||
expect(activeBlockId.value).toBe(a);
|
expect(activeBlockId.value).toBe(a);
|
||||||
|
@ -22,9 +22,9 @@ describe('@schlechtenburg/core', () => {
|
||||||
expect(activeBlockId.value).toBe(b);
|
expect(activeBlockId.value).toBe(b);
|
||||||
expect(isActive.value).toBeFalsy();
|
expect(isActive.value).toBeFalsy();
|
||||||
|
|
||||||
deactivate();
|
deactivate(activeBlockId.value);
|
||||||
expect(isActive.value).toBeFalsy();
|
expect(isActive.value).toBeFalsy();
|
||||||
expect(activeBlockId.value).toBe(undefined);
|
expect(activeBlockId.value).toBe(null);
|
||||||
|
|
||||||
activate();
|
activate();
|
||||||
expect(activeBlockId.value).toBe(a);
|
expect(activeBlockId.value).toBe(a);
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
/**
|
import { FormatTypeStore } from './use-format-types';
|
||||||
* Internal dependencies
|
|
||||||
*/
|
|
||||||
import { useFormatTypes } from './use-format-types';
|
|
||||||
import { createElement } from './create-element';
|
import { createElement } from './create-element';
|
||||||
import { mergePair } from './concat';
|
import { mergePair } from './concat';
|
||||||
import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from './special-characters';
|
import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from './special-characters';
|
||||||
|
@ -15,8 +12,14 @@ function createEmptyValue(): RichTextValue {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function toFormat( { tagName, attributes }: { tagName: string, attributes: Record<string,any> } ): RichTextFormat {
|
function toFormat(
|
||||||
const { getFormatTypeForClassName, getFormatTypeForBareElement } = useFormatTypes();
|
{ tagName, attributes }: { tagName: string, attributes: Record<string,any> },
|
||||||
|
store: FormatTypeStore,
|
||||||
|
): RichTextFormat {
|
||||||
|
if (!store) {
|
||||||
|
console.dir((new Error()).stack);
|
||||||
|
}
|
||||||
|
const { getFormatTypeForClassName, getFormatTypeForBareElement } = store;
|
||||||
let formatType: RichTextFormatType|undefined;
|
let formatType: RichTextFormatType|undefined;
|
||||||
|
|
||||||
if ( attributes && attributes.class ) {
|
if ( attributes && attributes.class ) {
|
||||||
|
@ -81,14 +84,14 @@ function toFormat( { tagName, attributes }: { tagName: string, attributes: Recor
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fromPlainText = (text: string) => create({ text });
|
export const fromPlainText = (text: string, store: FormatTypeStore) => create({ text }, store);
|
||||||
export const fromHTMLString = (html: string) => create({ html });
|
export const fromHTMLString = (html: string, store: FormatTypeStore) => create({ html }, store);
|
||||||
export const fromHTMLElement = (htmlElement: Element, options: { preserveWhiteSpace?: boolean } = {}) => {
|
export const fromHTMLElement = (htmlElement: Element, options: { preserveWhiteSpace?: boolean } = {}, store: FormatTypeStore) => {
|
||||||
const { preserveWhiteSpace = false } = options;
|
const { preserveWhiteSpace = false } = options;
|
||||||
const element = preserveWhiteSpace
|
const element = preserveWhiteSpace
|
||||||
? htmlElement
|
? htmlElement
|
||||||
: collapseWhiteSpace( htmlElement );
|
: collapseWhiteSpace( htmlElement );
|
||||||
const richTextValue = create({ element });
|
const richTextValue = create({ element }, store);
|
||||||
Object.defineProperty( richTextValue, 'originalHTML', {
|
Object.defineProperty( richTextValue, 'originalHTML', {
|
||||||
value: htmlElement.innerHTML,
|
value: htmlElement.innerHTML,
|
||||||
} );
|
} );
|
||||||
|
@ -120,27 +123,23 @@ export const fromHTMLElement = (htmlElement: Element, options: { preserveWhiteSp
|
||||||
* holds information about the formatting at the relevant text indices. Finally
|
* holds information about the formatting at the relevant text indices. Finally
|
||||||
* `start` and `end` state which text indices are selected. They are only
|
* `start` and `end` state which text indices are selected. They are only
|
||||||
* provided if a `Range` was given.
|
* provided if a `Range` was given.
|
||||||
*
|
|
||||||
* @param {Object} [$1] Optional named arguments.
|
|
||||||
* @param {Element} [$1.element] Element to create value from.
|
|
||||||
* @param {string} [$1.text] Text to create value from.
|
|
||||||
* @param {string} [$1.html] HTML to create value from.
|
|
||||||
* @param {Range} [$1.range] Range to create value from.
|
|
||||||
* @return {RichTextValue} A rich text value.
|
|
||||||
*/
|
*/
|
||||||
export function create({
|
export function create(
|
||||||
element,
|
{
|
||||||
text,
|
element,
|
||||||
html,
|
text,
|
||||||
range,
|
html,
|
||||||
isEditableTree = false,
|
range,
|
||||||
}: {
|
isEditableTree = false,
|
||||||
element?: Element|Node,
|
}: {
|
||||||
text?: string,
|
element?: Element|Node,
|
||||||
html?: string,
|
text?: string,
|
||||||
range?: SimpleRange,
|
html?: string,
|
||||||
isEditableTree?: boolean,
|
range?: SimpleRange,
|
||||||
} = {} ): RichTextValue {
|
isEditableTree?: boolean,
|
||||||
|
} = {},
|
||||||
|
store: FormatTypeStore,
|
||||||
|
): RichTextValue {
|
||||||
if ( typeof text === 'string' && text.length > 0 ) {
|
if ( typeof text === 'string' && text.length > 0 ) {
|
||||||
return {
|
return {
|
||||||
formats: Array( text.length ),
|
formats: Array( text.length ),
|
||||||
|
@ -163,17 +162,12 @@ export function create({
|
||||||
element,
|
element,
|
||||||
range,
|
range,
|
||||||
isEditableTree,
|
isEditableTree,
|
||||||
} );
|
}, store );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to accumulate the value's selection start and end from the current
|
* Helper to accumulate the value's selection start and end from the current
|
||||||
* node and range.
|
* node and range.
|
||||||
*
|
|
||||||
* @param {Object} accumulator Object to accumulate into.
|
|
||||||
* @param {Node} node Node to create value with.
|
|
||||||
* @param {Range} range Range to create value with.
|
|
||||||
* @param {Object} value Value that is being accumulated.
|
|
||||||
*/
|
*/
|
||||||
function accumulateSelection(
|
function accumulateSelection(
|
||||||
accumulator: RichTextValue,
|
accumulator: RichTextValue,
|
||||||
|
@ -238,12 +232,6 @@ function accumulateSelection(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts the start and end offsets from a range based on a text filter.
|
* Adjusts the start and end offsets from a range based on a text filter.
|
||||||
*
|
|
||||||
* @param {Node} node Node of which the text should be filtered.
|
|
||||||
* @param {Range} range The range to filter.
|
|
||||||
* @param {Function} filter Function to use to filter the text.
|
|
||||||
*
|
|
||||||
* @return {Object|undefined} Object containing range properties.
|
|
||||||
*/
|
*/
|
||||||
function filterRange(node: Node, range?: SimpleRange, filter?: Function): SimpleRange|undefined {
|
function filterRange(node: Node, range?: SimpleRange, filter?: Function): SimpleRange|undefined {
|
||||||
if ( ! range ) {
|
if ( ! range ) {
|
||||||
|
@ -280,11 +268,6 @@ function filterRange(node: Node, range?: SimpleRange, filter?: Function): Simple
|
||||||
*
|
*
|
||||||
* @see
|
* @see
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/CSS/white-space-collapse#collapsing_of_white_space
|
* https://developer.mozilla.org/en-US/docs/Web/CSS/white-space-collapse#collapsing_of_white_space
|
||||||
*
|
|
||||||
* @param {HTMLElement} element
|
|
||||||
* @param {boolean} isRoot
|
|
||||||
*
|
|
||||||
* @return {HTMLElement} New element with collapsed whitespace.
|
|
||||||
*/
|
*/
|
||||||
function collapseWhiteSpace(element: HTMLElement, isRoot: boolean = true): HTMLElement {
|
function collapseWhiteSpace(element: HTMLElement, isRoot: boolean = true): HTMLElement {
|
||||||
const clone = element.cloneNode( true ) as HTMLElement;
|
const clone = element.cloneNode( true ) as HTMLElement;
|
||||||
|
@ -346,25 +329,18 @@ export function removeReservedCharacters( string: string ): string {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Rich Text value from a DOM element and range.
|
* Creates a Rich Text value from a DOM element and range.
|
||||||
*
|
|
||||||
* @param {Object} $1 Named arguments.
|
|
||||||
* @param {Element} [$1.element] Element to create value from.
|
|
||||||
* @param {Range} [$1.range] Range to create value from.
|
|
||||||
* @param {boolean} [$1.isEditableTree]
|
|
||||||
*
|
|
||||||
* @return {RichTextValue} A rich text value.
|
|
||||||
*/
|
*/
|
||||||
function createFromElement(
|
function createFromElement(
|
||||||
{
|
{
|
||||||
element,
|
element,
|
||||||
range,
|
range,
|
||||||
isEditableTree,
|
isEditableTree,
|
||||||
}:
|
}: {
|
||||||
{
|
|
||||||
element?:Element|Node,
|
element?:Element|Node,
|
||||||
range?:SimpleRange,
|
range?:SimpleRange,
|
||||||
isEditableTree?: boolean,
|
isEditableTree?: boolean,
|
||||||
}
|
},
|
||||||
|
store: FormatTypeStore,
|
||||||
): RichTextValue {
|
): RichTextValue {
|
||||||
const accumulator = createEmptyValue();
|
const accumulator = createEmptyValue();
|
||||||
|
|
||||||
|
@ -435,14 +411,14 @@ function createFromElement(
|
||||||
|
|
||||||
if ( tagName === 'br' ) {
|
if ( tagName === 'br' ) {
|
||||||
accumulateSelection( accumulator, node, newRange, createEmptyValue() );
|
accumulateSelection( accumulator, node, newRange, createEmptyValue() );
|
||||||
mergePair( accumulator, create( { text: '\n' } ) );
|
mergePair( accumulator, create( { text: '\n' }, store ) );
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const format = toFormat( {
|
const format = toFormat( {
|
||||||
tagName,
|
tagName,
|
||||||
attributes: getAttributes( { element: node as HTMLElement } ),
|
attributes: getAttributes( { element: node as HTMLElement } ),
|
||||||
} );
|
}, store );
|
||||||
|
|
||||||
// When a format type is declared as not editable, replace it with an
|
// When a format type is declared as not editable, replace it with an
|
||||||
// object replacement character and preserve the inner HTML.
|
// object replacement character and preserve the inner HTML.
|
||||||
|
@ -472,7 +448,7 @@ function createFromElement(
|
||||||
element: node as HTMLElement,
|
element: node as HTMLElement,
|
||||||
range: newRange,
|
range: newRange,
|
||||||
isEditableTree,
|
isEditableTree,
|
||||||
} );
|
}, store );
|
||||||
|
|
||||||
accumulateSelection( accumulator, node, newRange, value );
|
accumulateSelection( accumulator, node, newRange, value );
|
||||||
|
|
||||||
|
@ -524,12 +500,6 @@ function createFromElement(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the attributes of an element in object shape.
|
* Gets the attributes of an element in object shape.
|
||||||
*
|
|
||||||
* @param {Object} $1 Named arguments.
|
|
||||||
* @param {Element} $1.element Element to get attributes from.
|
|
||||||
*
|
|
||||||
* @return {Object} Attribute object or `undefined` if the element has no
|
|
||||||
* attributes.
|
|
||||||
*/
|
*/
|
||||||
function getAttributes({ element }: { element: Element }): Record<string, any>{
|
function getAttributes({ element }: { element: Element }): Record<string, any>{
|
||||||
let accumulator: Record<string, any> = {};
|
let accumulator: Record<string, any> = {};
|
||||||
|
|
|
@ -1,17 +1,27 @@
|
||||||
import { describe, expect, it, beforeAll } from 'vitest'
|
import { describe, expect, it, beforeAll } from 'vitest'
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import { defineComponent, Component } from 'vue'
|
||||||
|
import { useFormatTypes, FormatTypeStore } from '../use-format-types';
|
||||||
import { create, removeReservedCharacters } from '../create';
|
import { create, removeReservedCharacters } from '../create';
|
||||||
import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from '../special-characters';
|
import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from '../special-characters';
|
||||||
import { createElement } from '../create-element';
|
import { createElement } from '../create-element';
|
||||||
import { getSparseArrayLength, spec, specWithRegistration } from './helpers';
|
import { getSparseArrayLength, spec, specWithRegistration } from './helpers';
|
||||||
|
|
||||||
describe( 'create', () => {
|
describe( 'create', () => {
|
||||||
const em = { type: 'em' };
|
const em = { type: 'em', attributes: {} };
|
||||||
const strong = { type: 'strong' };
|
const strong = { type: 'strong', attributes: {} };
|
||||||
|
|
||||||
beforeAll( () => {
|
let TestComponent: Component;
|
||||||
// Initialize the rich-text store.
|
let store: FormatTypeStore;
|
||||||
// require( '../store' );
|
beforeAll(async () => {
|
||||||
} );
|
TestComponent = defineComponent({
|
||||||
|
setup () {
|
||||||
|
return useFormatTypes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
store = mount(TestComponent).vm;
|
||||||
|
});
|
||||||
|
|
||||||
spec.forEach( ( { description, html, createRange, record } ) => {
|
spec.forEach( ( { description, html, createRange, record } ) => {
|
||||||
if ( html === undefined ) {
|
if ( html === undefined ) {
|
||||||
|
@ -25,7 +35,7 @@ describe( 'create', () => {
|
||||||
const createdRecord = create( {
|
const createdRecord = create( {
|
||||||
element,
|
element,
|
||||||
range,
|
range,
|
||||||
} );
|
}, store );
|
||||||
const formatsLength = getSparseArrayLength( record.formats );
|
const formatsLength = getSparseArrayLength( record.formats );
|
||||||
const createdFormatsLength = getSparseArrayLength(
|
const createdFormatsLength = getSparseArrayLength(
|
||||||
createdRecord.formats
|
createdRecord.formats
|
||||||
|
@ -47,13 +57,13 @@ describe( 'create', () => {
|
||||||
// eslint-disable-next-line jest/valid-title
|
// eslint-disable-next-line jest/valid-title
|
||||||
it( description, () => {
|
it( description, () => {
|
||||||
if ( formatName ) {
|
if ( formatName ) {
|
||||||
registerFormatType( formatName, formatType );
|
store.registerFormatTypes( formatName, formatType );
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = create( { html } );
|
const result = create({ html }, store);
|
||||||
|
|
||||||
if ( formatName ) {
|
if ( formatName ) {
|
||||||
unregisterFormatType( formatName );
|
store.unregisterFormatTypes( formatName );
|
||||||
}
|
}
|
||||||
|
|
||||||
expect( result ).toEqual( expectedValue );
|
expect( result ).toEqual( expectedValue );
|
||||||
|
@ -62,7 +72,7 @@ describe( 'create', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
it( 'should reference formats', () => {
|
it( 'should reference formats', () => {
|
||||||
const value = create( { html: '<em>te<strong>st</strong></em>' } );
|
const value = create( { html: '<em>te<strong>st</strong></em>' }, store );
|
||||||
|
|
||||||
expect( value ).toEqual( {
|
expect( value ).toEqual( {
|
||||||
formats: [ [ em ], [ em ], [ em, strong ], [ em, strong ] ],
|
formats: [ [ em ], [ em ], [ em, strong ], [ em, strong ] ],
|
||||||
|
@ -81,7 +91,7 @@ describe( 'create', () => {
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'should use different reference for equal format', () => {
|
it( 'should use different reference for equal format', () => {
|
||||||
const value = create( { html: '<a href="#">a</a><a href="#">a</a>' } );
|
const value = create( { html: '<a href="#">a</a><a href="#">a</a>' }, store );
|
||||||
|
|
||||||
// Format objects.
|
// Format objects.
|
||||||
expect( value.formats[ 0 ][ 0 ] ).not.toBe( value.formats[ 1 ][ 0 ] );
|
expect( value.formats[ 0 ][ 0 ] ).not.toBe( value.formats[ 1 ][ 0 ] );
|
||||||
|
@ -91,7 +101,7 @@ describe( 'create', () => {
|
||||||
} );
|
} );
|
||||||
|
|
||||||
it( 'should use different reference for different format', () => {
|
it( 'should use different reference for different format', () => {
|
||||||
const value = create( { html: '<a href="#">a</a><a href="#a">a</a>' } );
|
const value = create( { html: '<a href="#">a</a><a href="#a">a</a>' }, store );
|
||||||
|
|
||||||
// Format objects.
|
// Format objects.
|
||||||
expect( value.formats[ 0 ][ 0 ] ).not.toBe( value.formats[ 1 ][ 0 ] );
|
expect( value.formats[ 0 ][ 0 ] ).not.toBe( value.formats[ 1 ][ 0 ] );
|
||||||
|
|
|
@ -1,15 +1,27 @@
|
||||||
import { describe, expect, it, beforeAll } from 'vitest'
|
import { describe, expect, it, beforeAll } from 'vitest'
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import { defineComponent, ComponentInstance, Component } from 'vue'
|
||||||
|
import { useFormatTypes } from '../use-format-types';
|
||||||
import { toDom, applyValue } from '../to-dom';
|
import { toDom, applyValue } from '../to-dom';
|
||||||
import { createElement } from '../create-element';
|
import { createElement } from '../create-element';
|
||||||
import { spec } from './helpers';
|
import { spec } from './helpers';
|
||||||
|
|
||||||
describe( 'recordToDom', () => {
|
describe( 'recordToDom', () => {
|
||||||
|
let TestComponent: Component;
|
||||||
|
let wrapper: ComponentInstance<typeof TestComponent>;
|
||||||
|
beforeAll(async () => {
|
||||||
|
TestComponent = defineComponent({
|
||||||
|
setup () {
|
||||||
|
return useFormatTypes();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper = mount(TestComponent);
|
||||||
|
});
|
||||||
spec.forEach( ( { description, record, startPath, endPath } ) => {
|
spec.forEach( ( { description, record, startPath, endPath } ) => {
|
||||||
// eslint-disable-next-line jest/valid-title
|
// eslint-disable-next-line jest/valid-title
|
||||||
it( description, () => {
|
it( description, () => {
|
||||||
const { body, selection } = toDom( {
|
const { body, selection } = toDom({ value: record }, wrapper.componentVM);
|
||||||
value: record,
|
|
||||||
} );
|
|
||||||
expect( body ).toMatchSnapshot();
|
expect( body ).toMatchSnapshot();
|
||||||
expect( selection ).toEqual( { startPath, endPath } );
|
expect( selection ).toEqual( { startPath, endPath } );
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -2,10 +2,10 @@ import { describe, expect, it, beforeAll } from 'vitest'
|
||||||
import { mount } from '@vue/test-utils';
|
import { mount } from '@vue/test-utils';
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
import { useFormatTypes } from '../use-format-types';
|
import { useFormatTypes, FormatTypeStore } from '../use-format-types';
|
||||||
import { create } from '../create';
|
import { create } from '../create';
|
||||||
import { toHTMLString } from '../to-html-string';
|
import { toHTMLString } from '../to-html-string';
|
||||||
import { withSetup, specWithRegistration } from './helpers';
|
import { specWithRegistration } from './helpers';
|
||||||
|
|
||||||
function createNode( HTML ) {
|
function createNode( HTML ) {
|
||||||
const doc = document.implementation.createHTMLDocument( '' );
|
const doc = document.implementation.createHTMLDocument( '' );
|
||||||
|
@ -14,7 +14,8 @@ function createNode( HTML ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe( 'toHTMLString', () => {
|
describe( 'toHTMLString', () => {
|
||||||
let TestComponent, wrapper;
|
let TestComponent;
|
||||||
|
let store: FormatTypeStore;
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
TestComponent = defineComponent({
|
TestComponent = defineComponent({
|
||||||
setup () {
|
setup () {
|
||||||
|
@ -22,7 +23,7 @@ describe( 'toHTMLString', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
wrapper = mount(TestComponent);
|
store = mount(TestComponent).vm;
|
||||||
});
|
});
|
||||||
|
|
||||||
specWithRegistration.forEach(
|
specWithRegistration.forEach(
|
||||||
|
@ -42,13 +43,13 @@ describe( 'toHTMLString', () => {
|
||||||
it.skip( description, () => {
|
it.skip( description, () => {
|
||||||
console.log(description, formatName);
|
console.log(description, formatName);
|
||||||
if ( formatName ) {
|
if ( formatName ) {
|
||||||
wrapper.vm.addFormatTypes( { name: formatName, ...formatType } );
|
store.registerFormatTypes( { name: formatName, ...formatType } );
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = toHTMLString( { value } );
|
const result = toHTMLString( { value } );
|
||||||
|
|
||||||
if ( formatName ) {
|
if ( formatName ) {
|
||||||
wrapper.vm.removeFormatTypes( formatName );
|
store.unregisterFormatTypes( formatName );
|
||||||
}
|
}
|
||||||
|
|
||||||
expect( result ).toEqual( html );
|
expect( result ).toEqual( html );
|
||||||
|
|
|
@ -1,23 +1,12 @@
|
||||||
/**
|
|
||||||
* Internal dependencies
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { toTree } from './to-tree';
|
import { toTree } from './to-tree';
|
||||||
|
import { FormatTypeStore } from './use-format-types';
|
||||||
import { createElement } from './create-element';
|
import { createElement } from './create-element';
|
||||||
import { isRangeEqual } from './is-range-equal';
|
import { isRangeEqual } from './is-range-equal';
|
||||||
import { RichTextValue } from './types';
|
import { RichTextValue } from './types';
|
||||||
|
|
||||||
/** @typedef {import('./types').RichTextValue} RichTextValue */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a path as an array of indices from the given root node to the given
|
* Creates a path as an array of indices from the given root node to the given
|
||||||
* node.
|
* node.
|
||||||
*
|
|
||||||
* @param {Node} node Node to find the path of.
|
|
||||||
* @param {HTMLElement} rootNode Root node to find the path from.
|
|
||||||
* @param {Array} path Initial path to build on.
|
|
||||||
*
|
|
||||||
* @return {Array} The path from the root node to the node.
|
|
||||||
*/
|
*/
|
||||||
function createPathToNode( node: Node, rootNode: HTMLElement, path: number[] ) {
|
function createPathToNode( node: Node, rootNode: HTMLElement, path: number[] ) {
|
||||||
let workingNode: Node|null = node;
|
let workingNode: Node|null = node;
|
||||||
|
@ -39,11 +28,6 @@ function createPathToNode( node: Node, rootNode: HTMLElement, path: number[] ) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a node given a path (array of indices) from the given node.
|
* Gets a node given a path (array of indices) from the given node.
|
||||||
*
|
|
||||||
* @param {HTMLElement} node Root node to find the wanted node in.
|
|
||||||
* @param {Array} path Path (indices) to the wanted node.
|
|
||||||
*
|
|
||||||
* @return {Object} Object with the found node and the remaining offset (if any).
|
|
||||||
*/
|
*/
|
||||||
function getNodeByPath( node: HTMLElement, path: number[] ) {
|
function getNodeByPath( node: HTMLElement, path: number[] ) {
|
||||||
let workingNode: Node = node;
|
let workingNode: Node = node;
|
||||||
|
@ -105,19 +89,22 @@ function remove( node ) {
|
||||||
return node.parentNode.removeChild( node );
|
return node.parentNode.removeChild( node );
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toDom({
|
export function toDom(
|
||||||
value,
|
{
|
||||||
prepareEditableTree,
|
value,
|
||||||
isEditableTree = true,
|
prepareEditableTree,
|
||||||
placeholder,
|
isEditableTree = true,
|
||||||
doc = document,
|
placeholder,
|
||||||
}: {
|
doc = document,
|
||||||
value: RichTextValue,
|
}: {
|
||||||
prepareEditableTree: Function,
|
value: RichTextValue,
|
||||||
isEditableTree?: boolean,
|
prepareEditableTree: Function,
|
||||||
placeholder?: string,
|
isEditableTree?: boolean,
|
||||||
doc?: Document,
|
placeholder?: string,
|
||||||
}) {
|
doc?: Document,
|
||||||
|
},
|
||||||
|
store: FormatTypeStore,
|
||||||
|
) {
|
||||||
let startPath: number[] = [];
|
let startPath: number[] = [];
|
||||||
let endPath: number[] = [];
|
let endPath: number[] = [];
|
||||||
|
|
||||||
|
@ -135,8 +122,6 @@ export function toDom({
|
||||||
* Note: The current implementation will return a shared reference, reset on
|
* Note: The current implementation will return a shared reference, reset on
|
||||||
* each call to `createEmpty`. Therefore, you should not hold a reference to
|
* each call to `createEmpty`. Therefore, you should not hold a reference to
|
||||||
* the value to operate upon asynchronously, as it may have unexpected results.
|
* the value to operate upon asynchronously, as it may have unexpected results.
|
||||||
*
|
|
||||||
* @return {Object} RichText tree.
|
|
||||||
*/
|
*/
|
||||||
const createEmpty = () => createElement( doc, '' );
|
const createEmpty = () => createElement( doc, '' );
|
||||||
|
|
||||||
|
@ -162,7 +147,8 @@ export function toDom({
|
||||||
},
|
},
|
||||||
isEditableTree,
|
isEditableTree,
|
||||||
placeholder,
|
placeholder,
|
||||||
} );
|
},
|
||||||
|
store );
|
||||||
|
|
||||||
return {
|
return {
|
||||||
body: tree,
|
body: tree,
|
||||||
|
@ -173,32 +159,28 @@ export function toDom({
|
||||||
/**
|
/**
|
||||||
* Create an `Element` tree from a Rich Text value and applies the difference to
|
* Create an `Element` tree from a Rich Text value and applies the difference to
|
||||||
* the `Element` tree contained by `current`.
|
* the `Element` tree contained by `current`.
|
||||||
*
|
|
||||||
* @param {Object} $1 Named arguments.
|
|
||||||
* @param {RichTextValue} $1.value Value to apply.
|
|
||||||
* @param {HTMLElement} $1.current The live root node to apply the element tree to.
|
|
||||||
* @param {Function} [$1.prepareEditableTree] Function to filter editorable formats.
|
|
||||||
* @param {boolean} [$1.__unstableDomOnly] Only apply elements, no selection.
|
|
||||||
* @param {string} [$1.placeholder] Placeholder text.
|
|
||||||
*/
|
*/
|
||||||
export function apply( {
|
export function apply(
|
||||||
value,
|
{
|
||||||
current,
|
value,
|
||||||
prepareEditableTree,
|
current,
|
||||||
placeholder,
|
prepareEditableTree,
|
||||||
}: {
|
placeholder,
|
||||||
value: RichTextValue,
|
}: {
|
||||||
current: HTMLElement,
|
value: RichTextValue,
|
||||||
prepareEditableTree: Function,
|
current: HTMLElement,
|
||||||
placeholder: string,
|
prepareEditableTree: Function,
|
||||||
}) {
|
placeholder: string,
|
||||||
|
},
|
||||||
|
store: FormatTypeStore,
|
||||||
|
) {
|
||||||
// Construct a new element tree in memory.
|
// Construct a new element tree in memory.
|
||||||
const { body, selection } = toDom( {
|
const { body, selection } = toDom({
|
||||||
value,
|
value,
|
||||||
prepareEditableTree,
|
prepareEditableTree,
|
||||||
placeholder,
|
placeholder,
|
||||||
doc: current.ownerDocument,
|
doc: current.ownerDocument,
|
||||||
} );
|
}, store );
|
||||||
|
|
||||||
applyValue( body, current );
|
applyValue( body, current );
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useFormatTypes } from './use-format-types';
|
|
||||||
import { getActiveFormats } from './get-active-formats';
|
import { getActiveFormats } from './get-active-formats';
|
||||||
import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from './special-characters';
|
import { OBJECT_REPLACEMENT_CHARACTER, ZWNBSP } from './special-characters';
|
||||||
import { RichTextValue } from './types';
|
import { RichTextValue } from './types';
|
||||||
|
import { FormatTypeStore } from './use-format-types';
|
||||||
|
|
||||||
function restoreOnAttributes( attributes: Record<string, any>, isEditableTree: boolean ): Record<string, any> {
|
function restoreOnAttributes( attributes: Record<string, any>, isEditableTree: boolean ): Record<string, any> {
|
||||||
if ( isEditableTree ) {
|
if ( isEditableTree ) {
|
||||||
|
@ -25,40 +25,28 @@ function restoreOnAttributes( attributes: Record<string, any>, isEditableTree: b
|
||||||
/**
|
/**
|
||||||
* Converts a format object to information that can be used to create an element
|
* Converts a format object to information that can be used to create an element
|
||||||
* from (type, attributes and object).
|
* from (type, attributes and object).
|
||||||
*
|
|
||||||
* @param {Object} $1 Named parameters.
|
|
||||||
* @param {string} $1.type The format type.
|
|
||||||
* @param {string} $1.tagName The tag name.
|
|
||||||
* @param {Object} $1.attributes The format attributes.
|
|
||||||
* @param {Object} $1.unregisteredAttributes The unregistered format
|
|
||||||
* attributes.
|
|
||||||
* @param {boolean} $1.object Whether or not it is an object
|
|
||||||
* format.
|
|
||||||
* @param {boolean} $1.boundaryClass Whether or not to apply a boundary
|
|
||||||
* class.
|
|
||||||
* @param {boolean} $1.isEditableTree
|
|
||||||
*
|
|
||||||
* @return {Object} Information to be used for element creation.
|
|
||||||
*/
|
*/
|
||||||
function fromFormat( {
|
function fromFormat(
|
||||||
type,
|
{
|
||||||
tagName,
|
type,
|
||||||
attributes,
|
tagName,
|
||||||
unregisteredAttributes,
|
attributes,
|
||||||
object,
|
unregisteredAttributes,
|
||||||
boundaryClass,
|
object,
|
||||||
isEditableTree,
|
boundaryClass,
|
||||||
}: {
|
isEditableTree,
|
||||||
type: string,
|
}: {
|
||||||
tagName: string,
|
type: string,
|
||||||
attributes: Record<string, any>,
|
tagName?: string,
|
||||||
unregisteredAttributes: Record<string, any>
|
attributes: Record<string, any>,
|
||||||
object: boolean,
|
unregisteredAttributes: Record<string, any>
|
||||||
boundaryClass: boolean,
|
object: boolean,
|
||||||
isEditableTree: boolean,
|
boundaryClass: boolean,
|
||||||
}) {
|
isEditableTree: boolean,
|
||||||
const { findFormatTypeByName } = useFormatTypes();
|
},
|
||||||
const formatType = findFormatTypeByName(type);
|
store: FormatTypeStore,
|
||||||
|
) {
|
||||||
|
const formatType = store.getFormatTypeByName(type);
|
||||||
|
|
||||||
let elementAttributes: Record<string, any> = {};
|
let elementAttributes: Record<string, any> = {};
|
||||||
|
|
||||||
|
@ -118,10 +106,6 @@ function fromFormat( {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if both arrays of formats up until a certain index are equal.
|
* Checks if both arrays of formats up until a certain index are equal.
|
||||||
*
|
|
||||||
* @param {Array} a Array of formats to compare.
|
|
||||||
* @param {Array} b Array of formats to compare.
|
|
||||||
* @param {number} index Index to check until.
|
|
||||||
*/
|
*/
|
||||||
function isEqualUntil<T>( a: T[], b: T[], index: number ): boolean {
|
function isEqualUntil<T>( a: T[], b: T[], index: number ): boolean {
|
||||||
do {
|
do {
|
||||||
|
@ -133,37 +117,40 @@ function isEqualUntil<T>( a: T[], b: T[], index: number ): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toTree( {
|
export function toTree(
|
||||||
value,
|
{
|
||||||
preserveWhiteSpace,
|
value,
|
||||||
createEmpty,
|
preserveWhiteSpace,
|
||||||
append,
|
createEmpty,
|
||||||
getLastChild,
|
append,
|
||||||
getParent,
|
getLastChild,
|
||||||
isText,
|
getParent,
|
||||||
getText,
|
isText,
|
||||||
remove,
|
getText,
|
||||||
appendText,
|
remove,
|
||||||
onStartIndex,
|
appendText,
|
||||||
onEndIndex,
|
onStartIndex,
|
||||||
isEditableTree,
|
onEndIndex,
|
||||||
placeholder,
|
isEditableTree,
|
||||||
}: {
|
placeholder,
|
||||||
value: RichTextValue,
|
}: {
|
||||||
preserveWhiteSpace?: boolean,
|
value: RichTextValue,
|
||||||
createEmpty: Function,
|
preserveWhiteSpace?: boolean,
|
||||||
append: Function,
|
createEmpty: Function,
|
||||||
getLastChild: Function,
|
append: Function,
|
||||||
getParent: Function,
|
getLastChild: Function,
|
||||||
isText: Function,
|
getParent: Function,
|
||||||
getText: Function,
|
isText: Function,
|
||||||
remove: Function,
|
getText: Function,
|
||||||
appendText: Function,
|
remove: Function,
|
||||||
onStartIndex?: Function,
|
appendText: Function,
|
||||||
onEndIndex?: Function,
|
onStartIndex?: Function,
|
||||||
isEditableTree?: boolean,
|
onEndIndex?: Function,
|
||||||
placeholder?: string,
|
isEditableTree?: boolean,
|
||||||
}) {
|
placeholder?: string,
|
||||||
|
},
|
||||||
|
store: FormatTypeStore,
|
||||||
|
) {
|
||||||
const { formats, replacements, text, start, end } = value;
|
const { formats, replacements, text, start, end } = value;
|
||||||
const formatsLength = formats.length + 1;
|
const formatsLength = formats.length + 1;
|
||||||
const tree = createEmpty();
|
const tree = createEmpty();
|
||||||
|
@ -220,7 +207,7 @@ export function toTree( {
|
||||||
unregisteredAttributes,
|
unregisteredAttributes,
|
||||||
boundaryClass,
|
boundaryClass,
|
||||||
isEditableTree,
|
isEditableTree,
|
||||||
} )
|
}, store )
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( isText( pointer ) && getText( pointer ).length === 0 ) {
|
if ( isText( pointer ) && getText( pointer ).length === 0 ) {
|
||||||
|
@ -248,7 +235,7 @@ export function toTree( {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { type, attributes, innerHTML } = replacement;
|
const { type, attributes, innerHTML } = replacement;
|
||||||
const formatType = getFormatType( type );
|
const formatType = store.getFormatTypeByName( type );
|
||||||
|
|
||||||
if ( ! isEditableTree && type === 'script' ) {
|
if ( ! isEditableTree && type === 'script' ) {
|
||||||
pointer = append(
|
pointer = append(
|
||||||
|
@ -256,7 +243,7 @@ export function toTree( {
|
||||||
fromFormat( {
|
fromFormat( {
|
||||||
type: 'script',
|
type: 'script',
|
||||||
isEditableTree,
|
isEditableTree,
|
||||||
} )
|
}, store )
|
||||||
);
|
);
|
||||||
append( pointer, {
|
append( pointer, {
|
||||||
html: decodeURIComponent(
|
html: decodeURIComponent(
|
||||||
|
@ -271,7 +258,7 @@ export function toTree( {
|
||||||
...replacement,
|
...replacement,
|
||||||
isEditableTree,
|
isEditableTree,
|
||||||
boundaryClass: start === i && end === i + 1,
|
boundaryClass: start === i && end === i + 1,
|
||||||
} )
|
}, store )
|
||||||
);
|
);
|
||||||
|
|
||||||
if ( innerHTML ) {
|
if ( innerHTML ) {
|
||||||
|
@ -286,7 +273,7 @@ export function toTree( {
|
||||||
...replacement,
|
...replacement,
|
||||||
object: true,
|
object: true,
|
||||||
isEditableTree,
|
isEditableTree,
|
||||||
} )
|
}, store )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Ensure pointer is text node.
|
// Ensure pointer is text node.
|
||||||
|
|
|
@ -6,17 +6,27 @@ import {
|
||||||
import { RichTextFormatType } from './types';
|
import { RichTextFormatType } from './types';
|
||||||
|
|
||||||
export const SymFormatTypes = Symbol('Schlechtenburg rich text formats');
|
export const SymFormatTypes = Symbol('Schlechtenburg rich text formats');
|
||||||
export function useFormatTypes() {
|
export interface FormatTypeStore {
|
||||||
|
formatTypes: Ref<RichTextFormatType[]>;
|
||||||
|
registerFormatTypes: (types: RichTextFormatType|RichTextFormatType[]) => void;
|
||||||
|
unregisterFormatTypes: (types: string|string[]) => void;
|
||||||
|
findFormatType: (fn: (f:RichTextFormatType) => boolean) => RichTextFormatType|undefined;
|
||||||
|
getFormatTypeByName: (name: string) => RichTextFormatType|undefined;
|
||||||
|
getFormatTypeForClassName: (name: string) => RichTextFormatType|undefined;
|
||||||
|
getFormatTypeForBareElement: (name: string) => RichTextFormatType|undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFormatTypes(): FormatTypeStore {
|
||||||
const formatTypes: Ref<RichTextFormatType[]> = inject(SymFormatTypes, ref([]));
|
const formatTypes: Ref<RichTextFormatType[]> = inject(SymFormatTypes, ref([]));
|
||||||
|
|
||||||
const addFormatTypes = (typesToAdd: RichTextFormatType|RichTextFormatType[]) => {
|
const registerFormatTypes = (typesToAdd: RichTextFormatType|RichTextFormatType[]) => {
|
||||||
formatTypes.value = [
|
formatTypes.value = [
|
||||||
...formatTypes.value,
|
...formatTypes.value,
|
||||||
...(Array.isArray(typesToAdd) ? typesToAdd : [typesToAdd]),
|
...(Array.isArray(typesToAdd) ? typesToAdd : [typesToAdd]),
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeFormatTypes = (typesToRemove: string|string[]) => {
|
const unregisterFormatTypes = (typesToRemove: string|string[]) => {
|
||||||
const isArray = Array.isArray(typesToRemove);
|
const isArray = Array.isArray(typesToRemove);
|
||||||
|
|
||||||
formatTypes.value = formatTypes.value.filter(({ name }) => {
|
formatTypes.value = formatTypes.value.filter(({ name }) => {
|
||||||
|
@ -29,16 +39,16 @@ export function useFormatTypes() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const findFormatType = (fn: (f:RichTextFormatType) => boolean) => formatTypes.value.find(type => fn(type));
|
const findFormatType = (fn: (f:RichTextFormatType) => boolean) => formatTypes.value.find(type => fn(type));
|
||||||
const findFormatTypeByName = (name: string) => formatTypes.value.find(type => type.name === name);
|
const getFormatTypeByName = (name: string) => formatTypes.value.find(type => type.name === name);
|
||||||
const getFormatTypeForClassName = (name: string) => formatTypes.value.find(type => type.className === name);
|
const getFormatTypeForClassName = (name: string) => formatTypes.value.find(type => type.className === name);
|
||||||
const getFormatTypeForBareElement = (name: string) => formatTypes.value.find(type => type.tagName === name);
|
const getFormatTypeForBareElement = (name: string) => formatTypes.value.find(type => type.tagName === name);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
formatTypes,
|
formatTypes,
|
||||||
addFormatTypes,
|
registerFormatTypes,
|
||||||
removeFormatTypes,
|
unregisterFormatTypes,
|
||||||
findFormatTypeByName,
|
|
||||||
findFormatType,
|
findFormatType,
|
||||||
|
getFormatTypeByName,
|
||||||
getFormatTypeForClassName,
|
getFormatTypeForClassName,
|
||||||
getFormatTypeForBareElement,
|
getFormatTypeForBareElement,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue