diff --git a/component/customDomSelector/index.tsx b/component/customDomSelector/index.tsx deleted file mode 100644 index 43749c3..0000000 --- a/component/customDomSelector/index.tsx +++ /dev/null @@ -1,248 +0,0 @@ -import React, { useEffect, useRef } from "react" -import { createRoot } from "react-dom/client" - -import { useMessage } from "@plasmohq/messaging/hook" -import { useStorage } from "@plasmohq/storage/dist/hook" - -import ValidateContent from "~component/contents/validateContent" -import { addCss, saveHtml, saveMarkdown } from "~tools" -import { useEditMarkdown } from "~utils/editMarkdownHook" -import { useParseMarkdown } from "~utils/parseMarkdownHook" -import { Print } from "~utils/print" -import Turndown from "~utils/turndown" - -import Tooltip from "../ui/tooltip" - -const turndownService = Turndown() - -export default function CustomDomSelector() { - const [aiType, setAiType] = useStorage("app-aiType") - const isReady = useRef(false) - const isSelect = useRef(false) - const downloadType = useRef("") - const [editContent, setEditContent] = useEditMarkdown() - const [parseContent, setParseContent] = useParseMarkdown() - - const selectorRef = useRef(null) - const tooltipRef = useRef(null) - const modalRef = useRef(null) - - const articleTitle = document - .querySelector("head title") - ?.innerText.trim() - - useEffect(() => { - if (aiType) { - addEventListeners() - addCss(`.codebox-current { outline: 2px solid #42b88350 !important; }`) - } - return () => { - removeEventListeners() - removeTooltip() - removeHighlight() - } - }, [aiType]) - - 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)" - - document.body.appendChild(tooltip) - const root = createRoot(tooltip) - root.render( - - ) - tooltipRef.current = tooltip - } - - 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) - } - } - - 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) - 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 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` - window.scrollTo({ top: top - 150, behavior: "smooth" }) - } - - useMessage(async (req: any, res: any) => { - const name = req.name - setCustom(name.split("-")[1]) - }) - - const setCustom = (type: string) => { - downloadType.current = type - isReady.current = true - isSelect.current = false - } - - const handleOkModal = () => { - if (!selectorRef.current || !downloadType.current) return - - const current = document.querySelector(".codebox-current") - current.classList.remove("codebox-current") - - switch (downloadType.current) { - case "downloadHtml": - saveHtml(selectorRef.current, articleTitle) - break - case "downloadMarkdown": - const markdown = turndownService.turndown(selectorRef.current) - saveMarkdown(markdown, articleTitle) - break - case "editMarkdown": - setEditContent(selectorRef.current) - break - case "parseMarkdown": - setParseContent(selectorRef.current, aiType) - break - case "downloadPdf": - const elementToPrint = selectorRef.current - if (elementToPrint) { - Print.print(elementToPrint, { title: articleTitle }) - .then(() => console.log("Printing complete")) - .catch((error) => console.error("Printing failed:", error)) - } - break - } - - resetState() - setTimeout(() => { - document.body.removeChild(modalRef.current) - modalRef.current = null - }, 1000) - } - - const handleConfirm = () => { - const modal = document.createElement("div") - modal.classList.add("codebox-modal") - modal.style.position = "fixed" - modal.style.zIndex = "2147483648" - modal.style.backgroundColor = "rgba(0, 0, 0, 0.7)" - modal.style.width = "100vw" - modal.style.height = "100vh" - modal.style.top = "0" - modal.style.left = "0" - document.body.appendChild(modal) - const root = createRoot(modal) - root.render( - - ) - modalRef.current = modal - } - - const handleCancel = () => { - resetState() - } - - const resetState = () => { - removeHighlight() - 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 handleCancelModal = () => { - document.body.removeChild(modalRef.current) - modalRef.current = null - } - - return
-} diff --git a/component/ui/QRCodeModal.tsx b/component/ui/QRCodeModal.tsx index f6554c6..193ed3e 100644 --- a/component/ui/QRCodeModal.tsx +++ b/component/ui/QRCodeModal.tsx @@ -8,7 +8,6 @@ interface QRCodeModalProps { onClose: () => void } -// 新增样式 const styles = { overlay: { position: "fixed" as const, @@ -70,10 +69,6 @@ const styles = { letterSpacing: "2px", color: "#333" }, - countdown: { - color: "#666", - fontSize: "0.9rem" - }, inputGroup: { display: "flex", gap: "0.5rem" @@ -130,7 +125,6 @@ export default function QRCodeModal({ onClose, onConfirm }: QRCodeModalProps) { return (
- {/* 原有关闭按钮和二维码内容... */} diff --git a/component/ui/customDomSelector.tsx b/component/ui/customDomSelector.tsx new file mode 100644 index 0000000..5e0d52a --- /dev/null +++ b/component/ui/customDomSelector.tsx @@ -0,0 +1,204 @@ +import React, { useEffect, useRef, useState } from "react" + +import { useMessage } from "@plasmohq/messaging/hook" +import { useStorage } from "@plasmohq/storage/dist/hook" + +import ValidModal from "~component/ui/validModal" +import { addCss, saveHtml, saveMarkdown } from "~tools" +import { useEditMarkdown } from "~utils/editMarkdownHook" +import { useParseMarkdown } from "~utils/parseMarkdownHook" +import { Print } from "~utils/print" +import Turndown from "~utils/turndown" + +import Tooltip from "../ui/tooltip" + +const turndownService = Turndown() + +export default function CustomDomSelector() { + const [aiType, setAiType] = useStorage("app-aiType") + const [position, setPosition] = useState({ top: 0, left: 0 }) + const [isReady, setIsReady] = useState(false) + const [isSelect, setIsSelect] = useState(false) + const [isModalOpen, setIsModalOpen] = useState(false) + const downloadType = useRef("") + const [editContent, setEditContent] = useEditMarkdown() + const [parseContent, setParseContent] = useParseMarkdown() + + const articleTitle = document + .querySelector("head title") + ?.innerText.trim() + + useEffect(() => { + if (aiType) { + addCss(`.codebox-current { outline: 2px solid #42b88350 !important; }`) + if (isReady) { + addEventListeners() + } else { + removeEventListeners() + } + } + return () => { + removeEventListeners() + } + }, [aiType, isReady, isSelect]) + + 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 && !isSelect) { + const target = event.target as HTMLElement + highlightElement(target) + } + } + + const handleClick = (event: MouseEvent) => { + if (isReady) { + const target = event.target as HTMLElement + const tooltip = target.closest(".codebox-tooltip") + const modal = target.closest(".codebox-modal") + const csui = target.closest("#codebox-csui") + + if (!tooltip && !modal && !csui) { + setIsSelect(true) + highlightElement(target) + updateTooltipPosition(target) + event.stopPropagation() + event.preventDefault() + } + } + } + + const highlightElement = (element: HTMLElement) => { + removeHighlight() + element.classList.add("codebox-current") + } + + const removeHighlight = () => { + const currentList = document.querySelectorAll(".codebox-current") + currentList.forEach((el) => el.classList.remove("codebox-current")) + } + + 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 + const left = distanceLeft + 5 + + setPosition({ top: top, left: left }) + window.scrollTo({ top: top - 150, behavior: "smooth" }) + } + + useMessage(async (req: any, res: any) => { + const name = req.name + setCustom(name.split("-")[1]) + }) + + const setCustom = (type: string) => { + downloadType.current = type + setIsReady(true) + setIsSelect(false) + } + + const handleOkModal = () => { + const selector = document.querySelector(".codebox-current") + if (!selector || !downloadType.current) return + + selector.classList.remove("codebox-current") + + switch (downloadType.current) { + case "downloadHtml": + saveHtml(selector, articleTitle) + break + case "downloadMarkdown": + const markdown = turndownService.turndown(selector) + saveMarkdown(markdown, articleTitle) + break + case "editMarkdown": + setEditContent(selector) + break + case "parseMarkdown": + setParseContent(selector, aiType) + break + case "downloadPdf": + Print.print(selector, { title: articleTitle }) + .then(() => console.log("Printing complete")) + .catch((error) => console.error("Printing failed:", error)) + break + } + + resetState() + setTimeout(() => { + setIsModalOpen(false) + }, 1000) + } + + const handleConfirm = () => { + setIsModalOpen(true) + } + + const handleCancel = () => { + resetState() + } + + const resetState = () => { + removeHighlight() + setIsSelect(false) + setIsReady(false) + } + + const navigateElement = (direction: "parent" | "child" | "prev" | "next") => { + const selector = document.querySelector(".codebox-current") + if (!selector) return + let newElement: HTMLElement | null = null + switch (direction) { + case "parent": + newElement = selector.parentElement + break + case "child": + newElement = selector.firstElementChild as HTMLElement + break + case "prev": + newElement = selector.previousElementSibling as HTMLElement + break + case "next": + newElement = selector.nextElementSibling as HTMLElement + break + } + + if (newElement) { + highlightElement(newElement) + updateTooltipPosition(newElement) + } + } + + const handleCancelModal = () => { + setIsModalOpen(false) + } + + return ( + <> + {isSelect && ( + + )} + {isModalOpen && ( + + )} + + ) +} diff --git a/component/ui/tooltip.tsx b/component/ui/tooltip.tsx index 3402826..4e845d8 100644 --- a/component/ui/tooltip.tsx +++ b/component/ui/tooltip.tsx @@ -10,7 +10,16 @@ import React from "react" const tooltipStyles = { tooltip: { - backgroundColor: "#fff" + position: "absolute" as const, + zIndex: "2147483641", + backgroundColor: "#fff", + border: "1px solid #eee", + borderRadius: "5px", + padding: "8px", + boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)", + width: "450px", + display: "flex", + justifyContent: "space-between" }, button: { backgroundColor: "#fff", @@ -18,7 +27,8 @@ const tooltipStyles = { color: "#000", borderRadius: "4px", padding: "2px 7px", - fontSize: "14px" + fontSize: "14px", + cursor: "pointer" }, blueButton: { backgroundColor: "#1677FF", @@ -51,8 +61,12 @@ export default function Tooltip(props: any) { props.onCancel() } + const setTooltipStyle = () => { + return { ...tooltipStyles.tooltip, left: props.left, top: props.top } + } + return ( -
+
diff --git a/component/ui/validModal.tsx b/component/ui/validModal.tsx new file mode 100644 index 0000000..a83aa80 --- /dev/null +++ b/component/ui/validModal.tsx @@ -0,0 +1,238 @@ +import dayjs from "dayjs" +import qrcodeUrl from "raw:~/public/wx/qrcode_wx.jpg" +import React, { useRef, useState } from "react" + +import { sendToBackground } from "@plasmohq/messaging" +import { useStorage } from "@plasmohq/storage/dist/hook" + +import { verifyTOTP } from "~utils/2FA" + +const styles = { + overlay: { + position: "fixed" as const, + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: "rgba(0, 0, 0, 0.5)", + display: "flex", + justifyContent: "center", + alignItems: "center", + zIndex: 1000 + }, + container: { + width: "350px", + padding: "2rem", + position: "absolute" as const, + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + backgroundColor: "white", + borderRadius: "5px", + textAlign: "center" as const, + boxShadow: "0 2px 10px rgba(0, 0, 0, 0.1)" + }, + closeButton: { + position: "absolute" as const, + top: "10px", + right: "10px", + background: "none", + border: "none", + fontSize: "1.5rem", + cursor: "pointer", + padding: "0.5rem" + }, + activatedText: { + fontSize: "16px", + color: "red" + }, + noticeText: { + fontSize: "14px", + color: "red" + }, + helpLink: { + cursor: "pointer" + }, + qrcodeImage: { + margin: "auto" + }, + inputContainer: { + marginTop: "10px", + display: "flex", + justifyContent: "center", + alignItems: "center" + }, + label: { + display: "block", + marginRight: "7px" + }, + captchaSection: { + marginTop: "1.5rem", + borderTop: "1px solid #eee", + paddingTop: "1.5rem" + }, + captchaRow: { + display: "flex", + justifyContent: "space-between", + alignItems: "center", + marginBottom: "1rem" + }, + captchaText: { + fontSize: "1.2rem", + letterSpacing: "2px", + color: "#333" + }, + inputGroup: { + display: "flex", + gap: "0.5rem" + }, + input: { + flex: 1, + padding: "0.8rem", + border: "1px solid #ddd", + borderRadius: "4px", + fontSize: "1rem", + outline: "none", + "&:focus": { + borderColor: "#007bff" + } + }, + verifyButton: { + padding: "0.8rem 1.5rem", + backgroundColor: "#007bff", + color: "white", + border: "none", + borderRadius: "4px", + cursor: "pointer", + "&:hover": { + backgroundColor: "#0056b3" + } + }, + errorText: { + color: "#ff4444", + fontSize: "0.9rem", + marginTop: "0.5rem" + } +} + +export default function ValidModal(props) { + const [validTime, setValidTime] = useStorage("app-validTime", "1730390400") + const [inputCode, setInputCode] = useState("") + const [isValid, setIsValid] = useState(true) + + function handleSubmit() { + if (Number(validTime) > dayjs().unix()) { + props.handleOk() + } else if ( + verifyTOTP(process.env.PLASMO_PUBLIC_CODEBOX_SECRET1, inputCode) + ) { + let time = dayjs().add(20, "day").unix() + setValidTime(String(time)) + setIsValid(true) + props.handleOk() + } else if ( + verifyTOTP(process.env.PLASMO_PUBLIC_CODEBOX_SECRET2, inputCode) + ) { + let time = dayjs().add(65, "day").unix() + setValidTime(String(time)) + setIsValid(true) + props.handleOk() + } else if (process.env.PLASMO_PUBLIC_CODEBOX_SECRET3 == inputCode) { + let time = dayjs().add(7, "day").unix() + setValidTime(String(time)) + setIsValid(true) + props.handleOk() + } else if ( + verifyTOTP(process.env.PLASMO_PUBLIC_CODEBOX_SECRET4, inputCode) + ) { + setIsValid(true) + props.handleOk() + } else { + setIsValid(false) + } + } + + function handleCancel() { + props.onClose() + } + + function help() { + sendToBackground({ + name: "tab", + body: { + url: "feed.html" + } + }) + } + + const typeMap = { + downloadHtml: "下载HTML", + downloadMarkdown: "下载markdown", + editMarkdown: "编辑markdown", + parseMarkdown: "解析markdown", + downloadPdf: "下载PDF" + } + + return ( +
+
+ + {Number(validTime) > dayjs().unix() ? ( +

+ 已激活!确认{typeMap[props.type]}? +

+ ) : ( + <> +
+ 此功能需要关注公众号 +

+ 【codebox代码助手】获取验证码( + + 帮助 + + ) +

+
+
+ 微信公众号 +
+
+
+ { + const val = e.target.value.replace(/[^\d\s]/g, "") + setInputCode(val) + setIsValid(null) // 清除验证状态 + }} + placeholder="请输入验证码" + style={{ + ...styles.input, + borderColor: isValid === false ? "#ff4444" : "#ddd" + }} + maxLength={11} // 6数字+5空格 + /> + +
+ {isValid === false && ( +
验证码错误,请重新输入
+ )} +
+ + )} +
+
+ ) +} diff --git a/contents/custom.tsx b/contents/custom.tsx index 98ad0b3..a25b9fa 100644 --- a/contents/custom.tsx +++ b/contents/custom.tsx @@ -1,5 +1,3 @@ -import { StyleProvider } from "@ant-design/cssinjs" -import antdResetCssText from "data-text:antd/dist/reset.css" import type { PlasmoCSConfig, PlasmoGetShadowHostId, @@ -11,8 +9,7 @@ import { sendToBackground } from "@plasmohq/messaging" import { useMessage } from "@plasmohq/messaging/hook" import { useStorage } from "@plasmohq/storage/hook" -import CustomDomSelector from "~component/customDomSelector" -import { ThemeProvider } from "~theme" +import CustomDomSelector from "~component/ui/customDomSelector" import { setIcon } from "~tools" import { getSummary } from "~utils/coze" import DrawImages from "~utils/drawImages" @@ -21,13 +18,6 @@ const HOST_ID = "codebox-csui" export const getShadowHostId: PlasmoGetShadowHostId = () => HOST_ID -export const getStyle: PlasmoGetStyle = () => { - const style = document.createElement("style") - - style.textContent = antdResetCssText - return style -} - const articleTitle = document .querySelector("head title") ?.innerText.trim() @@ -101,11 +91,5 @@ export default function CustomOverlay() { } } - return ( - - - - - - ) + return } diff --git a/package.json b/package.json index 6e96092..305e799 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-box", "displayName": "__MSG_extensionName__", - "version": "1.0.4", + "version": "1.0.5", "description": "__MSG_extensionDescription__", "author": "027xiguapi. <458813868@qq.com>", "scripts": {