feat(preview): cross-browser scrollbar, keyboard nav, dev-mode client simulation, docs overhaul
preview/index.html: - Fix scrollbar CSS: scoped webkit pseudo-elements + standards-track fallback, removed deprecated overflow:overlay - Keyboard navigation: arrows cycle focus between Theme/Template/Client selects, up/down select within focused dropdown, d/m toggle viewport - Dev/static mode detection: delayed static warning only when WebSocket absent, WebSocket errors only on disconnect (not on initial fail) - iframe sandbox: removed static sandbox attr, applied dynamically only in dev mode (file: protocol rejects sandboxed srcdoc) - Loading fix: removed double-toggle between init and render(); added 5s safety timeout - Transform clean: returns HTML as-is; CSS stripping moved to server-side - Dev disclaimer: blue info banner shown on WebSocket connect tools/server/inliner.mjs: - Added stripGmail() / stripOutlook() — server-side CSS property stripping for email client simulation tools/server/server.mjs: - Fixed WebSocket upgrade: use app.listen() instead of createServer(app).listen() - Juice post-processing now generates three rendered.js variants (modern/gmail/outlook) - Initial startup runs juice-only pass (avoids duplicate Go compilation) docs/ (all 6 languages — en, zh-CN, zh-TW, ja, ko, ru): - Image size limits: max 50KiB, recommended 10-20KiB - Dev disclaimer: simulation cannot 100% reproduce every client - Changed 'accurate' to 'relatively accurate' across all static-mode warnings - Converted all plain blockquotes to GitHub admonitions ([!WARNING] / [!NOTE])
This commit is contained in:
1 parent
4bc9a0667f
commit
d8a714b2b5
17 files changed
+315
-196
No files matched your search
@@ -19,6 +19,70 @@ export function inlineCSS(html) {
|
||||
return juice(html, JUICE_OPTIONS);
|
||||
}
|
||||
|
||||
// Gmail / Outlook CSS stripping
|
||||
|
||||
const GMAIL_UNSUPPORTED = [
|
||||
'border-radius','box-shadow','text-shadow',
|
||||
'backdrop-filter','filter',
|
||||
'animation','transition','transform',
|
||||
'background-image\\s*:\\s*(linear|radial|conic)-gradient',
|
||||
];
|
||||
const OUTLOOK_UNSUPPORTED = GMAIL_UNSUPPORTED.concat([
|
||||
'display\\s*:\\s*(flex|grid|inline-flex|inline-grid)',
|
||||
'flex[^;]*','grid[^;]*','gap\\s*:','order\\s*:',
|
||||
]);
|
||||
|
||||
function stripDeclarations(styleValue, patterns) {
|
||||
let v = styleValue;
|
||||
for (const p of patterns) {
|
||||
v = v.replace(new RegExp('(^|;\\s*)' + p + '[^;]*(;|$)', 'gi'), (m) => m.endsWith(';') ? ';' : '');
|
||||
v = v.replace(new RegExp(p + '[^;]*;', 'gi'), '');
|
||||
}
|
||||
return v.replace(/;;+/g, ';').replace(/^\s*;\s*/, '').replace(/\s*;\s*$/, '').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip CSS properties unsupported by Gmail from inline styles and <style> blocks.
|
||||
*/
|
||||
export function stripGmail(html) {
|
||||
return stripClientCSS(html, GMAIL_UNSUPPORTED, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip CSS properties unsupported by Outlook (more aggressive than Gmail).
|
||||
*/
|
||||
export function stripOutlook(html) {
|
||||
return stripClientCSS(html, OUTLOOK_UNSUPPORTED, true);
|
||||
}
|
||||
|
||||
function stripClientCSS(html, patterns, isOutlook) {
|
||||
let result = html;
|
||||
|
||||
// 1. Strip from inline style="..." attributes
|
||||
result = result.replace(/style="([^"]*)"/gi, (_, styles) => {
|
||||
const cleaned = stripDeclarations(styles, patterns);
|
||||
return cleaned ? 'style="' + cleaned + '"' : '';
|
||||
});
|
||||
|
||||
// 2. Strip from <style> blocks
|
||||
result = result.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (_, cssContent) => {
|
||||
let cleaned = cssContent;
|
||||
for (const p of patterns) {
|
||||
const re = new RegExp(p + '[^;{}]*[;}]?', 'gi');
|
||||
cleaned = cleaned.replace(re, (m) => m.endsWith('}') ? '}' : '');
|
||||
}
|
||||
return '<style>' + cleaned + '</style>';
|
||||
});
|
||||
|
||||
// 3. Outlook: replace CSS custom properties with initial
|
||||
if (isOutlook) {
|
||||
result = result.replace(/style="([^"]*)"/gi,
|
||||
(_, s) => 'style="' + s.replace(/var\(--[^)]*\)/g, 'initial') + '"');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply juice inlining to a rendered.js-style HTML snippet and return the
|
||||
* result. Handles escaped HTML (JSON-stringified) by first unescaping,
|
||||
|
||||
Reference in new issue
Block a user