Assign Interaction: autofill

Infer information for the interaction you're assigning.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Assign Interaction: autofill
// @namespace    https://github.com/nate-kean/
// @version      2026.5.10
// @description  Infer information for the interaction you're assigning.
// @author       Nate Kean
// @match        https://jamesriver.fellowshiponego.com/members/view/*
// @match        https://jamesriver.fellowshiponego.com/members/family/*
// @match        https://jamesriver.fellowshiponego.com/members/timeline/*
// @match        https://jamesriver.fellowshiponego.com/members/giving/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=fellowshiponego.com
// @require      https://update.greasyfork.org/scripts/559387/1730426/getRelativeWeekday.js
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    const actionSpan = document
        .querySelector("#aid") // TODO: this could be condensed in to one, smarter selector
        .parentElement
        .querySelector(".chosen-container > a > span");
    const completeByDateInput = document.querySelector("#completeByDate");
    const today = new Date();

    const instructionsInput = document.querySelector("#instructions");

    const firstName = () => document.querySelector(".pg-title").textContent.trim().split(" ")[0];

    function campus() {
        let campusName;
        const additionalDetailsEntries = document.querySelectorAll(".other-panel > .panel-body > .info-right-column > .other-details");
        for (const entry of additionalDetailsEntries) {
            if (entry.textContent.includes("Campus")) {
                return entry.textContent.trim().split(" ")[0];
            }
        }
        return "__";
    }

    function firstVisitDate() {
        let campusName;
        const additionalDetailsEntries = document.querySelectorAll(".date-panel > .panel-body > .info-right-column > .date-details");
        for (const entry of additionalDetailsEntries) {
            if (entry.textContent.includes("First Visit Date")) {
                return entry.textContent.trim();
            }
        }
        return "__";
    }

    function friday() {
        // This Friday on Sun, Mon, Tue, Wed, Sat; next Friday on Thu, Fri
        const fridayDate = getRelativeWeekday(today, DateDir.THIS, Day.FRIDAY, false);
        if ([Day.THURSDAY, Day.FRIDAY].includes(today.getDay())) {
            // If it's already Thursday or Friday, make it next Friday
            fridayDate.setDate(fridayDate.getDate() + 7);
        }
        return fridayDate;
    }

    function whose() {
        // Determine possessive adjective to use based on gender
        for (const p of document.querySelectorAll(".family-name > p")) {
            switch (p.textContent) {
                case "Male":
                    return "his";
                case "Female":
                    return "her";
            }
        }
        return "their";
    }

    function familyName() {
        // Scrape or request own name and spouse's name, if applicable
        const path = window.location.pathname.split("/");
        const page = path.at(-2);
        const uid = path.at(-1);
        switch (page) {
            case "view": {
                const familyMembers = document.querySelectorAll(".family-panel .family-item .family-item-details");

                const primary = familyMembers[0];
                const primaryName = primary.querySelector("a").textContent.trim().split(" ")[0];

                const spouse = familyMembers[1];
                if (spouse === undefined) return primaryName;
                const role = spouse.querySelector("h5").textContent.trim();
                if (role === "Wife" || role === "Partner") {
                    const spouseName = spouse.querySelector("a").textContent.trim().split(" ")[0];
                    return `${primaryName} & ${spouseName}`;
                }
                return primaryName;
            }
            case "family":
                // TODO: scrape another way
                return "__";
                //break;
            default:
                // TODO: request
                return "__";
        }
    }


    function connectionPoint() {
        return "__";
    }


    function experienceType() {
        // TODO: the one with the latest date
        return "__";
    }


    function generateAutofillContent(actionName) {
        switch (actionName) {
            case "New Visitor Connections Follow-Up":
                return {
                    date: today,
                    instructions: `${campus().toUpperCase()}/TEXT - Please send ${familyName()} a welcome text by 5pm. Thank you!`,
                };
            case "New Visitor Follow-Up: Cookie Visit":
                return {
                    date: today,
                    instructions: `COOKIE VISIT • ${connectionPoint()} • First visited ${firstVisitDate()}`,
                };
            case "New Visitor Follow- Up: Phone Call":
                return {
                    date: friday(),
                    instructions: `PHONE CALL • ${connectionPoint()} • First visited ${firstVisitDate()}`,
                };
            case "Altar Follow-Up": {
                return {
                    date: friday(),
                    instructions: `Please follow up with ${firstName()} regarding ${whose()} ${experienceType()} this week. Thank you!`,
                };
            }
            case "Discipleship":
                return {
                    date: getRelativeWeekday(today, DateDir.THIS, Day.TUESDAY, false),
                    instructions: `Please follow up with ${firstName()} regarding ${whose()} baptism this week. Thank you!`,
                }
        }
        return {
            date: null,
            instructions: null,
        };
    }


    function formatDate(date) {
        const month = String(date.getMonth() + 1).padStart(2, "0");
        const day = String(date.getDate()).padStart(2, "0");
        const year = date.getFullYear();
        return `${month}/${day}/${year}`;
    }


    function onMutation(records) {
        const content = generateAutofillContent(actionSpan.textContent);
        if (completeByDateInput.value === "" && content.date !== null) {
            completeByDateInput.value = formatDate(content.date);
        }
        if (instructionsInput.value === "" && content.instructions !== null) {
            instructionsInput.value = content.instructions;
        }
    }


    const observer = new MutationObserver(onMutation);
    const options = {
        subtree: false,
        childList: true,
        attributes: false,
        characterData: true,
        characterDataOldValue: false,
    };
    observer.observe(actionSpan, options);
})();