@pixelated-tech/components 3.2.13 → 3.3.0

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 (192) hide show
  1. package/README.COMPONENTS.md +435 -78
  2. package/README.md +59 -32
  3. package/dist/components/callout/callout.scss +0 -3
  4. package/dist/components/cms/flickr.js +8 -2
  5. package/dist/components/cms/google.reviews.components.js +1 -1
  6. package/dist/components/general/tab.css +105 -0
  7. package/dist/components/general/tab.js +26 -0
  8. package/dist/components/menu/menu-expando.js +7 -1
  9. package/dist/components/nerdjoke/nerdjoke.js +13 -7
  10. package/dist/components/seo/manifest.js +40 -0
  11. package/dist/components/seo/metadata.components.js +0 -19
  12. package/dist/components/seo/metadata.functions.js +111 -0
  13. package/dist/components/seo/schema-blogposting.functions.js +42 -0
  14. package/dist/components/seo/schema-blogposting.js +0 -46
  15. package/dist/components/seo/schema-localbusiness.js +46 -2
  16. package/dist/components/seo/schema-website.js +31 -2
  17. package/dist/components/seo/sitemap.js +4 -4
  18. package/dist/components/shoppingcart/shoppingcart.components.js +4 -4
  19. package/dist/components/sitebuilder/config/ConfigBuilder.css +266 -0
  20. package/dist/components/sitebuilder/config/ConfigBuilder.js +221 -0
  21. package/dist/components/{pagebuilder → sitebuilder}/form/form.css +55 -30
  22. package/dist/components/sitebuilder/form/formbuilder.js +106 -0
  23. package/dist/components/sitebuilder/form/formcomponents.js +356 -0
  24. package/dist/components/sitebuilder/form/formengine.js +82 -0
  25. package/dist/components/{pagebuilder/form/form.js → sitebuilder/form/formextractor.js} +10 -211
  26. package/dist/components/sitebuilder/form/formutils.js +206 -0
  27. package/dist/components/sitebuilder/form/formvalidator.js +123 -0
  28. package/dist/components/{pagebuilder → sitebuilder/page}/components/ComponentPropertiesForm.js +1 -1
  29. package/dist/components/{pagebuilder → sitebuilder/page}/components/PageBuilderUI.js +2 -2
  30. package/dist/components/{pagebuilder → sitebuilder/page}/components/PageEngine.js +1 -1
  31. package/dist/components/sitebuilder/page/documentation/api-examples/save-route-example.js +37 -0
  32. package/dist/components/{pagebuilder → sitebuilder/page}/lib/componentMap.js +3 -3
  33. package/dist/components/{pagebuilder → sitebuilder/page}/lib/componentMetadata.js +2 -2
  34. package/dist/components/{pagebuilder → sitebuilder/page}/lib/pageStorageContentful.js +2 -2
  35. package/dist/components/sitebuilder/page/lib/pageStorageTypes.js +1 -0
  36. package/dist/data/form.json +18 -18
  37. package/dist/data/routes.json +25 -0
  38. package/dist/data/routes2.json +25 -0
  39. package/dist/data/shipping.to.json +9 -9
  40. package/dist/data/siteinfo-form.json +200 -0
  41. package/dist/index.js +31 -23
  42. package/dist/index.server.js +24 -18
  43. package/dist/types/components/cms/flickr.d.ts.map +1 -1
  44. package/dist/types/components/cms/google.reviews.components.d.ts.map +1 -1
  45. package/dist/types/components/config/config.types.d.ts +30 -0
  46. package/dist/types/components/config/config.types.d.ts.map +1 -1
  47. package/dist/types/components/general/semantic.d.ts +3 -3
  48. package/dist/types/components/general/tab.d.ts +18 -0
  49. package/dist/types/components/general/tab.d.ts.map +1 -0
  50. package/dist/types/components/menu/menu-expando.d.ts.map +1 -1
  51. package/dist/types/components/nerdjoke/nerdjoke.d.ts.map +1 -1
  52. package/dist/types/components/seo/manifest.d.ts +19 -0
  53. package/dist/types/components/seo/manifest.d.ts.map +1 -0
  54. package/dist/types/components/seo/metadata.components.d.ts +0 -17
  55. package/dist/types/components/seo/metadata.components.d.ts.map +1 -1
  56. package/dist/types/components/seo/{metadata.d.ts → metadata.functions.d.ts} +15 -1
  57. package/dist/types/components/seo/metadata.functions.d.ts.map +1 -0
  58. package/dist/types/components/seo/schema-blogposting.d.ts +1 -25
  59. package/dist/types/components/seo/schema-blogposting.d.ts.map +1 -1
  60. package/dist/types/components/seo/schema-blogposting.functions.d.ts +26 -0
  61. package/dist/types/components/seo/schema-blogposting.functions.d.ts.map +1 -0
  62. package/dist/types/components/seo/schema-localbusiness.d.ts +22 -23
  63. package/dist/types/components/seo/schema-localbusiness.d.ts.map +1 -1
  64. package/dist/types/components/seo/schema-website.d.ts +17 -18
  65. package/dist/types/components/seo/schema-website.d.ts.map +1 -1
  66. package/dist/types/components/seo/sitemap.d.ts.map +1 -1
  67. package/dist/types/components/shoppingcart/shoppingcart.components.d.ts +1 -1
  68. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts +86 -0
  69. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -0
  70. package/dist/types/components/sitebuilder/form/formbuilder.d.ts +11 -0
  71. package/dist/types/components/sitebuilder/form/formbuilder.d.ts.map +1 -0
  72. package/dist/types/components/{pagebuilder → sitebuilder}/form/formcomponents.d.ts +12 -16
  73. package/dist/types/components/sitebuilder/form/formcomponents.d.ts.map +1 -0
  74. package/dist/types/components/{pagebuilder/form/form.submit.d.ts → sitebuilder/form/formemailer.d.ts} +1 -1
  75. package/dist/types/components/sitebuilder/form/formemailer.d.ts.map +1 -0
  76. package/dist/types/components/sitebuilder/form/formengine.d.ts +14 -0
  77. package/dist/types/components/sitebuilder/form/formengine.d.ts.map +1 -0
  78. package/dist/types/components/sitebuilder/form/formextractor.d.ts +25 -0
  79. package/dist/types/components/sitebuilder/form/formextractor.d.ts.map +1 -0
  80. package/dist/types/components/{pagebuilder/form/formvalidations.d.ts → sitebuilder/form/formfieldvalidations.d.ts} +1 -1
  81. package/dist/types/components/sitebuilder/form/formfieldvalidations.d.ts.map +1 -0
  82. package/dist/types/components/sitebuilder/form/formtypes.d.ts +66 -0
  83. package/dist/types/components/sitebuilder/form/formtypes.d.ts.map +1 -0
  84. package/dist/types/components/sitebuilder/form/formutils.d.ts +20 -0
  85. package/dist/types/components/sitebuilder/form/formutils.d.ts.map +1 -0
  86. package/dist/types/components/sitebuilder/form/formvalidator.d.ts +20 -0
  87. package/dist/types/components/sitebuilder/form/formvalidator.d.ts.map +1 -0
  88. package/dist/types/components/sitebuilder/page/components/ComponentPropertiesForm.d.ts.map +1 -0
  89. package/dist/types/components/sitebuilder/page/components/ComponentSelector.d.ts.map +1 -0
  90. package/dist/types/components/sitebuilder/page/components/ComponentTree.d.ts.map +1 -0
  91. package/dist/types/components/{pagebuilder → sitebuilder/page}/components/PageBuilderUI.d.ts +1 -1
  92. package/dist/types/components/sitebuilder/page/components/PageBuilderUI.d.ts.map +1 -0
  93. package/dist/types/components/sitebuilder/page/components/PageEngine.d.ts.map +1 -0
  94. package/dist/types/components/sitebuilder/page/components/SaveLoadSection.d.ts.map +1 -0
  95. package/dist/types/components/sitebuilder/page/documentation/api-examples/save-route-example.d.ts +6 -0
  96. package/dist/types/components/sitebuilder/page/documentation/api-examples/save-route-example.d.ts.map +1 -0
  97. package/dist/types/components/sitebuilder/page/lib/componentGeneration.d.ts.map +1 -0
  98. package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/componentMap.d.ts +3 -3
  99. package/dist/types/components/sitebuilder/page/lib/componentMap.d.ts.map +1 -0
  100. package/dist/types/components/sitebuilder/page/lib/componentMetadata.d.ts.map +1 -0
  101. package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/pageStorageContentful.d.ts +1 -1
  102. package/dist/types/components/sitebuilder/page/lib/pageStorageContentful.d.ts.map +1 -0
  103. package/dist/types/components/sitebuilder/page/lib/pageStorageLocal.d.ts.map +1 -0
  104. package/dist/types/components/sitebuilder/page/lib/pageStorageTypes.d.ts.map +1 -0
  105. package/dist/types/components/sitebuilder/page/lib/propTypeIntrospection.d.ts.map +1 -0
  106. package/dist/types/components/sitebuilder/page/lib/types.d.ts.map +1 -0
  107. package/dist/types/components/sitebuilder/page/lib/usePageBuilder.d.ts.map +1 -0
  108. package/dist/types/index.d.ts +30 -21
  109. package/dist/types/index.server.d.ts +23 -17
  110. package/dist/types/stories/general/tab.stories.d.ts +45 -0
  111. package/dist/types/stories/general/tab.stories.d.ts.map +1 -0
  112. package/dist/types/stories/seo/seo.googleanalytics.stories.d.ts.map +1 -1
  113. package/dist/types/stories/seo/seo.metadata.stories.d.ts +1 -1
  114. package/dist/types/stories/seo/seo.metadata.stories.d.ts.map +1 -1
  115. package/dist/types/stories/seo/seo.schema.stories.d.ts +23 -0
  116. package/dist/types/stories/seo/seo.schema.stories.d.ts.map +1 -0
  117. package/dist/types/stories/sitebuilder/configbuilder.stories.d.ts +48 -0
  118. package/dist/types/stories/sitebuilder/configbuilder.stories.d.ts.map +1 -0
  119. package/dist/types/stories/{pagebuilder → sitebuilder}/form-builder.stories.d.ts +1 -1
  120. package/dist/types/stories/sitebuilder/form-builder.stories.d.ts.map +1 -0
  121. package/dist/types/stories/{pagebuilder → sitebuilder}/form-engine.stories.d.ts +1 -1
  122. package/dist/types/stories/sitebuilder/form-engine.stories.d.ts.map +1 -0
  123. package/dist/types/stories/{pagebuilder → sitebuilder}/form-extractor.stories.d.ts +1 -1
  124. package/dist/types/stories/sitebuilder/form-extractor.stories.d.ts.map +1 -0
  125. package/dist/types/stories/{pagebuilder → sitebuilder}/pagebuilder.stories.d.ts +1 -1
  126. package/dist/types/stories/{pagebuilder → sitebuilder}/pagebuilder.stories.d.ts.map +1 -1
  127. package/dist/types/stories/{pagebuilder → sitebuilder}/pagebuilder.usageguide.stories.d.ts +1 -1
  128. package/dist/types/stories/{pagebuilder → sitebuilder}/pagebuilder.usageguide.stories.d.ts.map +1 -1
  129. package/dist/types/stories/{pagebuilder → sitebuilder}/pageengine.stories.d.ts +1 -1
  130. package/dist/types/stories/{pagebuilder → sitebuilder}/pageengine.stories.d.ts.map +1 -1
  131. package/dist/types/tests/configbuilder.test.d.ts +2 -0
  132. package/dist/types/tests/configbuilder.test.d.ts.map +1 -0
  133. package/dist/types/tests/manifest.test.d.ts +2 -0
  134. package/dist/types/tests/manifest.test.d.ts.map +1 -0
  135. package/dist/types/tests/tab.test.d.ts +2 -0
  136. package/dist/types/tests/tab.test.d.ts.map +1 -0
  137. package/package.json +6 -5
  138. package/dist/components/pagebuilder/form/formcomponents.js +0 -359
  139. package/dist/components/seo/metadata.js +0 -108
  140. package/dist/components/utilities/api.js +0 -36
  141. package/dist/types/components/pagebuilder/components/ComponentPropertiesForm.d.ts.map +0 -1
  142. package/dist/types/components/pagebuilder/components/ComponentSelector.d.ts.map +0 -1
  143. package/dist/types/components/pagebuilder/components/ComponentTree.d.ts.map +0 -1
  144. package/dist/types/components/pagebuilder/components/PageBuilderUI.d.ts.map +0 -1
  145. package/dist/types/components/pagebuilder/components/PageEngine.d.ts.map +0 -1
  146. package/dist/types/components/pagebuilder/components/SaveLoadSection.d.ts.map +0 -1
  147. package/dist/types/components/pagebuilder/form/form.d.ts +0 -46
  148. package/dist/types/components/pagebuilder/form/form.d.ts.map +0 -1
  149. package/dist/types/components/pagebuilder/form/form.submit.d.ts.map +0 -1
  150. package/dist/types/components/pagebuilder/form/formcomponents.d.ts.map +0 -1
  151. package/dist/types/components/pagebuilder/form/formvalidations.d.ts.map +0 -1
  152. package/dist/types/components/pagebuilder/lib/componentGeneration.d.ts.map +0 -1
  153. package/dist/types/components/pagebuilder/lib/componentMap.d.ts.map +0 -1
  154. package/dist/types/components/pagebuilder/lib/componentMetadata.d.ts.map +0 -1
  155. package/dist/types/components/pagebuilder/lib/pageStorageContentful.d.ts.map +0 -1
  156. package/dist/types/components/pagebuilder/lib/pageStorageLocal.d.ts.map +0 -1
  157. package/dist/types/components/pagebuilder/lib/pageStorageTypes.d.ts.map +0 -1
  158. package/dist/types/components/pagebuilder/lib/propTypeIntrospection.d.ts.map +0 -1
  159. package/dist/types/components/pagebuilder/lib/types.d.ts.map +0 -1
  160. package/dist/types/components/pagebuilder/lib/usePageBuilder.d.ts.map +0 -1
  161. package/dist/types/components/seo/metadata.d.ts.map +0 -1
  162. package/dist/types/components/utilities/api.d.ts +0 -16
  163. package/dist/types/components/utilities/api.d.ts.map +0 -1
  164. package/dist/types/stories/pagebuilder/form-builder.stories.d.ts.map +0 -1
  165. package/dist/types/stories/pagebuilder/form-engine.stories.d.ts.map +0 -1
  166. package/dist/types/stories/pagebuilder/form-extractor.stories.d.ts.map +0 -1
  167. package/dist/types/tests/api.test.d.ts +0 -2
  168. package/dist/types/tests/api.test.d.ts.map +0 -1
  169. /package/dist/components/{pagebuilder/form/form.submit.js → sitebuilder/form/formemailer.js} +0 -0
  170. /package/dist/components/{pagebuilder/form/formvalidations.js → sitebuilder/form/formfieldvalidations.js} +0 -0
  171. /package/dist/components/{pagebuilder/lib/pageStorageTypes.js → sitebuilder/form/formtypes.js} +0 -0
  172. /package/dist/components/{pagebuilder → sitebuilder/page}/components/ComponentSelector.js +0 -0
  173. /package/dist/components/{pagebuilder → sitebuilder/page}/components/ComponentTree.js +0 -0
  174. /package/dist/components/{pagebuilder → sitebuilder/page}/components/SaveLoadSection.js +0 -0
  175. /package/dist/components/{pagebuilder → sitebuilder/page}/components/pagebuilder.scss +0 -0
  176. /package/dist/components/{pagebuilder → sitebuilder/page}/lib/componentGeneration.js +0 -0
  177. /package/dist/components/{pagebuilder → sitebuilder/page}/lib/pageStorageLocal.js +0 -0
  178. /package/dist/components/{pagebuilder → sitebuilder/page}/lib/propTypeIntrospection.js +0 -0
  179. /package/dist/components/{pagebuilder → sitebuilder/page}/lib/types.js +0 -0
  180. /package/dist/components/{pagebuilder → sitebuilder/page}/lib/usePageBuilder.js +0 -0
  181. /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/ComponentPropertiesForm.d.ts +0 -0
  182. /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/ComponentSelector.d.ts +0 -0
  183. /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/ComponentTree.d.ts +0 -0
  184. /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/PageEngine.d.ts +0 -0
  185. /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/SaveLoadSection.d.ts +0 -0
  186. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/componentGeneration.d.ts +0 -0
  187. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/componentMetadata.d.ts +0 -0
  188. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/pageStorageLocal.d.ts +0 -0
  189. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/pageStorageTypes.d.ts +0 -0
  190. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/propTypeIntrospection.d.ts +0 -0
  191. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/types.d.ts +0 -0
  192. /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/usePageBuilder.d.ts +0 -0
