mirror of
https://github.com/tcsenpai/Zundler.git
synced 2025-06-06 11:35:40 +00:00
247 lines
7.5 KiB
JavaScript
247 lines
7.5 KiB
JavaScript
var splitUrl = function(url) {
|
|
// Return a list of three elements: path, GET parameters, anchor
|
|
var anchor = url.split('#')[1] || "";
|
|
var getParameters = url.split('#')[0].split('?')[1] || "";
|
|
var path = url.split('#')[0];
|
|
path = path.split('?')[0];
|
|
let result = [path, getParameters, anchor];
|
|
// console.log("Split URL", url, result);
|
|
return result;
|
|
}
|
|
|
|
|
|
var virtualClick = function(evnt) {
|
|
// Handle GET parameters and anchors
|
|
// console.log("Virtual click", evnt);
|
|
|
|
var el = evnt.currentTarget;
|
|
var name = el.tagName.toLowerCase();
|
|
|
|
if (name == 'a') {
|
|
var [path, getParameters, anchor] = splitUrl(el.getAttribute('href'));
|
|
} else if (name == 'form') {
|
|
var [path, getParameters, anchor] = splitUrl(el.getAttribute('action'));
|
|
const formData = new FormData(el);
|
|
getParameters = new URLSearchParams(formData).toString();
|
|
} else {
|
|
console.error("Invalid element", el);
|
|
}
|
|
|
|
path = normalizePath(path);
|
|
|
|
window.parent.postMessage({
|
|
action: "virtualClick",
|
|
argument: {
|
|
path: path,
|
|
getParameters: getParameters,
|
|
anchor: anchor,
|
|
}
|
|
}, '*');
|
|
evnt.preventDefault();
|
|
evnt.stopPropagation();
|
|
return false;
|
|
};
|
|
|
|
var isVirtual = function(url) {
|
|
// Return true if the url should be retrieved from the virtual file tree
|
|
var _url = url.toString().toLowerCase();
|
|
return (! (
|
|
_url == "" ||
|
|
_url[0] == "#" ||
|
|
_url.startsWith('https:/') ||
|
|
_url.startsWith('http:/') ||
|
|
_url.startsWith('data:') ||
|
|
_url.startsWith('about:srcdoc') ||
|
|
_url.startsWith('blob:')
|
|
));
|
|
};
|
|
|
|
var retrieveFile = function(path) {
|
|
// console.log("Retrieving file: " + path);
|
|
var fileTree = window.globalContext.fileTree;
|
|
var file = fileTree[path];
|
|
if (!file) {
|
|
console.warn("File not found: " + path);
|
|
return "";
|
|
} else {
|
|
return file;
|
|
}
|
|
};
|
|
|
|
var normalizePath = function(path) {
|
|
// make relative paths absolute in context of our virtual file tree
|
|
|
|
while (path && path[0] == '/') {
|
|
path = path.substr(1);
|
|
}
|
|
|
|
var result = window.globalContext.current_path;
|
|
result = result.split('/');
|
|
result.pop();
|
|
result = result.concat(path.split('/'));
|
|
|
|
// resolve relative directories
|
|
var array = [];
|
|
Array.from(result).forEach( component => {
|
|
if (component == '..') {
|
|
if (array) {
|
|
array.pop();
|
|
}
|
|
} else if (component == '.') {
|
|
} else {
|
|
if (component) { array.push(component); }
|
|
}
|
|
});
|
|
|
|
result = array.join('/');
|
|
// console.log(`Normalized path: ${path} -> ${result} (@${window.globalContext.current_path})`);
|
|
return result;
|
|
};
|
|
|
|
|
|
var onSetData = function(argument) {
|
|
// window.globalContext = argument;
|
|
console.debug("Received data from parent", window.globalContext);
|
|
try {
|
|
// window.document.dispatchEvent(new Event("DOMContentLoaded", { bubbles: true, cancelable: true }));
|
|
} finally {
|
|
observer.observe(window.document.body, {subtree: true, childList: true});
|
|
|
|
// Set parent window title and trigger data transmission
|
|
var favicon = window.document.querySelector("link[rel*='icon']");
|
|
if (favicon) { favicon = favicon.getAttribute('href'); }
|
|
var title = window.document.title;
|
|
|
|
window.parent.postMessage({
|
|
action: "set_title",
|
|
argument: {
|
|
title: title,
|
|
favicon: favicon
|
|
}
|
|
}, '*');
|
|
|
|
window.parent.postMessage({
|
|
action: "show_iframe",
|
|
argument: "",
|
|
}, '*');
|
|
}
|
|
}
|
|
|
|
|
|
var fixLink = function(a) {
|
|
if (isVirtual(a.getAttribute('href'))) {
|
|
// a.addEventListener('click', virtualClick);
|
|
a.setAttribute("onclick", "virtualClick(event)");
|
|
} else if (a.getAttribute('href').startsWith('#')) {
|
|
a.setAttribute('href', "about:srcdoc" + a.getAttribute('href'))
|
|
} else if (!a.getAttribute('href').startsWith('about:srcdoc')) {
|
|
// External links should open in a new tab. Browsers block links to
|
|
// sites of different origin within an iframe for security reasons.
|
|
a.setAttribute('target', "_blank");
|
|
}
|
|
};
|
|
|
|
|
|
var fixForm = function(form) {
|
|
var href = form.getAttribute('action');
|
|
if (isVirtual(href) && form.getAttribute('method').toLowerCase() == 'get') {
|
|
// form.addEventListener('submit', virtualClick);
|
|
form.setAttribute("onsubmit", "virtualClick(event)");
|
|
}
|
|
};
|
|
|
|
|
|
var embedImg = function(img) {
|
|
if (img.hasAttribute('src')) {
|
|
const src = img.getAttribute('src');
|
|
if (isVirtual(src)) {
|
|
var path = normalizePath(src);
|
|
const file = retrieveFile(path);
|
|
const mime_type = window.globalContext.fileTree[path].mime_type;
|
|
if (mime_type == 'image/svg+xml') {
|
|
img.setAttribute('src', "data:image/svg+xml;charset=utf-8;base64, " + btoa(file));
|
|
} else {
|
|
img.setAttribute('src', `data:${mime_type};base64, ${file}`);
|
|
}
|
|
};
|
|
};
|
|
};
|
|
|
|
|
|
var onScrollToAnchor = function(argument) {
|
|
if (window.globalContext.anchor) {
|
|
document.location.replace("about:srcdoc#" + window.globalContext.anchor);
|
|
}
|
|
}
|
|
|
|
|
|
const observer = new MutationObserver((mutationList) => {
|
|
// console.log("Fix mutated elements...", mutationList);
|
|
mutationList.forEach((mutation) => {
|
|
if (mutation.type == 'childList') {
|
|
Array.from(mutation.target.querySelectorAll("a")).forEach( a => {
|
|
fixLink(a);
|
|
});
|
|
Array.from(mutation.target.querySelectorAll("img")).forEach( img => {
|
|
embedImg(img);
|
|
});
|
|
Array.from(mutation.target.querySelectorAll("form")).forEach( form => {
|
|
fixForm(form);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
|
|
var monkeyPatch = function() {
|
|
if (typeof jQuery === 'undefined') {return;} // Only for jQuery at the moment
|
|
/**
|
|
* Monkey patch getQueryParameters
|
|
* This function is defined in Sphinx' (v4) doctools.js and incompatible with our
|
|
* approach.
|
|
* This is a copy with effectively only the third line changed.
|
|
* See: https://github.com/sphinx-doc/sphinx/blob/2329fdef8c20c6c75194f5d842b8f62ebad5c79d/sphinx/themes/basic/static/doctools.js#L54
|
|
*/
|
|
jQuery._getQueryParameters = jQuery.getQueryParameters;
|
|
jQuery.getQueryParameters = function(s) {
|
|
if (typeof s === 'undefined')
|
|
s = '?' + window.globalContext.getParameters;
|
|
return jQuery._getQueryParameters(s);
|
|
};
|
|
|
|
/**
|
|
* Monkey patch jQuery.ajax
|
|
* Only settings.url and settings.complete are supported for virtual
|
|
* URLs.
|
|
*/
|
|
jQuery._ajax = jQuery.ajax;
|
|
jQuery.ajax = function(settings) {
|
|
url = normalizePath(settings.url);
|
|
if (isVirtual(url)) {
|
|
var result;
|
|
var data;
|
|
data = retrieveFile(url);
|
|
result = settings.complete({responseText: data}, "");
|
|
return; // Return value not actually needed in searchtools.js
|
|
} else {
|
|
return jQuery.ajax(settings);
|
|
};
|
|
};
|
|
}
|
|
|
|
monkeyPatch();
|
|
|
|
// Set up message listener
|
|
window.addEventListener("message", (evnt) => {
|
|
console.log("Received message in iframe", evnt);
|
|
if (evnt.data.action == 'set_data') {
|
|
onSetData(evnt.data.argument);
|
|
} else if (evnt.data.action == 'scroll_to_anchor') {
|
|
onScrollToAnchor(evnt.data.argument);
|
|
}
|
|
}, false);
|
|
|
|
window.parent.postMessage({
|
|
action: "ready",
|
|
}, '*');
|