]> git.proxmox.com Git - mirror_xterm.js.git/commitdiff
Implement link validation
authorDaniel Imms <daimms@microsoft.com>
Mon, 27 Feb 2017 16:35:56 +0000 (08:35 -0800)
committerDaniel Imms <daimms@microsoft.com>
Mon, 27 Feb 2017 16:35:59 +0000 (08:35 -0800)
Fixes #570

src/Interfaces.ts
src/Linkifier.ts
src/Types.ts [new file with mode: 0644]
src/xterm.css
src/xterm.js

index 92094c9553a820042d8a16bb0d7d8e5292adab45..324eec16f075ad5513ef42fab8c954d1173f88a0 100644 (file)
@@ -2,6 +2,8 @@
  * @license MIT
  */
 
+import { LinkMatcherValidationCallback } from './Types';
+
 export interface IBrowser {
   isNode: boolean;
   userAgent: string;
@@ -64,6 +66,26 @@ interface ICircularList<T> {
   shiftElements(start: number, count: number, offset: number): void;
 }
 
+export interface LinkMatcherOptions {
+  /**
+   * The index of the link from the regex.match(text) call. This defaults to 0
+   * (for regular expressions without capture groups).
+   */
+  matchIndex?: number;
+  /**
+   * A callback that validates an individual link, returning true if valid and
+   * false if invalid.
+   */
+  validationCallback?: LinkMatcherValidationCallback;
+}
+
+/**
+ *
+ * @param a
+ */
+function a(a) {
+}
+
 /**
  * Handles actions generated by the parser.
  */
index e0f5eb926f5d81bd450fa640787e39a4aa77572f..73ad04a5716f6d3df66fc7c89b31cab52bd13545 100644 (file)
@@ -2,9 +2,20 @@
  * @license MIT
  */
 
+import { LinkMatcherOptions } from './Interfaces';
+import { LinkMatcherValidationCallback } from './Types';
+
 export type LinkHandler = (uri: string) => void;
 
-type LinkMatcher = {id: number, regex: RegExp, matchIndex?: number, handler: LinkHandler};
+type LinkMatcher = {
+  id: number,
+  regex: RegExp,
+  handler: LinkHandler,
+  matchIndex?: number,
+  validationCallback?: LinkMatcherValidationCallback;
+};
+
+const INVALID_LINK_CLASS = 'xterm-invalid-link';
 
 const protocolClause = '(https?:\\/\\/)';
 const domainCharacterSet = '[\\da-z\\.-]+';
@@ -79,11 +90,10 @@ export class Linkifier {
    * this searches the textContent of the rows. You will want to use \s to match
    * a space ' ' character for example.
    * @param {LinkHandler} handler The callback when the link is called.
-   * @param {number} matchIndex The index of the link from the regex.match(text)
-   * call. This defaults to 0 (for regular expressions without capture groups).
+   * @param {LinkMatcherOptions} [options] Options for the link matcher.
    * @return {number} The ID of the new matcher, this can be used to deregister.
    */
-  public registerLinkMatcher(regex: RegExp, handler: LinkHandler, matchIndex?: number): number {
+  public registerLinkMatcher(regex: RegExp, handler: LinkHandler, options: LinkMatcherOptions = {}): number {
     if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {
       throw new Error('handler cannot be falsy');
     }
@@ -91,7 +101,8 @@ export class Linkifier {
       id: this._nextLinkMatcherId++,
       regex,
       handler,
-      matchIndex
+      matchIndex: options.matchIndex,
+      validationCallback: options.validationCallback
     };
     this._linkMatchers.push(matcher);
     return matcher.id;
@@ -123,11 +134,20 @@ export class Linkifier {
       return;
     }
     const text = row.textContent;
+    // TODO: Onl execute handler if isValid
     for (let i = 0; i < this._linkMatchers.length; i++) {
       const matcher = this._linkMatchers[i];
       const uri = this._findLinkMatch(text, matcher.regex, matcher.matchIndex);
       if (uri) {
-        this._doLinkifyRow(rowIndex, uri, matcher.handler);
+        const linkElement = this._doLinkifyRow(rowIndex, uri, matcher.handler);
+        // Fire validation callback
+        if (matcher.validationCallback) {
+          matcher.validationCallback(uri, isValid => {
+            if (!isValid) {
+              linkElement.classList.add(INVALID_LINK_CLASS);
+            }
+          });
+        }
         // Only allow a single LinkMatcher to trigger on any given row.
         return;
       }
@@ -139,8 +159,9 @@ export class Linkifier {
    * @param {number} rowIndex The index of the row to linkify.
    * @param {string} uri The uri that has been found.
    * @param {handler} handler The handler to trigger when the link is triggered.
+   * @return The link element if it was added, otherwise undefined.
    */
-  private _doLinkifyRow(rowIndex: number, uri: string, handler?: LinkHandler): void {
+  private _doLinkifyRow(rowIndex: number, uri: string, handler?: LinkHandler): HTMLElement {
     // Iterate over nodes as we want to consider text nodes
     const nodes = this._rows[rowIndex].childNodes;
     for (let i = 0; i < nodes.length; i++) {
@@ -165,6 +186,7 @@ export class Linkifier {
           // Matches part of string
           this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex);
         }
+        return linkElement;
       }
     }
   }
@@ -192,7 +214,12 @@ export class Linkifier {
     const element = document.createElement('a');
     element.textContent = uri;
     if (handler) {
-      element.addEventListener('click', () => handler(uri));
+      element.addEventListener('click', () => {
+        // Only execute the handler if the link is not flagged as invalid
+        if (!element.classList.contains(INVALID_LINK_CLASS)) {
+          handler(uri);
+        }
+      });
     } else {
       element.href = uri;
       // Force link on another tab so work is not lost
diff --git a/src/Types.ts b/src/Types.ts
new file mode 100644 (file)
index 0000000..1eafa10
--- /dev/null
@@ -0,0 +1,5 @@
+/**
+ * @license MIT
+ */
+
+export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
index f99eb688579c9b2f57d4c69dd838f09765415d7f..0401e607b214547345aac14e70ca383555ce8d03 100644 (file)
     text-decoration: underline;
 }
 
+.terminal a.xterm-invalid-link:hover {
+    cursor: default;
+    text-decoration: none;
+}
+
 .terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor {
     background-color: #fff;
     color: #000;
index d09d5fb2e4575907310c9375725f955323200ada..69eb0a1f0b3716283780089bff11d5a733830fb9 100644 (file)
@@ -1303,13 +1303,12 @@ Terminal.prototype.attachHypertextLinkHandler = function(handler) {
    * this searches the textContent of the rows. You will want to use \s to match
    * a space ' ' character for example.
    * @param {LinkHandler} handler The callback when the link is called.
-   * @param {number} matchIndex The index of the link from the regex.match(text)
-   * call. This defaults to 0 (for regular expressions without capture groups).
+   * @param {LinkMatcherOptions} [options] Options for the link matcher.
    * @return {number} The ID of the new matcher, this can be used to deregister.
  */
-Terminal.prototype.registerLinkMatcher = function(regex, handler, matchIndex) {
+Terminal.prototype.registerLinkMatcher = function(regex, handler, options) {
   if (this.linkifier) {
-    var matcherId = this.linkifier.registerLinkMatcher(regex, handler, matchIndex);
+    var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);
     this.refresh(0, this.rows - 1);
     return matcherId;
   }