119 lines
3.3 KiB
TypeScript
119 lines
3.3 KiB
TypeScript
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<string, string> = {
|
|
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<CSSStyleDeclaration> = {};
|
|
|
|
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;
|
|
},
|
|
);
|