// Configuration object const CONFIG = { SINGLE_PAGE: { SITE_ID: "642338e703a127cc457d7b6b", LAYOUT: { ENGLISH: [ { type: "home_alt", name: "Home", id: '6423394f03a127cc457d7c4f' }, { type: "redirect", name: "Meet the Team", url: "#team" }, { type: "redirect", name: "Services & Solutions", url: "#services" }, { type: "redirect", name: "Contact", url: "#contact" }, { type: "redirect", name: "Client Login", style: "button", url: "https://manulifewealth.ca/clients/en/sign-in" } ], FRENCH: [ { type: "home_alt", name: "Accueil", id: '64f0c97c6ffaf8c435fd70c2' }, { type: "redirect", name: "Notre équipe", url: "#team" }, { type: "redirect", name: "Services et solutions", url: "#services" }, { type: "redirect", name: "Contact", url: "#contact" }, { type: "redirect", name: "Session client", style: "button", url: "https://manulifewealth.ca/clients/fr/sign-in" } ] } }, MULTI_PAGE: { SITE_ID: '5b71917a1cbe736dff66dc27', LAYOUT: { ENGLISH: [ { type: "home_alt", name: "Home", id: "5b719261138837295d1d7edf" }, { type: "standard", name: "About Us", id: "5b719261138837295d1d7ee0", children: [ { type: "members", name: "Your Team", id: "5b719261138837295d1d7ee1" }, { type: "standard", name: "Our Process", id: "5b719261138837295d1d7ee5" }, { type: "standard", name: "Testimonials", id: "61e880e9a96c7436cc507c6f" }, { type: "standard", name: "Referrals", id: "61e8e39693909a22246d4d11" } ] }, { type: "standard", name: "Services & Solutions", id: "5b719261138837295d1d7ee6" }, { type: "blog", name: "Insights & Articles", id: "61e8e0c893909a22246d49fa" }, { type: "standard", name: "Client Resources", id: "6298ae2f282cca59a30e8f2e", children: [ { type: "standard", name: "Client Centre", id: "5b71bb021cbe736dff66e078" }, { type: "standard", name: "Knowledge Centre", id: "620e6edf1ba9141f550dcba0" } ] }, { type: "standard", name: "Contact", id: "5b719261138837295d1d7eef" }, { type: "redirect", name: "Client Login", url: "https://manulifewealth.ca/clients/en/sign-in" } ], FRENCH: [ { type: "home_alt", name: "Accueil", id: "6298af47282cca59a30e8fda" }, { type: "standard", name: "À Propos", id: "6298b042282cca59a30e925e", children: [ { type: "members", name: "Notre équipe", id: "6298b046282cca59a30e9260" }, { type: "standard", name: "Nous Processus", id: "6298b04c282cca59a30e9264" }, { type: "standard", name: "Témoignages", id: "6298b04e282cca59a30e9267" }, { type: "standard", name: "Recommandations", id: "6298b050282cca59a30e926b" } ] }, { type: "standard", name: "Services", id: "6298b0c9282cca59a30e92b6" }, { type: "blog", name: "Articles", id: "6298b0cc282cca59a30e92b7" }, { type: "standard", name: "Ressources", id: "6298b0ce282cca59a30e92b8", children: [ { type: "standard", name: "Centre client", id: "6298b0db282cca59a30e92be" }, { type: "standard", name: "Centre d'information", id: "6298b0dd282cca59a30e92c1" } ] }, { type: "standard", name: "Contact", id: "6298b0e4282cca59a30e92cd" }, { type: "redirect", name: "Accès du client", url: "https://manulifewealth.ca/clients/fr/sign-in" } ] } }, SETTINGS: { REFRESH_AFTER: false, REMOVE_OTHER_PAGES: true }, API_BASE: 'https://app.twentyoverten.com' } /* ------------------------------------------------------ */ /** * Helper function to create URL-friendly slugs from page names * @param {string} name - The page name to convert to a slug * @returns {string} - URL-friendly slug */ function parseSlug(name) { return name .toLowerCase() .replace(/&/g, 'and') // Replace & with 'and' .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/[^a-z0-9-]/g, '') // Remove any other special characters .replace(/-+/g, '-') // Replace multiple hyphens with single hyphen .replace(/^-|-$/g, '') // Remove leading/trailing hyphens } /** * Fetch site data by site ID * @param {string} site_id - The ID of the site to fetch data for * @returns {Promise} - The site data */ async function getSiteData(site_id) { try { const response = await fetch(`${CONFIG.API_BASE}/manage/advisor/notes/${site_id}`) if (!response.ok) { throw new Error(`Failed to fetch site data: ${response.status} ${response.statusText}`) } const data = await response.json() if (!data?.site) { throw new Error('Invalid site data structure received') } const { site } = data const { settings, content, favicons, styles, code } = site return { title: settings.title, text_logo: settings.text_logo, header: settings.header, footer: content.footer, favicons, styles, custom_css: code.css, header_inject: code.header, footer_inject: code.footer, } } catch (error) { console.error('Error fetching site data:', error) throw error } } /** * Setup the site with the given configuration * @param {boolean} is_single_page - Whether the site is a single page application * @param {string} default_language - The default language for the site * @param {boolean} is_bilingual - Whether the site is bilingual */ async function setupItems(is_single_page, default_language, is_bilingual) { let item_structure = {main: [], other: []} console.log(`🚀 Starting item setup...`) if (CONFIG.SETTINGS.REMOVE_OTHER_PAGES) { let pages = await getItems() await removeAllButHome(pages) } const home_page_id = document.querySelector(".dd-item.home").getAttribute("data-id") console.log(`🏠 Found home page ID: ${home_page_id}`) /* - Process either the layout of the single page or multi-page as passed - If website is bilingual - If website is - Update the existing home page hero_content and content based off the layout - Create items based off the layout; use hero_content, content, background of each item from the page data grabbed through the use of the id; children are needed here; - Create a Navigation page for other language; hidden = true - Add the items that would exist but only create them as links; hidden = true; no children are needed here - Add the items from the layout; use hero_content, content, background of each item from the page data grabbed through the use of the id; hidden = true; children are needed here; - If website is not bilingual - Update the existing home page hero_content and content based off the layout - Create items based off the layout; use hero_content, content, background of each item from the page data grabbed through the use of the id; children are needed here; */ // Get the appropriate layout based on site type const layout = is_single_page ? (default_language === 'en' ? CONFIG.SINGLE_PAGE.LAYOUT.ENGLISH : CONFIG.SINGLE_PAGE.LAYOUT.FRENCH) : (default_language === 'en' ? CONFIG.MULTI_PAGE.LAYOUT.ENGLISH : CONFIG.MULTI_PAGE.LAYOUT.FRENCH) console.log(`📖 Selected layout: ${is_single_page ? 'Single-page' : 'Multi-page'} ${default_language.toUpperCase()}`) // Update home page with content from default language layout console.log(`🏠 Updating home page with ${default_language.toUpperCase()} content...`) const homePage = layout[0] if (homePage.id) { const page_data = await getItemData(homePage.id) item_structure.main.push({id: home_page_id.toString()}) console.log(`✅ Home page updated with content from ${homePage.name}`) } // Create items based on the default language layout (skip home page) console.log(`📄 Creating ${default_language.toUpperCase()} items...`) const layoutItems = layout.slice(1) let createdPages = 0 let createdLinks = 0 let mainLanguagePageIds = [] // Store main language page IDs for later bilingual updates for (const item of layoutItems) { if (item.type === "redirect") { // Create redirect items const isExternal = item.url.startsWith('http') await createLink(item.name, item.url, true, isExternal, item.style ?? "normal", item_structure) createdLinks++ } else { // Create pages with content from page data let parent_id = null if (item.id) { const pageData = await getItemData(item.id) parent_id = await createPage(item.type, item.name, parseSlug(item.name), item.name, true, {}, item_structure) } else { parent_id = await createPage(item.type, item.name, parseSlug(item.name), item.name, true, {}, item_structure) } if (parent_id) { mainLanguagePageIds.push(parent_id) } createdPages++ // Create child pages if they exist if (item.children) { console.log(` ↳ Creating ${item.children.length} child pages for ${item.name}...`) for (const child of item.children) { let child_id = null if (child.id) { const childPageData = await getItemData(child.id) child_id = await createPage(child.type, child.name, parseSlug(child.name), child.name, false, {}, item_structure, parent_id) } else { child_id = await createPage(child.type, child.name, parseSlug(child.name), child.name, false, {}, item_structure, parent_id) } if (child_id) { mainLanguagePageIds.push(child_id) } createdPages++ } } } } console.log(`✅ Created ${createdPages} pages and ${createdLinks} links for ${default_language.toUpperCase()} layout`) // Check if bilingual setup is needed after creating the first language pages if (is_bilingual) { console.log(`🌐 Setting up bilingual site - adding ${default_language === 'en' ? 'French' : 'English'} navigation...`) // Create navigation for the other language const otherLanguage = default_language === 'en' ? 'French' : 'English' console.log(`🗂️ Creating ${otherLanguage} navigation structure...`) const otherLanguageLayout = default_language === 'en' ? (is_single_page ? CONFIG.SINGLE_PAGE.LAYOUT.FRENCH : CONFIG.MULTI_PAGE.LAYOUT.FRENCH) : (is_single_page ? CONFIG.SINGLE_PAGE.LAYOUT.ENGLISH : CONFIG.MULTI_PAGE.LAYOUT.ENGLISH) const navName = default_language === 'en' ? "Navigation Française" : "English Navigation" const navSlug = default_language === 'en' ? "nav-francaise" : "english-navigation" const navTitle = default_language === 'en' ? "Navigation Française" : "English Navigation" // Create navigation page for other language (hidden = true) console.log(`📁 Creating navigation parent: ${navName} (hidden = true)`) try { // Step 1: Create a Navigation page for other language; hidden = true const nav = await createItem("standard", false, item_structure) // hidden = true (in_main_nav = false) if (nav.ok) { const nav_data = nav.data const nav_id = nav_data._id await updateItem(nav_id, { name: navName, page_slug: parseSlug(navSlug), title: navTitle }) let createdLinks = 0 let createdPages = 0 const layoutItems = otherLanguageLayout // Don't skip the home page - include all items // Step 2: Add the items that would exist but only create them as links; hidden = true; no children are needed here console.log(` 🔗 Creating navigation links (hidden = true, no children)...`) for (const item of layoutItems) { if (item.type === "redirect") { // Create redirect links (external or internal) const isExternal = item.url.startsWith('http') await createLink(item.name, item.url, false, isExternal, item.style ?? "normal", item_structure, nav_id) // hidden = true (in_main_nav = false) } else { // Create navigation links pointing to pages (no children needed here) await createLink(item.name, `/${parseSlug(item.name)}`, false, false, item.style ?? "normal", item_structure, nav_id) // hidden = true (in_main_nav = false) } createdLinks++ } // Step 3: Add the items from the layout; use hero_content, content, background; hidden = true; children are needed here console.log(` 📄 Creating actual pages with content (hidden = true, children included)...`) // Set alternative_logo_link based on the second language being created // English pages should always point to "/home", French pages to "/accueil" const secondLanguage = default_language === 'en' ? 'fr' : 'en' const alternativeLogoLink = secondLanguage === 'en' ? '/home-en' : '/accueil' for (const item of layoutItems) { if (item.type !== "redirect") { // Create the actual page with content (hidden from main nav, but not as child of navigation) let parent_id = null const pageData = item.id ? await getItemData(item.id) : {} // Add bilingual navigation data pointing to appropriate home page const bilingualData = { alternate_nav: nav_id, alternate_logo_link: alternativeLogoLink } if (item.id) { parent_id = await createPage(item.type, item.name, parseSlug(item.name), item.name, false, { ...bilingualData }, item_structure) // Remove nav_id parent - create as standalone hidden page } else { parent_id = await createPage(item.type, item.name, parseSlug(item.name), item.name, false, bilingualData, item_structure) // Remove nav_id parent } createdPages++ // Create child pages if they exist (children are needed here) if (item.children) { console.log(` ↳ Creating ${item.children.length} child pages for ${item.name} (hidden = true)...`) for (const child of item.children) { const childData = child.id ? await getItemData(child.id) : {} if (child.id) { await createPage(child.type, child.name, parseSlug(child.name), child.name, false, { ...bilingualData }, item_structure, parent_id) } else { await createPage(child.type, child.name, parseSlug(child.name), child.name, false, bilingualData, item_structure, parent_id) } createdPages++ } } } } console.log(` ✅ Created ${createdLinks} navigation links and ${createdPages} pages under ${navName}`) } else { throw new Error(`Failed to create navigation parent: ${navName}`) } } catch (error) { console.error(`❌ Error creating navigation ${navName}:`, error) throw error } console.log(`✅ ${otherLanguage} navigation structure created successfully`) } else { console.log(`🌍 Single-language site setup complete - no additional language navigation needed`) } console.log(`🔄 Updating page structure...`) await updatePageStructure(item_structure) console.log(`✅ Page structure updated successfully`) // Reload the window after 2 seconds if (CONFIG.SETTINGS.REFRESH_AFTER) { console.log(`🔄 Page will reload in 1 second...`) setTimeout(() => window.location.reload(), 1000) } } /** * Update the site settings * @param {Object} new_data - The new site settings data */ async function updateSiteSettings(new_data) { try { const response = await fetch(`${CONFIG.API_BASE}/api/sites`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(new_data) }) if (response.ok) { // Remove individual success message - handled by higher level logging } else { throw new Error(`Failed to update site settings: ${response.status} ${response.statusText}`) } } catch (error) { console.error("Failed to update site settings:", error) throw error } } /** * Gets the items of the logged in user */ async function getItems() { try { const response = await fetch(`${CONFIG.API_BASE}/api/pages`) if (!response.ok) { throw new Error(`Failed to fetch items: ${response.status} ${response.statusText}`) } const data = await response.json() return data.pages || [] } catch (error) { console.error("Failed to get items:", error) return [] } } /** * Remove all pages except Home */ async function removeAllButHome(pages) { console.log("🗑️ Cleaning up existing pages (keeping only Home page)...") // Iterate through pages, and remove ones that don't say Home const deletePromises = [] let deletedCount = 0 for (const e of pages) { if (e.name != "Home" ) { console.log(` Deleting page: "${e.name}"`) deletePromises.push(deleteItem(e._id)) deletedCount++ } } await Promise.all(deletePromises) console.log(`✅ Cleaned up ${deletedCount} pages (kept Home page)`) } /** * Delete a page by ID * @param {string} item_id - The ID of the item to delete */ async function deleteItem(item_id) { try { const response = await fetch(`${CONFIG.API_BASE}/api/pages/${item_id}`, { method: 'DELETE' }) if (response.ok) { console.info(`Successfully deleted ${item_id}`) } else { throw new Error(`Failed to delete item: ${response.status} ${response.statusText}`) } } catch (error) { console.error(`Failed to delete ${item_id}:`, error) } } /** * Gets the data for a single item * @param {string} item_id - The ID of the item to retrieve */ async function getItemData(item_id) { try { const response = await fetch(`${CONFIG.API_BASE}/api/pages/${item_id}`) if (!response.ok) { throw new Error(`Failed to fetch item data: ${response.status} ${response.statusText}`) } const data = await response.json() return data } catch (error) { console.error(`Failed to get page data for ${id}:`, error) throw error } } /** * Creates a new item * @param {string} type - The type of the item to create ['standard', 'redirect', 'members', 'blog'] * @param {boolean} in_main_nav - Whether the item should be in the main navigation * @param {Object} item_structure - Object to track page IDs by category */ async function createItem(type, in_main_nav, item_structure = null) { try { const response = await fetch(`${CONFIG.API_BASE}/api/pages`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ type, hidden: !in_main_nav }) }) if (!response.ok) { throw new Error(`Failed to create item: ${response.status} ${response.statusText}`) } // Track the created item ID in item_structure if provided if (item_structure && response.ok) { const data = await response.json() const pageId = data._id if (in_main_nav) { // Add to main navigation items item_structure.main.push({ id: pageId }) } else { // Add to other (hidden) items item_structure.other.push({ id: pageId }) } // Return the data instead of the response since we've already read it return { ok: true, data } } // If no item_structure tracking, return the response as before if (response.ok) { const data = await response.json() return { ok: true, data } } else { return { ok: false } } } catch (error) { console.error(`Failed to create item of type ${type}:`, error) throw error } } /** * Updates a page by ID with new data * @param {string} item_id - The ID of the item to update * @param {Object} data - The new data to update the item with */ async function updateItem(item_id, data) { try { const response = await fetch(`${CONFIG.API_BASE}/api/pages/${item_id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }) if (!response.ok) { throw new Error(`Failed to update item: ${response.status} ${response.statusText}`) } return response } catch (error) { console.error(`Failed to update item ${item_id}:`, error) throw error } } /** * Creates a new link * @param {string} name - The name of the link * @param {string} url - The URL of the link * @param {boolean} in_main_nav - Whether the link should be in the main navigation * @param {boolean} target - Whether the link should open in a new tab * @param {string} style - The style of the link * @param {Object} item_structure - Object to track page IDs by category * @param {string} parent_id - ID of parent page (for tracking children relationships) * @returns {Promise} - The created link data */ async function createLink(name, url, in_main_nav = true, target = false, style = "normal", item_structure = null, parent_id = null) { try { const link_result = await createItem("redirect", in_main_nav, item_structure) if (link_result.ok) { const link_json = link_result.data const link_id = link_json._id const link_update = await updateItem(link_id, { link_title: name, link_url: url, link_target: target, link_style: style, }) if (link_update.ok) { // If this is a child link and we have item_structure tracking if (parent_id && item_structure) { // Find the parent in the item_structure and add this child const updateParentWithChild = (items) => { for (let item of items) { if (item.id === parent_id) { if (!item.children) { item.children = [] } item.children.push({ id: link_id }) return true } if (item.children && updateParentWithChild(item.children)) { return true } } return false } // Try to find parent in main items, then other items if (!updateParentWithChild(item_structure.main)) { updateParentWithChild(item_structure.other) } } // Remove individual success message - handled by higher level logging return link_update } else { console.error('Failed to update link:', name) } } else { console.error('Failed to create link:', name) } } catch (error) { console.error(`Failed to create link ${name}:`, error) throw error } } /** * Creates a new page * @param {string} type - The type of the page to create ['standard', 'members', 'blog'] * @param {string} name - The name of the page * @param {string} slug - The slug of the page * @param {string} title - The title of the page * @param {boolean} in_main_nav - Whether the page should be in the main navigation * @param {Object} data - Additional data for the page * @param {Object} item_structure - Object to track page IDs by category * @param {string} parent_id - ID of parent page (for tracking children relationships) * @returns {Promise} - The created page ID */ async function createPage(type, name, slug, title, in_main_nav = true, data = {}, item_structure = null, parent_id = null) { try { const page_result = await createItem(type, in_main_nav, item_structure) if (page_result.ok) { const page_json = page_result.data const page_id = page_json._id const page_update = await updateItem(page_id, { name, page_slug: parseSlug(slug), title, ...data }) if (page_update.ok) { // Remove individual success message - handled by higher level logging // If this is a child page and we have item_structure tracking if (parent_id && item_structure) { // Find the parent in the item_structure and add this child const updateParentWithChild = (items) => { for (let item of items) { if (item.id === parent_id) { if (!item.children) { item.children = [] } item.children.push({ id: page_id }) return true } if (item.children && updateParentWithChild(item.children)) { return true } } return false } // Try to find parent in main items, then other items if (!updateParentWithChild(item_structure.main)) { updateParentWithChild(item_structure.other) } } return page_id } else { console.error('Failed to update page:', type, name) } } else { console.error('Failed to create page:', type, name) } } catch (error) { console.error(`Failed to create page ${type} ${name}:`, error) throw error } } async function updatePageStructure(pages){ try { const response = await fetch(`${CONFIG.API_BASE}/api/pages/update`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ type:'', pages: JSON.stringify(pages) }) }) if (!response.ok) { throw new Error(`Failed to update page order: ${response.status} ${response.statusText}`) } return response } catch (error) { console.error(`Failed to update page order:`, error) throw error } } async function assistWithBuild(is_single_page) { console.log(`🛠️ Assist with build mode activated - click on a page in the list to log its data to the console`) document.querySelector(".pages-wrapper").addEventListener("click", async (e)=>{ // Find the closest .dd-item element (handles clicks on child nodes) const ddItem = e.target.closest(".dd-item") if(ddItem){ const current_page_name = ddItem.querySelector(".page-title").textContent // Go through config's layout and find a page that matches the name. Also check children const target_page_id = findPageIdByName(is_single_page ? CONFIG.SINGLE_PAGE : CONFIG.MULTI_PAGE, current_page_name) const target_page_data = await getItemData(target_page_id) console.log( `Assisting with build for page ID ${target_page_id}:\n`, target_page_data?.content, '\n\n--------------------\n\n', target_page_data?.hero_content, ) } }) } // Recursive function to find a page ID by name. Go through the whole config function findPageIdByName(tree, name) { for (const lang of ['ENGLISH', 'FRENCH']) { for (const item of tree.LAYOUT[lang]) { if (item.name === name) { return item.id } if (item.children) { const childId = findPageIdByName({LAYOUT: {[lang]: item.children}}, name) if (childId) { return childId } } } } return null } /** * Main function to run the site setup * @param {boolean} is_single_page - Flag indicating if the site is single-page * @param {string} default_language - The default language for the site [en|fr] * @param {boolean} is_bilingual - Flag indicating if the site is bilingual * @param {boolean} update_settings - Flag indicating if site settings should be updated * @param {boolean} replace_items - Flag indicating if items should be replaced */ async function run(is_single_page = true, update_settings = true, replace_items = true, default_language = 'en', is_bilingual = true) { console.log(`🎯 Initializing site setup automation...`) console.log(`📋 Parameters: ${is_single_page ? 'Single-page' : 'Multi-page'} | ${default_language.toUpperCase()} | ${is_bilingual ? 'Bilingual' : 'Single language'}`) try { console.log(`📡 Fetching site data...`) const site_data = await getSiteData(is_single_page ? CONFIG.SINGLE_PAGE.SITE_ID : CONFIG.MULTI_PAGE.SITE_ID) console.log(`✅ Site data retrieved successfully`) if (update_settings) { console.log(`⚙️ Updating site settings...`) updateSiteSettings({ settings: { title: site_data.title, text_logo: site_data.text_logo, header: site_data.header }, content: { footer: site_data.footer }, favicons: { footer: site_data.favicons }, styles: site_data.styles, code: { css: site_data.custom_css, header: site_data.header_inject, footer: site_data.footer_inject } }) console.log(`✅ Site settings updated successfully`) } if (replace_items) { await setupItems(is_single_page, default_language, is_bilingual) } const siteType = is_single_page ? 'Single-page' : 'Multi-page' console.log(`🏆 ${siteType} site setup completed successfully!`) } catch (error) { console.error(`❌ Site setup failed:`, error) } } // run(is_single_page, update_settings, replace_items, default_language, is_bilingual) run(true, true, true, 'en', true) // assistWithBuild(is_single_page) facebook twitter instagram linkedin google youtube vimeo tumblr yelp rss email podcast phone blog search brokercheck brokercheck Play Pause

Code

lorem ipsum...