|
@@ -1,5 +1,6 @@
|
|
|
// Copyright (c) Jupyter Development Team.
|
|
|
// Distributed under the terms of the Modified BSD License.
|
|
|
+
|
|
|
import { ISanitizer } from '@jupyterlab/apputils';
|
|
|
|
|
|
import {
|
|
@@ -22,13 +23,12 @@ import { TableOfContents } from '../../toc';
|
|
|
|
|
|
import { NotebookGeneratorOptionsManager } from './optionsmanager';
|
|
|
|
|
|
+import { INotebookHeading } from './heading';
|
|
|
+
|
|
|
import {
|
|
|
generateNumbering,
|
|
|
- getMarkdownHeadings,
|
|
|
isDOM,
|
|
|
isMarkdown,
|
|
|
- INotebookHeading,
|
|
|
- INotebookHeadingTypes,
|
|
|
sanitizerOptions
|
|
|
} from '../shared';
|
|
|
|
|
@@ -68,9 +68,9 @@ export function createNotebookGenerator(
|
|
|
// determine whether one header has child
|
|
|
let prevHeading: INotebookHeading | null = null;
|
|
|
let cellNum = panel.content.widgets.length;
|
|
|
- for (var i = 0; i <= panel.content.widgets.length; i++) {
|
|
|
+ for (let i = 0; i <= panel.content.widgets.length; i++) {
|
|
|
let cell: Cell | null = null;
|
|
|
- if (i != cellNum) {
|
|
|
+ if (i !== cellNum) {
|
|
|
cell = panel.content.widgets[i];
|
|
|
}
|
|
|
let collapsed = false;
|
|
@@ -82,7 +82,7 @@ export function createNotebookGenerator(
|
|
|
if (cell) {
|
|
|
model = cell.model;
|
|
|
}
|
|
|
- if (cell && model && model.type === 'code' && i != cellNum) {
|
|
|
+ if (cell && model && model.type === 'code' && i !== cellNum) {
|
|
|
// Get the execution count prompt for code cells
|
|
|
let executionCountNumber = (cell as CodeCell).model
|
|
|
.executionCount as number;
|
|
@@ -185,10 +185,7 @@ export function createNotebookGenerator(
|
|
|
cell
|
|
|
);
|
|
|
let renderedHeading = renderedHeadings[0];
|
|
|
- if (
|
|
|
- renderedHeading &&
|
|
|
- renderedHeading.type === INotebookHeadingTypes.markdown
|
|
|
- ) {
|
|
|
+ if (renderedHeading && renderedHeading.type === 'markdown') {
|
|
|
// Do not put the item in TOC if its filtered out by tags
|
|
|
if (
|
|
|
currentCollapseLevel < 0 &&
|
|
@@ -199,14 +196,11 @@ export function createNotebookGenerator(
|
|
|
) {
|
|
|
headings = headings.concat(renderedHeadings);
|
|
|
}
|
|
|
- } else if (
|
|
|
- renderedHeading &&
|
|
|
- renderedHeading.type === INotebookHeadingTypes.header
|
|
|
- ) {
|
|
|
+ } else if (renderedHeading && renderedHeading.type === 'header') {
|
|
|
// Determine whether the heading has children
|
|
|
if (
|
|
|
prevHeading &&
|
|
|
- prevHeading.type === INotebookHeadingTypes.header &&
|
|
|
+ prevHeading.type === 'header' &&
|
|
|
prevHeading.level >= renderedHeading.level
|
|
|
) {
|
|
|
prevHeading.hasChild = false;
|
|
@@ -246,19 +240,19 @@ export function createNotebookGenerator(
|
|
|
)
|
|
|
) {
|
|
|
if (
|
|
|
- !(renderedHeading.type === INotebookHeadingTypes.markdown) ||
|
|
|
+ !(renderedHeading.type === 'markdown') ||
|
|
|
options.showMarkdown
|
|
|
) {
|
|
|
prevHeading = renderedHeading;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- } else if ((model && model.type === 'markdown') || i == cellNum) {
|
|
|
+ } else if ((model && model.type === 'markdown') || i === cellNum) {
|
|
|
// If the cell is rendered, generate the ToC items from
|
|
|
// the HTML. If it is not rendered, generate them from
|
|
|
// the text of the cell.
|
|
|
if (
|
|
|
- i == cellNum ||
|
|
|
+ i === cellNum ||
|
|
|
((cell as MarkdownCell).rendered &&
|
|
|
!(cell as MarkdownCell).inputHidden)
|
|
|
) {
|
|
@@ -289,7 +283,7 @@ export function createNotebookGenerator(
|
|
|
let lastLevel = Private.getLastLevel(headings);
|
|
|
let renderedHeadings: INotebookHeading[] = [];
|
|
|
let renderedHeading: INotebookHeading | null = null;
|
|
|
- if (i != cellNum) {
|
|
|
+ if (i !== cellNum) {
|
|
|
renderedHeadings = Private.getRenderedHTMLHeadings(
|
|
|
cell!.node,
|
|
|
onClickFactory,
|
|
@@ -301,10 +295,7 @@ export function createNotebookGenerator(
|
|
|
);
|
|
|
renderedHeading = renderedHeadings[0];
|
|
|
}
|
|
|
- if (
|
|
|
- renderedHeading &&
|
|
|
- renderedHeading.type === INotebookHeadingTypes.markdown
|
|
|
- ) {
|
|
|
+ if (renderedHeading && renderedHeading.type === 'markdown') {
|
|
|
// Do not put the item in TOC if its filtered out by tags
|
|
|
if (
|
|
|
currentCollapseLevel < 0 &&
|
|
@@ -316,14 +307,13 @@ export function createNotebookGenerator(
|
|
|
headings = headings.concat(renderedHeadings);
|
|
|
}
|
|
|
} else if (
|
|
|
- (renderedHeading &&
|
|
|
- renderedHeading.type === INotebookHeadingTypes.header) ||
|
|
|
+ (renderedHeading && renderedHeading.type === 'header') ||
|
|
|
!renderedHeading
|
|
|
) {
|
|
|
// Determine whether the heading has children
|
|
|
if (
|
|
|
prevHeading &&
|
|
|
- prevHeading.type === INotebookHeadingTypes.header &&
|
|
|
+ prevHeading.type === 'header' &&
|
|
|
(i === cellNum ||
|
|
|
(renderedHeading &&
|
|
|
prevHeading.level >= renderedHeading.level))
|
|
@@ -366,8 +356,7 @@ export function createNotebookGenerator(
|
|
|
)
|
|
|
) {
|
|
|
if (
|
|
|
- (renderedHeading &&
|
|
|
- !(renderedHeading.type === INotebookHeadingTypes.markdown)) ||
|
|
|
+ (renderedHeading && !(renderedHeading.type === 'markdown')) ||
|
|
|
options.showMarkdown
|
|
|
) {
|
|
|
prevHeading = renderedHeading;
|
|
@@ -400,7 +389,7 @@ export function createNotebookGenerator(
|
|
|
let renderedHeadings: INotebookHeading[] = [];
|
|
|
let renderedHeading: INotebookHeading | null = null;
|
|
|
if (cell) {
|
|
|
- renderedHeadings = getMarkdownHeadings(
|
|
|
+ renderedHeadings = Private.getMarkdownHeadings(
|
|
|
model!.value.text,
|
|
|
onClickFactory,
|
|
|
numberingDict,
|
|
@@ -409,10 +398,7 @@ export function createNotebookGenerator(
|
|
|
);
|
|
|
renderedHeading = renderedHeadings[0];
|
|
|
}
|
|
|
- if (
|
|
|
- renderedHeading &&
|
|
|
- renderedHeading.type === INotebookHeadingTypes.markdown
|
|
|
- ) {
|
|
|
+ if (renderedHeading && renderedHeading.type === 'markdown') {
|
|
|
if (
|
|
|
renderedHeading &&
|
|
|
currentCollapseLevel < 0 &&
|
|
@@ -423,14 +409,11 @@ export function createNotebookGenerator(
|
|
|
) {
|
|
|
headings = headings.concat(renderedHeadings);
|
|
|
}
|
|
|
- } else if (
|
|
|
- renderedHeading &&
|
|
|
- renderedHeading.type === INotebookHeadingTypes.header
|
|
|
- ) {
|
|
|
+ } else if (renderedHeading && renderedHeading.type === 'header') {
|
|
|
// Determine whether the heading has children
|
|
|
if (
|
|
|
prevHeading &&
|
|
|
- prevHeading.type === INotebookHeadingTypes.header &&
|
|
|
+ prevHeading.type === 'header' &&
|
|
|
(i === cellNum || prevHeading.level >= renderedHeading.level)
|
|
|
) {
|
|
|
prevHeading.hasChild = false;
|
|
@@ -491,9 +474,9 @@ namespace Private {
|
|
|
let cellMetadata = heading.cellRef.model.metadata;
|
|
|
let cellTagsData = cellMetadata.get('tags') as string[];
|
|
|
if (cellTagsData) {
|
|
|
- for (var j = 0; j < cellTagsData.length; j++) {
|
|
|
+ for (let j = 0; j < cellTagsData.length; j++) {
|
|
|
let name = cellTagsData[j];
|
|
|
- for (var k = 0; k < tags.length; k++) {
|
|
|
+ for (let k = 0; k < tags.length; k++) {
|
|
|
if (tags[k] === name) {
|
|
|
return false;
|
|
|
}
|
|
@@ -510,7 +493,7 @@ namespace Private {
|
|
|
if (headings.length > 0) {
|
|
|
let location = headings.length - 1;
|
|
|
while (location >= 0) {
|
|
|
- if (headings[location].type === INotebookHeadingTypes.header) {
|
|
|
+ if (headings[location].type === 'header') {
|
|
|
return headings[location].level;
|
|
|
}
|
|
|
location = location - 1;
|
|
@@ -547,7 +530,7 @@ namespace Private {
|
|
|
text: headingText,
|
|
|
level,
|
|
|
onClick,
|
|
|
- type: INotebookHeadingTypes.code,
|
|
|
+ type: 'code',
|
|
|
prompt: executionCount,
|
|
|
cellRef: cellRef,
|
|
|
hasChild: false
|
|
@@ -558,6 +541,89 @@ namespace Private {
|
|
|
}
|
|
|
|
|
|
namespace Private {
|
|
|
+ /**
|
|
|
+ * Given a string of markdown, get the markdown headings
|
|
|
+ * in that string.
|
|
|
+ */
|
|
|
+ export function getMarkdownHeadings(
|
|
|
+ text: string,
|
|
|
+ onClickFactory: (line: number) => (() => void),
|
|
|
+ numberingDict: any,
|
|
|
+ lastLevel: number,
|
|
|
+ cellRef: Cell
|
|
|
+ ): INotebookHeading[] {
|
|
|
+ // Split the text into lines.
|
|
|
+ const lines = text.split('\n');
|
|
|
+ let headings: INotebookHeading[] = [];
|
|
|
+ // Iterate over the lines to get the header level and
|
|
|
+ // the text for the line.
|
|
|
+ let line = lines[0];
|
|
|
+ let idx = 0;
|
|
|
+ // Make an onClick handler for this line.
|
|
|
+ const onClick = onClickFactory(idx);
|
|
|
+
|
|
|
+ // First test for '#'-style headers.
|
|
|
+ let match = line.match(/^([#]{1,6}) (.*)/);
|
|
|
+ let match2 = line.match(/^([=]{2,}|[-]{2,})/);
|
|
|
+ let match3 = line.match(/<h([1-6])>(.*)<\/h\1>/i);
|
|
|
+ if (match) {
|
|
|
+ const level = match[1].length;
|
|
|
+ // Take special care to parse markdown links into raw text.
|
|
|
+ const text = match[2].replace(/\[(.+)\]\(.+\)/g, '$1');
|
|
|
+ let numbering = generateNumbering(numberingDict, level);
|
|
|
+ headings.push({
|
|
|
+ text,
|
|
|
+ level,
|
|
|
+ numbering,
|
|
|
+ onClick,
|
|
|
+ type: 'header',
|
|
|
+ cellRef: cellRef,
|
|
|
+ hasChild: true
|
|
|
+ });
|
|
|
+ } else if (match2 && idx > 0) {
|
|
|
+ // Next test for '==='-style headers.
|
|
|
+ const level = match2[1][0] === '=' ? 1 : 2;
|
|
|
+ // Take special care to parse markdown links into raw text.
|
|
|
+ const text = lines[idx - 1].replace(/\[(.+)\]\(.+\)/g, '$1');
|
|
|
+ let numbering = generateNumbering(numberingDict, level);
|
|
|
+ headings.push({
|
|
|
+ text,
|
|
|
+ level,
|
|
|
+ numbering,
|
|
|
+ onClick,
|
|
|
+ type: 'header',
|
|
|
+ cellRef: cellRef,
|
|
|
+ hasChild: true
|
|
|
+ });
|
|
|
+ } else if (match3) {
|
|
|
+ // Finally test for HTML headers. This will not catch multiline
|
|
|
+ // headers, nor will it catch multiple headers on the same line.
|
|
|
+ // It should do a decent job of catching many, though.
|
|
|
+ const level = parseInt(match3[1], 10);
|
|
|
+ const text = match3[2];
|
|
|
+ let numbering = generateNumbering(numberingDict, level);
|
|
|
+ headings.push({
|
|
|
+ text,
|
|
|
+ level,
|
|
|
+ numbering,
|
|
|
+ onClick,
|
|
|
+ type: 'header',
|
|
|
+ cellRef: cellRef,
|
|
|
+ hasChild: true
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ headings.push({
|
|
|
+ text: line,
|
|
|
+ level: lastLevel + 1,
|
|
|
+ onClick,
|
|
|
+ type: 'markdown',
|
|
|
+ cellRef: cellRef,
|
|
|
+ hasChild: false
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return headings;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Given an HTML element, generate ToC headings
|
|
|
* by finding all the headers and making IHeading objects for them.
|
|
@@ -587,14 +653,14 @@ namespace Private {
|
|
|
html: html,
|
|
|
text: markdownCell.textContent ? markdownCell.textContent : '',
|
|
|
onClick: onClickFactory(markdownCell),
|
|
|
- type: INotebookHeadingTypes.markdown,
|
|
|
+ type: 'markdown',
|
|
|
cellRef: cellRef,
|
|
|
hasChild: true
|
|
|
});
|
|
|
}
|
|
|
} else {
|
|
|
const heading = headingNodes[0];
|
|
|
- const level = parseInt(heading.tagName[1]);
|
|
|
+ const level = parseInt(heading.tagName[1], 10);
|
|
|
const text = heading.textContent ? heading.textContent : '';
|
|
|
let shallHide = !needsNumbering;
|
|
|
if (heading.getElementsByClassName('numbering-entry').length > 0) {
|
|
@@ -617,7 +683,7 @@ namespace Private {
|
|
|
numbering,
|
|
|
html,
|
|
|
onClick,
|
|
|
- type: INotebookHeadingTypes.header,
|
|
|
+ type: 'header',
|
|
|
cellRef: cellRef,
|
|
|
hasChild: true
|
|
|
});
|