feat(privacy+extension): publier la page /privacy et préparer les builds Chrome/Firefox Store
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 5m49s
CI / Deploy production (on server) (push) Successful in 24s

- Nouvelle page /privacy bilingue (FR/EN) avec sélection par Accept-Language
  ou ?lang=, sections complètes (données collectées, IA/BYOK, RGPD, sécurité,
  contact). Style sombre aligné sur la landing (x.ai premium).
- Liens footer landing.footer.legal.* mis à jour vers /privacy dans les 15 locales.
- Extension Web Clipper rendue cross-browser :
  * background.js détecte chrome.sidePanel vs browser.sidebarAction
  * sidepanel.js ajoute un shim chrome<->browser pour Firefox
  * manifest.json inclut sidebar_action + browser_specific_settings.gecko
    + optional_host_permissions (Chrome les ignore)
- Script scripts/build-firefox.mjs pour produire un .xpi signé pour AMO,
  symétrique de build-chrome-store.mjs. Exclusion mutuelle des artefacts
  Chrome/Firefox pour éviter la cross-contamination.
- Branding corrigé : 176 occurrences de 'Momento' -> 'Memento' dans le
  bundle de traductions embarqué.
- Version bumpée 0.3.1 -> 0.4.0.
This commit is contained in:
Antigravity
2026-07-18 09:00:44 +00:00
parent 88a7d2ad0a
commit a7c15e9fc3
70 changed files with 5550 additions and 3464 deletions

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "مومنتو ويب كليبر"
},
"extDescription": {
"message": "التقط صفحات الويب والنص المميز في دفاتر ملاحظات Memento الخاصة بك - ويتصل بخادم Memento الخاص بك."
},
"extActionTitle": {
"message": "مقطع إلى مومنتو"
},
"webClipper": {
"message": "مقص الويب"
},
"connected": {
"message": "متصل"
},
"disconnected": {
"message": "غير متصل"
},
"instanceSettings": {
"message": "عنوان URL لمومنتو"
},
"instanceUrlLabel": {
"message": "عنوان URL لمثيل Memento الخاص بك"
},
"presetProduction": {
"message": "إعداد مسبق للإنتاج · memento-note.com"
},
"applyReconnect": {
"message": "تطبيق وإعادة الاتصال"
},
"openMomento": {
"message": "افتح مومنتو"
},
"settingsHint": {
"message": "الصق عنوان URL الخاص بـ HTTPS (أو LAN) لخادم Memento الخاص بك. تتعامل ملفات تعريف الارتباط الموجودة في هذا المتصفح مع تسجيل الدخول."
},
"footerVersion": {
"message": "Memento Web Clipper <<<الإصدار>>>"
},
"errPermissionDenied": {
"message": "لا يستطيع Memento الوصول إلى علامة التبويب هذه. تحقق من أذونات ملحق لوحة المفاتيح/الموقع — أو افتح اللوحة الجانبية."
},
"notebookUnnamed": {
"message": "دفتر بلا عنوان"
},
"noNotebooks": {
"message": "لا توجد دفاتر ملاحظات حتى الآن"
},
"readingTimeOne": {
"message": "~1 دقيقة قراءة"
},
"readingTimeOther": {
"message": "تقريبا. $COUNT$ دقيقة قراءة",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "تم الكشف عن التحديد"
},
"ignore": {
"message": "يتجاهل"
},
"selectionHint": {
"message": "نصيحة: قم بتمييز النص الموجود على الصفحة لقص التحديد الدقيق كملاحظة."
},
"clipSelection": {
"message": "اختيار المقطع"
},
"clipPage": {
"message": "قص هذه الصفحة"
},
"saveLinkOnly": {
"message": "حفظ الرابط فقط"
},
"pageNotAccessible": {
"message": "لا يمكن القص هنا — هذه الصفحة تحظر الوصول إلى الإضافات."
},
"errLoginRequired": {
"message": "يرجى تسجيل الدخول إلى Memento في هذا المتصفح أولاً."
},
"errLoadNotebooks": {
"message": "تعذر تحميل دفاتر الملاحظات. حاول إعادة الاتصال."
},
"notebooksLoaded": {
"message": "تم تحميل دفاتر الملاحظات"
},
"connecting": {
"message": "جارٍ الاتصال…"
},
"connectedToUrl": {
"message": "متصل بـ $URL$",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "صفحة مقيدة - قم بالقص عبر شريط أدوات Memento أو اللوحة الجانبية."
},
"destinationNotebook": {
"message": "دفتر الوجهة"
},
"activePage": {
"message": "صفحة نشطة"
},
"previewBeforeSave": {
"message": "المراجعة قبل الحفظ"
},
"noteTitleLabel": {
"message": "عنوان"
},
"excerptLabel": {
"message": "مقتطفات"
},
"saveToMomento": {
"message": "حفظ إلى مومنتو"
},
"back": {
"message": "خلف"
},
"analyzingSource": {
"message": "تحليل المصدر"
},
"statusAnalyzing": {
"message": "جارٍ التحليل…"
},
"statusSaving": {
"message": "توفير…"
},
"processingDetail": {
"message": "إنشاء العلامات والملخص الدلالي والتضمينات."
},
"noteSaved": {
"message": "تم حفظ الملاحظة"
},
"sentToNotebook": {
"message": "تم الحفظ في $NOTEBOOK$",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "عرض في مومنتو"
},
"clipAnother": {
"message": "قص صفحة أخرى"
},
"failure": {
"message": "لا يمكن إكماله"
},
"genericError": {
"message": "حدث خطأ ما أثناء الوصول إلى مثيل Memento."
},
"retry": {
"message": "أعد المحاولة"
},
"errNoSelection": {
"message": "حدد النص أولاً، أو قم بقص الصفحة بأكملها."
},
"errAnalyzeFailed": {
"message": "لا يمكن تحليل هذه الصفحة."
},
"errSaveFailed": {
"message": "لا يمكن حفظ ملاحظتك."
},
"errNetwork": {
"message": "مشكلة في الشبكة - تحقق من اتصالك وعنوان URL الخاص بـ Memento."
},
"bannerPickText": {
"message": "قم بتمييز النص الموجود على الصفحة، أو قم بقص الصفحة بأكملها."
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "Memento Web Clipper"
},
"extDescription": {
"message": "Erfassen Sie Webseiten und hervorgehobenen Text in Ihren Memento-Notizbüchern stellt eine Verbindung zu Ihrem eigenen Memento-Server her."
},
"extActionTitle": {
"message": "Clip auf Memento"
},
"webClipper": {
"message": "Web Clipper"
},
"connected": {
"message": "Verbunden"
},
"disconnected": {
"message": "Nicht verbunden"
},
"instanceSettings": {
"message": "Memento-URL"
},
"instanceUrlLabel": {
"message": "Ihre Memento-Instanz-URL"
},
"presetProduction": {
"message": "Produktionsvoreinstellung · memento-note.com"
},
"applyReconnect": {
"message": "Anwenden und erneut verbinden"
},
"openMomento": {
"message": "Öffnen Sie Memento"
},
"settingsHint": {
"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.0"
},
"errPermissionDenied": {
"message": "Memento kann nicht auf diese Registerkarte zugreifen. Überprüfen Sie die Tastatur-/Site-Erweiterungsberechtigungen oder öffnen Sie den Seitenbereich."
},
"notebookUnnamed": {
"message": "Notizbuch ohne Titel"
},
"noNotebooks": {
"message": "Noch keine Notizbücher"
},
"readingTimeOne": {
"message": "~1 Minute gelesen"
},
"readingTimeOther": {
"message": "Ca. $COUNT$ min. gelesen",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "Auswahl erkannt"
},
"ignore": {
"message": "ignorieren"
},
"selectionHint": {
"message": "Tipp: Markieren Sie Text auf der Seite, um eine präzise Auswahl als Notiz auszuschneiden."
},
"clipSelection": {
"message": "Clip-Auswahl"
},
"clipPage": {
"message": "Clip diese Seite aus"
},
"saveLinkOnly": {
"message": "Nur Link speichern"
},
"pageNotAccessible": {
"message": "Hier kann kein Clip erstellt werden diese Seite blockiert den Zugriff auf die Erweiterung."
},
"errLoginRequired": {
"message": "Bitte melden Sie sich zunächst in diesem Browser bei Memento an."
},
"errLoadNotebooks": {
"message": "Notebooks konnten nicht geladen werden. Versuchen Sie, die Verbindung wiederherzustellen."
},
"notebooksLoaded": {
"message": "Notizbücher geladen"
},
"connecting": {
"message": "Verbinden…"
},
"connectedToUrl": {
"message": "Verbunden mit $URL$",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "Eingeschränkte Seite Ausschneiden über die Memento-Symbolleiste oder den Seitenbereich."
},
"destinationNotebook": {
"message": "Zielnotizbuch"
},
"activePage": {
"message": "Aktive Seite"
},
"previewBeforeSave": {
"message": "Vor dem Speichern überprüfen"
},
"noteTitleLabel": {
"message": "Titel"
},
"excerptLabel": {
"message": "Auszug"
},
"saveToMomento": {
"message": "In Memento speichern"
},
"back": {
"message": "Zurück"
},
"analyzingSource": {
"message": "Quelle analysieren"
},
"statusAnalyzing": {
"message": "Analysieren…"
},
"statusSaving": {
"message": "Sparen…"
},
"processingDetail": {
"message": "Generieren von Tags, einer semantischen Zusammenfassung und Einbettungen."
},
"noteSaved": {
"message": "Notiz gespeichert"
},
"sentToNotebook": {
"message": "Gespeichert unter $NOTEBOOK$",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "In Memento ansehen"
},
"clipAnother": {
"message": "Schneiden Sie eine weitere Seite aus"
},
"failure": {
"message": "Konnte nicht abgeschlossen werden"
},
"genericError": {
"message": "Beim Erreichen Ihrer Memento-Instanz ist ein Fehler aufgetreten."
},
"retry": {
"message": "Wiederholen"
},
"errNoSelection": {
"message": "Wählen Sie zuerst den Text aus oder schneiden Sie die gesamte Seite aus."
},
"errAnalyzeFailed": {
"message": "Diese Seite konnte nicht analysiert werden."
},
"errSaveFailed": {
"message": "Ihre Notiz konnte nicht gespeichert werden."
},
"errNetwork": {
"message": "Netzwerkproblem überprüfen Sie Ihre Verbindung und Memento-URL."
},
"bannerPickText": {
"message": "Markieren Sie Text auf der Seite oder schneiden Sie die gesamte Seite aus."
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "Memento Web Clipper"
},
"extDescription": {
"message": "Capture web pages and highlighted text into your Memento notebooks — connects to your own Memento server."
},
"extActionTitle": {
"message": "Clip to Memento"
},
"webClipper": {
"message": "Web Clipper"
},
"connected": {
"message": "Connected"
},
"disconnected": {
"message": "Not connected"
},
"instanceSettings": {
"message": "Memento URL"
},
"instanceUrlLabel": {
"message": "Your Memento instance URL"
},
"presetProduction": {
"message": "Production preset · memento-note.com"
},
"applyReconnect": {
"message": "Apply and reconnect"
},
"openMomento": {
"message": "Open Memento"
},
"settingsHint": {
"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.0"
},
"errPermissionDenied": {
"message": "Memento can't access this tab. Check keyboard/site extension permissions — or open the Side Panel."
},
"notebookUnnamed": {
"message": "Untitled notebook"
},
"noNotebooks": {
"message": "No notebooks yet"
},
"readingTimeOne": {
"message": "~1 minute read"
},
"readingTimeOther": {
"message": "Approx. $COUNT$ min read",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "Selection detected"
},
"ignore": {
"message": "ignore"
},
"selectionHint": {
"message": "Tip: highlight text on the page to clip a precise selection as a note."
},
"clipSelection": {
"message": "Clip selection"
},
"clipPage": {
"message": "Clip this page"
},
"saveLinkOnly": {
"message": "Save link only"
},
"pageNotAccessible": {
"message": "Can't clip here — this page blocks extension access."
},
"errLoginRequired": {
"message": "Please sign in to Memento in this browser first."
},
"errLoadNotebooks": {
"message": "Could not load notebooks. Try reconnecting."
},
"notebooksLoaded": {
"message": "Notebooks loaded"
},
"connecting": {
"message": "Connecting…"
},
"connectedToUrl": {
"message": "Connected to $URL$",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "Restricted page — clip via the Memento toolbar or Side Panel."
},
"destinationNotebook": {
"message": "Destination notebook"
},
"activePage": {
"message": "Active page"
},
"previewBeforeSave": {
"message": "Review before saving"
},
"noteTitleLabel": {
"message": "Title"
},
"excerptLabel": {
"message": "Excerpt"
},
"saveToMomento": {
"message": "Save to Memento"
},
"back": {
"message": "Back"
},
"analyzingSource": {
"message": "Analyzing source"
},
"statusAnalyzing": {
"message": "Analyzing…"
},
"statusSaving": {
"message": "Saving…"
},
"processingDetail": {
"message": "Generating tags, a semantic summary, and embeddings."
},
"noteSaved": {
"message": "Note saved"
},
"sentToNotebook": {
"message": "Saved to $NOTEBOOK$",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "View in Memento"
},
"clipAnother": {
"message": "Clip another page"
},
"failure": {
"message": "Could not complete"
},
"genericError": {
"message": "Something went wrong reaching your Memento instance."
},
"retry": {
"message": "Retry"
},
"errNoSelection": {
"message": "Select text first, or clip the full page."
},
"errAnalyzeFailed": {
"message": "Could not analyze this page."
},
"errSaveFailed": {
"message": "Could not save your note."
},
"errNetwork": {
"message": "Network issue — check your connection and Memento URL."
},
"bannerPickText": {
"message": "Highlight text on the page, or clip the whole page."
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "Cortadora web Memento"
},
"extDescription": {
"message": "Capture páginas web y texto resaltado en sus cuadernos Memento: se conecta a su propio servidor Memento."
},
"extActionTitle": {
"message": "Clip al momento"
},
"webClipper": {
"message": "Cortadora web"
},
"connected": {
"message": "Conectado"
},
"disconnected": {
"message": "No conectado"
},
"instanceSettings": {
"message": "URL del momento"
},
"instanceUrlLabel": {
"message": "La URL de tu instancia de Memento"
},
"presetProduction": {
"message": "Preajuste de producción · memento-note.com"
},
"applyReconnect": {
"message": "Aplicar y reconectar"
},
"openMomento": {
"message": "Memento abierto"
},
"settingsHint": {
"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 <<<VERSIÓN>>>"
},
"errPermissionDenied": {
"message": "Memento no puede acceder a esta pestaña. Verifique los permisos de extensión del sitio/teclado o abra el Panel lateral."
},
"notebookUnnamed": {
"message": "Cuaderno sin título"
},
"noNotebooks": {
"message": "Aún no hay cuadernos"
},
"readingTimeOne": {
"message": "~1 minuto de lectura"
},
"readingTimeOther": {
"message": "Aprox. <<<CONTAR>>> lectura mínima (Approx. $COUNT$ min read)",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "Selección detectada"
},
"ignore": {
"message": "ignorar"
},
"selectionHint": {
"message": "Consejo: resalte el texto en la página para recortar una selección precisa como nota."
},
"clipSelection": {
"message": "Selección de clips"
},
"clipPage": {
"message": "Recortar esta página"
},
"saveLinkOnly": {
"message": "Guardar enlace solamente"
},
"pageNotAccessible": {
"message": "No se puede recortar aquí: esta página bloquea el acceso a la extensión."
},
"errLoginRequired": {
"message": "Primero inicie sesión en Memento en este navegador."
},
"errLoadNotebooks": {
"message": "No se pudieron cargar los cuadernos. Intente volver a conectarse."
},
"notebooksLoaded": {
"message": "Cuadernos cargados"
},
"connecting": {
"message": "Conectando…"
},
"connectedToUrl": {
"message": "Conectado a $URL$",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "Página restringida: recorte mediante la barra de herramientas de Memento o el panel lateral."
},
"destinationNotebook": {
"message": "Cuaderno de destino"
},
"activePage": {
"message": "Página activa"
},
"previewBeforeSave": {
"message": "Revisar antes de guardar"
},
"noteTitleLabel": {
"message": "Título"
},
"excerptLabel": {
"message": "Extracto"
},
"saveToMomento": {
"message": "Guardar en momento"
},
"back": {
"message": "Atrás"
},
"analyzingSource": {
"message": "Analizando fuente"
},
"statusAnalyzing": {
"message": "Analizando…"
},
"statusSaving": {
"message": "Ahorro…"
},
"processingDetail": {
"message": "Generación de etiquetas, resumen semántico e incrustaciones."
},
"noteSaved": {
"message": "Nota guardada"
},
"sentToNotebook": {
"message": "Guardado en $NOTEBOOK$",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "Ver en momento"
},
"clipAnother": {
"message": "Recortar otra página"
},
"failure": {
"message": "No se pudo completar"
},
"genericError": {
"message": "Algo salió mal al llegar a tu instancia de Memento."
},
"retry": {
"message": "Rever"
},
"errNoSelection": {
"message": "Seleccione el texto primero o recorte la página completa."
},
"errAnalyzeFailed": {
"message": "No se pudo analizar esta página."
},
"errSaveFailed": {
"message": "No se pudo guardar tu nota."
},
"errNetwork": {
"message": "Problema de red: verifique su conexión y la URL de Memento."
},
"bannerPickText": {
"message": "Resalte el texto de la página o recorte toda la página."
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "Memento Web Clipper"
},
"extDescription": {
"message": "صفحات وب و متن هایلایت شده را در نوت بوک های Memento خود ضبط کنید — به سرور Memento خودتان متصل می شود."
},
"extActionTitle": {
"message": "کلیپ به لحظه"
},
"webClipper": {
"message": "Web Clipper"
},
"connected": {
"message": "متصل شد"
},
"disconnected": {
"message": "متصل نیست"
},
"instanceSettings": {
"message": "آدرس لحظه ای"
},
"instanceUrlLabel": {
"message": "URL نمونه Memento شما"
},
"presetProduction": {
"message": "پیش تنظیم تولید · memento-note.com"
},
"applyReconnect": {
"message": "درخواست کنید و دوباره وصل شوید"
},
"openMomento": {
"message": "Memento را باز کنید"
},
"settingsHint": {
"message": "URL HTTPS (یا LAN) سرور Memento خود را جایگذاری کنید. کوکی‌های این مرورگر ورود به سیستم را کنترل می‌کنند."
},
"footerVersion": {
"message": "Memento Web Clipper 0.4.0"
},
"errPermissionDenied": {
"message": "Memento نمی تواند به این برگه دسترسی پیدا کند. مجوزهای افزونه صفحه کلید/سایت را بررسی کنید - یا پانل جانبی را باز کنید."
},
"notebookUnnamed": {
"message": "دفترچه بدون عنوان"
},
"noNotebooks": {
"message": "هنوز نوت بوک نیست"
},
"readingTimeOne": {
"message": "~ 1 دقیقه مطالعه کنید"
},
"readingTimeOther": {
"message": "تقریبا $COUNT$ دقیقه خواندن",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "انتخاب شناسایی شد"
},
"ignore": {
"message": "نادیده گرفتن"
},
"selectionHint": {
"message": "نکته: متن را در صفحه برجسته کنید تا یک انتخاب دقیق به عنوان یادداشت بریده شود."
},
"clipSelection": {
"message": "انتخاب کلیپ"
},
"clipPage": {
"message": "این صفحه را کلیپ کنید"
},
"saveLinkOnly": {
"message": "فقط لینک را ذخیره کنید"
},
"pageNotAccessible": {
"message": "در اینجا نمی توان کلیپ کرد - این صفحه دسترسی برنامه های افزودنی را مسدود می کند."
},
"errLoginRequired": {
"message": "لطفاً ابتدا با این مرورگر وارد Memento شوید."
},
"errLoadNotebooks": {
"message": "نوت‌بوک‌ها بارگیری نشد. سعی کنید دوباره وصل شوید."
},
"notebooksLoaded": {
"message": "نوت بوک ها بارگیری شدند"
},
"connecting": {
"message": "در حال اتصال…"
},
"connectedToUrl": {
"message": "به $URL$ متصل شد",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "صفحه محدود - از طریق نوار ابزار Memento یا پانل جانبی کلیپ کنید."
},
"destinationNotebook": {
"message": "دفترچه یادداشت مقصد"
},
"activePage": {
"message": "صفحه فعال"
},
"previewBeforeSave": {
"message": "قبل از ذخیره بررسی کنید"
},
"noteTitleLabel": {
"message": "عنوان"
},
"excerptLabel": {
"message": "گزیده"
},
"saveToMomento": {
"message": "ذخیره در Memento"
},
"back": {
"message": "برگشت"
},
"analyzingSource": {
"message": "تجزیه و تحلیل منبع"
},
"statusAnalyzing": {
"message": "در حال تجزیه و تحلیل…"
},
"statusSaving": {
"message": "در حال ذخیره…"
},
"processingDetail": {
"message": "تولید برچسب ها، خلاصه معنایی، و جاسازی ها."
},
"noteSaved": {
"message": "یادداشت ذخیره شد"
},
"sentToNotebook": {
"message": "در $NOTEBOOK$ ذخیره شد",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "مشاهده در Memento"
},
"clipAnother": {
"message": "یک صفحه دیگر را کلیپ کنید"
},
"failure": {
"message": "تکمیل نشد"
},
"genericError": {
"message": "هنگام رسیدن به نمونه Memento شما مشکلی پیش آمد."
},
"retry": {
"message": "دوباره امتحان کنید"
},
"errNoSelection": {
"message": "ابتدا متن را انتخاب کنید، یا صفحه کامل را کلیپ کنید."
},
"errAnalyzeFailed": {
"message": "نمی توان این صفحه را تجزیه و تحلیل کرد."
},
"errSaveFailed": {
"message": "یادداشت شما ذخیره نشد."
},
"errNetwork": {
"message": "مشکل شبکه - اتصال و URL Memento خود را بررسی کنید."
},
"bannerPickText": {
"message": "متن را در صفحه برجسته کنید یا کل صفحه را برش دهید."
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "Memento · Web Clipper"
},
"extDescription": {
"message": "Enregistrez des pages web et du texte surligné dans vos carnets Memento — connecté à votre propre serveur Memento."
},
"extActionTitle": {
"message": "Clipper vers Memento"
},
"webClipper": {
"message": "Tondeuse Web"
},
"connected": {
"message": "Connecté"
},
"disconnected": {
"message": "Non connecté"
},
"instanceSettings": {
"message": "Adresse Memento"
},
"instanceUrlLabel": {
"message": "URL de votre instance Memento"
},
"presetProduction": {
"message": "Préréglage production · memento-note.com"
},
"applyReconnect": {
"message": "Appliquer et reconnecter"
},
"openMomento": {
"message": "Ouvrir Memento"
},
"settingsHint": {
"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.0"
},
"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"
},
"noNotebooks": {
"message": "Pas encore de carnets"
},
"readingTimeOne": {
"message": "≈ 1 minute de lecture"
},
"readingTimeOther": {
"message": "≈ $COUNT$ minutes de lecture",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "Sélection détectée"
},
"ignore": {
"message": "ignorer"
},
"selectionHint": {
"message": "Astuce : surlignez du texte à lécran pour clipper une sélection précise de la page en tant que note."
},
"clipSelection": {
"message": "Clipper la sélection"
},
"clipPage": {
"message": "Clipper cette page"
},
"saveLinkOnly": {
"message": "Enregistrer le lien uniquement"
},
"pageNotAccessible": {
"message": "Impossible de découper ici : cette page bloque l'accès aux extensions."
},
"errLoginRequired": {
"message": "Veuillez d'abord vous connecter à Memento dans ce navigateur."
},
"errLoadNotebooks": {
"message": "Impossible de charger les blocs-notes. Essayez de vous reconnecter."
},
"notebooksLoaded": {
"message": "Carnets chargés"
},
"connecting": {
"message": "Connexion…"
},
"connectedToUrl": {
"message": "Connecté à $URL$",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "Page restreinte : clip via la barre d'outils Memento ou le panneau latéral."
},
"destinationNotebook": {
"message": "Carnet de destination"
},
"activePage": {
"message": "Page active"
},
"previewBeforeSave": {
"message": "Vérifier avant d'enregistrer"
},
"noteTitleLabel": {
"message": "Titre"
},
"excerptLabel": {
"message": "Extrait"
},
"saveToMomento": {
"message": "Enregistrer dans Memento"
},
"back": {
"message": "Dos"
},
"analyzingSource": {
"message": "Analyse de la source"
},
"statusAnalyzing": {
"message": "Analyser…"
},
"statusSaving": {
"message": "Économie…"
},
"processingDetail": {
"message": "Génération automatique des tags, résumé sémantique et calcul des embeddings."
},
"noteSaved": {
"message": "Remarque enregistrée"
},
"sentToNotebook": {
"message": "Enregistré dans $NOTEBOOK$",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "Voir dans Memento"
},
"clipAnother": {
"message": "Clipper une autre page"
},
"failure": {
"message": "Impossible de terminer"
},
"genericError": {
"message": "Une erreur est survenue lors de la communication avec votre instance Memento."
},
"retry": {
"message": "Réessayer"
},
"errNoSelection": {
"message": "Sélectionnez dabord le texte ou coupez la page entière."
},
"errAnalyzeFailed": {
"message": "Impossible d'analyser cette page."
},
"errSaveFailed": {
"message": "Impossible d'enregistrer votre note."
},
"errNetwork": {
"message": "Problème de réseau : vérifiez votre connexion et l'URL Memento."
},
"bannerPickText": {
"message": "Mettez en surbrillance le texte sur la page ou coupez la page entière."
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "मोमेंटो वेब क्लिपर"
},
"extDescription": {
"message": "अपने मोमेंटो नोटबुक में वेब पेज और हाइलाइट किए गए टेक्स्ट को कैप्चर करें - यह आपके अपने मोमेंटो सर्वर से जुड़ता है।"
},
"extActionTitle": {
"message": "मोमेंटो पर क्लिप करें"
},
"webClipper": {
"message": "वेब क्लिपर"
},
"connected": {
"message": "जुड़े हुए"
},
"disconnected": {
"message": "जुड़े नहीं हैं"
},
"instanceSettings": {
"message": "मोमेंटो यूआरएल"
},
"instanceUrlLabel": {
"message": "आपका मोमेंटो इंस्टेंस यूआरएल"
},
"presetProduction": {
"message": "प्रोडक्शन प्रीसेट · memento-note.com"
},
"applyReconnect": {
"message": "आवेदन करें और पुनः कनेक्ट करें"
},
"openMomento": {
"message": "मोमेंटो खोलें"
},
"settingsHint": {
"message": "अपने मोमेंटो सर्वर का HTTPS (या LAN) URL चिपकाएँ। इस ब्राउज़र में कुकीज़ साइन-इन को संभालती हैं।"
},
"footerVersion": {
"message": "मोमेंटो वेब क्लिपर <<<संस्करण>>>"
},
"errPermissionDenied": {
"message": "मोमेंटो इस टैब तक नहीं पहुंच सकता. कीबोर्ड/साइट एक्सटेंशन अनुमतियां जांचें - या साइड पैनल खोलें।"
},
"notebookUnnamed": {
"message": "शीर्षक रहित नोटबुक"
},
"noNotebooks": {
"message": "अभी तक कोई नोटबुक नहीं"
},
"readingTimeOne": {
"message": "~1 मिनट पढ़ें"
},
"readingTimeOther": {
"message": "लगभग। $COUNT$ मिनट पढ़ा",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "चयन का पता चला"
},
"ignore": {
"message": "अनदेखा करना"
},
"selectionHint": {
"message": "युक्ति: किसी सटीक चयन को नोट के रूप में क्लिप करने के लिए पृष्ठ पर टेक्स्ट को हाइलाइट करें।"
},
"clipSelection": {
"message": "क्लिप चयन"
},
"clipPage": {
"message": "इस पृष्ठ को क्लिप करें"
},
"saveLinkOnly": {
"message": "केवल लिंक सहेजें"
},
"pageNotAccessible": {
"message": "यहां क्लिप नहीं किया जा सकता - यह पेज एक्सटेंशन एक्सेस को ब्लॉक करता है।"
},
"errLoginRequired": {
"message": "कृपया पहले इस ब्राउज़र में मोमेंटो में साइन इन करें।"
},
"errLoadNotebooks": {
"message": "नोटबुक लोड नहीं हो सकीं. पुनः कनेक्ट करने का प्रयास करें."
},
"notebooksLoaded": {
"message": "नोटबुक लोड किए गए"
},
"connecting": {
"message": "कनेक्ट हो रहा है..."
},
"connectedToUrl": {
"message": "$URL$ से कनेक्ट किया गया",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "प्रतिबंधित पृष्ठ - मोमेंटो टूलबार या साइड पैनल के माध्यम से क्लिप करें।"
},
"destinationNotebook": {
"message": "गंतव्य नोटबुक"
},
"activePage": {
"message": "सक्रिय पृष्ठ"
},
"previewBeforeSave": {
"message": "सहेजने से पहले समीक्षा करें"
},
"noteTitleLabel": {
"message": "शीर्षक"
},
"excerptLabel": {
"message": "अंश"
},
"saveToMomento": {
"message": "मोमेंटो में सहेजें"
},
"back": {
"message": "पीछे"
},
"analyzingSource": {
"message": "स्रोत का विश्लेषण"
},
"statusAnalyzing": {
"message": "विश्लेषण कर रहा हूँ..."
},
"statusSaving": {
"message": "सहेजा जा रहा है..."
},
"processingDetail": {
"message": "टैग, सिमेंटिक सारांश और एम्बेडिंग तैयार करना।"
},
"noteSaved": {
"message": "नोट सहेजा गया"
},
"sentToNotebook": {
"message": "$NOTEBOOK$ में सहेजा गया",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "मोमेंटो में देखें"
},
"clipAnother": {
"message": "दूसरे पेज को क्लिप करें"
},
"failure": {
"message": "पूरा नहीं हो सका"
},
"genericError": {
"message": "आपके मोमेंटो इंस्टेंस तक पहुँचने में कुछ गड़बड़ी हुई।"
},
"retry": {
"message": "पुन: प्रयास करें"
},
"errNoSelection": {
"message": "पहले टेक्स्ट चुनें, या पूरा पेज क्लिप करें।"
},
"errAnalyzeFailed": {
"message": "इस पृष्ठ का विश्लेषण नहीं किया जा सका."
},
"errSaveFailed": {
"message": "आपका नोट सहेजा नहीं जा सका."
},
"errNetwork": {
"message": "नेटवर्क समस्या - अपना कनेक्शन और मोमेंटो यूआरएल जांचें।"
},
"bannerPickText": {
"message": "पृष्ठ पर टेक्स्ट को हाइलाइट करें, या पूरे पृष्ठ को क्लिप करें।"
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "Memento Web Clipper"
},
"extDescription": {
"message": "Cattura pagine web e testo evidenziato nei tuoi taccuini Memento: si connette al tuo server Memento."
},
"extActionTitle": {
"message": "Clip su Memento"
},
"webClipper": {
"message": "Tagliatore di fotoricettore"
},
"connected": {
"message": "Collegato"
},
"disconnected": {
"message": "Non connesso"
},
"instanceSettings": {
"message": "URL del momento"
},
"instanceUrlLabel": {
"message": "L'URL dell'istanza di Memento"
},
"presetProduction": {
"message": "Preimpostazione di produzione · memento-note.com"
},
"applyReconnect": {
"message": "Applicare e riconnettersi"
},
"openMomento": {
"message": "Memento aperto"
},
"settingsHint": {
"message": "Incolla l'URL HTTPS (o LAN) del tuo server Memento. I cookie in questo browser gestiscono l'accesso."
},
"footerVersion": {
"message": "Memento Web Clipper <<<VERSIONE>>>"
},
"errPermissionDenied": {
"message": "Memento non può accedere a questa scheda. Controlla le autorizzazioni per tastiera/estensione del sito oppure apri il pannello laterale."
},
"notebookUnnamed": {
"message": "Taccuino senza titolo"
},
"noNotebooks": {
"message": "Nessun taccuino ancora"
},
"readingTimeOne": {
"message": "~1 minuto di lettura"
},
"readingTimeOther": {
"message": "ca. $COUNT$ min letto",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "Selezione rilevata"
},
"ignore": {
"message": "ignorare"
},
"selectionHint": {
"message": "Suggerimento: evidenzia il testo sulla pagina per ritagliare una selezione precisa come nota."
},
"clipSelection": {
"message": "Selezione clip"
},
"clipPage": {
"message": "Ritaglia questa pagina"
},
"saveLinkOnly": {
"message": "Salva solo il collegamento"
},
"pageNotAccessible": {
"message": "Impossibile ritagliare qui: questa pagina blocca l'accesso all'estensione."
},
"errLoginRequired": {
"message": "Accedi prima a Memento in questo browser."
},
"errLoadNotebooks": {
"message": "Impossibile caricare i taccuini. Prova a riconnetterti."
},
"notebooksLoaded": {
"message": "Taccuini caricati"
},
"connecting": {
"message": "Connessione…"
},
"connectedToUrl": {
"message": "Connesso a $URL$",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "Pagina limitata: ritaglia tramite la barra degli strumenti Memento o il pannello laterale."
},
"destinationNotebook": {
"message": "Taccuino di destinazione"
},
"activePage": {
"message": "Pagina attiva"
},
"previewBeforeSave": {
"message": "Rivedi prima di salvare"
},
"noteTitleLabel": {
"message": "Titolo"
},
"excerptLabel": {
"message": "Estratto"
},
"saveToMomento": {
"message": "Salva su Memento"
},
"back": {
"message": "Indietro"
},
"analyzingSource": {
"message": "Analisi della fonte"
},
"statusAnalyzing": {
"message": "Analizzando..."
},
"statusSaving": {
"message": "Risparmio…"
},
"processingDetail": {
"message": "Generazione di tag, riepilogo semantico e incorporamenti."
},
"noteSaved": {
"message": "Nota salvata"
},
"sentToNotebook": {
"message": "Salvato in $NOTEBOOK$",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "Visualizza in Memento"
},
"clipAnother": {
"message": "Ritaglia un'altra pagina"
},
"failure": {
"message": "Impossibile completare"
},
"genericError": {
"message": "Qualcosa è andato storto nel raggiungere la tua istanza Memento."
},
"retry": {
"message": "Riprova"
},
"errNoSelection": {
"message": "Seleziona prima il testo o ritaglia l'intera pagina."
},
"errAnalyzeFailed": {
"message": "Impossibile analizzare questa pagina."
},
"errSaveFailed": {
"message": "Impossibile salvare la nota."
},
"errNetwork": {
"message": "Problema di rete: controlla la connessione e l'URL Memento."
},
"bannerPickText": {
"message": "Evidenzia il testo sulla pagina o ritaglia l'intera pagina."
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "モーメントウェブクリッパー"
},
"extDescription": {
"message": "Web ページとハイライトされたテキストを Memento ノートブックにキャプチャします。独自の Memento サーバーに接続します。"
},
"extActionTitle": {
"message": "モーメントにクリップ"
},
"webClipper": {
"message": "ウェブクリッパー"
},
"connected": {
"message": "接続済み"
},
"disconnected": {
"message": "接続されていません"
},
"instanceSettings": {
"message": "モーメントのURL"
},
"instanceUrlLabel": {
"message": "Memento インスタンスの URL"
},
"presetProduction": {
"message": "プロダクションプリセット・memento-note.com"
},
"applyReconnect": {
"message": "適用して再接続する"
},
"openMomento": {
"message": "モーメントを開く"
},
"settingsHint": {
"message": "Memento サーバーの HTTPS (または LAN) URL を貼り付けます。このブラウザの Cookie がサインインを処理します。"
},
"footerVersion": {
"message": "Memento Web クリッパー <<<バージョン>>>"
},
"errPermissionDenied": {
"message": "Memento はこのタブにアクセスできません。キーボード/サイト拡張機能の権限を確認するか、サイド パネルを開きます。"
},
"notebookUnnamed": {
"message": "無題のノート"
},
"noNotebooks": {
"message": "まだノートはありません"
},
"readingTimeOne": {
"message": "約 1 分で読めます"
},
"readingTimeOther": {
"message": "約$COUNT$ 分読み取り",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "選択が検出されました"
},
"ignore": {
"message": "無視する"
},
"selectionHint": {
"message": "ヒント: ページ上のテキストをハイライト表示して、正確な選択範囲をメモとしてクリップします。"
},
"clipSelection": {
"message": "クリップの選択"
},
"clipPage": {
"message": "このページをクリップします"
},
"saveLinkOnly": {
"message": "リンクのみを保存"
},
"pageNotAccessible": {
"message": "ここではクリップできません — このページは拡張機能へのアクセスをブロックしています。"
},
"errLoginRequired": {
"message": "まずこのブラウザで Memento にサインインしてください。"
},
"errLoadNotebooks": {
"message": "ノートブックをロードできませんでした。再接続してみてください。"
},
"notebooksLoaded": {
"message": "ノートブックがロードされました"
},
"connecting": {
"message": "接続中…"
},
"connectedToUrl": {
"message": "$URL$ に接続しました",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "制限されたページ — Memento ツールバーまたはサイド パネルを介してクリップします。"
},
"destinationNotebook": {
"message": "宛先ノートブック"
},
"activePage": {
"message": "アクティブなページ"
},
"previewBeforeSave": {
"message": "保存する前に確認してください"
},
"noteTitleLabel": {
"message": "タイトル"
},
"excerptLabel": {
"message": "抜粋"
},
"saveToMomento": {
"message": "モーメントに保存"
},
"back": {
"message": "戻る"
},
"analyzingSource": {
"message": "ソースを分析中"
},
"statusAnalyzing": {
"message": "分析中…"
},
"statusSaving": {
"message": "保存中…"
},
"processingDetail": {
"message": "タグ、意味の概要、および埋め込みを生成します。"
},
"noteSaved": {
"message": "メモが保存されました"
},
"sentToNotebook": {
"message": "$NOTEBOOK$ に保存されました",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "モメントで見る"
},
"clipAnother": {
"message": "別のページをクリップする"
},
"failure": {
"message": "完了できませんでした"
},
"genericError": {
"message": "Memento インスタンスに到達する際に問題が発生しました。"
},
"retry": {
"message": "リトライ"
},
"errNoSelection": {
"message": "最初にテキストを選択するか、ページ全体をクリップします。"
},
"errAnalyzeFailed": {
"message": "このページを分析できませんでした。"
},
"errSaveFailed": {
"message": "メモを保存できませんでした。"
},
"errNetwork": {
"message": "ネットワークの問題 — 接続と Memento URL を確認してください。"
},
"bannerPickText": {
"message": "ページ上のテキストを強調表示するか、ページ全体をクリップします。"
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "모멘토 웹 클리퍼"
},
"extDescription": {
"message": "웹 페이지와 강조 표시된 텍스트를 Memento 노트북에 캡처하여 자체 Memento 서버에 연결합니다."
},
"extActionTitle": {
"message": "순간에 클립"
},
"webClipper": {
"message": "웹 클리퍼"
},
"connected": {
"message": "연결됨"
},
"disconnected": {
"message": "연결되지 않음"
},
"instanceSettings": {
"message": "모멘토 URL"
},
"instanceUrlLabel": {
"message": "귀하의 Memento 인스턴스 URL"
},
"presetProduction": {
"message": "프로덕션 프리셋 · memento-note.com"
},
"applyReconnect": {
"message": "적용하고 다시 연결하세요"
},
"openMomento": {
"message": "모멘토 열기"
},
"settingsHint": {
"message": "Memento 서버의 HTTPS(또는 LAN) URL을 붙여넣습니다. 이 브라우저의 쿠키는 로그인을 처리합니다."
},
"footerVersion": {
"message": "Memento Web Clipper <<<버전>>>"
},
"errPermissionDenied": {
"message": "Memento는 이 탭에 접근할 수 없습니다. 키보드/사이트 확장 권한을 확인하거나 측면 패널을 엽니다."
},
"notebookUnnamed": {
"message": "제목 없는 노트"
},
"noNotebooks": {
"message": "아직 노트가 없습니다."
},
"readingTimeOne": {
"message": "~1분 읽기"
},
"readingTimeOther": {
"message": "대략. $COUNT$분 읽음",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "선택 항목이 감지되었습니다."
},
"ignore": {
"message": "무시하다"
},
"selectionHint": {
"message": "팁: 페이지의 텍스트를 강조 표시하여 정확한 선택 항목을 메모로 자릅니다."
},
"clipSelection": {
"message": "클립 선택"
},
"clipPage": {
"message": "이 페이지 클립"
},
"saveLinkOnly": {
"message": "링크만 저장"
},
"pageNotAccessible": {
"message": "여기서 클립할 수 없습니다. 이 페이지는 확장 프로그램 액세스를 차단합니다."
},
"errLoginRequired": {
"message": "먼저 이 브라우저에서 Memento에 로그인하세요."
},
"errLoadNotebooks": {
"message": "노트북을 로드할 수 없습니다. 다시 연결해 보세요."
},
"notebooksLoaded": {
"message": "노트북이 로드되었습니다."
},
"connecting": {
"message": "연결 중…"
},
"connectedToUrl": {
"message": "$URL$에 연결됨",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "제한된 페이지 — Memento 도구 모음 또는 측면 패널을 통해 클립합니다."
},
"destinationNotebook": {
"message": "대상 노트북"
},
"activePage": {
"message": "활성 페이지"
},
"previewBeforeSave": {
"message": "저장하기 전에 검토하세요"
},
"noteTitleLabel": {
"message": "제목"
},
"excerptLabel": {
"message": "발췌"
},
"saveToMomento": {
"message": "모멘토에 저장"
},
"back": {
"message": "뒤쪽에"
},
"analyzingSource": {
"message": "소스 분석 중"
},
"statusAnalyzing": {
"message": "분석 중…"
},
"statusSaving": {
"message": "절약…"
},
"processingDetail": {
"message": "태그, 의미 요약 및 임베딩을 생성합니다."
},
"noteSaved": {
"message": "메모가 저장되었습니다."
},
"sentToNotebook": {
"message": "$NOTEBOOK$에 저장됨",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "Memento에서 보기"
},
"clipAnother": {
"message": "다른 페이지 자르기"
},
"failure": {
"message": "완료할 수 없습니다."
},
"genericError": {
"message": "Memento 인스턴스에 연결하는 데 문제가 발생했습니다."
},
"retry": {
"message": "다시 해 보다"
},
"errNoSelection": {
"message": "먼저 텍스트를 선택하거나 전체 페이지를 자릅니다."
},
"errAnalyzeFailed": {
"message": "이 페이지를 분석할 수 없습니다."
},
"errSaveFailed": {
"message": "메모를 저장할 수 없습니다."
},
"errNetwork": {
"message": "네트워크 문제 - 연결 및 Memento URL을 확인하세요."
},
"bannerPickText": {
"message": "페이지의 텍스트를 강조 표시하거나 전체 페이지를 자릅니다."
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "Memento Webclipper"
},
"extDescription": {
"message": "Leg webpagina's en gemarkeerde tekst vast in uw Memento-notebooks - maakt verbinding met uw eigen Memento-server."
},
"extActionTitle": {
"message": "Clip naar Memento"
},
"webClipper": {
"message": "Webclipper"
},
"connected": {
"message": "Aangesloten"
},
"disconnected": {
"message": "Niet verbonden"
},
"instanceSettings": {
"message": "Memento-URL"
},
"instanceUrlLabel": {
"message": "Uw Memento-instantie-URL"
},
"presetProduction": {
"message": "Productievoorinstelling · memento-note.com"
},
"applyReconnect": {
"message": "Toepassen en opnieuw verbinden"
},
"openMomento": {
"message": "Memento openen"
},
"settingsHint": {
"message": "Plak de HTTPS (of LAN) URL van uw Memento-server. Cookies in deze browser zorgen voor het inloggen."
},
"footerVersion": {
"message": "Memento Web Clipper <<<VERSIE>>>"
},
"errPermissionDenied": {
"message": "Memento heeft geen toegang tot dit tabblad. Controleer de rechten voor toetsenbord-/site-extensies — of open het zijpaneel."
},
"notebookUnnamed": {
"message": "Naamloos notitieboekje"
},
"noNotebooks": {
"message": "Nog geen notitieboekjes"
},
"readingTimeOne": {
"message": "~1 minuut lezen"
},
"readingTimeOther": {
"message": "Ongeveer. $COUNT$ min gelezen",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "Selectie gedetecteerd"
},
"ignore": {
"message": "negeren"
},
"selectionHint": {
"message": "Tip: markeer tekst op de pagina om een precieze selectie als notitie te knippen."
},
"clipSelection": {
"message": "Clipselectie"
},
"clipPage": {
"message": "Knip deze pagina uit"
},
"saveLinkOnly": {
"message": "Alleen link opslaan"
},
"pageNotAccessible": {
"message": "Kan hier niet knippen: deze pagina blokkeert de toegang tot extensies."
},
"errLoginRequired": {
"message": "Meld u eerst aan bij Memento in deze browser."
},
"errLoadNotebooks": {
"message": "Kan notitieboekjes niet laden. Probeer opnieuw verbinding te maken."
},
"notebooksLoaded": {
"message": "Notitieboekjes geladen"
},
"connecting": {
"message": "Verbinden…"
},
"connectedToUrl": {
"message": "Verbonden met $URL$",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "Beperkte pagina — clip via de Memento-werkbalk of het zijpaneel."
},
"destinationNotebook": {
"message": "Bestemmingsnotitieboekje"
},
"activePage": {
"message": "Actieve pagina"
},
"previewBeforeSave": {
"message": "Controleer voordat u opslaat"
},
"noteTitleLabel": {
"message": "Titel"
},
"excerptLabel": {
"message": "Uittreksel"
},
"saveToMomento": {
"message": "Opslaan in Memento"
},
"back": {
"message": "Rug"
},
"analyzingSource": {
"message": "Bron analyseren"
},
"statusAnalyzing": {
"message": "Analyseren…"
},
"statusSaving": {
"message": "Besparing…"
},
"processingDetail": {
"message": "Tags, een semantische samenvatting en insluitingen genereren."
},
"noteSaved": {
"message": "Notitie opgeslagen"
},
"sentToNotebook": {
"message": "Opgeslagen in $NOTEBOOK$",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "Bekijk in Memento"
},
"clipAnother": {
"message": "Knip nog een pagina uit"
},
"failure": {
"message": "Kon niet voltooien"
},
"genericError": {
"message": "Er is iets misgegaan bij het bereiken van uw Memento-instantie."
},
"retry": {
"message": "Opnieuw proberen"
},
"errNoSelection": {
"message": "Selecteer eerst tekst of knip de volledige pagina uit."
},
"errAnalyzeFailed": {
"message": "Kan deze pagina niet analyseren."
},
"errSaveFailed": {
"message": "Kan uw notitie niet opslaan."
},
"errNetwork": {
"message": "Netwerkprobleem: controleer uw verbinding en Memento-URL."
},
"bannerPickText": {
"message": "Markeer tekst op de pagina of knip de hele pagina uit."
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "Narzędzie do strzyżenia sieci Memento"
},
"extDescription": {
"message": "Przechwytuj strony internetowe i zaznaczony tekst do swoich notatników Memento — łączy się z Twoim własnym serwerem Memento."
},
"extActionTitle": {
"message": "Klip do Memento"
},
"webClipper": {
"message": "Obcinacz sieci"
},
"connected": {
"message": "Połączony"
},
"disconnected": {
"message": "Nie podłączony"
},
"instanceSettings": {
"message": "Adres URL chwili"
},
"instanceUrlLabel": {
"message": "Adres URL Twojej instancji Memento"
},
"presetProduction": {
"message": "Wstępne ustawienia produkcyjne · memento-note.com"
},
"applyReconnect": {
"message": "Zastosuj i połącz ponownie"
},
"openMomento": {
"message": "Otwórz Memento"
},
"settingsHint": {
"message": "Wklej adres URL HTTPS (lub LAN) swojego serwera Memento. Pliki cookie w tej przeglądarce obsługują logowanie."
},
"footerVersion": {
"message": "Memento Web Clipper <<<WERSJA>>>"
},
"errPermissionDenied": {
"message": "Memento nie ma dostępu do tej karty. Sprawdź uprawnienia rozszerzenia klawiatury/witryny — lub otwórz Panel boczny."
},
"notebookUnnamed": {
"message": "Notatnik bez tytułu"
},
"noNotebooks": {
"message": "Nie ma jeszcze żadnych notatników"
},
"readingTimeOne": {
"message": "~1 minuta czytania"
},
"readingTimeOther": {
"message": "Około. $COUNT$ min odczytu",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "Wykryto wybór"
},
"ignore": {
"message": "ignorować"
},
"selectionHint": {
"message": "Wskazówka: zaznacz tekst na stronie, aby wyciąć dokładne zaznaczenie jako notatkę."
},
"clipSelection": {
"message": "Wybór klipu"
},
"clipPage": {
"message": "Przytnij tę stronę"
},
"saveLinkOnly": {
"message": "Zapisz tylko link"
},
"pageNotAccessible": {
"message": "Nie można tutaj przyciąć — ta strona blokuje dostęp do rozszerzenia."
},
"errLoginRequired": {
"message": "Najpierw zaloguj się do Memento w tej przeglądarce."
},
"errLoadNotebooks": {
"message": "Nie udało się załadować notatników. Spróbuj połączyć się ponownie."
},
"notebooksLoaded": {
"message": "Notatniki załadowane"
},
"connecting": {
"message": "Złączony…"
},
"connectedToUrl": {
"message": "Połączono z $URL$",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "Strona z ograniczeniami — klip za pomocą paska narzędzi Memento lub panelu bocznego."
},
"destinationNotebook": {
"message": "Notatnik docelowy"
},
"activePage": {
"message": "Aktywna strona"
},
"previewBeforeSave": {
"message": "Przejrzyj przed zapisaniem"
},
"noteTitleLabel": {
"message": "Tytuł"
},
"excerptLabel": {
"message": "Fragment"
},
"saveToMomento": {
"message": "Zapisz w Memento"
},
"back": {
"message": "Z powrotem"
},
"analyzingSource": {
"message": "Analizowanie źródła"
},
"statusAnalyzing": {
"message": "Analizuję…"
},
"statusSaving": {
"message": "Oszczędność…"
},
"processingDetail": {
"message": "Generowanie tagów, podsumowań semantycznych i osadzania."
},
"noteSaved": {
"message": "Uwaga zapisana"
},
"sentToNotebook": {
"message": "Zapisano w $NOTEBOOK$",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "Zobacz w Memento"
},
"clipAnother": {
"message": "Wytnij kolejną stronę"
},
"failure": {
"message": "Nie udało się ukończyć"
},
"genericError": {
"message": "Coś poszło nie tak podczas docierania do Twojej instancji Memento."
},
"retry": {
"message": "Spróbować ponownie"
},
"errNoSelection": {
"message": "Najpierw zaznacz tekst lub wytnij całą stronę."
},
"errAnalyzeFailed": {
"message": "Nie można przeanalizować tej strony."
},
"errSaveFailed": {
"message": "Nie można zapisać notatki."
},
"errNetwork": {
"message": "Problem z siecią — sprawdź swoje połączenie i adres URL Memento."
},
"bannerPickText": {
"message": "Zaznacz tekst na stronie lub przytnij całą stronę."
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "Memento Web Clipper"
},
"extDescription": {
"message": "Capture páginas da web e texto destacado em seus blocos de anotações Memento conecte-se ao seu próprio servidor Memento."
},
"extActionTitle": {
"message": "Clipe para Memento"
},
"webClipper": {
"message": "Clipper da Web"
},
"connected": {
"message": "Conectado"
},
"disconnected": {
"message": "Não conectado"
},
"instanceSettings": {
"message": "URL do momento"
},
"instanceUrlLabel": {
"message": "URL da sua instância do Memento"
},
"presetProduction": {
"message": "Predefinição de produção · memento-note.com"
},
"applyReconnect": {
"message": "Aplicar e reconectar"
},
"openMomento": {
"message": "Memento aberto"
},
"settingsHint": {
"message": "Cole o URL HTTPS (ou LAN) do seu servidor Memento. Os cookies neste navegador controlam o login."
},
"footerVersion": {
"message": "Memento Web Clipper <<<VERSÃO>>>"
},
"errPermissionDenied": {
"message": "Memento não pode acessar esta guia. Verifique as permissões de extensão de teclado/site ou abra o painel lateral."
},
"notebookUnnamed": {
"message": "Caderno sem título"
},
"noNotebooks": {
"message": "Ainda não há cadernos"
},
"readingTimeOne": {
"message": "~1 minuto de leitura"
},
"readingTimeOther": {
"message": "Aprox. $COUNT$ minutos de leitura",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "Seleção detectada"
},
"ignore": {
"message": "ignorar"
},
"selectionHint": {
"message": "Dica: destaque o texto na página para recortar uma seleção precisa como uma nota."
},
"clipSelection": {
"message": "Seleção de clipe"
},
"clipPage": {
"message": "Recorte esta página"
},
"saveLinkOnly": {
"message": "Salvar apenas link"
},
"pageNotAccessible": {
"message": "Não é possível recortar aqui esta página bloqueia o acesso à extensão."
},
"errLoginRequired": {
"message": "Faça login no Memento neste navegador primeiro."
},
"errLoadNotebooks": {
"message": "Não foi possível carregar os notebooks. Tente reconectar."
},
"notebooksLoaded": {
"message": "Cadernos carregados"
},
"connecting": {
"message": "Conectando…"
},
"connectedToUrl": {
"message": "Conectado a $URL$",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "Página restrita recorte por meio da barra de ferramentas do Memento ou do painel lateral."
},
"destinationNotebook": {
"message": "Caderno de destino"
},
"activePage": {
"message": "Página ativa"
},
"previewBeforeSave": {
"message": "Revise antes de salvar"
},
"noteTitleLabel": {
"message": "Título"
},
"excerptLabel": {
"message": "Trecho"
},
"saveToMomento": {
"message": "Salvar no Memento"
},
"back": {
"message": "Voltar"
},
"analyzingSource": {
"message": "Analisando fonte"
},
"statusAnalyzing": {
"message": "Analisando…"
},
"statusSaving": {
"message": "Salvando…"
},
"processingDetail": {
"message": "Geração de tags, resumo semântico e incorporações."
},
"noteSaved": {
"message": "Nota salva"
},
"sentToNotebook": {
"message": "Salvo em $NOTEBOOK$",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "Ver em Memento"
},
"clipAnother": {
"message": "Recortar outra página"
},
"failure": {
"message": "Não foi possível concluir"
},
"genericError": {
"message": "Algo deu errado ao chegar à sua instância do Memento."
},
"retry": {
"message": "Tentar novamente"
},
"errNoSelection": {
"message": "Selecione o texto primeiro ou recorte a página inteira."
},
"errAnalyzeFailed": {
"message": "Não foi possível analisar esta página."
},
"errSaveFailed": {
"message": "Não foi possível salvar sua nota."
},
"errNetwork": {
"message": "Problema de rede verifique sua conexão e URL do Memento."
},
"bannerPickText": {
"message": "Destaque o texto na página ou recorte a página inteira."
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "Веб-клипер Memento"
},
"extDescription": {
"message": "Сохраняйте веб-страницы и выделенный текст в свои блокноты Memento — подключайтесь к вашему собственному серверу Memento."
},
"extActionTitle": {
"message": "Клип на Memento"
},
"webClipper": {
"message": "Веб-клипер"
},
"connected": {
"message": "Подключено"
},
"disconnected": {
"message": "Не подключено"
},
"instanceSettings": {
"message": "URL-адрес момента"
},
"instanceUrlLabel": {
"message": "URL-адрес вашего экземпляра Memento"
},
"presetProduction": {
"message": "Настройки производства · memento-note.com"
},
"applyReconnect": {
"message": "Подать заявку и повторно подключиться"
},
"openMomento": {
"message": "Открыть Моменто"
},
"settingsHint": {
"message": "Вставьте URL-адрес HTTPS (или LAN) вашего сервера Memento. Файлы cookie в этом браузере обрабатывают вход в систему."
},
"footerVersion": {
"message": "Memento Web Clipper <<<ВЕРСИЯ>>>"
},
"errPermissionDenied": {
"message": "Memento не имеет доступа к этой вкладке. Проверьте разрешения для расширения клавиатуры/сайта или откройте боковую панель."
},
"notebookUnnamed": {
"message": "Блокнот без названия"
},
"noNotebooks": {
"message": "Блокнотов пока нет"
},
"readingTimeOne": {
"message": "~1 минута чтения"
},
"readingTimeOther": {
"message": "Прибл. $COUNT$ мин чтения",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "Выбор обнаружен"
},
"ignore": {
"message": "игнорировать"
},
"selectionHint": {
"message": "Совет: выделите текст на странице, чтобы выделить его в виде заметки."
},
"clipSelection": {
"message": "Выбор клипа"
},
"clipPage": {
"message": "Вырезать эту страницу"
},
"saveLinkOnly": {
"message": "Сохранить только ссылку"
},
"pageNotAccessible": {
"message": "Невозможно обрезать здесь — эта страница блокирует доступ к расширению."
},
"errLoginRequired": {
"message": "Сначала войдите в Memento в этом браузере."
},
"errLoadNotebooks": {
"message": "Не удалось загрузить блокноты. Попробуйте переподключиться."
},
"notebooksLoaded": {
"message": "Ноутбуки загружены"
},
"connecting": {
"message": "Подключение…"
},
"connectedToUrl": {
"message": "Подключено к $URL$",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "Страница с ограниченным доступом — вырезайте с помощью панели инструментов Memento или боковой панели."
},
"destinationNotebook": {
"message": "Блокнот назначения"
},
"activePage": {
"message": "Активная страница"
},
"previewBeforeSave": {
"message": "Проверьте перед сохранением"
},
"noteTitleLabel": {
"message": "Заголовок"
},
"excerptLabel": {
"message": "Отрывок"
},
"saveToMomento": {
"message": "Сохранить в Моменто"
},
"back": {
"message": "Назад"
},
"analyzingSource": {
"message": "Анализ источника"
},
"statusAnalyzing": {
"message": "Анализ…"
},
"statusSaving": {
"message": "Сохранение…"
},
"processingDetail": {
"message": "Генерация тегов, семантического резюме и вложений."
},
"noteSaved": {
"message": "Заметка сохранена."
},
"sentToNotebook": {
"message": "Сохранено в $NOTEBOOK$",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "Посмотреть в Моменто"
},
"clipAnother": {
"message": "Вырезать другую страницу"
},
"failure": {
"message": "Не удалось завершить"
},
"genericError": {
"message": "Что-то пошло не так при получении вашего экземпляра Memento."
},
"retry": {
"message": "Повторить попытку"
},
"errNoSelection": {
"message": "Сначала выделите текст или вырежьте всю страницу."
},
"errAnalyzeFailed": {
"message": "Не удалось проанализировать эту страницу."
},
"errSaveFailed": {
"message": "Не удалось сохранить заметку."
},
"errNetwork": {
"message": "Проблема с сетью. Проверьте подключение и URL-адрес Memento."
},
"bannerPickText": {
"message": "Выделите текст на странице или вырежьте всю страницу."
}
}

