@lvce-editor/preview-worker 1.4.0 → 1.6.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/dist/previewWorkerMain.js +244 -38
- package/package.json +1 -1
|
@@ -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,12 +1846,60 @@ 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']);
|
|
1870
|
+
const isDefaultAllowedAttribute = (attributeName, defaultAllowedAttributes) => {
|
|
1871
|
+
// Allow data-* attributes
|
|
1872
|
+
if (attributeName.startsWith('data-')) {
|
|
1873
|
+
return true;
|
|
1874
|
+
}
|
|
1875
|
+
// Allow aria-* attributes
|
|
1876
|
+
if (attributeName.startsWith('aria-')) {
|
|
1877
|
+
return true;
|
|
1878
|
+
}
|
|
1879
|
+
// Check if it's a common HTML attribute
|
|
1880
|
+
if (commonAllowedAttributes.has(attributeName)) {
|
|
1881
|
+
return true;
|
|
1882
|
+
}
|
|
1883
|
+
// Check if in default list
|
|
1884
|
+
return defaultAllowedAttributes.includes(attributeName);
|
|
1885
|
+
};
|
|
1886
|
+
|
|
1829
1887
|
const isSelfClosingTag = tag => {
|
|
1830
|
-
switch (tag) {
|
|
1888
|
+
switch (tag.toLowerCase()) {
|
|
1889
|
+
case 'area':
|
|
1890
|
+
case 'base':
|
|
1891
|
+
case 'col':
|
|
1831
1892
|
case Br:
|
|
1832
1893
|
case Hr:
|
|
1833
1894
|
case Img:
|
|
1834
1895
|
case Input:
|
|
1896
|
+
case 'embed':
|
|
1897
|
+
case 'link':
|
|
1898
|
+
case 'meta':
|
|
1899
|
+
case 'param':
|
|
1900
|
+
case 'source':
|
|
1901
|
+
case 'track':
|
|
1902
|
+
case 'wbr':
|
|
1835
1903
|
return true;
|
|
1836
1904
|
default:
|
|
1837
1905
|
return false;
|
|
@@ -1865,7 +1933,7 @@ const State = {
|
|
|
1865
1933
|
TopLevelContent: 1
|
|
1866
1934
|
};
|
|
1867
1935
|
const RE_ANGLE_BRACKET_OPEN = /^</;
|
|
1868
|
-
const RE_ANGLE_BRACKET_OPEN_TAG = /^<(?![\s
|
|
1936
|
+
const RE_ANGLE_BRACKET_OPEN_TAG = /^<(?![\s%])/;
|
|
1869
1937
|
const RE_ANGLE_BRACKET_CLOSE = /^>/;
|
|
1870
1938
|
const RE_SLASH = /^\//;
|
|
1871
1939
|
const RE_TAGNAME = /^[a-zA-Z\d$]+/;
|
|
@@ -2100,70 +2168,143 @@ const tokenizeHtml = text => {
|
|
|
2100
2168
|
return tokens;
|
|
2101
2169
|
};
|
|
2102
2170
|
|
|
2103
|
-
|
|
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']);
|
|
2179
|
+
const parseHtml = (html, allowedAttributes = [], defaultAllowedAttributes = []) => {
|
|
2104
2180
|
string(html);
|
|
2105
2181
|
array(allowedAttributes);
|
|
2182
|
+
array(defaultAllowedAttributes);
|
|
2183
|
+
|
|
2184
|
+
// Combine default allowed attributes with any additional ones provided
|
|
2185
|
+
const allAllowedAttributes = new Set([...defaultAllowedAttributes, ...allowedAttributes]);
|
|
2186
|
+
const useBuiltInDefaults = allowedAttributes.length === 0;
|
|
2106
2187
|
const tokens = tokenizeHtml(html);
|
|
2107
2188
|
const dom = [];
|
|
2189
|
+
const css = [];
|
|
2108
2190
|
const root = {
|
|
2109
2191
|
childCount: 0,
|
|
2110
2192
|
type: 0
|
|
2111
2193
|
};
|
|
2112
2194
|
let current = root;
|
|
2113
2195
|
const stack = [root];
|
|
2196
|
+
const tagStack = []; // Track tag names to match closing tags
|
|
2114
2197
|
let attributeName = '';
|
|
2115
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
|
+
|
|
2116
2203
|
for (const token of tokens) {
|
|
2117
2204
|
switch (token.type) {
|
|
2118
2205
|
case AttributeName:
|
|
2119
|
-
|
|
2206
|
+
if (skipDepth === 0 && !captureCss) {
|
|
2207
|
+
attributeName = token.text;
|
|
2208
|
+
}
|
|
2120
2209
|
break;
|
|
2121
2210
|
case AttributeValue:
|
|
2122
|
-
if (
|
|
2211
|
+
if (skipDepth === 0 && !captureCss && (allAllowedAttributes.has(attributeName) || useBuiltInDefaults && isDefaultAllowedAttribute(attributeName, defaultAllowedAttributes))) {
|
|
2123
2212
|
const finalAttributeName = attributeName === 'class' ? 'className' : attributeName;
|
|
2124
2213
|
current[finalAttributeName] = token.text;
|
|
2125
2214
|
}
|
|
2126
2215
|
attributeName = '';
|
|
2127
2216
|
break;
|
|
2128
2217
|
case ClosingAngleBracket:
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
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
|
+
}
|
|
2139
2230
|
}
|
|
2140
2231
|
break;
|
|
2141
2232
|
case Content:
|
|
2142
|
-
|
|
2143
|
-
|
|
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
|
|
2144
2242
|
break;
|
|
2145
2243
|
case TagNameEnd:
|
|
2146
|
-
|
|
2147
|
-
|
|
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;
|
|
2148
2261
|
}
|
|
2149
|
-
current = stack.at(-1) || root;
|
|
2150
2262
|
break;
|
|
2151
2263
|
case TagNameStart:
|
|
2152
|
-
|
|
2153
|
-
const newNode = {
|
|
2154
|
-
childCount: 0,
|
|
2155
|
-
type: getVirtualDomTag(token.text)
|
|
2156
|
-
};
|
|
2157
|
-
dom.push(newNode);
|
|
2158
|
-
current = newNode;
|
|
2264
|
+
const tagNameLower = token.text.toLowerCase();
|
|
2159
2265
|
lastTagWasSelfClosing = isSelfClosingTag(token.text);
|
|
2160
|
-
|
|
2161
|
-
|
|
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
|
+
}
|
|
2162
2302
|
}
|
|
2163
2303
|
break;
|
|
2164
2304
|
case WhitespaceInsideOpeningTag:
|
|
2305
|
+
if (skipDepth === 0 && !captureCss &&
|
|
2165
2306
|
// Handle boolean attributes (attributes without values)
|
|
2166
|
-
|
|
2307
|
+
attributeName && (allAllowedAttributes.has(attributeName) || useBuiltInDefaults && isDefaultAllowedAttribute(attributeName, defaultAllowedAttributes))) {
|
|
2167
2308
|
const finalAttributeName = attributeName === 'class' ? 'className' : attributeName;
|
|
2168
2309
|
current[finalAttributeName] = attributeName;
|
|
2169
2310
|
}
|
|
@@ -2180,7 +2321,10 @@ const parseHtml = (html, allowedAttributes) => {
|
|
|
2180
2321
|
} catch {
|
|
2181
2322
|
dom.rootChildCount = root.childCount;
|
|
2182
2323
|
}
|
|
2183
|
-
return
|
|
2324
|
+
return {
|
|
2325
|
+
css,
|
|
2326
|
+
dom
|
|
2327
|
+
};
|
|
2184
2328
|
};
|
|
2185
2329
|
|
|
2186
2330
|
const handleEditorChanged = async () => {
|
|
@@ -2216,12 +2360,13 @@ const handleEditorChanged = async () => {
|
|
|
2216
2360
|
if (matchingEditorUid !== null) {
|
|
2217
2361
|
try {
|
|
2218
2362
|
const content = await invoke$1('Editor.getText', matchingEditorUid);
|
|
2219
|
-
const
|
|
2363
|
+
const parseResult = parseHtml(content, []);
|
|
2220
2364
|
const updatedState = {
|
|
2221
2365
|
...state,
|
|
2222
2366
|
content,
|
|
2367
|
+
css: parseResult.css,
|
|
2223
2368
|
errorMessage: '',
|
|
2224
|
-
parsedDom
|
|
2369
|
+
parsedDom: parseResult.dom
|
|
2225
2370
|
};
|
|
2226
2371
|
set(previewUid, state, updatedState);
|
|
2227
2372
|
} catch (error) {
|
|
@@ -2230,6 +2375,7 @@ const handleEditorChanged = async () => {
|
|
|
2230
2375
|
const updatedState = {
|
|
2231
2376
|
...state,
|
|
2232
2377
|
content: '',
|
|
2378
|
+
css: [],
|
|
2233
2379
|
errorMessage,
|
|
2234
2380
|
parsedDom: []
|
|
2235
2381
|
};
|
|
@@ -2271,11 +2417,16 @@ const updateContent = async (state, uri) => {
|
|
|
2271
2417
|
// @ts-ignore
|
|
2272
2418
|
const content = await readFile(uri);
|
|
2273
2419
|
|
|
2274
|
-
// Parse the content into virtual DOM
|
|
2275
|
-
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;
|
|
2276
2426
|
const parsedNodesChildNodeCount = getParsedNodesChildNodeCount(parsedDom);
|
|
2277
2427
|
return {
|
|
2278
2428
|
content,
|
|
2429
|
+
css,
|
|
2279
2430
|
errorMessage: '',
|
|
2280
2431
|
parsedDom,
|
|
2281
2432
|
parsedNodesChildNodeCount
|
|
@@ -2285,6 +2436,7 @@ const updateContent = async (state, uri) => {
|
|
|
2285
2436
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
2286
2437
|
return {
|
|
2287
2438
|
content: '',
|
|
2439
|
+
css: [],
|
|
2288
2440
|
errorMessage,
|
|
2289
2441
|
parsedDom: [],
|
|
2290
2442
|
parsedNodesChildNodeCount: 0
|
|
@@ -2295,6 +2447,7 @@ const updateContent = async (state, uri) => {
|
|
|
2295
2447
|
const handleFileEdited = async state => {
|
|
2296
2448
|
const {
|
|
2297
2449
|
content,
|
|
2450
|
+
css,
|
|
2298
2451
|
errorMessage,
|
|
2299
2452
|
parsedDom,
|
|
2300
2453
|
parsedNodesChildNodeCount
|
|
@@ -2302,6 +2455,7 @@ const handleFileEdited = async state => {
|
|
|
2302
2455
|
return {
|
|
2303
2456
|
...state,
|
|
2304
2457
|
content,
|
|
2458
|
+
css,
|
|
2305
2459
|
errorMessage,
|
|
2306
2460
|
parsedDom,
|
|
2307
2461
|
parsedNodesChildNodeCount
|
|
@@ -2323,11 +2477,13 @@ const loadContent = async state => {
|
|
|
2323
2477
|
// Read and parse file contents if we have a URI
|
|
2324
2478
|
const {
|
|
2325
2479
|
content,
|
|
2480
|
+
css,
|
|
2326
2481
|
errorMessage,
|
|
2327
2482
|
parsedDom,
|
|
2328
2483
|
parsedNodesChildNodeCount
|
|
2329
2484
|
} = state.uri ? await updateContent(state, state.uri) : {
|
|
2330
2485
|
content: state.content,
|
|
2486
|
+
css: state.css,
|
|
2331
2487
|
errorMessage: state.errorMessage,
|
|
2332
2488
|
parsedDom: state.parsedDom,
|
|
2333
2489
|
parsedNodesChildNodeCount: state.parsedNodesChildNodeCount
|
|
@@ -2335,6 +2491,7 @@ const loadContent = async state => {
|
|
|
2335
2491
|
return {
|
|
2336
2492
|
...state,
|
|
2337
2493
|
content,
|
|
2494
|
+
css,
|
|
2338
2495
|
errorCount: 0,
|
|
2339
2496
|
errorMessage,
|
|
2340
2497
|
initial: false,
|
|
@@ -2344,6 +2501,51 @@ const loadContent = async state => {
|
|
|
2344
2501
|
};
|
|
2345
2502
|
};
|
|
2346
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
|
+
|
|
2347
2549
|
const getEmptyPreviewDom = () => {
|
|
2348
2550
|
return [{
|
|
2349
2551
|
childCount: 1,
|
|
@@ -2410,6 +2612,8 @@ const renderIncremental = (oldState, newState) => {
|
|
|
2410
2612
|
|
|
2411
2613
|
const getRenderer = diffType => {
|
|
2412
2614
|
switch (diffType) {
|
|
2615
|
+
case RenderCss:
|
|
2616
|
+
return renderCss;
|
|
2413
2617
|
case RenderIncremental:
|
|
2414
2618
|
return renderIncremental;
|
|
2415
2619
|
case RenderItems:
|
|
@@ -2478,6 +2682,7 @@ const saveState = state => {
|
|
|
2478
2682
|
const setUri = async (state, uri) => {
|
|
2479
2683
|
const {
|
|
2480
2684
|
content,
|
|
2685
|
+
css,
|
|
2481
2686
|
errorMessage,
|
|
2482
2687
|
parsedDom,
|
|
2483
2688
|
parsedNodesChildNodeCount
|
|
@@ -2485,6 +2690,7 @@ const setUri = async (state, uri) => {
|
|
|
2485
2690
|
return {
|
|
2486
2691
|
...state,
|
|
2487
2692
|
content,
|
|
2693
|
+
css,
|
|
2488
2694
|
errorMessage,
|
|
2489
2695
|
parsedDom,
|
|
2490
2696
|
parsedNodesChildNodeCount,
|