feat(insights): fix DBSCAN, Persian embeddings crash, D3 physics layouts, and D3 node not found runtime error
This commit is contained in:
207
memento-note/extension/content.js
Normal file
207
memento-note/extension/content.js
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Content script Momento — sélection live, surlignage, communication avec le side panel.
|
||||
* Injecté automatiquement sur http(s) ; ré-injecté à la demande si l’onglet était déjà ouvert.
|
||||
*/
|
||||
;(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()
|
||||
})()
|
||||
Reference in New Issue
Block a user