Share base style syntax between rendering and editor validation
This commit is contained in:
1 parent
16d804e16f
commit
14669bb4f3
4 files changed
+45
-10
No files matched your search
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/email-renderer",
|
"name": "@wrnexus/email-renderer",
|
||||||
"version": "0.1.41",
|
"version": "0.1.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "WRNexus block email renderers with HTML, text, AMP Email, and MJML interoperability.",
|
"description": "WRNexus block email renderers with HTML, text, AMP Email, and MJML interoperability.",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.ts",
|
".": "./src/index.ts",
|
||||||
"./blocks": "./src/blocks.ts",
|
"./blocks": "./src/blocks.ts",
|
||||||
"./create": "./src/create.ts"
|
"./create": "./src/create.ts",
|
||||||
|
"./style-values": "./src/style-values.ts"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"src",
|
"src",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
type ValueSchema,
|
type ValueSchema,
|
||||||
} from "@wrnexus/blocks";
|
} from "@wrnexus/blocks";
|
||||||
import { brandColor, brandFont, brandWidth } from "./brand.ts";
|
import { brandColor, brandFont, brandWidth } from "./brand.ts";
|
||||||
|
import { emailStyleValue } from "./style-values.ts";
|
||||||
|
|
||||||
const styles = record(json, { maxKeys: 50 }) as never;
|
const styles = record(json, { maxKeys: 50 }) as never;
|
||||||
const label = (key: string, fallback: string) => ({ key, fallback });
|
const label = (key: string, fallback: string) => ({ key, fallback });
|
||||||
@@ -87,14 +88,14 @@ const safeCssValue = (value: unknown, pattern: RegExp) => {
|
|||||||
};
|
};
|
||||||
const semanticStyle = (node: BlockNode) => {
|
const semanticStyle = (node: BlockNode) => {
|
||||||
const declarations: string[] = [];
|
const declarations: string[] = [];
|
||||||
const color = safeCssValue(styleBase(node, "color"), /^(#[0-9a-f]{3,8}|rgba?\([0-9., %]+\))$/i);
|
const color = emailStyleValue("color", styleBase(node, "color"));
|
||||||
const background = safeCssValue(styleBase(node, "backgroundColor"), /^(#[0-9a-f]{3,8}|rgba?\([0-9., %]+\))$/i);
|
const background = emailStyleValue("backgroundColor", styleBase(node, "backgroundColor"));
|
||||||
const padding = safeCssValue(styleBase(node, "padding"), /^\d+(?:\.\d+)?(?:px|rem|em|%)?(?:\s+\d+(?:\.\d+)?(?:px|rem|em|%)?){0,3}$/);
|
const padding = emailStyleValue("padding", styleBase(node, "padding"));
|
||||||
const margin = safeCssValue(styleBase(node, "margin"), /^\d+(?:\.\d+)?(?:px|rem|em|%)?(?:\s+\d+(?:\.\d+)?(?:px|rem|em|%)?){0,3}$/);
|
const margin = emailStyleValue("margin", styleBase(node, "margin"));
|
||||||
const fontSize = safeCssValue(styleBase(node, "fontSize"), /^\d+(?:\.\d+)?(?:px|rem|em|%)$/);
|
const fontSize = emailStyleValue("fontSize", styleBase(node, "fontSize"));
|
||||||
const radius = safeCssValue(styleBase(node, "borderRadius"), /^\d+(?:\.\d+)?(?:px|rem|em|%)$/);
|
const radius = emailStyleValue("borderRadius", styleBase(node, "borderRadius"));
|
||||||
const align = safeCssValue(styleBase(node, "textAlign"), /^(left|center|right|start|end)$/);
|
const align = emailStyleValue("textAlign", styleBase(node, "textAlign"));
|
||||||
const weight = safeCssValue(styleBase(node, "fontWeight"), /^(normal|bold|[1-9]00)$/);
|
const weight = emailStyleValue("fontWeight", styleBase(node, "fontWeight"));
|
||||||
if (color) declarations.push(`color:${color}`);
|
if (color) declarations.push(`color:${color}`);
|
||||||
if (background) declarations.push(`background-color:${background}`);
|
if (background) declarations.push(`background-color:${background}`);
|
||||||
if (padding) declarations.push(`padding:${padding}`);
|
if (padding) declarations.push(`padding:${padding}`);
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
const color = /^(#[0-9a-f]{3}|#[0-9a-f]{4}|#[0-9a-f]{6}|#[0-9a-f]{8}|rgba?\([0-9., %]+\))$/i;
|
||||||
|
const spacing = /^(?:0|\d+(?:\.\d+)?(?:px|rem|em|%))(?:\s+(?:0|\d+(?:\.\d+)?(?:px|rem|em|%))){0,3}$/;
|
||||||
|
const patterns: Record<string, RegExp> = {
|
||||||
|
color, backgroundColor: color, padding: spacing, margin: spacing,
|
||||||
|
fontSize: /^\d+(?:\.\d+)?(?:px|rem|em|%)$/,
|
||||||
|
borderRadius: /^(?:0|\d+(?:\.\d+)?(?:px|rem|em|%))$/,
|
||||||
|
textAlign: /^(left|center|right|start|end)$/,
|
||||||
|
fontWeight: /^(normal|bold|[1-9]00)$/,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Accepted base-value syntax shared by the inspector and semantic style compiler. */
|
||||||
|
export function emailStyleValue(key: string, value: unknown): string {
|
||||||
|
if (typeof value !== "string") return "";
|
||||||
|
const candidate = value.trim();
|
||||||
|
return candidate.length <= 80 && Object.prototype.hasOwnProperty.call(patterns, key) && patterns[key]!.test(candidate) ? candidate : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateEmailStyleValue(key: string, value: string): void {
|
||||||
|
if (!emailStyleValue(key, value)) throw new Error(`styles.${key}: Unsupported style value. Use an email-safe value with units where required.`);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { expect, test } from "bun:test";
|
||||||
|
import { emailStyleValue, validateEmailStyleValue } from "../src/style-values.ts";
|
||||||
|
|
||||||
|
test("base style syntax rejects ignored or injected values and accepts supported units", () => {
|
||||||
|
for (const [key, value] of [["padding","12"],["padding","1px 2px 3px 4px 5px"],["color","#12345"],["fontWeight","heavy"],["fontSize","12px;color:red"],["position","fixed"],["constructor","x"]]) {
|
||||||
|
expect(emailStyleValue(key!, value)).toBe("");
|
||||||
|
expect(() => validateEmailStyleValue(key!, value!)).toThrow();
|
||||||
|
}
|
||||||
|
for (const [key, value] of [["padding","0 12px"],["margin","1rem 2%"],["color","#1234"],["fontWeight","700"],["fontSize","16px"],["borderRadius","0"],["textAlign","end"]]) {
|
||||||
|
expect(emailStyleValue(key!, value)).toBe(value!);
|
||||||
|
expect(() => validateEmailStyleValue(key!, value!)).not.toThrow();
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in new issue
Block a user