@phis/ui 0.4.1 → 0.4.2
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/components/widgets/shared/markdown-body-client.d.ts +2 -1
- package/components/widgets/shared/markdown-body-client.js +3 -1
- package/components/widgets/shared/markdown-render.js +8 -1
- package/package.json +1 -1
- package/plugins/runtime-modules/core/widgets/markdown/config.d.ts +3 -1
- package/plugins/runtime-modules/core/widgets/markdown/config.js +14 -0
- package/styles/controls.css +26 -0
- package/types/core-widget-placements.d.ts +3 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PhiClientBlockBaseProps, PhiNoLabels } from "../../../types";
|
|
2
2
|
import type { PhiCodeToken } from "./markdown-code-tokens";
|
|
3
|
-
import type { PhiMarkdownTextAlign } from "../../../types/core-widget-placements";
|
|
3
|
+
import type { PhiMarkdownImageFloat, PhiMarkdownTextAlign } from "../../../types/core-widget-placements";
|
|
4
4
|
import { type PhiSpacingScaleKey } from "../config/spacing-options";
|
|
5
5
|
import type { PhiMarkdownTocHeading } from "../../../types/markdown";
|
|
6
6
|
import { type PhiBulletListStyle } from "./bullet-list-vocabulary";
|
|
@@ -75,6 +75,7 @@ export type PhiMarkdownWidgetClientProps = PhiClientBlockBaseProps<PhiNoLabels,
|
|
|
75
75
|
tocKey?: string;
|
|
76
76
|
headings?: PhiMarkdownTocHeading[];
|
|
77
77
|
textAlign?: PhiMarkdownTextAlign;
|
|
78
|
+
imageFloat?: PhiMarkdownImageFloat;
|
|
78
79
|
textBlockSpacingBefore?: PhiSpacingScaleKey;
|
|
79
80
|
textBlockSpacingAfter?: PhiSpacingScaleKey;
|
|
80
81
|
headingBlockSpacingBefore?: PhiSpacingScaleKey;
|
|
@@ -162,7 +162,9 @@ export function PhiMarkdownWidgetClient({ config }) {
|
|
|
162
162
|
marginBlockStart: resolvePhiSpacingScaleValue(config?.headingBlockSpacingBefore ?? "none"),
|
|
163
163
|
marginBlockEnd: resolvePhiSpacingScaleValue(config?.headingBlockSpacingAfter ?? "sm"),
|
|
164
164
|
};
|
|
165
|
-
return (_jsx("div", {
|
|
165
|
+
return (_jsx("div", { className: config?.imageFloat ? `phi-markdown-body phi-markdown-body--float-${config.imageFloat}` : undefined, style: config?.imageFloat
|
|
166
|
+
? { display: "flow-root", width: "100%", textAlign: config.textAlign, containerType: "inline-size" }
|
|
167
|
+
: { display: "grid", gap: 0, width: "100%", textAlign: config?.textAlign }, children: renderBlocks(blocks, token, paragraphBlockStyle, headingBlockStyle, {
|
|
166
168
|
style: resolvePhiBulletListStyle(config?.list),
|
|
167
169
|
depth: 1,
|
|
168
170
|
}) }));
|
|
@@ -56,6 +56,13 @@ function normalizePotentialPhiScheme(value) {
|
|
|
56
56
|
* accepted let `javascript:` and `data:` through here while the inline path refused them.
|
|
57
57
|
*/
|
|
58
58
|
const PHI_EXTERNAL_DOCUMENT_URL_SCHEMES = /^(?:https?:|mailto:|tel:)$/iu;
|
|
59
|
+
/*
|
|
60
|
+
* A picture written into the text itself may also be a data URL of an image type -- the one way a Module
|
|
61
|
+
* brings a drawing with its copy, since it cannot put a file into the Site's `public/`. An `<img>` runs
|
|
62
|
+
* nothing it loads, an SVG's scripts included, so the type list is what keeps this a picture. Links
|
|
63
|
+
* stay on the scheme list above, and a fetched document gets no data URLs at all.
|
|
64
|
+
*/
|
|
65
|
+
const PHI_INLINE_IMAGE_DATA_URL = /^data:image\/(?:png|jpeg|gif|webp|svg\+xml)[;,]/iu;
|
|
59
66
|
function resolveExternalDocumentUrl(value, sourceUrl) {
|
|
60
67
|
const normalized = value.trim();
|
|
61
68
|
if (!normalized || normalizePotentialPhiScheme(normalized).startsWith("phis:"))
|
|
@@ -119,7 +126,7 @@ async function projectInternalMarkdownNodes(tree, runtime) {
|
|
|
119
126
|
const src = resolved.assetUrls.get(reference.assetId);
|
|
120
127
|
return src ? [{ ...node, url: src, children }] : [];
|
|
121
128
|
}
|
|
122
|
-
if (!/^https?:/iu.test(node.url ?? ""))
|
|
129
|
+
if (!/^https?:/iu.test(node.url ?? "") && !PHI_INLINE_IMAGE_DATA_URL.test(node.url ?? ""))
|
|
123
130
|
return [];
|
|
124
131
|
return [{ ...node, children }];
|
|
125
132
|
}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PhiMarkdownTextAlign } from "../../../../../types/core-widget-placements";
|
|
1
|
+
import type { PhiMarkdownImageFloat, PhiMarkdownTextAlign } from "../../../../../types/core-widget-placements";
|
|
2
2
|
import { type PhiSpacingScaleKey } from "../../../../../components/widgets/config/spacing-options";
|
|
3
3
|
import type { PhiCmsResolvedContent } from "../../../../../types/cms";
|
|
4
4
|
import { type PhiBulletListStyle } from "../../../../../components/widgets/shared/bullet-list-vocabulary";
|
|
@@ -13,6 +13,8 @@ export type PhiCmsMarkdownWidgetConfig = PhiCmsWidgetConfigBase & {
|
|
|
13
13
|
/** Set at render from the node's `NoTranslate` flag, or by a caller that knows; never authored. */
|
|
14
14
|
translate?: boolean;
|
|
15
15
|
textAlign?: PhiMarkdownTextAlign;
|
|
16
|
+
/** Pictures stand at this edge and the text flows past them; absent, a picture is a line of its own. */
|
|
17
|
+
imageFloat?: PhiMarkdownImageFloat;
|
|
16
18
|
textBlockSpacingBefore?: PhiSpacingScaleKey;
|
|
17
19
|
textBlockSpacingAfter?: PhiSpacingScaleKey;
|
|
18
20
|
headingBlockSpacingBefore?: PhiSpacingScaleKey;
|
|
@@ -9,6 +9,13 @@ const PHI_MARKDOWN_TEXT_ALIGN_OPTIONS = [
|
|
|
9
9
|
{ value: "end", label: "End" },
|
|
10
10
|
{ value: "justify", label: "Justify" },
|
|
11
11
|
];
|
|
12
|
+
const PHI_MARKDOWN_IMAGE_FLOAT_OPTIONS = [
|
|
13
|
+
{ value: "start", label: "Start" },
|
|
14
|
+
{ value: "end", label: "End" },
|
|
15
|
+
];
|
|
16
|
+
function readMarkdownImageFloat(value) {
|
|
17
|
+
return value === "start" || value === "end" ? value : undefined;
|
|
18
|
+
}
|
|
12
19
|
// The one spacing scale every padding and gap speaks (`theme/phi-tokens.ts`), stored by its key.
|
|
13
20
|
const PHI_MARKDOWN_SPACING_OPTIONS = PHI_SPACING_SCALE_KEYS.map((key) => ({ value: key, label: key }));
|
|
14
21
|
function readMarkdownSourceMode(value) {
|
|
@@ -41,6 +48,7 @@ export function parsePhiCmsMarkdownWidgetConfig(config) {
|
|
|
41
48
|
sourceLocale: readString(config.sourceLocale),
|
|
42
49
|
revalidateSeconds: readNumber(config.revalidateSeconds),
|
|
43
50
|
textAlign: readMarkdownTextAlign(config.textAlign),
|
|
51
|
+
imageFloat: readMarkdownImageFloat(config.imageFloat),
|
|
44
52
|
textBlockSpacingBefore: readMarkdownSpacingKey(config.textBlockSpacingBefore),
|
|
45
53
|
textBlockSpacingAfter: readMarkdownSpacingKey(config.textBlockSpacingAfter),
|
|
46
54
|
headingBlockSpacingBefore: readMarkdownSpacingKey(config.headingBlockSpacingBefore),
|
|
@@ -115,6 +123,12 @@ export const PHI_MARKDOWN_WIDGET_DEFINITION = {
|
|
|
115
123
|
label: "Align",
|
|
116
124
|
options: PHI_MARKDOWN_TEXT_ALIGN_OPTIONS,
|
|
117
125
|
},
|
|
126
|
+
{
|
|
127
|
+
key: "imageFloat",
|
|
128
|
+
type: "choice",
|
|
129
|
+
label: "Image Float",
|
|
130
|
+
options: PHI_MARKDOWN_IMAGE_FLOAT_OPTIONS,
|
|
131
|
+
},
|
|
118
132
|
{
|
|
119
133
|
key: "textBlockSpacingBefore",
|
|
120
134
|
type: "choice",
|
package/styles/controls.css
CHANGED
|
@@ -598,3 +598,29 @@ h5.phi-typography, .phi-typography h5 {
|
|
|
598
598
|
.phi-plain-table tbody tr:last-child td {
|
|
599
599
|
border-block-end: none;
|
|
600
600
|
}
|
|
601
|
+
|
|
602
|
+
/*
|
|
603
|
+
* Markdown pictures the text flows past (`imageFloat` on the Markdown Widget).
|
|
604
|
+
*
|
|
605
|
+
* Only where there is room for both: below 560px a picture beside the words leaves each of them a column
|
|
606
|
+
* nobody reads, so there the picture stands on its own line at full width, as an unfloated one does. The
|
|
607
|
+
* width is a share of the text with a ceiling, so a wide column does not grow the picture past a glance.
|
|
608
|
+
*/
|
|
609
|
+
.phi-markdown-body :is(img) {
|
|
610
|
+
display: block;
|
|
611
|
+
margin-block-end: var(--ant-margin, 16px);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
@container (min-width: 560px) {
|
|
615
|
+
.phi-markdown-body--float-start :is(img) {
|
|
616
|
+
float: inline-start;
|
|
617
|
+
width: min(45%, 440px);
|
|
618
|
+
margin-inline-end: var(--ant-margin-lg, 24px);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.phi-markdown-body--float-end :is(img) {
|
|
622
|
+
float: inline-end;
|
|
623
|
+
width: min(45%, 440px);
|
|
624
|
+
margin-inline-start: var(--ant-margin-lg, 24px);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
@@ -42,6 +42,8 @@ export type PhiWidgetPlacementSignalRoutes = {
|
|
|
42
42
|
type PhiControlPlacement = Omit<PhiControlConfig, "signalRoutes">;
|
|
43
43
|
/** Logical, not left and right: a Site in an RTL locale reads "start" as the right-hand edge. */
|
|
44
44
|
export type PhiMarkdownTextAlign = "start" | "center" | "end" | "justify";
|
|
45
|
+
/** Which edge the text's pictures stand at, with the words flowing past them; logical like the align. */
|
|
46
|
+
export type PhiMarkdownImageFloat = "start" | "end";
|
|
45
47
|
export type PhiMarkdownWidgetPlacement = PhiWidgetPlacementBase & {
|
|
46
48
|
sourceMode?: "inline" | "url";
|
|
47
49
|
markdown?: string;
|
|
@@ -49,6 +51,7 @@ export type PhiMarkdownWidgetPlacement = PhiWidgetPlacementBase & {
|
|
|
49
51
|
sourceLocale?: string;
|
|
50
52
|
revalidateSeconds?: number;
|
|
51
53
|
textAlign?: PhiMarkdownTextAlign;
|
|
54
|
+
imageFloat?: PhiMarkdownImageFloat;
|
|
52
55
|
textBlockSpacingBefore?: PhiSpacingScaleKey;
|
|
53
56
|
textBlockSpacingAfter?: PhiSpacingScaleKey;
|
|
54
57
|
headingBlockSpacingBefore?: PhiSpacingScaleKey;
|