soames-astro-theme 0.1.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/LICENSE +29 -0
- package/README.md +67 -0
- package/package.json +68 -0
- package/src/components/Bio.tsx +36 -0
- package/src/components/BlogSidebar.tsx +48 -0
- package/src/components/DocsSidebar.tsx +45 -0
- package/src/components/Footer.tsx +59 -0
- package/src/components/FooterMenu.tsx +36 -0
- package/src/components/Header.tsx +57 -0
- package/src/components/HeaderMenu.tsx +77 -0
- package/src/components/HeroHeader.tsx +66 -0
- package/src/components/Logo.tsx +37 -0
- package/src/components/shortcodes/RemoveContentAreaPadding.tsx +13 -0
- package/src/components/shortcodes/Shortcodes.tsx +319 -0
- package/src/components/shortcodes/SoamesFeature.tsx +54 -0
- package/src/components/shortcodes/SoamesGalleryMenu.tsx +90 -0
- package/src/components/shortcodes/SoamesIconList.tsx +90 -0
- package/src/components/shortcodes/SoamesSoundCloud.tsx +71 -0
- package/src/components/shortcodes/SoamesTextBlock.tsx +27 -0
- package/src/components/shortcodes/SoamesTextList.tsx +27 -0
- package/src/components/shortcodes/SoamesTitle.tsx +24 -0
- package/src/components/shortcodes/SoamesTitleBar.tsx +22 -0
- package/src/components/shortcodes/SoamesTitleBarLg.tsx +56 -0
- package/src/components/shortcodes/SoamesVideo.tsx +35 -0
- package/src/components/shortcodes/getAttributes.ts +19 -0
- package/src/components/shortcodes/getContent.ts +4 -0
- package/src/integration.ts +147 -0
- package/src/layouts/Base.astro +93 -0
- package/src/lib/wp.ts +351 -0
- package/src/routes/[...uri].astro +41 -0
- package/src/routes/blog/[...page].astro +88 -0
- package/src/routes/blog/post/[...slug].astro +79 -0
- package/src/routes/docs/[...slug].astro +58 -0
- package/src/routes/docs/index.astro +66 -0
- package/src/routes/index.astro +39 -0
- package/src/scripts/soames-nav.js +72 -0
- package/src/styles/site-overrides.css +4 -0
- package/src/styles/soames/base.css +593 -0
- package/src/styles/soames/components.css +1556 -0
- package/src/styles/soames/layout.css +209 -0
- package/src/styles/soames/overrides.css +2138 -0
- package/src/styles/soames/typography.css +23 -0
- package/src/styles/theme.css +8 -0
- package/src/styles/vendor/normalize.css +343 -0
- package/src/styles/vendor/wordpress-blocks.css +3451 -0
- package/src/theme-shadow.ts +39 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import React, { ReactNode } from "react";
|
|
2
|
+
import parse, { domToReact, DOMNode, HTMLReactParserOptions, Element as DomElement } from "html-react-parser";
|
|
3
|
+
|
|
4
|
+
import { getAttributes } from "./getAttributes";
|
|
5
|
+
import { getContent } from "./getContent";
|
|
6
|
+
|
|
7
|
+
import RemoveContentAreaPadding from "./RemoveContentAreaPadding";
|
|
8
|
+
import SoamesTitle from "./SoamesTitle";
|
|
9
|
+
import SoamesTitleBar from "./SoamesTitleBar";
|
|
10
|
+
import SoamesTitleBarLg from "./SoamesTitleBarLg";
|
|
11
|
+
import SoamesTextBlock from "./SoamesTextBlock";
|
|
12
|
+
import SoamesIconList from "./SoamesIconList";
|
|
13
|
+
import SoamesFeature from "./SoamesFeature";
|
|
14
|
+
import SoamesGalleryMenu from "./SoamesGalleryMenu";
|
|
15
|
+
import SoamesVideo from "./SoamesVideo";
|
|
16
|
+
import SoamesTextList from "./SoamesTextList";
|
|
17
|
+
import SoamesSoundCloud from "./SoamesSoundCloud";
|
|
18
|
+
|
|
19
|
+
interface ShortcodesProps {
|
|
20
|
+
// Astro passes slotted children as rendered nodes, not a raw string, so the
|
|
21
|
+
// WP HTML comes in as an explicit `html` prop (only change for Astro).
|
|
22
|
+
html: string;
|
|
23
|
+
// Prose mode (docs/blog bodies): render Gutenberg headings/paragraphs as plain,
|
|
24
|
+
// left-aligned HTML instead of the centered, padded marketing transform.
|
|
25
|
+
prose?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Set per-render in <Shortcodes>. Safe as a module-level flag because parse()
|
|
29
|
+
// runs synchronously (no async boundary, single-threaded SSR/render), so one
|
|
30
|
+
// parse completes before another begins.
|
|
31
|
+
let proseMode = false;
|
|
32
|
+
|
|
33
|
+
type Attributes = {
|
|
34
|
+
[key: string]: string[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleShortcodes: HTMLReactParserOptions["replace"] = (node) => {
|
|
38
|
+
if (node.type === "tag") {
|
|
39
|
+
const classes = ((node as DomElement).attribs?.class ?? "").split(" ");
|
|
40
|
+
const children = ((node as DomElement).children || []) as DOMNode[];
|
|
41
|
+
const opts = { replace: handleShortcodes };
|
|
42
|
+
|
|
43
|
+
// --- Gutenberg built-in block mappings ---
|
|
44
|
+
|
|
45
|
+
if (classes.includes("wp-block-heading")) {
|
|
46
|
+
if (proseMode) return undefined; // keep the plain <h2>/<h3>, left-aligned
|
|
47
|
+
return <SoamesTitle title={domToReact(children, opts)} />;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Gutenberg paragraph blocks render as plain <p> tags (no wp-block-paragraph class in server HTML).
|
|
51
|
+
// Skip paragraphs whose first child text starts with "[" — those are classic editor shortcodes.
|
|
52
|
+
if ((node as DomElement).name === "p") {
|
|
53
|
+
const firstChild = children[0] as any;
|
|
54
|
+
const isShortcode = firstChild?.type === "text" && firstChild?.data?.trim().startsWith("[");
|
|
55
|
+
if (!isShortcode) {
|
|
56
|
+
if (proseMode) return undefined; // keep the plain <p>, left-aligned
|
|
57
|
+
return (
|
|
58
|
+
<section className="soames-section article soames-article">
|
|
59
|
+
<div className="container col-md-10">
|
|
60
|
+
<div className="inner-container" style={{ width: "100%" }}>
|
|
61
|
+
<div className="section-text align-center mbr-fonts-style display-7 pb-2">
|
|
62
|
+
<p className="block-text mbr-fonts-style display-7">
|
|
63
|
+
{domToReact(children, opts)}
|
|
64
|
+
</p>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</section>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// --- Custom Soames block mappings ---
|
|
74
|
+
|
|
75
|
+
if (classes.includes("wp-block-soames-title-bar")) {
|
|
76
|
+
return <SoamesTitleBar title={domToReact(children, opts)} />;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (classes.includes("wp-block-soames-title-bar-lg")) {
|
|
80
|
+
const attrs = (node as DomElement).attribs;
|
|
81
|
+
return (
|
|
82
|
+
<SoamesTitleBarLg
|
|
83
|
+
title={attrs["data-title"] ?? ""}
|
|
84
|
+
attributes={{
|
|
85
|
+
subtitle: [attrs["data-subtitle"] ?? ""],
|
|
86
|
+
background: [attrs["data-background"] ?? ""],
|
|
87
|
+
}}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (classes.includes("wp-block-soames-icon-list")) {
|
|
93
|
+
const attrs = (node as DomElement).attribs;
|
|
94
|
+
// ORBI-20: new blocks emit JSON `data-items`; old blocks/shortcodes use
|
|
95
|
+
// positional comma attrs. Prefer items, fall back to the legacy arrays.
|
|
96
|
+
if (attrs["data-items"]) {
|
|
97
|
+
try {
|
|
98
|
+
const items = JSON.parse(attrs["data-items"]);
|
|
99
|
+
if (Array.isArray(items)) return <SoamesIconList items={items} />;
|
|
100
|
+
} catch {
|
|
101
|
+
/* malformed JSON — fall through to legacy parsing below */
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const csv = (key: string) => (attrs[key] ?? "").split(",");
|
|
105
|
+
return (
|
|
106
|
+
<SoamesIconList
|
|
107
|
+
attributes={{
|
|
108
|
+
images: csv("data-images"),
|
|
109
|
+
labels: csv("data-labels"),
|
|
110
|
+
links: csv("data-links"),
|
|
111
|
+
css: csv("data-css"),
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (classes.includes("wp-block-soames-feature")) {
|
|
118
|
+
const attrs = (node as DomElement).attribs;
|
|
119
|
+
const image = attrs["data-image"];
|
|
120
|
+
const title = attrs["data-title"];
|
|
121
|
+
const css = attrs["data-css"];
|
|
122
|
+
return (
|
|
123
|
+
<section className="soames-features">
|
|
124
|
+
<div className="container">
|
|
125
|
+
<div className="col-md-12">
|
|
126
|
+
<div className="media-container-row">
|
|
127
|
+
<div className="align-left aside-content">
|
|
128
|
+
{title && (
|
|
129
|
+
<h2 className="mbr-title pt-2 mbr-fonts-style display-2">
|
|
130
|
+
<div>{title}</div>
|
|
131
|
+
</h2>
|
|
132
|
+
)}
|
|
133
|
+
<div className="block-content">
|
|
134
|
+
<div className={`card ${css ?? ""}`}>
|
|
135
|
+
<div className="media"><div className="media-body"></div></div>
|
|
136
|
+
<div className="card-box">
|
|
137
|
+
<p className="block-text mbr-fonts-style display-7">
|
|
138
|
+
{domToReact(children, opts)}
|
|
139
|
+
</p>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
{image && (
|
|
145
|
+
<div className="soames-figure" style={{ width: "50%" }}>
|
|
146
|
+
<img src={image} alt={title ?? "Feature"} title={title ?? "Feature"} />
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</section>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (classes.includes("wp-block-soames-gallery-menu")) {
|
|
157
|
+
const attrs = (node as DomElement).attribs;
|
|
158
|
+
// ORBI-20: prefer JSON `data-items`, fall back to legacy comma attrs.
|
|
159
|
+
if (attrs["data-items"]) {
|
|
160
|
+
try {
|
|
161
|
+
const items = JSON.parse(attrs["data-items"]);
|
|
162
|
+
if (Array.isArray(items)) return <SoamesGalleryMenu items={items} />;
|
|
163
|
+
} catch {
|
|
164
|
+
/* malformed JSON — fall through to legacy parsing below */
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const csv = (key: string) => (attrs[key] ?? "").split(",");
|
|
168
|
+
return (
|
|
169
|
+
<SoamesGalleryMenu
|
|
170
|
+
attributes={{
|
|
171
|
+
images: csv("data-images"),
|
|
172
|
+
labels: csv("data-labels"),
|
|
173
|
+
links: csv("data-links"),
|
|
174
|
+
css: csv("data-css"),
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (classes.includes("wp-block-soames-video")) {
|
|
181
|
+
const attrs = (node as DomElement).attribs;
|
|
182
|
+
return (
|
|
183
|
+
<SoamesVideo
|
|
184
|
+
attributes={{
|
|
185
|
+
link: attrs["data-link"] ?? "",
|
|
186
|
+
title: attrs["data-title"] ?? "",
|
|
187
|
+
}}
|
|
188
|
+
/>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (classes.includes("wp-block-soames-soundcloud")) {
|
|
193
|
+
const attrs = (node as DomElement).attribs;
|
|
194
|
+
return (
|
|
195
|
+
<SoamesSoundCloud
|
|
196
|
+
attributes={{
|
|
197
|
+
bandName: attrs["data-band-name"] ?? "",
|
|
198
|
+
siteLink: attrs["data-site-link"] ?? "",
|
|
199
|
+
playlistId: attrs["data-playlist-id"] ?? "",
|
|
200
|
+
albumLink: attrs["data-album-link"] ?? "",
|
|
201
|
+
albumName: attrs["data-album-name"] ?? "",
|
|
202
|
+
}}
|
|
203
|
+
/>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (classes.includes("wp-block-soames-text-list")) {
|
|
208
|
+
return (
|
|
209
|
+
<section className="soames-section article soames-list pb-0">
|
|
210
|
+
<div className="container">
|
|
211
|
+
<div className="media-container-row">
|
|
212
|
+
<div className="soames-text counter-container col-12 col-md-10 mbr-fonts-style pt-0 display-7">
|
|
213
|
+
{domToReact(children, opts)}
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</section>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// --- Legacy text shortcode detection (unchanged) ---
|
|
222
|
+
}
|
|
223
|
+
if (node.type === "tag" && node.children && node.children.length > 0) {
|
|
224
|
+
const child = node.children[0];
|
|
225
|
+
if (child.type === "text") {
|
|
226
|
+
const shortcode = child.data.trim();
|
|
227
|
+
|
|
228
|
+
if (shortcode === "[soames-remove-content-area-padding]") {
|
|
229
|
+
return <RemoveContentAreaPadding />;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const shortcodeMappings: {
|
|
233
|
+
regex: RegExp;
|
|
234
|
+
component: React.FC<any>;
|
|
235
|
+
propsExtractor: (match: RegExpMatchArray) => any;
|
|
236
|
+
}[] = [
|
|
237
|
+
{
|
|
238
|
+
regex: /\[soames-title([^\]]*)\]([\s\S]*?)\[\/soames-title\]/,
|
|
239
|
+
component: SoamesTitle,
|
|
240
|
+
propsExtractor: (match) => ({ title: getContent(match) }),
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
regex: /\[soames-title-bar([^\]]*)\]([\s\S]*?)\[\/soames-title-bar\]/,
|
|
244
|
+
component: SoamesTitleBar,
|
|
245
|
+
propsExtractor: (match) => ({ title: getContent(match) }),
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
regex: /\[soames-title-bar-lg([^\]]*)\]([\s\S]*?)\[\/soames-title-bar-lg\]/,
|
|
249
|
+
component: SoamesTitleBarLg,
|
|
250
|
+
propsExtractor: (match) => ({
|
|
251
|
+
title: getContent(match),
|
|
252
|
+
attributes: getAttributes(match),
|
|
253
|
+
}),
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
regex: /\[soames-text-block([^\]]*)\]([\s\S]*?)\[\/soames-text-block\]/,
|
|
257
|
+
component: SoamesTextBlock,
|
|
258
|
+
propsExtractor: (match) => ({ content: getContent(match) }),
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
regex: /\[soames-text-list([^\]]*)\]([\s\S]*?)\[\/soames-text-list\]/,
|
|
262
|
+
component: SoamesTextList,
|
|
263
|
+
propsExtractor: (match) => ({ content: getContent(match) }),
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
regex: /\[soames-icon-list([^\]]*)\]/,
|
|
267
|
+
component: SoamesIconList,
|
|
268
|
+
propsExtractor: (match) => ({ attributes: getAttributes(match) }),
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
regex: /\[soames-feature([^\]]*)\]([\s\S]*?)\[\/soames-feature\]/,
|
|
272
|
+
component: SoamesFeature,
|
|
273
|
+
propsExtractor: (match) => ({
|
|
274
|
+
content: getContent(match),
|
|
275
|
+
attributes: getAttributes(match),
|
|
276
|
+
}),
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
regex: /\[soames-gallery-menu([^\]]*)\]/,
|
|
280
|
+
component: SoamesGalleryMenu,
|
|
281
|
+
propsExtractor: (match) => ({ attributes: getAttributes(match) }),
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
regex: /\[soames-video([^\]]*)\]/,
|
|
285
|
+
component: SoamesVideo,
|
|
286
|
+
propsExtractor: (match) => ({ attributes: getAttributes(match) }),
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
regex: /\[soames-soundcloud([^\]]*)\]/,
|
|
290
|
+
component: SoamesSoundCloud,
|
|
291
|
+
propsExtractor: (match) => ({ attributes: getAttributes(match) }),
|
|
292
|
+
},
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
for (const { regex, component: Component, propsExtractor } of shortcodeMappings) {
|
|
296
|
+
const match = shortcode.match(regex);
|
|
297
|
+
if (match) {
|
|
298
|
+
const props = propsExtractor(match);
|
|
299
|
+
return <Component {...props} />;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return undefined;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const Shortcodes: React.FC<ShortcodesProps> = ({ html, prose = false }) => {
|
|
309
|
+
proseMode = !!prose;
|
|
310
|
+
const reactElements = parse(html || "", {
|
|
311
|
+
replace: handleShortcodes,
|
|
312
|
+
});
|
|
313
|
+
proseMode = false;
|
|
314
|
+
|
|
315
|
+
return <div>{reactElements}</div>;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export { Shortcodes };
|
|
319
|
+
export default Shortcodes;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
interface SoamesFeatureProps {
|
|
4
|
+
content: string;
|
|
5
|
+
attributes: {
|
|
6
|
+
image?: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
css?: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const SoamesFeature: React.FC<SoamesFeatureProps> = ({ content, attributes }) => {
|
|
13
|
+
const { image, title, css } = attributes;
|
|
14
|
+
const paragraphs = content.split('__SOAMES_P__');
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<section className="soames-features">
|
|
18
|
+
<div className="container">
|
|
19
|
+
<div className="col-md-12">
|
|
20
|
+
<div className="media-container-row">
|
|
21
|
+
<div className="align-left aside-content">
|
|
22
|
+
{title && (
|
|
23
|
+
<h2 className="mbr-title pt-2 mbr-fonts-style display-2">
|
|
24
|
+
<div>{title}</div>
|
|
25
|
+
</h2>
|
|
26
|
+
)}
|
|
27
|
+
<div className="block-content">
|
|
28
|
+
<div className={`card ${css ?? ""}`}>
|
|
29
|
+
<div className="media">
|
|
30
|
+
<div className="media-body"></div>
|
|
31
|
+
</div>
|
|
32
|
+
<div className="card-box">
|
|
33
|
+
{paragraphs.map((paragraph, key) => (
|
|
34
|
+
<p key={key} className="block-text mbr-fonts-style display-7">
|
|
35
|
+
{paragraph}
|
|
36
|
+
</p>
|
|
37
|
+
))}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
{image && (
|
|
43
|
+
<div className="soames-figure" style={{ width: "50%" }}>
|
|
44
|
+
<img src={image} alt={title ?? "Feature"} title={title ?? "Feature"} />
|
|
45
|
+
</div>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</section>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default SoamesFeature;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
// New (ORBI-20) grouped format: one object per item.
|
|
4
|
+
interface GalleryMenuItemInput {
|
|
5
|
+
image: string;
|
|
6
|
+
label?: string | null;
|
|
7
|
+
link?: string | null;
|
|
8
|
+
css?: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Legacy format: parallel, index-aligned comma arrays.
|
|
12
|
+
interface GalleryMenuAttributes {
|
|
13
|
+
images: string[];
|
|
14
|
+
links?: string[];
|
|
15
|
+
labels?: string[];
|
|
16
|
+
css?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface GalleryMenuItem {
|
|
20
|
+
id: string;
|
|
21
|
+
imageUrl: string;
|
|
22
|
+
label?: string | null;
|
|
23
|
+
link?: string | null;
|
|
24
|
+
css?: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface SoamesGalleryMenuProps {
|
|
28
|
+
items?: GalleryMenuItemInput[];
|
|
29
|
+
attributes?: GalleryMenuAttributes;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const SoamesGalleryMenu: React.FC<SoamesGalleryMenuProps> = ({ items, attributes }) => {
|
|
33
|
+
const normalizeUrl = (url: string) => (url ?? "").replace(/['""]+/g, '"');
|
|
34
|
+
|
|
35
|
+
let menuItems: GalleryMenuItem[];
|
|
36
|
+
if (items && items.length) {
|
|
37
|
+
menuItems = items.map((it, i) => ({
|
|
38
|
+
id: `icon_${i}`,
|
|
39
|
+
imageUrl: normalizeUrl(it.image),
|
|
40
|
+
label: it.label ?? null,
|
|
41
|
+
link: it.link ?? null,
|
|
42
|
+
css: it.css ?? null,
|
|
43
|
+
}));
|
|
44
|
+
} else if (attributes) {
|
|
45
|
+
const { images, links, labels, css } = attributes;
|
|
46
|
+
menuItems = (images ?? []).map((image, i) => ({
|
|
47
|
+
id: `icon_${i}`,
|
|
48
|
+
imageUrl: normalizeUrl(image),
|
|
49
|
+
label: labels?.[i] ?? null,
|
|
50
|
+
link: links?.[i] ?? null,
|
|
51
|
+
css: css?.[i] ?? null,
|
|
52
|
+
}));
|
|
53
|
+
} else {
|
|
54
|
+
menuItems = [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Drop rows with no image (e.g. a trailing comma in the legacy format).
|
|
58
|
+
menuItems = menuItems.filter((item) => item.imageUrl.trim().length > 0);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<section className="features1 soames-gallery-menu">
|
|
62
|
+
<div className="container-fluid">
|
|
63
|
+
<div className="media-container-row">
|
|
64
|
+
{menuItems.map(menuItem => (
|
|
65
|
+
<div key={menuItem.id} className="card p-3 col-md-12 col-lg-3">
|
|
66
|
+
<div className="card-wrapper">
|
|
67
|
+
<div className="card-img">
|
|
68
|
+
<a href={menuItem.link ?? "#"}>
|
|
69
|
+
<img
|
|
70
|
+
src={menuItem.imageUrl}
|
|
71
|
+
alt={menuItem.label ?? ""}
|
|
72
|
+
title={menuItem.label ?? ""}
|
|
73
|
+
/>
|
|
74
|
+
</a>
|
|
75
|
+
</div>
|
|
76
|
+
<div className="card-box">
|
|
77
|
+
<h4 className="card-title pb-3 mbr-fonts-style display-7">
|
|
78
|
+
{menuItem.label}
|
|
79
|
+
</h4>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</section>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default SoamesGalleryMenu;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
// New (ORBI-20) grouped format: one object per icon.
|
|
4
|
+
interface IconItem {
|
|
5
|
+
image: string;
|
|
6
|
+
label?: string | null;
|
|
7
|
+
link?: string | null;
|
|
8
|
+
css?: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Legacy format: parallel, index-aligned comma arrays.
|
|
12
|
+
interface IconListAttributes {
|
|
13
|
+
images: string[];
|
|
14
|
+
links?: string[];
|
|
15
|
+
labels?: string[];
|
|
16
|
+
css?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Icon {
|
|
20
|
+
id: string;
|
|
21
|
+
imageUrl: string;
|
|
22
|
+
label?: string | null;
|
|
23
|
+
link?: string | null;
|
|
24
|
+
css?: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface SoamesIconListProps {
|
|
28
|
+
items?: IconItem[];
|
|
29
|
+
attributes?: IconListAttributes;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const SoamesIconList: React.FC<SoamesIconListProps> = ({ items, attributes }) => {
|
|
33
|
+
const normalizeUrl = (url: string) => (url ?? "").replace(/['""]+/g, '"');
|
|
34
|
+
|
|
35
|
+
let icons: Icon[];
|
|
36
|
+
if (items && items.length) {
|
|
37
|
+
// New grouped format.
|
|
38
|
+
icons = items.map((it, i) => ({
|
|
39
|
+
id: `icon_${i}`,
|
|
40
|
+
imageUrl: normalizeUrl(it.image),
|
|
41
|
+
label: it.label ?? null,
|
|
42
|
+
link: it.link ?? null,
|
|
43
|
+
css: it.css ?? null,
|
|
44
|
+
}));
|
|
45
|
+
} else if (attributes) {
|
|
46
|
+
// Legacy positional arrays.
|
|
47
|
+
const { images, links, labels, css } = attributes;
|
|
48
|
+
icons = (images ?? []).map((image, i) => ({
|
|
49
|
+
id: `icon_${i}`,
|
|
50
|
+
imageUrl: normalizeUrl(image),
|
|
51
|
+
label: labels?.[i] ?? null,
|
|
52
|
+
link: links?.[i] ?? null,
|
|
53
|
+
css: css?.[i] ?? null,
|
|
54
|
+
}));
|
|
55
|
+
} else {
|
|
56
|
+
icons = [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Drop rows with no image (e.g. a trailing comma in the legacy format).
|
|
60
|
+
icons = icons.filter((icon) => icon.imageUrl.trim().length > 0);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<section className="soames-section">
|
|
64
|
+
<div className="container">
|
|
65
|
+
<div className="d-flex flex-wrap align-items-center justify-content-center">
|
|
66
|
+
{icons.map((icon) => (
|
|
67
|
+
<a
|
|
68
|
+
key={icon.id}
|
|
69
|
+
className={`d-flex flex-column align-items-center text-decoration-none p-2 ${icon.css ?? ''}`}
|
|
70
|
+
href={icon.link ?? "#"}
|
|
71
|
+
target="_blank"
|
|
72
|
+
rel="noreferrer"
|
|
73
|
+
>
|
|
74
|
+
<img
|
|
75
|
+
className="d-block mb-2"
|
|
76
|
+
src={icon.imageUrl}
|
|
77
|
+
alt={icon.label ?? ""}
|
|
78
|
+
height="116"
|
|
79
|
+
loading="lazy"
|
|
80
|
+
/>
|
|
81
|
+
<span className="text-muted">{icon.label}</span>
|
|
82
|
+
</a>
|
|
83
|
+
))}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</section>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default SoamesIconList;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
interface SoamesSoundCloudProps {
|
|
4
|
+
attributes: {
|
|
5
|
+
bandName: string;
|
|
6
|
+
siteLink: string;
|
|
7
|
+
playlistId: string;
|
|
8
|
+
albumLink: string;
|
|
9
|
+
albumName: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const SoamesSoundCloud: React.FC<SoamesSoundCloudProps> = ({ attributes }) => {
|
|
14
|
+
const { bandName, siteLink, playlistId, albumLink, albumName } = attributes;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<section className="soames-section article soames-list">
|
|
18
|
+
<div className="container">
|
|
19
|
+
<div className="media-container-row">
|
|
20
|
+
<div className="soames-text counter-container col-12 col-md-10 pt-2 mbr-fonts-style display-7">
|
|
21
|
+
<iframe
|
|
22
|
+
title={albumName}
|
|
23
|
+
width="100%"
|
|
24
|
+
height="300"
|
|
25
|
+
scrolling="no"
|
|
26
|
+
frameBorder="no"
|
|
27
|
+
allow="autoplay"
|
|
28
|
+
src={`https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/playlists/${playlistId}&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true&visual=true`}
|
|
29
|
+
></iframe>
|
|
30
|
+
<div
|
|
31
|
+
style={{
|
|
32
|
+
fontSize: '10px',
|
|
33
|
+
color: '#cccccc',
|
|
34
|
+
lineBreak: 'anywhere',
|
|
35
|
+
wordBreak: 'normal',
|
|
36
|
+
overflow: 'hidden',
|
|
37
|
+
whiteSpace: 'nowrap',
|
|
38
|
+
textOverflow: 'ellipsis',
|
|
39
|
+
fontFamily:
|
|
40
|
+
'Interstate,Lucida Grande,Lucida Sans Unicode,Lucida Sans,Garuda,Verdana,Tahoma,sans-serif',
|
|
41
|
+
fontWeight: 100,
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<a
|
|
45
|
+
href={siteLink}
|
|
46
|
+
title={bandName}
|
|
47
|
+
target="_blank"
|
|
48
|
+
style={{ color: '#cccccc', textDecoration: 'none' }}
|
|
49
|
+
rel="noreferrer"
|
|
50
|
+
>
|
|
51
|
+
{bandName}
|
|
52
|
+
</a>{' '}
|
|
53
|
+
·{' '}
|
|
54
|
+
<a
|
|
55
|
+
href={albumLink}
|
|
56
|
+
title={albumName}
|
|
57
|
+
target="_blank"
|
|
58
|
+
style={{ color: '#cccccc', textDecoration: 'none' }}
|
|
59
|
+
rel="noreferrer"
|
|
60
|
+
>
|
|
61
|
+
{albumName}
|
|
62
|
+
</a>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</section>
|
|
68
|
+
);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default SoamesSoundCloud;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
interface SoamesTextBlockProps {
|
|
4
|
+
content: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const SoamesTextBlock: React.FC<SoamesTextBlockProps> = ({ content }) => {
|
|
8
|
+
const paragraphs = content?.split('__SOAMES_P__') ?? [];
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<section className="soames-section article soames-article">
|
|
12
|
+
<div className="container col-md-10">
|
|
13
|
+
<div className="inner-container" style={{ width: "100%" }}>
|
|
14
|
+
<div className="section-text align-center mbr-fonts-style display-7 pb-2">
|
|
15
|
+
{paragraphs.map((paragraph, key) => (
|
|
16
|
+
<p key={key} className="block-text mbr-fonts-style display-7">
|
|
17
|
+
{paragraph}
|
|
18
|
+
</p>
|
|
19
|
+
))}
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</section>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default SoamesTextBlock;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
interface SoamesTextListProps {
|
|
4
|
+
content: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const SoamesTextList: React.FC<SoamesTextListProps> = ({ content }) => {
|
|
8
|
+
const lineItems = content?.split('__SOAMES_LI__') ?? [];
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<section className="soames-section article soames-list pb-0">
|
|
12
|
+
<div className="container">
|
|
13
|
+
<div className="media-container-row">
|
|
14
|
+
<div className="soames-text counter-container col-12 col-md-10 mbr-fonts-style pt-0 display-7">
|
|
15
|
+
<ul>
|
|
16
|
+
{lineItems.map((lineItem, key) => (
|
|
17
|
+
<li key={key}>{lineItem}</li>
|
|
18
|
+
))}
|
|
19
|
+
</ul>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</section>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default SoamesTextList;
|