Skip to content

Commit f8a52d7

Browse files
authored
Allow deletion of orphan nodes! (#420)
1 parent 59649e9 commit f8a52d7

File tree

3 files changed

+43
-4
lines changed

3 files changed

+43
-4
lines changed

packages/lexical/src/convert/markdown/convertToMarkdown.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
} from './utils';
2626

2727
export function $convertToMarkdownString(): string {
28-
const output = [];
28+
const output: string[] = [];
2929
const children = $getRoot().getChildren();
3030

3131
for (const child of children) {
@@ -56,7 +56,7 @@ function exportTopLevelElementOrDecorator(node: LexicalNode): string | null {
5656
}
5757

5858
function exportChildren(node: ElementNode): string {
59-
const output = [];
59+
const output: string[] = [];
6060
const children = node.getChildren();
6161

6262
for (const child of children) {

packages/lexical/src/nodes/JupyterOutputNode.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
Kernel,
2929
} from '@datalayer/jupyter-react';
3030
import { createNoKernelWarning } from './jupyterUtils';
31+
import { isJupyterOutputNodeOrphaned } from './JupyterOutputNodeUtils';
3132

3233
export type SerializedJupyterOutputNode = Spread<
3334
{
@@ -191,7 +192,10 @@ export class JupyterOutputNode extends DecoratorNode<JSX.Element> {
191192

192193
/** @override */
193194
isIsolated(): boolean {
194-
return false;
195+
// Treat orphaned output nodes as isolated blocks for editing purposes
196+
// Return true only for orphaned nodes so they are handled as isolated blocks
197+
// Normal output nodes with valid parents remain non-isolated
198+
return isJupyterOutputNodeOrphaned(this);
195199
}
196200

197201
/** @override */
@@ -223,7 +227,15 @@ export class JupyterOutputNode extends DecoratorNode<JSX.Element> {
223227

224228
/** @override */
225229
remove(_preserveEmptyParent?: boolean): void {
226-
// Do not delete JupyterOutputNode.
230+
// Check if this output node is orphaned (parent input node was deleted)
231+
if (isJupyterOutputNodeOrphaned(this)) {
232+
// Allow deletion of orphaned output nodes
233+
// Explicitly set preserveEmptyParent to false for orphaned nodes,
234+
// since their parent has already been deleted and we do not want to preserve an
235+
// empty parent.
236+
super.remove(false);
237+
}
238+
// Otherwise, do not delete (output nodes with valid parents are protected)
227239
}
228240

229241
removeForce(): void {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (c) 2021-2023 Datalayer, Inc.
3+
*
4+
* MIT License
5+
*/
6+
7+
import { INPUT_UUID_TO_CODE_KEY } from '../plugins/JupyterInputOutputPlugin';
8+
import type { JupyterOutputNode } from './JupyterOutputNode';
9+
10+
/**
11+
* Check if a JupyterOutputNode is orphaned (its parent input node was deleted).
12+
*
13+
* An output node is considered orphaned if:
14+
* 1. Its parent input node UUID is not in the INPUT_UUID_TO_CODE_KEY map, OR
15+
* 2. The output node has no parent in the Lexical tree
16+
*
17+
* @param outputNode - The JupyterOutputNode to check
18+
* @returns true if the output node is orphaned, false otherwise
19+
*/
20+
export function isJupyterOutputNodeOrphaned(
21+
outputNode: JupyterOutputNode,
22+
): boolean {
23+
const inputNodeKey = INPUT_UUID_TO_CODE_KEY.get(
24+
outputNode.getJupyterInputNodeUuid(),
25+
);
26+
return !inputNodeKey || !outputNode.getParent();
27+
}

0 commit comments

Comments
 (0)