Quorafind / Obsidian-Surfing-Key

17 stars 2 forks source link

Feature Request: Add the ability to select textarea elements #5

Open ianTevesAcc opened 4 months ago

ianTevesAcc commented 4 months ago

Feature Requested

Just like the title I want to be able to select textarea elements inside obsidian.

Use Case: For people who use plugins that require to select a textarea element in order to type text into. An example is that I use Obsidian Copilot and I regularly select the textarea element of my copilot plugin in order to gpt questions.

Current work around is the mouse. But using the mouse is for the weak only.

Relevant Screenshot

No response

Checklist

ianTevesAcc commented 4 months ago

I tried to fix my own problem and came up with the following.

(child) => child instanceof HTMLTextAreaElement ? true : null,
(child) => child instanceof HTMLInputElement && (child.type === "text" || child.type === "search") ? true : null

or

(child: HTMLElement): boolean | null => child instanceof HTMLTextAreaElement ? true : null,
(child: HTMLElement): boolean | null => child instanceof HTMLInputElement && (child.getAttribute("type") === "text" || child.getAttribute("type") === "search") ? true : null

Problem is the textarea / input element is not being selected. I tried applying some more code other than this (more logic in order to handle key presses and etc.) but it didn't end fruitful for me. Do you know how to solve this problem?

ianTevesAcc commented 4 months ago

FIXED IT!!!

Added the following...

if (this.elementsWithUniqueStrings.has(inputString)) {
        let elementToClick = this.elementsWithUniqueStrings.get(inputString);
        if (elementToClick instanceof SVGSVGElement && elementToClick.parentElement) {
            elementToClick = elementToClick.parentElement;
        }
        this.removeOverlay();
        if (isTickPressed) {
            elementToClick?.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true, clientX: elementToClick.getBoundingClientRect().left + elementToClick.clientWidth / 2, clientY: elementToClick.getBoundingClientRect().top + elementToClick.clientHeight / 2 }));
        } else {
            // Find the textarea child element within the div container
            const textareaElement = elementToClick?.querySelector('textarea');
            if (textareaElement) {
                textareaElement.dispatchEvent(new MouseEvent("click", { bubbles: true }));
                // Focus on textarea element after click
                if (document && document.activeElement !== textareaElement) {
                    document.activeElement?.blur();
                }
                setTimeout(() => {
                    textareaElement?.focus();
                }, 0);
            }
        }
        isTickPressed = false;
      }

or

if (this.elementsWithUniqueStrings.has(inputString)) {
        let elementToClick = this.elementsWithUniqueStrings.get(inputString);
        if (elementToClick instanceof SVGSVGElement && elementToClick.parentElement) {
            elementToClick = elementToClick.parentElement;
        }
        this.removeOverlay();
        if (isTickPressed) {
            elementToClick?.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true, clientX: elementToClick.getBoundingClientRect().left + elementToClick.clientWidth / 2, clientY: elementToClick.getBoundingClientRect().top + elementToClick.clientHeight / 2 }));
        } else {
            // Find the textarea child element within the div container
            const textareaElement = elementToClick?.querySelector('textarea');
            if (textareaElement) {
                textareaElement.dispatchEvent(new MouseEvent("click", { bubbles: true }));
                // Focus on textarea element after click
                if (document && document.activeElement !== textareaElement) {
                    document.activeElement?.blur();
                }
                setTimeout(() => {
                    textareaElement?.focus();
                }, 0);
            }
        }
        isTickPressed = false;
      }
ianTevesAcc commented 4 months ago

To be more specific...