package/README.md CHANGED
@@ -182,6 +182,8 @@ npm run storybook
182
182
  - [ ] Buffer Integration (or Sendible, Sprout Social, Hootsuite)
183
183
  - [ ] Zapier Integration
184
184
  - [ ] Hero Banner: headline, subtext, CTA, background image/video, overlay.
185
+ - [ ] Accessibility Enhancer: wrapper component that automatically improves accessibility across Pixelated sites by adding ARIA labels, roles, and states to existing components. Includes color contrast checking, keyboard navigation helpers, and alt-text suggestions for images.
186
+ - [ ] SEO Dashboard with AI Integration: component that analyzes site content, suggests optimizations, integrates with AI for meta descriptions and keyword research.
185
187
 
186
188
  ### CI / CD Improvements
187
189
  - [ ] Add CI workflow to run tests and lints on pull requests.
@@ -202,8 +204,15 @@ npm run storybook
202
204
  - [ ] **GoogleReviews Component**: Add API key to config provider instead of hardcoding.
203
205
  - [ ] **Instagram Component**: Add accessToken and userId to config provider for centralized API credentials.
204
206
 
205
-
206
-
207
+ ### SSR Fixes
208
+ - [ ] **cloudinary.image.tsx** (`SmartImage`): Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
209
+ - [ ] **wordpress.components.tsx** (`BlogPostList`, etc.): Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
210
+ - [ ] **pagebuilder/form/formcomponents.tsx**: Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
211
+ - [ ] **cms/hubspot.components.tsx**: Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
212
+ - [ ] **cms/gravatar.components.tsx**: Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
213
+ - [ ] **structured/recipe.tsx**: Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
214
+ - [ ] **structured/timeline.tsx**: Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
215
+ - [ ] **structured/markdown.tsx**: Add `"use client"` or refactor to avoid `usePixelatedConfig` in server contexts
207
216
 
