diff --git a/src/contentScript/contentScript.ts b/src/contentScript/contentScript.ts index a84cd47..eed09e8 100644 --- a/src/contentScript/contentScript.ts +++ b/src/contentScript/contentScript.ts @@ -1,11 +1,121 @@ -const PROBLEM_NAME_CLASS = "mr-2" -const DIFFICULTY_CONTAINER_CLASS = "mt-3" +// LeetCode selectors, based on 2025 website structure +const PROBLEM_NAME_SELECTORS = [".mr-2", "h4", ".text-title-large"] +const DIFFICULTY_SELECTORS = [ + ".mt-3 div span", // Legacy selector + "[role='difficulty']", // Possible new role attribute + ".difficulty-pill" // Possible difficulty label class +] + +/** + * Extract problem slug from URL + */ +function extractSlugFromUrl(url: string): string | null { + const match = url.match(/\/problems\/([^\/]+)/) + return match ? match[1] : null +} + +/** + * Fetch problem metadata using LeetCode's GraphQL API + */ +async function getQuestionMeta(slug: string) { + const body = { + query: ` + query ($titleSlug: String!) { + question(titleSlug: $titleSlug) { + difficulty + questionId + title + likes + dislikes + } + }`, + variables: { titleSlug: slug }, + } + + try { + const res = await fetch("https://leetcode.com/graphql", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }) + + const { data } = await res.json() + return data?.question + } catch (error) { + console.error("Error fetching from LeetCode GraphQL:", error) + return null + } +} chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { - const title = document.querySelector(".mr-2").textContent - const link = window.location.href - const difficulty = - document.querySelector(".mt-3").firstChild.firstChild.textContent - console.log(title, link, difficulty) - sendResponse([title, link, difficulty]) -}) + // Process using async function + (async () => { + try { + // Get slug from URL + const slug = extractSlugFromUrl(window.location.href) + let title = "" + let difficulty = "Unknown" + + // If slug exists, try using GraphQL API + if (slug) { + const meta = await getQuestionMeta(slug) + if (meta) { + title = meta.title + difficulty = meta.difficulty + const link = window.location.href + + console.log("LeetCode2Notion extracted (API):", { title, link, difficulty }) + sendResponse([title, link, difficulty]) + return + } + } + + // Fallback to DOM parsing method + // Try multiple possible problem name selectors + let titleElement = null + for (const selector of PROBLEM_NAME_SELECTORS) { + titleElement = document.querySelector(selector) + if (titleElement) break + } + + // Try multiple possible difficulty selectors + let difficultyElement = null + for (const selector of DIFFICULTY_SELECTORS) { + const elements = document.querySelectorAll(selector) + for (const el of elements) { + // Check if text content contains difficulty keywords + const text = el.textContent?.toLowerCase() || "" + if (text.includes("easy") || text.includes("medium") || text.includes("hard")) { + difficultyElement = el + break + } + } + if (difficultyElement) break + } + + // If problem name not found, try parsing from URL + title = titleElement?.textContent?.trim() || "" + if (!title && slug) { + title = slug.split("-").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" ") + } + + const link = window.location.href + + // Parse difficulty + if (difficultyElement) { + difficulty = difficultyElement.textContent?.trim() || "Unknown" + } + + console.log("LeetCode2Notion extracted (DOM):", { title, link, difficulty }) + sendResponse([title, link, difficulty]) + } catch (error) { + console.error("Error in LeetCode extension:", error) + sendResponse({ error: error.message }) + } + })() + + // Return true to indicate async response handling + return true +}) \ No newline at end of file diff --git a/src/popup/popup.tsx b/src/popup/popup.tsx index 71e8f63..d2ec9ed 100644 --- a/src/popup/popup.tsx +++ b/src/popup/popup.tsx @@ -8,21 +8,63 @@ import getDateString from "../utils/helpers" const App: React.FC<{}> = () => { const [noteInput, setNoteInput] = useState("") + const [error, setError] = useState(null) + const [loading, setLoading] = useState(false) const handleButtonClick = () => { + setError(null) + setLoading(true) let data = [noteInput, getDateString()] chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { - chrome.tabs.sendMessage( - tabs[0].id, - "ask for lc title from pop", - (problemData) => { - // console.log("problemData=", problemData) - data = [...problemData, ...data] - console.log(data) - addItem(data) - } - ) + if (!tabs || !tabs[0] || !tabs[0].id) { + setError("Cannot get current tab information") + setLoading(false) + return + } + + try { + chrome.tabs.sendMessage( + tabs[0].id, + "ask for lc title from pop", + (problemData) => { + // Check for Chrome extension API errors + if (chrome.runtime.lastError) { + console.error("Chrome extension error:", chrome.runtime.lastError) + setError(`Failed to get problem info: ${chrome.runtime.lastError.message}`) + setLoading(false) + return + } + + // Check if problemData is valid + if (!problemData || !Array.isArray(problemData) || problemData.length < 3) { + setError("Invalid problem data received") + setLoading(false) + return + } + + console.log("Successfully received problem data:", problemData) + data = [...problemData, ...data] + console.log("Complete data:", data) + + // Add to Notion + addItem(data) + .catch(err => { + console.error("Failed to add to Notion:", err) + // Display more detailed error information + const errorMessage = err.message || "Failed to add to Notion"; + setError(errorMessage); + }) + .finally(() => { + setLoading(false) + }) + } + ) + } catch (err) { + console.error("Error sending message:", err) + setError("Failed to send message to content script") + setLoading(false) + } }) } @@ -31,28 +73,37 @@ const App: React.FC<{}> = () => { mx="8px" my="16px" > - {/* setNoteInput(event.target.value)} - /> */} setNoteInput(event.target.value)} + disabled={loading} /> + + {error && ( + + Error: {error} + + )} + + {loading && ( + + Processing... + + )} ) } const root = document.createElement("div") document.body.appendChild(root) +// Use React 17 compatible rendering method ReactDOM.render(, root) diff --git a/src/utils/api.ts b/src/utils/api.ts index 81a03c5..2043b85 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -1,6 +1,12 @@ import axios from "axios" import { NOTION_KEY, NOTION_DATABASE_ID } from "../secret" + +// Extend Error type to include originalError property +interface EnhancedError extends Error { + originalError?: any; +} + const BASE_URL = "https://api.notion.com/v1" const getDatabase = async () => { @@ -118,15 +124,33 @@ const addItem = async (data) => { }, } - axios - .request(options) + // Return Promise for subsequent processing + return axios.request(options) .then(function (response) { - console.log(response.data) + console.log("Successfully added to Notion:", response.data) window.close() + return response.data }) .catch(function (error) { - console.error(error) - alert("error") + console.error("Error adding to Notion:", error); + // Convert error object to more useful error message + let errorMessage = "Unknown error"; + if (error.response) { + // Server responded with a status code outside of 2xx range + errorMessage = `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`; + console.error("Error response data:", error.response.data); + } else if (error.request) { + // Request was made but no response was received + errorMessage = "Notion API did not respond, please check your network connection"; + } else { + // Error occurred while setting up the request + errorMessage = `Request error: ${error.message}`; + } + // Throw error with more information + const enhancedError = new Error(errorMessage) as EnhancedError; + enhancedError.originalError = error; + throw enhancedError; }) } export { getDatabase, getPages, addItem } +