View File

@@ -0,0 +1,182 @@
{
"extName": {
"message": "Memento 网页剪辑器"
},
"extDescription": {
"message": "将网页和突出显示的文本捕获到您的 Memento 笔记本中 — 连接到您自己的 Memento 服务器。"
},
"extActionTitle": {
"message": "剪辑到时刻"
},
"webClipper": {
"message": "网页剪辑器"
},
"connected": {
"message": "已连接"
},
"disconnected": {
"message": "未连接"
},
"instanceSettings": {
"message": "时刻网址"
},
"instanceUrlLabel": {
"message": "您的 Memento 实例 URL"
},
"presetProduction": {
"message": "制作预设 · memento-note.com"
},
"applyReconnect": {
"message": "应用并重新连接"
},
"openMomento": {
"message": "打开时刻"
},
"settingsHint": {
"message": "粘贴 Memento 服务器的 HTTPS或 LANURL。此浏览器中的 Cookie 处理登录。"
},
"footerVersion": {
"message": "Memento Web Clipper <<<版本>>>"
},
"errPermissionDenied": {
"message": "Memento 无法访问此选项卡。检查键盘/站点扩展权限 - 或打开侧面板。"
},
"notebookUnnamed": {
"message": "无标题笔记本"
},
"noNotebooks": {
"message": "还没有笔记本"
},
"readingTimeOne": {
"message": "阅读约 1 分钟"
},
"readingTimeOther": {
"message": "大约。 $COUNT$ 最少阅读次数",
"placeholders": {
"COUNT": {
"content": "$1",
"example": "5"
}
}
},
"selectionDetected": {
"message": "检测到选择"
},
"ignore": {
"message": "忽略"
},
"selectionHint": {
"message": "提示:突出显示页面上的文本以将精确的选择剪辑为注释。"
},
"clipSelection": {
"message": "剪辑选择"
},
"clipPage": {
"message": "剪辑此页"
},
"saveLinkOnly": {
"message": "仅保存链接"
},
"pageNotAccessible": {
"message": "此处无法剪辑 — 此页面阻止扩展程序访问。"
},
"errLoginRequired": {
"message": "请先在此浏览器中登录 Memento。"
},
"errLoadNotebooks": {
"message": "无法加载笔记本。尝试重新连接。"
},
"notebooksLoaded": {
"message": "笔记本已加载"
},
"connecting": {
"message": "正在连接…"
},
"connectedToUrl": {
"message": "连接到 $URL$",
"placeholders": {
"URL": {
"content": "$1",
"example": "https://memento-note.com"
}
}
},
"restrictedPage": {
"message": "受限页面 — 通过 Memento 工具栏或侧面板进行剪辑。"
},
"destinationNotebook": {
"message": "目的地笔记本"
},
"activePage": {
"message": "活动页面"
},
"previewBeforeSave": {
"message": "保存前查看"
},
"noteTitleLabel": {
"message": "标题"
},
"excerptLabel": {
"message": "摘抄"
},
"saveToMomento": {
"message": "保存到时刻"
},
"back": {
"message": "后退"
},
"analyzingSource": {
"message": "分析来源"
},
"statusAnalyzing": {
"message": "正在分析……"
},
"statusSaving": {
"message": "保存…"
},
"processingDetail": {
"message": "生成标签、语义摘要和嵌入。"
},
"noteSaved": {
"message": "注释已保存"
},
"sentToNotebook": {
"message": "保存至$NOTEBOOK$",
"placeholders": {
"NOTEBOOK": {
"content": "$1",
"example": "Read later"
}
}
},
"viewInMomento": {
"message": "在 Memento 中查看"
},
"clipAnother": {
"message": "剪辑另一页"
},
"failure": {
"message": "无法完成"
},
"genericError": {
"message": "您的 Memento 实例出现问题。"
},
"retry": {
"message": "重试"
},
"errNoSelection": {
"message": "首先选择文本,或剪辑整个页面。"
},
"errAnalyzeFailed": {
"message": "无法分析此页面。"
},
"errSaveFailed": {
"message": "无法保存您的笔记。"
},
"errNetwork": {
"message": "网络问题 — 检查您的连接和 Memento URL。"
},
"bannerPickText": {
"message": "突出显示页面上的文本,或剪辑整个页面。"
}
}