208
217
  See the [open issues](https://github.com/brianwhaley/pixelated-components/issues) for a full list of proposed features (and known issues).
209
218
 
@@ -261,16 +270,16 @@ Project Link: [https://github.com/brianwhaley/pixelated-components](https://gith
261
270
 
262
271
  ### Overview
263
272
 
264
- **Current Status**: ✅ 2,184 tests passing across 59 test files
273
+ **Current Status**: ✅ 2,244 tests passing across 67 test files
265
274
 
266
275
  | Metric | Value |
267
276
  |--------|-------|
268
- | Test Files | 59 |
269
- | Total Tests | 2,184 |
270
- | Coverage (Statements) | 79.27% |
271
- | Coverage (Lines) | 82.74% |
272
- | Coverage (Functions) | 84.74% |
273
- | Coverage (Branches) | 67.19% |
277
+ | Test Files | 67 |
278
+ | Total Tests | 2,244 |
279
+ | Coverage (Statements) | 76.59% |
280
+ | Coverage (Lines) | 79.45% |
281
+ | Coverage (Functions) | 78.33% |
282
+ | Coverage (Branches) | 66.79% |
274
283
  | Test Framework | Vitest 4.x |
275
284
  | Testing Library | @testing-library/react + jsdom |
276
285
 
@@ -288,42 +297,60 @@ npm run test:run # Single run (for CI)
288
297
  **Component Coverage Summary**
289
298
 
290
299
  #### Component Coverage (Sorted by Statement Coverage)
300
+ - **tiles.tsx**: 100% statements
291
301
  - **google.reviews.functions.ts**: 100% statements
292
- - **googlesearch.tsx**: 100% statements
302
+ - **config.server.tsx**: 100% statements
303
+ - **accordion.tsx**: 100% statements
304
+ - **modal.tsx**: 100% statements
305
+ - **tab.tsx**: 100% statements
306
+ - **ComponentPropertiesForm.tsx**: 100% statements
307
+ - **ComponentSelector.tsx**: 100% statements
308
+ - **ComponentTree.tsx**: 100% statements
293
309
  - **formvalidations.tsx**: 100% statements
294
- - **tiles.tsx**: 100% statements
295
- - **markdown.tsx**: 100% statements
310
+ - **componentMetadata.tsx**: 100% statements
311
+ - **googlesearch.tsx**: 100% statements
312
+ - **schema-localbusiness.tsx**: 100% statements
313
+ - **schema-recipe.tsx**: 100% statements
314
+ - **schema-services.tsx**: 100% statements
315
+ - **schema-website.tsx**: 100% statements
296
316
  - **buzzwordbingo.tsx**: 100% statements
317
+ - **markdown.tsx**: 100% statements
297
318
  - **timeline.tsx**: 100% statements
298
- - **config.server.tsx**: 100% statements
299
- - **modal.tsx**: 100% statements
300
- - **recipe.tsx**: 98.8% statements
301
319
  - **sidepanel.tsx**: 97.5% statements
320
+ - **config.ts**: 96.55% statements
302
321
  - **google.reviews.components.tsx**: 95.83% statements
322
+ - **schema-blogposting.tsx**: 95.24% statements
323
+ - **recipe.tsx**: 94.59% statements
303
324
  - **resume.tsx**: 94.38% statements
304
325
  - **contentful.delivery.ts**: 92.5% statements
305
- - **css.tsx**: 91.42% statements
306
- - **functions.ts**: 90.9% statements
326
+ - **css.tsx**: 91.43% statements
327
+ - **functions.ts**: 90.91% statements
328
+ - **menu-expando.tsx**: 90.12% statements
307
329
  - **config.client.tsx**: 90% statements
308
- - **api.ts**: 87.5% statements
309
330
  - **loading.tsx**: 85.71% statements
331
+ - **SaveLoadSection.tsx**: 84.85% statements
310
332
  - **table.tsx**: 84.48% statements
333
+ - **ConfigBuilder.tsx**: 83.52% statements
311
334
  - **cloudinary.ts**: 83.33% statements
312
- - **shoppingcart.functions.ts**: 81.69% statements
313
- - **callout.tsx**: 80.00% statements
314
- - **sitemap.ts**: 76.38% statements
315
- - **carousel.tsx**: 71.70% statements
316
- - **nerdjoke.tsx**: 70.58% statements
335
+ - **formcomponents.tsx**: 83.33% statements
336
+ - **form.tsx**: 83.2% statements
337
+ - **shoppingcart.functions.ts**: 81.7% statements
338
+ - **callout.tsx**: 80% statements
339
+ - **microinteractions.tsx**: 80% statements
340
+ - **cloudinary.image.tsx**: 78.57% statements
341
+ - **sitemap.ts**: 76.06% statements
342
+ - **manifest.tsx**: 75% statements
343
+ - **carousel.tsx**: 71.7% statements
344
+ - **nerdjoke.tsx**: 69.44% statements
317
345
  - **menu-accordion.tsx**: 68.13% statements
318
- - **semantic.tsx**: 60.81% statements
319
- - **config.ts**: 55.17% statements
320
- - **socialcard.tsx**: 29.5% statements
321
- - **ComponentPropertiesForm.tsx**: 0% statements (no tests)
322
- - **ComponentSelector.tsx**: 0% statements (no tests)
323
- - **ComponentTree.tsx**: 0% statements (no tests)
324
- - **PageBuilderUI.tsx**: 0% statements (no tests)
325
- - **PageEngine.tsx**: 0% statements (no tests)
326
- - **SaveLoadSection.tsx**: 0% statements (no tests)
346
+ - **semantic.tsx**: 63.51% statements
347
+ - **componentMap.tsx**: 60% statements
348
+ - **propTypeIntrospection.tsx**: 60% statements
349
+ - **wordpress.functions.ts**: 51.43% statements
350
+ - **PageEngine.tsx**: 48% statements
351
+ - **componentGeneration.tsx**: 38.89% statements
352
+ - **socialcard.tsx**: 29.51% statements
353
+ - **PageBuilderUI.tsx**: 26.67% statements
327
354
 
328
355
  ### Testing Next Steps
329
356
 
@@ -149,9 +149,6 @@
149
149
  }
150
150
 
151
151
 
152
- /* .callout .callout-button a {
153
- display: inline-block;
154
- } */
155
152
 
156
153
  /* ========================================
157
154
  ============= BOXED CALLOUT =============
@@ -1,5 +1,4 @@
1
1
  import PropTypes from 'prop-types';
2
- import { generateURL } from '../utilities/api';
3
2
  import { mergeDeep } from '../utilities/functions';
4
3
  const defaultFlickr = {
5
4
  flickr: {
@@ -57,7 +56,14 @@ export function GetFlickrData(props) {
57
56
  flickrConfig = mergeDeep(flickrConfig, props.flickr);
58
57
  }
59
58
  const flickr = flickrConfig;
60
- const myURL = generateURL(flickr.baseURL, flickr.urlProps);
59
+ // Build URL with query parameters
60
+ let myURL = flickr.baseURL;
61
+ let queryParams = '';
62
+ Object.keys(flickr.urlProps).forEach((prop) => {
63
+ const value = flickr.urlProps[prop];
64
+ queryParams += (queryParams.length === 0) ? prop + '=' + value : '&' + prop + '=' + value;
65
+ });
66
+ myURL += queryParams;
61
67
  const fetchFlickrData = async () => {
62
68
  try {
63
69
  const response = await fetch(myURL);
@@ -50,5 +50,5 @@ export function GoogleReviewsCard(props) {
50
50
  if (error) {
51
51
  return (_jsx("div", { className: "google-reviews-card", children: _jsxs("p", { className: "error", children: ["Error: ", error] }) }));
52
52
  }
53
- return (_jsxs("div", { className: "google-reviews-card", children: [_jsx("h3", { children: place?.name || 'Reviews' }), place?.formatted_address && (_jsx("p", { className: "address", children: place.formatted_address })), reviews.length === 0 ? (_jsx("p", { className: "no-reviews", children: "No reviews found." })) : (_jsx("ul", { children: reviews.map((r, i) => (_jsxs("li", { children: [_jsxs("div", { className: "review-header", children: [r.profile_photo_url && (_jsx("img", { src: r.profile_photo_url, alt: r.author_name, className: "profile-photo" })), _jsxs("div", { children: [_jsx("div", { className: "author-name", children: r.author_name }), _jsxs("div", { className: "rating", children: ['★'.repeat(r.rating), '☆'.repeat(5 - r.rating), " ", r.rating, "/5", r.relative_time_description && _jsxs("span", { children: [" \u00B7 ", r.relative_time_description] })] })] })] }), r.text && _jsx("div", { className: "review-text", children: r.text })] }, i))) })), place && (_jsx("a", { href: `https://search.google.com/local/writereview?placeid=${place.place_id}`, target: "_blank", rel: "noopener noreferrer", className: "write-review", children: "Write a review on Google \u2192" }))] }));
53
+ return (_jsxs("div", { className: "google-reviews-card", children: [_jsx("h3", { children: place?.name || 'Reviews' }), place?.formatted_address && (_jsx("p", { className: "address", children: place.formatted_address })), reviews.length === 0 ? (_jsx("p", { className: "no-reviews", children: "No reviews found." })) : (_jsx("ul", { children: reviews.map((r, i) => (_jsxs("li", { children: [_jsxs("div", { className: "review-header", children: [r.profile_photo_url && (_jsx("div", { className: "profile-photo-container", children: _jsx("img", { src: r.profile_photo_url, alt: r.author_name, className: "profile-photo" }) })), _jsxs("div", { children: [_jsx("div", { className: "author-name", children: r.author_name }), _jsxs("div", { className: "rating", children: ['★'.repeat(r.rating), '☆'.repeat(5 - r.rating), " ", r.rating, "/5", r.relative_time_description && _jsxs("span", { children: [" \u00B7 ", r.relative_time_description] })] })] })] }), r.text && _jsx("div", { className: "review-text", children: r.text })] }, i))) })), place && (_jsx("a", { href: `https://search.google.com/local/writereview?placeid=${place.place_id}`, target: "_blank", rel: "noopener noreferrer", className: "write-review", children: "Write a review on Google \u2192" }))] }));
54
54
  }
@@ -0,0 +1,105 @@
1
+ /* Tab Component Styles */
2
+
3
+ .tab-container {
4
+ display: flex;
5
+ border: 1px solid #ddd;
6
+ border-radius: 4px;
7
+ overflow: hidden;
8
+ height: 100%;
9
+ }
10
+
11
+ .tab-top {
12
+ flex-direction: column;
13
+ }
14
+
15
+ .tab-bottom {
16
+ flex-direction: column-reverse;
17
+ }
18
+
19
+ .tab-left {
20
+ flex-direction: row;
21
+ }
22
+
23
+ .tab-right {
24
+ flex-direction: row-reverse;
25
+ }
26
+
27
+ .tab-headers {
28
+ display: flex;
29
+ background-color: #f5f5f5;
30
+ }
31
+
32
+ .tab-top .tab-headers,
33
+ .tab-bottom .tab-headers {
34
+ flex-direction: row;
35
+ width: 100%;
36
+ }
37
+
38
+ .tab-left .tab-headers,
39
+ .tab-right .tab-headers {
40
+ flex-direction: column;
41
+ height: 100%;
42
+ }
43
+
44
+ .tab-header {
45
+ padding: 10px 20px;
46
+ border: none;
47
+ background: none;
48
+ cursor: pointer;
49
+ transition: background-color 0.3s;
50
+ white-space: nowrap;
51
+ flex-shrink: 0;
52
+ }
53
+
54
+ .tab-top .tab-header,
55
+ .tab-bottom .tab-header {
56
+ border-right: 1px solid #ddd;
57
+ border-bottom: none;
58
+ }
59
+
60
+ .tab-left .tab-header,
61
+ .tab-right .tab-header {
62
+ border-right: none;
63
+ border-bottom: 1px solid #ddd;
64
+ height: auto;
65
+ padding: 20px 10px;
66
+ }
67
+
68
+ .tab-left .tab-header {
69
+ transform: rotate(-90deg);
70
+ }
71
+
72
+ .tab-right .tab-header {
73
+ transform: rotate(90deg);
74
+ }
75
+
76
+ .tab-header:last-child {
77
+ border-right: none;
78
+ }
79
+
80
+ .tab-top .tab-header:last-child,
81
+ .tab-bottom .tab-header:last-child {
82
+ border-right: none;
83
+ }
84
+
85
+ .tab-left .tab-header:last-child,
86
+ .tab-right .tab-header:last-child {
87
+ border-bottom: none;
88
+ }
89
+
90
+ .tab-header:hover {
91
+ background-color: #e0e0e0;
92
+ }
93
+
94
+ .tab-header.active {
95
+ background-color: #fff;
96
+ font-weight: bold;
97
+ }
98
+
99
+ .tab-content {
100
+ flex: 1;
101
+ padding: 20px;
102
+ background-color: #fff;
103
+ min-height: 200px;
104
+ overflow: auto;
105
+ }
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import PropTypes from 'prop-types';
4
+ import './tab.css';
5
+ const TabItemPropTypes = {
6
+ id: PropTypes.string.isRequired,
7
+ label: PropTypes.string.isRequired, //
8
+ content: PropTypes.node.isRequired,
9
+ };
10
+ // type TabItemType = InferProps<typeof TabItemPropTypes>;
11
+ Tab.propTypes = {
12
+ tabs: PropTypes.arrayOf(PropTypes.shape(TabItemPropTypes).isRequired).isRequired,
13
+ orientation: PropTypes.oneOf(['top', 'bottom', 'left', 'right']),
14
+ defaultActiveTab: PropTypes.string,
15
+ onTabChange: PropTypes.func,
16
+ };
17
+ export function Tab({ tabs, orientation = 'top', defaultActiveTab, onTabChange }) {
18
+ const [activeTab, setActiveTab] = useState(defaultActiveTab || tabs[0]?.id || '');
19
+ const handleTabClick = (tabId) => {
20
+ setActiveTab(tabId);
21
+ onTabChange?.(tabId);
22
+ };
23
+ const activeContent = tabs.find(tab => tab.id === activeTab)?.content;
24
+ const tabClass = `tab-container tab-${orientation}`;
25
+ return (_jsxs("div", { className: tabClass, children: [_jsx("div", { className: "tab-headers", children: tabs.map(tab => (_jsx("button", { className: `tab-header ${activeTab === tab.id ? 'active' : ''}`, onClick: () => handleTabClick(tab.id), children: tab.label }, tab.id))) }), _jsx("div", { className: "tab-content", children: activeContent })] }));
26
+ }
@@ -136,6 +136,12 @@ export function MenuExpandoButton() {
136
136
  if (details)
137
137
  details.open = !details.open;
138
138
  }
139
- return (_jsx("div", { className: "menuExpandoButton", id: "menuExpandoButton", onClick: handleMenuExpandoButtonClick, children: _jsx("img", { src: "/images/icons/mobile-menu2.png", title: "Mobile Menu", alt: "Mobile Menu" }) }));
139
+ function handleKeyDown(event) {
140
+ if (event.key === 'Enter' || event.key === ' ') {
141
+ event.preventDefault();
142
+ handleMenuExpandoButtonClick(event);
143
+ }
144
+ }
145
+ return (_jsx("div", { className: "menuExpandoButton", id: "menuExpandoButton", onClick: handleMenuExpandoButtonClick, onKeyDown: handleKeyDown, tabIndex: 0, role: "button", "aria-label": "Toggle mobile menu", children: _jsx("img", { src: "/images/icons/mobile-menu2.png", title: "Mobile Menu", alt: "Mobile Menu" }) }));
140
146
  }
