@uniformdev/richtext 20.72.2-alpha.3 → 20.72.3-alpha.14
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/dist/index.d.mts +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.esm.js +80 -7
- package/dist/index.js +84 -7
- package/dist/index.mjs +80 -7
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -28,6 +28,29 @@ type NodeStringRenderer = (props: NodeStringRendererProps) => string;
|
|
|
28
28
|
type ParameterRichTextValue = SerializedEditorState | undefined | null;
|
|
29
29
|
type RichTextBuiltInFormat = 'code' | 'bold' | 'italic' | 'underline' | 'strikethrough' | 'superscript' | 'subscript';
|
|
30
30
|
type RichTextBuiltInElement = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'unorderedList' | 'orderedList' | 'link' | 'quote' | 'code' | 'variable' | 'table' | 'asset';
|
|
31
|
+
/**
|
|
32
|
+
* One entry in the link-attributes config list.
|
|
33
|
+
* `options`, `label`, and `helpText` are optional presentation hints.
|
|
34
|
+
* The React SDK translates the literal `class` key to `className` on output.
|
|
35
|
+
*/
|
|
36
|
+
interface LinkAttrConfig {
|
|
37
|
+
key: string;
|
|
38
|
+
/** When present, the editor renders a select; the value must be one of these. */
|
|
39
|
+
options?: Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
value: string;
|
|
42
|
+
}>;
|
|
43
|
+
label?: string;
|
|
44
|
+
helpText?: string;
|
|
45
|
+
}
|
|
46
|
+
interface LinkAttributesConfiguration {
|
|
47
|
+
/** When true, the editor sees an Anchor input; the fragment lives on value.path. */
|
|
48
|
+
enableAnchor?: boolean;
|
|
49
|
+
/** Flat, ordered list of attribute entries a project wants on its links. */
|
|
50
|
+
entries?: Array<LinkAttrConfig>;
|
|
51
|
+
}
|
|
52
|
+
/** Normalize a stored entry for rendering — fills in a default label. */
|
|
53
|
+
declare function resolveLinkAttrConfig(entry: LinkAttrConfig): Required<Pick<LinkAttrConfig, 'key' | 'label'>> & Pick<LinkAttrConfig, 'options' | 'helpText'>;
|
|
31
54
|
interface RichTextParamConfiguration {
|
|
32
55
|
required?: boolean;
|
|
33
56
|
formatting?: {
|
|
@@ -40,6 +63,8 @@ interface RichTextParamConfiguration {
|
|
|
40
63
|
asset?: {
|
|
41
64
|
allowedTypes?: AssetDefinitionType[];
|
|
42
65
|
};
|
|
66
|
+
/** Optional anchor (fragment) toggle and custom attribute config for the link element. */
|
|
67
|
+
link?: LinkAttributesConfiguration;
|
|
43
68
|
};
|
|
44
69
|
}
|
|
45
70
|
|
|
@@ -65,12 +90,34 @@ type ProjectMapLinkParamValue = {
|
|
|
65
90
|
nodeId: string;
|
|
66
91
|
path: string;
|
|
67
92
|
dynamicInputValues?: Record<string, string>;
|
|
93
|
+
attributes?: Record<string, string>;
|
|
68
94
|
};
|
|
69
95
|
type NonProjectMapLinkParamValue = {
|
|
70
96
|
type: Exclude<LinkParameterType, 'projectMapNode'>;
|
|
71
97
|
path: string;
|
|
98
|
+
attributes?: Record<string, string>;
|
|
72
99
|
};
|
|
73
100
|
type LinkParamValue = ProjectMapLinkParamValue | NonProjectMapLinkParamValue | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* True for attribute keys the Uniform web SDKs strip at render time: inline event
|
|
103
|
+
* handlers (`on*`) and dangerous attribute names (href, src, style, formaction).
|
|
104
|
+
* Authoring UIs should use this to warn before such keys are ever persisted.
|
|
105
|
+
*/
|
|
106
|
+
declare function isDangerousLinkAttributeKey(key: string): boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Converts a link parameter value into a plain HTML attribute bag suitable for
|
|
109
|
+
* an `<a>` element. Applies a web-channel security denylist (event handlers,
|
|
110
|
+
* script-executing URL schemes on the href and any value, dangerous attribute names) and auto-adds
|
|
111
|
+
* `rel="noopener noreferrer"` for any case variant of `target="_blank"`.
|
|
112
|
+
*/
|
|
113
|
+
declare function linkParamValueToHtmlAttributes(link: LinkParamValue): Record<string, string>;
|
|
114
|
+
/**
|
|
115
|
+
* Converts a link parameter value into a React-compatible anchor prop bag.
|
|
116
|
+
* Delegates to `linkParamValueToHtmlAttributes` for the security denylist /
|
|
117
|
+
* auto-rel logic, then renames the literal `class` key to `className` for React.
|
|
118
|
+
*/
|
|
119
|
+
declare function linkParamValueToAnchorProps(link: LinkParamValue): Record<string, string>;
|
|
120
|
+
/** Derives the href string from a link parameter value. */
|
|
74
121
|
declare function linkParamValueToHref(link: LinkParamValue): string | undefined;
|
|
75
122
|
|
|
76
123
|
interface ListNode extends RichTextNode {
|
|
@@ -223,4 +270,4 @@ declare const emptyRichTextValue: {
|
|
|
223
270
|
};
|
|
224
271
|
declare const defaultParameterConfiguration: RichTextParamConfiguration;
|
|
225
272
|
|
|
226
|
-
export { type AssetNode, type HeadingNode, type LinkNode, type ListItemNode, type ListNode, type NodeStringRenderer, type NodeStringRendererProps, type ParagraphNode, type ParameterRichTextValue, type ResolveStringRenderer, type RichTextBuiltInElement, type RichTextBuiltInFormat, type RichTextNode, type RichTextNodeWithChildren, type RichTextParamConfiguration, type StringRenderContext, type TableCellNode, type TableNode, type TableRowNode, type TextNode, assetHtmlRenderer, defaultParameterConfiguration, emptyRichTextValue, getLabelForElement, getLabelForFormat, getRichTextTagFromTableCellHeaderState, getRichTextTagsFromTextFormat, hasChildren, headingHtmlRenderer, isArrayWithLength, isPureDirection, isPureTextAlign, isRichTextNode, isRichTextNodeType, isRichTextValue, isRichTextValueConsideredEmpty, linkHtmlRenderer, linkParamValueToHref, listHtmlRenderer, listitemHtmlRenderer, paragraphHtmlRenderer, purifyText, renderChildrenToHtml, renderChildrenToText, renderHtmlElement, renderToHtml, renderToText, resolveDefaultRenderer$1 as resolveDefaultHtmlRenderer, resolveDefaultRenderer as resolveDefaultTextRenderer, richTextBuiltInElements, richTextBuiltInFormats, rootHtmlRenderer, tableHtmlRenderer, tablecellHtmlRenderer, tablerowHtmlRenderer, textHtmlRenderer, walkRichTextTree };
|
|
273
|
+
export { type AssetNode, type HeadingNode, type LinkAttrConfig, type LinkAttributesConfiguration, type LinkNode, type ListItemNode, type ListNode, type NodeStringRenderer, type NodeStringRendererProps, type ParagraphNode, type ParameterRichTextValue, type ResolveStringRenderer, type RichTextBuiltInElement, type RichTextBuiltInFormat, type RichTextNode, type RichTextNodeWithChildren, type RichTextParamConfiguration, type StringRenderContext, type TableCellNode, type TableNode, type TableRowNode, type TextNode, assetHtmlRenderer, defaultParameterConfiguration, emptyRichTextValue, getLabelForElement, getLabelForFormat, getRichTextTagFromTableCellHeaderState, getRichTextTagsFromTextFormat, hasChildren, headingHtmlRenderer, isArrayWithLength, isDangerousLinkAttributeKey, isPureDirection, isPureTextAlign, isRichTextNode, isRichTextNodeType, isRichTextValue, isRichTextValueConsideredEmpty, linkHtmlRenderer, linkParamValueToAnchorProps, linkParamValueToHref, linkParamValueToHtmlAttributes, listHtmlRenderer, listitemHtmlRenderer, paragraphHtmlRenderer, purifyText, renderChildrenToHtml, renderChildrenToText, renderHtmlElement, renderToHtml, renderToText, resolveDefaultRenderer$1 as resolveDefaultHtmlRenderer, resolveDefaultRenderer as resolveDefaultTextRenderer, resolveLinkAttrConfig, richTextBuiltInElements, richTextBuiltInFormats, rootHtmlRenderer, tableHtmlRenderer, tablecellHtmlRenderer, tablerowHtmlRenderer, textHtmlRenderer, walkRichTextTree };
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,29 @@ type NodeStringRenderer = (props: NodeStringRendererProps) => string;
|
|
|
28
28
|
type ParameterRichTextValue = SerializedEditorState | undefined | null;
|
|
29
29
|
type RichTextBuiltInFormat = 'code' | 'bold' | 'italic' | 'underline' | 'strikethrough' | 'superscript' | 'subscript';
|
|
30
30
|
type RichTextBuiltInElement = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'unorderedList' | 'orderedList' | 'link' | 'quote' | 'code' | 'variable' | 'table' | 'asset';
|
|
31
|
+
/**
|
|
32
|
+
* One entry in the link-attributes config list.
|
|
33
|
+
* `options`, `label`, and `helpText` are optional presentation hints.
|
|
34
|
+
* The React SDK translates the literal `class` key to `className` on output.
|
|
35
|
+
*/
|
|
36
|
+
interface LinkAttrConfig {
|
|
37
|
+
key: string;
|
|
38
|
+
/** When present, the editor renders a select; the value must be one of these. */
|
|
39
|
+
options?: Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
value: string;
|
|
42
|
+
}>;
|
|
43
|
+
label?: string;
|
|
44
|
+
helpText?: string;
|
|
45
|
+
}
|
|
46
|
+
interface LinkAttributesConfiguration {
|
|
47
|
+
/** When true, the editor sees an Anchor input; the fragment lives on value.path. */
|
|
48
|
+
enableAnchor?: boolean;
|
|
49
|
+
/** Flat, ordered list of attribute entries a project wants on its links. */
|
|
50
|
+
entries?: Array<LinkAttrConfig>;
|
|
51
|
+
}
|
|
52
|
+
/** Normalize a stored entry for rendering — fills in a default label. */
|
|
53
|
+
declare function resolveLinkAttrConfig(entry: LinkAttrConfig): Required<Pick<LinkAttrConfig, 'key' | 'label'>> & Pick<LinkAttrConfig, 'options' | 'helpText'>;
|
|
31
54
|
interface RichTextParamConfiguration {
|
|
32
55
|
required?: boolean;
|
|
33
56
|
formatting?: {
|
|
@@ -40,6 +63,8 @@ interface RichTextParamConfiguration {
|
|
|
40
63
|
asset?: {
|
|
41
64
|
allowedTypes?: AssetDefinitionType[];
|
|
42
65
|
};
|
|
66
|
+
/** Optional anchor (fragment) toggle and custom attribute config for the link element. */
|
|
67
|
+
link?: LinkAttributesConfiguration;
|
|
43
68
|
};
|
|
44
69
|
}
|
|
45
70
|
|
|
@@ -65,12 +90,34 @@ type ProjectMapLinkParamValue = {
|
|
|
65
90
|
nodeId: string;
|
|
66
91
|
path: string;
|
|
67
92
|
dynamicInputValues?: Record<string, string>;
|
|
93
|
+
attributes?: Record<string, string>;
|
|
68
94
|
};
|
|
69
95
|
type NonProjectMapLinkParamValue = {
|
|
70
96
|
type: Exclude<LinkParameterType, 'projectMapNode'>;
|
|
71
97
|
path: string;
|
|
98
|
+
attributes?: Record<string, string>;
|
|
72
99
|
};
|
|
73
100
|
type LinkParamValue = ProjectMapLinkParamValue | NonProjectMapLinkParamValue | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* True for attribute keys the Uniform web SDKs strip at render time: inline event
|
|
103
|
+
* handlers (`on*`) and dangerous attribute names (href, src, style, formaction).
|
|
104
|
+
* Authoring UIs should use this to warn before such keys are ever persisted.
|
|
105
|
+
*/
|
|
106
|
+
declare function isDangerousLinkAttributeKey(key: string): boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Converts a link parameter value into a plain HTML attribute bag suitable for
|
|
109
|
+
* an `<a>` element. Applies a web-channel security denylist (event handlers,
|
|
110
|
+
* script-executing URL schemes on the href and any value, dangerous attribute names) and auto-adds
|
|
111
|
+
* `rel="noopener noreferrer"` for any case variant of `target="_blank"`.
|
|
112
|
+
*/
|
|
113
|
+
declare function linkParamValueToHtmlAttributes(link: LinkParamValue): Record<string, string>;
|
|
114
|
+
/**
|
|
115
|
+
* Converts a link parameter value into a React-compatible anchor prop bag.
|
|
116
|
+
* Delegates to `linkParamValueToHtmlAttributes` for the security denylist /
|
|
117
|
+
* auto-rel logic, then renames the literal `class` key to `className` for React.
|
|
118
|
+
*/
|
|
119
|
+
declare function linkParamValueToAnchorProps(link: LinkParamValue): Record<string, string>;
|
|
120
|
+
/** Derives the href string from a link parameter value. */
|
|
74
121
|
declare function linkParamValueToHref(link: LinkParamValue): string | undefined;
|
|
75
122
|
|
|
76
123
|
interface ListNode extends RichTextNode {
|
|
@@ -223,4 +270,4 @@ declare const emptyRichTextValue: {
|
|
|
223
270
|
};
|
|
224
271
|
declare const defaultParameterConfiguration: RichTextParamConfiguration;
|
|
225
272
|
|
|
226
|
-
export { type AssetNode, type HeadingNode, type LinkNode, type ListItemNode, type ListNode, type NodeStringRenderer, type NodeStringRendererProps, type ParagraphNode, type ParameterRichTextValue, type ResolveStringRenderer, type RichTextBuiltInElement, type RichTextBuiltInFormat, type RichTextNode, type RichTextNodeWithChildren, type RichTextParamConfiguration, type StringRenderContext, type TableCellNode, type TableNode, type TableRowNode, type TextNode, assetHtmlRenderer, defaultParameterConfiguration, emptyRichTextValue, getLabelForElement, getLabelForFormat, getRichTextTagFromTableCellHeaderState, getRichTextTagsFromTextFormat, hasChildren, headingHtmlRenderer, isArrayWithLength, isPureDirection, isPureTextAlign, isRichTextNode, isRichTextNodeType, isRichTextValue, isRichTextValueConsideredEmpty, linkHtmlRenderer, linkParamValueToHref, listHtmlRenderer, listitemHtmlRenderer, paragraphHtmlRenderer, purifyText, renderChildrenToHtml, renderChildrenToText, renderHtmlElement, renderToHtml, renderToText, resolveDefaultRenderer$1 as resolveDefaultHtmlRenderer, resolveDefaultRenderer as resolveDefaultTextRenderer, richTextBuiltInElements, richTextBuiltInFormats, rootHtmlRenderer, tableHtmlRenderer, tablecellHtmlRenderer, tablerowHtmlRenderer, textHtmlRenderer, walkRichTextTree };
|
|
273
|
+
export { type AssetNode, type HeadingNode, type LinkAttrConfig, type LinkAttributesConfiguration, type LinkNode, type ListItemNode, type ListNode, type NodeStringRenderer, type NodeStringRendererProps, type ParagraphNode, type ParameterRichTextValue, type ResolveStringRenderer, type RichTextBuiltInElement, type RichTextBuiltInFormat, type RichTextNode, type RichTextNodeWithChildren, type RichTextParamConfiguration, type StringRenderContext, type TableCellNode, type TableNode, type TableRowNode, type TextNode, assetHtmlRenderer, defaultParameterConfiguration, emptyRichTextValue, getLabelForElement, getLabelForFormat, getRichTextTagFromTableCellHeaderState, getRichTextTagsFromTextFormat, hasChildren, headingHtmlRenderer, isArrayWithLength, isDangerousLinkAttributeKey, isPureDirection, isPureTextAlign, isRichTextNode, isRichTextNodeType, isRichTextValue, isRichTextValueConsideredEmpty, linkHtmlRenderer, linkParamValueToAnchorProps, linkParamValueToHref, linkParamValueToHtmlAttributes, listHtmlRenderer, listitemHtmlRenderer, paragraphHtmlRenderer, purifyText, renderChildrenToHtml, renderChildrenToText, renderHtmlElement, renderToHtml, renderToText, resolveDefaultRenderer$1 as resolveDefaultHtmlRenderer, resolveDefaultRenderer as resolveDefaultTextRenderer, resolveLinkAttrConfig, richTextBuiltInElements, richTextBuiltInFormats, rootHtmlRenderer, tableHtmlRenderer, tablecellHtmlRenderer, tablerowHtmlRenderer, textHtmlRenderer, walkRichTextTree };
|
package/dist/index.esm.js
CHANGED
|
@@ -29,7 +29,7 @@ var voidElements = /* @__PURE__ */ new Set([
|
|
|
29
29
|
"wbr"
|
|
30
30
|
]);
|
|
31
31
|
function renderHtmlElement(tag, attributes, children) {
|
|
32
|
-
const attributesString = attributes && attributes.size > 0 ? " " + Array.from(attributes).reduce((result, [key, value]) => [...result, `${key}="${value}"`], []).join(" ") : "";
|
|
32
|
+
const attributesString = attributes && attributes.size > 0 ? " " + Array.from(attributes).reduce((result, [key, value]) => [...result, `${key}="${purifyText(value)}"`], []).join(" ") : "";
|
|
33
33
|
return voidElements.has(tag) ? `<${tag}${attributesString}>` : `<${tag}${attributesString}>${children != null ? children : ""}</${tag}>`;
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -99,13 +99,68 @@ var headingTextRenderer = ({ context, renderChildren }) => {
|
|
|
99
99
|
// src/nodes/link.ts
|
|
100
100
|
var linkHtmlRenderer = ({ context, renderChildren }) => {
|
|
101
101
|
const node = context.currentNode;
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
if (href) {
|
|
105
|
-
attributes.set("href", href);
|
|
106
|
-
}
|
|
102
|
+
const htmlAttrs = linkParamValueToHtmlAttributes(node.link);
|
|
103
|
+
const attributes = new Map(Object.entries(htmlAttrs));
|
|
107
104
|
return renderHtmlElement("a", attributes, renderChildren(context.currentNode.children));
|
|
108
105
|
};
|
|
106
|
+
var DENYLIST_EXACT = /* @__PURE__ */ new Set(["href", "src", "style", "formaction"]);
|
|
107
|
+
var EVENT_HANDLER_PATTERN = /^on/i;
|
|
108
|
+
var SAFE_ATTRIBUTE_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9:_.-]*$/;
|
|
109
|
+
var DANGEROUS_SCHEME_PATTERN = /^(?:javascript|data|vbscript):/i;
|
|
110
|
+
function hasDangerousScheme(value) {
|
|
111
|
+
const normalized = value.replace(/[\u0000-\u0020\u007f-\u00a0]+/g, "").toLowerCase();
|
|
112
|
+
return DANGEROUS_SCHEME_PATTERN.test(normalized);
|
|
113
|
+
}
|
|
114
|
+
function isDangerousLinkAttributeKey(key) {
|
|
115
|
+
return EVENT_HANDLER_PATTERN.test(key) || DENYLIST_EXACT.has(key.toLowerCase());
|
|
116
|
+
}
|
|
117
|
+
function linkParamValueToHtmlAttributes(link) {
|
|
118
|
+
var _a;
|
|
119
|
+
const result = {};
|
|
120
|
+
const href = linkParamValueToHref(link);
|
|
121
|
+
if (href) {
|
|
122
|
+
result.href = href;
|
|
123
|
+
}
|
|
124
|
+
if (!(link == null ? void 0 : link.attributes)) {
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
for (const [key, value] of Object.entries(link.attributes)) {
|
|
128
|
+
if (!key || typeof value !== "string") {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (!SAFE_ATTRIBUTE_NAME_PATTERN.test(key)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (EVENT_HANDLER_PATTERN.test(key)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (DENYLIST_EXACT.has(key.toLowerCase())) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (hasDangerousScheme(value)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
result[key] = value;
|
|
144
|
+
}
|
|
145
|
+
const targetEntry = Object.entries(result).find(([key]) => key.toLowerCase() === "target");
|
|
146
|
+
const hasRel = Object.keys(result).some((key) => key.toLowerCase() === "rel");
|
|
147
|
+
if (((_a = targetEntry == null ? void 0 : targetEntry[1]) == null ? void 0 : _a.toLowerCase()) === "_blank" && !hasRel) {
|
|
148
|
+
result.rel = "noopener noreferrer";
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
function linkParamValueToAnchorProps(link) {
|
|
153
|
+
const raw = linkParamValueToHtmlAttributes(link);
|
|
154
|
+
const result = {};
|
|
155
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
156
|
+
if (key === "class") {
|
|
157
|
+
result["className"] = value;
|
|
158
|
+
} else {
|
|
159
|
+
result[key] = value;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
109
164
|
function linkParamValueToHref(link) {
|
|
110
165
|
switch (link == null ? void 0 : link.type) {
|
|
111
166
|
case "email":
|
|
@@ -113,8 +168,11 @@ function linkParamValueToHref(link) {
|
|
|
113
168
|
case "tel":
|
|
114
169
|
return `tel:${link.path}`;
|
|
115
170
|
case "url":
|
|
116
|
-
return link.path;
|
|
117
171
|
case "projectMapNode":
|
|
172
|
+
if (hasDangerousScheme(link.path)) {
|
|
173
|
+
console.warn(`[richtext] Dropped disallowed link URL scheme: "${link.path}"`);
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
118
176
|
return link.path;
|
|
119
177
|
}
|
|
120
178
|
}
|
|
@@ -387,6 +445,17 @@ function renderChildrenToText(children, context) {
|
|
|
387
445
|
return Array.isArray(children) ? children.map((node) => renderToText(node, context)).join("") : "";
|
|
388
446
|
}
|
|
389
447
|
|
|
448
|
+
// src/types.ts
|
|
449
|
+
function resolveLinkAttrConfig(entry) {
|
|
450
|
+
var _a;
|
|
451
|
+
return {
|
|
452
|
+
key: entry.key,
|
|
453
|
+
options: entry.options,
|
|
454
|
+
label: (_a = entry.label) != null ? _a : entry.key,
|
|
455
|
+
helpText: entry.helpText
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
390
459
|
// src/utils.ts
|
|
391
460
|
var walkRichTextTree = (node, callback, parent) => {
|
|
392
461
|
if (!isRichTextNode(node)) {
|
|
@@ -548,6 +617,7 @@ export {
|
|
|
548
617
|
hasChildren,
|
|
549
618
|
headingHtmlRenderer,
|
|
550
619
|
isArrayWithLength,
|
|
620
|
+
isDangerousLinkAttributeKey,
|
|
551
621
|
isPureDirection,
|
|
552
622
|
isPureTextAlign,
|
|
553
623
|
isRichTextNode,
|
|
@@ -555,7 +625,9 @@ export {
|
|
|
555
625
|
isRichTextValue,
|
|
556
626
|
isRichTextValueConsideredEmpty,
|
|
557
627
|
linkHtmlRenderer,
|
|
628
|
+
linkParamValueToAnchorProps,
|
|
558
629
|
linkParamValueToHref,
|
|
630
|
+
linkParamValueToHtmlAttributes,
|
|
559
631
|
listHtmlRenderer,
|
|
560
632
|
listitemHtmlRenderer,
|
|
561
633
|
paragraphHtmlRenderer,
|
|
@@ -567,6 +639,7 @@ export {
|
|
|
567
639
|
renderToText,
|
|
568
640
|
resolveDefaultRenderer as resolveDefaultHtmlRenderer,
|
|
569
641
|
resolveDefaultRenderer2 as resolveDefaultTextRenderer,
|
|
642
|
+
resolveLinkAttrConfig,
|
|
570
643
|
richTextBuiltInElements,
|
|
571
644
|
richTextBuiltInFormats,
|
|
572
645
|
rootHtmlRenderer,
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ __export(index_exports, {
|
|
|
30
30
|
hasChildren: () => hasChildren,
|
|
31
31
|
headingHtmlRenderer: () => headingHtmlRenderer,
|
|
32
32
|
isArrayWithLength: () => isArrayWithLength,
|
|
33
|
+
isDangerousLinkAttributeKey: () => isDangerousLinkAttributeKey,
|
|
33
34
|
isPureDirection: () => isPureDirection,
|
|
34
35
|
isPureTextAlign: () => isPureTextAlign,
|
|
35
36
|
isRichTextNode: () => isRichTextNode,
|
|
@@ -37,7 +38,9 @@ __export(index_exports, {
|
|
|
37
38
|
isRichTextValue: () => isRichTextValue,
|
|
38
39
|
isRichTextValueConsideredEmpty: () => isRichTextValueConsideredEmpty,
|
|
39
40
|
linkHtmlRenderer: () => linkHtmlRenderer,
|
|
41
|
+
linkParamValueToAnchorProps: () => linkParamValueToAnchorProps,
|
|
40
42
|
linkParamValueToHref: () => linkParamValueToHref,
|
|
43
|
+
linkParamValueToHtmlAttributes: () => linkParamValueToHtmlAttributes,
|
|
41
44
|
listHtmlRenderer: () => listHtmlRenderer,
|
|
42
45
|
listitemHtmlRenderer: () => listitemHtmlRenderer,
|
|
43
46
|
paragraphHtmlRenderer: () => paragraphHtmlRenderer,
|
|
@@ -49,6 +52,7 @@ __export(index_exports, {
|
|
|
49
52
|
renderToText: () => renderToText,
|
|
50
53
|
resolveDefaultHtmlRenderer: () => resolveDefaultRenderer,
|
|
51
54
|
resolveDefaultTextRenderer: () => resolveDefaultRenderer2,
|
|
55
|
+
resolveLinkAttrConfig: () => resolveLinkAttrConfig,
|
|
52
56
|
richTextBuiltInElements: () => richTextBuiltInElements,
|
|
53
57
|
richTextBuiltInFormats: () => richTextBuiltInFormats,
|
|
54
58
|
rootHtmlRenderer: () => rootHtmlRenderer,
|
|
@@ -91,7 +95,7 @@ var voidElements = /* @__PURE__ */ new Set([
|
|
|
91
95
|
"wbr"
|
|
92
96
|
]);
|
|
93
97
|
function renderHtmlElement(tag, attributes, children) {
|
|
94
|
-
const attributesString = attributes && attributes.size > 0 ? " " + Array.from(attributes).reduce((result, [key, value]) => [...result, `${key}="${value}"`], []).join(" ") : "";
|
|
98
|
+
const attributesString = attributes && attributes.size > 0 ? " " + Array.from(attributes).reduce((result, [key, value]) => [...result, `${key}="${purifyText(value)}"`], []).join(" ") : "";
|
|
95
99
|
return voidElements.has(tag) ? `<${tag}${attributesString}>` : `<${tag}${attributesString}>${children != null ? children : ""}</${tag}>`;
|
|
96
100
|
}
|
|
97
101
|
|
|
@@ -161,13 +165,68 @@ var headingTextRenderer = ({ context, renderChildren }) => {
|
|
|
161
165
|
// src/nodes/link.ts
|
|
162
166
|
var linkHtmlRenderer = ({ context, renderChildren }) => {
|
|
163
167
|
const node = context.currentNode;
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
if (href) {
|
|
167
|
-
attributes.set("href", href);
|
|
168
|
-
}
|
|
168
|
+
const htmlAttrs = linkParamValueToHtmlAttributes(node.link);
|
|
169
|
+
const attributes = new Map(Object.entries(htmlAttrs));
|
|
169
170
|
return renderHtmlElement("a", attributes, renderChildren(context.currentNode.children));
|
|
170
171
|
};
|
|
172
|
+
var DENYLIST_EXACT = /* @__PURE__ */ new Set(["href", "src", "style", "formaction"]);
|
|
173
|
+
var EVENT_HANDLER_PATTERN = /^on/i;
|
|
174
|
+
var SAFE_ATTRIBUTE_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9:_.-]*$/;
|
|
175
|
+
var DANGEROUS_SCHEME_PATTERN = /^(?:javascript|data|vbscript):/i;
|
|
176
|
+
function hasDangerousScheme(value) {
|
|
177
|
+
const normalized = value.replace(/[\u0000-\u0020\u007f-\u00a0]+/g, "").toLowerCase();
|
|
178
|
+
return DANGEROUS_SCHEME_PATTERN.test(normalized);
|
|
179
|
+
}
|
|
180
|
+
function isDangerousLinkAttributeKey(key) {
|
|
181
|
+
return EVENT_HANDLER_PATTERN.test(key) || DENYLIST_EXACT.has(key.toLowerCase());
|
|
182
|
+
}
|
|
183
|
+
function linkParamValueToHtmlAttributes(link) {
|
|
184
|
+
var _a;
|
|
185
|
+
const result = {};
|
|
186
|
+
const href = linkParamValueToHref(link);
|
|
187
|
+
if (href) {
|
|
188
|
+
result.href = href;
|
|
189
|
+
}
|
|
190
|
+
if (!(link == null ? void 0 : link.attributes)) {
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
for (const [key, value] of Object.entries(link.attributes)) {
|
|
194
|
+
if (!key || typeof value !== "string") {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (!SAFE_ATTRIBUTE_NAME_PATTERN.test(key)) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (EVENT_HANDLER_PATTERN.test(key)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (DENYLIST_EXACT.has(key.toLowerCase())) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (hasDangerousScheme(value)) {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
result[key] = value;
|
|
210
|
+
}
|
|
211
|
+
const targetEntry = Object.entries(result).find(([key]) => key.toLowerCase() === "target");
|
|
212
|
+
const hasRel = Object.keys(result).some((key) => key.toLowerCase() === "rel");
|
|
213
|
+
if (((_a = targetEntry == null ? void 0 : targetEntry[1]) == null ? void 0 : _a.toLowerCase()) === "_blank" && !hasRel) {
|
|
214
|
+
result.rel = "noopener noreferrer";
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
function linkParamValueToAnchorProps(link) {
|
|
219
|
+
const raw = linkParamValueToHtmlAttributes(link);
|
|
220
|
+
const result = {};
|
|
221
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
222
|
+
if (key === "class") {
|
|
223
|
+
result["className"] = value;
|
|
224
|
+
} else {
|
|
225
|
+
result[key] = value;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
171
230
|
function linkParamValueToHref(link) {
|
|
172
231
|
switch (link == null ? void 0 : link.type) {
|
|
173
232
|
case "email":
|
|
@@ -175,8 +234,11 @@ function linkParamValueToHref(link) {
|
|
|
175
234
|
case "tel":
|
|
176
235
|
return `tel:${link.path}`;
|
|
177
236
|
case "url":
|
|
178
|
-
return link.path;
|
|
179
237
|
case "projectMapNode":
|
|
238
|
+
if (hasDangerousScheme(link.path)) {
|
|
239
|
+
console.warn(`[richtext] Dropped disallowed link URL scheme: "${link.path}"`);
|
|
240
|
+
return void 0;
|
|
241
|
+
}
|
|
180
242
|
return link.path;
|
|
181
243
|
}
|
|
182
244
|
}
|
|
@@ -449,6 +511,17 @@ function renderChildrenToText(children, context) {
|
|
|
449
511
|
return Array.isArray(children) ? children.map((node) => renderToText(node, context)).join("") : "";
|
|
450
512
|
}
|
|
451
513
|
|
|
514
|
+
// src/types.ts
|
|
515
|
+
function resolveLinkAttrConfig(entry) {
|
|
516
|
+
var _a;
|
|
517
|
+
return {
|
|
518
|
+
key: entry.key,
|
|
519
|
+
options: entry.options,
|
|
520
|
+
label: (_a = entry.label) != null ? _a : entry.key,
|
|
521
|
+
helpText: entry.helpText
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
452
525
|
// src/utils.ts
|
|
453
526
|
var walkRichTextTree = (node, callback, parent) => {
|
|
454
527
|
if (!isRichTextNode(node)) {
|
|
@@ -611,6 +684,7 @@ var defaultParameterConfiguration = {
|
|
|
611
684
|
hasChildren,
|
|
612
685
|
headingHtmlRenderer,
|
|
613
686
|
isArrayWithLength,
|
|
687
|
+
isDangerousLinkAttributeKey,
|
|
614
688
|
isPureDirection,
|
|
615
689
|
isPureTextAlign,
|
|
616
690
|
isRichTextNode,
|
|
@@ -618,7 +692,9 @@ var defaultParameterConfiguration = {
|
|
|
618
692
|
isRichTextValue,
|
|
619
693
|
isRichTextValueConsideredEmpty,
|
|
620
694
|
linkHtmlRenderer,
|
|
695
|
+
linkParamValueToAnchorProps,
|
|
621
696
|
linkParamValueToHref,
|
|
697
|
+
linkParamValueToHtmlAttributes,
|
|
622
698
|
listHtmlRenderer,
|
|
623
699
|
listitemHtmlRenderer,
|
|
624
700
|
paragraphHtmlRenderer,
|
|
@@ -630,6 +706,7 @@ var defaultParameterConfiguration = {
|
|
|
630
706
|
renderToText,
|
|
631
707
|
resolveDefaultHtmlRenderer,
|
|
632
708
|
resolveDefaultTextRenderer,
|
|
709
|
+
resolveLinkAttrConfig,
|
|
633
710
|
richTextBuiltInElements,
|
|
634
711
|
richTextBuiltInFormats,
|
|
635
712
|
rootHtmlRenderer,
|
package/dist/index.mjs
CHANGED
|
@@ -29,7 +29,7 @@ var voidElements = /* @__PURE__ */ new Set([
|
|
|
29
29
|
"wbr"
|
|
30
30
|
]);
|
|
31
31
|
function renderHtmlElement(tag, attributes, children) {
|
|
32
|
-
const attributesString = attributes && attributes.size > 0 ? " " + Array.from(attributes).reduce((result, [key, value]) => [...result, `${key}="${value}"`], []).join(" ") : "";
|
|
32
|
+
const attributesString = attributes && attributes.size > 0 ? " " + Array.from(attributes).reduce((result, [key, value]) => [...result, `${key}="${purifyText(value)}"`], []).join(" ") : "";
|
|
33
33
|
return voidElements.has(tag) ? `<${tag}${attributesString}>` : `<${tag}${attributesString}>${children != null ? children : ""}</${tag}>`;
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -99,13 +99,68 @@ var headingTextRenderer = ({ context, renderChildren }) => {
|
|
|
99
99
|
// src/nodes/link.ts
|
|
100
100
|
var linkHtmlRenderer = ({ context, renderChildren }) => {
|
|
101
101
|
const node = context.currentNode;
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
if (href) {
|
|
105
|
-
attributes.set("href", href);
|
|
106
|
-
}
|
|
102
|
+
const htmlAttrs = linkParamValueToHtmlAttributes(node.link);
|
|
103
|
+
const attributes = new Map(Object.entries(htmlAttrs));
|
|
107
104
|
return renderHtmlElement("a", attributes, renderChildren(context.currentNode.children));
|
|
108
105
|
};
|
|
106
|
+
var DENYLIST_EXACT = /* @__PURE__ */ new Set(["href", "src", "style", "formaction"]);
|
|
107
|
+
var EVENT_HANDLER_PATTERN = /^on/i;
|
|
108
|
+
var SAFE_ATTRIBUTE_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9:_.-]*$/;
|
|
109
|
+
var DANGEROUS_SCHEME_PATTERN = /^(?:javascript|data|vbscript):/i;
|
|
110
|
+
function hasDangerousScheme(value) {
|
|
111
|
+
const normalized = value.replace(/[\u0000-\u0020\u007f-\u00a0]+/g, "").toLowerCase();
|
|
112
|
+
return DANGEROUS_SCHEME_PATTERN.test(normalized);
|
|
113
|
+
}
|
|
114
|
+
function isDangerousLinkAttributeKey(key) {
|
|
115
|
+
return EVENT_HANDLER_PATTERN.test(key) || DENYLIST_EXACT.has(key.toLowerCase());
|
|
116
|
+
}
|
|
117
|
+
function linkParamValueToHtmlAttributes(link) {
|
|
118
|
+
var _a;
|
|
119
|
+
const result = {};
|
|
120
|
+
const href = linkParamValueToHref(link);
|
|
121
|
+
if (href) {
|
|
122
|
+
result.href = href;
|
|
123
|
+
}
|
|
124
|
+
if (!(link == null ? void 0 : link.attributes)) {
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
for (const [key, value] of Object.entries(link.attributes)) {
|
|
128
|
+
if (!key || typeof value !== "string") {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (!SAFE_ATTRIBUTE_NAME_PATTERN.test(key)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (EVENT_HANDLER_PATTERN.test(key)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (DENYLIST_EXACT.has(key.toLowerCase())) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (hasDangerousScheme(value)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
result[key] = value;
|
|
144
|
+
}
|
|
145
|
+
const targetEntry = Object.entries(result).find(([key]) => key.toLowerCase() === "target");
|
|
146
|
+
const hasRel = Object.keys(result).some((key) => key.toLowerCase() === "rel");
|
|
147
|
+
if (((_a = targetEntry == null ? void 0 : targetEntry[1]) == null ? void 0 : _a.toLowerCase()) === "_blank" && !hasRel) {
|
|
148
|
+
result.rel = "noopener noreferrer";
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
function linkParamValueToAnchorProps(link) {
|
|
153
|
+
const raw = linkParamValueToHtmlAttributes(link);
|
|
154
|
+
const result = {};
|
|
155
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
156
|
+
if (key === "class") {
|
|
157
|
+
result["className"] = value;
|
|
158
|
+
} else {
|
|
159
|
+
result[key] = value;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
109
164
|
function linkParamValueToHref(link) {
|
|
110
165
|
switch (link == null ? void 0 : link.type) {
|
|
111
166
|
case "email":
|
|
@@ -113,8 +168,11 @@ function linkParamValueToHref(link) {
|
|
|
113
168
|
case "tel":
|
|
114
169
|
return `tel:${link.path}`;
|
|
115
170
|
case "url":
|
|
116
|
-
return link.path;
|
|
117
171
|
case "projectMapNode":
|
|
172
|
+
if (hasDangerousScheme(link.path)) {
|
|
173
|
+
console.warn(`[richtext] Dropped disallowed link URL scheme: "${link.path}"`);
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
118
176
|
return link.path;
|
|
119
177
|
}
|
|
120
178
|
}
|
|
@@ -387,6 +445,17 @@ function renderChildrenToText(children, context) {
|
|
|
387
445
|
return Array.isArray(children) ? children.map((node) => renderToText(node, context)).join("") : "";
|
|
388
446
|
}
|
|
389
447
|
|
|
448
|
+
// src/types.ts
|
|
449
|
+
function resolveLinkAttrConfig(entry) {
|
|
450
|
+
var _a;
|
|
451
|
+
return {
|
|
452
|
+
key: entry.key,
|
|
453
|
+
options: entry.options,
|
|
454
|
+
label: (_a = entry.label) != null ? _a : entry.key,
|
|
455
|
+
helpText: entry.helpText
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
390
459
|
// src/utils.ts
|
|
391
460
|
var walkRichTextTree = (node, callback, parent) => {
|
|
392
461
|
if (!isRichTextNode(node)) {
|
|
@@ -548,6 +617,7 @@ export {
|
|
|
548
617
|
hasChildren,
|
|
549
618
|
headingHtmlRenderer,
|
|
550
619
|
isArrayWithLength,
|
|
620
|
+
isDangerousLinkAttributeKey,
|
|
551
621
|
isPureDirection,
|
|
552
622
|
isPureTextAlign,
|
|
553
623
|
isRichTextNode,
|
|
@@ -555,7 +625,9 @@ export {
|
|
|
555
625
|
isRichTextValue,
|
|
556
626
|
isRichTextValueConsideredEmpty,
|
|
557
627
|
linkHtmlRenderer,
|
|
628
|
+
linkParamValueToAnchorProps,
|
|
558
629
|
linkParamValueToHref,
|
|
630
|
+
linkParamValueToHtmlAttributes,
|
|
559
631
|
listHtmlRenderer,
|
|
560
632
|
listitemHtmlRenderer,
|
|
561
633
|
paragraphHtmlRenderer,
|
|
@@ -567,6 +639,7 @@ export {
|
|
|
567
639
|
renderToText,
|
|
568
640
|
resolveDefaultRenderer as resolveDefaultHtmlRenderer,
|
|
569
641
|
resolveDefaultRenderer2 as resolveDefaultTextRenderer,
|
|
642
|
+
resolveLinkAttrConfig,
|
|
570
643
|
richTextBuiltInElements,
|
|
571
644
|
richTextBuiltInFormats,
|
|
572
645
|
rootHtmlRenderer,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniformdev/richtext",
|
|
3
|
-
"version": "20.72.
|
|
3
|
+
"version": "20.72.3-alpha.14+1e232e59cd",
|
|
4
4
|
"description": "Common functionality and types for Uniform Rich Text parameters",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@lexical/link": "0.45.0",
|
|
45
45
|
"@lexical/list": "0.45.0",
|
|
46
46
|
"@lexical/rich-text": "0.45.0",
|
|
47
|
-
"@uniformdev/assets": "20.72.
|
|
47
|
+
"@uniformdev/assets": "20.72.3-alpha.14+1e232e59cd",
|
|
48
48
|
"lexical": "0.45.0"
|
|
49
49
|
},
|
|
50
50
|
"files": [
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
"publishConfig": {
|
|
54
54
|
"access": "public"
|
|
55
55
|
},
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "1e232e59cd3ded97b85b063986cc95e954b8c937"
|
|
57
57
|
}
|