@pixelated-tech/components 3.2.3 → 3.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.md +5 -0
  2. package/dist/components/carousel/carousel.css +5 -0
  3. package/dist/components/cms/contentful.items.components.js +1 -1
  4. package/dist/components/cms/wordpress.components.js +14 -9
  5. package/dist/components/general/loading.js +16 -15
  6. package/dist/components/menu/menu-accordion.js +9 -14
  7. package/dist/components/menu/menu-simple.css +4 -0
  8. package/dist/components/menu/menu-simple.js +8 -3
  9. package/dist/components/pagebuilder/components/SaveLoadSection.js +1 -1
  10. package/dist/components/seo/404.js +1 -1
  11. package/dist/components/seo/schema-blogposting.js +53 -0
  12. package/dist/components/seo/schema-localbusiness.js +27 -0
  13. package/dist/components/seo/schema-recipe.js +5 -0
  14. package/dist/components/seo/schema-services.js +24 -0
  15. package/dist/components/seo/schema-website.js +13 -0
  16. package/dist/components/shoppingcart/shoppingcart.components.js +1 -1
  17. package/dist/components/structured/buzzwordbingo.js +1 -1
  18. package/dist/components/structured/recipe.css +1 -1
  19. package/dist/components/structured/recipe.js +59 -9
  20. package/dist/components/structured/socialcard.js +1 -1
  21. package/dist/data/recipes.json +3157 -1821
  22. package/dist/index.js +5 -0
  23. package/dist/types/components/cms/wordpress.components.d.ts +1 -0
  24. package/dist/types/components/cms/wordpress.components.d.ts.map +1 -1
  25. package/dist/types/components/general/loading.d.ts.map +1 -1
  26. package/dist/types/components/menu/menu-accordion.d.ts.map +1 -1
  27. package/dist/types/components/menu/menu-simple.d.ts +1 -1
  28. package/dist/types/components/menu/menu-simple.d.ts.map +1 -1
  29. package/dist/types/components/seo/schema-blogposting.d.ts +31 -0
  30. package/dist/types/components/seo/schema-blogposting.d.ts.map +1 -0
  31. package/dist/types/components/seo/schema-localbusiness.d.ts +25 -0
  32. package/dist/types/components/seo/schema-localbusiness.d.ts.map +1 -0
  33. package/dist/types/components/seo/schema-recipe.d.ts +34 -0
  34. package/dist/types/components/seo/schema-recipe.d.ts.map +1 -0
  35. package/dist/types/components/seo/schema-services.d.ts +25 -0
  36. package/dist/types/components/seo/schema-services.d.ts.map +1 -0
  37. package/dist/types/components/seo/schema-website.d.ts +21 -0
  38. package/dist/types/components/seo/schema-website.d.ts.map +1 -0
  39. package/dist/types/components/structured/recipe.d.ts +34 -13
  40. package/dist/types/components/structured/recipe.d.ts.map +1 -1
  41. package/dist/types/index.d.ts +5 -0
  42. package/dist/types/tests/api.test.d.ts +2 -0
  43. package/dist/types/tests/api.test.d.ts.map +1 -0
  44. package/dist/types/tests/buzzwordbingo.test.d.ts +2 -0
  45. package/dist/types/tests/buzzwordbingo.test.d.ts.map +1 -0
  46. package/dist/types/tests/calendly.test.d.ts +2 -0
  47. package/dist/types/tests/calendly.test.d.ts.map +1 -0
  48. package/dist/types/tests/callout.test.d.ts +2 -0
  49. package/dist/types/tests/callout.test.d.ts.map +1 -0
  50. package/dist/types/tests/carousel-drag.test.d.ts +2 -0
  51. package/dist/types/tests/carousel-drag.test.d.ts.map +1 -0
  52. package/dist/types/tests/carousel.test.d.ts +2 -0
  53. package/dist/types/tests/carousel.test.d.ts.map +1 -0
  54. package/dist/types/tests/cloudinary-image.test.d.ts +2 -0
  55. package/dist/types/tests/cloudinary-image.test.d.ts.map +1 -0
  56. package/dist/types/tests/cloudinary.test.d.ts +2 -0
  57. package/dist/types/tests/cloudinary.test.d.ts.map +1 -0
  58. package/dist/types/tests/config.client.test.d.ts +2 -0
  59. package/dist/types/tests/config.client.test.d.ts.map +1 -0
  60. package/dist/types/tests/config.server.test.d.ts +2 -0
  61. package/dist/types/tests/config.server.test.d.ts.map +1 -0
  62. package/dist/types/tests/contentful-items.test.d.ts +2 -0
  63. package/dist/types/tests/contentful-items.test.d.ts.map +1 -0
  64. package/dist/types/tests/contentful.delivery.test.d.ts +2 -0
  65. package/dist/types/tests/contentful.delivery.test.d.ts.map +1 -0
  66. package/dist/types/tests/css.test.d.ts +2 -0
  67. package/dist/types/tests/css.test.d.ts.map +1 -0
  68. package/dist/types/tests/ebay.test.d.ts +2 -0
  69. package/dist/types/tests/ebay.test.d.ts.map +1 -0
  70. package/dist/types/tests/form.test.d.ts +2 -0
  71. package/dist/types/tests/form.test.d.ts.map +1 -0
  72. package/dist/types/tests/formcomponents.test.d.ts +2 -0
  73. package/dist/types/tests/formcomponents.test.d.ts.map +1 -0
  74. package/dist/types/tests/formvalidations.test.d.ts +2 -0
  75. package/dist/types/tests/formvalidations.test.d.ts.map +1 -0
  76. package/dist/types/tests/functions.test.d.ts +2 -0
  77. package/dist/types/tests/functions.test.d.ts.map +1 -0
  78. package/dist/types/tests/google-analytics.test.d.ts +2 -0
  79. package/dist/types/tests/google-analytics.test.d.ts.map +1 -0
  80. package/dist/types/tests/google-map.test.d.ts +2 -0
  81. package/dist/types/tests/google-map.test.d.ts.map +1 -0
  82. package/dist/types/tests/google-reviews.test.d.ts +2 -0
  83. package/dist/types/tests/google-reviews.test.d.ts.map +1 -0
  84. package/dist/types/tests/googlesearch.test.d.ts +2 -0
  85. package/dist/types/tests/googlesearch.test.d.ts.map +1 -0
  86. package/dist/types/tests/gravatar.test.d.ts +2 -0
  87. package/dist/types/tests/gravatar.test.d.ts.map +1 -0
  88. package/dist/types/tests/hubspot.test.d.ts +2 -0
  89. package/dist/types/tests/hubspot.test.d.ts.map +1 -0
  90. package/dist/types/tests/image.test.d.ts +2 -0
  91. package/dist/types/tests/image.test.d.ts.map +1 -0
  92. package/dist/types/tests/instagram.test.d.ts +2 -0
  93. package/dist/types/tests/instagram.test.d.ts.map +1 -0
  94. package/dist/types/tests/loading.test.d.ts +2 -0
  95. package/dist/types/tests/loading.test.d.ts.map +1 -0
  96. package/dist/types/tests/markdown.test.d.ts +2 -0
  97. package/dist/types/tests/markdown.test.d.ts.map +1 -0
  98. package/dist/types/tests/menu-accordion.test.d.ts +2 -0
  99. package/dist/types/tests/menu-accordion.test.d.ts.map +1 -0
  100. package/dist/types/tests/menu-expando.test.d.ts +2 -0
  101. package/dist/types/tests/menu-expando.test.d.ts.map +1 -0
  102. package/dist/types/tests/menu-simple.test.d.ts +2 -0
  103. package/dist/types/tests/menu-simple.test.d.ts.map +1 -0
  104. package/dist/types/tests/metadata.components.test.d.ts +2 -0
  105. package/dist/types/tests/metadata.components.test.d.ts.map +1 -0
  106. package/dist/types/tests/microinteractions.test.d.ts +2 -0
  107. package/dist/types/tests/microinteractions.test.d.ts.map +1 -0
  108. package/dist/types/tests/modal.test.d.ts +2 -0
  109. package/dist/types/tests/modal.test.d.ts.map +1 -0
  110. package/dist/types/tests/nerdjoke.test.d.ts +2 -0
  111. package/dist/types/tests/nerdjoke.test.d.ts.map +1 -0
  112. package/dist/types/tests/paypal.test.d.ts +2 -0
  113. package/dist/types/tests/paypal.test.d.ts.map +1 -0
  114. package/dist/types/tests/pixelated.menu-expando.test.d.ts +2 -0
  115. package/dist/types/tests/pixelated.menu-expando.test.d.ts.map +1 -0
  116. package/dist/types/tests/recipe.test.d.ts +2 -0
  117. package/dist/types/tests/recipe.test.d.ts.map +1 -0
  118. package/dist/types/tests/resume.test.d.ts +2 -0
  119. package/dist/types/tests/resume.test.d.ts.map +1 -0
  120. package/dist/types/tests/schema-blogposting.test.d.ts +2 -0
  121. package/dist/types/tests/schema-blogposting.test.d.ts.map +1 -0
  122. package/dist/types/tests/schema-localbusiness.test.d.ts +2 -0
  123. package/dist/types/tests/schema-localbusiness.test.d.ts.map +1 -0
  124. package/dist/types/tests/schema-recipe.test.d.ts +2 -0
  125. package/dist/types/tests/schema-recipe.test.d.ts.map +1 -0
  126. package/dist/types/tests/schema-services.test.d.ts +2 -0
  127. package/dist/types/tests/schema-services.test.d.ts.map +1 -0
  128. package/dist/types/tests/schema-website.test.d.ts +2 -0
  129. package/dist/types/tests/schema-website.test.d.ts.map +1 -0
  130. package/dist/types/tests/semantic.test.d.ts +2 -0
  131. package/dist/types/tests/semantic.test.d.ts.map +1 -0
  132. package/dist/types/tests/setup.d.ts +2 -0
  133. package/dist/types/tests/setup.d.ts.map +1 -0
  134. package/dist/types/tests/shopping-cart.test.d.ts +2 -0
  135. package/dist/types/tests/shopping-cart.test.d.ts.map +1 -0
  136. package/dist/types/tests/shoppingcart.components.test.d.ts +2 -0
  137. package/dist/types/tests/shoppingcart.components.test.d.ts.map +1 -0
  138. package/dist/types/tests/shoppingcart.functions.test.d.ts +2 -0
  139. package/dist/types/tests/shoppingcart.functions.test.d.ts.map +1 -0
  140. package/dist/types/tests/sidepanel.test.d.ts +2 -0
  141. package/dist/types/tests/sidepanel.test.d.ts.map +1 -0
  142. package/dist/types/tests/socialcard.test.d.ts +2 -0
  143. package/dist/types/tests/socialcard.test.d.ts.map +1 -0
  144. package/dist/types/tests/table.test.d.ts +2 -0
  145. package/dist/types/tests/table.test.d.ts.map +1 -0
  146. package/dist/types/tests/tiles.test.d.ts +2 -0
  147. package/dist/types/tests/tiles.test.d.ts.map +1 -0
  148. package/dist/types/tests/timeline.test.d.ts +2 -0
  149. package/dist/types/tests/timeline.test.d.ts.map +1 -0
  150. package/dist/types/tests/wordpress.test.d.ts +2 -0
  151. package/dist/types/tests/wordpress.test.d.ts.map +1 -0
  152. package/dist/types/tests/yelp.test.d.ts +2 -0
  153. package/dist/types/tests/yelp.test.d.ts.map +1 -0
  154. package/package.json +23 -10
  155. package/dist/types/tests/pixelated.api.test.d.ts +0 -2
  156. package/dist/types/tests/pixelated.api.test.d.ts.map +0 -1
  157. package/dist/types/tests/pixelated.callout.test.d.ts +0 -2
  158. package/dist/types/tests/pixelated.callout.test.d.ts.map +0 -1
  159. package/dist/types/tests/pixelated.carousel.test.d.ts +0 -2
  160. package/dist/types/tests/pixelated.carousel.test.d.ts.map +0 -1
  161. package/dist/types/tests/pixelated.menu-accordion.test.d.ts +0 -2
  162. package/dist/types/tests/pixelated.menu-accordion.test.d.ts.map +0 -1
  163. package/dist/types/tests/pixelated.menu-simple.test.d.ts +0 -2
  164. package/dist/types/tests/pixelated.menu-simple.test.d.ts.map +0 -1
  165. package/dist/types/tests/pixelated.nerdjoke.test.d.ts +0 -2
  166. package/dist/types/tests/pixelated.nerdjoke.test.d.ts.map +0 -1
  167. package/dist/types/tests/pixelated.recipe.test.d.ts +0 -2
  168. package/dist/types/tests/pixelated.recipe.test.d.ts.map +0 -1
  169. package/dist/types/tests/pixelated.resume.test.d.ts +0 -2
  170. package/dist/types/tests/pixelated.resume.test.d.ts.map +0 -1
  171. package/dist/types/tests/pixelated.socialcard.test.d.ts +0 -2
  172. package/dist/types/tests/pixelated.socialcard.test.d.ts.map +0 -1
