Доступ к метаданным ячейки записной книжки и атрибутам класса HTML в расширениях JupyterLab

В минимально жизнеспособном расширении JupyterLab, например, протестированном с использованием Площадка для плагинов JupyterLab, как я могу добавить кнопку панели инструментов, которая будет переключать определенный атрибут класса в HTML, связанный с одной или несколькими выбранными ячейками блокнота (либо ячейкой кода, либо ячейкой уценки)?

Чтобы еще больше обобщить пример:

  • как мне применить разные атрибуты класса к ячейкам кода и ячейкам уценки?
  • как добавить класс в HTML на основе наличия определенного атрибута метаданных или элемента тега метаданных в структуре JSON записной книжки?

В качестве отправной точки следующий код (взятый из 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;

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы должны начать с получения дескриптора экземпляра класса 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');
        }
      }
    });
    
    который также должен работать (в принципе) с совместным редактированием.

@krassowksi Спасибо, это было действительно полезно. Метаданные доступны только для чтения или функция кнопки-переключателя может записывать их? Кроме того, есть ли способ перейти к другим (дочерним) элементам структуры ячейки HTML из средства доступа CELL.node?

psychemedia 05.04.2022 14:33

Предположительно, note.modelChanged запускается, если записная книжка изменяется, пока она открыта в редакторе. Если я хочу проверить все элементы метаданных ячейки при первом открытии блокнота в редакторе, есть ли блокнот.готовый или аналогичный, к которому я могу подключиться?

psychemedia 05.04.2022 15:08

Ах, отвечая на мой второй вопрос, возможно, ноутбук.

psychemedia 05.04.2022 15:39

Я считаю, что вы тоже можете писать в метаданные. Вы можете перейти к дочерним элементам, используя стандартные методы DOM, или лучше использовать методы доступа Cell (например, .editor, .editorWidget, .inputArea.node, .inputArea.promptNode и т. д.

krassowski 05.04.2022 18:25
notebook.fullyRendered довольно новый и может быть не тем, что вы ищете. Раньше не существовало понятия «блокнот. они не прокручиваются до самой последней ячейки); в любом случае все это может быть изменено на основе отзывов, которые мы получили о проблемах с этой функцией (много изменений уже было внесено), поэтому лично я бы просто рассматривал каждое изменение по одному.
krassowski 05.04.2022 18:31

так будет ли modelChanged всегда срабатывать при загрузке ноутбука? (Всякий раз, когда я открываю блокнот, я хочу перебирать ячейки и проверять теги метаданных). Условия гонки в пользовательском интерфейсе мы являемся одним из жупелов взаимодействующих расширений в классическом nb и IIRC. Большое заявление, сделанное ранее для JupyterLab, заключалось в том, что он предложит более стабильный способ ведения дел... Re: метаданные - да, похоже, работают ( .metadata .set(k,v); еще раз спасибо :-)

psychemedia 05.04.2022 18:42

Если я отслеживаю изменения и повторяю все ячейки при каждом изменении, если изменение запускается при добавлении новых ячеек, я буду проверять метаданные в первой ячейке num_cells раз? При изменении могу ли я получить только измененную ячейку?

psychemedia 05.04.2022 18:47

Да, есть вычислительная нагрузка, но я бы посоветовал начать с этого. Если вам нужно оптимизировать производительность, то вы действительно можете прослушивать событие notebook.model.cells.changed, но обработка данных этого сигнала может быть немного... требовательной. См. мой метод handle_cell_change для примера (он работает только на уровне модели, поэтому вам нужно будет получить виджеты для этих моделей ячеек).

krassowski 07.04.2022 13:35

Другие вопросы по теме