CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-jupyterlab--rendermime-interfaces

TypeScript interfaces for implementing MIME renderer extensions in JupyterLab

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

utility-services.mddocs/

Utility Services

Supporting interfaces for HTML sanitization, URL resolution, LaTeX typesetting, and Markdown parsing.

Capabilities

HTML Sanitization

ISanitizer Interface

Handles HTML sanitization to ensure safe rendering of untrusted content.

/**
 * An object that handles html sanitization
 */
interface ISanitizer {
  /** Whether to replace URLs by HTML anchors */
  getAutolink?(): boolean;
  /**
   * Sanitize an HTML string
   * @param dirty - The dirty text
   * @param options - The optional sanitization options
   * @returns The sanitized string
   */
  sanitize(dirty: string, options?: ISanitizerOptions): string;
  /** Whether to allow name and id properties */
  readonly allowNamedProperties?: boolean;
}

ISanitizerOptions Interface

Configuration options for HTML sanitization.

/**
 * The options used to sanitize
 */
interface ISanitizerOptions {
  /** The allowed tags */
  allowedTags?: string[];
  /** The allowed attributes for a given tag */
  allowedAttributes?: { [key: string]: string[] };
  /** The allowed style values for a given tag */
  allowedStyles?: { [key: string]: { [key: string]: RegExp[] } };
}

Usage Example:

import { IRenderMime } from "@jupyterlab/rendermime-interfaces";

class CustomRenderer implements IRenderMime.IRenderer {
  constructor(private options: IRenderMime.IRendererOptions) {}

  async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
    const htmlContent = model.data['text/html'] as string;
    
    // Custom sanitization options for this renderer
    const sanitizeOptions: IRenderMime.ISanitizerOptions = {
      allowedTags: ['div', 'span', 'p', 'strong', 'em', 'a', 'img'],
      allowedAttributes: {
        'a': ['href', 'title'],
        'img': ['src', 'alt', 'width', 'height'],
        'div': ['class', 'style']
      },
      allowedStyles: {
        'div': {
          'color': [/^#[0-9a-f]{6}$/i],
          'background-color': [/^#[0-9a-f]{6}$/i],
          'font-size': [/^\d+px$/]
        }
      }
    };
    
    // Sanitize the HTML
    const sanitizedHtml = this.options.sanitizer.sanitize(htmlContent, sanitizeOptions);
    
    // Check if autolink is enabled
    const hasAutolink = this.options.sanitizer.getAutolink?.() ?? false;
    if (hasAutolink) {
      console.log('URLs will be automatically converted to links');
    }
    
    this.node.innerHTML = sanitizedHtml;
  }
}

URL Resolution

IResolver Interface

Resolves relative URLs and handles file path resolution within JupyterLab.

/**
 * An object that resolves relative URLs
 */
interface IResolver {
  /** Resolve a relative url to an absolute url path */
  resolveUrl(url: string): Promise<string>;
  /**
   * Get the download url for a given absolute url path.
   * This URL may include a query parameter.
   */
  getDownloadUrl(url: string): Promise<string>;
  /**
   * Whether the URL should be handled by the resolver or not.
   * This is similar to the `isLocal` check in `URLExt`,
   * but can also perform additional checks on whether the
   * resolver should handle a given URL.
   * @param allowRoot - Whether the paths starting at Unix-style filesystem root (`/`) are permitted
   */
  isLocal?(url: string, allowRoot?: boolean): boolean;
  /**
   * Resolve a path from Jupyter kernel to a path:
   * - relative to `root_dir` (preferably) this is in jupyter-server scope,
   * - path understood and known by kernel (if such a path exists).
   * Returns `null` if there is no file matching provided path in neither
   * kernel nor jupyter-server contents manager.
   */
  resolvePath?(path: string): Promise<IResolvedLocation | null>;
}

IResolvedLocation Interface

Represents a resolved file location with scope information.

interface IResolvedLocation {
  /** Location scope */
  scope: 'kernel' | 'server';
  /** Resolved path */
  path: string;
}

Usage Example:

import { IRenderMime } from "@jupyterlab/rendermime-interfaces";

class ImageRenderer implements IRenderMime.IRenderer {
  constructor(private options: IRenderMime.IRendererOptions) {}

  async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
    const imagePath = model.data['image/path'] as string;
    const resolver = this.options.resolver;
    
    if (resolver) {
      try {
        // Check if this is a local path we should handle
        const isLocal = resolver.isLocal?.(imagePath) ?? true;
        
        if (isLocal) {
          // Resolve relative path to absolute
          const absoluteUrl = await resolver.resolveUrl(imagePath);
          
          // Get download URL for the image
          const downloadUrl = await resolver.getDownloadUrl(absoluteUrl);
          
          // Resolve kernel path if needed
          const resolved = await resolver.resolvePath?.(imagePath);
          if (resolved) {
            console.log(`Image resolved to ${resolved.scope}: ${resolved.path}`);
          }
          
          // Create image element with resolved URL
          const img = document.createElement('img');
          img.src = downloadUrl;
          img.alt = 'Resolved image';
          this.node.appendChild(img);
        }
      } catch (error) {
        console.error('Failed to resolve image path:', error);
        this.node.textContent = `Failed to load image: ${imagePath}`;
      }
    }
  }
}

