فهرست منبع

Merge pull request #2333 from ian-r-rose/collaborator_hover

Collaborator hover
Steven Silvester 8 سال پیش
والد
کامیت
8ffbd12426
2فایلهای تغییر یافته به همراه119 افزوده شده و 9 حذف شده
  1. 105 8
      packages/codemirror/src/editor.ts
  2. 14 1
      packages/codemirror/style/index.css

+ 105 - 8
packages/codemirror/src/editor.ts

@@ -25,7 +25,7 @@ import {
 } from '@jupyterlab/codeeditor';
 
 import {
-  IObservableMap, IObservableString, uuid
+  IObservableMap, IObservableString, uuid, ICollaborator
 } from '@jupyterlab/coreutils';
 
 import {
@@ -50,6 +50,16 @@ const EDITOR_CLASS = 'jp-CodeMirrorEditor';
  */
 const READ_ONLY_CLASS = 'jp-mod-readOnly';
 
+/**
+ * The class name for the hover box for collaborator cursors.
+ */
+const COLLABORATOR_CURSOR_CLASS = 'jp-CollaboratorCursor';
+
+/**
+ * The class name for the hover box for collaborator cursors.
+ */
+const COLLABORATOR_HOVER_CLASS = 'jp-CollaboratorCursor-hover';
+
 /**
  * The key code for the up arrow key.
  */
@@ -60,6 +70,11 @@ const UP_ARROW = 38;
  */
 const DOWN_ARROW = 40;
 
+/**
+ * The time that a collaborator name hover persists.
+ */
+const HOVER_TIMEOUT = 1000;
+
 
 /**
  * CodeMirror editor.
@@ -73,6 +88,7 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
     let host = this.host = options.host;
     host.classList.add(EDITOR_CLASS);
     host.addEventListener('focus', this, true);
+    host.addEventListener('scroll', this, true);
 
     this._uuid = options.uuid || uuid();
     this._selectionStyle = options.selectionStyle || {};
@@ -246,6 +262,7 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
       return;
     }
     this.host.removeEventListener('focus', this, true);
+    this.host.removeEventListener('scroll', this, true);
     this._editor = null;
     this._model = null;
     this._keydownHandlers.length = 0;
@@ -337,6 +354,7 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
     } else {
       this._needsRefresh = true;
     }
+    this._clearHover();
   }
 
   /**
@@ -551,15 +569,37 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
    */
   private _markSelections(uuid: string, selections: CodeEditor.ITextSelection[]) {
     const markers: CodeMirror.TextMarker[] = [];
+
+    // If we are marking selections corresponding to an active hover,
+    // remove it.
+    if (uuid === this._hoverId) {
+      this._clearHover();
+    }
+    // If we can id the selection to a specific collaborator,
+    // use that information.
+    let collaborator: ICollaborator;
+    if (this._model.modelDB.collaborators) {
+      collaborator = this._model.modelDB.collaborators.get(uuid);
+    }
+
+    // Style each selection for the uuid.
     selections.forEach(selection => {
       // Only render selections if the start is not equal to the end.
       // In that case, we don't need to render the cursor.
       if (!JSONExt.deepEqual(selection.start, selection.end)) {
         const { anchor, head } = this._toCodeMirrorSelection(selection);
-        const markerOptions = this._toTextMarkerOptions(selection.style);
+        let markerOptions: CodeMirror.TextMarkerOptions;
+        if (collaborator) {
+          markerOptions = this._toTextMarkerOptions({
+            ...selection.style,
+            color: collaborator.color
+          });
+        } else {
+          markerOptions = this._toTextMarkerOptions(selection.style);
+        }
         markers.push(this.doc.markText(anchor, head, markerOptions));
       } else {
-        let caret = this._getCaret(selection.uuid);
+        let caret = this._getCaret(collaborator);
         markers.push(this.doc.setBookmark(
           this._toCodeMirrorPosition(selection.end), {widget: caret}));
       }
@@ -601,7 +641,7 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
         let r = parseInt(style.color.slice(1,3), 16);
         let g  = parseInt(style.color.slice(3,5), 16);
         let b  = parseInt(style.color.slice(5,7), 16);
-        css = `background-color: rgba( ${r}, ${g}, ${b}, 0.1)`;
+        css = `background-color: rgba( ${r}, ${g}, ${b}, 0.15)`;
       }
       return {
         className: style.className,
@@ -725,6 +765,9 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
     case 'focus':
       this._evtFocus(event as FocusEvent);
       break;
+    case 'scroll':
+      this._evtScroll();
+      break;
     default:
       break;
     }
@@ -739,21 +782,75 @@ class CodeMirrorEditor implements CodeEditor.IEditor {
     }
   }
 
+  /**
+   * Handle `scroll` events for the editor.
+   */
+  private _evtScroll(): void {
+    // Remove any active hover.
+    this._clearHover();
+  }
+
+  /**
+   * Clear the hover for a caret, due to things like
+   * scrolling, resizing, deactivation, etc, where
+   * the position is no longer valid.
+   */
+  private _clearHover(): void {
+    if (this._caretHover) {
+      window.clearTimeout(this._hoverTimeout);
+      document.body.removeChild(this._caretHover);
+      this._caretHover = null;
+    }
+  }
+
   /**
    * Construct a caret element representing the position
    * of a collaborator's cursor.
    */
-  private _getCaret(uuid: string): HTMLElement {
+  private _getCaret(collaborator: ICollaborator): HTMLElement {
+    let name = collaborator ? collaborator.displayName : 'Anonymous';
+    let color = collaborator ? collaborator.color : this._selectionStyle.color;
     let caret: HTMLElement = document.createElement('span');
-    caret.className = 'jp-CollaboratorCursor';
-    caret.style.borderBottomColor=`${this._selectionStyle.color}`
-    caret.appendChild(document.createTextNode('\u00a0'));
+    caret.className = COLLABORATOR_CURSOR_CLASS;
+    caret.style.borderBottomColor = color;
+    caret.onmouseenter = () => {
+      this._clearHover();
+      this._hoverId = collaborator.sessionId;
+      let rect = caret.getBoundingClientRect();
+      // Construct and place the hover box.
+      let hover = document.createElement('div');
+      hover.className = COLLABORATOR_HOVER_CLASS;
+      hover.style.left = String(rect.left)+'px';
+      hover.style.top = String(rect.bottom)+'px';
+      hover.textContent = name;
+      hover.style.backgroundColor = color;
+
+      // If the user mouses over the hover, take over the timer.
+      hover.onmouseenter = () => {
+        window.clearTimeout(this._hoverTimeout);
+      }
+      hover.onmouseleave = () => {
+        this._hoverTimeout = window.setTimeout(() => {
+          this._clearHover();
+        }, HOVER_TIMEOUT);
+      }
+      this._caretHover = hover;
+      document.body.appendChild(hover);
+    };
+    caret.onmouseleave = () => {
+      this._hoverTimeout = window.setTimeout(() => {
+        this._clearHover();
+      }, HOVER_TIMEOUT);
+    };
     return caret;
   }
 
   private _model: CodeEditor.IModel;
   private _editor: CodeMirror.Editor;
   protected selectionMarkers: { [key: string]: CodeMirror.TextMarker[] | undefined } = {};
+  private _caretHover: HTMLElement = null;
+  private _hoverTimeout: number = null; 
+  private _hoverId: string = null;
   private _keydownHandlers = new Array<CodeEditor.KeydownHandler>();
   private _changeGuard = false;
   private _selectionStyle: CodeEditor.ISelectionStyle;

+ 14 - 1
packages/codemirror/style/index.css

@@ -71,11 +71,24 @@
   border-right: 5px solid transparent;
   border-top: none;
   border-bottom: 3px solid;
+  background-clip: content-box;
+  margin-left: -5px;
+  margin-right: -5px;
+}
+
+.jp-CollaboratorCursor-hover {
   position: absolute;
-  width: 0;
+  z-index: 1;
+  transform: translateX(-50%);
+  color: white;
+  border-radius: 3px;
+  padding: 1px;
+  text-align: center;
+  white-space: nowrap;
 }
 
 
+
 /*
   Here is our jupyter theme for CodeMirror syntax highlighting
   This is used in our marked.js syntax highlighting and CodeMirror itself