UNPKG

@unhead/schema-org-vue

Version:

Vue Schema.org for Simple and Automated Google Rich Results for everyone

1,827 lines (1,784 loc) 67.2 kB
import { useHead } from '@unhead/vue'; import { defineComponent, ref, computed, unref, h, onMounted, nextTick, watch, onBeforeUnmount } from 'vue'; const name = "@unhead/schema-org-vue"; const schemaAutoImports = [ "defineAddress", "defineAggregateOffer", "defineAggregateRating", "defineArticle", "defineBook", "defineBookEdition", "defineBreadcrumb", "defineComment", "defineCourse", "defineEvent", "defineHowTo", "defineHowToStep", "defineImage", "defineItemList", "defineJobPosting", "defineListItem", "defineLocalBusiness", "defineMovie", "defineOffer", "defineOpeningHours", "defineOrganization", "definePerson", "definePlace", "defineProduct", "defineQuestion", "defineReadAction", "defineRecipe", "defineReview", "defineSearchAction", "defineSoftwareApp", "defineVideo", "defineVirtualLocation", "defineWebPage", "defineWebSite", "useSchemaOrg" ]; const schemaOrgAutoImports = [ { from: name, imports: schemaAutoImports } ]; const schemaOrgComponents = [ "SchemaOrgDebug", "SchemaOrgArticle", "SchemaOrgBreadcrumb", "SchemaOrgComment", "SchemaOrgEvent", "SchemaOrgHowTo", "SchemaOrgImage", "SchemaOrgJobPosting", "SchemaOrgLocalBusiness", "SchemaOrgOrganization", "SchemaOrgPerson", "SchemaOrgProduct", "SchemaOrgQuestion", "SchemaOrgRecipe", "SchemaOrgReview", "SchemaOrgVideo", "SchemaOrgWebPage", "SchemaOrgWebSite", "SchemaOrgMovie", "SchemaOrgCourse", "SchemaOrgItemList", "SchemaOrgBook", "SchemaOrgSoftwareApp" ]; function SchemaOrgResolver(options = {}) { const { prefix = "" } = options; return { type: "component", resolve: (name$1) => { if (name$1.startsWith(prefix)) { const componentName = name$1.substring(prefix.length); if (schemaOrgComponents.includes(componentName)) { return { name: componentName, from: name }; } } } }; } function provideResolver(input, resolver) { if (!input) input = {}; input._resolver = resolver; return input; } function defineAddress(input) { return provideResolver(input, "address"); } function defineAggregateOffer(input) { return provideResolver(input, "aggregateOffer"); } function defineAggregateRating(input) { return provideResolver(input, "aggregateRating"); } function defineArticle(input) { return provideResolver(input, "article"); } function defineBreadcrumb(input) { return provideResolver(input, "breadcrumb"); } function defineComment(input) { return provideResolver(input, "comment"); } function defineEvent(input) { return provideResolver(input, "event"); } function defineVirtualLocation(input) { return provideResolver(input, "virtualLocation"); } function definePlace(input) { return provideResolver(input, "place"); } function defineHowTo(input) { return provideResolver(input, "howTo"); } function defineHowToStep(input) { return provideResolver(input, "howToStep"); } function defineImage(input) { return provideResolver(input, "image"); } function defineJobPosting(input) { return provideResolver(input, "jobPosting"); } function defineLocalBusiness(input) { return provideResolver(input, "localBusiness"); } function defineOffer(input) { return provideResolver(input, "offer"); } function defineOpeningHours(input) { return provideResolver(input, "openingHours"); } function defineOrganization(input) { return provideResolver(input, "organization"); } function definePerson(input) { return provideResolver(input, "person"); } function defineProduct(input) { return provideResolver(input, "product"); } function defineQuestion(input) { return provideResolver(input, "question"); } function defineRecipe(input) { return provideResolver(input, "recipe"); } function defineReview(input) { return provideResolver(input, "review"); } function defineVideo(input) { return provideResolver(input, "video"); } function defineWebPage(input) { return provideResolver(input, "webPage"); } function defineWebSite(input) { return provideResolver(input, "webSite"); } function defineBook(input) { return provideResolver(input, "book"); } function defineCourse(input) { return provideResolver(input, "course"); } function defineItemList(input) { return provideResolver(input, "itemList"); } function defineListItem(input) { return provideResolver(input, "listItem"); } function defineMovie(input) { return provideResolver(input, "movie"); } function defineSearchAction(input) { return provideResolver(input, "searchAction"); } function defineReadAction(input) { return provideResolver(input, "readAction"); } function defineSoftwareApp(input) { return provideResolver(input, "softwareApp"); } function defineBookEdition(input) { return provideResolver(input, "bookEdition"); } let isSPA = null; function useSchemaOrg(input) { if (process.env.NODE_ENV !== "development" && typeof window !== "undefined") { if (isSPA === null && !window.document.querySelector("#schema-org-graph")) isSPA = true; if (!isSPA) return; } return useHead({ script: [ { type: "application/ld+json", id: "schema-org-graph", key: "schema-org-graph", // @ts-expect-error runtime type nodes: input } ] }, { mode: isSPA ? "all" : "server" }); } function shallowVNodesToText(nodes) { let text = ""; for (const node of nodes) { if (typeof node.children === "string") text += node.children.trim(); } return text; } function fixKey(s) { let key = s.replace(/-./g, (x) => x[1].toUpperCase()); if (key === "type" || key === "id") key = `@${key}`; return key; } function ignoreKey(s) { if (s.startsWith("aria-") || s.startsWith("data-")) return false; return ["class", "style"].includes(s); } function defineSchemaOrgComponent(name, defineFn) { return defineComponent({ name, props: { as: String }, setup(props, { slots, attrs }) { const node = ref(null); const nodePartial = computed(() => { const val = {}; Object.entries(unref(attrs)).forEach(([key, value]) => { if (!ignoreKey(key)) { val[fixKey(key)] = unref(value); } }); if (!node.value) { for (const [key, slot] of Object.entries(slots)) { if (!slot || key === "default") continue; val[fixKey(key)] = shallowVNodesToText(slot(props)); } } return val; }); if (defineFn) { useSchemaOrg(defineFn(unref(nodePartial))); } return () => { const data = unref(nodePartial); if (!slots.default) return null; const childSlots = []; if (slots.default) childSlots.push(slots.default(data)); return h(props.as || "div", {}, childSlots); }; } }); } const SchemaOrgArticle = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgArticle", defineArticle); const SchemaOrgBreadcrumb = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgBreadcrumb", defineBreadcrumb); const SchemaOrgComment = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgComment", defineComment); const SchemaOrgEvent = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgEvent", defineEvent); const SchemaOrgHowTo = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgHowTo", defineHowTo); const SchemaOrgImage = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgImage", defineImage); const SchemaOrgJobPosting = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgJobPosting", defineJobPosting); const SchemaOrgLocalBusiness = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgLocalBusiness", defineLocalBusiness); const SchemaOrgOrganization = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgOrganization", defineOrganization); const SchemaOrgPerson = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgPerson", definePerson); const SchemaOrgProduct = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgProduct", defineProduct); const SchemaOrgQuestion = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgQuestion", defineQuestion); const SchemaOrgRecipe = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgRecipe", defineRecipe); const SchemaOrgReview = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgReview", defineReview); const SchemaOrgVideo = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgVideo", defineVideo); const SchemaOrgWebPage = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgWebPage", defineWebPage); const SchemaOrgWebSite = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgWebSite", defineWebSite); const SchemaOrgMovie = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgMovie", defineMovie); const SchemaOrgCourse = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgCourse", defineCourse); const SchemaOrgItemList = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgItemList", defineItemList); const SchemaOrgBook = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgBook", defineBook); const SchemaOrgSoftwareApp = /* @__PURE__ */ defineSchemaOrgComponent("SchemaOrgSoftwareApp", defineSoftwareApp); const SchemaOrgDebug = defineComponent({ name: "SchemaOrgDebug", props: { console: { type: Boolean, default: false } }, setup(props) { const schemaRaw = ref(""); let observer; onMounted(() => { nextTick(() => { let $el = document.querySelector('script[type="application/ld+json"]'); if (!$el) return; const fetchSchema = () => { $el = document.querySelector('script[type="application/ld+json"]'); schemaRaw.value = $el?.textContent || ""; }; observer = new MutationObserver(fetchSchema); observer.observe(document.body, { childList: true, characterData: true, attributes: true, subtree: true }); fetchSchema(); }); }); if (props.console) { watch(schemaRaw, (val) => { console.info("[SchemaOrgDebug]", JSON.parse(unref(val))); }); } onBeforeUnmount(() => { observer?.disconnect(); }); return () => { return h("div", { style: { display: "inline-block" } }, [h("div", { style: { backgroundColor: "#282839", color: "#c5c6c9", padding: "5px", borderRadius: "5px", width: "900px", height: "600px", overflowY: "auto", boxShadow: "3px 4px 15px rgb(0 0 0 / 10%)" } }, [ h("pre", { style: { textAlign: "left" } }, schemaRaw.value) ])]); }; } }); const PROTOCOL_STRICT_REGEX = /^\w{2,}:([/\\]{1,2})/; const PROTOCOL_REGEX = /^\w{2,}:([/\\]{2})?/; const PROTOCOL_RELATIVE_REGEX = /^([/\\]\s*){2,}[^/\\]/; function hasProtocol(inputString, opts = {}) { if (typeof opts === "boolean") { opts = { acceptRelative: opts }; } if (opts.strict) { return PROTOCOL_STRICT_REGEX.test(inputString); } return PROTOCOL_REGEX.test(inputString) || (opts.acceptRelative ? PROTOCOL_RELATIVE_REGEX.test(inputString) : false); } const TRAILING_SLASH_RE = /\/$|\/\?/; function hasTrailingSlash(input = "", queryParameters = false) { if (!queryParameters) { return input.endsWith("/"); } return TRAILING_SLASH_RE.test(input); } function withoutTrailingSlash(input = "", queryParameters = false) { if (!queryParameters) { return (hasTrailingSlash(input) ? input.slice(0, -1) : input) || "/"; } if (!hasTrailingSlash(input, true)) { return input || "/"; } const [s0, ...s] = input.split("?"); return (s0.slice(0, -1) || "/") + (s.length > 0 ? `?${s.join("?")}` : ""); } function withTrailingSlash(input = "", queryParameters = false) { if (!queryParameters) { return input.endsWith("/") ? input : input + "/"; } if (hasTrailingSlash(input, true)) { return input || "/"; } const [s0, ...s] = input.split("?"); return s0 + "/" + (s.length > 0 ? `?${s.join("?")}` : ""); } function hasLeadingSlash(input = "") { return input.startsWith("/"); } function withoutLeadingSlash(input = "") { return (hasLeadingSlash(input) ? input.slice(1) : input) || "/"; } function withBase(input, base) { if (isEmptyURL(base) || hasProtocol(input)) { return input; } const _base = withoutTrailingSlash(base); if (input.startsWith(_base)) { return input; } return joinURL(_base, input); } function isEmptyURL(url) { return !url || url === "/"; } function isNonEmptyURL(url) { return url && url !== "/"; } function joinURL(base, ...input) { let url = base || ""; for (const index of input.filter((url2) => isNonEmptyURL(url2))) { url = url ? withTrailingSlash(url) + withoutLeadingSlash(index) : index; } return url; } function defineSchemaOrgResolver(schema) { return schema; } function idReference(node) { return { "@id": typeof node !== "string" ? node["@id"] : node }; } function resolvableDateToDate(val) { try { const date = val instanceof Date ? val : new Date(Date.parse(val)); return `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`; } catch (e) { } return typeof val === "string" ? val : val.toString(); } function resolvableDateToIso(val) { if (!val) return val; try { if (val instanceof Date) return val.toISOString(); else return new Date(Date.parse(val)).toISOString(); } catch (e) { } return typeof val === "string" ? val : val.toString(); } const IdentityId = "#identity"; function setIfEmpty(node, field, value) { if (!node?.[field] && value) node[field] = value; } function asArray(input) { return Array.isArray(input) ? input : [input]; } function dedupeMerge(node, field, value) { const dedupeMerge2 = []; const input = asArray(node[field]); dedupeMerge2.push(...input); const data = new Set(dedupeMerge2); data.add(value); node[field] = [...data.values()].filter(Boolean); } function prefixId(url, id) { if (hasProtocol(id)) return url; if (!id.startsWith("#")) id = `#${id}`; return joinURL(url, id); } function trimLength(val, length) { if (!val) return val; if (val.length > length) { const trimmedString = val.substring(0, length); return trimmedString.substring(0, Math.min(trimmedString.length, trimmedString.lastIndexOf(" "))); } return val; } function resolveDefaultType(node, defaultType) { const val = node["@type"]; if (val === defaultType) return; const types = /* @__PURE__ */ new Set([ ...asArray(defaultType), ...asArray(val) ]); node["@type"] = types.size === 1 ? val : [...types.values()]; } function resolveWithBase(base, urlOrPath) { if (!urlOrPath || hasProtocol(urlOrPath) || !urlOrPath.startsWith("/") && !urlOrPath.startsWith("#")) return urlOrPath; return withBase(urlOrPath, base); } function resolveAsGraphKey(key) { if (!key) return key; return key.substring(key.lastIndexOf("#")); } function stripEmptyProperties(obj) { Object.keys(obj).forEach((k) => { if (obj[k] && typeof obj[k] === "object") { if (obj[k].__v_isReadonly || obj[k].__v_isRef) return; stripEmptyProperties(obj[k]); return; } if (obj[k] === "" || obj[k] === null || typeof obj[k] === "undefined") delete obj[k]; }); return obj; } function hashCode(s) { let h = 9; for (let i = 0; i < s.length; ) h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9); return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase(); } const offerResolver = defineSchemaOrgResolver({ cast(node) { if (typeof node === "number" || typeof node === "string") { return { price: node }; } return node; }, defaults: { "@type": "Offer", "availability": "InStock" }, resolve(node, ctx) { setIfEmpty(node, "priceCurrency", ctx.meta.currency); setIfEmpty(node, "priceValidUntil", new Date(Date.UTC((/* @__PURE__ */ new Date()).getFullYear() + 1, 12, -1, 0, 0, 0))); if (node.url) resolveWithBase(ctx.meta.host, node.url); if (node.availability) node.availability = withBase(node.availability, "https://schema.org/"); if (node.priceValidUntil) node.priceValidUntil = resolvableDateToIso(node.priceValidUntil); return node; } }); const aggregateOfferResolver = defineSchemaOrgResolver({ defaults: { "@type": "AggregateOffer" }, inheritMeta: [ { meta: "currency", key: "priceCurrency" } ], resolve(node, ctx) { node.offers = resolveRelation(node.offers, ctx, offerResolver); if (node.offers) setIfEmpty(node, "offerCount", asArray(node.offers).length); return node; } }); const aggregateRatingResolver = defineSchemaOrgResolver({ defaults: { "@type": "AggregateRating" } }); const searchActionResolver = defineSchemaOrgResolver({ defaults: { "@type": "SearchAction", "target": { "@type": "EntryPoint" }, "query-input": { "@type": "PropertyValueSpecification", "valueRequired": true, "valueName": "search_term_string" } }, resolve(node, ctx) { if (typeof node.target === "string") { node.target = { "@type": "EntryPoint", "urlTemplate": resolveWithBase(ctx.meta.host, node.target) }; } return node; } }); const PrimaryWebSiteId = "#website"; const webSiteResolver = defineSchemaOrgResolver({ defaults: { "@type": "WebSite" }, inheritMeta: [ "inLanguage", { meta: "host", key: "url" } ], idPrefix: ["host", PrimaryWebSiteId], resolve(node, ctx) { node.potentialAction = resolveRelation(node.potentialAction, ctx, searchActionResolver, { array: true }); node.publisher = resolveRelation(node.publisher, ctx); return node; }, resolveRootNode(node, { find }) { if (resolveAsGraphKey(node["@id"]) === PrimaryWebSiteId) { const identity = find(IdentityId); if (identity) setIfEmpty(node, "publisher", idReference(identity)); const webPage = find(PrimaryWebPageId); if (webPage) setIfEmpty(webPage, "isPartOf", idReference(node)); } return node; } }); const listItemResolver = defineSchemaOrgResolver({ cast(node) { if (typeof node === "string") { node = { name: node }; } return node; }, defaults: { "@type": "ListItem" }, resolve(node, ctx) { if (typeof node.item === "string") node.item = resolveWithBase(ctx.meta.host, node.item); else if (typeof node.item === "object") node.item = resolveRelation(node.item, ctx); return node; } }); const PrimaryBreadcrumbId = "#breadcrumb"; const breadcrumbResolver = defineSchemaOrgResolver({ defaults: { "@type": "BreadcrumbList" }, idPrefix: ["url", PrimaryBreadcrumbId], resolve(breadcrumb, ctx) { if (breadcrumb.itemListElement) { let index = 1; breadcrumb.itemListElement = resolveRelation(breadcrumb.itemListElement, ctx, listItemResolver, { array: true, afterResolve(node) { setIfEmpty(node, "position", index++); } }); } return breadcrumb; }, resolveRootNode(node, { find }) { const webPage = find(PrimaryWebPageId); if (webPage) setIfEmpty(webPage, "breadcrumb", idReference(node)); } }); const imageResolver = defineSchemaOrgResolver({ alias: "image", cast(input) { if (typeof input === "string") { input = { url: input }; } return input; }, defaults: { "@type": "ImageObject" }, inheritMeta: [ // @todo possibly only do if there's a caption "inLanguage" ], idPrefix: "host", resolve(image, { meta }) { image.url = resolveWithBase(meta.host, image.url); setIfEmpty(image, "contentUrl", image.url); if (image.height && !image.width) delete image.height; if (image.width && !image.height) delete image.width; return image; } }); const addressResolver = defineSchemaOrgResolver({ defaults: { "@type": "PostalAddress" } }); const organizationResolver = defineSchemaOrgResolver({ defaults: { "@type": "Organization" }, idPrefix: ["host", IdentityId], inheritMeta: [ { meta: "host", key: "url" } ], resolve(node, ctx) { resolveDefaultType(node, "Organization"); node.address = resolveRelation(node.address, ctx, addressResolver); return node; }, resolveRootNode(node, ctx) { const isIdentity = resolveAsGraphKey(node["@id"]) === IdentityId; const webPage = ctx.find(PrimaryWebPageId); if (node.logo) { node.logo = resolveRelation(node.logo, ctx, imageResolver, { root: true, afterResolve(logo) { if (isIdentity) logo["@id"] = prefixId(ctx.meta.host, "#logo"); setIfEmpty(logo, "caption", node.name); } }); if (webPage) setIfEmpty(webPage, "primaryImageOfPage", idReference(node.logo)); } if (isIdentity && webPage) setIfEmpty(webPage, "about", idReference(node)); const webSite = ctx.find(PrimaryWebSiteId); if (webSite) setIfEmpty(webSite, "publisher", idReference(node)); } }); const personResolver = defineSchemaOrgResolver({ cast(node) { if (typeof node === "string") { return { name: node }; } return node; }, defaults: { "@type": "Person" }, idPrefix: ["host", IdentityId], resolveRootNode(node, { find, meta }) { if (resolveAsGraphKey(node["@id"]) === IdentityId) { setIfEmpty(node, "url", meta.host); const webPage = find(PrimaryWebPageId); if (webPage) setIfEmpty(webPage, "about", idReference(node)); const webSite = find(PrimaryWebSiteId); if (webSite) setIfEmpty(webSite, "publisher", idReference(node)); } const article = find(PrimaryArticleId); if (article) setIfEmpty(article, "author", idReference(node)); } }); const readActionResolver = defineSchemaOrgResolver({ defaults: { "@type": "ReadAction" }, resolve(node, ctx) { if (!node.target.includes(ctx.meta.url)) node.target.unshift(ctx.meta.url); return node; } }); const PrimaryWebPageId = "#webpage"; const webPageResolver = defineSchemaOrgResolver({ defaults({ meta }) { const endPath = withoutTrailingSlash(meta.url.substring(meta.url.lastIndexOf("/") + 1)); let type = "WebPage"; switch (endPath) { case "about": case "about-us": type = "AboutPage"; break; case "search": type = "SearchResultsPage"; break; case "checkout": type = "CheckoutPage"; break; case "contact": case "get-in-touch": case "contact-us": type = "ContactPage"; break; case "faq": type = "FAQPage"; break; } const defaults = { "@type": type }; return defaults; }, idPrefix: ["url", PrimaryWebPageId], inheritMeta: [ { meta: "title", key: "name" }, "description", "datePublished", "dateModified", "url" ], resolve(node, ctx) { node.dateModified = resolvableDateToIso(node.dateModified); node.datePublished = resolvableDateToIso(node.datePublished); resolveDefaultType(node, "WebPage"); node.about = resolveRelation(node.about, ctx, organizationResolver); node.breadcrumb = resolveRelation(node.breadcrumb, ctx, breadcrumbResolver); node.author = resolveRelation(node.author, ctx, personResolver); node.primaryImageOfPage = resolveRelation(node.primaryImageOfPage, ctx, imageResolver); node.potentialAction = resolveRelation(node.potentialAction, ctx, readActionResolver); if (node["@type"] === "WebPage") { setIfEmpty(node, "potentialAction", [ { "@type": "ReadAction", "target": [ctx.meta.url] } ]); } return node; }, resolveRootNode(webPage, { find, meta }) { const identity = find(IdentityId); const webSite = find(PrimaryWebSiteId); const logo = find("#logo"); if (identity && meta.url === meta.host) setIfEmpty(webPage, "about", idReference(identity)); if (logo) setIfEmpty(webPage, "primaryImageOfPage", idReference(logo)); if (webSite) setIfEmpty(webPage, "isPartOf", idReference(webSite)); const breadcrumb = find(PrimaryBreadcrumbId); if (breadcrumb) setIfEmpty(webPage, "breadcrumb", idReference(breadcrumb)); return webPage; } }); const PrimaryArticleId = "#article"; const articleResolver = defineSchemaOrgResolver({ defaults: { "@type": "Article" }, inheritMeta: [ "inLanguage", "description", "image", "dateModified", "datePublished", { meta: "title", key: "headline" } ], idPrefix: ["url", PrimaryArticleId], resolve(node, ctx) { node.author = resolveRelation(node.author, ctx, personResolver, { root: true }); node.publisher = resolveRelation(node.publisher, ctx); node.dateModified = resolvableDateToIso(node.dateModified); node.datePublished = resolvableDateToIso(node.datePublished); resolveDefaultType(node, "Article"); node.headline = trimLength(node.headline, 110); return node; }, resolveRootNode(node, { find, meta }) { const webPage = find(PrimaryWebPageId); const identity = find(IdentityId); if (node.image && !node.thumbnailUrl) { const firstImage = asArray(node.image)[0]; if (typeof firstImage === "string") setIfEmpty(node, "thumbnailUrl", resolveWithBase(meta.host, firstImage)); else if (firstImage?.["@id"]) setIfEmpty(node, "thumbnailUrl", find(firstImage["@id"])?.url); } if (identity) { setIfEmpty(node, "publisher", idReference(identity)); setIfEmpty(node, "author", idReference(identity)); } if (webPage) { setIfEmpty(node, "isPartOf", idReference(webPage)); setIfEmpty(node, "mainEntityOfPage", idReference(webPage)); setIfEmpty(webPage, "potentialAction", [ { "@type": "ReadAction", "target": [meta.url] } ]); setIfEmpty(webPage, "dateModified", node.dateModified); setIfEmpty(webPage, "datePublished", node.datePublished); } return node; } }); const bookEditionResolver = defineSchemaOrgResolver({ defaults: { "@type": "Book" }, inheritMeta: [ "inLanguage" ], resolve(node, ctx) { if (node.bookFormat) node.bookFormat = withBase(node.bookFormat, "https://schema.org/"); if (node.datePublished) node.datePublished = resolvableDateToDate(node.datePublished); node.author = resolveRelation(node.author, ctx); return node; }, resolveRootNode(node, { find }) { const identity = find(IdentityId); if (identity) setIfEmpty(node, "provider", idReference(identity)); return node; } }); const PrimaryBookId = "#book"; const bookResolver = defineSchemaOrgResolver({ defaults: { "@type": "Book" }, inheritMeta: [ "description", "url", { meta: "title", key: "name" } ], idPrefix: ["url", PrimaryBookId], resolve(node, ctx) { node.workExample = resolveRelation(node.workExample, ctx, bookEditionResolver); node.author = resolveRelation(node.author, ctx); if (node.url) withBase(node.url, ctx.meta.host); return node; }, resolveRootNode(node, { find }) { const identity = find(IdentityId); if (identity) setIfEmpty(node, "author", idReference(identity)); return node; } }); const commentResolver = defineSchemaOrgResolver({ defaults: { "@type": "Comment" }, idPrefix: "url", resolve(node, ctx) { node.author = resolveRelation(node.author, ctx, personResolver, { root: true }); return node; }, resolveRootNode(node, { find }) { const article = find(PrimaryArticleId); if (article) setIfEmpty(node, "about", idReference(article)); } }); const courseResolver = defineSchemaOrgResolver({ defaults: { "@type": "Course" }, resolve(node, ctx) { node.provider = resolveRelation(node.provider, ctx, organizationResolver, { root: true }); return node; }, resolveRootNode(node, { find }) { const identity = find(IdentityId); if (identity) setIfEmpty(node, "provider", idReference(identity)); return node; } }); const placeResolver = defineSchemaOrgResolver({ defaults: { "@type": "Place" }, resolve(node, ctx) { if (typeof node.address !== "string") node.address = resolveRelation(node.address, ctx, addressResolver); return node; } }); const virtualLocationResolver = defineSchemaOrgResolver({ cast(node) { if (typeof node === "string") { return { url: node }; } return node; }, defaults: { "@type": "VirtualLocation" } }); const PrimaryEventId = "#event"; const eventResolver = defineSchemaOrgResolver({ defaults: { "@type": "Event" }, inheritMeta: [ "inLanguage", "description", "image", { meta: "title", key: "name" } ], idPrefix: ["url", PrimaryEventId], resolve(node, ctx) { if (node.location) { const isVirtual = node.location === "string" || node.location?.url !== "undefined"; node.location = resolveRelation(node.location, ctx, isVirtual ? virtualLocationResolver : placeResolver); } node.performer = resolveRelation(node.performer, ctx, personResolver, { root: true }); node.organizer = resolveRelation(node.organizer, ctx, organizationResolver, { root: true }); node.offers = resolveRelation(node.offers, ctx, offerResolver); if (node.eventAttendanceMode) node.eventAttendanceMode = withBase(node.eventAttendanceMode, "https://schema.org/"); if (node.eventStatus) node.eventStatus = withBase(node.eventStatus, "https://schema.org/"); const isOnline = node.eventStatus === "https://schema.org/EventMovedOnline"; const dates = ["startDate", "previousStartDate", "endDate"]; dates.forEach((date) => { if (!isOnline) { if (node[date] instanceof Date && node[date].getHours() === 0 && node[date].getMinutes() === 0) node[date] = resolvableDateToDate(node[date]); } else { node[date] = resolvableDateToIso(node[date]); } }); setIfEmpty(node, "endDate", node.startDate); return node; }, resolveRootNode(node, { find }) { const identity = find(IdentityId); if (identity) setIfEmpty(node, "organizer", idReference(identity)); } }); const howToStepDirectionResolver = defineSchemaOrgResolver({ cast(node) { if (typeof node === "string") { return { text: node }; } return node; }, defaults: { "@type": "HowToDirection" } }); const howToStepResolver = defineSchemaOrgResolver({ cast(node) { if (typeof node === "string") { return { text: node }; } return node; }, defaults: { "@type": "HowToStep" }, resolve(step, ctx) { if (step.url) step.url = resolveWithBase(ctx.meta.url, step.url); if (step.image) { step.image = resolveRelation(step.image, ctx, imageResolver, { root: true }); } if (step.itemListElement) step.itemListElement = resolveRelation(step.itemListElement, ctx, howToStepDirectionResolver); return step; } }); const HowToId = "#howto"; const howToResolver = defineSchemaOrgResolver({ defaults: { "@type": "HowTo" }, inheritMeta: [ "description", "image", "inLanguage", { meta: "title", key: "name" } ], idPrefix: ["url", HowToId], resolve(node, ctx) { node.step = resolveRelation(node.step, ctx, howToStepResolver); return node; }, resolveRootNode(node, { find }) { const webPage = find(PrimaryWebPageId); if (webPage) setIfEmpty(node, "mainEntityOfPage", idReference(webPage)); } }); const itemListResolver = defineSchemaOrgResolver({ defaults: { "@type": "ItemList" }, resolve(node, ctx) { if (node.itemListElement) { let index = 1; node.itemListElement = resolveRelation(node.itemListElement, ctx, listItemResolver, { array: true, afterResolve(node2) { setIfEmpty(node2, "position", index++); } }); } return node; } }); const quantitativeValueResolver = defineSchemaOrgResolver({ defaults: { "@type": "QuantitativeValue" } }); const monetaryAmountResolver = defineSchemaOrgResolver({ defaults: { "@type": "MonetaryAmount" }, resolve(node, ctx) { node.value = resolveRelation(node.value, ctx, quantitativeValueResolver); return node; } }); const jobPostingResolver = defineSchemaOrgResolver({ defaults: { "@type": "JobPosting" }, resolve(node, ctx) { node.datePosted = resolvableDateToIso(node.datePosted); node.hiringOrganization = resolveRelation(node.hiringOrganization, ctx, organizationResolver); node.jobLocation = resolveRelation(node.jobLocation, ctx, placeResolver); node.baseSalary = resolveRelation(node.baseSalary, ctx, monetaryAmountResolver); node.validThrough = resolvableDateToIso(node.validThrough); return node; } }); const openingHoursResolver = defineSchemaOrgResolver({ defaults: { "@type": "OpeningHoursSpecification", "opens": "00:00", "closes": "23:59" } }); const localBusinessResolver = defineSchemaOrgResolver({ defaults: { "@type": ["Organization", "LocalBusiness"] }, inheritMeta: [ { key: "url", meta: "host" }, { key: "currenciesAccepted", meta: "currency" } ], idPrefix: ["host", IdentityId], resolve(node, ctx) { resolveDefaultType(node, ["Organization", "LocalBusiness"]); node.address = resolveRelation(node.address, ctx, addressResolver); node.openingHoursSpecification = resolveRelation(node.openingHoursSpecification, ctx, openingHoursResolver); node.logo = resolveRelation(node.logo, ctx, imageResolver, { afterResolve(logo) { const hasLogo = !!ctx.find("#logo"); if (!hasLogo) logo["@id"] = prefixId(ctx.meta.host, "#logo"); setIfEmpty(logo, "caption", node.name); } }); return node; } }); const ratingResolver = defineSchemaOrgResolver({ cast(node) { if (node === "number") { return { ratingValue: node }; } return node; }, defaults: { "@type": "Rating", "bestRating": 5, "worstRating": 1 } }); const reviewResolver = defineSchemaOrgResolver({ defaults: { "@type": "Review" }, inheritMeta: [ "inLanguage" ], resolve(review, ctx) { review.reviewRating = resolveRelation(review.reviewRating, ctx, ratingResolver); review.author = resolveRelation(review.author, ctx, personResolver); return review; } }); const videoResolver = defineSchemaOrgResolver({ cast(input) { if (typeof input === "string") { input = { url: input }; } return input; }, alias: "video", defaults: { "@type": "VideoObject" }, inheritMeta: [ { meta: "title", key: "name" }, "description", "image", "inLanguage", { meta: "datePublished", key: "uploadDate" } ], idPrefix: "host", resolve(video, ctx) { if (video.uploadDate) video.uploadDate = resolvableDateToIso(video.uploadDate); video.url = resolveWithBase(ctx.meta.host, video.url); if (video.caption && !video.description) video.description = video.caption; if (!video.description) video.description = "No description"; if (video.thumbnailUrl) video.thumbnailUrl = resolveRelation(video.thumbnailUrl, ctx, imageResolver); return video; }, resolveRootNode(video, { find }) { if (video.image && !video.thumbnailUrl) { const firstImage = asArray(video.image)[0]; setIfEmpty(video, "thumbnailUrl", find(firstImage["@id"])?.url); } } }); const movieResolver = defineSchemaOrgResolver({ defaults: { "@type": "Movie" }, resolve(node, ctx) { node.aggregateRating = resolveRelation(node.aggregateRating, ctx, aggregateRatingResolver); node.review = resolveRelation(node.review, ctx, reviewResolver); node.director = resolveRelation(node.director, ctx, personResolver); node.actor = resolveRelation(node.actor, ctx, personResolver); node.trailer = resolveRelation(node.trailer, ctx, videoResolver); if (node.dateCreated) node.dateCreated = resolvableDateToDate(node.dateCreated); return node; } }); const defaults = { ignoreUnknown: false, respectType: false, respectFunctionNames: false, respectFunctionProperties: false, unorderedObjects: true, unorderedArrays: false, unorderedSets: false }; function objectHash(object, options = {}) { options = { ...defaults, ...options }; const hasher = createHasher(options); hasher.dispatch(object); return hasher.toString(); } function createHasher(options) { const buff = []; let context = []; const write = (str) => { buff.push(str); }; return { toString() { return buff.join(""); }, getContext() { return context; }, dispatch(value) { if (options.replacer) { value = options.replacer(value); } const type = value === null ? "null" : typeof value; return this["_" + type](value); }, _object(object) { if (object && typeof object.toJSON === "function") { return this._object(object.toJSON()); } const pattern = /\[object (.*)]/i; const objString = Object.prototype.toString.call(object); const _objType = pattern.exec(objString); const objType = _objType ? _objType[1].toLowerCase() : "unknown:[" + objString.toLowerCase() + "]"; let objectNumber = null; if ((objectNumber = context.indexOf(object)) >= 0) { return this.dispatch("[CIRCULAR:" + objectNumber + "]"); } else { context.push(object); } if (typeof Buffer !== "undefined" && Buffer.isBuffer && Buffer.isBuffer(object)) { write("buffer:"); return write(object.toString("utf8")); } if (objType !== "object" && objType !== "function" && objType !== "asyncfunction") { if (this["_" + objType]) { this["_" + objType](object); } else if (!options.ignoreUnknown) { this._unkown(object, objType); } } else { let keys = Object.keys(object); if (options.unorderedObjects) { keys = keys.sort(); } if (options.respectType !== false && !isNativeFunction(object)) { keys.splice(0, 0, "prototype", "__proto__", "letructor"); } if (options.excludeKeys) { keys = keys.filter(function(key) { return !options.excludeKeys(key); }); } write("object:" + keys.length + ":"); for (const key of keys) { this.dispatch(key); write(":"); if (!options.excludeValues) { this.dispatch(object[key]); } write(","); } } }, _array(arr, unordered) { unordered = typeof unordered !== "undefined" ? unordered : options.unorderedArrays !== false; write("array:" + arr.length + ":"); if (!unordered || arr.length <= 1) { for (const entry of arr) { this.dispatch(entry); } return; } const contextAdditions = []; const entries = arr.map((entry) => { const hasher = createHasher(options); hasher.dispatch(entry); contextAdditions.push(hasher.getContext()); return hasher.toString(); }); context = [...context, ...contextAdditions]; entries.sort(); return this._array(entries, false); }, _date(date) { return write("date:" + date.toJSON()); }, _symbol(sym) { return write("symbol:" + sym.toString()); }, _unkown(value, type) { write(type); if (!value) { return; } write(":"); if (value && typeof value.entries === "function") { return this._array( Array.from(value.entries()), true /* ordered */ ); } }, _error(err) { return write("error:" + err.toString()); }, _boolean(bool) { return write("bool:" + bool.toString()); }, _string(string) { write("string:" + string.length + ":"); write(string.toString()); }, _function(fn) { write("fn:"); if (isNativeFunction(fn)) { this.dispatch("[native]"); } else { this.dispatch(fn.toString()); } if (options.respectFunctionNames !== false) { this.dispatch("function-name:" + String(fn.name)); } if (options.respectFunctionProperties) { this._object(fn); } }, _number(number) { return write("number:" + number.toString()); }, _xml(xml) { return write("xml:" + xml.toString()); }, _null() { return write("Null"); }, _undefined() { return write("Undefined"); }, _regexp(regex) { return write("regex:" + regex.toString()); }, _uint8array(arr) { write("uint8array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, _uint8clampedarray(arr) { write("uint8clampedarray:"); return this.dispatch(Array.prototype.slice.call(arr)); }, _int8array(arr) { write("int8array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, _uint16array(arr) { write("uint16array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, _int16array(arr) { write("int16array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, _uint32array(arr) { write("uint32array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, _int32array(arr) { write("int32array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, _float32array(arr) { write("float32array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, _float64array(arr) { write("float64array:"); return this.dispatch(Array.prototype.slice.call(arr)); }, _arraybuffer(arr) { write("arraybuffer:"); return this.dispatch(new Uint8Array(arr)); }, _url(url) { return write("url:" + url.toString()); }, _map(map) { write("map:"); const arr = [...map]; return this._array(arr, options.unorderedSets !== false); }, _set(set) { write("set:"); const arr = [...set]; return this._array(arr, options.unorderedSets !== false); }, _file(file) { write("file:"); return this.dispatch([file.name, file.size, file.type, file.lastModfied]); }, _blob() { if (options.ignoreUnknown) { return write("[blob]"); } throw new Error( 'Hashing Blob objects is currently not supported\nUse "options.replacer" or "options.ignoreUnknown"\n' ); }, _domwindow() { return write("domwindow"); }, _bigint(number) { return write("bigint:" + number.toString()); }, /* Node.js standard native objects */ _process() { return write("process"); }, _timer() { return write("timer"); }, _pipe() { return write("pipe"); }, _tcp() { return write("tcp"); }, _udp() { return write("udp"); }, _tty() { return write("tty"); }, _statwatcher() { return write("statwatcher"); }, _securecontext() { return write("securecontext"); }, _connection() { return write("connection"); }, _zlib() { return write("zlib"); }, _context() { return write("context"); }, _nodescript() { return write("nodescript"); }, _httpparser() { return write("httpparser"); }, _dataview() { return write("dataview"); }, _signal() { return write("signal"); }, _fsevent() { return write("fsevent"); }, _tlswrap() { return write("tlswrap"); } }; } function isNativeFunction(f) { if (typeof f !== "function") { return false; } const exp = /^function\s+\w*\s*\(\s*\)\s*{\s+\[native code]\s+}$/i; return exp.exec(Function.prototype.toString.call(f)) != null; } class WordArray { constructor(words, sigBytes) { words = this.words = words || []; this.sigBytes = sigBytes !== void 0 ? sigBytes : words.length * 4; } toString(encoder) { return (encoder || Hex).stringify(this); } concat(wordArray) { this.clamp(); if (this.sigBytes % 4) { for (let i = 0; i < wordArray.sigBytes; i++) { const thatByte = wordArray.words[i >>> 2] >>> 24 - i % 4 * 8 & 255; this.words[this.sigBytes + i >>> 2] |= thatByte << 24 - (this.sigBytes + i) % 4 * 8; } } else { for (let j = 0; j < wordArray.sigBytes; j += 4) { this.words[this.sigBytes + j >>> 2] = wordArray.words[j >>> 2]; } } this.sigBytes += wordArray.sigBytes; return this; } clamp() { this.words[this.sigBytes >>> 2] &= 4294967295 << 32 - this.sigBytes % 4 * 8; this.words.length = Math.ceil(this.sigBytes / 4); } clone() { return new WordArray([...this.words]); } } const Hex = { stringify(wordArray) { const hexChars = []; for (let i = 0; i < wordArray.sigBytes; i++) { const bite = wordArray.words[i >>> 2] >>> 24 - i % 4 * 8 & 255; hexChars.push((bite >>> 4).toString(16), (bite & 15).toString(16)); } return hexChars.join(""); } }; const Base64 = { stringify(wordArray) { const keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const base64Chars = []; for (let i = 0; i < wordArray.sigBytes; i += 3) { const byte1 = wordArray.words[i >>> 2] >>> 24 - i % 4 * 8 & 255; const byte2 = wordArray.words[i + 1 >>> 2] >>> 24 - (i + 1) % 4 * 8 & 255; const byte3 = wordArray.words[i + 2 >>> 2] >>> 24 - (i + 2) % 4 * 8 & 255; const triplet = byte1 << 16 | byte2 << 8 | byte3; for (let j = 0; j < 4 && i * 8 + j * 6 < wordArray.sigBytes * 8; j++) { base64Chars.push(keyStr.charAt(triplet >>> 6 * (3 - j) & 63)); } } return base64Chars.join(""); } }; const Latin1 = { parse(latin1Str) { const latin1StrLength = latin1Str.length; const words = []; for (let i = 0; i < latin1StrLength; i++) { words[i >>> 2] |= (latin1Str.charCodeAt(i) & 255) << 24 - i % 4 * 8; } return new WordArray(words, latin1StrLength); } }; const Utf8 = { parse(utf8Str) { return Latin1.parse(unescape(encodeURIComponent(utf8Str))); } }; class BufferedBlockAlgorithm { constructor() { this._minBufferSize = 0; this.blockSize = 512 / 32; this.reset(); } reset() { this._data = new WordArray(); this._nDataBytes = 0; } _append(data) { if (typeof data === "string") { data = Utf8.parse(data); } this._data.concat(data); this._nDataBytes += data.sigBytes; } // eslint-disable-next-line @typescript-eslint/no-unused-vars _doProcessBlock(_dataWords, _offset) { } _process(doFlush) { let processedWords; let nBlocksReady = this._data.sigBytes / (this.blockSize * 4); if (doFlush) { nBlocksReady = Math.ceil(nBlocksReady); } else { nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); } const nWordsReady = nBlocksReady * this.blockSize; const nBytesReady = Math.min(nWordsReady * 4, this._data.sigBytes); if (nWordsReady) { for (let offset = 0; offset < nWordsReady; offset += this.blockSize) { this._doProcessBlock(this._data.words, offset); } processedWords = this._data.words.splice(0, nWordsReady); this._data.sigBytes -= nBytesReady; } return new WordArray(processedWords, nBytesReady); } } class Hasher extends BufferedBlockAlgorithm { update(messageUpdate) { this._append(messageUpdate); this._process(); return this; } finalize(messageUpdate) { if (messageUpdate) { this._append(messageUpdate); } } } const H = [ 1779033703, -1150833019, 1013904242, -1521486534, 1359893119, -1694144372, 528734635, 1541459225 ]; const K = [ 1116352408, 1899447441, -1245643825, -373957723, 961987163, 1508970993, -1841331548, -1424204075, -670586216, 310598401, 607225278, 1426881987, 1925078388, -2132889090, -1680079193, -1046744716, -459576895, -272742522, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, -1740746414, -1473132947, -1341970488, -1084653625, -958395405, -710438585, 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, -2117940946, -1838011259, -1564481375, -1474664885, -1035236496, -949202525, -778901479, -694614492, -200395387, 275423344, 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779, 1955562222, 2024104815, -2067236844, -1933114872, -1866530822, -1538233109, -1090935817, -965641998 ]; const W = []; class SHA256 extends Hasher { constructor() { super(); this.reset(); } reset() { super.reset(); this._hash = new WordArray([...H]); } _doProcessBlock(M, offset) { const H2 = this._hash.words; let a = H2[0]; let b = H2[1]; let c = H2[2]; let d = H2[3]; let e = H2[4]; let f = H2[5]; let g = H2[6]; let h = H2[7]; for (let i = 0; i < 64; i++) { if (i < 16) { W[i] = M[offset + i] | 0; } else { const gamma0x = W[i - 15]; const gamma0 = (gamma0x << 25 | gamma0x >>> 7) ^ (gamma0x << 14 | gamma0x >>> 18) ^ gamma0