Browse Source

Improve handling of URI fragment identifiers.

Raffaele De Feo 6 years ago
parent
commit
38b6df8f13

+ 1 - 0
packages/cells/src/widget.ts

@@ -864,6 +864,7 @@ export class MarkdownCell extends Cell {
     this._updateRenderedInput().then(() => {
       this._ready.resolve(void 0);
     });
+    this.renderInput(this._renderer);
 
     super.initializeState();
   }

+ 7 - 0
packages/docregistry/src/default.ts

@@ -463,6 +463,13 @@ export class DocumentWidget<
     });
   }
 
+  /**
+   * Set URI fragment identifier.
+   */
+  setFragment(fragment: string): void {
+    /* no-op */
+  }
+
   /**
    * Handle a path change.
    */

+ 20 - 2
packages/docregistry/src/mimedocument.ts

@@ -85,6 +85,14 @@ export class MimeContent extends Widget {
     return this._ready.promise;
   }
 
+  /**
+   * Set URI fragment identifier.
+   */
+  setFragment(fragment: string) {
+    this._fragment = fragment;
+    this.update();
+  }
+
   /**
    * Dispose of the resources held by the widget.
    */
@@ -105,6 +113,7 @@ export class MimeContent extends Widget {
   protected onUpdateRequest(msg: Message): void {
     if (this._context.isReady) {
       this._render();
+      this._fragment = '';
     }
   }
 
@@ -133,7 +142,11 @@ export class MimeContent extends Widget {
     } else {
       data[this.mimeType] = model.toJSON();
     }
-    let mimeModel = new MimeModel({ data, callback: this._changeCallback });
+    let mimeModel = new MimeModel({
+      data,
+      callback: this._changeCallback,
+      metadata: { fragment: this._fragment }
+    });
 
     try {
       // Do the rendering asynchronously.
@@ -178,6 +191,7 @@ export class MimeContent extends Widget {
   readonly renderer: IRenderMime.IRenderer;
 
   private _context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
+  private _fragment = '';
   private _monitor: ActivityMonitor<any, any> | null;
   private _ready = new PromiseDelegate<void>();
   private _dataType: 'string' | 'json';
@@ -223,7 +237,11 @@ export namespace MimeContent {
 /**
  * A document widget for mime content.
  */
-export class MimeDocument extends DocumentWidget<MimeContent> {}
+export class MimeDocument extends DocumentWidget<MimeContent> {
+  setFragment(fragment: string): void {
+    this.content.setFragment(fragment);
+  }
+}
 
 /**
  * An implementation of a widget factory for a rendered mimetype document.

+ 5 - 0
packages/docregistry/src/registry.ts

@@ -1303,6 +1303,11 @@ export interface IDocumentWidget<
    * The toolbar for the widget.
    */
   readonly toolbar: Toolbar<Widget>;
+
+  /**
+   * Set URI fragment identifier.
+   */
+  setFragment(fragment: string): void;
 }
 
 /**

+ 9 - 0
packages/notebook/src/panel.ts

@@ -111,6 +111,15 @@ export class NotebookPanel extends DocumentWidget<Notebook, INotebookModel> {
     return this.content ? this.content.model : null;
   }
 
+  /**
+   * Set URI fragment identifier.
+   */
+  setFragment(fragment: string) {
+    this.context.ready.then(() => {
+      this.content.setFragment(fragment);
+    });
+  }
+
   /**
    * Dispose of the resources used by the widget.
    */

+ 24 - 0
packages/notebook/src/widget.ts

@@ -1160,6 +1160,17 @@ export class Notebook extends StaticNotebook {
     }
   }
 
+  /**
+   * Set URI fragment identifier.
+   */
+  setFragment(fragment: string): void {
+    // Wait all cells are rendered then set fragment and update.
+    Promise.all(this.widgets.map(widget => widget.ready)).then(() => {
+      this._fragment = fragment;
+      this.update();
+    });
+  }
+
   /**
    * Handle the DOM events for the widget.
    *
@@ -1310,6 +1321,18 @@ export class Notebook extends StaticNotebook {
     if (count > 1) {
       activeCell.addClass(OTHER_SELECTED_CLASS);
     }
+    if (this._fragment) {
+      let el;
+      try {
+        el = this.node.querySelector(this._fragment);
+      } catch (error) {
+        console.warn('Unable to set URI fragment identifier', error);
+      }
+      if (el) {
+        el.scrollIntoView();
+      }
+      this._fragment = '';
+    }
   }
 
   /**
@@ -2029,6 +2052,7 @@ export class Notebook extends StaticNotebook {
   private _activeCell: Cell | null = null;
   private _mode: NotebookMode = 'command';
   private _drag: Drag = null;
+  private _fragment = '';
   private _dragData: { pressX: number; pressY: number; index: number } = null;
   private _mouseMode: 'select' | 'couldDrag' | null = null;
   private _activeCellChanged = new Signal<this, Cell>(this);

+ 3 - 0
packages/pdf-extension/src/index.ts

@@ -37,6 +37,9 @@ export class RenderedPDF extends Widget implements IRenderMime.IRenderer {
 
     let oldUrl = this._objectUrl;
     this._objectUrl = URL.createObjectURL(blob);
+    if (model.metadata.fragment) {
+      this._objectUrl += model.metadata.fragment;
+    }
     this.node.querySelector('embed').setAttribute('src', this._objectUrl);
 
     // Release reference to any previous object url.

+ 4 - 17
packages/rendermime-extension/src/index.ts

@@ -61,24 +61,11 @@ function activate(
             path
           );
           const widget = docManager.openOrReveal(path, factory.name);
-          if (!widget) {
-            return;
+
+          // Handle the hash if one has been provided.
+          if (widget && id) {
+            widget.setFragment(id);
           }
-          return widget.revealed.then(() => {
-            // Once the widget is ready, attempt to scroll the hash into view
-            // if one has been provided.
-            if (!id) {
-              return;
-            }
-            // Look for the an element with the hash id in the document.
-            // This id is set automatically for headers tags when
-            // we render markdown.
-            const element = widget.node.querySelector(id);
-            if (element) {
-              element.scrollIntoView();
-            }
-            return;
-          });
         });
     }
   });

+ 29 - 2
packages/rendermime/src/widgets.ts

@@ -64,14 +64,20 @@ export abstract class RenderedCommon extends Widget
    *
    * @returns A promise which resolves when rendering is complete.
    */
-  renderModel(model: IRenderMime.IMimeModel): Promise<void> {
+  async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
     // TODO compare model against old model for early bail?
 
     // Toggle the trusted class on the widget.
     this.toggleClass('jp-mod-trusted', model.trusted);
 
     // Render the actual content.
-    return this.render(model);
+    await this.render(model);
+
+    // Handle the fragment identifier if given.
+    const { fragment } = model.metadata;
+    if (fragment) {
+      this.setFragment(fragment as string);
+    }
   }
 
   /**
@@ -82,6 +88,15 @@ export abstract class RenderedCommon extends Widget
    * @returns A promise which resolves when rendering is complete.
    */
   abstract render(model: IRenderMime.IMimeModel): Promise<void>;
+
+  /**
+   * Set the URI fragment identifier.
+   *
+   * @param fragment - The URI fragment identifier.
+   */
+  protected setFragment(fragment: string) {
+    /* no-op */
+  }
 }
 
 /**
@@ -97,6 +112,18 @@ export abstract class RenderedHTMLCommon extends RenderedCommon {
     super(options);
     this.addClass('jp-RenderedHTMLCommon');
   }
+
+  setFragment(fragment: string) {
+    let el;
+    try {
+      el = this.node.querySelector(fragment);
+    } catch (error) {
+      console.warn('Unable to set URI fragment identifier.', error);
+    }
+    if (el) {
+      el.scrollIntoView();
+    }
+  }
 }
 
 /**