123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import { showErrorMessage } from '@jupyterlab/apputils';
- import { ActivityMonitor } from '@jupyterlab/coreutils';
- import {
- ABCWidgetFactory,
- DocumentRegistry,
- DocumentWidget
- } from '@jupyterlab/docregistry';
- import {
- IRenderMime,
- IRenderMimeRegistry,
- MimeModel
- } from '@jupyterlab/rendermime';
- import { PromiseDelegate } from '@lumino/coreutils';
- import { Message } from '@lumino/messaging';
- import { JSONObject } from '@lumino/coreutils';
- import { StackedLayout, Widget } from '@lumino/widgets';
- /**
- * The class name added to a markdown viewer.
- */
- const MARKDOWNVIEWER_CLASS = 'jp-MarkdownViewer';
- /**
- * The markdown MIME type.
- */
- const MIMETYPE = 'text/markdown';
- /**
- * A widget for markdown documents.
- */
- export class MarkdownViewer extends Widget {
- /**
- * Construct a new markdown viewer widget.
- */
- constructor(options: MarkdownViewer.IOptions) {
- super();
- this.context = options.context;
- this.renderer = options.renderer;
- this.node.tabIndex = -1;
- this.addClass(MARKDOWNVIEWER_CLASS);
- const layout = (this.layout = new StackedLayout());
- layout.addWidget(this.renderer);
- void this.context.ready.then(async () => {
- await this._render();
- // Throttle the rendering rate of the widget.
- this._monitor = new ActivityMonitor({
- signal: this.context.model.contentChanged,
- timeout: this._config.renderTimeout
- });
- this._monitor.activityStopped.connect(this.update, this);
- this._ready.resolve(undefined);
- });
- }
- /**
- * A promise that resolves when the markdown viewer is ready.
- */
- get ready(): Promise<void> {
- return this._ready.promise;
- }
- /**
- * Set URI fragment identifier.
- */
- setFragment(fragment: string) {
- this._fragment = fragment;
- this.update();
- }
- /**
- * Set a config option for the markdown viewer.
- */
- setOption<K extends keyof MarkdownViewer.IConfig>(
- option: K,
- value: MarkdownViewer.IConfig[K]
- ): void {
- if (this._config[option] === value) {
- return;
- }
- this._config[option] = value;
- const { style } = this.renderer.node;
- switch (option) {
- case 'fontFamily':
- style.fontFamily = value as string | null;
- break;
- case 'fontSize':
- style.fontSize = value ? value + 'px' : null;
- break;
- case 'hideFrontMatter':
- this.update();
- break;
- case 'lineHeight':
- style.lineHeight = value ? value.toString() : null;
- break;
- case 'lineWidth':
- const padding = value ? `calc(50% - ${(value as number) / 2}ch)` : null;
- style.paddingLeft = padding;
- style.paddingRight = padding;
- break;
- case 'renderTimeout':
- this._monitor.timeout = value as number;
- break;
- default:
- break;
- }
- }
- /**
- * Dispose of the resources held by the widget.
- */
- dispose(): void {
- if (this.isDisposed) {
- return;
- }
- if (this._monitor) {
- this._monitor.dispose();
- }
- this._monitor = null;
- super.dispose();
- }
- /**
- * Handle an `update-request` message to the widget.
- */
- protected onUpdateRequest(msg: Message): void {
- if (this.context.isReady && !this.isDisposed) {
- void this._render();
- this._fragment = '';
- }
- }
- /**
- * Handle `'activate-request'` messages.
- */
- protected onActivateRequest(msg: Message): void {
- this.node.focus();
- }
- /**
- * Render the mime content.
- */
- private async _render(): Promise<void> {
- if (this.isDisposed) {
- return;
- }
- // Since rendering is async, we note render requests that happen while we
- // actually are rendering for a future rendering.
- if (this._isRendering) {
- this._renderRequested = true;
- return;
- }
- // Set up for this rendering pass.
- this._renderRequested = false;
- const { context } = this;
- const { model } = context;
- const source = model.toString();
- const data: JSONObject = {};
- // If `hideFrontMatter`is true remove front matter.
- data[MIMETYPE] = this._config.hideFrontMatter
- ? Private.removeFrontMatter(source)
- : source;
- const mimeModel = new MimeModel({
- data,
- metadata: { fragment: this._fragment }
- });
- try {
- // Do the rendering asynchronously.
- this._isRendering = true;
- await this.renderer.renderModel(mimeModel);
- this._isRendering = false;
- // If there is an outstanding request to render, go ahead and render
- if (this._renderRequested) {
- return this._render();
- }
- } catch (reason) {
- // Dispose the document if rendering fails.
- requestAnimationFrame(() => {
- this.dispose();
- });
- void showErrorMessage(`Renderer Failure: ${context.path}`, reason);
- }
- }
- readonly context: DocumentRegistry.Context;
- readonly renderer: IRenderMime.IRenderer;
- private _config = { ...MarkdownViewer.defaultConfig };
- private _fragment = '';
- private _monitor: ActivityMonitor<DocumentRegistry.IModel, void> | null;
- private _ready = new PromiseDelegate<void>();
- private _isRendering = false;
- private _renderRequested = false;
- }
- /**
- * The namespace for MarkdownViewer class statics.
- */
- export namespace MarkdownViewer {
- /**
- * The options used to initialize a MarkdownViewer.
- */
- export interface IOptions {
- /**
- * Context
- */
- context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
- /**
- * The renderer instance.
- */
- renderer: IRenderMime.IRenderer;
- }
- export interface IConfig {
- /**
- * User preferred font family for markdown viewer.
- */
- fontFamily: string | null;
- /**
- * User preferred size in pixel of the font used in markdown viewer.
- */
- fontSize: number | null;
- /**
- * User preferred text line height, as a multiplier of font size.
- */
- lineHeight: number | null;
- /**
- * User preferred text line width expressed in CSS ch units.
- */
- lineWidth: number | null;
- /**
- * Whether to hide the YALM front matter.
- */
- hideFrontMatter: boolean;
- /**
- * The render timeout.
- */
- renderTimeout: number;
- }
- /**
- * The default configuration options for an editor.
- */
- export const defaultConfig: MarkdownViewer.IConfig = {
- fontFamily: null,
- fontSize: null,
- lineHeight: null,
- lineWidth: null,
- hideFrontMatter: true,
- renderTimeout: 1000
- };
- }
- /**
- * A document widget for markdown content.
- */
- export class MarkdownDocument extends DocumentWidget<MarkdownViewer> {
- setFragment(fragment: string): void {
- this.content.setFragment(fragment);
- }
- }
- /**
- * A widget factory for markdown viewers.
- */
- export class MarkdownViewerFactory extends ABCWidgetFactory<MarkdownDocument> {
- /**
- * Construct a new markdown viewer widget factory.
- */
- constructor(options: MarkdownViewerFactory.IOptions) {
- super(Private.createRegistryOptions(options));
- this._fileType = options.primaryFileType;
- this._rendermime = options.rendermime;
- }
- /**
- * Create a new widget given a context.
- */
- protected createNewWidget(
- context: DocumentRegistry.Context
- ): MarkdownDocument {
- const rendermime = this._rendermime.clone({
- resolver: context.urlResolver
- });
- const renderer = rendermime.createRenderer(MIMETYPE);
- const content = new MarkdownViewer({ context, renderer });
- content.title.iconClass = this._fileType.iconClass;
- content.title.iconLabel = this._fileType.iconLabel;
- const widget = new MarkdownDocument({ content, context });
- return widget;
- }
- private _fileType: DocumentRegistry.IFileType;
- private _rendermime: IRenderMimeRegistry;
- }
- /**
- * The namespace for MarkdownViewerFactory class statics.
- */
- export namespace MarkdownViewerFactory {
- /**
- * The options used to initialize a MarkdownViewerFactory.
- */
- export interface IOptions extends DocumentRegistry.IWidgetFactoryOptions {
- /**
- * The primary file type associated with the document.
- */
- primaryFileType: DocumentRegistry.IFileType;
- /**
- * The rendermime instance.
- */
- rendermime: IRenderMimeRegistry;
- }
- }
- /**
- * A namespace for markdown viewer widget private data.
- */
- namespace Private {
- /**
- * Create the document registry options.
- */
- export function createRegistryOptions(
- options: MarkdownViewerFactory.IOptions
- ): DocumentRegistry.IWidgetFactoryOptions {
- return {
- ...options,
- readOnly: true
- } as DocumentRegistry.IWidgetFactoryOptions;
- }
- /**
- * Remove YALM front matter from source.
- */
- export function removeFrontMatter(source: string): string {
- const re = /^---\n[^]*?\n(---|...)\n/;
- const match = source.match(re);
- if (!match) {
- return source;
- }
- const { length } = match[0];
- return source.slice(length);
- }
- }
|