141
147
  MenuExpandoButton.propTypes = {};
@@ -2,7 +2,6 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useRef, useState } from "react";
4
4
  import PropTypes from "prop-types";
5
- import { getXHRData, generateURL } from "../utilities/api";
6
5
  import "../../css/pixelated.grid.scss";
7
6
  import "./nerdjoke.css";
8
7
  const debug = false;
@@ -36,7 +35,7 @@ export function NerdJoke( /* props: NerdJokeType */) {
36
35
  elapsedPath.style.width = myWidth;
37
36
  }
38
37
  }, [formatTimeLeft]);
39
- const loadJoke = useCallback(() => {
38
+ const loadJoke = useCallback(async () => {
40
39
  if (debug)
41
40
  console.log("Loading Joke");
42
41
  timePassedRef.current = 0;
@@ -52,11 +51,18 @@ export function NerdJoke( /* props: NerdJokeType */) {
52
51
  }, TIME_LIMIT * 1000);
53
52
  const myURL = "https://vvqyc1xpw6.execute-api.us-east-2.amazonaws.com/prod/nerdjokes?";
54
53
  const myURLProps = { command: "%2Fnerdjokes", text: "getjokejson" };
55
- const myMethod = "GET";
56
- getXHRData(generateURL(myURL, myURLProps), myMethod, (jokeData) => {
57
- const myJokeData = jokeData;
58
- setJoke(myJokeData);
59
- });
54
+ try {
55
+ const url = myURL + "command=" + myURLProps.command + "&text=" + myURLProps.text;
56
+ const response = await fetch(url);
57
+ if (!response.ok)
58
+ throw new Error(`HTTP error! status: ${response.status}`);
59
+ const jokeData = await response.json();
60
+ setJoke(jokeData);
61
+ }
62
+ catch (error) {
63
+ console.error('Failed to fetch joke:', error);
64
+ // Optionally set a fallback joke or handle error
65
+ }
60
66
  }, []);
61
67
  const startTimer = useCallback(() => {
62
68
  if (debug)
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Generates a PWA manifest from siteinfo configuration
3
+ * @param options - Configuration options
4
+ * @returns Next.js manifest object
5
+ */
6
+ export function generateManifest(options) {
7
+ const { siteInfo, customProperties = {} } = options;
8
+ const baseManifest = {
9
+ // @ts-expect-error - 'author' is not in standard Manifest type but used by some PWA implementations
10
+ author: siteInfo.author,
11
+ background_color: siteInfo.background_color,
12
+ default_locale: siteInfo.default_locale,
13
+ description: siteInfo.description,
14
+ developer: {
15
+ name: siteInfo.author || "Developer",
16
+ url: siteInfo.url
17
+ },
18
+ display: siteInfo.display || "standalone",
19
+ homepage_url: siteInfo.url,
20
+ icons: [{
21
+ src: siteInfo.favicon || "/favicon.ico",
22
+ sizes: siteInfo.favicon_sizes || "64x64 32x32 24x24 16x16",
23
+ type: siteInfo.favicon_type || "image/x-icon"
24
+ }],
25
+ name: siteInfo.name,
26
+ short_name: siteInfo.name,
27
+ start_url: ".",
28
+ theme_color: siteInfo.theme_color,
29
+ };
30
+ // Merge with custom properties, allowing overrides
31
+ return { ...baseManifest, ...customProperties };
32
+ }
33
+ /**
34
+ * Default export for Next.js manifest route
35
+ * @param options - Configuration options
36
+ * @returns Next.js manifest object
37
+ */
38
+ export function Manifest(options) {
39
+ return generateManifest(options);
40
+ }
@@ -1,23 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
1
  import PropTypes from "prop-types";
3
- // https://gist.github.com/whitingx/3840905
4
- generateMetaTags.propTypes = {
5
- title: PropTypes.string.isRequired,
6
- description: PropTypes.string.isRequired,
7
- keywords: PropTypes.string.isRequired,
8
- site_name: PropTypes.string.isRequired,
9
- email: PropTypes.string.isRequired,
10
- origin: PropTypes.string.isRequired,
11
- url: PropTypes.string.isRequired,
12
- image: PropTypes.string.isRequired,
13
- image_height: PropTypes.string.isRequired,
14
- image_width: PropTypes.string.isRequired,
15
- favicon: PropTypes.string.isRequired,
16
- };
17
- export function generateMetaTags(props) {
18
- const { title, description, keywords, site_name, email, origin, url, image, image_height, image_width, favicon } = props;
19
- return (_jsxs(_Fragment, { children: [_jsx("title", { children: title }), _jsx("meta", { charSet: "UTF-8" }), _jsx("meta", { httpEquiv: "content-type", content: "text/html; charset=UTF-8" }), _jsx("meta", { httpEquiv: 'Expires', content: '0' }), _jsx("meta", { httpEquiv: 'Pragma', content: 'no-cache' }), _jsx("meta", { httpEquiv: 'Cache-Control', content: 'no-cache' }), _jsx("meta", { name: "application-name", content: site_name }), _jsx("meta", { name: "author", content: site_name + ", " + email }), _jsx("meta", { name: 'copyright', content: site_name }), _jsx("meta", { name: "creator", content: site_name }), _jsx("meta", { name: "description", content: description }), _jsx("meta", { name: "keywords", content: keywords }), _jsx("meta", { name: 'language', content: 'EN' }), _jsx("meta", { name: 'owner', content: site_name }), _jsx("meta", { name: "publisher", content: site_name }), _jsx("meta", { name: 'rating', content: 'General' }), _jsx("meta", { name: 'reply-to', content: email }), _jsx("meta", { name: "robots", content: "index, follow" }), _jsx("meta", { name: 'url', content: url }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0, shrink-to-fit=no" }), _jsx("meta", { property: "og:description", content: description }), _jsx("meta", { property: 'og:email', content: email }), _jsx("meta", { property: "og:image", content: image }), _jsx("meta", { property: "og:image:height", content: image_height }), _jsx("meta", { property: "og:image:width", content: image_width }), _jsx("meta", { property: "og:locale", content: "en_US" }), _jsx("meta", { property: "og:site_name", content: site_name }), _jsx("meta", { property: "og:title", content: title }), _jsx("meta", { property: "og:type", content: "website" }), _jsx("meta", { property: "og:url", content: url }), _jsx("meta", { itemProp: "name", content: site_name }), _jsx("meta", { itemProp: "url", content: url }), _jsx("meta", { itemProp: "description", content: description }), _jsx("meta", { itemProp: "thumbnailUrl", content: image }), _jsx("meta", { property: "twitter:domain", content: new URL(origin).hostname }), _jsx("meta", { property: "twitter:url", content: url }), _jsx("meta", { name: "twitter:card", content: "summary_large_image" }), _jsx("meta", { name: "twitter:creator", content: site_name }), _jsx("meta", { name: "twitter:description", content: description }), _jsx("meta", { name: "twitter:image", content: image }), _jsx("meta", { name: "twitter:image:height", content: image_height }), _jsx("meta", { name: "twitter:image:width", content: image_width }), _jsx("meta", { name: "twitter:title", content: title }), _jsx("link", { rel: "author", href: origin }), _jsx("link", { rel: "canonical", href: url }), _jsx("link", { rel: "icon", type: "image/x-icon", href: favicon }), _jsx("link", { rel: "shortcut icon", type: "image/x-icon", href: favicon }), _jsx("link", { rel: "manifest", href: "/manifest.webmanifest" }), _jsx("link", { rel: "preconnect", href: "https://images.ctfassets.net/" }), _jsx("link", { rel: "preconnect", href: "https://res.cloudinary.com/" }), _jsx("link", { rel: "preconnect", href: "https://farm2.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm6.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm8.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm66.static.flickr.com" })] }));
20
- }
21
2
  setClientMetadata.propTypes = {
22
3
  title: PropTypes.string.isRequired,
23
4
  description: PropTypes.string.isRequired,
@@ -0,0 +1,111 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function descriptionToKeywords(descriptionText, numKeywords = 5, customStopWords = []) {
3
+ if (!descriptionText) {
4
+ return [];
5
+ }
6
+ // Define a default list of common English stop words
7
+ const defaultStopWords = new Set([
8
+ 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'for', 'if', 'in', 'into',
9
+ 'is', 'it', 'no', 'not', 'of', 'on', 'or', 'such', 'that', 'the', 'their',
10
+ 'then', 'there', 'these', 'they', 'this', 'to', 'was', 'will', 'with'
11
+ ]);
12
+ const allStopWords = new Set([...defaultStopWords, ...customStopWords]);
13
+ // Pre-process the text: make lowercase and remove punctuation
14
+ const cleanedText = descriptionText.toLowerCase().replace(/[.,/#!$%^&*;:{}=\-_`~()]/g, '');
15
+ // Tokenize the text into individual words
16
+ const words = cleanedText.split(/\s+/);
17
+ // Count word frequencies, excluding stop words
18
+ const wordFrequency = words.reduce((counts, word) => {
19
+ if (word.length > 2 && !allStopWords.has(word)) {
20
+ counts[word] = (counts[word] || 0) + 1;
21
+ }
22
+ return counts;
23
+ }, {});
24
+ // Sort words by frequency and get the top N keywords
25
+ const sortedKeywords = Object.keys(wordFrequency).sort((a, b) => wordFrequency[b] - wordFrequency[a]);
26
+ // Return the top N keywords
27
+ return sortedKeywords.slice(0, numKeywords);
28
+ }
29
+ export function getRouteByKey(obj, key, value) {
30
+ if (typeof obj !== 'object' || obj === null) {
31
+ return null;
32
+ }
33
+ if (obj[key] && obj[key] === value) {
34
+ return obj;
35
+ }
36
+ for (const prop in obj) {
37
+ if (obj[prop] && typeof obj[prop] === 'object') {
38
+ const result = getRouteByKey(obj[prop], key, value);
39
+ if (result) {
40
+ return result;
41
+ }
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+ export function getAllRoutes(routes, key) {
47
+ const result = [];
48
+ function traverse(obj) {
49
+ if (typeof obj !== 'object' || obj === null) {
50
+ return;
51
+ }
52
+ if (Array.isArray(obj)) {
53
+ obj.forEach(item => traverse(item));
54
+ }
55
+ else {
56
+ if (obj[key]) {
57
+ traverse(obj[key]);
58
+ }
59
+ else {
60
+ result.push(obj);
61
+ }
62
+ }
63
+ }
64
+ traverse(routes);
65
+ return result;
66
+ }
67
+ export const getMetadata = (routes, key = "name", value = "Home") => {
68
+ const foundObject = getRouteByKey(routes, key, value);
69
+ if (foundObject) {
70
+ const metadata = {
71
+ title: foundObject.title,
72
+ description: foundObject.description,
73
+ keywords: foundObject.keywords,
74
+ };
75
+ return metadata;
76
+ }
77
+ else {
78
+ const metadata = {
79
+ title: "",
80
+ description: "",
81
+ keywords: "",
82
+ };
83
+ return metadata;
84
+ }
85
+ };
86
+ export function getAccordionMenuData(myRoutes) {
87
+ const menuItems = myRoutes.map((thisRoute) => (thisRoute.routes
88
+ ? { [thisRoute.name]: thisRoute.routes.map((subRoute) => ({ [subRoute.name]: subRoute.path })) }
89
+ : { [thisRoute.name]: thisRoute.path })).reduce((obj, item) => {
90
+ if (typeof Object.values(item)[0] == "object") {
91
+ // Nested Object
92
+ const subitems = Object.values(item)[0];
93
+ const newSubitems = subitems.reduce((obj2, item2) => {
94
+ Object.assign(obj2, item2);
95
+ return obj2;
96
+ });
97
+ Object.assign(obj, { [Object.keys(item)[0]]: newSubitems });
98
+ return obj;
99
+ }
100
+ else {
101
+ // String
102
+ Object.assign(obj, item);
103
+ return obj;
104
+ }
105
+ });
106
+ return menuItems;
107
+ }
108
+ export function generateMetaTags(props) {
109
+ const { title, description, keywords, site_name, email, origin, url, image, image_height, image_width, favicon } = props;
110
+ return (_jsxs(_Fragment, { children: [_jsx("title", { children: title }), _jsx("meta", { charSet: "UTF-8" }), _jsx("meta", { httpEquiv: "content-type", content: "text/html; charset=UTF-8" }), _jsx("meta", { httpEquiv: 'Expires', content: '0' }), _jsx("meta", { httpEquiv: 'Pragma', content: 'no-cache' }), _jsx("meta", { httpEquiv: 'Cache-Control', content: 'no-cache' }), _jsx("meta", { name: "application-name", content: site_name }), _jsx("meta", { name: "author", content: site_name + ", " + email }), _jsx("meta", { name: 'copyright', content: site_name }), _jsx("meta", { name: "creator", content: site_name }), _jsx("meta", { name: "description", content: description }), _jsx("meta", { name: "keywords", content: keywords }), _jsx("meta", { name: 'language', content: 'EN' }), _jsx("meta", { name: 'owner', content: site_name }), _jsx("meta", { name: "publisher", content: site_name }), _jsx("meta", { name: 'rating', content: 'General' }), _jsx("meta", { name: 'reply-to', content: email }), _jsx("meta", { name: "robots", content: "index, follow" }), _jsx("meta", { name: 'url', content: url }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0, shrink-to-fit=no" }), _jsx("meta", { property: "og:description", content: description }), _jsx("meta", { property: 'og:email', content: email }), _jsx("meta", { property: "og:image", content: image }), _jsx("meta", { property: "og:image:height", content: image_height }), _jsx("meta", { property: "og:image:width", content: image_width }), _jsx("meta", { property: "og:locale", content: "en_US" }), _jsx("meta", { property: "og:site_name", content: site_name }), _jsx("meta", { property: "og:title", content: title }), _jsx("meta", { property: "og:type", content: "website" }), _jsx("meta", { property: "og:url", content: url }), _jsx("meta", { itemProp: "name", content: site_name }), _jsx("meta", { itemProp: "url", content: url }), _jsx("meta", { itemProp: "description", content: description }), _jsx("meta", { itemProp: "thumbnailUrl", content: image }), _jsx("meta", { property: "twitter:domain", content: new URL(origin).hostname }), _jsx("meta", { property: "twitter:url", content: url }), _jsx("meta", { name: "twitter:card", content: "summary_large_image" }), _jsx("meta", { name: "twitter:creator", content: site_name }), _jsx("meta", { name: "twitter:description", content: description }), _jsx("meta", { name: "twitter:image", content: image }), _jsx("meta", { name: "twitter:image:height", content: image_height }), _jsx("meta", { name: "twitter:image:width", content: image_width }), _jsx("meta", { name: "twitter:title", content: title }), _jsx("link", { rel: "author", href: origin }), _jsx("link", { rel: "canonical", href: url }), _jsx("link", { rel: "icon", type: "image/x-icon", href: favicon }), _jsx("link", { rel: "shortcut icon", type: "image/x-icon", href: favicon }), _jsx("link", { rel: "manifest", href: "/manifest.webmanifest" }), _jsx("link", { rel: "preconnect", href: "https://images.ctfassets.net/" }), _jsx("link", { rel: "preconnect", href: "https://res.cloudinary.com/" }), _jsx("link", { rel: "preconnect", href: "https://farm2.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm6.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm8.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm66.static.flickr.com" })] }));
111
+ }
@@ -0,0 +1,42 @@
1
+ import { decode } from 'html-entities';
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 cleanContent = (content) => {
9
+ if (!content)
10
+ return '';
11
+ // Strip HTML tags and decode all HTML entities
12
+ const stripped = content.replace(/<[^>]*>/g, '');
13
+ return decode(stripped).replace(/\[…\]/g, '').trim();
14
+ };
15
+ const description = cleanContent(post.excerpt);
16
+ const articleBody = includeFullContent ? cleanContent(post.content || '') : undefined;
17
+ const schema = {
18
+ '@context': 'https://schema.org',
19
+ '@type': 'BlogPosting',
20
+ headline: decode(post.title.replace(/<[^>]*>/g, '')),
21
+ description: description || decode(post.title.replace(/<[^>]*>/g, '')),
22
+ datePublished: post.date,
23
+ image: post.featured_image || post.post_thumbnail?.URL,
24
+ articleSection: Array.isArray(post.categories) && post.categories.length > 0
25
+ ? post.categories[0]
26
+ : 'Blog',
27
+ keywords: Array.isArray(post.categories) ? post.categories : [],
28
+ };
29
+ if (articleBody) {
30
+ schema.articleBody = articleBody;
31
+ }
32
+ if (post.modified) {
33
+ schema.dateModified = post.modified;
34
+ }
35
+ if (post.author) {
36
+ schema.author = {
37
+ '@type': 'Person',
38
+ name: post.author,
39
+ };
40
+ }
41
+ return schema;
42
+ }
@@ -1,50 +1,4 @@
1
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
2
  export function SchemaBlogPosting({ post }) {
49
3
  return (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: {
50
4
  __html: JSON.stringify(post),