slate#Text TypeScript Examples
The following examples show how to use
slate#Text.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: utils.ts From slate-ot with MIT License | 7 votes |
checkOp = (snapshot: Editor, op: Operation) => {
switch (op.type) {
case 'remove_text': {
const leaf = Node.leaf(snapshot, op.path);
const textToRemove = leaf.text.slice(
op.offset,
op.offset + op.text.length
);
expect(textToRemove).toBe(op.text);
break;
}
case 'merge_node': {
const prev = Node.get(snapshot, Path.previous(op.path));
const prevLen = Text.isText(prev)
? prev.text.length
: prev.children.length;
expect(prevLen).toBe(op.position);
break;
}
case 'remove_node': {
// op.node needs to be checked
break;
}
default:
return;
}
}
Example #2
Source File: MessageFormatEditor.tsx From project-loved-web with MIT License | 6 votes |
export default function MessageFormatEditor({
className,
readOnly,
setValue,
value,
...props
}: MessageFormatEditorProps & TextareaHTMLAttributes<HTMLDivElement>) {
const editor = useMemo(() => withReact(createEditor()), []);
return (
<Slate
editor={editor}
onChange={(value) => {
if (!readOnly) {
setValue!(((value[0] as Element).children[0] as Text).text);
}
}}
value={[{ children: [{ text: value, type: 0 }] }]}
>
<Editable
{...props}
className={`slate-editor${readOnly ? '' : ' editable'} ${className ?? ''}`}
decorate={decorate}
onKeyDown={onKeyDown}
readOnly={readOnly}
renderLeaf={renderLeaf}
/>
</Slate>
);
}
Example #3
Source File: advanced-editable.component.ts From slate-angular with MIT License | 6 votes |
generateDecorate(keywords: string) {
this.decorate = ([node, path]) => {
const ranges = [];
if (keywords && Text.isText(node)) {
const { text } = node;
const parts = text.split(keywords);
let offset = 0;
parts.forEach((part, i) => {
if (i !== 0) {
ranges.push({
anchor: { path, offset: offset - keywords.length },
focus: { path, offset },
highlight: true,
});
}
offset = offset + part.length + keywords.length;
});
}
return ranges;
};
}
Example #4
Source File: leaves.component.ts From slate-angular with MIT License | 6 votes |
ngOnChanges(simpleChanges: SimpleChanges) {
if (!this.initialized) {
return;
}
const context = simpleChanges['context'];
const previousValue: SlateTextContext = context.previousValue;
const currentValue: SlateTextContext = context.currentValue;
if (previousValue.text !== currentValue.text || !isDecoratorRangeListEqual(previousValue.decorations, currentValue.decorations)) {
this.leaves = Text.decorations(this.context.text, this.context.decorations);
}
this.leafContexts = this.getLeafCotexts();
}
Example #5
Source File: search-highlighting.component.ts From slate-angular with MIT License | 6 votes |
generateDecorate() {
this.decorate = ([node, path]) => {
const ranges = [];
if (this.keywords && Text.isText(node)) {
const { text } = node;
const parts = text.split(this.keywords);
let offset = 0;
parts.forEach((part, i) => {
if (i !== 0) {
ranges.push({
anchor: { path, offset: offset - this.keywords.length },
focus: { path, offset },
highlight: true,
});
}
offset = offset + part.length + this.keywords.length;
});
}
return ranges;
};
}
Example #6
Source File: query_field.tsx From grafana-chinese with Apache License 2.0 | 6 votes |
makeFragment = (text: string) => {
const lines = text.split('\n').map((line: any) =>
Block.create({
type: 'paragraph',
nodes: [Text.create(line)],
} as any)
);
const fragment = Document.create({
nodes: lines,
});
return fragment;
}
Example #7
Source File: slate.ts From grafana-chinese with Apache License 2.0 | 6 votes |
makeFragment = (text: string, syntax?: string): Document => {
const lines = text.split('\n').map(line =>
Block.create({
type: 'code_line',
nodes: [Text.create(line)],
})
);
const block = Block.create({
data: {
syntax,
},
type: 'code_block',
nodes: lines,
});
return Document.create({
nodes: [block],
});
}
Example #8
Source File: op-generator.ts From slate-ot with MIT License | 6 votes |
generateRandomSplitNodeOp = (snapshot): Operation | null => {
const randomPath = getRandomPathFrom(snapshot);
const node = Node.get(snapshot, randomPath);
const position = Text.isText(node)
? <number>fuzzer.randomInt(node.text.length + 1)
: <number>fuzzer.randomInt(node.children.length + 1);
return randomPath.length
? {
type: 'split_node',
path: randomPath,
position,
target: null,
properties: {},
}
: null;
}
Example #9
Source File: op-generator.ts From slate-ot with MIT License | 6 votes |
generateRandomInsertNodeOp = (snapshot): Operation => {
const randomPath = getRandomPathTo(snapshot);
const parent = <Ancestor>Node.get(snapshot, Path.parent(randomPath));
let node;
if (parent.children[0] && Text.isText(parent.children[0])) {
node = { text: fuzzer.randomWord() };
} else if (!parent.children[0] && fuzzer.randomInt(3) === 0) {
node = { text: fuzzer.randomWord() };
} else if (fuzzer.randomInt(2) === 0) {
node = {
type: BLOCKS[fuzzer.randomInt(BLOCKS.length)],
children: [{ text: fuzzer.randomWord() }, { text: fuzzer.randomWord() }],
};
} else {
node = {
type: BLOCKS[fuzzer.randomInt(BLOCKS.length)],
children: [
{
type: BLOCKS[fuzzer.randomInt(BLOCKS.length)],
children: [
{ text: fuzzer.randomWord() },
{ text: fuzzer.randomWord() },
],
},
],
};
}
return {
type: 'insert_node',
path: randomPath,
node,
};
}
Example #10
Source File: op-generator.ts From slate-ot with MIT License | 6 votes |
getRandomPathTo = (snapshot: Node): Path => {
const path: Path = [];
// let currentNode = snapshot;
while (1) {
// stop when you get to a leaf
if (Text.isText(snapshot)) {
return path;
}
if (snapshot.children.length === 0) {
return [...path, 0];
}
// randomly stop at the next level
if (fuzzer.randomInt(3) === 0) {
const index = <number>fuzzer.randomInt(snapshot.children.length + 1);
return [...path, index];
}
// continue
const index = <number>fuzzer.randomInt(snapshot.children.length);
path.push(index);
snapshot = snapshot.children[index];
}
return path;
}
Example #11
Source File: op-generator.ts From slate-ot with MIT License | 6 votes |
getRandomPathFrom = (snapshot: Node): Path => {
if (Text.isText(snapshot) || snapshot.children.length === 0) {
return [];
}
const path: Path = [];
// let currentNode = snapshot;
while (1) {
// stop when you get to a leaf
if (Text.isText(snapshot)) {
return path;
}
if (snapshot.children.length === 0) {
return path;
}
if (fuzzer.randomInt(3) === 0 && path.length > 0) {
return path;
}
// continue
const index = <number>fuzzer.randomInt(snapshot.children.length);
path.push(index);
snapshot = snapshot.children[index];
}
return path;
}
Example #12
Source File: MessageFormatEditor.tsx From project-loved-web with MIT License | 6 votes |
function decorate([node, path]: NodeEntry): Range[] {
if (!Text.isText(node)) {
return [];
}
const ranges: Range[] = [];
try {
addRanges(ranges, path, parse(node.text, { captureLocation: true }));
} catch {}
return ranges;
}
Example #13
Source File: leaf.tsx From slate-vue with MIT License | 5 votes |
Leaf = tsx.component({
props: {
text: {
type: Object as PropType<Text>
},
leaf: {
type: Object as PropType<Text>
},
},
inject: ['renderLeaf'],
components: {
string,
fragment
},
// For types
data(): Pick<providedByEditable, 'renderLeaf'> {
return {}
},
render(h): VNode {
const { renderLeaf = DefaultLeaf, text, leaf} = this;
let children = (
<string text={text} editor={this.$editor} leaf={leaf}/>
);
if ((leaf as any)[PLACEHOLDER_SYMBOL]) {
children = (
<fragment>
<span
contenteditable={false}
style={{
pointerEvents: 'none',
display: 'inline-block',
verticalAlign: 'text-top',
width: '0',
maxWidth: '100%',
whiteSpace: 'nowrap',
opacity: '0.333',
}}
>
{(leaf as any).placeholder}
</span>
{children}
</fragment>
)
}
const attributes: {
'data-slate-leaf': true
} = {
'data-slate-leaf': true,
};
const renderChildren = renderLeaf({
children,
attributes,
leaf,
text
})
return h(renderChildren)
}
})
Example #14
Source File: global-normalize.ts From slate-angular with MIT License | 5 votes |
isValid = (value: Descendant) => (
Element.isElement(value) &&
value.children.length > 0 &&
(value.children as Descendant[]).every((child) => isValid(child))) ||
Text.isText(value)
Example #15
Source File: advanced-editable.component.ts From slate-angular with MIT License | 5 votes |
renderLeaf = (text: Text) => {
if (text['highlight']) {
return TestingLeafComponent;
}
return null;
}
Example #16
Source File: leaves.component.ts From slate-angular with MIT License | 5 votes |
leaves: Text[];
Example #17
Source File: leaves.component.ts From slate-angular with MIT License | 5 votes |
ngOnInit() {
this.leaves = Text.decorations(this.context.text, this.context.decorations);
this.leafContexts = this.getLeafCotexts();
this.initialized = true;
}
Example #18
Source File: tables.component.ts From slate-angular with MIT License | 5 votes |
renderText = (text: Text) => {
if (text[MarkTypes.bold] || text[MarkTypes.italic] || text[MarkTypes.code] || text[MarkTypes.underline]) {
return DemoTextMarkComponent;
}
}
Example #19
Source File: search-highlighting.component.ts From slate-angular with MIT License | 5 votes |
renderLeaf = (text: Text) => {
if (text['highlight']) {
return DemoLeafComponent;
}
return null;
}
Example #20
Source File: search-highlighting.component.ts From slate-angular with MIT License | 5 votes |
renderText = (text: Text) => {
if (text[MarkTypes.bold] || text[MarkTypes.italic] || text[MarkTypes.code] || text[MarkTypes.underline]) {
return DemoTextMarkComponent;
}
}
Example #21
Source File: richtext.component.ts From slate-angular with MIT License | 5 votes |
renderText = (text: Text) => {
if (text[MarkTypes.bold] || text[MarkTypes.italic] || text[MarkTypes.code] || text[MarkTypes.underline]) {
return DemoTextMarkComponent;
}
}
Example #22
Source File: string.tsx From slate-vue with MIT License | 5 votes |
string = tsx.component({
props: {
leaf: {
type: Object as PropType<Text>
},
editor: Object
},
inject: ['isLast', 'parent', 'text'],
components: {
TextString
},
data(): providedByText {
return {}
},
render() {
const { leaf, editor, isLast, parent, text } = this
const path = VueEditor.findPath(editor, text as Node)
const parentPath = Path.parent(path)
// COMPAT: Render text inside void nodes with a zero-width space.
// So the node can contain selection but the text is not visible.
if (editor.isVoid(parent)) {
return <ZeroWidthString length={Node.string(parent as Node).length} />
}
// COMPAT: If this is the last text node in an empty block, render a zero-
// width space that will convert into a line break when copying and pasting
// to support expected plain text.
if (
leaf.text === '' &&
(parent as Element & Editor).children[(parent as Element & Editor).children.length - 1] === text &&
!editor.isInline(parent) &&
Editor.string(editor, parentPath) === ''
) {
return <ZeroWidthString isLineBreak={true} />
}
// COMPAT: If the text is empty, it's because it's on the edge of an inline
// node, so we render a zero-width space so that the selection can be
// inserted next to it still.
if (leaf.text === '') {
return <ZeroWidthString />
}
// COMPAT: Browsers will collapse trailing new lines at the end of blocks,
// so we need to add an extra trailing new lines to prevent that.
if (isLast && leaf.text.slice(-1) === '\n') {
return <TextString isTrailing={true} text={leaf.text} />
}
return <TextString text={leaf.text} />
}
})
Example #23
Source File: index.ts From grafana-chinese with Apache License 2.0 | 5 votes |
/**
* Return a decoration range for the given text.
*/
function createDecoration({
text,
textStart,
textEnd,
start,
end,
className,
block,
}: {
text: Text; // The text being decorated
textStart: number; // Its start position in the whole text
textEnd: number; // Its end position in the whole text
start: number; // The position in the whole text where the token starts
end: number; // The position in the whole text where the token ends
className: string; // The prism token classname
block: Block;
}): Decoration | null {
if (start >= textEnd || end <= textStart) {
// Ignore, the token is not in the text
return null;
}
// Shrink to this text boundaries
start = Math.max(start, textStart);
end = Math.min(end, textEnd);
// Now shift offsets to be relative to this text
start -= textStart;
end -= textStart;
const myDec = block.createDecoration({
object: 'decoration',
anchor: {
key: text.key,
offset: start,
object: 'point',
},
focus: {
key: text.key,
offset: end,
object: 'point',
},
type: TOKEN_MARK,
data: { className },
});
return myDec;
}
Example #24
Source File: runtime-util.ts From slate-vue with MIT License | 5 votes |
transform = function(editor: Editor, op: Operation) {
switch (op.type) {
case 'insert_node': {
const { path, node } = op
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
getChildren(parent).splice(index, 0, clone(node))
break
}
case 'insert_text': {
const { path, offset, text } = op
const node = Node.leaf(editor, path)
const before = node.text.slice(0, offset)
const after = node.text.slice(offset)
node.text = before + text + after
break
}
case 'merge_node': {
const { path } = op
const node = Node.get(editor, path)
const prevPath = Path.previous(path)
const prev = Node.get(editor, prevPath)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
if (Text.isText(node) && Text.isText(prev)) {
prev.text += node.text
} else if (!Text.isText(node) && !Text.isText(prev)) {
getChildren(prev).push(...getChildren(node))
} else {
throw new Error(
`Cannot apply a "merge_node" operation at path [${path}] to nodes of different interaces: ${node} ${prev}`
)
}
getChildren(parent).splice(index, 1)
break
}
case 'move_node': {
const { path, newPath } = op
if (Path.isAncestor(path, newPath)) {
throw new Error(
`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`
)
}
const node = Node.get(editor, path)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
// This is tricky, but since the `path` and `newPath` both refer to
// the same snapshot in time, there's a mismatch. After either
// removing the original position, the second step's path can be out
// of date. So instead of using the `op.newPath` directly, we
// transform `op.path` to ascertain what the `newPath` would be after
// the operation was applied.
getChildren(parent).splice(index, 1)
const truePath = Path.transform(path, op)!
const newParent = Node.get(editor, Path.parent(truePath))
const newIndex = truePath[truePath.length - 1]
getChildren(newParent).splice(newIndex, 0, node)
break
}
case 'remove_node': {
const { path } = op
NODE_TO_KEY.delete(Node.get(editor, path))
const index = path[path.length - 1]
const parent = Node.parent(editor, path)
getChildren(parent).splice(index, 1)
break
}
case 'remove_text': {
const { path, offset, text } = op
const node = Node.leaf(editor, path)
const before = node.text.slice(0, offset)
const after = node.text.slice(offset + text.length)
node.text = before + after
break
}
case 'set_node': {
const { path, newProperties } = op
if (path.length === 0) {
throw new Error(`Cannot set properties on the root node!`)
}
const node = Node.get(editor, path)
for (const key in newProperties) {
if (key === 'children' || key === 'text') {
throw new Error(`Cannot set the "${key}" property of nodes!`)
}
const value = (newProperties as any)[key]
if (value == null) {
Vue.delete(node, key)
} else {
Vue.set(node, key, value)
}
}
break
}
case 'set_selection': {
break
}
case 'split_node': {
const { path, position, properties } = op
if (path.length === 0) {
throw new Error(
`Cannot apply a "split_node" operation at path [${path}] because the root node cannot be split.`
)
}
const node = Node.get(editor, path)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
let newNode: Descendant
if (Text.isText(node)) {
const before = node.text.slice(0, position)
const after = node.text.slice(position)
node.text = before
newNode = {
...node,
...(properties as Partial<Text>),
text: after,
}
} else {
const before = node.children.slice(0, position)
const after = node.children.slice(position)
node.children = before
newNode = {
...node,
...(properties as Partial<Element>),
children: after,
}
}
getChildren(parent).splice(index + 1, 0, newNode)
break
}
}
}
Example #25
Source File: op-generator.ts From slate-ot with MIT License | 5 votes |
generateRandomMergeNodeOp = (snapshot): Operation | null => {
const randomPath = getRandomPathFrom(snapshot);
if (randomPath.length == 0 || randomPath[randomPath.length - 1] == 0) {
return null;
}
const prev = Node.get(snapshot, Path.previous(randomPath));
const node = Node.get(snapshot, randomPath);
const properties = {};
// Object.keys(prev).forEach((key) => {
// if (key !== 'text' && key !== 'children') {
// properties[key] = null;
// }
// });
// Object.keys(node).forEach((key) => {
// if (key !== 'text' && key !== 'children') {
// properties[key] = node[key];
// }
// });
if (Text.isText(prev) && Text.isText(node)) {
return {
type: 'merge_node',
path: randomPath,
position: prev.text.length,
target: null,
properties,
};
}
if (!Text.isText(prev) && !Text.isText(node)) {
return {
type: 'merge_node',
path: randomPath,
position: prev.children.length,
target: null,
properties,
};
}
return null;
}
Example #26
Source File: runtime-util.ts From slate-vue with MIT License | 5 votes |
runtimeNode = {
child(root: Node, index: number): Descendant {
if (Text.isText(root)) {
throw new Error(
`Cannot get the child of a text node: ${JSON.stringify(root)}`
)
}
const c = getChildren(root)[index] as Descendant
if (c == null) {
throw new Error(
`Cannot get child at index \`${index}\` in node: ${JSON.stringify(
root
)}`
)
}
return c
},
has(root: Node, path: Path): boolean {
let node = root
for (let i = 0; i < path.length; i++) {
const p = path[i]
const children = getChildren(node)
if (Text.isText(node) || !children[p]) {
return false
}
node = children[p]
}
return true
},
get(root: Node, path: Path): Node {
let node = root
for (let i = 0; i < path.length; i++) {
const p = path[i]
const children = getChildren(node)
if (Text.isText(node) || !children[p]) {
throw new Error(
`Cannot find a descendant at path [${path}] in node: ${JSON.stringify(
root
)}`
)
}
node = children[p]
}
return node
},
first(root: Node, path: Path): NodeEntry {
const p = path.slice()
let n = Node.get(root, p)
const children = getChildren(n)
while (n) {
if (Text.isText(n) || children.length === 0) {
break
} else {
n = children[0]
p.push(0)
}
}
return [n, p]
},
last(root: Node, path: Path): NodeEntry {
const p = path.slice()
let n = Node.get(root, p)
const children = getChildren(n)
while (n) {
if (Text.isText(n) || children.length === 0) {
break
} else {
const i = children.length - 1
n = children[i]
p.push(i)
}
}
return [n, p]
},
*nodes(
root: Node,
options: {
from?: Path
to?: Path
reverse?: boolean
pass?: (entry: NodeEntry) => boolean
} = {}
): Generator<NodeEntry> {
const { pass, reverse = false } = options
const { from = [], to } = options
const visited = new Set()
let p: Path = []
let n = root
while (true) {
if (to && (reverse ? Path.isBefore(p, to) : Path.isAfter(p, to))) {
break
}
if (!visited.has(n)) {
yield [n, p]
}
// If we're allowed to go downward and we haven't decsended yet, do.
if (
!visited.has(n) &&
!Text.isText(n) &&
getChildren(n).length !== 0 &&
(pass == null || pass([n, p]) === false)
) {
visited.add(n)
let nextIndex = reverse ? getChildren(n).length - 1 : 0
if (Path.isAncestor(p, from)) {
nextIndex = from[p.length]
}
p = p.concat(nextIndex)
n = Node.get(root, p)
continue
}
// If we're at the root and we can't go down, we're done.
if (p.length === 0) {
break
}
// If we're going forward...
if (!reverse) {
const newPath = Path.next(p)
if (Node.has(root, newPath)) {
p = newPath
n = Node.get(root, p)
continue
}
}
// If we're going backward...
if (reverse && p[p.length - 1] !== 0) {
const newPath = Path.previous(p)
p = newPath
n = Node.get(root, p)
continue
}
// Otherwise we're going upward...
p = Path.parent(p)
n = Node.get(root, p)
visited.add(n)
}
}
}
Example #27
Source File: runtime-util.ts From slate-vue with MIT License | 5 votes |
transform = function(editor: Editor, op: Operation, Vue?: any) {
switch (op.type) {
case 'insert_node': {
const { path, node } = op
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
getChildren(parent).splice(index, 0, clone(node))
break
}
case 'insert_text': {
const { path, offset, text } = op
const node = Node.leaf(editor, path)
const before = node.text.slice(0, offset)
const after = node.text.slice(offset)
node.text = before + text + after
break
}
case 'merge_node': {
const { path } = op
const node = Node.get(editor, path)
const prevPath = Path.previous(path)
const prev = Node.get(editor, prevPath)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
if (Text.isText(node) && Text.isText(prev)) {
prev.text += node.text
} else if (!Text.isText(node) && !Text.isText(prev)) {
getChildren(prev).push(...getChildren(node))
} else {
throw new Error(
`Cannot apply a "merge_node" operation at path [${path}] to nodes of different interaces: ${node} ${prev}`
)
}
getChildren(parent).splice(index, 1)
break
}
case 'move_node': {
const { path, newPath } = op
if (Path.isAncestor(path, newPath)) {
throw new Error(
`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`
)
}
const node = Node.get(editor, path)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
// This is tricky, but since the `path` and `newPath` both refer to
// the same snapshot in time, there's a mismatch. After either
// removing the original position, the second step's path can be out
// of date. So instead of using the `op.newPath` directly, we
// transform `op.path` to ascertain what the `newPath` would be after
// the operation was applied.
getChildren(parent).splice(index, 1)
const truePath = Path.transform(path, op)!
const newParent = Node.get(editor, Path.parent(truePath))
const newIndex = truePath[truePath.length - 1]
getChildren(newParent).splice(newIndex, 0, node)
break
}
case 'remove_node': {
const { path } = op
NODE_TO_KEY.delete(Node.get(editor, path))
const index = path[path.length - 1]
const parent = Node.parent(editor, path)
getChildren(parent).splice(index, 1)
break
}
case 'remove_text': {
const { path, offset, text } = op
const node = Node.leaf(editor, path)
const before = node.text.slice(0, offset)
const after = node.text.slice(offset + text.length)
node.text = before + after
break
}
case 'set_node': {
const { path, newProperties } = op
if (path.length === 0) {
throw new Error(`Cannot set properties on the root node!`)
}
const node = Node.get(editor, path)
for (const key in newProperties) {
if (key === 'children' || key === 'text') {
throw new Error(`Cannot set the "${key}" property of nodes!`)
}
const value = (newProperties as any)[key]
if(Vue) {
if (value == null) {
Vue.delete(node, key)
} else {
Vue.set(node, key, value)
}
}
}
break
}
case 'set_selection': {
break
}
case 'split_node': {
const { path, position, properties } = op
if (path.length === 0) {
throw new Error(
`Cannot apply a "split_node" operation at path [${path}] because the root node cannot be split.`
)
}
const node = Node.get(editor, path)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
let newNode: Descendant
if (Text.isText(node)) {
const before = node.text.slice(0, position)
const after = node.text.slice(position)
node.text = before
newNode = {
...node,
...(properties as Partial<Text>),
text: after,
}
} else {
const before = node.children.slice(0, position)
const after = node.children.slice(position)
node.children = before
newNode = {
...node,
...(properties as Partial<Element>),
children: after,
}
}
getChildren(parent).splice(index + 1, 0, newNode)
break
}
}
}
Example #28
Source File: runtime-util.ts From slate-vue with MIT License | 5 votes |
runtimeNode = {
child(root: Node, index: number): Descendant {
if (Text.isText(root)) {
throw new Error(
`Cannot get the child of a text node: ${JSON.stringify(root)}`
)
}
const c = getChildren(root)[index] as Descendant
if (c == null) {
throw new Error(
`Cannot get child at index \`${index}\` in node: ${JSON.stringify(
root
)}`
)
}
return c
},
has(root: Node, path: Path): boolean {
let node = root
for (let i = 0; i < path.length; i++) {
const p = path[i]
const children = getChildren(node)
if (Text.isText(node) || !children[p]) {
return false
}
node = children[p]
}
return true
},
get(root: Node, path: Path): Node {
let node = root
for (let i = 0; i < path.length; i++) {
const p = path[i]
const children = getChildren(node)
if (Text.isText(node) || !children[p]) {
throw new Error(
`Cannot find a descendant at path [${path}] in node: ${JSON.stringify(
root
)}`
)
}
node = children[p]
}
return node
},
first(root: Node, path: Path): NodeEntry {
const p = path.slice()
let n = Node.get(root, p)
const children = getChildren(n)
while (n) {
if (Text.isText(n) || children.length === 0) {
break
} else {
n = children[0]
p.push(0)
}
}
return [n, p]
},
last(root: Node, path: Path): NodeEntry {
const p = path.slice()
let n = Node.get(root, p)
const children = getChildren(n)
while (n) {
if (Text.isText(n) || children.length === 0) {
break
} else {
const i = children.length - 1
n = children[i]
p.push(i)
}
}
return [n, p]
},
*nodes(
root: Node,
options: {
from?: Path
to?: Path
reverse?: boolean
pass?: (entry: NodeEntry) => boolean
} = {}
): Generator<NodeEntry> {
const { pass, reverse = false } = options
const { from = [], to } = options
const visited = new Set()
let p: Path = []
let n = root
while (true) {
if (to && (reverse ? Path.isBefore(p, to) : Path.isAfter(p, to))) {
break
}
if (!visited.has(n)) {
yield [n, p]
}
// If we're allowed to go downward and we haven't decsended yet, do.
if (
!visited.has(n) &&
!Text.isText(n) &&
getChildren(n).length !== 0 &&
(pass == null || pass([n, p]) === false)
) {
visited.add(n)
let nextIndex = reverse ? getChildren(n).length - 1 : 0
if (Path.isAncestor(p, from)) {
nextIndex = from[p.length]
}
p = p.concat(nextIndex)
n = Node.get(root, p)
continue
}
// If we're at the root and we can't go down, we're done.
if (p.length === 0) {
break
}
// If we're going forward...
if (!reverse) {
const newPath = Path.next(p)
if (Node.has(root, newPath)) {
p = newPath
n = Node.get(root, p)
continue
}
}
// If we're going backward...
if (reverse && p[p.length - 1] !== 0) {
const newPath = Path.previous(p)
p = newPath
n = Node.get(root, p)
continue
}
// Otherwise we're going upward...
p = Path.parent(p)
n = Node.get(root, p)
visited.add(n)
}
}
}
Example #29
Source File: stringifyRichText.ts From payload with MIT License | 5 votes |
stringifyRichText = (content: unknown): string => {
if (Array.isArray(content)) {
return content.reduce((output, node) => {
const isTextNode = Text.isText(node);
const {
text,
} = node;
if (isTextNode) {
// convert straight single quotations to curly
// "\u201C" is starting double curly
// "\u201D" is ending double curly
const sanitizedText = text?.replace(/'/g, '\u2019'); // single quotes
return `${output}${sanitizedText}`;
}
if (node) {
let nodeHTML;
switch (node.type) {
case 'h1':
case 'h2':
case 'h3':
case 'h4':
case 'h5':
case 'h6':
case 'li':
case 'p':
case undefined:
nodeHTML = `${stringifyRichText(node.children)}\n`;
break;
case 'ul':
case 'ol':
nodeHTML = `${stringifyRichText(node.children)}\n\n`;
break;
case 'link':
nodeHTML = `${stringifyRichText(node.children)}`;
break;
case 'relationship':
nodeHTML = `Relationship to ${node.relationTo}: ${node?.value?.id}\n\n`;
break;
case 'upload':
nodeHTML = `${node.relationTo} Upload: ${node?.value?.id}\n\n`;
break;
default:
nodeHTML = `${node.type}: ${JSON.stringify(node)}\n\n`;
break;
}
return `${output}${nodeHTML}`;
}
return output;
}, '');
}
return '';
}