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 {
|
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}`);
|
if (/<head[^>]*>/i.test(doc)) return doc.replace(/<head[^>]*>/i, (m) => `${m}${payload}`);
|
||||||
return payload + doc;
|
return payload + doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
function injectBeforeBodyEnd(doc: string, payload: string): string {
|
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;
|
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-inspect-mode] body * { cursor: crosshair !important; }
|
||||||
html[data-od-comment-mode][data-od-comment-mode-kind="pod"] body * { cursor: cell !important; }
|
html[data-od-comment-mode][data-od-comment-mode-kind="pod"] body * { cursor: cell !important; }
|
||||||
</style>`;
|
</style>`;
|
||||||
const withStyle = /<\/head>/i.test(doc)
|
return injectBeforeBodyEnd(injectBeforeHeadEnd(doc, style), script);
|
||||||
? 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The deck bridge supports three deck conventions found across our skills
|
// 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>
|
const styleFix = `<style data-od-deck-fix>
|
||||||
.stage, .deck-stage, .deck-shell { place-content: center !important; }
|
.stage, .deck-stage, .deck-shell { place-content: center !important; }
|
||||||
</style>`;
|
</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(){
|
const script = `<script data-od-deck-bridge>(function(){
|
||||||
var initialSlideIndex = ${safeInitialSlideIndex};
|
var initialSlideIndex = ${safeInitialSlideIndex};
|
||||||
var didRestoreInitialSlide = initialSlideIndex <= 0;
|
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(){
|
function scroller(){
|
||||||
if (document.body && document.body.scrollWidth > document.body.clientWidth + 1) return document.body;
|
if (document.body && document.body.scrollWidth > document.body.clientWidth + 1) return document.body;
|
||||||
return document.scrollingElement || document.documentElement;
|
return document.scrollingElement || document.documentElement;
|
||||||
|
|
@ -834,11 +848,19 @@ function injectDeckBridge(doc: string, initialSlideIndex = 0): string {
|
||||||
function report(){
|
function report(){
|
||||||
try {
|
try {
|
||||||
var list = slides();
|
var list = slides();
|
||||||
|
var i = activeIndex(list);
|
||||||
|
var count = list.length;
|
||||||
window.parent.postMessage({
|
window.parent.postMessage({
|
||||||
type: 'od:slide-state',
|
type: 'od:slide-state',
|
||||||
active: activeIndex(list),
|
active: i,
|
||||||
count: list.length,
|
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) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
function restoreInitialSlide(){
|
function restoreInitialSlide(){
|
||||||
|
|
@ -925,7 +947,5 @@ function injectDeckBridge(doc: string, initialSlideIndex = 0): string {
|
||||||
}
|
}
|
||||||
observeSlides();
|
observeSlides();
|
||||||
})();</script>`;
|
})();</script>`;
|
||||||
if (/<\/body>/i.test(doc))
|
return injectBeforeBodyEnd(injectBeforeHeadEnd(doc, styleFix), script);
|
||||||
return doc.replace(/<\/body>/i, `${script}</body>`);
|
|
||||||
return doc + script;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue