test: fix tests

This commit is contained in:
b12f 2024-10-09 16:47:31 +02:00
parent 9ff091bd39
commit a51aad42ee
Signed by: b12f
GPG key ID: 729956E1124F8F26
8 changed files with 198 additions and 226 deletions

View file

@ -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);

View file

@ -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> = {};

View file

@ -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 ] );

View file

@ -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 } );
} ); } );

View file

@ -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 );

View file

@ -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 );

View file

@ -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.

View file

@ -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,
}; };