Zundler/zundler/assets/inject_post.js
2024-04-21 16:23:03 +02:00

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",
}, '*');