mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-12-29 11:30:28 +00:00
ece8b0ee01
* Initial Commit
* Add note to preview build
* Fix whitespace and indenting
Thanks @pmario
* Fix crash with unset $tiddler attribute on <$data> widget
Thanks @CodaCodr
* Don't duplicate "description" field in test cases
* Use different background colours for nested testcase widgets
* Extend the testcase widget to run tests
* Add testcases to control panel
* Add a view template body template to render testcase tiddlers
* Test edition should display testcases
* Whitespace fixes
* Make testcase tiddler tempalte link to itself
* Styling tweaks
* Docs improvements
* Styling tweaks
* Run the new tw5.com testcases in the test edition
* Update data widget to display its content in JSON
* Add testcase convenience procedure
* Clearer testcases for data widget, and docs tweaks
* Don't expect our intentionally failing test to pass
* Extend testcase default template so that the display format can be chosen
It is selected by setting the variable "displayFormat"
* DataWidget docs typo
* Fix data widget not refreshing
* Links in testcase output switch to the tab containing that tiddler
Thanks to @btheado for the suggestion
* Docs update for 648855e8a5
* Wording tweak
* Add support for narrative tiddlers in test cases
* Documentation improvements
* Cleanup comments
* Remove obsolete code comments
* Simplify template
* Docs update
* Rename $:/core/ui/testcases/DefaultTemplate/SourceTabs from $:/core/ui/testcases/DefaultTemplate/Source
* Use the view template body for failing tests
* Don't reference the geospatial plugin
* "Test case" should be two words
* Fix handling of currentTiddler variable
Fixes problem reported by @btheado in https://github.com/Jermolene/TiddlyWiki5/pull/7817#issuecomment-2103704468
* Prepare for merging
352 lines
11 KiB
JavaScript
352 lines
11 KiB
JavaScript
/*\
|
|
title: $:/plugins/tiddlywiki/innerwiki/innerwiki.js
|
|
type: application/javascript
|
|
module-type: widget
|
|
|
|
Widget to display an innerwiki in an iframe
|
|
|
|
\*/
|
|
(function(){
|
|
|
|
/*jslint node: true, browser: true */
|
|
/*global $tw: false */
|
|
"use strict";
|
|
|
|
var DEFAULT_INNERWIKI_TEMPLATE = "$:/plugins/tiddlywiki/innerwiki/template";
|
|
|
|
var Widget = require("$:/core/modules/widgets/widget.js").widget,
|
|
DataWidget = require("$:/core/modules/widgets/data.js").data,
|
|
dm = $tw.utils.domMaker;
|
|
|
|
var InnerWikiWidget = function(parseTreeNode,options) {
|
|
this.initialise(parseTreeNode,options);
|
|
};
|
|
|
|
/*
|
|
Inherit from the base widget class
|
|
*/
|
|
InnerWikiWidget.prototype = new Widget();
|
|
|
|
/*
|
|
Render this widget into the DOM
|
|
*/
|
|
InnerWikiWidget.prototype.render = function(parent,nextSibling) {
|
|
var self = this;
|
|
this.parentDomNode = parent;
|
|
this.computeAttributes();
|
|
this.execute();
|
|
// Create wrapper
|
|
this.domWrapper = dm("div",{
|
|
document: this.document,
|
|
"class": (this.innerWikiClass || "").split(" ").concat(["tc-innerwiki-wrapper"]).join(" "),
|
|
style: {
|
|
overflow: "hidden",
|
|
position: "relative",
|
|
boxSizing: "content-box"
|
|
}
|
|
});
|
|
// Set up the SVG container
|
|
this.domSVG = dm("svg",{
|
|
namespace: "http://www.w3.org/2000/svg",
|
|
document: this.document,
|
|
style: {
|
|
width: "100%",
|
|
position: "absolute",
|
|
zIndex: "1",
|
|
pointerEvents: "none"
|
|
},
|
|
attributes: {
|
|
"viewBox": "0 0 " + this.innerWikiClipWidth + " " + this.innerWikiClipHeight
|
|
}
|
|
});
|
|
this.domWrapper.appendChild(this.domSVG);
|
|
this.setVariable("namespace","http://www.w3.org/2000/svg");
|
|
// Create the iframe for the browser or image for Node.js
|
|
if(!this.document.isTiddlyWikiFakeDom) {
|
|
// Create iframe
|
|
this.domIFrame = dm("iframe",{
|
|
document: this.document,
|
|
"class": "tc-innerwiki-iframe",
|
|
style: {
|
|
position: "absolute",
|
|
maxWidth: "none",
|
|
border: "none"
|
|
},
|
|
attributes: {
|
|
width: this.innerWikiWidth,
|
|
height: this.innerWikiHeight
|
|
}
|
|
});
|
|
this.domWrapper.appendChild(this.domIFrame);
|
|
} else {
|
|
// Create image placeholder
|
|
this.domImage = dm("img",{
|
|
document: this.document,
|
|
style: {
|
|
width: "100%"
|
|
},
|
|
attributes: {
|
|
src: this.innerWikiFilename
|
|
}
|
|
});
|
|
this.domWrapper.appendChild(this.domImage);
|
|
}
|
|
// Insert wrapper into the DOM
|
|
parent.insertBefore(this.domWrapper,nextSibling);
|
|
this.renderChildren(this.domSVG,null);
|
|
this.domNodes.push(this.domWrapper);
|
|
// If we're on the real DOM, finish the initialisation that needs us to be in the DOM
|
|
if(!this.document.isTiddlyWikiFakeDom) {
|
|
// Write the HTML
|
|
this.domIFrame.contentDocument.open();
|
|
this.domIFrame.contentDocument.write(this.createInnerHTML());
|
|
this.domIFrame.contentDocument.close();
|
|
}
|
|
// Scale the iframe and adjust the height of the wrapper
|
|
this.clipLeft = this.innerWikiClipLeft;
|
|
this.clipTop = this.innerWikiClipTop;
|
|
this.clipWidth = this.innerWikiClipWidth;
|
|
this.clipHeight = this.innerWikiClipHeight;
|
|
this.scale = this.domWrapper.clientWidth / this.clipWidth;
|
|
// Display the anchors
|
|
if(!this.document.isTiddlyWikiFakeDom) {
|
|
this.domAnchorContainer = dm("div",{
|
|
document: this.document,
|
|
style: {
|
|
position: "relative",
|
|
zIndex: "2",
|
|
transformOrigin: "0 0",
|
|
transform: "scale(" + this.scale + ")"
|
|
}
|
|
});
|
|
this.domAnchorBackdrop = dm("div",{
|
|
document: this.document,
|
|
style: {
|
|
position: "absolute",
|
|
display: "none"
|
|
}
|
|
});
|
|
this.domAnchorContainer.appendChild(this.domAnchorBackdrop);
|
|
this.domWrapper.insertBefore(this.domAnchorContainer,this.domWrapper.firstChild);
|
|
self.createAnchors();
|
|
}
|
|
// Scale the iframe and adjust the height of the wrapper
|
|
if(!this.document.isTiddlyWikiFakeDom) {
|
|
this.domIFrame.style.transformOrigin = this.clipLeft + "px " + this.clipTop + "px";
|
|
this.domIFrame.style.transform = "translate(" + (-this.clipLeft) + "px," + (-this.clipTop) + "px) scale(" + this.scale + ")";
|
|
this.domWrapper.style.height = (this.clipHeight * this.scale) + "px";
|
|
}
|
|
};
|
|
|
|
/*
|
|
Create the anchors
|
|
*/
|
|
InnerWikiWidget.prototype.createAnchors = function() {
|
|
var self = this;
|
|
this.findChildrenDataWidgets(this.children,"anchor",function(widget) {
|
|
var anchorWidth = 40,
|
|
anchorHeight = 40,
|
|
getAnchorCoordinate = function(name) {
|
|
return parseInt(self.wiki.getTiddlerText(widget.getAttribute(name)),10) || 0;
|
|
},
|
|
setAnchorCoordinate = function(name,value) {
|
|
self.wiki.addTiddler({
|
|
title: widget.getAttribute(name),
|
|
text: value + ""
|
|
});
|
|
},
|
|
domAnchor = dm("img",{
|
|
document: self.document,
|
|
style: {
|
|
position: "absolute",
|
|
width: anchorWidth + "px",
|
|
height: anchorHeight + "px",
|
|
transformOrigin: "50% 50%",
|
|
transform: "scale(" + (1 / self.scale) + ")",
|
|
left: (getAnchorCoordinate("x") - anchorWidth / 2) + "px",
|
|
top: (getAnchorCoordinate("y") - anchorHeight / 2) + "px"
|
|
},
|
|
attributes: {
|
|
draggable: false,
|
|
src: "data:image/svg+xml," + encodeURIComponent(self.wiki.getTiddlerText("$:/plugins/tiddlywiki/innerwiki/crosshairs.svg"))
|
|
}
|
|
});
|
|
self.domAnchorContainer.appendChild(domAnchor);
|
|
var posX,posY,dragStartX,dragStartY,deltaX,deltaY,
|
|
fnMouseDown = function(event) {
|
|
self.domAnchorBackdrop.style.width = self.clipWidth + "px";
|
|
self.domAnchorBackdrop.style.height = self.clipHeight + "px";
|
|
self.domAnchorBackdrop.style.display = "block";
|
|
posX = domAnchor.offsetLeft;
|
|
posY = domAnchor.offsetTop;
|
|
dragStartX = event.clientX;
|
|
dragStartY = event.clientY;
|
|
deltaX = 0;
|
|
deltaY = 0;
|
|
self.document.addEventListener("mousemove",fnMouseMove,false);
|
|
self.document.addEventListener("mouseup",fnMouseUp,false);
|
|
},
|
|
fnMouseMove = function(event) {
|
|
deltaX = (event.clientX - dragStartX) / self.scale;
|
|
deltaY = (event.clientY - dragStartY) / self.scale;
|
|
domAnchor.style.left = (posX + deltaX) + "px";
|
|
domAnchor.style.top = (posY + deltaY) + "px";
|
|
},
|
|
fnMouseUp = function(event) {
|
|
var x = getAnchorCoordinate("x") + deltaX,
|
|
y = getAnchorCoordinate("y") + deltaY;
|
|
if(x >= 0 && x < self.clipWidth && y >= 0 && y < self.clipHeight) {
|
|
setAnchorCoordinate("x",x);
|
|
setAnchorCoordinate("y",y);
|
|
} else {
|
|
domAnchor.style.left = posX + "px";
|
|
domAnchor.style.top = posY + "px";
|
|
}
|
|
self.domAnchorBackdrop.style.display = "none";
|
|
self.document.removeEventListener("mousemove",fnMouseMove,false);
|
|
self.document.removeEventListener("mouseup",fnMouseUp,false);
|
|
};
|
|
domAnchor.addEventListener("mousedown",fnMouseDown,false);
|
|
});
|
|
};
|
|
|
|
/*
|
|
Delete the anchors
|
|
*/
|
|
InnerWikiWidget.prototype.deleteAnchors = function() {
|
|
for(var index=this.domAnchorContainer.childNodes.length-1; index>=0; index--) {
|
|
var node = this.domAnchorContainer.childNodes[index];
|
|
if(node.tagName === "IMG") {
|
|
node.parentNode.removeChild(node);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
Create the HTML of the innerwiki
|
|
*/
|
|
InnerWikiWidget.prototype.createInnerHTML = function() {
|
|
// Get the HTML of the iframe
|
|
var html = this.wiki.renderTiddler("text/plain",this.innerWikiTemplate);
|
|
// Insert the overlay tiddlers
|
|
var SPLIT_MARKER = "<!--~~ Boot" + " kernel ~~-->\n",
|
|
IMPLANT_PREFIX = "<" + "script>\n$tw.preloadTiddlerArray(",
|
|
IMPLANT_SUFFIX = ");\n</" + "script>\n",
|
|
parts = html.split(SPLIT_MARKER),
|
|
tiddlers = [];
|
|
this.findChildrenDataWidgets(this.children,"data",function(widget) {
|
|
Array.prototype.push.apply(tiddlers,widget.readDataTiddlerValues());
|
|
});
|
|
if(parts.length === 2) {
|
|
html = parts[0] + IMPLANT_PREFIX + JSON.stringify(tiddlers) + IMPLANT_SUFFIX + SPLIT_MARKER + parts[1];
|
|
}
|
|
return html;
|
|
};
|
|
|
|
/*
|
|
Compute the internal state of the widget
|
|
*/
|
|
InnerWikiWidget.prototype.execute = function() {
|
|
var parseStringAsNumber = function(num,defaultValue) {
|
|
num = parseInt(num + "",10);
|
|
if(!isNaN(num)) {
|
|
return num;
|
|
} else {
|
|
return parseInt(defaultValue + "",10);
|
|
}
|
|
};
|
|
// Get our parameters
|
|
this.innerWikiTemplate = this.getAttribute("template",DEFAULT_INNERWIKI_TEMPLATE);
|
|
this.innerWikiWidth = parseStringAsNumber(this.getAttribute("width"),800);
|
|
this.innerWikiHeight = parseStringAsNumber(this.getAttribute("height"),600);
|
|
this.innerWikiStyle = this.getAttribute("style");
|
|
this.innerWikiClass = this.getAttribute("class");
|
|
this.innerWikiFilename = this.getAttribute("filename");
|
|
this.innerWikiClipLeft = parseStringAsNumber(this.getAttribute("clipLeft"),0);
|
|
this.innerWikiClipTop = parseStringAsNumber(this.getAttribute("clipTop"),0);
|
|
this.innerWikiClipWidth = parseStringAsNumber(this.getAttribute("clipWidth"),this.innerWikiWidth);
|
|
this.innerWikiClipHeight = parseStringAsNumber(this.getAttribute("clipHeight"),this.innerWikiHeight);
|
|
// Construct the child widgets
|
|
this.makeChildWidgets();
|
|
};
|
|
|
|
/*
|
|
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
|
|
*/
|
|
InnerWikiWidget.prototype.refresh = function(changedTiddlers) {
|
|
var changedAttributes = this.computeAttributes();
|
|
if(changedAttributes.template || changedAttributes.width || changedAttributes.height || changedAttributes.style || changedAttributes.class) {
|
|
this.refreshSelf();
|
|
return true;
|
|
} else {
|
|
var childrenRefreshed = this.refreshChildren(changedTiddlers);
|
|
if(childrenRefreshed) {
|
|
this.deleteAnchors();
|
|
this.createAnchors();
|
|
}
|
|
return childrenRefreshed
|
|
}
|
|
};
|
|
|
|
/*
|
|
Use Puppeteer to save a screenshot to a file
|
|
*/
|
|
InnerWikiWidget.prototype.saveScreenshot = function(options,callback) {
|
|
var self = this,
|
|
basepath = options.basepath || ".",
|
|
deviceScaleFactor = options.deviceScaleFactor || 1;
|
|
// Don't do anything if we don't have a filename
|
|
if(!this.innerWikiFilename) {
|
|
return callback(null);
|
|
}
|
|
var path = require("path"),
|
|
filepath = path.resolve(basepath,this.innerWikiFilename);
|
|
$tw.utils.createFileDirectories(filepath);
|
|
console.log("Taking screenshot",filepath);
|
|
// Fire up Puppeteer
|
|
var puppeteer;
|
|
try {
|
|
puppeteer = require("puppeteer");
|
|
} catch(e) {
|
|
throw "Google Puppeteer not found";
|
|
}
|
|
// Take screenshots
|
|
puppeteer.launch().then(async browser => {
|
|
// NOTE: Copying Google's sample code by using new fangled promises "await"
|
|
const page = await browser.newPage();
|
|
await page.setContent(self.createInnerHTML(),{
|
|
waitUntil: "domcontentloaded"
|
|
});
|
|
await page.setViewport({
|
|
width: Math.trunc(self.innerWikiWidth),
|
|
height: Math.trunc(self.innerWikiHeight),
|
|
deviceScaleFactor: deviceScaleFactor
|
|
});
|
|
// PDF generation isn't great: there's no clipping, and pagination is hard to control
|
|
// await page.emulateMedia("screen");
|
|
// await page.pdf({
|
|
// scale: 0.5,
|
|
// width: self.innerWikiWidth + "px",
|
|
// height: self.innerWikiHeight + "px",
|
|
// path: filepath + ".pdf",
|
|
// printBackground: true
|
|
// });
|
|
await page.screenshot({
|
|
path: filepath,
|
|
clip: {
|
|
x: self.innerWikiClipLeft,
|
|
y: self.innerWikiClipTop,
|
|
width: self.innerWikiClipWidth,
|
|
height: self.innerWikiClipHeight
|
|
},
|
|
type: "png"
|
|
});
|
|
await browser.close();
|
|
callback(null);
|
|
});
|
|
};
|
|
|
|
exports.innerwiki = InnerWikiWidget;
|
|
|
|
})();
|