Files
Momento/memento-note/extension/content.js
Antigravity e881004c77
Some checks failed
CI / Lint, Test & Build (push) Failing after 1m7s
CI / Deploy production (on server) (push) Has been skipped
feat(insights): fix DBSCAN, Persian embeddings crash, D3 physics layouts, and D3 node not found runtime error
2026-05-24 18:57:33 +00:00

208 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Content script Momento — 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 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>Surlignez le texte à clipper</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()
})()