diff --git a/memento-note/extension/background.js b/memento-note/extension/background.js index 4aa7216..4931e9a 100644 --- a/memento-note/extension/background.js +++ b/memento-note/extension/background.js @@ -21,3 +21,26 @@ extApi.runtime?.onStartup?.addListener?.(() => { extApi.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {}) } }) + +// === Forwarding content script → side panel === +// En Chrome MV3, chrome.runtime.sendMessage depuis un content script arrive +// uniquement au background service worker (PAS au side panel directement). +// On doit broadcaster manuellement vers toutes les surfaces ouvertes. +extApi.runtime?.onMessage?.addListener?.((msg, sender) => { + if (!msg || typeof msg !== 'object') return + // Messages émis par le content script qui doivent atteindre le side panel. + const FORWARD_TYPES = new Set([ + 'SELECTION_CHANGED', + 'PAGE_CONTEXT_CHANGED', + ]) + if (!FORWARD_TYPES.has(msg.type)) return + // Broadcast à toutes les surfaces (side panel, popup). + // runAt permet au receiver de filtrer par URL s'il veut. + extApi.runtime.sendMessage({ + ...msg, + _fromContentScript: true, + _fromTabId: sender?.tab?.id, + }).catch(() => { /* no listener */ }) + // Retourne true pour permettre une réponse asynchrone si besoin. + return false +}) diff --git a/memento-note/extension/dist-chrome-store/background.js b/memento-note/extension/dist-chrome-store/background.js index 4aa7216..4931e9a 100644 --- a/memento-note/extension/dist-chrome-store/background.js +++ b/memento-note/extension/dist-chrome-store/background.js @@ -21,3 +21,26 @@ extApi.runtime?.onStartup?.addListener?.(() => { extApi.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {}) } }) + +// === Forwarding content script → side panel === +// En Chrome MV3, chrome.runtime.sendMessage depuis un content script arrive +// uniquement au background service worker (PAS au side panel directement). +// On doit broadcaster manuellement vers toutes les surfaces ouvertes. +extApi.runtime?.onMessage?.addListener?.((msg, sender) => { + if (!msg || typeof msg !== 'object') return + // Messages émis par le content script qui doivent atteindre le side panel. + const FORWARD_TYPES = new Set([ + 'SELECTION_CHANGED', + 'PAGE_CONTEXT_CHANGED', + ]) + if (!FORWARD_TYPES.has(msg.type)) return + // Broadcast à toutes les surfaces (side panel, popup). + // runAt permet au receiver de filtrer par URL s'il veut. + extApi.runtime.sendMessage({ + ...msg, + _fromContentScript: true, + _fromTabId: sender?.tab?.id, + }).catch(() => { /* no listener */ }) + // Retourne true pour permettre une réponse asynchrone si besoin. + return false +}) diff --git a/memento-note/extension/dist-chrome-store/sidepanel.js b/memento-note/extension/dist-chrome-store/sidepanel.js index e7f0185..d441400 100644 --- a/memento-note/extension/dist-chrome-store/sidepanel.js +++ b/memento-note/extension/dist-chrome-store/sidepanel.js @@ -293,11 +293,41 @@ function updateSelectionUI() { } function applySelectionFromMessage(msg) { - if (!msg || msg.url !== pageUrl) return + if (!msg) return + // Tolérance sur la comparaison d'URL : ignorer trailing slash, query, + // fragment. Si pageUrl est vide (panel ouvert avant refreshPageContext), + // on accepte la première sélection qui arrive. + const normalize = (u) => { + try { + const p = new URL(u) + return `${p.origin}${p.pathname.replace(/\/$/, '')}` + } catch { return u || '' } + } + // Si on n'a pas encore de pageUrl mais qu'un message arrive avec URL, + // on l'utilise pour amorcer la fiche page. Très utile quand chrome.tabs.query + // rate (ex. onglet sans permission, side panel non focus, etc.). + if (msg.url && (!pageUrl || normalize(msg.url) !== normalize(pageUrl))) { + if (!pageUrl) { + pageUrl = msg.url + pageTitle = msg.title || pageTitle || '' + try { + const u = new URL(msg.url) + pageDomain = u.hostname + pageFavicon = `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32` + } catch { /* ignore */ } + } else { + // URL vraiment différente — ignorer. + return + } + } selectionText = msg.text || '' if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl' if (msg.lang) pageLang = msg.lang - if (state === 'idle') updateSelectionUI() + if (state === 'idle') { + updateSelectionUI() + } else { + console.debug('[memento] selection updated while busy, deferring UI') + } } async function refreshPageContext() { diff --git a/memento-note/extension/dist-firefox/background.js b/memento-note/extension/dist-firefox/background.js index 4aa7216..4931e9a 100644 --- a/memento-note/extension/dist-firefox/background.js +++ b/memento-note/extension/dist-firefox/background.js @@ -21,3 +21,26 @@ extApi.runtime?.onStartup?.addListener?.(() => { extApi.sidePanel.setPanelBehavior({ openPanelOnActionClick: true }).catch(() => {}) } }) + +// === Forwarding content script → side panel === +// En Chrome MV3, chrome.runtime.sendMessage depuis un content script arrive +// uniquement au background service worker (PAS au side panel directement). +// On doit broadcaster manuellement vers toutes les surfaces ouvertes. +extApi.runtime?.onMessage?.addListener?.((msg, sender) => { + if (!msg || typeof msg !== 'object') return + // Messages émis par le content script qui doivent atteindre le side panel. + const FORWARD_TYPES = new Set([ + 'SELECTION_CHANGED', + 'PAGE_CONTEXT_CHANGED', + ]) + if (!FORWARD_TYPES.has(msg.type)) return + // Broadcast à toutes les surfaces (side panel, popup). + // runAt permet au receiver de filtrer par URL s'il veut. + extApi.runtime.sendMessage({ + ...msg, + _fromContentScript: true, + _fromTabId: sender?.tab?.id, + }).catch(() => { /* no listener */ }) + // Retourne true pour permettre une réponse asynchrone si besoin. + return false +}) diff --git a/memento-note/extension/dist-firefox/sidepanel.js b/memento-note/extension/dist-firefox/sidepanel.js index e7f0185..d441400 100644 --- a/memento-note/extension/dist-firefox/sidepanel.js +++ b/memento-note/extension/dist-firefox/sidepanel.js @@ -293,11 +293,41 @@ function updateSelectionUI() { } function applySelectionFromMessage(msg) { - if (!msg || msg.url !== pageUrl) return + if (!msg) return + // Tolérance sur la comparaison d'URL : ignorer trailing slash, query, + // fragment. Si pageUrl est vide (panel ouvert avant refreshPageContext), + // on accepte la première sélection qui arrive. + const normalize = (u) => { + try { + const p = new URL(u) + return `${p.origin}${p.pathname.replace(/\/$/, '')}` + } catch { return u || '' } + } + // Si on n'a pas encore de pageUrl mais qu'un message arrive avec URL, + // on l'utilise pour amorcer la fiche page. Très utile quand chrome.tabs.query + // rate (ex. onglet sans permission, side panel non focus, etc.). + if (msg.url && (!pageUrl || normalize(msg.url) !== normalize(pageUrl))) { + if (!pageUrl) { + pageUrl = msg.url + pageTitle = msg.title || pageTitle || '' + try { + const u = new URL(msg.url) + pageDomain = u.hostname + pageFavicon = `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32` + } catch { /* ignore */ } + } else { + // URL vraiment différente — ignorer. + return + } + } selectionText = msg.text || '' if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl' if (msg.lang) pageLang = msg.lang - if (state === 'idle') updateSelectionUI() + if (state === 'idle') { + updateSelectionUI() + } else { + console.debug('[memento] selection updated while busy, deferring UI') + } } async function refreshPageContext() { diff --git a/memento-note/extension/memento-web-clipper-chrome-store.zip b/memento-note/extension/memento-web-clipper-chrome-store.zip index bcca1c7..0701cc7 100644 Binary files a/memento-note/extension/memento-web-clipper-chrome-store.zip and b/memento-note/extension/memento-web-clipper-chrome-store.zip differ diff --git a/memento-note/extension/memento-web-clipper-firefox.xpi b/memento-note/extension/memento-web-clipper-firefox.xpi index 2a18efb..62a9bba 100644 Binary files a/memento-note/extension/memento-web-clipper-firefox.xpi and b/memento-note/extension/memento-web-clipper-firefox.xpi differ diff --git a/memento-note/extension/sidepanel.js b/memento-note/extension/sidepanel.js index 287d9af..716975b 100644 --- a/memento-note/extension/sidepanel.js +++ b/memento-note/extension/sidepanel.js @@ -293,11 +293,41 @@ function updateSelectionUI() { } function applySelectionFromMessage(msg) { - if (!msg || msg.url !== pageUrl) return + if (!msg) return + // Tolérance sur la comparaison d'URL : ignorer trailing slash, query, + // fragment. Si pageUrl est vide (panel ouvert avant refreshPageContext), + // on accepte la première sélection qui arrive. + const normalize = (u) => { + try { + const p = new URL(u) + return `${p.origin}${p.pathname.replace(/\/$/, '')}` + } catch { return u || '' } + } + // Si on n'a pas encore de pageUrl mais qu'un message arrive avec URL, + // on l'utilise pour amorcer la fiche page. Très utile quand chrome.tabs.query + // rate (ex. onglet sans permission, side panel non focus, etc.). + if (msg.url && (!pageUrl || normalize(msg.url) !== normalize(pageUrl))) { + if (!pageUrl) { + pageUrl = msg.url + pageTitle = msg.title || pageTitle || '' + try { + const u = new URL(msg.url) + pageDomain = u.hostname + pageFavicon = `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=32` + } catch { /* ignore */ } + } else { + // URL vraiment différente — ignorer. + return + } + } selectionText = msg.text || '' if (msg.dir?.toLowerCase() === 'rtl') pageDir = 'rtl' if (msg.lang) pageLang = msg.lang - if (state === 'idle') updateSelectionUI() + if (state === 'idle') { + updateSelectionUI() + } else { + console.debug('[memento] selection updated while busy, deferring UI') + } } async function refreshPageContext() {