function escapeRegExp(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}

/**
 * Removes excluded text from a Node.
 *
 * @param {Node} target Node to filter.
 * @param {string} exclude CSS selector of nodes to exclude.
 * @returns {DOMString} Text from `target` with text removed.
 */
function filterText(target, exclude) {
    const clone = target.cloneNode(true);  // clone as to not modify the live DOM
    if (exclude) {
        // remove excluded nodes
        clone.querySelectorAll(exclude).forEach(node => node.remove());
    }
    return clone.innerText;
}

// Callback when a copy button is clicked. Will be passed the node that was clicked
// should then grab the text and replace pieces of text that shouldn't be used in output
function formatCopyText(textContent, copybuttonPromptText, isRegexp = false, onlyCopyPromptLines = true, removePrompts = true, copyEmptyLines = true, lineContinuationChar = "", hereDocDelim = "") {
    var regexp;
    var match;

    // Do we check for line continuation characters and "HERE-documents"?
    var useLineCont = !!lineContinuationChar
    var useHereDoc = !!hereDocDelim

    // create regexp to capture prompt and remaining line
    if (isRegexp) {
        regexp = new RegExp('^(' + copybuttonPromptText + ')(.*)')
    } else {
        regexp = new RegExp('^(' + escapeRegExp(copybuttonPromptText) + ')(.*)')
    }

    const outputLines = [];
    var promptFound = false;
    var gotLineCont = false;
    var gotHereDoc = false;
    const lineGotPrompt = [];
    for (const line of textContent.split('\n')) {
        match = line.match(regexp)
        if (match || gotLineCont || gotHereDoc) {
            promptFound = regexp.test(line)
            lineGotPrompt.push(promptFound)
            if (removePrompts && promptFound) {
                outputLines.push(match[2])
            } else {
                outputLines.push(line)
            }
            gotLineCont = line.endsWith(lineContinuationChar) & useLineCont
            if (line.includes(hereDocDelim) & useHereDoc)
                gotHereDoc = !gotHereDoc
        } else if (!onlyCopyPromptLines) {
            outputLines.push(line)
        } else if (copyEmptyLines && line.trim() === '') {
        }
    }

    // If no lines with the prompt were found then just use original lines
    if (lineGotPrompt.some(v => v === true)) {
        textContent = outputLines.join('\n');
    }

    // Remove a trailing newline to avoid auto-running when pasting
    if (textContent.endsWith("\n")) {
        textContent = textContent.slice(0, -1)
    }
    return textContent
}

Déployer et configurer une instance GeoNature (Atelier du 31 mars 2023)

Intégrer des données dans Occtax et Monitoring (Atelier du 30 avril 2025)

Développer un module monitorings GeoNature (Atelier du 25 mai 2023)

Tutos de l’Atelier SI en réseau de Réserves Naturelles de France !

Système d’Informations de Réserves Naturelles de France et son GeoNature (Atelier du 30 novembre 2023)

Windows Sub-System Linux / Environnement virtuel / Flask (Atelier du 9 mars 2023)

Structure par défaut d’un fichier de niveau (site, visite, observation…)

Utiliser les icônes intégrées de QGIS pour égayer ses plugins (PyQGIS Icons Cheatsheet)