1 |
|
2 |
|
3 | import { Cell, MarkdownCell } from '@jupyterlab/cells';
|
4 | import { TableOfContentsFactory, TableOfContentsModel, TableOfContentsUtils } from '@jupyterlab/toc';
|
5 | import { NotebookActions } from './actions';
|
6 |
|
7 |
|
8 |
|
9 | export var RunningStatus;
|
10 | (function (RunningStatus) {
|
11 | |
12 |
|
13 |
|
14 | RunningStatus[RunningStatus["Idle"] = -1] = "Idle";
|
15 | |
16 |
|
17 |
|
18 | RunningStatus[RunningStatus["Scheduled"] = 0] = "Scheduled";
|
19 | |
20 |
|
21 |
|
22 | RunningStatus[RunningStatus["Running"] = 1] = "Running";
|
23 | })(RunningStatus || (RunningStatus = {}));
|
24 |
|
25 |
|
26 |
|
27 | export class NotebookToCModel extends TableOfContentsModel {
|
28 | |
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | constructor(widget, parser, sanitizer, configuration) {
|
37 | super(widget, configuration);
|
38 | this.parser = parser;
|
39 | this.sanitizer = sanitizer;
|
40 | |
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 | this.configMetadataMap = {
|
48 | numberHeaders: ['toc-autonumbering', 'toc/number_sections'],
|
49 | numberingH1: ['!toc/skip_h1_title'],
|
50 | baseNumbering: ['toc/base_numbering']
|
51 | };
|
52 | this._runningCells = new Array();
|
53 | this._cellToHeadingIndex = new WeakMap();
|
54 | void widget.context.ready.then(() => {
|
55 |
|
56 | this.setConfiguration({});
|
57 | });
|
58 | this.widget.context.model.metadataChanged.connect(this.onMetadataChanged, this);
|
59 | this.widget.content.activeCellChanged.connect(this.onActiveCellChanged, this);
|
60 | NotebookActions.executionScheduled.connect(this.onExecutionScheduled, this);
|
61 | NotebookActions.executed.connect(this.onExecuted, this);
|
62 | this.headingsChanged.connect(this.onHeadingsChanged, this);
|
63 | }
|
64 | |
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 | get documentType() {
|
72 | return 'notebook';
|
73 | }
|
74 | |
75 |
|
76 |
|
77 |
|
78 | get isAlwaysActive() {
|
79 | return true;
|
80 | }
|
81 | |
82 |
|
83 |
|
84 | get supportedOptions() {
|
85 | return [
|
86 | 'baseNumbering',
|
87 | 'maximalDepth',
|
88 | 'numberingH1',
|
89 | 'numberHeaders',
|
90 | 'includeOutput',
|
91 | 'syncCollapseState'
|
92 | ];
|
93 | }
|
94 | |
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 | getCellHeadings(cell) {
|
101 | const headings = new Array();
|
102 | let headingIndex = this._cellToHeadingIndex.get(cell);
|
103 | if (headingIndex !== undefined) {
|
104 | const candidate = this.headings[headingIndex];
|
105 | headings.push(candidate);
|
106 | while (this.headings[headingIndex - 1] &&
|
107 | this.headings[headingIndex - 1].cellRef === candidate.cellRef) {
|
108 | headingIndex--;
|
109 | headings.unshift(this.headings[headingIndex]);
|
110 | }
|
111 | }
|
112 | return headings;
|
113 | }
|
114 | |
115 |
|
116 |
|
117 | dispose() {
|
118 | var _a, _b, _c;
|
119 | if (this.isDisposed) {
|
120 | return;
|
121 | }
|
122 | this.headingsChanged.disconnect(this.onHeadingsChanged, this);
|
123 | (_b = (_a = this.widget.context) === null || _a === void 0 ? void 0 : _a.model) === null || _b === void 0 ? void 0 : _b.metadataChanged.disconnect(this.onMetadataChanged, this);
|
124 | (_c = this.widget.content) === null || _c === void 0 ? void 0 : _c.activeCellChanged.disconnect(this.onActiveCellChanged, this);
|
125 | NotebookActions.executionScheduled.disconnect(this.onExecutionScheduled, this);
|
126 | NotebookActions.executed.disconnect(this.onExecuted, this);
|
127 | this._runningCells.length = 0;
|
128 | super.dispose();
|
129 | }
|
130 | |
131 |
|
132 |
|
133 |
|
134 |
|
135 | setConfiguration(c) {
|
136 |
|
137 | const metadataConfig = this.loadConfigurationFromMetadata();
|
138 | super.setConfiguration({ ...this.configuration, ...metadataConfig, ...c });
|
139 | }
|
140 | |
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 | toggleCollapse(options) {
|
147 | super.toggleCollapse(options);
|
148 | this.updateRunningStatus(this.headings);
|
149 | }
|
150 | |
151 |
|
152 |
|
153 |
|
154 |
|
155 | getHeadings() {
|
156 | const cells = this.widget.content.widgets;
|
157 | const headings = [];
|
158 | const documentLevels = new Array();
|
159 |
|
160 | for (let i = 0; i < cells.length; i++) {
|
161 | const cell = cells[i];
|
162 | const model = cell.model;
|
163 | switch (model.type) {
|
164 | case 'code': {
|
165 |
|
166 | if (!this.configuration.syncCollapseState &&
|
167 | this.configuration.includeOutput) {
|
168 | headings.push(...TableOfContentsUtils.filterHeadings(cell.headings, this.configuration, documentLevels).map(heading => {
|
169 | return {
|
170 | ...heading,
|
171 | cellRef: cell,
|
172 | collapsed: false,
|
173 | isRunning: RunningStatus.Idle
|
174 | };
|
175 | }));
|
176 | }
|
177 | break;
|
178 | }
|
179 | case 'markdown': {
|
180 | const cellHeadings = TableOfContentsUtils.filterHeadings(cell.headings, this.configuration, documentLevels).map((heading, index) => {
|
181 | return {
|
182 | ...heading,
|
183 | cellRef: cell,
|
184 | collapsed: false,
|
185 | isRunning: RunningStatus.Idle
|
186 | };
|
187 | });
|
188 |
|
189 |
|
190 | if (this.configuration.syncCollapseState &&
|
191 | cell.headingCollapsed) {
|
192 | const minLevel = Math.min(...cellHeadings.map(h => h.level));
|
193 | const minHeading = cellHeadings.find(h => h.level === minLevel);
|
194 | minHeading.collapsed = cell.headingCollapsed;
|
195 | }
|
196 | headings.push(...cellHeadings);
|
197 | break;
|
198 | }
|
199 | }
|
200 | if (headings.length > 0) {
|
201 | this._cellToHeadingIndex.set(cell, headings.length - 1);
|
202 | }
|
203 | }
|
204 | this.updateRunningStatus(headings);
|
205 | return Promise.resolve(headings);
|
206 | }
|
207 | |
208 |
|
209 |
|
210 |
|
211 |
|
212 | loadConfigurationFromMetadata() {
|
213 | const nbModel = this.widget.content.model;
|
214 | const newConfig = {};
|
215 | if (nbModel) {
|
216 | for (const option in this.configMetadataMap) {
|
217 | const keys = this.configMetadataMap[option];
|
218 | for (const k of keys) {
|
219 | let key = k;
|
220 | const negate = key[0] === '!';
|
221 | if (negate) {
|
222 | key = key.slice(1);
|
223 | }
|
224 | const keyPath = key.split('/');
|
225 | let value = nbModel.getMetadata(keyPath[0]);
|
226 | for (let p = 1; p < keyPath.length; p++) {
|
227 | value = (value !== null && value !== void 0 ? value : {})[keyPath[p]];
|
228 | }
|
229 | if (value !== undefined) {
|
230 | if (typeof value === 'boolean' && negate) {
|
231 | value = !value;
|
232 | }
|
233 | newConfig[option] = value;
|
234 | }
|
235 | }
|
236 | }
|
237 | }
|
238 | return newConfig;
|
239 | }
|
240 | onActiveCellChanged(notebook, cell) {
|
241 |
|
242 | const activeHeading = this.getCellHeadings(cell)[0];
|
243 | this.setActiveHeading(activeHeading !== null && activeHeading !== void 0 ? activeHeading : null, false);
|
244 | }
|
245 | onHeadingsChanged() {
|
246 | if (this.widget.content.activeCell) {
|
247 | this.onActiveCellChanged(this.widget.content, this.widget.content.activeCell);
|
248 | }
|
249 | }
|
250 | onExecuted(_, args) {
|
251 | this._runningCells.forEach((cell, index) => {
|
252 | if (cell === args.cell) {
|
253 | this._runningCells.splice(index, 1);
|
254 | const headingIndex = this._cellToHeadingIndex.get(cell);
|
255 | if (headingIndex !== undefined) {
|
256 | const heading = this.headings[headingIndex];
|
257 | heading.isRunning = RunningStatus.Idle;
|
258 | }
|
259 | }
|
260 | });
|
261 | this.updateRunningStatus(this.headings);
|
262 | this.stateChanged.emit();
|
263 | }
|
264 | onExecutionScheduled(_, args) {
|
265 | if (!this._runningCells.includes(args.cell)) {
|
266 | this._runningCells.push(args.cell);
|
267 | }
|
268 | this.updateRunningStatus(this.headings);
|
269 | this.stateChanged.emit();
|
270 | }
|
271 | onMetadataChanged() {
|
272 | this.setConfiguration({});
|
273 | }
|
274 | updateRunningStatus(headings) {
|
275 |
|
276 | this._runningCells.forEach((cell, index) => {
|
277 | const headingIndex = this._cellToHeadingIndex.get(cell);
|
278 | if (headingIndex !== undefined) {
|
279 | const heading = this.headings[headingIndex];
|
280 | heading.isRunning = Math.max(index > 0 ? RunningStatus.Scheduled : RunningStatus.Running, heading.isRunning);
|
281 | }
|
282 | });
|
283 | let globalIndex = 0;
|
284 | while (globalIndex < headings.length) {
|
285 | const heading = headings[globalIndex];
|
286 | globalIndex++;
|
287 | if (heading.collapsed) {
|
288 | const maxIsRunning = Math.max(heading.isRunning, getMaxIsRunning(headings, heading.level));
|
289 | heading.dataset = {
|
290 | ...heading.dataset,
|
291 | 'data-running': maxIsRunning.toString()
|
292 | };
|
293 | }
|
294 | else {
|
295 | heading.dataset = {
|
296 | ...heading.dataset,
|
297 | 'data-running': heading.isRunning.toString()
|
298 | };
|
299 | }
|
300 | }
|
301 | function getMaxIsRunning(headings, collapsedLevel) {
|
302 | let maxIsRunning = RunningStatus.Idle;
|
303 | while (globalIndex < headings.length) {
|
304 | const heading = headings[globalIndex];
|
305 | heading.dataset = {
|
306 | ...heading.dataset,
|
307 | 'data-running': heading.isRunning.toString()
|
308 | };
|
309 | if (heading.level > collapsedLevel) {
|
310 | globalIndex++;
|
311 | maxIsRunning = Math.max(heading.isRunning, maxIsRunning);
|
312 | if (heading.collapsed) {
|
313 | maxIsRunning = Math.max(maxIsRunning, getMaxIsRunning(headings, heading.level));
|
314 | heading.dataset = {
|
315 | ...heading.dataset,
|
316 | 'data-running': maxIsRunning.toString()
|
317 | };
|
318 | }
|
319 | }
|
320 | else {
|
321 | break;
|
322 | }
|
323 | }
|
324 | return maxIsRunning;
|
325 | }
|
326 | }
|
327 | }
|
328 |
|
329 |
|
330 |
|
331 | export class NotebookToCFactory extends TableOfContentsFactory {
|
332 | |
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 | constructor(tracker, parser, sanitizer) {
|
340 | super(tracker);
|
341 | this.parser = parser;
|
342 | this.sanitizer = sanitizer;
|
343 | }
|
344 | |
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 | _createNew(widget, configuration) {
|
352 | const model = new NotebookToCModel(widget, this.parser, this.sanitizer, configuration);
|
353 |
|
354 | let headingToElement = new WeakMap();
|
355 | const onActiveHeadingChanged = (model, heading) => {
|
356 | if (heading) {
|
357 | const onCellInViewport = (cell) => {
|
358 | if (!cell.inViewport) {
|
359 |
|
360 | return;
|
361 | }
|
362 | const el = headingToElement.get(heading);
|
363 | if (el) {
|
364 | const widgetBox = widget.content.node.getBoundingClientRect();
|
365 | const elementBox = el.getBoundingClientRect();
|
366 | if (elementBox.top > widgetBox.bottom ||
|
367 | elementBox.bottom < widgetBox.top) {
|
368 | el.scrollIntoView({ block: 'center' });
|
369 | }
|
370 | }
|
371 | };
|
372 | const cell = heading.cellRef;
|
373 | const cells = widget.content.widgets;
|
374 | const idx = cells.indexOf(cell);
|
375 | widget.content.activeCellIndex = idx;
|
376 | if (cell.inViewport) {
|
377 | onCellInViewport(cell);
|
378 | }
|
379 | else {
|
380 | widget.content
|
381 | .scrollToItem(idx)
|
382 | .then(() => {
|
383 | onCellInViewport(cell);
|
384 | })
|
385 | .catch(reason => {
|
386 | console.error('Fail to scroll to cell to display the required heading.');
|
387 | });
|
388 | }
|
389 | }
|
390 | };
|
391 | const findHeadingElement = (cell) => {
|
392 | model.getCellHeadings(cell).forEach(async (heading) => {
|
393 | var _a, _b;
|
394 | const elementId = await getIdForHeading(heading, this.parser);
|
395 | const selector = elementId
|
396 | ? `h${heading.level}[id="${elementId}"]`
|
397 | : `h${heading.level}`;
|
398 | if (heading.outputIndex !== undefined) {
|
399 |
|
400 | headingToElement.set(heading, TableOfContentsUtils.addPrefix(heading.cellRef.outputArea.widgets[heading.outputIndex].node, selector, (_a = heading.prefix) !== null && _a !== void 0 ? _a : ''));
|
401 | }
|
402 | else {
|
403 | headingToElement.set(heading, TableOfContentsUtils.addPrefix(heading.cellRef.node, selector, (_b = heading.prefix) !== null && _b !== void 0 ? _b : ''));
|
404 | }
|
405 | });
|
406 | };
|
407 | const onHeadingsChanged = (model) => {
|
408 | if (!this.parser) {
|
409 | return;
|
410 | }
|
411 |
|
412 | TableOfContentsUtils.clearNumbering(widget.content.node);
|
413 |
|
414 | headingToElement = new WeakMap();
|
415 | widget.content.widgets.forEach(cell => {
|
416 | findHeadingElement(cell);
|
417 | });
|
418 | };
|
419 | const onHeadingCollapsed = (_, heading) => {
|
420 | var _a, _b, _c, _d;
|
421 | if (model.configuration.syncCollapseState) {
|
422 | if (heading !== null) {
|
423 | const cell = heading.cellRef;
|
424 | if (cell.headingCollapsed !== ((_a = heading.collapsed) !== null && _a !== void 0 ? _a : false)) {
|
425 | cell.headingCollapsed = (_b = heading.collapsed) !== null && _b !== void 0 ? _b : false;
|
426 | }
|
427 | }
|
428 | else {
|
429 | const collapseState = (_d = (_c = model.headings[0]) === null || _c === void 0 ? void 0 : _c.collapsed) !== null && _d !== void 0 ? _d : false;
|
430 | widget.content.widgets.forEach(cell => {
|
431 | if (cell instanceof MarkdownCell) {
|
432 | if (cell.headingInfo.level >= 0) {
|
433 | cell.headingCollapsed = collapseState;
|
434 | }
|
435 | }
|
436 | });
|
437 | }
|
438 | }
|
439 | };
|
440 | const onCellCollapsed = (_, cell) => {
|
441 | if (model.configuration.syncCollapseState) {
|
442 | const h = model.getCellHeadings(cell)[0];
|
443 | if (h) {
|
444 | model.toggleCollapse({
|
445 | heading: h,
|
446 | collapsed: cell.headingCollapsed
|
447 | });
|
448 | }
|
449 | }
|
450 | };
|
451 | const onCellInViewportChanged = (_, cell) => {
|
452 | if (cell.inViewport) {
|
453 | findHeadingElement(cell);
|
454 | }
|
455 | else {
|
456 |
|
457 | TableOfContentsUtils.clearNumbering(cell.node);
|
458 | }
|
459 | };
|
460 | void widget.context.ready.then(() => {
|
461 | onHeadingsChanged(model);
|
462 | model.activeHeadingChanged.connect(onActiveHeadingChanged);
|
463 | model.headingsChanged.connect(onHeadingsChanged);
|
464 | model.collapseChanged.connect(onHeadingCollapsed);
|
465 | widget.content.cellCollapsed.connect(onCellCollapsed);
|
466 | widget.content.cellInViewportChanged.connect(onCellInViewportChanged);
|
467 | widget.disposed.connect(() => {
|
468 | model.activeHeadingChanged.disconnect(onActiveHeadingChanged);
|
469 | model.headingsChanged.disconnect(onHeadingsChanged);
|
470 | model.collapseChanged.disconnect(onHeadingCollapsed);
|
471 | widget.content.cellCollapsed.disconnect(onCellCollapsed);
|
472 | widget.content.cellInViewportChanged.disconnect(onCellInViewportChanged);
|
473 | });
|
474 | });
|
475 | return model;
|
476 | }
|
477 | }
|
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 | export async function getIdForHeading(heading, parser) {
|
485 | let elementId = null;
|
486 | if (heading.type === Cell.HeadingType.Markdown) {
|
487 | elementId = await TableOfContentsUtils.Markdown.getHeadingId(parser,
|
488 |
|
489 | heading.raw, heading.level);
|
490 | }
|
491 | else if (heading.type === Cell.HeadingType.HTML) {
|
492 |
|
493 | elementId = heading.id;
|
494 | }
|
495 | return elementId;
|
496 | }
|
497 |
|
\ | No newline at end of file |