attachStringsToElements() {
    const rules = [
// ADDED CODE -------------------------------------------------------------------------- //
      (child) => child instanceof HTMLTextAreaElement ? true : null,
      (child) => child instanceof HTMLInputElement && (child.type === "text" || child.type === "search") ? true : null,
// ---------------------------------------------------------------------------------------//
      (child) => {
        var _a, _b;
        return child instanceof SVGSVGElement && !((_a = child.classList) == null ? void 0 : _a.contains("canvas-background")) && !((_b = child.classList) == null ? void 0 : _b.contains("canvas-edges")) ? true : null;
      },
      (child) => child instanceof HTMLInputElement && child.type === "checkbox" ? true : null,
      (child) => {
        var _a;
        return ((_a = child.classList) == null ? void 0 : _a.contains("canvas-node-label")) ? false : null;
      },
      (child) => {
        var _a;
        return ((_a = child.classList) == null ? void 0 : _a.contains("canvas-node-container")) ? true : null;
      }
    ];
    const processQueue = (queue) => {
      var _a, _b;
      if (!queue.length)
        return;
      const element = queue.shift();
      if (!element) {
        processQueue(queue);
        return;
      }
      const pushChildren = (child) => {
        var _a2;
        if (child instanceof Element) {
          const style = window.getComputedStyle(child);
          if (style.display === "none") {
            return false;
          }
        }
        queue.push(child);
        for (const rule of rules) {
          const result = rule(child);
          if (result !== null) {
            return result;
          }
        }
        return !!(child.nodeType === Node.TEXT_NODE && ((_a2 = child.textContent) == null ? void 0 : _a2.trim()) && child.textContent !== "/");
      };
      const hasSvgOrTextContentChild = (Array.from(element.childNodes).some(pushChildren) || ((_a = element.classList) == null ? void 0 : _a.contains("canvas-color-picker-item")) || element instanceof HTMLInputElement && element.type === "search" || element instanceof HTMLSelectElement) && !((_b = element == null ? void 0 : element.classList) == null ? void 0 : _b.contains("canvas-icon-placeholder"));
      if (hasSvgOrTextContentChild) {
        const style = window.getComputedStyle(element);
        if (style.display !== "none" && this.isElementInViewport(element)) {
          const elementPosition = element.getBoundingClientRect();
          if (elementPosition.top !== 0 || elementPosition.left !== 0) {
            const uniqueString = this.uniqueStrings.generateUniqueString();
            this.elementsWithUniqueStrings.set(uniqueString, element);
            const stringElement = this.overlay.createEl("span", {
              cls: "surfing-key-string",
              attr: {
                id: uniqueString
              }
            });
            stringElement.textContent = uniqueString;
            const midPointX = elementPosition.left + elementPosition.width / 2;
            const midPointY = elementPosition.top + elementPosition.height / 2;
            const stringElementRect = stringElement.getBoundingClientRect();
            const overlayRect = this.overlay.getBoundingClientRect();
            const stringWidth = stringElementRect.width;
            const stringHeight = stringElementRect.height;
            const rightPosition = midPointX + stringWidth / 2;
            const bottomPosition = midPointY + stringHeight / 2;
            if (rightPosition > overlayRect.right) {
              stringElement.style.left = `${midPointX - stringWidth}px`;
            } else {
              stringElement.style.left = `${midPointX}px`;
            }
            if (bottomPosition > overlayRect.bottom) {
              stringElement.style.top = `${midPointY - stringHeight}px`;
            } else if (midPointY + stringHeight / 2 > overlayRect.bottom) {
              stringElement.style.top = `${midPointY - stringHeight}px`;
            } else {
              stringElement.style.top = `${midPointY - 2}px`;
            }
          }
        }
      }
      processQueue(queue);
    };
    processQueue([this.doc instanceof Document ? this.doc.documentElement : this.doc]);
  }
