diff --git a/src/index.ts b/src/index.ts index 63cd57ccd8..a7a0e6422d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,13 +5,15 @@ import { kernelStatusPlugin } from './status'; import { FileRadarPlugin } from './fileradar'; import { SessionExpPlugin } from './sessionexp'; import { SyntaxHighlightPlugin } from './syntax-highlight'; +import { HTMLReplacementPlugin } from './overridehtml'; const plugins: JupyterFrontEndPlugin[] = [ kernelStatusPlugin, ExternalLinksPlugin, FileRadarPlugin, SessionExpPlugin, - SyntaxHighlightPlugin + SyntaxHighlightPlugin, + HTMLReplacementPlugin ]; export default plugins; diff --git a/src/overridehtml.tsx b/src/overridehtml.tsx new file mode 100644 index 0000000000..efbd8027e1 --- /dev/null +++ b/src/overridehtml.tsx @@ -0,0 +1,54 @@ +import { + JupyterFrontEnd, + JupyterFrontEndPlugin +} from '@jupyterlab/application'; + +import { ReadonlyPartialJSONObject } from '@lumino/coreutils'; + +import { + IRenderMime, + IRenderMimeRegistry, + RenderedHTML +} from '@jupyterlab/rendermime'; + +class CustomRenderedHTML extends RenderedHTML { + /** + * Render a mime model as html content, replacing unsupported output. + */ + render(model: IRenderMime.IMimeModel): Promise { + const data = { ...model.data }; + const html = data['text/html']; + // Replace html as needed. + if (html && typeof html === 'string') { + // Look for PixieDust-related content + if (html.indexOf('#twistie') !== -1 || html.indexOf('pd_save') !== -1) { + data['text/html'] = + '

PixieDust is not yet supported in JupyterLab

'; + // Look for jQuery content + } else if (html.indexOf('$(') !== -1) { + data['text/html'] = '

JQuery is not supported in JupyterLab

'; + } + } + return super.render({ ...model, data: data as ReadonlyPartialJSONObject }); + } +} + +/** + * HTML Replacement plugin. + */ +export const HTMLReplacementPlugin: JupyterFrontEndPlugin = { + id: 'data_studio:html_replacement_plugin', + autoStart: true, + requires: [IRenderMimeRegistry], + activate: async (app: JupyterFrontEnd, registry: IRenderMimeRegistry) => { + console.log('JupyterLab extension "HTML Replacement" is activated!'); + + const factory: IRenderMime.IRendererFactory = { + safe: true, + mimeTypes: ['text/html'], + defaultRank: 50, + createRenderer: options => new CustomRenderedHTML(options) + }; + registry.addFactory(factory, 50); + } +}; diff --git a/test/overridehtml.spec.ts b/test/overridehtml.spec.ts new file mode 100644 index 0000000000..86f8b87c80 --- /dev/null +++ b/test/overridehtml.spec.ts @@ -0,0 +1,70 @@ +import { JupyterLab } from '@jupyterlab/application'; + +import { + IRenderMimeRegistry, + RenderMimeRegistry, + standardRendererFactories +} from '@jupyterlab/rendermime'; + +import { HTMLReplacementPlugin } from '../src/overridehtml'; + +import { defaultSanitizer } from '@jupyterlab/apputils'; + +describe('html override', () => { + let app: JupyterLab; + let registry: IRenderMimeRegistry; + const sanitizer = defaultSanitizer; + const options = { + mimeType: 'text/html', + sanitizer, + resolver: null, + linkHandler: null, + latexTypesetter: null + }; + + beforeEach(async () => { + app = new JupyterLab(); + registry = new RenderMimeRegistry({ + initialFactories: standardRendererFactories + }); + }); + + it('should replace pixiedust content', async () => { + await HTMLReplacementPlugin.activate(app, registry); + const data = { 'text/html': '$("#twistie6a9330b3").click(function()' }; + const factory = registry.getFactory('text/html'); + const renderer = factory!.createRenderer(options); + await renderer.renderModel(registry.createModel({ data })); + expect(renderer.node.innerHTML).toBe( + '

PixieDust is not yet supported in JupyterLab

' + ); + + data['text/html'] = '