1import { fetchPlaceholders, getMetadata } from "./aem.js"; 2import { getLanguage, PATH_PREFIX } from "./utils.js"; 3 4/** 5 * Fetches schema data from API, transforms it into structured data, 6 * and injects it into the document head as a script tag. 7 */ 8export async function insertSchema() { 9 let pagePath = window.location.pathname; 10 let schemaApiUrl = ""; 11 if (pagePath.startsWith("/content")) { 12 schemaApiUrl = pagePath.replace(".html", ".wbgschema"); 13 } else { 14 const globalProperties = await fetchPlaceholders(PATH_PREFIX); 15 16 if (!globalProperties?.schemaUrl) { 17 console.warn( 18 "Schema.js - No schemaUrl found in global properties. Skipping structured data injection." 19 ); 20 return; 21 } 22 const environment = globalProperties?.environment 23 ? globalProperties.environment.toLowerCase() 24 : ""; 25 schemaApiUrl = `${globalProperties?.schemaUrl}${pagePath}${environment === "qa" || environment === "dev" 26 ? `?env=${environment.toLowerCase()}` 27 : "" 28 }`; 29 } 30 31 fetch(schemaApiUrl) 32 .then((response) => { 33 if (!response.ok) { 34 throw new Error(`HTTP error! status: ${response.status}`); 35 } else { 36 return response.json(); 37 } 38 }) 39 .then((apiData) => { 40 if (!apiData || typeof apiData !== "object") { 41 console.warn("schema.js - Invalid or empty data received from API"); 42 return; 43 } 44 45 // Transform API data to actual schema structure 46 const structuredData = createStructuredData(apiData.data); 47 48 const structuredDataText = JSON.stringify(structuredData, null, 2); 49 50 // Remove any existing schema script for this path to avoid duplicates 51 const existingScript = document.querySelector( 52 'script[type="application/ld+json"]' 53 ); 54 if (existingScript) { 55 existingScript.remove(); 56 } 57 58 const schemaScript = document.createElement("script"); 59 schemaScript.setAttribute("type", "application/ld+json"); 60 schemaScript.textContent = structuredDataText; 61 document.head.appendChild(schemaScript); 62 }) 63 .catch((error) => { 64 console.error("Error fetching schema data:", error); 65 }); 66} 67 68/** 69 * Transforms API response data into structured data format 70 * @param {Object} apiData - Raw data from the API response 71 * @returns {Object} - structured data object 72 */ 73function createStructuredData(apiData) { 74 const { 75 title, 76 navTitle, 77 pageTitle, 78 pageName, 79 description, 80 primaryImageOfPage, 81 publisher, 82 organization, 83 breadcrumbs, 84 contacts, 85 } = apiData; 86 const { pathname: pagePath, href: currentPageURL } = window.location; 87 88 const getName = () => { 89 if (title && title.trim()) return title.trim(); 90 if (navTitle && navTitle.trim()) return navTitle.trim(); 91 if (pageTitle && pageTitle.trim()) return pageTitle.trim(); 92 if (pageName && pageName.trim()) return pageName.trim(); 93 return "World Bank Group"; 94 }; 95 96 const getDescription = () => { 97 if (description && description.trim()) return description.trim(); 98 if (getMetadata("description")) return getMetadata("description"); 99 return ""; 100 }; 101 102 const getBreadcrumbItems = () => { 103 if (!breadcrumbs || !Array.isArray(breadcrumbs)) { 104 return []; 105 } 106 return breadcrumbs.map((bcrumb) => ({ 107 "@type": "ListItem", 108 position: bcrumb.position, 109 name: bcrumb.name, 110 item: bcrumb.item, 111 })); 112 }; 113 114 const getType = () => { 115 if (getMetadata("schema-type")) return getMetadata("schema-type"); 116 if (pagePath.endsWith("home")) return "WebSite"; 117 return "WebPage"; 118 }; 119 const isRegionPage = pagePath.includes('/region/'); 120 121 let structuredData = { 122 "@context": "https://schema.org", 123 "@graph": [], 124 }; 125 126 let finalPrimaryImageOfPage = JSON.parse(JSON.stringify(primaryImageOfPage)); 127 if (primaryImageOfPage && primaryImageOfPage?.originalPath) { 128 delete finalPrimaryImageOfPage?.originalPath; 129 } 130 131 const pageData = { 132 "@type": getType(), 133 url: currentPageURL, 134 name: getName(), 135 ...(!pagePath?.endsWith("home") && { 136 description: getDescription(), 137 }), 138 ...(getType() !== "Person" && { inLanguage: getLanguage() }), 139 ...(pagePath?.endsWith("home") && 140 organization?.image && { image: organization.image }), 141 ...(pagePath?.endsWith("home") && { 142 potentialAction: { 143 "@type": "SearchAction", 144 target: "https://www.worldbank.org/en/search?q={search_term_string}", 145 "query-input": "required name=search_term_string", 146 }, 147 }), 148 ...(finalPrimaryImageOfPage && 149 typeof finalPrimaryImageOfPage === "object" && 150 Object.keys(finalPrimaryImageOfPage).length > 0 && 151 !pagePath?.endsWith("home") && 152 getType() !== "Person" && { 153 primaryImageOfPage: finalPrimaryImageOfPage, 154 }), 155 ...(publisher && 156 typeof publisher === "object" && 157 Object.keys(publisher).length > 0 && 158 getType() !== "Person" && { 159 publisher: publisher, 160 }), 161 }; 162 163 if ( 164 apiData["bio-detail"] && 165 typeof apiData["bio-detail"] === "object" && 166 Object.keys(apiData["bio-detail"]).length > 0 167 ) { 168 let sameAs = []; 169 const links = Object.keys(apiData["bio-detail"])?.filter((item) => 170 item.includes("-link") 171 ); 172 links.forEach((link) => { 173 if (apiData["bio-detail"][link]) { 174 sameAs.push(apiData["bio-detail"][link].trim()); 175 } 176 }); 177 pageData.jobTitle = apiData["bio-detail"]["job-title"]?.trim(); 178 pageData.worksFor = apiData.worksfor;
179 pageData.sameAs = sameAs; 180 pageData.image = apiData["bio-detail"].image?.trim(); 181 } 182 183 structuredData["@graph"].push(pageData); 184 185 if ( 186 organization && 187 typeof organization === "object" && 188 Object.keys(organization) && 189 getType() !== "Person" && 190 !isRegionPage 191 ) { 192 let finalOrganization = JSON.parse(JSON.stringify(organization)); 193 if (pagePath.endsWith("home")) { 194 delete finalOrganization?.image; 195 } 196 structuredData["@graph"].push(finalOrganization); 197 } 198 199 const breadcrumbItems = getBreadcrumbItems(); 200 201 if (pagePath.endsWith("contacts")) { 202 const contactsSchema = { 203 ...contacts, 204 ...(breadcrumbItems?.length > 0 && { 205 breadcrumb: { 206 "@type": "BreadcrumbList", 207 itemListElement: breadcrumbItems, 208 }, 209 }), 210 }; 211 structuredData["@graph"].push(contactsSchema); 212 } else { 213 if (breadcrumbItems.length > 0) { 214 structuredData["@graph"].push({ 215 "@type": "BreadcrumbList", 216 itemListElement: breadcrumbItems, 217 }); 218 } 219 } 220 return structuredData; 221}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.