@getvitops/emdash 0.3.10 → 1.0.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/CHANGELOG.md +328 -0
- package/README.md +66 -20
- package/dist/index.d.mts +81 -20
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +548 -25
- package/dist/index.mjs.map +1 -1
- package/package.json +34 -10
- package/src/admin/DesignSystemEditorPage.tsx +51 -0
- package/src/admin/core-editor.d.ts +7 -0
- package/src/admin/index.tsx +12 -0
- package/src/astro/blocks/Accordion.astro +39 -0
- package/src/astro/blocks/ActionLink.astro +4 -2
- package/src/astro/blocks/Cards.astro +52 -0
- package/src/astro/blocks/Carousel.astro +25 -8
- package/src/astro/blocks/Gallery.astro +40 -0
- package/src/astro/blocks/Hero.astro +61 -0
- package/src/astro/blocks/ImageCompare.astro +5 -2
- package/src/astro/blocks/Marquee.astro +38 -0
- package/src/astro/blocks/value.test.ts +21 -0
- package/src/astro/blocks/value.ts +26 -1
- package/src/astro/index.ts +10 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { resolveBlockValue } from './value.ts';
|
|
2
|
+
import { resolveBlockValue, safeHref } from './value.ts';
|
|
3
3
|
|
|
4
4
|
interface Value {
|
|
5
5
|
before?: string;
|
|
@@ -11,7 +11,10 @@ interface Value {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
const value = resolveBlockValue<Value>(Astro.props);
|
|
14
|
-
const {
|
|
14
|
+
const { beforeAlt = '', beforeLabel = 'Before', afterAlt = '', afterLabel = 'After' } = value;
|
|
15
|
+
// A `javascript:`/`data:` src is dropped, so the block renders nothing rather than that image.
|
|
16
|
+
const before = safeHref(value.before);
|
|
17
|
+
const after = safeHref(value.after);
|
|
15
18
|
---
|
|
16
19
|
|
|
17
20
|
{before && after && (
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Thin adapter over `@getvitops/astro`'s `<Marquee />`, which emits `<wc-marquee>` itself
|
|
4
|
+
* and duplicates the slot (the copies are `aria-hidden`) for the seamless loop. Without
|
|
5
|
+
* JS the row still scrolls from CSS alone; `prefers-reduced-motion` is handled there.
|
|
6
|
+
*/
|
|
7
|
+
import Marquee from '@getvitops/astro/components/Marquee.astro';
|
|
8
|
+
import { resolveBlockValue, rows, safeHref, text } from './value.ts';
|
|
9
|
+
|
|
10
|
+
interface Value {
|
|
11
|
+
items?: unknown;
|
|
12
|
+
pauseOnHover?: boolean;
|
|
13
|
+
reverse?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const value = resolveBlockValue<Value>(Astro.props);
|
|
17
|
+
const items = rows<Record<string, unknown>>(value.items)
|
|
18
|
+
.map((row) => ({
|
|
19
|
+
text: text(row.text),
|
|
20
|
+
image: safeHref(row.image),
|
|
21
|
+
alt: text(row.alt),
|
|
22
|
+
}))
|
|
23
|
+
.filter((item) => item.text || item.image);
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
{
|
|
27
|
+
items.length > 0 && (
|
|
28
|
+
<Marquee pauseOnHover={Boolean(value.pauseOnHover)} reverse={Boolean(value.reverse)}>
|
|
29
|
+
{items.map((item) =>
|
|
30
|
+
item.image ? (
|
|
31
|
+
<img src={item.image} alt={item.alt} loading="lazy" />
|
|
32
|
+
) : (
|
|
33
|
+
<span>{item.text}</span>
|
|
34
|
+
),
|
|
35
|
+
)}
|
|
36
|
+
</Marquee>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vite-plus/test';
|
|
2
|
+
import { paragraphs, rows, text } from './value.ts';
|
|
3
|
+
|
|
4
|
+
describe('repeater helpers', () => {
|
|
5
|
+
it('rows keeps object rows and drops everything else', () => {
|
|
6
|
+
expect(rows([{ a: 1 }, null, 'x', [1], { b: 2 }])).toEqual([{ a: 1 }, { b: 2 }]);
|
|
7
|
+
expect(rows(undefined)).toEqual([]);
|
|
8
|
+
expect(rows('not an array')).toEqual([]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('text trims strings and treats other values as empty', () => {
|
|
12
|
+
expect(text(' hi ')).toBe('hi');
|
|
13
|
+
expect(text(5)).toBe('');
|
|
14
|
+
expect(text(undefined)).toBe('');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('paragraphs splits on blank lines only', () => {
|
|
18
|
+
expect(paragraphs('one\ntwo\n\n three \n\n\n')).toEqual(['one\ntwo', 'three']);
|
|
19
|
+
expect(paragraphs('')).toEqual([]);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Normalize the props EmDash passes to a Portable Text block component.
|
|
3
3
|
*
|
|
4
|
-
* Verified against emdash
|
|
4
|
+
* Verified against emdash 1.4: <PortableText> still delegates to astro-portabletext,
|
|
5
5
|
* which passes the block object as `props.node` (fields flat on the block next
|
|
6
6
|
* to _type/_key). The `value` and spread branches are kept as tolerance for the
|
|
7
7
|
* shapes shown elsewhere in the EmDash docs, so a host-side change is a
|
|
@@ -14,6 +14,9 @@ export function resolveBlockValue<T extends Record<string, unknown>>(
|
|
|
14
14
|
return candidate as T;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// Shared with the Astro components and `nodesToHtml`; re-exported so blocks import one place.
|
|
18
|
+
export { safeHref } from '@getvitops/utils/url';
|
|
19
|
+
|
|
17
20
|
/** Split a newline/comma-separated editor field into trimmed non-empty items. */
|
|
18
21
|
export function splitLines(input: unknown): string[] {
|
|
19
22
|
if (typeof input !== 'string') return [];
|
|
@@ -22,3 +25,25 @@ export function splitLines(input: unknown): string[] {
|
|
|
22
25
|
.map((s) => s.trim())
|
|
23
26
|
.filter(Boolean);
|
|
24
27
|
}
|
|
28
|
+
|
|
29
|
+
/** A repeater field's saved value: an array of row objects. Anything else is no rows. */
|
|
30
|
+
export function rows<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
31
|
+
input: unknown,
|
|
32
|
+
): T[] {
|
|
33
|
+
return Array.isArray(input)
|
|
34
|
+
? (input.filter((row) => row && typeof row === 'object' && !Array.isArray(row)) as T[])
|
|
35
|
+
: [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A trimmed string field, or `''` when the value is absent or not a string. */
|
|
39
|
+
export function text(input: unknown): string {
|
|
40
|
+
return typeof input === 'string' ? input.trim() : '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Split a multiline editor field into paragraphs on blank lines. */
|
|
44
|
+
export function paragraphs(input: unknown): string[] {
|
|
45
|
+
return text(input)
|
|
46
|
+
.split(/\n\s*\n/)
|
|
47
|
+
.map((p) => p.trim())
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
}
|
package/src/astro/index.ts
CHANGED
|
@@ -3,12 +3,17 @@
|
|
|
3
3
|
* <PortableText> automatically — the export name MUST be `blockComponents`
|
|
4
4
|
* and keys MUST match the block `type`s declared in ../blocks.ts.
|
|
5
5
|
*/
|
|
6
|
+
import Accordion from './blocks/Accordion.astro';
|
|
6
7
|
import ActionLink from './blocks/ActionLink.astro';
|
|
7
8
|
import Banner from './blocks/Banner.astro';
|
|
9
|
+
import Cards from './blocks/Cards.astro';
|
|
8
10
|
import Carousel from './blocks/Carousel.astro';
|
|
9
11
|
import CopyButton from './blocks/CopyButton.astro';
|
|
10
12
|
import Details from './blocks/Details.astro';
|
|
13
|
+
import Gallery from './blocks/Gallery.astro';
|
|
14
|
+
import Hero from './blocks/Hero.astro';
|
|
11
15
|
import ImageCompare from './blocks/ImageCompare.astro';
|
|
16
|
+
import Marquee from './blocks/Marquee.astro';
|
|
12
17
|
|
|
13
18
|
export const blockComponents = {
|
|
14
19
|
'vitops.actionLink': ActionLink,
|
|
@@ -17,4 +22,9 @@ export const blockComponents = {
|
|
|
17
22
|
'vitops.banner': Banner,
|
|
18
23
|
'vitops.details': Details,
|
|
19
24
|
'vitops.carousel': Carousel,
|
|
25
|
+
'vitops.gallery': Gallery,
|
|
26
|
+
'vitops.cards': Cards,
|
|
27
|
+
'vitops.accordion': Accordion,
|
|
28
|
+
'vitops.hero': Hero,
|
|
29
|
+
'vitops.marquee': Marquee,
|
|
20
30
|
};
|