Merge branch 'dev' into diagnostics

This commit is contained in:
Stephen McLaughlin
2022-04-27 12:08:32 +01:00
committed by GitHub
210 changed files with 26309 additions and 21234 deletions

View File

@@ -474,7 +474,7 @@ var RED = (function() {
var parts = topic.split("/");
var node = RED.nodes.node(parts[1]);
if (node) {
if (msg.hasOwnProperty("text") && msg.text !== null && /^[a-zA-Z]/.test(msg.text)) {
if (msg.hasOwnProperty("text") && msg.text !== null && /^[@a-zA-Z]/.test(msg.text)) {
msg.text = node._(msg.text.toString(),{defaultValue:msg.text.toString()});
}
node.status = msg;

View File

@@ -9,12 +9,14 @@
*
* options:
*
* search : function(value, [done])
* A function that is passed the current contents of the input whenever
* it changes.
* The function must either return auto-complete options, or pass them
* to the optional 'done' parameter.
* If the function signature includes 'done', it must be used
* search: function(value, [done])
* A function that is passed the current contents of the input whenever
* it changes.
* The function must either return auto-complete options, or pass them
* to the optional 'done' parameter.
* If the function signature includes 'done', it must be used
* minLength: number
* If `minLength` is 0, pressing down arrow will show the list
*
* The auto-complete options should be an array of objects in the form:
* {
@@ -26,10 +28,11 @@
$.widget( "nodered.autoComplete", {
_create: function() {
var that = this;
const that = this;
this.completionMenuShown = false;
this.options.search = this.options.search || function() { return [] }
this.element.addClass("red-ui-autoComplete")
this.options.minLength = parseInteger(this.options.minLength, 1, 0);
this.options.search = this.options.search || function() { return [] };
this.element.addClass("red-ui-autoComplete");
this.element.on("keydown.red-ui-autoComplete", function(evt) {
if ((evt.keyCode === 13 || evt.keyCode === 9) && that.completionMenuShown) {
var opts = that.menu.options();
@@ -71,8 +74,8 @@
this.completionMenuShown = true;
},
_updateCompletions: function(val) {
var that = this;
if (val.trim() === "") {
const that = this;
if (val.trim().length < this.options.minLength) {
if (this.completionMenuShown) {
this.menu.hide();
}
@@ -96,7 +99,7 @@
}
}
if (this.options.search.length === 2) {
var requestId = 1+Math.floor(Math.random()*10000);
const requestId = 1+Math.floor(Math.random()*10000);
this.pendingRequest = requestId;
this.options.search(val,function(completions) { displayResults(completions,requestId);})
} else {
@@ -112,4 +115,12 @@
}
}
});
function parseInteger(input, def, min, max) {
if(input == null) { return (def || 0); }
min = min == null ? Number.NEGATIVE_INFINITY : min;
max = max == null ? Number.POSITIVE_INFINITY : max;
let n = parseInt(input);
if(isNaN(n) || n < min || n > max) { n = def || 0; }
return n;
}
})(jQuery);

View File

@@ -359,6 +359,7 @@ RED.popover = (function() {
setTimeout(closePopup,delay.hide);
}
});
if (trigger === 'hover') {
target.on('mouseenter',function(e) {
clearTimeout(timer);
@@ -470,6 +471,10 @@ RED.popover = (function() {
popover.setAction = function(newAction) {
action = newAction;
}
popover.delete = function() {
target.off("mouseenter");
target.off("mouseleave");
};
return popover;
},

View File

@@ -105,8 +105,8 @@
}
});
this.element.on("keydown",function(e) {
if (!menuShown && e.keyCode === 40) {
//DOWN
if (!menuShown && e.keyCode === 40 && $(this).val() === '') {
//DOWN (only show menu if search field is emty)
showMenu();
}
});

View File

@@ -670,7 +670,7 @@ RED.tabs = (function() {
}
var link = $("<a/>",{href:"#"+tab.id, class:"red-ui-tab-label"}).appendTo(li);
if (tab.icon) {
$('<img src="'+tab.icon+'" class="red-ui-tab-icon"/>').appendTo(link);
$('<i>',{class:"red-ui-tab-icon", style:"mask-image: url("+tab.icon+"); -webkit-mask-image: url("+tab.icon+");"}).appendTo(link);
} else if (tab.iconClass) {
$('<i>',{class:"red-ui-tab-icon "+tab.iconClass}).appendTo(link);
}

View File

@@ -55,34 +55,46 @@
}
var autoComplete = function(options) {
function getMatch(value, searchValue) {
const idx = value.toLowerCase().indexOf(searchValue.toLowerCase());
const len = idx > -1 ? searchValue.length : 0;
return {
index: idx,
found: idx > -1,
pre: value.substring(0,idx),
match: value.substring(idx,idx+len),
post: value.substring(idx+len),
}
}
function generateSpans(match) {
const els = [];
if(match.pre) { els.push($('<span/>').text(match.pre)); }
if(match.match) { els.push($('<span/>',{style:"font-weight: bold; color: var(--red-ui-text-color-link);"}).text(match.match)); }
if(match.post) { els.push($('<span/>').text(match.post)); }
return els;
}
return function(val) {
var matches = [];
options.forEach(opt => {
let v = opt.value;
var i = v.toLowerCase().indexOf(val.toLowerCase());
if (i > -1) {
var pre = v.substring(0,i);
var matchedVal = v.substring(i,i+val.length);
var post = v.substring(i+val.length)
var el = $('<div/>',{style:"white-space:nowrap; overflow: hidden; flex-grow:1"});
$('<span/>').text(pre).appendTo(el);
$('<span/>',{style:"font-weight: bold"}).text(matchedVal).appendTo(el);
$('<span/>').text(post).appendTo(el);
var element = $('<div>',{style: "display: flex"});
el.appendTo(element);
if (opt.source) {
$('<div>').css({
"font-size": "0.8em"
}).text(opt.source.join(",")).appendTo(element);
const optVal = opt.value;
const optSrc = (opt.source||[]).join(",");
const valMatch = getMatch(optVal, val);
const srcMatch = getMatch(optSrc, val);
if (valMatch.found || srcMatch.found) {
const element = $('<div>',{style: "display: flex"});
const valEl = $('<div/>',{style:"font-family: var(--red-ui-monospace-font); white-space:nowrap; overflow: hidden; flex-grow:1"});
valEl.append(generateSpans(valMatch));
valEl.appendTo(element);
if (optSrc) {
const optEl = $('<div>').css({ "font-size": "0.8em" });
optEl.append(generateSpans(srcMatch));
optEl.appendTo(element);
}
matches.push({
value: v,
label: element,
i:i
})
matches.push({
value: optVal,
label: element,
i: (valMatch.found ? valMatch.index : srcMatch.index)
});
}
})
matches.sort(function(A,B){return A.i-B.i})
@@ -93,6 +105,36 @@
// This is a hand-generated list of completions for the core nodes (based on the node help html).
var msgCompletions = [
{ value: "payload" },
{ value: "topic", source: ["mqtt","inject","rbe"] },
{ value: "action", source: ["mqtt"] },
{ value: "complete", source: ["join"] },
{ value: "contentType", source: ["mqtt"] },
{ value: "cookies", source: ["http request","http response"] },
{ value: "correlationData", source: ["mqtt"] },
{ value: "delay", source: ["delay","trigger"] },
{ value: "encoding", source: ["file"] },
{ value: "error", source: ["catch"] },
{ value: "error.message", source: ["catch"] },
{ value: "error.source", source: ["catch"] },
{ value: "error.source.id", source: ["catch"] },
{ value: "error.source.type", source: ["catch"] },
{ value: "error.source.name", source: ["catch"] },
{ value: "filename", source: ["file","file in"] },
{ value: "flush", source: ["delay"] },
{ value: "followRedirects", source: ["http request"] },
{ value: "headers", source: ["http response","http request"] },
{ value: "host", source: ["tcp request","http request"] },
{ value: "ip", source: ["udp out"] },
{ value: "kill", source: ["exec"] },
{ value: "messageExpiryInterval", source: ["mqtt"] },
{ value: "method", source: ["http request"] },
{ value: "options", source: ["xml"] },
{ value: "parts", source: ["split","join","batch","sort"] },
{ value: "pid", source: ["exec"] },
{ value: "port", source: ["tcp request"," udp out"] },
{ value: "qos", source: ["mqtt"] },
{ value: "rate", source: ["delay"] },
{ value: "rejectUnauthorized", source: ["http request"] },
{ value: "req", source: ["http in"]},
{ value: "req.body", source: ["http in"]},
{ value: "req.headers", source: ["http in"]},
@@ -100,38 +142,28 @@
{ value: "req.params", source: ["http in"]},
{ value: "req.cookies", source: ["http in"]},
{ value: "req.files", source: ["http in"]},
{ value: "complete", source: ["join"] },
{ value: "contentType", source: ["mqtt"] },
{ value: "cookies", source: ["http in","http request"] },
{ value: "correlationData", source: ["mqtt"] },
{ value: "delay", source: ["delay","trigger"] },
{ value: "encoding", source: ["file"] },
{ value: "error", source: ["catch"] },
{ value: "filename", source: ["file","file in"] },
{ value: "flush", source: ["delay"] },
{ value: "followRedirects", source: ["http request"] },
{ value: "headers", source: ["http in"," http request"] },
{ value: "kill", source: ["exec"] },
{ value: "messageExpiryInterval", source: ["mqtt"] },
{ value: "method", source: ["http-request"] },
{ value: "options", source: ["xml"] },
{ value: "parts", source: ["split","join"] },
{ value: "pid", source: ["exec"] },
{ value: "qos", source: ["mqtt"] },
{ value: "rate", source: ["delay"] },
{ value: "rejectUnauthorized", source: ["http request"] },
{ value: "requestTimeout", source: ["http request"] },
{ value: "reset", source: ["delay","trigger","join","rbe"] },
{ value: "responseCookies", source: ["http request"] },
{ value: "responseTopic", source: ["mqtt"] },
{ value: "responseURL", source: ["http request"] },
{ value: "restartTimeout", source: ["join"] },
{ value: "retain", source: ["mqtt"] },
{ value: "schema", source: ["json"] },
{ value: "select", source: ["html"] },
{ value: "statusCode", source: ["http in"] },
{ value: "statusCode", source: ["http response","http request"] },
{ value: "status", source: ["status"] },
{ value: "status.text", source: ["status"] },
{ value: "status.source", source: ["status"] },
{ value: "status.source.type", source: ["status"] },
{ value: "status.source.id", source: ["status"] },
{ value: "status.source.name", source: ["status"] },
{ value: "target", source: ["link call"] },
{ value: "template", source: ["template"] },
{ value: "toFront", source: ["delay"] },
{ value: "topic", source: ["inject","mqtt","rbe"] },
{ value: "url", source: ["http request"] },
{ value: "userProperties", source: ["mqtt"] }
{ value: "userProperties", source: ["mqtt"] },
{ value: "_session", source: ["websocket out","tcp out"] },
]
var allOptions = {
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression, autoComplete: autoComplete(msgCompletions)},
@@ -637,7 +669,7 @@
if (opt.icon.indexOf("<") === 0) {
$(opt.icon).prependTo(op);
} else if (opt.icon.indexOf("/") !== -1) {
$('<img>',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px; height: 18px;"}).prependTo(op);
$('<i>',{class:"red-ui-typedInput-icon", style:"mask-image: url("+opt.icon+"); -webkit-mask-image: url("+opt.icon+");"}).prependTo(op);
} else {
$('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(op);
}
@@ -1147,7 +1179,8 @@
this.elementDiv.show();
if (opt.autoComplete) {
this.input.autoComplete({
search: opt.autoComplete
search: opt.autoComplete,
minLength: 0
})
}
}

View File

@@ -319,205 +319,208 @@ RED.deploy = (function() {
},delta);
});
}
function save(skipValidation,force) {
if (!$("#red-ui-header-button-deploy").hasClass("disabled")) {
if (!RED.user.hasPermission("flows.write")) {
RED.notify(RED._("user.errors.deploy"),"error");
function save(skipValidation, force) {
if ($("#red-ui-header-button-deploy").hasClass("disabled")) {
return; //deploy is disabled
}
if ($("#red-ui-header-shade").is(":visible")) {
return; //deploy is shaded
}
if (!RED.user.hasPermission("flows.write")) {
RED.notify(RED._("user.errors.deploy"), "error");
return;
}
let hasUnusedConfig = false;
if (!skipValidation) {
let hasUnknown = false;
let hasInvalid = false;
const unknownNodes = [];
const invalidNodes = [];
RED.nodes.eachConfig(function (node) {
if (node.valid === undefined) {
RED.editor.validateNode(node);
}
if (!node.valid && !node.d) {
invalidNodes.push(getNodeInfo(node));
}
if (node.type === "unknown") {
if (unknownNodes.indexOf(node.name) == -1) {
unknownNodes.push(node.name);
}
}
});
RED.nodes.eachNode(function (node) {
if (!node.valid && !node.d) {
invalidNodes.push(getNodeInfo(node));
}
if (node.type === "unknown") {
if (unknownNodes.indexOf(node.name) == -1) {
unknownNodes.push(node.name);
}
}
});
hasUnknown = unknownNodes.length > 0;
hasInvalid = invalidNodes.length > 0;
const unusedConfigNodes = [];
RED.nodes.eachConfig(function (node) {
if ((node._def.hasUsers !== false) && (node.users.length === 0)) {
unusedConfigNodes.push(getNodeInfo(node));
hasUnusedConfig = true;
}
});
let showWarning = false;
let notificationMessage;
let notificationButtons = [];
let notification;
if (hasUnknown && !ignoreDeployWarnings.unknown) {
showWarning = true;
notificationMessage = "<p>" + RED._('deploy.confirm.unknown') + "</p>" +
'<ul class="red-ui-deploy-dialog-confirm-list"><li>' + cropList(unknownNodes).map(function (n) { return sanitize(n) }).join("</li><li>") + "</li></ul><p>" +
RED._('deploy.confirm.confirm') +
"</p>";
notificationButtons = [
{
id: "red-ui-deploy-dialog-confirm-deploy-deploy",
text: RED._("deploy.confirm.button.confirm"),
class: "primary",
click: function () {
save(true);
notification.close();
}
}
];
} else if (hasInvalid && !ignoreDeployWarnings.invalid) {
showWarning = true;
invalidNodes.sort(sortNodeInfo);
notificationMessage = "<p>" + RED._('deploy.confirm.improperlyConfigured') + "</p>" +
'<ul class="red-ui-deploy-dialog-confirm-list"><li>' + cropList(invalidNodes.map(function (A) { return sanitize((A.tab ? "[" + A.tab + "] " : "") + A.label + " (" + A.type + ")") })).join("</li><li>") + "</li></ul><p>" +
RED._('deploy.confirm.confirm') +
"</p>";
notificationButtons = [
{
id: "red-ui-deploy-dialog-confirm-deploy-deploy",
text: RED._("deploy.confirm.button.confirm"),
class: "primary",
click: function () {
save(true);
notification.close();
}
}
];
}
if (showWarning) {
notificationButtons.unshift(
{
text: RED._("common.label.cancel"),
click: function () {
notification.close();
}
}
);
notification = RED.notify(notificationMessage, {
modal: true,
fixed: true,
buttons: notificationButtons
});
return;
}
if (!skipValidation) {
var hasUnknown = false;
var hasInvalid = false;
var hasUnusedConfig = false;
var unknownNodes = [];
var invalidNodes = [];
RED.nodes.eachConfig(function(node) {
if (node.valid === undefined) {
RED.editor.validateNode(node);
}
if (!node.valid && !node.d) {
invalidNodes.push(getNodeInfo(node));
}
if (node.type === "unknown") {
if (unknownNodes.indexOf(node.name) == -1) {
unknownNodes.push(node.name);
}
}
});
RED.nodes.eachNode(function(node) {
if (!node.valid && !node.d) {
invalidNodes.push(getNodeInfo(node));
}
if (node.type === "unknown") {
if (unknownNodes.indexOf(node.name) == -1) {
unknownNodes.push(node.name);
}
}
});
hasUnknown = unknownNodes.length > 0;
hasInvalid = invalidNodes.length > 0;
var unusedConfigNodes = [];
RED.nodes.eachConfig(function(node) {
if ((node._def.hasUsers !== false) && (node.users.length === 0)) {
unusedConfigNodes.push(getNodeInfo(node));
hasUnusedConfig = true;
}
});
var showWarning = false;
var notificationMessage;
var notificationButtons = [];
var notification;
if (hasUnknown && !ignoreDeployWarnings.unknown) {
showWarning = true;
notificationMessage = "<p>"+RED._('deploy.confirm.unknown')+"</p>"+
'<ul class="red-ui-deploy-dialog-confirm-list"><li>'+cropList(unknownNodes).map(function(n) { return sanitize(n) }).join("</li><li>")+"</li></ul><p>"+
RED._('deploy.confirm.confirm')+
"</p>";
notificationButtons= [
{
id: "red-ui-deploy-dialog-confirm-deploy-deploy",
text: RED._("deploy.confirm.button.confirm"),
class: "primary",
click: function() {
save(true);
notification.close();
}
}
];
} else if (hasInvalid && !ignoreDeployWarnings.invalid) {
showWarning = true;
invalidNodes.sort(sortNodeInfo);
notificationMessage = "<p>"+RED._('deploy.confirm.improperlyConfigured')+"</p>"+
'<ul class="red-ui-deploy-dialog-confirm-list"><li>'+cropList(invalidNodes.map(function(A) { return sanitize( (A.tab?"["+A.tab+"] ":"")+A.label+" ("+A.type+")")})).join("</li><li>")+"</li></ul><p>"+
RED._('deploy.confirm.confirm')+
"</p>";
notificationButtons= [
{
id: "red-ui-deploy-dialog-confirm-deploy-deploy",
text: RED._("deploy.confirm.button.confirm"),
class: "primary",
click: function() {
save(true);
notification.close();
}
}
];
}
if (showWarning) {
notificationButtons.unshift(
{
text: RED._("common.label.cancel"),
click: function() {
notification.close();
}
}
);
notification = RED.notify(notificationMessage,{
modal: true,
fixed: true,
buttons:notificationButtons
});
return;
}
}
var nns = RED.nodes.createCompleteNodeSet();
var startTime = Date.now();
$(".red-ui-deploy-button-content").css('opacity',0);
$(".red-ui-deploy-button-spinner").show();
$("#red-ui-header-button-deploy").addClass("disabled");
var data = {flows:nns};
if (!force) {
data.rev = RED.nodes.version();
}
deployInflight = true;
$("#red-ui-header-shade").show();
$("#red-ui-editor-shade").show();
$("#red-ui-palette-shade").show();
$("#red-ui-sidebar-shade").show();
$.ajax({
url:"flows",
type: "POST",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
headers: {
"Node-RED-Deployment-Type":deploymentType
}
}).done(function(data,textStatus,xhr) {
RED.nodes.dirty(false);
RED.nodes.version(data.rev);
RED.nodes.originalFlow(nns);
if (hasUnusedConfig) {
RED.notify(
'<p>'+RED._("deploy.successfulDeploy")+'</p>'+
'<p>'+RED._("deploy.unusedConfigNodes")+' <a href="#" onclick="RED.sidebar.config.show(true); return false;">'+RED._("deploy.unusedConfigNodesLink")+'</a></p>',"success",false,6000);
} else {
RED.notify('<p>'+RED._("deploy.successfulDeploy")+'</p>',"success");
}
RED.nodes.eachNode(function(node) {
if (node.changed) {
node.dirty = true;
node.changed = false;
}
if (node.moved) {
node.dirty = true;
node.moved = false;
}
if(node.credentials) {
delete node.credentials;
}
});
RED.nodes.eachConfig(function (confNode) {
confNode.changed = false;
if (confNode.credentials) {
delete confNode.credentials;
}
});
RED.nodes.eachSubflow(function(subflow) {
subflow.changed = false;
});
RED.nodes.eachWorkspace(function(ws) {
ws.changed = false;
});
// Once deployed, cannot undo back to a clean state
RED.history.markAllDirty();
RED.view.redraw();
RED.events.emit("deploy");
}).fail(function(xhr,textStatus,err) {
RED.nodes.dirty(true);
$("#red-ui-header-button-deploy").removeClass("disabled");
if (xhr.status === 401) {
RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error");
} else if (xhr.status === 409) {
resolveConflict(nns, true);
} else if (xhr.responseText) {
RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error");
} else {
RED.notify(RED._("deploy.deployFailed",{message:RED._("deploy.errors.noResponse")}),"error");
}
}).always(function() {
deployInflight = false;
var delta = Math.max(0,300-(Date.now()-startTime));
setTimeout(function() {
$(".red-ui-deploy-button-content").css('opacity',1);
$(".red-ui-deploy-button-spinner").hide();
$("#red-ui-header-shade").hide();
$("#red-ui-editor-shade").hide();
$("#red-ui-palette-shade").hide();
$("#red-ui-sidebar-shade").hide();
},delta);
});
}
const nns = RED.nodes.createCompleteNodeSet();
const startTime = Date.now();
$(".red-ui-deploy-button-content").css('opacity', 0);
$(".red-ui-deploy-button-spinner").show();
$("#red-ui-header-button-deploy").addClass("disabled");
const data = { flows: nns };
if (!force) {
data.rev = RED.nodes.version();
}
deployInflight = true;
$("#red-ui-header-shade").show();
$("#red-ui-editor-shade").show();
$("#red-ui-palette-shade").show();
$("#red-ui-sidebar-shade").show();
$.ajax({
url: "flows",
type: "POST",
data: JSON.stringify(data),
contentType: "application/json; charset=utf-8",
headers: {
"Node-RED-Deployment-Type": deploymentType
}
}).done(function (data, textStatus, xhr) {
RED.nodes.dirty(false);
RED.nodes.version(data.rev);
RED.nodes.originalFlow(nns);
if (hasUnusedConfig) {
RED.notify(
'<p>' + RED._("deploy.successfulDeploy") + '</p>' +
'<p>' + RED._("deploy.unusedConfigNodes") + ' <a href="#" onclick="RED.sidebar.config.show(true); return false;">' + RED._("deploy.unusedConfigNodesLink") + '</a></p>', "success", false, 6000);
} else {
RED.notify('<p>' + RED._("deploy.successfulDeploy") + '</p>', "success");
}
RED.nodes.eachNode(function (node) {
if (node.changed) {
node.dirty = true;
node.changed = false;
}
if (node.moved) {
node.dirty = true;
node.moved = false;
}
if (node.credentials) {
delete node.credentials;
}
});
RED.nodes.eachConfig(function (confNode) {
confNode.changed = false;
if (confNode.credentials) {
delete confNode.credentials;
}
});
RED.nodes.eachSubflow(function (subflow) {
subflow.changed = false;
});
RED.nodes.eachWorkspace(function (ws) {
ws.changed = false;
});
// Once deployed, cannot undo back to a clean state
RED.history.markAllDirty();
RED.view.redraw();
RED.events.emit("deploy");
}).fail(function (xhr, textStatus, err) {
RED.nodes.dirty(true);
$("#red-ui-header-button-deploy").removeClass("disabled");
if (xhr.status === 401) {
RED.notify(RED._("deploy.deployFailed", { message: RED._("user.notAuthorized") }), "error");
} else if (xhr.status === 409) {
resolveConflict(nns, true);
} else if (xhr.responseText) {
RED.notify(RED._("deploy.deployFailed", { message: xhr.responseText }), "error");
} else {
RED.notify(RED._("deploy.deployFailed", { message: RED._("deploy.errors.noResponse") }), "error");
}
}).always(function () {
deployInflight = false;
const delta = Math.max(0, 300 - (Date.now() - startTime));
setTimeout(function () {
$(".red-ui-deploy-button-content").css('opacity', 1);
$(".red-ui-deploy-button-spinner").hide();
$("#red-ui-header-shade").hide();
$("#red-ui-editor-shade").hide();
$("#red-ui-palette-shade").hide();
$("#red-ui-sidebar-shade").hide();
}, delta);
});
}
return {
init: init,

View File

@@ -110,7 +110,11 @@ RED.editor = (function() {
var result = [];
for (var prop in definition) {
if (definition.hasOwnProperty(prop)) {
if (!validateNodeProperty(node, definition, prop, properties[prop])) {
var valid = validateNodeProperty(node, definition, prop, properties[prop]);
if ((typeof valid) === "string") {
result.push(valid);
}
else if(!valid) {
result.push(prop);
}
}
@@ -124,7 +128,7 @@ RED.editor = (function() {
* @param definition - the node property definitions (either def.defaults or def.creds)
* @param property - the property name being validated
* @param value - the property value being validated
* @returns {boolean} whether the node proprty is valid
* @returns {boolean|string} whether the node proprty is valid. `true`: valid `false|String`: invalid
*/
function validateNodeProperty(node,definition,property,value) {
var valid = true;
@@ -136,22 +140,74 @@ RED.editor = (function() {
if (/^\$\{[a-zA-Z_][a-zA-Z0-9_]*\}$/.test(value)) {
return true;
}
var label = null;
if (("label" in definition[property]) &&
((typeof definition[property].label) == "string")) {
label = definition[property].label;
}
if ("required" in definition[property] && definition[property].required) {
valid = value !== "";
if (!valid && label) {
return RED._("validator.errors.missing-required-prop", {
prop: label
});
}
}
if (valid && "validate" in definition[property]) {
try {
valid = definition[property].validate.call(node,value);
var opt = {};
if (label) {
opt.label = label;
}
valid = definition[property].validate.call(node,value, opt);
// If the validator takes two arguments, it is a 3.x validator that
// can return a String to mean 'invalid' and provide a reason
if ((definition[property].validate.length === 2) &&
((typeof valid) === "string")) {
return valid;
} else {
// Otherwise, a 2.x returns a truth-like/false-like value that
// we should cooerce to a boolean.
valid = !!valid
}
} catch(err) {
console.log("Validation error:",node.type,node.id,"property: "+property,"value:",value,err);
return RED._("validator.errors.validation-error", {
prop: property,
node: node.type,
id: node.id,
error: err.message
});
}
}
if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) {
if (!value || value == "_ADD_") {
valid = definition[property].hasOwnProperty("required") && !definition[property].required;
if (!valid && label) {
return RED._("validator.errors.missing-required-prop", {
prop: label
});
}
} else {
var configNode = RED.nodes.node(value);
valid = (configNode && (configNode.valid == null || configNode.valid));
if (configNode) {
if ((configNode.valid == null) || configNode.valid) {
return true;
}
if (label) {
return RED._("validator.errors.invalid-config", {
prop: label
});
}
}
else {
if (label) {
return RED._("validator.errors.missing-config", {
prop: label
});
}
}
return false;
}
}
return valid;
@@ -179,10 +235,26 @@ RED.editor = (function() {
if (defaults[property].hasOwnProperty("format") && defaults[property].format !== "" && input[0].nodeName === "DIV") {
value = input.text();
}
if (!validateNodeProperty(node, defaults, property,value)) {
var valid = validateNodeProperty(node, defaults, property,value);
if (((typeof valid) === "string") || !valid) {
input.addClass("input-error");
if ((typeof valid) === "string") {
var tooltip = input.data("tooltip");
if (tooltip) {
tooltip.setContent(valid);
}
else {
tooltip = RED.popover.tooltip(input, valid);
input.data("tooltip", tooltip);
}
}
} else {
input.removeClass("input-error");
var tooltip = input.data("tooltip");
if (tooltip) {
input.data("tooltip", null);
tooltip.delete();
}
}
}
}

View File

@@ -1151,7 +1151,7 @@ RED.editor.codeEditor.monaco = (function() {
try {
var _model = ed.getModel();
if (_model !== null) {
var id = _model.getModeId(); // e.g. javascript
var id = _model._languageId; // e.g. javascript
var ra = _model._associatedResource.authority; //e.g. model
var rp = _model._associatedResource.path; //e.g. /18
var rs = _model._associatedResource.scheme; //e.g. inmemory

View File

@@ -168,7 +168,7 @@
'b': { before:"**", after: "**", tooltip: RED._("markdownEditor.bold")},
'i': { before:"_", after: "_", tooltip: RED._("markdownEditor.italic")},
'code': { before:"`", after: "`", tooltip: RED._("markdownEditor.code")},
'ol': { before:" * ", newline: true, tooltip: RED._("markdownEditor.ordered-list")},
'ol': { before:" 1. ", newline: true, tooltip: RED._("markdownEditor.ordered-list")},
'ul': { before:" - ", newline: true, tooltip: RED._("markdownEditor.unordered-list")},
'bq': { before:"> ", newline: true, tooltip: RED._("markdownEditor.quote")},
'link': { before:"[", after: "]()", tooltip: RED._("markdownEditor.link")},

View File

@@ -977,18 +977,31 @@ RED.view.tools = (function() {
* doesn't clash with any existing nodes of that type
* @param {Object} node The node to set the name of - if not provided, uses current selection
*/
function generateNodeNames(node) {
const nodes = node?[node]:RED.view.selection().nodes;
function generateNodeNames(node, options) {
options = options || {
renameBlank: true,
renameClash: true,
generateHistory: true
}
let nodes = node;
if (node) {
if (!Array.isArray(node)) {
nodes = [ node ]
}
} else {
nodes = RED.view.selection().nodes;
}
if (nodes && nodes.length > 0) {
// Generate history event if using the workspace selection,
// or if the provided node already exists
const generateHistory = !node || !!RED.nodes.node(node.id)
const generateHistory = options.generateHistory && (!node || !!RED.nodes.node(node.id))
const historyEvents = []
const typeIndex = {}
let changed = false;
nodes.forEach(n => {
if (n._def && n._def.defaults && n._def.defaults.name) {
const paletteLabel = RED.utils.getPaletteLabel(n.type, n._def)
const nodeDef = n._def || RED.nodes.getType(n.type)
if (nodeDef && nodeDef.defaults && nodeDef.defaults.name) {
const paletteLabel = RED.utils.getPaletteLabel(n.type, nodeDef)
const defaultNodeNameRE = new RegExp('^'+paletteLabel+' (\\d+)$')
if (!typeIndex.hasOwnProperty(n.type)) {
const existingNodes = RED.nodes.filterNodes({type: n.type})
@@ -1004,7 +1017,7 @@ RED.view.tools = (function() {
})
typeIndex[n.type] = maxNameNumber + 1
}
if (n.name === '') {
if ((options.renameBlank && n.name === '') || (options.renameClash && defaultNodeNameRE.test(n.name))) {
if (generateHistory) {
historyEvents.push({
t:'edit',

View File

@@ -455,7 +455,7 @@ RED.view = (function() {
}
});
//add search to status-toolbar
//add search to status-toolbar
RED.statusBar.add({
id: "view-search-tools",
align: "left",
@@ -604,7 +604,7 @@ RED.view = (function() {
RED.actions.add("core:copy-selection-to-internal-clipboard",copySelection);
RED.actions.add("core:cut-selection-to-internal-clipboard",function(){copySelection();deleteSelection();});
RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true});});
RED.actions.add("core:paste-from-internal-clipboard",function(){importNodes(clipboard,{generateIds: true, generateDefaultNames: true});});
RED.actions.add("core:detach-selected-nodes", function() { detachSelectedNodes() })
@@ -2463,7 +2463,7 @@ RED.view = (function() {
var removedEntities = RED.nodes.remove(node.id);
removedNodes.push(node);
removedNodes = removedNodes.concat(removedEntities.nodes);
addToRemovedLinks(removedNodes.removedLinks);
addToRemovedLinks(removedEntities.links);
if (node.g) {
var group = RED.nodes.group(node.g);
if (selectedGroups.indexOf(group) === -1) {
@@ -3480,7 +3480,6 @@ RED.view = (function() {
enterActiveGroup(ag);
activeGroup.selected = true;
}
console.log(d3.event);
var cnodes = RED.nodes.getAllFlowNodes(mousedown_node);
for (var n=0;n<cnodes.length;n++) {
if (!cnodes[n].selected) {
@@ -5403,12 +5402,16 @@ RED.view = (function() {
* - addFlow - whether to import nodes to a new tab
* - touchImport - whether this is a touch import. If not, imported nodes are
* attachedto mouse for placing - "IMPORT_DRAGGING" state
* - generateIds - whether to automatically generate new ids for all imported nodes
* - generateDefaultNames - whether to automatically update any nodes with clashing
* default names
*/
function importNodes(newNodesObj,options) {
options = options || {
addFlow: false,
touchImport: false,
generateIds: false
generateIds: false,
generateDefaultNames: false
}
var addNewFlow = options.addFlow
var touchImport = options.touchImport;
@@ -5436,7 +5439,13 @@ RED.view = (function() {
if (!$.isArray(nodesToImport)) {
nodesToImport = [nodesToImport];
}
if (options.generateDefaultNames) {
RED.actions.invoke("core:generate-node-names", nodesToImport, {
renameBlank: false,
renameClash: true,
generateHistory: false
})
}
try {
var activeSubflowChanged;

View File

@@ -118,20 +118,26 @@ RED.user = (function() {
});
} else if (data.type == "strategy") {
var sessionMessage = /[?&]session_message=(.*?)(?:$|&)/.exec(window.location.search);
RED.sessionMessages = RED.sessionMessages || [];
if (sessionMessage) {
RED.sessionMessages.push(decodeURIComponent(sessionMessage[1]));
if (history.pushState) {
var newurl = window.location.protocol+"//"+window.location.host+window.location.pathname
window.history.replaceState({ path: newurl }, "", newurl);
} else {
window.location.search = "";
}
}
if (RED.sessionMessages.length === 0 && data.autoLogin) {
document.location = data.loginRedirect
return
}
i = 0;
for (;i<data.prompts.length;i++) {
var field = data.prompts[i];
var sessionMessage = /[?&]session_message=(.*?)(?:$|&)/.exec(window.location.search);
if (sessionMessage) {
RED.sessionMessages = RED.sessionMessages || [];
RED.sessionMessages.push(sessionMessage[1]);
if (history.pushState) {
var newurl = window.location.protocol+"//"+window.location.host+window.location.pathname
window.history.replaceState({ path: newurl }, "", newurl);
} else {
window.location.search = "";
}
}
if (RED.sessionMessages) {
var sessionMessages = $("<div/>",{class:"form-row",style:"text-align: center"}).appendTo("#node-dialog-login-fields");
RED.sessionMessages.forEach(function (msg) {

View File

@@ -14,22 +14,72 @@
* limitations under the License.
**/
RED.validators = {
number: function(blankAllowed){return function(v) { return (blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v));}},
regex: function(re){return function(v) { return re.test(v);}},
typedInput: function(ptypeName,isConfig) { return function(v) {
var ptype = $("#node-"+(isConfig?"config-":"")+"input-"+ptypeName).val() || this[ptypeName];
if (ptype === 'json') {
try {
JSON.parse(v);
number: function(blankAllowed,mopt){
return function(v, opt) {
if ((blankAllowed&&(v===''||v===undefined)) || (v!=='' && !isNaN(v))) {
return true;
} catch(err) {
return false;
}
} else if (ptype === 'msg' || ptype === 'flow' || ptype === 'global' ) {
return RED.utils.validatePropertyExpression(v);
} else if (ptype === 'num') {
return /^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v);
}
return true;
}}
if (opt && opt.label) {
return RED._("validator.errors.invalid-num-prop", {
prop: opt.label
});
}
return opt ? RED._("validator.errors.invalid-num") : false;
};
},
regex: function(re, mopt) {
return function(v, opt) {
if (re.test(v)) {
return true;
}
if (opt && opt.label) {
return RED._("validator.errors.invalid-regex-prop", {
prop: opt.label
});
}
return opt ? RED._("validator.errors.invalid-regexp") : false;
};
},
typedInput: function(ptypeName,isConfig,mopt) {
return function(v, opt) {
var ptype = $("#node-"+(isConfig?"config-":"")+"input-"+ptypeName).val() || this[ptypeName];
if (ptype === 'json') {
try {
JSON.parse(v);
return true;
} catch(err) {
if (opt && opt.label) {
return RED._("validator.errors.invalid-json-prop", {
error: err.message,
prop: opt.label,
});
}
return opt ? RED._("validator.errors.invalid-json", {
error: err.message
}) : false;
}
} else if (ptype === 'msg' || ptype === 'flow' || ptype === 'global' ) {
if (RED.utils.validatePropertyExpression(v)) {
return true;
}
if (opt && opt.label) {
return RED._("validator.errors.invalid-prop-prop", {
prop: opt.label
});
}
return opt ? RED._("validator.errors.invalid-prop") : false;
} else if (ptype === 'num') {
if (/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v)) {
return true;
}
if (opt && opt.label) {
return RED._("validator.errors.invalid-num-prop", {
prop: opt.label
});
}
return opt ? RED._("validator.errors.invalid-num") : false;
}
return true;
};
}
};