View File

@@ -0,0 +1,23 @@
/** Service worker — ouvre le panneau latéral (Chrome Side Panel ou Firefox sidebar) au clic sur l'icône. */
const extApi = (typeof browser !== 'undefined' && browser.sidebarAction) ? browser : chrome
if (extApi.sidePanel?.setPanelBehavior) {
// Chrome MV3 : Side Panel API
extApi.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {})
} else if (extApi.sidebarAction?.open) {
// Firefox MV3 : sidebarAction
extApi.action?.onClicked?.addListener?.(() => {
extApi.sidebarAction.open().catch(() => {})
})
}
extApi.runtime?.onInstalled?.addListener?.(() => {
if (extApi.sidePanel?.setPanelBehavior) {
extApi.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {})
}
})
extApi.runtime?.onStartup?.addListener?.(() => {
if (extApi.sidePanel?.setPanelBehavior) {
extApi.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {})
}
})

View File

@@ -0,0 +1,211 @@
/**
* Content script Memento — sélection live, surlignage, communication avec le side panel.
* Injecté automatiquement sur http(s) ; ré-injecté à la demande si longlet était déjà ouvert.
*/
;(function initMementoClipperContent() {
if (globalThis.__mementoClipperContent) return
globalThis.__mementoClipperContent = true
const HIGHLIGHT_ID = 'memento-clipper-highlight-root'
const BANNER_ID = 'memento-clipper-banner-root'
const STYLE_ID = 'memento-clipper-styles'
let pickMode = false
let debounceTimer = null
function getSelectionText() {
return window.getSelection()?.toString().trim() || ''
}
function getPageMeta() {
const dir =
document.documentElement.getAttribute('dir') ||
document.body?.getAttribute('dir') ||
''
const lang = (
document.documentElement.getAttribute('lang') ||
document.body?.getAttribute('lang') ||
''
).split('-')[0]
return {
text: getSelectionText(),
dir,
lang,
url: location.href,
title: document.title,
}
}
function broadcastSelection() {
clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => {
const payload = { type: 'SELECTION_CHANGED', ...getPageMeta() }
try {
chrome.runtime.sendMessage(payload).catch(() => {})
} catch {
/* ignore */
}
if (pickMode) paintHighlight()
}, 80)
}
function removeHighlight() {
document.getElementById(HIGHLIGHT_ID)?.remove()
}
function paintHighlight() {
removeHighlight()
const sel = window.getSelection()
if (!sel || sel.isCollapsed || !sel.rangeCount) return
let range
try {
range = sel.getRangeAt(0)
} catch {
return
}
const host = document.createElement('div')
host.id = HIGHLIGHT_ID
host.setAttribute('aria-hidden', 'true')
host.style.cssText =
'position:fixed;inset:0;pointer-events:none;z-index:2147483644;overflow:hidden;'
for (const rect of range.getClientRects()) {
if (rect.width < 2 || rect.height < 2) continue
const box = document.createElement('div')
box.style.cssText = [
'position:fixed',
`left:${rect.left - 2}px`,
`top:${rect.top - 1}px`,
`width:${rect.width + 4}px`,
`height:${rect.height + 2}px`,
'background:rgba(164,113,72,0.28)',
'border-radius:3px',
'box-shadow:0 0 0 1px rgba(164,113,72,0.35)',
'transition:opacity 0.15s ease',
].join(';')
host.appendChild(box)
}
if (host.childNodes.length) document.documentElement.appendChild(host)
}
function ensureStyles() {
if (document.getElementById(STYLE_ID)) return
const style = document.createElement('style')
style.id = STYLE_ID
style.textContent = `
html.memento-clipper-pick ::selection {
background: rgba(164, 113, 72, 0.45) !important;
color: inherit !important;
}
html.memento-clipper-pick {
scroll-behavior: auto;
}
`
document.documentElement.appendChild(style)
}
function removeBanner() {
document.getElementById(BANNER_ID)?.remove()
}
function ensureBanner() {
if (document.getElementById(BANNER_ID)) return
const bannerText =
(typeof chrome !== 'undefined' && chrome.i18n?.getMessage?.('bannerPickText')) ||
'Highlight the text to clip'
const host = document.createElement('div')
host.id = BANNER_ID
host.style.cssText =
'all:initial;position:fixed;top:16px;left:50%;transform:translateX(-50%);z-index:2147483647;pointer-events:none;font-family:Inter,system-ui,sans-serif;'
const shadow = host.attachShadow({ mode: 'open' })
shadow.innerHTML = `
<style>
.pill {
display: flex; align-items: center; gap: 10px;
padding: 10px 18px; border-radius: 999px;
background: #1c1c1c; color: #faf9f5;
box-shadow: 0 12px 32px rgba(0,0,0,0.22);
font-size: 12px; font-weight: 600;
letter-spacing: 0.02em;
animation: slideIn 0.35s cubic-bezier(0.22,1,0.36,1);
}
.logo {
width: 22px; height: 22px; border-radius: 7px;
background: #faf9f5; color: #1c1c1c;
display: flex; align-items: center; justify-content: center;
font-family: Georgia, serif; font-weight: 900; font-size: 12px;
}
.dot {
width: 8px; height: 8px; border-radius: 50%;
background: #a47148; animation: pulse 1.2s ease infinite;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(-8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.85); }
}
</style>
<div class="pill">
<span class="logo">M</span>
<span class="dot"></span>
<span>${bannerText.replace(/</g, '&lt;')}</span>
</div>
`
document.documentElement.appendChild(host)
}
function setPickMode(enabled) {
pickMode = !!enabled
ensureStyles()
if (pickMode) {
document.documentElement.classList.add('memento-clipper-pick')
ensureBanner()
paintHighlight()
} else {
document.documentElement.classList.remove('memento-clipper-pick')
removeBanner()
removeHighlight()
}
}
function onScrollOrResize() {
if (pickMode) paintHighlight()
}
document.addEventListener('selectionchange', broadcastSelection)
document.addEventListener('mouseup', broadcastSelection)
document.addEventListener('keyup', broadcastSelection)
window.addEventListener('scroll', onScrollOrResize, { passive: true, capture: true })
window.addEventListener('resize', onScrollOrResize, { passive: true })
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message?.type === 'PING') {
sendResponse({ ok: true })
return true
}
if (message?.type === 'GET_CONTEXT') {
sendResponse({
html: document.documentElement.outerHTML,
...getPageMeta(),
})
return true
}
if (message?.type === 'SET_PICK_MODE') {
setPickMode(!!message.enabled)
sendResponse({ ok: true, pickMode })
return true
}
return false
})
broadcastSelection()
})()

