mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix(web): correct srcdoc injection and deck bridge for JS strings con… (#938)
* fix(web): correct srcdoc injection and deck bridge for JS strings containing HTML tags
- Use indexOf for </head> (real tag precedes JS string occurrences)
- Use lastIndexOf for </body> (real tag follows JS string occurrences)
- Scope deck bridge slide selector to direct children of deck containers
to avoid counting cloned overview thumbnails
- Update .slide-number data attributes and .progress-bar width from
deck bridge's updateDeckChrome so page counter and progress track
with deck navigation
* fix(web): move deck chrome sync into report() for all slide modes
.slide-number and .progress-bar updates were only in updateDeckChrome()
which is called exclusively from setActive(). Scroll decks go through
scrollGo() → report() without ever calling setActive(), so their page
counter and progress bar stayed frozen.
Move the sync logic into report() which is the convergence point for
all navigation paths (class-toggle, scroll, keyboard-dispatch).
* fix(web): use DOMParser for srcdoc injection instead of string matching
Replace all regex/indexOf/lastIndexOf based </head> and </body> matching
with DOM-based injection via domMutate helper. DOMParser correctly
separates raw-text element content from structural markup, so </head>/
</body> inside JavaScript strings no longer hijack injection points.
Also: scope deck bridge slide selector to direct children of deck
containers, and move .slide-number/.progress-bar sync into report()
so all navigation paths update page chrome.
* fix(web): add robust string fallback for srcdoc injection in non-browser environments
When DOMParser is unavailable (Node tests), fall back to structural
boundary matching: lastIndexOf('</head>', before <body) and
lastIndexOf('</body>', before </html>) to skip literal occurrences
inside script/style blocks. Annotate catch blocks.
---------
Co-authored-by: yangjingting <yangjingting@yxqiche.com>
This commit is contained in:
parent
34b5b85614
commit
83ddf7609c
1 changed files with 41 additions and 21 deletions
|
|
@ -105,13 +105,39 @@ function injectManualEditBridge(doc: string): string {
|
|||
}
|
||||
|
||||
function injectBeforeHeadEnd(doc: string, payload: string): string {
|
||||
if (/<\/head>/i.test(doc)) return doc.replace(/<\/head>/i, `${payload}</head>`);
|
||||
if (typeof DOMParser !== 'undefined') {
|
||||
try {
|
||||
const parsed = new DOMParser().parseFromString(doc, 'text/html');
|
||||
if (parsed.head) parsed.head.insertAdjacentHTML('beforeend', payload);
|
||||
return serializeHtmlDocument(parsed);
|
||||
} catch { /* DOMParser failed; fall through to string path */ }
|
||||
}
|
||||
// String fallback: find the real </head> (last one before <body>)
|
||||
// to skip </head> literals inside <script>/<style> in <head>.
|
||||
const lower = doc.toLowerCase();
|
||||
const bodyStart = lower.indexOf('<body');
|
||||
const limit = bodyStart >= 0 ? bodyStart : lower.length;
|
||||
const idx = lower.lastIndexOf('</head>', limit - 1);
|
||||
if (idx >= 0) return doc.slice(0, idx) + payload + doc.slice(idx);
|
||||
if (/<head[^>]*>/i.test(doc)) return doc.replace(/<head[^>]*>/i, (m) => `${m}${payload}`);
|
||||
return payload + doc;
|
||||
}
|
||||
|
||||
function injectBeforeBodyEnd(doc: string, payload: string): string {
|
||||
if (/<\/body>/i.test(doc)) return doc.replace(/<\/body>/i, `${payload}</body>`);
|
||||
if (typeof DOMParser !== 'undefined') {
|
||||
try {
|
||||
const parsed = new DOMParser().parseFromString(doc, 'text/html');
|
||||
if (parsed.body) parsed.body.insertAdjacentHTML('beforeend', payload);
|
||||
return serializeHtmlDocument(parsed);
|
||||
} catch { /* DOMParser failed; fall through to string path */ }
|
||||
}
|
||||
// String fallback: find the real </body> (last one before </html>)
|
||||
// to skip </body> literals inside <script>/<style> in <body>.
|
||||
const lower = doc.toLowerCase();
|
||||
const htmlEnd = lower.lastIndexOf('</html>');
|
||||
const limit = htmlEnd >= 0 ? htmlEnd : lower.length;
|
||||
const idx = lower.lastIndexOf('</body>', limit - 1);
|
||||
if (idx >= 0) return doc.slice(0, idx) + payload + doc.slice(idx);
|
||||
return doc + payload;
|
||||
}
|
||||
|
||||
|
|
@ -631,13 +657,7 @@ html[data-od-comment-mode] body * { cursor: crosshair !important; }
|
|||
html[data-od-inspect-mode] body * { cursor: crosshair !important; }
|
||||
html[data-od-comment-mode][data-od-comment-mode-kind="pod"] body * { cursor: cell !important; }
|
||||
</style>`;
|
||||
const withStyle = /<\/head>/i.test(doc)
|
||||
? doc.replace(/<\/head>/i, style + '</head>')
|
||||
: /<head[^>]*>/i.test(doc)
|
||||
? doc.replace(/<head[^>]*>/i, (m) => m + style)
|
||||
: style + doc;
|
||||
if (/<\/body>/i.test(withStyle)) return withStyle.replace(/<\/body>/i, script + '</body>');
|
||||
return withStyle + script;
|
||||
return injectBeforeBodyEnd(injectBeforeHeadEnd(doc, style), script);
|
||||
}
|
||||
|
||||
// The deck bridge supports three deck conventions found across our skills
|
||||
|
|
@ -670,16 +690,10 @@ function injectDeckBridge(doc: string, initialSlideIndex = 0): string {
|
|||
const styleFix = `<style data-od-deck-fix>
|
||||
.stage, .deck-stage, .deck-shell { place-content: center !important; }
|
||||
</style>`;
|
||||
const docWithStyle = /<\/head>/i.test(doc)
|
||||
? doc.replace(/<\/head>/i, styleFix + "</head>")
|
||||
: /<head[^>]*>/i.test(doc)
|
||||
? doc.replace(/<head[^>]*>/i, (m) => m + styleFix)
|
||||
: styleFix + doc;
|
||||
doc = docWithStyle;
|
||||
const script = `<script data-od-deck-bridge>(function(){
|
||||
var initialSlideIndex = ${safeInitialSlideIndex};
|
||||
var didRestoreInitialSlide = initialSlideIndex <= 0;
|
||||
function slides(){ return document.querySelectorAll('.slide'); }
|
||||
function slides(){ return document.querySelectorAll('.deck > .slide, .deck-stage > .slide, .deck-shell > .slide, body > .slide'); }
|
||||
function scroller(){
|
||||
if (document.body && document.body.scrollWidth > document.body.clientWidth + 1) return document.body;
|
||||
return document.scrollingElement || document.documentElement;
|
||||
|
|
@ -834,11 +848,19 @@ function injectDeckBridge(doc: string, initialSlideIndex = 0): string {
|
|||
function report(){
|
||||
try {
|
||||
var list = slides();
|
||||
var i = activeIndex(list);
|
||||
var count = list.length;
|
||||
window.parent.postMessage({
|
||||
type: 'od:slide-state',
|
||||
active: activeIndex(list),
|
||||
count: list.length,
|
||||
active: i,
|
||||
count: count,
|
||||
}, '*');
|
||||
document.querySelectorAll('.slide-number').forEach(function(el){
|
||||
el.setAttribute('data-current',i+1); el.setAttribute('data-total',count);
|
||||
});
|
||||
document.querySelectorAll('.progress-bar>span').forEach(function(el){
|
||||
el.style.width=(count?((i+1)/count*100)+'%':'0');
|
||||
});
|
||||
} catch (e) {}
|
||||
}
|
||||
function restoreInitialSlide(){
|
||||
|
|
@ -925,7 +947,5 @@ function injectDeckBridge(doc: string, initialSlideIndex = 0): string {
|
|||
}
|
||||
observeSlides();
|
||||
})();</script>`;
|
||||
if (/<\/body>/i.test(doc))
|
||||
return doc.replace(/<\/body>/i, `${script}</body>`);
|
||||
return doc + script;
|
||||
return injectBeforeBodyEnd(injectBeforeHeadEnd(doc, styleFix), script);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue