Greasy Fork is available in English.
Make invalid dates block saving instead of throwing away progress.
// ==UserScript==
// @name Add/Edit: Validate dates
// @namespace https://github.com/nate-kean/
// @version 2026.1.29.2
// @description Make invalid dates block saving instead of throwing away progress.
// @author Nate Kean
// @match https://jamesriver.fellowshiponego.com/members/add*
// @match https://jamesriver.fellowshiponego.com/members/edit/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=fellowshiponego.com
// @grant none
// @license MIT
// ==/UserScript==
(function() {
document.head.insertAdjacentHTML("beforeend", `
<style id="nates-date-validation-css-fixes">
.input-holder,
/* Victim of CSS specificity arms race */
#memberEditForm #existingGroupsHolder .form-group.group-dates-holder {
border: none !important;
&:focus-within {
border: none !important;
}
& > input.form-control.hasDatepicker,
& input#memberEditSearchGroups {
border-width: 1px;
border-style: solid;
border-color: #d8d8d8;
margin-left: 0;
&.dateYear {
padding-left: 0 !important;
}
/* Red border if invalid AND not empty */
/* (Empty should not count as invalid */
&:invalid:not(:placeholder-shown) {
border-color: #fd5b63 !important;
}
&:focus {
border: 2px solid #176bfb !important;
}
}
}
input#memberEditSearchGroups {
padding-left: 26px !important;
}
.group-dates-holder {
& input.hasDatepicker {
width: 132px !important;
}
& i {
right: 23px !important;
}
}
.date-holder .input-holder i {
top: 36px;
}
/* Insanely specific selector from F1 Go that I have to make extra effort to compete with */
#memberEditForm .group-dates .input-holder:focus-within input {
border: 2px solid #176bfb !important;
}
</style>
`);
function patch(dateField) {
dateField.pattern = "\\d{2}\\/\\d{2}\\/\\d{4}";
dateField.addEventListener("keyup", () => {
// Make invalid input fields temporarily required so that the user agent
// prevents the form from being submitted until they're corrected
dateField.required = dateField.value.length > 0 && !dateField.checkValidity();
});
dateField.placeholder = dateField.placeholder || " ";
}
// Patch the page's date fields
const form = document.querySelector("form#memberEditForm");
for (const dateField of document.querySelectorAll("input.hasDatepicker")) {
patch(dateField);
}
// Patch jQuery to run requestSubmit() instead of submit() on the page's form
// so that the validation checks get run
const patchedForm = $(form);
patchedForm.submit = () => {
form.requestSubmit();
};
const jq = $;
window.$ = new Proxy(jq, {
apply(target, thisArg, args) {
if (args[0] === "#memberEditForm") return patchedForm;
return Reflect.apply(target, thisArg, args);
},
get(...args) {
return Reflect.get(...args);
},
});
// Patch new date fields as they're generated in the Add Groups section
const observer = new MutationObserver(records => {
for (const record of records) {
for (const node of record.addedNodes) {
const dateField = node.querySelector(".hasDatepicker");
if (dateField === null) continue;
patch(dateField);
}
}
});
observer.observe(document.querySelector("#memberEditGroupHolder"), {
subtree: true,
childList: true,
});
})();