您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds additional functionality to the UK General Register Office (GRO) BMD index search
当前为
// ==UserScript== // @name GRO Index Search Helper // @description Adds additional functionality to the UK General Register Office (GRO) BMD index search // @namespace cuffie81.scripts // @match https://www.gro.gov.uk/gro/content/certificates/indexes_search.asp* // @version 1.18 // @grant GM_listValues // @grant GM_getValue // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.2.0/handlebars.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js // ==/UserScript== this.$ = this.jQuery = jQuery.noConflict(true); $(function() { let resources, recordType, results; var main = function() { // Register Handlebars helper operators Handlebars.registerHelper({ eq: function (v1, v2) { return v1 === v2; }, ne: function (v1, v2) { return v1 !== v2; }, lt: function (v1, v2) { return v1 < v2; }, gt: function (v1, v2) { return v1 > v2; }, lte: function (v1, v2) { return v1 <= v2; }, gte: function (v1, v2) { return v1 >= v2; }, and: function () { return Array.prototype.slice.call(arguments).every(Boolean); }, or: function () { return Array.prototype.slice.call(arguments, 0, -1).some(Boolean); } }); buildResources(); recordType = getRecordType(); //console.log("resources:\r\n%s", JSON.stringify(resources)); // Load the general css $("body").append($(resources.baseStyle)); initialiseSearchForm(); initialiseResultViews(); // Scroll down to the form. Do this last as we may add/remove/change elements in the previous calls. $("h1:contains('Search the GRO Online Index')")[0].scrollIntoView(); // Wire up accesskeys to clicks, to avoid having to use the full accesskey combo (eg ALT+SHFT+#) $(document).on("keypress", function(e) { if (!document.activeElement || document.activeElement.tagName.toLowerCase() !== "input") { let char = String.fromCharCode(e.which); //console.log("keypress: %s", char); if ($("*[id^='groish'][accesskey='" + char + "']").length) $("*[id^='groish'][accesskey='" + char + "']").click(); else if (char == "{") adjustSearchYear(-10); else if (char == "}") adjustSearchYear(10); else if (char == "?") $("form[name='SearchIndexes'] input[type='submit']").click(); else if (char == '@') switchRecordType(); } }); } var initialiseSearchForm = function() { // Hide superfluous spacing, text and buttons $("form[name='SearchIndexes'] input[type='submit'][value='Reset']").hide(); $("form[name='SearchIndexes'] a.tooltip").hide(); $("form[name='SearchIndexes'] span.main_text").has("i > a[href^='most_customers_want_to_know.asp']").hide(); $("form[name='SearchIndexes'] a:contains('FreeBMD')").hide(); $("form[name='SearchIndexes'] a:contains('(used 1837')").hide(); $("form[name='SearchIndexes'] td.main_text[colspan='5'] > br").closest("tr").hide(); $("form[name='SearchIndexes'] td.main_text[colspan='5'] > strong").closest("tr").hide(); $("form[name='SearchIndexes'] #SurnameMatchesText").closest("tr").hide(); $("form[name='SearchIndexes'] #ForenameMatchesText").closest("tr").hide(); $("form[name='SearchIndexes'] #MothersMaidenSurnameMatchesText").closest("tr").hide(); // Change text $("form[name='SearchIndexes'] td span.main_text:contains('year(s)')").text("yrs"); $("form[name='SearchIndexes'] td.main_text:contains('Surname at Death:')").html("Surname:<span class='redStar'>*</span>"); $("form[name='SearchIndexes'] td.main_text:contains('First Forename at Death:')").text("Forename 1:"); $("form[name='SearchIndexes'] td.main_text:contains('Second Forename at Death:')").text("Forename 2:"); $("form[name='SearchIndexes'] td.main_text:contains('District of Death:')").text("District:"); $("form[name='SearchIndexes'] td.main_text:contains('Age at'):contains('Death'):contains('in years')").text("Age:"); $("form[name='SearchIndexes'] td.main_text:contains('Surname at Birth:')").html("Surname:<span class='redStar'>*</span>"); $("form[name='SearchIndexes'] td.main_text:contains('First Forename:')").text("Forename 1:"); $("form[name='SearchIndexes'] td.main_text:contains('Second Forename:')").text("Forename 2:"); $("form[name='SearchIndexes'] td.main_text:contains('Maiden Surname:')").text("Mother:"); $("form[name='SearchIndexes'] td.main_text:contains('District of Birth:')").text("District:"); $("form[name='SearchIndexes'] td.main_text:contains('Register No:')").text("Register:"); $("form[name='SearchIndexes'] td.main_text:contains('Entry'):contains('No:')").text("Entry:"); $("form[name='SearchIndexes'] a:contains('View list of registration districts')").text("Districts"); // Add gender and year navigation buttons, and style them let searchButton = $("form[name='SearchIndexes'] input[type='submit'][value='Search']"); $(searchButton).attr("accesskey", "?"); $(searchButton).parent().find("br").remove(); $("<input type='button' class='formButton' accesskey='#' id='groish_BtnToggleGender' value='Gender' />").insertBefore($(searchButton)); $("<input type='button' class='formButton' accesskey='[' id='groish_BtnYearsPrev' value='< Years' />").insertBefore($(searchButton)); $("<input type='button' class='formButton' accesskey=']' id='groish_BtnYearsNext' value='Years >' />").insertBefore($(searchButton)); let buttonContainer = $("form[name='SearchIndexes'] input[type='submit'][value='Search']").closest("td").addClass("groish_ButtonContainer"); // Add button event handlers $("input#groish_BtnYearsPrev").click(function() { navigateYears(false); }); $("input#groish_BtnYearsNext").click(function() { navigateYears(true); }); $("input#groish_BtnToggleGender").click(function() { toggleGender(); }); // Set encoding if (typeof $("form[name='SearchIndexes']").attr("accept-charset") === typeof undefined) { $("form[name='SearchIndexes']").attr("accept-charset", "UTF-8"); } } var initialiseResultViews = function() { // Move default results table into a view container let defaultTable = $("form[name='SearchIndexes'] h3:contains('Results:')").closest("table").css("width", "100%").addClass("groish_ResultsTable"); $(defaultTable).before($("<div results-view='default' />")); let defaultView = $("div[results-view='default']"); $(defaultView).append($("table.groish_ResultsTable")); // Move header row to before default view $(defaultView).before($("<div class='groish_ResultsHeader' style='margin: 10px 0px; position: relative' />")); $(".groish_ResultsHeader").append($("table.groish_ResultsTable h3:contains('Results:')")); // Move pager row contents to after default view $(defaultView).after($("table.groish_ResultsTable > tbody > tr:last table:first")); $("div[results-view='default'] + table").css("width", "100%").addClass("groish_ResultsInfo"); // Get results, sort them and populate views results = getResults(recordType); sortResults(); populateAlternateViews(); } var sortResults = function(reverse, sortFieldsCsv) { //console.log("sorting results, sort fields: %s", sortFieldsCsv); if (!results || !results.items) return; let defaultSortFields = "year,quarter"; // Get the last sort fields and order for the record type let sortFieldsKey = recordType + "-sort-fields"; let sortOrderKey = recordType + "-sort-order"; let lastSortFields = sessionStorage.getItem(sortFieldsKey); let lastSortOrder = sessionStorage.getItem(sortOrderKey); // Cleanup values sortFieldsCsv = (sortFieldsCsv || "").replace(/\s\s+/g, ' '); lastSortFields = (lastSortFields || "").replace(/\s\s+/g, ' '); //console.log("last sort fields: %s; last sort order: %s", lastSortFields, lastSortOrder); let sortOrder = "asc"; if (!sortFieldsCsv) { sortFieldsCsv = lastSortFields || defaultSortFields; sortOrder = lastSortOrder || "asc"; } else if (sortFieldsCsv.localeCompare(lastSortFields) == 0 && sortOrder.localeCompare(lastSortOrder) == 0 && reverse) { sortOrder = "desc"; } // Build sort fields and order arrays let sortFields = sortFieldsCsv.split(","); let sortOrders = Array.apply(null, Array(sortFields.length)).map(String.prototype.valueOf, sortOrder); // Append defaults if needed if (sortFieldsCsv.localeCompare(defaultSortFields) != 0) { sortFields.push("year"); sortFields.push("quarter"); sortOrders.push("asc"); sortOrders.push("asc"); } //console.log("sorting results by: %s (%s)", sortFields, sortOrders); results.items = _.orderBy(results.items, sortFields, sortOrders); sessionStorage.setItem(sortFieldsKey, sortFieldsCsv); sessionStorage.setItem(sortOrderKey, sortOrder); } var populateAlternateViews = function() { // Add alternate view(s) if (recordType && resources && results && results.items && results.items.length > 0) { // Remove any existing views $("div[results-view][results-view!='default']").remove(); // Add alternate views //console.log("Adding alternate views..."); let viewPrefix = "view_" + recordType; // record type = EW_Birth, EW_Death for (let resourceName in resources) { let resourceNamePrefix = resourceName.substring(0, viewPrefix.length); if (resources.hasOwnProperty(resourceName) && viewPrefix.localeCompare(resourceNamePrefix) == 0) { let template = resources[resourceName].toString(); let compiledTemplate = Handlebars.compile(template); let html = compiledTemplate(results); if (html) { $("div[results-view]").filter(":last").after($(html)); //console.log("Added alternate view"); } } } // Add view helpers and event handlers, if not already added if ($("div[results-view]").length > 1) { // Add event handler to hide/show actions row // TODO: Make adding view event handlers more dynamic, so they can be specific to the view $("div[results-view][results-view!='default'] tbody tr.rec") .off("click.groish") .on("click.groish", function(event) { event.preventDefault(); $(this).next("tr.rec-actions:not(:empty)").toggle(); } ); // Add event handler for column sorting $("div[results-view][results-view!='default'] thead td[sort-fields]") .off("click.groish") .on("click.groish", function(event) { event.preventDefault(); //let defaultSortFields = ($(this).closet("div[results-view]").attr("default-sort-fields"); let sortFields = ($(this).attr("sort-fields") ? $(this).attr("sort-fields") : $(this).text()); sortResults(true, sortFields); populateAlternateViews(); } ); // Add view switcher, if it doesn't already exist if ($("#groish_ViewSwitcher").length == 0) { $(".groish_ResultsHeader").append($("<a href='#' id='groish_ViewSwitcher' class='main_text' accesskey='~'>Switch view</a>")); $("#groish_ViewSwitcher").off("click.groish").on("click.groish", function() { switchResultsView(); return false; }); // Add results copier (if supported) if (window.getSelection && document.createRange) { $(".groish_ResultsHeader").append($("<a href='#' id='groish_ResultsCopier' class='main_text' accesskey='|'>Copy results</a>")); $("#groish_ResultsCopier") .off("click.groish") .on("click.groish", function(event) { event.preventDefault(); // Get most specific element containing results, typically a table body let resultsContent = $("div[results-view]:visible tbody"); if (resultsContent.length == 0) resultsContent = $("div[results-view]:visible"); if (resultsContent.length > 0) { resultsContent = resultsContent[0]; let selection = window.getSelection(); let range = document.createRange(); range.selectNodeContents(resultsContent); selection.removeAllRanges(); selection.addRange(range); try { if (document.execCommand("copy")) { selection.removeAllRanges(); $(".groish_Message").text("Results copied to clipboard").show(); setTimeout(function() { $(".groish_Message").fadeOut(); }, 3000); } } catch(e) { } } return false; }); } } } // Show the last used view let viewName = sessionStorage.getItem("groish_view." + recordType); //console.log("initialising view: %s", viewName); if (viewName && $("div[results-view='" + viewName + "']:hidden").length == 1) { //console.log("setting active view: %s", viewName); $("div[results-view][results-view!='" + viewName + "']").hide(); $("div[results-view][results-view='" + viewName + "']").show(); } } } var switchResultsView = function() { let views = $("div[results-view]"); if (views.length > 1) { let curIndex = -1; $(views).each(function(index) { if ($(this).css("display") != "none") curIndex = index; }); //console.log("current view index: %s", curIndex); if (curIndex !== -1) { let newIndex = ((curIndex == (views.length-1)) ? 0 : curIndex+1); $(views).hide(); $("div[results-view]:eq(" + newIndex + ")").show(); $(".groish_Message").hide(); // Get the name and save it let viewName = $("div[results-view]:eq(" + newIndex + ")").attr("results-view") sessionStorage.setItem("groish_view." + recordType, viewName); //save it //console.log("new view: %s", viewName); } } } var getResults = function(recordType) { let results = { "ageWarningThreshold": 24, "items": [], "failures": [] }; // Lookup record type - birth or death if (recordType !== null && (recordType === "EW_Birth" || recordType === "EW_Death")) { let gender = $("form[name='SearchIndexes'] select#Gender").val(); let year = parseInt($("form[name='SearchIndexes'] select#Year").val(), 10); let dataFormat = (year >= 1993 ? 1993 : (year >= 1984 ? 1984 : 1837)); // Save the data format results["dataFormat" + dataFormat] = true; $("div[results-view='default'] > table > tbody > tr") .has("input[type='radio'][name='SearchResult']") .each(function(index) { try { //console.log("Parsing record (%d)...", index); let quarterNames = [ "Mar", "Jun", "Sep", "Dec" ]; // Get result id, contains year and record id let recordId = null; let resultId = $(this).find("input[type='radio'][name='SearchResult']:first").val(); if (resultId && resultId.length > 5 && resultId.indexOf('.') == 4) recordId = resultId.substring(5); // Get names and reference let names = $(this).find("td:eq(1)").text().replace(/\u00a0/g, " ").replace(/\s\s+/g, ' ').trim(); let ref = $(this).next().find("td:eq(0)").text(); // Clean up reference ref = ref.replace(/\u00a0/g, " "); ref = ref.replace(/\s\s+/g, ' '); ref = ref.replace(/GRO Reference: /g, ""); ref = ref.replace(/M Quarter in/g, "Q1"); ref = ref.replace(/J Quarter in/g, "Q2"); ref = ref.replace(/S Quarter in/g, "Q3"); ref = ref.replace(/D Quarter in/g, "Q4"); ref = ref.replace(/Order this entry as a:/g, ""); ref = ref.replace(/Entry Number(:|)/gi, "Entry"); ref = ref.replace(/Occasional Copy(:|)/gi, "Copy"); ref = ref.replace(/^DOR /gi, ""); ref = ref.replace(/ Union /gi, " "); if (/(((-|Q[1-9])\/[0-9]{4}) in )/gi.test(ref)) ref = ref.replace(/(((-|Q[1-9])\/[0-9]{4}) in )/gi, "$2 "); ref = ref.replace(/\s\s+/g, ' '); ref = ref.trim(); // Parse forenames, surname let namesArr = /([a-z' -]+),([a-z' -]*)/gi.exec(names); //console.log("index: %d, namesArr: %s", index, namesArr); // Parse mother's maiden name let mother = null; if (recordType === "EW_Birth") mother = toTitleCase($(this).find("td:eq(2)").text().replace(/\u00a0/g, " ").replace(/\s\s+/g, ' ')).trim(); // Initialise record let record = { "recordId": recordId, "ref": ref, "gender": gender, "forenames": toTitleCase(namesArr[2]).trim(), "surname": toTitleCase(namesArr[1]).trim(), "age": null, "yob": null, "birth": null, "mother": mother, "actions": [] }; // Parse reference // TODO: Use named capture groups when widely supported in browsers let refPatterns = [ { // 1937 Q3 NORTHAMPTON Volume 03B Page 32 "pattern": "([0-9]{4}) Q([1-4]) ([a-z\\.\\-,\\(\\)0-9\\&'\\/ ]*) (Volume ([a-z0-9]+)) (Page ([0-9a-z]+))( Copy ([0-9a-z]+)|)", "indexes": { "year": 1, "quarter": 2, "district": 3, "volume": 5, "page": 7, "copy": 9 } }, { // 1937 Q3 NORTHAMPTON Volume 03B "pattern": "([0-9]{4}) Q([1-4]) ([a-z\\.\\-,\\(\\)0-9\\&'\\/ ]*) (Volume ([a-z0-9]+))( Copy: ([0-9a-z]+)|)", "indexes": { "year": 1, "quarter": 2, "district": 3, "volume": 5, "copy": 7 } }, { // DOR -/1992 NORTHAMPTON (6701C) Volume 7 Page 2375 Entry Number 126 "pattern": "(-|Q([1-4]))\\/([0-9\\-]{1,4}) ([a-z\\.\\-,\\(\\)0-9\\&'\\/ ]*) (Volume ([a-z0-9]+)) (Page ([0-9a-z]+)) (Entry ([0-9a-z]+))( Copy ([0-9a-z]+)|)", "indexes": { "quarter": 2, "year": 3, "district": 4, "volume": 6, "page": 8, "entry": 10, "copy": 12 } }, { // DOR Q4/1984 NORTHAMPTON (6701B) Volume 7 Page 2456 "pattern": "(-|Q([1-4]))\\/([0-9\\-]{1,4}) ([a-z\\.\\-,\\(\\)0-9\\&'\\/ ]*) (Volume ([a-z0-9]+)) (Page ([0-9a-z]+))( Copy ([0-9a-z]+)|)", "indexes": { "quarter": 2, "year": 3, "district": 4, "volume": 6, "page": 8, "copy": 10 } }, { // DOR Q2/2000 Northampton (6701A) Reg A59B Entry Number 96 "pattern": "(-|Q([1-4]))\\/([0-9\\-]{1,4}) ([a-z\\.\\-,\\(\\)0-9\\&'\\/ ]*) (Reg ([a-z0-9]+)) (Entry ([0-9a-z]+))( Copy ([0-9a-z]+)|)", "indexes": { "quarter": 2, "year": 3, "district": 4, "reg": 6, "entry": 8, "copy": 10 } }, { // DOR Q2/2000 Northampton (6701A) Entry Number 96 "pattern": "(-|Q([1-4]))\\/([0-9\\-]{1,4}) ([a-z\\.\\-,\\(\\)0-9\\&'\\/ ]*) (Entry ([0-9a-z]+))( Copy ([0-9a-z]+)|)", "indexes": { "quarter": 2, "year": 3, "district": 4, "entry": 6, "copy": 8 } } ]; for (let p of refPatterns) { let re = new RegExp(p.pattern, "gi"); let result = re.exec(ref); if (result) { if (p.indexes) { for (const [key, value] of Object.entries(p.indexes)) { //console.log("index: %d, name: %s, value: %s", value, key, result[value]); record[key] = (result && result.length > value && result[value]) ? result[value] : null; } } break; } } // Set format let recordYear = (record.year ? record.year : year); let recordDataFormat = (recordYear >= 1993 ? 1993 : (recordYear >= 1984 ? 1984 : 1837)); record["dataFormat"] = recordDataFormat; results["dataFormat" + recordDataFormat] = true; // Parse age and year of birth if (recordType === "EW_Death") { if (record.dataFormat == 1837) { let ageArr = /^([0-9]{1,3})$/.exec($(this).find("td:eq(2)").text().replace(/\u00a0/g, " ").replace(/\s\s+/g, ' ').trim()); if (ageArr) record.age = parseInt(ageArr[1], 10); } else if (record.dataFormat >= 1984) { let yobArr = /^([0-9]{4})$/.exec($(this).find("td:eq(2)").text().replace(/\u00a0/g, " ").replace(/\s\s+/g, ' ').trim()); if (yobArr) record.yob = parseInt(yobArr[1], 10); } } // Tidy up data... // Tidy up strings if (record.district) { record.district = toTitleCase(record.district); } for (let key of [ "forenames", "surname", "district", "volume", "page", "reg", "entry", "copy" ]) { if (record[key]) { record[key] = record[key].trim(); } } // Tidy up integers for (let key of [ "age", "yob", "birth", "quarter", "year" ]) { if (record[key]) { record[key] = parseInt(record[key], 10); if (isNaN(record[key])) record[key] = null; } } // Set calculated data if (record.yob && record.yob > 0) { record.birth = record.yob; if (record.year && record.year > 0) record.age = record.year - record.yob; } else if (record.age != null && record.year && record.year > 0) { record.birth = record.year - record.age; } record.noForenames = (!record.forenames || record.forenames == "-"); record.ageWarning = (record.age && record.age > 0 && record.age <= results.ageWarningThreshold); record.quarterName = ((record.quarter && record.quarter >=1 && record.quarter <= 4) ? quarterNames[record.quarter-1] : null); //console.log("resultId: %s, record.recordId: %s, record.year: %s, recordType: %s", resultId, record.recordId, record.year, recordType); // Determine what actions are supported for the record and add them if (record.recordId && record.year && recordType) { // Define possible actions let actions = [ { "text": "Order Certificate", "url": null, "itemType": "Certificate", "pdfStatus": 0, "selector": "img[src$='order_certificate_button.gif']" }, { "text": "Order PDF", "url": null, "itemType": "PDF", "pdfStatus": 5, "selector": "img[src$='order_pdf_button.gif']" } //{ "text": "Order MSF + Bundle", "url": null, "itemType": "MSFBundle", "pdfStatus": 0, "selector": "img[src$='order_certificate_button.gif']" } ]; for (let i = 0; i < actions.length; i++) { if ($(this).next().find(actions[i].selector).length) { // Build order url let orderUrl = "https://www.gro.gov.uk/gro/content/certificates/indexes_order.asp?"; orderUrl += "Index=" + recordType; orderUrl += "&Year=" + record.year; orderUrl += "&EntryID=" + record.recordId; orderUrl += "&ItemType=" + actions[i].itemType; if (actions[i].pdfStatus && actions[i].pdfStatus > 0) orderUrl += "&PDF=" + actions[i].pdfStatus; actions[i].url = orderUrl; record.actions.push(actions[i]); } //console.log("action '%s' (%s), url: %s", actions[i].itemType, actions[i].selector, actions[i].url); } } //console.log(record); results.items.push(record); } catch (e) { //console.log("Failed to parse record (%d): %s", index, e.message); results.failures.push({ "index": index, "ex": e }); } }); } return results; } var toTitleCase = function(str) { return str.replace(/([^\W_]+[^\s-]*) */g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}); } var switchRecordType = function() { let recordTypes = $("form[name='SearchIndexes'] input[type='Radio'][name='index']"); let curIndex = -1; for (let i = 0; i < recordTypes.length; i++) { if ($(recordTypes).eq(i).prop("checked")) { curIndex = i; break; } } //console.log("current record type: %d", curIndex); if (curIndex >= 0) { let nextIndex = (curIndex == (recordTypes.length-1)) ? 0 : curIndex + 1; if (nextIndex != curIndex) $(recordTypes).eq(nextIndex).prop("checked", true).click(); //console.log("next record type: %d", nextIndex); } } var toggleGender = function() { let curGender = $("form[name='SearchIndexes'] select#Gender").val(); $("form[name='SearchIndexes'] select#Gender").val((curGender === "F" ? "M" : "F")); $("form[name='SearchIndexes'] input[type='submit'][value='Search']").click(); } var adjustSearchYear = function(step) { let adjusted = false; // Get min and max years let minYear = parseInt($("form[name='SearchIndexes'] select#Year option:eq(2)").val(), 10); let maxYear = parseInt($("form[name='SearchIndexes'] select#Year option:last").val(), 10); //console.log("Year range: %s - %s", minYear, maxYear); if (!isNaN(step) && !isNaN(minYear) && !isNaN(maxYear)) { // Read current year and range let curYear = parseInt($("form[name='SearchIndexes'] select#Year").val(), 10); let curRange = parseInt($("form[name='SearchIndexes'] select#Range").val(), 10); if (isNaN(curYear) || curYear === 0) curYear = 1837; if (!isNaN(curRange)) { // Calculate the new year let newYear = (isNaN(curYear) ? minYear : curYear+step); newYear = Math.min(Math.max(newYear, minYear), maxYear); if (newYear != curYear) { // Get list of all years let years = $("form[name='SearchIndexes'] select#Year option[value]") .toArray() .map(el => parseInt(el.value, 10)) .filter(y => !isNaN(y) && y > 0) .sort(); //console.log(years); // If the year doesn't exist try and find the closest if (!years.find(y => y === newYear)) { let stepRange = (Math.abs(step)-1)/2; let nowYear = new Date().getFullYear(); let minYear = (step > 0) ? newYear - stepRange : 1837; let maxYear = (step < 0) ? newYear + stepRange : nowYear; minYear = Math.min(Math.max(minYear, 1837), nowYear); maxYear = Math.min(Math.max(maxYear, 1837), nowYear); years = years.filter(y => y >= minYear && y <= maxYear); newYear = findClosestNumber(years, newYear, minYear, maxYear); } //console.log("newYear: %d", newYear); if (newYear && newYear > 0 && newYear != curYear) { $("form[name='SearchIndexes'] select#Year").val(newYear); adjusted = true; } } } //console.log("Current year: %d +-%d (%d-%d), New year: %d (%d-%d)", curYear, curRange, curYear-curRange, curYear+curRange, newYear, newYear-curRange, newYear+curRange); } return adjusted; } var findClosestNumber = function(numbers, target, minNumber, maxNumber) { //console.log("target: %d, minNumber: %d, maxNumber: %d", target, minNumber, maxNumber); let number = 0; if (numbers && target && minNumber && maxNumber && minNumber <= maxNumber) { target = parseInt(target, 10); minNumber = parseInt(minNumber, 10); maxNumber = parseInt(maxNumber, 10); if (numbers.find(n => n === target)) { number = target; } else { for (let i = 0; i < numbers.length; i++) { let n = numbers[i]; if (!isNaN(n) && n >= minNumber && n <= maxNumber && Math.abs(target-n) < Math.abs(target-number)) { number = n; if (Math.abs(target-number) == 1) break; } } } } return number; } var navigateYears = function(forward) { let curRange = parseInt($("form[name='SearchIndexes'] select#Range").val(), 10); if (!isNaN(curRange)) { // Calculate the new year let step = (curRange * 2) + 1; if (!forward) step = -step; if (adjustSearchYear(step)) { $("form[name='SearchIndexes'] input[type='submit'][value='Search']").click(); } } } var getRecordType = function() { return $("form[name='SearchIndexes'] input[type='radio'][name='index']:checked").val(); } var buildResources = function() { resources = { baseStyle: ` <style type="text/css"> body { min-height: 1200px; background-color: #EAEAEA; } /* widen the page */ body > table[width="800"] { width: 960px !important; } /* widen header */ table[width="780"][height="80"].banner { width: 100% !important; } /* widen content area */ body > table[width="800"] td[width="600"], body > table[width="800"] table[width="600"] { width: 760px !important; } form[name="SearchIndexes"] { position: relative !important; } .groish_ButtonContainer { padding-bottom: 10px; } .groish_ButtonContainer input[type='submit'], .groish_ButtonContainer input[type='button'] { margin-right: 20px; min-width: 100px; font-size: 13px; padding: 6px 10px; background-color: #15377E; border-width: 0px; } .groish_ButtonContainer input[type='submit'] { margin-right: 0px; } #groish_ResultsCopier, #groish_ViewSwitcher { display:inline-block; position: absolute; bottom: 0px; color: #0076C0; font-weight: bold; cursor: pointer; } #groish_ResultsCopier { right: 120px; } #groish_ViewSwitcher { right: 10px; } div[results-view] td[sort-fields]:hover { cursor: pointer; } .groish_Message { position: absolute; bottom: -30px; left: 5px; } </style> `, view_EW_Birth_Table: ` <style type="text/css"> div[results-view='EW_Birth-Table'] td { padding: 5px 3px; font-size: 75%; color: #222; vertical-align: top; } div[results-view='EW_Birth-Table'] thead td { font-weight: bold; } div[results-view='EW_Birth-Table'] tbody tr:nth-child(4n+1), div[results-view='EW_Birth-Table'] tbody tr:nth-child(4n+2) { background-color: #CCE0FF; } div[results-view='EW_Birth-Table'] tr.rec-actions a { padding: 0px 5px; font-size: 90%; color: #15377E; text-decoration: none; } </style> <div results-view='EW_Birth-Table' style='display: none; margin-bottom: 25px' default-sort-fields='year,quarter'> <table style='width: 100%; border-collapse: collapse'> <thead> <tr> <td style='width: 12%' sort-fields='year,quarter'>Date</td> <td style='width: 30%' sort-fields='forenames,surname'>Name</td> <td style='width: 15%' sort-fields='mother'>Mother</td> <td style='width: 25%' sort-fields='district'>District</td> <td style='width: 6%' sort-fields='volume,district'>Vol</td> <td style='width: 6%' sort-fields='page,volume'>Page</td> <td style='width: 6%' sort-fields='copy,volume'>Copy</td> </tr> </thead> <tbody> {{#each items}} <tr class='rec'> <td>{{year}} Q{{quarter}}</td> <td><span class='forenames'>{{forenames}}</span> <span class='surname'>{{surname}}</span>{{#if noForenames}} ({{gender}}){{/if}}</td> <td>{{mother}}</td> <td>{{district}}</td> <td>{{volume}}</td> <td>{{page}}</td> <td>{{copy}}</td> </tr> <tr class='rec-actions' style='display: none'> <td colspan='7' style='text-align: right'> {{#actions}} <a href='{{url}}' {{#if title}}title='{{title}}'{{/if}}>{{text}}</a> {{/actions}} </td> </tr> {{/each}} </tbody> </table> {{#if failures}} <p class='main_text' style='color: Red'>WARNING: Failed to parse {{failures.length}} records. See default view for full list.</p> <!-- {{#each failures}}record parse exception ({{index}}): exception: {{ex.message}}{{/each}} --> {{/if}} <p class='main_text groish_Message'></p> </div>`, view_EW_Birth_Delimited: ` <style type="text/css"> div[results-view='EW_Birth-Delimited'] td { padding: 5px 3px; font-size: 75%; color: #222; vertical-align: top; } div[results-view='EW_Birth-Delimited'] thead td { font-weight: bold; } div[results-view='EW_Birth-Delimited'] tbody tr:nth-child(odd) { background-color: #CCE0FF; } </style> <div results-view='EW_Birth-Delimited' style='display: none; margin-bottom: 25px' default-sort-fields='year,quarter'> <table style='width: 100%; border-collapse: collapse'> <thead> <tr> <td style='width: 100%' sort-fields='year,quarter'>Births</td> </tr> </thead> <tbody> {{#each items}} <tr class='rec'> <td> {{year}} Q{{quarter}} Birth: {{forenames}} {{surname}}{{#if noForenames}} ({{gender}}){{/if}} (mmn: {{mother}}); {{district}};{{#if volume}} Vol {{volume}};{{/if}}{{#if page}} Page {{page}};{{/if}}{{#if copy}} Copy {{copy}};{{/if}} </td> </tr> {{/each}} </tbody> </table> {{#if failures}} <p class='main_text' style='color: Red'>WARNING: Failed to parse {{failures.length}} records. See default view for full list.</p> <!-- {{#each failures}}record parse exception ({{index}}): exception: {{ex.message}}{{/each}} --> {{/if}} <p class='main_text groish_Message'></p> </div>`, view_EW_Death_Table: ` <style type="text/css"> div[results-view='EW_Death-Table'] td { padding: 5px 3px; font-size: 75%; color: #222; vertical-align: top; } div[results-view='EW_Death-Table'] thead td { font-weight: bold; } div[results-view='EW_Death-Table'] tbody tr:nth-child(4n+1), div[results-view='EW_Death-Table'] tbody tr:nth-child(4n+2) { background-color: #CCE0FF; } div[results-view='EW_Death-Table'] tr.rec-actions a { padding: 0px 5px; font-size: 90%; color: #15377E; text-decoration: none; } </style> <div results-view='EW_Death-Table' style='display: none; margin-bottom: 25px' default-sort-fields='year,quarter'> <table style='width: 100%; border-collapse: collapse'> <thead> <tr> <td style='width: 12%' sort-fields='year,quarter'>Date</td> <td style='width: 26%' sort-fields='forenames,surname'>Name</td> <td style='width: 8%' sort-fields='age'>Age{{#if ageCautionThreshold}}*{{/if}}</td> <td style='width: 8%' sort-fields='birth'>Birth</td> <td style='width: 28%' sort-fields='district'>District</td> {{#if (and dataFormat1984 dataFormat1993)}} <td style='width: 6%' >Vl/Rg</td> <td style='width: 6%' >Pg/Ey</td> {{else if dataFormat1993}} <td style='width: 6%' sort-fields='reg,district'>Reg</td> <td style='width: 6%' sort-fields='entry,volume'>Entry</td> {{else}} <td style='width: 6%' sort-fields='volume,district'>Vol</td> <td style='width: 6%' sort-fields='page,volume'>Page</td> {{/if}} <td style='width: 6%' sort-fields='copy,volume'>Copy</td> </tr> </thead> <tbody> {{#each items}} <tr class='rec'> <td>{{year}}{{#if quarter}} Q{{quarter}}{{/if}}</td> <td><span class='forenames'>{{forenames}}</span> <span class='surname'>{{surname}}</span>{{#if noForenames}} ({{gender}}){{/if}}</td> <td>{{age}}</td> <td>{{birth}} <td>{{district}}</td> <td>{{#if volume}}{{volume}}{{else if reg}}{{reg}}{{/if}}</td> <td>{{#if page}}{{page}}{{else if entry}}{{entry}}{{/if}}</td> <td>{{copy}}</td> </tr> <tr class='rec-actions' style='display: none'> <td colspan='8' style='text-align: right'> {{#actions}} <a href='{{url}}' {{#if title}}title='{{title}}'{{/if}}>{{text}}</a> {{/actions}} </td> </tr> {{/each}} </tbody> </table> {{#if failures}} <p class='main_text' style='color: Red'>WARNING: Failed to parse {{failures.length}} records. See default view for full list.</p> <!-- {{#each failures}}record parse exception ({{index}}): exception: {{ex.message}}{{/each}} --> {{/if}} <p class='main_text groish_Message'></p> </div>`, view_EW_Death_Delimited: ` <style type="text/css"> div[results-view='EW_Death-Delimited'] td { padding: 5px 3px; font-size: 75%; color: #222; vertical-align: top; } div[results-view='EW_Death-Delimited'] thead td { font-weight: bold; } div[results-view='EW_Death-Delimited'] tbody tr:nth-child(odd) { background-color: #CCE0FF; } </style> <div results-view='EW_Death-Delimited' style='display: none; margin-bottom: 25px' default-sort-fields='year,quarter'> <table style='width: 100%; border-collapse: collapse'> <thead> <tr> <td style='width: 100%' sort-fields='year,quarter'>Deaths</td> </tr> </thead> <tbody> {{#each items}} <tr class='rec'> <td> {{year}}{{#if quarter}} Q{{quarter}}{{/if}} Death: {{forenames}} {{surname}}{{#if noForenames}} ({{gender}}){{/if}}; {{#if yob}} Born {{yob}} (age {{age}}); {{else}} Age {{age}} (b{{birth}}); {{/if}} {{district}};{{#if volume}} Vol {{volume}};{{/if}}{{#if page}} Page {{page}};{{/if}}{{#if reg}} Reg {{reg}};{{/if}}{{#if entry}} Entry {{entry}};{{/if}}{{#if copy}} Copy {{copy}};{{/if}} </td> </tr> {{/each}} </tbody> </table> {{#if failures}} <p class='main_text' style='color: Red'>WARNING: Failed to parse {{failures.length}} records. See default view for full list.</p> <!-- {{#each failures}}record parse exception ({{index}}): exception: {{ex.message}}{{/each}} --> {{/if}} <p class='main_text groish_Message'></p> </div>` }; // Add custom views // NB: Although GreaseMonkey has replaced the GM_* functions with functions on the GM object // both ViolentMonkey and TamperMonkey still support the GM_* functions so that's being used. // Custom views are defined as GM values (one value per view). The value name must begin with // either view_EW_Birth or view_EW_Death. The view may contain CSS and HTML and the views are // Handlebars.js templates (see default views above for examples). //console.log("adding custom views"); if (typeof GM_listValues === "function" && typeof GM_getValue === "function") { let valueKeys = GM_listValues(); for(let i = 0; i < valueKeys.length; i++) { let valueKey = valueKeys[i]; //console.log("value key: %", valueKey); if (valueKey && valueKey.length > 13 && (valueKey.startsWith("view_EW_Birth") || valueKey.startsWith("view_EW_Death"))) { // Check the key isn't already in use if (!resources.hasOwnProperty(valueKey)) { let viewContent = GM_getValue(valueKey, null); if (viewContent) { //console.log("adding view: %s", valueKey); resources[valueKey] = viewContent; } } } } } } //Get the ball rolling... main(); });