From 338dc6aa67412dad9a4860f7dce56d649227533d Mon Sep 17 00:00:00 2001 From: Ian Beacall Date: Tue, 31 Dec 2024 17:17:27 +0900 Subject: [PATCH] Sibling Status Sync: Check or uncheck 'No more siblings' and have it apply to all siblings. --- scripts/create-feature.js | 2 +- src/content.js | 2 + src/features/register_feature_options.js | 1 + .../sibling_status_sync.css | 1 + .../sibling_status_sync.js | 150 ++++++++++++++++++ .../sibling_status_sync_options.js | 17 ++ .../space_watchlist_sorter.js | 30 ++-- 7 files changed, 187 insertions(+), 16 deletions(-) create mode 100644 src/features/sibling_status_sync/sibling_status_sync.css create mode 100644 src/features/sibling_status_sync/sibling_status_sync.js create mode 100644 src/features/sibling_status_sync/sibling_status_sync_options.js diff --git a/scripts/create-feature.js b/scripts/create-feature.js index 4d07bb3e..8038336d 100644 --- a/scripts/create-feature.js +++ b/scripts/create-feature.js @@ -187,7 +187,7 @@ console.log("Feature creation complete."); * - `-p` or `--pageTypes`: A comma-separated list of page types this feature applies to (e.g., `isProfilePage, isSpacePage`). * * **Important Notes** - * - **Page Types**: These are used to specify where the feature should be applied. Some important page types include `isProfilePage`, `isProfileEdit`, `isSpacePage`, `isSpaceEdit`, `isMainDomain`, etc. For more available page types, see `src/core/page_type.js`. + * - **Page Types**: These are used to specify where the feature should be applied. Some important page types include `isProfilePage`, `isProfileEdit`, `isSpacePage`, `isSpaceEdit`, `isMainDomain`, etc. For more available page types, see `src/core/pageType.js`. * - **Categories**: The category helps to classify the feature in the extension. The available categories are: * - `Global` * - `Global/Style` diff --git a/src/content.js b/src/content.js index 0e168554..7c57d2b9 100644 --- a/src/content.js +++ b/src/content.js @@ -97,6 +97,8 @@ import "./features/randomProfile/randomProfile"; import "./features/what_links_here/what_links_here"; /* MARKER: Default place for new features. Move these to a more appropriate place.*/ +import "./features/sibling_status_sync/sibling_status_sync"; + import "./features/space_watchlist_sorter/space_watchlist_sorter"; import "./features/help/help"; diff --git a/src/features/register_feature_options.js b/src/features/register_feature_options.js index 3f19efbe..dfd76fa8 100644 --- a/src/features/register_feature_options.js +++ b/src/features/register_feature_options.js @@ -98,6 +98,7 @@ import "./send_to_merge/send_to_merge_options"; import "./shareable_sources/shareable_sources_options"; import "./show_edits/show_edits_options"; import "./show_suggestions/show_suggestions_options"; +import "./sibling_status_sync/sibling_status_sync_options"; import "./sort_theme_people/sort_theme_people_options"; import "./sourcepreview/sourcepreview_options"; import "./space_drafts/space_drafts_options"; diff --git a/src/features/sibling_status_sync/sibling_status_sync.css b/src/features/sibling_status_sync/sibling_status_sync.css new file mode 100644 index 00000000..824eb81a --- /dev/null +++ b/src/features/sibling_status_sync/sibling_status_sync.css @@ -0,0 +1 @@ +/* Styles for siblingStatusSync feature */ diff --git a/src/features/sibling_status_sync/sibling_status_sync.js b/src/features/sibling_status_sync/sibling_status_sync.js new file mode 100644 index 00000000..7ff1df9d --- /dev/null +++ b/src/features/sibling_status_sync/sibling_status_sync.js @@ -0,0 +1,150 @@ +/* +Created By: Ian Beacall (Beacall-6) +Feature: Sibling Status Sync +*/ + +import $ from "jquery"; +import { WikiTreeAPI } from "../../core/API/WikiTreeAPI"; +import { shouldInitializeFeature } from "../../core/options/options_storage"; + +// Initialize the feature conditionally +shouldInitializeFeature("siblingStatusSync").then((result) => { + if (result) { + init(); + } +}); + +// Main initialization function +function init() { + console.log("Initializing Sibling Status Sync feature..."); + $("input[name='mNoSiblings']").on("change", async function () { + const isChecked = $(this).is(":checked"); + const userId = getUserIdFromPage(); + const siblings = await fetchSiblings(userId); + + // Process siblings sequentially to avoid iframe conflicts + await updateSiblingsSequentially(siblings, isChecked); + }); +} + +// Extract User ID from the page URL +function getUserIdFromPage() { + const urlParams = new URLSearchParams(window.location.search); + return urlParams.get("u"); +} + +// Fetch sibling data using the WikiTree API +async function fetchSiblings(userId) { + const fields = ["Id", "Father", "Mother", "Name", "FirstName", "RealName", "mNoSiblings"]; + const options = { nuclear: 1 }; + + const result = await WikiTreeAPI.getPeople("WBE-siblingStatusSync", [userId], fields, options); + + const statusText = result[0]; + const peopleList = result[2]; + + if (statusText) { + console.error("Error fetching people:", statusText); + return []; + } + + const currentPerson = peopleList[userId]; + const parentIds = [currentPerson.Father, currentPerson.Mother]; + + return Object.values(peopleList).filter( + (person) => person.Father === parentIds[0] && person.Mother === parentIds[1] && person.Id !== userId + ); +} + +// Process siblings sequentially +async function updateSiblingsSequentially(siblings, isChecked) { + for (const sibling of siblings) { + try { + //console.log(`Processing sibling ${sibling.Id}...`); + await updateSiblingPageWithIframe(sibling, isChecked); + //console.log(`Successfully updated sibling ${sibling.Id}`); + } catch (error) { + //console.error(`Failed to update sibling ${sibling.Id}:`, error); + } + } +} + +// Update a sibling's edit page using an iframe +async function updateSiblingPageWithIframe(sibling, isChecked) { + return new Promise((resolve, reject) => { + const iframeId = `iframe-${sibling.Id}`; + const existingIframe = document.getElementById(iframeId); + + // Remove any stale iframes + if (existingIframe) { + //console.warn(`Removing stale iframe for sibling ${sibling.Id}`); + document.body.removeChild(existingIframe); + } + + const iframe = document.createElement("iframe"); + iframe.id = iframeId; + iframe.style.display = "none"; + iframe.src = `https://www.wikitree.com/index.php?title=Special:EditPerson&u=${sibling.Id}`; + + let retries = 5; + + const cleanUpAndReject = (error) => { + if (document.body.contains(iframe)) { + document.body.removeChild(iframe); + } + reject(error); + }; + + const processIframe = () => { + const iframeDoc = iframe.contentWindow.document; + + const checkbox = iframeDoc.querySelector("input[name='mNoSiblings']"); + const summaryField = iframeDoc.querySelector("#wpSummary"); + const saveButton = iframeDoc.querySelector("#wpSave"); + + if (checkbox && summaryField && saveButton) { + checkbox.checked = isChecked; + checkbox.dispatchEvent(new Event("input", { bubbles: true })); + + summaryField.value = isChecked ? "Checked 'No more siblings'" : "Unchecked 'No more siblings'"; + summaryField.dispatchEvent(new Event("input", { bubbles: true })); + + saveButton.click(); + + // console.log(`Sibling ${sibling.Id} updated successfully.`); + document.body.removeChild(iframe); + + const $div = $("
"); + $div.text(`Sibling ${sibling.FirstName || sibling.RealName || sibling.Name} updated.`); + $div.css({ + position: "fixed", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + background: "white", + padding: "20px", + border: "1px solid black", + zIndex: 10000, + textAlign: "center", + fontSize: "16px", + borderRadius: "8px", + }); + $("body").append($div); + + setTimeout(() => $div.fadeOut(300, () => $div.remove()), 3000); + resolve(); + } else if (retries > 0) { + retries -= 1; + // console.warn(`Retrying for sibling ${sibling.Id}, attempts left: ${retries}`); + setTimeout(processIframe, 1000); + } else { + cleanUpAndReject(new Error(`Failed to find required elements for sibling ${sibling.Id}`)); + } + }; + + iframe.onload = processIframe; + iframe.onerror = () => cleanUpAndReject(new Error(`Failed to load iframe for sibling ${sibling.Id}`)); + + document.body.appendChild(iframe); + }); +} diff --git a/src/features/sibling_status_sync/sibling_status_sync_options.js b/src/features/sibling_status_sync/sibling_status_sync_options.js new file mode 100644 index 00000000..b1c53279 --- /dev/null +++ b/src/features/sibling_status_sync/sibling_status_sync_options.js @@ -0,0 +1,17 @@ +/* +Created By: Ian Beacall (Beacall-6) +*/ + +import { registerFeature, OptionType } from "../../core/options/options_registry"; +import { isProfileEdit } from "../../core/pageType"; + +registerFeature({ + name: "Sibling Status Sync", + id: "siblingStatusSync", + description: "Check or uncheck 'No more siblings' and have it apply to all siblings.", + category: "Editing", + creators: [{ name: "Ian Beacall", wikitreeid: "Beacall-6" }], + contributors: [], + defaultValue: false, + pages: [isProfileEdit], +}); diff --git a/src/features/space_watchlist_sorter/space_watchlist_sorter.js b/src/features/space_watchlist_sorter/space_watchlist_sorter.js index 19e57799..6d4e946d 100644 --- a/src/features/space_watchlist_sorter/space_watchlist_sorter.js +++ b/src/features/space_watchlist_sorter/space_watchlist_sorter.js @@ -196,7 +196,7 @@ async function loadSpaceWatchlist() { }); if (response?.clientLogin?.result === "Success") { - console.log("Login successful:", response.clientLogin); + //console.log("Login successful:", response.clientLogin); // Remove the authcode from the URL to clean it up const cleanURL = window.location.href.split("?")[0]; window.history.replaceState({}, document.title, cleanURL); @@ -217,7 +217,7 @@ async function loadSpaceWatchlist() { const watchlist = await WikiTreeAPI.getSpaceWatchlist(appId, limit, fields); if (watchlist.length === 0) { - console.log("No watchlist data. Redirecting to login..."); + //console.log("No watchlist data. Redirecting to login..."); // Set redirecting flag in localStorage localStorage.setItem("spaceWatchlistSorterRedirecting", "true"); @@ -262,18 +262,18 @@ async function loadWatchlistFromDB() { getRequest.onsuccess = function () { const result = getRequest.result || { unorganizedItems: [], folders: [] }; - console.log("Watchlist loaded from IndexedDB:", result); + // console.log("Watchlist loaded from IndexedDB:", result); resolve(result); }; getRequest.onerror = function (e) { - console.error("Error reading from IndexedDB:", e.target.error); + // console.error("Error reading from IndexedDB:", e.target.error); resolve({ unorganizedItems: [], folders: [] }); }; }; dbRequest.onerror = function (e) { - console.error("Error opening IndexedDB:", e.target.error); + //console.error("Error opening IndexedDB:", e.target.error); resolve({ unorganizedItems: [], folders: [] }); }; }); @@ -281,7 +281,7 @@ async function loadWatchlistFromDB() { async function populateInterface() { try { - console.log("Populating interface..."); + //console.log("Populating interface..."); const apiWatchlist = await loadSpaceWatchlist(); const dbWatchlist = await loadWatchlistFromDB(); @@ -350,7 +350,7 @@ async function populateInterface() { } async function saveWatchlistToDB() { - console.log("Saving watchlist to IndexedDB..."); + //console.log("Saving watchlist to IndexedDB..."); const userId = await getUserWtId(); // Fetch the logged-in user's ID if (!userId) { console.error("Unable to fetch user ID. Cannot save watchlist."); @@ -401,7 +401,7 @@ function addButton() { console.log("Adding the sorter button..."); const clipboardContainer = $(".clipboardContainer"); if (clipboardContainer.find(".spaceWatchlistSorterButton").length === 0) { - console.log("Adding Space Watchlist Sorter button..."); + //console.log("Adding Space Watchlist Sorter button..."); const sorterButton = $("", { title: "Space Watchlist Sorter", class: "button small spaceWatchlistSorterButton", @@ -412,10 +412,10 @@ function addButton() { clipboardContainer.append(sorterButton); sorterButton.on("click", async function () { - console.log("Sorter button clicked."); + //console.log("Sorter button clicked."); const $popup = $("#spaceWatchlistSorter-popup"); if ($popup.length === 0) { - console.log("Appending popup and loading screen to body..."); + //console.log("Appending popup and loading screen to body..."); $("body").append(`
Loading... @@ -460,7 +460,7 @@ function addButton() { } function addFolder() { - console.log("Adding a new folder..."); + // console.log("Adding a new folder..."); const folderId = `spaceWatchlistSorterFolder-${Date.now()}`; $("#spaceWatchlistSorterFolderContainer").append(`
@@ -476,7 +476,7 @@ function addFolder() { initializeSortable(); // Reinitialize sortable for the new folder initializeDroppable(); // Initialize droppable for the new folder - console.log(`Folder added with ID: ${folderId}`); + // console.log(`Folder added with ID: ${folderId}`); } function initializeDroppable() { @@ -506,7 +506,7 @@ function initializeDroppable() { shouldInitializeFeature("spaceWatchlistSorter").then((result) => { if (result) { - console.log("Feature initialized."); + // console.log("Feature initialized."); // Import CSS import("./space_watchlist_sorter.css"); @@ -535,11 +535,11 @@ shouldInitializeFeature("spaceWatchlistSorter").then((result) => { $(document).on("click", "#spaceWatchlistSorterClosePopup", function () { saveWatchlistToDB(); $("#spaceWatchlistSorter-popup").hide(); - console.log("Popup closed and data saved."); + // console.log("Popup closed and data saved."); }); $(document).on("click", ".spaceWatchlistSorter-removeFolder", function () { const folderId = $(this).closest(".spaceWatchlistSorter-folder").attr("id"); - console.log(`Removing folder with ID: ${folderId}`); + // console.log(`Removing folder with ID: ${folderId}`); $(this).closest(".spaceWatchlistSorter-folder").remove(); }); $(document).on("click", ".spaceWatchlistSorter-toggleFolder", function () {