Link Handling

ILinkHandler Interface

Handles click events on links within rendered content.

/**
 * An object that handles links on a node
 */
interface ILinkHandler {
  /**
   * Add the link handler to the node
   * @param node the anchor node for which to handle the link
   * @param path the path to open when the link is clicked
   * @param id an optional element id to scroll to when the path is opened
   */
  handleLink(node: HTMLElement, path: string, id?: string): void;
  /**
   * Add the path handler to the node
   * @param node the anchor node for which to handle the link
   * @param path the path to open when the link is clicked
   * @param scope the scope to which the path is bound
   * @param id an optional element id to scroll to when the path is opened
   */
  handlePath?(node: HTMLElement, path: string, scope: 'kernel' | 'server', id?: string): void;
}

Usage Example:

import { IRenderMime } from "@jupyterlab/rendermime-interfaces";

class LinkEnabledRenderer implements IRenderMime.IRenderer {
  constructor(private options: IRenderMime.IRendererOptions) {}

  async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
    const htmlContent = model.data['text/html'] as string;
    const sanitized = this.options.sanitizer.sanitize(htmlContent);
    this.node.innerHTML = sanitized;
    
    // Setup link handling
    if (this.options.linkHandler) {
      this.setupLinkHandling();
    }
  }
  
  private setupLinkHandling(): void {
    const links = this.node.querySelectorAll('a[href]');
    
    links.forEach(link => {
      const href = link.getAttribute('href')!;
      const linkElement = link as HTMLElement;
      
      // Handle different types of links
      if (href.startsWith('#')) {
        // Fragment link - scroll to element
        const elementId = href.substring(1);
        this.options.linkHandler!.handleLink(linkElement, '', elementId);
      } else if (href.startsWith('/')) {
        // Absolute path - specify scope
        this.options.linkHandler!.handlePath?.(linkElement, href, 'server');
      } else if (!href.startsWith('http')) {
        // Relative path
        this.options.linkHandler!.handleLink(linkElement, href);
      }
      // External links (http/https) are handled by default browser behavior
    });
  }
}

LaTeX Typesetting

ILatexTypesetter Interface

Handles LaTeX mathematical expression rendering.

/**
 * The interface for a LaTeX typesetter
 */
interface ILatexTypesetter {
  /**
   * Typeset a DOM element.
   * The typesetting may happen synchronously or asynchronously.
   * @param element - the DOM element to typeset
   */
  typeset(element: HTMLElement): void;
}

Usage Example:

import { IRenderMime } from "@jupyterlab/rendermime-interfaces";

class MathRenderer implements IRenderMime.IRenderer {
  constructor(private options: IRenderMime.IRendererOptions) {}

  async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
    const mathContent = model.data['text/latex'] as string;
    
    // Create container for math content
    const mathContainer = document.createElement('div');
    mathContainer.className = 'math-content';
    mathContainer.textContent = mathContent;
    
    this.node.appendChild(mathContainer);
    
    // Typeset LaTeX if typesetter is available
    if (this.options.latexTypesetter) {
      this.options.latexTypesetter.typeset(this.node);
    }
  }
}

Markdown Parsing

IMarkdownParser Interface

Converts Markdown text to HTML.

/**
 * The interface for a Markdown parser
 */
interface IMarkdownParser {
  /**
   * Render a markdown source into unsanitized HTML
   * @param source - The string to render
   * @returns A promise of the string containing HTML which may require sanitization
   */
  render(source: string): Promise<string>;
}

Usage Example:

import { IRenderMime } from "@jupyterlab/rendermime-interfaces";

class MarkdownRenderer implements IRenderMime.IRenderer {
  constructor(private options: IRenderMime.IRendererOptions) {}

  async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
    const markdownSource = model.data['text/markdown'] as string;
    
    if (this.options.markdownParser) {
      try {
        // Parse markdown to HTML
        const rawHtml = await this.options.markdownParser.render(markdownSource);
        
        // Sanitize the resulting HTML
        const sanitizedHtml = this.options.sanitizer.sanitize(rawHtml);
        
        this.node.innerHTML = sanitizedHtml;
        
        // Apply LaTeX typesetting if available
        if (this.options.latexTypesetter) {
          this.options.latexTypesetter.typeset(this.node);
        }
        
        // Setup link handling if available
        if (this.options.linkHandler) {
          this.setupLinks();
        }
        
      } catch (error) {
        console.error('Failed to render markdown:', error);
        this.node.textContent = 'Failed to render markdown content';
      }
    } else {
      // Fallback: display raw markdown
      const pre = document.createElement('pre');
      pre.textContent = markdownSource;
      this.node.appendChild(pre);
    }
  }
  
  private setupLinks(): void {
    const links = this.node.querySelectorAll('a[href]');
    links.forEach(link => {
      const href = link.getAttribute('href')!;
      this.options.linkHandler!.handleLink(link as HTMLElement, href);
    });
  }
}

docs

data-models.md

document-system.md

extension-system.md

icon-system.md

index.md

renderer-system.md

translation-system.md

utility-services.md

tile.json