Repository: ian-r-rose/jupyterlab-toc
Branch: master
Commit: d1f81f37aad2
Files: 29
Total size: 88.9 KB
Directory structure:
gitextract_6lby2670/
├── .gitignore
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── package.json
├── src/
│ ├── extension.ts
│ ├── generators/
│ │ ├── index.ts
│ │ ├── latexgenerator.ts
│ │ ├── markdowndocgenerator/
│ │ │ ├── index.ts
│ │ │ ├── itemrenderer.tsx
│ │ │ ├── optionsmanager.ts
│ │ │ └── toolbargenerator.tsx
│ │ ├── notebookgenerator/
│ │ │ ├── codemirror.tsx
│ │ │ ├── heading.ts
│ │ │ ├── index.ts
│ │ │ ├── itemrenderer.tsx
│ │ │ ├── optionsmanager.ts
│ │ │ ├── tagstool/
│ │ │ │ ├── index.tsx
│ │ │ │ ├── tag.tsx
│ │ │ │ └── tagslist.tsx
│ │ │ └── toolbargenerator.tsx
│ │ └── shared.ts
│ ├── index.ts
│ ├── registry.ts
│ └── toc.tsx
├── style/
│ └── index.css
├── tsconfig.json
└── tslint.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.bundle.*
lib/
node_modules/
*.egg-info/
.ipynb_checkpoints
package-lock.json
================================================
FILE: .prettierignore
================================================
node_modules
**/lib
================================================
FILE: .prettierrc
================================================
{
"singleQuote": true
}
================================================
FILE: LICENSE
================================================
Copyright (c) 2017, Project Jupyter Contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
# jupyterlab-toc
A Table of Contents extension for JupyterLab.
This auto-generates a table of contents in the left area when you have a notebook
or markdown document open. The entries are clickable, and scroll the document
to the heading in question.
Here is an animation showing the extension's use, with a notebook from the
[Python Data Science Handbook](https://github.com/jakevdp/PythonDataScienceHandbook):

## Prerequisites
- JupyterLab 1.0
## Installation
```bash
jupyter labextension install @jupyterlab/toc
```
## Development
For a development install, do the following in the repository directory:
```bash
jlpm install
jlpm run build
jupyter labextension install .
```
You can then run JupyterLab in watch mode to automatically pick up changes to `@jupyterlab/toc`.
Open a terminal in the `@jupyterlab/toc` repository directory and enter
```bash
jlpm run watch
```
Then launch JupyterLab using
```bash
jupyter lab --watch
```
This will automatically recompile `@jupyterlab/toc` upon changes,
and JupyterLab will rebuild itself. You should then be able to refresh the
page and see your changes.
================================================
FILE: package.json
================================================
{
"name": "@jupyterlab/toc",
"version": "1.0.0-pre.1",
"private": false,
"description": "Table of Contents extension for JupyterLab",
"keywords": [
"jupyter",
"jupyterlab",
"jupyterlab-extension"
],
"homepage": "https://github.com/jupyterlab/jupyterlab-toc",
"bugs": {
"url": "https://github.com/jupyterlab/jupyterlab-toc/issues"
},
"license": "BSD-3-Clause",
"author": "Project Jupyter",
"files": [
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
"style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/jupyterlab/jupyterlab-toc.git"
},
"scripts": {
"build": "tsc",
"clean": "rimraf lib",
"precommit": "lint-staged",
"prettier": "prettier --write '**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}'",
"watch": "tsc -w"
},
"dependencies": {
"@jupyterlab/application": "^1.0.0-alpha.6",
"@jupyterlab/apputils": "^1.0.0-alpha.6",
"@jupyterlab/cells": "^1.0.0-alpha.6",
"@jupyterlab/coreutils": "3.0.0-alpha.6",
"@jupyterlab/docmanager": "^1.0.0-alpha.6",
"@jupyterlab/docregistry": "^1.0.0-alpha.6",
"@jupyterlab/fileeditor": "^1.0.0-alpha.6",
"@jupyterlab/markdownviewer": "^1.0.0-alpha.6",
"@jupyterlab/notebook": "^1.0.0-alpha.7",
"@jupyterlab/rendermime": "^1.0.0-alpha.6",
"@phosphor/algorithm": "^1.1.2",
"@phosphor/coreutils": "^1.3.0",
"@phosphor/messaging": "^1.2.2",
"@phosphor/widgets": "^1.6.0",
"react": "~16.4.2",
"react-dom": "~16.4.2"
},
"devDependencies": {
"@types/react": "~16.4.13",
"@types/react-dom": "~16.0.5",
"husky": "^0.14.3",
"lint-staged": "^7.2.0",
"prettier": "^1.13.7",
"rimraf": "^2.6.1",
"tslint": "^5.10.0",
"tslint-config-prettier": "^1.13.0",
"tslint-plugin-prettier": "^1.3.0",
"typescript": "~3.1.1"
},
"jupyterlab": {
"extension": "lib/extension.js"
},
"lint-staged": {
"**/*{.ts,.tsx,.css,.json,.md}": [
"prettier --write",
"git add"
]
},
"resolutions": {
"@types/react": "~16.4.13"
},
"publishConfig": {
"access": "public"
}
}
================================================
FILE: src/extension.ts
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import {
ILabShell,
ILayoutRestorer,
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { IDocumentManager } from '@jupyterlab/docmanager';
import { IEditorTracker } from '@jupyterlab/fileeditor';
import { IMarkdownViewerTracker } from '@jupyterlab/markdownviewer';
import { INotebookTracker } from '@jupyterlab/notebook';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { TableOfContents } from './toc';
import {
createLatexGenerator,
createNotebookGenerator,
createMarkdownGenerator,
createRenderedMarkdownGenerator
} from './generators';
import { ITableOfContentsRegistry, TableOfContentsRegistry } from './registry';
import '../style/index.css';
/**
* Initialization data for the jupyterlab-toc extension.
*/
const extension: JupyterFrontEndPlugin<ITableOfContentsRegistry> = {
id: 'jupyterlab-toc',
autoStart: true,
provides: ITableOfContentsRegistry,
requires: [
IDocumentManager,
IEditorTracker,
ILabShell,
ILayoutRestorer,
IMarkdownViewerTracker,
INotebookTracker,
IRenderMimeRegistry
],
activate: activateTOC
};
/**
* Activate the ToC extension.
*/
function activateTOC(
app: JupyterFrontEnd,
docmanager: IDocumentManager,
editorTracker: IEditorTracker,
labShell: ILabShell,
restorer: ILayoutRestorer,
markdownViewerTracker: IMarkdownViewerTracker,
notebookTracker: INotebookTracker,
rendermime: IRenderMimeRegistry
): ITableOfContentsRegistry {
// Create the ToC widget.
const toc = new TableOfContents({ docmanager, rendermime });
// Create the ToC registry.
const registry = new TableOfContentsRegistry();
// Add the ToC to the left area.
toc.title.iconClass = 'jp-TableOfContents-icon jp-SideBar-tabIcon';
toc.title.caption = 'Table of Contents';
toc.id = 'table-of-contents';
labShell.add(toc, 'left', { rank: 700 });
// Add the ToC widget to the application restorer.
restorer.add(toc, 'juputerlab-toc');
// Create a notebook TableOfContentsRegistry.IGenerator
const notebookGenerator = createNotebookGenerator(
notebookTracker,
rendermime.sanitizer,
toc
);
registry.addGenerator(notebookGenerator);
// Create an markdown editor TableOfContentsRegistry.IGenerator
const markdownGenerator = createMarkdownGenerator(
editorTracker,
toc,
rendermime.sanitizer
);
registry.addGenerator(markdownGenerator);
// Create an rendered markdown editor TableOfContentsRegistry.IGenerator
const renderedMarkdownGenerator = createRenderedMarkdownGenerator(
markdownViewerTracker,
rendermime.sanitizer,
toc
);
registry.addGenerator(renderedMarkdownGenerator);
// Create a latex editor TableOfContentsRegistry.IGenerator
const latexGenerator = createLatexGenerator(editorTracker);
registry.addGenerator(latexGenerator);
// Change the ToC when the active widget changes.
labShell.currentChanged.connect(() => {
let widget = app.shell.currentWidget;
if (!widget) {
return;
}
let generator = registry.findGeneratorForWidget(widget);
if (!generator) {
// If the previously used widget is still available, stick with it.
// Otherwise, set the current TOC widget to null.
if (toc.current && toc.current.widget.isDisposed) {
toc.current = null;
}
return;
}
toc.current = { widget, generator };
});
return registry;
}
export default extension;
================================================
FILE: src/generators/index.ts
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
export * from './markdowndocgenerator';
export * from './latexgenerator';
export * from './notebookgenerator';
================================================
FILE: src/generators/latexgenerator.ts
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { IDocumentWidget } from '@jupyterlab/docregistry';
import { FileEditor, IEditorTracker } from '@jupyterlab/fileeditor';
import { TableOfContentsRegistry } from '../registry';
import { IHeading } from '../toc';
/**
* Create a TOC generator for LaTeX files.
*
* @param tracker: A file editor tracker.
*
* @returns A TOC generator that can parse LaTeX files.
*/
export function createLatexGenerator(
tracker: IEditorTracker
): TableOfContentsRegistry.IGenerator<IDocumentWidget<FileEditor>> {
return {
tracker,
usesLatex: true,
isEnabled: editor => {
// Only enable this if the editor mimetype matches
// one of a few LaTeX variants.
let mime = editor.content.model.mimeType;
return mime === 'text/x-latex' || mime === 'text/x-stex';
},
generate: editor => {
let headings: IHeading[] = [];
let model = editor.content.model;
// Split the text into lines, with the line number for each.
// We will use the line number to scroll the editor upon
// TOC item click.
const lines = model.value.text.split('\n').map((value, idx) => {
return { value, idx };
});
// Iterate over the lines to get the header level and
// the text for the line.
lines.forEach(line => {
const match = line.value.match(
/^\s*\\(section|subsection|subsubsection){(.+)}/
);
if (match) {
const level = Private.latexLevels[match[1]];
const text = match[2];
const onClick = () => {
editor.content.editor.setCursorPosition({
line: line.idx,
column: 0
});
};
headings.push({ text, level, onClick });
}
});
return headings;
}
};
}
/**
* A private namespace for miscellaneous things.
*/
namespace Private {
/**
* A mapping from LaTeX section headers to HTML header
* levels. `part` and `chapter` are less common in my experience,
* so assign them to header level 1.
*/
export const latexLevels: { [label: string]: number } = {
part: 1, // Only available for report and book classes
chapter: 1, // Only available for report and book classes
section: 1,
subsection: 2,
subsubsection: 3,
paragraph: 4,
subparagraph: 5
};
}
================================================
FILE: src/generators/markdowndocgenerator/index.ts
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { ISanitizer } from '@jupyterlab/apputils';
import { IDocumentWidget } from '@jupyterlab/docregistry';
import { FileEditor, IEditorTracker } from '@jupyterlab/fileeditor';
import {
IMarkdownViewerTracker,
MarkdownDocument
} from '@jupyterlab/markdownviewer';
import { TableOfContentsRegistry } from '../../registry';
import {
generateNumbering,
sanitizerOptions,
isMarkdown,
INumberedHeading
} from '../shared';
import { MarkdownDocGeneratorOptionsManager } from './optionsmanager';
import { TableOfContents } from '../../toc';
import { markdownDocItemRenderer } from './itemrenderer';
import { markdownDocGeneratorToolbar } from './toolbargenerator';
/**
* Create a TOC generator for markdown files.
*
* @param tracker: A file editor tracker.
*
* @returns A TOC generator that can parse markdown files.
*/
export function createMarkdownGenerator(
tracker: IEditorTracker,
widget: TableOfContents,
sanitizer: ISanitizer
): TableOfContentsRegistry.IGenerator<IDocumentWidget<FileEditor>> {
// Create a option manager to manage user settings
const options = new MarkdownDocGeneratorOptionsManager(widget, {
needsNumbering: true,
sanitizer
});
return {
tracker,
usesLatex: true,
options: options,
toolbarGenerator: () => {
return markdownDocGeneratorToolbar(options);
},
itemRenderer: (item: INumberedHeading) => {
return markdownDocItemRenderer(options, item);
},
isEnabled: editor => {
// Only enable this if the editor mimetype matches
// one of a few markdown variants.
return isMarkdown(editor.content.model.mimeType);
},
generate: editor => {
let numberingDict: { [level: number]: number } = {};
let model = editor.content.model;
let onClickFactory = (line: number) => {
return () => {
editor.content.editor.setCursorPosition({ line, column: 0 });
};
};
return Private.getMarkdownDocHeadings(
model.value.text,
onClickFactory,
numberingDict
);
}
};
}
/**
* Create a TOC generator for rendered markdown files.
*
* @param tracker: A file editor tracker.
*
* @returns A TOC generator that can parse markdown files.
*/
export function createRenderedMarkdownGenerator(
tracker: IMarkdownViewerTracker,
sanitizer: ISanitizer,
widget: TableOfContents
): TableOfContentsRegistry.IGenerator<MarkdownDocument> {
const options = new MarkdownDocGeneratorOptionsManager(widget, {
needsNumbering: true,
sanitizer
});
return {
tracker,
usesLatex: true,
options: options,
toolbarGenerator: () => {
return markdownDocGeneratorToolbar(options);
},
itemRenderer: (item: INumberedHeading) => {
return markdownDocItemRenderer(options, item);
},
generate: widget => {
let numberingDict: { [level: number]: number } = {};
return Private.getRenderedHTMLHeadingsForMarkdownDoc(
widget.content.node,
sanitizer,
numberingDict,
options.numbering
);
}
};
}
/**
* A private namespace for miscellaneous things.
*/
namespace Private {
export function getMarkdownDocHeadings(
text: string,
onClickFactory: (line: number) => (() => void),
numberingDict: { [level: number]: number }
): INumberedHeading[] {
// Split the text into lines.
const lines = text.split('\n');
let headings: INumberedHeading[] = [];
let inCodeBlock = false;
// Iterate over the lines to get the header level and
// the text for the line.
lines.forEach((line, idx) => {
// Don't check for markdown headings if we
// are in a code block (demarcated by backticks).
if (line.indexOf('```') === 0) {
inCodeBlock = !inCodeBlock;
}
if (inCodeBlock) {
return;
}
// Make an onClick handler for this line.
const onClick = onClickFactory(idx);
// First test for '#'-style headers.
let match = line.match(/^([#]{1,6}) (.*)/);
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,
numbering,
level,
onClick
});
return;
}
// Next test for '==='-style headers.
match = line.match(/^([=]{2,}|[-]{2,})/);
if (match && idx > 0) {
const level = match[1][0] === '=' ? 1 : 2;
const prev = lines[idx - 1];
// If the previous line is already a '#'-style heading,
// then this is not a '===' style heading.
const prevMatch = prev.match(/^([#]{1,6}) (.*)/);
if (prevMatch) {
return;
}
// Take special care to parse markdown links into raw text.
const text = prev.replace(/\[(.+)\]\(.+\)/g, '$1');
let numbering = generateNumbering(numberingDict, level);
headings.push({
text,
numbering,
level,
onClick
});
return;
}
// 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.
match = line.match(/<h([1-6])>(.*)<\/h\1>/i);
if (match) {
const level = parseInt(match[1], 10);
const text = match[2];
let numbering = generateNumbering(numberingDict, level);
headings.push({
text,
numbering,
level,
onClick
});
return;
}
});
return headings;
}
/**
* Given a HTML DOM element, get the markdown headings
* in that string.
*/
export function getRenderedHTMLHeadingsForMarkdownDoc(
node: HTMLElement,
sanitizer: ISanitizer,
numberingDict: { [level: number]: number },
needsNumbering = true
): INumberedHeading[] {
let headings: INumberedHeading[] = [];
let headingNodes = node.querySelectorAll('h1, h2, h3, h4, h5, h6');
for (let i = 0; i < headingNodes.length; i++) {
const heading = headingNodes[i];
const level = parseInt(heading.tagName[1], 10);
let text = heading.textContent ? heading.textContent : '';
let shallHide = !needsNumbering;
// Show/hide numbering DOM element based on user settings
if (heading.getElementsByClassName('numbering-entry').length > 0) {
heading.removeChild(
heading.getElementsByClassName('numbering-entry')[0]
);
}
let html = sanitizer.sanitize(heading.innerHTML, sanitizerOptions);
html = html.replace('¶', ''); // Remove the anchor symbol.
const onClick = () => {
heading.scrollIntoView();
};
// Get the numbering string
let numbering = generateNumbering(numberingDict, level);
// Generate the DOM element for numbering
let numDOM = '';
if (!shallHide) {
numDOM = '<span class="numbering-entry">' + numbering + '</span>';
}
// Add DOM numbering element to document
heading.innerHTML = numDOM + html;
text = text.replace('¶', '');
headings.push({
level,
text,
numbering,
html,
onClick
});
}
return headings;
}
}
================================================
FILE: src/generators/markdowndocgenerator/itemrenderer.tsx
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { MarkdownDocGeneratorOptionsManager } from './optionsmanager';
import { INumberedHeading, sanitizerOptions } from '../shared';
import * as React from 'react';
export function markdownDocItemRenderer(
options: MarkdownDocGeneratorOptionsManager,
item: INumberedHeading
) {
let fontSizeClass = 'toc-level-size-default';
// Render numbering if needed
let numbering = item.numbering && options.numbering ? item.numbering : '';
fontSizeClass = 'toc-level-size-' + item.level;
let jsx;
if (item.html) {
jsx = (
<span
dangerouslySetInnerHTML={{
__html:
numbering + options.sanitizer.sanitize(item.html, sanitizerOptions)
}}
className={'toc-markdown-cell ' + fontSizeClass}
/>
);
} else {
jsx = <span className={fontSizeClass}> {numbering + item.text}</span>;
}
return jsx;
}
================================================
FILE: src/generators/markdowndocgenerator/optionsmanager.ts
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { ISanitizer } from '@jupyterlab/apputils';
import { TableOfContentsRegistry } from '../../registry';
import { TableOfContents } from '../../toc';
export class MarkdownDocGeneratorOptionsManager extends TableOfContentsRegistry.IGeneratorOptionsManager {
constructor(
widget: TableOfContents,
options: { needsNumbering: boolean; sanitizer: ISanitizer }
) {
super();
this._numbering = options.needsNumbering;
this._widget = widget;
this.sanitizer = options.sanitizer;
}
readonly sanitizer: ISanitizer;
set numbering(value: boolean) {
this._numbering = value;
this._widget.update();
}
get numbering() {
return this._numbering;
}
// initialize options, will NOT change notebook metadata
initializeOptions(numbering: boolean) {
this._numbering = numbering;
this._widget.update();
}
private _numbering: boolean;
private _widget: TableOfContents;
}
================================================
FILE: src/generators/markdowndocgenerator/toolbargenerator.tsx
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { MarkdownDocGeneratorOptionsManager } from './optionsmanager';
import * as React from 'react';
interface INotebookGeneratorToolbarProps {}
interface INotebookGeneratorToolbarState {
numbering: boolean;
}
export function markdownDocGeneratorToolbar(
options: MarkdownDocGeneratorOptionsManager
) {
// Render the toolbar
return class extends React.Component<
INotebookGeneratorToolbarProps,
INotebookGeneratorToolbarState
> {
constructor(props: INotebookGeneratorToolbarProps) {
super(props);
this.state = { numbering: false };
options.initializeOptions(false);
}
render() {
const toggleAutoNumbering = () => {
options.numbering = !options.numbering;
this.setState({ numbering: options.numbering });
};
let numberingIcon = this.state.numbering ? (
<div
className="toc-toolbar-auto-numbering-button toc-toolbar-button"
onClick={event => toggleAutoNumbering()}
>
<div
role="text"
aria-label="Toggle Auto-Numbering"
title="Toggle Auto-Numbering"
className="toc-toolbar-auto-numbering-icon toc-toolbar-icon-selected"
/>
</div>
) : (
<div
className="toc-toolbar-auto-numbering-button toc-toolbar-button"
onClick={event => toggleAutoNumbering()}
>
<div
role="text"
aria-label="Toggle Auto-Numbering"
title="Toggle Auto-Numbering"
className="toc-toolbar-auto-numbering-icon toc-toolbar-icon"
/>
</div>
);
return (
<div>
<div className={'toc-toolbar'}>{numberingIcon}</div>
</div>
);
}
};
}
================================================
FILE: src/generators/notebookgenerator/codemirror.tsx
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import * as React from 'react';
import { ISanitizer } from '@jupyterlab/apputils';
import { INotebookHeading } from './heading';
import { sanitizerOptions } from '../shared';
export interface ICodeComponentProps {
sanitizer: ISanitizer;
heading: INotebookHeading;
}
export interface ICodeComponentState {
heading: INotebookHeading;
}
export class CodeComponent extends React.Component<
ICodeComponentProps,
ICodeComponentState
> {
constructor(props: ICodeComponentProps) {
super(props);
this.state = { heading: props.heading };
}
componentWillReceiveProps(nextProps: ICodeComponentProps) {
this.setState({ heading: nextProps.heading });
}
render() {
// Grab the rendered CodeMirror DOM in the document, show it in TOC.
let html = this.state.heading.cellRef!.editor.host.innerHTML;
// Sanitize it to be safe.
html = this.props.sanitizer.sanitize(html, sanitizerOptions);
return (
<div className="cm-toc" dangerouslySetInnerHTML={{ __html: html }} />
);
}
}
================================================
FILE: src/generators/notebookgenerator/heading.ts
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { Cell } from '@jupyterlab/cells';
import { INumberedHeading } from '../shared';
/**
* A heading for a notebook cell.
*/
export interface INotebookHeading extends INumberedHeading {
type: 'header' | 'markdown' | 'code';
prompt?: string;
cellRef: Cell;
hasChild?: boolean;
}
================================================
FILE: src/generators/notebookgenerator/index.ts
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { ISanitizer } from '@jupyterlab/apputils';
import { CodeCell, CodeCellModel, MarkdownCell, Cell } from '@jupyterlab/cells';
import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
import { notebookItemRenderer } from './itemrenderer';
import { notebookGeneratorToolbar } from './toolbargenerator';
import { TableOfContentsRegistry } from '../../registry';
import { TableOfContents } from '../../toc';
import { NotebookGeneratorOptionsManager } from './optionsmanager';
import { INotebookHeading } from './heading';
import {
generateNumbering,
isDOM,
isMarkdown,
sanitizerOptions
} from '../shared';
/**
* Create a TOC generator for notebooks.
*
* @param tracker: A notebook tracker.
*
* @returns A TOC generator that can parse notebooks.
*/
export function createNotebookGenerator(
tracker: INotebookTracker,
sanitizer: ISanitizer,
widget: TableOfContents
): TableOfContentsRegistry.IGenerator<NotebookPanel> {
// Create a option manager to manage user settings
const options = new NotebookGeneratorOptionsManager(widget, tracker, {
needsNumbering: false,
sanitizer: sanitizer
});
return {
tracker,
usesLatex: true,
options: options,
toolbarGenerator: () => {
return notebookGeneratorToolbar(options, tracker);
},
itemRenderer: (item: INotebookHeading) => {
return notebookItemRenderer(options, item);
},
generate: panel => {
let headings: INotebookHeading[] = [];
let numberingDict: { [level: number]: number } = {};
let collapseLevel = -1;
// Keep track of the previous heading, so it can be
// marked as having a child if one is discovered
let prevHeading: INotebookHeading | null = null;
// Iterate through the cells in the notebook, generating their headings
for (let i = 0; i < panel.content.widgets.length; i++) {
let cell: Cell = panel.content.widgets[i];
let collapsed = cell.model.metadata.get('toc-hr-collapsed') as boolean;
collapsed = collapsed !== undefined ? collapsed : false;
let model = cell.model;
if (model.type === 'code') {
// Code is shown by default, overridden by previously saved settings
if (!widget || (widget && options.showCode)) {
// Generate the heading and add to headings if appropriate
let executionCountNumber = (cell as CodeCell).model
.executionCount as number | null;
let executionCount =
executionCountNumber !== null
? '[' + executionCountNumber + ']: '
: '[ ]: ';
let text = (model as CodeCellModel).value.text;
const onClickFactory = (line: number) => {
return () => {
panel.content.activeCellIndex = i;
cell.node.scrollIntoView();
};
};
let lastLevel = Private.getLastLevel(headings);
let renderedHeading = Private.getCodeCells(
text,
onClickFactory,
executionCount,
lastLevel,
cell
);
[headings, prevHeading] = Private.addMDOrCode(
headings,
renderedHeading,
prevHeading,
collapseLevel,
options.filtered
);
}
// Iterate over the code cell outputs to check for MD/HTML
for (let j = 0; j < (model as CodeCellModel).outputs.length; j++) {
const outputModel = (model as CodeCellModel).outputs.get(j);
const dataTypes = Object.keys(outputModel.data);
const htmlData = dataTypes.filter(t => isMarkdown(t) || isDOM(t));
if (!htmlData.length) {
continue;
}
// If MD/HTML generate the heading and add to headings if applicable
const outputWidget = (cell as CodeCell).outputArea.widgets[j];
const onClickFactory = (el: Element) => {
return () => {
panel.content.activeCellIndex = i;
panel.content.mode = 'command';
el.scrollIntoView();
};
};
let lastLevel = Private.getLastLevel(headings);
let numbering = options.numbering;
let renderedHeading = Private.getRenderedHTMLHeading(
outputWidget.node,
onClickFactory,
sanitizer,
numberingDict,
lastLevel,
numbering,
cell
);
[headings, prevHeading, collapseLevel] = Private.processMD(
renderedHeading,
options.showMarkdown,
headings,
prevHeading,
collapseLevel,
options.filtered,
collapsed
);
}
} else if (model.type === 'markdown') {
let mdCell = cell as MarkdownCell;
let renderedHeading: INotebookHeading | undefined = undefined;
let lastLevel = Private.getLastLevel(headings);
// If the cell is rendered, generate the ToC items from the HTML
if (mdCell.rendered && !mdCell.inputHidden) {
const onClickFactory = (el: Element) => {
return () => {
if (!mdCell.rendered) {
panel.content.activeCellIndex = i;
el.scrollIntoView();
} else {
panel.content.mode = 'command';
cell.node.scrollIntoView();
panel.content.activeCellIndex = i;
}
};
};
renderedHeading = Private.getRenderedHTMLHeading(
cell.node,
onClickFactory,
sanitizer,
numberingDict,
lastLevel,
options.numbering,
cell
);
// If not rendered, generate ToC items from the text of the cell
} else {
const onClickFactory = (line: number) => {
return () => {
panel.content.activeCellIndex = i;
cell.node.scrollIntoView();
};
};
renderedHeading = Private.getMarkdownHeading(
model!.value.text,
onClickFactory,
numberingDict,
lastLevel,
cell
);
}
// Add to headings if applicable
[headings, prevHeading, collapseLevel] = Private.processMD(
renderedHeading,
options.showMarkdown,
headings,
prevHeading,
collapseLevel,
options.filtered,
collapsed
);
}
}
return headings;
}
};
}
namespace Private {
/**
* Determine whether a heading is filtered out by selected tags.
*/
export function headingIsFilteredOut(
heading: INotebookHeading,
tags: string[]
) {
if (tags.length === 0) {
return false;
}
if (heading && heading.cellRef) {
let cellMetadata = heading.cellRef.model.metadata;
let cellTagsData = cellMetadata.get('tags') as string[];
if (cellTagsData) {
for (let j = 0; j < cellTagsData.length; j++) {
let name = cellTagsData[j];
for (let k = 0; k < tags.length; k++) {
if (tags[k] === name) {
return false;
}
}
}
}
}
return true;
}
export function getLastLevel(headings: INotebookHeading[]) {
if (headings.length > 0) {
let location = headings.length - 1;
while (location >= 0) {
if (headings[location].type === 'header') {
return headings[location].level;
}
location = location - 1;
}
}
return 0;
}
export function processMD(
renderedHeading: INotebookHeading | undefined,
showMarkdown: boolean,
headings: INotebookHeading[],
prevHeading: INotebookHeading | null,
collapseLevel: number,
filtered: string[],
collapsed: boolean
): [INotebookHeading[], INotebookHeading | null, number] {
// If the heading is MD and MD is shown, add to headings
if (
renderedHeading &&
renderedHeading.type === 'markdown' &&
showMarkdown
) {
[headings, prevHeading] = Private.addMDOrCode(
headings,
renderedHeading,
prevHeading,
collapseLevel,
filtered
);
// Otherwise, if the heading is a header, add to headings
} else if (renderedHeading && renderedHeading.type === 'header') {
[headings, prevHeading, collapseLevel] = Private.addHeader(
headings,
renderedHeading,
prevHeading,
collapseLevel,
filtered,
collapsed
);
}
return [headings, prevHeading, collapseLevel];
}
export function addMDOrCode(
headings: INotebookHeading[],
renderedHeading: INotebookHeading,
prevHeading: INotebookHeading | null,
collapseLevel: number,
filtered: string[]
): [INotebookHeading[], INotebookHeading | null] {
if (
!Private.headingIsFilteredOut(renderedHeading, filtered) &&
renderedHeading &&
renderedHeading.text
) {
// If there is a previous header, find it and mark hasChild true
if (prevHeading && prevHeading.type === 'header') {
for (let j = headings.length - 1; j >= 0; j--) {
if (headings[j] === prevHeading) {
headings[j].hasChild = true;
}
}
}
if (collapseLevel < 0) {
headings.push(renderedHeading);
}
prevHeading = renderedHeading;
}
return [headings, prevHeading];
}
export function addHeader(
headings: INotebookHeading[],
renderedHeading: INotebookHeading,
prevHeading: INotebookHeading | null,
collapseLevel: number,
filtered: string[],
collapsed: boolean
): [INotebookHeading[], INotebookHeading | null, number] {
if (!Private.headingIsFilteredOut(renderedHeading, filtered)) {
// if the previous heading is a header of a higher level,
// find it and mark it as having a child
if (
prevHeading &&
prevHeading.type === 'header' &&
prevHeading.level < renderedHeading.level
) {
for (let j = headings.length - 1; j >= 0; j--) {
if (headings[j] === prevHeading) {
headings[j].hasChild = true;
}
}
}
// if the collapse level doesn't include the header, or if there is no
// collapsing, add to headings and adjust the collapse level appropriately
if (collapseLevel >= renderedHeading.level || collapseLevel < 0) {
headings.push(renderedHeading);
collapseLevel = collapsed ? renderedHeading.level : -1;
}
prevHeading = renderedHeading;
} else if (prevHeading && renderedHeading.level <= prevHeading.level) {
// If header is filtered out and has a previous heading of smaller level, go
// back through headings to determine if it has a parent
let k = headings.length - 1;
let parentHeading = false;
while (k >= 0 && parentHeading === false) {
if (headings[k].level < renderedHeading.level) {
prevHeading = headings[k];
parentHeading = true;
}
k--;
}
// If there is no parent, set prevHeading to null and reset collapsing
if (!parentHeading) {
prevHeading = null;
collapseLevel = -1;
// Otherwise, reset collapsing appropriately
} else {
let parentState = headings[k + 1].cellRef.model.metadata.get(
'toc-hr-collapsed'
) as boolean;
parentState = parentState !== undefined ? parentState : false;
collapseLevel = parentState ? headings[k + 1].level : -1;
}
}
return [headings, prevHeading, collapseLevel];
}
/**
* Given a string of code, get the code entry.
*/
export function getCodeCells(
text: string,
onClickFactory: (line: number) => (() => void),
executionCount: string,
lastLevel: number,
cellRef: Cell
): INotebookHeading {
let headings: INotebookHeading[] = [];
if (text) {
const lines = text.split('\n');
let headingText = '';
let numLines = Math.min(lines.length, 3);
for (let i = 0; i < numLines - 1; i++) {
headingText = headingText + lines[i] + '\n';
}
headingText = headingText + lines[numLines - 1];
const onClick = onClickFactory(0);
const level = lastLevel + 1;
headings.push({
text: headingText,
level,
onClick,
type: 'code',
prompt: executionCount,
cellRef: cellRef,
hasChild: false
});
}
return headings[0];
}
/**
* Given a string of markdown, get the markdown headings in that string.
*/
export function getMarkdownHeading(
text: string,
onClickFactory: (line: number) => (() => void),
numberingDict: any,
lastLevel: number,
cellRef: Cell
): INotebookHeading {
const lines = text.split('\n');
const line = lines[0];
const line2 = lines.length > 1 ? lines[1] : undefined;
const onClick = onClickFactory(0);
// First test for '#'-style headers.
let match = line.match(/^([#]{1,6}) (.*)/);
let match2 = line2 && line2.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);
return {
text,
level,
numbering,
onClick,
type: 'header',
cellRef: cellRef,
hasChild: false
};
} else if (match2) {
// Next test for '==='-style headers.
const level = match2[1][0] === '=' ? 1 : 2;
// Take special care to parse markdown links into raw text.
const text = line.replace(/\[(.+)\]\(.+\)/g, '$1');
let numbering = generateNumbering(numberingDict, level);
return {
text,
level,
numbering,
onClick,
type: 'header',
cellRef: cellRef,
hasChild: false
};
} 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);
return {
text,
level,
numbering,
onClick,
type: 'header',
cellRef: cellRef,
hasChild: false
};
} else {
return {
text: line,
level: lastLevel + 1,
onClick,
type: 'markdown',
cellRef: cellRef,
hasChild: false
};
}
}
/**
* Given an HTML element, generate ToC headings
* by finding all the headers and making IHeading objects for them.
*/
export function getRenderedHTMLHeading(
node: HTMLElement,
onClickFactory: (el: Element) => (() => void),
sanitizer: ISanitizer,
numberingDict: { [level: number]: number },
lastLevel: number,
needsNumbering = false,
cellRef: Cell
): INotebookHeading | undefined {
let headingNodes = node.querySelectorAll('h1, h2, h3, h4, h5, h6, p');
if (headingNodes.length > 0) {
let markdownCell = headingNodes[0];
if (markdownCell.nodeName.toLowerCase() === 'p') {
if (markdownCell.innerHTML) {
let html = sanitizer.sanitize(
markdownCell.innerHTML,
sanitizerOptions
);
html = html.replace('¶', '');
return {
level: lastLevel + 1,
html: html,
text: markdownCell.textContent ? markdownCell.textContent : '',
onClick: onClickFactory(markdownCell),
type: 'markdown',
cellRef: cellRef,
hasChild: false
};
}
} else {
const heading = headingNodes[0];
const level = parseInt(heading.tagName[1], 10);
const text = heading.textContent ? heading.textContent : '';
let shallHide = !needsNumbering;
if (heading.getElementsByClassName('numbering-entry').length > 0) {
heading.removeChild(
heading.getElementsByClassName('numbering-entry')[0]
);
}
let html = sanitizer.sanitize(heading.innerHTML, sanitizerOptions);
html = html.replace('¶', '');
const onClick = onClickFactory(heading);
let numbering = generateNumbering(numberingDict, level);
let numDOM = '';
if (!shallHide) {
numDOM = '<span class="numbering-entry">' + numbering + '</span>';
}
heading.innerHTML = numDOM + html;
return {
level,
text,
numbering,
html,
onClick,
type: 'header',
cellRef: cellRef,
hasChild: false
};
}
}
return undefined;
}
}
================================================
FILE: src/generators/notebookgenerator/itemrenderer.tsx
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { CodeComponent } from './codemirror';
import { Cell } from '@jupyterlab/cells';
import { NotebookGeneratorOptionsManager } from './optionsmanager';
import { INotebookHeading } from './heading';
import { sanitizerOptions } from '../shared';
import * as React from 'react';
export function notebookItemRenderer(
options: NotebookGeneratorOptionsManager,
item: INotebookHeading
) {
let jsx;
if (item.type === 'markdown' || item.type === 'header') {
const collapseOnClick = (cellRef?: Cell) => {
let collapsed = cellRef!.model.metadata.get(
'toc-hr-collapsed'
) as boolean;
collapsed = collapsed != undefined ? collapsed : false;
cellRef!.model.metadata.set('toc-hr-collapsed', !collapsed);
options.updateWidget();
};
let fontSizeClass = 'toc-level-size-default';
let numbering = item.numbering && options.numbering ? item.numbering : '';
if (item.type === 'header') {
fontSizeClass = 'toc-level-size-' + item.level;
}
if (item.html && (item.type === 'header' || options.showMarkdown)) {
jsx = (
<span
dangerouslySetInnerHTML={{
__html:
numbering +
options.sanitizer.sanitize(item.html, sanitizerOptions)
}}
className={item.type + '-cell toc-cell-item ' + fontSizeClass}
/>
);
// Render the headers
if (item.type === 'header') {
let collapsed = item.cellRef!.model.metadata.get(
'toc-hr-collapsed'
) as boolean;
collapsed = collapsed != undefined ? collapsed : false;
// Render the twist button
let twistButton = (
<div
className="toc-collapse-button"
onClick={event => {
event.stopPropagation();
collapseOnClick(item.cellRef);
}}
>
<div className="toc-twist-placeholder">placeholder</div>
<div className="toc-downarrow-img toc-arrow-img" />
</div>
);
if (collapsed) {
twistButton = (
<div
className="toc-collapse-button"
onClick={event => {
event.stopPropagation();
collapseOnClick(item.cellRef);
}}
>
<div className="toc-twist-placeholder">placeholder</div>
<div className="toc-rightarrow-img toc-arrow-img" />
</div>
);
}
// Render the header item
jsx = (
<div className="toc-entry-holder">
{item.hasChild && twistButton}
{jsx}
</div>
);
}
} else if (item.type === 'header' || options.showMarkdown) {
// Render headers/markdown for plain text
jsx = (
<span className={item.type + '-cell toc-cell-item ' + fontSizeClass}>
{numbering + item.text}
</span>
);
if (item.type === 'header') {
let collapsed = item.cellRef!.model.metadata.get(
'toc-hr-collapsed'
) as boolean;
collapsed = collapsed != undefined ? collapsed : false;
let twistButton = (
<div
className="toc-collapse-button"
onClick={event => {
event.stopPropagation();
collapseOnClick(item.cellRef);
}}
>
<div className="toc-twist-placeholder">placeholder</div>
<div className="toc-downarrow-img toc-arrow-img" />
</div>
);
if (collapsed) {
twistButton = (
<div
className="toc-collapse-button"
onClick={event => {
event.stopPropagation();
collapseOnClick(item.cellRef);
}}
>
<div className="toc-twist-placeholder">placeholder</div>
<div className="toc-rightarrow-img toc-arrow-img" />
</div>
);
}
jsx = (
<div className="toc-entry-holder">
{item.hasChild && twistButton}
{jsx}
</div>
);
}
} else {
jsx = null;
}
} else if (item.type === 'code' && options.showCode) {
// Render code cells
jsx = (
<div className="toc-code-cell-div">
<div className="toc-code-cell-prompt">{item.prompt}</div>
<span className={'toc-code-span'}>
<CodeComponent sanitizer={options.sanitizer} heading={item} />
</span>
</div>
);
} else {
jsx = null;
}
return jsx;
}
================================================
FILE: src/generators/notebookgenerator/optionsmanager.ts
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { ISanitizer } from '@jupyterlab/apputils';
import { INotebookTracker } from '@jupyterlab/notebook';
import { TableOfContentsRegistry } from '../../registry';
import { TableOfContents } from '../../toc';
import { TagsToolComponent } from './tagstool';
export class NotebookGeneratorOptionsManager extends TableOfContentsRegistry.IGeneratorOptionsManager {
constructor(
widget: TableOfContents,
notebook: INotebookTracker,
options: {
needsNumbering: boolean;
sanitizer: ISanitizer;
tagTool?: TagsToolComponent;
}
) {
super();
this._numbering = options.needsNumbering;
this._widget = widget;
this._notebook = notebook;
this.sanitizer = options.sanitizer;
this.tagTool = null;
this.storeTags = [];
}
readonly sanitizer: ISanitizer;
public tagTool?: TagsToolComponent | null;
set notebookMetadata(value: [string, any]) {
if (this._notebook.currentWidget != null) {
this._notebook.currentWidget.model.metadata.set(value[0], value[1]);
}
}
set numbering(value: boolean) {
this._numbering = value;
this._widget.update();
this.notebookMetadata = ['toc-autonumbering', this._numbering];
}
get numbering() {
return this._numbering;
}
set showCode(value: boolean) {
this._showCode = value;
this.notebookMetadata = ['toc-showcode', this._showCode];
this._widget.update();
}
get showCode() {
return this._showCode;
}
set showMarkdown(value: boolean) {
this._showMarkdown = value;
this.notebookMetadata = ['toc-showmarkdowntxt', this._showMarkdown];
this._widget.update();
}
get showMarkdown() {
return this._showMarkdown;
}
set showTags(value: boolean) {
this._showTags = value;
this.notebookMetadata = ['toc-showtags', this._showTags];
this._widget.update();
}
get showTags() {
return this._showTags;
}
get filtered() {
if (this.tagTool) {
this._filtered = this.tagTool.getFiltered();
} else if (this.storeTags.length > 0) {
this._filtered = this.storeTags;
} else {
this._filtered = [];
}
return this._filtered;
}
set preRenderedToolbar(value: any) {
this._preRenderedToolbar = value;
}
get preRenderedToolbar() {
return this._preRenderedToolbar;
}
updateWidget() {
this._widget.update();
}
setTagTool(tagTool: TagsToolComponent | null) {
this.tagTool = tagTool;
}
// initialize options, will NOT change notebook metadata
initializeOptions(
numbering: boolean,
showCode: boolean,
showMarkdown: boolean,
showTags: boolean
) {
this._numbering = numbering;
this._showCode = showCode;
this._showMarkdown = showMarkdown;
this._showTags = showTags;
this._widget.update();
}
private _preRenderedToolbar: any = null;
private _filtered: string[] = [];
private _numbering: boolean;
private _showCode = false;
private _showMarkdown = false;
private _showTags = false;
private _notebook: INotebookTracker;
private _widget: TableOfContents;
public storeTags: string[];
}
================================================
FILE: src/generators/notebookgenerator/tagstool/index.tsx
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { INotebookTracker } from '@jupyterlab/notebook';
import { Cell } from '@jupyterlab/cells';
import { TagListComponent } from './tagslist';
import * as React from 'react';
import { NotebookGeneratorOptionsManager } from '../optionsmanager';
export interface ITagsToolComponentProps {
allTagsList: string[];
tracker: INotebookTracker;
generatorOptionsRef: NotebookGeneratorOptionsManager;
inputFilter: string[];
}
export interface ITagsToolComponentState {
selected: string[];
}
/*
* Create a React component that handles state for the tag dropdown
*/
export class TagsToolComponent extends React.Component<
ITagsToolComponentProps,
ITagsToolComponentState
> {
constructor(props: ITagsToolComponentProps) {
super(props);
this.state = {
selected: this.props.inputFilter
};
}
/*
* Manage the selection state of the dropdown, taking in the name of a tag and
* whether to add or remove it.
*/
changeSelectionState = (newState: string, add: boolean) => {
if (add) {
let selectedTags = this.state.selected;
selectedTags.push(newState);
this.setState({ selected: selectedTags });
this.filterTags(selectedTags);
} else {
let selectedTags = this.state.selected;
let newSelectedTags: string[] = [];
for (let i = 0; i < selectedTags.length; i++) {
if (selectedTags[i] !== newState) {
newSelectedTags.push(selectedTags[i]);
}
}
if (newSelectedTags.length === 0) {
newSelectedTags = [];
}
this.setState({ selected: newSelectedTags });
this.filterTags(newSelectedTags);
}
};
public getFiltered() {
return this.state.selected;
}
/*
* Deselect all tags in the dropdown and clear filters in the TOC.
*/
deselectAllTags = () => {
this.setState({ selected: [] });
this.props.generatorOptionsRef.updateWidget();
};
/**
* Check whether a cell is tagged with a certain string
*/
containsTag(tag: string, cell: Cell) {
if (cell === null) {
return false;
}
let tagList = cell.model.metadata.get('tags') as string[];
if (tagList) {
for (let i = 0; i < tagList.length; i++) {
if (tagList[i] === tag) {
return true;
}
}
return false;
}
}
/*
* Tells the generator to filter the TOC by the selected tags.
*/
filterTags = (selected: string[]) => {
this.setState({ selected });
this.props.generatorOptionsRef.updateWidget();
};
updateFilters = () => {
let temp: string[] = [];
let idx = 0;
let needsUpdate = false;
for (let i = 0; i < this.state.selected.length; i++) {
if (
this.props.allTagsList.indexOf(this.state.selected[i] as string) > -1
) {
temp[idx] = this.state.selected[i];
idx++;
} else if (this.props.generatorOptionsRef.showTags === true) {
needsUpdate = true;
}
}
if (needsUpdate) {
this.filterTags(temp);
this.setState({ selected: temp });
}
};
componentWillUpdate() {
this.updateFilters();
}
/*
* Render the interior of the tag dropdown.
*/
render() {
let renderedJSX = <div className="toc-no-tags-div">No Tags Available</div>;
let filterText;
if (this.state.selected.length === 0) {
filterText = (
<span className={'toc-filter-button-na'}> Clear Filters </span>
);
} else if (this.state.selected.length === 1) {
filterText = (
<span
className={'toc-filter-button'}
onClick={() => this.deselectAllTags()}
>
{' '}
Clear 1 Filter{' '}
</span>
);
} else {
filterText = (
<span
className={'toc-filter-button'}
onClick={() => this.deselectAllTags()}
>
{' '}
Clear {this.state.selected.length} Filters{' '}
</span>
);
}
if (this.props.allTagsList && this.props.allTagsList.length > 0) {
renderedJSX = (
<div className={'toc-tags-container'}>
<TagListComponent
allTagsList={this.props.allTagsList}
selectionStateHandler={this.changeSelectionState}
selectedTags={this.state.selected}
/>
{filterText}
</div>
);
}
return renderedJSX;
}
}
================================================
FILE: src/generators/notebookgenerator/tagstool/tag.tsx
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import * as React from 'react';
export interface ITagComponentProps {
selectionStateHandler: (newState: string, add: boolean) => void;
selectedTags: string[];
tag: string;
}
/*
* Create a React component containing one tag label
*/
export abstract class TagComponent extends React.Component<ITagComponentProps> {
constructor(props: ITagComponentProps) {
super(props);
}
render() {
const tag = this.props.tag as string;
return (
<div>
<label className="toc-tag-label" key={new Date().toLocaleTimeString()}>
{tag}
</label>
</div>
);
}
}
================================================
FILE: src/generators/notebookgenerator/tagstool/tagslist.tsx
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { TagComponent } from './tag';
import * as React from 'react';
/*
* The TagList takes a list of selected tags, a handler to change selection state,
* and a list of all tags (strings).
*/
export interface ITagListComponentProps {
selectedTags: string[];
selectionStateHandler: (newState: string, add: boolean) => void;
allTagsList: string[] | null;
}
/*
* The TagList state contains a list of selected tags
*/
export interface ITagListComponentState {
selected: string[];
}
/*
* Create a React component that renders all tags in a list.
*/
export class TagListComponent extends React.Component<
ITagListComponentProps,
ITagListComponentState
> {
constructor(props: ITagListComponentProps) {
super(props);
this.state = { selected: this.props.selectedTags };
}
/*
* Toggle whether a tag is selected when it is clicked
*/
selectedTagWithName = (name: string) => {
if (this.props.selectedTags.indexOf(name) >= 0) {
this.props.selectionStateHandler(name, false);
} else {
this.props.selectionStateHandler(name, true);
}
};
/*
* Render a tag, putting it in a TagComponent
*/
renderElementForTags = (tags: string[]) => {
const selectedTags = this.props.selectedTags;
const _self = this;
return tags.map((tag, index) => {
const tagClass =
selectedTags.indexOf(tag) >= 0
? 'toc-selected-tag toc-tag'
: 'toc-unselected-tag toc-tag';
return (
<div
key={tag}
className={tagClass}
onClick={event => {
_self.selectedTagWithName(tag);
}}
tabIndex={-1}
>
<TagComponent
selectionStateHandler={this.props.selectionStateHandler}
selectedTags={this.props.selectedTags}
tag={tag}
/>
</div>
);
});
};
/*
* Render the list of tags in the TOC tags dropdown.
*/
render() {
let allTagsList = this.props.allTagsList;
let renderedTagsForAllCells = null;
if (allTagsList) {
renderedTagsForAllCells = this.renderElementForTags(allTagsList);
}
return <div className="toc-tag-holder">{renderedTagsForAllCells}</div>;
}
}
================================================
FILE: src/generators/notebookgenerator/toolbargenerator.tsx
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { INotebookTracker } from '@jupyterlab/notebook';
import { JSONValue } from '@phosphor/coreutils';
import { NotebookGeneratorOptionsManager } from './optionsmanager';
import * as React from 'react';
import { TagsToolComponent } from './tagstool';
interface INotebookGeneratorToolbarProps {}
interface INotebookGeneratorToolbarState {
showCode: boolean;
showMarkdown: boolean;
showTags: boolean;
numbering: boolean;
}
export function notebookGeneratorToolbar(
options: NotebookGeneratorOptionsManager,
tracker: INotebookTracker
) {
// Render the toolbar
return class extends React.Component<
INotebookGeneratorToolbarProps,
INotebookGeneratorToolbarState
> {
constructor(props: INotebookGeneratorToolbarProps) {
super(props);
this.tagTool = null;
this.state = {
showCode: true,
showMarkdown: false,
showTags: false,
numbering: false
};
if (tracker.currentWidget) {
// Read saved user settings in notebook metadata
tracker.currentWidget.context.ready.then(() => {
if (tracker.currentWidget) {
tracker.currentWidget.content.activeCellChanged.connect(() => {
options.updateWidget();
});
let _numbering = tracker.currentWidget.model.metadata.get(
'toc-autonumbering'
) as boolean;
let numbering =
_numbering != undefined ? _numbering : options.numbering;
let _showCode = tracker.currentWidget.model.metadata.get(
'toc-showcode'
) as boolean;
let showCode =
_showCode != undefined ? _showCode : options.showCode;
let _showMarkdown = tracker.currentWidget.model.metadata.get(
'toc-showmarkdowntxt'
) as boolean;
let showMarkdown =
_showMarkdown != undefined ? _showMarkdown : options.showMarkdown;
let _showTags = tracker.currentWidget.model.metadata.get(
'toc-showtags'
) as boolean;
let showTags =
_showTags != undefined ? _showTags : options.showTags;
this.allTags = [];
options.initializeOptions(
numbering,
showCode,
showMarkdown,
showTags
);
this.setState({
showCode: options.showCode,
showMarkdown: options.showMarkdown,
showTags: options.showTags,
numbering: options.numbering
});
}
});
}
}
toggleCode = (component: React.Component) => {
options.showCode = !options.showCode;
this.setState({ showCode: options.showCode });
};
toggleMarkdown = (component: React.Component) => {
options.showMarkdown = !options.showMarkdown;
this.setState({ showMarkdown: options.showMarkdown });
};
toggleAutoNumbering = () => {
options.numbering = !options.numbering;
this.setState({ numbering: options.numbering });
};
toggleTagDropdown = () => {
if (options.showTags && this.tagTool) {
options.storeTags = this.tagTool.state.selected;
}
options.showTags = !options.showTags;
this.setState({ showTags: options.showTags });
};
// Load all tags in the document
getTags = () => {
let notebook = tracker.currentWidget;
if (notebook) {
const cells = notebook.model.cells;
const tagSet = new Set<string>();
this.allTags = [];
for (let i = 0; i < cells.length; i++) {
const cell = cells.get(i)!;
const tagData = cell.metadata.get('tags') as JSONValue;
if (Array.isArray(tagData)) {
tagData.forEach((tag: string) => tag && tagSet.add(tag));
}
}
this.allTags = Array.from(tagSet);
}
};
render() {
let codeIcon = this.state.showCode ? (
<div
className="toc-toolbar-code-button toc-toolbar-button"
onClick={event => this.toggleCode.bind(this)()}
>
<div
role="text"
aria-label="Toggle Code Cells"
title="Toggle Code Cells"
className="toc-toolbar-code-icon toc-toolbar-icon-selected"
/>
</div>
) : (
<div
className="toc-toolbar-code-button toc-toolbar-button"
onClick={event => this.toggleCode.bind(this)()}
>
<div
role="text"
aria-label="Toggle Code Cells"
title="Toggle Code Cells"
className="toc-toolbar-code-icon toc-toolbar-icon"
/>
</div>
);
let markdownIcon = this.state.showMarkdown ? (
<div
className="toc-toolbar-markdown-button toc-toolbar-button"
onClick={event => this.toggleMarkdown.bind(this)()}
>
<div
role="text"
aria-label="Toggle Markdown Text Cells"
title="Toggle Markdown Text Cells"
className="toc-toolbar-markdown-icon toc-toolbar-icon-selected"
/>
</div>
) : (
<div
className="toc-toolbar-markdown-button toc-toolbar-button"
onClick={event => this.toggleMarkdown.bind(this)()}
>
<div
role="text"
aria-label="Toggle Markdown Text Cells"
title="Toggle Markdown Text Cells"
className="toc-toolbar-markdown-icon toc-toolbar-icon"
/>
</div>
);
let numberingIcon = this.state.numbering ? (
<div
className="toc-toolbar-auto-numbering-button toc-toolbar-button"
onClick={event => this.toggleAutoNumbering()}
>
<div
role="text"
aria-label="Toggle Auto-Numbering"
title="Toggle Auto-Numbering"
className="toc-toolbar-auto-numbering-icon toc-toolbar-icon-selected"
/>
</div>
) : (
<div
className="toc-toolbar-auto-numbering-button toc-toolbar-button"
onClick={event => this.toggleAutoNumbering()}
>
<div
role="text"
aria-label="Toggle Auto-Numbering"
title="Toggle Auto-Numbering"
className="toc-toolbar-auto-numbering-icon toc-toolbar-icon"
/>
</div>
);
let tagDropdown = <div />;
let tagIcon = (
<div className="toc-toolbar-button">
<div
role="text"
aria-label="Show Tags Menu"
title="Show Tags Menu"
className="toc-toolbar-tag-icon toc-toolbar-icon"
/>
</div>
);
if (this.state.showTags) {
this.getTags();
let tagTool = (
<TagsToolComponent
allTagsList={this.allTags}
tracker={tracker}
generatorOptionsRef={options}
inputFilter={options.storeTags}
ref={tagTool => (this.tagTool = tagTool)}
/>
);
options.setTagTool(this.tagTool);
tagDropdown = <div className={'toc-tag-dropdown'}> {tagTool} </div>;
tagIcon = (
<div
role="text"
aria-label="Hide Tags Menu"
title="Hide Tags Menu"
className="toc-toolbar-tag-icon toc-toolbar-icon-selected"
/>
);
}
return (
<div>
<div className={'toc-toolbar'}>
{codeIcon}
{markdownIcon}
{numberingIcon}
<div
className={'toc-tag-dropdown-button'}
onClick={event => this.toggleTagDropdown()}
>
{tagIcon}
</div>
</div>
{tagDropdown}
</div>
);
}
allTags: string[];
tagTool: TagsToolComponent | null;
};
}
================================================
FILE: src/generators/shared.ts
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { IHeading } from '../toc';
const VDOM_MIME_TYPE = 'application/vdom.v1+json';
const HTML_MIME_TYPE = 'text/html';
export interface INumberedHeading extends IHeading {
numbering?: string | null;
}
/**
* Given a dictionary that keep tracks of the numbering and the level,
* update the dictionary.
*/
function incrementNumberingDict(dict: any, level: number) {
let x = level + 1;
while (x <= 6) {
if (dict[x] != undefined) {
dict[x] = undefined;
}
x++;
}
if (dict[level] === undefined) {
dict[level] = 1;
} else {
dict[level]++;
}
}
/**
* Given a dictionary that keep tracks of the numbering and the current level,
* generate the current numbering based on the dictionary and current level.
*/
export function generateNumbering(
numberingDict: { [level: number]: number },
level: number
) {
let numbering = undefined;
if (numberingDict != null) {
incrementNumberingDict(numberingDict, level);
numbering = '';
for (let j = 1; j <= level; j++) {
numbering +=
(numberingDict[j] == undefined ? '0' : numberingDict[j]) + '.';
if (j === level) {
numbering += ' ';
}
}
}
return numbering;
}
/**
* Return whether the mime type is some flavor of markdown.
*/
export function isMarkdown(mime: string): boolean {
return (
mime === 'text/x-ipythongfm' ||
mime === 'text/x-markdown' ||
mime === 'text/x-gfm' ||
mime === 'text/markdown'
);
}
/**
* Return whether the mime type is DOM-ish (html or vdom).
*/
export function isDOM(mime: string): boolean {
return mime === VDOM_MIME_TYPE || mime === HTML_MIME_TYPE;
}
/**
* Allowed HTML tags for the ToC entries. We use this to
* sanitize HTML headings, if they are given. We specifically
* disallow anchor tags, since we are adding our own.
*/
export const sanitizerOptions = {
allowedTags: [
'p',
'blockquote',
'b',
'i',
'strong',
'em',
'strike',
'code',
'br',
'div',
'span',
'pre',
'del'
],
allowedAttributes: {
// Allow "class" attribute for <code> tags.
code: ['class'],
// Allow "class" attribute for <span> tags.
span: ['class'],
// Allow "class" attribute for <div> tags.
div: ['class'],
// Allow "class" attribute for <p> tags.
p: ['class'],
// Allow "class" attribute for <pre> tags.
pre: ['class']
}
};
================================================
FILE: src/index.ts
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
export * from './toc';
export * from './registry';
export * from './generators';
================================================
FILE: src/registry.ts
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { IInstanceTracker } from '@jupyterlab/apputils';
import { Token } from '@phosphor/coreutils';
import { Widget } from '@phosphor/widgets';
import { IHeading } from './toc';
/**
* An interface for a TableOfContentsRegistry.
*/
export interface ITableOfContentsRegistry extends TableOfContentsRegistry {}
/* tslint:disable */
/**
* The TableOfContentsRegistry token.
*/
export const ITableOfContentsRegistry = new Token<TableOfContentsRegistry>(
'jupyterlab-toc:ITableOfContentsRegistry'
);
/* tslint:enable */
/**
* A class that keeps track of the different kinds
* of widgets for which there can be tables-of-contents.
*/
export class TableOfContentsRegistry {
/**
* Given a widget, find an IGenerator for it,
* or undefined if none can be found.
*/
findGeneratorForWidget(
widget: Widget
): TableOfContentsRegistry.IGenerator | undefined {
let generator: TableOfContentsRegistry.IGenerator | undefined;
this._generators.forEach(gen => {
if (gen.tracker.has(widget)) {
// If isEnabled is present, check for it.
if (gen.isEnabled && !gen.isEnabled(widget)) {
return;
}
generator = gen;
}
});
return generator;
}
/**
* Add a new IGenerator to the registry.
*/
addGenerator(generator: TableOfContentsRegistry.IGenerator): void {
this._generators.push(generator);
}
private _generators: TableOfContentsRegistry.IGenerator[] = [];
}
/**
* A namespace for TableOfContentsRegistry statics.
*/
export namespace TableOfContentsRegistry {
/**
* An interface for an object that knows how to generate a table-of-contents
* for a type of widget.
*/
export abstract class IGeneratorOptionsManager {}
export interface IGenerator<W extends Widget = Widget> {
/**
* An instance tracker for the widget.
*/
tracker: IInstanceTracker<W>;
/**
* A function to test whether to generate a ToC for a widget.
*
* #### Notes
* By default is assumed to be enabled if the widget
* is hosted in `tracker`. However, the user may want to add
* additional checks. For instance, this can be used to generate
* a ToC for text files only if they have a given mimeType.
*/
isEnabled?: (widget: W) => boolean;
/**
* Whether the document uses LaTeX typesetting.
*
* Defaults to `false`.
*/
usesLatex?: boolean;
/**
* An object that manage user settings for the generator.
*
* Defaults to `undefined`.
*/
options?: IGeneratorOptionsManager;
/**
* A function that generates JSX element for each heading
*
* If not given, the default renderer will be used, which renders the text
*/
itemRenderer?: (item: IHeading) => JSX.Element | null;
/**
* A function that generates a toolbar for the generator
*
* If not given, no toolbar will show up
*/
toolbarGenerator?: () => any;
/**
* A function that takes the widget, and produces
* a list of headings.
*/
generate(widget: W): IHeading[];
}
}
================================================
FILE: src/toc.tsx
================================================
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { ActivityMonitor, PathExt } from '@jupyterlab/coreutils';
import { IDocumentManager } from '@jupyterlab/docmanager';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { Message } from '@phosphor/messaging';
import { Widget } from '@phosphor/widgets';
import { TableOfContentsRegistry } from './registry';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
/**
* Timeout for throttling TOC rendering.
*/
const RENDER_TIMEOUT = 1000;
/**
* A widget for hosting a notebook table-of-contents.
*/
export class TableOfContents extends Widget {
/**
* Create a new table of contents.
*/
constructor(options: TableOfContents.IOptions) {
super();
this._docmanager = options.docmanager;
this._rendermime = options.rendermime;
}
/**
* The current widget-generator tuple for the ToC.
*/
get current(): TableOfContents.ICurrentWidget | null {
return this._current;
}
set current(value: TableOfContents.ICurrentWidget | null) {
// If they are the same as previously, do nothing.
if (
value &&
this._current &&
this._current.widget === value.widget &&
this._current.generator === value.generator
) {
return;
}
this._current = value;
if (this.generator && this.generator.toolbarGenerator) {
this._toolbar = this.generator.toolbarGenerator();
}
// Dispose an old activity monitor if it existsd
if (this._monitor) {
this._monitor.dispose();
this._monitor = null;
}
// If we are wiping the ToC, update and return.
if (!this._current) {
this.updateTOC();
return;
}
// Find the document model associated with the widget.
const context = this._docmanager.contextForWidget(this._current.widget);
if (!context || !context.model) {
throw Error('Could not find a context for the Table of Contents');
}
// Throttle the rendering rate of the table of contents.
this._monitor = new ActivityMonitor({
signal: context.model.contentChanged,
timeout: RENDER_TIMEOUT
});
this._monitor.activityStopped.connect(this.update, this);
this.updateTOC();
}
/**
* Handle an update request.
*/
protected onUpdateRequest(msg: Message): void {
// Don't bother if the TOC is not visible
/* if (!this.isVisible) {
return;
} */
this.updateTOC();
}
updateTOC() {
let toc: IHeading[] = [];
let title = 'Table of Contents';
if (this._current) {
toc = this._current.generator.generate(this._current.widget);
const context = this._docmanager.contextForWidget(this._current.widget);
if (context) {
title = PathExt.basename(context.localPath);
}
}
let itemRenderer: (item: IHeading) => JSX.Element | null = (
item: IHeading
) => {
return <span>{item.text}</span>;
};
if (this._current && this._current.generator.itemRenderer) {
itemRenderer = this._current.generator.itemRenderer!;
}
let renderedJSX = (
<div className="jp-TableOfContents">
<header>{title}</header>
</div>
);
if (this._current && this._current.generator) {
renderedJSX = (
<TOCTree
title={title}
toc={toc}
generator={this.generator}
itemRenderer={itemRenderer}
toolbar={this._toolbar}
/>
);
}
ReactDOM.render(renderedJSX, this.node, () => {
if (
this._current &&
this._current.generator.usesLatex === true &&
this._rendermime.latexTypesetter
) {
this._rendermime.latexTypesetter.typeset(this.node);
}
});
}
get generator() {
if (this._current) {
return this._current.generator;
}
return null;
}
/**
* Rerender after showing.
*/
protected onAfterShow(msg: Message): void {
this.update();
}
private _toolbar: any;
private _rendermime: IRenderMimeRegistry;
private _docmanager: IDocumentManager;
private _current: TableOfContents.ICurrentWidget | null;
private _monitor: ActivityMonitor<any, any> | null;
}
/**
* A namespace for TableOfContents statics.
*/
export namespace TableOfContents {
/**
* Options for the constructor.
*/
export interface IOptions {
/**
* The document manager for the application.
*/
docmanager: IDocumentManager;
/**
* The rendermime for the application.
*/
rendermime: IRenderMimeRegistry;
}
/**
* A type representing a tuple of a widget,
* and a generator that knows how to generate
* heading information from that widget.
*/
export interface ICurrentWidget<W extends Widget = Widget> {
widget: W;
generator: TableOfContentsRegistry.IGenerator<W>;
}
}
/**
* An object that represents a heading.
*/
export interface IHeading {
/**
* The text of the heading.
*/
text: string;
/**
* The HTML header level for the heading.
*/
level: number;
/**
* A function to execute when clicking the ToC
* item. Typically this will be used to scroll
* the parent widget to this item.
*/
onClick: () => void;
/**
* If there is special markup, we can instead
* render the heading using a raw HTML string. This
* HTML *should be properly sanitized!*
*
* For instance, this can be used to render
* already-renderd-to-html markdown headings.
*/
html?: string;
}
/**
* Props for the TOCItem component.
*/
export interface ITOCItemProps extends React.Props<TOCItem> {
/**
* An IHeading to render.
*/
heading: IHeading;
itemRenderer: (item: IHeading) => JSX.Element | null;
}
export interface ITOCItemStates {}
/**
* A React component for a table of contents entry.
*/
export class TOCItem extends React.Component<ITOCItemProps, ITOCItemStates> {
/**
* Render the item.
*/
render() {
const { heading } = this.props;
// Create an onClick handler for the TOC item
// that scrolls the anchor into view.
const handleClick = (event: React.SyntheticEvent<HTMLSpanElement>) => {
event.preventDefault();
event.stopPropagation();
heading.onClick();
};
let content = this.props.itemRenderer(heading);
return content && <li onClick={handleClick}>{content}</li>;
}
}
export interface ITOCTreeStates {}
/**
* Props for the TOCTree component.
*/
export interface ITOCTreeProps extends React.Props<TOCTree> {
/**
* A title to display.
*/
title: string;
/**
* A list of IHeadings to render.
*/
toc: IHeading[];
toolbar: any;
generator: TableOfContentsRegistry.IGenerator<Widget> | null;
itemRenderer: (item: IHeading) => JSX.Element | null;
}
/**
* A React component for a table of contents.
*/
export class TOCTree extends React.Component<ITOCTreeProps, ITOCTreeStates> {
/**
* Render the TOCTree.
*/
render() {
// Map the heading objects onto a list of JSX elements.
let i = 0;
const Toolbar = this.props.toolbar;
let listing: JSX.Element[] = this.props.toc.map(el => {
return (
<TOCItem
heading={el}
itemRenderer={this.props.itemRenderer}
key={`${el.text}-${el.level}-${i++}`}
/>
);
});
return (
<div className="jp-TableOfContents">
<header>{this.props.title}</header>
{Toolbar && <Toolbar />}
<ul className="jp-TableOfContents-content">{listing}</ul>
</div>
);
}
}
================================================
FILE: style/index.css
================================================
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------
| Table of Contents
|----------------------------------------------------------------------------*/
.jp-TableOfContents-content {
flex: 1 1 auto;
margin: 0;
padding: 0;
list-style-type: none;
overflow: auto;
background-color: var(--jp-layout-color1);
}
.jp-TableOfContents-content li {
display: flex;
flex-direction: row;
padding: 4px 12px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: pointer;
padding-top: 8px;
padding-bottom: 8px;
}
.jp-TableOfContents-content li:hover {
background: var(--jp-layout-color2);
}
.jp-TableOfContents {
display: flex;
flex-direction: column;
background: var(--jp-layout-color1);
color: var(--jp-ui-font-color1);
font-size: var(--jp-ui-font-size0);
height: 100%;
}
.jp-TableOfContents header {
border-bottom: var(--jp-border-width) solid var(--jp-border-color2);
flex: 0 0 auto;
font-size: var(--jp-ui-font-size0);
font-weight: 600;
letter-spacing: 1px;
margin: 0px;
padding: 12px 0 4px 12px;
text-transform: uppercase;
}
[data-theme-light='true'] .jp-TableOfContents-icon {
background-image: url(list-light.svg);
}
[data-theme-light='false'] .jp-TableOfContents-icon {
background-image: url(list-dark.svg);
}
.jp-TableOfContents-codeContainer {
overflow: hidden;
}
.jp-TableOfContents-code {
font-size: 9px;
max-height: 70px;
}
.cm-toc .CodeMirror {
font-size: 9px;
z-index: 0;
border: var(--jp-border-width) solid var(--jp-cell-editor-border-color);
border-radius: 0px;
background: var(--jp-cell-editor-background);
max-width: 100%;
max-height: 36px;
}
.toc-code-span {
width: 100%;
max-width: 100%;
overflow: hidden;
}
.cm-toc .CodeMirror-scroll {
overflow: hidden !important;
}
.CodeMirror-scroll::-webkit-scrollbar-track {
background-color: transparent;
}
.toc-toolbar-icon,
.toc-toolbar-icon-selected {
float: left;
padding: 0px;
margin: 4px;
background-repeat: no-repeat;
background-color: none;
background-size: 100%;
background-position: center;
height: 24px;
width: 24px;
margin: 4px;
border-radius: 2px;
}
[data-theme-light='true'] .toc-toolbar-code-icon {
background-image: url('img/code.svg');
}
[data-theme-light='false'] .toc-toolbar-code-icon {
background-image: url('img/code_darktheme.svg');
}
[data-theme-light='true'] .toc-toolbar-markdown-icon {
background-image: url('img/markdown.svg');
}
[data-theme-light='false'] .toc-toolbar-markdown-icon {
background-image: url('img/markdown_darktheme.svg');
}
[data-theme-light='true'] .toc-toolbar-auto-numbering-icon {
background-image: url('img/autonumbering.svg');
}
[data-theme-light='false'] .toc-toolbar-auto-numbering-icon {
background-image: url('img/autonumbering_darktheme.svg');
}
.toc-toolbar-tag-icon {
width: 30px;
height: 22px;
margin: 4px 9px 4px 4px;
}
[data-theme-light='true'] .toc-toolbar-tag-icon {
background-image: url('img/tag.svg');
}
[data-theme-light='false'] .toc-toolbar-tag-icon {
background-image: url('img/tag_darktheme.svg');
}
[data-theme-light='true'] .toc-toolbar-icon:hover {
background-color: var(--jp-input-background);
}
[data-theme-light='false'] .toc-toolbar-icon:hover {
background-color: #3a3a3a;
}
[data-theme-light='true'] .toc-toolbar-icon-selected {
background-color: var(--jp-layout-color2);
}
[data-theme-light='false'] .toc-toolbar-icon-selected {
background-color: #565656;
}
.toc-code-cell-prompt {
flex: 0 0 27px;
color: var(--jp-cell-prompt-not-active-font-color);
opacity: var(--jp-cell-prompt-not-active-opacity);
font-family: var(--jp-cell-prompt-font-family);
padding: var(--jp-code-padding);
padding-right: 0px;
padding-left: 0px;
letter-spacing: var(--jp-cell-prompt-letter-spacing);
line-height: var(--jp-code-line-height);
font-size: 8px;
border: var(--jp-border-width) solid transparent;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.toc-toolbar {
position: relative;
width: 100%;
margin: 0px;
user-select: none;
border-bottom: var(--jp-border-width) solid var(--jp-border-color2);
height: 36px;
display: flex;
align-items: center;
}
.toc-code-cell-div {
display: inline-flex;
width: 100%;
}
.toc-entry-holder {
display: inline-flex;
position: relative;
align-items: center;
width: 100%;
}
.toc-collapse-button {
padding-left: 3px;
cursor: default;
min-width: 11px;
position: absolute;
}
.toc-arrow-img {
top: 0;
bottom: 0;
margin: auto;
position: absolute;
background-repeat: no-repeat;
}
.toc-downarrow-img {
width: 12px;
height: 6px;
}
[data-theme-light='true'] .toc-downarrow-img {
background-image: url('img/toggle_down.svg');
}
[data-theme-light='false'] .toc-downarrow-img {
background-image: url('img/toggle_down_darktheme.svg');
}
.toc-rightarrow-img {
width: 7px;
height: 12px;
}
[data-theme-light='true'] .toc-rightarrow-img {
background-image: url('img/toggle_right.svg');
}
[data-theme-light='false'] .toc-rightarrow-img {
background-image: url('img/toggle_right_darktheme.svg');
}
.toc-twist-placeholder {
max-width: 10px;
opacity: 0;
overflow: hidden;
}
.cm-toc-plain-span {
width: 100%;
white-space: pre-wrap;
display: block;
}
.cm-toc-plain-textarea {
font-size: 9px;
z-index: 0;
border: var(--jp-border-width) solid var(--jp-cell-editor-border-color2);
border-radius: 0px;
background: var(--jp-cell-editor-background);
width: calc(100% - 9px);
overflow: hidden;
max-height: 74px;
resize: none;
font-family: var(--jp-code-font-family);
outline: none;
user-select: none;
white-space: pre;
padding: var(--jp-code-padding);
}
.cm-toc .CodeMirror-sizer {
min-width: 0px !important;
min-height: 0px !important;
margin-bottom: 0px !important;
}
.cm-toc .CodeMirror-line {
white-space: pre-wrap;
cursor: pointer;
}
.cm-toc .CodeMirror-lines {
cursor: pointer;
}
.toc-tag-dropdown {
display: flex;
width: 100%;
}
.toc-tag-dropdown-button {
margin-left: auto;
}
.toc-tags-container {
padding: 4px;
border-bottom: var(--jp-border-width) solid var(--jp-border-color2);
}
.toc-clear-button {
font-size: 12px;
color: var(--jp-ui-font-color1);
padding-left: 15px;
/* padding-top: 7px; */
user-select: none;
float: right;
}
.toc-clear-button:hover {
font-size: 12px;
color: var(--jp-ui-font-color2);
padding-left: 15px;
/* padding-top: 7px; */
user-select: none;
}
.toc-filter-button {
background-color: var(--jp-layout-color1);
border: solid 1px var(--jp-layout-color4);
border-radius: 3px;
width: fit-content;
padding: 5px;
padding-left: 6px;
padding-right: 6px;
margin-right: 17px;
color: var(--jp-layout-color5);
float: right;
font-size: 12px;
user-select: none;
margin-bottom: 13px;
}
.toc-filter-button:hover {
background-color: var(--jp-layout-color4);
border: solid 1px var(--jp-layout-color4);
color: var(--jp-layout-color1);
}
.toc-filter-button-na {
background-color: var(--jp-layout-color1);
border: solid 1px var(--jp-ui-font-color3);
border-radius: 3px;
width: fit-content;
padding: 5px;
padding-left: 6px;
padding-right: 6px;
margin-right: 17px;
color: var(--jp-ui-font-color3);
float: right;
font-size: 12px;
user-select: none;
margin-bottom: 13px;
}
.toc-no-tags-div {
font-size: 12px;
padding: 3px;
padding-bottom: 6px;
margin: auto;
color: var(--jp-layout-color4);
}
.toc-tags-container {
width: 100%;
}
.jp-TableOfContents-content code {
font-size: inherit;
}
.toc-cell-item {
padding-left: 24px;
}
/* styles for tags */
.toc-tag-label {
font-size: 11px;
max-width: 100%;
text-overflow: ellipsis;
display: inline-block;
overflow: hidden;
box-sizing: border-box;
padding-top: 0px;
margin-top: -1px;
margin-bottom: 0px;
user-select: none;
}
.toc-tag {
box-sizing: border-box;
height: 24px;
border-radius: 20px;
padding: 10px;
padding-bottom: 4px;
padding-top: 5px;
margin: 3px;
width: fit-content;
max-width: calc(100% - 25px);
}
.toc-selected-tag {
color: white;
background-color: #2196f3;
outline: none;
}
.toc-unselected-tag {
background-color: var(--jp-layout-color2);
outline: none;
}
.toc-tag-holder {
display: flex;
flex-wrap: wrap;
height: fit-content;
padding-bottom: 6px;
padding-right: 20px;
padding-left: 9px;
padding-top: 6px;
}
/* Font level sizes */
.toc-level-size-1 {
font-size: 16.89px;
}
.toc-level-size-2 {
font-size: 14.82px;
}
.toc-level-size-3 {
font-size: 13px;
}
.toc-level-size-4 {
font-size: 11.4px;
}
.toc-level-size-5 {
font-size: 10px;
}
.toc-level-size-6 {
font-size: 9px;
}
.toc-level-size-default {
font-size: 9px;
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"declaration": true,
"noImplicitAny": true,
"strictNullChecks": true,
"skipLibCheck": true,
"noEmitOnError": true,
"noUnusedLocals": true,
"lib": ["DOM", "ES6"],
"module": "commonjs",
"moduleResolution": "node",
"target": "ES6",
"outDir": "lib",
"rootDir": "src",
"jsx": "react"
},
"include": ["src/**/*"]
}
================================================
FILE: tslint.json
================================================
{
"rulesDirectory": ["tslint-plugin-prettier"],
"rules": {
"prettier": [true, { "singleQuote": true }],
"align": [true, "parameters", "statements"],
"ban": [
true,
["_", "forEach"],
["_", "each"],
["$", "each"],
["angular", "forEach"]
],
"class-name": true,
"comment-format": [true, "check-space"],
"curly": true,
"eofline": true,
"forin": false,
"indent": [true, "spaces", 2],
"interface-name": [true, "always-prefix"],
"jsdoc-format": true,
"label-position": true,
"max-line-length": [false],
"member-access": false,
"member-ordering": [false],
"new-parens": true,
"no-angle-bracket-type-assertion": true,
"no-any": false,
"no-arg": true,
"no-bitwise": true,
"no-conditional-assignment": true,
"no-consecutive-blank-lines": false,
"no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
"no-construct": true,
"no-debugger": true,
"no-default-export": false,
"no-duplicate-variable": true,
"no-empty": true,
"no-eval": true,
"no-inferrable-types": false,
"no-internal-module": true,
"no-invalid-this": [true, "check-function-in-method"],
"no-null-keyword": false,
"no-reference": true,
"no-require-imports": false,
"no-shadowed-variable": false,
"no-string-literal": false,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-use-before-declare": false,
"no-var-keyword": true,
"no-var-requires": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-finally",
"check-whitespace"
],
"one-variable-per-declaration": [true, "ignore-for-loop"],
"quotemark": [true, "single", "avoid-escape", "jsx-double"],
"radix": true,
"semicolon": [true, "always", "ignore-bound-class-methods"],
"switch-default": true,
"trailing-comma": [
false,
{
"multiline": "never",
"singleline": "never"
}
],
"triple-equals": [true, "allow-null-check", "allow-undefined-check"],
"typedef": [false],
"typedef-whitespace": [
false,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "space",
"index-signature": "space",
"parameter": "space",
"property-declaration": "space",
"variable-declaration": "space"
}
],
"use-isnan": true,
"use-strict": [false],
"variable-name": [
true,
"check-format",
"allow-leading-underscore",
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-operator",
"check-separator",
"check-type"
]
}
}
gitextract_6lby2670/ ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── package.json ├── src/ │ ├── extension.ts │ ├── generators/ │ │ ├── index.ts │ │ ├── latexgenerator.ts │ │ ├── markdowndocgenerator/ │ │ │ ├── index.ts │ │ │ ├── itemrenderer.tsx │ │ │ ├── optionsmanager.ts │ │ │ └── toolbargenerator.tsx │ │ ├── notebookgenerator/ │ │ │ ├── codemirror.tsx │ │ │ ├── heading.ts │ │ │ ├── index.ts │ │ │ ├── itemrenderer.tsx │ │ │ ├── optionsmanager.ts │ │ │ ├── tagstool/ │ │ │ │ ├── index.tsx │ │ │ │ ├── tag.tsx │ │ │ │ └── tagslist.tsx │ │ │ └── toolbargenerator.tsx │ │ └── shared.ts │ ├── index.ts │ ├── registry.ts │ └── toc.tsx ├── style/ │ └── index.css ├── tsconfig.json └── tslint.json
SYMBOL INDEX (100 symbols across 18 files)
FILE: src/extension.ts
function activateTOC (line 56) | function activateTOC(
FILE: src/generators/latexgenerator.ts
function createLatexGenerator (line 19) | function createLatexGenerator(
FILE: src/generators/markdowndocgenerator/index.ts
function createMarkdownGenerator (line 39) | function createMarkdownGenerator(
function createRenderedMarkdownGenerator (line 88) | function createRenderedMarkdownGenerator(
function getMarkdownDocHeadings (line 123) | function getMarkdownDocHeadings(
function getRenderedHTMLHeadingsForMarkdownDoc (line 210) | function getRenderedHTMLHeadingsForMarkdownDoc(
FILE: src/generators/markdowndocgenerator/itemrenderer.tsx
function markdownDocItemRenderer (line 10) | function markdownDocItemRenderer(
FILE: src/generators/markdowndocgenerator/optionsmanager.ts
class MarkdownDocGeneratorOptionsManager (line 10) | class MarkdownDocGeneratorOptionsManager extends TableOfContentsRegistry...
method constructor (line 11) | constructor(
method numbering (line 23) | set numbering(value: boolean) {
method numbering (line 28) | get numbering() {
method initializeOptions (line 33) | initializeOptions(numbering: boolean) {
FILE: src/generators/markdowndocgenerator/toolbargenerator.tsx
type INotebookGeneratorToolbarProps (line 8) | interface INotebookGeneratorToolbarProps {}
type INotebookGeneratorToolbarState (line 10) | interface INotebookGeneratorToolbarState {
function markdownDocGeneratorToolbar (line 14) | function markdownDocGeneratorToolbar(
FILE: src/generators/notebookgenerator/codemirror.tsx
type ICodeComponentProps (line 12) | interface ICodeComponentProps {
type ICodeComponentState (line 17) | interface ICodeComponentState {
class CodeComponent (line 21) | class CodeComponent extends React.Component<
method constructor (line 25) | constructor(props: ICodeComponentProps) {
method componentWillReceiveProps (line 30) | componentWillReceiveProps(nextProps: ICodeComponentProps) {
method render (line 34) | render() {
FILE: src/generators/notebookgenerator/heading.ts
type INotebookHeading (line 11) | interface INotebookHeading extends INumberedHeading {
FILE: src/generators/notebookgenerator/index.ts
function createNotebookGenerator (line 36) | function createNotebookGenerator(
function headingIsFilteredOut (line 204) | function headingIsFilteredOut(
function getLastLevel (line 228) | function getLastLevel(headings: INotebookHeading[]) {
function processMD (line 241) | function processMD(
function addMDOrCode (line 277) | function addMDOrCode(
function addHeader (line 305) | function addHeader(
function getCodeCells (line 365) | function getCodeCells(
function getMarkdownHeading (line 399) | function getMarkdownHeading(
function getRenderedHTMLHeading (line 475) | function getRenderedHTMLHeading(
FILE: src/generators/notebookgenerator/itemrenderer.tsx
function notebookItemRenderer (line 16) | function notebookItemRenderer(
FILE: src/generators/notebookgenerator/optionsmanager.ts
class NotebookGeneratorOptionsManager (line 14) | class NotebookGeneratorOptionsManager extends TableOfContentsRegistry.IG...
method constructor (line 15) | constructor(
method notebookMetadata (line 36) | set notebookMetadata(value: [string, any]) {
method numbering (line 42) | set numbering(value: boolean) {
method numbering (line 48) | get numbering() {
method showCode (line 52) | set showCode(value: boolean) {
method showCode (line 58) | get showCode() {
method showMarkdown (line 62) | set showMarkdown(value: boolean) {
method showMarkdown (line 68) | get showMarkdown() {
method showTags (line 72) | set showTags(value: boolean) {
method showTags (line 78) | get showTags() {
method filtered (line 82) | get filtered() {
method preRenderedToolbar (line 93) | set preRenderedToolbar(value: any) {
method preRenderedToolbar (line 97) | get preRenderedToolbar() {
method updateWidget (line 101) | updateWidget() {
method setTagTool (line 105) | setTagTool(tagTool: TagsToolComponent | null) {
method initializeOptions (line 110) | initializeOptions(
FILE: src/generators/notebookgenerator/tagstool/index.tsx
type ITagsToolComponentProps (line 10) | interface ITagsToolComponentProps {
type ITagsToolComponentState (line 17) | interface ITagsToolComponentState {
class TagsToolComponent (line 24) | class TagsToolComponent extends React.Component<
method constructor (line 28) | constructor(props: ITagsToolComponentProps) {
method getFiltered (line 61) | public getFiltered() {
method containsTag (line 76) | containsTag(tag: string, cell: Cell) {
method componentWillUpdate (line 119) | componentWillUpdate() {
method render (line 126) | render() {
FILE: src/generators/notebookgenerator/tagstool/tag.tsx
type ITagComponentProps (line 6) | interface ITagComponentProps {
method constructor (line 16) | constructor(props: ITagComponentProps) {
method render (line 20) | render() {
FILE: src/generators/notebookgenerator/tagstool/tagslist.tsx
type ITagListComponentProps (line 12) | interface ITagListComponentProps {
type ITagListComponentState (line 21) | interface ITagListComponentState {
class TagListComponent (line 28) | class TagListComponent extends React.Component<
method constructor (line 32) | constructor(props: ITagListComponentProps) {
method render (line 81) | render() {
FILE: src/generators/notebookgenerator/toolbargenerator.tsx
type INotebookGeneratorToolbarProps (line 14) | interface INotebookGeneratorToolbarProps {}
type INotebookGeneratorToolbarState (line 16) | interface INotebookGeneratorToolbarState {
function notebookGeneratorToolbar (line 23) | function notebookGeneratorToolbar(
FILE: src/generators/shared.ts
constant VDOM_MIME_TYPE (line 6) | const VDOM_MIME_TYPE = 'application/vdom.v1+json';
constant HTML_MIME_TYPE (line 8) | const HTML_MIME_TYPE = 'text/html';
type INumberedHeading (line 10) | interface INumberedHeading extends IHeading {
function incrementNumberingDict (line 18) | function incrementNumberingDict(dict: any, level: number) {
function generateNumbering (line 37) | function generateNumbering(
function isMarkdown (line 59) | function isMarkdown(mime: string): boolean {
function isDOM (line 71) | function isDOM(mime: string): boolean {
FILE: src/registry.ts
type ITableOfContentsRegistry (line 15) | interface ITableOfContentsRegistry extends TableOfContentsRegistry {}
class TableOfContentsRegistry (line 30) | class TableOfContentsRegistry {
method findGeneratorForWidget (line 35) | findGeneratorForWidget(
method addGenerator (line 54) | addGenerator(generator: TableOfContentsRegistry.IGenerator): void {
type IGenerator (line 72) | interface IGenerator<W extends Widget = Widget> {
FILE: src/toc.tsx
constant RENDER_TIMEOUT (line 22) | const RENDER_TIMEOUT = 1000;
class TableOfContents (line 27) | class TableOfContents extends Widget {
method constructor (line 31) | constructor(options: TableOfContents.IOptions) {
method current (line 40) | get current(): TableOfContents.ICurrentWidget | null {
method current (line 43) | set current(value: TableOfContents.ICurrentWidget | null) {
method onUpdateRequest (line 88) | protected onUpdateRequest(msg: Message): void {
method updateTOC (line 96) | updateTOC() {
method generator (line 141) | get generator() {
method onAfterShow (line 151) | protected onAfterShow(msg: Message): void {
type IOptions (line 169) | interface IOptions {
type ICurrentWidget (line 186) | interface ICurrentWidget<W extends Widget = Widget> {
type IHeading (line 195) | interface IHeading {
type ITOCItemProps (line 227) | interface ITOCItemProps extends React.Props<TOCItem> {
type ITOCItemStates (line 235) | interface ITOCItemStates {}
class TOCItem (line 240) | class TOCItem extends React.Component<ITOCItemProps, ITOCItemStates> {
method render (line 244) | render() {
type ITOCTreeStates (line 260) | interface ITOCTreeStates {}
type ITOCTreeProps (line 265) | interface ITOCTreeProps extends React.Props<TOCTree> {
class TOCTree (line 283) | class TOCTree extends React.Component<ITOCTreeProps, ITOCTreeStates> {
method render (line 288) | render() {
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (97K chars).
[
{
"path": ".gitignore",
"chars": 79,
"preview": "*.bundle.*\nlib/\nnode_modules/\n*.egg-info/\n.ipynb_checkpoints\npackage-lock.json\n"
},
{
"path": ".prettierignore",
"chars": 20,
"preview": "node_modules\n**/lib\n"
},
{
"path": ".prettierrc",
"chars": 25,
"preview": "{\n \"singleQuote\": true\n}"
},
{
"path": "LICENSE",
"chars": 1514,
"preview": "Copyright (c) 2017, Project Jupyter Contributors\nAll rights reserved.\n\nRedistribution and use in source and binary forms"
},
{
"path": "README.md",
"chars": 1167,
"preview": "# jupyterlab-toc\n\nA Table of Contents extension for JupyterLab.\nThis auto-generates a table of contents in the left area"
},
{
"path": "package.json",
"chars": 2242,
"preview": "{\n \"name\": \"@jupyterlab/toc\",\n \"version\": \"1.0.0-pre.1\",\n \"private\": false,\n \"description\": \"Table of Contents exten"
},
{
"path": "src/extension.ts",
"chars": 3551,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport {\n ILabS"
},
{
"path": "src/generators/index.ts",
"chars": 215,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nexport * from '."
},
{
"path": "src/generators/latexgenerator.ts",
"chars": 2428,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { IDocume"
},
{
"path": "src/generators/markdowndocgenerator/index.ts",
"chars": 7532,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { ISaniti"
},
{
"path": "src/generators/markdowndocgenerator/itemrenderer.tsx",
"chars": 976,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { Markdow"
},
{
"path": "src/generators/markdowndocgenerator/optionsmanager.ts",
"chars": 1031,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { ISaniti"
},
{
"path": "src/generators/markdowndocgenerator/toolbargenerator.tsx",
"chars": 1861,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { Markdow"
},
{
"path": "src/generators/notebookgenerator/codemirror.tsx",
"chars": 1132,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport * as Reac"
},
{
"path": "src/generators/notebookgenerator/heading.ts",
"chars": 397,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { Cell } "
},
{
"path": "src/generators/notebookgenerator/index.ts",
"chars": 17458,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { ISaniti"
},
{
"path": "src/generators/notebookgenerator/itemrenderer.tsx",
"chars": 4679,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { CodeCom"
},
{
"path": "src/generators/notebookgenerator/optionsmanager.ts",
"chars": 3202,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { ISaniti"
},
{
"path": "src/generators/notebookgenerator/tagstool/index.tsx",
"chars": 4446,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { INotebo"
},
{
"path": "src/generators/notebookgenerator/tagstool/tag.tsx",
"chars": 710,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport * as Reac"
},
{
"path": "src/generators/notebookgenerator/tagstool/tagslist.tsx",
"chars": 2314,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { TagComp"
},
{
"path": "src/generators/notebookgenerator/toolbargenerator.tsx",
"chars": 8026,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { INotebo"
},
{
"path": "src/generators/shared.ts",
"chars": 2502,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { IHeadin"
},
{
"path": "src/index.ts",
"chars": 185,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nexport * from '."
},
{
"path": "src/registry.ts",
"chars": 3212,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { IInstan"
},
{
"path": "src/toc.tsx",
"chars": 7584,
"preview": "// Copyright (c) Jupyter Development Team.\n// Distributed under the terms of the Modified BSD License.\n\nimport { Activit"
},
{
"path": "style/index.css",
"chars": 9213,
"preview": "/*-----------------------------------------------------------------------------\n| Copyright (c) Jupyter Development Team"
},
{
"path": "tsconfig.json",
"chars": 389,
"preview": "{\n \"compilerOptions\": {\n \"declaration\": true,\n \"noImplicitAny\": true,\n \"strictNullChecks\": true,\n \"skipLibC"
},
{
"path": "tslint.json",
"chars": 2983,
"preview": "{\n \"rulesDirectory\": [\"tslint-plugin-prettier\"],\n \"rules\": {\n \"prettier\": [true, { \"singleQuote\": true }],\n \"ali"
}
]
About this extraction
This page contains the full source code of the ian-r-rose/jupyterlab-toc GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (88.9 KB), approximately 22.8k tokens, and a symbol index with 100 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.