TiddlyWiki5/plugins/tiddlywiki/innerwiki/innerwiki.js

412 lines
12 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("$:/plugins/tiddlywiki/innerwiki/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.findDataWidgets(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.readTiddlerDataWidgets(this.children);
if(parts.length === 2) {
html = parts[0] + IMPLANT_PREFIX + JSON.stringify(tiddlers) + IMPLANT_SUFFIX + SPLIT_MARKER + parts[1];
}
return html;
};
/*
Find child data widgets
*/
InnerWikiWidget.prototype.findDataWidgets = function(children,tag,callback) {
var self = this;
$tw.utils.each(children,function(child) {
if(child.dataWidgetTag === tag) {
callback(child);
}
if(child.children) {
self.findDataWidgets(child.children,tag,callback);
}
});
};
/*
Find the child data widgets
*/
InnerWikiWidget.prototype.readTiddlerDataWidgets = function(children) {
var self = this,
results = [];
this.findDataWidgets(children,"data",function(widget) {
Array.prototype.push.apply(results,self.readTiddlerDataWidget(widget));
});
return results;
};
/*
Read the value(s) from a data widget
*/
InnerWikiWidget.prototype.readTiddlerDataWidget = function(dataWidget) {
// Start with a blank object
var item = Object.create(null);
// Read any attributes not prefixed with $
$tw.utils.each(dataWidget.attributes,function(value,name) {
if(name.charAt(0) !== "$") {
item[name] = value;
}
});
// Deal with $tiddler or $filter attributes
var titles;
if(dataWidget.hasAttribute("$tiddler")) {
titles = [dataWidget.getAttribute("$tiddler")];
} else if(dataWidget.hasAttribute("$filter")) {
titles = this.wiki.filterTiddlers(dataWidget.getAttribute("$filter"));
}
if(titles) {
var self = this;
var results = [];
$tw.utils.each(titles,function(title,index) {
var tiddler = self.wiki.getTiddler(title),
fields;
if(tiddler) {
fields = tiddler.getFieldStrings();
}
results.push($tw.utils.extend({},item,fields));
})
return results;
} else {
return [item];
}
};
/*
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;
})();