@blockslides/extension-slide 0.3.2 → 0.4.1
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 +10 -0
- package/dist/index.js +31 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +31 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/slide.ts +31 -1
package/CHANGELOG.md
ADDED
package/dist/index.js
CHANGED
|
@@ -61,6 +61,36 @@ var slideStyles = `
|
|
|
61
61
|
background-color: var(--slide-bg-overlay-color, #000);
|
|
62
62
|
opacity: var(--slide-bg-overlay-opacity, 0.35);
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
/* When multiple .column elements are adjacent siblings in a slide, */
|
|
67
|
+
/* they automatically form a horizontal row layout. */
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
/* Adjacent columns: make them display side-by-side */
|
|
71
|
+
.slide > .column + .column {
|
|
72
|
+
/* Column that follows another column */
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Wrapper div for groups of columns to enable flex-row layout */
|
|
76
|
+
.slide > .column-group {
|
|
77
|
+
display: flex;
|
|
78
|
+
flex-direction: row;
|
|
79
|
+
flex: 0 0 auto;
|
|
80
|
+
min-height: 0;
|
|
81
|
+
gap: var(--slide-column-gap, 16px);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* When fill is true, the group should take remaining space */
|
|
85
|
+
.slide > .column-group[data-fill="true"] {
|
|
86
|
+
flex: 1 1 auto;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Columns within a group share space equally by default */
|
|
90
|
+
.slide > .column-group > .column {
|
|
91
|
+
flex: 1;
|
|
92
|
+
min-width: 0;
|
|
93
|
+
}
|
|
64
94
|
`;
|
|
65
95
|
var getFixedSizeStyles = (scale) => `
|
|
66
96
|
.slide { --slide-scale: ${scale}; }
|
|
@@ -137,7 +167,7 @@ var SlidePluginKey = new import_state.PluginKey("slide");
|
|
|
137
167
|
var Slide = import_core.Node.create({
|
|
138
168
|
name: "slide",
|
|
139
169
|
isolating: true,
|
|
140
|
-
content: "
|
|
170
|
+
content: "block+",
|
|
141
171
|
group: "slide",
|
|
142
172
|
defining: true,
|
|
143
173
|
addOptions() {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/slide.ts"],"sourcesContent":["export { Slide } from \"./slide\";\nexport type { SlideOptions } from \"./slide\";\n","import { Node, mergeAttributes, createStyleTag } from \"@blockslides/core\";\nimport { Plugin, PluginKey } from \"@blockslides/pm/state\";\n\nconst slideStyles = `\n.slide {\n position: relative;\n height: var(--slide-height, 100%);\n min-height: var(--slide-min-height, 250px);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--slide-bg);\n border-radius: var(--slide-border-radius);\n box-shadow: var(--slide-shadow);\n margin-bottom: var(--slide-margin-bottom);\n}\n\n/* Background helpers (driven by data attributes + inline CSS vars) */\n.slide[data-bg-mode=\"color\"] {\n background-color: var(--slide-bg-color, var(--slide-bg));\n}\n\n.slide[data-bg-mode=\"image\"],\n.slide[data-bg-mode=\"imageOverlay\"] {\n background-size: cover;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.slide[data-bg-mode=\"imageOverlay\"]::before {\n content: \"\";\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-color: var(--slide-bg-overlay-color, #000);\n opacity: var(--slide-bg-overlay-opacity, 0.35);\n}\n`;\n\nconst getFixedSizeStyles = (scale: number) => `\n.slide { --slide-scale: ${scale}; }\n.slide[data-size=\"16x9\"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }\n.slide[data-size=\"4x3\"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }\n.slide[data-size=\"a4-portrait\"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }\n.slide[data-size=\"a4-landscape\"] { width: calc(297mm * var(--slide-scale)); height: calc(210mm * var(--slide-scale)); }\n.slide[data-size=\"letter-portrait\"] { width: calc(8.5in * var(--slide-scale)); height: calc(11in * var(--slide-scale)); }\n.slide[data-size=\"letter-landscape\"] { width: calc(11in * var(--slide-scale)); height: calc(8.5in * var(--slide-scale)); }\n.slide[data-size=\"linkedin-banner\"] { width: calc(1584px * var(--slide-scale)); height: calc(396px * var(--slide-scale)); }\n`.trim();\n\nconst dynamicSizeStyles = `\n.slide[data-size=\"16x9\"] { width: 100%; height: auto; aspect-ratio: 16 / 9; }\n.slide[data-size=\"4x3\"] { width: 100%; height: auto; aspect-ratio: 4 / 3; }\n.slide[data-size=\"a4-portrait\"] { width: 100%; height: auto; aspect-ratio: 210 / 297; }\n.slide[data-size=\"a4-landscape\"] { width: 100%; height: auto; aspect-ratio: 297 / 210; }\n.slide[data-size=\"letter-portrait\"] { width: 100%; height: auto; aspect-ratio: 8.5 / 11; }\n.slide[data-size=\"letter-landscape\"] { width: 100%; height: auto; aspect-ratio: 11 / 8.5; }\n.slide[data-size=\"linkedin-banner\"] { width: 100%; height: auto; aspect-ratio: 1584 / 396; }\n`.trim();\n\nconst printSizeStyles = `\n@media print {\n .slide[data-size=\"a4-portrait\"] { width: 210mm; height: 297mm; }\n @page { size: A4 portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"a4-landscape\"] { width: 297mm; height: 210mm; }\n @page { size: A4 landscape; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-portrait\"] { width: 8.5in; height: 11in; }\n @page { size: Letter portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-landscape\"] { width: 11in; height: 8.5in; }\n @page { size: Letter landscape; margin: 0; }\n}\n`.trim();\n\nconst hoverableSelector = [\n '[data-node-type]:not([data-node-type=\"slide\"])',\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"blockquote\",\n \"ul\",\n \"ol\",\n \"li\",\n \"pre\",\n \"figure\",\n \"table\",\n].join(\", \");\n\nconst hoverOutlineStyles = `\n.slide[data-hover-outline=\"on\"]\n :where(${hoverableSelector}):hover:not(:has(:hover)) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n\n/* Cascade: when hovering a container, outline its descendant blocks too */\n.slide[data-hover-outline=\"on\"][data-hover-outline-cascade=\"on\"]\n :where(${hoverableSelector}):hover\n :where(${hoverableSelector}) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n`.trim();\n\nexport interface SlideOptions {\n /**\n * The HTML attributes for a slide node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>;\n /**\n * Whether to inject CSS styles\n * @default true\n */\n injectCSS: boolean;\n /**\n * Render mode for sizing\n * - fixed: width/height set from size registry (mm/in/px)\n * - dynamic: width:100% with preserved aspect ratio\n * @default 'fixed'\n */\n renderMode: \"fixed\" | \"dynamic\";\n /**\n * Default size applied when attrs.size is absent\n * @default '16x9'\n */\n defaultSize: \"16x9\" | \"4x3\" | \"a4-portrait\" | \"a4-landscape\" | \"letter-portrait\" | \"letter-landscape\" | \"linkedin-banner\";\n /**\n * Scale factor for fixed-size slides.\n * Only applies when renderMode is 'fixed'.\n * Helpful for shrinking/expanding fixed previews without changing size presets.\n * @default 1\n */\n scale: number;\n /**\n * Inject @media print/@page CSS for paper sizes\n * @default true\n */\n injectPrintCSS: boolean;\n /**\n * Content Security Policy nonce\n */\n injectNonce?: string;\n /**\n * Hover outline highlight for nodes within a slide.\n * - false: disabled (default)\n * - true: enabled with defaults\n * - object: enabled with overrides\n */\n hoverOutline:\n | false\n | true\n | {\n color?: string;\n width?: string;\n offset?: string;\n };\n /**\n * When enabled, hovering a container outlines its descendant blocks too.\n * @default false\n */\n hoverOutlineCascade?: boolean;\n}\n\nconst SlidePluginKey = new PluginKey(\"slide\");\n\nexport const Slide = Node.create<SlideOptions>({\n name: \"slide\",\n isolating: true,\n content: \"row+\",\n\n group: \"slide\",\n\n defining: true,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n injectCSS: true,\n renderMode: \"fixed\",\n defaultSize: \"16x9\",\n scale: 1,\n injectPrintCSS: true,\n injectNonce: undefined,\n hoverOutline: false,\n hoverOutlineCascade: false,\n };\n },\n\n addAttributes() {\n return {\n size: {\n default: this.options.defaultSize,\n parseHTML: (element) => element.getAttribute(\"data-size\") || this.options.defaultSize,\n renderHTML: (attributes) => {\n if (!attributes.size) {\n return { \"data-size\": this.options.defaultSize };\n }\n return { \"data-size\": attributes.size };\n },\n },\n className: {\n default: \"\",\n },\n id: {\n default: null,\n },\n backgroundMode: {\n default: \"none\",\n },\n backgroundColor: {\n default: null,\n },\n backgroundImage: {\n default: null,\n },\n backgroundOverlayColor: {\n default: null,\n },\n backgroundOverlayOpacity: {\n default: null,\n },\n };\n },\n\n parseHTML() {\n return [{ tag: \"div.slide\" }];\n },\n\n renderHTML({ HTMLAttributes }) {\n const merged = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes);\n const {\n backgroundMode,\n backgroundColor,\n backgroundImage,\n backgroundOverlayColor,\n backgroundOverlayOpacity,\n ...rest\n } = merged;\n\n const styleParts: string[] = [];\n\n const hoverEnabled = this.options.hoverOutline !== false;\n const hoverConfig =\n this.options.hoverOutline === true ? {} : (this.options.hoverOutline || {});\n const hoverCascade = !!this.options.hoverOutlineCascade;\n\n if (backgroundColor) {\n styleParts.push(`--slide-bg-color: ${backgroundColor}`);\n }\n\n if (backgroundImage) {\n const escaped = String(backgroundImage).replace(/\"/g, '\\\\\"');\n styleParts.push(`background-image: url(\"${escaped}\")`);\n }\n\n if (backgroundOverlayColor) {\n styleParts.push(`--slide-bg-overlay-color: ${backgroundOverlayColor}`);\n }\n\n if (backgroundOverlayOpacity != null) {\n styleParts.push(`--slide-bg-overlay-opacity: ${backgroundOverlayOpacity}`);\n }\n\n if (hoverEnabled) {\n const {\n color = \"rgba(59, 130, 246, 0.65)\",\n width = \"1.5px\",\n offset = \"4px\",\n } = hoverConfig;\n styleParts.push(`--slide-hover-outline-color: ${color}`);\n styleParts.push(`--slide-hover-outline-width: ${width}`);\n styleParts.push(`--slide-hover-outline-offset: ${offset}`);\n }\n\n const style = [rest.style, styleParts.join(\"; \")].filter(Boolean).join(\"; \");\n\n const className = [rest.class, rest.className, \"slide\"].filter(Boolean).join(\" \");\n\n delete (rest as any).className;\n delete (rest as any).class;\n\n return [\n \"div\",\n {\n ...rest,\n class: className || \"slide\",\n \"data-node-type\": \"slide\",\n \"data-bg-mode\": backgroundMode || \"none\",\n \"data-hover-outline\": hoverEnabled ? \"on\" : undefined,\n \"data-hover-outline-cascade\": hoverCascade ? \"on\" : undefined,\n style: style || undefined,\n },\n 0,\n ];\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: SlidePluginKey,\n state: {\n init: () => {\n // Warn if scale is set but renderMode is not 'fixed'\n if (this.options.scale !== 1 && this.options.renderMode !== \"fixed\") {\n console.warn(\n `[Slide] The 'scale' option only applies when renderMode is 'fixed'. ` +\n `Current renderMode is '${this.options.renderMode}', so the scale value of ${this.options.scale} will be ignored.`\n );\n }\n\n if (this.options.injectCSS && typeof document !== \"undefined\") {\n createStyleTag(slideStyles, this.options.injectNonce, \"slide\");\n const sizingCss =\n this.options.renderMode === \"dynamic\"\n ? dynamicSizeStyles\n : getFixedSizeStyles(this.options.scale);\n if (sizingCss) {\n createStyleTag(sizingCss, this.options.injectNonce, \"slide-sizes\");\n }\n if (this.options.injectPrintCSS) {\n const printCss = printSizeStyles;\n if (printCss) {\n createStyleTag(printCss, this.options.injectNonce, \"slide-print\");\n }\n }\n if (this.options.hoverOutline !== false) {\n createStyleTag(\n hoverOutlineStyles,\n this.options.injectNonce,\n \"slide-hover-outline\"\n );\n }\n }\n return {};\n },\n apply: (_tr, pluginState: Record<string, never>) => pluginState,\n },\n }),\n ];\n },\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/slide.ts"],"sourcesContent":["export { Slide } from \"./slide\";\nexport type { SlideOptions } from \"./slide\";\n","import { Node, mergeAttributes, createStyleTag } from \"@blockslides/core\";\nimport { Plugin, PluginKey } from \"@blockslides/pm/state\";\n\nconst slideStyles = `\n.slide {\n position: relative;\n height: var(--slide-height, 100%);\n min-height: var(--slide-min-height, 250px);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--slide-bg);\n border-radius: var(--slide-border-radius);\n box-shadow: var(--slide-shadow);\n margin-bottom: var(--slide-margin-bottom);\n}\n\n/* Background helpers (driven by data attributes + inline CSS vars) */\n.slide[data-bg-mode=\"color\"] {\n background-color: var(--slide-bg-color, var(--slide-bg));\n}\n\n.slide[data-bg-mode=\"image\"],\n.slide[data-bg-mode=\"imageOverlay\"] {\n background-size: cover;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.slide[data-bg-mode=\"imageOverlay\"]::before {\n content: \"\";\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-color: var(--slide-bg-overlay-color, #000);\n opacity: var(--slide-bg-overlay-opacity, 0.35);\n}\n\n \n/* When multiple .column elements are adjacent siblings in a slide, */\n/* they automatically form a horizontal row layout. */\n\n\n/* Adjacent columns: make them display side-by-side */\n.slide > .column + .column {\n /* Column that follows another column */\n}\n\n/* Wrapper div for groups of columns to enable flex-row layout */\n.slide > .column-group {\n display: flex;\n flex-direction: row;\n flex: 0 0 auto;\n min-height: 0;\n gap: var(--slide-column-gap, 16px);\n}\n\n/* When fill is true, the group should take remaining space */\n.slide > .column-group[data-fill=\"true\"] {\n flex: 1 1 auto;\n}\n\n/* Columns within a group share space equally by default */\n.slide > .column-group > .column {\n flex: 1;\n min-width: 0;\n}\n`;\n\nconst getFixedSizeStyles = (scale: number) => `\n.slide { --slide-scale: ${scale}; }\n.slide[data-size=\"16x9\"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }\n.slide[data-size=\"4x3\"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }\n.slide[data-size=\"a4-portrait\"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }\n.slide[data-size=\"a4-landscape\"] { width: calc(297mm * var(--slide-scale)); height: calc(210mm * var(--slide-scale)); }\n.slide[data-size=\"letter-portrait\"] { width: calc(8.5in * var(--slide-scale)); height: calc(11in * var(--slide-scale)); }\n.slide[data-size=\"letter-landscape\"] { width: calc(11in * var(--slide-scale)); height: calc(8.5in * var(--slide-scale)); }\n.slide[data-size=\"linkedin-banner\"] { width: calc(1584px * var(--slide-scale)); height: calc(396px * var(--slide-scale)); }\n`.trim();\n\nconst dynamicSizeStyles = `\n.slide[data-size=\"16x9\"] { width: 100%; height: auto; aspect-ratio: 16 / 9; }\n.slide[data-size=\"4x3\"] { width: 100%; height: auto; aspect-ratio: 4 / 3; }\n.slide[data-size=\"a4-portrait\"] { width: 100%; height: auto; aspect-ratio: 210 / 297; }\n.slide[data-size=\"a4-landscape\"] { width: 100%; height: auto; aspect-ratio: 297 / 210; }\n.slide[data-size=\"letter-portrait\"] { width: 100%; height: auto; aspect-ratio: 8.5 / 11; }\n.slide[data-size=\"letter-landscape\"] { width: 100%; height: auto; aspect-ratio: 11 / 8.5; }\n.slide[data-size=\"linkedin-banner\"] { width: 100%; height: auto; aspect-ratio: 1584 / 396; }\n`.trim();\n\nconst printSizeStyles = `\n@media print {\n .slide[data-size=\"a4-portrait\"] { width: 210mm; height: 297mm; }\n @page { size: A4 portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"a4-landscape\"] { width: 297mm; height: 210mm; }\n @page { size: A4 landscape; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-portrait\"] { width: 8.5in; height: 11in; }\n @page { size: Letter portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-landscape\"] { width: 11in; height: 8.5in; }\n @page { size: Letter landscape; margin: 0; }\n}\n`.trim();\n\nconst hoverableSelector = [\n '[data-node-type]:not([data-node-type=\"slide\"])',\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"blockquote\",\n \"ul\",\n \"ol\",\n \"li\",\n \"pre\",\n \"figure\",\n \"table\",\n].join(\", \");\n\nconst hoverOutlineStyles = `\n.slide[data-hover-outline=\"on\"]\n :where(${hoverableSelector}):hover:not(:has(:hover)) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n\n/* Cascade: when hovering a container, outline its descendant blocks too */\n.slide[data-hover-outline=\"on\"][data-hover-outline-cascade=\"on\"]\n :where(${hoverableSelector}):hover\n :where(${hoverableSelector}) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n`.trim();\n\nexport interface SlideOptions {\n /**\n * The HTML attributes for a slide node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>;\n /**\n * Whether to inject CSS styles\n * @default true\n */\n injectCSS: boolean;\n /**\n * Render mode for sizing\n * - fixed: width/height set from size registry (mm/in/px)\n * - dynamic: width:100% with preserved aspect ratio\n * @default 'fixed'\n */\n renderMode: \"fixed\" | \"dynamic\";\n /**\n * Default size applied when attrs.size is absent\n * @default '16x9'\n */\n defaultSize: \"16x9\" | \"4x3\" | \"a4-portrait\" | \"a4-landscape\" | \"letter-portrait\" | \"letter-landscape\" | \"linkedin-banner\";\n /**\n * Scale factor for fixed-size slides.\n * Only applies when renderMode is 'fixed'.\n * Helpful for shrinking/expanding fixed previews without changing size presets.\n * @default 1\n */\n scale: number;\n /**\n * Inject @media print/@page CSS for paper sizes\n * @default true\n */\n injectPrintCSS: boolean;\n /**\n * Content Security Policy nonce\n */\n injectNonce?: string;\n /**\n * Hover outline highlight for nodes within a slide.\n * - false: disabled (default)\n * - true: enabled with defaults\n * - object: enabled with overrides\n */\n hoverOutline:\n | false\n | true\n | {\n color?: string;\n width?: string;\n offset?: string;\n };\n /**\n * When enabled, hovering a container outlines its descendant blocks too.\n * @default false\n */\n hoverOutlineCascade?: boolean;\n}\n\nconst SlidePluginKey = new PluginKey(\"slide\");\n\nexport const Slide = Node.create<SlideOptions>({\n name: \"slide\",\n isolating: true,\n content: \"block+\",\n\n group: \"slide\",\n\n defining: true,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n injectCSS: true,\n renderMode: \"fixed\",\n defaultSize: \"16x9\",\n scale: 1,\n injectPrintCSS: true,\n injectNonce: undefined,\n hoverOutline: false,\n hoverOutlineCascade: false,\n };\n },\n\n addAttributes() {\n return {\n size: {\n default: this.options.defaultSize,\n parseHTML: (element) => element.getAttribute(\"data-size\") || this.options.defaultSize,\n renderHTML: (attributes) => {\n if (!attributes.size) {\n return { \"data-size\": this.options.defaultSize };\n }\n return { \"data-size\": attributes.size };\n },\n },\n className: {\n default: \"\",\n },\n id: {\n default: null,\n },\n backgroundMode: {\n default: \"none\",\n },\n backgroundColor: {\n default: null,\n },\n backgroundImage: {\n default: null,\n },\n backgroundOverlayColor: {\n default: null,\n },\n backgroundOverlayOpacity: {\n default: null,\n },\n };\n },\n\n parseHTML() {\n return [{ tag: \"div.slide\" }];\n },\n\n renderHTML({ HTMLAttributes }) {\n const merged = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes);\n const {\n backgroundMode,\n backgroundColor,\n backgroundImage,\n backgroundOverlayColor,\n backgroundOverlayOpacity,\n ...rest\n } = merged;\n\n const styleParts: string[] = [];\n\n const hoverEnabled = this.options.hoverOutline !== false;\n const hoverConfig =\n this.options.hoverOutline === true ? {} : (this.options.hoverOutline || {});\n const hoverCascade = !!this.options.hoverOutlineCascade;\n\n if (backgroundColor) {\n styleParts.push(`--slide-bg-color: ${backgroundColor}`);\n }\n\n if (backgroundImage) {\n const escaped = String(backgroundImage).replace(/\"/g, '\\\\\"');\n styleParts.push(`background-image: url(\"${escaped}\")`);\n }\n\n if (backgroundOverlayColor) {\n styleParts.push(`--slide-bg-overlay-color: ${backgroundOverlayColor}`);\n }\n\n if (backgroundOverlayOpacity != null) {\n styleParts.push(`--slide-bg-overlay-opacity: ${backgroundOverlayOpacity}`);\n }\n\n if (hoverEnabled) {\n const {\n color = \"rgba(59, 130, 246, 0.65)\",\n width = \"1.5px\",\n offset = \"4px\",\n } = hoverConfig;\n styleParts.push(`--slide-hover-outline-color: ${color}`);\n styleParts.push(`--slide-hover-outline-width: ${width}`);\n styleParts.push(`--slide-hover-outline-offset: ${offset}`);\n }\n\n const style = [rest.style, styleParts.join(\"; \")].filter(Boolean).join(\"; \");\n\n const className = [rest.class, rest.className, \"slide\"].filter(Boolean).join(\" \");\n\n delete (rest as any).className;\n delete (rest as any).class;\n\n return [\n \"div\",\n {\n ...rest,\n class: className || \"slide\",\n \"data-node-type\": \"slide\",\n \"data-bg-mode\": backgroundMode || \"none\",\n \"data-hover-outline\": hoverEnabled ? \"on\" : undefined,\n \"data-hover-outline-cascade\": hoverCascade ? \"on\" : undefined,\n style: style || undefined,\n },\n 0,\n ];\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: SlidePluginKey,\n state: {\n init: () => {\n // Warn if scale is set but renderMode is not 'fixed'\n if (this.options.scale !== 1 && this.options.renderMode !== \"fixed\") {\n console.warn(\n `[Slide] The 'scale' option only applies when renderMode is 'fixed'. ` +\n `Current renderMode is '${this.options.renderMode}', so the scale value of ${this.options.scale} will be ignored.`\n );\n }\n\n if (this.options.injectCSS && typeof document !== \"undefined\") {\n createStyleTag(slideStyles, this.options.injectNonce, \"slide\");\n const sizingCss =\n this.options.renderMode === \"dynamic\"\n ? dynamicSizeStyles\n : getFixedSizeStyles(this.options.scale);\n if (sizingCss) {\n createStyleTag(sizingCss, this.options.injectNonce, \"slide-sizes\");\n }\n if (this.options.injectPrintCSS) {\n const printCss = printSizeStyles;\n if (printCss) {\n createStyleTag(printCss, this.options.injectNonce, \"slide-print\");\n }\n }\n if (this.options.hoverOutline !== false) {\n createStyleTag(\n hoverOutlineStyles,\n this.options.injectNonce,\n \"slide-hover-outline\"\n );\n }\n }\n return {};\n },\n apply: (_tr, pluginState: Record<string, never>) => pluginState,\n },\n }),\n ];\n },\n});\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -35,6 +35,36 @@ var slideStyles = `
|
|
|
35
35
|
background-color: var(--slide-bg-overlay-color, #000);
|
|
36
36
|
opacity: var(--slide-bg-overlay-opacity, 0.35);
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
/* When multiple .column elements are adjacent siblings in a slide, */
|
|
41
|
+
/* they automatically form a horizontal row layout. */
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
/* Adjacent columns: make them display side-by-side */
|
|
45
|
+
.slide > .column + .column {
|
|
46
|
+
/* Column that follows another column */
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Wrapper div for groups of columns to enable flex-row layout */
|
|
50
|
+
.slide > .column-group {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: row;
|
|
53
|
+
flex: 0 0 auto;
|
|
54
|
+
min-height: 0;
|
|
55
|
+
gap: var(--slide-column-gap, 16px);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* When fill is true, the group should take remaining space */
|
|
59
|
+
.slide > .column-group[data-fill="true"] {
|
|
60
|
+
flex: 1 1 auto;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Columns within a group share space equally by default */
|
|
64
|
+
.slide > .column-group > .column {
|
|
65
|
+
flex: 1;
|
|
66
|
+
min-width: 0;
|
|
67
|
+
}
|
|
38
68
|
`;
|
|
39
69
|
var getFixedSizeStyles = (scale) => `
|
|
40
70
|
.slide { --slide-scale: ${scale}; }
|
|
@@ -111,7 +141,7 @@ var SlidePluginKey = new PluginKey("slide");
|
|
|
111
141
|
var Slide = Node.create({
|
|
112
142
|
name: "slide",
|
|
113
143
|
isolating: true,
|
|
114
|
-
content: "
|
|
144
|
+
content: "block+",
|
|
115
145
|
group: "slide",
|
|
116
146
|
defining: true,
|
|
117
147
|
addOptions() {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/slide.ts"],"sourcesContent":["import { Node, mergeAttributes, createStyleTag } from \"@blockslides/core\";\nimport { Plugin, PluginKey } from \"@blockslides/pm/state\";\n\nconst slideStyles = `\n.slide {\n position: relative;\n height: var(--slide-height, 100%);\n min-height: var(--slide-min-height, 250px);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--slide-bg);\n border-radius: var(--slide-border-radius);\n box-shadow: var(--slide-shadow);\n margin-bottom: var(--slide-margin-bottom);\n}\n\n/* Background helpers (driven by data attributes + inline CSS vars) */\n.slide[data-bg-mode=\"color\"] {\n background-color: var(--slide-bg-color, var(--slide-bg));\n}\n\n.slide[data-bg-mode=\"image\"],\n.slide[data-bg-mode=\"imageOverlay\"] {\n background-size: cover;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.slide[data-bg-mode=\"imageOverlay\"]::before {\n content: \"\";\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-color: var(--slide-bg-overlay-color, #000);\n opacity: var(--slide-bg-overlay-opacity, 0.35);\n}\n`;\n\nconst getFixedSizeStyles = (scale: number) => `\n.slide { --slide-scale: ${scale}; }\n.slide[data-size=\"16x9\"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }\n.slide[data-size=\"4x3\"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }\n.slide[data-size=\"a4-portrait\"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }\n.slide[data-size=\"a4-landscape\"] { width: calc(297mm * var(--slide-scale)); height: calc(210mm * var(--slide-scale)); }\n.slide[data-size=\"letter-portrait\"] { width: calc(8.5in * var(--slide-scale)); height: calc(11in * var(--slide-scale)); }\n.slide[data-size=\"letter-landscape\"] { width: calc(11in * var(--slide-scale)); height: calc(8.5in * var(--slide-scale)); }\n.slide[data-size=\"linkedin-banner\"] { width: calc(1584px * var(--slide-scale)); height: calc(396px * var(--slide-scale)); }\n`.trim();\n\nconst dynamicSizeStyles = `\n.slide[data-size=\"16x9\"] { width: 100%; height: auto; aspect-ratio: 16 / 9; }\n.slide[data-size=\"4x3\"] { width: 100%; height: auto; aspect-ratio: 4 / 3; }\n.slide[data-size=\"a4-portrait\"] { width: 100%; height: auto; aspect-ratio: 210 / 297; }\n.slide[data-size=\"a4-landscape\"] { width: 100%; height: auto; aspect-ratio: 297 / 210; }\n.slide[data-size=\"letter-portrait\"] { width: 100%; height: auto; aspect-ratio: 8.5 / 11; }\n.slide[data-size=\"letter-landscape\"] { width: 100%; height: auto; aspect-ratio: 11 / 8.5; }\n.slide[data-size=\"linkedin-banner\"] { width: 100%; height: auto; aspect-ratio: 1584 / 396; }\n`.trim();\n\nconst printSizeStyles = `\n@media print {\n .slide[data-size=\"a4-portrait\"] { width: 210mm; height: 297mm; }\n @page { size: A4 portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"a4-landscape\"] { width: 297mm; height: 210mm; }\n @page { size: A4 landscape; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-portrait\"] { width: 8.5in; height: 11in; }\n @page { size: Letter portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-landscape\"] { width: 11in; height: 8.5in; }\n @page { size: Letter landscape; margin: 0; }\n}\n`.trim();\n\nconst hoverableSelector = [\n '[data-node-type]:not([data-node-type=\"slide\"])',\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"blockquote\",\n \"ul\",\n \"ol\",\n \"li\",\n \"pre\",\n \"figure\",\n \"table\",\n].join(\", \");\n\nconst hoverOutlineStyles = `\n.slide[data-hover-outline=\"on\"]\n :where(${hoverableSelector}):hover:not(:has(:hover)) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n\n/* Cascade: when hovering a container, outline its descendant blocks too */\n.slide[data-hover-outline=\"on\"][data-hover-outline-cascade=\"on\"]\n :where(${hoverableSelector}):hover\n :where(${hoverableSelector}) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n`.trim();\n\nexport interface SlideOptions {\n /**\n * The HTML attributes for a slide node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>;\n /**\n * Whether to inject CSS styles\n * @default true\n */\n injectCSS: boolean;\n /**\n * Render mode for sizing\n * - fixed: width/height set from size registry (mm/in/px)\n * - dynamic: width:100% with preserved aspect ratio\n * @default 'fixed'\n */\n renderMode: \"fixed\" | \"dynamic\";\n /**\n * Default size applied when attrs.size is absent\n * @default '16x9'\n */\n defaultSize: \"16x9\" | \"4x3\" | \"a4-portrait\" | \"a4-landscape\" | \"letter-portrait\" | \"letter-landscape\" | \"linkedin-banner\";\n /**\n * Scale factor for fixed-size slides.\n * Only applies when renderMode is 'fixed'.\n * Helpful for shrinking/expanding fixed previews without changing size presets.\n * @default 1\n */\n scale: number;\n /**\n * Inject @media print/@page CSS for paper sizes\n * @default true\n */\n injectPrintCSS: boolean;\n /**\n * Content Security Policy nonce\n */\n injectNonce?: string;\n /**\n * Hover outline highlight for nodes within a slide.\n * - false: disabled (default)\n * - true: enabled with defaults\n * - object: enabled with overrides\n */\n hoverOutline:\n | false\n | true\n | {\n color?: string;\n width?: string;\n offset?: string;\n };\n /**\n * When enabled, hovering a container outlines its descendant blocks too.\n * @default false\n */\n hoverOutlineCascade?: boolean;\n}\n\nconst SlidePluginKey = new PluginKey(\"slide\");\n\nexport const Slide = Node.create<SlideOptions>({\n name: \"slide\",\n isolating: true,\n content: \"row+\",\n\n group: \"slide\",\n\n defining: true,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n injectCSS: true,\n renderMode: \"fixed\",\n defaultSize: \"16x9\",\n scale: 1,\n injectPrintCSS: true,\n injectNonce: undefined,\n hoverOutline: false,\n hoverOutlineCascade: false,\n };\n },\n\n addAttributes() {\n return {\n size: {\n default: this.options.defaultSize,\n parseHTML: (element) => element.getAttribute(\"data-size\") || this.options.defaultSize,\n renderHTML: (attributes) => {\n if (!attributes.size) {\n return { \"data-size\": this.options.defaultSize };\n }\n return { \"data-size\": attributes.size };\n },\n },\n className: {\n default: \"\",\n },\n id: {\n default: null,\n },\n backgroundMode: {\n default: \"none\",\n },\n backgroundColor: {\n default: null,\n },\n backgroundImage: {\n default: null,\n },\n backgroundOverlayColor: {\n default: null,\n },\n backgroundOverlayOpacity: {\n default: null,\n },\n };\n },\n\n parseHTML() {\n return [{ tag: \"div.slide\" }];\n },\n\n renderHTML({ HTMLAttributes }) {\n const merged = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes);\n const {\n backgroundMode,\n backgroundColor,\n backgroundImage,\n backgroundOverlayColor,\n backgroundOverlayOpacity,\n ...rest\n } = merged;\n\n const styleParts: string[] = [];\n\n const hoverEnabled = this.options.hoverOutline !== false;\n const hoverConfig =\n this.options.hoverOutline === true ? {} : (this.options.hoverOutline || {});\n const hoverCascade = !!this.options.hoverOutlineCascade;\n\n if (backgroundColor) {\n styleParts.push(`--slide-bg-color: ${backgroundColor}`);\n }\n\n if (backgroundImage) {\n const escaped = String(backgroundImage).replace(/\"/g, '\\\\\"');\n styleParts.push(`background-image: url(\"${escaped}\")`);\n }\n\n if (backgroundOverlayColor) {\n styleParts.push(`--slide-bg-overlay-color: ${backgroundOverlayColor}`);\n }\n\n if (backgroundOverlayOpacity != null) {\n styleParts.push(`--slide-bg-overlay-opacity: ${backgroundOverlayOpacity}`);\n }\n\n if (hoverEnabled) {\n const {\n color = \"rgba(59, 130, 246, 0.65)\",\n width = \"1.5px\",\n offset = \"4px\",\n } = hoverConfig;\n styleParts.push(`--slide-hover-outline-color: ${color}`);\n styleParts.push(`--slide-hover-outline-width: ${width}`);\n styleParts.push(`--slide-hover-outline-offset: ${offset}`);\n }\n\n const style = [rest.style, styleParts.join(\"; \")].filter(Boolean).join(\"; \");\n\n const className = [rest.class, rest.className, \"slide\"].filter(Boolean).join(\" \");\n\n delete (rest as any).className;\n delete (rest as any).class;\n\n return [\n \"div\",\n {\n ...rest,\n class: className || \"slide\",\n \"data-node-type\": \"slide\",\n \"data-bg-mode\": backgroundMode || \"none\",\n \"data-hover-outline\": hoverEnabled ? \"on\" : undefined,\n \"data-hover-outline-cascade\": hoverCascade ? \"on\" : undefined,\n style: style || undefined,\n },\n 0,\n ];\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: SlidePluginKey,\n state: {\n init: () => {\n // Warn if scale is set but renderMode is not 'fixed'\n if (this.options.scale !== 1 && this.options.renderMode !== \"fixed\") {\n console.warn(\n `[Slide] The 'scale' option only applies when renderMode is 'fixed'. ` +\n `Current renderMode is '${this.options.renderMode}', so the scale value of ${this.options.scale} will be ignored.`\n );\n }\n\n if (this.options.injectCSS && typeof document !== \"undefined\") {\n createStyleTag(slideStyles, this.options.injectNonce, \"slide\");\n const sizingCss =\n this.options.renderMode === \"dynamic\"\n ? dynamicSizeStyles\n : getFixedSizeStyles(this.options.scale);\n if (sizingCss) {\n createStyleTag(sizingCss, this.options.injectNonce, \"slide-sizes\");\n }\n if (this.options.injectPrintCSS) {\n const printCss = printSizeStyles;\n if (printCss) {\n createStyleTag(printCss, this.options.injectNonce, \"slide-print\");\n }\n }\n if (this.options.hoverOutline !== false) {\n createStyleTag(\n hoverOutlineStyles,\n this.options.injectNonce,\n \"slide-hover-outline\"\n );\n }\n }\n return {};\n },\n apply: (_tr, pluginState: Record<string, never>) => pluginState,\n },\n }),\n ];\n },\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/slide.ts"],"sourcesContent":["import { Node, mergeAttributes, createStyleTag } from \"@blockslides/core\";\nimport { Plugin, PluginKey } from \"@blockslides/pm/state\";\n\nconst slideStyles = `\n.slide {\n position: relative;\n height: var(--slide-height, 100%);\n min-height: var(--slide-min-height, 250px);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n background-color: var(--slide-bg);\n border-radius: var(--slide-border-radius);\n box-shadow: var(--slide-shadow);\n margin-bottom: var(--slide-margin-bottom);\n}\n\n/* Background helpers (driven by data attributes + inline CSS vars) */\n.slide[data-bg-mode=\"color\"] {\n background-color: var(--slide-bg-color, var(--slide-bg));\n}\n\n.slide[data-bg-mode=\"image\"],\n.slide[data-bg-mode=\"imageOverlay\"] {\n background-size: cover;\n background-repeat: no-repeat;\n background-position: center;\n}\n\n.slide[data-bg-mode=\"imageOverlay\"]::before {\n content: \"\";\n position: absolute;\n inset: 0;\n pointer-events: none;\n background-color: var(--slide-bg-overlay-color, #000);\n opacity: var(--slide-bg-overlay-opacity, 0.35);\n}\n\n \n/* When multiple .column elements are adjacent siblings in a slide, */\n/* they automatically form a horizontal row layout. */\n\n\n/* Adjacent columns: make them display side-by-side */\n.slide > .column + .column {\n /* Column that follows another column */\n}\n\n/* Wrapper div for groups of columns to enable flex-row layout */\n.slide > .column-group {\n display: flex;\n flex-direction: row;\n flex: 0 0 auto;\n min-height: 0;\n gap: var(--slide-column-gap, 16px);\n}\n\n/* When fill is true, the group should take remaining space */\n.slide > .column-group[data-fill=\"true\"] {\n flex: 1 1 auto;\n}\n\n/* Columns within a group share space equally by default */\n.slide > .column-group > .column {\n flex: 1;\n min-width: 0;\n}\n`;\n\nconst getFixedSizeStyles = (scale: number) => `\n.slide { --slide-scale: ${scale}; }\n.slide[data-size=\"16x9\"] { width: calc(1920px * var(--slide-scale)); height: calc(1080px * var(--slide-scale)); }\n.slide[data-size=\"4x3\"] { width: calc(1600px * var(--slide-scale)); height: calc(1200px * var(--slide-scale)); }\n.slide[data-size=\"a4-portrait\"] { width: calc(210mm * var(--slide-scale)); height: calc(297mm * var(--slide-scale)); }\n.slide[data-size=\"a4-landscape\"] { width: calc(297mm * var(--slide-scale)); height: calc(210mm * var(--slide-scale)); }\n.slide[data-size=\"letter-portrait\"] { width: calc(8.5in * var(--slide-scale)); height: calc(11in * var(--slide-scale)); }\n.slide[data-size=\"letter-landscape\"] { width: calc(11in * var(--slide-scale)); height: calc(8.5in * var(--slide-scale)); }\n.slide[data-size=\"linkedin-banner\"] { width: calc(1584px * var(--slide-scale)); height: calc(396px * var(--slide-scale)); }\n`.trim();\n\nconst dynamicSizeStyles = `\n.slide[data-size=\"16x9\"] { width: 100%; height: auto; aspect-ratio: 16 / 9; }\n.slide[data-size=\"4x3\"] { width: 100%; height: auto; aspect-ratio: 4 / 3; }\n.slide[data-size=\"a4-portrait\"] { width: 100%; height: auto; aspect-ratio: 210 / 297; }\n.slide[data-size=\"a4-landscape\"] { width: 100%; height: auto; aspect-ratio: 297 / 210; }\n.slide[data-size=\"letter-portrait\"] { width: 100%; height: auto; aspect-ratio: 8.5 / 11; }\n.slide[data-size=\"letter-landscape\"] { width: 100%; height: auto; aspect-ratio: 11 / 8.5; }\n.slide[data-size=\"linkedin-banner\"] { width: 100%; height: auto; aspect-ratio: 1584 / 396; }\n`.trim();\n\nconst printSizeStyles = `\n@media print {\n .slide[data-size=\"a4-portrait\"] { width: 210mm; height: 297mm; }\n @page { size: A4 portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"a4-landscape\"] { width: 297mm; height: 210mm; }\n @page { size: A4 landscape; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-portrait\"] { width: 8.5in; height: 11in; }\n @page { size: Letter portrait; margin: 0; }\n}\n@media print {\n .slide[data-size=\"letter-landscape\"] { width: 11in; height: 8.5in; }\n @page { size: Letter landscape; margin: 0; }\n}\n`.trim();\n\nconst hoverableSelector = [\n '[data-node-type]:not([data-node-type=\"slide\"])',\n \"p\",\n \"h1\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"h5\",\n \"h6\",\n \"blockquote\",\n \"ul\",\n \"ol\",\n \"li\",\n \"pre\",\n \"figure\",\n \"table\",\n].join(\", \");\n\nconst hoverOutlineStyles = `\n.slide[data-hover-outline=\"on\"]\n :where(${hoverableSelector}):hover:not(:has(:hover)) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n\n/* Cascade: when hovering a container, outline its descendant blocks too */\n.slide[data-hover-outline=\"on\"][data-hover-outline-cascade=\"on\"]\n :where(${hoverableSelector}):hover\n :where(${hoverableSelector}) {\n outline: var(--slide-hover-outline-width, 1.5px) solid var(--slide-hover-outline-color, rgba(59, 130, 246, 0.65));\n outline-offset: var(--slide-hover-outline-offset, 4px);\n transition: outline-color 120ms ease, outline-width 120ms ease;\n}\n`.trim();\n\nexport interface SlideOptions {\n /**\n * The HTML attributes for a slide node.\n * @default {}\n * @example { class: 'foo' }\n */\n HTMLAttributes: Record<string, any>;\n /**\n * Whether to inject CSS styles\n * @default true\n */\n injectCSS: boolean;\n /**\n * Render mode for sizing\n * - fixed: width/height set from size registry (mm/in/px)\n * - dynamic: width:100% with preserved aspect ratio\n * @default 'fixed'\n */\n renderMode: \"fixed\" | \"dynamic\";\n /**\n * Default size applied when attrs.size is absent\n * @default '16x9'\n */\n defaultSize: \"16x9\" | \"4x3\" | \"a4-portrait\" | \"a4-landscape\" | \"letter-portrait\" | \"letter-landscape\" | \"linkedin-banner\";\n /**\n * Scale factor for fixed-size slides.\n * Only applies when renderMode is 'fixed'.\n * Helpful for shrinking/expanding fixed previews without changing size presets.\n * @default 1\n */\n scale: number;\n /**\n * Inject @media print/@page CSS for paper sizes\n * @default true\n */\n injectPrintCSS: boolean;\n /**\n * Content Security Policy nonce\n */\n injectNonce?: string;\n /**\n * Hover outline highlight for nodes within a slide.\n * - false: disabled (default)\n * - true: enabled with defaults\n * - object: enabled with overrides\n */\n hoverOutline:\n | false\n | true\n | {\n color?: string;\n width?: string;\n offset?: string;\n };\n /**\n * When enabled, hovering a container outlines its descendant blocks too.\n * @default false\n */\n hoverOutlineCascade?: boolean;\n}\n\nconst SlidePluginKey = new PluginKey(\"slide\");\n\nexport const Slide = Node.create<SlideOptions>({\n name: \"slide\",\n isolating: true,\n content: \"block+\",\n\n group: \"slide\",\n\n defining: true,\n\n addOptions() {\n return {\n HTMLAttributes: {},\n injectCSS: true,\n renderMode: \"fixed\",\n defaultSize: \"16x9\",\n scale: 1,\n injectPrintCSS: true,\n injectNonce: undefined,\n hoverOutline: false,\n hoverOutlineCascade: false,\n };\n },\n\n addAttributes() {\n return {\n size: {\n default: this.options.defaultSize,\n parseHTML: (element) => element.getAttribute(\"data-size\") || this.options.defaultSize,\n renderHTML: (attributes) => {\n if (!attributes.size) {\n return { \"data-size\": this.options.defaultSize };\n }\n return { \"data-size\": attributes.size };\n },\n },\n className: {\n default: \"\",\n },\n id: {\n default: null,\n },\n backgroundMode: {\n default: \"none\",\n },\n backgroundColor: {\n default: null,\n },\n backgroundImage: {\n default: null,\n },\n backgroundOverlayColor: {\n default: null,\n },\n backgroundOverlayOpacity: {\n default: null,\n },\n };\n },\n\n parseHTML() {\n return [{ tag: \"div.slide\" }];\n },\n\n renderHTML({ HTMLAttributes }) {\n const merged = mergeAttributes(this.options.HTMLAttributes, HTMLAttributes);\n const {\n backgroundMode,\n backgroundColor,\n backgroundImage,\n backgroundOverlayColor,\n backgroundOverlayOpacity,\n ...rest\n } = merged;\n\n const styleParts: string[] = [];\n\n const hoverEnabled = this.options.hoverOutline !== false;\n const hoverConfig =\n this.options.hoverOutline === true ? {} : (this.options.hoverOutline || {});\n const hoverCascade = !!this.options.hoverOutlineCascade;\n\n if (backgroundColor) {\n styleParts.push(`--slide-bg-color: ${backgroundColor}`);\n }\n\n if (backgroundImage) {\n const escaped = String(backgroundImage).replace(/\"/g, '\\\\\"');\n styleParts.push(`background-image: url(\"${escaped}\")`);\n }\n\n if (backgroundOverlayColor) {\n styleParts.push(`--slide-bg-overlay-color: ${backgroundOverlayColor}`);\n }\n\n if (backgroundOverlayOpacity != null) {\n styleParts.push(`--slide-bg-overlay-opacity: ${backgroundOverlayOpacity}`);\n }\n\n if (hoverEnabled) {\n const {\n color = \"rgba(59, 130, 246, 0.65)\",\n width = \"1.5px\",\n offset = \"4px\",\n } = hoverConfig;\n styleParts.push(`--slide-hover-outline-color: ${color}`);\n styleParts.push(`--slide-hover-outline-width: ${width}`);\n styleParts.push(`--slide-hover-outline-offset: ${offset}`);\n }\n\n const style = [rest.style, styleParts.join(\"; \")].filter(Boolean).join(\"; \");\n\n const className = [rest.class, rest.className, \"slide\"].filter(Boolean).join(\" \");\n\n delete (rest as any).className;\n delete (rest as any).class;\n\n return [\n \"div\",\n {\n ...rest,\n class: className || \"slide\",\n \"data-node-type\": \"slide\",\n \"data-bg-mode\": backgroundMode || \"none\",\n \"data-hover-outline\": hoverEnabled ? \"on\" : undefined,\n \"data-hover-outline-cascade\": hoverCascade ? \"on\" : undefined,\n style: style || undefined,\n },\n 0,\n ];\n },\n\n addProseMirrorPlugins() {\n return [\n new Plugin({\n key: SlidePluginKey,\n state: {\n init: () => {\n // Warn if scale is set but renderMode is not 'fixed'\n if (this.options.scale !== 1 && this.options.renderMode !== \"fixed\") {\n console.warn(\n `[Slide] The 'scale' option only applies when renderMode is 'fixed'. ` +\n `Current renderMode is '${this.options.renderMode}', so the scale value of ${this.options.scale} will be ignored.`\n );\n }\n\n if (this.options.injectCSS && typeof document !== \"undefined\") {\n createStyleTag(slideStyles, this.options.injectNonce, \"slide\");\n const sizingCss =\n this.options.renderMode === \"dynamic\"\n ? dynamicSizeStyles\n : getFixedSizeStyles(this.options.scale);\n if (sizingCss) {\n createStyleTag(sizingCss, this.options.injectNonce, \"slide-sizes\");\n }\n if (this.options.injectPrintCSS) {\n const printCss = printSizeStyles;\n if (printCss) {\n createStyleTag(printCss, this.options.injectNonce, \"slide-print\");\n }\n }\n if (this.options.hoverOutline !== false) {\n createStyleTag(\n hoverOutlineStyles,\n this.options.injectNonce,\n \"slide-hover-outline\"\n );\n }\n }\n return {};\n },\n apply: (_tr, pluginState: Record<string, never>) => pluginState,\n },\n }),\n ];\n },\n});\n"],"mappings":";AAAA,SAAS,MAAM,iBAAiB,sBAAsB;AACtD,SAAS,QAAQ,iBAAiB;AAElC,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkEpB,IAAM,qBAAqB,CAAC,UAAkB;AAAA,0BACpB,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ7B,KAAK;AAEP,IAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQxB,KAAK;AAEP,IAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBtB,KAAK;AAEP,IAAM,oBAAoB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,IAAI;AAEX,IAAM,qBAAqB;AAAA;AAAA,WAEhB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAQjB,iBAAiB;AAAA,WACjB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA,EAK1B,KAAK;AA+DP,IAAM,iBAAiB,IAAI,UAAU,OAAO;AAErC,IAAM,QAAQ,KAAK,OAAqB;AAAA,EAC7C,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AAAA,EAET,OAAO;AAAA,EAEP,UAAU;AAAA,EAEV,aAAa;AACX,WAAO;AAAA,MACL,gBAAgB,CAAC;AAAA,MACjB,WAAW;AAAA,MACX,YAAY;AAAA,MACZ,aAAa;AAAA,MACb,OAAO;AAAA,MACP,gBAAgB;AAAA,MAChB,aAAa;AAAA,MACb,cAAc;AAAA,MACd,qBAAqB;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,gBAAgB;AACd,WAAO;AAAA,MACL,MAAM;AAAA,QACJ,SAAS,KAAK,QAAQ;AAAA,QACtB,WAAW,CAAC,YAAY,QAAQ,aAAa,WAAW,KAAK,KAAK,QAAQ;AAAA,QAC1E,YAAY,CAAC,eAAe;AAC1B,cAAI,CAAC,WAAW,MAAM;AACpB,mBAAO,EAAE,aAAa,KAAK,QAAQ,YAAY;AAAA,UACjD;AACA,iBAAO,EAAE,aAAa,WAAW,KAAK;AAAA,QACxC;AAAA,MACF;AAAA,MACA,WAAW;AAAA,QACT,SAAS;AAAA,MACX;AAAA,MACA,IAAI;AAAA,QACF,SAAS;AAAA,MACX;AAAA,MACA,gBAAgB;AAAA,QACd,SAAS;AAAA,MACX;AAAA,MACA,iBAAiB;AAAA,QACf,SAAS;AAAA,MACX;AAAA,MACA,iBAAiB;AAAA,QACf,SAAS;AAAA,MACX;AAAA,MACA,wBAAwB;AAAA,QACtB,SAAS;AAAA,MACX;AAAA,MACA,0BAA0B;AAAA,QACxB,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EAEA,YAAY;AACV,WAAO,CAAC,EAAE,KAAK,YAAY,CAAC;AAAA,EAC9B;AAAA,EAEA,WAAW,EAAE,eAAe,GAAG;AAC7B,UAAM,SAAS,gBAAgB,KAAK,QAAQ,gBAAgB,cAAc;AAC1E,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,GAAG;AAAA,IACL,IAAI;AAEJ,UAAM,aAAuB,CAAC;AAE9B,UAAM,eAAe,KAAK,QAAQ,iBAAiB;AACnD,UAAM,cACJ,KAAK,QAAQ,iBAAiB,OAAO,CAAC,IAAK,KAAK,QAAQ,gBAAgB,CAAC;AAC3E,UAAM,eAAe,CAAC,CAAC,KAAK,QAAQ;AAEpC,QAAI,iBAAiB;AACnB,iBAAW,KAAK,qBAAqB,eAAe,EAAE;AAAA,IACxD;AAEA,QAAI,iBAAiB;AACnB,YAAM,UAAU,OAAO,eAAe,EAAE,QAAQ,MAAM,KAAK;AAC3D,iBAAW,KAAK,0BAA0B,OAAO,IAAI;AAAA,IACvD;AAEA,QAAI,wBAAwB;AAC1B,iBAAW,KAAK,6BAA6B,sBAAsB,EAAE;AAAA,IACvE;AAEA,QAAI,4BAA4B,MAAM;AACpC,iBAAW,KAAK,+BAA+B,wBAAwB,EAAE;AAAA,IAC3E;AAEA,QAAI,cAAc;AAChB,YAAM;AAAA,QACJ,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,SAAS;AAAA,MACX,IAAI;AACJ,iBAAW,KAAK,gCAAgC,KAAK,EAAE;AACvD,iBAAW,KAAK,gCAAgC,KAAK,EAAE;AACvD,iBAAW,KAAK,iCAAiC,MAAM,EAAE;AAAA,IAC3D;AAEA,UAAM,QAAQ,CAAC,KAAK,OAAO,WAAW,KAAK,IAAI,CAAC,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;AAE3E,UAAM,YAAY,CAAC,KAAK,OAAO,KAAK,WAAW,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAEhF,WAAQ,KAAa;AACrB,WAAQ,KAAa;AAErB,WAAO;AAAA,MACL;AAAA,MACA;AAAA,QACE,GAAG;AAAA,QACH,OAAO,aAAa;AAAA,QACpB,kBAAkB;AAAA,QAClB,gBAAgB,kBAAkB;AAAA,QAClC,sBAAsB,eAAe,OAAO;AAAA,QAC5C,8BAA8B,eAAe,OAAO;AAAA,QACpD,OAAO,SAAS;AAAA,MAClB;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,wBAAwB;AACtB,WAAO;AAAA,MACL,IAAI,OAAO;AAAA,QACT,KAAK;AAAA,QACL,OAAO;AAAA,UACL,MAAM,MAAM;AAEV,gBAAI,KAAK,QAAQ,UAAU,KAAK,KAAK,QAAQ,eAAe,SAAS;AACnE,sBAAQ;AAAA,gBACN,8FAC0B,KAAK,QAAQ,UAAU,4BAA4B,KAAK,QAAQ,KAAK;AAAA,cACjG;AAAA,YACF;AAEA,gBAAI,KAAK,QAAQ,aAAa,OAAO,aAAa,aAAa;AAC7D,6BAAe,aAAa,KAAK,QAAQ,aAAa,OAAO;AAC7D,oBAAM,YACJ,KAAK,QAAQ,eAAe,YACxB,oBACA,mBAAmB,KAAK,QAAQ,KAAK;AAC3C,kBAAI,WAAW;AACb,+BAAe,WAAW,KAAK,QAAQ,aAAa,aAAa;AAAA,cACnE;AACA,kBAAI,KAAK,QAAQ,gBAAgB;AAC/B,sBAAM,WAAW;AACjB,oBAAI,UAAU;AACZ,iCAAe,UAAU,KAAK,QAAQ,aAAa,aAAa;AAAA,gBAClE;AAAA,cACF;AACA,kBAAI,KAAK,QAAQ,iBAAiB,OAAO;AACvC;AAAA,kBACE;AAAA,kBACA,KAAK,QAAQ;AAAA,kBACb;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AACA,mBAAO,CAAC;AAAA,UACV;AAAA,UACA,OAAO,CAAC,KAAK,gBAAuC;AAAA,QACtD;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockslides/extension-slide",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Slide node extension for BlockSlides slide editor",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
"slide"
|
|
12
12
|
],
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"@blockslides/core": "^0.
|
|
15
|
-
"@blockslides/pm": "^0.1.
|
|
14
|
+
"@blockslides/core": "^0.3.1",
|
|
15
|
+
"@blockslides/pm": "^0.1.1"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@blockslides/core": "^0.
|
|
19
|
-
"@blockslides/pm": "^0.1.
|
|
18
|
+
"@blockslides/core": "^0.3.1",
|
|
19
|
+
"@blockslides/pm": "^0.1.1"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"tsup": "^8.0.0",
|
package/src/slide.ts
CHANGED
|
@@ -35,6 +35,36 @@ const slideStyles = `
|
|
|
35
35
|
background-color: var(--slide-bg-overlay-color, #000);
|
|
36
36
|
opacity: var(--slide-bg-overlay-opacity, 0.35);
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
/* When multiple .column elements are adjacent siblings in a slide, */
|
|
41
|
+
/* they automatically form a horizontal row layout. */
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
/* Adjacent columns: make them display side-by-side */
|
|
45
|
+
.slide > .column + .column {
|
|
46
|
+
/* Column that follows another column */
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* Wrapper div for groups of columns to enable flex-row layout */
|
|
50
|
+
.slide > .column-group {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: row;
|
|
53
|
+
flex: 0 0 auto;
|
|
54
|
+
min-height: 0;
|
|
55
|
+
gap: var(--slide-column-gap, 16px);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* When fill is true, the group should take remaining space */
|
|
59
|
+
.slide > .column-group[data-fill="true"] {
|
|
60
|
+
flex: 1 1 auto;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Columns within a group share space equally by default */
|
|
64
|
+
.slide > .column-group > .column {
|
|
65
|
+
flex: 1;
|
|
66
|
+
min-width: 0;
|
|
67
|
+
}
|
|
38
68
|
`;
|
|
39
69
|
|
|
40
70
|
const getFixedSizeStyles = (scale: number) => `
|
|
@@ -179,7 +209,7 @@ const SlidePluginKey = new PluginKey("slide");
|
|
|
179
209
|
export const Slide = Node.create<SlideOptions>({
|
|
180
210
|
name: "slide",
|
|
181
211
|
isolating: true,
|
|
182
|
-
content: "
|
|
212
|
+
content: "block+",
|
|
183
213
|
|
|
184
214
|
group: "slide",
|
|
185
215
|
|