point-median-extension/content.js
François Pelletier 9840633305 feat: Implémentation de la correction via le menu contextuel et améliorations de la construction
-  Ajout de la fonctionnalité de correction via le menu contextuel.
- 🗑️ Suppression de la fonctionnalité de correction en temps réel.
-  Ajout du script de fond (background.js) pour gérer le menu contextuel.
- 📝 Mise à jour du manifest.json (permissions, service_worker, description).
- 🐛 Correction de la logique de remplacement de texte dans content.js pour une meilleure compatibilité.
- 🏗️ Mise à jour du Dockerfile pour construire l'extension Chrome dans un répertoire (non zippé).
- 🛠️ Mise à jour du script build.sh pour gérer le nouveau format de sortie et nettoyer le répertoire.
- 🌐 Traduction du manifest.json en français.
- 📄 Ajout d'un README.md complet avec les instructions d'installation.
- 🐛 Ajout de logs pour faciliter le débogage.
2025-07-01 07:16:08 -04:00

122 lines
6.8 KiB
JavaScript

/**
* This content script handles the conversion of selected text to use the inclusive middle dot.
* It receives messages from the background script to perform the conversion.
*/
/**
* Converts text to use the inclusive middle dot (·).
* This function contains the original conversion logic.
* @param {string} texte The input text.
* @returns {string} The converted text.
*/
function convertirPointMedian(texte) {
let currentText = texte;
const sep = /[.\-]/; // Recognizes '.' or '-' as separators
// Rules with 3 components (e.g., "mot.e.s")
const regles3 = [
{ find: new RegExp(`(\\w+)${sep.source}(\\w+[es]*)${sep.source}([es]+)`, 'g'), replace: '$1·$2·$3' },
{ find: new RegExp(`([td]eur)${sep.source}([td]rice)${sep.source}([s]+)`, 'g'), replace: '$1·$2·$3' },
{ find: new RegExp(`(eur)${sep.source}(ice|euse)${sep.source}([s]+)`, 'g'), replace: '$1·$2·$3' },
{ find: new RegExp(`(er)${sep.source}(ère)${sep.source}([s]+)`, 'g'), replace: '$1·$2·$3' },
{ find: new RegExp(`([fs])${sep.source}([fs]{2}e)${sep.source}([s]+)`, 'g'), replace: '$1·$2·$3' },
{ find: new RegExp(`([eo][lnt])${sep.source}([eo]?[lnt]+e)${sep.source}([s]+)`, 'g'), replace: '$1·$2·$3' }
];
regles3.forEach(rule => { currentText = currentText.replace(rule.find, rule.replace); });
// Rules with 2 components (e.g., "mot.s")
const regles2 = [
{ find: new RegExp(`(\\w+)${sep.source}([es]+)`, 'g'), replace: '$1·$2' },
{ find: new RegExp(`([td]eur[s]?)${sep.source}([td]?rice[s]?)`, 'g'), replace: '$1·$2' },
{ find: new RegExp(`(eur[s]?)${sep.source}(euse[s]?|ice[s]?)`, 'g'), replace: '$1·$2' },
{ find: new RegExp(`(eux|el)${sep.source}(elle[s]?|euse[s]?|le[s]?)`, 'g'), replace: '$1·$2' },
{ find: new RegExp(`(aux|al)${sep.source}(ale[s]?)`, 'g'), replace: '$1·$2' },
{ find: new RegExp(`(er[s]?)${sep.source}(ère[s]?)`, 'g'), replace: '$1·$2' },
{ find: new RegExp(`([s])${sep.source}([s]{2}e[s]?)`, 'g'), replace: '$1·$2' },
{ find: new RegExp(`([eo][flnt][s]?)${sep.source}([eo]?[flnt]+e[s]?)`, 'g'), replace: '$1·$2' },
{ find: new RegExp(`(\\w+e)${sep.source}(\\w+a)`, 'g'), replace: '$1·$2' },
{ find: new RegExp(`(\\w+t)${sep.source}(te)`, 'g'), replace: '$1·$2' }
];
regles2.forEach(rule => { currentText = currentText.replace(rule.find, rule.replace); });
// Rules using parentheses (e.g., "mot(s)")
const reglesp = [
{ find: /(\w+)[\(]([es]+)[\)]/g, replace: '$1·$2' },
{ find: /([td]eur[s]?)[\(]([td]?rice[s]?)[\)]/g, replace: '$1·$2' },
{ find: /(eur[s]?)[\(](euse[s]?|ice[s]?)[\)]/g, replace: '$1·$2' },
{ find: /(eux|el)[\(](elle[s]?|euse[s]?|le[s]?)[\)]/g, replace: '$1·$2' },
{ find: /(aux|al)[\(](ale[s]?)[\)]/g, replace: '$1·$2' },
{ find: /(er[s]?)[\(](ère[s]?)[\)]/g, replace: '$1·$2' },
{ find: /([s])[\(]([s]{2}e[s]?)[\)]/g, replace: '$1·$2' },
{ find: /([eo][flnt][s]?)[\(]([eo]?[flnt]+e[s]?)[\)]/g, replace: '$1·$2' },
{ find: /(\w+e)[\(](\w+a)[\)]/g, replace: '$1·$2' },
{ find: /(\w+t)[\(](te)[\)]/g, replace: '$1·$2' }
];
reglesp.forEach(rule => { currentText = currentText.replace(rule.find, rule.replace); });
return currentText;
}
// Listen for messages from the background script.
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
console.log("Content script: Message received.", request);
if (request.action === "convertSelectedText" && request.text) {
console.log(`Content script: Converting text: "${request.text}"`);
const convertedText = convertirPointMedian(request.text);
console.log(`Content script: Converted text: "${convertedText}"`);
// Try to use document.execCommand for better compatibility with contenteditable elements
// and to maintain undo/redo history.
if (document.execCommand('insertText', false, convertedText)) {
console.log("Content script: Text replaced using execCommand.");
// If execCommand was successful, dispatch an input event to notify frameworks.
const activeElement = document.activeElement;
if (activeElement) {
activeElement.dispatchEvent(new Event('input', { bubbles: true }));
console.log("Content script: Dispatched input event.");
}
sendResponse({ success: true, newText: convertedText });
} else {
// Fallback for elements where execCommand might not work or for direct text selections.
console.log("Content script: execCommand failed, falling back to window.getSelection.");
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
range.deleteContents(); // Remove the selected text
range.insertNode(document.createTextNode(convertedText)); // Insert the new text
console.log("Content script: Text replaced using window.getSelection.");
// Manually dispatch an input event if the target is an editable element
// and execCommand didn't handle it.
const activeElement = document.activeElement;
if (activeElement && (activeElement.isContentEditable ||
activeElement.tagName.toLowerCase() === 'textarea' ||
(activeElement.tagName.toLowerCase() === 'input' && /text|search|email|url|tel/.test(activeElement.type)))) {
activeElement.dispatchEvent(new Event('input', { bubbles: true }));
console.log("Content script: Dispatched input event (fallback).");
}
sendResponse({ success: true, newText: convertedText });
} else {
console.log("Content script: No selection found for fallback replacement.");
sendResponse({ success: false, error: "No selection found." });
}
}
// After replacement, clear the selection to prevent accidental re-insertion
// or issues with the "buffer" as described by the user.
const selection = window.getSelection();
if (selection) {
selection.empty(); // For Chrome/Safari
if (selection.removeAllRanges) { // For Firefox
selection.removeAllRanges();
}
console.log("Content script: Selection cleared.");
}
} else {
console.log("Content script: Message not for text conversion or missing text.", request);
sendResponse({ success: false, error: "Invalid request." });
}
// Return true to indicate that sendResponse will be called asynchronously.
return true;
});