在 JupyterLab Extensions 中访问笔记本单元元数据和 HTML class 属性
Accessing notebook cell metadata and HTML class attributes in JupyterLab Extensions
在最小可行的 JupyterLab 扩展中,例如使用 JupyterLab Plugin Playground 进行测试,我如何添加一个工具栏按钮来切换 HTML 关联的特定 class 属性使用一个或多个选定的笔记本单元格(代码单元格或降价单元格)?
进一步概括示例:
- 如何将不同的 class 属性应用于代码单元格和降价单元格?
- 如何根据笔记本 JSON 结构中特定元数据属性或元数据标签元素的存在,将 class 添加到 HTML?
作为起点,以下代码(取自 JupyterLab extension examples)应向工具栏添加一个按钮:
import { IDisposable, DisposableDelegate } from '@lumino/disposable';
import {
JupyterFrontEnd,
JupyterFrontEndPlugin,
} from '@jupyterlab/application';
import { ToolbarButton } from '@jupyterlab/apputils';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import {
NotebookActions,
NotebookPanel,
INotebookModel,
} from '@jupyterlab/notebook';
/**
* The plugin registration information.
*/
const plugin: JupyterFrontEndPlugin<void> = {
activate,
id: 'toolbar-button',
autoStart: true,
};
/**
* A notebook widget extension that adds a button to the toolbar.
*/
export class ButtonExtension
implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel>
{
/**
* Create a new extension for the notebook panel widget.
*
* @param panel Notebook panel
* @param context Notebook context
* @returns Disposable on the added button
*/
createNew(
panel: NotebookPanel,
context: DocumentRegistry.IContext<INotebookModel>
): IDisposable {
const myButtonAction = () => {
// Perform some action
};
const button = new ToolbarButton({
className: 'my-action-button',
label: 'My Button',
onClick: myButtonAction,
tooltip: 'Perform My Button action',
});
panel.toolbar.insertItem(10, 'myNewAction', button);
return new DisposableDelegate(() => {
button.dispose();
});
}
}
/**
* Activate the extension.
*
* @param app Main application object
*/
function activate(app: JupyterFrontEnd): void {
app.docRegistry.addWidgetExtension('Notebook', new ButtonExtension());
}
/**
* Export the plugin as default.
*/
export default plugin;
您应该首先获取 Notebook
class 实例的句柄(这是您已经可用的笔记本 panel
的内容):
// in anonymous function assigned to myButtonAction
const notebook = panel.content;
笔记本实例为您提供活动的 Cell
小部件:
const activeCell = notebook.activeCell;
单元格小部件有两个有趣的属性:model
允许您访问元数据,node
允许操作 DOM 结构。
例如,如果单元格是降价单元格 (=ICellModel
has .type
(CellType
) 等于 'markdown'
,则可以切换单元格节点的 class:
if (activeCell.model.type === 'markdown') {
activeCell.node.classList.toggle('someClass');
}
元数据存储在cell.model.metadata
。
对于单元格的选择,以下内容应该有效:
const {head, anchor} = notebook.getContiguousSelection();
if (head === null || anchor === null) {
// no selection
return;
}
const start = head > anchor ? anchor : head;
const end = head > anchor ? head : anchor;
for (let cellIndex = start; cellIndex <= end; cellIndex++) {
const cell = notebook.widgets[cellIndex];
if (cell.model.type === 'code') {
cell.node.classList.toggle('someOtherClass');
}
}
然而,这种方法存在一个问题,因为当笔记本在单独的视图中打开,或者只是重新加载时,classes 将消失(因为它们只在 DOM 节点)。如果你需要坚持,我会推荐:
- 使用按钮仅写入单元格元数据
- 添加一个单独的回调函数来监听笔记本型号的任何变化,大致(未测试!):
// in createNew()
const notebook = panel.content;
notebook.modelChanged.connect((notebook) => {
// iterate cells and toggle DOM classes as needed, e.g.
for (const cell of notebook.widgets) {
if (cell.model.metadata.get('someMetaData')) {
cell.node.classList.toggle('someOtherClass');
}
}
});
这也应该(原则上)与协作编辑一起工作。
在最小可行的 JupyterLab 扩展中,例如使用 JupyterLab Plugin Playground 进行测试,我如何添加一个工具栏按钮来切换 HTML 关联的特定 class 属性使用一个或多个选定的笔记本单元格(代码单元格或降价单元格)?
进一步概括示例:
- 如何将不同的 class 属性应用于代码单元格和降价单元格?
- 如何根据笔记本 JSON 结构中特定元数据属性或元数据标签元素的存在,将 class 添加到 HTML?
作为起点,以下代码(取自 JupyterLab extension examples)应向工具栏添加一个按钮:
import { IDisposable, DisposableDelegate } from '@lumino/disposable';
import {
JupyterFrontEnd,
JupyterFrontEndPlugin,
} from '@jupyterlab/application';
import { ToolbarButton } from '@jupyterlab/apputils';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import {
NotebookActions,
NotebookPanel,
INotebookModel,
} from '@jupyterlab/notebook';
/**
* The plugin registration information.
*/
const plugin: JupyterFrontEndPlugin<void> = {
activate,
id: 'toolbar-button',
autoStart: true,
};
/**
* A notebook widget extension that adds a button to the toolbar.
*/
export class ButtonExtension
implements DocumentRegistry.IWidgetExtension<NotebookPanel, INotebookModel>
{
/**
* Create a new extension for the notebook panel widget.
*
* @param panel Notebook panel
* @param context Notebook context
* @returns Disposable on the added button
*/
createNew(
panel: NotebookPanel,
context: DocumentRegistry.IContext<INotebookModel>
): IDisposable {
const myButtonAction = () => {
// Perform some action
};
const button = new ToolbarButton({
className: 'my-action-button',
label: 'My Button',
onClick: myButtonAction,
tooltip: 'Perform My Button action',
});
panel.toolbar.insertItem(10, 'myNewAction', button);
return new DisposableDelegate(() => {
button.dispose();
});
}
}
/**
* Activate the extension.
*
* @param app Main application object
*/
function activate(app: JupyterFrontEnd): void {
app.docRegistry.addWidgetExtension('Notebook', new ButtonExtension());
}
/**
* Export the plugin as default.
*/
export default plugin;
您应该首先获取 Notebook
class 实例的句柄(这是您已经可用的笔记本 panel
的内容):
// in anonymous function assigned to myButtonAction
const notebook = panel.content;
笔记本实例为您提供活动的 Cell
小部件:
const activeCell = notebook.activeCell;
单元格小部件有两个有趣的属性:model
允许您访问元数据,node
允许操作 DOM 结构。
例如,如果单元格是降价单元格 (=ICellModel
has .type
(CellType
) 等于 'markdown'
,则可以切换单元格节点的 class:
if (activeCell.model.type === 'markdown') {
activeCell.node.classList.toggle('someClass');
}
元数据存储在cell.model.metadata
。
对于单元格的选择,以下内容应该有效:
const {head, anchor} = notebook.getContiguousSelection();
if (head === null || anchor === null) {
// no selection
return;
}
const start = head > anchor ? anchor : head;
const end = head > anchor ? head : anchor;
for (let cellIndex = start; cellIndex <= end; cellIndex++) {
const cell = notebook.widgets[cellIndex];
if (cell.model.type === 'code') {
cell.node.classList.toggle('someOtherClass');
}
}
然而,这种方法存在一个问题,因为当笔记本在单独的视图中打开,或者只是重新加载时,classes 将消失(因为它们只在 DOM 节点)。如果你需要坚持,我会推荐:
- 使用按钮仅写入单元格元数据
- 添加一个单独的回调函数来监听笔记本型号的任何变化,大致(未测试!):
这也应该(原则上)与协作编辑一起工作。// in createNew() const notebook = panel.content; notebook.modelChanged.connect((notebook) => { // iterate cells and toggle DOM classes as needed, e.g. for (const cell of notebook.widgets) { if (cell.model.metadata.get('someMetaData')) { cell.node.classList.toggle('someOtherClass'); } } });