package/README.md CHANGED
@@ -96,6 +96,11 @@ Components to help build websites quicker:
96
96
  1. Form Components and Form Builder
97
97
  1. Google Analytics, Map, and Search Integration
98
98
  1. Gravatar Card Integration
99
+ 1. Local Business JSON-LD Schema for SEO
100
+ 1. Website JSON-LD Schema for SEO
101
+ 1. Services JSON-LD Schema for SEO
102
+ 1. Recipe JSON-LD Schema for SEO
103
+ 1. BlogPosting JSON-LD Schema for SEO
99
104
  1. Page and Page Section Header Components
100
105
  1. Hubspot Calendar and Form Integration
101
106
  1. Instagram Image Fetch Integration
@@ -146,6 +146,11 @@
146
146
  background-color: transparent;
147
147
  }
148
148
 
149
+ .carousel-container .carousel-buttons button.carousel-button {
150
+ /* fix for buttonring microinteraction */
151
+ outline: none !important;
152
+ }
153
+
149
154
  .carousel-arrow,
150
155
  .carousel-arrow-left,
151
156
  .carousel-arrow-right {
@@ -215,7 +215,7 @@ export function ContentfulItemDetail(props) {
215
215
  }
216
216
  }
217
217
  fetchStuff();
218
- }, []);
218
+ }, [props.entry_id, apiProps]);
219
219
  if (item && Object.keys(item) && Object.keys(item).length > 0) {
220
220
  const thisItem = { ...item };
221
221
  if (debug)
@@ -7,17 +7,22 @@ import { getWordPressItems } from './wordpress.functions';
7
7
  import { Loading, ToggleLoading } from '../general/loading';
8
8
  import "./wordpress.css";
9
9
  // https://microformats.org/wiki/h-entry
10
- function decodeString(s) {
11
- let temp = document.createElement('p');
12
- temp.innerHTML = s;
13
- const str = temp.textContent || temp.innerText;
14
- temp = null;
15
- return str;
10
+ function decodeString(str) {
11
+ const textarea = { value: '' };
12
+ textarea.innerHTML = str;
13
+ return textarea.value || str;
16
14
  }
17
15
  export function BlogPostList(props) {
18
- const { site, count } = props;
19
- const [posts, setPosts] = useState([]);
16
+ const { site, count, posts: cachedPosts } = props;
17
+ const [posts, setPosts] = useState(cachedPosts ?? []);
20
18
  useEffect(() => {
19
+ // If posts are provided, use them directly without fetching
20
+ if (cachedPosts && cachedPosts.length > 0) {
21
+ const sorted = cachedPosts.sort((a, b) => ((a.date ?? '') < (b.date ?? '')) ? 1 : -1);
22
+ setPosts(sorted);
23
+ return;
24
+ }
25
+ // Otherwise, fetch from WordPress
21
26
  ToggleLoading({ show: true });
22
27
  (async () => {
23
28
  const data = (await getWordPressItems({ site, count })) ?? [];
@@ -25,7 +30,7 @@ export function BlogPostList(props) {
25
30
  setPosts(sorted);
26
31
  ToggleLoading({ show: false });
27
32
  })();
28
- }, [site, count]);
33
+ }, [site, count, cachedPosts]);
29
34
  return (_jsxs(_Fragment, { children: [_jsx(Loading, {}), posts.map((post) => (_jsx(PageGridItem, { children: _jsx(BlogPostSummary, { ID: post.ID, title: post.title, date: post.date, excerpt: post.excerpt, URL: post.URL, categories: post.categories, featured_image: post.featured_image }) }, post.ID)))] }));
30
35
  }
31
36
  export function BlogPostSummary(props) {
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
3
  import PropTypes from "prop-types";
3
4
  import "./loading.scss";
@@ -13,24 +14,24 @@ ToggleLoading.propTypes = {
13
14
  show: PropTypes.bool,
14
15
  };
15
16
  export function ToggleLoading(props) {
16
- if (typeof window !== "undefined" && typeof document !== "undefined") {
17
- const loadingElem = document.getElementById("loadingSpinner");
18
- if (!loadingElem)
19
- return;
20
- if (props.show && props.show === true) {
17
+ if (typeof window === 'undefined' || typeof document === 'undefined')
18
+ return;
19
+ const loadingElem = document.getElementById("loadingSpinner");
20
+ if (!loadingElem)
21
+ return;
22
+ if (props.show === true) {
23
+ loadingElem.style.display = "inline-block";
24
+ }
25
+ else if (props.show === false) {
26
+ loadingElem.style.display = "none";
27
+ }
28
+ else {
29
+ // Toggle behavior
30
+ if (loadingElem.style.display === "none" || !loadingElem.style.display) {
21
31
  loadingElem.style.display = "inline-block";
22
- return;
23
- } // Show content
24
- if (props.show && props.show === false) {
25
- loadingElem.style.display = "none";
26
- return;
27
- } // Hide content
28
- if (loadingElem && loadingElem.style.display === "none") {
29
- loadingElem.style.display = "inline-block"; // Show content
30
32
  }
31
33
  else {
32
- if (loadingElem)
33
- loadingElem.style.display = "none"; // Hide content
34
+ loadingElem.style.display = "none";
34
35
  }
35
36
  }
36
37
  }
@@ -140,7 +140,7 @@ export function MenuAccordion(props) {
140
140
  document.removeEventListener('click', handleMenuClick);
141
141
  };
142
142
  }, []);
143
- return (_jsx("div", { className: "accordionMenuWrapper accordionUp", children: _jsx("div", { className: "accordionMenu", id: "accordionMenu", children: _jsx(MenuAccordionGroup, { menuItems: menuItems, state: undefined }, "accordionRoot") }) }));
143
+ return (_jsx("div", { className: "accordionMenuWrapper accordionUp", suppressHydrationWarning: true, children: _jsx("div", { className: "accordionMenu", id: "accordionMenu", children: _jsx(MenuAccordionGroup, { menuItems: menuItems, state: undefined }, "accordionRoot") }) }));
144
144
  }
145
145
  /* ========== MENU GROUP ========== */
146
146
  MenuAccordionGroup.propTypes = {
@@ -158,17 +158,9 @@ MenuAccordionItem.propTypes = {
158
158
  target: PropTypes.string,
159
159
  };
160
160
  export function MenuAccordionItem(props) {
161
- if (props.href && props.href.length > 0) {
162
- if (props.target && props.target.length > 0) {
163
- return (_jsx("li", { children: _jsx("a", { href: props.href, target: props.target, children: props.name }) }, "menu-item-" + props.name));
164
- }
165
- else {
166
- return (_jsx("li", { children: _jsx("a", { href: props.href, children: props.name }) }, "menu-item-" + props.name));
167
- }
168
- }
169
- else {
170
- return (_jsx("li", { children: _jsx("a", { children: props.name }) }, "menu-item-" + props.name));
171
- }
161
+ // Always render the same JSX structure to avoid hydration mismatch
162
+ // href will be undefined or an empty string, target might be undefined
163
+ return (_jsx("li", { children: _jsx("a", { href: props.href || undefined, target: props.target || undefined, children: props.name }) }, "menu-item-" + props.name));
172
164
  }
173
165
  /* ========== MENU BUTTON ========== */
174
166
  /*
@@ -177,7 +169,10 @@ https://www.unclebigbay.com/blog/building-the-world-simplest-hamburger-with-html
177
169
  MenuAccordionButton.propTypes = {};
178
170
  export function MenuAccordionButton() {
179
171
  function slideMobilePanel() {
180
- window.moveMenu();
172
+ if (typeof window !== 'undefined' && window.moveMenu) {
173
+ window.moveMenu();
174
+ }
181
175
  }
182
- return (_jsx("button", { className: "panelMenuButton", id: "panelMenuButton", onClick: slideMobilePanel, children: _jsx("span", { className: "hamburger text-outline", children: "|||" }) }));
176
+ // suppressHydrationWarning suppresses hydration mismatch warnings for this button
177
+ return (_jsx("button", { className: "panelMenuButton", id: "panelMenuButton", onClick: slideMobilePanel, suppressHydrationWarning: true, children: _jsx("span", { className: "hamburger text-outline", children: "|||" }) }));
183
178
  }
@@ -50,6 +50,10 @@
50
50
  flex-basis: content; /* each item is its own size instead of auto */
51
51
  }
52
52
 
53
+ .menu-item-hidden {
54
+ display: none;
55
+ }
56
+
53
57
  .menu-item:hover {
54
58
  background-color: var(--accent1-color);
55
59
  }
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import { useEffect } from 'react';
3
4
  import PropTypes from "prop-types";
@@ -26,6 +27,8 @@ export function MenuSimple(props) {
26
27
  return myItems;
27
28
  }
28
29
  function styleSelectedMenuItem() {
30
+ if (typeof window === 'undefined')
31
+ return;
29
32
  const menuitems = document.querySelectorAll('.menu-item a');
30
33
  const currentURL = window.location.href;
31
34
  menuitems.forEach((menuitem) => {
@@ -48,9 +51,11 @@ MenuSimpleItem.propTypes = {
48
51
  routes: PropTypes.array,
49
52
  };
50
53
  export function MenuSimpleItem(props) {
51
- if (props.hidden)
52
- return null;
53
- return (_jsx("li", { className: 'menu-item', children: props.target
54
+ const classNames = ['menu-item'];
55
+ if (props.hidden) {
56
+ classNames.push('menu-item-hidden');
57
+ }
58
+ return (_jsx("li", { className: classNames.join(' '), children: props.target
54
59
  ? _jsx("a", { href: props.path || undefined, target: props.target, children: props.name })
55
60
  : _jsx("a", { href: props.path || undefined, children: props.name }) }));
56
61
  }
@@ -16,7 +16,7 @@ export function SaveLoadSection({ pageData, onLoad, apiEndpoint = '/api/pagebuil
16
16
  // Fetch list of saved pages on mount
17
17
  useEffect(() => {
18
18
  fetchPages();
19
- }, []);
19
+ }, [apiEndpoint]);
20
20
  async function fetchPages() {
21
21
  try {
22
22
  const response = await fetch(`${apiEndpoint}/list`);
@@ -19,7 +19,7 @@ export function FourOhFour(props) {
19
19
  // const cloudinaryURL = getCloudinaryRemoteFetchURL({ url: images[randomIndex].img, product_env:"dlbon7tpq" });
20
20
  // setCloudinaryURL(cloudinaryURL);
21
21
  setImageURL(images[randomIndex].img);
22
- }, []);
22
+ }, [images]);
23
23
  const config = usePixelatedConfig();
24
24
  if (randomIndex !== null && imageURL /* cloudinaryURL */ !== '') {
25
25
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "fof-body-container", children: [_jsxs("h1", { className: "centered text-outline", children: ["404 - ", images[randomIndex].text] }), _jsx("div", { className: "centered-button", children: _jsx("a", { href: "/", target: "_self", rel: "noopener noreferrer", children: "Go Home" }) })] }), _jsx("div", { className: "fof-image-container", children: _jsx("div", { className: "fof-image-wrapper", children: _jsx(SmartImage, { src: imageURL,
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Converts WordPress REST API blog post to schema.org BlogPosting format
4
+ * @param post WordPress blog post
5
+ * @param includeFullContent Whether to include articleBody (true) or just description (false)
6
+ */
7
+ export function mapWordPressToBlogPosting(post, includeFullContent = false) {
8
+ const decodeString = (s) => {
9
+ if (typeof document === 'undefined')
10
+ return s;
11
+ const temp = document.createElement('p');
12
+ temp.innerHTML = s;
13
+ return temp.textContent || temp.innerText || s;
14
+ };
15
+ const cleanContent = (content) => {
16
+ if (!content)
17
+ return '';
18
+ return decodeString(content).replace(/\[…\]/g, '').trim();
19
+ };
20
+ const description = cleanContent(post.excerpt);
21
+ const articleBody = includeFullContent ? cleanContent(post.content || '') : undefined;
22
+ const schema = {
23
+ '@context': 'https://schema.org',
24
+ '@type': 'BlogPosting',
25
+ headline: decodeString(post.title),
26
+ description: description || decodeString(post.title),
27
+ datePublished: post.date,
28
+ image: post.featured_image || post.post_thumbnail?.URL,
29
+ articleSection: Array.isArray(post.categories) && post.categories.length > 0
30
+ ? post.categories[0]
31
+ : 'Blog',
32
+ keywords: Array.isArray(post.categories) ? post.categories : [],
33
+ };
34
+ if (articleBody) {
35
+ schema.articleBody = articleBody;
36
+ }
37
+ if (post.modified) {
38
+ schema.dateModified = post.modified;
39
+ }
40
+ if (post.author) {
41
+ schema.author = {
42
+ '@type': 'Person',
43
+ name: post.author,
44
+ };
45
+ }
46
+ return schema;
47
+ }
48
+ export function SchemaBlogPosting({ post }) {
49
+ return (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: {
50
+ __html: JSON.stringify(post),
51
+ } }));
52
+ }
53
+ export default SchemaBlogPosting;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function LocalBusinessSchema({ name, streetAddress, addressLocality, addressRegion, postalCode, addressCountry = 'United States', telephone, url, logo, image, openingHours, description, email, priceRange, sameAs }) {
3
+ const schemaData = {
4
+ '@context': 'https://schema.org',
5
+ '@type': 'LocalBusiness',
6
+ name,
7
+ address: {
8
+ '@type': 'PostalAddress',
9
+ streetAddress,
10
+ addressLocality,
11
+ addressRegion,
12
+ postalCode,
13
+ addressCountry
14
+ },
15
+ telephone,
16
+ url,
17
+ ...(logo && { logo }),
18
+ ...(image && { image }),
19
+ ...(openingHours && { openingHours }),
20
+ ...(description && { description }),
21
+ ...(email && { email }),
22
+ ...(priceRange && { priceRange }),
23
+ ...(sameAs && sameAs.length > 0 && { sameAs })
24
+ };
25
+ return (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(schemaData) } }));
26
+ }
27
+ export default LocalBusinessSchema;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function RecipeSchema({ recipe }) {
3
+ return (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(recipe) } }));
4
+ }
5
+ export default RecipeSchema;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ export function ServicesSchema({ provider, services }) {
3
+ const serviceObjects = services.map(service => ({
4
+ '@type': 'Service',
5
+ name: service.name,
6
+ description: service.description,
7
+ ...(service.url && { url: service.url }),
8
+ ...(service.image && { image: service.image }),
9
+ ...(service.areaServed && { areaServed: service.areaServed }),
10
+ provider: {
11
+ '@type': 'LocalBusiness',
12
+ name: provider.name,
13
+ url: provider.url,
14
+ ...(provider.logo && { logo: provider.logo }),
15
+ ...(provider.telephone && { telephone: provider.telephone }),
16
+ ...(provider.email && { email: provider.email })
17
+ }
18
+ }));
19
+ return (_jsx(_Fragment, { children: serviceObjects.map((service, idx) => (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify({
20
+ '@context': 'https://schema.org',
21
+ ...service
22
+ }) } }, idx))) }));
23
+ }
24
+ export default ServicesSchema;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function WebsiteSchema({ name, url, description, potentialAction }) {
3
+ const schemaData = {
4
+ '@context': 'https://schema.org',
5
+ '@type': 'WebSite',
6
+ name,
7
+ url,
8
+ ...(description && { description }),
9
+ ...(potentialAction && { potentialAction })
10
+ };
11
+ return (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(schemaData) } }));
12
+ }
13
+ export default WebsiteSchema;
@@ -245,7 +245,7 @@ export function AddToCartButton(props) {
245
245
  useEffect(() => {
246
246
  const myContent = _jsxs("div", { className: "centered", children: [_jsx("br", {}), _jsx("br", {}), "Item ", props.itemID, " has been added to your cart.", _jsx("br", {}), _jsx("br", {}), GoToCartButton({ href: "/cart", itemID: props.itemID }), _jsx("br", {}), _jsx("br", {})] });
247
247
  setModalContent(myContent);
248
- }, []);
248
+ }, [props.itemID]);
249
249
  function handleClick(e) {
250
250
  props.handler(props.item);
251
251
  handleModalOpen(e.nativeEvent, "-" + props.itemID);
@@ -18,7 +18,7 @@ export function BuzzwordBingo(props) {
18
18
  const [bingoWords, setBingoWords] = useState([]);
19
19
  useEffect(() => {
20
20
  setBingoWords(getBingoWords(buzzwords, 24));
21
- }, []);
21
+ }, [buzzwords]);
22
22
  return (_jsxs("div", { className: "bingoCard rowfix-5col", children: [myBingoHeaders.map((word) => (_jsx(BingoHeader, { word: word }, word))), bingoWords.map((word) => (_jsx(BingoBox, { word: word }, word)))] }));
