在 Chrome 扩展程序的 Google 文档中捕获文本选择

问题描述 投票:0回答:1

我正在开发一个 Chrome 扩展程序,当在网页上选择文本时,该扩展程序会显示一个弹出窗口。它在大多数网站上运行良好,但我在使用 Google 文档时遇到问题。这是我的内容脚本的相关部分:

import React from 'react'
import ReactDOM from 'react-dom/client'
import PopupComponent from './popup'

let popupContainer = null

function showPopup(rect, selectedText) {
  if (!popupContainer) {
    popupContainer = document.createElement('div')
    document.body.appendChild(popupContainer)
  }

  const popupStyle = {
    position: 'absolute',
    left: `${rect.left}px`,
    top: `${rect.bottom + window.scrollY}px`,
    zIndex: 9999999999999999999,
  }

  const root = ReactDOM.createRoot(popupContainer)
  root.render(
    <div style={popupStyle}>
      <PopupComponent />
    </div>
  )
}

function hidePopup() {
  if (popupContainer) {
    const root = ReactDOM.createRoot(popupContainer)
    root.unmount()
    // ReactDOM.unmountComponentAtNode(popupContainer)
  }
}

function handleTextSelection() {
  const selectedText = window.getSelection().toString().trim()
  if (selectedText) {
    const range = window.getSelection().getRangeAt(0)
    const rect = range.getBoundingClientRect()
    showPopup(rect, selectedText)
  } else {
    hidePopup()
  }
}

// Listen for text selection
document.addEventListener('mouseup', handleTextSelection)

// Listen for scroll events to reposition the popup
window.addEventListener('scroll', () => {
  const selection = window.getSelection()
  if (!selection.isCollapsed) {
    const range = selection.getRangeAt(0)
    const rect = range.getBoundingClientRect()
    showPopup(rect, selection.toString().trim())
  }
})

// Listen for clicks outside the popup to close it
document.addEventListener('mousedown', (event) => {
  if (popupContainer && !popupContainer.contains(event.target)) {
    hidePopup()
  }
})

// Cleanup function
function cleanup() {
  document.removeEventListener('mouseup', handleTextSelection)
  window.removeEventListener('scroll', handleTextSelection)
  if (popupContainer) {
    document.body.removeChild(popupContainer)
  }
}

// Call cleanup when the content script is unloaded
window.addEventListener('unload', cleanup)

问题出在这个方法上:

function handleTextSelection() {
  const selectedText = window.getSelection().toString().trim()
  if (selectedText) {
    const range = window.getSelection().getRangeAt(0)
    const rect = range.getBoundingClientRect()
    showPopup(rect, selectedText)
  } else {
    hidePopup()
  }
}

document.addEventListener('mouseup', handleTextSelection)

这在常规网页上效果很好,但在 Google Docs 中,window.getSelection() 返回空字符串。我怀疑这是因为 Google 文档使用画布或其他一些不与标准 DOM 选择 API 交互的技术。 我注意到其他扩展(例如 Grammarly)可以成功捕获 Google 文档中的文本选择。有没有办法实现这个功能?

任何见解或建议将不胜感激。谢谢!

javascript reactjs google-chrome-extension google-docs
1个回答
0
投票

window.getSelection()
在Google文档上返回空字符串的原因是Google对其文本使用自定义渲染方法。请尝试以下操作,它利用
document.activeElement
从 Google 文档获取内容。

import React from 'react';
import ReactDOM from 'react-dom/client';
import PopupComponent from './popup';

let popupContainer = null;

function showPopup(rect, selectedText) {
  if (!popupContainer) {
    popupContainer = document.createElement('div');
    document.body.appendChild(popupContainer);
  }

  const popupStyle = {
    position: 'absolute',
    left: `${rect.left}px`,
    top: `${rect.bottom + window.scrollY}px`,
    zIndex: 9999999999999999999,
  };

  const root = ReactDOM.createRoot(popupContainer);
  root.render(
    <div style={popupStyle}>
      <PopupComponent text={selectedText} />
    </div>
  );
}

function hidePopup() {
  if (popupContainer) {
    const root = ReactDOM.createRoot(popupContainer);
    root.unmount();
    document.body.removeChild(popupContainer);
    popupContainer = null;
  }
}

function getSelectedText() {
  const activeElement = document.activeElement;
  if (activeElement && activeElement.tagName === 'TEXTAREA') {
    return activeElement.value.substring(
      activeElement.selectionStart,
      activeElement.selectionEnd
    );
  } else if (window.getSelection) {
    return window.getSelection().toString();
  }
  return '';
}

function handleTextSelection() {
  const selectedText = getSelectedText().trim();
  if (selectedText) {
    const range = window.getSelection().getRangeAt(0);
    const rect = range.getBoundingClientRect();
    showPopup(rect, selectedText);
  } else {
    hidePopup();
  }
}

document.addEventListener('mouseup', handleTextSelection);

window.addEventListener('scroll', () => {
  const selection = window.getSelection();
  if (!selection.isCollapsed) {
    const range = selection.getRangeAt(0);
    const rect = range.getBoundingClientRect();
    showPopup(rect, selection.toString().trim());
  }
});

document.addEventListener('mousedown', (event) => {
  if (popupContainer && !popupContainer.contains(event.target)) {
    hidePopup();
  }
});

document.addEventListener('copy', async () => {
  try {
    const clipboardData = await navigator.clipboard.readText();
    if (clipboardData) {
      showPopup(null, clipboardData);
    }
  } catch (err) {
    console.error('Failed to read clipboard contents:', err);
  }
});

function cleanup() {
  document.removeEventListener('mouseup', handleTextSelection);
  window.removeEventListener('scroll', handleTextSelection);
  if (popupContainer) {
    document.body.removeChild(popupContainer);
  }
}

window.addEventListener('unload', cleanup);
© www.soinside.com 2019 - 2024. All rights reserved.