diff --git a/_locales/kr/messages.json b/_locales/kr/messages.json new file mode 100644 index 0000000..37df6d4 --- /dev/null +++ b/_locales/kr/messages.json @@ -0,0 +1,522 @@ +{ + "extensionName": { + "message": "DeepShare: AI 채팅 내용을 Word로 내보내기" + }, + "extensionDescription": { + "message": "DeepSeek, ChatGPT, Gemini, Grok 등의 AI 채팅 내용을 Word 문서로 내보내고, 수식을 복사하며, DeepSeek 채팅의 긴 스크린샷을 찍을 수 있습니다." + }, + "shareButton": { + "message": "대화 공유" + }, + "modalTitle": { + "message": "대화 공유" + }, + "imageTab": { + "message": "이미지" + }, + "textTab": { + "message": "텍스트" + }, + "wordTab": { + "message": "Word" + }, + "wordExportInfo": { + "message": "다운로드 또는 복사 버튼을 클릭하여 사용자 질문과 AI 응답을 모두 Word 문서로 내보내세요" + }, + "noAiResponses": { + "message": "AI 응답이 없습니다" + }, + "downloadButton": { + "message": "다운로드" + }, + "copyButton": { + "message": "복사" + }, + "docxButton": { + "message": "DOCX로 저장" + }, + "docxFeatureExplanation": { + "message": "AI를 Word로 변환하는 기능을 설정하세요. 스크린샷 및 수식 복사는 무료로 제공되며 별도의 설정이 필요하지 않습니다." + }, + "docxConversionSuccess": { + "message": "Word 문서로 성공적으로 저장되었습니다!" + }, + "docxConverting": { + "message": "Word 문서로 저장 중입니다. 창을 닫지 마세요." + }, + "docxConversionError": { + "message": "Word 문서로 저장하는 데 실패했습니다. 네트워크 연결을 확인하거나 페이지를 새로 고친 후 다시 시도해 주세요!" + }, + "watermarkSettings": { + "message": "워터마크 설정" + }, + "screenshotSettings": { + "message": "채팅 스크린샷" + }, + "docxSettings": { + "message": "AI 채팅에서 Word로" + }, + "docxServerUrlLabel": { + "message": "서버 URL" + }, + "docxApiKeyLabel": { + "message": "API 키" + }, + "docxModeLabel": { + "message": "변환 모드" + }, + "modeLocalLabel": { + "message": "로컬 (Pandoc)" + }, + "modeApiLabel": { + "message": "API" + }, + "copied": { + "message": "복사됨!" + }, + "copyFailed": { + "message": "복사 실패!" + }, + "formulaCopied": { + "message": "수식 복사됨!" + }, + "clickToCopyFormula": { + "message": "수식을 복사하려면 클릭하세요" + }, + "generatingImage": { + "message": "스크린샷 생성 중..." + }, + "generateFailed": { + "message": "스크린샷 생성에 실패했습니다. 페이지를 새로 고침해 주세요!" + }, + "defaultWatermark": { + "message": "DeepSeek AI에서 생성하고 DeepShare 확장 프로그램으로 캡처한 콘텐츠" + }, + "settingsButton": { + "message": "워터마크 설정" + }, + "hideDefaultWatermarkLabel": { + "message": "기본 워터마크 숨기기" + }, + "customWatermarkLabel": { + "message": "사용자 지정 워터마크 텍스트 (선택 사항)" + }, + "customWatermarkPlaceholder": { + "message": "여기에 사용자 지정 워터마크를 입력하세요" + }, + "saveSettings": { + "message": "설정 저장" + }, + "settingsSaved": { + "message": "저장됨" + }, + "sponsorTitle": { + "message": "이용해 주셔서 감사합니다! 도움이 되셨다면, 위챗 QR 코드를 통해 후원해 주세요 😊" + }, + "selectButton": { + "message": "메시지 선택" + }, + "selectAllButton": { + "message": "모두 선택" + }, + "selectAllResponsesButton": { + "message": "모든 응답 선택" + }, + "unselectAllButton": { + "message": "모두 선택 해제" + }, + "loadingHistory": { + "message": "로딩 중..." + }, + "noMessageSelected": { + "message": "메시지를 하나 이상 선택해 주세요" + }, + "sponsorTabLabel": { + "message": "소개" + }, + "totalQuotaLabel": { + "message": "총량:" + }, + "usedQuotaLabel": { + "message": "사용량:" + }, + "remainingQuotaLabel": { + "message": "잔여량:" + }, + "quotaTitle": { + "message": "전환 할당량" + }, + "dailyQuotaLabel": { + "message": "일일 할당량" + }, + "addonQuotaLabel": { + "message": "애드온 할당량" + }, + "dailyResetNote": { + "message": "매일 초기화됨" + }, + "subscriptionExpiryNote": { + "message": "매일 초기화됨 · {date}까지" + }, + "addonExpiryNote": { + "message": "{date}에 만료됨" + }, + "apiKeyHint": { + "message": "API 키를 구매하시겠습니까? 여기를 클릭하세요" + }, + "apiKeyMissing": { + "message": "문서 변환 기능을 사용하려면 확장 프로그램 아이콘을 클릭하여 API 키를 구매하고 입력해 주세요." + }, + "apiKeyMissingShort": { + "message": "구매 후 API 키를 입력해 주세요" + }, + "manualConversionExplanation": { + "message": "Grok, Claude, Meta AI 등을 지원합니다. 대화 내용을 마크다운 입력란에 복사한 후 \"문서로 변환\"을 클릭하면 수식 지원이 포함된 깔끔하게 서식 처리된 Word 문서를 다운로드할 수 있습니다!" + }, + "manualConversionTitle": { + "message": "AI 채팅 내용 붙여넣기" + }, + "markdownInputLabel": { + "message": "마크다운 텍스트" + }, + "markdownInputPlaceholder": { + "message": "여기에 마크다운 형식의 텍스트를 붙여넣으세요..." + }, + "convertToDocx": { + "message": "문서로 변환" + }, + "clearMarkdown": { + "message": "지우기" + }, + "emptyMarkdownError": { + "message": "마크다운 텍스트를 입력해 주세요" + }, + "manualDocxSettings": { + "message": "AI 채팅 내용 붙여넣기" + }, + "formulaTabLabel": { + "message": "수식 복사" + }, + "formulaSettingsTitle": { + "message": "수식 복사 설정" + }, + "formulaCopyTutorialText": { + "message": "수식 복사에 대해 자세히 알아보기:" + }, + "formulaCopyTutorialLink": { + "message": "튜토리얼 보기" + }, + "enableFormulaCopyLabel": { + "message": "수식 복사 활성화" + }, + "formulaFormatLabel": { + "message": "수식 복사 형식" + }, + "formatMathMLLabel": { + "message": "MathML" + }, + "formatLaTeXLabel": { + "message": "LaTeX" + }, + "formatDollarLatexLabel": { + "message": "마크다운" + }, + "formulaFormatHint": { + "message": "Word용 MathML, WPS/Overleaf용 LaTeX, Lark/Notion/Obsidian용 마크다운" + }, + "formulaEngineLabel": { + "message": "변환 엔진" + }, + "engineMathJaxLabel": { + "message": "MathJax" + }, + "engineKaTeXLabel": { + "message": "KaTeX" + }, + "formulaEngineHint": { + "message": "MathJax는 호환성이 더 우수하고, KaTeX는 더 빠릅니다" + }, + "screenshotMethodLabel": { + "message": "스크린샷 방법" + }, + "methodDomToImageLabel": { + "message": "dom-to-image" + }, + "methodHtml2CanvasLabel": { + "message": "html2canvas" + }, + "screenshotMethodHint": { + "message": "스크린샷에 사용할 방법을 선택하세요. 한 방법이 작동하지 않으면 다른 방법을 시도해 보세요. 방법을 변경한 후 페이지를 새로 고침해 주세요." + }, + "removeDividersLabel": { + "message": "구분선 제거" + }, + "removeEmojisLabel": { + "message": "이모지 제거" + }, + "convertMermaidLabel": { + "message": "Mermaid 변환" + }, + "compatModeLabel": { + "message": "호환 모드" + }, + "compatModeTooltip": { + "message": "비표준 마크다운 지원" + }, + "compatModeDocUrl": { + "message": "https://docs.deepshare.app/en/faq/compat-mode" + }, + "hardLineBreaksLabel": { + "message": "강제 줄바꿈" + }, + "hardLineBreaksTooltip": { + "message": "단일 줄 바꿈을 강제 줄 바꿈으로 처리" + }, + "hardLineBreaksDocUrl": { + "message": "https://docs.deepshare.app/en/faq/hard-line-breaks" + }, + "disableAutoNumberingLabel": { + "message": "자동 번호 매기기 안 함" + }, + "disableAutoNumberingTooltip": { + "message": "번호가 매겨진 순서 목록을 일반 텍스트로 변환함" + }, + "disableAutoNumberingDocUrl": { + "message": "https://docs.deepshare.app/en/faq/hard-line-breaks" + }, + "getClipboardError": { + "message": "콘텐츠 가져오기에 실패했습니다" + }, + "clipboardPermissionError": { + "message": "콘텐츠 가져오기에 실패했습니다. 클립보드 읽기 권한을 허용해 주세요" + }, + "refreshButton": { + "message": "새로 고침" + }, + "purchaseQuota": { + "message": "할당량 구매" + }, + "purchaseAddonQuota": { + "message": "애드온 할당량 구매" + }, + "purchaseSubscription": { + "message": "구독 구매" + }, + "renewSubscription": { + "message": "구독 갱신" + }, + "manageSubscription": { + "message": "구독 관리" + }, + "pricePageUrl": { + "message": "https://ds.rick216.cn/en/price.html" + }, + "subscriptionExpiredDays": { + "message": "{days}일 전에 만료됨" + }, + "expirationLabel": { + "message": "유효 기간:" + }, + "unknown": { + "message": "알 수 없음" + }, + "templateLabel": { + "message": "Word 템플릿" + }, + "universalTemplate": { + "message": "범용" + }, + "saveAsImageButton": { + "message": "이미지로 저장" + }, + "screenshotInitiated": { + "message": "스크린샷 생성 중..." + }, + "screenshotSuccess": { + "message": "스크린샷 저장됨" + }, + "screenshotFailed": { + "message": "스크린샷 생성에 실패했습니다. 새로 고침을 시도해 보시겠습니까?" + }, + "conversationTooLong": { + "message": "대화가 너무 길어 하나의 이미지로 캡처할 수 없습니다. 메시지를 더 적게 선택하고 다시 시도해 주세요." + }, + "imageCopied": { + "message": "이미지가 클립보드에 복사되었습니다" + }, + "imageCopyFailed": { + "message": "이미지를 클립보드에 복사하는 데 실패했습니다" + }, + "apiKeyError": { + "message": "API 키 오류 또는 만료되었습니다. 이메일을 확인하거나 고객 서비스(contact@deepshare.app)에 문의해 주세요." + }, + "quotaExceededError": { + "message": "할당량 초과. 충전해 주십시오." + }, + "aboutTabTitle": { + "message": "DeepShare 정보" + }, + "versionLabel": { + "message": "버전:" + }, + "documentationLabel": { + "message": "문서:" + }, + "documentationUrl": { + "message": "https://docs.deepshare.app/en" + }, + + "githubLabel": { + "message": "GitHub:" + }, + "developerEmailLabel": { + "message": "개발자 이메일:" + }, + "acknowledgmentText": { + "message": "DeepShare에 대한 제안을 보내주신 모든 분께 감사드립니다! 많은 기능이 실제 사용자의 필요에서 탄생합니다. 함께 생산성을 높이고, 인생에서 진정으로 중요한 일에 더 많은 시간을 할애해 봅시다." + }, + "referenceSources": { + "message": "참조 자료" + }, + "unusedSources": { + "message": "검토했으나 사용되지 않은 자료" + }, + "saveAsMarkdown": { + "message": "Markdown으로 저장" + }, + "otherSettingsTabLabel": { + "message": "기타 설정" + }, + "otherSettingsTitle": { + "message": "기타 설정" + }, + "geminiSettingsTitle": { + "message": "Gemini" + }, + "exportGeminiSourcesLabel": { + "message": "Deep Research 출처 내보내기" + }, + "exportGeminiSourcesHint": { + "message": "Gemini Deep Research 보고서를 내보낼 때 참조 출처 포함" + }, + "includeGeminiChatLinkLabel": { + "message": "채팅 링크 내보내기" + }, + "includeGeminiChatLinkHint": { + "message": "내보낸 콘텐츠에는 원본 대화 링크가 포함됩니다" + }, + "itemsSelected": { + "message": "$COUNT$개 항목 선택됨", + "description": "Gemini 선택 막대에서 선택한 개수", + "placeholders": { + "COUNT": { + "content": "$1" + } + } + }, + "selectAllQuestions": { + "message": "모든 질문 선택" + }, + "confirmExport": { + "message": "내보내기 확인" + }, + "cancelButton": { + "message": "취소" + }, + "roleUser": { + "message": "사용자" + }, + "roleAssistant": { + "message": "어시스턴트" + }, + "exportedViaDeepShare": { + "message": "DeepShare를 통해 내보냄" + }, + "geminiConversation": { + "message": "Gemini 대화" + }, + "sourceConversationLabel": { + "message": "소스" + }, + "languageSettingsTitle": { + "message": "언어" + }, + "languageSelectLabel": { + "message": "표시 언어" + }, + "languageAuto": { + "message": "자동 (브라우저 기본값)" + }, + "languageHint": { + "message": "원하는 표시 언어를 선택하세요" + }, + "networkError": { + "message": "변환에 실패했습니다. 네트워크 연결을 확인해 주세요" + }, + "onboardingWelcomeTitle": { + "message": "DeepShare에 오신 것을 환영합니다!" + }, + "onboardingWelcomeSubtitle": { + "message": "적절한 수식 형식을 선택하고 확장 프로그램을 고정해 보세요" + }, + "onboardingSelectionTitle": { + "message": "수식 형식 선택" + }, + "onboardingSelectionDescription": { + "message": "나중에 확장 프로그램 설정에서 변경할 수 있습니다" + }, + "onboardingMathMLDescription": { + "message": "Microsoft Word 사용자에게 안성맞춤입니다" + }, + "onboardingLaTeXDescription": { + "message": "WPS, MathType 및 Overleaf 사용자용" + }, + "onboardingDollarLatexDescription": { + "message": "Lark, Notion 및 Obsidian 사용자용" + }, + "onboardingRecommended": { + "message": "권장" + }, + "onboardingLearnMoreText": { + "message": "수식 복사에 대해 자세히 알아보기:" + }, + "onboardingLearnMoreLink": { + "message": "튜토리얼 보기" + }, + "onboardingContinue": { + "message": "계속하기" + }, + "onboardingPinTitle": { + "message": "DeepShare를 도구 모음에 고정하세요" + }, + "onboardingPinSubtitle": { + "message": "설정에 빠르게 접근하려면 확장 프로그램을 고정하세요" + }, + "onboardingPinStep1": { + "message": "브라우저 툴바에서 퍼즐 아이콘을 클릭하세요" + }, + "onboardingPinStep2": { + "message": "DeepShare를 찾아 핀 아이콘을 클릭하세요" + }, + "onboardingPinStep3": { + "message": "설정에 액세스하려면 언제든지 DeepShare 아이콘을 클릭하세요" + }, + "onboardingFinish": { + "message": "알겠습니다!" + }, + "onboardingBack": { + "message": "뒤로" + }, + "onboardingLearnMoreUsageText": { + "message": "확장 프로그램 사용법에 대해 자세히 알아보기:" + }, + "onboardingLearnMoreUsageLink": { + "message": "튜토리얼 보기" + }, + "onboardingRefreshHint": { + "message": "처음 사용하시는 경우, 열려 있는 모든 AI 대화를 새로 고침해 주세요." + }, + "scrollCollecting": { + "message": "메시지를 수집하기 위해 스크롤 중입니다. 잠시만 기다려 주세요..." + } +} \ No newline at end of file diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index fb8c8a9..09bcc25 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -529,4 +529,4 @@ "scrollCollecting": { "message": "正在滚动读取对话内容,请勿操作..." } -} +} \ No newline at end of file diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index ee113e8..c169e63 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -510,5 +510,23 @@ }, "documentationUrl": { "message": "https://docs.deepshare.app/en" + }, + "aboutTabTitle": { + "message": "關於 DeepShare" + }, + "versionLabel": { + "message": "版本:" + }, + "documentationLabel": { + "message": "文件:" + }, + "githubLabel": { + "message": "GitHub:" + }, + "developerEmailLabel": { + "message": "開發者電子郵件:" + }, + "acknowledgmentText": { + "message": "感謝所有為 DeepShare 提出建議的人!許多功能都源自真實用戶的需求。讓我們一起提升生產力,為生活中真正重要的事情節省時間。" } } diff --git a/manifest.json b/manifest.json index a95c26e..adfeb20 100644 --- a/manifest.json +++ b/manifest.json @@ -66,7 +66,8 @@ "scripts/injectDocxButton.js", "scripts/captureDeepSeek.js", "scripts/saveDeepSeekAsDocx.js", - "scripts/injectDeepSeekButtons.js" + "scripts/injectDeepSeekButtons.js", + "scripts/copyDeepSeekMarkdown.js" ] }, { @@ -129,7 +130,7 @@ "lib/katex.min.js", "scripts/formulaConverter.js", "scripts/notifications.js", - "scripts/copyKatex4Doubao.js" + "scripts/copyKatex4DouBao.js" ] }, { diff --git a/popup/popup.html b/popup/popup.html index 111ba2e..a76244e 100644 --- a/popup/popup.html +++ b/popup/popup.html @@ -409,6 +409,7 @@

Language

+ Select your preferred display language @@ -434,10 +435,11 @@

Gemini

- + How long to wait for Gemini to load more history after each auto-scroll (3-10 seconds)
+ @@ -482,4 +484,4 @@

About

- + \ No newline at end of file diff --git a/scripts/copyDeepSeekMarkdown.js b/scripts/copyDeepSeekMarkdown.js new file mode 100644 index 0000000..9e4b5e4 --- /dev/null +++ b/scripts/copyDeepSeekMarkdown.js @@ -0,0 +1,328 @@ +/** + * DeepSeek Markdown Copy functionality + * Adds a button to copy AI responses as Markdown format + */ + +(function() { + 'use strict'; + console.debug('DeepShare: Initializing DeepSeek Markdown copy functionality'); + + // Function to extract AI response as markdown + function extractAiResponseAsMarkdown(messageDiv) { + // Find the markdown content + const markdownDiv = messageDiv.querySelector('.ds-markdown'); + if (!markdownDiv) return null; + + // Use the same extraction logic as in injectDocxButton.js + return extractMarkdownFromElement(markdownDiv); + } + + // Extract user question as markdown + function extractUserQuestionAsMarkdown(messageDiv) { + const userElement = messageDiv.querySelector('.fbb737a4'); + if (!userElement) return null; + + // Get text content from user message + const userText = Array.from(userElement.childNodes || []) + .find(node => node.nodeType === Node.TEXT_NODE)?.textContent?.trim(); + + return userText || null; + } + + // Extract markdown from element with formula handling + function extractMarkdownFromElement(element) { + + // Returns inline markdown string for a node and its children + function extractInline(node) { + if (node.nodeType === Node.TEXT_NODE) return node.textContent; + + if (node.nodeType !== Node.ELEMENT_NODE) return ''; + + // Skip UI chrome + if (node.classList && ( + node.classList.contains('message-checkbox-wrapper') || + node.classList.contains('ds-checkbox-wrapper') + )) return ''; + + // KaTeX inline + if (node.classList && node.classList.contains('katex')) { + if (node.closest('.katex-display')) return ''; // handled at block level + const ann = node.querySelector('annotation[encoding="application/x-tex"]'); + return ann ? '$' + ann.textContent.trim() + '$' : node.textContent; + } + // Skip the MathML sibling inside katex-display (avoid double output) + if (node.classList && node.classList.contains('katex-mathml')) return ''; + + const tag = node.tagName; + + if (tag === 'STRONG' || tag === 'B') return '**' + extractChildren(node) + '**'; + if (tag === 'EM' || tag === 'I') return '*' + extractChildren(node) + '*'; + if (tag === 'S' || tag === 'DEL') return '~~' + extractChildren(node) + '~~'; + if (tag === 'CODE' && !node.closest('pre')) return '`' + node.textContent + '`'; + if (tag === 'A') { + const href = node.getAttribute('href'); + const text = extractChildren(node); + return (href && href !== text) ? '[' + text + '](' + href + ')' : text; + } + if (tag === 'BR') return '\n'; + + return extractChildren(node); + } + + function extractChildren(node) { + return Array.from(node.childNodes).map(extractInline).join(''); + } + + // Returns block-level markdown string (with surrounding newlines) + function extractBlock(node, depth) { + if (depth === undefined) depth = 0; + + if (node.nodeType === Node.TEXT_NODE) { + const t = node.textContent; + return t.trim() ? t : ''; + } + + if (node.nodeType !== Node.ELEMENT_NODE) return ''; + + // Skip UI chrome + if (node.classList && ( + node.classList.contains('message-checkbox-wrapper') || + node.classList.contains('ds-checkbox-wrapper') + )) return ''; + + const tag = node.tagName; + + // KaTeX display block + if (node.classList && node.classList.contains('katex-display')) { + const ann = node.querySelector('annotation[encoding="application/x-tex"]'); + if (ann) return '\n$$\n' + ann.textContent.trim() + '\n$$\n'; + return ''; + } + if (node.classList && (node.classList.contains('katex') || node.classList.contains('katex-mathml'))) { + return extractInline(node); + } + + // Headings + if (/^H[1-6]$/.test(tag)) { + const level = '#'.repeat(parseInt(tag[1])); + return '\n' + level + ' ' + extractChildren(node).trim() + '\n'; + } + + // Code block + if (tag === 'PRE') { + const code = node.querySelector('code'); + if (code) { + const lang = code.className.match(/language-(\w+)/)?.[1] || ''; + return '\n```' + lang + '\n' + code.textContent.replace(/\n$/, '') + '\n```\n'; + } + return '\n```\n' + node.textContent + '\n```\n'; + } + + // Blockquote + if (tag === 'BLOCKQUOTE') { + const inner = extractChildren(node).trim(); + return '\n' + inner.split('\n').map(l => '> ' + l).join('\n') + '\n'; + } + + // Horizontal rule + if (tag === 'HR') return '\n---\n'; + + // Table + if (tag === 'TABLE') { + let out = '\n'; + const rows = Array.from(node.querySelectorAll('tr')); + rows.forEach((row, i) => { + const cells = Array.from(row.querySelectorAll('th, td')); + out += '| ' + cells.map(c => extractChildren(c).trim()).join(' | ') + ' |\n'; + if (i === 0) { + out += '| ' + cells.map(() => '---').join(' | ') + ' |\n'; + } + }); + return out; + } + if (tag === 'THEAD' || tag === 'TBODY' || tag === 'TR' || tag === 'TH' || tag === 'TD') return ''; + + // Lists + if (tag === 'UL' || tag === 'OL') { + return extractList(node, depth); + } + if (tag === 'LI') return ''; // handled inside extractList + + // Paragraph + if (tag === 'P') { + return '\n' + extractChildren(node).trim() + '\n'; + } + + // Inline elements inside block context — emit inline + if (['STRONG','B','EM','I','S','DEL','CODE','A','SPAN','BR'].includes(tag)) { + return extractInline(node); + } + + // Generic container — recurse + return Array.from(node.childNodes).map(c => extractBlock(c, depth)).join(''); + } + + function extractList(listNode, depth) { + const isOrdered = listNode.tagName === 'OL'; + const indent = ' '.repeat(depth); + let out = '\n'; + let orderedIdx = 1; + Array.from(listNode.children).forEach(li => { + if (li.tagName !== 'LI') return; + const prefix = isOrdered ? (orderedIdx++) + '. ' : '* '; + + // Separate inline content from nested lists + let inlinePart = ''; + let nestedPart = ''; + Array.from(li.childNodes).forEach(child => { + if (child.nodeType === Node.ELEMENT_NODE && (child.tagName === 'UL' || child.tagName === 'OL')) { + nestedPart += extractList(child, depth + 1); + } else { + inlinePart += extractInline(child); + } + }); + + out += indent + prefix + inlinePart.trim() + '\n'; + if (nestedPart) out += nestedPart; + }); + return out; + } + + const raw = Array.from(element.childNodes).map(c => extractBlock(c, 0)).join(''); + return raw.replace(/\n{3,}/g, '\n\n').trim(); + } + + // Save markdown content as a .md file + function saveMarkdownFile(content) { + const filename = generateFilename(content) + '.md'; + const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + window.showToastNotification(chrome.i18n?.getMessage('markdownSaved') || 'Saved as Markdown file', 'success'); + } + + function generateFilename(content) { + const now = new Date(); + const timestamp = now.toLocaleString('zh-CN', { + year: 'numeric', month: '2-digit', day: '2-digit', + hour: '2-digit', minute: '2-digit', second: '2-digit', + hour12: false + }).replace(/[\/\s:]/g, '-').replace(',', ''); + + if (!content) return `deepseek_${timestamp}`; + const lines = content.split('\n').filter(l => l.trim().length > 0); + let firstLine = ''; + if (lines.length > 0) { + firstLine = lines[0].replace(/^#+\s*/, '').replace(/[^a-zA-Z0-9_\u4e00-\u9fa5]/g, '').substring(0, 15).trim(); + } + return `${firstLine || 'deepseek'}_${timestamp}`; + } + + + + // Copy entire conversation as markdown + async function saveConversationAsMarkdown() { + // Find all messages + const messageSelector = '._9663006, ._4f9bf79._43c05b5, ._4f9bf79.d7dc56a8._43c05b5'; + const messages = document.querySelectorAll(messageSelector); + + if (messages.length === 0) { + window.showToastNotification(chrome.i18n?.getMessage('noMessages') || 'No messages found', 'error'); + return; + } + + let conversationContent = ''; + let messageIndex = 1; + + for (const messageDiv of messages) { + const isUserMessage = messageDiv.matches('._9663006') || messageDiv.querySelector('.d29f3d7d'); + + if (isUserMessage) { + const userContent = extractUserQuestionAsMarkdown(messageDiv); + if (userContent) { + conversationContent += `## Message ${messageIndex}: User Question\n\n${userContent}\n\n---\n\n`; + messageIndex++; + } + } else if (!isUserMessage) { + let aiContent = extractAiResponseAsMarkdown(messageDiv); + + + if (aiContent) { + conversationContent += `## Message ${messageIndex}: AI Response\n\n${aiContent}\n\n---\n\n`; + messageIndex++; + } + } + } + + if (conversationContent.trim()) { + // Add title + const title = document.querySelector('.afa34042')?.textContent?.trim() || 'DeepSeek Conversation'; + const timestamp = new Date().toLocaleString(); + const fullMarkdown = `# ${title}\n\n*Exported on ${timestamp} via DeepShare*\n\n---\n\n${conversationContent}`; + + saveMarkdownFile(fullMarkdown); + } else { + window.showToastNotification(chrome.i18n?.getMessage('noContent') || 'No content to save', 'error'); + } + } + + + // Inject conversation markdown copy button in the share panel + function injectConversationMarkdownButton() { + const shareContainer = document.querySelector('._43d222b'); + if (!shareContainer) return; + + const buttonContainer = shareContainer.querySelector('.fab07e97'); + if (!buttonContainer) return; + + // Check if already injected + if (document.getElementById('copy-conversation-md-btn')) return; + + const createLinkButton = buttonContainer.querySelector('.ds-basic-button--primary'); + if (!createLinkButton) return; + + const mdCopyButton = createLinkButton.cloneNode(true); + mdCopyButton.id = 'copy-conversation-md-btn'; + const span = mdCopyButton.querySelector('span'); + span.textContent = chrome.i18n?.getMessage('saveConversationMarkdown') || 'Save as Markdown'; + + // Remove icon if exists + const iconContainer = mdCopyButton.querySelector('.ds-icon'); + if (iconContainer) { + iconContainer.remove(); + } + + mdCopyButton.addEventListener('click', async () => { + await saveConversationAsMarkdown(); + }); + + buttonContainer.insertBefore(mdCopyButton, createLinkButton); + } + + // Observe and inject buttons + function observeAndInject() { + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === 'childList') { + // Inject conversation-level markdown button + injectConversationMarkdownButton(); + } + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true + }); + + } + + // Initialize + observeAndInject(); +})(); \ No newline at end of file diff --git a/styles/style.css b/styles/style.css index b565f8b..41c91b8 100644 --- a/styles/style.css +++ b/styles/style.css @@ -1,3 +1,376 @@ +:root { + --modal-bg: #ffffff; + --modal-text: #333333; + --modal-border: #eeeeee; + --button-color: rgb(139, 139, 139); + --button-hover-bg: rgba(77, 107, 254, 0.1); + --button-hover-color: rgb(77, 107, 254); + --modal-overlay: rgba(0, 0, 0, 0.5); +} + +@media (prefers-color-scheme: dark) { + :root { + --modal-bg: #1e1e1e; + --modal-text: #e0e0e0; + --modal-border: #333333; + --button-color: rgb(173, 178, 184); + --button-hover-bg: rgba(77, 107, 254, 0.2); + --button-hover-color: rgb(107, 137, 254); + --modal-overlay: rgba(0, 0, 0, 0.7); + } +} + +/* 分享按钮样式 */ +.deepseek-share-btn { + display: flex; + align-items: center; + gap: 10px; + /* 增加按钮之间的间距 */ + margin-right: 8px; + /* 添加右边距 */ +} + +/* 按钮通用样式 */ +.deepseek-share-btn .share-button, +.deepseek-share-btn .select-button { + background: none; + border: none; + cursor: pointer; + padding: 6px; + color: rgb(139, 139, 139); + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + transition: all 0.2s ease; +} + +.deepseek-share-btn .share-button:hover, +.deepseek-share-btn .select-button:hover { + background: rgba(139, 139, 139, 0.1); + color: rgb(139, 139, 139); + transform: scale(1.05); +} + +.deepseek-share-btn .share-button:active { + transform: scale(0.95); +} + +/* 深色模式覆盖 */ +@media (prefers-color-scheme: dark) { + + .deepseek-share-btn .share-button, + .deepseek-share-btn .select-button { + color: rgb(173, 178, 184); + /* 深色模式下的颜色 */ + } + + .deepseek-share-btn .share-button:hover, + .deepseek-share-btn .select-button:hover { + background: rgba(173, 178, 184, 0.1); + /* 深色模式下的悬停背景色 */ + color: rgb(98, 99, 99); + } +} + +.select-button svg { + width: 23px; + /* 增加SVG图标大小 */ + height: 23px; + min-width: 23px; + /* 确保最小尺寸 */ + min-height: 23px; +} + +.f8d1e4c0 { + position: relative; +} + +.deepseek-share-modal { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--modal-overlay); + z-index: 1000; +} + +.deepseek-share-modal .modal-content { + background: var(--modal-bg); + color: var(--modal-text); + width: 46%; + min-width: 400px; + max-width: 1200px; + margin: 30px auto; + /* 减小顶部边距 */ + border-radius: 12px; + /* 增加圆角 */ + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +.modal-header { + padding: 6px 20px; + border-bottom: 1px solid var(--modal-border); + display: flex; + justify-content: space-between; + align-items: center; +} + +.header-buttons { + display: flex; + align-items: center; + gap: 4px; +} + +.header-btn { + width: 32px; + height: 32px; + background: none; + border: none; + padding: 6px; + cursor: pointer; + color: var(--button-color); + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + transition: all 0.2s ease; +} + +.header-btn:hover { + background: var(--button-hover-bg); + color: var(--button-hover-color); +} + +.header-btn:active { + transform: scale(0.95); +} + +.modal-body { + padding: 17px 20px 20px 20px; + max-height: 80vh; + /* 增加最大高度 */ + overflow-y: auto; +} + +#conversation-content { + white-space: pre-wrap; + word-break: break-all; + color: var(--modal-text); +} + +/* 标签页样式 */ +.tab-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.tab-container { + display: flex; + gap: 2px; + border-bottom: 1px solid var(--modal-border); + position: relative; +} + +.tab-btn { + padding: 6px; + background: none; + border: none; + color: var(--modal-text); + cursor: pointer; + opacity: 0.7; + transition: all 0.2s; + min-width: 60px; + text-align: center; + position: relative; +} + +.tab-btn.active { + opacity: 1; +} + +.tab-indicator { + position: absolute; + bottom: -1px; + left: 0; + height: 2px; + background-color: var(--button-hover-color); + transition: transform 0.3s ease; + width: 60px; +} + +.tab-btn[data-tab="text"].active~.tab-indicator { + transform: translateX(calc(100% + 2px)); +} + +.tab-btn[data-tab="image"].active~.tab-indicator { + transform: translateX(0); +} + +/* 面板样式 */ +.tab-panel { + display: none; +} + +.tab-panel.active { + display: block; +} + +/* 文本容器样式 */ +#text-panel { + min-height: 200px; + max-height: 70vh; + overflow-y: auto; + border: 1px solid var(--modal-border); + border-radius: 8px; + background: var(--modal-bg); + position: relative; +} + +#conversation-content { + white-space: pre-wrap; + word-break: break-all; + color: var(--modal-text); + padding: 16px; + margin: 0; +} + +/* 图片容器样式 */ +.image-container { + min-height: 200px; + max-height: 70vh; + /* 增加图片容器最大高度 */ + overflow-y: auto; + /* 移除底部间距 */ + border: 1px solid var(--modal-border); + border-radius: 8px; + /* 增加图片容器圆角 */ + background: var(--modal-bg); + position: relative; +} + +#conversation-image { + width: 100%; + height: auto; + display: block; +} + +.image-loading { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: var(--modal-text); +} + +.action-buttons { + display: flex; + gap: 8px; +} + +/* 按钮样式 */ +.download-btn, +.copy-btn { + padding: 4px 10px; + font-size: 12px; + background: var(--button-hover-bg); + color: var(--button-hover-color); + border: 1px solid var(--button-hover-color); + border-radius: 4px; + cursor: pointer; + transition: all 0.2s; +} + +.download-btn:hover, +.copy-btn:hover { + background: var(--button-hover-color); + color: white; + opacity: 0.9; +} + +/* 对话选择复选框样式 */ +.message-checkbox-wrapper { + position: absolute; + top: 3px; + right: 3px; +} + +.message-checkbox { + appearance: none; + -webkit-appearance: none; + width: 18px; + height: 18px; + border: 2px solid var(--button-color); + border-radius: 50%; + cursor: pointer; + position: relative; + outline: none; + transition: all 0.2s ease; +} + +.message-checkbox:checked { + background-color: var(--button-hover-color); + border-color: var(--button-hover-color); +} + +.message-checkbox:checked::after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + width: 8px; + height: 8px; + border-radius: 50%; + background: white; +} + +.message-checkbox:hover { + border-color: var(--button-hover-color); +} + +.select-all-btn, +.select-all-responses-btn { + margin-right: 8px; + padding: 4px 12px; + background: none; + border: 1px solid var(--button-color); + cursor: pointer; + color: var(--button-color); + border-radius: 4px; + font-size: 13px; + height: 28px; + transition: all 0.2s ease; +} + +.select-all-btn:hover, +.select-all-responses-btn:hover { + background: var(--button-hover-bg); + color: var(--button-hover-color); + border-color: var(--button-hover-color); +} + +@media (prefers-color-scheme: dark) { + + .select-all-btn, + .select-all-responses-btn { + color: rgb(173, 178, 184); + border-color: rgb(173, 178, 184); + } + + .select-all-btn:hover, + .select-all-responses-btn:hover { + background: var(--button-hover-bg); + color: var(--button-hover-color); + border-color: var(--button-hover-color); + } +} + /* KaTeX copy functionality styles */ .katex { position: relative; @@ -46,6 +419,82 @@ } } +/* Show format selector when text tab is active */ +.tab-btn[data-tab="text"].active~.action-buttons .format-dropdown-container { + display: inline-block; +} + +/* Format select dropdown styles */ +.format-dropdown-container { + position: relative; + display: inline-block; +} + +.format-select { + appearance: none; + -webkit-appearance: none; + background: var(--modal-bg); + color: var(--modal-text); + border: 1px solid var(--modal-border); + border-radius: 4px; + padding: 4px 24px 4px 8px; + font-size: 13px; + cursor: pointer; + transition: all 0.2s ease; + outline: none; +} + +.format-select:hover { + border-color: var(--button-hover-color); + background: var(--button-hover-bg); +} + +.format-select:focus { + border-color: var(--button-hover-color); + box-shadow: 0 0 0 2px rgba(77, 107, 254, 0.1); +} + +/* Custom dropdown arrow */ +.format-dropdown-container::after { + content: ''; + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid var(--button-color); + pointer-events: none; + transition: all 0.2s ease; +} + +.format-dropdown-container:hover::after { + border-top-color: var(--button-hover-color); +} + +/* Dark mode adjustments */ +@media (prefers-color-scheme: dark) { + .format-select { + background: var(--modal-bg); + color: var(--modal-text); + border-color: var(--modal-border); + } + + .format-select:hover { + background: var(--button-hover-bg); + } + + .format-dropdown-container::after { + border-top-color: var(--button-color); + } + + .format-dropdown-container:hover::after { + border-top-color: var(--button-hover-color); + } +} + /* Tooltips for injected buttons (JS-controlled) */ .deepshare-gpt-tooltip { position: fixed; @@ -126,3 +575,16 @@ border-bottom-color: #000000; } } + +/* Compact the share panel button row so all buttons fit on one line */ +._43d222b .fab07e97 { + flex-wrap: nowrap; + gap: 6px !important; +} + +._43d222b .fab07e97 .ds-basic-button { + padding: 4px 10px !important; + font-size: 12px !important; + min-width: unset !important; + white-space: nowrap; +}