23
23
  }
24
24
  BingoHeader.propTypes = {
@@ -27,7 +27,7 @@
27
27
 
28
28
  /* https://microformats.org/wiki/h-recipe */
29
29
 
30
- .h-recipe { font-size: 12px; }
30
+ .h-recipe { --do-nothing: true; }
31
31
  .h-recipe p { margin: 0px !important; }
32
32
  .h-recipe.p-name {
33
33
  background-color: #F9F9F9;
@@ -4,6 +4,55 @@ import PropTypes from 'prop-types';
4
4
  import { SmartImage } from '../cms/cloudinary.image';
5
5
  import { usePixelatedConfig } from '../config/config.client';
6
6
  import './recipe.css';
7
+ export function mapSchemaRecipeToDisplay(schemaRecipe) {
8
+ // Parse ISO 8601 duration and convert to readable format
9
+ function parseDuration(iso8601) {
10
+ if (!iso8601 || iso8601 === 'PT0M')
11
+ return '';
12
+ const regex = /PT(?:(\d+)H)?(?:(\d+)M)?/;
13
+ const match = iso8601.match(regex);
14
+ if (!match)
15
+ return iso8601;
16
+ const hours = match[1] ? parseInt(match[1]) : 0;
17
+ const minutes = match[2] ? parseInt(match[2]) : 0;
18
+ if (hours > 0 && minutes > 0) {
19
+ return `${hours} hour${hours > 1 ? 's' : ''} ${minutes} minutes`;
20
+ }
21
+ else if (hours > 0) {
22
+ return `${hours} hour${hours > 1 ? 's' : ''}`;
23
+ }
24
+ else if (minutes > 0) {
25
+ return `${minutes} minutes`;
26
+ }
27
+ return '';
28
+ }
29
+ // Extract author name
30
+ const authorName = typeof schemaRecipe.author === 'string'
31
+ ? schemaRecipe.author
32
+ : schemaRecipe.author?.name || '';
33
+ // Convert instructions from HowToStep format to plain text
34
+ const instructions = Array.isArray(schemaRecipe.recipeInstructions)
35
+ ? schemaRecipe.recipeInstructions.map((instruction) => typeof instruction === 'string' ? instruction : instruction.text || '')
36
+ : [];
37
+ // Combine cook and prep times for display
38
+ let displayDuration = '';
39
+ if (schemaRecipe.totalTime) {
40
+ displayDuration = parseDuration(schemaRecipe.totalTime);
41
+ }
42
+ return {
43
+ name: schemaRecipe.name || '',
44
+ photo: schemaRecipe.image || '',
45
+ summary: schemaRecipe.description || '',
46
+ author: authorName,
47
+ published: schemaRecipe.datePublished || '',
48
+ duration: displayDuration,
49
+ yield: schemaRecipe.recipeYield || '',
50
+ ingredients: schemaRecipe.recipeIngredient || [],
51
+ instructions,
52
+ category: schemaRecipe.recipeCategory ? [schemaRecipe.recipeCategory] : [],
53
+ license: schemaRecipe.license || ''
54
+ };
55
+ }
7
56
  /* ========== RECIPE BOOK ========== */
8
57
  RecipeBook.propTypes = {
9
58
  recipeData: PropTypes.shape({
@@ -25,9 +74,10 @@ export function RecipeBook(props) {
25
74
  myElems[category] = [];
26
75
  for (const recipeKey in recipeBookItems) {
27
76
  const recipe = recipeBookItems[recipeKey];
28
- const cats = recipe.properties.category;
29
- if (cats.includes(category)) {
30
- myElems[category].push(recipe);
77
+ const outputRecipe = mapSchemaRecipeToDisplay(recipe);
78
+ const recipeCat = outputRecipe.category;
79
+ if (recipeCat.includes(category)) {
80
+ myElems[category].push(outputRecipe);
31
81
  }
32
82
  }
33
83
  }
@@ -41,7 +91,7 @@ export function RecipeBook(props) {
41
91
  myElems.push(_jsx(RecipeCategory, { id: cID, className: 'h-recipe-category', category: category, showOnly: showOnlyCat }, cID));
42
92
  for (const recipeKey in recipeElems[category]) {
43
93
  const recipe = recipeElems[category][recipeKey];
44
- const cats = recipe.properties.category;
94
+ const cats = recipe.category;
45
95
  const rID = cID + '-r' + (parseInt(recipeKey, 10) + 1);
46
96
  if (cats.includes(category)) {
47
97
  myElems.push(_jsx(RecipeBookItem, { id: rID, recipeData: recipe, showOnly: showOnlyRecipe }, rID));
@@ -92,8 +142,7 @@ RecipeBookItem.propTypes = {
92
142
  };
93
143
  export function RecipeBookItem(props) {
94
144
  const config = usePixelatedConfig();
95
- const recipeData = props.recipeData;
96
- const recipe = recipeData.properties;
145
+ const recipe = props.recipeData;
97
146
  const ingredients = recipe.ingredients.map((ingredient, iKey) => _jsx("li", { className: "p-ingredient", children: ingredient }, iKey));
98
147
  const instructions = recipe.instructions.map((instruction, iKey) => _jsx("li", { className: "p-instruction", children: instruction }, iKey));
99
148
  /* ? <img className='u-photo' src={recipe.photo} title={recipe.name} alt={recipe.name} /> */
@@ -124,9 +173,10 @@ export function RecipePickList(props) {
124
173
  const recipeDataItems = recipeData.items;
125
174
  for (const recipeKey in recipeDataItems) {
126
175
  const recipe = recipeDataItems[recipeKey];
127
- const cats = recipe.properties.category;
176
+ const outputRecipe = mapSchemaRecipeToDisplay(recipe);
177
+ const cats = outputRecipe.category;
128
178
  if (cats.includes(category)) {
129
- myOpts.push(_jsx("option", { value: cID + '-r' + rID, children: recipe.properties.name }, cID + '-r' + rID));
179
+ myOpts.push(_jsx("option", { value: cID + '-r' + rID, children: outputRecipe.name }, cID + '-r' + rID));
130
180
  rID += 1;
131
181
  }
132
182
  }
@@ -143,7 +193,7 @@ export function RecipePickList(props) {
143
193
  }
144
194
  useEffect(() => {
145
195
  setRecipeOptions(generateMyOptions());
146
- }, []);
196
+ }, [props.recipeData, props.recipeCategories]);
147
197
  return (_jsx("form", { children: _jsx("select", { id: "recipe-list", name: "recipe-list", onChange: recipeListChanged, children: recipeOptions }) }));
148
198
  }
149
199
  /* ========== RECIPE BACK TO TOP ========== */
@@ -293,7 +293,7 @@ export function SocialCards(props) {
293
293
  }
294
294
  };
295
295
  generateSocialCards();
296
- }, []);
296
+ }, [props.sources]);
297
297
  if (state.loading) {
298
298
  return (_jsx(SocialCardsLoading, {}));
299
299
  }