В минимально жизнеспособном расширении JupyterLab, например, протестированном с использованием Площадка для плагинов JupyterLab, как я могу добавить кнопку панели инструментов, которая будет переключать определенный атрибут класса в HTML, связанный с одной или несколькими выбранными ячейками блокнота (либо ячейкой кода, либо ячейкой уценки)?
Чтобы еще больше обобщить пример:
В качестве отправной точки следующий код (взятый из JupyterLab примеры расширения) должен добавить кнопку на панель инструментов:
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
(который является содержимым блокнота panel
, который у вас уже есть):
// in anonymous function assigned to myButtonAction
const notebook = panel.content;
Экземпляр ноутбука предоставляет вам активный виджет Cell
:
const activeCell = notebook.activeCell;
Виджет ячейки имеет два интересующих атрибута: model
, который позволяет вам получить доступ к метаданным, и node
, который позволяет манипулировать структурой DOM.
Например, вы можете переключить класс узла ячейки, если это ячейка уценки (=ICellModel
имеет .type
(CellType
), равное 'markdown'
):
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');
}
}
Однако с этим подходом есть проблема, так как когда блокнот открывается в отдельном представлении или просто перезагружается, классы исчезают (поскольку они переключаются только на узлах 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');
}
}
});
который также должен работать (в принципе) с совместным редактированием.Предположительно, note.modelChanged запускается, если записная книжка изменяется, пока она открыта в редакторе. Если я хочу проверить все элементы метаданных ячейки при первом открытии блокнота в редакторе, есть ли блокнот.готовый или аналогичный, к которому я могу подключиться?
Ах, отвечая на мой второй вопрос, возможно, ноутбук.
Я считаю, что вы тоже можете писать в метаданные. Вы можете перейти к дочерним элементам, используя стандартные методы DOM, или лучше использовать методы доступа Cell (например, .editor
, .editorWidget
, .inputArea.node
, .inputArea.promptNode
и т. д.
notebook.fullyRendered
довольно новый и может быть не тем, что вы ищете. Раньше не существовало понятия «блокнот. они не прокручиваются до самой последней ячейки); в любом случае все это может быть изменено на основе отзывов, которые мы получили о проблемах с этой функцией (много изменений уже было внесено), поэтому лично я бы просто рассматривал каждое изменение по одному.
так будет ли modelChanged всегда срабатывать при загрузке ноутбука? (Всякий раз, когда я открываю блокнот, я хочу перебирать ячейки и проверять теги метаданных). Условия гонки в пользовательском интерфейсе мы являемся одним из жупелов взаимодействующих расширений в классическом nb и IIRC. Большое заявление, сделанное ранее для JupyterLab, заключалось в том, что он предложит более стабильный способ ведения дел... Re: метаданные - да, похоже, работают ( .metadata .set(k,v); еще раз спасибо :-)
Если я отслеживаю изменения и повторяю все ячейки при каждом изменении, если изменение запускается при добавлении новых ячеек, я буду проверять метаданные в первой ячейке num_cells раз? При изменении могу ли я получить только измененную ячейку?
Да, есть вычислительная нагрузка, но я бы посоветовал начать с этого. Если вам нужно оптимизировать производительность, то вы действительно можете прослушивать событие notebook.model.cells.changed
, но обработка данных этого сигнала может быть немного... требовательной. См. мой метод handle_cell_change
для примера (он работает только на уровне модели, поэтому вам нужно будет получить виджеты для этих моделей ячеек).
@krassowksi Спасибо, это было действительно полезно. Метаданные доступны только для чтения или функция кнопки-переключателя может записывать их? Кроме того, есть ли способ перейти к другим (дочерним) элементам структуры ячейки HTML из средства доступа CELL.node?