@developer_tribe/react-builder 1.2.31 → 1.2.33
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/assets/prompt-scheme-onboard.generated.d.ts +1 -0
- package/dist/assets/prompt-scheme-paywall.generated.d.ts +1 -0
- package/dist/build-components/BIcon/BIconProps.generated.d.ts +1 -1
- package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +1 -1
- package/dist/build-components/Button/ButtonProps.generated.d.ts +1 -1
- package/dist/build-components/Carousel/CarouselProps.generated.d.ts +1 -1
- package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +1 -1
- package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +1 -1
- package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +1 -1
- package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +1 -1
- package/dist/build-components/CountDown/CountDownProps.generated.d.ts +1 -1
- package/dist/build-components/Image/ImageProps.generated.d.ts +1 -1
- package/dist/build-components/Main/MainProps.generated.d.ts +1 -1
- package/dist/build-components/NavigationBarColor/NavigationBarColorProps.generated.d.ts +1 -1
- package/dist/build-components/Onboard/OnboardProps.generated.d.ts +1 -1
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +2 -2
- package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +1 -1
- package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +1 -1
- package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +1 -1
- package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +1 -1
- package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +1 -1
- package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +1 -1
- package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +1 -1
- package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +1 -1
- package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +1 -1
- package/dist/build-components/PriceTag/PriceTagProps.generated.d.ts +1 -1
- package/dist/build-components/Pricing/PricingProps.generated.d.ts +1 -1
- package/dist/build-components/Promo/PromoProps.generated.d.ts +1 -1
- package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +1 -1
- package/dist/build-components/Separator/SeparatorProps.generated.d.ts +1 -1
- package/dist/build-components/StatusBarColor/StatusBarColorProps.generated.d.ts +1 -1
- package/dist/build-components/Text/TextProps.generated.d.ts +1 -1
- package/dist/build-components/View/ViewProps.generated.d.ts +1 -1
- package/dist/build-components/patterns.generated.d.ts +62 -52
- package/dist/components/BuilderProvider.d.ts +2 -4
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.web.cjs.js +6 -6
- package/dist/index.web.cjs.js.map +1 -1
- package/dist/index.web.esm.js +6 -6
- package/dist/index.web.esm.js.map +1 -1
- package/dist/modals/PromptManagerModal.d.ts +9 -0
- package/dist/modals/index.d.ts +1 -0
- package/dist/styles.css +1 -1
- package/dist/utils/attributeStyle.d.ts +2 -2
- package/dist/utils/nodeXml.d.ts +11 -0
- package/dist/utils/patterns.d.ts +2 -2
- package/package.json +5 -1
- package/scripts/prebuild/assets/prompt_scheme.md +44 -0
- package/scripts/prebuild/generate-prompt-schemes.js +328 -0
- package/scripts/prebuild/prebuild.js +4 -0
- package/scripts/prebuild/utils/createGeneratedProps.js +9 -9
- package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +9 -9
- package/src/RenderPage.tsx +48 -7
- package/src/assets/meta.json +1 -1
- package/src/assets/prompt-scheme-onboard.generated.ts +4 -0
- package/src/assets/prompt-scheme-paywall.generated.ts +4 -0
- package/src/assets/samples/vpn-onboard-1.json +0 -24
- package/src/attribute-analyser/style/native/useExtractImageStyle.ts +1 -1
- package/src/attribute-analyser/style/native/useExtractTextStyle.ts +1 -1
- package/src/attribute-analyser/style/native/useExtractViewStyle.ts +1 -1
- package/src/attribute-analyser/style/web/useExtractImageStyle.ts +1 -1
- package/src/attribute-analyser/style/web/useExtractTextStyle.ts +1 -1
- package/src/attribute-analyser/style/web/useExtractViewStyle.ts +1 -1
- package/src/build-components/BIcon/BIconProps.generated.ts +1 -1
- package/src/build-components/BIcon/pattern.json +1 -1
- package/src/build-components/BackgroundImage/BackgroundImage.tsx +1 -1
- package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +1 -1
- package/src/build-components/BackgroundImage/pattern.json +1 -1
- package/src/build-components/Button/Button.tsx +2 -2
- package/src/build-components/Button/ButtonProps.generated.ts +1 -1
- package/src/build-components/Button/pattern.json +2 -2
- package/src/build-components/Carousel/CarouselProps.generated.ts +1 -1
- package/src/build-components/Carousel/pattern.json +1 -1
- package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +1 -1
- package/src/build-components/CarouselDots/CarouselDots.tsx +1 -1
- package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +1 -1
- package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +1 -1
- package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +1 -1
- package/src/build-components/CarouselProvider/pattern.json +1 -1
- package/src/build-components/CountDown/CountDownProps.generated.ts +1 -1
- package/src/build-components/CountDown/pattern.json +1 -1
- package/src/build-components/Image/ImageProps.generated.ts +1 -1
- package/src/build-components/Image/pattern.json +2 -2
- package/src/build-components/Main/MainProps.generated.ts +1 -1
- package/src/build-components/Main/pattern.json +1 -1
- package/src/build-components/NavigationBarColor/NavigationBarColor.tsx +1 -1
- package/src/build-components/NavigationBarColor/NavigationBarColorProps.generated.ts +1 -1
- package/src/build-components/NavigationBarColor/pattern.json +2 -2
- package/src/build-components/Onboard/OnboardProps.generated.ts +1 -1
- package/src/build-components/Onboard/pattern.json +1 -1
- package/src/build-components/OnboardButton/OnboardButton.tsx +3 -24
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +2 -2
- package/src/build-components/OnboardButton/pattern.json +30 -27
- package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +1 -1
- package/src/build-components/OnboardButtons/pattern.json +1 -1
- package/src/build-components/OnboardDot/OnboardDot.tsx +1 -1
- package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +1 -1
- package/src/build-components/OnboardDot/pattern.json +1 -1
- package/src/build-components/OnboardFooter/OnboardFooter.tsx +3 -3
- package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +1 -1
- package/src/build-components/OnboardFooter/pattern.json +1 -1
- package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +1 -1
- package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +1 -1
- package/src/build-components/OnboardItem/pattern.json +1 -1
- package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +1 -1
- package/src/build-components/OnboardProvider/pattern.json +1 -1
- package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +1 -1
- package/src/build-components/OnboardSubtitle/pattern.json +1 -1
- package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +1 -1
- package/src/build-components/OnboardTitle/pattern.json +1 -1
- package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +1 -1
- package/src/build-components/PaywallBackground/pattern.json +1 -1
- package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +1 -1
- package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +1 -1
- package/src/build-components/PaywallCloseButton/pattern.json +1 -1
- package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +1 -1
- package/src/build-components/PaywallOptions/pattern.json +1 -1
- package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +1 -1
- package/src/build-components/PaywallProvider/pattern.json +1 -1
- package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +1 -1
- package/src/build-components/PaywallSubscribeButton/pattern.json +1 -1
- package/src/build-components/PriceTag/PriceTagProps.generated.ts +1 -1
- package/src/build-components/PriceTag/pattern.json +2 -2
- package/src/build-components/Pricing/PricingProps.generated.ts +1 -1
- package/src/build-components/Pricing/pattern.json +1 -1
- package/src/build-components/Promo/PromoProps.generated.ts +1 -1
- package/src/build-components/Promo/pattern.json +1 -1
- package/src/build-components/RadioButton/RadioButtonProps.generated.ts +1 -1
- package/src/build-components/Separator/SeparatorProps.generated.ts +1 -1
- package/src/build-components/Separator/pattern.json +2 -2
- package/src/build-components/StatusBarColor/StatusBarColor.tsx +1 -1
- package/src/build-components/StatusBarColor/StatusBarColorProps.generated.ts +1 -1
- package/src/build-components/StatusBarColor/pattern.json +2 -2
- package/src/build-components/Text/Text.tsx +1 -1
- package/src/build-components/Text/TextProps.generated.ts +1 -1
- package/src/build-components/Text/pattern.json +2 -2
- package/src/build-components/View/ViewProps.generated.ts +1 -1
- package/src/build-components/View/pattern.json +2 -2
- package/src/build-components/patterns.generated.ts +62 -52
- package/src/build-components/useNode.ts +2 -16
- package/src/components/BottomBar.tsx +28 -1
- package/src/components/BuilderProvider.tsx +5 -14
- package/src/hooks/useLocalize.ts +1 -1
- package/src/migrations/migrations/1.1.2_extract_component_attributes_from_style.ts +31 -10
- package/src/modals/PromptManagerModal.tsx +270 -0
- package/src/modals/index.ts +1 -0
- package/src/styles/index.scss +1 -0
- package/src/styles/modals/_prompt-manager-modal.scss +95 -0
- package/src/utils/analyseNodeByPatterns.ts +2 -2
- package/src/utils/attributeStyle.ts +9 -3
- package/src/utils/extractImageStyle.ts +4 -4
- package/src/utils/nodeXml.ts +196 -0
- package/src/utils/patterns.ts +3 -3
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import Modal from './Modal';
|
|
3
|
+
import { useRenderStore } from '../store';
|
|
4
|
+
import type { Node } from '../types/Node';
|
|
5
|
+
import { nodeToXml, xmlToNode } from '../utils/nodeXml';
|
|
6
|
+
import { analyseAndProccess } from '../utils/analyseNode';
|
|
7
|
+
import { onboardPromptScheme } from '../assets/prompt-scheme-onboard.generated';
|
|
8
|
+
import { paywallPromptScheme } from '../assets/prompt-scheme-paywall.generated';
|
|
9
|
+
|
|
10
|
+
type PromptManagerTab = 'xml-editor' | 'prompt-schema';
|
|
11
|
+
|
|
12
|
+
type PromptManagerModalProps = {
|
|
13
|
+
data: Node;
|
|
14
|
+
setData: React.Dispatch<React.SetStateAction<Node>>;
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function PromptManagerModal({
|
|
19
|
+
data,
|
|
20
|
+
setData,
|
|
21
|
+
onClose,
|
|
22
|
+
}: PromptManagerModalProps) {
|
|
23
|
+
const [activeTab, setActiveTab] = useState<PromptManagerTab>('xml-editor');
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Modal
|
|
27
|
+
onClose={onClose}
|
|
28
|
+
ariaLabelledBy="prompt-manager-modal-title"
|
|
29
|
+
className="modal--large modal--scrollable"
|
|
30
|
+
contentClassName="prompt-manager-modal__content"
|
|
31
|
+
>
|
|
32
|
+
<div className="modal__header prompt-manager-modal__header">
|
|
33
|
+
<h3 id="prompt-manager-modal-title" className="modal__title">
|
|
34
|
+
Prompt Manager
|
|
35
|
+
</h3>
|
|
36
|
+
<button type="button" className="editor-button" onClick={onClose}>
|
|
37
|
+
Close
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div className="prompt-manager-modal__tabs">
|
|
42
|
+
<button
|
|
43
|
+
type="button"
|
|
44
|
+
className={`prompt-manager-modal__tab${
|
|
45
|
+
activeTab === 'xml-editor' ? ' is-active' : ''
|
|
46
|
+
}`}
|
|
47
|
+
onClick={() => setActiveTab('xml-editor')}
|
|
48
|
+
>
|
|
49
|
+
XML Editor
|
|
50
|
+
</button>
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
className={`prompt-manager-modal__tab${
|
|
54
|
+
activeTab === 'prompt-schema' ? ' is-active' : ''
|
|
55
|
+
}`}
|
|
56
|
+
onClick={() => setActiveTab('prompt-schema')}
|
|
57
|
+
>
|
|
58
|
+
Prompt Şeması
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div className="prompt-manager-modal__body">
|
|
63
|
+
{activeTab === 'xml-editor' && (
|
|
64
|
+
<XmlEditorTab data={data} setData={setData} />
|
|
65
|
+
)}
|
|
66
|
+
{activeTab === 'prompt-schema' && <PromptSchemaTab />}
|
|
67
|
+
</div>
|
|
68
|
+
</Modal>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ------------------------------------------------------------------ */
|
|
73
|
+
/* Tab 1 – XML Editor */
|
|
74
|
+
/* ------------------------------------------------------------------ */
|
|
75
|
+
|
|
76
|
+
function XmlEditorTab({
|
|
77
|
+
data,
|
|
78
|
+
setData,
|
|
79
|
+
}: {
|
|
80
|
+
data: Node;
|
|
81
|
+
setData: React.Dispatch<React.SetStateAction<Node>>;
|
|
82
|
+
}) {
|
|
83
|
+
const setCurrent = useRenderStore((s) => s.setCurrent);
|
|
84
|
+
|
|
85
|
+
const initialXml = useMemo(() => {
|
|
86
|
+
try {
|
|
87
|
+
return nodeToXml(data);
|
|
88
|
+
} catch {
|
|
89
|
+
return '';
|
|
90
|
+
}
|
|
91
|
+
}, [data]);
|
|
92
|
+
|
|
93
|
+
const [text, setText] = useState(initialXml);
|
|
94
|
+
const [parseError, setParseError] = useState<string | null>(null);
|
|
95
|
+
const [applyError, setApplyError] = useState<string | null>(null);
|
|
96
|
+
const [applySuccess, setApplySuccess] = useState(false);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
setText(initialXml);
|
|
100
|
+
setParseError(null);
|
|
101
|
+
setApplyError(null);
|
|
102
|
+
setApplySuccess(false);
|
|
103
|
+
}, [initialXml]);
|
|
104
|
+
|
|
105
|
+
const handleChange = useCallback((value: string) => {
|
|
106
|
+
setText(value);
|
|
107
|
+
setApplyError(null);
|
|
108
|
+
setApplySuccess(false);
|
|
109
|
+
}, []);
|
|
110
|
+
|
|
111
|
+
const handleApply = () => {
|
|
112
|
+
try {
|
|
113
|
+
const parsed = xmlToNode(text);
|
|
114
|
+
setParseError(null);
|
|
115
|
+
setApplyError(null);
|
|
116
|
+
setApplySuccess(false);
|
|
117
|
+
try {
|
|
118
|
+
const processed = analyseAndProccess(parsed as Node) as Node;
|
|
119
|
+
setData(processed);
|
|
120
|
+
// Keep selection in sync with the new root.
|
|
121
|
+
setCurrent(processed);
|
|
122
|
+
setApplySuccess(true);
|
|
123
|
+
} catch (e) {
|
|
124
|
+
setApplyError(e instanceof Error ? e.message : 'Failed to apply XML');
|
|
125
|
+
}
|
|
126
|
+
} catch (e) {
|
|
127
|
+
setParseError(e instanceof Error ? e.message : 'Invalid XML');
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const handleCopy = async () => {
|
|
132
|
+
try {
|
|
133
|
+
await navigator.clipboard.writeText(text);
|
|
134
|
+
} catch {
|
|
135
|
+
// ignore
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div className="prompt-manager-modal__editor-wrap">
|
|
141
|
+
<div className="prompt-manager-modal__toolbar">
|
|
142
|
+
<div style={{ fontSize: 12, opacity: 0.75 }}>node.xml</div>
|
|
143
|
+
<div style={{ display: 'flex', gap: 8 }}>
|
|
144
|
+
<button type="button" className="editor-button" onClick={handleCopy}>
|
|
145
|
+
Copy
|
|
146
|
+
</button>
|
|
147
|
+
<button type="button" className="editor-button" onClick={handleApply}>
|
|
148
|
+
Apply
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<textarea
|
|
154
|
+
className="prompt-manager-modal__editor"
|
|
155
|
+
value={text}
|
|
156
|
+
onChange={(e) => {
|
|
157
|
+
setText(e.target.value);
|
|
158
|
+
setApplyError(null);
|
|
159
|
+
setApplySuccess(false);
|
|
160
|
+
}}
|
|
161
|
+
spellCheck={false}
|
|
162
|
+
/>
|
|
163
|
+
|
|
164
|
+
{parseError ? (
|
|
165
|
+
<div style={{ fontSize: 12, color: '#d12f2f' }}>
|
|
166
|
+
Invalid XML: {parseError}
|
|
167
|
+
</div>
|
|
168
|
+
) : applyError ? (
|
|
169
|
+
<div style={{ fontSize: 12, color: '#d12f2f' }}>
|
|
170
|
+
Could not apply: {applyError}
|
|
171
|
+
</div>
|
|
172
|
+
) : applySuccess ? (
|
|
173
|
+
<div style={{ fontSize: 12, color: '#2e7d32' }}>
|
|
174
|
+
✓ Applied successfully
|
|
175
|
+
</div>
|
|
176
|
+
) : (
|
|
177
|
+
<div style={{ fontSize: 12, opacity: 0.7 }}>
|
|
178
|
+
Edit XML and press Apply to update the node tree.
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* ------------------------------------------------------------------ */
|
|
186
|
+
/* Tab 2 – Prompt Şeması */
|
|
187
|
+
/* ------------------------------------------------------------------ */
|
|
188
|
+
|
|
189
|
+
type PromptSchemaSubTab = 'onboard' | 'paywall';
|
|
190
|
+
|
|
191
|
+
function PromptSchemaTab() {
|
|
192
|
+
const [subTab, setSubTab] = useState<PromptSchemaSubTab>('onboard');
|
|
193
|
+
const [copied, setCopied] = useState(false);
|
|
194
|
+
|
|
195
|
+
const activeScheme =
|
|
196
|
+
subTab === 'onboard' ? onboardPromptScheme : paywallPromptScheme;
|
|
197
|
+
|
|
198
|
+
const handleCopy = async () => {
|
|
199
|
+
try {
|
|
200
|
+
await navigator.clipboard.writeText(activeScheme);
|
|
201
|
+
setCopied(true);
|
|
202
|
+
setTimeout(() => setCopied(false), 1500);
|
|
203
|
+
} catch {
|
|
204
|
+
// ignore
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div className="prompt-manager-modal__editor-wrap">
|
|
210
|
+
{/* Sub-tab selector + copy */}
|
|
211
|
+
<div className="prompt-manager-modal__toolbar">
|
|
212
|
+
<div style={{ display: 'flex', gap: 4 }}>
|
|
213
|
+
<button
|
|
214
|
+
type="button"
|
|
215
|
+
className="editor-button"
|
|
216
|
+
onClick={() => {
|
|
217
|
+
setSubTab('onboard');
|
|
218
|
+
setCopied(false);
|
|
219
|
+
}}
|
|
220
|
+
style={
|
|
221
|
+
subTab === 'onboard'
|
|
222
|
+
? {
|
|
223
|
+
background: '#1565c0',
|
|
224
|
+
color: '#fff',
|
|
225
|
+
borderColor: '#1565c0',
|
|
226
|
+
}
|
|
227
|
+
: undefined
|
|
228
|
+
}
|
|
229
|
+
>
|
|
230
|
+
Onboard
|
|
231
|
+
</button>
|
|
232
|
+
<button
|
|
233
|
+
type="button"
|
|
234
|
+
className="editor-button"
|
|
235
|
+
onClick={() => {
|
|
236
|
+
setSubTab('paywall');
|
|
237
|
+
setCopied(false);
|
|
238
|
+
}}
|
|
239
|
+
style={
|
|
240
|
+
subTab === 'paywall'
|
|
241
|
+
? {
|
|
242
|
+
background: '#6a1b9a',
|
|
243
|
+
color: '#fff',
|
|
244
|
+
borderColor: '#6a1b9a',
|
|
245
|
+
}
|
|
246
|
+
: undefined
|
|
247
|
+
}
|
|
248
|
+
>
|
|
249
|
+
Paywall
|
|
250
|
+
</button>
|
|
251
|
+
</div>
|
|
252
|
+
<button type="button" className="editor-button" onClick={handleCopy}>
|
|
253
|
+
{copied ? '✓ Copied' : 'Copy'}
|
|
254
|
+
</button>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<textarea
|
|
258
|
+
className="prompt-manager-modal__editor"
|
|
259
|
+
value={activeScheme}
|
|
260
|
+
readOnly
|
|
261
|
+
spellCheck={false}
|
|
262
|
+
/>
|
|
263
|
+
|
|
264
|
+
<div style={{ fontSize: 12, opacity: 0.7 }}>
|
|
265
|
+
{activeScheme.length.toLocaleString()} chars · read-only
|
|
266
|
+
(auto-generated)
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
package/src/modals/index.ts
CHANGED
|
@@ -10,3 +10,4 @@ export { ProductPresetsModal } from './ProductPresetsModal';
|
|
|
10
10
|
export { BenefitEditModal } from './BenefitEditModal';
|
|
11
11
|
export { BenefitPresetsModal } from './BenefitPresetsModal';
|
|
12
12
|
export { InspectModal } from './InspectModal';
|
|
13
|
+
export { PromptManagerModal } from './PromptManagerModal';
|
package/src/styles/index.scss
CHANGED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
@use '../foundation/colors' as colors;
|
|
2
|
+
@use '../foundation/sizes' as sizes;
|
|
3
|
+
@use '../foundation/typography' as typography;
|
|
4
|
+
|
|
5
|
+
.prompt-manager-modal__content {
|
|
6
|
+
width: calc(100vw - 32px);
|
|
7
|
+
height: calc(100vh - 32px);
|
|
8
|
+
max-width: 1400px;
|
|
9
|
+
max-height: calc(100vh - 32px);
|
|
10
|
+
padding: 0;
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.prompt-manager-modal__header {
|
|
16
|
+
padding: sizes.$spaceComfy sizes.$spaceRoomy;
|
|
17
|
+
border-bottom: 1px solid colors.$borderColor;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: space-between;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.prompt-manager-modal__tabs {
|
|
24
|
+
display: flex;
|
|
25
|
+
padding: 0 sizes.$spaceRoomy;
|
|
26
|
+
border-bottom: 1px solid colors.$borderColor;
|
|
27
|
+
background: colors.$canvasColor;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.prompt-manager-modal__tab {
|
|
31
|
+
padding: sizes.$spaceComfy sizes.$spaceRoomy;
|
|
32
|
+
font-size: 14px;
|
|
33
|
+
font-weight: 500;
|
|
34
|
+
color: colors.$mutedTextColor;
|
|
35
|
+
background: none;
|
|
36
|
+
border: none;
|
|
37
|
+
border-bottom: 2px solid transparent;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
transition: all 0.2s ease;
|
|
40
|
+
|
|
41
|
+
&:hover {
|
|
42
|
+
color: colors.$textColor;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&.is-active {
|
|
46
|
+
color: colors.$primary;
|
|
47
|
+
border-bottom-color: colors.$primary;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.prompt-manager-modal__body {
|
|
52
|
+
flex: 1;
|
|
53
|
+
min-height: 0;
|
|
54
|
+
padding: sizes.$spaceRoomy;
|
|
55
|
+
display: flex;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.prompt-manager-modal__editor-wrap {
|
|
61
|
+
flex: 1;
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: sizes.$spaceComfy;
|
|
65
|
+
min-height: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.prompt-manager-modal__toolbar {
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
justify-content: space-between;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.prompt-manager-modal__editor {
|
|
75
|
+
flex: 1;
|
|
76
|
+
width: 100%;
|
|
77
|
+
height: 100%;
|
|
78
|
+
resize: none;
|
|
79
|
+
border: 1px solid colors.$borderColor;
|
|
80
|
+
border-radius: sizes.$radiusRounded;
|
|
81
|
+
padding: sizes.$spaceComfy;
|
|
82
|
+
font-family:
|
|
83
|
+
'ui-monospace', 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas',
|
|
84
|
+
'Liberation Mono', 'Courier New', monospace;
|
|
85
|
+
font-size: 13px;
|
|
86
|
+
line-height: 1.5;
|
|
87
|
+
background: colors.$muted;
|
|
88
|
+
color: colors.$textColor;
|
|
89
|
+
outline: none;
|
|
90
|
+
|
|
91
|
+
&:focus {
|
|
92
|
+
border-color: colors.$primary;
|
|
93
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, colors.$primary, transparent 90%);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -22,7 +22,7 @@ export type AnalyseResultWithPath = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
type AttributeTypeSpec = string | string[];
|
|
25
|
-
// schema v2 supports nesting style specs under `attributes.
|
|
25
|
+
// schema v2 supports nesting style specs under `attributes.styles` (one level deep)
|
|
26
26
|
type NestedAttributeSchema = Record<string, AttributeTypeSpec>;
|
|
27
27
|
type AttributeSchema = Record<
|
|
28
28
|
string,
|
|
@@ -60,7 +60,7 @@ function getStyleSubSchema(
|
|
|
60
60
|
schema: AttributeSchema | undefined,
|
|
61
61
|
): AttributeSchema {
|
|
62
62
|
if (!schema) return {};
|
|
63
|
-
const maybe = schema.
|
|
63
|
+
const maybe = schema.styles;
|
|
64
64
|
if (!maybe) return {};
|
|
65
65
|
if (typeof maybe === 'string' || Array.isArray(maybe)) return {};
|
|
66
66
|
return (maybe ?? {}) as AttributeSchema;
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared utilities for attribute/style bag access.
|
|
3
|
-
* schemaVersion=2 uses `attributes.styles
|
|
3
|
+
* schemaVersion=2 uses `attributes.styles` exclusively.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { NodeDefaultAttribute } from '../types/Node';
|
|
7
7
|
|
|
8
|
-
/** Returns styles
|
|
8
|
+
/** Returns styles bag from attributes (schemaVersion=2 uses `styles` only). Throws if legacy `style` is found. */
|
|
9
9
|
export function getStyleBag(
|
|
10
10
|
attributes: NodeDefaultAttribute | undefined,
|
|
11
11
|
): Record<string, unknown> | undefined {
|
|
12
12
|
const record = toAttributeRecord(attributes);
|
|
13
|
-
|
|
13
|
+
if ('style' in record && record.style != null) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
'[getStyleBag] Legacy "style" key detected in attributes. Use "styles" instead (schemaVersion=2).',
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
const bag = record.styles;
|
|
14
19
|
return isPlainObject(bag) ? (bag as Record<string, unknown>) : undefined;
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
//TODO: sil
|
|
17
23
|
/** Safe indexed access to attributes. Use for reading style/direct props. */
|
|
18
24
|
export function toAttributeRecord(
|
|
19
25
|
attributes: unknown,
|
|
@@ -19,9 +19,9 @@ export function extractImageStyle<T extends ImagePropsGenerated['attributes']>(
|
|
|
19
19
|
|
|
20
20
|
if (!attributes) return style;
|
|
21
21
|
|
|
22
|
-
// Map resizeMode to CSS object-fit (schemaVersion=2 uses `styles
|
|
22
|
+
// Map resizeMode to CSS object-fit (schemaVersion=2 uses `styles`)
|
|
23
23
|
const stylesBag = getStyleBag(attributes) as
|
|
24
|
-
| ImagePropsGenerated['attributes']['
|
|
24
|
+
| ImagePropsGenerated['attributes']['styles']
|
|
25
25
|
| undefined;
|
|
26
26
|
const resizeMode: ResizeModeOptionType | undefined =
|
|
27
27
|
stylesBag?.resizeMode ?? (attributes as any).resizeMode;
|
|
@@ -40,9 +40,9 @@ export function extractImageStyleNative<
|
|
|
40
40
|
const attributes = node.attributes;
|
|
41
41
|
if (!attributes) return {};
|
|
42
42
|
|
|
43
|
-
// schemaVersion=2 uses `styles
|
|
43
|
+
// schemaVersion=2 uses `styles`
|
|
44
44
|
const nativeStylesBag = getStyleBag(attributes) as
|
|
45
|
-
| ImagePropsGenerated['attributes']['
|
|
45
|
+
| ImagePropsGenerated['attributes']['styles']
|
|
46
46
|
| undefined;
|
|
47
47
|
const resizeMode: ResizeModeOptionType | undefined =
|
|
48
48
|
nativeStylesBag?.resizeMode ?? (attributes as any).resizeMode;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { Node, NodeData } from '../types/Node';
|
|
2
|
+
|
|
3
|
+
/* ------------------------------------------------------------------ */
|
|
4
|
+
/* Node → XML */
|
|
5
|
+
/* ------------------------------------------------------------------ */
|
|
6
|
+
|
|
7
|
+
function escapeXmlAttr(str: string, quote: '"' | "'"): string {
|
|
8
|
+
let result = str
|
|
9
|
+
.replace(/&/g, '&')
|
|
10
|
+
.replace(/</g, '<')
|
|
11
|
+
.replace(/>/g, '>');
|
|
12
|
+
if (quote === '"') {
|
|
13
|
+
result = result.replace(/"/g, '"');
|
|
14
|
+
} else {
|
|
15
|
+
result = result.replace(/'/g, ''');
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function attrValueToString(value: unknown): string {
|
|
21
|
+
if (value === null || value === undefined) return '';
|
|
22
|
+
if (typeof value === 'string') return value;
|
|
23
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
24
|
+
return String(value);
|
|
25
|
+
return JSON.stringify(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function nodeToXmlInner(node: Node, indent: number): string {
|
|
29
|
+
const pad = ' '.repeat(indent);
|
|
30
|
+
|
|
31
|
+
if (node === null || node === undefined) return '';
|
|
32
|
+
if (typeof node === 'string')
|
|
33
|
+
return `${pad}${escapeXmlAttr(node, '"').replace(/"/g, '"')}`; // Text content doesn't need to escape quotes
|
|
34
|
+
|
|
35
|
+
if (Array.isArray(node)) {
|
|
36
|
+
return node.map((child) => nodeToXmlInner(child, indent)).join('\n');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (typeof node !== 'object') return '';
|
|
40
|
+
|
|
41
|
+
const record = node as NodeData;
|
|
42
|
+
const type = record.type;
|
|
43
|
+
if (!type) return '';
|
|
44
|
+
|
|
45
|
+
// Build attribute string
|
|
46
|
+
const attrs: string[] = [];
|
|
47
|
+
|
|
48
|
+
const addAttr = (key: string, val: unknown) => {
|
|
49
|
+
const strValue = attrValueToString(val);
|
|
50
|
+
// Smart quote selection:
|
|
51
|
+
// If value contains double quotes but NOT single quotes, use single quotes.
|
|
52
|
+
// Otherwise default to double quotes (and escape internal double quotes).
|
|
53
|
+
const useSingle = strValue.includes('"') && !strValue.includes("'");
|
|
54
|
+
const quote = useSingle ? "'" : '"';
|
|
55
|
+
const escaped = escapeXmlAttr(strValue, quote);
|
|
56
|
+
attrs.push(`${key}=${quote}${escaped}${quote}`);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (record.key) addAttr('key', record.key);
|
|
60
|
+
if (record.sourceType) addAttr('sourceType', record.sourceType);
|
|
61
|
+
if (record.isMain) addAttr('isMain', 'true');
|
|
62
|
+
|
|
63
|
+
if (record.attributes && typeof record.attributes === 'object') {
|
|
64
|
+
for (const [k, v] of Object.entries(record.attributes)) {
|
|
65
|
+
addAttr(k, v);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const attrStr = attrs.length > 0 ? ' ' + attrs.join(' ') : '';
|
|
70
|
+
|
|
71
|
+
// Children
|
|
72
|
+
const children = record.children;
|
|
73
|
+
if (
|
|
74
|
+
children === null ||
|
|
75
|
+
children === undefined ||
|
|
76
|
+
(Array.isArray(children) && children.length === 0)
|
|
77
|
+
) {
|
|
78
|
+
return `${pad}<${type}${attrStr} />`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const childXml = nodeToXmlInner(children, indent + 1);
|
|
82
|
+
return `${pad}<${type}${attrStr}>\n${childXml}\n${pad}</${type}>`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Serialize a Node tree to an XML string.
|
|
87
|
+
*/
|
|
88
|
+
export function nodeToXml(node: Node): string {
|
|
89
|
+
if (node === null || node === undefined) return '';
|
|
90
|
+
return nodeToXmlInner(node, 0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* ------------------------------------------------------------------ */
|
|
94
|
+
/* XML → Node */
|
|
95
|
+
/* ------------------------------------------------------------------ */
|
|
96
|
+
|
|
97
|
+
function unescapeXml(str: string): string {
|
|
98
|
+
return str
|
|
99
|
+
.replace(/"/g, '"')
|
|
100
|
+
.replace(/>/g, '>')
|
|
101
|
+
.replace(/</g, '<')
|
|
102
|
+
.replace(/&/g, '&');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseAttrValue(raw: string): unknown {
|
|
106
|
+
const str = unescapeXml(raw);
|
|
107
|
+
|
|
108
|
+
// Boolean
|
|
109
|
+
if (str === 'true') return true;
|
|
110
|
+
if (str === 'false') return false;
|
|
111
|
+
|
|
112
|
+
// Number
|
|
113
|
+
if (str !== '' && !Number.isNaN(Number(str))) return Number(str);
|
|
114
|
+
|
|
115
|
+
// JSON object / array
|
|
116
|
+
if (
|
|
117
|
+
(str.startsWith('{') && str.endsWith('}')) ||
|
|
118
|
+
(str.startsWith('[') && str.endsWith(']'))
|
|
119
|
+
) {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(str);
|
|
122
|
+
} catch {
|
|
123
|
+
// fall through to string
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return str;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function domElementToNode(el: Element): NodeData {
|
|
131
|
+
const type = el.tagName;
|
|
132
|
+
const nodeData: NodeData = { type, children: null };
|
|
133
|
+
|
|
134
|
+
// Reserved XML attributes → NodeData fields
|
|
135
|
+
const reserved = new Set(['key', 'sourceType', 'isMain']);
|
|
136
|
+
const attributes: Record<string, unknown> = {};
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < el.attributes.length; i++) {
|
|
139
|
+
const attr = el.attributes[i];
|
|
140
|
+
if (reserved.has(attr.name)) {
|
|
141
|
+
if (attr.name === 'key') nodeData.key = attr.value;
|
|
142
|
+
else if (attr.name === 'sourceType') nodeData.sourceType = attr.value;
|
|
143
|
+
else if (attr.name === 'isMain') nodeData.isMain = attr.value === 'true';
|
|
144
|
+
} else {
|
|
145
|
+
attributes[attr.name] = parseAttrValue(attr.value);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (Object.keys(attributes).length > 0) {
|
|
150
|
+
nodeData.attributes = attributes;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Children
|
|
154
|
+
const childNodes: Node[] = [];
|
|
155
|
+
for (let i = 0; i < el.childNodes.length; i++) {
|
|
156
|
+
const child = el.childNodes[i];
|
|
157
|
+
if (child.nodeType === 1) {
|
|
158
|
+
// Element
|
|
159
|
+
childNodes.push(domElementToNode(child as Element));
|
|
160
|
+
} else if (child.nodeType === 3) {
|
|
161
|
+
// Text
|
|
162
|
+
const text = (child.textContent ?? '').trim();
|
|
163
|
+
if (text) childNodes.push(text);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (childNodes.length === 0) {
|
|
168
|
+
nodeData.children = null;
|
|
169
|
+
} else if (childNodes.length === 1) {
|
|
170
|
+
nodeData.children = childNodes[0];
|
|
171
|
+
} else {
|
|
172
|
+
nodeData.children = childNodes;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return nodeData;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Parse an XML string back into a Node tree.
|
|
180
|
+
*
|
|
181
|
+
* Uses the browser's built-in DOMParser.
|
|
182
|
+
*/
|
|
183
|
+
export function xmlToNode(xml: string): Node {
|
|
184
|
+
if (!xml.trim()) return null;
|
|
185
|
+
|
|
186
|
+
const parser = new DOMParser();
|
|
187
|
+
const doc = parser.parseFromString(xml, 'text/xml');
|
|
188
|
+
|
|
189
|
+
const parseError = doc.querySelector('parsererror');
|
|
190
|
+
if (parseError) {
|
|
191
|
+
throw new Error(parseError.textContent ?? 'XML parse error');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const root = doc.documentElement;
|
|
195
|
+
return domElementToNode(root);
|
|
196
|
+
}
|
package/src/utils/patterns.ts
CHANGED
|
@@ -73,7 +73,7 @@ function normalizePlatform(platform?: BuilderPlatform | null): BuilderPlatform {
|
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* Keys that conceptually behave like component props (behavior/flags) rather than
|
|
76
|
-
* style properties. These should NOT be normalized into `attributes.
|
|
76
|
+
* style properties. These should NOT be normalized into `attributes.styles`.
|
|
77
77
|
*/
|
|
78
78
|
export const NON_STYLE_ATTRIBUTE_KEYS = new Set<string>([
|
|
79
79
|
'scrollable',
|
|
@@ -82,7 +82,7 @@ export const NON_STYLE_ATTRIBUTE_KEYS = new Set<string>([
|
|
|
82
82
|
]);
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* schemaVersion=2 stores style-like keys inside `attributes.
|
|
85
|
+
* schemaVersion=2 stores style-like keys inside `attributes.styles`.
|
|
86
86
|
*
|
|
87
87
|
* We treat View+Text schema keys as style-like by default, but explicitly exclude
|
|
88
88
|
* known non-style props (behavior flags).
|
|
@@ -97,7 +97,7 @@ export function getStyleAttributeKeySet(): Set<string> {
|
|
|
97
97
|
unknown
|
|
98
98
|
>;
|
|
99
99
|
const getNestedStyleKeys = (schema: Record<string, unknown>): string[] => {
|
|
100
|
-
const nested = schema.
|
|
100
|
+
const nested = schema.styles;
|
|
101
101
|
return typeof nested === 'object' && nested
|
|
102
102
|
? Object.keys(nested)
|
|
103
103
|
: Object.keys(schema);
|