-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* FormattingNode#unformat mehtod & some tests refactoring * Add RootInlineNode & start integration tests * build fixes * Lint fixes * Move common methods of RootInlineNode and FormattingNode to a separate class. Add normalization. Add more tests * Apply suggestions from code review Co-authored-by: Peter Savchenko <specc.dev@gmail.com> * updates after review * Update src/entities/BlockNode/index.ts Co-authored-by: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com> --------- Co-authored-by: Peter Savchenko <specc.dev@gmail.com> Co-authored-by: Ilya Maroz <37909603+ilyamore88@users.noreply.github.com>
- Loading branch information
1 parent
6cdd08d
commit 76035db
Showing
31 changed files
with
1,046 additions
and
278 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,9 @@ | ||
import { DataKey } from './DataKey'; | ||
import { TextNode } from '../../TextNode'; | ||
import { ValueNode } from '../../ValueNode'; | ||
import { FormattingNode } from '../../FormattingNode'; | ||
import { TextInlineNode, FormattingInlineNode } from '../../inline-fragments'; | ||
|
||
/** | ||
* Represents a record object containing the data of a block node. | ||
* Each root node is associated with a specific data key. | ||
*/ | ||
export type BlockNodeData = Record<DataKey, ValueNode | (FormattingNode | TextNode)[]>; | ||
export type BlockNodeData = Record<DataKey, ValueNode | (FormattingInlineNode | TextInlineNode)[]>; |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
247 changes: 247 additions & 0 deletions
247
src/entities/inline-fragments/FormattingInlineNode/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
import { | ||
FormattingInlineNodeConstructorParameters, | ||
InlineToolName, | ||
InlineToolData | ||
} from './types'; | ||
import type { InlineFragment, InlineNode } from '../InlineNode'; | ||
import { ParentNode } from '../mixins/ParentNode'; | ||
import { ChildNode } from '../mixins/ChildNode'; | ||
import { ParentInlineNode } from '../ParentInlineNode'; | ||
|
||
export * from './types'; | ||
|
||
/** | ||
* We need to extend FormattingInlineNode interface with ChildNode and ParentNode ones to use the methods from mixins | ||
*/ | ||
export interface FormattingInlineNode extends ChildNode {} | ||
|
||
/** | ||
* FormattingInlineNode class represents a node in a tree-like structure, used to store and manipulate formatted text content | ||
*/ | ||
@ParentNode | ||
@ChildNode | ||
export class FormattingInlineNode extends ParentInlineNode implements InlineNode { | ||
/** | ||
* Property representing the name of the formatting tool applied to the content | ||
*/ | ||
public readonly tool: InlineToolName; | ||
|
||
/** | ||
* Any additional data associated with the formatting tool | ||
*/ | ||
public readonly data?: InlineToolData; | ||
|
||
/** | ||
* Constructor for FormattingInlineNode class. | ||
* | ||
* @param args - FormattingInlineNode constructor arguments. | ||
* @param args.tool - The name of the formatting tool applied to the content. | ||
* @param args.data - Any additional data associated with the formatting. | ||
*/ | ||
// Stryker disable next-line BlockStatement -- Styker's bug, see https://github.com/stryker-mutator/stryker-js/issues/2474 | ||
constructor({ tool, data }: FormattingInlineNodeConstructorParameters) { | ||
super(); | ||
|
||
this.tool = tool; | ||
this.data = data; | ||
} | ||
|
||
/** | ||
* Removes text from the specified range. If there is no text left in a node, removes a node from a parent. | ||
* | ||
* @param [start] - start char index of the range, by default 0 | ||
* @param [end] - end char index of the range, by default length of the text value | ||
* @returns {string} removed text | ||
*/ | ||
public removeText(start = 0, end = this.length): string { | ||
const result = super.removeText(start, end); | ||
|
||
if (this.length === 0) { | ||
this.remove(); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
/** | ||
* Returns inline fragments for node from the specified character range | ||
* | ||
* If start and/or end is specified, method will return partial fragments for the specified range | ||
* | ||
* @param [start] - start char index of the range, by default 0 | ||
* @param [end] - end char index of the range, by default length of the text value | ||
*/ | ||
public getFragments(start = 0, end = this.length): InlineFragment[] { | ||
const fragments = super.getFragments(start, end); | ||
|
||
const currentFragment: InlineFragment = { | ||
tool: this.tool, | ||
range: [start, end], | ||
}; | ||
|
||
if (this.data) { | ||
currentFragment.data = this.data; | ||
} | ||
|
||
/** | ||
* Current node is not processed in super.getFragments, so we need to add it manually at the beginning | ||
*/ | ||
fragments.unshift(currentFragment); | ||
|
||
return fragments; | ||
} | ||
|
||
/** | ||
* Splits current node by the specified index | ||
* | ||
* @param index - char index where to split the node | ||
* @returns {FormattingInlineNode | null} new node | ||
*/ | ||
public split(index: number): FormattingInlineNode | null { | ||
if (index === 0 || index === this.length) { | ||
return null; | ||
} | ||
|
||
const newNode = new FormattingInlineNode({ | ||
tool: this.tool, | ||
data: this.data, | ||
}); | ||
|
||
const [child, offset] = this.findChildByIndex(index); | ||
|
||
if (!child) { | ||
return null; | ||
} | ||
|
||
// Have to save length as it is changed after split | ||
const childLength = child.length; | ||
|
||
const splitNode = child.split(index - offset); | ||
let midNodeIndex = this.children.indexOf(child); | ||
|
||
/** | ||
* If node is split or if node is not split but index equals to child length, we should split children from the next node | ||
*/ | ||
if (splitNode || (index - offset === childLength)) { | ||
midNodeIndex += 1; | ||
} | ||
|
||
newNode.append(...this.children.slice(midNodeIndex)); | ||
|
||
this.parent?.insertAfter(this, newNode); | ||
|
||
return newNode; | ||
} | ||
|
||
/** | ||
* Applies formatting to the text with specified inline tool in the specified range | ||
* | ||
* @param tool - name of inline tool to apply | ||
* @param start - char start index of the range | ||
* @param end - char end index of the range | ||
* @param [data] - inline tool data if applicable | ||
*/ | ||
public format(tool: InlineToolName, start: number, end: number, data?: InlineToolData): InlineNode[] { | ||
/** | ||
* In case current tool is the same as new one, do nothing | ||
* | ||
* @todo Compare data as well | ||
*/ | ||
if (tool === this.tool) { | ||
return []; | ||
} | ||
|
||
|
||
return super.format(tool, start, end, data); | ||
} | ||
|
||
/** | ||
* Removes formatting from the text for a specified inline tool in the specified range | ||
* | ||
* @param tool - name of inline tool to remove | ||
* @param start - char start index of the range | ||
* @param end - char end index of the range | ||
* @todo Possibly pass data or some InlineTool identifier to relevant only required fragments | ||
*/ | ||
public unformat(tool: InlineToolName, start: number, end: number): InlineNode[] { | ||
if (this.tool === tool) { | ||
const middleNode = this.split(start); | ||
const endNode = middleNode?.split(end); | ||
|
||
const result: ChildNode[] = []; | ||
|
||
/** | ||
* If start > 0, then there is a middle node, so we'll need to append its children to the parent | ||
*/ | ||
if (middleNode) { | ||
result.push(this, ...middleNode.children); | ||
/** | ||
* Else we'll need to append current nodes children to the parent | ||
*/ | ||
} else { | ||
result.push(...this.children); | ||
} | ||
|
||
/** | ||
* If end < this.length, we just append it to the parent | ||
*/ | ||
if (endNode) { | ||
result.push(endNode); | ||
} | ||
|
||
this.parent?.insertAfter(this, ...result); | ||
|
||
if (middleNode) { | ||
middleNode.remove(); | ||
} else { | ||
this.remove(); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
return super.unformat(tool, start, end); | ||
} | ||
|
||
/** | ||
* Checks if node is equal to passed node | ||
* | ||
* @param node - node to check | ||
*/ | ||
public isEqual(node: InlineNode): node is FormattingInlineNode { | ||
if (!(node instanceof FormattingInlineNode)) { | ||
return false; | ||
} | ||
|
||
if (this.tool !== node.tool) { | ||
return false; | ||
} | ||
|
||
/** | ||
* @todo check data equality | ||
*/ | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Merges current node with passed node | ||
* | ||
* @param node - node to merge with | ||
*/ | ||
public mergeWith(node: InlineNode): void { | ||
if (!this.isEqual(node)) { | ||
throw new Error('Can not merge unequal nodes'); | ||
} | ||
|
||
/** | ||
* @todo merge data | ||
*/ | ||
|
||
node.children.forEach((child) => { | ||
this.append(child); | ||
}); | ||
|
||
node.remove(); | ||
} | ||
} |
5 changes: 3 additions & 2 deletions
5
...es/FormattingNodeConstructorParameters.ts → ...mattingInlineNodeConstructorParameters.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...es/FormattingNode/types/InlineToolData.ts → ...mattingInlineNode/types/InlineToolData.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
src/entities/FormattingNode/types/index.ts → ...ments/FormattingInlineNode/types/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
export { FormattingNodeConstructorParameters } from './FormattingNodeConstructorParameters'; | ||
export { FormattingInlineNodeConstructorParameters } from './FormattingInlineNodeConstructorParameters'; | ||
export { InlineToolName, createInlineToolName } from './InlineToolName'; | ||
export { InlineToolData, createInlineToolData } from './InlineToolData'; |
Oops, something went wrong.