Skip to content

Commit

Permalink
update package
Browse files Browse the repository at this point in the history
  • Loading branch information
mj10021 committed Apr 9, 2024
1 parent 4d55a5f commit 6bf6580
Show file tree
Hide file tree
Showing 4 changed files with 378 additions and 30 deletions.
12 changes: 7 additions & 5 deletions html_toc_export/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@
import tornado

class RouteHandler(APIHandler):
# The following decorator should be present on all verb methods (head, get, post,
# The following ecorator should be present on all verb methods (head, get, post,
# patch, put, delete, options) to ensure only authorized user can request the
# Jupyter server
@tornado.web.authenticated
def get(self):
self.finish(json.dumps({
"data": "This is /html-toc-export/get-example endpoint!"
}))
with open(r"./style/toc.css", "r") as f:
content = f.read()
self.set_header("Content-Type", "text/css")
self.write(content)



def setup_handlers(web_app):
host_pattern = ".*$"

base_url = web_app.settings["base_url"]
route_pattern = url_path_join(base_url, "html-toc-export", "get-example")
route_pattern = url_path_join(base_url, "html-toc-export", "toc-css")
handlers = [(route_pattern, RouteHandler)]
web_app.add_handlers(host_pattern, handlers)
23 changes: 13 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"files": [
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
"style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
"src/**/*.{ts,tsx}"
"src/**/*.{ts,tsx}",
"schema/*.json"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand Down Expand Up @@ -58,7 +59,8 @@
"dependencies": {
"@jupyterlab/application": "^4.0.0",
"@jupyterlab/coreutils": "^6.0.0",
"@jupyterlab/services": "^7.0.0"
"@jupyterlab/services": "^7.0.0",
"toc": "^0.4.0"
},
"devDependencies": {
"@jupyterlab/builder": "^4.0.0",
Expand Down Expand Up @@ -98,17 +100,18 @@
},
"jupyterlab": {
"discovery": {
"server": {
"managers": [
"pip"
],
"base": {
"name": "html_toc_export"
"server": {
"managers": [
"pip"
],
"base": {
"name": "html_toc_export"
}
}
}
},
"extension": true,
"outputDir": "html_toc_export/labextension"
"outputDir": "html_toc_export/labextension",
"schemaDir": "schema"
},
"eslintIgnore": [
"node_modules",
Expand Down
189 changes: 174 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,187 @@ import {
JupyterFrontEndPlugin
} from '@jupyterlab/application';

import { requestAPI } from './handler';
import {
INotebookTracker, NotebookPanel
} from '@jupyterlab/notebook';

import {
ITableOfContentsTracker,
TableOfContents
} from '@jupyterlab/toc';

import { ICommandPalette } from '@jupyterlab/apputils';

import { PageConfig, URLExt } from '@jupyterlab/coreutils';

import { ServerConnection } from '@jupyterlab/services';




/**
* Initialization data for the html-toc-export extension.
* Initialization data for the myextension extension.
*/
const plugin: JupyterFrontEndPlugin<void> = {
id: 'html-toc-export:plugin',
description: 'Export HTML with table of contents',
description: 'A JupyterLab extension.',
autoStart: true,
activate: (app: JupyterFrontEnd) => {
console.log('JupyterLab extension html-toc-export is activated!');

requestAPI<any>('get-example')
.then(data => {
console.log(data);
})
.catch(reason => {
console.error(
`The html_toc_export server extension appears to be missing.\n${reason}`
);
});
requires: [ICommandPalette, INotebookTracker, ITableOfContentsTracker],
activate: (
app: JupyterFrontEnd,
palette: ICommandPalette,
tracker: INotebookTracker,
toc: ITableOfContentsTracker,
) => {
console.log('JupyterLab extension myextension is activated!');
console.log('ICommandPalette:', palette);


const command: string = 'toc:export';
app.commands.addCommand( command, {
label: 'Export HTML w/ ToC',
execute: () => {
let current = getCurrent(tracker, app.shell);
getHTML(current.context.path)
.then((response) => {
let toc_model = toc.get(current)!;
let domParser = new DOMParser();
let doc = domParser.parseFromString(response, "text/html");
let toc_html = doc.createElement("div");
toc_html.innerHTML = generateToCHTML(toc_model);
doc.body.prepend(toc_html);
getCSS()
.then((response) => {
let css_node = doc.createElement("style");
css_node.innerHTML = response;
doc.head.appendChild(css_node);
downloadHtmlDocument(doc, "test.html");
})
.catch((error) => {
console.error('Error:', error);
});
});
}
});
palette.addItem({ command, category: 'Tutorial'});
}
};

function generateToCHTML(model: TableOfContents.IModel<TableOfContents.IHeading>): string {
// Recursive function to process headings and generate HTML
function processHeadings(headings: TableOfContents.IHeading[], level: number): string {
let html = '';
let index = 0;

while (index < headings.length) {
const heading = headings[index];
if (heading.text.length < 1) {
index++;
continue;
}

if (heading.level === level) {
html += `<li class="toc-item"><span><h${level}>${heading.text}</h${level}></span>`;

// Find and process subheadings
const subHeadingsStart = index + 1;
let subHeadingsEnd = headings.findIndex((h, i) => i >= subHeadingsStart && h.level <= level);
subHeadingsEnd = subHeadingsEnd === -1 ? headings.length : subHeadingsEnd;
const subHeadings = headings.slice(subHeadingsStart, subHeadingsEnd);

if (subHeadings.length > 0) {
html += `<ul class="toc-sublist toc-level-${level + 1}">${processHeadings(subHeadings, level + 1)}</ul>`;
}

html += `</li>`;
index = subHeadingsEnd;
} else {
index++;
}
}

return html;
}

return `<div id="toc-wrapper" class="toc-wrapper">
<div class="toc">
<ul id="navigate_menu" class="toc toc-level-1">${processHeadings(model.headings, 1)}</ul>
</div>
</div>`;
}


function downloadHtmlDocument(htmlDocument: Document, filename: string) {
// Serialize the HTMLDocument to a string
const serializer = new XMLSerializer();
const htmlString = serializer.serializeToString(htmlDocument);

// Create a Blob from the HTML string
const blob = new Blob([htmlString], { type: 'text/html' });

// Create a URL for the Blob
const url = URL.createObjectURL(blob);

// Create a temporary link element and trigger the download
const link = document.createElement('a');
link.href = url;
link.download = filename || 'document.html';
document.body.appendChild(link);
link.click();

// Clean up by removing the link element and revoking the Blob URL
document.body.removeChild(link);
URL.revokeObjectURL(url);
}

function getCurrent(
tracker: INotebookTracker,
shell: JupyterFrontEnd.IShell,
): NotebookPanel {
const widget = tracker.currentWidget;

if (widget) {
shell.activateById(widget.id);
}

return widget!;
}

async function getHTML(path: string): Promise<string>{
const settings = ServerConnection.makeSettings();
let response: Response;
let url = PageConfig.getNBConvertURL({
format: 'HTML',
download: false,
path,
});
try {
response = await ServerConnection.makeRequest(url, {}, settings);
} catch (error) {
throw new ServerConnection.NetworkError(error as any);
}
let data: any = await response.text();
if (!response.ok) {
throw new ServerConnection.ResponseError(response, data.message || data);
}
return data
}

async function getCSS(): Promise<string>{
const settings = ServerConnection.makeSettings();
let response: Response;
let baseUrl = PageConfig.getBaseUrl();
let url = URLExt.join(baseUrl, "html-toc-export", "toc-css");
try {
response = await ServerConnection.makeRequest(url, {}, settings);
} catch (error) {
throw new ServerConnection.NetworkError(error as any);
}
let data: any = await response.text();
if (!response.ok) {
throw new ServerConnection.ResponseError(response, data.message || data);
}
return data
}

export default plugin;
Loading

0 comments on commit 6bf6580

Please sign in to comment.