monitorUserInput() {
    const inputQueue = [];
    let isTickPressed = false;
    this.keydownHandler = (e) => {
      var _a, _b;
      if (!this.overlay)
        return;
      e.stopPropagation();
      e.preventDefault();
      if (e.key === "Escape") {
        this.removeOverlay();
        return;
      }
      if (import_obsidian.Keymap.isModifier(e, "Mod") || import_obsidian.Keymap.isModifier(e, "Shift") || import_obsidian.Keymap.isModifier(e, "Alt")) {
        return;
      }
      if (e.key === "`") {
        isTickPressed = true;
        return;
      }
      if (!/^[qwertasdfgzxcvbQWERTASDFGZXCVB]$/i.test(e.key)) {
        if (e.key === "Backspace" || e.key === "Delete") {
          inputQueue.pop();
          isTickPressed = false;
          const inputDisplay2 = this.overlay.querySelector("#inputDisplay");
          if (inputDisplay2) {
            inputDisplay2.textContent = inputQueue.join("");
          }
          for (const [uniqueString, _] of this.elementsWithUniqueStrings.entries()) {
            const stringElement = document.getElementById(uniqueString);
            stringElement == null ? void 0 : stringElement.show();
          }
        }
        return;
      }
      const input = e.key.toUpperCase();
      if (inputQueue.length >= 2 && this.uniqueStrings.usedChars.has(inputQueue.join("")[0])) {
        inputQueue.shift();
      } else if (inputQueue.length >= 3) {
        inputQueue.shift();
      }
      inputQueue.push(input);
      const inputDisplay = this.overlay.querySelector("#inputDisplay");
      if (inputDisplay) {
        inputDisplay.textContent = inputQueue.join("");
      }
      const inputString = inputQueue.join("");
      for (const [uniqueString, _] of this.elementsWithUniqueStrings.entries()) {
        const stringElement = document.getElementById(uniqueString);
        if (stringElement && ((_a = stringElement.textContent) == null ? void 0 : _a.startsWith(inputString))) {
          stringElement.style.backgroundColor = "yellow";
          stringElement.style.color = "black";
        } else {
          stringElement == null ? void 0 : stringElement.hide();
        }
      }
      if (this.elementsWithUniqueStrings.has(inputString)) {
        let elementToClick = this.elementsWithUniqueStrings.get(inputString);
        if (elementToClick instanceof SVGSVGElement && elementToClick.parentElement) {
          elementToClick = elementToClick.parentElement;
        }
        this.removeOverlay();
        if (isTickPressed) {
          elementToClick == null ? void 0 : elementToClick.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true, clientX: elementToClick.getBoundingClientRect().left + elementToClick.clientWidth / 2, clientY: elementToClick.getBoundingClientRect().top + elementToClick.clientHeight / 2 }));
        } else {
          elementToClick == null ? void 0 : elementToClick.dispatchEvent(new MouseEvent("click", { bubbles: true }));
          if (elementToClick instanceof HTMLInputElement) {
            if (document && document.activeElement !== elementToClick) {
              (_b = document.activeElement) == null ? void 0 : _b.blur();
            }
            setTimeout(() => {
              elementToClick == null ? void 0 : elementToClick.focus();
            }, 0);
          }
        }
        isTickPressed = false;
      }
      // ADDED CODE -------------------------------------------------------- //
      if (this.elementsWithUniqueStrings.has(inputString)) {
        let elementToClick = this.elementsWithUniqueStrings.get(inputString);
        if (elementToClick instanceof SVGSVGElement && elementToClick.parentElement) {
            elementToClick = elementToClick.parentElement;
        }
        this.removeOverlay();
        if (isTickPressed) {
            elementToClick?.dispatchEvent(new MouseEvent("contextmenu", { bubbles: true, clientX: elementToClick.getBoundingClientRect().left + elementToClick.clientWidth / 2, clientY: elementToClick.getBoundingClientRect().top + elementToClick.clientHeight / 2 }));
        } else {
            // Find the textarea child element within the div container
            const textareaElement = elementToClick?.querySelector('textarea');
            if (textareaElement) {
                textareaElement.dispatchEvent(new MouseEvent("click", { bubbles: true }));
                // Focus on textarea element after click
                if (document && document.activeElement !== textareaElement) {
                    document.activeElement?.blur();
                }
                setTimeout(() => {
                    textareaElement?.focus();
                }, 0);
            }
        }
        isTickPressed = false;
      }
    // -----------------------------------------------------------------------------------//
    };
    window.addEventListener("keydown", this.keydownHandler, true);
  }