UNPKG

@gregoriusrippenstein/node-red-contrib-flowhub

Version:
741 lines 62.2 kB
<script src="FlowHubLib/jslib/diff.min.js"></script>

<script type="text/javascript">
(function() {
   var globalYourConfigNode = null;
      
   function escapeHtml(e){return String(e).replace(/[&<>"'`=\/]/g,function(e){return entityMap[e]})}function zoomToNodeOrJunction(e){var t=$("#red-ui-workspace-chart"),o=RED.view.scale(),n=[(t[0].clientWidth-$("#red-ui-sidebar").width())/o,t[0].clientHeight/o],i=[t.scrollLeft()/o,t.scrollTop()/o],a=e.x,e=e.y;(a<i[0]||e<i[1]||n[0]+i[0]<a||n[1]+i[1]<e)&&t.animate({scrollLeft:"-="+(i[0]-a+n[0]/2)*o,scrollTop:"-="+(i[1]-e+n[1]/2)*o},200)}function checkToken(e){$("#flowhubpush-check-token-result-content").fadeOut(300,()=>{$.get({url:"https://flowhub.org/integration/token/check?format=json&t="+e}).done(e=>{if(!e.data)return RED.notify("Check failed: "+e.msg,{type:"error",id:"FlowHubTokenCheck",timeout:2e3});RED.notify("FlowHub token is good!",{type:"success",id:"FlowHubTokenCheck",timeout:2e3}),$("#flowhubpush-check-token-result-content").find(".user").attr("href","mailto:"+e.data.email).html(e.data.name),$("#flowhubpush-check-token-result-content").find(".email").html(e.data.email),$("#flowhubpush-check-token-result-content").find(".repo").attr("href",e.data.repo_url).html(e.data.repo),$("#flowhubpush-check-token-result-content").find(".permission").html(e.data.permission),$("#flowhubpush-check-token-result-content").find(".branch").html(e.data.branch),$("#flowhubpush-check-token-result-content").fadeIn(300)}).fail(e=>{RED.notify("Check failed: "+e.msg,{type:"error",id:"FlowHubTokenCheck",timeout:2e3})})})}var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"},descMap={z:"Flow Tab Id",g:"Group Id",d:"Disabled"},linkNodeIds=["wires","links","nodes"],setOldToNewForNode=(e,t,o)=>{e[t]=o,e.dirty=!0,RED.nodes.dirty(!0),RED.view.redraw()},defaultRevertHandler=(e,t,o)=>{e=RED.nodes.node(e)||RED.nodes.junction(e);e&&setOldToNewForNode(e,t,o)},revertableAttributes={},revertableAttributesNames=(["x","y","template","name","info","func","label","tooltip","timeout","repeat"].forEach(function(e){revertableAttributes[e]=defaultRevertHandler}),Object.keys(revertableAttributes));function attrDesc(e){return descMap[e]?" <i style='font-size: 80%;'>("+descMap[e]+")</i>":""}function clickBaitDiff(){$("th.revertable").on("click",e=>{e.preventDefault();var t,o=$($(e.target).parent()).data("attr");confirm('Really revert changes to attribute "'+o+'"?')&&(t=revertableAttributes[o])&&t($($(e.target).parent()).data("id"),o,JSON.parse(atob($($(e.target).parent()).data("oldval"))))})}function createDiff(r,s){var c=[],f=[];try{if(Object.keys(s).forEach(function(e){var t;Object.keys(r).indexOf(e)<0&&(t="object"==typeof s[e]?JSON.stringify(s[e]):s[e],c.push("<tr class='dv-removed'><th>"+e+attrDesc(e)+"</th><td><i>MISSING</i></td><td><code>"+escapeHtml(t)+"</code></td></tr>"),f.push(e))}),Object.keys(r).forEach(function(e){var t;Object.keys(s).indexOf(e)<0&&(t="object"==typeof r[e]?JSON.stringify(r[e]):r[e],c.push("<tr class='dv-added'><th>"+e+attrDesc(e)+"</th><td><code>"+escapeHtml(t)+"</code></td><td><i>ADDED</i></td></tr>"),f.push(e))}),Object.keys(r).forEach(function(a){if(-1<Object.keys(s).indexOf(a))if(JSON.stringify(r[a])!==JSON.stringify(s[a]))try{let n=null,t=void 0;f.push(a);try{t=Diff.diffLines(s[a],r[a])}catch(e){t=Diff.diffLines(JSON.stringify(s[a],void 0,1),JSON.stringify(r[a],void 0,1))}let i=document.createDocumentFragment();t.forEach(e=>{let o=e.added?"green":e.removed?"red":"#040506";(n=document.createElement("pre")).setAttribute("class",e.added||e.removed?"dv-pre-elem":"dv-pre-elem-no-change"),n.style.color=o,-1<linkNodeIds.indexOf(a)&&e.value.match(/[0-9a-f]{16}/)?e.value.split(/([0-9a-fA-F]{16})/).forEach(e=>{var t;e.match(/[0-9a-fA-F]{16}/)?((t=document.createElement("a")).appendChild(document.createTextNode(e)),t.title="search for "+e,t.href="#",t.setAttribute("onclick",`javascript:RED.search.show('${e}'); return false`),t.style.color=o,n.appendChild(t)):n.appendChild(document.createTextNode(e))}):n.appendChild(document.createTextNode(e.value)),n.appendChild(document.createElement("br")),i.appendChild(n)});var o=-1<revertableAttributesNames.indexOf(a)?" revertable":"",e=document.createElement("tr");e.setAttribute("class","dv-differ"),e.setAttribute("data-id",r.id),e.setAttribute("data-attr",a);try{e.setAttribute("data-oldval",btoa(JSON.stringify(s[a])))}catch(e){o=""}var d,l=document.createElement("th");l.setAttribute("class",o),l.appendChild(document.createTextNode(a)),descMap[a]&&((d=document.createElement("i")).style["font-size"]="80%",d.appendChild(document.createTextNode("("+descMap[a]+")")),l.appendChild(d)),e.append(l),(l=document.createElement("td")).setAttribute("colspan","2"),l.append(i),e.append(l),c.push(e.outerHTML)}catch(e){console.log("--Exception",e),c.push("<tr><th class='dv-error'>"+a+" CAUSED EXCEP</th><td><code>"+escapeHtml(r[a])+"</code></td><td><code>"+escapeHtml(s[a])+"</code></td></tr>")}else c.push("<tr><th>"+a+"</th><td><code class=prop-code-value>"+escapeHtml(r[a])+"</code></td><td><code></code></td></tr>")}),0==f.length){let o=null;var e=Diff.diffJson(s,r);let n=document.createDocumentFragment();e.forEach(e=>{var t=e.added?"green":e.removed?"red":"#040506";(o=document.createElement("pre")).setAttribute("class",e.added||e.removed?"dv-pre-elem":"dv-pre-elem-no-change"),o.style.color=t,o.appendChild(document.createTextNode(e.value)),o.appendChild(document.createElement("br")),n.appendChild(o)});var t,i=document.createElement("tr"),a=(i.setAttribute("class","dv-differ"),document.createElement("th"));return a.appendChild(document.createTextNode("OBJ")),descMap.OBJ&&((t=document.createElement("i")).style["font-size"]="80%",t.appendChild(document.createTextNode("("+descMap.OBJ+")")),a.appendChild(t)),i.append(a),(a=document.createElement("td")).setAttribute("colspan","2"),a.append(n),i.append(a),{html:i.outerHTML,icon:"fa-question"}}}catch(e){console.log("Exception",e)}var o,n;return{html:c.join(""),icon:(e=f,n=[...o=["x","y","w","h","g"],"wires","d","nodes"],0==e.filter(e=>o.indexOf(e)<0).length?"fa-eye":0==e.filter(e=>n.indexOf(e)<0).length?"fa-paint-brush":"fa-pencil")}}function getInstalledNodeDetails(e){$.ajax({url:"nodes",headers:{Accept:"application/json"},success:e,error:()=>{e(["error"])}})}function doTokenCheck(e,t){$.ajax({url:"FlowHubTokenCheck",type:"POST",contentType:"application/json; charset=utf-8",data:JSON.stringify({cfgnode:e}),success:function(e){t(e)},error:function(e,t,o){RED.notify("Check failed: "+t,{type:"error",id:"FlowHubTokenCheck",timeout:2e3})}})}function doPushSubmission(e,t,o,n){var i=RED.workspaces.active();let a=e.map(e=>e.type);n=n.filter(e=>-1<a.indexOf(e.types[0])),$.ajax({url:"FlowHubPush",type:"POST",contentType:"application/json; charset=utf-8",data:JSON.stringify({flowid:i,flowdata:e||{},flowlabel:RED.nodes.workspace(i).label,cfgnode:t,svgdata:o,nodedetails:n}),success:function(e){RED.notify("Submission triggered",{type:"warning",id:"FlowHubPush",timeout:2e3}),obtainFhbToken(obtainConfigNode(),e=>{""!==e&&(e=e&&"?t="+tokenToUrl(e)),$("#flowhubpush-view-flow-link").attr("href","https://flowhub.org/f/"+i+e).show()})},error:function(e,t,o){RED.notify("Submission failed: "+t,{type:"error",id:"FlowHubPush",timeout:2e3}),console.log(t,o)}})}function getFlowDataFromCurrentWorkspace(e){var e=e||RED.workspaces.active(),t=RED.nodes.groups(e),e=(t=(t=t.concat(RED.nodes.junctions(e))).concat(RED.nodes.filterNodes({z:e})),RED.nodes.eachConfig(function(e){e.z===RED.workspaces.active()&&!1===e._def.hasUsers&&t.push(e)}),RED.nodes.workspace(e)||RED.nodes.subflow(e));return t.unshift(e),RED.nodes.createExportableNodeSet(t)}function iframeRemoveHighlight(){document.getElementById("flowhubdiff-svgimageiframe").contentWindow.postMessage({msg:"removehighlight"},"*")}function iframeHighlightItem(e){iframeRemoveHighlight(),document.getElementById("flowhubdiff-svgimageiframe").contentWindow.postMessage({item:{label:e.label,icon:e.icon,class:e.class,sublabel:e.sublabel,objid:e.objid},msg:"highlightitem"},"*")}function iframeHasBeenLoaded(e){let t=$(e).data("flowid");var o=getFlowDataFromCurrentWorkspace(t);setTimeout(()=>{e.contentWindow&&e.contentWindow.postMessage({nodes:o,msg:"renderlocalflow",flowid:t||RED.workspaces.active()},"*")},345)}function doComparison(e,t){var n=RED.workspaces.active();if($("#node-input-flowhubdiff-next-change-btn").hide(),RED.nodes.workspace(n))RED.notify("Flow Comparison Triggered",{type:"warning",id:"FlowHubDiff",timeout:2e3}),$.ajax({url:"FlowHubDiff",type:"POST",contentType:"application/json; charset=utf-8",data:JSON.stringify({flowid:n,flowhub:"submission from ",flowdata:e||{},flowlabel:RED.nodes.workspace(n).label,cfgnode:t}),success:function(t){var e,d=[],l={};if("failed"==t.status){RED.notify(t.msg||"This does not seem to be a FlowHub flow id: "+n,{type:"error",timeout:2e3}),$("#flowhubdiff-view-flow-link").hide();try{$("#node-input-flowhubdiff-diffcontainer").html(""),$("#node-input-flowhubdiff-svgcontainer").html(""),$("#node-input-flowhubdiff-target-container-div").treeList("empty")}catch(e){}}else if(t.revision&&""!=t.revision&&(e=ensureYourConfigNodeExists()).flowrevisions&&e.flowrevisions[t.flowid]&&e.flowrevisions[t.flowid].substr(0,8)!=t.revision.substr(0,8)&&RED.notify(`WARNING: Revision mismatch: Local: ${e.flowrevisions[t.flowid].substr(0,8)}, Remote: `+t.revision.substr(0,8),{type:"warning",timeout:4e3}),obtainFhbToken(obtainConfigNode(),e=>{(""!==e&&e?$("#flowhubdiff-view-flow-link").attr("href","https://flowhub.org/f/"+t.flowid+"?t="+tokenToUrl(e)):$("#flowhubdiff-view-flow-link").attr("href","https://flowhub.org/f/"+t.flowid)).show()}),t.changes.forEach(function(e){var t,o,n,i,a=e.id;"deleted"==e.type?(l[a]={label:e.oldObj.name||e.oldObj.type,icon:"fa fa-trash",class:"",diffcontent:createDiff({},e.oldObj).html,sublabel:e.oldObj.type,selected:!1,checkbox:!1,children:void 0,objid:a},d.push(l[a])):"added"==e.type?(l[a]={objid:a,label:e.newObj.name||e.newObj.type,icon:"fa fa-plus-square",class:"",diffcontent:createDiff(e.newObj,{}).html,sublabel:e.newObj.type,selected:!1,checkbox:!1,children:void 0},d.push(l[a])):"tab"==e.oldObj.type||"group"==e.oldObj.type||"junction"==e.oldObj.type?(i=createDiff(e.newObj,e.oldObj),l[a]={objid:a,label:e.oldObj.name||e.oldObj.type,icon:"fa "+i.icon,class:"",diffcontent:i.html,sublabel:e.type+" - "+e.oldObj.type,selected:!1,checkbox:!1,children:void 0},d.push(l[a])):(t=RED.nodes.node(a))?((o=RED.nodes.getType(t.type))&&(n=("function"==typeof(n=o.label)?n.call(t):n)||""),o&&n||(n=t.type),i=createDiff(e.newObj,e.oldObj),l[t.id]={node:t,objid:a,label:n,icon:"fa "+i.icon,class:"",diffcontent:i.html,sublabel:e.oldObj.type,selected:!1,checkbox:!1,children:void 0},d.push(l[t.id])):console.log("Node ignore, Type: "+e.type,e)}),t.ifurl&&$("#node-input-flowhubdiff-svgcontainer").html('<iframe id="flowhubdiff-svgimageiframe" width="100%" height="100%" src="'+t.ifurl+'" onload="window.iframeHasBeenLoaded(this)" style="border: none; min-height: 300px;" data-flowid="'+n+'"></iframe>'),0==d.length){RED.notify("No Flow Changes",{type:"warning",timeout:2e3});try{$("#node-input-flowhubdiff-diffcontainer").html(""),$("#node-input-flowhubdiff-target-container-div").treeList("empty")}catch(e){}}else{try{$("#node-input-flowhubdiff-target-container-div").treeList("empty")}catch(e){$("#node-input-flowhubdiff-target-container-div").css({width:"100%",height:"calc(100%)"}).treeList({multi:!1}).on("treelistitemmouseover",function(e,t){t.objid&&iframeHighlightItem(t)}).on("treelistitemmouseout",function(e,t){}).on("treelistselect",function(e,t){t.objid&&(RED.view.reveal(t.objid,!0),RED.view.redraw()),t.diffcontent?($("#node-input-flowhubdiff-next-change-btn").show(),$("#node-input-flowhubdiff-next-change-btn").data("cntr",0),$("#node-input-flowhubdiff-diffcontainer").html(t.diffcontent),clickBaitDiff(),setTimeout(()=>{var e=$("#node-input-flowhubdiff-diffcontainer").find(".dv-pre-elem").length;0<e&&RED.notify("Total changes: "+e,{type:"success"})},300)):($("#node-input-flowhubdiff-diffcontainer").html(""),$("#node-input-flowhubdiff-next-change-btn").hide(),$("#node-input-flowhubdiff-next-change-btn").data("cntr",void 0))}).on("treelistconfirm",function(e,t){var o;t.objid&&(o=t.objid,setTimeout(()=>{var e;RED.nodes.node(o)?(RED.view.reveal(o),RED.view.select(o),RED.editor.edit(RED.nodes.node(o))):o==RED.workspaces.active()?RED.workspaces.edit():RED.nodes.junction(o)&&(e=RED.nodes.junction(o),$($(`g[transform="translate(${e.x},${e.y})"] rect`)[0]).addClass("red-ui-flow-junction-hovered"),$(`g[transform="translate(${e.x},${e.y})"]`).addClass("selected"),RED.view.select({nodes:[e]}),zoomToNodeOrJunction(e))},50))}),$("#node-input-flowhubdiff-target-filter").show();var o=$("#node-input-flowhubdiff-target-filter").searchBox({style:"compact",delay:300,change:function(){var e,t=$(this).val().trim().toLowerCase();""===t?($("#node-input-flowhubdiff-target-container-div").treeList("filter",null),o.searchBox("count","")):(e=$("#node-input-flowhubdiff-target-container-div").treeList("filter",function(e){return-1<e.label.toLowerCase().indexOf(t)||-1<e.node.type.toLowerCase().indexOf(t)}),o.searchBox("count",e+" / "+$("#node-input-flowhubdiff-target-container-div").treeList("data").length))}})}$("#node-input-flowhubdiff-target-container-div").treeList("data",d.sort((e,t)=>e.icon==t.icon?e.label.toLowerCase()>t.label.toLowerCase():e.icon>t.icon))}},error:function(e,t,o){$("#flowhubdiff-view-flow-link").hide();try{$("#node-input-flowhubdiff-diffcontainer").html(""),$("#node-input-flowhubdiff-svgcontainer").html(""),$("#node-input-flowhubdiff-target-container-div").treeList("empty")}catch(e){}404==e.status?RED.notify("Node has not yet been deployed, please deploy.","error"):405==e.status?RED.notify("Not Allowed.","error"):500==e.status?RED.notify("Not Allowed - 500.","error"):0==e.status?RED.notify("Not Allowed - 0.","error"):RED.notify("Not Allowed - Unexpected.","error")}});else{RED.notify("Comparing subflows not supported.",{type:"error",id:"FlowHubDiff",timeout:2e3}),$("#flowhubdiff-view-flow-link").hide();try{$("#node-input-flowhubdiff-diffcontainer").html(""),$("#node-input-flowhubdiff-svgcontainer").html(""),$("#node-input-flowhubdiff-target-container-div").treeList("empty")}catch(e){}}}function addTokenToList(e,t){$("#node-input-flowhubpush-token-list").append(`<tr class='${e}' data-type='${t.type}' data-name='${t.name}' data-token='${t.val}'>`+`<td><a class='flowhubpush-token-select-link' href='#'>${t.name}</a></td>`+"<td><a class='flowhubpush-token-select-link button' href='#'>Use</a></td><td><a class='flowhubpush-token-remove-link button' href='#'>Remove</a></td><td><a class='flowhubpush-token-hide-link button' href='#'>Hide</a></td><td><a class='flowhubpush-token-check-link button' href='#'>Check</a></td><td><a class='flowhubpush-token-copy-link button' href='#'>Copy</a></td></tr>")}function addEventHandlersToTokenList(n){$(".flowhubpush-token-copy-link").off("click"),$(".flowhubpush-token-copy-link").on("click",e=>{RED.clipboard.copyText($($(e.target).closest("tr")).data("token"))&&RED.notify("Token copied","success")}),$(".flowhubpush-token-check-link").off("click"),$(".flowhubpush-token-check-link").on("click",e=>{checkToken($($(e.target).closest("tr")).data("token"))}),$(".flowhubpush-token-hide-link").off("click"),$(".flowhubpush-token-hide-link").on("click",e=>{e&&e.preventDefault();let t=$($(e.target).closest("tr"));$($(e.target).closest("tr")).fadeOut(300,()=>{RED.notify("Hidden token <b>"+t.data("name")+"</b>, will reappear with reload","success")})}),$(".flowhubpush-token-remove-link").off("click"),$(".flowhubpush-token-remove-link").on("click",e=>{e&&e.preventDefault();let t=$($(e.target).closest("tr")),o=n;confirm(`Remove token "${t.data("name")}"?`)&&$($(e.target).closest("tr")).fadeOut(300,()=>{o.tokens=o.tokens.filter(e=>!(e.name==t.data("name")&&e.val==t.data("token")&&e.type==t.data("type"))),o.credentials.tokens=o.tokens,RED.nodes.dirty(!0),RED.notify("Removed token <b>"+t.data("name")+"</b>.","success")})}),$(".flowhubpush-token-select-link").off("click"),$(".flowhubpush-token-select-link").on("click",e=>{e&&e.preventDefault();e=$($(e.target).closest("tr"));$(".flowhub-token-selected").removeClass("flowhub-token-selected"),$("#node-input-flowhubpush-apiToken").typedInput("value",e.data("token")),$("#node-input-flowhubpush-apiToken").typedInput("type",e.data("type")),e.addClass("flowhub-token-selected"),RED.notify("Using token <b>"+e.data("name")+"</b>","success")})}function showNextChangeButtonPressed(e){var t;e&&e.preventDefault(),0<$("#node-input-flowhubdiff-diffcontainer").find(".dv-pre-elem").length&&(e=parseInt($("#node-input-flowhubdiff-next-change-btn").data("cntr")),(t=$("#node-input-flowhubdiff-diffcontainer").find(".dv-pre-elem")[e])?(t.scrollIntoView(),$("#node-input-flowhubdiff-next-change-btn").data("cntr",e+1)):(RED.notify("Cycled through all changes.",{type:"success"}),$("#node-input-flowhubdiff-next-change-btn").data("cntr",0)))}window.iframeHasBeenLoaded=iframeHasBeenLoaded;

   function nr_intro_generate_svg_5_0(n){return t=>{try{handleSvgObject($($("#red-ui-workspace-chart").find("svg")[0]),t)}catch(e){var r="Error Generating SVG: "+JSON.stringify(e);n.notify(r,{type:"error"}),t('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+r+"</text></svg>")}}}function nr_intro_generate_svg_4_1(n){return t=>{try{handleSvgObject($($("#red-ui-workspace-chart").find("svg")[0]),t)}catch(e){var r="Error Generating SVG: "+JSON.stringify(e);n.notify(r,{type:"error"}),t('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+r+"</text></svg>")}}}function nr_intro_generate_svg_4_0(n){return t=>{try{handleSvgObject($($("#red-ui-workspace-chart").find("svg")[0]),t)}catch(e){var r="Error Generating SVG: "+JSON.stringify(e);n.notify(r,{type:"error"}),t('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+r+"</text></svg>")}}}function nr_intro_generate_svg_3_1(n){return t=>{try{handleSvgObject($($("#red-ui-workspace-chart").find("svg")[0]),t)}catch(e){var r="Error Generating SVG: "+JSON.stringify(e);n.notify(r,{type:"error"}),t('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+r+"</text></svg>")}}}function nr_intro_generate_svg_3_0(n){return t=>{try{handleSvgObject($($("svg")[0]),t)}catch(e){var r="Error Generating SVG: "+JSON.stringify(e);n.notify(r,{type:"error"}),t('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+r+"</text></svg>")}}}function handleSvgObject(o,e){o.find("g.red-ui-flow-group-selected").removeClass("red-ui-flow-group-selected"),o.find("rect.red-ui-flow-group-outline-select").css("stroke-opacity","0"),o.find(".red-ui-flow-node-selected").removeClass("red-ui-flow-node-selected"),o.find(".red-ui-flow-link-selected").removeClass("red-ui-flow-link-selected");var t=o.clone(),r=(t.find("foreignObject").remove(),t.find(".mindmap-image-preview-element").remove(),t.find("svg.__screenshot").remove(),'width="'+o.attr("width")+'" height="'+o.attr("height")+'"'),n=RED.settings.version.split("."),s=parseInt(n[0]),n=parseInt(n[1]);if(3==s&&1==n||4==s&&0==n){var i=$($($(o).children("g")[0]).children("g")[0]).children("g"),a={x:8e3,y:8e3,w:-1,h:-1};for(let e=1;e<i.length;e++){var l=i[e].getBBox();0==l.width&&0==l.height||(a.x=Math.min(l.x,a.x),a.y=Math.min(l.y,a.y),a.w=Math.max(l.width,a.w),a.h=Math.max(l.height,a.h))}r+=` viewBox='${a.x} ${a.y} ${a.w} ${a.h}'`}function g(e,t){for(var r=0;r<e.length;r++){var s=e.item(r),i=t[r];["stroke-width","fill-opacity","stroke-opacity","opacity","stroke-dasharray"].forEach(function(e){s.setAttribute(e,$(i).attr(e)||$(i).css(e))}),["fill","stroke"].forEach(function(e){var t,r,n=(t=$(i).attr(e)||$(i).css(e))&&null!==t&&"none"!=t?(r=t.match(/^#(.)(.)(.)$/))?"#"+r[1]+r[1]+r[2]+r[2]+r[3]+r[3]:(r=t.match(/^#......$/))?t:null===(r=t.match(/^rgb\(([0-9]+),\s+([0-9]+),\s+([0-9]+)/))?(n=t.match(/^rgba\(([0-9]+),\s+([0-9]+),\s+([0-9]+),\s+([0-9]+)/))?{clr:"#"+("0"+parseInt(n[1],10).toString(16)).slice(-2)+("0"+parseInt(n[2],10).toString(16)).slice(-2)+("0"+parseInt(n[3],10).toString(16)).slice(-2),opa:n[4]}:t:"#"+("0"+parseInt(r[1],10).toString(16)).slice(-2)+("0"+parseInt(r[2],10).toString(16)).slice(-2)+("0"+parseInt(r[3],10).toString(16)).slice(-2):"none";"object"==typeof n?(s.setAttribute(e+"-opacity",n.opa),s.setAttribute(e,n.clr)):s.setAttribute(e,n)}),$(i).hasClass("hide")&&("g"==s.tagName&&s.setAttribute("opacity","0"),s.setAttribute("visibility","hidden"))}}var s='<?xml version="1.0" standalone="no"?>\r\n<svg '+r+' pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" class="__screenshot" xmlns:xlink="http://www.w3.org/1999/xlink">\r\n',n=t.html(),c=(new DOMParser).parseFromString(s+n+"\r\n</svg>","image/svg+xml"),h=t=>(["g","rect","line","path","circle","image","text"].forEach(e=>{$(t.getElementsByTagName(e)).each((e,t)=>{t.setAttribute("class",""),t.setAttribute("id","")})}),t),f=(["g","rect","line","path","circle","image"].forEach(function(e){g(c.getElementsByTagName(e),o.find(e))}),["text"].forEach(function(e){g(c.getElementsByTagName(e),o.find(e));for(var t=c.getElementsByTagName(e),r=o.find(e),n=0;n<t.length;n++){var s=t.item(n),i=r[n];["font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","text-anchor","dominant-baseline"].forEach(function(e){s.setAttribute(e,$(i).attr(e)||$(i).css(e))})}}),c.getElementsByTagName("image")),v={},w=(r,n,s)=>{var i=r.getAttribute("xlink:href")||r.getAttribute("href");if(!i)return s(n-1);var o=i.substr(-4,4).toLowerCase(),a={".jpg":"jpeg",jpeg:"jpeg",".png":"png",".svg":"svg+xml"};if(v[i])return r.setAttribute("xlink:href","data:image/"+a[o]+";base64,"+v[i]),s(n-1);switch(o){case".jpg":case"jpeg":case".png":var l=new XMLHttpRequest;l.open("GET",i,!0),l.responseType="arraybuffer";l.onerror=function(e){s(n-1)},l.onload=function(e){var t=l.response;t&&(t=(e=>{for(var t="",r=new Uint8Array(e),n=r.byteLength,s=0;s<n;s++)t+=String.fromCharCode(r[s]);return window.btoa(t)})(t),v[i]=t,r.setAttribute("xlink:href","data:image/"+a[o]+";base64,"+t)),s(n-1)},l.send(null);break;case".svg":$.get(i,function(e){var t=new XMLSerializer,t=btoa(t.serializeToString(e));v[i]=t,r.setAttribute("xlink:href","data:image/svg+xml;base64,"+t),s(n-1)});break;default:s(n-1)}},d=t=>{if(t<0)e((new XMLSerializer).serializeToString(h(c)));else try{w(f.item(t),t,d)}catch(e){d(t-1)}};0<f.length?w(f.item(f.length-1),f.length-1,d):e((new XMLSerializer).serializeToString(h(c)))}function svgGeneratorFunctionForVersion(e){var r,t=e.settings.version.split("."),n=t[0],t=t[1];if("3"==n){if("0"==t)return nr_intro_generate_svg_3_0(e);if("1"==t)return nr_intro_generate_svg_3_1(e)}if("4"==n){if("0"==t)return nr_intro_generate_svg_4_0(e);if("1"==t)return nr_intro_generate_svg_4_1(e)}return"5"==n&&"0"==t?nr_intro_generate_svg_5_0(e):(r=e,e=>{var t="Node-RED version ("+r.settings.version+") not supported";r.notify(t,{type:"error"}),e&&e('<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg width="1000" height="1000" viewBox="0 0 1000 1000" pointer-events="all" style="cursor: crosshair; touch-action: none;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style>.small { font: bold 20px sans-serif; fill: red;}</style><text x="10" y="30" class="small">'+t+"</text></svg>")})}
   
   function ensureYourConfigNodeExists() {
      // If we had found it previously, check if it has been deleted by the user behind our back
      if (globalYourConfigNode !== null) {
         var configNode = RED.nodes.node(globalYourConfigNode.id);
         if (configNode === null) { globalYourConfigNode = null; }
      }

      // If not found previously, let's go find it
      if (globalYourConfigNode === null) {
         var configNodes = [];
         RED.nodes.eachConfig(function(configNode) {
             if (configNode.type === 'FlowHubCfg') { 
                 configNodes.push(configNode); 
             }
         });

         // Make sure we only have 1 config node
         while (configNodes.length > 1) {
             var configNode = configNodes.pop();
             RED.nodes.remove(configNode.id);
             RED.nodes.dirty(true);
         }

         // When we found a config node, let's use that one
         if (configNodes.length === 1) { globalYourConfigNode = configNodes[0]; }
      }

      // When it doesn't exist yet, create it if required
      if (globalYourConfigNode === null) {

         let typ = RED.nodes.getType("FlowHubCfg");
         
         globalYourConfigNode = {
            id: RED.nodes.id(), // on the server side, this is called RED.util.generateId()
            _def: typ,
            type: "FlowHubCfg",
            hasUsers: false, 
            users: [],
            name: "FlowHubCfg",
            label: function() { return this.name || "FlowHubCfg"},
            /* default data */
            apiTokenType: "cred",
            email: "",
            fullname: "",
            flowid: "",
            flowrevision: "",
            notab: false,
            pushcomment: "",
            pushnewflows: false,
            tokens: [],
            forcepush: false,
            credentials: {
              tokens: [],
              apiToken: ""
            }
         }

         // Add the new config node to the collection of Node-RED nodes
         RED.nodes.add(globalYourConfigNode);

         // Make sure the "Deploy" button becomes active
         RED.nodes.dirty(true);
      }   
      
      return globalYourConfigNode
   }

   function flowHubPullUpdateFlows(e=null,o=null){let t=t=>{if($("#func-sidebar-flowhub-pull-spinner").hide(),$("#node-input-flowhub-pull-spinner").hide(),"failed"==t.status)return RED.notify(t.msg,{type:"error",id:"FlowHubPull",timeout:4e3});let i=[],r={},l=[];Object.keys(t.data).forEach(function(e){var o=t.data[e].name.match(/^(\[.+\])/);o?r[o[0]]?r[o[0]].push(t.data[e]):r[o[0]]=[t.data[e]]:l.push(t.data[e])}),Object.keys(r).sort((e,o)=>e<o?-1:1).forEach(e=>{var o=r[e].map(e=>({label:e.name,icon:"fa "+(RED.nodes.workspace(e.id)?"fa-check-circle-o":""),flowid:e.id,sublabel:e.id,selected:$("#node-input-flowhubpull-flowid").val()==e.id,checkbox:!1,children:void 0}));i.push({label:e,icon:"",flowid:"",sublabel:"",selected:!1,checkbox:!1,children:o.sort((e,o)=>e.label<o.label?-1:1)})}),l.sort((e,o)=>e.name<o.name?-1:1).forEach(e=>{i.push({label:e.name,icon:"fa "+(RED.nodes.workspace(e.id)?"fa-check-circle-o":""),flowid:e.id,sublabel:e.id,selected:$("#node-input-flowhubpull-flowid").val()==e.id,checkbox:!1,children:void 0})}),e&&e(i)};$.get({url:"https://api.flowhub.org/v1/flows?cb="+(new Date).getTime(),headers:{"X-FHB-TOKEN":o}}).done(e=>{t(e)}).fail(e=>{var i="";"rejected"==e.state()?$.ajax({url:"FlowHubCatalogue",type:"POST",contentType:"application/json; charset=utf-8",data:JSON.stringify({token:o}),success:function(e){t(e)},error:function(e,o,t){$("#func-sidebar-flowhub-pull-spinner").hide(),$("#node-input-flowhub-pull-spinner").hide(),i="<p>Failed to retrieve FlowHub catalogue, request blocked.</p><p>Request: <b>https://api.flowhub.org/v1/flows</b></p><p>AdBlocker or uBlock might have prevented request.</p><p>Please check browser console for more details.</p>",console.log("Error",o),RED.notify(i,{type:"error",id:"FlowHubPull",timeout:4e3})}}):($("#func-sidebar-flowhub-pull-spinner").hide(),$("#node-input-flowhub-pull-spinner").hide(),i="<p>Failed to retrieve FlowHub catalogue.</p><p>Check browser console for more details.</p>",console.warn("Error loading https://api.flowhub.org/v1/flows:",e),RED.notify(i,{type:"error",id:"FlowHubPull",timeout:4e3}))})}function obtainConfigNode(){let o=[];return RED.nodes.eachConfig(function(e){"FlowHubCfg"===e.type&&o.push(e)}),o[0]}function tokenToUrl(e){e=e.substring(4);var o="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_".split("");let t=Object.fromEntries(o.map((e,o)=>[e,o])),i=Object.fromEntries(o.map((e,o)=>[o,e]));return e.replaceAll(/./gi,(e,o)=>i[(t[e]+o+1)%64])}function obtainFhbToken(e,i){$.ajax({url:"FlowHubToken",type:"POST",contentType:"application/json; charset=utf-8",data:JSON.stringify({cfgnode:e}),success:function(e){i(e.token)},error:function(e,o,t){i("")}})}function doFlowImportForSidebar(i,e){var o=$("#node-input-flowhubpull-flowid").val().trim();""==$("#node-input-flowhubpull-flowrevision").val().trim()&&RED.nodes.workspace(o)?(RED.notify("Remove existing flow tab to re-import",{type:"warning"}),RED.view.reveal(o)):(RED.notify("Pull triggered",{type:"warning",timeout:3e3}),$.get({url:"https://api.flowhub.org/v3/flows/"+$("#node-input-flowhubpull-flowid").val().trim()+"?cb="+(new Date).getTime()+"&v="+$("#node-input-flowhubpull-flowrevision").val().trim(),headers:{"X-FHB-TOKEN":e,"User-Agent":"FlowHub.org Sidebar"}}).done((o,e)=>{try{if(!o||!o.flowdata||!Array.isArray(JSON.parse(o.flowdata)))return RED.notify("Access denied or revision not found.",{type:"error",timeout:3e3})}catch(e){return RED.notify("Access Denied, parse error.",{type:"error",timeout:3e3})}var t;RED.nodes.workspace(o.flowid)||((t=ensureYourConfigNodeExists()).flowrevisions||={},t.flowrevisions[o.flowid]!=o.revision&&(t.flowrevisions[o.flowid]=o.revision)),RED.clipboard.import(),setTimeout(()=>{var e=JSON.parse(o.flowdata);i&&(e=e.filter(function(e){return"tab"!=e.type})),$("#red-ui-clipboard-dialog-import-text").val(JSON.stringify(e)).trigger("paste")},300)}).fail(e=>{var o="<p>Failed to retrieve flow data from FlowHub, request blocked.</p><p>Request: https://api.flowhub.org/v3/flows/"+$("#node-input-flowhubpull-flowid").val().trim()+"</p><p>AdBlocker or uBlock might have prevented request.</p><p>Please check browser console for more details.</p>";RED.notify(o,{type:"error",id:"FlowHubPull",timeout:4e3})}))}function doFlowImportForPullNode(e,o){RED.notify("Pull triggered",{type:"warning",timeout:3e3});$.get({url:"https://api.flowhub.org/v3/flows/"+$("#node-input-flowid").val().trim()+"?cb="+(new Date).getTime()+"&v="+$("#node-input-flowrevision").val().trim(),headers:{"X-FHB-TOKEN":o,"User-Agent":"FlowHub.org Pull Node"}}).done((o,e)=>{try{if(!o||!o.flowdata||!Array.isArray(JSON.parse(o.flowdata)))return RED.notify("Access denied or revision not found.",{type:"error",timeout:3e3})}catch(e){return RED.notify("Access Denied, parse error.",{type:"error",timeout:3e3})}var t;RED.nodes.workspace(o.flowid)||(t=(()=>{let o=void 0;return RED.nodes.eachConfig(function(e){"FlowHubCfg"===e.type&&(o=e)}),o})())&&(t.flowrevisions||={},t.flowrevisions[o.flowid]!=o.revision)&&(t.flowrevisions[o.flowid]=o.revision),RED.clipboard.import(),setTimeout(()=>{var e=JSON.parse(o.flowdata);$("#red-ui-clipboard-dialog-import-text").val(JSON.stringify(e)).trigger("paste")},300)}).fail(e=>{var o="<p>Failed to retrieve flow data from FlowHub, request blocked.</p><p>Request: https://api.flowhub.org/v3/flows/"+$("#node-input-flowhubpull-flowid").val().trim()+"</p><p>AdBlocker or uBlock might have prevented request.</p><p>Please check browser console for more details.</p>";RED.notify(o,{type:"error",id:"FlowHubPull",timeout:4e3})})}function checkForDuplication(e){$.get({url:"https://api.flowhub.org/v1/flows/"+$("#node-input-flowhubpull-flowid").val()+"?cb="+(new Date).getTime()+"&v="+$("#node-input-flowhubpull-flowrevision").val(),headers:{"X-FHB-TOKEN":e}},function(e,o){let t=[];if(!Array.isArray(e))return RED.notify("Access denied or revision not found.","error");if(e.forEach((e,o)=>{"tab"==e.type?RED.nodes.workspace(e.id)&&t.push(e):"junction"==e.type?RED.nodes.junction(e.id)&&t.push(e):"group"==e.type?RED.nodes.group(e.id)&&t.push(e):"subflow"==e.type?RED.nodes.subflow(e.id)&&t.push(e):RED.nodes.node(e.id)&&t.push(e)}),0==t.length){RED.notify("Found no duplication.","success");try{$("#node-input-flowhubpull-sb-duplication-container-div").treeList("empty")}catch(e){}}else{RED.notify("Found "+t.length+" items of duplication","warning");try{$("#node-input-flowhubpull-sb-duplication-container-div").treeList("empty")}catch(e){$("#node-input-flowhubpull-sb-duplication-container-div").css({width:"100%",height:"calc(100%)"}).treeList({multi:!1}).on("treelistitemmouseover",function(e,o){}).on("treelistitemmouseout",function(e,o){}).on("treelistselect",function(e,o){o.node&&(RED.workspaces.show(o.node.z,!1,!1,!0),RED.view.reveal(o.node.id,!0),RED.view.redraw())}).on("treelistconfirm",function(e,o){var t;o.node&&(t=o.node.id,setTimeout(()=>{var e=RED.nodes.node(t);e&&(RED.view.reveal(e.id),RED.view.select(e.id),RED.editor.edit(e)),t==RED.workspaces.active()&&RED.workspaces.edit()},50))})}t=t.map(e=>({node:e,label:e.id,sublabel:e.type,selected:!1,checkbox:!1})),$("#node-input-flowhubpull-sb-duplication-container-div").treeList("data",t)}})}
   
   // Add your plugin as a new tabsheet in the right sidebar AFTER the flow editor is completely started
var initSidebarComparePullPush = () => {
    // The html content of the sidebar has been specified below as a data-template, from where it can be loaded:
    var content = $($('script[type="text/x-red"][data-template-name="FlowHubDiff"]').i18n().html());

    // Add a "Your sidebar" tabsheet to the right sidebar panel, in which this sidebar panel can be displayed
    // --> more details: https://nodered.org/docs/api/ui/sidebar/
    RED.sidebar.addTab({
        id: "FlowHubDiff",
        label: "FlowHub", // short name for the tab
        name: "FlowHub.org", // long name for the menu
        content: content,
        enableOnEdit: true,
        iconClass: "fa fa-window-minimize",
        visible: true
    });

    var tabs = RED.tabs.create({
        id: 'func-flowhub-tabs',
        onchange: function (tab) {
            $('#func-flowhub-tabs-content').children().hide();
            $('#' + tab.id).show();
        }
    });

    // Add pull tab
    tabs.addTab({
        id: 'func-flowhub-tab-pull',
        iconClass: 'fa fa-arrow-down',
        label: 'Pull'
    });

    // Add compare tab
    tabs.addTab({
        id: 'func-flowhub-tab-compare',
        iconClass: 'fa fa-balance-scale',
        label: 'Compare'
    });

    // Add push tab
    tabs.addTab({
        id: 'func-flowhub-tab-push',
        iconClass: 'fa fa-arrow-up',
        label: 'Push'
    });

    tabs.activateTab("func-flowhub-tab-pull");

    $('#node-input-flowhubdiff-compare-btn').on('click', (e) => {
        e.preventDefault();
        ensureYourConfigNodeExists();
        doComparison(getFlowDataFromCurrentWorkspace(), globalYourConfigNode)
    })

    $('#node-input-flowhubdiff-next-change-btn').on('click', showNextChangeButtonPressed)

    $('#node-input-flowhubpush-push-btn').on('click', (e) => {
        if (e) { e.preventDefault(); }
        ensureYourConfigNodeExists();

        RED.notify("Preparing Submission", {
            type: "warning",
            id: "FlowHubPush",
            timeout: 2000
        });

        globalYourConfigNode.pushcomment = $('#node-input-flowhubpush-comment').val();

        svgGeneratorFunctionForVersion(RED)((svgdata) => {
            getInstalledNodeDetails((nodedetails) => {
                doPushSubmission(getFlowDataFromCurrentWorkspace(), globalYourConfigNode, svgdata, nodedetails)
            })
        });
    });

    $("#node-input-flowhubpush-apiToken").typedInput({
        types: ["env", "global", "cred"],
        typeField: "#node-input-flowhubpush-apiTokenType"
    });

    $("#node-input-flowhubpush-apiToken").typedInput('value', "");
    $("#node-input-flowhubpush-apiToken").typedInput('type', "cred");

    $("#node-input-flowhubpush-apiToken").on('change', () => {
        ensureYourConfigNodeExists();

        var val = $("#node-input-flowhubpush-apiToken").typedInput('value');
        var typ = $("#node-input-flowhubpush-apiToken").typedInput('type');

        if (val != globalYourConfigNode.apiToken) {
            if (!globalYourConfigNode.credentials) { globalYourConfigNode.credentials = {} }
            globalYourConfigNode.credentials.apiToken = val;
        }

        if (typ != globalYourConfigNode.apiTokenType) {
            globalYourConfigNode.apiTokenType = typ;
        }

        globalYourConfigNode.apiToken = val
    })

    $('#node-input-flowhubpush-comment').on('change', (e) => {
        if (e) { e.preventDefault(); }
        ensureYourConfigNodeExists();

        let val = $('#node-input-flowhubpush-comment').val();
        if (val != globalYourConfigNode.pushcomment) {
            globalYourConfigNode.pushcomment = val;
            RED.nodes.dirty(true);
        }
    })

    RED.comms.subscribe('flowhub:submission-result', (event, data) => {
        RED.notify("Submission status: " + data.status, {
            type: data.statusType,
            id: "FlowHubPushResult",
            timeout: data.statusType == "error" ? 3000 : 2000
        });

        /* update an existing revision of a flow, this happens after the 
          flow was successfully submitted and updated */
        if (data.flowid && data.flowrevision) {
            let cfgNode = ensureYourConfigNodeExists();
            cfgNode.flowrevisions ||= {}

            if (cfgNode.flowrevisions[data.flowid] != data.flowrevision) {
                cfgNode.flowrevisions[data.flowid] = data.flowrevision
                cfgNode.pushcomment = ""
                $('#node-input-flowhubpush-comment').val(cfgNode.pushcomment)
                RED.nodes.dirty(true)
            }
        }
    });

    $('#flowhubpull-dialog-import-opt-sb-current').on('click', (e) => {
        e.preventDefault();

        ensureYourConfigNodeExists();
        globalYourConfigNode.notab = true
        RED.nodes.dirty(true);

        $('#flowhubpull-dialog-import-opt-sb-new').removeClass('selected')
        $('#flowhubpull-dialog-import-opt-sb-current').addClass('selected')
    })

    $('#flowhubpull-dialog-import-opt-sb-new').on('click', (e) => {
        e.preventDefault();

        ensureYourConfigNodeExists();
        globalYourConfigNode.notab = false;
        RED.nodes.dirty(true);

        $('#flowhubpull-dialog-import-opt-sb-current').removeClass('selected')
        $('#flowhubpull-dialog-import-opt-sb-new').addClass('selected')
    })

    var dirList = $("#node-input-flowhubpull-sb-target-container-div").css({
        width: "100%",
        height: "100%"
    }).treeList(
        {
            multi: false
        }
    ).on('treelistselect', function (event, item) {
        if (item.flowid) {
            $('#node-input-flowhubpull-flowid').val(item.flowid);

            ensureYourConfigNodeExists();
            globalYourConfigNode.flowid = item.flowid;

            obtainFhbToken(globalYourConfigNode, (token) => {
                if (token !== "" && token) {
                    token = "?t=" + tokenToUrl(token)
                }
                $('#flowhubpull-sb-view-flow-link').attr(
                    'href', 'https://flowhub.org/f/' + item.flowid + token
                ).show();
            })

            $('#node-input-flowhubpull-sb-import-flow-but').prop('disabled', false)
        }
    }).on('treelistconfirm', function (event, item) {
        if (item.flowid) {
            $('#node-input-flowhubpull-flowid').val(item.flowid);

            ensureYourConfigNodeExists();
            globalYourConfigNode.flowid = item.flowid;

            obtainFhbToken(globalYourConfigNode, (token) => {
                if (token !== "" && token) {
                    token = "?t=" + tokenToUrl(token)
                }

                $('#flowhubpull-sb-view-flow-link').attr(
                    'href', 'https://flowhub.org/f/' + item.flowid + token
                ).show();
            })

            $('#node-input-flowhubpull-sb-import-flow-but').prop('disabled', false)
            $("#node-input-flowhubpull-sb-import-flow-but").trigger('click');
        }
    });

    var search = $("#node-input-flowhubpull-sb-target-filter").searchBox({
        style: "compact",
        delay: 300,
        change: function () {
            var val = $(this).val().trim().toLowerCase();
            if (val === "") {
                dirList.treeList("filter", null);
                search.searchBox("count", "");
            } else {
                var count = dirList.treeList("filter", function (item) {
                    return item.label.toLowerCase().indexOf(val) > -1 || item.sublabel.toLowerCase().indexOf(val) > -1
                });
                search.searchBox("count", count + " / " + dirList.treeList("data").length);
            }
        }
    });

    $('#node-input-flowhubpull-sb-refresh-list-but').on('click', function (e) {
        e.preventDefault();
        $('#func-sidebar-flowhub-pull-spinner').show()

        setTimeout(() => {
            ensureYourConfigNodeExists();

            obtainFhbToken(globalYourConfigNode, (token) => {
                flowHubPullUpdateFlows((lst) => {
                    dirList.treeList('empty')
                    $('#flowhubpull-sb-view-flow-link').hide()
                    setTimeout(() => {
                        dirList.treeList('data', lst)
                    }, 300)
                }, token);
            })
        }, 300);
    });

    $('#node-input-flowhubpush-new-token-field').on('change', e => {
        if ($('#node-input-flowhubpush-new-token-field').val()) {
            checkToken($('#node-input-flowhubpush-new-token-field').val())
        } else {
            $('#flowhubpush-check-token-result-content').fadeOut(300)
        }
    })

    $('#node-input-flowhubpush-clear-token-btn').on('click', e => {
        if (e) { e.preventDefault() }
        $('#node-input-flowhubpush-new-token-name-field').val("")
        $('#node-input-flowhubpush-new-token-field').val("")
        $('#node-input-flowhubpush-new-token-field').trigger('change')
    })

    $('#node-input-flowhubpush-add-token-btn').on('click', e => {
        if (e) { e.preventDefault() }

        ensureYourConfigNodeExists();

        if (!globalYourConfigNode.tokens) {
            globalYourConfigNode.tokens = []
        }

        let tk = {
            name: $('#node-input-flowhubpush-new-token-name-field').val() || "",
            type: "cred",
            val: $('#node-input-flowhubpush-new-token-field').val() || "empty"
        }

        if (tk.name == "") {
            return RED.notify(RED._("notification.warning", {
                message: "Token name empty, doing nothing."
            }), "warning");
        }

        globalYourConfigNode.tokens.push(tk)
        if (!globalYourConfigNode.credentials) {
            globalYourConfigNode.credentials = { tokens: globalYourConfigNode.tokens }
        } else {
            globalYourConfigNode.credentials.tokens = globalYourConfigNode.tokens
        }
        RED.nodes.dirty(true);

        addTokenToList("", tk)
        addEventHandlersToTokenList(globalYourConfigNode)
    })

    $("#node-input-flowhubpull-sb-check-duplicates-flow-but").on("click", function (e) {
        if (e) { e.preventDefault(); }

        if ($('#node-input-flowhubpull-flowid').val().trim() == "" ||
            !$('#node-input-flowhubpull-flowid').val()) {
            return RED.notify(RED._("notification.warning", {
                message: "FlowID not provided, doing nothing."
            }), "warning");
        }

        RED.notify("Checking for flow duplication.", "success");

        ensureYourConfigNodeExists();

        obtainFhbToken(globalYourConfigNode, (token) => {
            checkForDuplication(token)
        })
    })

    $("#node-input-flowhubpull-sb-import-flow-but").on("click", function (e) {
        if (e) { e.preventDefault(); }

        if ($('#node-input-flowhubpull-flowid').val().trim() == "" ||
            !$('#node-input-flowhubpull-flowid').val()) {
            return RED.notify(RED._("notification.warning", {
                message: "FlowID not provided, doing nothing."
            }), "warning");
        }

        ensureYourConfigNodeExists();
        var notab = globalYourConfigNode.notab;

        obtainFhbToken(globalYourConfigNode, (token) => {
            doFlowImportForSidebar(notab, token)
        })
    });

    $('#node-input-flowhubpush-pushnewflows').on('change', () => {
        ensureYourConfigNodeExists();
        globalYourConfigNode.pushnewflows = $('#node-input-flowhubpush-pushnewflows').is(":checked")
        RED.nodes.dirty(true);
    })

    $('#node-input-flowhubpush-forcepush').on('change', () => {
        ensureYourConfigNodeExists();
        globalYourConfigNode.forcepush = $('#node-input-flowhubpush-forcepush').is(":checked")
        RED.nodes.dirty(true);
    })

    $('#node-input-flowhubpull-flowrevision').on('change', () => {
        ensureYourConfigNodeExists();
        let val = $('#node-input-flowhubpull-flowrevision').val();

        if (globalYourConfigNode.flowrevision != val) {
            globalYourConfigNode.flowrevision = val
            RED.nodes.dirty(true);
        }
    })

    RED.panels.create({
        id: 'func-flowhub-tab-compare',
        resize: (topSze, botSze) => {
            $('#node-input-flowhubdiff-target-container-div').parent().css('height', `calc( ${topSze}px - 32vh)`)
            $('#node-input-flowhubdiff-diffcontainer').parent().parent().css('height', botSze)
        }
    })

    // The above is done before the nodes are initialised, the following is done after
    // the sidebar has been initialised and the config nodes are available.
    let fillSidebarForms = () => {
        RED.events.off('flows:loaded', fillSidebarForms);

        let cfgNode = ensureYourConfigNodeExists();

        /* for older config nodes, add new parameters */
        if (cfgNode.forcepush == undefined) {
            cfgNode.forcepush = false
            RED.nodes.dirty(true)
        }
        if (cfgNode.pushnewflows == undefined) {
            cfgNode.pushnewflows = false
            RED.nodes.dirty(true)
        }

        if (cfgNode.flowrevisions == undefined) {
            cfgNode.flowrevisions = {}
            RED.nodes.dirty(true)
        }

        $.ajax({
            url: "FlowHubTokens",
            type: "POST",
            contentType: "application/json; charset=utf-8",

            data: JSON.stringify({
                cfgnode: globalYourConfigNode,
            }),

            success: function (resp) {
                if (!globalYourConfigNode.credentials) {
                    globalYourConfigNode.credentials = { apiToken: resp.apiToken, tokens: resp.tokens }
                }

                globalYourConfigNode.tokens = resp.tokens
                globalYourConfigNode.apiToken = resp.apiToken
                $("#node-input-flowhubpush-apiToken").typedInput('value', resp.apiToken);

                resp.tokens.forEach(tk => {
                    addTokenToList((tk.val == resp.apiToken ? "flowhub-token-selected" : ""), tk)
                })
                addEventHandlersToTokenList(globalYourConfigNode)
            },

            error: function (jqXHR, textStatus, errorThrown) { }
        });

        $('#node-input-flowhubpush-pushnewflows').prop('checked', globalYourConfigNode.pushnewflows)
        $('#node-input-flowhubpush-comment').val(globalYourConfigNode.pushcomment)
        $('#node-input-flowhubpush-forcepush').prop('checked', globalYourConfigNode.forcepush)

        if (globalYourConfigNode.notab) {
            $('#flowhubpull-dialog-import-opt-sb-current').addClass('selected')
            $('#flowhubpull-dialog-import-opt-sb-new').removeClass('selected')
        } else {
            $('#flowhubpull-dialog-import-opt-sb-new').addClass('selected')
            $('#flowhubpull-dialog-import-opt-sb-current').removeClass('selected')
        }

        if (globalYourConfigNode.flowrevision != "") {
            $('#node-input-flowhubpull-flowrevision').val(globalYourConfigNode.flowrevision)
        }

        if (globalYourConfigNode.flowid) {
            $('#node-input-flowhubpull-flowid').val(globalYourConfigNode.flowid);

            obtainFhbToken(globalYourConfigNode, (token) => {
                if (token !== "" && token) {
                    token = "?t=" + tokenToUrl(token)
                }
                $('#flowhubpull-sb-view-flow-link').attr(
                    'href', 'https://flowhub.org/f/' + globalYourConfigNode.flowid + token
                ).show();
            })
        } else {
            $('#node-input-flowhubpull-sb-import-flow-but').prop('disabled', true)
        }

        obtainFhbToken(globalYourConfigNode, (token) => {
            $('#func-sidebar-flowhub-pull-spinner').show()
            flowHubPullUpdateFlows((lst) => { dirList.treeList('data', lst) }, token);
        })
    }
    RED.events.on('flows:loaded', fillSidebarForms);
};

RED.plugins.registerPlugin("flowhub-major-sidebar-plugin", { onadd: initSidebarComparePullPush });

})();
</script>

<style>
.flowhub-diff-viewer{overflow:scroll;max-width:90vw;padding-bottom:5px}.dv-differ{background-color:rgba(244,170,23,.476)!important}.dv-removed{background-color:#cbcb26!important}.dv-added{background-color:#56de78!important}.dv-error{background-color:#ff4b09!important}.dv-pre-elem,.dv-pre-elem-no-change{page-break-inside:avoid;font-family:monospace;max-width:100%;overflow:auto;display:block;word-wrap:break-word;margin:0!important}.prop-code-value{white-space:pre-wrap!important;white-space:-moz-pre-wrap!important;white-space:-pre-wrap!important;white-space:-o-pre-wrap!important;word-wrap:break-word!important}.flowhublogo{background-image:url("icons/@gregoriusrippenstein/node-red-contrib-flowhub/flowhublogo.svg");background-size:cover;background-repeat:no-repeat;background-position-x:center;background-position-y:top}th.revertable:after{content:" ⟲";color:"green"}.red-ui-tray-content #dialog-form{white-space:nowrap}.full-row .red-ui-typedInput-container{min-width:70%}.col input{min-width:100%}.sml-lbl{height:66px}.sml-lbl label{font-size:smaller;margin-bottom:0;display:block!important}.reg-lbl label{white-space:nowrap;margin-top:5px;height:0}.red-ui-editor .form-row label.full-lbl{white-space:normal;width:100%}.col{float:left;margin-right:5px;min-height:36px}.col .red-ui-typedInput-container{width:100%!important}.col-50{width:50%}.col-33{width:33%}.col-66{width:66%}.col-25{width:25%}.col-75{width:75%}.col-100 .red-ui-typedInput-container{width:70%!important}.col-100.no-label .red-ui-typedInput-container{width:100%!important}.txtarea{padding-bottom:26px}.txtarea label{vertical-align:top;margin-top:3px}.txtarea textarea{width:70%;margin-bottom:-28px!important}.btn-regular{margin-bottom:14px!important}.flowhub-token-selected{background-color:rgba(12,208,208,.709)}
</style>

<!-- The html for the right sidebar plugin screen -->
<script type="text/x-red" data-template-name="FlowHubDiff">
  <div style="position: relative; height: 100%; overflow-y: auto;">
    <div class="form-row func-flowhub-tabs-row">
   <ul style="margin-bottom: 20px;" id="func-flowhub-tabs"></ul>
</div>

<!--func-flowhub-tabs-row-->
<div id="func-flowhub-tabs-content">


   <!--func-flowhub-tab-pull#-->
   <div id="func-flowhub-tab-pull" style="display:none" style="min-height: calc(100% - 95px);">
      <div class="form-row">
         <div class="col-100">

            <div class="form-row" style="margin-left: 10px; margin-top: 30px;">
               <button id="node-input-flowhubpull-sb-refresh-list-but"
                        class="red-ui-button"><i class="fa fa-refresh"></i> Refresh Flow List</button>

               <img class='hide' id="func-sidebar-flowhub-pull-spinner" src='red/images/spin.svg'/>

               <a id="flowhubpull-sb-view-flow-link" style="color: blue; display: none; margin-left: 40px;"
                  target="_blank" href="">Webiliser <i class="fa fa-external-link"></i></a>
            </div>

            <div class="form-row node-input-target-row node-input-target-list-row"
               style="position: relative; min-height: 100px; height: 500px; margin-left: 10px; margin-right: 15px; margin-top: 5px;">
               <div style="position: absolute; top: -30px; right: 0px;">
                  <input type="text" id="node-input-flowhubpull-sb-target-filter" placeholder="Name or Flow Id">
               </div>

               <div id="node-input-flowhubpull-sb-target-container-div" style="min-height: 500px;"></div>
            </div>

            <div class="form-row" style="margin-left: 10px; margin-top: 30px;">
               <label>
                 <span>Flow ID:</span>
               </label>
               <input type="text"
               disabled="disabled"
               id="node-input-flowhubpull-flowid"
               style="display:inline-block; width:40%; vertical-align:baseline;"
               placeholder="Flow ID to Import">
            </div>

            <div class="form-row" style="margin-left: 10px;">
               <label>
                <span>Import to:</span>
              </label>

               <span>
                 <a id="flowhubpull-dialog-import-opt-sb-current" class="red-ui-button toggle" href="#" data-i18n="clipboard.export.current">current flow tab</a>
                 <a id="flowhubpull-dialog-import-opt-sb-new" class="red-ui-button toggle" href="#" data-i18n="clipboard.import.newFlow">new flow tab</a>
              </span>
            </div>

            <div class="form-row" style="margin-left: 10px;">
               <label for="node-input-flowhubpull-flowrevision">Revision:</label>
               <input type="text" id="node-input-flowhubpull-flowrevision" placeholder="Revision"/>
            </div>

            <div class="form-row" style="margin-left: 10px;">
               <button id="node-input-flowhubpull-sb-import-flow-but"
                                             class="red-ui-button">Import Flow</button>
               <button id="node-input-flowhubpull-sb-check-duplicates-flow-but"
                                             class="red-ui-button">Duplication?</button>
            </div>


            <div class="form-row node-input-target-row node-input-target-list-row"
               style="position: relative; min-height: 100px; height: 300px; margin-left: 10px; margin-right: 15px;">
               <div id="node-input-flowhubpull-sb-duplication-container-div" style="min-height: 300px;"></div>
            </div>
         </div>
      </div>
   </div>

   <!--func-flowhub-tab-compare#-->
   <div id="func-flowhub-tab-compare" style="display:none; height: 87vh;">
      <div>
         <div style="margin-left: 10px; height: 5vh;">
            <button id="node-input-flowhubdiff-compare-btn"
               class="red-ui-button"><i class="fa fa-balance-scale"></i> Compare</button>
            <button id="node-input-flowhubdiff-next-change-btn"
               class="red-ui-button hide"><i class="fa fa-arrow-right"></i> Next Change</button>

            <a id="flowhubdiff-view-flow-link" style="color: blue; display: none; margin-left: 40px;"
               target="_blank" href="">Webiliser <i class="fa fa-external-link"></i></a>
         </div>

         <div style="margin-left: 10px; margin-right: 15px; height: 15vh; min-height: 5vh;">
            <div style="margin-right: 15px; width: 35%; float: right; margin-top: -38px;">
               <input type="text" id="node-input-flowhubdiff-target-filter" style="display: none;">
            </div>

            <div id="node-input-flowhubdiff-target-container-div"></div>
         </div>

         <div style="height: 25vh; margin-left: 10px; margin-right: 15px;">
            <div id="node-input-flowhubdiff-svgcontainer"></div>
         </div>
      </div>

      <div>
         <div style="margin-left: 10px; overflow: hidden; display: flex; flex-direction: column; width: 97%; height: 47vh; margin-top: 10px;">
            <div style="flex: 1 1 auto; overflow-y: auto;">
               <div id="node-input-flowhubdiff-diffcontainer" class="flowhub-diff-viewer"></div>
            </div>
         </div>
      </div>
   </div>

   <!--func-flowhub-tab-push#-->
   <div id="func-flowhub-tab-push" style="display:none">
      <div class="form-row">
         <div class="col-100">

            <div class="form-row" style="margin-top: 30px; margin-left: 10px;">
               <input type="checkbox" id="node-input-flowhubpush-pushnewflows"
                  style="display:inline-block; width:15px; vertical-align:baseline;">
               <label for="node-input-flowhubpush-pushnewflows" style="width: 230px;">
                  <span>Create new flows when pushing?</span>
               </label>
            </div>

            <div class="form-row" style="margin-left: 10px;">
               <input type="checkbox" id="node-input-flowhubpush-forcepush"
                              style="display:inline-block; width:15px; vertical-align:baseline;">
               <label for="node-input-flowhubpush-forcepush" style="width: 230px;">
                  <span>Overwrite more recent changes?</span>
               </label>
            </div>

            <div class="form-row" style="margin-left: 10px;">
               <button id="node-input-flowhubpush-push-btn"
                                    class="red-ui-button"><i class="fa fa-share-square-o"></i> Push to GitHub</button>
               <a id="flowhubpush-view-flow-link" style="color: blue; display: none; margin-left: 80px;" target="_blank"
                  href="">Webiliser <i class="fa fa-external-link"></i></a>
            </div>

            <div class="form-row" style="margin-left: 10px;">
               <hr />
            </div>

            <div class="form-row" style="margin-left: 10px;">
               <label for="node-input-flowhubpush-comment">
                  <i class="fa fa-tag"></i> Comment
               </label>
               <textarea id="node-input-flowhubpush-comment" placeholder="comment"></textarea>
            </div>

            <div class="form-row" style="margin-left: 10px;">
               Comment will be added to each push. Leave blank for a push to be commentless.
            </div>

            <div class="form-row" style="margin-left: 10px;">
               <hr />
            </div>

            <div class="form-row" style="margin-left: 10px; margin-top: 20px; display: none;">
               <label for="node-input-flowhubpush-apiToken">
                                    <i class="fa fa-key"></i>
                                    Current Token
                                 </label>
               <input type="text" id="node-input-flowhubpush-apiToken">
               <input type="hidden" id="node-input-flowhubpush-apiTokenType">
            </div>

            <div class="form-row" style="margin-left: 10px;">
               Check out the instructions for generating your own <a style="color: blue;" target="_blank"
                  href="https://flowhub.org/integration">FlowHub.org token <i class='fa fa-external-link'></i></a>. You
               will then be
               able to commit your Node-RED flows directly to your own GitHub repository - public or private. You will
               also be able
               to view those flows at FlowHub.org using the "View Flow" links.
            </div>

            <div class="form-row" style="margin-top: 30px; margin-left: 10px;">
               <input id="node-input-flowhubpush-new-token-field" placeholder="New token fhb_XXXX">
            </div>

            <div class="form-row" style="margin-left: 10px; display:none;" id="flowhubpush-check-token-result-content">
               Repo: <a target=_blank href="#" class="repo"></a> @ <span class="branch"></span><br>
               Permission: <span class="permission"></span><br>
               User: <a target=_blank href="#" class="user"></a>, <span class="email"></span>
            </div>

            <div class="form-row" style="margin-left: 10px;">
               <input id="node-input-flowhubpush-new-token-name-field" placeholder="Token Name" style="width: 250px;">
               <button id="node-input-flowhubpush-add-token-btn"
                                    class="red-ui-button"><i class="fa fa-plus"></i> Add</button>
               <button id="node-input-flowhubpush-clear-token-btn"
                                    class="red-ui-button"><i class="fa fa-times"></i> Clear</button>
            </div>

            <div class="form-row" style="margin-left: 10px;">
               <table border=0 id="node-input-flowhubpush-token-list" style="margin-left: 10px;">
               </table>
            </div>

         </div>
      </div>
      <!--form-row-->
   </div>
   <!--func-flowhub-tab-tab#-->
</div>
<!--func-flowhub-tabs-content-->
  </div>
</script>