@lvce-editor/preview-worker 1.5.0 → 1.7.0
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/README.md +9 -0
- package/dist/previewWorkerMain.js +223 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1127,6 +1127,8 @@ const Code$1 = 65;
|
|
|
1127
1127
|
const Label$1 = 66;
|
|
1128
1128
|
const Dt$1 = 67;
|
|
1129
1129
|
const Iframe$1 = 68;
|
|
1130
|
+
const Style = 72;
|
|
1131
|
+
const Html = 73;
|
|
1130
1132
|
const Reference = 100;
|
|
1131
1133
|
|
|
1132
1134
|
const TargetName = 'event.target.name';
|
|
@@ -1306,6 +1308,7 @@ const create = (uid, uri, x, y, width, height, platform, assetDir) => {
|
|
|
1306
1308
|
const state = {
|
|
1307
1309
|
assetDir,
|
|
1308
1310
|
content: '',
|
|
1311
|
+
css: [],
|
|
1309
1312
|
errorCount: 0,
|
|
1310
1313
|
errorMessage: '',
|
|
1311
1314
|
initial: true,
|
|
@@ -1319,15 +1322,28 @@ const create = (uid, uri, x, y, width, height, platform, assetDir) => {
|
|
|
1319
1322
|
set(uid, state, state);
|
|
1320
1323
|
};
|
|
1321
1324
|
|
|
1325
|
+
const iEqual = (oldState, newState) => {
|
|
1326
|
+
if (oldState.css.length !== newState.css.length) {
|
|
1327
|
+
return false;
|
|
1328
|
+
}
|
|
1329
|
+
for (let i = 0; i < oldState.css.length; i++) {
|
|
1330
|
+
if (oldState.css[i] !== newState.css[i]) {
|
|
1331
|
+
return false;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return true;
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1322
1337
|
const isEqual = (oldState, newState) => {
|
|
1323
|
-
return oldState.warningCount === newState.warningCount && oldState.initial === newState.initial && oldState.content === newState.content && oldState.parsedDom === newState.parsedDom && oldState.parsedNodesChildNodeCount === newState.parsedNodesChildNodeCount;
|
|
1338
|
+
return oldState.warningCount === newState.warningCount && oldState.initial === newState.initial && oldState.content === newState.content && oldState.parsedDom === newState.parsedDom && oldState.parsedNodesChildNodeCount === newState.parsedNodesChildNodeCount && oldState.css === newState.css;
|
|
1324
1339
|
};
|
|
1325
1340
|
|
|
1326
1341
|
const RenderItems = 4;
|
|
1342
|
+
const RenderCss = 10;
|
|
1327
1343
|
const RenderIncremental = 11;
|
|
1328
1344
|
|
|
1329
|
-
const modules = [isEqual];
|
|
1330
|
-
const numbers = [RenderIncremental];
|
|
1345
|
+
const modules = [isEqual, iEqual];
|
|
1346
|
+
const numbers = [RenderIncremental, RenderCss];
|
|
1331
1347
|
|
|
1332
1348
|
const diff = (oldState, newState) => {
|
|
1333
1349
|
const diffResult = [];
|
|
@@ -1798,6 +1814,10 @@ const getVirtualDomTag = text => {
|
|
|
1798
1814
|
return Tr$1;
|
|
1799
1815
|
case Ul:
|
|
1800
1816
|
return Ul$1;
|
|
1817
|
+
case 'html':
|
|
1818
|
+
return Html;
|
|
1819
|
+
case 'style':
|
|
1820
|
+
return Style;
|
|
1801
1821
|
default:
|
|
1802
1822
|
return Div$1;
|
|
1803
1823
|
}
|
|
@@ -1826,6 +1846,27 @@ const EndCommentTag = 19;
|
|
|
1826
1846
|
const Text = 20;
|
|
1827
1847
|
const CommentStart = 21;
|
|
1828
1848
|
|
|
1849
|
+
/* eslint-disable @cspell/spellchecker */
|
|
1850
|
+
// Common HTML attributes that are safe to allow by default
|
|
1851
|
+
const commonAllowedAttributes = new Set([
|
|
1852
|
+
// Global attributes
|
|
1853
|
+
'id', 'title', 'tabindex', 'class', 'style', 'lang', 'dir', 'hidden', 'contenteditable', 'draggable', 'spellcheck', 'translate', 'role',
|
|
1854
|
+
// Form input attributes
|
|
1855
|
+
'disabled', 'name', 'type', 'value', 'placeholder', 'required', 'readonly', 'checked', 'autofocus', 'autocomplete', 'multiple', 'accept', 'min', 'max', 'step', 'pattern', 'maxlength', 'minlength', 'size', 'rows', 'cols', 'wrap', 'inputmode',
|
|
1856
|
+
// Form attributes
|
|
1857
|
+
'action', 'method', 'enctype', 'target', 'novalidate', 'form',
|
|
1858
|
+
// Link attributes
|
|
1859
|
+
'href', 'rel', 'download', 'hreflang',
|
|
1860
|
+
// Image attributes
|
|
1861
|
+
'src', 'alt', 'width', 'height', 'loading', 'decoding', 'crossorigin', 'srcset', 'sizes',
|
|
1862
|
+
// Media attributes
|
|
1863
|
+
'controls', 'autoplay', 'loop', 'muted', 'preload', 'poster',
|
|
1864
|
+
// Table attributes
|
|
1865
|
+
'colspan', 'rowspan', 'headers', 'scope',
|
|
1866
|
+
// List attributes
|
|
1867
|
+
'reversed', 'start',
|
|
1868
|
+
// Other semantic attributes
|
|
1869
|
+
'open', 'datetime', 'cite', 'for', 'label']);
|
|
1829
1870
|
const isDefaultAllowedAttribute = (attributeName, defaultAllowedAttributes) => {
|
|
1830
1871
|
// Allow data-* attributes
|
|
1831
1872
|
if (attributeName.startsWith('data-')) {
|
|
@@ -1835,8 +1876,8 @@ const isDefaultAllowedAttribute = (attributeName, defaultAllowedAttributes) => {
|
|
|
1835
1876
|
if (attributeName.startsWith('aria-')) {
|
|
1836
1877
|
return true;
|
|
1837
1878
|
}
|
|
1838
|
-
//
|
|
1839
|
-
if (attributeName
|
|
1879
|
+
// Check if it's a common HTML attribute
|
|
1880
|
+
if (commonAllowedAttributes.has(attributeName)) {
|
|
1840
1881
|
return true;
|
|
1841
1882
|
}
|
|
1842
1883
|
// Check if in default list
|
|
@@ -1844,11 +1885,21 @@ const isDefaultAllowedAttribute = (attributeName, defaultAllowedAttributes) => {
|
|
|
1844
1885
|
};
|
|
1845
1886
|
|
|
1846
1887
|
const isSelfClosingTag = tag => {
|
|
1847
|
-
switch (tag) {
|
|
1888
|
+
switch (tag.toLowerCase()) {
|
|
1889
|
+
case 'area':
|
|
1890
|
+
case 'base':
|
|
1891
|
+
case 'col':
|
|
1848
1892
|
case Br:
|
|
1849
1893
|
case Hr:
|
|
1850
1894
|
case Img:
|
|
1851
1895
|
case Input:
|
|
1896
|
+
case 'embed':
|
|
1897
|
+
case 'link':
|
|
1898
|
+
case 'meta':
|
|
1899
|
+
case 'param':
|
|
1900
|
+
case 'source':
|
|
1901
|
+
case 'track':
|
|
1902
|
+
case 'wbr':
|
|
1852
1903
|
return true;
|
|
1853
1904
|
default:
|
|
1854
1905
|
return false;
|
|
@@ -1882,7 +1933,7 @@ const State = {
|
|
|
1882
1933
|
TopLevelContent: 1
|
|
1883
1934
|
};
|
|
1884
1935
|
const RE_ANGLE_BRACKET_OPEN = /^</;
|
|
1885
|
-
const RE_ANGLE_BRACKET_OPEN_TAG = /^<(?![\s
|
|
1936
|
+
const RE_ANGLE_BRACKET_OPEN_TAG = /^<(?![\s%])/;
|
|
1886
1937
|
const RE_ANGLE_BRACKET_CLOSE = /^>/;
|
|
1887
1938
|
const RE_SLASH = /^\//;
|
|
1888
1939
|
const RE_TAGNAME = /^[a-zA-Z\d$]+/;
|
|
@@ -2117,6 +2168,14 @@ const tokenizeHtml = text => {
|
|
|
2117
2168
|
return tokens;
|
|
2118
2169
|
};
|
|
2119
2170
|
|
|
2171
|
+
// Tags that should be completely skipped (both tag and content)
|
|
2172
|
+
const TAGS_TO_SKIP_COMPLETELY = new Set(['meta', 'title']);
|
|
2173
|
+
|
|
2174
|
+
// Tags that should have their opening/closing tags skipped but content processed
|
|
2175
|
+
const TAGS_TO_SKIP_TAG_ONLY = new Set(['html', 'head']);
|
|
2176
|
+
|
|
2177
|
+
// Tags where we capture content as CSS
|
|
2178
|
+
const TAGS_TO_CAPTURE_AS_CSS = new Set(['style']);
|
|
2120
2179
|
const parseHtml = (html, allowedAttributes = [], defaultAllowedAttributes = []) => {
|
|
2121
2180
|
string(html);
|
|
2122
2181
|
array(allowedAttributes);
|
|
@@ -2127,65 +2186,125 @@ const parseHtml = (html, allowedAttributes = [], defaultAllowedAttributes = [])
|
|
|
2127
2186
|
const useBuiltInDefaults = allowedAttributes.length === 0;
|
|
2128
2187
|
const tokens = tokenizeHtml(html);
|
|
2129
2188
|
const dom = [];
|
|
2189
|
+
const css = [];
|
|
2130
2190
|
const root = {
|
|
2131
2191
|
childCount: 0,
|
|
2132
2192
|
type: 0
|
|
2133
2193
|
};
|
|
2134
2194
|
let current = root;
|
|
2135
2195
|
const stack = [root];
|
|
2196
|
+
const tagStack = []; // Track tag names to match closing tags
|
|
2136
2197
|
let attributeName = '';
|
|
2137
2198
|
let lastTagWasSelfClosing = false;
|
|
2199
|
+
let skipDepth = 0; // Track how many levels deep we are in skipped content
|
|
2200
|
+
let captureCss = false; // Track if we're inside a style tag
|
|
2201
|
+
let cssContent = ''; // Accumulate CSS content
|
|
2202
|
+
|
|
2138
2203
|
for (const token of tokens) {
|
|
2139
2204
|
switch (token.type) {
|
|
2140
2205
|
case AttributeName:
|
|
2141
|
-
|
|
2206
|
+
if (skipDepth === 0 && !captureCss) {
|
|
2207
|
+
attributeName = token.text;
|
|
2208
|
+
}
|
|
2142
2209
|
break;
|
|
2143
2210
|
case AttributeValue:
|
|
2144
|
-
if (allAllowedAttributes.has(attributeName) || useBuiltInDefaults && isDefaultAllowedAttribute(attributeName, defaultAllowedAttributes)) {
|
|
2211
|
+
if (skipDepth === 0 && !captureCss && (allAllowedAttributes.has(attributeName) || useBuiltInDefaults && isDefaultAllowedAttribute(attributeName, defaultAllowedAttributes))) {
|
|
2145
2212
|
const finalAttributeName = attributeName === 'class' ? 'className' : attributeName;
|
|
2146
2213
|
current[finalAttributeName] = token.text;
|
|
2147
2214
|
}
|
|
2148
2215
|
attributeName = '';
|
|
2149
2216
|
break;
|
|
2150
2217
|
case ClosingAngleBracket:
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2218
|
+
if (skipDepth === 0 && !captureCss) {
|
|
2219
|
+
// Handle boolean attributes (attributes without values)
|
|
2220
|
+
if (attributeName && (allAllowedAttributes.has(attributeName) || useBuiltInDefaults && isDefaultAllowedAttribute(attributeName, defaultAllowedAttributes))) {
|
|
2221
|
+
const finalAttributeName = attributeName === 'class' ? 'className' : attributeName;
|
|
2222
|
+
current[finalAttributeName] = attributeName;
|
|
2223
|
+
}
|
|
2224
|
+
attributeName = '';
|
|
2225
|
+
// Return to parent if the current tag is self-closing
|
|
2226
|
+
if (lastTagWasSelfClosing) {
|
|
2227
|
+
current = stack.at(-1) || root;
|
|
2228
|
+
lastTagWasSelfClosing = false;
|
|
2229
|
+
}
|
|
2161
2230
|
}
|
|
2162
2231
|
break;
|
|
2163
2232
|
case Content:
|
|
2164
|
-
|
|
2165
|
-
|
|
2233
|
+
if (captureCss) {
|
|
2234
|
+
cssContent += token.text;
|
|
2235
|
+
} else if (skipDepth === 0) {
|
|
2236
|
+
current.childCount++;
|
|
2237
|
+
dom.push(text(parseText(token.text)));
|
|
2238
|
+
}
|
|
2239
|
+
break;
|
|
2240
|
+
case Doctype:
|
|
2241
|
+
// Ignore DOCTYPE - it's parsed but not rendered since we're in a div
|
|
2166
2242
|
break;
|
|
2167
2243
|
case TagNameEnd:
|
|
2168
|
-
|
|
2169
|
-
|
|
2244
|
+
const tagNameToClose = tagStack.pop()?.toLowerCase() || '';
|
|
2245
|
+
if (TAGS_TO_CAPTURE_AS_CSS.has(tagNameToClose)) {
|
|
2246
|
+
// Finished capturing CSS
|
|
2247
|
+
if (cssContent.trim()) {
|
|
2248
|
+
css.push(cssContent);
|
|
2249
|
+
}
|
|
2250
|
+
cssContent = '';
|
|
2251
|
+
captureCss = false;
|
|
2252
|
+
} else if (TAGS_TO_SKIP_COMPLETELY.has(tagNameToClose)) {
|
|
2253
|
+
// We were skipping this content, so decrement skipDepth
|
|
2254
|
+
skipDepth--;
|
|
2255
|
+
} else if (TAGS_TO_SKIP_TAG_ONLY.has(tagNameToClose)) ; else {
|
|
2256
|
+
// Normal tag - pop from stack
|
|
2257
|
+
if (stack.length > 1) {
|
|
2258
|
+
stack.pop();
|
|
2259
|
+
}
|
|
2260
|
+
current = stack.at(-1) || root;
|
|
2170
2261
|
}
|
|
2171
|
-
current = stack.at(-1) || root;
|
|
2172
2262
|
break;
|
|
2173
2263
|
case TagNameStart:
|
|
2174
|
-
|
|
2175
|
-
const newNode = {
|
|
2176
|
-
childCount: 0,
|
|
2177
|
-
type: getVirtualDomTag(token.text)
|
|
2178
|
-
};
|
|
2179
|
-
dom.push(newNode);
|
|
2180
|
-
current = newNode;
|
|
2264
|
+
const tagNameLower = token.text.toLowerCase();
|
|
2181
2265
|
lastTagWasSelfClosing = isSelfClosingTag(token.text);
|
|
2182
|
-
|
|
2183
|
-
|
|
2266
|
+
|
|
2267
|
+
// Check if this tag captures CSS content
|
|
2268
|
+
if (TAGS_TO_CAPTURE_AS_CSS.has(tagNameLower)) {
|
|
2269
|
+
captureCss = true;
|
|
2270
|
+
cssContent = '';
|
|
2271
|
+
tagStack.push(token.text);
|
|
2272
|
+
}
|
|
2273
|
+
// Check if this tag should be completely skipped (meta, title)
|
|
2274
|
+
else if (TAGS_TO_SKIP_COMPLETELY.has(tagNameLower)) {
|
|
2275
|
+
if (!lastTagWasSelfClosing) {
|
|
2276
|
+
// For non-self-closing tags like title, mark as skipped
|
|
2277
|
+
skipDepth++;
|
|
2278
|
+
tagStack.push(token.text);
|
|
2279
|
+
}
|
|
2280
|
+
// For self-closing tags like meta, we just skip them without tracking
|
|
2281
|
+
}
|
|
2282
|
+
// Check if this tag should have its opening/closing tags skipped (html, head)
|
|
2283
|
+
else if (TAGS_TO_SKIP_TAG_ONLY.has(tagNameLower)) {
|
|
2284
|
+
if (!lastTagWasSelfClosing) {
|
|
2285
|
+
// Track the tag name for matching the closing tag
|
|
2286
|
+
tagStack.push(token.text);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
// Normal tag processing
|
|
2290
|
+
else if (skipDepth === 0) {
|
|
2291
|
+
current.childCount++;
|
|
2292
|
+
const newNode = {
|
|
2293
|
+
childCount: 0,
|
|
2294
|
+
type: getVirtualDomTag(token.text)
|
|
2295
|
+
};
|
|
2296
|
+
dom.push(newNode);
|
|
2297
|
+
current = newNode;
|
|
2298
|
+
if (!lastTagWasSelfClosing) {
|
|
2299
|
+
stack.push(current);
|
|
2300
|
+
tagStack.push(token.text);
|
|
2301
|
+
}
|
|
2184
2302
|
}
|
|
2185
2303
|
break;
|
|
2186
2304
|
case WhitespaceInsideOpeningTag:
|
|
2305
|
+
if (skipDepth === 0 && !captureCss &&
|
|
2187
2306
|
// Handle boolean attributes (attributes without values)
|
|
2188
|
-
|
|
2307
|
+
attributeName && (allAllowedAttributes.has(attributeName) || useBuiltInDefaults && isDefaultAllowedAttribute(attributeName, defaultAllowedAttributes))) {
|
|
2189
2308
|
const finalAttributeName = attributeName === 'class' ? 'className' : attributeName;
|
|
2190
2309
|
current[finalAttributeName] = attributeName;
|
|
2191
2310
|
}
|
|
@@ -2202,7 +2321,10 @@ const parseHtml = (html, allowedAttributes = [], defaultAllowedAttributes = [])
|
|
|
2202
2321
|
} catch {
|
|
2203
2322
|
dom.rootChildCount = root.childCount;
|
|
2204
2323
|
}
|
|
2205
|
-
return
|
|
2324
|
+
return {
|
|
2325
|
+
css,
|
|
2326
|
+
dom
|
|
2327
|
+
};
|
|
2206
2328
|
};
|
|
2207
2329
|
|
|
2208
2330
|
const handleEditorChanged = async () => {
|
|
@@ -2238,12 +2360,13 @@ const handleEditorChanged = async () => {
|
|
|
2238
2360
|
if (matchingEditorUid !== null) {
|
|
2239
2361
|
try {
|
|
2240
2362
|
const content = await invoke$1('Editor.getText', matchingEditorUid);
|
|
2241
|
-
const
|
|
2363
|
+
const parseResult = parseHtml(content, []);
|
|
2242
2364
|
const updatedState = {
|
|
2243
2365
|
...state,
|
|
2244
2366
|
content,
|
|
2367
|
+
css: parseResult.css,
|
|
2245
2368
|
errorMessage: '',
|
|
2246
|
-
parsedDom
|
|
2369
|
+
parsedDom: parseResult.dom
|
|
2247
2370
|
};
|
|
2248
2371
|
set(previewUid, state, updatedState);
|
|
2249
2372
|
} catch (error) {
|
|
@@ -2252,6 +2375,7 @@ const handleEditorChanged = async () => {
|
|
|
2252
2375
|
const updatedState = {
|
|
2253
2376
|
...state,
|
|
2254
2377
|
content: '',
|
|
2378
|
+
css: [],
|
|
2255
2379
|
errorMessage,
|
|
2256
2380
|
parsedDom: []
|
|
2257
2381
|
};
|
|
@@ -2293,11 +2417,16 @@ const updateContent = async (state, uri) => {
|
|
|
2293
2417
|
// @ts-ignore
|
|
2294
2418
|
const content = await readFile(uri);
|
|
2295
2419
|
|
|
2296
|
-
// Parse the content into virtual DOM
|
|
2297
|
-
const
|
|
2420
|
+
// Parse the content into virtual DOM and CSS
|
|
2421
|
+
const parseResult = parseHtml(content);
|
|
2422
|
+
const parsedDom = parseResult.dom;
|
|
2423
|
+
const {
|
|
2424
|
+
css
|
|
2425
|
+
} = parseResult;
|
|
2298
2426
|
const parsedNodesChildNodeCount = getParsedNodesChildNodeCount(parsedDom);
|
|
2299
2427
|
return {
|
|
2300
2428
|
content,
|
|
2429
|
+
css,
|
|
2301
2430
|
errorMessage: '',
|
|
2302
2431
|
parsedDom,
|
|
2303
2432
|
parsedNodesChildNodeCount
|
|
@@ -2307,6 +2436,7 @@ const updateContent = async (state, uri) => {
|
|
|
2307
2436
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
2308
2437
|
return {
|
|
2309
2438
|
content: '',
|
|
2439
|
+
css: [],
|
|
2310
2440
|
errorMessage,
|
|
2311
2441
|
parsedDom: [],
|
|
2312
2442
|
parsedNodesChildNodeCount: 0
|
|
@@ -2317,6 +2447,7 @@ const updateContent = async (state, uri) => {
|
|
|
2317
2447
|
const handleFileEdited = async state => {
|
|
2318
2448
|
const {
|
|
2319
2449
|
content,
|
|
2450
|
+
css,
|
|
2320
2451
|
errorMessage,
|
|
2321
2452
|
parsedDom,
|
|
2322
2453
|
parsedNodesChildNodeCount
|
|
@@ -2324,6 +2455,7 @@ const handleFileEdited = async state => {
|
|
|
2324
2455
|
return {
|
|
2325
2456
|
...state,
|
|
2326
2457
|
content,
|
|
2458
|
+
css,
|
|
2327
2459
|
errorMessage,
|
|
2328
2460
|
parsedDom,
|
|
2329
2461
|
parsedNodesChildNodeCount
|
|
@@ -2345,11 +2477,13 @@ const loadContent = async state => {
|
|
|
2345
2477
|
// Read and parse file contents if we have a URI
|
|
2346
2478
|
const {
|
|
2347
2479
|
content,
|
|
2480
|
+
css,
|
|
2348
2481
|
errorMessage,
|
|
2349
2482
|
parsedDom,
|
|
2350
2483
|
parsedNodesChildNodeCount
|
|
2351
2484
|
} = state.uri ? await updateContent(state, state.uri) : {
|
|
2352
2485
|
content: state.content,
|
|
2486
|
+
css: state.css,
|
|
2353
2487
|
errorMessage: state.errorMessage,
|
|
2354
2488
|
parsedDom: state.parsedDom,
|
|
2355
2489
|
parsedNodesChildNodeCount: state.parsedNodesChildNodeCount
|
|
@@ -2357,6 +2491,7 @@ const loadContent = async state => {
|
|
|
2357
2491
|
return {
|
|
2358
2492
|
...state,
|
|
2359
2493
|
content,
|
|
2494
|
+
css,
|
|
2360
2495
|
errorCount: 0,
|
|
2361
2496
|
errorMessage,
|
|
2362
2497
|
initial: false,
|
|
@@ -2366,6 +2501,51 @@ const loadContent = async state => {
|
|
|
2366
2501
|
};
|
|
2367
2502
|
};
|
|
2368
2503
|
|
|
2504
|
+
const BODY_SELECTOR_REGEX = /\bbody\b/g;
|
|
2505
|
+
const HTML_SELECTOR_REGEX = /\bhtml\b/g;
|
|
2506
|
+
|
|
2507
|
+
/**
|
|
2508
|
+
* Wraps CSS in a CSS nesting block (.Preview { ... }) and replaces 'html' and 'body'
|
|
2509
|
+
* selectors with '&' (the parent selector in CSS nesting).
|
|
2510
|
+
* This approach uses CSS nesting to automatically scope all selectors to the preview div.
|
|
2511
|
+
* Other selectors like 'button' or '*' are automatically scoped within the nesting.
|
|
2512
|
+
*
|
|
2513
|
+
* @param css The CSS string to process
|
|
2514
|
+
* @returns The CSS string wrapped in .Preview nesting block with proper selector replacements
|
|
2515
|
+
*/
|
|
2516
|
+
const replaceCssBodySelector = css => {
|
|
2517
|
+
if (!css.trim()) {
|
|
2518
|
+
return css;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
// Replace 'html' selector with '&' (CSS nesting parent selector)
|
|
2522
|
+
let result = css.replaceAll(HTML_SELECTOR_REGEX, '&');
|
|
2523
|
+
|
|
2524
|
+
// Replace 'body' selector with '&' (CSS nesting parent selector)
|
|
2525
|
+
result = result.replaceAll(BODY_SELECTOR_REGEX, '&');
|
|
2526
|
+
|
|
2527
|
+
// Wrap the entire CSS in .Preview nesting block
|
|
2528
|
+
result = `.Preview {\n${result}\n}`;
|
|
2529
|
+
return result;
|
|
2530
|
+
};
|
|
2531
|
+
|
|
2532
|
+
const renderCss = (oldState, newState) => {
|
|
2533
|
+
const {
|
|
2534
|
+
css,
|
|
2535
|
+
uid
|
|
2536
|
+
} = newState;
|
|
2537
|
+
|
|
2538
|
+
// Combine all CSS strings into a single string
|
|
2539
|
+
let cssString = css.join('\n');
|
|
2540
|
+
|
|
2541
|
+
// Replace body selector with .Preview since we render the preview in a div element, not a body
|
|
2542
|
+
cssString = replaceCssBodySelector(cssString);
|
|
2543
|
+
|
|
2544
|
+
// Return command in format that can be handled by the viewlet
|
|
2545
|
+
// The 'Viewlet.setCss' is a method that should be called on the viewlet
|
|
2546
|
+
return ['Viewlet.setCss', uid, cssString];
|
|
2547
|
+
};
|
|
2548
|
+
|
|
2369
2549
|
const getEmptyPreviewDom = () => {
|
|
2370
2550
|
return [{
|
|
2371
2551
|
childCount: 1,
|
|
@@ -2432,6 +2612,8 @@ const renderIncremental = (oldState, newState) => {
|
|
|
2432
2612
|
|
|
2433
2613
|
const getRenderer = diffType => {
|
|
2434
2614
|
switch (diffType) {
|
|
2615
|
+
case RenderCss:
|
|
2616
|
+
return renderCss;
|
|
2435
2617
|
case RenderIncremental:
|
|
2436
2618
|
return renderIncremental;
|
|
2437
2619
|
case RenderItems:
|
|
@@ -2500,6 +2682,7 @@ const saveState = state => {
|
|
|
2500
2682
|
const setUri = async (state, uri) => {
|
|
2501
2683
|
const {
|
|
2502
2684
|
content,
|
|
2685
|
+
css,
|
|
2503
2686
|
errorMessage,
|
|
2504
2687
|
parsedDom,
|
|
2505
2688
|
parsedNodesChildNodeCount
|
|
@@ -2507,6 +2690,7 @@ const setUri = async (state, uri) => {
|
|
|
2507
2690
|
return {
|
|
2508
2691
|
...state,
|
|
2509
2692
|
content,
|
|
2693
|
+
css,
|
|
2510
2694
|
errorMessage,
|
|
2511
2695
|
parsedDom,
|
|
2512
2696
|
parsedNodesChildNodeCount,
|