From 0ee2afbd628df9e5502d2b687310e9f033fbfb0a Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sat, 18 Jul 2026 16:56:51 +0000 Subject: [PATCH] fix(extension): sync robuste content/sidepanel, diagnostic panel, preview propre v0.4.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - content.js : écrit la sélection dans chrome.storage.session/local en plus du message runtime - sidepanel.js : lit le storage en fallback, loggue chaque étape, affiche un diagnostic panel - sidepanel.js : avertissement explicite si le content script n'est pas détecté - sidepanel.js : sépare le rendu Markdown du résumé et l'affichage texte brut de l'extrait - sidepanel.css : agrandit la zone de prévisualisation - i18n : nouvelles clés contentScriptMissing, diagnosticsTitle, copyDiagnostics - locales : correction FR (Retour, Enregistrement, Note, etc.) + version 0.4.4 partout - extract-article.ts : nettoie textContent des métadonnées (dates, temps de lecture, sources) - analyze/route.ts : excerpt propre via makeExcerpt - builds Chrome Store et Firefox regénérés en 0.4.4 --- memento-note/app/api/clip/analyze/route.ts | 4 +- .../extension/_locales/ar/messages.json | 13 +- .../extension/_locales/de/messages.json | 13 +- .../extension/_locales/en/messages.json | 11 +- .../extension/_locales/es/messages.json | 13 +- .../extension/_locales/fa/messages.json | 13 +- .../extension/_locales/fr/messages.json | 23 +- .../extension/_locales/hi/messages.json | 13 +- .../extension/_locales/it/messages.json | 13 +- .../extension/_locales/ja/messages.json | 13 +- .../extension/_locales/ko/messages.json | 13 +- .../extension/_locales/nl/messages.json | 13 +- .../extension/_locales/pl/messages.json | 13 +- .../extension/_locales/pt/messages.json | 13 +- .../extension/_locales/ru/messages.json | 13 +- .../extension/_locales/zh/messages.json | 13 +- memento-note/extension/content.js | 35 +- .../_locales/ar/messages.json | 13 +- .../_locales/de/messages.json | 13 +- .../_locales/en/messages.json | 11 +- .../_locales/es/messages.json | 13 +- .../_locales/fa/messages.json | 13 +- .../_locales/fr/messages.json | 23 +- .../_locales/hi/messages.json | 13 +- .../_locales/it/messages.json | 13 +- .../_locales/ja/messages.json | 13 +- .../_locales/ko/messages.json | 13 +- .../_locales/nl/messages.json | 13 +- .../_locales/pl/messages.json | 13 +- .../_locales/pt/messages.json | 13 +- .../_locales/ru/messages.json | 13 +- .../_locales/zh/messages.json | 13 +- .../extension/dist-chrome-store/content.js | 35 +- .../extension/dist-chrome-store/i18n.js | 9 + .../extension/dist-chrome-store/manifest.json | 2 +- .../extension/dist-chrome-store/sidepanel.css | 86 +++- .../dist-chrome-store/sidepanel.html | 11 + .../extension/dist-chrome-store/sidepanel.js | 450 +++++++++++------- .../dist-firefox/_locales/ar/messages.json | 13 +- .../dist-firefox/_locales/de/messages.json | 13 +- .../dist-firefox/_locales/en/messages.json | 11 +- .../dist-firefox/_locales/es/messages.json | 13 +- .../dist-firefox/_locales/fa/messages.json | 13 +- .../dist-firefox/_locales/fr/messages.json | 23 +- .../dist-firefox/_locales/hi/messages.json | 13 +- .../dist-firefox/_locales/it/messages.json | 13 +- .../dist-firefox/_locales/ja/messages.json | 13 +- .../dist-firefox/_locales/ko/messages.json | 13 +- .../dist-firefox/_locales/nl/messages.json | 13 +- .../dist-firefox/_locales/pl/messages.json | 13 +- .../dist-firefox/_locales/pt/messages.json | 13 +- .../dist-firefox/_locales/ru/messages.json | 13 +- .../dist-firefox/_locales/zh/messages.json | 13 +- .../extension/dist-firefox/content.js | 35 +- memento-note/extension/dist-firefox/i18n.js | 9 + .../extension/dist-firefox/manifest.json | 2 +- .../extension/dist-firefox/sidepanel.css | 86 +++- .../extension/dist-firefox/sidepanel.html | 11 + .../extension/dist-firefox/sidepanel.js | 450 +++++++++++------- memento-note/extension/i18n.js | 9 + memento-note/extension/manifest.json | 2 +- .../memento-web-clipper-chrome-store.zip | Bin 46851 -> 49619 bytes .../extension/memento-web-clipper-firefox.xpi | Bin 46916 -> 49684 bytes memento-note/extension/sidepanel.css | 86 +++- memento-note/extension/sidepanel.html | 11 + memento-note/extension/sidepanel.js | 450 +++++++++++------- memento-note/lib/clip/extract-article.ts | 42 +- 67 files changed, 1794 insertions(+), 640 deletions(-) diff --git a/memento-note/app/api/clip/analyze/route.ts b/memento-note/app/api/clip/analyze/route.ts index 99b2f8e..26220ed 100644 --- a/memento-note/app/api/clip/analyze/route.ts +++ b/memento-note/app/api/clip/analyze/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { auth } from '@/auth' -import { extractArticleFromHtml } from '@/lib/clip/extract-article' +import { extractArticleFromHtml, makeExcerpt } from '@/lib/clip/extract-article' import { analyzeClipContent } from '@/lib/clip/analyze-clip' import { resolveClipLocale, wrapClipPlainParagraph } from '@/lib/clip/rtl-content' @@ -92,7 +92,7 @@ export async function POST(request: NextRequest) { tags: analysis.tags, readingTime: analysis.readingTimeMinutes, content: contentHtml, - excerpt: textContent.slice(0, 500), + excerpt: makeExcerpt(textContent, 500), }) } catch (error) { console.error('[POST /api/clip/analyze]', error) diff --git a/memento-note/extension/_locales/ar/messages.json b/memento-note/extension/_locales/ar/messages.json index 5d7cf77..6bc9617 100644 --- a/memento-note/extension/_locales/ar/messages.json +++ b/memento-note/extension/_locales/ar/messages.json @@ -36,7 +36,7 @@ "message": "الصق عنوان URL الخاص بـ HTTPS (أو LAN) لخادم Memento الخاص بك. تتعامل ملفات تعريف الارتباط الموجودة في هذا المتصفح مع تسجيل الدخول." }, "footerVersion": { - "message": "Memento Web Clipper <<<الإصدار>>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "لا يستطيع Memento الوصول إلى علامة التبويب هذه. تحقق من أذونات ملحق لوحة المفاتيح/الموقع — أو افتح اللوحة الجانبية." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "ملخص" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/_locales/de/messages.json b/memento-note/extension/_locales/de/messages.json index 9f27af3..c1fb642 100644 --- a/memento-note/extension/_locales/de/messages.json +++ b/memento-note/extension/_locales/de/messages.json @@ -36,7 +36,7 @@ "message": "Fügen Sie die HTTPS- (oder LAN-)URL Ihres Memento-Servers ein. Cookies in diesem Browser verarbeiten die Anmeldung." }, "footerVersion": { - "message": "Memento Web Clipper 0.4.3" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento kann nicht auf diese Registerkarte zugreifen. Überprüfen Sie die Tastatur-/Site-Erweiterungsberechtigungen – oder öffnen Sie den Seitenbereich." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Zusammenfassung" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/_locales/en/messages.json b/memento-note/extension/_locales/en/messages.json index 1abefad..ac8fd82 100644 --- a/memento-note/extension/_locales/en/messages.json +++ b/memento-note/extension/_locales/en/messages.json @@ -36,7 +36,7 @@ "message": "Paste the HTTPS (or LAN) URL of your Memento server. Cookies in this browser handle sign-in." }, "footerVersion": { - "message": "Memento Web Clipper 0.4.3" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento can't access this tab. Check keyboard/site extension permissions — or open the Side Panel." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Summary" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } } diff --git a/memento-note/extension/_locales/es/messages.json b/memento-note/extension/_locales/es/messages.json index 4bf950a..9e8d01f 100644 --- a/memento-note/extension/_locales/es/messages.json +++ b/memento-note/extension/_locales/es/messages.json @@ -36,7 +36,7 @@ "message": "Pegue la URL HTTPS (o LAN) de su servidor Memento. Las cookies en este navegador controlan el inicio de sesión." }, "footerVersion": { - "message": "Memento Web Clipper <<>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento no puede acceder a esta pestaña. Verifique los permisos de extensión del sitio/teclado o abra el Panel lateral." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Resumen" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/_locales/fa/messages.json b/memento-note/extension/_locales/fa/messages.json index 6028ece..925a1ac 100644 --- a/memento-note/extension/_locales/fa/messages.json +++ b/memento-note/extension/_locales/fa/messages.json @@ -36,7 +36,7 @@ "message": "URL HTTPS (یا LAN) سرور Memento خود را جایگذاری کنید. کوکی‌های این مرورگر ورود به سیستم را کنترل می‌کنند." }, "footerVersion": { - "message": "Memento Web Clipper 0.4.3" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento نمی تواند به این برگه دسترسی پیدا کند. مجوزهای افزونه صفحه کلید/سایت را بررسی کنید - یا پانل جانبی را باز کنید." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "خلاصه" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/_locales/fr/messages.json b/memento-note/extension/_locales/fr/messages.json index 3c15307..d4e487e 100644 --- a/memento-note/extension/_locales/fr/messages.json +++ b/memento-note/extension/_locales/fr/messages.json @@ -36,13 +36,13 @@ "message": "Collez l'URL HTTPS (ou LAN) de votre serveur Memento. Les cookies de ce navigateur gèrent la connexion." }, "footerVersion": { - "message": "Memento Web Clipper 0.4.3" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento ne peut pas accéder à cet onglet. Vérifiez les autorisations du clavier/extension de site – ou ouvrez le panneau latéral." }, "notebookUnnamed": { - "message": "Carnet sans titre" + "message": "Carnet sans nom" }, "noNotebooks": { "message": "Pas encore de carnets" @@ -63,7 +63,7 @@ "message": "Sélection détectée" }, "ignore": { - "message": "ignorer" + "message": "Ignorer" }, "selectionHint": { "message": "Astuce : surlignez du texte sur la page pour clipper une sélection précise en note." @@ -123,22 +123,22 @@ "message": "Enregistrer dans Memento" }, "back": { - "message": "Dos" + "message": "Retour" }, "analyzingSource": { "message": "Analyse de la source" }, "statusAnalyzing": { - "message": "Analyser…" + "message": "Analyse en cours…" }, "statusSaving": { - "message": "Économie…" + "message": "Enregistrement…" }, "processingDetail": { "message": "Génération automatique des tags, résumé sémantique et calcul des embeddings." }, "noteSaved": { - "message": "Remarque enregistrée" + "message": "Note enregistrée" }, "sentToNotebook": { "message": "Enregistré dans $NOTEBOOK$", @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Résumé" + }, + "contentScriptMissing": { + "message": "Content script non détecté — rechargez la page pour clipper." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copier" } } diff --git a/memento-note/extension/_locales/hi/messages.json b/memento-note/extension/_locales/hi/messages.json index 2f4dac7..cde25fd 100644 --- a/memento-note/extension/_locales/hi/messages.json +++ b/memento-note/extension/_locales/hi/messages.json @@ -36,7 +36,7 @@ "message": "अपने मोमेंटो सर्वर का HTTPS (या LAN) URL चिपकाएँ। इस ब्राउज़र में कुकीज़ साइन-इन को संभालती हैं।" }, "footerVersion": { - "message": "मोमेंटो वेब क्लिपर <<<संस्करण>>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "मोमेंटो इस टैब तक नहीं पहुंच सकता. कीबोर्ड/साइट एक्सटेंशन अनुमतियां जांचें - या साइड पैनल खोलें।" @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "सारांश" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/_locales/it/messages.json b/memento-note/extension/_locales/it/messages.json index d9ff357..ab2ef01 100644 --- a/memento-note/extension/_locales/it/messages.json +++ b/memento-note/extension/_locales/it/messages.json @@ -36,7 +36,7 @@ "message": "Incolla l'URL HTTPS (o LAN) del tuo server Memento. I cookie in questo browser gestiscono l'accesso." }, "footerVersion": { - "message": "Memento Web Clipper <<>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento non può accedere a questa scheda. Controlla le autorizzazioni per tastiera/estensione del sito oppure apri il pannello laterale." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Riepilogo" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/_locales/ja/messages.json b/memento-note/extension/_locales/ja/messages.json index cdb498c..46df827 100644 --- a/memento-note/extension/_locales/ja/messages.json +++ b/memento-note/extension/_locales/ja/messages.json @@ -36,7 +36,7 @@ "message": "Memento サーバーの HTTPS (または LAN) URL を貼り付けます。このブラウザの Cookie がサインインを処理します。" }, "footerVersion": { - "message": "Memento Web クリッパー <<<バージョン>>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento はこのタブにアクセスできません。キーボード/サイト拡張機能の権限を確認するか、サイド パネルを開きます。" @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "要約" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/_locales/ko/messages.json b/memento-note/extension/_locales/ko/messages.json index 1ed370e..40709dd 100644 --- a/memento-note/extension/_locales/ko/messages.json +++ b/memento-note/extension/_locales/ko/messages.json @@ -36,7 +36,7 @@ "message": "Memento 서버의 HTTPS(또는 LAN) URL을 붙여넣습니다. 이 브라우저의 쿠키는 로그인을 처리합니다." }, "footerVersion": { - "message": "Memento Web Clipper <<<버전>>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento는 이 탭에 접근할 수 없습니다. 키보드/사이트 확장 권한을 확인하거나 측면 패널을 엽니다." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "요약" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/_locales/nl/messages.json b/memento-note/extension/_locales/nl/messages.json index aae890b..8b0da13 100644 --- a/memento-note/extension/_locales/nl/messages.json +++ b/memento-note/extension/_locales/nl/messages.json @@ -36,7 +36,7 @@ "message": "Plak de HTTPS (of LAN) URL van uw Memento-server. Cookies in deze browser zorgen voor het inloggen." }, "footerVersion": { - "message": "Memento Web Clipper <<>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento heeft geen toegang tot dit tabblad. Controleer de rechten voor toetsenbord-/site-extensies — of open het zijpaneel." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Samenvatting" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/_locales/pl/messages.json b/memento-note/extension/_locales/pl/messages.json index a1a4788..4c344b8 100644 --- a/memento-note/extension/_locales/pl/messages.json +++ b/memento-note/extension/_locales/pl/messages.json @@ -36,7 +36,7 @@ "message": "Wklej adres URL HTTPS (lub LAN) swojego serwera Memento. Pliki cookie w tej przeglądarce obsługują logowanie." }, "footerVersion": { - "message": "Memento Web Clipper <<>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento nie ma dostępu do tej karty. Sprawdź uprawnienia rozszerzenia klawiatury/witryny — lub otwórz Panel boczny." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Streszczenie" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/_locales/pt/messages.json b/memento-note/extension/_locales/pt/messages.json index 26b76bf..294b351 100644 --- a/memento-note/extension/_locales/pt/messages.json +++ b/memento-note/extension/_locales/pt/messages.json @@ -36,7 +36,7 @@ "message": "Cole o URL HTTPS (ou LAN) do seu servidor Memento. Os cookies neste navegador controlam o login." }, "footerVersion": { - "message": "Memento Web Clipper <<>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento não pode acessar esta guia. Verifique as permissões de extensão de teclado/site – ou abra o painel lateral." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Resumo" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/_locales/ru/messages.json b/memento-note/extension/_locales/ru/messages.json index 9de51e1..7755616 100644 --- a/memento-note/extension/_locales/ru/messages.json +++ b/memento-note/extension/_locales/ru/messages.json @@ -36,7 +36,7 @@ "message": "Вставьте URL-адрес HTTPS (или LAN) вашего сервера Memento. Файлы cookie в этом браузере обрабатывают вход в систему." }, "footerVersion": { - "message": "Memento Web Clipper <<<ВЕРСИЯ>>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento не имеет доступа к этой вкладке. Проверьте разрешения для расширения клавиатуры/сайта или откройте боковую панель." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Сводка" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/_locales/zh/messages.json b/memento-note/extension/_locales/zh/messages.json index d3398e6..c50c7b1 100644 --- a/memento-note/extension/_locales/zh/messages.json +++ b/memento-note/extension/_locales/zh/messages.json @@ -36,7 +36,7 @@ "message": "粘贴 Memento 服务器的 HTTPS(或 LAN)URL。此浏览器中的 Cookie 处理登录。" }, "footerVersion": { - "message": "Memento Web Clipper <<<版本>>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento 无法访问此选项卡。检查键盘/站点扩展权限 - 或打开侧面板。" @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "摘要" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/content.js b/memento-note/extension/content.js index 6ad75d4..fffd8ed 100644 --- a/memento-note/extension/content.js +++ b/memento-note/extension/content.js @@ -1,6 +1,12 @@ /** * Content script Momento — sélection live, surlignage, communication avec le side panel. * Injecté automatiquement sur http(s) ; ré-injecté à la demande si l’onglet était déjà ouvert. + * + * Architecture v0.4.4 : + * - La sélection courante est écrite dans chrome.storage.session/local comme source de vérité + * (filet de sécurité si le runtime.sendMessage est perdu). + * - Les messages runtime servent de notification rapide au side panel. + * - SET_PICK_MODE répond toujours avec l’état pickMode + la sélection courante. */ ;(function initMementoClipperContent() { if (globalThis.__mementoClipperContent) return @@ -9,10 +15,16 @@ const HIGHLIGHT_ID = 'memento-clipper-highlight-root' const BANNER_ID = 'memento-clipper-banner-root' const STYLE_ID = 'memento-clipper-styles' + const SESSION_KEY = 'memento_clipper_session' let pickMode = false let debounceTimer = null + function storageArea() { + // Firefox < 128 n’a pas storage.session en MV3 ; on fallback sur local. + return chrome.storage.session || chrome.storage.local + } + function getSelectionText() { return window.getSelection()?.toString().trim() || '' } @@ -36,10 +48,25 @@ } } + async function writeSession(extra = {}) { + try { + await storageArea().set({ + [SESSION_KEY]: { + ...getPageMeta(), + ...extra, + updatedAt: Date.now(), + }, + }) + } catch (err) { + console.debug('[memento] storage session write failed', err) + } + } + function broadcastSelection() { clearTimeout(debounceTimer) - debounceTimer = setTimeout(() => { + debounceTimer = setTimeout(async () => { const payload = { type: 'SELECTION_CHANGED', ...getPageMeta() } + writeSession({ source: 'selection' }) try { chrome.runtime.sendMessage(payload).catch(() => {}) } catch { @@ -189,7 +216,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (message?.type === 'PING') { - sendResponse({ ok: true }) + sendResponse({ ok: true, pickMode, selection: getPageMeta() }) return true } if (message?.type === 'GET_CONTEXT') { @@ -201,11 +228,13 @@ } if (message?.type === 'SET_PICK_MODE') { setPickMode(!!message.enabled) - sendResponse({ ok: true, pickMode }) + sendResponse({ ok: true, pickMode, selection: getPageMeta() }) return true } return false }) + // Amorcer le storage dès l'injection (utile si une sélection existait avant l'ouverture du side panel). + writeSession({ source: 'init' }) broadcastSelection() })() diff --git a/memento-note/extension/dist-chrome-store/_locales/ar/messages.json b/memento-note/extension/dist-chrome-store/_locales/ar/messages.json index 5d7cf77..6bc9617 100644 --- a/memento-note/extension/dist-chrome-store/_locales/ar/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/ar/messages.json @@ -36,7 +36,7 @@ "message": "الصق عنوان URL الخاص بـ HTTPS (أو LAN) لخادم Memento الخاص بك. تتعامل ملفات تعريف الارتباط الموجودة في هذا المتصفح مع تسجيل الدخول." }, "footerVersion": { - "message": "Memento Web Clipper <<<الإصدار>>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "لا يستطيع Memento الوصول إلى علامة التبويب هذه. تحقق من أذونات ملحق لوحة المفاتيح/الموقع — أو افتح اللوحة الجانبية." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "ملخص" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/_locales/de/messages.json b/memento-note/extension/dist-chrome-store/_locales/de/messages.json index 9f27af3..c1fb642 100644 --- a/memento-note/extension/dist-chrome-store/_locales/de/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/de/messages.json @@ -36,7 +36,7 @@ "message": "Fügen Sie die HTTPS- (oder LAN-)URL Ihres Memento-Servers ein. Cookies in diesem Browser verarbeiten die Anmeldung." }, "footerVersion": { - "message": "Memento Web Clipper 0.4.3" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento kann nicht auf diese Registerkarte zugreifen. Überprüfen Sie die Tastatur-/Site-Erweiterungsberechtigungen – oder öffnen Sie den Seitenbereich." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Zusammenfassung" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/_locales/en/messages.json b/memento-note/extension/dist-chrome-store/_locales/en/messages.json index 1abefad..ac8fd82 100644 --- a/memento-note/extension/dist-chrome-store/_locales/en/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/en/messages.json @@ -36,7 +36,7 @@ "message": "Paste the HTTPS (or LAN) URL of your Memento server. Cookies in this browser handle sign-in." }, "footerVersion": { - "message": "Memento Web Clipper 0.4.3" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento can't access this tab. Check keyboard/site extension permissions — or open the Side Panel." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Summary" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } } diff --git a/memento-note/extension/dist-chrome-store/_locales/es/messages.json b/memento-note/extension/dist-chrome-store/_locales/es/messages.json index 4bf950a..9e8d01f 100644 --- a/memento-note/extension/dist-chrome-store/_locales/es/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/es/messages.json @@ -36,7 +36,7 @@ "message": "Pegue la URL HTTPS (o LAN) de su servidor Memento. Las cookies en este navegador controlan el inicio de sesión." }, "footerVersion": { - "message": "Memento Web Clipper <<>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento no puede acceder a esta pestaña. Verifique los permisos de extensión del sitio/teclado o abra el Panel lateral." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Resumen" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/_locales/fa/messages.json b/memento-note/extension/dist-chrome-store/_locales/fa/messages.json index 6028ece..925a1ac 100644 --- a/memento-note/extension/dist-chrome-store/_locales/fa/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/fa/messages.json @@ -36,7 +36,7 @@ "message": "URL HTTPS (یا LAN) سرور Memento خود را جایگذاری کنید. کوکی‌های این مرورگر ورود به سیستم را کنترل می‌کنند." }, "footerVersion": { - "message": "Memento Web Clipper 0.4.3" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento نمی تواند به این برگه دسترسی پیدا کند. مجوزهای افزونه صفحه کلید/سایت را بررسی کنید - یا پانل جانبی را باز کنید." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "خلاصه" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/_locales/fr/messages.json b/memento-note/extension/dist-chrome-store/_locales/fr/messages.json index 3c15307..d4e487e 100644 --- a/memento-note/extension/dist-chrome-store/_locales/fr/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/fr/messages.json @@ -36,13 +36,13 @@ "message": "Collez l'URL HTTPS (ou LAN) de votre serveur Memento. Les cookies de ce navigateur gèrent la connexion." }, "footerVersion": { - "message": "Memento Web Clipper 0.4.3" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento ne peut pas accéder à cet onglet. Vérifiez les autorisations du clavier/extension de site – ou ouvrez le panneau latéral." }, "notebookUnnamed": { - "message": "Carnet sans titre" + "message": "Carnet sans nom" }, "noNotebooks": { "message": "Pas encore de carnets" @@ -63,7 +63,7 @@ "message": "Sélection détectée" }, "ignore": { - "message": "ignorer" + "message": "Ignorer" }, "selectionHint": { "message": "Astuce : surlignez du texte sur la page pour clipper une sélection précise en note." @@ -123,22 +123,22 @@ "message": "Enregistrer dans Memento" }, "back": { - "message": "Dos" + "message": "Retour" }, "analyzingSource": { "message": "Analyse de la source" }, "statusAnalyzing": { - "message": "Analyser…" + "message": "Analyse en cours…" }, "statusSaving": { - "message": "Économie…" + "message": "Enregistrement…" }, "processingDetail": { "message": "Génération automatique des tags, résumé sémantique et calcul des embeddings." }, "noteSaved": { - "message": "Remarque enregistrée" + "message": "Note enregistrée" }, "sentToNotebook": { "message": "Enregistré dans $NOTEBOOK$", @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Résumé" + }, + "contentScriptMissing": { + "message": "Content script non détecté — rechargez la page pour clipper." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copier" } } diff --git a/memento-note/extension/dist-chrome-store/_locales/hi/messages.json b/memento-note/extension/dist-chrome-store/_locales/hi/messages.json index 2f4dac7..cde25fd 100644 --- a/memento-note/extension/dist-chrome-store/_locales/hi/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/hi/messages.json @@ -36,7 +36,7 @@ "message": "अपने मोमेंटो सर्वर का HTTPS (या LAN) URL चिपकाएँ। इस ब्राउज़र में कुकीज़ साइन-इन को संभालती हैं।" }, "footerVersion": { - "message": "मोमेंटो वेब क्लिपर <<<संस्करण>>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "मोमेंटो इस टैब तक नहीं पहुंच सकता. कीबोर्ड/साइट एक्सटेंशन अनुमतियां जांचें - या साइड पैनल खोलें।" @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "सारांश" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/_locales/it/messages.json b/memento-note/extension/dist-chrome-store/_locales/it/messages.json index d9ff357..ab2ef01 100644 --- a/memento-note/extension/dist-chrome-store/_locales/it/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/it/messages.json @@ -36,7 +36,7 @@ "message": "Incolla l'URL HTTPS (o LAN) del tuo server Memento. I cookie in questo browser gestiscono l'accesso." }, "footerVersion": { - "message": "Memento Web Clipper <<>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento non può accedere a questa scheda. Controlla le autorizzazioni per tastiera/estensione del sito oppure apri il pannello laterale." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Riepilogo" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/_locales/ja/messages.json b/memento-note/extension/dist-chrome-store/_locales/ja/messages.json index cdb498c..46df827 100644 --- a/memento-note/extension/dist-chrome-store/_locales/ja/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/ja/messages.json @@ -36,7 +36,7 @@ "message": "Memento サーバーの HTTPS (または LAN) URL を貼り付けます。このブラウザの Cookie がサインインを処理します。" }, "footerVersion": { - "message": "Memento Web クリッパー <<<バージョン>>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento はこのタブにアクセスできません。キーボード/サイト拡張機能の権限を確認するか、サイド パネルを開きます。" @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "要約" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/_locales/ko/messages.json b/memento-note/extension/dist-chrome-store/_locales/ko/messages.json index 1ed370e..40709dd 100644 --- a/memento-note/extension/dist-chrome-store/_locales/ko/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/ko/messages.json @@ -36,7 +36,7 @@ "message": "Memento 서버의 HTTPS(또는 LAN) URL을 붙여넣습니다. 이 브라우저의 쿠키는 로그인을 처리합니다." }, "footerVersion": { - "message": "Memento Web Clipper <<<버전>>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento는 이 탭에 접근할 수 없습니다. 키보드/사이트 확장 권한을 확인하거나 측면 패널을 엽니다." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "요약" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/_locales/nl/messages.json b/memento-note/extension/dist-chrome-store/_locales/nl/messages.json index aae890b..8b0da13 100644 --- a/memento-note/extension/dist-chrome-store/_locales/nl/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/nl/messages.json @@ -36,7 +36,7 @@ "message": "Plak de HTTPS (of LAN) URL van uw Memento-server. Cookies in deze browser zorgen voor het inloggen." }, "footerVersion": { - "message": "Memento Web Clipper <<>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento heeft geen toegang tot dit tabblad. Controleer de rechten voor toetsenbord-/site-extensies — of open het zijpaneel." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Samenvatting" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/_locales/pl/messages.json b/memento-note/extension/dist-chrome-store/_locales/pl/messages.json index a1a4788..4c344b8 100644 --- a/memento-note/extension/dist-chrome-store/_locales/pl/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/pl/messages.json @@ -36,7 +36,7 @@ "message": "Wklej adres URL HTTPS (lub LAN) swojego serwera Memento. Pliki cookie w tej przeglądarce obsługują logowanie." }, "footerVersion": { - "message": "Memento Web Clipper <<>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento nie ma dostępu do tej karty. Sprawdź uprawnienia rozszerzenia klawiatury/witryny — lub otwórz Panel boczny." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Streszczenie" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/_locales/pt/messages.json b/memento-note/extension/dist-chrome-store/_locales/pt/messages.json index 26b76bf..294b351 100644 --- a/memento-note/extension/dist-chrome-store/_locales/pt/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/pt/messages.json @@ -36,7 +36,7 @@ "message": "Cole o URL HTTPS (ou LAN) do seu servidor Memento. Os cookies neste navegador controlam o login." }, "footerVersion": { - "message": "Memento Web Clipper <<>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento não pode acessar esta guia. Verifique as permissões de extensão de teclado/site – ou abra o painel lateral." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Resumo" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/_locales/ru/messages.json b/memento-note/extension/dist-chrome-store/_locales/ru/messages.json index 9de51e1..7755616 100644 --- a/memento-note/extension/dist-chrome-store/_locales/ru/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/ru/messages.json @@ -36,7 +36,7 @@ "message": "Вставьте URL-адрес HTTPS (или LAN) вашего сервера Memento. Файлы cookie в этом браузере обрабатывают вход в систему." }, "footerVersion": { - "message": "Memento Web Clipper <<<ВЕРСИЯ>>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento не имеет доступа к этой вкладке. Проверьте разрешения для расширения клавиатуры/сайта или откройте боковую панель." @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "Сводка" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/_locales/zh/messages.json b/memento-note/extension/dist-chrome-store/_locales/zh/messages.json index d3398e6..c50c7b1 100644 --- a/memento-note/extension/dist-chrome-store/_locales/zh/messages.json +++ b/memento-note/extension/dist-chrome-store/_locales/zh/messages.json @@ -36,7 +36,7 @@ "message": "粘贴 Memento 服务器的 HTTPS(或 LAN)URL。此浏览器中的 Cookie 处理登录。" }, "footerVersion": { - "message": "Memento Web Clipper <<<版本>>>" + "message": "Memento Web Clipper 0.4.4" }, "errPermissionDenied": { "message": "Memento 无法访问此选项卡。检查键盘/站点扩展权限 - 或打开侧面板。" @@ -192,5 +192,14 @@ }, "summaryLabel": { "message": "摘要" + }, + "contentScriptMissing": { + "message": "Content script not detected — reload the page to clip." + }, + "diagnosticsTitle": { + "message": "Diagnostic" + }, + "copyDiagnostics": { + "message": "Copy" } -} +} \ No newline at end of file diff --git a/memento-note/extension/dist-chrome-store/content.js b/memento-note/extension/dist-chrome-store/content.js index 6ad75d4..fffd8ed 100644 --- a/memento-note/extension/dist-chrome-store/content.js +++ b/memento-note/extension/dist-chrome-store/content.js @@ -1,6 +1,12 @@ /** * Content script Momento — sélection live, surlignage, communication avec le side panel. * Injecté automatiquement sur http(s) ; ré-injecté à la demande si l’onglet était déjà ouvert. + * + * Architecture v0.4.4 : + * - La sélection courante est écrite dans chrome.storage.session/local comme source de vérité + * (filet de sécurité si le runtime.sendMessage est perdu). + * - Les messages runtime servent de notification rapide au side panel. + * - SET_PICK_MODE répond toujours avec l’état pickMode + la sélection courante. */ ;(function initMementoClipperContent() { if (globalThis.__mementoClipperContent) return @@ -9,10 +15,16 @@ const HIGHLIGHT_ID = 'memento-clipper-highlight-root' const BANNER_ID = 'memento-clipper-banner-root' const STYLE_ID = 'memento-clipper-styles' + const SESSION_KEY = 'memento_clipper_session' let pickMode = false let debounceTimer = null + function storageArea() { + // Firefox < 128 n’a pas storage.session en MV3 ; on fallback sur local. + return chrome.storage.session || chrome.storage.local + } + function getSelectionText() { return window.getSelection()?.toString().trim() || '' } @@ -36,10 +48,25 @@ } } + async function writeSession(extra = {}) { + try { + await storageArea().set({ + [SESSION_KEY]: { + ...getPageMeta(), + ...extra, + updatedAt: Date.now(), + }, + }) + } catch (err) { + console.debug('[memento] storage session write failed', err) + } + } + function broadcastSelection() { clearTimeout(debounceTimer) - debounceTimer = setTimeout(() => { + debounceTimer = setTimeout(async () => { const payload = { type: 'SELECTION_CHANGED', ...getPageMeta() } + writeSession({ source: 'selection' }) try { chrome.runtime.sendMessage(payload).catch(() => {}) } catch { @@ -189,7 +216,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { if (message?.type === 'PING') { - sendResponse({ ok: true }) + sendResponse({ ok: true, pickMode, selection: getPageMeta() }) return true } if (message?.type === 'GET_CONTEXT') { @@ -201,11 +228,13 @@ } if (message?.type === 'SET_PICK_MODE') { setPickMode(!!message.enabled) - sendResponse({ ok: true, pickMode }) + sendResponse({ ok: true, pickMode, selection: getPageMeta() }) return true } return false }) + // Amorcer le storage dès l'injection (utile si une sélection existait avant l'ouverture du side panel). + writeSession({ source: 'init' }) broadcastSelection() })() diff --git a/memento-note/extension/dist-chrome-store/i18n.js b/memento-note/extension/dist-chrome-store/i18n.js index 2d694a1..f386e26 100644 --- a/memento-note/extension/dist-chrome-store/i18n.js +++ b/memento-note/extension/dist-chrome-store/i18n.js @@ -44,4 +44,13 @@ function applyShellI18n() { if (hint) hint.textContent = t('settingsHint') const footer = document.querySelector('.footer-meta') if (footer) footer.textContent = t('footerVersion') + if (typeof els !== 'undefined' && els.diagToggle) { + const diagTitle = t('diagnosticsTitle') || 'Diagnostic' + els.diagToggle.title = diagTitle + els.diagToggle.setAttribute('aria-label', diagTitle) + } + const diagTitleEl = document.querySelector('.diag-title') + if (diagTitleEl) diagTitleEl.textContent = t('diagnosticsTitle') || 'Diagnostic' + const copyDiagBtn = document.getElementById('copyDiagBtn') + if (copyDiagBtn) copyDiagBtn.textContent = t('copyDiagnostics') || 'Copier' } diff --git a/memento-note/extension/dist-chrome-store/manifest.json b/memento-note/extension/dist-chrome-store/manifest.json index 3540b5d..822e671 100644 --- a/memento-note/extension/dist-chrome-store/manifest.json +++ b/memento-note/extension/dist-chrome-store/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "__MSG_extName__", - "version": "0.4.3", + "version": "0.4.4", "description": "__MSG_extDescription__", "default_locale": "en", "permissions": [ diff --git a/memento-note/extension/dist-chrome-store/sidepanel.css b/memento-note/extension/dist-chrome-store/sidepanel.css index 935796f..0603663 100644 --- a/memento-note/extension/dist-chrome-store/sidepanel.css +++ b/memento-note/extension/dist-chrome-store/sidepanel.css @@ -519,12 +519,23 @@ input[type="text"]:focus, overflow-wrap: anywhere; } .excerpt-block .preview-block-body { - min-height: 180px; - max-height: 360px; + min-height: 220px; + max-height: 520px; overflow-y: auto; background: #fcfbf9; + font-size: 14px; + line-height: 1.75; + padding: 16px 18px; } + +.excerpt-plain { + white-space: normal; +} + .summary-block .preview-block-body { + min-height: 120px; + max-height: 360px; + overflow-y: auto; font-style: normal; color: rgba(28, 28, 28, 0.78); background: rgba(2, 132, 199, 0.04); @@ -639,6 +650,77 @@ html[dir="rtl"] .sub { .page-row .title { font-size: 14px; } } +/* Avertissement content script */ +.cs-warning { + padding: 10px 12px; + border-radius: var(--radius-sm); + background: #fff7ed; + border: 1px solid #fdba74; + color: #9a3412; + font-size: 11px; + line-height: 1.5; + margin-bottom: 8px; +} + +/* Panneau de diagnostic */ +.diag-panel { + border-top: 1px solid var(--border); + background: #1c1c1c; + color: #e5e5e5; + font-family: ui-monospace, 'SF Mono', Menlo, monospace; + font-size: 10px; + line-height: 1.5; +} +.diag-panel[hidden] { display: none !important; } +.diag-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 8px 12px; + background: #2a2a2a; +} +.diag-title { + font-weight: 700; + color: #a3a3a3; + text-transform: uppercase; + letter-spacing: 0.08em; +} +.diag-copy { + background: transparent; + border: 1px solid #525252; + color: #d4d4d4; + border-radius: 6px; + padding: 4px 8px; + font-size: 10px; + cursor: pointer; +} +.diag-copy:hover { background: #404040; } +.diag-body { + max-height: 160px; + overflow-y: auto; + padding: 8px 12px; +} +.diag-line { + padding: 2px 0; + border-bottom: 1px solid #333; + white-space: pre-wrap; + word-break: break-word; +} +.diag-line:last-child { border-bottom: none; } + +.diag-toggle { + background: none; + border: none; + color: var(--muted); + cursor: pointer; + padding: 4px; + display: flex; + align-items: center; + justify-content: center; +} +.diag-toggle:hover { color: var(--ink); } + /* Très petits écrans (mobile debug, fenetres étirées) */ @media (max-width: 320px) { .brand-text { display: none; } diff --git a/memento-note/extension/dist-chrome-store/sidepanel.html b/memento-note/extension/dist-chrome-store/sidepanel.html index b11e1c2..cbed8bd 100644 --- a/memento-note/extension/dist-chrome-store/sidepanel.html +++ b/memento-note/extension/dist-chrome-store/sidepanel.html @@ -24,6 +24,9 @@ + @@ -53,6 +56,14 @@
+ + diff --git a/memento-note/extension/dist-chrome-store/sidepanel.js b/memento-note/extension/dist-chrome-store/sidepanel.js index 236a0b0..05591d5 100644 --- a/memento-note/extension/dist-chrome-store/sidepanel.js +++ b/memento-note/extension/dist-chrome-store/sidepanel.js @@ -2,6 +2,7 @@ const ALLOW_INSTANCE_CONFIG = false const DEFAULT_BASE = 'https://memento-note.com' const STORAGE_KEYS = { baseUrl: 'memento_clipper_base_url', notebookId: 'memento_clipper_notebook_id' } +const SESSION_KEY = 'memento_clipper_session' let state = 'idle' let notebooks = [] @@ -25,6 +26,9 @@ let pendingClipType = 'page' let analyzeResult = null let editableTitle = '' let connected = false +let contentScriptReady = false +const diagLogs = [] +let diagOpen = false const els = { screen: document.getElementById('screen'), @@ -36,6 +40,10 @@ const els = { settingsStatus: document.getElementById('settingsStatus'), applyInstanceBtn: document.getElementById('applyInstanceBtn'), openLoginBtn: document.getElementById('openLoginBtn'), + diagPanel: document.getElementById('diagPanel'), + diagToggle: document.getElementById('diagToggle'), + diagBody: document.getElementById('diagBody'), + copyDiagBtn: document.getElementById('copyDiagBtn'), } const ICON_SELECT = @@ -71,6 +79,10 @@ function escapeHtml(s) { .replace(/"/g, '"') } +function escapePlainText(s) { + return escapeHtml(s).replace(/\n/g, '
') +} + /** * Mini-renderer Markdown sûr pour le side panel. * - Échappe d'abord le texte (anti-XSS) @@ -136,6 +148,38 @@ function rtlAttrs(text) { return ` class="text-rtl" dir="rtl"${lang}` } +// --------------------------------------------------------------------------- +// Diagnostic logging +// --------------------------------------------------------------------------- + +function logDiag(label, detail = '') { + const line = `[${new Date().toLocaleTimeString()}] ${label}${detail ? ` — ${detail}` : ''}` + diagLogs.push(line) + if (diagLogs.length > 80) diagLogs.shift() + renderDiag() + console.debug('[memento-diag]', label, detail) +} + +function renderDiag() { + if (!els.diagBody) return + els.diagBody.innerHTML = diagLogs.map((l) => `
${escapeHtml(l)}
`).join('') + els.diagBody.scrollTop = els.diagBody.scrollHeight +} + +async function copyDiagnostics() { + try { + const text = diagLogs.join('\n') + await navigator.clipboard.writeText(text) + logDiag('diagnostics copied') + } catch (e) { + logDiag('copy failed', String(e)) + } +} + +// --------------------------------------------------------------------------- +// Notebooks +// --------------------------------------------------------------------------- + function sortNotebooksHierarchy(list) { const byParent = new Map() for (const n of list) { @@ -189,29 +233,60 @@ async function getActiveTab() { return tab } +// --------------------------------------------------------------------------- +// Content script synchronization (v0.4.4) +// --------------------------------------------------------------------------- + +function storageArea() { + return chrome.storage.session || chrome.storage.local +} + async function ensureContentScript(tabId) { + logDiag('ensureContentScript start', `tab=${tabId}`) try { const resp = await chrome.tabs.sendMessage(tabId, { type: 'PING' }) - if (resp?.ok) return true - } catch { - /* inject */ + if (resp?.ok) { + contentScriptReady = true + logDiag('PING ok', `pickMode=${resp.pickMode} sel=${resp.selection?.text?.length || 0}`) + if (resp.selection) applySelectionFromMessage(resp.selection, { fromPing: true }) + return true + } + } catch (err) { + logDiag('PING failed', String(err?.message || err)) } try { await chrome.scripting.executeScript({ target: { tabId }, files: ['content.js'] }) - return true - } catch { + logDiag('content script injected') + // attendre un court instant que le script s'enregistre + await new Promise((r) => setTimeout(r, 120)) + const resp = await chrome.tabs.sendMessage(tabId, { type: 'PING' }) + contentScriptReady = resp?.ok || false + logDiag('PING after inject', `ok=${contentScriptReady}`) + return contentScriptReady + } catch (err) { + contentScriptReady = false + logDiag('content script inject failed', String(err?.message || err)) return false } } async function setPickModeOnTab(enabled) { - if (!activeTabId || pageRestricted) return + if (!activeTabId || pageRestricted) { + logDiag('setPickMode skipped', `tab=${activeTabId} restricted=${pageRestricted}`) + return false + } const ok = await ensureContentScript(activeTabId) - if (!ok) return + if (!ok) return false try { - await chrome.tabs.sendMessage(activeTabId, { type: 'SET_PICK_MODE', enabled }) - } catch { - /* ignore */ + const resp = await chrome.tabs.sendMessage(activeTabId, { type: 'SET_PICK_MODE', enabled }) + contentScriptReady = resp?.ok || false + logDiag('SET_PICK_MODE ok', `enabled=${enabled} pickMode=${resp?.pickMode}`) + if (resp?.selection) applySelectionFromMessage(resp.selection, { fromSetPickMode: true }) + return contentScriptReady + } catch (err) { + contentScriptReady = false + logDiag('SET_PICK_MODE failed', String(err?.message || err)) + return false } } @@ -219,6 +294,158 @@ async function syncPickMode() { await setPickModeOnTab(state === 'idle' && !pageRestricted) } +async function readSelectionFromStorage() { + try { + const data = await storageArea().get(SESSION_KEY) + const session = data?.[SESSION_KEY] + if (!session) return null + logDiag('storage read', `url=${session.url?.slice(0, 60)} sel=${session.text?.length || 0}`) + return session + } catch (err) { + logDiag('storage read failed', String(err?.message || err)) + return null + } +} + +function applySelectionFromMessage(msg, { fromStorage = false, fromPing = false, fromSetPickMode = false } = {}) { + if (!msg) return + const normalizeUrl = (u) => { + if (!u) return '' + try { return new URL(u).origin + new URL(u).pathname.replace(/\/+$/, '') } + catch { return u } + } + const msgUrl = msg.url || '' + const curUrl = pageUrl || '' + + if (!curUrl) { + if (msgUrl) { + pageUrl = msgUrl + pageTitle = msg.title || pageTitle || '' + try { + const u = new URL(msgUrl) + pageDomain = u.hostname + pageFavicon = `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32` + } catch { /* ignore */ } + } else { + return + } + } else if (normalizeUrl(msgUrl) !== normalizeUrl(curUrl)) { + return + } + + selectionText = msg.text || '' + if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl' + if (msg.lang) pageLang = msg.lang + logDiag('selection applied', `len=${selectionText.length} source=${fromStorage ? 'storage' : fromPing ? 'ping' : fromSetPickMode ? 'setPickMode' : 'message'}`) + if (state === 'idle') { + if (!curUrl) render() + else updateSelectionUI() + } +} + +async function refreshPageContext() { + logDiag('refreshPageContext start') + const tab = await getActiveTab() + activeTabId = tab?.id ?? null + pageRestricted = tab?.url ? isRestrictedUrl(tab.url) : false + logDiag('active tab', `id=${activeTabId} url=${tab?.url?.slice(0, 80)} restricted=${pageRestricted}`) + + if (!tab?.id || pageRestricted) { + pageUrl = tab?.url || '' + pageTitle = tab?.title || t('pageNotAccessible') + selectionText = '' + contentScriptReady = false + return + } + + pageUrl = tab.url + pageTitle = tab.title || '' + try { + const u = new URL(pageUrl) + pageDomain = u.hostname + pageFavicon = `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32` + } catch { + pageDomain = pageUrl + pageFavicon = 'https://www.google.com/s2/favicons?domain=google.com&sz=32' + } + + const ok = await ensureContentScript(tab.id) + if (!ok) { + // En dernier recours, lire depuis le storage session/local. + const session = await readSelectionFromStorage() + if (session) applySelectionFromMessage(session, { fromStorage: true }) + return + } + + try { + const ctx = await chrome.tabs.sendMessage(tab.id, { type: 'GET_CONTEXT' }) + pageHtml = ctx?.html || '' + selectionText = ctx?.text || '' + pageDir = ctx?.dir?.toLowerCase() === 'rtl' ? 'rtl' : 'ltr' + pageLang = ctx?.lang || '' + logDiag('GET_CONTEXT ok', `html=${pageHtml.length} sel=${selectionText.length}`) + } catch (err) { + logDiag('GET_CONTEXT failed', String(err?.message || err)) + // Fallback storage + const session = await readSelectionFromStorage() + if (session) applySelectionFromMessage(session, { fromStorage: true }) + } +} + +async function loadSettings() { + const stored = await chrome.storage.sync.get([STORAGE_KEYS.baseUrl, STORAGE_KEYS.notebookId]) + if (els.baseUrl) { + els.baseUrl.value = ALLOW_INSTANCE_CONFIG + ? stored[STORAGE_KEYS.baseUrl] || DEFAULT_BASE + : DEFAULT_BASE + } + await loadNotebooks(stored[STORAGE_KEYS.notebookId]) +} + +async function loadNotebooks(preferredId) { + try { + await ensureApiPermission() + const res = await fetch(`${apiBase()}/api/clip/notebooks`, { credentials: 'include' }) + if (!res.ok) { + connected = false + updateConnBadge() + if (res.status === 401) { + throw new Error(t('errLoginRequired')) + } + throw new Error(t('errLoadNotebooks')) + } + const data = await res.json() + notebooks = data.notebooks || [] + selectedNotebookId = + (preferredId && notebooks.some((n) => n.id === preferredId) ? preferredId : '') || + notebooks[0]?.id || + '' + connected = true + updateConnBadge() + errorMessage = '' + setSettingsStatus(t('notebooksLoaded'), false) + logDiag('notebooks loaded', `${notebooks.length}`) + } catch (e) { + notebooks = [] + connected = false + updateConnBadge() + errorMessage = e.message + setSettingsStatus(e.message, true) + logDiag('notebooks failed', e.message) + } +} + +async function applyInstance() { + const url = (els.baseUrl?.value || DEFAULT_BASE).replace(/\/$/, '') + if (els.baseUrl) els.baseUrl.value = url + await chrome.storage.sync.set({ [STORAGE_KEYS.baseUrl]: url }) + setSettingsStatus(t('connecting'), false) + await loadNotebooks(selectedNotebookId) + if (connected) { + setSettingsStatus(t('connectedToUrl', url), false) + } +} + function updateConnBadge() { if (!els.connBadge) return els.connBadge.hidden = !connected @@ -256,7 +483,10 @@ function selectionBlockHtml() { function actionsBlockHtml() { const hasSel = Boolean(selectionText) - return `
+ const contentScriptWarning = !contentScriptReady && !pageRestricted + ? `
${escapeHtml(t('contentScriptMissing') || 'Content script non détecté — rechargez la page pour clipper.')}
` + : '' + return `${contentScriptWarning}
${ hasSel ? ` @@ -53,6 +56,14 @@
+ +
diff --git a/memento-note/extension/dist-firefox/sidepanel.js b/memento-note/extension/dist-firefox/sidepanel.js index 236a0b0..05591d5 100644 --- a/memento-note/extension/dist-firefox/sidepanel.js +++ b/memento-note/extension/dist-firefox/sidepanel.js @@ -2,6 +2,7 @@ const ALLOW_INSTANCE_CONFIG = false const DEFAULT_BASE = 'https://memento-note.com' const STORAGE_KEYS = { baseUrl: 'memento_clipper_base_url', notebookId: 'memento_clipper_notebook_id' } +const SESSION_KEY = 'memento_clipper_session' let state = 'idle' let notebooks = [] @@ -25,6 +26,9 @@ let pendingClipType = 'page' let analyzeResult = null let editableTitle = '' let connected = false +let contentScriptReady = false +const diagLogs = [] +let diagOpen = false const els = { screen: document.getElementById('screen'), @@ -36,6 +40,10 @@ const els = { settingsStatus: document.getElementById('settingsStatus'), applyInstanceBtn: document.getElementById('applyInstanceBtn'), openLoginBtn: document.getElementById('openLoginBtn'), + diagPanel: document.getElementById('diagPanel'), + diagToggle: document.getElementById('diagToggle'), + diagBody: document.getElementById('diagBody'), + copyDiagBtn: document.getElementById('copyDiagBtn'), } const ICON_SELECT = @@ -71,6 +79,10 @@ function escapeHtml(s) { .replace(/"/g, '"') } +function escapePlainText(s) { + return escapeHtml(s).replace(/\n/g, '
') +} + /** * Mini-renderer Markdown sûr pour le side panel. * - Échappe d'abord le texte (anti-XSS) @@ -136,6 +148,38 @@ function rtlAttrs(text) { return ` class="text-rtl" dir="rtl"${lang}` } +// --------------------------------------------------------------------------- +// Diagnostic logging +// --------------------------------------------------------------------------- + +function logDiag(label, detail = '') { + const line = `[${new Date().toLocaleTimeString()}] ${label}${detail ? ` — ${detail}` : ''}` + diagLogs.push(line) + if (diagLogs.length > 80) diagLogs.shift() + renderDiag() + console.debug('[memento-diag]', label, detail) +} + +function renderDiag() { + if (!els.diagBody) return + els.diagBody.innerHTML = diagLogs.map((l) => `
${escapeHtml(l)}
`).join('') + els.diagBody.scrollTop = els.diagBody.scrollHeight +} + +async function copyDiagnostics() { + try { + const text = diagLogs.join('\n') + await navigator.clipboard.writeText(text) + logDiag('diagnostics copied') + } catch (e) { + logDiag('copy failed', String(e)) + } +} + +// --------------------------------------------------------------------------- +// Notebooks +// --------------------------------------------------------------------------- + function sortNotebooksHierarchy(list) { const byParent = new Map() for (const n of list) { @@ -189,29 +233,60 @@ async function getActiveTab() { return tab } +// --------------------------------------------------------------------------- +// Content script synchronization (v0.4.4) +// --------------------------------------------------------------------------- + +function storageArea() { + return chrome.storage.session || chrome.storage.local +} + async function ensureContentScript(tabId) { + logDiag('ensureContentScript start', `tab=${tabId}`) try { const resp = await chrome.tabs.sendMessage(tabId, { type: 'PING' }) - if (resp?.ok) return true - } catch { - /* inject */ + if (resp?.ok) { + contentScriptReady = true + logDiag('PING ok', `pickMode=${resp.pickMode} sel=${resp.selection?.text?.length || 0}`) + if (resp.selection) applySelectionFromMessage(resp.selection, { fromPing: true }) + return true + } + } catch (err) { + logDiag('PING failed', String(err?.message || err)) } try { await chrome.scripting.executeScript({ target: { tabId }, files: ['content.js'] }) - return true - } catch { + logDiag('content script injected') + // attendre un court instant que le script s'enregistre + await new Promise((r) => setTimeout(r, 120)) + const resp = await chrome.tabs.sendMessage(tabId, { type: 'PING' }) + contentScriptReady = resp?.ok || false + logDiag('PING after inject', `ok=${contentScriptReady}`) + return contentScriptReady + } catch (err) { + contentScriptReady = false + logDiag('content script inject failed', String(err?.message || err)) return false } } async function setPickModeOnTab(enabled) { - if (!activeTabId || pageRestricted) return + if (!activeTabId || pageRestricted) { + logDiag('setPickMode skipped', `tab=${activeTabId} restricted=${pageRestricted}`) + return false + } const ok = await ensureContentScript(activeTabId) - if (!ok) return + if (!ok) return false try { - await chrome.tabs.sendMessage(activeTabId, { type: 'SET_PICK_MODE', enabled }) - } catch { - /* ignore */ + const resp = await chrome.tabs.sendMessage(activeTabId, { type: 'SET_PICK_MODE', enabled }) + contentScriptReady = resp?.ok || false + logDiag('SET_PICK_MODE ok', `enabled=${enabled} pickMode=${resp?.pickMode}`) + if (resp?.selection) applySelectionFromMessage(resp.selection, { fromSetPickMode: true }) + return contentScriptReady + } catch (err) { + contentScriptReady = false + logDiag('SET_PICK_MODE failed', String(err?.message || err)) + return false } } @@ -219,6 +294,158 @@ async function syncPickMode() { await setPickModeOnTab(state === 'idle' && !pageRestricted) } +async function readSelectionFromStorage() { + try { + const data = await storageArea().get(SESSION_KEY) + const session = data?.[SESSION_KEY] + if (!session) return null + logDiag('storage read', `url=${session.url?.slice(0, 60)} sel=${session.text?.length || 0}`) + return session + } catch (err) { + logDiag('storage read failed', String(err?.message || err)) + return null + } +} + +function applySelectionFromMessage(msg, { fromStorage = false, fromPing = false, fromSetPickMode = false } = {}) { + if (!msg) return + const normalizeUrl = (u) => { + if (!u) return '' + try { return new URL(u).origin + new URL(u).pathname.replace(/\/+$/, '') } + catch { return u } + } + const msgUrl = msg.url || '' + const curUrl = pageUrl || '' + + if (!curUrl) { + if (msgUrl) { + pageUrl = msgUrl + pageTitle = msg.title || pageTitle || '' + try { + const u = new URL(msgUrl) + pageDomain = u.hostname + pageFavicon = `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32` + } catch { /* ignore */ } + } else { + return + } + } else if (normalizeUrl(msgUrl) !== normalizeUrl(curUrl)) { + return + } + + selectionText = msg.text || '' + if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl' + if (msg.lang) pageLang = msg.lang + logDiag('selection applied', `len=${selectionText.length} source=${fromStorage ? 'storage' : fromPing ? 'ping' : fromSetPickMode ? 'setPickMode' : 'message'}`) + if (state === 'idle') { + if (!curUrl) render() + else updateSelectionUI() + } +} + +async function refreshPageContext() { + logDiag('refreshPageContext start') + const tab = await getActiveTab() + activeTabId = tab?.id ?? null + pageRestricted = tab?.url ? isRestrictedUrl(tab.url) : false + logDiag('active tab', `id=${activeTabId} url=${tab?.url?.slice(0, 80)} restricted=${pageRestricted}`) + + if (!tab?.id || pageRestricted) { + pageUrl = tab?.url || '' + pageTitle = tab?.title || t('pageNotAccessible') + selectionText = '' + contentScriptReady = false + return + } + + pageUrl = tab.url + pageTitle = tab.title || '' + try { + const u = new URL(pageUrl) + pageDomain = u.hostname + pageFavicon = `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32` + } catch { + pageDomain = pageUrl + pageFavicon = 'https://www.google.com/s2/favicons?domain=google.com&sz=32' + } + + const ok = await ensureContentScript(tab.id) + if (!ok) { + // En dernier recours, lire depuis le storage session/local. + const session = await readSelectionFromStorage() + if (session) applySelectionFromMessage(session, { fromStorage: true }) + return + } + + try { + const ctx = await chrome.tabs.sendMessage(tab.id, { type: 'GET_CONTEXT' }) + pageHtml = ctx?.html || '' + selectionText = ctx?.text || '' + pageDir = ctx?.dir?.toLowerCase() === 'rtl' ? 'rtl' : 'ltr' + pageLang = ctx?.lang || '' + logDiag('GET_CONTEXT ok', `html=${pageHtml.length} sel=${selectionText.length}`) + } catch (err) { + logDiag('GET_CONTEXT failed', String(err?.message || err)) + // Fallback storage + const session = await readSelectionFromStorage() + if (session) applySelectionFromMessage(session, { fromStorage: true }) + } +} + +async function loadSettings() { + const stored = await chrome.storage.sync.get([STORAGE_KEYS.baseUrl, STORAGE_KEYS.notebookId]) + if (els.baseUrl) { + els.baseUrl.value = ALLOW_INSTANCE_CONFIG + ? stored[STORAGE_KEYS.baseUrl] || DEFAULT_BASE + : DEFAULT_BASE + } + await loadNotebooks(stored[STORAGE_KEYS.notebookId]) +} + +async function loadNotebooks(preferredId) { + try { + await ensureApiPermission() + const res = await fetch(`${apiBase()}/api/clip/notebooks`, { credentials: 'include' }) + if (!res.ok) { + connected = false + updateConnBadge() + if (res.status === 401) { + throw new Error(t('errLoginRequired')) + } + throw new Error(t('errLoadNotebooks')) + } + const data = await res.json() + notebooks = data.notebooks || [] + selectedNotebookId = + (preferredId && notebooks.some((n) => n.id === preferredId) ? preferredId : '') || + notebooks[0]?.id || + '' + connected = true + updateConnBadge() + errorMessage = '' + setSettingsStatus(t('notebooksLoaded'), false) + logDiag('notebooks loaded', `${notebooks.length}`) + } catch (e) { + notebooks = [] + connected = false + updateConnBadge() + errorMessage = e.message + setSettingsStatus(e.message, true) + logDiag('notebooks failed', e.message) + } +} + +async function applyInstance() { + const url = (els.baseUrl?.value || DEFAULT_BASE).replace(/\/$/, '') + if (els.baseUrl) els.baseUrl.value = url + await chrome.storage.sync.set({ [STORAGE_KEYS.baseUrl]: url }) + setSettingsStatus(t('connecting'), false) + await loadNotebooks(selectedNotebookId) + if (connected) { + setSettingsStatus(t('connectedToUrl', url), false) + } +} + function updateConnBadge() { if (!els.connBadge) return els.connBadge.hidden = !connected @@ -256,7 +483,10 @@ function selectionBlockHtml() { function actionsBlockHtml() { const hasSel = Boolean(selectionText) - return `
+ const contentScriptWarning = !contentScriptReady && !pageRestricted + ? `
${escapeHtml(t('contentScriptMissing') || 'Content script non détecté — rechargez la page pour clipper.')}
` + : '' + return `${contentScriptWarning}
${ hasSel ? ` @@ -53,6 +56,14 @@
+ +
diff --git a/memento-note/extension/sidepanel.js b/memento-note/extension/sidepanel.js index 29c9306..5c06ba8 100644 --- a/memento-note/extension/sidepanel.js +++ b/memento-note/extension/sidepanel.js @@ -2,6 +2,7 @@ const ALLOW_INSTANCE_CONFIG = true const DEFAULT_BASE = 'https://memento-note.com' const STORAGE_KEYS = { baseUrl: 'memento_clipper_base_url', notebookId: 'memento_clipper_notebook_id' } +const SESSION_KEY = 'memento_clipper_session' let state = 'idle' let notebooks = [] @@ -25,6 +26,9 @@ let pendingClipType = 'page' let analyzeResult = null let editableTitle = '' let connected = false +let contentScriptReady = false +const diagLogs = [] +let diagOpen = false const els = { screen: document.getElementById('screen'), @@ -36,6 +40,10 @@ const els = { settingsStatus: document.getElementById('settingsStatus'), applyInstanceBtn: document.getElementById('applyInstanceBtn'), openLoginBtn: document.getElementById('openLoginBtn'), + diagPanel: document.getElementById('diagPanel'), + diagToggle: document.getElementById('diagToggle'), + diagBody: document.getElementById('diagBody'), + copyDiagBtn: document.getElementById('copyDiagBtn'), } const ICON_SELECT = @@ -71,6 +79,10 @@ function escapeHtml(s) { .replace(/"/g, '"') } +function escapePlainText(s) { + return escapeHtml(s).replace(/\n/g, '
') +} + /** * Mini-renderer Markdown sûr pour le side panel. * - Échappe d'abord le texte (anti-XSS) @@ -136,6 +148,38 @@ function rtlAttrs(text) { return ` class="text-rtl" dir="rtl"${lang}` } +// --------------------------------------------------------------------------- +// Diagnostic logging +// --------------------------------------------------------------------------- + +function logDiag(label, detail = '') { + const line = `[${new Date().toLocaleTimeString()}] ${label}${detail ? ` — ${detail}` : ''}` + diagLogs.push(line) + if (diagLogs.length > 80) diagLogs.shift() + renderDiag() + console.debug('[memento-diag]', label, detail) +} + +function renderDiag() { + if (!els.diagBody) return + els.diagBody.innerHTML = diagLogs.map((l) => `
${escapeHtml(l)}
`).join('') + els.diagBody.scrollTop = els.diagBody.scrollHeight +} + +async function copyDiagnostics() { + try { + const text = diagLogs.join('\n') + await navigator.clipboard.writeText(text) + logDiag('diagnostics copied') + } catch (e) { + logDiag('copy failed', String(e)) + } +} + +// --------------------------------------------------------------------------- +// Notebooks +// --------------------------------------------------------------------------- + function sortNotebooksHierarchy(list) { const byParent = new Map() for (const n of list) { @@ -189,29 +233,60 @@ async function getActiveTab() { return tab } +// --------------------------------------------------------------------------- +// Content script synchronization (v0.4.4) +// --------------------------------------------------------------------------- + +function storageArea() { + return chrome.storage.session || chrome.storage.local +} + async function ensureContentScript(tabId) { + logDiag('ensureContentScript start', `tab=${tabId}`) try { const resp = await chrome.tabs.sendMessage(tabId, { type: 'PING' }) - if (resp?.ok) return true - } catch { - /* inject */ + if (resp?.ok) { + contentScriptReady = true + logDiag('PING ok', `pickMode=${resp.pickMode} sel=${resp.selection?.text?.length || 0}`) + if (resp.selection) applySelectionFromMessage(resp.selection, { fromPing: true }) + return true + } + } catch (err) { + logDiag('PING failed', String(err?.message || err)) } try { await chrome.scripting.executeScript({ target: { tabId }, files: ['content.js'] }) - return true - } catch { + logDiag('content script injected') + // attendre un court instant que le script s'enregistre + await new Promise((r) => setTimeout(r, 120)) + const resp = await chrome.tabs.sendMessage(tabId, { type: 'PING' }) + contentScriptReady = resp?.ok || false + logDiag('PING after inject', `ok=${contentScriptReady}`) + return contentScriptReady + } catch (err) { + contentScriptReady = false + logDiag('content script inject failed', String(err?.message || err)) return false } } async function setPickModeOnTab(enabled) { - if (!activeTabId || pageRestricted) return + if (!activeTabId || pageRestricted) { + logDiag('setPickMode skipped', `tab=${activeTabId} restricted=${pageRestricted}`) + return false + } const ok = await ensureContentScript(activeTabId) - if (!ok) return + if (!ok) return false try { - await chrome.tabs.sendMessage(activeTabId, { type: 'SET_PICK_MODE', enabled }) - } catch { - /* ignore */ + const resp = await chrome.tabs.sendMessage(activeTabId, { type: 'SET_PICK_MODE', enabled }) + contentScriptReady = resp?.ok || false + logDiag('SET_PICK_MODE ok', `enabled=${enabled} pickMode=${resp?.pickMode}`) + if (resp?.selection) applySelectionFromMessage(resp.selection, { fromSetPickMode: true }) + return contentScriptReady + } catch (err) { + contentScriptReady = false + logDiag('SET_PICK_MODE failed', String(err?.message || err)) + return false } } @@ -219,6 +294,158 @@ async function syncPickMode() { await setPickModeOnTab(state === 'idle' && !pageRestricted) } +async function readSelectionFromStorage() { + try { + const data = await storageArea().get(SESSION_KEY) + const session = data?.[SESSION_KEY] + if (!session) return null + logDiag('storage read', `url=${session.url?.slice(0, 60)} sel=${session.text?.length || 0}`) + return session + } catch (err) { + logDiag('storage read failed', String(err?.message || err)) + return null + } +} + +function applySelectionFromMessage(msg, { fromStorage = false, fromPing = false, fromSetPickMode = false } = {}) { + if (!msg) return + const normalizeUrl = (u) => { + if (!u) return '' + try { return new URL(u).origin + new URL(u).pathname.replace(/\/+$/, '') } + catch { return u } + } + const msgUrl = msg.url || '' + const curUrl = pageUrl || '' + + if (!curUrl) { + if (msgUrl) { + pageUrl = msgUrl + pageTitle = msg.title || pageTitle || '' + try { + const u = new URL(msgUrl) + pageDomain = u.hostname + pageFavicon = `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32` + } catch { /* ignore */ } + } else { + return + } + } else if (normalizeUrl(msgUrl) !== normalizeUrl(curUrl)) { + return + } + + selectionText = msg.text || '' + if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl' + if (msg.lang) pageLang = msg.lang + logDiag('selection applied', `len=${selectionText.length} source=${fromStorage ? 'storage' : fromPing ? 'ping' : fromSetPickMode ? 'setPickMode' : 'message'}`) + if (state === 'idle') { + if (!curUrl) render() + else updateSelectionUI() + } +} + +async function refreshPageContext() { + logDiag('refreshPageContext start') + const tab = await getActiveTab() + activeTabId = tab?.id ?? null + pageRestricted = tab?.url ? isRestrictedUrl(tab.url) : false + logDiag('active tab', `id=${activeTabId} url=${tab?.url?.slice(0, 80)} restricted=${pageRestricted}`) + + if (!tab?.id || pageRestricted) { + pageUrl = tab?.url || '' + pageTitle = tab?.title || t('pageNotAccessible') + selectionText = '' + contentScriptReady = false + return + } + + pageUrl = tab.url + pageTitle = tab.title || '' + try { + const u = new URL(pageUrl) + pageDomain = u.hostname + pageFavicon = `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32` + } catch { + pageDomain = pageUrl + pageFavicon = 'https://www.google.com/s2/favicons?domain=google.com&sz=32' + } + + const ok = await ensureContentScript(tab.id) + if (!ok) { + // En dernier recours, lire depuis le storage session/local. + const session = await readSelectionFromStorage() + if (session) applySelectionFromMessage(session, { fromStorage: true }) + return + } + + try { + const ctx = await chrome.tabs.sendMessage(tab.id, { type: 'GET_CONTEXT' }) + pageHtml = ctx?.html || '' + selectionText = ctx?.text || '' + pageDir = ctx?.dir?.toLowerCase() === 'rtl' ? 'rtl' : 'ltr' + pageLang = ctx?.lang || '' + logDiag('GET_CONTEXT ok', `html=${pageHtml.length} sel=${selectionText.length}`) + } catch (err) { + logDiag('GET_CONTEXT failed', String(err?.message || err)) + // Fallback storage + const session = await readSelectionFromStorage() + if (session) applySelectionFromMessage(session, { fromStorage: true }) + } +} + +async function loadSettings() { + const stored = await chrome.storage.sync.get([STORAGE_KEYS.baseUrl, STORAGE_KEYS.notebookId]) + if (els.baseUrl) { + els.baseUrl.value = ALLOW_INSTANCE_CONFIG + ? stored[STORAGE_KEYS.baseUrl] || DEFAULT_BASE + : DEFAULT_BASE + } + await loadNotebooks(stored[STORAGE_KEYS.notebookId]) +} + +async function loadNotebooks(preferredId) { + try { + await ensureApiPermission() + const res = await fetch(`${apiBase()}/api/clip/notebooks`, { credentials: 'include' }) + if (!res.ok) { + connected = false + updateConnBadge() + if (res.status === 401) { + throw new Error(t('errLoginRequired')) + } + throw new Error(t('errLoadNotebooks')) + } + const data = await res.json() + notebooks = data.notebooks || [] + selectedNotebookId = + (preferredId && notebooks.some((n) => n.id === preferredId) ? preferredId : '') || + notebooks[0]?.id || + '' + connected = true + updateConnBadge() + errorMessage = '' + setSettingsStatus(t('notebooksLoaded'), false) + logDiag('notebooks loaded', `${notebooks.length}`) + } catch (e) { + notebooks = [] + connected = false + updateConnBadge() + errorMessage = e.message + setSettingsStatus(e.message, true) + logDiag('notebooks failed', e.message) + } +} + +async function applyInstance() { + const url = (els.baseUrl?.value || DEFAULT_BASE).replace(/\/$/, '') + if (els.baseUrl) els.baseUrl.value = url + await chrome.storage.sync.set({ [STORAGE_KEYS.baseUrl]: url }) + setSettingsStatus(t('connecting'), false) + await loadNotebooks(selectedNotebookId) + if (connected) { + setSettingsStatus(t('connectedToUrl', url), false) + } +} + function updateConnBadge() { if (!els.connBadge) return els.connBadge.hidden = !connected @@ -256,7 +483,10 @@ function selectionBlockHtml() { function actionsBlockHtml() { const hasSel = Boolean(selectionText) - return `
+ const contentScriptWarning = !contentScriptReady && !pageRestricted + ? `
${escapeHtml(t('contentScriptMissing') || 'Content script non détecté — rechargez la page pour clipper.')}
` + : '' + return `${contentScriptWarning}
${ hasSel ? `