View File

@@ -0,0 +1,47 @@
/** Helpers i18n — langue UI Chrome (chrome.i18n.getUILanguage). */
function t(key, ...subs) {
const msg = chrome.i18n.getMessage(key, subs)
return msg || key
}
function uiLocaleTag() {
return (chrome.i18n.getUILanguage() || 'en').replace('_', '-').split('-')[0]
}
function isUiRtl() {
return ['ar', 'fa', 'he', 'ur'].includes(uiLocaleTag())
}
function applyDocumentLocale() {
const lang = uiLocaleTag()
document.documentElement.lang = lang
document.documentElement.dir = isUiRtl() ? 'rtl' : 'ltr'
document.body?.classList.toggle('ui-rtl', isUiRtl())
}
function applyShellI18n() {
document.title = t('extName')
const brandSub = document.querySelector('.brand-sub')
if (brandSub) brandSub.textContent = t('webClipper')
if (typeof els !== 'undefined' && els.connLabel) {
els.connLabel.textContent = t('connected')
}
if (typeof els !== 'undefined' && els.settingsBtn) {
els.settingsBtn.title = t('instanceSettings')
els.settingsBtn.setAttribute('aria-label', t('instanceSettings'))
}
const urlLabel = document.getElementById('instanceUrlLabel') || document.querySelector('#settingsPanel .field > span')
if (urlLabel) urlLabel.textContent = t('instanceUrlLabel')
const presetProd = document.querySelector('[data-url="https://memento-note.com"]')
if (presetProd) presetProd.textContent = t('presetProduction')
if (typeof els !== 'undefined' && els.applyInstanceBtn) {
els.applyInstanceBtn.textContent = t('applyReconnect')
}
if (typeof els !== 'undefined' && els.openLoginBtn) {
els.openLoginBtn.textContent = `${t('openMomento')}`
}
const hint = document.querySelector('.settings-hint')
if (hint) hint.textContent = t('settingsHint')
const footer = document.querySelector('.footer-meta')
if (footer) footer.textContent = t('footerVersion')
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192">
<rect width="192" height="192" fill="#1C1C1C" rx="40"/>
<g transform="translate(96,96)">
<rect x="-34" y="-34" width="68" height="68" rx="5" fill="white"/>
<path d="M34,34 L34,11 L11,34 Z" fill="#1C1C1C" opacity="0.28"/>
<line x1="-23" y1="-17" x2="23" y2="-17" stroke="#e5e7eb" stroke-width="3" stroke-linecap="round"/>
<line x1="-23" y1="-4" x2="23" y2="-4" stroke="#e5e7eb" stroke-width="3" stroke-linecap="round"/>
<line x1="-23" y1="9" x2="11" y2="9" stroke="#e5e7eb" stroke-width="3" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 587 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<rect width="512" height="512" fill="#1C1C1C" rx="100"/>
<g transform="translate(256,256)">
<rect x="-90" y="-90" width="180" height="180" rx="12" fill="white"/>
<path d="M90,90 L90,30 L30,90 Z" fill="#1C1C1C" opacity="0.28"/>
<line x1="-60" y1="-45" x2="60" y2="-45" stroke="#e5e7eb" stroke-width="8" stroke-linecap="round"/>
<line x1="-60" y1="-10" x2="60" y2="-10" stroke="#e5e7eb" stroke-width="8" stroke-linecap="round"/>
<line x1="-60" y1="25" x2="30" y2="25" stroke="#e5e7eb" stroke-width="8" stroke-linecap="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 626 B

View File

@@ -0,0 +1,65 @@
{
"manifest_version": 3,
"name": "__MSG_extName__",
"version": "0.4.0",
"description": "__MSG_extDescription__",
"default_locale": "en",
"permissions": [
"activeTab",
"scripting",
"storage",
"sidePanel",
"tabs"
],
"host_permissions": [
"https://memento-note.com/*",
"https://www.memento-note.com/*"
],
"optional_host_permissions": [
"https://*/*"
],
"background": {
"service_worker": "background.js"
},
"sidebar_action": {
"default_title": "__MSG_extActionTitle__",
"default_panel": "sidepanel.html",
"default_icon": {
"16": "icon-16.png",
"48": "icon-48.png",
"128": "icon-128.png"
}
},
"browser_specific_settings": {
"gecko": {
"id": "memento-clipper@memento-note.com",
"strict_min_version": "115.0"
}
},
"content_scripts": [
{
"matches": [
"https://memento-note.com/*",
"https://www.memento-note.com/*"
],
"js": [
"content.js"
],
"run_at": "document_idle",
"all_frames": false
}
],
"action": {
"default_title": "__MSG_extActionTitle__",
"default_icon": {
"16": "icon-16.png",
"48": "icon-48.png",
"128": "icon-128.png"
}
},
"icons": {
"16": "icon-16.png",
"48": "icon-48.png",
"128": "icon-128.png"
}
}

View File

@@ -0,0 +1,507 @@
:root {
--ink: #1c1c1c;
--paper: #faf9f5;
--card: #ffffff;
--muted: #6b7280;
--border: #e8e4dc;
--accent: #a47148;
--accent-soft: rgba(164, 113, 72, 0.12);
--accent-glow: rgba(164, 113, 72, 0.35);
--success: #10b981;
--danger: #ef4444;
--shadow: 0 18px 40px rgba(28, 28, 28, 0.08);
--radius: 14px;
--radius-sm: 10px;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
min-height: 100%;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
font-size: 13px;
color: var(--ink);
background: var(--paper);
}
.shell {
min-height: 100vh;
display: flex;
flex-direction: column;
background: var(--paper);
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 18px;
background: linear-gradient(180deg, #fff 0%, #fcfcfa 100%);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 10;
}
.brand { display: flex; align-items: center; gap: 10px; }
.brand-logo {
width: 34px; height: 34px; border-radius: 11px;
background: var(--ink); color: #faf9f5;
display: flex; align-items: center; justify-content: center;
font-family: Georgia, 'Times New Roman', serif;
font-weight: 900; font-size: 16px;
box-shadow: 0 4px 14px rgba(28, 28, 28, 0.18);
}
.brand-text { line-height: 1.1; }
.brand-name {
display: block; font-size: 14px; font-weight: 700;
font-family: Georgia, 'Times New Roman', serif;
}
.brand-sub {
display: block; font-size: 9px; letter-spacing: 0.16em;
text-transform: uppercase; color: var(--accent); font-weight: 700;
}
.icon-btn {
width: 34px; height: 34px; border-radius: 10px;
border: 1px solid var(--border); background: #fff;
color: var(--muted); cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.icon-btn:hover {
border-color: var(--accent);
color: var(--accent);
background: var(--accent-soft);
}
.header-right {
display: flex;
align-items: center;
gap: 10px;
}
.conn-badge {
display: flex;
align-items: center;
gap: 6px;
font-size: 9px;
font-weight: 800;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--muted);
}
.conn-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #10b981;
}
.settings-panel {
padding: 14px 18px;
background: #fff;
border-bottom: 1px solid var(--border);
}
.settings-panel[hidden] { display: none !important; }
.settings-hint {
margin: 8px 0 0;
font-size: 11px;
color: var(--muted);
line-height: 1.5;
}
.settings-hint code {
font-size: 10px;
background: var(--paper);
padding: 1px 4px;
border-radius: 4px;
}
.settings-status {
margin: 10px 0 0;
font-size: 11px;
line-height: 1.45;
}
.settings-status.is-ok { color: #059669; }
.settings-status.is-error { color: #dc2626; }
.preset-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 10px;
}
.preset-btn {
border: 1px solid var(--border);
background: var(--paper);
border-radius: 999px;
padding: 6px 10px;
font-size: 10px;
font-weight: 700;
cursor: pointer;
color: var(--muted);
}
.preset-btn:hover {
border-color: var(--accent);
color: var(--accent);
background: var(--accent-soft);
}
.settings-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.field { display: flex; flex-direction: column; gap: 6px; }
.field span {
font-size: 9px; text-transform: uppercase; letter-spacing: 0.12em;
color: var(--muted); font-weight: 700;
}
input[type="url"],
input[type="text"],
.notebook-select {
width: 100%; padding: 10px 12px; border: 1px solid var(--border);
border-radius: var(--radius-sm); background: var(--paper);
font-family: inherit; font-size: 12px;
}
input[type="url"]:focus,
input[type="text"]:focus,
.notebook-select:focus {
outline: none; border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.notebook-select {
background: #fff;
font-weight: 600;
cursor: pointer;
}
.main {
flex: 1;
padding: 16px 18px 20px;
display: flex;
flex-direction: column;
gap: 14px;
min-height: 0;
}
.main > .actions {
margin-top: auto;
}
.footer {
padding: 10px 18px 14px;
border-top: 1px solid var(--border);
background: #fff;
text-align: center;
}
.footer-meta { font-size: 9px; color: #9ca3af; letter-spacing: 0.06em; }
.label {
font-size: 9px; text-transform: uppercase; letter-spacing: 0.14em;
color: var(--muted); font-weight: 700; margin-bottom: 8px; display: block;
}
.auth-hint {
padding: 12px 14px;
border-radius: var(--radius);
background: #fffbeb;
border: 1px solid #fde68a;
font-size: 11px;
color: #92400e;
line-height: 1.5;
}
.page-card {
padding: 14px; border: 1px solid var(--border); border-radius: var(--radius);
background: #fff;
box-shadow: 0 1px 0 rgba(255,255,255,0.8) inset;
}
.page-card .sub {
font-size: 9px; text-transform: uppercase; letter-spacing: 0.14em;
color: var(--muted); font-weight: 700; display: block; margin-bottom: 8px;
}
.page-row { display: flex; gap: 10px; align-items: flex-start; min-width: 0; }
.page-row img {
width: 20px; height: 20px; border-radius: 5px;
flex-shrink: 0; margin-top: 2px;
}
.page-text { min-width: 0; flex: 1; }
.page-row .title {
font-size: 12px; font-weight: 700; line-height: 1.45;
display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
unicode-bidi: plaintext;
}
.page-row .url {
font-size: 10px; color: var(--muted); margin-top: 4px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
direction: ltr; text-align: left;
}
.text-rtl {
direction: rtl;
text-align: right;
font-family: 'Vazirmatn', 'Inter', sans-serif;
unicode-bidi: plaintext;
}
.selection-panel {
border-radius: var(--radius);
border: 1px solid var(--border);
background: #fff;
overflow: hidden;
min-height: 140px;
display: flex;
flex-direction: column;
}
.selection-panel.has-text {
border-color: #bae6fd;
background: rgba(14, 165, 233, 0.05);
box-shadow: 0 0 0 1px rgba(14, 165, 233, 0.12);
}
.selection-hint {
padding: 16px;
border: 1px dashed var(--border);
border-radius: var(--radius);
text-align: center;
background: #fff;
}
.selection-hint p {
margin: 0;
font-size: 11px;
color: var(--muted);
line-height: 1.55;
}
.selection-head {
display: flex; align-items: center; justify-content: space-between;
padding: 12px 14px;
background: linear-gradient(180deg, #fff 0%, #fdfcfa 100%);
border-bottom: 1px solid var(--border);
}
.selection-head .status {
display: flex; align-items: center; gap: 8px;
font-size: 10px; font-weight: 800; text-transform: uppercase;
letter-spacing: 0.08em; color: var(--muted);
}
.selection-head .status.live { color: #0284c7; }
.selection-head .count {
font-size: 10px; font-weight: 700; color: var(--muted);
background: var(--paper); padding: 4px 8px; border-radius: 999px;
}
.selection-head .count.active { color: var(--accent); background: var(--accent-soft); }
.selection-body {
flex: 1;
padding: 14px;
font-size: 13px;
line-height: 1.75;
color: rgba(28, 28, 28, 0.88);
max-height: 220px;
overflow-y: auto;
unicode-bidi: plaintext;
border-inline-start: 3px solid transparent;
}
.selection-panel.has-text .selection-body {
border-inline-start-color: #38bdf8;
padding-inline-start: 16px;
font-style: italic;
font-size: 12px;
max-height: 150px;
}
.pulse-dot {
width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
animation: pulse 1.4s ease infinite;
}
.pulse-dot.sky { background: #0ea5e9; }
@keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.4;transform:scale(.85)} }
.clear-btn {
border: none; background: none; font-size: 10px;
color: var(--muted); cursor: pointer; font-weight: 600;
padding: 4px 6px; border-radius: 6px;
}
.clear-btn:hover { color: var(--ink); background: var(--paper); }
.actions {
display: flex; flex-direction: column; gap: 10px;
margin-top: auto; padding-top: 6px;
}
.btn {
padding: 14px 16px; border-radius: var(--radius); border: none; cursor: pointer;
font-weight: 700; font-size: 10px; text-transform: uppercase;
letter-spacing: 0.1em;
display: flex; align-items: center; justify-content: center; gap: 8px;
transition: transform 0.12s ease, opacity 0.12s ease, filter 0.12s ease;
}
.btn:active { transform: scale(0.98); }
.btn:disabled {
opacity: 0.42; cursor: not-allowed; transform: none;
box-shadow: none !important;
}
.btn-primary {
background: var(--ink); color: #fff;
box-shadow: 0 10px 24px rgba(28, 28, 28, 0.18);
}
.btn-primary:hover:not(:disabled) { opacity: 0.94; }
.btn-sky {
background: #0284c7;
color: #fff;
box-shadow: 0 10px 22px rgba(2, 132, 199, 0.22);
}
.btn-sky:hover:not(:disabled) { background: #0369a1; }
.btn-secondary {
background: #f3f4f6;
color: #374151;
box-shadow: none;
}
.btn-secondary:hover:not(:disabled) { background: #e5e7eb; }
.btn-sm {
padding: 10px 12px;
font-size: 10px;
}
.btn-danger { background: var(--danger); color: #fff; }
.btn-link.link-only {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
.btn-link {
background: none; border: none; color: var(--muted); font-size: 11px;
cursor: pointer; padding: 8px; font-weight: 500;
}
.btn-link:hover { color: var(--ink); text-decoration: underline; }
.btn-icon { width: 14px; height: 14px; display: inline-flex; }
.center-state {
flex: 1; display: flex; flex-direction: column; align-items: center;
justify-content: center; text-align: center; gap: 14px; padding: 32px 12px;
min-height: 280px;
}
.spinner-wrap { position: relative; width: 52px; height: 52px; }
.spinner-ring {
position: absolute; inset: 0; border-radius: 50%;
border: 1px solid var(--border); animation: ping 1.2s ease infinite;
}
@keyframes ping { 0%{transform:scale(1);opacity:.6} 100%{transform:scale(1.35);opacity:0} }
.spinner {
position: absolute; inset: 6px;
border: 3px solid var(--border); border-top-color: var(--accent);
border-radius: 50%; animation: spin 0.75s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.state-title {
font-size: 10px; font-weight: 800; text-transform: uppercase;
letter-spacing: 0.14em; color: var(--muted);
}
.state-sub { font-size: 15px; font-weight: 700; color: var(--ink); }
.state-detail {
font-size: 11px; color: var(--muted); max-width: 280px;
line-height: 1.55; margin: 0 auto;
}
.success-icon, .error-icon {
width: 58px; height: 58px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 26px; font-weight: 700;
}
.success-icon {
background: rgba(16, 185, 129, 0.1);
border: 1px solid rgba(16, 185, 129, 0.25); color: var(--success);
}
.error-icon { background: #fef2f2; color: var(--danger); }
.badge-ok {
display: inline-block; margin-bottom: 8px;
font-size: 9px; background: rgba(16, 185, 129, 0.12);
color: #059669; font-weight: 800; padding: 3px 8px; border-radius: 6px;
text-transform: uppercase; letter-spacing: 0.1em;
}
.note-title {
font-size: 15px; font-weight: 700;
font-family: Georgia, 'Times New Roman', serif;
line-height: 1.35; margin-top: 6px;
unicode-bidi: plaintext;
}
.tags {
display: flex; flex-wrap: wrap; gap: 6px; justify-content: center;
padding-top: 14px; border-top: 1px solid var(--border); margin-top: 10px;
width: 100%;
}
.tag-chip {
font-size: 9px; font-weight: 800; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--accent);
background: var(--accent-soft); border: 1px solid rgba(164, 113, 72, 0.2);
padding: 5px 10px; border-radius: 999px;
}
.restricted-note {
padding: 14px; border-radius: var(--radius);
background: #fef2f2; border: 1px solid #fecaca;
font-size: 11px; color: #991b1b; line-height: 1.5;
}
.confirm-panel {
display: flex;
flex-direction: column;
gap: 12px;
}
.summary-preview {
margin: 0;
font-size: 12px;
color: var(--muted);
line-height: 1.55;
font-style: italic;
}
.excerpt-preview {
padding: 12px 14px;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
background: #fff;
font-size: 12px;
line-height: 1.65;
max-height: 150px;
overflow-y: auto;
unicode-bidi: plaintext;
}
.excerpt-label {
display: block;
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--muted);
font-weight: 700;
margin-bottom: 8px;
}
.meta-row { margin-top: -4px; }
.reading-time {
font-size: 10px;
font-weight: 700;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.preview-tags { justify-content: flex-start; border-top: none; margin-top: 0; padding-top: 0; }
html[dir="rtl"] .header,
html[dir="rtl"] .header-right,
html[dir="rtl"] .selection-head,
html[dir="rtl"] .page-row {
flex-direction: row-reverse;
}
html[dir="rtl"] .page-row .url {
direction: ltr;
text-align: left;
}
html[dir="rtl"] .notebook-select,
html[dir="rtl"] .dropdown-item,
html[dir="rtl"] .label,
html[dir="rtl"] .sub {
text-align: right;
}

View File

@@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Memento Web Clipper</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Vazirmatn:wght@400;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="sidepanel.css" />
</head>
<body>
<div id="app" class="shell">
<header class="header">
<div class="brand">
<div class="brand-logo">M</div>
<div class="brand-text">
<span class="brand-name">Memento</span>
<span class="brand-sub">Web Clipper</span>
</div>
</div>
<div class="header-right">
<div id="connBadge" class="conn-badge" hidden>
<span class="conn-dot"></span>
<span id="connLabel"></span>
</div>
<button type="button" id="settingsBtn" class="icon-btn" title="" aria-label="">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
</button>
</div>
</header>
<div id="settingsPanel" class="settings-panel" hidden>
<label class="field">
<span id="instanceUrlLabel"></span>
<input id="baseUrl" type="text" spellcheck="false" placeholder="http://localhost:3000" />
</label>
<div class="preset-row">
<button type="button" class="preset-btn" data-url="https://memento-note.com"></button>
<button type="button" class="preset-btn" data-url="http://localhost:3000">localhost:3000</button>
<button type="button" class="preset-btn" data-url="http://127.0.0.1:3000">127.0.0.1:3000</button>
</div>
<div class="settings-actions">
<button type="button" id="applyInstanceBtn" class="btn btn-primary btn-sm"></button>
<button type="button" id="openLoginBtn" class="btn btn-secondary btn-sm"></button>
</div>
<p class="settings-hint"></p>
<p id="settingsStatus" class="settings-status" hidden></p>
</div>
<main id="screen" class="main"></main>
<footer class="footer">
<span class="footer-meta"></span>
</footer>
</div>
<script src="i18n.js"></script>
<script src="sidepanel.js"></script>
</body>
</html>

View File

@@ -0,0 +1,726 @@
/** Mettre à false pour le build Chrome Web Store (URL production en dur). */
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' }
/** Compatibilité Chrome/Firefox : Firefox expose l'API sous `browser.*`,
* Chrome sous `chrome.*`. On normalise pour utiliser `chrome.*` partout dans le code. */
if (typeof chrome === 'undefined' && typeof browser !== 'undefined') {
globalThis.chrome = browser
}
let state = 'idle'
let notebooks = []
let selectedNotebookId = ''
let pageUrl = ''
let pageTitle = ''
let pageDomain = ''
let pageFavicon = ''
let pageHtml = ''
let pageDir = 'ltr'
let pageLang = ''
let selectionText = ''
let pageRestricted = false
let lastNoteId = ''
let lastNoteUrl = ''
let successTitle = ''
let successTags = []
let errorMessage = ''
let activeTabId = null
let pendingClipType = 'page'
let analyzeResult = null
let editableTitle = ''
let connected = false
const els = {
screen: document.getElementById('screen'),
baseUrl: document.getElementById('baseUrl'),
settingsPanel: document.getElementById('settingsPanel'),
settingsBtn: document.getElementById('settingsBtn'),
connBadge: document.getElementById('connBadge'),
connLabel: document.getElementById('connLabel'),
settingsStatus: document.getElementById('settingsStatus'),
applyInstanceBtn: document.getElementById('applyInstanceBtn'),
openLoginBtn: document.getElementById('openLoginBtn'),
}
const ICON_SELECT =
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>'
const ICON_CLIP =
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>'
const ICON_LINK =
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>'
function apiBase() {
if (!ALLOW_INSTANCE_CONFIG) return DEFAULT_BASE
return (els.baseUrl?.value || DEFAULT_BASE).replace(/\/$/, '')
}
function isRestrictedUrl(url) {
return !url || /^(chrome|chrome-extension|edge|about|moz-extension|devtools):/i.test(url)
}
async function ensureApiPermission() {
const origin = `${apiBase()}/*`
const has = await chrome.permissions.contains({ origins: [origin] })
if (!has) {
const granted = await chrome.permissions.request({ origins: [origin] })
if (!granted) throw new Error(t('errPermissionDenied'))
}
}
function escapeHtml(s) {
return String(s)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
}
const RTL_CHAR = /[\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/
const LTR_CHAR = /[A-Za-z0-9]/
function detectTextDirection(text) {
const sample = String(text || '').replace(/\s+/g, '').slice(0, 3000)
if (!sample) return 'ltr'
let rtl = 0
let ltr = 0
for (const ch of sample) {
if (RTL_CHAR.test(ch)) rtl++
else if (LTR_CHAR.test(ch)) ltr++
}
if (rtl === 0) return 'ltr'
return rtl >= ltr ? 'rtl' : 'ltr'
}
function resolveUiDirection(text) {
if (pageDir === 'rtl') return 'rtl'
if (pageLang === 'fa' || pageLang === 'ar' || pageLang === 'he') return 'rtl'
if (/\/persian\b|\/fa\b|bbc\.com\/persian/i.test(pageUrl)) return 'rtl'
return detectTextDirection(text)
}
function rtlAttrs(text) {
if (resolveUiDirection(text) !== 'rtl') return ''
const lang = pageLang && ['fa', 'ar', 'he'].includes(pageLang) ? ` lang="${pageLang}"` : ''
return ` class="text-rtl" dir="rtl"${lang}`
}
function sortNotebooksHierarchy(list) {
const byParent = new Map()
for (const n of list) {
const pid = n.parentId || '__root__'
if (!byParent.has(pid)) byParent.set(pid, [])
byParent.get(pid).push(n)
}
for (const items of byParent.values()) {
items.sort((a, b) => (a.name || '').localeCompare(b.name || '', uiLocaleTag()))
}
const out = []
const seen = new Set()
function walk(parentKey, depth) {
for (const n of byParent.get(parentKey) || []) {
if (seen.has(n.id)) continue
seen.add(n.id)
out.push({ ...n, depth })
walk(n.id, depth + 1)
}
}
walk('__root__', 0)
for (const n of list) {
if (!seen.has(n.id)) out.push({ ...n, depth: 0 })
}
return out
}
function notebookSelectHtml() {
const sorted = sortNotebooksHierarchy(notebooks)
const opts = sorted
.map((n) => {
const indent = n.depth > 0 ? '\u00A0\u00A0'.repeat(n.depth) + '↳ ' : ''
const sel = n.id === selectedNotebookId ? ' selected' : ''
return `<option value="${escapeHtml(n.id)}"${sel}>${escapeHtml(indent + (n.name || t('notebookUnnamed')))}</option>`
})
.join('')
return `<select id="notebookSelect" class="notebook-select" aria-label="${escapeHtml(t('destinationNotebook'))}">
${notebooks.length ? opts : `<option value="">${escapeHtml(t('noNotebooks'))}</option>`}
</select>`
}
function formatReadingTime(minutes) {
const m = Number(minutes) || 0
if (m <= 0) return ''
if (m === 1) return t('readingTimeOne')
return t('readingTimeOther', String(m))
}
async function getActiveTab() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true })
return tab
}
async function ensureContentScript(tabId) {
try {
const resp = await chrome.tabs.sendMessage(tabId, { type: 'PING' })
if (resp?.ok) return true
} catch {
/* inject */
}
try {
await chrome.scripting.executeScript({ target: { tabId }, files: ['content.js'] })
return true
} catch {
return false
}
}
async function setPickModeOnTab(enabled) {
if (!activeTabId || pageRestricted) return
const ok = await ensureContentScript(activeTabId)
if (!ok) return
try {
await chrome.tabs.sendMessage(activeTabId, { type: 'SET_PICK_MODE', enabled })
} catch {
/* ignore */
}
}
async function syncPickMode() {
await setPickModeOnTab(state === 'idle' && !pageRestricted)
}
function updateConnBadge() {
if (!els.connBadge) return
els.connBadge.hidden = !connected
if (els.connLabel) els.connLabel.textContent = connected ? t('connected') : t('disconnected')
}
function setSettingsStatus(msg, isError) {
if (!els.settingsStatus) return
els.settingsStatus.hidden = !msg
els.settingsStatus.textContent = msg || ''
els.settingsStatus.className = `settings-status${isError ? ' is-error' : ' is-ok'}`
}
function applyInstanceConfigVisibility() {
if (ALLOW_INSTANCE_CONFIG) return
els.settingsPanel?.setAttribute('hidden', '')
els.settingsBtn?.setAttribute('hidden', '')
if (els.baseUrl) els.baseUrl.value = DEFAULT_BASE
}
function selectionBlockHtml() {
if (selectionText) {
return `<div class="selection-panel has-text" id="selectionSlot">
<div class="selection-head">
<span class="status live"><span class="pulse-dot sky"></span> ${escapeHtml(t('selectionDetected'))}</span>
<button type="button" class="clear-btn" id="clearSel">${escapeHtml(t('ignore'))}</button>
</div>
<div class="selection-body"${rtlAttrs(selectionText)}>「 ${escapeHtml(selectionText)} 」</div>
</div>`
}
return `<div class="selection-hint" id="selectionSlot">
<p>${escapeHtml(t('selectionHint'))}</p>
</div>`
}
function actionsBlockHtml() {
const hasSel = Boolean(selectionText)
return `<div class="actions" id="actionsSlot">
${
hasSel
? `<button type="button" class="btn btn-sky" id="clipSelBtn">
${ICON_SELECT} ${escapeHtml(t('clipSelection'))}
</button>`
: ''
}
<button type="button" class="btn ${hasSel ? 'btn-secondary' : 'btn-primary'}" id="clipPageBtn" ${pageRestricted ? 'disabled' : ''}>
${ICON_CLIP} ${escapeHtml(t('clipPage'))}
</button>
<button type="button" class="btn-link link-only" id="clipLinkBtn" ${pageRestricted ? 'disabled' : ''}>
${ICON_LINK} ${escapeHtml(t('saveLinkOnly'))}
</button>
</div>`
}
function bindIdleHandlers() {
document.getElementById('notebookSelect')?.addEventListener('change', async (e) => {
selectedNotebookId = e.target.value || ''
await chrome.storage.sync.set({ [STORAGE_KEYS.notebookId]: selectedNotebookId })
})
document.getElementById('clearSel')?.addEventListener('click', () => void clearSelection())
document.getElementById('clipSelBtn')?.addEventListener('click', () => void runAnalyze('selection'))
document.getElementById('clipPageBtn')?.addEventListener('click', () => void runAnalyze('page'))
document.getElementById('clipLinkBtn')?.addEventListener('click', () => void runAnalyze('link'))
// Gérer l'erreur de chargement du favicon
document.querySelector('.page-favicon')?.addEventListener('error', function() {
const fallback = this.getAttribute('data-fallback')
if (fallback && this.src !== fallback) {
this.src = fallback
}
})
}
async function clearSelection() {
selectionText = ''
if (activeTabId) {
try {
await chrome.scripting.executeScript({
target: { tabId: activeTabId },
func: () => window.getSelection()?.removeAllRanges(),
})
} catch {
/* ignore */
}
}
updateSelectionUI()
}
function updateSelectionUI() {
const slot = document.getElementById('selectionSlot')
const actions = document.getElementById('actionsSlot')
if (!slot || !actions || state !== 'idle') {
render()
return
}
slot.outerHTML = selectionBlockHtml()
actions.outerHTML = actionsBlockHtml()
bindIdleHandlers()
}
function applySelectionFromMessage(msg) {
if (!msg || msg.url !== pageUrl) return
selectionText = msg.text || ''
if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl'
if (msg.lang) pageLang = msg.lang
if (state === 'idle') updateSelectionUI()
}
async function refreshPageContext() {
const tab = await getActiveTab()
activeTabId = tab?.id ?? null
pageRestricted = isRestrictedUrl(tab?.url)
if (!tab?.id || pageRestricted) {
pageUrl = tab?.url || ''
pageTitle = tab?.title || t('pageNotAccessible')
selectionText = ''
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) 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 || ''
} catch {
try {
const [{ result }] = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => ({
html: document.documentElement.outerHTML,
text: window.getSelection()?.toString().trim() || '',
dir: document.documentElement.getAttribute('dir') || '',
lang: (document.documentElement.getAttribute('lang') || '').split('-')[0],
}),
})
pageHtml = result?.html || ''
selectionText = result?.text || ''
pageDir = result?.dir?.toLowerCase() === 'rtl' ? 'rtl' : 'ltr'
pageLang = result?.lang || ''
} catch {
/* ignore */
}
}
}
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)
} catch (e) {
notebooks = []
connected = false
updateConnBadge()
errorMessage = e.message
setSettingsStatus(e.message, true)
}
}
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 renderIdle() {
const restrictedBlock = pageRestricted
? `<div class="restricted-note">${escapeHtml(t('restrictedPage'))}</div>`
: ''
const authHint =
!connected && errorMessage
? `<div class="auth-hint">${escapeHtml(errorMessage)}</div>`
: ''
els.screen.innerHTML = `
${restrictedBlock}
${authHint}
<div>
<span class="label">${escapeHtml(t('destinationNotebook'))}</span>
${notebookSelectHtml()}
</div>
<div class="page-card">
<span class="sub">${escapeHtml(t('activePage'))}</span>
<div class="page-row">
<img src="${escapeHtml(pageFavicon)}" alt="" class="page-favicon" data-fallback="https://www.google.com/s2/favicons?domain=google.com&sz=32" />
<div class="page-text">
<div class="title"${rtlAttrs(pageTitle)}>${escapeHtml(pageTitle || '—')}</div>
<div class="url">${escapeHtml(pageUrl || '—')}</div>
</div>
</div>
</div>
${selectionBlockHtml()}
${actionsBlockHtml()}
`
bindIdleHandlers()
}
function renderLoading(label) {
els.screen.innerHTML = `
<div class="center-state">
<div class="spinner-wrap">
<div class="spinner-ring"></div>
<div class="spinner"></div>
</div>
<div>
<div class="state-title">${escapeHtml(t('analyzingSource'))}</div>
<div class="state-sub">${escapeHtml(label || t('statusAnalyzing'))}</div>
<div class="state-detail">${escapeHtml(t('processingDetail'))}</div>
</div>
</div>
`
}
function renderConfirm() {
const excerpt = analyzeResult?.excerpt || ''
const tags = analyzeResult?.tags || []
const reading = formatReadingTime(analyzeResult?.readingTime)
const tagsHtml = tags.map((t) => `<span class="tag-chip">${escapeHtml(t)}</span>`).join('')
els.screen.innerHTML = `
<div class="confirm-panel">
<span class="label">${escapeHtml(t('previewBeforeSave'))}</span>
<label class="field">
<span>${escapeHtml(t('noteTitleLabel'))}</span>
<input id="titleInput" type="text" value="${escapeHtml(editableTitle)}" maxlength="300" />
</label>
${
reading
? `<div class="meta-row"><span class="reading-time">${escapeHtml(reading)}</span></div>`
: ''
}
${
analyzeResult?.summary
? `<p class="summary-preview"${rtlAttrs(analyzeResult.summary)}>${escapeHtml(analyzeResult.summary)}</p>`
: ''
}
${
excerpt && pendingClipType !== 'link'
? `<div class="excerpt-preview"${rtlAttrs(excerpt)}>
<span class="excerpt-label">${escapeHtml(t('excerptLabel'))}</span>
${escapeHtml(excerpt)}
</div>`
: ''
}
${tagsHtml ? `<div class="tags preview-tags">${tagsHtml}</div>` : ''}
</div>
<div class="actions">
<button type="button" class="btn btn-primary" id="saveBtn">${escapeHtml(t('saveToMomento'))}</button>
<button type="button" class="btn-link" id="cancelConfirmBtn">${escapeHtml(t('back'))}</button>
</div>
`
document.getElementById('titleInput')?.addEventListener('input', (e) => {
editableTitle = e.target.value
})
document.getElementById('saveBtn')?.addEventListener('click', () => void runSave())
document.getElementById('cancelConfirmBtn')?.addEventListener('click', async () => {
state = 'idle'
analyzeResult = null
await syncPickMode()
render()
})
}
function renderSuccess() {
const nb = notebooks.find((n) => n.id === selectedNotebookId)
const tagsHtml = successTags.map((t) => `<span class="tag-chip">${escapeHtml(t)}</span>`).join('')
const reading = formatReadingTime(analyzeResult?.readingTime)
els.screen.innerHTML = `
<div class="center-state" style="justify-content:flex-start;padding-top:12px">
<div class="success-icon">✓</div>
<div>
<span class="badge-ok">${escapeHtml(t('noteSaved'))}</span>
<div class="note-title"${rtlAttrs(successTitle)}>${escapeHtml(successTitle)}</div>
<div class="state-detail">${escapeHtml(t('sentToNotebook', nb?.name || ''))}</div>
${reading ? `<div class="state-detail">${escapeHtml(reading)}</div>` : ''}
</div>
${tagsHtml ? `<div class="tags">${tagsHtml}</div>` : ''}
</div>
<div class="actions">
<button type="button" class="btn btn-primary" id="viewBtn">${escapeHtml(t('viewInMomento'))} ↗</button>
<button type="button" class="btn-link" id="againBtn">${escapeHtml(t('clipAnother'))}</button>
</div>
`
document.getElementById('viewBtn')?.addEventListener('click', () => {
if (lastNoteUrl) chrome.tabs.create({ url: `${apiBase()}${lastNoteUrl}` })
})
document.getElementById('againBtn')?.addEventListener('click', async () => {
state = 'idle'
analyzeResult = null
await refreshPageContext()
await syncPickMode()
render()
})
}
function renderError() {
els.screen.innerHTML = `
<div class="center-state">
<div class="error-icon">!</div>
<div>
<div class="state-title" style="color:#ef4444">${escapeHtml(t('failure'))}</div>
<div class="state-detail">${escapeHtml(errorMessage || t('genericError'))}</div>
</div>
</div>
<div class="actions">
<button type="button" class="btn btn-danger" id="retryBtn">${escapeHtml(t('retry'))}</button>
<button type="button" class="btn-link" id="backIdleBtn">${escapeHtml(t('back'))}</button>
</div>
`
document.getElementById('retryBtn')?.addEventListener('click', () => {
if (analyzeResult) void runSave()
else void runAnalyze(pendingClipType)
})
document.getElementById('backIdleBtn')?.addEventListener('click', async () => {
state = 'idle'
errorMessage = ''
analyzeResult = null
await refreshPageContext()
await syncPickMode()
render()
})
}
function render() {
if (state === 'loading' || state === 'saving') {
return renderLoading(state === 'saving' ? t('statusSaving') : t('statusAnalyzing'))
}
if (state === 'confirm') return renderConfirm()
if (state === 'success') return renderSuccess()
if (state === 'error') return renderError()
renderIdle()
}
async function runAnalyze(type) {
pendingClipType = type
state = 'loading'
await setPickModeOnTab(false)
render()
try {
await ensureApiPermission()
await chrome.storage.sync.set({
[STORAGE_KEYS.baseUrl]: apiBase(),
[STORAGE_KEYS.notebookId]: selectedNotebookId,
})
if (type === 'selection') {
if (!selectionText) throw new Error(t('errNoSelection'))
await refreshPageContext()
}
let analyzeBody
if (type === 'link') {
analyzeBody = { url: pageUrl, title: pageTitle, mode: 'link' }
} else if (type === 'selection' && selectionText) {
analyzeBody = { url: pageUrl, title: pageTitle, mode: 'selection', selection: selectionText }
} else {
analyzeBody = { url: pageUrl, html: pageHtml, title: pageTitle, mode: 'article' }
}
const analyzeRes = await fetch(`${apiBase()}/api/clip/analyze`, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(analyzeBody),
})
const analysis = await analyzeRes.json()
if (!analyzeRes.ok) throw new Error(analysis.error || t('errAnalyzeFailed'))
analyzeResult = analysis
editableTitle = analysis.title || pageTitle || pageDomain
state = 'confirm'
render()
} catch (e) {
errorMessage = e.message || t('errNetwork')
state = 'error'
render()
}
}
async function runSave() {
if (!analyzeResult) return
state = 'saving'
render()
try {
const title = (editableTitle || analyzeResult.title || pageTitle || pageDomain).trim()
const saveRes = await fetch(`${apiBase()}/api/clip/save`, {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: pageUrl,
title,
content: analyzeResult.content,
summary: analyzeResult.summary,
tags: analyzeResult.tags || [],
notebookId: selectedNotebookId || undefined,
}),
})
const saved = await saveRes.json()
if (!saveRes.ok) throw new Error(saved.error || t('errSaveFailed'))
successTitle = title
successTags = analyzeResult.tags || []
lastNoteId = saved.noteId
lastNoteUrl = saved.noteUrl
state = 'success'
render()
} catch (e) {
errorMessage = e.message || t('errNetwork')
state = 'error'
render()
}
}
chrome.runtime.onMessage.addListener((msg) => {
if (msg?.type === 'SELECTION_CHANGED') applySelectionFromMessage(msg)
})
chrome.tabs.onActivated.addListener(async () => {
if (state !== 'idle') return
await refreshPageContext()
await syncPickMode()
render()
})
chrome.tabs.onUpdated.addListener(async (tabId, info) => {
if (info.status !== 'complete' || state !== 'idle') return
const tab = await getActiveTab()
if (tab?.id === tabId) {
await refreshPageContext()
await syncPickMode()
render()
}
})
els.settingsBtn?.addEventListener('click', () => {
if (!ALLOW_INSTANCE_CONFIG) return
els.settingsPanel.hidden = !els.settingsPanel.hidden
})
document.querySelectorAll('.preset-btn').forEach((btn) => {
btn.addEventListener('click', () => {
const url = btn.getAttribute('data-url')
if (url && els.baseUrl) els.baseUrl.value = url
})
})
els.applyInstanceBtn?.addEventListener('click', () => void applyInstance())
els.openLoginBtn?.addEventListener('click', () => {
chrome.tabs.create({ url: apiBase() })
})
document.addEventListener('visibilitychange', async () => {
if (document.visibilityState === 'visible') {
if (state === 'idle') {
await refreshPageContext()
await syncPickMode()
render()
}
} else if (document.visibilityState === 'hidden') {
// Désactiver le pick mode quand le sidepanel est fermé
await setPickModeOnTab(false)
}
})
document.addEventListener('DOMContentLoaded', async () => {
applyDocumentLocale()
applyInstanceConfigVisibility()
applyShellI18n()
await loadSettings()
try {
await ensureApiPermission()
} catch (e) {
errorMessage = e.message
connected = false
updateConnBadge()
}
await refreshPageContext()
await syncPickMode()
render()
})