@zikojs/notebook 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/code-editor/index.js +53 -38
- package/src/notebook.js +130 -128
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zikojs/notebook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "A lightweight, extensible JavaScript notebook component with Markdown, executable cells, persistent state, and pluggable runtimes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
package/src/code-editor/index.js
CHANGED
|
@@ -166,47 +166,62 @@ export function createCodeEditor(app, cellData, actions) {
|
|
|
166
166
|
? view.state.doc.toString()
|
|
167
167
|
: cellData.code,
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
EditorView.editable.of(!cellData.readonly)
|
|
186
|
-
)
|
|
187
|
-
]
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const currentValue = view.state.doc.toString();
|
|
191
|
-
|
|
192
|
-
if (currentValue !== cellData.code) {
|
|
193
|
-
view.dispatch({
|
|
194
|
-
changes: {
|
|
195
|
-
from: 0,
|
|
196
|
-
to: currentValue.length,
|
|
197
|
-
insert: cellData.code
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
}
|
|
169
|
+
|
|
170
|
+
sync: (isActive, isMarkdownView) => {
|
|
171
|
+
if (!isMarkdownView) {
|
|
172
|
+
if (!view) {
|
|
173
|
+
requestAnimationFrame(() => {
|
|
174
|
+
initEditor();
|
|
175
|
+
|
|
176
|
+
// The editor may only become available after initialization.
|
|
177
|
+
// Check the current active cell, not a potentially stale
|
|
178
|
+
// isActive value captured by this callback.
|
|
179
|
+
if (
|
|
180
|
+
app.store.get("activeCellId") === cellData.id &&
|
|
181
|
+
!cellData.readonly &&
|
|
182
|
+
view
|
|
183
|
+
) {
|
|
184
|
+
view.focus();
|
|
201
185
|
}
|
|
186
|
+
});
|
|
187
|
+
} else {
|
|
188
|
+
view.dispatch({
|
|
189
|
+
effects: [
|
|
190
|
+
languageCompartment.reconfigure(
|
|
191
|
+
languageFor(cellData.type)
|
|
192
|
+
),
|
|
193
|
+
readOnlyCompartment.reconfigure(
|
|
194
|
+
EditorState.readOnly.of(cellData.readonly)
|
|
195
|
+
),
|
|
196
|
+
editableCompartment.reconfigure(
|
|
197
|
+
EditorView.editable.of(!cellData.readonly)
|
|
198
|
+
)
|
|
199
|
+
]
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const currentValue = view.state.doc.toString();
|
|
203
|
+
|
|
204
|
+
if (currentValue !== cellData.code) {
|
|
205
|
+
view.dispatch({
|
|
206
|
+
changes: {
|
|
207
|
+
from: 0,
|
|
208
|
+
to: currentValue.length,
|
|
209
|
+
insert: cellData.code
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
202
213
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
view
|
|
208
|
-
view = null;
|
|
214
|
+
if (
|
|
215
|
+
isActive &&
|
|
216
|
+
!cellData.readonly
|
|
217
|
+
) {
|
|
218
|
+
requestAnimationFrame(() => view?.focus());
|
|
209
219
|
}
|
|
210
220
|
}
|
|
221
|
+
} else if (view) {
|
|
222
|
+
view.destroy();
|
|
223
|
+
view = null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
211
226
|
};
|
|
212
227
|
}
|
package/src/notebook.js
CHANGED
|
@@ -424,8 +424,10 @@ export class UINotebook extends UIElement {
|
|
|
424
424
|
app._refreshCell(cellData);
|
|
425
425
|
},
|
|
426
426
|
|
|
427
|
-
addBelow: () =>
|
|
428
|
-
app.
|
|
427
|
+
addBelow: () => {
|
|
428
|
+
app.setActiveCell(cellData.id);
|
|
429
|
+
app.addCell(cellData.type, null, cellData.id);
|
|
430
|
+
},
|
|
429
431
|
|
|
430
432
|
deleteCell: () => app.deleteCell(cellData.id),
|
|
431
433
|
|
|
@@ -523,117 +525,111 @@ export class UINotebook extends UIElement {
|
|
|
523
525
|
});
|
|
524
526
|
}
|
|
525
527
|
|
|
526
|
-
focusCell(
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
cell.type === "markdown" &&
|
|
530
|
-
cell.isEditingMarkdown &&
|
|
531
|
-
cell.id !== targetId
|
|
532
|
-
) {
|
|
533
|
-
cell.isEditingMarkdown = false;
|
|
534
|
-
this._refreshCell(cell);
|
|
535
|
-
}
|
|
536
|
-
});
|
|
528
|
+
focusCell(id) {
|
|
529
|
+
const cells = this.store.get("cells");
|
|
530
|
+
const cell = cells.find((c) => c.id === id);
|
|
537
531
|
|
|
538
|
-
|
|
539
|
-
.get("cells")
|
|
540
|
-
.find((cell) => cell.id === targetId);
|
|
532
|
+
if (!cell) return;
|
|
541
533
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
!target.readonly
|
|
546
|
-
) {
|
|
547
|
-
target.isEditingMarkdown = true;
|
|
534
|
+
for (const c of cells) {
|
|
535
|
+
if (c.type === "markdown" && c.id !== id) {
|
|
536
|
+
c.isEditingMarkdown = false;
|
|
548
537
|
}
|
|
538
|
+
}
|
|
549
539
|
|
|
550
|
-
|
|
540
|
+
if (cell.type === "markdown" && !cell.readonly) {
|
|
541
|
+
cell.isEditingMarkdown = true;
|
|
551
542
|
}
|
|
552
543
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const cells = [...this.store.get("cells")];
|
|
560
|
-
|
|
561
|
-
if (cells.length >= this.maxCells) {
|
|
562
|
-
console.warn(
|
|
563
|
-
`Cannot create cell: Maximum cell limit (${this.maxCells}) reached.`
|
|
564
|
-
);
|
|
544
|
+
this.store.set("activeCellId", id);
|
|
545
|
+
this._refreshCell(cell);
|
|
546
|
+
|
|
547
|
+
// Scroll the target cell into view when it is outside the viewport.
|
|
548
|
+
requestAnimationFrame(() => {
|
|
549
|
+
const element = cell._dom;
|
|
565
550
|
|
|
566
|
-
|
|
551
|
+
if (!element?.isConnected) return;
|
|
552
|
+
|
|
553
|
+
const rect = element.getBoundingClientRect();
|
|
554
|
+
const viewportHeight = window.innerHeight;
|
|
555
|
+
|
|
556
|
+
if (rect.top < 0 || rect.bottom > viewportHeight) {
|
|
557
|
+
element.scrollIntoView({
|
|
558
|
+
behavior: "smooth",
|
|
559
|
+
block: rect.top < 0 ? "start" : "nearest",
|
|
560
|
+
});
|
|
567
561
|
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
568
564
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
? "### New Markdown Cell\nClick to edit..."
|
|
574
|
-
: "";
|
|
575
|
-
|
|
576
|
-
const source =
|
|
577
|
-
initialCode !== null ? initialCode : defaultText;
|
|
578
|
-
|
|
579
|
-
const pendingCode = isPromiseLike(source)
|
|
580
|
-
? Promise.resolve(source)
|
|
581
|
-
: null;
|
|
582
|
-
|
|
583
|
-
const newCell = {
|
|
584
|
-
id: newId,
|
|
585
|
-
type,
|
|
586
|
-
code: pendingCode
|
|
587
|
-
? ""
|
|
588
|
-
: typeof source === "string"
|
|
589
|
-
? source
|
|
590
|
-
: "",
|
|
591
|
-
_codePromise: pendingCode,
|
|
592
|
-
_codeLoadError: null,
|
|
593
|
-
|
|
594
|
-
readonly,
|
|
595
|
-
isEditingMarkdown:
|
|
596
|
-
!readonly &&
|
|
597
|
-
type === "markdown" &&
|
|
598
|
-
initialCode === null,
|
|
599
|
-
runtime: null,
|
|
600
|
-
execCount: null,
|
|
601
|
-
outputNode: null,
|
|
602
|
-
hasDomOutput: false,
|
|
603
|
-
|
|
604
|
-
_copiedFlag: false,
|
|
605
|
-
_dom: null,
|
|
606
|
-
_editor: null,
|
|
607
|
-
_actions: null,
|
|
608
|
-
_inPrompt: null,
|
|
609
|
-
_outPrompt: null,
|
|
610
|
-
_bodySlot: null,
|
|
611
|
-
_outputGrid: null,
|
|
612
|
-
_outputRef: null,
|
|
613
|
-
};
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
addCell(type = "code", initialCode = null, afterId = null, readonly = false) {
|
|
568
|
+
const cells = [...this.store.get("cells")];
|
|
614
569
|
|
|
615
|
-
|
|
570
|
+
if (cells.length >= this.maxCells) {
|
|
571
|
+
console.warn(
|
|
572
|
+
`Cannot create cell: Maximum cell limit (${this.maxCells}) reached.`
|
|
573
|
+
);
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Prefer the cell associated with the clicked "+" button.
|
|
578
|
+
// Fall back to the last focused cell, then the last cell.
|
|
579
|
+
const sourceCell =
|
|
580
|
+
(afterId !== null
|
|
581
|
+
? cells.find((cell) => cell.id === afterId)
|
|
582
|
+
: null) ??
|
|
583
|
+
cells.find(
|
|
584
|
+
(cell) => cell.id === this.store.get("activeCellId")
|
|
585
|
+
) ??
|
|
586
|
+
cells.at(-1);
|
|
587
|
+
|
|
588
|
+
const newId = this.nextCellId++;
|
|
589
|
+
const defaultText =
|
|
590
|
+
type === "markdown"
|
|
591
|
+
? "### New Markdown Cell\nClick to edit..."
|
|
592
|
+
: "";
|
|
593
|
+
|
|
594
|
+
const newCell = {
|
|
595
|
+
id: newId,
|
|
596
|
+
type,
|
|
597
|
+
code: initialCode !== null ? initialCode : defaultText,
|
|
598
|
+
readonly,
|
|
599
|
+
isEditingMarkdown:
|
|
600
|
+
!readonly && type === "markdown" && initialCode === null,
|
|
601
|
+
runtime: sourceCell?.runtime ?? null,
|
|
602
|
+
execCount: null,
|
|
603
|
+
outputNode: null,
|
|
604
|
+
hasDomOutput: false,
|
|
605
|
+
_copiedFlag: false,
|
|
606
|
+
_dom: null,
|
|
607
|
+
_editor: null,
|
|
608
|
+
_actions: null,
|
|
609
|
+
_inPrompt: null,
|
|
610
|
+
_outPrompt: null,
|
|
611
|
+
_bodySlot: null,
|
|
612
|
+
_outputGrid: null,
|
|
613
|
+
_outputRef: null,
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
if (afterId === null) {
|
|
617
|
+
cells.push(newCell);
|
|
618
|
+
} else {
|
|
619
|
+
const index = cells.findIndex((cell) => cell.id === afterId);
|
|
620
|
+
if (index === -1) {
|
|
616
621
|
cells.push(newCell);
|
|
617
622
|
} else {
|
|
618
|
-
const index = cells.findIndex(
|
|
619
|
-
(cell) => cell.id === afterId
|
|
620
|
-
);
|
|
621
|
-
|
|
622
623
|
cells.splice(index + 1, 0, newCell);
|
|
623
624
|
}
|
|
625
|
+
}
|
|
624
626
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
// Resolve newly added Promise-based code without blocking
|
|
630
|
-
// the synchronous addCell() API.
|
|
631
|
-
if (pendingCode) {
|
|
632
|
-
this._resolveCellCode(newCell);
|
|
633
|
-
}
|
|
627
|
+
this.store.set("cells", cells);
|
|
628
|
+
this._renderAllCells();
|
|
629
|
+
this.focusCell(newId);
|
|
634
630
|
|
|
635
|
-
|
|
636
|
-
|
|
631
|
+
return newId;
|
|
632
|
+
}
|
|
637
633
|
|
|
638
634
|
deleteCell(id) {
|
|
639
635
|
const cells = this.store.get("cells");
|
|
@@ -656,19 +652,26 @@ export class UINotebook extends UIElement {
|
|
|
656
652
|
this.focusCell(updatedCells[nextActiveIndex].id);
|
|
657
653
|
}
|
|
658
654
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
655
|
+
|
|
656
|
+
nextCell(currentId, defaultType) {
|
|
657
|
+
const cells = this.store.get("cells");
|
|
658
|
+
const index = cells.findIndex((cell) => cell.id === currentId);
|
|
659
|
+
|
|
660
|
+
if (index === -1) return;
|
|
664
661
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
662
|
+
if (index === cells.length - 1) {
|
|
663
|
+
const newId = this.addCell(defaultType, null, currentId);
|
|
664
|
+
|
|
665
|
+
if (newId !== null) {
|
|
666
|
+
this.focusCell(newId);
|
|
669
667
|
}
|
|
668
|
+
|
|
669
|
+
return;
|
|
670
670
|
}
|
|
671
671
|
|
|
672
|
+
this.focusCell(cells[index + 1].id);
|
|
673
|
+
}
|
|
674
|
+
|
|
672
675
|
async runCell(id) {
|
|
673
676
|
await this.ready;
|
|
674
677
|
|
|
@@ -761,31 +764,30 @@ export class UINotebook extends UIElement {
|
|
|
761
764
|
return window.__notebook_scope_map.get(this.id);
|
|
762
765
|
}
|
|
763
766
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
};
|
|
767
|
+
|
|
768
|
+
getNotebookData({ inputs = true, outputs = true } = {}) {
|
|
769
|
+
return this.store.get("cells").map((cell) => {
|
|
770
|
+
const exportedCell = {
|
|
771
|
+
id: cell.id,
|
|
772
|
+
type: cell.type,
|
|
773
|
+
readonly: cell.readonly,
|
|
774
|
+
runtime: cell.runtime ?? null,
|
|
775
|
+
order: cell.execCount,
|
|
776
|
+
};
|
|
775
777
|
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
778
|
+
if (inputs) {
|
|
779
|
+
exportedCell.code = cell._editor?.getValue() ?? cell.code;
|
|
780
|
+
}
|
|
779
781
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
782
|
+
if (outputs) {
|
|
783
|
+
exportedCell.outputHtml = cell.outputNode
|
|
784
|
+
? cell.outputNode.innerHTML
|
|
785
|
+
: cell._outputRef?.innerHTML ?? null;
|
|
786
|
+
}
|
|
785
787
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
788
|
+
return exportedCell;
|
|
789
|
+
});
|
|
790
|
+
}
|
|
789
791
|
}
|
|
790
792
|
|
|
791
793
|
export const Notebook = call_with_optional_props(UINotebook);
|