mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	This commit allows an example from an npm package that has an org scoped name (which includes an @ character) to be abbreviated properly in the import menu - i.e., showing 'myexample' for a package name of '@myorg/node-red-contrib-myexample' rather than '@myorg/node red-contrib-myexample'.
		
			
				
	
	
		
			494 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			494 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * Copyright JS Foundation and other contributors, http://js.foundation
 | 
						|
 *
 | 
						|
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
 * you may not use this file except in compliance with the License.
 | 
						|
 * You may obtain a copy of the License at
 | 
						|
 *
 | 
						|
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 *
 | 
						|
 * Unless required by applicable law or agreed to in writing, software
 | 
						|
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
 * See the License for the specific language governing permissions and
 | 
						|
 * limitations under the License.
 | 
						|
 **/
 | 
						|
RED.library = (function() {
 | 
						|
 | 
						|
 | 
						|
    var exportToLibraryDialog;
 | 
						|
 | 
						|
    function loadFlowLibrary() {
 | 
						|
        $.getJSON("library/flows",function(data) {
 | 
						|
            //console.log(data);
 | 
						|
 | 
						|
            var buildMenu = function(data,root) {
 | 
						|
                var i;
 | 
						|
                var li;
 | 
						|
                var a;
 | 
						|
                var ul = document.createElement("ul");
 | 
						|
                if (root === "") {
 | 
						|
                    ul.id = "menu-item-import-library-submenu";
 | 
						|
                }
 | 
						|
                ul.className = "dropdown-menu";
 | 
						|
                if (data.d) {
 | 
						|
                    for (i in data.d) {
 | 
						|
                        if (data.d.hasOwnProperty(i)) {
 | 
						|
                            li = document.createElement("li");
 | 
						|
                            li.className = "dropdown-submenu pull-left";
 | 
						|
                            a = document.createElement("a");
 | 
						|
                            a.href="#";
 | 
						|
                            var label = i.replace(/^@.*\//,"").replace(/^node-red-contrib-/,"").replace(/^node-red-node-/,"").replace(/-/," ").replace(/_/," ");
 | 
						|
                            a.innerHTML = label;
 | 
						|
                            li.appendChild(a);
 | 
						|
                            li.appendChild(buildMenu(data.d[i],root+(root!==""?"/":"")+i));
 | 
						|
                            ul.appendChild(li);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                if (data.f) {
 | 
						|
                    for (i in data.f) {
 | 
						|
                        if (data.f.hasOwnProperty(i)) {
 | 
						|
                            li = document.createElement("li");
 | 
						|
                            a = document.createElement("a");
 | 
						|
                            a.href="#";
 | 
						|
                            a.innerHTML = data.f[i];
 | 
						|
                            a.flowName = root+(root!==""?"/":"")+data.f[i];
 | 
						|
                            a.onclick = function() {
 | 
						|
                                $.get('library/flows/'+this.flowName, function(data) {
 | 
						|
                                    RED.view.importNodes(data);
 | 
						|
                                });
 | 
						|
                            };
 | 
						|
                            li.appendChild(a);
 | 
						|
                            ul.appendChild(li);
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
                return ul;
 | 
						|
            };
 | 
						|
            var examples;
 | 
						|
            if (data.d && data.d._examples_) {
 | 
						|
                examples = data.d._examples_;
 | 
						|
                delete data.d._examples_;
 | 
						|
            }
 | 
						|
            var menu = buildMenu(data,"");
 | 
						|
            $("#menu-item-import-examples").remove();
 | 
						|
            if (examples) {
 | 
						|
                RED.menu.addItem("menu-item-import",{id:"menu-item-import-examples",label:RED._("menu.label.examples"),options:[]})
 | 
						|
                $("#menu-item-import-examples-submenu").replaceWith(buildMenu(examples,"_examples_"));
 | 
						|
            }
 | 
						|
            //TODO: need an api in RED.menu for this
 | 
						|
            $("#menu-item-import-library-submenu").replaceWith(menu);
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    function createUI(options) {
 | 
						|
        var libraryData = {};
 | 
						|
        var selectedLibraryItem = null;
 | 
						|
        var libraryEditor = null;
 | 
						|
 | 
						|
        // Orion editor has set/getText
 | 
						|
        // ACE editor has set/getValue
 | 
						|
        // normalise to set/getValue
 | 
						|
        if (options.editor.setText) {
 | 
						|
            // Orion doesn't like having pos passed in, so proxy the call to drop it
 | 
						|
            options.editor.setValue = function(text,pos) {
 | 
						|
                options.editor.setText.call(options.editor,text);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        if (options.editor.getText) {
 | 
						|
            options.editor.getValue = options.editor.getText;
 | 
						|
        }
 | 
						|
 | 
						|
        function buildFileListItem(item) {
 | 
						|
            var li = document.createElement("li");
 | 
						|
            li.onmouseover = function(e) { $(this).addClass("list-hover"); };
 | 
						|
            li.onmouseout = function(e) { $(this).removeClass("list-hover"); };
 | 
						|
            return li;
 | 
						|
        }
 | 
						|
 | 
						|
        function buildFileList(root,data) {
 | 
						|
            var ul = document.createElement("ul");
 | 
						|
            var li;
 | 
						|
            for (var i=0; i<data.length; i++) {
 | 
						|
                var v = data[i];
 | 
						|
                if (typeof v === "string") {
 | 
						|
                    // directory
 | 
						|
                    li = buildFileListItem(v);
 | 
						|
                    li.onclick = (function () {
 | 
						|
                        var dirName = v;
 | 
						|
                        return function(e) {
 | 
						|
                            var bcli = $('<li class="active"><span class="divider">/</span> <a href="#">'+dirName+'</a></li>');
 | 
						|
                            $("a",bcli).click(function(e) {
 | 
						|
                                $(this).parent().nextAll().remove();
 | 
						|
                                $.getJSON("library/"+options.url+root+dirName,function(data) {
 | 
						|
                                    $("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
 | 
						|
                                });
 | 
						|
                                e.stopPropagation();
 | 
						|
                            });
 | 
						|
                            var bc = $("#node-dialog-library-breadcrumbs");
 | 
						|
                            $(".active",bc).removeClass("active");
 | 
						|
                            bc.append(bcli);
 | 
						|
                            $.getJSON("library/"+options.url+root+dirName,function(data) {
 | 
						|
                                $("#node-select-library").children().first().replaceWith(buildFileList(root+dirName+"/",data));
 | 
						|
                            });
 | 
						|
                        }
 | 
						|
                    })();
 | 
						|
                    li.innerHTML = '<i class="fa fa-folder"></i> '+v+"</i>";
 | 
						|
                    ul.appendChild(li);
 | 
						|
                } else {
 | 
						|
                    // file
 | 
						|
                    li = buildFileListItem(v);
 | 
						|
                    li.innerHTML = v.name;
 | 
						|
                    li.onclick = (function() {
 | 
						|
                        var item = v;
 | 
						|
                        return function(e) {
 | 
						|
                            $(".list-selected",ul).removeClass("list-selected");
 | 
						|
                            $(this).addClass("list-selected");
 | 
						|
                            $.get("library/"+options.url+root+item.fn, function(data) {
 | 
						|
                                selectedLibraryItem = item;
 | 
						|
                                libraryEditor.setValue(data,-1);
 | 
						|
                            });
 | 
						|
                        }
 | 
						|
                    })();
 | 
						|
                    ul.appendChild(li);
 | 
						|
                }
 | 
						|
            }
 | 
						|
            return ul;
 | 
						|
        }
 | 
						|
 | 
						|
        $('#node-input-name').css("width","66%").after(
 | 
						|
            '<div class="btn-group" style="margin-left: 5px;">'+
 | 
						|
            '<a id="node-input-'+options.type+'-lookup" class="editor-button" data-toggle="dropdown"><i class="fa fa-book"></i> <i class="fa fa-caret-down"></i></a>'+
 | 
						|
            '<ul class="dropdown-menu pull-right" role="menu">'+
 | 
						|
            '<li><a id="node-input-'+options.type+'-menu-open-library" tabindex="-1" href="#">'+RED._("library.openLibrary")+'</a></li>'+
 | 
						|
            '<li><a id="node-input-'+options.type+'-menu-save-library" tabindex="-1" href="#">'+RED._("library.saveToLibrary")+'</a></li>'+
 | 
						|
            '</ul></div>'
 | 
						|
        );
 | 
						|
 | 
						|
        $('#node-input-'+options.type+'-menu-open-library').click(function(e) {
 | 
						|
            $("#node-select-library").children().remove();
 | 
						|
            var bc = $("#node-dialog-library-breadcrumbs");
 | 
						|
            bc.children().first().nextAll().remove();
 | 
						|
            libraryEditor.setValue('',-1);
 | 
						|
 | 
						|
            $.getJSON("library/"+options.url,function(data) {
 | 
						|
                $("#node-select-library").append(buildFileList("/",data));
 | 
						|
                $("#node-dialog-library-breadcrumbs a").click(function(e) {
 | 
						|
                    $(this).parent().nextAll().remove();
 | 
						|
                    $("#node-select-library").children().first().replaceWith(buildFileList("/",data));
 | 
						|
                    e.stopPropagation();
 | 
						|
                });
 | 
						|
                $( "#node-dialog-library-lookup" ).dialog( "open" );
 | 
						|
            });
 | 
						|
 | 
						|
            e.preventDefault();
 | 
						|
        });
 | 
						|
 | 
						|
        $('#node-input-'+options.type+'-menu-save-library').click(function(e) {
 | 
						|
            //var found = false;
 | 
						|
            var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,"");
 | 
						|
 | 
						|
            //var buildPathList = function(data,root) {
 | 
						|
            //    var paths = [];
 | 
						|
            //    if (data.d) {
 | 
						|
            //        for (var i in data.d) {
 | 
						|
            //            var dn = root+(root==""?"":"/")+i;
 | 
						|
            //            var d = {
 | 
						|
            //                label:dn,
 | 
						|
            //                files:[]
 | 
						|
            //            };
 | 
						|
            //            for (var f in data.d[i].f) {
 | 
						|
            //                d.files.push(data.d[i].f[f].fn.split("/").slice(-1)[0]);
 | 
						|
            //            }
 | 
						|
            //            paths.push(d);
 | 
						|
            //            paths = paths.concat(buildPathList(data.d[i],root+(root==""?"":"/")+i));
 | 
						|
            //        }
 | 
						|
            //    }
 | 
						|
            //    return paths;
 | 
						|
            //};
 | 
						|
            $("#node-dialog-library-save-folder").attr("value","");
 | 
						|
 | 
						|
            var filename = name.replace(/[^\w-]/g,"-");
 | 
						|
            if (filename === "") {
 | 
						|
                filename = "unnamed-"+options.type;
 | 
						|
            }
 | 
						|
            $("#node-dialog-library-save-filename").attr("value",filename+".js");
 | 
						|
 | 
						|
            //var paths = buildPathList(libraryData,"");
 | 
						|
            //$("#node-dialog-library-save-folder").autocomplete({
 | 
						|
            //        minLength: 0,
 | 
						|
            //        source: paths,
 | 
						|
            //        select: function( event, ui ) {
 | 
						|
            //            $("#node-dialog-library-save-filename").autocomplete({
 | 
						|
            //                    minLength: 0,
 | 
						|
            //                    source: ui.item.files
 | 
						|
            //            });
 | 
						|
            //        }
 | 
						|
            //});
 | 
						|
 | 
						|
            $( "#node-dialog-library-save" ).dialog( "open" );
 | 
						|
            e.preventDefault();
 | 
						|
        });
 | 
						|
 | 
						|
        libraryEditor = ace.edit('node-select-library-text');
 | 
						|
        libraryEditor.setTheme("ace/theme/tomorrow");
 | 
						|
        if (options.mode) {
 | 
						|
            libraryEditor.getSession().setMode(options.mode);
 | 
						|
        }
 | 
						|
        libraryEditor.setOptions({
 | 
						|
            readOnly: true,
 | 
						|
            highlightActiveLine: false,
 | 
						|
            highlightGutterLine: false
 | 
						|
        });
 | 
						|
        libraryEditor.renderer.$cursorLayer.element.style.opacity=0;
 | 
						|
        libraryEditor.$blockScrolling = Infinity;
 | 
						|
 | 
						|
        $( "#node-dialog-library-lookup" ).dialog({
 | 
						|
            title: RED._("library.typeLibrary", {type:options.type}),
 | 
						|
            modal: true,
 | 
						|
            autoOpen: false,
 | 
						|
            width: 800,
 | 
						|
            height: 450,
 | 
						|
            buttons: [
 | 
						|
                {
 | 
						|
                    text: RED._("common.label.cancel"),
 | 
						|
                    click: function() {
 | 
						|
                        $( this ).dialog( "close" );
 | 
						|
                    }
 | 
						|
                },
 | 
						|
                {
 | 
						|
                    text: RED._("common.label.load"),
 | 
						|
                    class: "primary",
 | 
						|
                    click: function() {
 | 
						|
                        if (selectedLibraryItem) {
 | 
						|
                            for (var i=0; i<options.fields.length; i++) {
 | 
						|
                                var field = options.fields[i];
 | 
						|
                                $("#node-input-"+field).val(selectedLibraryItem[field]);
 | 
						|
                            }
 | 
						|
                            options.editor.setValue(libraryEditor.getValue(),-1);
 | 
						|
                        }
 | 
						|
                        $( this ).dialog( "close" );
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            ],
 | 
						|
            open: function(e) {
 | 
						|
                var form = $("form",this);
 | 
						|
                form.height(form.parent().height()-30);
 | 
						|
                $("#node-select-library-text").height("100%");
 | 
						|
                $(".form-row:last-child",form).children().height(form.height()-60);
 | 
						|
            },
 | 
						|
            resize: function(e) {
 | 
						|
                var form = $("form",this);
 | 
						|
                form.height(form.parent().height()-30);
 | 
						|
                $(".form-row:last-child",form).children().height(form.height()-60);
 | 
						|
            }
 | 
						|
        });
 | 
						|
 | 
						|
        function saveToLibrary(overwrite) {
 | 
						|
            var name = $("#node-input-name").val().replace(/(^\s*)|(\s*$)/g,"");
 | 
						|
            if (name === "") {
 | 
						|
                name = RED._("library.unnamedType",{type:options.type});
 | 
						|
            }
 | 
						|
            var filename = $("#node-dialog-library-save-filename").val().replace(/(^\s*)|(\s*$)/g,"");
 | 
						|
            var pathname = $("#node-dialog-library-save-folder").val().replace(/(^\s*)|(\s*$)/g,"");
 | 
						|
            if (filename === "" || !/.+\.js$/.test(filename)) {
 | 
						|
                RED.notify(RED._("library.invalidFilename"),"warning");
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            var fullpath = pathname+(pathname===""?"":"/")+filename;
 | 
						|
            if (!overwrite) {
 | 
						|
                //var pathnameParts = pathname.split("/");
 | 
						|
                //var exists = false;
 | 
						|
                //var ds = libraryData;
 | 
						|
                //for (var pnp in pathnameParts) {
 | 
						|
                //    if (ds.d && pathnameParts[pnp] in ds.d) {
 | 
						|
                //        ds = ds.d[pathnameParts[pnp]];
 | 
						|
                //    } else {
 | 
						|
                //        ds = null;
 | 
						|
                //        break;
 | 
						|
                //    }
 | 
						|
                //}
 | 
						|
                //if (ds && ds.f) {
 | 
						|
                //    for (var f in ds.f) {
 | 
						|
                //        if (ds.f[f].fn == fullpath) {
 | 
						|
                //            exists = true;
 | 
						|
                //            break;
 | 
						|
                //        }
 | 
						|
                //    }
 | 
						|
                //}
 | 
						|
                //if (exists) {
 | 
						|
                //    $("#node-dialog-library-save-content").html(RED._("library.dialogSaveOverwrite",{libraryType:options.type,libraryName:fullpath}));
 | 
						|
                //    $("#node-dialog-library-save-confirm").dialog( "open" );
 | 
						|
                //    return;
 | 
						|
                //}
 | 
						|
            }
 | 
						|
            var queryArgs = [];
 | 
						|
            var data = {};
 | 
						|
            for (var i=0; i<options.fields.length; i++) {
 | 
						|
                var field = options.fields[i];
 | 
						|
                if (field == "name") {
 | 
						|
                    data.name = name;
 | 
						|
                } else {
 | 
						|
                    data[field] = $("#node-input-"+field).val();
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            data.text = options.editor.getValue();
 | 
						|
            $.ajax({
 | 
						|
                url:"library/"+options.url+'/'+fullpath,
 | 
						|
                type: "POST",
 | 
						|
                data: JSON.stringify(data),
 | 
						|
                contentType: "application/json; charset=utf-8"
 | 
						|
            }).done(function(data,textStatus,xhr) {
 | 
						|
                RED.notify(RED._("library.savedType", {type:options.type}),"success");
 | 
						|
            }).fail(function(xhr,textStatus,err) {
 | 
						|
                if (xhr.status === 401) {
 | 
						|
                    RED.notify(RED._("library.saveFailed",{message:RED._("user.notAuthorized")}),"error");
 | 
						|
                } else {
 | 
						|
                    RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
 | 
						|
                }
 | 
						|
            });
 | 
						|
        }
 | 
						|
        $( "#node-dialog-library-save-confirm" ).dialog({
 | 
						|
            title: RED._("library.saveToLibrary"),
 | 
						|
            modal: true,
 | 
						|
            autoOpen: false,
 | 
						|
            width: 530,
 | 
						|
            height: 230,
 | 
						|
            buttons: [
 | 
						|
                {
 | 
						|
                    text: RED._("common.label.cancel"),
 | 
						|
                    click: function() {
 | 
						|
                        $( this ).dialog( "close" );
 | 
						|
                    }
 | 
						|
                },
 | 
						|
                {
 | 
						|
                    text: RED._("common.label.save"),
 | 
						|
                    class: "primary",
 | 
						|
                    click: function() {
 | 
						|
                        saveToLibrary(true);
 | 
						|
                        $( this ).dialog( "close" );
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            ]
 | 
						|
        });
 | 
						|
        $( "#node-dialog-library-save" ).dialog({
 | 
						|
            title: RED._("library.saveToLibrary"),
 | 
						|
            modal: true,
 | 
						|
            autoOpen: false,
 | 
						|
            width: 530,
 | 
						|
            height: 230,
 | 
						|
            buttons: [
 | 
						|
                {
 | 
						|
                    text: RED._("common.label.cancel"),
 | 
						|
                    click: function() {
 | 
						|
                        $( this ).dialog( "close" );
 | 
						|
                    }
 | 
						|
                },
 | 
						|
                {
 | 
						|
                    text: RED._("common.label.save"),
 | 
						|
                    class: "primary",
 | 
						|
                    click: function() {
 | 
						|
                        saveToLibrary(false);
 | 
						|
                        $( this ).dialog( "close" );
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            ]
 | 
						|
        });
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    function exportFlow() {
 | 
						|
        //TODO: don't rely on the main dialog
 | 
						|
        var nns = RED.nodes.createExportableNodeSet(RED.view.selection().nodes);
 | 
						|
        $("#node-input-library-filename").attr('nodes',JSON.stringify(nns));
 | 
						|
        exportToLibraryDialog.dialog( "open" );
 | 
						|
    }
 | 
						|
 | 
						|
    return {
 | 
						|
        init: function() {
 | 
						|
 | 
						|
            RED.actions.add("core:library-export",exportFlow);
 | 
						|
 | 
						|
            RED.events.on("view:selection-changed",function(selection) {
 | 
						|
                if (!selection.nodes) {
 | 
						|
                    RED.menu.setDisabled("menu-item-export",true);
 | 
						|
                    RED.menu.setDisabled("menu-item-export-clipboard",true);
 | 
						|
                    RED.menu.setDisabled("menu-item-export-library",true);
 | 
						|
                } else {
 | 
						|
                    RED.menu.setDisabled("menu-item-export",false);
 | 
						|
                    RED.menu.setDisabled("menu-item-export-clipboard",false);
 | 
						|
                    RED.menu.setDisabled("menu-item-export-library",false);
 | 
						|
                }
 | 
						|
            });
 | 
						|
 | 
						|
            if (RED.settings.theme("menu.menu-item-import-library") !== false) {
 | 
						|
                loadFlowLibrary();
 | 
						|
            }
 | 
						|
 | 
						|
            exportToLibraryDialog = $('<div id="library-dialog" class="hide"><form class="dialog-form form-horizontal"></form></div>')
 | 
						|
                .appendTo("body")
 | 
						|
                .dialog({
 | 
						|
                    modal: true,
 | 
						|
                    autoOpen: false,
 | 
						|
                    width: 500,
 | 
						|
                    resizable: false,
 | 
						|
                    title: RED._("library.exportToLibrary"),
 | 
						|
                    buttons: [
 | 
						|
                        {
 | 
						|
                            id: "library-dialog-cancel",
 | 
						|
                            text: RED._("common.label.cancel"),
 | 
						|
                            click: function() {
 | 
						|
                                $( this ).dialog( "close" );
 | 
						|
                            }
 | 
						|
                        },
 | 
						|
                        {
 | 
						|
                            id: "library-dialog-ok",
 | 
						|
                            class: "primary",
 | 
						|
                            text: RED._("common.label.export"),
 | 
						|
                            click: function() {
 | 
						|
                                //TODO: move this to RED.library
 | 
						|
                                var flowName = $("#node-input-library-filename").val();
 | 
						|
                                if (!/^\s*$/.test(flowName)) {
 | 
						|
                                    $.ajax({
 | 
						|
                                        url:'library/flows/'+flowName,
 | 
						|
                                        type: "POST",
 | 
						|
                                        data: $("#node-input-library-filename").attr('nodes'),
 | 
						|
                                        contentType: "application/json; charset=utf-8"
 | 
						|
                                    }).done(function() {
 | 
						|
                                        RED.library.loadFlowLibrary();
 | 
						|
                                        RED.notify(RED._("library.savedNodes"),"success");
 | 
						|
                                    }).fail(function(xhr,textStatus,err) {
 | 
						|
                                        if (xhr.status === 401) {
 | 
						|
                                            RED.notify(RED._("library.saveFailed",{message:RED._("user.notAuthorized")}),"error");
 | 
						|
                                        } else {
 | 
						|
                                            RED.notify(RED._("library.saveFailed",{message:xhr.responseText}),"error");
 | 
						|
                                        }
 | 
						|
                                    });
 | 
						|
                                }
 | 
						|
                                $( this ).dialog( "close" );
 | 
						|
                            }
 | 
						|
                        }
 | 
						|
                    ],
 | 
						|
                    open: function(e) {
 | 
						|
                        $(this).parent().find(".ui-dialog-titlebar-close").hide();
 | 
						|
                    },
 | 
						|
                    close: function(e) {
 | 
						|
                    }
 | 
						|
                });
 | 
						|
            exportToLibraryDialog.children(".dialog-form").append($(
 | 
						|
                '<div class="form-row">'+
 | 
						|
                '<label for="node-input-library-filename" data-i18n="[append]editor:library.filename"><i class="fa fa-file"></i> </label>'+
 | 
						|
                '<input type="text" id="node-input-library-filename" data-i18n="[placeholder]editor:library.fullFilenamePlaceholder">'+
 | 
						|
                '<input type="text" style="display: none;" />'+ // Second hidden input to prevent submit on Enter
 | 
						|
                '</div>'
 | 
						|
            ));
 | 
						|
        },
 | 
						|
        create: createUI,
 | 
						|
        loadFlowLibrary: loadFlowLibrary,
 | 
						|
 | 
						|
        export: exportFlow
 | 
						|
    }
 | 
						|
})();
 |