@samva/markup 0.18.1 → 0.19.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/AGENTS.md +11 -4
- package/CHANGELOG.md +36 -0
- package/README.md +89 -5
- package/dist/assets-BSpgZOSR.js +61 -0
- package/dist/assets-k7_v3IOJ.d.ts +39 -0
- package/dist/{assets-CS5Xa_mr.d.ts → brand-Bxb6Sm7y.d.ts} +34 -41
- package/dist/brand.d.ts +2 -2
- package/dist/brand.js +3 -3
- package/dist/compatibility.d.ts +15 -5
- package/dist/compatibility.js +48 -5
- package/dist/compile-C1GWLQ5C.js +490 -0
- package/dist/compile-CIeuGmP-.d.ts +98 -0
- package/dist/{compiled-template-Bc4Dq9zy.d.ts → compiled-template-CPDHvFlt.d.ts} +157 -6
- package/dist/compiler.d.ts +4 -89
- package/dist/compiler.js +8 -486
- package/dist/components-DNBCz20_.js +3267 -0
- package/dist/{css-color-DLM12Osu.js → dark-scheme-BGDCCaUq.js} +12 -1
- package/dist/{diagnostic-codes-DZKb0Xcg.js → diagnostic-codes-BCwfUgnF.js} +33 -36
- package/dist/diagnostic-model-DJtqbETd.js +36 -0
- package/dist/{diagnostics-D4vSLQrO.d.ts → diagnostics-C3TSx6SU.d.ts} +22 -2
- package/dist/diagnostics.d.ts +33 -1
- package/dist/diagnostics.js +2 -1
- package/dist/edit.d.ts +15 -4
- package/dist/edit.js +67 -4
- package/dist/email/jsx-dev-runtime.d.ts +2 -2
- package/dist/email/jsx-runtime.d.ts +1 -1
- package/dist/email.d.ts +6 -2
- package/dist/email.js +6 -2
- package/dist/index.d.ts +1 -1
- package/dist/{jsx-runtime-DL8NVF1f.d.ts → jsx-runtime-HHfrVZEf.d.ts} +1 -2
- package/dist/markdown.d.ts +69 -0
- package/dist/markdown.js +95 -0
- package/dist/parse-Bex7QFqj.js +921 -0
- package/dist/{preview-rNoJBEIY.d.ts → preview-DRLWgmfg.d.ts} +3 -3
- package/dist/profile-DFk4gs7o.js +56 -0
- package/dist/{profile-JTvgCD6X.js → project-B6D64SZS.js} +497 -234
- package/dist/render.d.ts +8 -8
- package/dist/render.js +1 -2
- package/dist/{css-B_aVKrDo.js → selectors-CKGuc9MO.js} +158 -4
- package/dist/sms.d.ts +1 -1
- package/dist/{template-DmNcoH4Q.d.ts → template-4Dse7_OC.d.ts} +1 -1
- package/dist/{theme-css-P9F5S7Ic.js → theme-css-BujDuuEA.js} +1 -2
- package/dist/whatsapp.d.ts +1 -1
- package/generated/contract.json +41 -1
- package/generated/reference.md +97 -89
- package/package.json +15 -4
- package/dist/brand-CIiEqkU7.js +0 -193
- package/dist/brand-plugin-Dj9SMpfS.d.ts +0 -33
- package/dist/components-C5850-Qg.js +0 -409
- package/dist/dark-scheme-CbCwGDWH.js +0 -12
- package/dist/html-DJUK2YJF.js +0 -483
- package/dist/render-template-DwPDm3Hq.js +0 -1226
- package/dist/selectors-CYWCoxZr.js +0 -157
- package/dist/source-locations-C8wCsiAy.d.ts +0 -22
package/AGENTS.md
CHANGED
|
@@ -49,6 +49,13 @@ Tooling entries are what a host (Vite, the CLI, the editor, the API, the templat
|
|
|
49
49
|
compiled and rendered as a template that imports the brand. It imports no
|
|
50
50
|
Vite, esbuild, Node builtins or caniemail, so it runs in Node, Bun, Workers and the browser
|
|
51
51
|
(`tests/browser-entries.test.ts` loads it without `process`).
|
|
52
|
+
- `@samva/markup/markdown` — `parseIssue(source, file)` reads typed YAML frontmatter and static
|
|
53
|
+
Markdown/SML component tags into semantic nodes with exact source ranges; `compileIssue` inserts
|
|
54
|
+
them into an email-only TSX layout (default `layout.tsx`) at one `IssueContent` directive imported
|
|
55
|
+
from `@samva/markup/email`, before CSS is applied. The issue slug/title/preheader set its compiled
|
|
56
|
+
identity/subject/preview; the layout supplies schema and fixtures. `inspectMarkdownNode` in
|
|
57
|
+
`./edit` resolves its origins; `applySourceReplacements` takes `{ markdown: { file } }` to validate
|
|
58
|
+
Markdown edits. Expressions, raw HTML, unknown tags and invalid metadata are diagnostics.
|
|
52
59
|
- `@samva/markup/compatibility` — `checkCompatibility`, which checks each fixture against
|
|
53
60
|
the client matrix and locates findings at source; `checkHtmlCompatibility` for HTML a host
|
|
54
61
|
already has; `hasBlockingCompatibility` and the matrix. It is the only entry that loads
|
|
@@ -56,9 +63,9 @@ Tooling entries are what a host (Vite, the CLI, the editor, the API, the templat
|
|
|
56
63
|
- `@samva/markup/diagnostics` — source spans and line maps, the `DIAGNOSTIC_CODES` registry with
|
|
57
64
|
each code's fix and upgrade recipe, and `EmailDiagnostic`. It does not load the compiler, so a
|
|
58
65
|
host that only reports imports it.
|
|
59
|
-
- `@samva/markup/render` — `render(template, input, {
|
|
60
|
-
which returns the
|
|
61
|
-
`RenderedWhatsApp`) and throws only `RenderError`, including for a template compiled by another
|
|
66
|
+
- `@samva/markup/render` — `render(template, input, { target, locale?, timeZone?, brandPlugin? })`,
|
|
67
|
+
which returns the target's output (`RenderedEmail`, `RenderedPage` with body HTML and head hints,
|
|
68
|
+
`RenderedSms` with its segment counts, or `RenderedWhatsApp`) and throws only `RenderError`, including for a template compiled by another
|
|
62
69
|
`COMPILED_TEMPLATE_VERSION`; the `Compiled*` types; the email element table and `escapeText` /
|
|
63
70
|
`escapeAttribute`; `smsSegmentInfo`. Its import closure excludes the compiler's libraries
|
|
64
71
|
(`tests/entry-closure.test.ts`): it is the send-time security boundary and what the API bundles.
|
|
@@ -168,7 +175,7 @@ Read the contract before authoring. Import only subpaths declared in `package.js
|
|
|
168
175
|
|
|
169
176
|
- `tsc --noEmit` over the template project catches unsupported elements, attributes, and props.
|
|
170
177
|
- `compileTemplate` reports every static-profile, binding and fixture finding with a source
|
|
171
|
-
location, and `render(compiled.template, fixture, {
|
|
178
|
+
location, and `render(compiled.template, fixture, { target: "email" })` proves one fixture's
|
|
172
179
|
output without executing the template.
|
|
173
180
|
- `samva templates check` (from `@samva/cli`) compiles every entry and renders every fixture,
|
|
174
181
|
reporting findings with source locations.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
## @samva/markup@0.19.0
|
|
2
|
+
|
|
3
|
+
### Author newsletter issues in SML Markdown
|
|
4
|
+
|
|
5
|
+
Parse `issues/*.md` with typed YAML frontmatter and static SML component tags through
|
|
6
|
+
`@samva/markup/markdown`. Unknown components, raw HTML, expressions, and invalid metadata return
|
|
7
|
+
located diagnostics. Prose and component attributes retain exact editable source spans. Prose
|
|
8
|
+
edits preserve ordinary punctuation and protect only syntax-sensitive delimiters and whitespace.
|
|
9
|
+
|
|
10
|
+
Compile each issue into an email-only TSX layout containing one `<IssueContent />` from
|
|
11
|
+
`@samva/markup/email`, then render the shared document to email or web. Vite discovers issues
|
|
12
|
+
separately from template entries and includes them in project build, render, and export workflows.
|
|
13
|
+
Existing template email output and golden digests are unchanged.
|
|
14
|
+
|
|
15
|
+
### Render email templates as web pages
|
|
16
|
+
|
|
17
|
+
**Breaking:** replace `render(template, input, { channel })` with
|
|
18
|
+
`render(template, input, { target })`. Use `target: "email"`, `"sms"`, or `"whatsapp"` for message
|
|
19
|
+
delivery, and `target: "web"` for the semantic HTML body and head hints of an email template.
|
|
20
|
+
Recompile stored templates to compiled document version `sml: 2` before rendering them with this
|
|
21
|
+
version. Compilation digests change with this document format.
|
|
22
|
+
|
|
23
|
+
Email and web share the template's components, bindings, conditions, loops, and source positions.
|
|
24
|
+
Email preserves the existing rendered HTML, subject, preheader, and text byte for byte, including
|
|
25
|
+
Outlook fallbacks. The web target emits semantic HTML with styles supplied separately to the page
|
|
26
|
+
shell. `templateEmailDegradations` locates component fidelity declarations in source, and
|
|
27
|
+
`checkCompatibility` reports content losses alongside client HTML findings.
|
|
28
|
+
|
|
29
|
+
### An important declaration keeps `!important` when it is inlined
|
|
30
|
+
|
|
31
|
+
A stylesheet `!important` that wins the cascade is written onto the element's `style` with `!important`. The flag used to decide the winner and then disappear, so a later important rule could override the inlined value. An author's own important inline style is kept the same way.
|
|
32
|
+
|
|
33
|
+
A logical padding shorthand keeps that flag on every physical side. `padding-inline: 3px 4px !important` becomes `padding-left: 3px !important` and `padding-right: 4px !important`.
|
|
34
|
+
|
|
35
|
+
A property the generated dark-mode class restates is inlined without `!important`, including a declaration that only overlaps the color that changed. That class also uses `!important`, and an important inline style would keep the light color. A weaker rule kept in the head, such as a hover, is copied onto a more specific selector so it does not take that color; the copy inside the dark-mode query keeps the theme switch. A stronger rule is copied again after that guard. A shorthand in that copy also restates any locked longhand that still outranks it, so the shorthand does not reset a stronger inline color. The dark-mode copy resolves that shorthand and the longhand at their dark values. A pseudo-element keeps the color it sets for itself.
|
|
36
|
+
|
|
1
37
|
## @samva/markup@0.17.0
|
|
2
38
|
|
|
3
39
|
### One compiler for every host, one render, one error model
|
package/README.md
CHANGED
|
@@ -107,10 +107,10 @@ export default defineTemplate({
|
|
|
107
107
|
|
|
108
108
|
A template is read, not run. `@samva/markup/compiler` parses the file and the project files it
|
|
109
109
|
imports without evaluating them, checks every binding against the schema, resolves Tailwind and CSS
|
|
110
|
-
against the markup, and produces
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
at send time.
|
|
110
|
+
against the markup, and produces a semantic document: JSON, versioned (`"sml": 2`), carrying
|
|
111
|
+
components, block and inline content, bindings, conditions, loops, formatters, and source positions.
|
|
112
|
+
`@samva/markup/render` lowers that document to the requested target against an input. Rendering is
|
|
113
|
+
pure, with no authored code or external I/O at send time.
|
|
114
114
|
|
|
115
115
|
```ts
|
|
116
116
|
import { render } from "@samva/markup/render";
|
|
@@ -125,12 +125,28 @@ const compiled = await compileTemplate({
|
|
|
125
125
|
if (compiled.template === undefined) throw new Error(compiled.diagnostics.map(String).join("\n"));
|
|
126
126
|
|
|
127
127
|
const { subject, preheader, html, text } = render(compiled.template, compiled.fixtures.default, {
|
|
128
|
-
|
|
128
|
+
target: "email",
|
|
129
129
|
locale: "en-US",
|
|
130
130
|
timeZone: "UTC",
|
|
131
131
|
});
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
Every email template also has a web target. It returns the page body and hints for the host's page
|
|
135
|
+
shell; the host owns the document head, canonical URL, indexing, and access control.
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
const page = render(compiled.template, compiled.fixtures.default, { target: "web" });
|
|
139
|
+
// page.html is semantic HTML; page.headHints carries the styles and document hints.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Email lowering preserves email layout, Outlook fallbacks, subject, preheader, and plain text.
|
|
143
|
+
Web lowering uses semantic elements and supplies styles separately from the HTML body. Both read
|
|
144
|
+
the same authored components and source positions. `COMPONENT_DEGRADATIONS` from
|
|
145
|
+
`@samva/markup/compatibility` declares what each component loses in email.
|
|
146
|
+
`templateEmailDegradations(compiled.template)` locates those declarations in the authored source;
|
|
147
|
+
`checkCompatibility` reports content degradations alongside client HTML findings. A `same`
|
|
148
|
+
declaration records that content is preserved and adds no finding.
|
|
149
|
+
|
|
134
150
|
### The static profile
|
|
135
151
|
|
|
136
152
|
The profile is the subset of TSX the compiler reads without running it: the forms an agent already
|
|
@@ -253,3 +269,71 @@ Portable validation uses `@cfworker/json-schema` without runtime code generation
|
|
|
253
269
|
2020-12 meta-schema is checked at every supported schema position, and the explicit profile rejects
|
|
254
270
|
keywords outside its contract before interpretation. The AJV dependency supplies static meta-schema
|
|
255
271
|
JSON; its code generator is not invoked in a loaded template.
|
|
272
|
+
|
|
273
|
+
## Markdown newsletter issues
|
|
274
|
+
|
|
275
|
+
`@samva/markup/markdown` parses newsletter prose into the same semantic document as TSX. Files
|
|
276
|
+
carry YAML frontmatter and CommonMark prose, with static tags from SML's email component set:
|
|
277
|
+
|
|
278
|
+
```md
|
|
279
|
+
---
|
|
280
|
+
title: October product news
|
|
281
|
+
slug: october-product-news
|
|
282
|
+
topic: product
|
|
283
|
+
preheader: What shipped this month
|
|
284
|
+
seo:
|
|
285
|
+
description: October updates from the team
|
|
286
|
+
image: https://example.com/october.png
|
|
287
|
+
gate:
|
|
288
|
+
leadIn: 2
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
# What shipped
|
|
292
|
+
|
|
293
|
+
Read the **latest updates**.
|
|
294
|
+
|
|
295
|
+
<Button href="https://example.com/updates">See every update</Button>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
`title`, `slug`, and `topic` are required nonempty strings. The slug is lowercase kebab-case.
|
|
299
|
+
`preheader` is optional text; `seo` accepts optional `title`, `description`, and an absolute HTTPS
|
|
300
|
+
`image`; `gate.leadIn` counts leading top-level content blocks before a host-owned subscription
|
|
301
|
+
wall. SML preserves this metadata; the host owns SEO tags, gating, and publication. Unknown fields,
|
|
302
|
+
invalid YAML, raw HTML, unknown components, expressions, spreads, and nonliteral props are errors.
|
|
303
|
+
Use explicit `[label](url)` links; angle autolinks are outside this grammar.
|
|
304
|
+
Numeric and boolean props use quoted values (`height="24"`, `stack="false"`); bare boolean flags
|
|
305
|
+
mean true. Fenced code is literal content, including any HTML or braces inside it.
|
|
306
|
+
|
|
307
|
+
A layout is an ordinary email-only `defineTemplate` entry containing one static `<IssueContent />`
|
|
308
|
+
imported from `@samva/markup/email`. The directive takes no props or children and must sit outside
|
|
309
|
+
conditions and loops. It disappears during compilation before styles are applied.
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
import { compileIssue, parseIssue } from "@samva/markup/markdown";
|
|
313
|
+
import { render } from "@samva/markup/render";
|
|
314
|
+
|
|
315
|
+
const parsed = parseIssue(files["issues/october.md"], "issues/october.md");
|
|
316
|
+
const compiled = await compileIssue({
|
|
317
|
+
files,
|
|
318
|
+
entry: "issues/october.md",
|
|
319
|
+
layout: "layout.tsx",
|
|
320
|
+
assetBase: "https://assets.example.com",
|
|
321
|
+
});
|
|
322
|
+
if (compiled.template) {
|
|
323
|
+
const page = render(compiled.template, compiled.fixtures.default, { target: "web" });
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
`compileIssue` takes its schema, fixtures, theme and body shell from the layout; the issue slug
|
|
328
|
+
becomes the compiled identity, its title becomes the subject, and its preheader becomes the inbox
|
|
329
|
+
preview. Frontmatter is returned separately and is not injected into layout inputs. Layout and issue
|
|
330
|
+
nodes retain their own source files. `parseIssue` returns no usable nodes when it reports an error.
|
|
331
|
+
|
|
332
|
+
`inspectMarkdownNode` from `@samva/markup/edit` resolves a rendered origin to editable prose and
|
|
333
|
+
literal attributes. Pass `{ tag: "a" }` as its fourth argument to distinguish a link from a
|
|
334
|
+
paragraph sharing its source origin; without a tag it prefers the editable element. Feed its
|
|
335
|
+
property to `literalReplacement`, then call `applySourceReplacements`
|
|
336
|
+
with `{ markdown: { file: "issues/october.md" } }` to validate the rewritten issue. Offsets are
|
|
337
|
+
UTF-16, end-exclusive, and stale spans are refused. Prose edits retain ordinary punctuation;
|
|
338
|
+
syntax delimiters are escaped only where they could change the authored structure, and literal
|
|
339
|
+
line breaks or edge whitespace are encoded as character references.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//#region src/email/assets.ts
|
|
2
|
+
/** Files a template may import, and what they are served as. */
|
|
3
|
+
const ASSET_CONTENT_TYPES = {
|
|
4
|
+
".png": "image/png",
|
|
5
|
+
".jpg": "image/jpeg",
|
|
6
|
+
".jpeg": "image/jpeg",
|
|
7
|
+
".gif": "image/gif",
|
|
8
|
+
".webp": "image/webp",
|
|
9
|
+
".avif": "image/avif",
|
|
10
|
+
".svg": "image/svg+xml",
|
|
11
|
+
".ico": "image/x-icon",
|
|
12
|
+
".woff": "font/woff",
|
|
13
|
+
".woff2": "font/woff2",
|
|
14
|
+
".ttf": "font/ttf",
|
|
15
|
+
".otf": "font/otf",
|
|
16
|
+
".pdf": "application/pdf",
|
|
17
|
+
".txt": "text/plain"
|
|
18
|
+
};
|
|
19
|
+
const extensionOf = (path) => {
|
|
20
|
+
const dot = path.lastIndexOf(".");
|
|
21
|
+
return dot < 0 ? "" : path.slice(dot).toLowerCase();
|
|
22
|
+
};
|
|
23
|
+
/** True when the compiler turns this import into a content-addressed asset. */
|
|
24
|
+
const isAssetPath = (path) => Object.hasOwn(ASSET_CONTENT_TYPES, extensionOf(path));
|
|
25
|
+
const hex = (buffer) => Array.from(new Uint8Array(buffer), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
26
|
+
/** Address one file by its content. */
|
|
27
|
+
const assetEntry = async (path, bytes) => {
|
|
28
|
+
const extension = extensionOf(path);
|
|
29
|
+
const view = new Uint8Array(bytes.byteLength);
|
|
30
|
+
view.set(bytes);
|
|
31
|
+
const digest = hex(await crypto.subtle.digest("SHA-256", view));
|
|
32
|
+
return {
|
|
33
|
+
path,
|
|
34
|
+
digest,
|
|
35
|
+
bytes: bytes.byteLength,
|
|
36
|
+
contentType: ASSET_CONTENT_TYPES[extension] ?? "application/octet-stream",
|
|
37
|
+
fileName: `${digest}${extension}`
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The URL an asset has under a base. The base is a build input: publication
|
|
42
|
+
* passes its hosted origin, and a local export passes whatever it will serve
|
|
43
|
+
* the exported files from.
|
|
44
|
+
*/
|
|
45
|
+
const assetUrl = (entry, base) => `${base.replace(/\/+$/, "")}/${entry.fileName}`;
|
|
46
|
+
const EXTERNAL_IMAGE = /^https:\/\//i;
|
|
47
|
+
/** Record, but never fetch, a URL the template points at. */
|
|
48
|
+
const isExternalAsset = (reference) => EXTERNAL_IMAGE.test(reference);
|
|
49
|
+
/**
|
|
50
|
+
* Reject an asset base that would produce URLs a client will not load. The
|
|
51
|
+
* relative forms are the ones a page on disk resolves against its own
|
|
52
|
+
* directory, which is what a local export writes.
|
|
53
|
+
*/
|
|
54
|
+
const checkAssetBase = (base) => /^(https:\/\/|\.{1,2}\/|\/)/i.test(base) ? [] : [{
|
|
55
|
+
code: "invalid-asset-base",
|
|
56
|
+
severity: "error",
|
|
57
|
+
message: `Asset base ${JSON.stringify(base)} must be an https URL, an absolute path, or a relative path for a local export.`,
|
|
58
|
+
origins: []
|
|
59
|
+
}];
|
|
60
|
+
//#endregion
|
|
61
|
+
export { isAssetPath as a, checkAssetBase as i, assetEntry as n, isExternalAsset as o, assetUrl as r, ASSET_CONTENT_TYPES as t };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { n as EmailDiagnostic } from "./diagnostics-C3TSx6SU.js";
|
|
2
|
+
//#region src/email/assets.d.ts
|
|
3
|
+
/** Files a template may import, and what they are served as. */
|
|
4
|
+
declare const ASSET_CONTENT_TYPES: Readonly<Record<string, string>>;
|
|
5
|
+
interface AssetEntry {
|
|
6
|
+
/** Project-relative path the template imported. */
|
|
7
|
+
readonly path: string;
|
|
8
|
+
/** SHA-256 of the exact bytes, lowercase hex. */
|
|
9
|
+
readonly digest: string;
|
|
10
|
+
readonly bytes: number;
|
|
11
|
+
readonly contentType: string;
|
|
12
|
+
/** `<digest><ext>` — the name the asset has under any base. */
|
|
13
|
+
readonly fileName: string;
|
|
14
|
+
}
|
|
15
|
+
interface AssetManifest {
|
|
16
|
+
/** Where these assets are served from in this build. */
|
|
17
|
+
readonly base: string;
|
|
18
|
+
readonly assets: readonly AssetEntry[];
|
|
19
|
+
}
|
|
20
|
+
/** True when the compiler turns this import into a content-addressed asset. */
|
|
21
|
+
declare const isAssetPath: (path: string) => boolean;
|
|
22
|
+
/** Address one file by its content. */
|
|
23
|
+
declare const assetEntry: (path: string, bytes: Uint8Array) => Promise<AssetEntry>;
|
|
24
|
+
/**
|
|
25
|
+
* The URL an asset has under a base. The base is a build input: publication
|
|
26
|
+
* passes its hosted origin, and a local export passes whatever it will serve
|
|
27
|
+
* the exported files from.
|
|
28
|
+
*/
|
|
29
|
+
declare const assetUrl: (entry: AssetEntry, base: string) => string;
|
|
30
|
+
/** Record, but never fetch, a URL the template points at. */
|
|
31
|
+
declare const isExternalAsset: (reference: string) => boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Reject an asset base that would produce URLs a client will not load. The
|
|
34
|
+
* relative forms are the ones a page on disk resolves against its own
|
|
35
|
+
* directory, which is what a local export writes.
|
|
36
|
+
*/
|
|
37
|
+
declare const checkAssetBase: (base: string) => readonly EmailDiagnostic[];
|
|
38
|
+
//#endregion
|
|
39
|
+
export { assetUrl as a, isExternalAsset as c, assetEntry as i, AssetEntry as n, checkAssetBase as o, AssetManifest as r, isAssetPath as s, ASSET_CONTENT_TYPES as t };
|
|
@@ -1,6 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { i as EmailNode } from "./jsx-runtime-HHfrVZEf.js";
|
|
2
|
+
//#region src/email/brand-plugin.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* How a template reaches its brand and how the footer `BrandFooter` renders is
|
|
5
|
+
* marked for the send path that delivers it. The compiler and the IR renderer
|
|
6
|
+
* read every brand import, footer marker and unsubscribe link from the plugin
|
|
7
|
+
* a build passes; `samvaBrandPlugin` is the default.
|
|
8
|
+
*/
|
|
9
|
+
interface BrandPlugin {
|
|
10
|
+
/**
|
|
11
|
+
* The module and stylesheet specifier a template imports its brand from.
|
|
12
|
+
* `<specifier>/<slug>` names one brand; the bare specifier names the default.
|
|
13
|
+
*/
|
|
14
|
+
readonly specifier: string;
|
|
15
|
+
/** Marks the block `BrandFooter` renders, so the send path finds a template's own footer. */
|
|
16
|
+
readonly footerAttribute: `data-${string}`;
|
|
17
|
+
/** Marks the footer row that carries the unsubscribe link, so a send can drop it. */
|
|
18
|
+
readonly unsubscribeRowAttribute: `data-${string}`;
|
|
19
|
+
/** The unsubscribe link's href; the send path substitutes each delivery's URL for it. */
|
|
20
|
+
readonly unsubscribeUrlPlaceholder: string;
|
|
21
|
+
/**
|
|
22
|
+
* Written on the unsubscribe link, so the send path delivers it without
|
|
23
|
+
* click tracking: a rewritten link would carry another recipient's token.
|
|
24
|
+
*/
|
|
25
|
+
readonly noTrackAttribute: `data-${string}`;
|
|
26
|
+
}
|
|
27
|
+
/** The brand a specifier names: `undefined` slug means the default brand; `undefined` when it names none. */
|
|
28
|
+
declare const parseBrandSpecifier: (specifier: string, options?: {
|
|
29
|
+
readonly brandPlugin?: BrandPlugin | undefined;
|
|
30
|
+
}) => {
|
|
31
|
+
readonly slug: string | undefined;
|
|
32
|
+
} | undefined;
|
|
33
|
+
//#endregion
|
|
4
34
|
//#region src/email/brand.d.ts
|
|
5
35
|
interface EmailBrandAddress {
|
|
6
36
|
readonly street1: string;
|
|
@@ -74,41 +104,4 @@ declare const withoutUnsubscribeRow: (rendered: {
|
|
|
74
104
|
readonly text: string | undefined;
|
|
75
105
|
};
|
|
76
106
|
//#endregion
|
|
77
|
-
|
|
78
|
-
/** Files a template may import, and what they are served as. */
|
|
79
|
-
declare const ASSET_CONTENT_TYPES: Readonly<Record<string, string>>;
|
|
80
|
-
interface AssetEntry {
|
|
81
|
-
/** Project-relative path the template imported. */
|
|
82
|
-
readonly path: string;
|
|
83
|
-
/** SHA-256 of the exact bytes, lowercase hex. */
|
|
84
|
-
readonly digest: string;
|
|
85
|
-
readonly bytes: number;
|
|
86
|
-
readonly contentType: string;
|
|
87
|
-
/** `<digest><ext>` — the name the asset has under any base. */
|
|
88
|
-
readonly fileName: string;
|
|
89
|
-
}
|
|
90
|
-
interface AssetManifest {
|
|
91
|
-
/** Where these assets are served from in this build. */
|
|
92
|
-
readonly base: string;
|
|
93
|
-
readonly assets: readonly AssetEntry[];
|
|
94
|
-
}
|
|
95
|
-
/** True when the compiler turns this import into a content-addressed asset. */
|
|
96
|
-
declare const isAssetPath: (path: string) => boolean;
|
|
97
|
-
/** Address one file by its content. */
|
|
98
|
-
declare const assetEntry: (path: string, bytes: Uint8Array) => Promise<AssetEntry>;
|
|
99
|
-
/**
|
|
100
|
-
* The URL an asset has under a base. The base is a build input: publication
|
|
101
|
-
* passes its hosted origin, and a local export passes whatever it will serve
|
|
102
|
-
* the exported files from.
|
|
103
|
-
*/
|
|
104
|
-
declare const assetUrl: (entry: AssetEntry, base: string) => string;
|
|
105
|
-
/** Record, but never fetch, a URL the template points at. */
|
|
106
|
-
declare const isExternalAsset: (reference: string) => boolean;
|
|
107
|
-
/**
|
|
108
|
-
* Reject an asset base that would produce URLs a client will not load. The
|
|
109
|
-
* relative forms are the ones a page on disk resolves against its own
|
|
110
|
-
* directory, which is what a local export writes.
|
|
111
|
-
*/
|
|
112
|
-
declare const checkAssetBase: (base: string) => readonly EmailDiagnostic[];
|
|
113
|
-
//#endregion
|
|
114
|
-
export { EmailBrandSocialLink as _, assetUrl as a, renderedFooterMarkers as b, isExternalAsset as c, BrandFooterProps as d, BrandLogoProps as f, EmailBrandFooter as g, EmailBrandAssets as h, assetEntry as i, BRAND_COMPONENT_CLASSES as l, EmailBrandAddress as m, AssetEntry as n, checkAssetBase as o, EmailBrand as p, AssetManifest as r, isAssetPath as s, ASSET_CONTENT_TYPES as t, BrandComponents as u, RenderedFooterMarkers as v, withoutUnsubscribeRow as x, brandComponents as y };
|
|
107
|
+
export { EmailBrand as a, EmailBrandFooter as c, brandComponents as d, renderedFooterMarkers as f, parseBrandSpecifier as h, BrandLogoProps as i, EmailBrandSocialLink as l, BrandPlugin as m, BrandComponents as n, EmailBrandAddress as o, withoutUnsubscribeRow as p, BrandFooterProps as r, EmailBrandAssets as s, BRAND_COMPONENT_CLASSES as t, RenderedFooterMarkers as u };
|
package/dist/brand.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { a as EmailBrand, c as EmailBrandFooter, d as brandComponents, f as renderedFooterMarkers, i as BrandLogoProps, l as EmailBrandSocialLink, m as BrandPlugin, n as BrandComponents, o as EmailBrandAddress, p as withoutUnsubscribeRow, r as BrandFooterProps, s as EmailBrandAssets, t as BRAND_COMPONENT_CLASSES, u as RenderedFooterMarkers } from "./brand-Bxb6Sm7y.js";
|
|
2
|
+
import { a as assetUrl, c as isExternalAsset, i as assetEntry, n as AssetEntry, o as checkAssetBase, r as AssetManifest, s as isAssetPath, t as ASSET_CONTENT_TYPES } from "./assets-k7_v3IOJ.js";
|
|
3
3
|
import { t as Diagnostic } from "./diagnostic-model-DxthUZBT.js";
|
|
4
4
|
import { Result } from "better-result";
|
|
5
5
|
//#region src/email/samva-brand-plugin.d.ts
|
package/dist/brand.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as
|
|
3
|
-
import { a as THEME_COLOR_ROLES, c as foregroundDefaults, i as THEME_COLORS, l as parseBrandCss, n as DEFAULT_THEME_CSS, o as THEME_TOKENS, r as FILL_COLORS, s as contrastForeground, t as DEFAULT_THEME, u as printBrandCss } from "./theme-css-
|
|
1
|
+
import { B as brandComponents, H as withoutUnsubscribeRow, V as renderedFooterMarkers, et as samvaBrandPlugin, z as BRAND_COMPONENT_CLASSES } from "./components-DNBCz20_.js";
|
|
2
|
+
import { a as isAssetPath, i as checkAssetBase, n as assetEntry, o as isExternalAsset, r as assetUrl, t as ASSET_CONTENT_TYPES } from "./assets-BSpgZOSR.js";
|
|
3
|
+
import { a as THEME_COLOR_ROLES, c as foregroundDefaults, i as THEME_COLORS, l as parseBrandCss, n as DEFAULT_THEME_CSS, o as THEME_TOKENS, r as FILL_COLORS, s as contrastForeground, t as DEFAULT_THEME, u as printBrandCss } from "./theme-css-BujDuuEA.js";
|
|
4
4
|
export { ASSET_CONTENT_TYPES, BRAND_COMPONENT_CLASSES, DEFAULT_THEME, DEFAULT_THEME_CSS, FILL_COLORS, THEME_COLORS, THEME_COLOR_ROLES, THEME_TOKENS, assetEntry, assetUrl, brandComponents, checkAssetBase, contrastForeground, foregroundDefaults, isAssetPath, isExternalAsset, parseBrandCss, printBrandCss, renderedFooterMarkers, samvaBrandPlugin, withoutUnsubscribeRow };
|
package/dist/compatibility.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as EmailDiagnostic } from "./diagnostics-
|
|
3
|
-
import { v as CompiledTemplate } from "./compiled-template-
|
|
4
|
-
import { r as EmittedPosition } from "./preview-
|
|
1
|
+
import { m as BrandPlugin } from "./brand-Bxb6Sm7y.js";
|
|
2
|
+
import { n as EmailDiagnostic } from "./diagnostics-C3TSx6SU.js";
|
|
3
|
+
import { G as EmailDegradation, K as SemanticComponentName, W as COMPONENT_DEGRADATIONS, q as componentDegradation, v as CompiledTemplate } from "./compiled-template-CPDHvFlt.js";
|
|
4
|
+
import { r as EmittedPosition } from "./preview-DRLWgmfg.js";
|
|
5
5
|
import { caniemail } from "caniemail";
|
|
6
6
|
//#region src/email/compatibility.d.ts
|
|
7
7
|
type CaniemailClient = Parameters<typeof caniemail>[0]["clients"][number];
|
|
@@ -62,6 +62,16 @@ declare const unknownCoverage: (titles: Iterable<string>) => readonly EmailDiagn
|
|
|
62
62
|
declare const hasBlockingCompatibility: (findings: readonly EmailDiagnostic[]) => boolean;
|
|
63
63
|
//#endregion
|
|
64
64
|
//#region src/sml/compatibility.d.ts
|
|
65
|
+
/** One authored component's content fidelity in email, independent of client presentation. */
|
|
66
|
+
interface ComponentEmailDegradation extends EmailDegradation {
|
|
67
|
+
readonly component: SemanticComponentName;
|
|
68
|
+
readonly origins: EmailDiagnostic["origins"];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The document's declarations, including conditional branches. A declaration describes the
|
|
72
|
+
* component when used; fixture-specific client observations still come from the rendered HTML.
|
|
73
|
+
*/
|
|
74
|
+
declare const templateEmailDegradations: (template: CompiledTemplate) => readonly ComponentEmailDegradation[];
|
|
65
75
|
/**
|
|
66
76
|
* Check what each declared fixture delivers against the pinned client matrix. The rules run on the
|
|
67
77
|
* rendered document, because what a client drops is what the compiler produced, and every finding
|
|
@@ -73,4 +83,4 @@ declare const checkCompatibility: (template: CompiledTemplate, fixtures: Readonl
|
|
|
73
83
|
readonly brandPlugin?: BrandPlugin | undefined;
|
|
74
84
|
}) => readonly EmailDiagnostic[];
|
|
75
85
|
//#endregion
|
|
76
|
-
export { COMPATIBILITY_PROVENANCE, type CompatibilityInput, type CompatibilityProvenance, EMAIL_CLIENT_MATRIX, ESSENTIAL_CONSTRUCTS, type EmailClientTarget, checkCompatibility, checkHtmlCompatibility, hasBlockingCompatibility, unknownCoverage };
|
|
86
|
+
export { COMPATIBILITY_PROVENANCE, COMPONENT_DEGRADATIONS, type CompatibilityInput, type CompatibilityProvenance, type ComponentEmailDegradation, EMAIL_CLIENT_MATRIX, ESSENTIAL_CONSTRUCTS, type EmailClientTarget, type EmailDegradation, type SemanticComponentName, checkCompatibility, checkHtmlCompatibility, componentDegradation, hasBlockingCompatibility, templateEmailDegradations, unknownCoverage };
|
package/dist/compatibility.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as matchesSelector } from "./selectors-
|
|
4
|
-
import { t as
|
|
1
|
+
import { A as renderEmail, K as isValueNode, U as COMPONENT_DEGRADATIONS, W as componentDegradation } from "./components-DNBCz20_.js";
|
|
2
|
+
import { o as parseColor } from "./dark-scheme-BGDCCaUq.js";
|
|
3
|
+
import { n as matchesSelector, r as parseStylesheet } from "./selectors-CKGuc9MO.js";
|
|
4
|
+
import { t as DIAGNOSTIC_CODES } from "./diagnostic-codes-BCwfUgnF.js";
|
|
5
5
|
import { generate, parse, walk } from "css-tree";
|
|
6
6
|
import { caniemail, parseHtml, rawData } from "caniemail";
|
|
7
7
|
//#region src/email/compatibility-assessment.ts
|
|
@@ -768,6 +768,48 @@ const hasBlockingCompatibility = (findings) => findings.some((finding) => findin
|
|
|
768
768
|
//#endregion
|
|
769
769
|
//#region src/sml/compatibility.ts
|
|
770
770
|
/**
|
|
771
|
+
* The document's declarations, including conditional branches. A declaration describes the
|
|
772
|
+
* component when used; fixture-specific client observations still come from the rendered HTML.
|
|
773
|
+
*/
|
|
774
|
+
const templateEmailDegradations = (template) => {
|
|
775
|
+
if (template.email === void 0) return [];
|
|
776
|
+
const declarations = [];
|
|
777
|
+
const pending = [template.email.body];
|
|
778
|
+
const seen = /* @__PURE__ */ new Set();
|
|
779
|
+
while (pending.length > 0) {
|
|
780
|
+
const node = pending.pop();
|
|
781
|
+
if (isValueNode(node) || seen.has(node)) continue;
|
|
782
|
+
seen.add(node);
|
|
783
|
+
if ("type" in node && node.type === "component") {
|
|
784
|
+
const src = node.src;
|
|
785
|
+
declarations.push({
|
|
786
|
+
component: node.name,
|
|
787
|
+
...componentDegradation(node.name),
|
|
788
|
+
origins: src === void 0 ? [] : [{
|
|
789
|
+
fileName: template.sources?.[src[2] ?? 0] ?? "",
|
|
790
|
+
lineNumber: src[0],
|
|
791
|
+
columnNumber: src[1]
|
|
792
|
+
}]
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
if ("if" in node) pending.push(...[...node.then, ...node.else ?? []].reverse());
|
|
796
|
+
else if ("children" in node) pending.push(...(node.children ?? []).toReversed());
|
|
797
|
+
}
|
|
798
|
+
return declarations;
|
|
799
|
+
};
|
|
800
|
+
/** Translate content losses into the same located report as HTML compatibility findings. */
|
|
801
|
+
const degradationFindings = (declarations) => declarations.flatMap((declaration) => declaration.kind === "same" ? [] : [{
|
|
802
|
+
code: "email-component-degradation",
|
|
803
|
+
severity: "warning",
|
|
804
|
+
message: `<${declaration.component}>: ${declaration.note}`,
|
|
805
|
+
origins: declaration.origins,
|
|
806
|
+
notes: DIAGNOSTIC_CODES["email-component-degradation"].fix,
|
|
807
|
+
compatibility: {
|
|
808
|
+
assessment: "degradation",
|
|
809
|
+
title: declaration.note
|
|
810
|
+
}
|
|
811
|
+
}]);
|
|
812
|
+
/**
|
|
771
813
|
* Check what each declared fixture delivers against the pinned client matrix. The rules run on the
|
|
772
814
|
* rendered document, because what a client drops is what the compiler produced, and every finding
|
|
773
815
|
* is located at the source of the element that produced it. A fixture the renderer refuses throws
|
|
@@ -801,7 +843,8 @@ const checkCompatibility = (template, fixtures, options = {}) => {
|
|
|
801
843
|
...options.clients === void 0 ? {} : { clients: options.clients }
|
|
802
844
|
}));
|
|
803
845
|
}
|
|
846
|
+
findings.push(...degradationFindings(templateEmailDegradations(template)));
|
|
804
847
|
return findings;
|
|
805
848
|
};
|
|
806
849
|
//#endregion
|
|
807
|
-
export { COMPATIBILITY_PROVENANCE, EMAIL_CLIENT_MATRIX, ESSENTIAL_CONSTRUCTS, checkCompatibility, checkHtmlCompatibility, hasBlockingCompatibility, unknownCoverage };
|
|
850
|
+
export { COMPATIBILITY_PROVENANCE, COMPONENT_DEGRADATIONS, EMAIL_CLIENT_MATRIX, ESSENTIAL_CONSTRUCTS, checkCompatibility, checkHtmlCompatibility, componentDegradation, hasBlockingCompatibility, templateEmailDegradations, unknownCoverage };
|