@gregoriusrippenstein/node-red-contrib-introspection 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,276 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('Seeker',{
3
+ color: '#e5e4ef',
4
+ icon: "font-awesome/fa-ship",
5
+ category: 'introspection',
6
+ paletteLabel: "Seeker",
7
+ defaults: {
8
+ name: {
9
+ value:"",
10
+ },
11
+ },
12
+ inputs:0,
13
+ outputs:1,
14
+
15
+ label: function() {
16
+ return (this.name || this._def.paletteLabel);
17
+ },
18
+
19
+ labelStyle: function() {
20
+ return this.name?"node_label_italic":"";
21
+ },
22
+
23
+ oneditprepare: function() {
24
+ var that = this;
25
+
26
+ this._resize = function() {
27
+ var rows = $("#dialog-form>div:not(.node-input-target-list-row)");
28
+ var height = $("#dialog-form").height();
29
+ for (var i=0;i<rows.length;i++) {
30
+ height -= $(rows[i]).outerHeight(true);
31
+ }
32
+ var editorRow = $("#dialog-form>div.node-input-target-list-row");
33
+ editorRow.css("height",height+"px");
34
+ };
35
+
36
+ var search = $("#node-input-seeker-target-filter").searchBox({
37
+ style: "compact",
38
+ delay: 300,
39
+ change: function() {
40
+ var val = $(this).val().trim().toLowerCase();
41
+ if (val === "") {
42
+ dirList.treeList("filter", null);
43
+ search.searchBox("count","");
44
+ } else {
45
+ var count = dirList.treeList("filter", function(item) {
46
+ return item.label.toLowerCase().indexOf(val) > -1 || item.node.type.toLowerCase().indexOf(val) > -1
47
+ });
48
+ search.searchBox("count",count+" / "+items.length);
49
+ }
50
+ }
51
+ });
52
+
53
+ var dirList = $("#node-input-seeker-target-container-div").css({
54
+ width: "100%",
55
+ height: "100%"
56
+ }).treeList({multi:true}).on("treelistitemmouseover", function(e, item) {
57
+
58
+ if ( item.node) {
59
+ if ( item.children) {
60
+ item.node.highlighted = true;
61
+ item.node.dirty = true;
62
+ item.children.forEach( function(n) {
63
+ n.node.highlighted = true;
64
+ n.node.dirty = true;
65
+ });
66
+ } else {
67
+ item.node.highlighted = true;
68
+ item.node.dirty = true;
69
+ }
70
+ RED.view.redraw();
71
+ }
72
+ }).on("treelistitemmouseout", function(e, item) {
73
+
74
+ if ( item.node ) {
75
+ if ( item.children ) {
76
+ item.node.highlighted = false;
77
+ item.node.dirty = true;
78
+ item.children.forEach( function(n) {
79
+ n.node.highlighted = false;
80
+ n.node.dirty = true;
81
+ });
82
+ } else {
83
+ item.node.highlighted = true;
84
+ item.node.dirty = true;
85
+ }
86
+ RED.view.redraw();
87
+ }
88
+ }).on('treelistselect', function(event, item) {
89
+ if ( item.node ) {
90
+ if ( item.children) {
91
+ var preselected = item.children.map(function(n) {
92
+ return n.node.id
93
+ });
94
+
95
+ RED.tray.hide();
96
+ RED.view.selectNodes({
97
+ selected: preselected,
98
+ onselect: function(selection) {
99
+ RED.tray.show();
100
+ },
101
+ oncancel: function() {
102
+ RED.tray.show();
103
+ }
104
+ });
105
+
106
+ } else {
107
+ RED.workspaces.show(item.node.z,false,false,true);
108
+ item.node.highlighted = true;
109
+ item.node.dirty = true;
110
+ RED.view.reveal(item.node.id,true)
111
+ }
112
+
113
+ RED.view.redraw();
114
+ }
115
+ });
116
+
117
+ var nodePaths = [];
118
+
119
+ var traverse = function( node, nodeIds ) {
120
+ if ( nodeIds.indexOf(node.id) < 0 ) {
121
+
122
+ switch( node.type ) {
123
+
124
+ case "link out":
125
+ node.links.forEach( function(lnkNdeId) {
126
+ var dNode = RED.nodes.node(lnkNdeId);
127
+ if ( !dNode ) { return }
128
+
129
+ traverse( dNode, [...nodeIds, node.id] );
130
+ });
131
+ break;
132
+
133
+ case "Sink":
134
+ nodePaths.push(nodeIds);
135
+ break;
136
+
137
+ default:
138
+ RED.nodes.getNodeLinks(node).forEach( function(l) {
139
+ traverse( l.target, [...nodeIds, node.id] );
140
+ })
141
+ }
142
+ }
143
+ };
144
+
145
+ RED.nodes.getNodeLinks(that).forEach( function(l) {
146
+ traverse(l.target, [])
147
+ });
148
+
149
+ var items = [];
150
+ var nodeItemMap = {};
151
+
152
+ var listOfNodes = function(nodePaths) {
153
+ var lenNodePaths = nodePaths.length;
154
+ if ( lenNodePaths > 25 ) {
155
+ var rdmIdx = Math.floor( Math.random() * lenNodePaths );
156
+ /* Avoid duplication by taking a random range of 25 nodes */
157
+ return [...nodePaths, ...nodePaths.slice(0,30)].slice(
158
+ rdmIdx, rdmIdx + 25
159
+ )
160
+ } else {
161
+ return nodePaths;
162
+ }
163
+ }
164
+
165
+ listOfNodes(nodePaths).forEach( function (path) {
166
+ var n = RED.nodes.node(path[0]);
167
+
168
+ var nodeDef = RED.nodes.getType(n.type);
169
+ var label;
170
+ var sublabel = "Nodes: " + path.length;
171
+
172
+ if (nodeDef) {
173
+ var l = nodeDef.label;
174
+ label = (typeof l === "function" ? l.call(n) : l)||"";
175
+
176
+ if (sublabel.indexOf("subflow:") === 0) {
177
+ return;
178
+ }
179
+ }
180
+
181
+ if (!nodeDef || !label) {
182
+ label = n.type;
183
+ }
184
+
185
+ var children = [];
186
+
187
+ path.forEach( function(nId) {
188
+ var nde = RED.nodes.node(nId);
189
+
190
+ /* junctions can be undefined */
191
+ if ( nde === undefined ) { return }
192
+
193
+ var ndeDef = RED.nodes.getType(nde.type);
194
+ var labelStr = nde.type;
195
+
196
+ if ( ndeDef ) {
197
+ labelStr = (typeof ndeDef.label === "function" ?
198
+ ndeDef.label.call(nde) : ndeDef.label) || nde.type ||"";
199
+ }
200
+
201
+ children.push(
202
+ {
203
+ "label": labelStr,
204
+ "node": nde,
205
+ "sublabel": nde.type
206
+ }
207
+ )
208
+ });
209
+
210
+ nodeItemMap[n.id] = {
211
+ node: n,
212
+ label: label,
213
+ sublabel: sublabel,
214
+ selected: false,
215
+ checkbox: false,
216
+ children: children
217
+ };
218
+ items.push(nodeItemMap[n.id]);
219
+ });
220
+
221
+ dirList.treeList('data',items);
222
+
223
+ $("#node-input-seeker-target-select").on("click", function(e) {
224
+ e.preventDefault();
225
+ var preselected = dirList.treeList('selected').map(function(n) {
226
+ return n.node.id
227
+ });
228
+
229
+ RED.tray.hide();
230
+ RED.view.selectNodes({
231
+ selected: preselected,
232
+ onselect: function(selection) {
233
+ RED.tray.show();
234
+ },
235
+ oncancel: function() {
236
+ RED.tray.show();
237
+ },
238
+ filter: function(n) {
239
+ return n.id !== that.id;
240
+ }
241
+ });
242
+ })
243
+ },
244
+
245
+ oneditsave: function() {
246
+ },
247
+
248
+ oneditresize: function(size) {
249
+ this._resize();
250
+ }
251
+
252
+ });
253
+ </script>
254
+
255
+ <script type="text/html" data-template-name="Seeker">
256
+
257
+ <div class="form-row node-input-target-row">
258
+ <button id="node-input-seeker-target-select" class="red-ui-button">Show Selected</button>
259
+ </div>
260
+
261
+ <div class="form-row node-input-target-row node-input-target-list-row" style="position: relative; min-height: 100px">
262
+ <div style="position: absolute; top: -30px; right: 0;"><input type="text" id="node-input-seeker-target-filter"></div>
263
+
264
+ <div id="node-input-seeker-target-container-div"></div>
265
+ </div>
266
+
267
+ <div class="form-row">
268
+ <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name">Name</span></label>
269
+ <input type="text" id="node-input-name" placeholder="Name">
270
+ </div>
271
+
272
+ </script>
273
+
274
+ <script type="text/html" data-help-name="Seeker">
275
+ <p>Ther Seeker seeks in the sink and presents all (max 25) paths that lead to the sink.</p>
276
+ </script>
@@ -0,0 +1,17 @@
1
+ module.exports = function(RED) {
2
+ function SeekerFunctionality(config) {
3
+ RED.nodes.createNode(this,config);
4
+
5
+ var node = this;
6
+ var cfg = config;
7
+
8
+ node.on('close', function() {
9
+ node.status({});
10
+ });
11
+
12
+ node.on("input",function(msg, send, done) {
13
+ send(msg);
14
+ });
15
+ }
16
+ RED.nodes.registerType("Seeker", SeekerFunctionality);
17
+ }
@@ -0,0 +1,34 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('Sink',{
3
+ color: '#e5e4ef',
4
+ icon: "font-awesome/fa-anchor",
5
+ category: 'introspection',
6
+ paletteLabel: "Sink",
7
+ defaults: {
8
+ name: {
9
+ value:"",
10
+ },
11
+ },
12
+ inputs:1,
13
+ outputs:0,
14
+
15
+ label: function() {
16
+ return (this.name || this._def.paletteLabel);
17
+ },
18
+
19
+ labelStyle: function() {
20
+ return this.name?"node_label_italic":"";
21
+ },
22
+ });
23
+ </script>
24
+
25
+ <script type="text/html" data-template-name="Sink">
26
+ <div class="form-row">
27
+ <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name">Name</span></label>
28
+ <input type="text" id="node-input-name" placeholder="Name">
29
+ </div>
30
+ </script>
31
+
32
+ <script type="text/html" data-help-name="Sink">
33
+ <p>Sink is the end of a chain of nodes to complete a full text.</p>
34
+ </script>
@@ -0,0 +1,17 @@
1
+ module.exports = function(RED) {
2
+ function SinkFunctionality(config) {
3
+ RED.nodes.createNode(this,config);
4
+
5
+ var node = this;
6
+ var cfg = config;
7
+
8
+ node.on('close', function() {
9
+ node.status({});
10
+ });
11
+
12
+ node.on("input",function(msg, send, done) {
13
+ send(msg);
14
+ });
15
+ }
16
+ RED.nodes.registerType("Sink", SinkFunctionality);
17
+ }
@@ -0,0 +1,326 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('Screenshot',{
3
+ color: '#e5e4ef',
4
+ icon: "font-awesome/fa-camera",
5
+ category: 'introspection',
6
+ paletteLabel: "Screenshot",
7
+ defaults: {
8
+ name: {
9
+ value:"",
10
+ },
11
+ screenshot: {
12
+ value:"Please wait, screenshot being prepared ..."
13
+ },
14
+ },
15
+ inputs:0,
16
+ outputs:0,
17
+
18
+ label: function() {
19
+ return (this.name || this._def.paletteLabel);
20
+ },
21
+
22
+ labelStyle: function() {
23
+ return this.name?"node_label_italic":"";
24
+ },
25
+
26
+ oneditprepare: function() {
27
+ const that = this;
28
+ const stateId = RED.editor.generateViewStateId("node", this, "");
29
+
30
+ if (!this.field) {
31
+ this.field = 'payload';
32
+ $("#node-input-field").val("payload");
33
+ }
34
+ if (!this.fieldType) {
35
+ this.fieldType = 'msg';
36
+ }
37
+ if (!this.syntax) {
38
+ this.syntax = 'mustache';
39
+ $("#node-input-syntax").val(this.syntax);
40
+ }
41
+
42
+ $("#node-input-field").typedInput({
43
+ default: 'msg',
44
+ types: ['msg','flow','global'],
45
+ typeField: $("#node-input-fieldType")
46
+ });
47
+
48
+ this.editor = RED.editor.createEditor({
49
+ id: 'node-input-screenshot-editor',
50
+ mode: 'ace/mode/html',
51
+ stateId: stateId,
52
+ value: $("#node-input-screenshot").val()
53
+ });
54
+
55
+ var hwAttrs = (
56
+ 'width="' + $($('svg')[0]).attr('width') + '" height="' +
57
+ $($('svg')[0]).attr('height') + '"'
58
+ );
59
+
60
+ //
61
+ // begin the SVG conversion code.
62
+ //
63
+ var svgHeader = (
64
+ '<?xml version="1.0" standalone="no"?>\r\n' +
65
+ '<svg ' + hwAttrs + ' pointer-events="all" style="cursor: crosshair; '+
66
+ 'touch-action: none;" xmlns="http://www.w3.org/2000/svg" '+
67
+ 'xmlns:xlink="http://www.w3.org/1999/xlink">\r\n'
68
+ );
69
+
70
+ var svgBody = $($('svg')[0]).html();
71
+
72
+ var parser = new DOMParser();
73
+ var doc = parser.parseFromString(svgHeader + svgBody + '\r\n</svg>',
74
+ "image/svg+xml");
75
+
76
+ // SVG has very specific requirements for colour specification, baseline
77
+ // all colors to #rrggbb. This converts: 'rgb(rrr, ggg, bbb)' and '#rgb'.
78
+ var rgb2hex = (rgb) => {
79
+ if ( !rgb ) { return "none" }
80
+ if ( rgb === null ) { return "none" }
81
+
82
+ rgb3 = rgb.match(/^#(.)(.)(.)$/);
83
+ if ( rgb3 ) {
84
+ return ("#" +rgb3[1]+rgb3[1]+rgb3[2]+rgb3[2]+rgb3[3]+rgb3[3]);
85
+ }
86
+
87
+ rgb3 = rgb.match(/^#......$/);
88
+ if ( rgb3 ) { return rgb; }
89
+
90
+ rgb3 = rgb.match(/^rgb\(([0-9]+),\s+([0-9]+),\s+([0-9]+)/);
91
+ if ( rgb3 === null ) { return rgb; }
92
+
93
+ return "#" +
94
+ ("0" + parseInt(rgb3[1],10).toString(16)).slice(-2) +
95
+ ("0" + parseInt(rgb3[2],10).toString(16)).slice(-2) +
96
+ ("0" + parseInt(rgb3[3],10).toString(16)).slice(-2);
97
+ }
98
+
99
+ // Set everything that might have been set via CSS directly on the
100
+ // element. All styling values must be defined as attributes on the
101
+ // SVG element.
102
+ var convertCssToAttr = function( collection ) {
103
+ for ( var idx = 0; idx < collection.length; idx++ ) {
104
+ var elem = collection.item(idx);
105
+ ["fill", "stroke"].forEach( function(attrname) {
106
+ elem.setAttribute(attrname,
107
+ rgb2hex($(elem).attr(attrname) ||
108
+ $(elem).css(attrname) ));
109
+ });
110
+
111
+ [
112
+ "stroke-width","fill-opacity","stroke-opacity","opacity"
113
+ ].forEach(function(attrname) {
114
+ elem.setAttribute(attrname,
115
+ $(elem).attr(attrname) || $(elem).css(attrname));
116
+ });
117
+
118
+ if ( $(elem).hasClass('hide') ) {
119
+ if ( elem.tagName == "g" ) {
120
+ // according to the SVG standard, visibility cannot be applied
121
+ // (or better said: has no effect) on 'g' (group) elements.
122
+ // Inkscape is rather pedantic about this and will show the
123
+ // group, so hack-it by using opacity. Browsers (firefox) doesn't
124
+ // seem to care and will apply visibility to 'g' elements.
125
+ elem.setAttribute("opacity", "0");
126
+ }
127
+ elem.setAttribute("visibility", "hidden");
128
+ }
129
+ }
130
+ }
131
+
132
+ // include font details on the text elements.
133
+ var convertFontsToAttr = function( collection ) {
134
+ for ( var idx = 0; idx < collection.length; idx++ ) {
135
+ var elem = collection.item(idx);
136
+ ["font-family", "font-size", "font-size-adjust", "font-stretch",
137
+ "font-style", "font-variant", "font-weight"
138
+ ].forEach( function(attrname) {
139
+ elem.setAttribute(attrname,
140
+ $(elem).attr(attrname) || $(elem).css(attrname) );
141
+ })
142
+ }
143
+ }
144
+
145
+ // probably missed some elements ...
146
+ convertCssToAttr(doc.getElementsByTagName("g"));
147
+ convertCssToAttr(doc.getElementsByTagName("rect"));
148
+ convertCssToAttr(doc.getElementsByTagName("line"));
149
+ convertCssToAttr(doc.getElementsByTagName("path"));
150
+ convertCssToAttr(doc.getElementsByTagName("circle"));
151
+ convertCssToAttr(doc.getElementsByTagName("image"));
152
+
153
+ convertCssToAttr(doc.getElementsByTagName("text"));
154
+ convertFontsToAttr(doc.getElementsByTagName("text"));
155
+
156
+ // inline image data. Image types supported: Jpeg, Png and Svg.
157
+ var imageColl = doc.getElementsByTagName("image");
158
+
159
+ var getDataAndCallbackWhenDone = (elem, cntr, cb) => {
160
+ var postfix = elem.getAttribute("xlink:href").substr(-4,4).toLowerCase();
161
+
162
+ switch(postfix){
163
+ case ".jpg":
164
+ case "jpeg":
165
+ case ".png":
166
+ var fileType = {
167
+ ".jpg": "jpeg",
168
+ "jpeg": "jpeg",
169
+ ".png": "png"
170
+ };
171
+
172
+ var oReq = new XMLHttpRequest();
173
+ oReq.open("GET", elem.getAttribute( "xlink:href" ), true);
174
+ oReq.responseType = "arraybuffer";
175
+
176
+ var arrayBufferToBase64 = ( buffer ) => {
177
+ var binary = '';
178
+ var bytes = new Uint8Array( buffer );
179
+ var len = bytes.byteLength;
180
+ for (var i = 0; i < len; i++) {
181
+ binary += String.fromCharCode( bytes[ i ] );
182
+ }
183
+ return window.btoa( binary );
184
+ };
185
+
186
+ oReq.onload = function (oEvent) {
187
+ var arrayBuffer = oReq.response; // Note: not oReq.responseText
188
+ if (arrayBuffer) {
189
+ elem.setAttribute("xlink:href",
190
+ "data:image/"+fileType[postfix]+";base64," +
191
+ arrayBufferToBase64(arrayBuffer));
192
+ cb(cntr-1)
193
+ } else {
194
+ cb(cntr-1)
195
+ }
196
+ };
197
+
198
+ oReq.send(null);
199
+ break;
200
+
201
+ case ".svg":
202
+ $.get( elem.getAttribute( "xlink:href" ), function(data) {
203
+ const serializer = new XMLSerializer();
204
+ elem.setAttribute( "xlink:href",
205
+ "data:image/svg+xml;base64," +
206
+ btoa(serializer.serializeToString(data)));
207
+ cb(cntr-1)
208
+ });
209
+ break;
210
+ }
211
+ };
212
+
213
+ var cb = (cntr) => {
214
+ if ( cntr < 0 ) {
215
+ const serializer = new XMLSerializer();
216
+ that.editor.setValue( serializer.serializeToString(doc) );
217
+ } else {
218
+ getDataAndCallbackWhenDone( imageColl.item(cntr), cntr, cb );
219
+ }
220
+ };
221
+
222
+ if ( imageColl.length > 0 ) {
223
+ getDataAndCallbackWhenDone( imageColl.item(imageColl.length-1),
224
+ imageColl.length-1,
225
+ cb );
226
+ }
227
+
228
+ // handle the download button under the editor window.
229
+ $('#node-screenshot-download-svg').on("click", function (e) {
230
+ e.preventDefault();
231
+ var svgBlob = new Blob([that.editor.getValue()], {type:"image/svg+xml;charset=utf-8"});
232
+ var svgUrl = URL.createObjectURL(svgBlob);
233
+ var downloadLink = document.createElement("a");
234
+ downloadLink.href = svgUrl;
235
+ downloadLink.download = "screenshot.svg";
236
+ document.body.appendChild(downloadLink);
237
+ downloadLink.click();
238
+ document.body.removeChild(downloadLink);
239
+ });
240
+
241
+ // normality resumes here, all the SVG handling is done.
242
+
243
+ $("#node-input-format").on("change", function() {
244
+ var mod = "ace/mode/"+$("#node-input-format").val();
245
+ that.editor.getSession().setMode({
246
+ path: mod,
247
+ v: Date.now()
248
+ });
249
+ });
250
+
251
+ RED.popover.tooltip($("#node-screenshot-expand-editor"), RED._("node-red:common.label.expand"));
252
+ $("#node-screenshot-expand-editor").on("click", function (e) {
253
+ e.preventDefault();
254
+ const value = that.editor.getValue();
255
+ that.editor.saveView();
256
+ RED.editor.editText({
257
+ mode: $("#node-input-format").val(),
258
+ value: value,
259
+ stateId: stateId,
260
+ width: "Infinity",
261
+ focus: true,
262
+ complete: function (v, cursor) {
263
+ that.editor.setValue(v, -1);
264
+ setTimeout(function () {
265
+ that.editor.restoreView();
266
+ that.editor.focus();
267
+ }, 250);
268
+ }
269
+ })
270
+ })
271
+ },
272
+
273
+ oneditresize: function(size) {
274
+ var rows = $("#dialog-form>div:not(.node-text-editor-row)");
275
+ var height = $("#dialog-form").height();
276
+ for (var i=0; i<rows.length; i++) {
277
+ height -= $(rows[i]).outerHeight(true);
278
+ }
279
+ var editorRow = $("#dialog-form>div.node-text-editor-row");
280
+ height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
281
+ $(".node-text-editor").css("height",height+"px");
282
+ this.editor.resize();
283
+ }
284
+ });
285
+ </script>
286
+
287
+ <script type="text/html" data-template-name="Screenshot">
288
+ <div class="form-row">
289
+ <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name">Name</span></label>
290
+ <input type="text" id="node-input-name" placeholder="Name">
291
+ </div>
292
+
293
+ <div class="form-row" style="position: relative; margin-bottom: 0px;">
294
+ <label for="node-input-screenshot"><i class="fa fa-file-code-o"></i> <span data-i18n="screenshot.label.screenshot">Screenshot</span></label>
295
+ <input type="hidden" id="node-input-screenshot" autofocus="autofocus">
296
+ <div style="position: absolute; right:0;display:inline-block; text-align: right; font-size: 0.8em;">
297
+ <span data-i18n="screenshot.label.format">Syntax</span>:
298
+ <select id="node-input-format" style="width:110px; font-size: 10px !important; height: 24px; padding:0;">
299
+ <option value="handlebars">mustache</option>
300
+ <option value="html">HTML</option>
301
+ <option value="json">JSON</option>
302
+ <option value="javascript">JavaScript</option>
303
+ <option value="css">CSS</option>
304
+ <option value="markdown">Markdown</option>
305
+ <option value="php">PHP</option>
306
+ <option value="python">Python</option>
307
+ <option value="sql">SQL</option>
308
+ <option value="yaml">YAML</option>
309
+ <option value="text" data-i18n="screenshot.label.none"></option>
310
+ </select>
311
+ <button type="button" id="node-screenshot-expand-editor" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button>
312
+ </div>
313
+ </div>
314
+
315
+ <div class="form-row node-text-editor-row">
316
+ <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-screenshot-editor" ></div>
317
+ </div>
318
+
319
+ <div class="form-row">
320
+ <button type="button" id="node-screenshot-download-svg" class="red-ui-button red-ui-button-small"><i class="fa fa-download"></i>Download</button>
321
+ </div>
322
+ </script>
323
+
324
+ <script type="text/html" data-help-name="Screenshot">
325
+ <p>Create a SVG screenshot of the current tab.</p>
326
+ </script>
@@ -0,0 +1,17 @@
1
+ module.exports = function(RED) {
2
+ function ScreenshotFunctionality(config) {
3
+ RED.nodes.createNode(this,config);
4
+
5
+ var node = this;
6
+ var cfg = config;
7
+
8
+ node.on('close', function() {
9
+ node.status({});
10
+ });
11
+
12
+ node.on("input",function(msg, send, done) {
13
+ send(msg);
14
+ });
15
+ }
16
+ RED.nodes.registerType("Screenshot", ScreenshotFunctionality);
17
+ }