From f62900a18571b7b3f71433f883a80b0f8ff083e8 Mon Sep 17 00:00:00 2001 From: fzk <458813868@qq.com> Date: Thu, 26 Dec 2024 10:12:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E6=A1=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- component/customDomSelector/Tooltip.tsx | 67 +++++ component/customDomSelector/index.tsx | 272 ++++++++++++++++++ contents/custom.tsx | 362 +----------------------- package.json | 2 +- tools.ts | 2 +- 5 files changed, 343 insertions(+), 362 deletions(-) create mode 100644 component/customDomSelector/Tooltip.tsx create mode 100644 component/customDomSelector/index.tsx diff --git a/component/customDomSelector/Tooltip.tsx b/component/customDomSelector/Tooltip.tsx new file mode 100644 index 0000000..baae45c --- /dev/null +++ b/component/customDomSelector/Tooltip.tsx @@ -0,0 +1,67 @@ +import { + CheckOutlined, + CloseOutlined, + DownSquareOutlined, + LeftSquareOutlined, + RightSquareOutlined, + UpSquareOutlined +} from "@ant-design/icons" +import { Button, Flex, message, Modal } from "antd" +import React, { useCallback, useEffect, useRef, useState } from "react" + +export default function Tooltip(props: any) { + const handleConfirm = () => { + props.onConfirm() + } + + const navigateElement = (direction: "parent" | "child" | "prev" | "next") => { + props.onNavigate(direction) + } + + const handleCancel = () => { + props.onCancel() + } + + return ( + + + + + + + + + ) +} diff --git a/component/customDomSelector/index.tsx b/component/customDomSelector/index.tsx new file mode 100644 index 0000000..4f89ec0 --- /dev/null +++ b/component/customDomSelector/index.tsx @@ -0,0 +1,272 @@ +import { Modal } from "antd" +import React, { useEffect, useRef, useState } from "react" +import { createRoot } from "react-dom/client" + +import { useMessage } from "@plasmohq/messaging/hook" +import { useStorage } from "@plasmohq/storage/hook" + +import { addCss, saveHtml, saveMarkdown, scrollToTop, setIcon } from "~tools" +import { savePdf } from "~utils/downloadPdf" +import { useContent } from "~utils/editMarkdownHook" +import Turndown from "~utils/turndown" + +import Tooltip from "./Tooltip" + +const turndownService = Turndown() + +export default function CustomDomSelector() { + const isReady = useRef(false) + const isSelect = useRef(false) + const downloadType = useRef("") + const [content, setContent] = useContent() + const [validTime] = useStorage("app-validTime", "1730390400") + const [isModalOpen, setIsModalOpen] = useState(false) + + const selectorRef = useRef(null) + const tooltipRef = useRef(null) + + const articleTitle = document + .querySelector("head title") + ?.innerText.trim() + + useEffect(() => { + addEventListeners() + setIcon(true) + addCss(`.codebox-current { outline: 2px solid #42b88350 !important; }`) + return () => { + removeEventListeners() + // removeSelector() + removeTooltip() + removeHighlight() + } + }, []) + + const createSelector = () => { + const selector = document.createElement("div") + selector.classList.add("codebox-selector") + selector.style.position = "absolute" + selector.style.pointerEvents = "none" + selector.style.zIndex = "2147483640" + selector.style.backgroundColor = "#42b88325" + selector.style.border = "2px solid #42b88350" + selector.style.borderRadius = "2px" + selector.style.transition = "all 0.1s ease-in" + selector.style.display = "none" + document.body.appendChild(selector) + selectorRef.current = selector + } + + const createTooltip = () => { + const tooltip = document.createElement("div") + tooltip.classList.add("codebox-tooltip") + tooltip.style.position = "absolute" + tooltip.style.zIndex = "2147483641" + tooltip.style.backgroundColor = "#fff" + tooltip.style.border = "1px solid #eee" + tooltip.style.borderRadius = "5px" + tooltip.style.padding = "8px" + tooltip.style.boxShadow = "0 2px 8px rgba(0, 0, 0, 0.15)" + // tooltip.style.display = "none" + document.body.appendChild(tooltip) + const root = createRoot(tooltip) + root.render( + + ) + tooltipRef.current = tooltip + } + + const removeSelector = () => { + if (selectorRef.current) { + document.body.removeChild(selectorRef.current) + } + } + + const removeTooltip = () => { + if (tooltipRef.current) { + document.body.removeChild(tooltipRef.current) + tooltipRef.current = null + } + } + + const addEventListeners = () => { + document.addEventListener("mousemove", handleMouseMove) + document.addEventListener("click", handleClick) + } + + const removeEventListeners = () => { + document.removeEventListener("mousemove", handleMouseMove) + document.removeEventListener("click", handleClick) + } + + const handleMouseMove = (event: MouseEvent) => { + if (isReady.current && !isSelect.current) { + const target = event.target as HTMLElement + highlightElement(target) + // updateSelectorPosition(target) + } + } + + const handleClick = (event: MouseEvent) => { + if (isReady.current) { + const target = event.target as HTMLElement + const tooltip = target.closest(".codebox-tooltip") + const modal = target.closest(".codebox-modal") + const submit = target.closest(".valid-submit") + + if (!tooltip && !modal && !submit) { + removeTooltip() + createTooltip() + isSelect.current = true + highlightElement(target) + // updateSelectorPosition(target) + updateTooltipPosition(target) + event.stopPropagation() + event.preventDefault() + } + } + } + + const highlightElement = (element: HTMLElement) => { + removeHighlight() + element.classList.add("codebox-current") + selectorRef.current = element + } + + const removeHighlight = () => { + const currentList = document.querySelectorAll(".codebox-current") + currentList.forEach((el) => el.classList.remove("codebox-current")) + selectorRef.current = null + } + + const updateSelectorPosition = (element: HTMLElement) => { + if (selectorRef.current) { + const rect = element.getBoundingClientRect() + selectorRef.current.style.top = `${rect.top + window.scrollY}px` + selectorRef.current.style.left = `${rect.left + window.scrollX}px` + selectorRef.current.style.width = `${rect.width}px` + selectorRef.current.style.height = `${rect.height}px` + selectorRef.current.style.display = "block" + } + } + + const updateTooltipPosition = (element: HTMLElement) => { + const rect = element.getBoundingClientRect() + const distanceTop = rect.top + window.scrollY + const distanceLeft = rect.left + window.scrollX + const top = + distanceTop < 50 ? distanceTop + rect.height + 5 : distanceTop - 40 + tooltipRef.current.style.top = `${top}px` + tooltipRef.current.style.left = `${distanceLeft + 5}px` + scrollToTop(tooltipRef.current) + } + + useMessage(async (req: any, res: any) => { + if (req.name == "custom-downloadHtml") { + setCustom("html") + } + if (req.name == "custom-downloadMarkdown") { + setCustom("downloadMarkdown") + } + if (req.name == "custom-editMarkdown") { + setCustom("editMarkdown") + } + if (req.name == "custom-downloadPdf") { + setCustom("pdf") + } + if (req.name == "custom-downloadImg") { + setCustom("img") + } + }) + + const setCustom = (type: string) => { + downloadType.current = type + isReady.current = true + isSelect.current = false + } + + const handleConfirm = () => { + if (!selectorRef.current || !downloadType.current) return + switch (downloadType.current) { + case "html": + saveHtml(selectorRef.current, articleTitle) + break + case "downloadMarkdown": + const markdown = turndownService.turndown(selectorRef.current) + saveMarkdown(markdown, articleTitle) + break + case "editMarkdown": + setContent(".codebox-current") + break + case "pdf": + savePdf(selectorRef.current, articleTitle) + break + } + + resetState() + } + + const handleCancel = () => { + resetState() + } + + const resetState = () => { + removeHighlight() + // removeSelector() + removeTooltip() + isReady.current = false + isSelect.current = false + } + + const navigateElement = (direction: "parent" | "child" | "prev" | "next") => { + if (!selectorRef.current) return + + let newElement: HTMLElement | null = null + switch (direction) { + case "parent": + newElement = selectorRef.current.parentElement + break + case "child": + newElement = selectorRef.current.firstElementChild as HTMLElement + break + case "prev": + newElement = selectorRef.current.previousElementSibling as HTMLElement + break + case "next": + newElement = selectorRef.current.nextElementSibling as HTMLElement + break + } + + if (newElement) { + highlightElement(newElement) + // updateSelectorPosition(newElement) + updateTooltipPosition(newElement) + } + } + + const handleOkModal = () => { + setIsModalOpen(false) + } + + const handleCancelModal = () => { + setIsModalOpen(false) + } + + return ( + <> + +

Some contents...

+

Some contents...

+

Some contents...

+
+ + ) +} diff --git a/contents/custom.tsx b/contents/custom.tsx index 46b6126..ecea34b 100644 --- a/contents/custom.tsx +++ b/contents/custom.tsx @@ -1,41 +1,13 @@ import { StyleProvider } from "@ant-design/cssinjs" -import { - CheckOutlined, - CloseOutlined, - DownSquareOutlined, - LeftSquareOutlined, - RightSquareOutlined, - UpSquareOutlined -} from "@ant-design/icons" -import { Button, Flex, message, Modal } from "antd" import antdResetCssText from "data-text:antd/dist/reset.css" -import dayjs from "dayjs" import type { PlasmoCSConfig, PlasmoGetShadowHostId, PlasmoGetStyle } from "plasmo" -import { useEffect, useRef, useState } from "react" -import { sendToBackground } from "@plasmohq/messaging" -import { useMessage } from "@plasmohq/messaging/hook" -import { useStorage } from "@plasmohq/storage/hook" - -import ValidateContent from "~component/contents/validateContent" +import CustomDomSelector from "~component/customDomSelector" import { ThemeProvider } from "~theme" -import { addCss, saveHtml, saveMarkdown, scrollToTop, setIcon } from "~tools" -import { getSummary } from "~utils/coze" -import useCssCodeHook from "~utils/cssCodeHook" -import { downloadAllImagesAsZip } from "~utils/downloadAllImg" -import { savePdf } from "~utils/downloadPdf" -import DrawImages from "~utils/drawImages" -import { useContent } from "~utils/editMarkdownHook" -import Turndown from "~utils/turndown" - -const turndownService = Turndown() -const articleTitle = document - .querySelector("head title") - ?.innerText.trim() const HOST_ID = "codebox-csui" @@ -48,341 +20,11 @@ export const getStyle: PlasmoGetStyle = () => { return style } -let isDownloadType = "markdown" -let isReady = false -let isSelect = false -let instance = null - export default function CustomOverlay() { - const [cssCode, runCss] = useCssCodeHook("custom") - const [content, setContent] = useContent() - const [summary, setSummary] = useStorage("app-summary", "") - const [validTime, setValidTime] = useStorage("app-validTime", "1730390400") - const [isCurrentDom, setIsCurrentDom] = useState(false) - const [rect, setRect] = useState(() => { - return { top: 0, right: 0 } - }) - const [messageApi, contextHolder] = message.useMessage() - - useEffect(() => { - getSelection() - setIcon(true) - }, []) - - useMessage(async (req: any, res: any) => { - if (req.name == "custom-isShow") { - res.send({ isShow: true }) - } - if (req.name == "custom-downloadHtml") { - setCustom("html") - } - if (req.name == "custom-downloadMarkdown") { - setCustom("downloadMarkdown") - } - if (req.name == "custom-downloadPdf") { - setCustom("pdf") - } - if (req.name == "custom-downloadImg") { - setCustom("img") - } - if (req.name == "custom-editMarkdown") { - setCustom("editMarkdown") - } - if (req.name == "app-downloadImages") { - await downloadImages(req.body?.onProgress) - } - if (req.name == "app-get-summary") { - setSummary("") - const res = await getSummary(location.href) - if (res.code == 0) { - const result = JSON.parse(res.data) - setSummary(result) - } - } - if (req.name == "app-full-page-screenshot") { - if (confirm("确认下载?")) { - const { scrollHeight, clientHeight } = document.documentElement - const devicePixelRatio = window.devicePixelRatio || 1 - - let capturedHeight = 0 - let capturedImages = [] - - const captureAndScroll = async () => { - const scrollAmount = clientHeight * devicePixelRatio - const res = await sendToBackground({ name: "screenshot" }) - const dataUrl = res.dataUrl - - capturedHeight += scrollAmount - if (capturedHeight < scrollHeight * devicePixelRatio) { - capturedImages.push(dataUrl) - window.scrollTo(0, capturedHeight) - setTimeout(captureAndScroll, 2000) // Adjust the delay as needed - } else { - DrawImages(capturedImages, articleTitle) - } - } - - captureAndScroll() - } - } - }) - - async function downloadImages( - onProgress?: (current: number, total: number) => void - ) { - const imageUrls = Array.from(document.images).map((img) => img.src) - try { - const res = await sendToBackground({ - name: "download", - body: { - action: "downloadAllImages", - imageUrls: imageUrls, - title: articleTitle, - onProgress: onProgress - } - }) - if (res.code == 0) { - alert("下载失败") - } - } catch (error) { - console.error(`Failed to download images:`, error) - } - } - - function setCustom(type) { - isReady = true - isSelect = false - isDownloadType = type - messageApi.info("请在页面选择要下载区域!") - } - - function getSelection() { - addCss(`.codebox-current { border: 1px solid #7983ff!important; }`) - document.addEventListener("mousemove", (event) => { - const target = event.target as HTMLElement - if (isReady && target && !isSelect) { - removeCurrentDom() - target.classList.add("codebox-current") - } - }) - document.addEventListener("click", (event) => { - const target = event.target as HTMLElement - const tooltip = target.closest("#codebox-csui") - const modal = target.closest(".ant-modal") - const submit = target.closest(".valid-submit") - - if (isReady && target && !tooltip && !modal && !submit) { - isSelect = true - setIsCurrentDom(true) - removeCurrentDom() - target.classList.add("codebox-current") - setTooltip() - event.stopPropagation() - event.preventDefault() - } - }) - } - - function setTooltip() { - const currentDom = document.querySelector(".codebox-current") - const rect = currentDom.getBoundingClientRect() - const distanceTop = rect.top + window.scrollY - const distanceLeft = rect.left + window.scrollX - const top = distanceTop < 50 ? distanceTop + 15 : distanceTop - 40 - const left = distanceLeft + 5 - - setRect({ top, left }) - scrollToTop(currentDom) - } - - function removeCurrentDom() { - const currentList = document.querySelectorAll(".codebox-current") - currentList.forEach((el) => { - el.classList.remove("codebox-current") - }) - } - - function downloadHtml(currentDom) { - saveHtml(currentDom, articleTitle) - } - - function downloadMarkdown(currentDom) { - const markdown = turndownService.turndown(currentDom) - saveMarkdown(markdown, articleTitle) - } - - function handleOk() { - const currentDom = document.querySelector(".codebox-current") - - if (isDownloadType == "html") { - removeCurrentDom() - downloadHtml(currentDom) - } else if (isDownloadType == "downloadMarkdown") { - removeCurrentDom() - downloadMarkdown(currentDom) - } else if (isDownloadType == "editMarkdown") { - setContent(".codebox-current") - removeCurrentDom() - } else if (isDownloadType == "pdf") { - removeCurrentDom() - savePdf(currentDom, articleTitle) - } - isReady = false - isSelect = false - setIsCurrentDom(false) - instance.destroy() - } - - function handleConfirm() { - instance = Modal.confirm({ - title: "提示", - content: ( - <> -
是否保存?
- {Number(validTime) > dayjs().unix() ? ( - <> - ) : ( - - )} - - ), - okText: "确认", - okButtonProps: { - disabled: Number(validTime) <= dayjs().unix() - }, - onOk: () => { - handleOk() - }, - cancelText: "取消", - onCancel: () => { - handleCancel() - } - }) - } - - function handleCancel() { - removeCurrentDom() - isReady = false - isSelect = false - setIsCurrentDom(false) - instance.destroy() - } - - function handleSetParent(event) { - const currentDom = document.querySelector(".codebox-current") - const parent = currentDom.parentElement - - if (parent) { - removeCurrentDom() - parent.classList.add("codebox-current") - setTooltip() - } - event.stopPropagation() - } - - function handleSetChild(event) { - const currentDom = document.querySelector(".codebox-current") - const child = currentDom.firstElementChild - if (child) { - removeCurrentDom() - child.classList.add("codebox-current") - setTooltip() - } - event.stopPropagation() - } - - function handleSetPrev(event) { - const currentDom = document.querySelector(".codebox-current") - const previousSibling = currentDom.previousElementSibling - if (previousSibling) { - removeCurrentDom() - previousSibling.classList.add("codebox-current") - setTooltip() - } - event.stopPropagation() - } - - function handleSetNext(event) { - const currentDom = document.querySelector(".codebox-current") - const nextSibling = currentDom.nextElementSibling - if (nextSibling) { - removeCurrentDom() - nextSibling.classList.add("codebox-current") - setTooltip() - } - event.stopPropagation() - } - return ( - {contextHolder} - {isCurrentDom ? ( -
- - - - - - - - -
- ) : ( - <> - )} +
) diff --git a/package.json b/package.json index cc4ca22..cfcde34 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-box", "displayName": "__MSG_extensionName__", - "version": "0.9.23", + "version": "0.9.24", "description": "__MSG_extensionDescription__", "author": "027xiguapi. <458813868@qq.com>", "scripts": { diff --git a/tools.ts b/tools.ts index c93cefd..59bc75f 100644 --- a/tools.ts +++ b/tools.ts @@ -5,7 +5,7 @@ import { sendToBackground } from "@plasmohq/messaging" export function scrollToTop(element) { window.scrollTo({ - top: element.offsetTop, + top: element.offsetTop + 50, behavior: "smooth" // 可选,平滑滚动 }) }