Files
2026-08-11 15:41:55 +02:00

152 lines
5.6 KiB
Python

"""Mode "capture de bloc au survol".
Ce script est injecté une seule fois par page (au tout début du chargement,
comme le pont QWebChannel) et reste actif en permanence. Il gère deux façons
indépendantes d'activer la capture, combinées via un petit état :
- "sticky" : activé/désactivé depuis Python (bouton de la barre d'outils
ou raccourci Ctrl+Shift+B) — reste actif jusqu'à nouvelle bascule.
- "ctrlHeld" : activé tant que la touche Ctrl est physiquement maintenue
enfoncée dans la page, désactivé au relâchement (ou si la fenêtre perd
le focus pendant que Ctrl est tenu, pour éviter un blocage).
Le mode est actif dès que l'un des deux est vrai. Quand actif :
1) le bloc de contenu (paragraphe, article, div, item de liste, etc.)
sous le curseur se surligne via un overlay flottant qui ne modifie
pas le DOM de la page ;
2) un clic sur ce bloc empêche la navigation normale et transmet son
texte à Python via le pont QWebChannel (window.pdfscrapperBridge,
voir bridge.py).
Le bloc retenu est le plus proche ancêtre "de type bloc" de l'élément
survolé (via Element.closest) : un <p> si on survole un paragraphe, un
<article>/<div> plus large si on survole une zone qui n'a pas de bloc plus
précis en dessous.
"""
# Script persistant, injecté une fois par page (voir main_window.py :
# QWebEngineScript à InjectionPoint.DocumentCreation). Attention : le
# <body> n'existe pas encore à ce stade, d'où l'accès différé via
# ensureOverlay()/setCursor().
BLOCK_PICKER_INIT_JS = r"""
(function () {
if (window.__pdfscrapperInitialized) { return; }
window.__pdfscrapperInitialized = true;
var BLOCK_SELECTOR = 'p, li, blockquote, h1, h2, h3, h4, h5, h6, pre, td, th, figcaption, article, section, div';
var state = { sticky: false, ctrlHeld: false, active: false };
var prevCursor = '';
var overlay = null;
function ensureOverlay() {
if (overlay || !document.body) { return overlay; }
overlay = document.createElement('div');
overlay.id = '__pdfscrapper_overlay';
overlay.style.position = 'fixed';
overlay.style.pointerEvents = 'none';
overlay.style.zIndex = '2147483647';
overlay.style.background = 'rgba(33, 150, 243, 0.22)';
overlay.style.border = '2px solid #2196f3';
overlay.style.borderRadius = '2px';
overlay.style.boxSizing = 'border-box';
overlay.style.display = 'none';
document.body.appendChild(overlay);
return overlay;
}
function hideOverlay() {
if (overlay) { overlay.style.display = 'none'; }
}
function positionOverlay(el) {
var ov = ensureOverlay();
if (!ov) { return; }
var rect = el.getBoundingClientRect();
ov.style.left = rect.left + 'px';
ov.style.top = rect.top + 'px';
ov.style.width = rect.width + 'px';
ov.style.height = rect.height + 'px';
ov.style.display = 'block';
}
function setCursor(value) {
if (document.body) { document.body.style.cursor = value; }
}
function updateActive() {
var shouldBeActive = state.sticky || state.ctrlHeld;
if (shouldBeActive === state.active) { return; }
state.active = shouldBeActive;
if (state.active) {
prevCursor = document.body ? document.body.style.cursor : '';
setCursor('crosshair');
} else {
setCursor(prevCursor || '');
hideOverlay();
window.__pdfscrapperCurrentBlock = null;
}
}
function findBlock(el) {
if (!el || typeof el.closest !== 'function') { return null; }
var block = el.closest(BLOCK_SELECTOR);
if (!block || block === document.body || block === document.documentElement) {
return null;
}
return block;
}
document.addEventListener('mousemove', function (e) {
if (!state.active) { return; }
var el = findBlock(e.target);
window.__pdfscrapperCurrentBlock = el;
if (!el) { hideOverlay(); return; }
positionOverlay(el);
}, true);
document.addEventListener('click', function (e) {
if (!state.active) { return; }
var el = window.__pdfscrapperCurrentBlock || findBlock(e.target);
if (!el) { return; }
e.preventDefault();
e.stopPropagation();
var text = (el.innerText || el.textContent || '').trim();
if (!text || !window.pdfscrapperBridge) { return; }
window.pdfscrapperBridge.captureBlock(text);
}, true);
document.addEventListener('keydown', function (e) {
if (e.key === 'Control') {
state.ctrlHeld = true;
updateActive();
}
}, true);
document.addEventListener('keyup', function (e) {
if (e.key === 'Control') {
state.ctrlHeld = false;
updateActive();
}
}, true);
// Filet de sécurité : si la fenêtre perd le focus pendant que Ctrl est
// tenu (ex. Alt-Tab), on ne recevra jamais le keyup correspondant.
window.addEventListener('blur', function () {
state.ctrlHeld = false;
updateActive();
});
// Appelé depuis Python (bouton de la barre d'outils / Ctrl+Shift+B).
window.__pdfscrapperSetSticky = function (value) {
state.sticky = !!value;
updateActive();
};
})();
"""
# Appelé après chaque chargement de page pour reporter l'état "sticky"
# (activé via le bouton/raccourci) sur la nouvelle page — l'état JS
# précédent ne survit pas à une navigation.
SET_STICKY_JS_TEMPLATE = "window.__pdfscrapperSetSticky && window.__pdfscrapperSetSticky({value});"