diff --git a/content/style-test.md b/content/style-test.md index ffc6550..35a6e81 100644 --- a/content/style-test.md +++ b/content/style-test.md @@ -1 +1,12 @@ :md-font[Pacifico]{source=google} hello? +:md-dice[d6]{.bg-amber-100} + +:md-border[crimson]{.b} + +:::div{.flex.justify-stretch.gap-1} + +foo + +bar + +::: diff --git a/src/components/index.ts b/src/components/index.ts index 1a7000b..ddee828 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -4,6 +4,7 @@ import "./md-table"; import "./md-link"; import "./md-pins"; import "./md-bg"; +import "./md-border"; import "./md-font"; import "./md-embed"; import "./md-deck"; @@ -26,6 +27,7 @@ export type { DiceProps } from "./md-dice"; export type { TableProps } from "./md-table"; export type { BgProps } from "./md-bg"; export type { FontProps } from "./md-font"; +export type { BorderProps } from "./md-border"; export type { EmbedProps } from "./md-embed"; export type { YarnSpinnerProps } from "./md-yarn-spinner"; export type { TokenProps } from "./md-token"; diff --git a/src/components/md-border.tsx b/src/components/md-border.tsx new file mode 100644 index 0000000..5b4928e --- /dev/null +++ b/src/components/md-border.tsx @@ -0,0 +1,118 @@ +import { customElement, noShadowDOM } from "solid-element"; +import { onCleanup } from "solid-js"; +import { resolvePath } from "./utils/path"; +import { registerStyle } from "./utils/article-style-manager"; + +const SIDE_CLASSES = new Set(["t", "b", "l", "r", "all"]); +const STYLE_CLASSES = new Set([ + "solid", + "dashed", + "dotted", + "double", + "groove", + "ridge", + "inset", + "outset", + "none", +]); +const REPEAT_CLASSES = new Set(["stretch", "repeat", "round", "space"]); + +const SIDE_CSS: Record = { + t: "Top", + b: "Bottom", + l: "Left", + r: "Right", +}; + +function parseSides(classes: string[]): string[] { + const sides = classes.filter((c) => SIDE_CLASSES.has(c)); + if (sides.length === 0) return ["Top", "Bottom", "Left", "Right"]; + if (sides.includes("all")) return ["Top", "Bottom", "Left", "Right"]; + return sides.map((s) => SIDE_CSS[s]).filter(Boolean); +} + +function parseStyle(classes: string[]): string { + const s = classes.find((c) => STYLE_CLASSES.has(c)); + return s || "solid"; +} + +function parseRepeat(classes: string[]): string { + const r = classes.find((c) => REPEAT_CLASSES.has(c)); + return r || "stretch"; +} + +function isImagePath(value: string): boolean { + return /^(\.{0,2}\/|[a-zA-Z]:\\|https?:\/\/)|\.(png|jpg|jpeg|gif|svg|webp|bmp)(\?|$)/i.test( + value, + ); +} + +export interface BorderProps { + width?: string; + slice?: string; +} + +customElement( + "md-border", + { width: undefined, slice: "10" }, + (props, { element }) => { + noShadowDOM(); + + const rawValue = element?.textContent?.trim() || ""; + if (element) element.textContent = ""; + + const articleEl = element?.closest("article") as HTMLElement; + const articlePath = + element?.closest("article[data-src]")?.getAttribute("data-src") || ""; + + // Parse classes from the element's className (marked-directive passes + // attrs.class as the element's className) + const classList = element?.className ? element.className.split(/\s+/) : []; + + const sides = parseSides(classList); + const image = isImagePath(rawValue); + const resolvedSrc = image ? resolvePath(articlePath, rawValue) : ""; + + const styles: Partial = {}; + + if (image) { + const repeat = parseRepeat(classList); + const borderWidth = props.width || "2mm"; + styles.borderImageSource = `url(${resolvedSrc})`; + styles.borderImageSlice = props.slice || "10"; + styles.borderImageWidth = borderWidth; + styles.borderImageRepeat = repeat; + styles.borderImageOutset = "0"; + (styles as any).borderStyle = "solid"; + + const allSides = ["Top", "Bottom", "Left", "Right"]; + for (const side of allSides) { + if (sides.includes(side)) { + (styles as any)[`border${side}Width`] = borderWidth; + } else { + (styles as any)[`border${side}Width`] = "0"; + } + } + } else { + const color = rawValue || "currentColor"; + const style = parseStyle(classList); + const borderWidth = props.width || ".2mm"; + + for (const side of sides) { + (styles as any)[`border${side}Width`] = borderWidth; + (styles as any)[`border${side}Style`] = style; + (styles as any)[`border${side}Color`] = color; + } + } + + const handle = registerStyle({ + key: "border", + article: articleEl, + styles, + }); + + onCleanup(() => handle.dispose()); + + return null; + }, +);