@pixelated-tech/components 3.2.4 → 3.2.6
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.md +6 -0
- package/dist/components/callout/callout.js +20 -2
- package/dist/components/callout/callout.scss +9 -2
- package/dist/components/cms/contentful.items.components.js +2 -2
- package/dist/components/cms/wordpress.components.js +14 -9
- package/dist/components/general/accordion.css +114 -0
- package/dist/components/general/accordion.js +13 -0
- package/dist/components/menu/menu-expando.js +1 -6
- package/dist/components/seo/schema-blogposting.js +53 -0
- package/dist/components/seo/schema-localbusiness.js +27 -0
- package/dist/components/seo/schema-recipe.js +5 -0
- package/dist/components/seo/schema-services.js +24 -0
- package/dist/components/seo/schema-website.js +13 -0
- package/dist/components/shoppingcart/shoppingcart.css +10 -0
- package/dist/components/structured/recipe.css +1 -1
- package/dist/components/structured/recipe.js +58 -8
- package/dist/data/recipes.json +3157 -1821
- package/dist/index.js +6 -0
- package/dist/types/components/callout/callout.d.ts +1 -1
- package/dist/types/components/callout/callout.d.ts.map +1 -1
- package/dist/types/components/cms/wordpress.components.d.ts +1 -0
- package/dist/types/components/cms/wordpress.components.d.ts.map +1 -1
- package/dist/types/components/general/accordion.d.ts +18 -0
- package/dist/types/components/general/accordion.d.ts.map +1 -0
- package/dist/types/components/menu/menu-expando.d.ts.map +1 -1
- package/dist/types/components/seo/schema-blogposting.d.ts +31 -0
- package/dist/types/components/seo/schema-blogposting.d.ts.map +1 -0
- package/dist/types/components/seo/schema-localbusiness.d.ts +25 -0
- package/dist/types/components/seo/schema-localbusiness.d.ts.map +1 -0
- package/dist/types/components/seo/schema-recipe.d.ts +34 -0
- package/dist/types/components/seo/schema-recipe.d.ts.map +1 -0
- package/dist/types/components/seo/schema-services.d.ts +25 -0
- package/dist/types/components/seo/schema-services.d.ts.map +1 -0
- package/dist/types/components/seo/schema-website.d.ts +21 -0
- package/dist/types/components/seo/schema-website.d.ts.map +1 -0
- package/dist/types/components/structured/recipe.d.ts +34 -13
- package/dist/types/components/structured/recipe.d.ts.map +1 -1
- package/dist/types/index.d.ts +6 -0
- package/dist/types/stories/general/accordion.stories.d.ts +10 -0
- package/dist/types/stories/general/accordion.stories.d.ts.map +1 -0
- package/dist/types/tests/accordion.test.d.ts +2 -0
- package/dist/types/tests/accordion.test.d.ts.map +1 -0
- package/dist/types/tests/schema-blogposting.test.d.ts +2 -0
- package/dist/types/tests/schema-blogposting.test.d.ts.map +1 -0
- package/dist/types/tests/schema-localbusiness.test.d.ts +2 -0
- package/dist/types/tests/schema-localbusiness.test.d.ts.map +1 -0
- package/dist/types/tests/schema-recipe.test.d.ts +2 -0
- package/dist/types/tests/schema-recipe.test.d.ts.map +1 -0
- package/dist/types/tests/schema-services.test.d.ts +2 -0
- package/dist/types/tests/schema-services.test.d.ts.map +1 -0
- package/dist/types/tests/schema-website.test.d.ts +2 -0
- package/dist/types/tests/schema-website.test.d.ts.map +1 -0
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -82,6 +82,7 @@ To get a local copy up and running follow these simple example steps.
|
|
|
82
82
|
|
|
83
83
|
Components to help build websites quicker:
|
|
84
84
|
1. Centralized 404 Error Page
|
|
85
|
+
1. Accordion Component
|
|
85
86
|
1. Buzzword Bingo Cards
|
|
86
87
|
1. Page Callouts
|
|
87
88
|
1. Image Carousel - Page, Header, and Simple
|
|
@@ -96,6 +97,11 @@ Components to help build websites quicker:
|
|
|
96
97
|
1. Form Components and Form Builder
|
|
97
98
|
1. Google Analytics, Map, and Search Integration
|
|
98
99
|
1. Gravatar Card Integration
|
|
100
|
+
1. Local Business JSON-LD Schema for SEO
|
|
101
|
+
1. Website JSON-LD Schema for SEO
|
|
102
|
+
1. Services JSON-LD Schema for SEO
|
|
103
|
+
1. Recipe JSON-LD Schema for SEO
|
|
104
|
+
1. BlogPosting JSON-LD Schema for SEO
|
|
99
105
|
1. Page and Page Section Header Components
|
|
100
106
|
1. Hubspot Calendar and Form Integration
|
|
101
107
|
1. Instagram Image Fetch Integration
|
|
@@ -86,11 +86,29 @@ export function CalloutHeader({ title, url, target }) {
|
|
|
86
86
|
/* ========== CALLOUT BUTTON ========== */
|
|
87
87
|
CalloutButton.propTypes = {
|
|
88
88
|
title: PropTypes.string.isRequired,
|
|
89
|
-
url: PropTypes.string,
|
|
89
|
+
url: PropTypes.string.isRequired,
|
|
90
90
|
target: PropTypes.string
|
|
91
91
|
};
|
|
92
|
+
/* export function CalloutButton( { title, url, target } : CalloutButtonType) {
|
|
93
|
+
return (
|
|
94
|
+
<div className="callout-button">
|
|
95
|
+
{ (url)
|
|
96
|
+
? <button type="button" className="callout-button"><a href={url || ""} target={target || ""} rel={target=="_blank" ? "noopener noreferrer" : ""}>{title}</a></button>
|
|
97
|
+
: null
|
|
98
|
+
}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
} */
|
|
92
102
|
export function CalloutButton({ title, url, target }) {
|
|
103
|
+
const handleClick = () => {
|
|
104
|
+
if (target === '_blank') {
|
|
105
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
window.location.href = url;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
93
111
|
return (_jsx("div", { className: "callout-button", children: (url)
|
|
94
|
-
? _jsx("button", { type: "button", className: "callout-button",
|
|
112
|
+
? _jsx("button", { type: "button", className: "callout-button", onClick: handleClick, children: title })
|
|
95
113
|
: null }));
|
|
96
114
|
}
|
|
@@ -142,10 +142,17 @@
|
|
|
142
142
|
/* cursor: pointer */
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
.callout .callout-button
|
|
146
|
-
|
|
145
|
+
.callout .callout-button button:hover {
|
|
146
|
+
color: #369;
|
|
147
|
+
text-decoration: underline;
|
|
148
|
+
cursor: pointer;
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
|
|
152
|
+
/* .callout .callout-button a {
|
|
153
|
+
display: inline-block;
|
|
154
|
+
} */
|
|
155
|
+
|
|
149
156
|
/* ========================================
|
|
150
157
|
============= BOXED CALLOUT =============
|
|
151
158
|
======================================== */
|
|
@@ -135,7 +135,7 @@ export function ContentfulListItem(props) {
|
|
|
135
135
|
? _jsx(ContentfulItemHeader, { url: itemURL, target: itemURLTarget, title: thisItem.fields.title })
|
|
136
136
|
: _jsx(ContentfulItemHeader, { title: thisItem.fields.title }) }), _jsxs("div", { className: "contentful-item-details grid12", children: [_jsxs("div", { children: [_jsx("b", { children: "Item ID: " }), thisItem.sys.id] }), _jsxs("div", { children: [_jsx("b", { children: "UPC ID: " }), thisItem.fields.id] }), _jsxs("div", { children: [_jsx("b", { children: "Quantity: " }), thisItem.fields.quantity] }), _jsxs("div", { children: [_jsx("b", { children: "Brand / Model: " }), thisItem.fields.brand, " ", thisItem.fields.model] }), _jsxs("div", { children: [_jsx("b", { children: "Listing Date: " }), thisItem.fields.date] })] }), _jsx("div", { className: "contentful-item-price", children: itemURL
|
|
137
137
|
? _jsxs("a", { href: itemURL, target: itemURLTarget, rel: "noreferrer", children: ["$", thisItem.fields.price, " USD"] })
|
|
138
|
-
: "$" + thisItem.fields.price + " USD" }), _jsx("br", {}), _jsxs("div", { className: "contentful-
|
|
138
|
+
: "$" + thisItem.fields.price + " USD" }), _jsx("br", {}), _jsxs("div", { className: "contentful-item-addtocart", children: [_jsx(ViewItemDetails, { href: "/store", itemID: thisItem.sys.id }), _jsx(AddToCartButton, { handler: AddToShoppingCart, item: shoppingCartItem, itemID: thisItem.sys.id })] })] })] }));
|
|
139
139
|
}
|
|
140
140
|
/* ========== CONTENTFUL ITEM HEADER ========== */
|
|
141
141
|
ContentfulItemHeader.propTypes = {
|
|
@@ -235,7 +235,7 @@ export function ContentfulItemDetail(props) {
|
|
|
235
235
|
? _jsx(ContentfulItemHeader, { url: itemURL, title: thisItem.fields.title })
|
|
236
236
|
: _jsx(ContentfulItemHeader, { title: thisItem.fields.title }) }), _jsx("br", {}), _jsx("div", { className: "contentful-item-photo-carousel grid-s1-e7", children: _jsx(Carousel, { cards: cards, draggable: true, imgFit: "contain" }) }), _jsxs("div", { className: "grid-s7-e13", children: [_jsx("div", { className: "contentful-item-details grid12", children: _jsx("div", { dangerouslySetInnerHTML: { __html: thisItem.fields.description.replace(/(<br\s*\/?>\s*){2,}/gi, '') } }) }), _jsx("br", {}), _jsxs("div", { className: "contentful-item-details grid12", children: [_jsxs("div", { children: [_jsx("b", { children: "Item ID: " }), thisItem.sys.id] }), _jsxs("div", { children: [_jsx("b", { children: "UPC ID: " }), thisItem.fields.id] }), _jsxs("div", { children: [_jsx("b", { children: "Quantity: " }), thisItem.fields.quantity] }), _jsxs("div", { children: [_jsx("b", { children: "Brand / Model: " }), thisItem.fields.brand, " ", thisItem.fields.model] }), _jsxs("div", { children: [_jsx("b", { children: "Listing Date: " }), thisItem.fields.date] }), _jsx("br", {})] }), _jsx("div", { className: "contentful-item-price", children: itemURL
|
|
237
237
|
? _jsxs("a", { href: itemURL, target: itemURLTarget, rel: "noreferrer", children: ["$", thisItem.fields.price, " USD"] })
|
|
238
|
-
: "$" + thisItem.fields.price + " USD" }), _jsx("br", {}), _jsx("div", { className: "contentful-
|
|
238
|
+
: "$" + thisItem.fields.price + " USD" }), _jsx("br", {}), _jsx("div", { className: "contentful-item-addtocart", children: _jsx(AddToCartButton, { handler: AddToShoppingCart, item: shoppingCartItem, itemID: thisItem.sys.id }) })] })] }) }));
|
|
239
239
|
}
|
|
240
240
|
else {
|
|
241
241
|
return (_jsx(_Fragment, { children: _jsx("div", { id: "contentful-items", className: "contentful-items", children: _jsx("div", { className: "centered", children: "Loading..." }) }) }));
|
|
@@ -7,17 +7,22 @@ import { getWordPressItems } from './wordpress.functions';
|
|
|
7
7
|
import { Loading, ToggleLoading } from '../general/loading';
|
|
8
8
|
import "./wordpress.css";
|
|
9
9
|
// https://microformats.org/wiki/h-entry
|
|
10
|
-
function decodeString(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
temp = null;
|
|
15
|
-
return str;
|
|
10
|
+
function decodeString(str) {
|
|
11
|
+
const textarea = document.createElement('textarea');
|
|
12
|
+
textarea.innerHTML = str;
|
|
13
|
+
return textarea.value;
|
|
16
14
|
}
|
|
17
15
|
export function BlogPostList(props) {
|
|
18
|
-
const { site, count } = props;
|
|
19
|
-
const [posts, setPosts] = useState([]);
|
|
16
|
+
const { site, count, posts: cachedPosts } = props;
|
|
17
|
+
const [posts, setPosts] = useState(cachedPosts ?? []);
|
|
20
18
|
useEffect(() => {
|
|
19
|
+
// If posts are provided, use them directly without fetching
|
|
20
|
+
if (cachedPosts && cachedPosts.length > 0) {
|
|
21
|
+
const sorted = cachedPosts.sort((a, b) => ((a.date ?? '') < (b.date ?? '')) ? 1 : -1);
|
|
22
|
+
setPosts(sorted);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Otherwise, fetch from WordPress
|
|
21
26
|
ToggleLoading({ show: true });
|
|
22
27
|
(async () => {
|
|
23
28
|
const data = (await getWordPressItems({ site, count })) ?? [];
|
|
@@ -25,7 +30,7 @@ export function BlogPostList(props) {
|
|
|
25
30
|
setPosts(sorted);
|
|
26
31
|
ToggleLoading({ show: false });
|
|
27
32
|
})();
|
|
28
|
-
}, [site, count]);
|
|
33
|
+
}, [site, count, cachedPosts]);
|
|
29
34
|
return (_jsxs(_Fragment, { children: [_jsx(Loading, {}), posts.map((post) => (_jsx(PageGridItem, { children: _jsx(BlogPostSummary, { ID: post.ID, title: post.title, date: post.date, excerpt: post.excerpt, URL: post.URL, categories: post.categories, featured_image: post.featured_image }) }, post.ID)))] }));
|
|
30
35
|
}
|
|
31
36
|
export function BlogPostSummary(props) {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/* ========================================
|
|
2
|
+
===== ACCORDION COMPONENT =====
|
|
3
|
+
======================================== */
|
|
4
|
+
|
|
5
|
+
.accordion {
|
|
6
|
+
margin: 0;
|
|
7
|
+
padding: 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.accordion-item {
|
|
11
|
+
margin-bottom: 0.5rem;
|
|
12
|
+
border: 1px solid #e1e5e9;
|
|
13
|
+
border-radius: 4px;
|
|
14
|
+
background: #fff;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.accordion-title {
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
padding: 1rem;
|
|
20
|
+
font-size: 1rem;
|
|
21
|
+
font-weight: 600;
|
|
22
|
+
color: #1a202c;
|
|
23
|
+
background: none;
|
|
24
|
+
border: none;
|
|
25
|
+
width: 100%;
|
|
26
|
+
text-align: left;
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: 0.75rem;
|
|
30
|
+
transition: background-color 0.2s ease;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.accordion-title:hover {
|
|
34
|
+
background-color: #f7fafc;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.accordion-title:focus {
|
|
38
|
+
outline: 2px solid #3182ce;
|
|
39
|
+
outline-offset: -2px;
|
|
40
|
+
background-color: #f7fafc;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Hide default triangle and add custom one */
|
|
44
|
+
.accordion-title::before {
|
|
45
|
+
content: '▶';
|
|
46
|
+
font-size: 0.75rem;
|
|
47
|
+
color: #718096;
|
|
48
|
+
transition: transform 0.2s ease;
|
|
49
|
+
flex-shrink: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
details[open] .accordion-title::before {
|
|
53
|
+
content: '🔽';
|
|
54
|
+
transform: rotate(0deg);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* Remove default marker */
|
|
58
|
+
.accordion-title::-webkit-details-marker,
|
|
59
|
+
.accordion-title::marker {
|
|
60
|
+
display: none;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.accordion-title h3 {
|
|
64
|
+
margin: 0;
|
|
65
|
+
font-size: inherit;
|
|
66
|
+
font-weight: inherit;
|
|
67
|
+
color: inherit;
|
|
68
|
+
flex: 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.accordion-content {
|
|
72
|
+
padding: 1rem;
|
|
73
|
+
border-top: 1px solid #e1e5e9;
|
|
74
|
+
background-color: #f7fafc;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.accordion-content p {
|
|
78
|
+
margin: 0 0 1rem 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.accordion-content p:last-child {
|
|
82
|
+
margin-bottom: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Animation for smooth expand/collapse */
|
|
86
|
+
.accordion-content {
|
|
87
|
+
animation: accordionSlideDown 0.3s ease-out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
details[open] .accordion-content {
|
|
91
|
+
animation: accordionSlideDown 0.3s ease-out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@keyframes accordionSlideDown {
|
|
95
|
+
from {
|
|
96
|
+
opacity: 0;
|
|
97
|
+
transform: translateY(-10px);
|
|
98
|
+
}
|
|
99
|
+
to {
|
|
100
|
+
opacity: 1;
|
|
101
|
+
transform: translateY(0);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Respect user's motion preferences */
|
|
106
|
+
@media (prefers-reduced-motion: reduce) {
|
|
107
|
+
.accordion-content {
|
|
108
|
+
animation: none;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.accordion-title::before {
|
|
112
|
+
transition: none;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
4
|
+
import './accordion.css';
|
|
5
|
+
Accordion.propTypes = {
|
|
6
|
+
items: PropTypes.arrayOf(PropTypes.shape({
|
|
7
|
+
title: PropTypes.string.isRequired,
|
|
8
|
+
content: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
|
|
9
|
+
})).isRequired,
|
|
10
|
+
};
|
|
11
|
+
export function Accordion({ items }) {
|
|
12
|
+
return (_jsx("div", { className: "accordion", children: items?.map((item, index) => (item ? (_jsxs("details", { className: "accordion-item", children: [_jsx("summary", { className: "accordion-title", children: _jsx("h3", { id: `accordion-header-${index}`, children: item.title }) }), _jsx("div", { className: "accordion-content", role: "region", "aria-labelledby": `accordion-header-${index}`, children: typeof item.content === 'string' ? (_jsx("p", { children: item.content })) : (item.content) })] }, index)) : null)) }));
|
|
13
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
1
2
|
'use client';
|
|
2
3
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
4
|
import { useEffect, useRef } from 'react';
|
|
@@ -86,14 +87,10 @@ export function MenuExpando(props) {
|
|
|
86
87
|
}, []);
|
|
87
88
|
function generateMenuItems() {
|
|
88
89
|
const myItems = [];
|
|
89
|
-
console.log('MenuExpando props.menuItems:', props.menuItems);
|
|
90
|
-
console.log('Is array?', Array.isArray(props.menuItems));
|
|
91
90
|
// Handle both object format (name: href) and array format (with name/path properties)
|
|
92
91
|
if (Array.isArray(props.menuItems)) {
|
|
93
92
|
// Array format like MenuAccordion
|
|
94
|
-
console.log('Processing as array, length:', props.menuItems.length);
|
|
95
93
|
for (const item of props.menuItems) {
|
|
96
|
-
console.log('Item:', item);
|
|
97
94
|
if (item.routes && item.routes.length > 0) {
|
|
98
95
|
// Item has nested routes - create expandable submenu
|
|
99
96
|
myItems.push(_jsx("li", { children: _jsxs("details", { className: "menuExpandoNested", children: [_jsx("summary", { children: _jsx("a", { href: item.path, children: item.name }) }), _jsx("ul", { children: item.routes.map((route) => (_jsx(MenuExpandoItem, { name: route.name, href: route.path }, route.name))) })] }) }, item.name));
|
|
@@ -106,12 +103,10 @@ export function MenuExpando(props) {
|
|
|
106
103
|
}
|
|
107
104
|
else {
|
|
108
105
|
// Object format
|
|
109
|
-
console.log('Processing as object');
|
|
110
106
|
for (const itemKey in props.menuItems) {
|
|
111
107
|
myItems.push(_jsx(MenuExpandoItem, { name: itemKey, href: props.menuItems[itemKey] }, itemKey));
|
|
112
108
|
}
|
|
113
109
|
}
|
|
114
|
-
console.log('Generated items count:', myItems.length);
|
|
115
110
|
return myItems;
|
|
116
111
|
}
|
|
117
112
|
return (_jsx("div", { className: "menuExpando", id: "menuExpando", children: _jsxs("details", { className: "menuExpandoWrapper", id: "menuExpandoWrapper", ref: detailsRef, children: [_jsx("summary", {}), _jsx("ul", { ref: ulRef, children: generateMenuItems() })] }) }));
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Converts WordPress REST API blog post to schema.org BlogPosting format
|
|
4
|
+
* @param post WordPress blog post
|
|
5
|
+
* @param includeFullContent Whether to include articleBody (true) or just description (false)
|
|
6
|
+
*/
|
|
7
|
+
export function mapWordPressToBlogPosting(post, includeFullContent = false) {
|
|
8
|
+
const decodeString = (s) => {
|
|
9
|
+
if (typeof document === 'undefined')
|
|
10
|
+
return s;
|
|
11
|
+
const temp = document.createElement('p');
|
|
12
|
+
temp.innerHTML = s;
|
|
13
|
+
return temp.textContent || temp.innerText || s;
|
|
14
|
+
};
|
|
15
|
+
const cleanContent = (content) => {
|
|
16
|
+
if (!content)
|
|
17
|
+
return '';
|
|
18
|
+
return decodeString(content).replace(/\[…\]/g, '').trim();
|
|
19
|
+
};
|
|
20
|
+
const description = cleanContent(post.excerpt);
|
|
21
|
+
const articleBody = includeFullContent ? cleanContent(post.content || '') : undefined;
|
|
22
|
+
const schema = {
|
|
23
|
+
'@context': 'https://schema.org',
|
|
24
|
+
'@type': 'BlogPosting',
|
|
25
|
+
headline: decodeString(post.title),
|
|
26
|
+
description: description || decodeString(post.title),
|
|
27
|
+
datePublished: post.date,
|
|
28
|
+
image: post.featured_image || post.post_thumbnail?.URL,
|
|
29
|
+
articleSection: Array.isArray(post.categories) && post.categories.length > 0
|
|
30
|
+
? post.categories[0]
|
|
31
|
+
: 'Blog',
|
|
32
|
+
keywords: Array.isArray(post.categories) ? post.categories : [],
|
|
33
|
+
};
|
|
34
|
+
if (articleBody) {
|
|
35
|
+
schema.articleBody = articleBody;
|
|
36
|
+
}
|
|
37
|
+
if (post.modified) {
|
|
38
|
+
schema.dateModified = post.modified;
|
|
39
|
+
}
|
|
40
|
+
if (post.author) {
|
|
41
|
+
schema.author = {
|
|
42
|
+
'@type': 'Person',
|
|
43
|
+
name: post.author,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return schema;
|
|
47
|
+
}
|
|
48
|
+
export function SchemaBlogPosting({ post }) {
|
|
49
|
+
return (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: {
|
|
50
|
+
__html: JSON.stringify(post),
|
|
51
|
+
} }));
|
|
52
|
+
}
|
|
53
|
+
export default SchemaBlogPosting;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
export function LocalBusinessSchema({ name, streetAddress, addressLocality, addressRegion, postalCode, addressCountry = 'United States', telephone, url, logo, image, openingHours, description, email, priceRange, sameAs }) {
|
|
3
|
+
const schemaData = {
|
|
4
|
+
'@context': 'https://schema.org',
|
|
5
|
+
'@type': 'LocalBusiness',
|
|
6
|
+
name,
|
|
7
|
+
address: {
|
|
8
|
+
'@type': 'PostalAddress',
|
|
9
|
+
streetAddress,
|
|
10
|
+
addressLocality,
|
|
11
|
+
addressRegion,
|
|
12
|
+
postalCode,
|
|
13
|
+
addressCountry
|
|
14
|
+
},
|
|
15
|
+
telephone,
|
|
16
|
+
url,
|
|
17
|
+
...(logo && { logo }),
|
|
18
|
+
...(image && { image }),
|
|
19
|
+
...(openingHours && { openingHours }),
|
|
20
|
+
...(description && { description }),
|
|
21
|
+
...(email && { email }),
|
|
22
|
+
...(priceRange && { priceRange }),
|
|
23
|
+
...(sameAs && sameAs.length > 0 && { sameAs })
|
|
24
|
+
};
|
|
25
|
+
return (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(schemaData) } }));
|
|
26
|
+
}
|
|
27
|
+
export default LocalBusinessSchema;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
export function ServicesSchema({ provider, services }) {
|
|
3
|
+
const serviceObjects = services.map(service => ({
|
|
4
|
+
'@type': 'Service',
|
|
5
|
+
name: service.name,
|
|
6
|
+
description: service.description,
|
|
7
|
+
...(service.url && { url: service.url }),
|
|
8
|
+
...(service.image && { image: service.image }),
|
|
9
|
+
...(service.areaServed && { areaServed: service.areaServed }),
|
|
10
|
+
provider: {
|
|
11
|
+
'@type': 'LocalBusiness',
|
|
12
|
+
name: provider.name,
|
|
13
|
+
url: provider.url,
|
|
14
|
+
...(provider.logo && { logo: provider.logo }),
|
|
15
|
+
...(provider.telephone && { telephone: provider.telephone }),
|
|
16
|
+
...(provider.email && { email: provider.email })
|
|
17
|
+
}
|
|
18
|
+
}));
|
|
19
|
+
return (_jsx(_Fragment, { children: serviceObjects.map((service, idx) => (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify({
|
|
20
|
+
'@context': 'https://schema.org',
|
|
21
|
+
...service
|
|
22
|
+
}) } }, idx))) }));
|
|
23
|
+
}
|
|
24
|
+
export default ServicesSchema;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
export function WebsiteSchema({ name, url, description, potentialAction }) {
|
|
3
|
+
const schemaData = {
|
|
4
|
+
'@context': 'https://schema.org',
|
|
5
|
+
'@type': 'WebSite',
|
|
6
|
+
name,
|
|
7
|
+
url,
|
|
8
|
+
...(description && { description }),
|
|
9
|
+
...(potentialAction && { potentialAction })
|
|
10
|
+
};
|
|
11
|
+
return (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(schemaData) } }));
|
|
12
|
+
}
|
|
13
|
+
export default WebsiteSchema;
|
|
@@ -104,6 +104,14 @@
|
|
|
104
104
|
height: auto;
|
|
105
105
|
margin: 5px auto;
|
|
106
106
|
}
|
|
107
|
+
|
|
108
|
+
.pixCartButton:hover,
|
|
109
|
+
.pixCart .pixCartButton:hover,
|
|
110
|
+
#pixCartButton.pixCartButton:hover {
|
|
111
|
+
color: #369;
|
|
112
|
+
text-decoration: underline;
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
#pixCartButton.pixCartButton {
|
|
108
116
|
width: 80px;
|
|
109
117
|
min-width: 80px;
|
|
@@ -115,6 +123,8 @@
|
|
|
115
123
|
|
|
116
124
|
|
|
117
125
|
|
|
126
|
+
|
|
127
|
+
|
|
118
128
|
input[readonly] {
|
|
119
129
|
border: none;
|
|
120
130
|
background-color: transparent;
|
|
@@ -4,6 +4,55 @@ import PropTypes from 'prop-types';
|
|
|
4
4
|
import { SmartImage } from '../cms/cloudinary.image';
|
|
5
5
|
import { usePixelatedConfig } from '../config/config.client';
|
|
6
6
|
import './recipe.css';
|
|
7
|
+
export function mapSchemaRecipeToDisplay(schemaRecipe) {
|
|
8
|
+
// Parse ISO 8601 duration and convert to readable format
|
|
9
|
+
function parseDuration(iso8601) {
|
|
10
|
+
if (!iso8601 || iso8601 === 'PT0M')
|
|
11
|
+
return '';
|
|
12
|
+
const regex = /PT(?:(\d+)H)?(?:(\d+)M)?/;
|
|
13
|
+
const match = iso8601.match(regex);
|
|
14
|
+
if (!match)
|
|
15
|
+
return iso8601;
|
|
16
|
+
const hours = match[1] ? parseInt(match[1]) : 0;
|
|
17
|
+
const minutes = match[2] ? parseInt(match[2]) : 0;
|
|
18
|
+
if (hours > 0 && minutes > 0) {
|
|
19
|
+
return `${hours} hour${hours > 1 ? 's' : ''} ${minutes} minutes`;
|
|
20
|
+
}
|
|
21
|
+
else if (hours > 0) {
|
|
22
|
+
return `${hours} hour${hours > 1 ? 's' : ''}`;
|
|
23
|
+
}
|
|
24
|
+
else if (minutes > 0) {
|
|
25
|
+
return `${minutes} minutes`;
|
|
26
|
+
}
|
|
27
|
+
return '';
|
|
28
|
+
}
|
|
29
|
+
// Extract author name
|
|
30
|
+
const authorName = typeof schemaRecipe.author === 'string'
|
|
31
|
+
? schemaRecipe.author
|
|
32
|
+
: schemaRecipe.author?.name || '';
|
|
33
|
+
// Convert instructions from HowToStep format to plain text
|
|
34
|
+
const instructions = Array.isArray(schemaRecipe.recipeInstructions)
|
|
35
|
+
? schemaRecipe.recipeInstructions.map((instruction) => typeof instruction === 'string' ? instruction : instruction.text || '')
|
|
36
|
+
: [];
|
|
37
|
+
// Combine cook and prep times for display
|
|
38
|
+
let displayDuration = '';
|
|
39
|
+
if (schemaRecipe.totalTime) {
|
|
40
|
+
displayDuration = parseDuration(schemaRecipe.totalTime);
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
name: schemaRecipe.name || '',
|
|
44
|
+
photo: schemaRecipe.image || '',
|
|
45
|
+
summary: schemaRecipe.description || '',
|
|
46
|
+
author: authorName,
|
|
47
|
+
published: schemaRecipe.datePublished || '',
|
|
48
|
+
duration: displayDuration,
|
|
49
|
+
yield: schemaRecipe.recipeYield || '',
|
|
50
|
+
ingredients: schemaRecipe.recipeIngredient || [],
|
|
51
|
+
instructions,
|
|
52
|
+
category: schemaRecipe.recipeCategory ? [schemaRecipe.recipeCategory] : [],
|
|
53
|
+
license: schemaRecipe.license || ''
|
|
54
|
+
};
|
|
55
|
+
}
|
|
7
56
|
/* ========== RECIPE BOOK ========== */
|
|
8
57
|
RecipeBook.propTypes = {
|
|
9
58
|
recipeData: PropTypes.shape({
|
|
@@ -25,9 +74,10 @@ export function RecipeBook(props) {
|
|
|
25
74
|
myElems[category] = [];
|
|
26
75
|
for (const recipeKey in recipeBookItems) {
|
|
27
76
|
const recipe = recipeBookItems[recipeKey];
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
77
|
+
const outputRecipe = mapSchemaRecipeToDisplay(recipe);
|
|
78
|
+
const recipeCat = outputRecipe.category;
|
|
79
|
+
if (recipeCat.includes(category)) {
|
|
80
|
+
myElems[category].push(outputRecipe);
|
|
31
81
|
}
|
|
32
82
|
}
|
|
33
83
|
}
|
|
@@ -41,7 +91,7 @@ export function RecipeBook(props) {
|
|
|
41
91
|
myElems.push(_jsx(RecipeCategory, { id: cID, className: 'h-recipe-category', category: category, showOnly: showOnlyCat }, cID));
|
|
42
92
|
for (const recipeKey in recipeElems[category]) {
|
|
43
93
|
const recipe = recipeElems[category][recipeKey];
|
|
44
|
-
const cats = recipe.
|
|
94
|
+
const cats = recipe.category;
|
|
45
95
|
const rID = cID + '-r' + (parseInt(recipeKey, 10) + 1);
|
|
46
96
|
if (cats.includes(category)) {
|
|
47
97
|
myElems.push(_jsx(RecipeBookItem, { id: rID, recipeData: recipe, showOnly: showOnlyRecipe }, rID));
|
|
@@ -92,8 +142,7 @@ RecipeBookItem.propTypes = {
|
|
|
92
142
|
};
|
|
93
143
|
export function RecipeBookItem(props) {
|
|
94
144
|
const config = usePixelatedConfig();
|
|
95
|
-
const
|
|
96
|
-
const recipe = recipeData.properties;
|
|
145
|
+
const recipe = props.recipeData;
|
|
97
146
|
const ingredients = recipe.ingredients.map((ingredient, iKey) => _jsx("li", { className: "p-ingredient", children: ingredient }, iKey));
|
|
98
147
|
const instructions = recipe.instructions.map((instruction, iKey) => _jsx("li", { className: "p-instruction", children: instruction }, iKey));
|
|
99
148
|
/* ? <img className='u-photo' src={recipe.photo} title={recipe.name} alt={recipe.name} /> */
|
|
@@ -124,9 +173,10 @@ export function RecipePickList(props) {
|
|
|
124
173
|
const recipeDataItems = recipeData.items;
|
|
125
174
|
for (const recipeKey in recipeDataItems) {
|
|
126
175
|
const recipe = recipeDataItems[recipeKey];
|
|
127
|
-
const
|
|
176
|
+
const outputRecipe = mapSchemaRecipeToDisplay(recipe);
|
|
177
|
+
const cats = outputRecipe.category;
|
|
128
178
|
if (cats.includes(category)) {
|
|
129
|
-
myOpts.push(_jsx("option", { value: cID + '-r' + rID, children:
|
|
179
|
+
myOpts.push(_jsx("option", { value: cID + '-r' + rID, children: outputRecipe.name }, cID + '-r' + rID));
|
|
130
180
|
rID += 1;
|
|
131
181
|
}
|
|
132
182
|
}
|