@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.
- package/README.COMPONENTS.md +435 -78
- package/README.md +59 -32
- package/dist/components/callout/callout.scss +0 -3
- package/dist/components/cms/flickr.js +8 -2
- package/dist/components/cms/google.reviews.components.js +1 -1
- package/dist/components/general/tab.css +105 -0
- package/dist/components/general/tab.js +26 -0
- package/dist/components/menu/menu-expando.js +7 -1
- package/dist/components/nerdjoke/nerdjoke.js +13 -7
- package/dist/components/seo/manifest.js +40 -0
- package/dist/components/seo/metadata.components.js +0 -19
- package/dist/components/seo/metadata.functions.js +111 -0
- package/dist/components/seo/schema-blogposting.functions.js +42 -0
- package/dist/components/seo/schema-blogposting.js +0 -46
- package/dist/components/seo/schema-localbusiness.js +46 -2
- package/dist/components/seo/schema-website.js +31 -2
- package/dist/components/seo/sitemap.js +4 -4
- package/dist/components/shoppingcart/shoppingcart.components.js +4 -4
- package/dist/components/sitebuilder/config/ConfigBuilder.css +266 -0
- package/dist/components/sitebuilder/config/ConfigBuilder.js +221 -0
- package/dist/components/{pagebuilder → sitebuilder}/form/form.css +55 -30
- package/dist/components/sitebuilder/form/formbuilder.js +106 -0
- package/dist/components/sitebuilder/form/formcomponents.js +356 -0
- package/dist/components/sitebuilder/form/formengine.js +82 -0
- package/dist/components/{pagebuilder/form/form.js → sitebuilder/form/formextractor.js} +10 -211
- package/dist/components/sitebuilder/form/formutils.js +206 -0
- package/dist/components/sitebuilder/form/formvalidator.js +123 -0
- package/dist/components/{pagebuilder → sitebuilder/page}/components/ComponentPropertiesForm.js +1 -1
- package/dist/components/{pagebuilder → sitebuilder/page}/components/PageBuilderUI.js +2 -2
- package/dist/components/{pagebuilder → sitebuilder/page}/components/PageEngine.js +1 -1
- package/dist/components/sitebuilder/page/documentation/api-examples/save-route-example.js +37 -0
- package/dist/components/{pagebuilder → sitebuilder/page}/lib/componentMap.js +3 -3
- package/dist/components/{pagebuilder → sitebuilder/page}/lib/componentMetadata.js +2 -2
- package/dist/components/{pagebuilder → sitebuilder/page}/lib/pageStorageContentful.js +2 -2
- package/dist/components/sitebuilder/page/lib/pageStorageTypes.js +1 -0
- package/dist/data/form.json +18 -18
- package/dist/data/routes.json +25 -0
- package/dist/data/routes2.json +25 -0
- package/dist/data/shipping.to.json +9 -9
- package/dist/data/siteinfo-form.json +200 -0
- package/dist/index.js +31 -23
- package/dist/index.server.js +24 -18
- package/dist/types/components/cms/flickr.d.ts.map +1 -1
- package/dist/types/components/cms/google.reviews.components.d.ts.map +1 -1
- package/dist/types/components/config/config.types.d.ts +30 -0
- package/dist/types/components/config/config.types.d.ts.map +1 -1
- package/dist/types/components/general/semantic.d.ts +3 -3
- package/dist/types/components/general/tab.d.ts +18 -0
- package/dist/types/components/general/tab.d.ts.map +1 -0
- package/dist/types/components/menu/menu-expando.d.ts.map +1 -1
- package/dist/types/components/nerdjoke/nerdjoke.d.ts.map +1 -1
- package/dist/types/components/seo/manifest.d.ts +19 -0
- package/dist/types/components/seo/manifest.d.ts.map +1 -0
- package/dist/types/components/seo/metadata.components.d.ts +0 -17
- package/dist/types/components/seo/metadata.components.d.ts.map +1 -1
- package/dist/types/components/seo/{metadata.d.ts → metadata.functions.d.ts} +15 -1
- package/dist/types/components/seo/metadata.functions.d.ts.map +1 -0
- package/dist/types/components/seo/schema-blogposting.d.ts +1 -25
- package/dist/types/components/seo/schema-blogposting.d.ts.map +1 -1
- package/dist/types/components/seo/schema-blogposting.functions.d.ts +26 -0
- package/dist/types/components/seo/schema-blogposting.functions.d.ts.map +1 -0
- package/dist/types/components/seo/schema-localbusiness.d.ts +22 -23
- package/dist/types/components/seo/schema-localbusiness.d.ts.map +1 -1
- package/dist/types/components/seo/schema-website.d.ts +17 -18
- package/dist/types/components/seo/schema-website.d.ts.map +1 -1
- package/dist/types/components/seo/sitemap.d.ts.map +1 -1
- package/dist/types/components/shoppingcart/shoppingcart.components.d.ts +1 -1
- package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts +86 -0
- package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/form/formbuilder.d.ts +11 -0
- package/dist/types/components/sitebuilder/form/formbuilder.d.ts.map +1 -0
- package/dist/types/components/{pagebuilder → sitebuilder}/form/formcomponents.d.ts +12 -16
- package/dist/types/components/sitebuilder/form/formcomponents.d.ts.map +1 -0
- package/dist/types/components/{pagebuilder/form/form.submit.d.ts → sitebuilder/form/formemailer.d.ts} +1 -1
- package/dist/types/components/sitebuilder/form/formemailer.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/form/formengine.d.ts +14 -0
- package/dist/types/components/sitebuilder/form/formengine.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/form/formextractor.d.ts +25 -0
- package/dist/types/components/sitebuilder/form/formextractor.d.ts.map +1 -0
- package/dist/types/components/{pagebuilder/form/formvalidations.d.ts → sitebuilder/form/formfieldvalidations.d.ts} +1 -1
- package/dist/types/components/sitebuilder/form/formfieldvalidations.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/form/formtypes.d.ts +66 -0
- package/dist/types/components/sitebuilder/form/formtypes.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/form/formutils.d.ts +20 -0
- package/dist/types/components/sitebuilder/form/formutils.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/form/formvalidator.d.ts +20 -0
- package/dist/types/components/sitebuilder/form/formvalidator.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/components/ComponentPropertiesForm.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/components/ComponentSelector.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/components/ComponentTree.d.ts.map +1 -0
- package/dist/types/components/{pagebuilder → sitebuilder/page}/components/PageBuilderUI.d.ts +1 -1
- package/dist/types/components/sitebuilder/page/components/PageBuilderUI.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/components/PageEngine.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/components/SaveLoadSection.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/documentation/api-examples/save-route-example.d.ts +6 -0
- package/dist/types/components/sitebuilder/page/documentation/api-examples/save-route-example.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/lib/componentGeneration.d.ts.map +1 -0
- package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/componentMap.d.ts +3 -3
- package/dist/types/components/sitebuilder/page/lib/componentMap.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/lib/componentMetadata.d.ts.map +1 -0
- package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/pageStorageContentful.d.ts +1 -1
- package/dist/types/components/sitebuilder/page/lib/pageStorageContentful.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/lib/pageStorageLocal.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/lib/pageStorageTypes.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/lib/propTypeIntrospection.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/lib/types.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/lib/usePageBuilder.d.ts.map +1 -0
- package/dist/types/index.d.ts +30 -21
- package/dist/types/index.server.d.ts +23 -17
- package/dist/types/stories/general/tab.stories.d.ts +45 -0
- package/dist/types/stories/general/tab.stories.d.ts.map +1 -0
- package/dist/types/stories/seo/seo.googleanalytics.stories.d.ts.map +1 -1
- package/dist/types/stories/seo/seo.metadata.stories.d.ts +1 -1
- package/dist/types/stories/seo/seo.metadata.stories.d.ts.map +1 -1
- package/dist/types/stories/seo/seo.schema.stories.d.ts +23 -0
- package/dist/types/stories/seo/seo.schema.stories.d.ts.map +1 -0
- package/dist/types/stories/sitebuilder/configbuilder.stories.d.ts +48 -0
- package/dist/types/stories/sitebuilder/configbuilder.stories.d.ts.map +1 -0
- package/dist/types/stories/{pagebuilder → sitebuilder}/form-builder.stories.d.ts +1 -1
- package/dist/types/stories/sitebuilder/form-builder.stories.d.ts.map +1 -0
- package/dist/types/stories/{pagebuilder → sitebuilder}/form-engine.stories.d.ts +1 -1
- package/dist/types/stories/sitebuilder/form-engine.stories.d.ts.map +1 -0
- package/dist/types/stories/{pagebuilder → sitebuilder}/form-extractor.stories.d.ts +1 -1
- package/dist/types/stories/sitebuilder/form-extractor.stories.d.ts.map +1 -0
- package/dist/types/stories/{pagebuilder → sitebuilder}/pagebuilder.stories.d.ts +1 -1
- package/dist/types/stories/{pagebuilder → sitebuilder}/pagebuilder.stories.d.ts.map +1 -1
- package/dist/types/stories/{pagebuilder → sitebuilder}/pagebuilder.usageguide.stories.d.ts +1 -1
- package/dist/types/stories/{pagebuilder → sitebuilder}/pagebuilder.usageguide.stories.d.ts.map +1 -1
- package/dist/types/stories/{pagebuilder → sitebuilder}/pageengine.stories.d.ts +1 -1
- package/dist/types/stories/{pagebuilder → sitebuilder}/pageengine.stories.d.ts.map +1 -1
- package/dist/types/tests/configbuilder.test.d.ts +2 -0
- package/dist/types/tests/configbuilder.test.d.ts.map +1 -0
- package/dist/types/tests/manifest.test.d.ts +2 -0
- package/dist/types/tests/manifest.test.d.ts.map +1 -0
- package/dist/types/tests/tab.test.d.ts +2 -0
- package/dist/types/tests/tab.test.d.ts.map +1 -0
- package/package.json +6 -5
- package/dist/components/pagebuilder/form/formcomponents.js +0 -359
- package/dist/components/seo/metadata.js +0 -108
- package/dist/components/utilities/api.js +0 -36
- package/dist/types/components/pagebuilder/components/ComponentPropertiesForm.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/components/ComponentSelector.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/components/ComponentTree.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/components/PageBuilderUI.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/components/PageEngine.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/components/SaveLoadSection.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/form/form.d.ts +0 -46
- package/dist/types/components/pagebuilder/form/form.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/form/form.submit.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/form/formcomponents.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/form/formvalidations.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/lib/componentGeneration.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/lib/componentMap.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/lib/componentMetadata.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/lib/pageStorageContentful.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/lib/pageStorageLocal.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/lib/pageStorageTypes.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/lib/propTypeIntrospection.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/lib/types.d.ts.map +0 -1
- package/dist/types/components/pagebuilder/lib/usePageBuilder.d.ts.map +0 -1
- package/dist/types/components/seo/metadata.d.ts.map +0 -1
- package/dist/types/components/utilities/api.d.ts +0 -16
- package/dist/types/components/utilities/api.d.ts.map +0 -1
- package/dist/types/stories/pagebuilder/form-builder.stories.d.ts.map +0 -1
- package/dist/types/stories/pagebuilder/form-engine.stories.d.ts.map +0 -1
- package/dist/types/stories/pagebuilder/form-extractor.stories.d.ts.map +0 -1
- package/dist/types/tests/api.test.d.ts +0 -2
- package/dist/types/tests/api.test.d.ts.map +0 -1
- /package/dist/components/{pagebuilder/form/form.submit.js → sitebuilder/form/formemailer.js} +0 -0
- /package/dist/components/{pagebuilder/form/formvalidations.js → sitebuilder/form/formfieldvalidations.js} +0 -0
- /package/dist/components/{pagebuilder/lib/pageStorageTypes.js → sitebuilder/form/formtypes.js} +0 -0
- /package/dist/components/{pagebuilder → sitebuilder/page}/components/ComponentSelector.js +0 -0
- /package/dist/components/{pagebuilder → sitebuilder/page}/components/ComponentTree.js +0 -0
- /package/dist/components/{pagebuilder → sitebuilder/page}/components/SaveLoadSection.js +0 -0
- /package/dist/components/{pagebuilder → sitebuilder/page}/components/pagebuilder.scss +0 -0
- /package/dist/components/{pagebuilder → sitebuilder/page}/lib/componentGeneration.js +0 -0
- /package/dist/components/{pagebuilder → sitebuilder/page}/lib/pageStorageLocal.js +0 -0
- /package/dist/components/{pagebuilder → sitebuilder/page}/lib/propTypeIntrospection.js +0 -0
- /package/dist/components/{pagebuilder → sitebuilder/page}/lib/types.js +0 -0
- /package/dist/components/{pagebuilder → sitebuilder/page}/lib/usePageBuilder.js +0 -0
- /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/ComponentPropertiesForm.d.ts +0 -0
- /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/ComponentSelector.d.ts +0 -0
- /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/ComponentTree.d.ts +0 -0
- /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/PageEngine.d.ts +0 -0
- /package/dist/types/components/{pagebuilder → sitebuilder/page}/components/SaveLoadSection.d.ts +0 -0
- /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/componentGeneration.d.ts +0 -0
- /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/componentMetadata.d.ts +0 -0
- /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/pageStorageLocal.d.ts +0 -0
- /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/pageStorageTypes.d.ts +0 -0
- /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/propTypeIntrospection.d.ts +0 -0
- /package/dist/types/components/{pagebuilder → sitebuilder/page}/lib/types.d.ts +0 -0
- /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,
|
|
273
|
+
**Current Status**: ✅ 2,244 tests passing across 67 test files
|
|
265
274
|
|
|
266
275
|
| Metric | Value |
|
|
267
276
|
|--------|-------|
|
|
268
|
-
| Test Files |
|
|
269
|
-
| Total Tests | 2,
|
|
270
|
-
| Coverage (Statements) |
|
|
271
|
-
| Coverage (Lines) |
|
|
272
|
-
| Coverage (Functions) |
|
|
273
|
-
| Coverage (Branches) |
|
|
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
|
-
- **
|
|
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
|
-
- **
|
|
295
|
-
- **
|
|
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.
|
|
306
|
-
- **functions.ts**: 90.
|
|
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
|
-
- **
|
|
313
|
-
- **
|
|
314
|
-
- **
|
|
315
|
-
- **
|
|
316
|
-
- **
|
|
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**:
|
|
319
|
-
- **
|
|
320
|
-
- **
|
|
321
|
-
- **
|
|
322
|
-
- **
|
|
323
|
-
- **
|
|
324
|
-
- **
|
|
325
|
-
- **
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
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),
|