Compare commits

..

62 Commits

Author SHA1 Message Date
Nick O'Leary
f309a9d537 Bump version to 0.20.0-beta.3 2019-01-10 13:34:47 +00:00
Nick O'Leary
a786b37cb9 Make ssh key dialog accessible when opened from new proj dialog 2019-01-10 13:28:40 +00:00
Nick O'Leary
6a519a30a2 Update changelog 2019-01-09 17:04:33 +00:00
Nick O'Leary
81ae552e69 Project ui code using incorrect error property 2019-01-09 17:03:17 +00:00
Nick O'Leary
0ec04a3624 Allow notifications to be reused in place rather than stack
For example, clipboard actions now reuse the same notification.
Similarly the Inject node will reuse its notification when
injecting.
2019-01-09 14:02:46 +00:00
Nick O'Leary
81d5b47fce Update ws dependency in sub-modules 2019-01-08 16:31:18 +00:00
Nick O'Leary
ed31a0cf15 Update to WS 6.x and fix all it broke
Significant update to the ws module to get it completely up to date.

The jump from 1.x to 6.x has required a rewrite of our WS handling. Most
specifically the means by which you can have multiple ws servers on a
single http server has completely changed; we now have to handle the
'upgrade' event on the server ourselves.
2019-01-08 16:21:36 +00:00
Nick O'Leary
201d1926bc Bump dependencies 2019-01-08 10:32:23 +00:00
Nick O'Leary
9ee6655bfa Bump jsonata in submodule package.json 2019-01-07 17:03:32 +00:00
Nick O'Leary
c4beab6b0d Bump JSONata to 1.6.4
Fixes #2023
2019-01-07 16:59:38 +00:00
Nick O'Leary
34b6643913 Remove unused variable declaration in Change node 2019-01-07 15:00:32 +00:00
Nick O'Leary
98e391b867 Add audit logging to admin api 2019-01-07 14:59:48 +00:00
Nick O'Leary
19eb8e9a6d Update palette manager properly when module updated 2019-01-07 14:54:35 +00:00
Nick O'Leary
43b7aa40c3 Remove promises from Join node 2019-01-02 22:37:06 +00:00
Nick O'Leary
747af44fc1 Tidy up variable naming in split.js 2019-01-01 23:05:13 +00:00
Nick O'Leary
d5ef428edd Remove promises from Change node 2018-12-21 14:37:04 +00:00
Nick O'Leary
5fa4d227b8 Merge pull request #2010 from node-red-hitachi/fix-require
Fix RED.require
2018-12-21 10:38:35 +00:00
Hiroyasu Nishiyama
cc7e3b0c26 fix failure of RED.require 2018-12-21 14:39:51 +09:00
Nick O'Leary
473a2ae275 Remove all Promises from Switch node
Promises are expensive and should not be used in the main
message handling path. The Switch node used them a lot if
the node references context - with a lot of duplicate code
to handle async and sync code paths.

This change modifies the code to use callbacks throughout
that are just as performant in either case.
2018-12-20 22:57:47 +00:00
Nick O'Leary
7f5d47f39d Update Link node UI to use TreeList 2018-12-20 13:15:42 +00:00
Nick O'Leary
6031f146aa Add TreeList common widget 2018-12-20 13:15:31 +00:00
Nick O'Leary
020a469f3b Fix visual jump when opening Comment editor on Safari
Part of #2008
2018-12-19 10:05:09 +00:00
Nick O'Leary
091de3aa66 Fix vertical align of markdown editor in Safari
Fixes #2008
2018-12-19 10:04:36 +00:00
Nick O'Leary
b837f7608c Avoid marking node as changed if label state is default
Fixes #2009
2018-12-19 09:30:20 +00:00
Nick O'Leary
afe9367bac Merge pull request #2005 from kazuhitoyokoi/dev-updatemessagecatalog
Update message catalog
2018-12-18 22:58:19 +00:00
Nick O'Leary
9bd9023cb6 Merge pull request #2007 from node-red-hitachi/dev-uitest-mac
Update UI test for mac OS
2018-12-18 22:57:48 +00:00
Nick O'Leary
8502cf8498 Highlight port on node hover while joining 2018-12-18 21:45:33 +00:00
Nick O'Leary
33dade0584 Support drag-wiring of link nodes 2018-12-18 10:57:53 +00:00
Nick O'Leary
84cc2ad0fa Allow TypeSearch to include a filter option 2018-12-18 10:57:33 +00:00
Nick O'Leary
dc2d3bc7c0 Improve diff colouring 2018-12-18 10:57:18 +00:00
Nick O'Leary
64df557423 Allow sections to toggle in 2-element stack 2018-12-18 10:56:54 +00:00
Yuma Matsuura
715cc77e76 Update UI test for mac os 2018-12-18 09:29:46 +09:00
Kazuhito Yokoi
b80d1af3d7 Fix typo 2018-12-17 19:01:33 +09:00
Kazuhito Yokoi
f05f534fd2 Update message catalogue 2018-12-17 18:48:19 +09:00
Nick O'Leary
c0837ead0e Add support for ${} env var syntax when skipping validation
Closes #1980

See also #825
2018-12-13 16:13:57 +00:00
Nick O'Leary
a1f135bd66 Allow oauth strategy callback method to be customised
Closes #1998

Method can be set via: `adminAuth.strategy.options.callbackMethod`

Can be either GET (default) or POST.
2018-12-13 13:43:57 +00:00
Nick O'Leary
978f4ecc58 Ensure fs context cache is flushed on close
Fixes #2001
2018-12-13 12:46:19 +00:00
Dave Conway-Jones
46a8d96997 fix library Buffer( to Buffer.alloc( for node 10 2018-12-13 11:32:58 +00:00
Nick O'Leary
c283224000 Merge branch 'master' into dev 2018-12-13 11:14:58 +00:00
Nick O'Leary
a6ef755139 Merge pull request #1993 from arunnattarayan/patch-1
Export to library produces empty folder when name has a trailing slash
2018-12-13 11:05:52 +00:00
Nick O'Leary
29a257d17a Merge pull request #1995 from node-red-hitachi/debug-node-with-jsonata
Add support of output editing in DEBUG node using JSONata
2018-12-13 11:05:21 +00:00
Nick O'Leary
368b76a183 Merge pull request #2000 from node-red-hitachi/i18n-markdown-tooltip
i18 support for markdown editor tooltips
2018-12-13 11:00:03 +00:00
Nick O'Leary
8bb861124d Catch file-not-found on startup when non-existant flow file specified 2018-12-13 10:59:03 +00:00
Hiroyasu Nishiyama
2f884ec778 i18 support for markdown editor tooltip 2018-12-11 22:33:11 +09:00
Nick O'Leary
8c561e92c8 Actively expire login sesssions and notify user 2018-12-11 11:32:12 +00:00
Hiroyasu Nishiyama
633b9180d7 update info text 2018-12-11 19:53:10 +09:00
Hiroyasu Nishiyama
0e2d0e1b6f merge dev 2018-12-11 19:02:49 +09:00
Nick O'Leary
ea4d65ceee Add RED.editor.registerTypeEditor for custom type editors 2018-12-10 22:21:21 +00:00
Nick O'Leary
d47ac84d2e Merge pull request #1999 from natcl/dev
JSON node: delete msg.schema before sending msg to avoid conflicts
2018-12-10 20:54:24 +00:00
Nathanaël Lécaudé
a97759aa35 JSON node: add help about schema deletion 2018-12-10 14:47:52 -05:00
Nathanaël Lécaudé
3fcfd4abdd JSON node: add help about schema deletion 2018-12-10 14:46:21 -05:00
Nathanaël Lécaudé
6d771da9a9 JSON node: delete msg.schema before sending msg to avoid conflicts 2018-12-10 13:47:55 -05:00
Nick O'Leary
6201247875 Tidy up markdown toolbar handling across all editors
Any editor for the markdown mode will now automatically get
the markdown toolbar added.

The comment node has been updated to handle this properly and
to not add two copies of its content to the sidebar.
2018-12-10 15:24:27 +00:00
Hiroyasu Nishiyama
8c367bcc53 update messages 2018-12-09 20:33:58 +09:00
Hiroyasu Nishiyama
8198132ca7 use output selector for specifying JSONata expression 2018-12-09 20:30:35 +09:00
Dave Conway-Jones
cf3b4e9e63 change check order for node.users
If node _def hasUsers is false then node.users may not exist... so won't have a length...
2018-12-08 18:09:33 +00:00
Hiroyasu Nishiyama
987dbf8a92 Merge branch 'dev' into debug-node-with-jsonata 2018-12-08 17:43:29 +09:00
Nick O'Leary
7b80ae42e1 removed regex in if statement
Co-Authored-By: arunnattarayan <arunkumarit02@gmail.com>
2018-12-06 19:20:43 +05:30
Hiroyasu Nishiyama
3c4f4d27d6 Support output editting of DEBUG node using JSONata 2018-12-01 18:03:04 +01:00
Arun Nattarayan
06a1f30350 Added validation while export into library 2018-11-30 20:04:12 +05:30
Nick O'Leary
2f93bb969b Merge pull request #1976 from MatthiasU/master
Add quotation marks for basic auth challenge
2018-11-13 23:16:41 +00:00
Matthias Uttendorfer
e094ea3d2a Add quotation marks for basic auth challenge
This is required by RFC 2617
2018-11-13 23:05:19 +01:00
84 changed files with 2044 additions and 1153 deletions

View File

@@ -1,3 +1,46 @@
#### 0.20.0-beta.3: Beta Release
Editor
- Update palette manager view properly when module updated
- Add TreeList common widget
- Fix visual jump when opening Comment editor on Safari Part of #2008
- Fix vertical align of markdown editor in Safari Fixes #2008
- Avoid marking node as changed if label state is default Fixes #2009
- Highlight port on node hover while joining
- Support drag-wiring of link nodes
- Allow TypeSearch to include a filter option
- Improve diff colouring
- Allow sections to toggle in 2-element stack
- Add support for ${} env var syntax when skipping validation Closes #1980
- i18 support for markdown editor tooltip
- Add RED.editor.registerTypeEditor for custom type editors
- Tidy up markdown toolbar handling across all editors
- Added validation while export into library
- Reuse notification boxes rather than stack multiple of the same type
- Make ssh key dialog accessible when opened from new proj dialog
Runtime
- Bump JSONata to 1.6.4 Fixes #2023
- Add audit logging to admin api
- Fix failure of RED.require #2010
- Allow oauth strategy callback method to be customised Closes #1998
- Ensure fs context cache is flushed on close Fixes #2001
- Fix library Buffer( to Buffer.alloc( for node 10
- Catch file-not-found on startup when non-existant flow file specified
- Actively expire login sesssions and notify user
- Add quotation marks for basic auth challenge #1976
Nodes
- Change: remove promises to improve performance
- Debug: add ability to apply JSONata expression to message
- Join: remove promises to improve performance
- JSON: delete msg.schema before sending msg to avoid conflicts
- Link: update UI to use common TreeList widget
- Switch: remove promises to improve performance
#### 0.20.0-beta.2: Beta Release
- Split Node-RED internals into multiple sub-modules

View File

@@ -135,6 +135,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/validators.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/utils.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/checkboxSet.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/panels.js",
@@ -445,7 +446,17 @@ module.exports = function(grunt) {
destination: 'docs',
configure: './jsdoc.json'
}
},
editor: {
src: [
'packages/node_modules/@node-red/editor-client/src/js'
],
options: {
destination: 'packages/node_modules/@node-red/editor-client/docs',
configure: './jsdoc.json'
}
}
},
jsdoc2md: {
runtimeAPI: {

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "0.20.0-beta.2",
"version": "0.20.0-beta.3",
"description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -24,7 +24,7 @@
}
],
"dependencies": {
"ajv": "6.6.1",
"ajv": "6.6.2",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"body-parser": "1.18.3",
@@ -33,7 +33,7 @@
"cookie": "0.3.1",
"cookie-parser": "1.4.3",
"cors": "2.8.5",
"cron": "1.5.1",
"cron": "1.6.0",
"denque": "1.4.0",
"express": "4.16.4",
"express-session": "1.15.6",
@@ -41,11 +41,11 @@
"fs.notify": "0.0.4",
"hash-sum": "1.0.2",
"https-proxy-agent": "2.2.1",
"i18next": "12.1.0",
"i18next": "13.1.0",
"is-utf8": "0.2.1",
"js-yaml": "3.12.0",
"js-yaml": "3.12.1",
"json-stringify-safe": "5.0.1",
"jsonata": "1.5.4",
"jsonata": "1.6.3",
"media-typer": "1.0.1",
"memorystore": "1.6.0",
"mime": "2.4.0",
@@ -70,14 +70,14 @@
"sentiment": "2.1.0",
"uglify-js": "3.4.9",
"when": "3.7.8",
"ws": "1.1.5",
"ws": "6.1.2",
"xml2js": "0.4.19"
},
"optionalDependencies": {
"bcrypt": "~2.0.0"
},
"devDependencies": {
"chromedriver": "2.43.1",
"chromedriver": "2.45.0",
"grunt": "~1.0.3",
"grunt-chmod": "~1.1.1",
"grunt-cli": "~1.3.2",

View File

@@ -182,7 +182,12 @@ function genericStrategy(adminApp,strategy) {
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
completeGenerateStrategyAuth
);
adminApp.get('/auth/strategy/callback',
var callbackMethodFunc = adminApp.get;
if (/^post$/i.test(options.callbackMethod)) {
callbackMethodFunc = adminApp.post;
}
callbackMethodFunc('/auth/strategy/callback',
passport.authenticate(strategy.name, {session:false, failureRedirect: settings.httpAdminRoot }),
completeGenerateStrategyAuth
);

View File

@@ -25,27 +25,39 @@ function generateToken(length) {
var storage;
var sessionExpiryTime
var sessions = {};
var loadedSessions = null;
var apiAccessTokens;
var sessionExpiryListeners = [];
var expiryTimeout;
function expireSessions() {
if (expiryTimeout) {
clearTimeout(expiryTimeout);
expiryTimeout = null;
}
var nextExpiry = Number.MAX_SAFE_INTEGER;
var now = Date.now();
var modified = false;
for (var t in sessions) {
if (sessions.hasOwnProperty(t)) {
var session = sessions[t];
if (!session.hasOwnProperty("expires") || session.expires < now) {
sessionExpiryListeners.forEach(listener => { listener(session) })
delete sessions[t];
modified = true;
} else {
if (session.expires < nextExpiry) {
nextExpiry = session.expires;
}
}
}
}
if (nextExpiry < Number.MAX_SAFE_INTEGER) {
// Allow 5 seconds grace
expiryTimeout = setTimeout(expireSessions,(nextExpiry - Date.now()) + 5000)
}
if (modified) {
return storage.saveSessions(sessions);
} else {
@@ -65,6 +77,9 @@ function loadSessions() {
module.exports = {
init: function(adminAuthSettings, _storage) {
storage = _storage;
sessionExpiryListeners = [];
sessionExpiryTime = adminAuthSettings.sessionExpiryTime || 604800; // 1 week in seconds
// At this point, storage will not have been initialised, so defer loading
// the sessions until there's a request for them.
@@ -112,6 +127,11 @@ module.exports = {
expires: accessTokenExpiresAt
};
sessions[accessToken] = session;
if (!expiryTimeout) {
expiryTimeout = setTimeout(expireSessions,(accessTokenExpiresAt - Date.now()) + 5000)
}
return storage.saveSessions(sessions).then(function() {
return {
accessToken: accessToken,
@@ -125,5 +145,8 @@ module.exports = {
delete sessions[token];
return storage.saveSessions(sessions);
});
},
onSessionExpiry: function(callback) {
sessionExpiryListeners.push(callback);
}
}

View File

@@ -15,6 +15,7 @@
**/
var ws = require("ws");
var url = require("url");
var log = require("@node-red/util").log; // TODO: separate module
var Tokens;
@@ -40,11 +41,19 @@ function init(_server,_settings,_runtimeAPI) {
settings = _settings;
runtimeAPI = _runtimeAPI;
Tokens = require("../auth/tokens");
Tokens.onSessionExpiry(handleSessionExpiry);
Users = require("../auth/users");
Permissions = require("../auth/permissions");
}
function handleSessionExpiry(session) {
activeConnections.forEach(connection => {
if (connection.token === session.accessToken) {
connection.ws.send(JSON.stringify({auth:"fail"}));
connection.ws.close();
}
})
}
function generateSession(length) {
var c = "ABCDEFGHIJKLMNOPQRSTUZWXYZabcdefghijklmnopqrstuvwxyz1234567890";
var token = [];
@@ -88,7 +97,7 @@ function CommsConnection(ws) {
// handleRemoteSubscription(ws,msg.subscribe);
}
} else {
var completeConnection = function(userScope,sendAck) {
var completeConnection = function(userScope,session,sendAck) {
try {
if (!userScope || !Permissions.hasPermission(userScope,"status.read")) {
ws.send(JSON.stringify({auth:"fail"}));
@@ -96,6 +105,7 @@ function CommsConnection(ws) {
} else {
pendingAuth = false;
addActiveConnection(self);
self.token = msg.auth;
if (sendAck) {
ws.send(JSON.stringify({auth:"ok"}));
}
@@ -113,29 +123,29 @@ function CommsConnection(ws) {
if (user) {
self.user = user;
log.audit({event: "comms.auth",user:self.user});
completeConnection(client.scope,true);
completeConnection(client.scope,msg.auth,true);
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,false);
completeConnection(null,null,false);
}
});
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,false);
completeConnection(null,null,false);
}
});
} else {
if (anonymousUser) {
log.audit({event: "comms.auth",user:anonymousUser});
self.user = anonymousUser;
completeConnection(anonymousUser.permissions,false);
completeConnection(anonymousUser.permissions,null,false);
//TODO: duplicated code - pull non-auth message handling out
if (msg.subscribe) {
self.subscribe(msg.subscribe);
}
} else {
log.audit({event: "comms.auth.fail"});
completeConnection(null,false);
completeConnection(null,null,false);
}
}
}
@@ -178,27 +188,27 @@ function start() {
Users.default().then(function(_anonymousUser) {
anonymousUser = _anonymousUser;
var webSocketKeepAliveTime = settings.webSocketKeepAliveTime || 15000;
var path = settings.httpAdminRoot || "/";
path = (path.slice(0,1) != "/" ? "/":"") + path + (path.slice(-1) == "/" ? "":"/") + "comms";
wsServer = new ws.Server({
server:server,
path:path,
// Disable the deflate option due to this issue
// https://github.com/websockets/ws/pull/632
// that is fixed in the 1.x release of the ws module
// that we cannot currently pickup as it drops node 0.10 support
//perMessageDeflate: false
});
var commsPath = settings.httpAdminRoot || "/";
commsPath = (commsPath.slice(0,1) != "/" ? "/":"") + commsPath + (commsPath.slice(-1) == "/" ? "":"/") + "comms";
wsServer = new ws.Server({ noServer: true });
wsServer.on('connection',function(ws) {
var commsConnection = new CommsConnection(ws);
});
wsServer.on('error', function(err) {
log.warn(log._("comms.error-server",{message:err.toString()}));
});
server.on('upgrade', function upgrade(request, socket, head) {
const pathname = url.parse(request.url).pathname;
if (pathname === commsPath) {
wsServer.handleUpgrade(request, socket, head, function done(ws) {
wsServer.emit('connection', ws, request);
});
}
// Don't destroy the socket as other listeners may want to handle the
// event.
});
lastSentTime = Date.now();
heartbeatTimer = setInterval(function() {

View File

@@ -21,6 +21,8 @@ var i18n = require("@node-red/util").i18n; // TODO: separate module
module.exports = {
errorHandler: function(err,req,res,next) {
//TODO: why this when rejectHandler also?!
if (err.message === "request entity too large") {
log.error(err);
} else {
@@ -39,7 +41,9 @@ module.exports = {
return lang;
},
rejectHandler: function(req,res,err) {
res.status(err.status||500).json({
//TODO: why this when errorHandler also?!
log.audit({event: "api.error",error:err.code||"unexpected_error",message:err.message||err.toString()},req);
res.status(err.status||400).json({
code: err.code||"unexpected_error",
message: err.message||err.toString()
});

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-api",
"version": "0.20.0-beta.2",
"version": "0.20.0-beta.3",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/util": "0.20.0-beta.2",
"@node-red/editor-client": "0.20.0-beta.2",
"@node-red/util": "0.20.0-beta.3",
"@node-red/editor-client": "0.20.0-beta.3",
"bcryptjs": "2.4.3",
"body-parser": "1.18.3",
"clone": "2.1.2",
@@ -32,6 +32,6 @@
"passport-oauth2-client-password": "0.1.2",
"passport": "0.4.0",
"when": "3.7.8",
"ws": "1.1.5"
"ws": "6.1.2"
}
}

View File

@@ -27,8 +27,7 @@
"status": "Status",
"enabled": "Enabled",
"disabled":"Disabled",
"info": "Description",
"tip": "Description accepts Markdown and will appear in the Info tab."
"info": "Description"
},
"menu": {
"label": {
@@ -119,7 +118,6 @@
"project_not_found": "<p>Project '__project__' not found.</p>",
"git_merge_conflict": "<p>Automatic merging of changes failed.</p><p>Fix the unmerged conflicts then commit the results.</p>"
},
"error": "<strong>Error</strong>: __message__",
"errors": {
"lostConnection": "Lost connection to server, reconnecting...",
@@ -155,7 +153,6 @@
"node_plural": "__count__ nodes",
"configNode": "__count__ configuration node",
"configNode_plural": "__count__ configuration nodes",
"node_plural": "__count__ nodes",
"flow": "__count__ flow",
"flow_plural": "__count__ flows",
"subflow": "__count__ subflow",
@@ -279,7 +276,6 @@
"deleteSubflow": "delete subflow",
"info": "Description",
"category": "Category",
"format":"markdown format",
"errors": {
"noNodesSelected": "<strong>Cannot create subflow</strong>: no nodes selected",
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
@@ -464,7 +460,6 @@
"update": "Update"
}
}
}
},
"sidebar": {
@@ -719,7 +714,20 @@
"format": "format JSON"
},
"markdownEditor": {
"title": "Markdown editor"
"title": "Markdown editor",
"format": "Formatted with markdown",
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"bold": "Bold",
"italic": "Italic",
"code": "Code",
"ordered-list": "Ordered list",
"unordered-list": "Unordered list",
"quote": "Quote",
"link": "Link",
"horizontal-rule": "Horizontal rule",
"toggle-preview": "Toggle preview"
},
"bufferEditor": {
"title": "Buffer editor",

View File

@@ -115,7 +115,6 @@
"args": "array",
"desc": "Returns the mean value of an `array` of numbers. It is an error if the input `array` contains an item which isn't a number."
},
"$boolean": {
"args": "arg",
"desc": "Casts the argument to a Boolean using the following rules:\n\n - `Boolean` : unchanged\n - `string`: empty : `false`\n - `string`: non-empty : `true`\n - `number`: `0` : `false`\n - `number`: non-zero : `true`\n - `null` : `false`\n - `array`: empty : `false`\n - `array`: contains a member that casts to `true` : `true`\n - `array`: all members cast to `false` : `false`\n - `object`: empty : `false`\n - `object`: non-empty : `true`\n - `function` : `false`"
@@ -219,5 +218,18 @@
"$env": {
"args": "arg",
"desc": "Returns the value of an environment variable.\n\nThis is a Node-RED defined function."
},
"$eval": {
"args": "expr [, context]",
"desc": "Parses and evaluates the string `expr` which contains literal JSON or a JSONata expression using the current context as the context for evaluation."
},
"$formatInteger": {
"args": "number, picture",
"desc": "Casts the `number` to a string and formats it to an integer representation as specified by the `picture` string. The picture string parameter defines how the number is formatted and has the same syntax as `fn:format-integer` from the XPath F&O 3.1 specification."
},
"$parseInteger": {
"args": "string, picture",
"desc": "Parses the contents of the `string` parameter to an integer (as a JSON number) using the format specified by the `picture` string. The `picture` string parameter has the same format as `$formatInteger`."
}
}

20
packages/node_modules/@node-red/editor-client/locales/ja/editor.json vendored Normal file → Executable file
View File

@@ -27,8 +27,7 @@
"status": "状態",
"enabled": "有効",
"disabled": "無効",
"info": "詳細",
"tip": "マークダウン形式で記述した「詳細」は「情報タブ」に表示されます。"
"info": "詳細"
},
"menu": {
"label": {
@@ -154,7 +153,6 @@
"node_plural": "__count__ 個のノード",
"configNode": "__count__ 個の設定ノード",
"configNode_plural": "__count__ 個の設定ノード",
"node_plural": "__count__ 個のノード",
"flow": "__count__ 個のフロー",
"flow_plural": "__count__ 個のフロー",
"subflow": "__count__ 個のサブフロー",
@@ -278,7 +276,6 @@
"deleteSubflow": "サブフローを削除",
"info": "詳細",
"category": "カテゴリ",
"format": "マークダウン形式",
"errors": {
"noNodesSelected": "<strong>サブフローを作成できません</strong>: ノードが選択されていません",
"multipleInputsToSelection": "<strong>サブフローを作成できません</strong>: 複数の入力が選択されています"
@@ -717,7 +714,20 @@
"format": "JSONフォーマット"
},
"markdownEditor": {
"title": "マークダウンエディタ"
"title": "マークダウンエディタ",
"format": "マークダウン形式で記述",
"heading1": "見出しレベル1",
"heading2": "見出しレベル2",
"heading3": "見出しレベル3",
"bold": "太字",
"italic": "斜体",
"code": "コード",
"ordered-list": "箇条書き(番号付き)",
"unordered-list": "箇条書き",
"quote": "引用",
"link": "リンク",
"horizontal-rule": "区切り線",
"toggle-preview": "プレビュー表示切替え"
},
"bufferEditor": {
"title": "バッファエディタ",

View File

@@ -22,8 +22,7 @@
"status": "状态",
"enabled": "有效",
"disabled": "无效",
"info": "详细描述",
"tip": "详细描述支持Markdown轻量级标记语言并将出现在信息标签中。"
"info": "详细描述"
},
"menu": {
"label": {
@@ -191,7 +190,6 @@
"output": "输出:",
"deleteSubflow": "删除子流程",
"info": "详细描述",
"format": "标记格式",
"errors": {
"noNodesSelected": "<strong>无法创建子流程</strong>: 未选择节点",
"multipleInputsToSelection": "<strong>无法创建子流程</strong>: 多个输入到了选择"

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/editor-client",
"version": "0.20.0-beta.2",
"version": "0.20.0-beta.3",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@@ -75,29 +75,39 @@ RED.comms = (function() {
}
ws.onmessage = function(event) {
var message = JSON.parse(event.data);
for (var m = 0; m < message.length; m++) {
var msg = message[m];
if (pendingAuth && msg.auth) {
if (msg.auth === "ok") {
if (message.auth) {
if (pendingAuth) {
if (message.auth === "ok") {
pendingAuth = false;
completeConnection();
} else if (msg.auth === "fail") {
} else if (message.auth === "fail") {
// anything else is an error...
active = false;
RED.user.login({updateMenu:true},function() {
connectWS();
})
}
} else if (message.auth === "fail") {
// Our current session has expired
active = false;
RED.user.login({updateMenu:true},function() {
connectWS();
})
}
else if (msg.topic) {
for (var t in subscriptions) {
if (subscriptions.hasOwnProperty(t)) {
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
if (re.test(msg.topic)) {
var subscribers = subscriptions[t];
if (subscribers) {
for (var i=0;i<subscribers.length;i++) {
subscribers[i](msg.topic,msg.data);
} else {
// Otherwise, 'message' is an array of actual comms messages
for (var m = 0; m < message.length; m++) {
var msg = message[m];
if (msg.topic) {
for (var t in subscriptions) {
if (subscriptions.hasOwnProperty(t)) {
var re = new RegExp("^"+t.replace(/([\[\]\?\(\)\\\\$\^\*\.|])/g,"\\$1").replace(/\+/g,"[^/]+").replace(/\/#$/,"(\/.*)?")+"$");
if (re.test(msg.topic)) {
var subscribers = subscriptions[t];
if (subscribers) {
for (var i=0;i<subscribers.length;i++) {
subscribers[i](msg.topic,msg.data);
}
}
}
}
@@ -132,10 +142,10 @@ RED.comms = (function() {
connectWS();
} else {
var msg = RED._("notification.errors.lostConnectionReconnect",{time: connectCountdown})+' <a href="#">'+ RED._("notification.errors.lostConnectionTry")+'</a>';
errornotification.update(msg);
errornotification.update(msg,{silent:true});
$(errornotification).find("a").click(function(e) {
e.preventDefault();
errornotification.update(RED._("notification.errors.lostConnection"));
errornotification.update(RED._("notification.errors.lostConnection"),{silent:true});
clearInterval(connectCountdownTimer);
connectWS();
})

View File

@@ -296,6 +296,7 @@ RED.history = (function() {
});
RED.nodes.dirty(ev.dirty);
RED.view.select(null);
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();

View File

@@ -391,7 +391,7 @@ var RED = (function() {
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
RED.notify(RED._("palette.event.nodeDisabled", {count:msg.types.length})+typeList,"success");
}
} else if (topic == "node/upgraded") {
} else if (topic == "notification/node/upgraded") {
RED.notify(RED._("palette.event.nodeUpgraded", {module:msg.module,version:msg.version}),"success");
RED.nodes.registry.setModulePendingUpdated(msg.module,msg.version);
}

View File

@@ -1242,7 +1242,7 @@ RED.text.format = (function() {
element.dispatchEvent(event);
return;
}
var range = selection.getRangeAt(0);
var tempRange = range.cloneRange(), startNode, startOffset;
startNode = range.startContainer;
@@ -1304,7 +1304,7 @@ RED.text.format = (function() {
}
return {
/**
/*!
* Returns the HTML representation of a given structured text
* @param text - the structured text
* @param type - could be one of filepath, url, email
@@ -1315,7 +1315,7 @@ RED.text.format = (function() {
getHtml: function (text, type, args, isRtl, locale) {
return getHandler(type).format(text, args, isRtl, true, locale);
},
/**
/*!
* Handle Structured text correct display for a given HTML element.
* @param element - the element : should be of type div contenteditable=true
* @param type - could be one of filepath, url, email

View File

@@ -72,7 +72,7 @@ RED.clipboard = (function() {
$("#clipboard-export").select();
document.execCommand("copy");
document.getSelection().removeAllRanges();
RED.notify(RED._("clipboard.nodesExported"));
RED.notify(RED._("clipboard.nodesExported"),{id:"clipboard"});
$( this ).dialog( "close" );
}
},

View File

@@ -38,7 +38,7 @@ RED.popover = (function() {
var direction = options.direction || "right";
var trigger = options.trigger;
var content = options.content;
var delay = options.delay;
var delay = options.delay || { show: 750, hide: 50 };
var autoClose = options.autoClose;
var width = options.width||"auto";
var size = options.size||"default";
@@ -172,6 +172,18 @@ RED.popover = (function() {
openPopup();
}
});
if (autoClose) {
target.on('mouseleave disabled', function(e) {
if (timer) {
clearTimeout(timer);
}
if (active) {
active = false;
setTimeout(closePopup,autoClose);
}
});
}
} else if (trigger === 'modal') {
$(document).on('mousedown.modal-popover-close', function (event) {
var target = event.target;

View File

@@ -66,6 +66,14 @@ RED.stack = (function() {
}
}
entry.expand();
} else if (entries.length === 2) {
if (entries[0] === entry) {
entries[0].collapse();
entries[1].expand();
} else {
entries[1].collapse();
entries[0].expand();
}
}
} else {
entry.toggle();

View File

@@ -0,0 +1,177 @@
/**
* 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.
**/
(function($) {
/**
* options:
* - data : array - initial items to display in tree
*
* methods:
* - data(items) - clears existing items and replaces with new data
*
* events:
* - treelistselect : function(event, item) {}
*
*
* data:
* [
* {
* label: 'Local', // label for the item
* icon: 'fa fa-rocket', // (optional) icon for the item
* selected: true/false, // (optional) if present, display checkbox accordingly
* children: [] | function(done) // (optional) an array of child items, or a function
* // that will call the `done` callback with an array
* // of child items
* }
* ]
*
*
*
* var treeList = $("<div>").css({width: "100%", height: "100%"}).treeList({data:[...]})
* treeList.on('treelistselect', function(e,item) { console.log(item)})
* treeList.treeList('data',[ ... ] )
*
*/
$.widget( "nodered.treeList", {
_create: function() {
var that = this;
this.element.addClass('red-ui-treeList');
var wrapper = $('<div>',{class:'red-ui-treeList-container'}).appendTo(this.element);
this._data = [];
this._topList = $('<ol>').css({
position:'absolute',
top: 0,
left:0,
right:0,
bottom:0
}).appendTo(wrapper).editableList({
addButton: false,
scrollOnAdd: false,
height: '100%',
addItem: function(container,i,item) {
that._addSubtree(container,item,0);
}
});
if (this.options.data) {
this.data(this.options.data);
}
},
_addChildren: function(container,children,depth) {
var that = this;
var subtree = $('<ol>').appendTo(container).editableList({
addButton: false,
scrollOnAdd: false,
height: 'auto',
addItem: function(container,i,item) {
that._addSubtree(container,item,depth+1);
}
});
for (var i=0;i<children.length;i++) {
subtree.editableList('addItem',children[i])
}
},
_addSubtree: function(container, item, depth) {
var that = this;
var labelNodeType = "<label>";
if (item.children && item.hasOwnProperty('selected')) {
labelNodeType = "<div>";
}
var label = $(labelNodeType,{tabindex:"0",class:"red-ui-treeList-label"}).appendTo(container);
if (item.class) {
label.addClass(item.class);
}
label.css({
paddingLeft: (depth*15)+'px'
})
label.on('mouseover',function(e) { that._trigger('itemmouseover',e,item); })
label.on('mouseout',function(e) { that._trigger('itemmouseout',e,item); })
if (item.children) {
$('<span class="red-ui-treeList-icon"><i class="fa fa-angle-right" /></span>').appendTo(label);
// $('<span class="red-ui-treeList-icon"><i class="fa fa-folder-o" /></span>').appendTo(label);
label.click(function(e) {
if (!container.hasClass("built") && typeof item.children === 'function') {
container.addClass('built');
var childrenAdded = false;
var spinner;
item.children(function(children) {
childrenAdded = true;
that._addChildren(container,children,depth);
if (spinner) {
spinner.remove();
}
});
if (!childrenAdded) {
spinner = $('<div class="red-ui-treeList-spinner">').css({
"background-position": (35+depth*15)+'px 50%'
}).appendTo(container);
}
}
container.toggleClass("expanded");
})
} else {
$('<span class="red-ui-treeList-icon"></span>').appendTo(label);
}
if (item.hasOwnProperty('selected')) {
var selectWrapper = $('<span class="red-ui-treeList-icon"></span>').appendTo(label);
var cb = $('<input type="checkbox">').prop('checked',item.selected).appendTo(selectWrapper);
cb.on('click', function(e) {
e.stopPropagation();
});
cb.on('change', function(e) {
item.selected = this.checked;
that._trigger("select",e,item);
})
} else if (!item.children) {
label.click(function(e) {
that._trigger("select",e,item)
})
}
if (item.icon) {
$('<span class="red-ui-treeList-icon"><i class="'+item.icon+'" /></span>').appendTo(label);
}
$('<span class="red-ui-treeList-label-text"></span>').html(item.label).appendTo(label);
if (item.children) {
if (Array.isArray(item.children)) {
that._addChildren(container,item.children,depth);
}
if (item.expanded) {
label.click();
}
}
},
empty: function() {
this._topList.editableList('empty');
},
data: function(items) {
if (items !== undefined) {
this._data = items;
this._topList.editableList('empty');
for (var i=0; i<items.length;i++) {
this._topList.editableList('addItem',items[i]);
}
} else {
return this._data;
}
}
});
})(jQuery);

View File

@@ -340,7 +340,7 @@ RED.deploy = (function() {
var unusedConfigNodes = [];
RED.nodes.eachConfig(function(node) {
if (node.users.length === 0 && (node._def.hasUsers !== false)) {
if ((node._def.hasUsers !== false) && (node.users.length === 0)) {
unusedConfigNodes.push(getNodeInfo(node));
hasUnusedConfig = true;
}

View File

@@ -13,6 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
/**
* @namespace RED.editor
*/
RED.editor = (function() {
@@ -21,6 +25,8 @@ RED.editor = (function() {
var editing_config_node = null;
var subflowEditor;
var customEditTypes = {};
var editTrayWidthCache = {};
function getCredentialsURL(nodeType, nodeID) {
@@ -124,6 +130,9 @@ RED.editor = (function() {
if (/^\$\([a-zA-Z_][a-zA-Z0-9_]*\)$/.test(value)) {
return true;
}
if (/^\$\{[a-zA-Z_][a-zA-Z0-9_]*\}$/.test(value)) {
return true;
}
if ("required" in definition[property] && definition[property].required) {
valid = value !== "";
}
@@ -929,7 +938,7 @@ RED.editor = (function() {
function buildDescriptionForm(container,node) {
var dialogForm = $('<form class="dialog-form form-horizontal" autocomplete="off"></form>').appendTo(container);
var toolbarRow = $('<div></div>').appendTo(dialogForm);
var row = $('<div class="form-row node-text-editor-row" style="position:relative; padding-top: 4px; height: calc(100% - 36px);"></div>').appendTo(dialogForm);
var row = $('<div class="form-row node-text-editor-row" style="position:relative; padding-top: 4px; height: 100%"></div>').appendTo(dialogForm);
$('<div style="height: 100%" class="node-text-editor" id="node-info-input-info-editor" ></div>').appendTo(row);
var nodeInfoEditor = RED.editor.createEditor({
id: "node-info-input-info-editor",
@@ -939,26 +948,6 @@ RED.editor = (function() {
if (node.info) {
nodeInfoEditor.getSession().setValue(node.info, -1);
}
var toolbar = RED.editor.types._markdown.buildToolbar(toolbarRow,nodeInfoEditor);
$('<button id="node-info-input-info-expand" class="editor-button" style="float: right;"><i class="fa fa-expand"></i></button>').appendTo(toolbar);
$('#node-info-input-info-expand').click(function(e) {
e.preventDefault();
var value = nodeInfoEditor.getValue();
RED.editor.editMarkdown({
value: value,
width: "Infinity",
cursor: nodeInfoEditor.getCursorPosition(),
complete: function(v,cursor) {
nodeInfoEditor.setValue(v, -1);
nodeInfoEditor.gotoLine(cursor.row+1,cursor.column,false);
setTimeout(function() {
nodeInfoEditor.focus();
},300);
}
})
});
return nodeInfoEditor;
}
@@ -1220,7 +1209,7 @@ RED.editor = (function() {
node.l = false;
} else {
// A link node - default state is false
if (node.hasOwnProperty('l')) {
if (node.hasOwnProperty('l') && node.l) {
changes.l = node.l
changed = true;
}
@@ -1230,7 +1219,7 @@ RED.editor = (function() {
// Checked - show label
if (!/^link (in|out)$/.test(node.type)) {
// Not a link node - default state is true
if (node.hasOwnProperty('l')) {
if (node.hasOwnProperty('l') && !node.l) {
changes.l = node.l
changed = true;
}
@@ -2157,7 +2146,7 @@ RED.editor = (function() {
}
function showTypeEditor(type, options) {
if (RED.editor.types.hasOwnProperty(type)) {
if (customEditTypes.hasOwnProperty(type)) {
if (editStack.length > 0) {
options.parent = editStack[editStack.length-1].id;
}
@@ -2166,12 +2155,99 @@ RED.editor = (function() {
options.onclose = function() {
editStack.pop();
}
RED.editor.types[type].show(options);
customEditTypes[type].show(options);
} else {
console.log("Unknown type editor:",type);
}
}
function createEditor(options) {
var el = options.element || $("#"+options.id)[0];
var toolbarRow = $("<div>").appendTo(el);
el = $("<div>").appendTo(el).addClass("node-text-editor-container")[0];
var editor = ace.edit(el);
editor.setTheme("ace/theme/tomorrow");
var session = editor.getSession();
session.on("changeAnnotation", function () {
var annotations = session.getAnnotations() || [];
var i = annotations.length;
var len = annotations.length;
while (i--) {
if (/doctype first\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); }
else if (/Unexpected End of file\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); }
}
if (len > annotations.length) { session.setAnnotations(annotations); }
});
if (options.mode) {
session.setMode(options.mode);
}
if (options.foldStyle) {
session.setFoldStyle(options.foldStyle);
} else {
session.setFoldStyle('markbeginend');
}
if (options.options) {
editor.setOptions(options.options);
} else {
editor.setOptions({
enableBasicAutocompletion:true,
enableSnippets:true,
tooltipFollowsMouse: false
});
}
if (options.readOnly) {
editor.setOption('readOnly',options.readOnly);
editor.container.classList.add("ace_read-only");
}
if (options.hasOwnProperty('lineNumbers')) {
editor.renderer.setOption('showGutter',options.lineNumbers);
}
editor.$blockScrolling = Infinity;
if (options.value) {
session.setValue(options.value,-1);
}
if (options.globals) {
setTimeout(function() {
if (!!session.$worker) {
session.$worker.send("setOptions", [{globals: options.globals, esversion:6, sub:true, asi:true, maxerr:1000}]);
}
},100);
}
if (options.mode === 'ace/mode/markdown') {
$(el).addClass("node-text-editor-container-toolbar");
editor.toolbar = customEditTypes['_markdown'].buildToolbar(toolbarRow,editor);
if (options.expandable !== false) {
var expandButton = $('<button class="editor-button" style="float: right;"><i class="fa fa-expand"></i></button>').appendTo(editor.toolbar);
expandButton.click(function(e) {
e.preventDefault();
var value = editor.getValue();
RED.editor.editMarkdown({
value: value,
width: "Infinity",
cursor: editor.getCursorPosition(),
complete: function(v,cursor) {
editor.setValue(v, -1);
editor.gotoLine(cursor.row+1,cursor.column,false);
setTimeout(function() {
editor.focus();
},300);
}
})
});
}
var helpButton = $('<button class="node-text-editor-help editor-button editor-button-small"><i class="fa fa-question"></i></button>').appendTo($(el).parent());
RED.popover.create({
target: helpButton,
trigger: 'click',
size: "small",
direction: "left",
content: RED._("markdownEditor.format"),
autoClose: 50
});
}
return editor;
}
return {
init: function() {
@@ -2184,14 +2260,7 @@ RED.editor = (function() {
$("#node-dialog-cancel").click();
$("#node-config-dialog-cancel").click();
});
for (var type in RED.editor.types) {
if (RED.editor.types.hasOwnProperty(type)) {
RED.editor.types[type].init();
}
}
},
types: {},
edit: showEditDialog,
editConfig: showEditConfigNodeDialog,
editSubflow: showEditSubflowDialog,
@@ -2204,57 +2273,32 @@ RED.editor = (function() {
validateNode: validateNode,
updateNodeProperties: updateNodeProperties, // TODO: only exposed for edit-undo
/**
* Show a type editor.
* @param {string} type - the type to display
* @param {object} options - options for the editor
* @function
* @memberof RED.editor
*/
showTypeEditor: showTypeEditor,
createEditor: function(options) {
var editor = ace.edit(options.id||options.element);
editor.setTheme("ace/theme/tomorrow");
var session = editor.getSession();
session.on("changeAnnotation", function () {
var annotations = session.getAnnotations() || [];
var i = annotations.length;
var len = annotations.length;
while (i--) {
if (/doctype first\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); }
else if (/Unexpected End of file\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); }
}
if (len > annotations.length) { session.setAnnotations(annotations); }
});
if (options.mode) {
session.setMode(options.mode);
}
if (options.foldStyle) {
session.setFoldStyle(options.foldStyle);
} else {
session.setFoldStyle('markbeginend');
}
if (options.options) {
editor.setOptions(options.options);
} else {
editor.setOptions({
enableBasicAutocompletion:true,
enableSnippets:true,
tooltipFollowsMouse: false
});
}
if (options.readOnly) {
editor.setOption('readOnly',options.readOnly);
editor.container.classList.add("ace_read-only");
}
if (options.hasOwnProperty('lineNumbers')) {
editor.renderer.setOption('showGutter',options.lineNumbers);
}
editor.$blockScrolling = Infinity;
if (options.value) {
session.setValue(options.value,-1);
}
if (options.globals) {
setTimeout(function() {
if (!!session.$worker) {
session.$worker.send("setOptions", [{globals: options.globals, esversion:6, sub:true, asi:true, maxerr:1000}]);
}
},100);
}
return editor;
}
/**
* Register a type editor.
* @param {string} type - the type name
* @param {object} options - the editor definition
* @function
* @memberof RED.editor
*/
registerTypeEditor: function(type, definition) {
customEditTypes[type] = definition;
},
/**
* Create a editor ui component
* @param {object} options - the editor options
* @function
* @memberof RED.editor
*/
createEditor: createEditor
}
})();

View File

@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._buffer = (function() {
(function() {
var template = '<script type="text/x-red" data-template-name="_buffer"><div id="node-input-buffer-panels"><div id="node-input-buffer-panel-str" class="red-ui-panel"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><span class="node-input-buffer-type"><i class="fa fa-exclamation-circle"></i> <span id="node-input-buffer-type-string" data-i18n="bufferEditor.modeString"></span><span id="node-input-buffer-type-array" data-i18n="bufferEditor.modeArray"></span></span></div><div class="form-row node-text-editor-row"><div class="node-text-editor" id="node-input-buffer-str"></div></div></div><div id="node-input-buffer-panel-bin" class="red-ui-panel"><div class="form-row node-text-editor-row" style="margin-top: 10px"><div class="node-text-editor" id="node-input-buffer-bin"></div></div></div></div></script>';
@@ -45,10 +44,7 @@ RED.editor.types._buffer = (function() {
}
return {
init: function() {
$(template).appendTo(document.body);
},
var definition = {
show: function(options) {
var value = options.value;
var onComplete = options.complete;
@@ -206,4 +202,7 @@ RED.editor.types._buffer = (function() {
RED.tray.show(trayOptions);
}
}
$(template).appendTo(document.body);
RED.editor.registerTypeEditor("_buffer", definition);
})();

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._expression = (function() {
(function() {
var template = '<script type="text/x-red" data-template-name="_expression">'+
@@ -46,10 +46,7 @@ RED.editor.types._expression = (function() {
'</script>';
var expressionTestCache = {};
return {
init: function() {
$(template).appendTo(document.body);
},
var definition = {
show: function(options) {
var expressionTestCacheId = options.parent||"_";
var value = options.value;
@@ -349,4 +346,6 @@ RED.editor.types._expression = (function() {
RED.tray.show(trayOptions);
}
}
$(template).appendTo(document.body);
RED.editor.registerTypeEditor("_expression", definition);
})();

View File

@@ -13,15 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._js = (function() {
(function() {
var template = '<script type="text/x-red" data-template-name="_js"><div class="form-row node-text-editor-row"><div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-js"></div></div></script>';
return {
init: function() {
$(template).appendTo(document.body);
},
var definition = {
show: function(options) {
var value = options.value;
var onComplete = options.complete;
@@ -99,4 +96,7 @@ RED.editor.types._js = (function() {
RED.tray.show(trayOptions);
}
}
$(template).appendTo(document.body);
RED.editor.registerTypeEditor("_js", definition);
})();

View File

@@ -13,15 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._json = (function() {
(function() {
var template = '<script type="text/x-red" data-template-name="_json"><div class="form-row" style="margin-bottom: 3px; text-align: right;"><button id="node-input-json-reformat" class="editor-button editor-button-small"><span data-i18n="jsonEditor.format"></span></button></div><div class="form-row node-text-editor-row"><div style="height: 200px;min-height: 150px;" class="node-text-editor" id="node-input-json"></div></div></script>';
return {
init: function() {
$(template).appendTo(document.body);
},
var definition = {
show: function(options) {
var value = options.value;
var onComplete = options.complete;
@@ -115,4 +112,6 @@ RED.editor.types._json = (function() {
RED.tray.show(trayOptions);
}
}
$(template).appendTo(document.body);
RED.editor.registerTypeEditor("_json", definition);
})();

View File

@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.editor.types._markdown = (function() {
(function() {
var toolbarTemplate = '<div style="margin-bottom: 5px">'+
'<span class="button-group">'+
@@ -41,7 +40,7 @@ RED.editor.types._markdown = (function() {
'<div id="node-input-markdown-panel-editor" class="red-ui-panel">'+
'<div style="height: 100%; margin: auto; max-width: 1000px;">'+
'<div id="node-input-markdown-toolbar"></div>'+
'<div class="node-text-editor" style="height: calc(100% - 50px)" id="node-input-markdown"></div>'+
'<div class="node-text-editor" style="height: 100%" id="node-input-markdown"></div>'+
'</div>'+
'</div>'+
'<div class="red-ui-panel">'+
@@ -52,24 +51,7 @@ RED.editor.types._markdown = (function() {
var panels;
var styleActions = {
'h1': { newline: true, before:"# ", tooltip:"Heading 1"},
'h2': { newline: true, before:"## ", tooltip:"Heading 2"},
'h3': { newline: true, before:"### ", tooltip:"Heading 3"},
'b': { before:"**", after: "**", tooltip: "Bold" },
'i': { before:"_", after: "_", tooltip: "Italic" },
'code': { before:"`", after: "`", tooltip: "Code" },
'ol': { before:" * ", newline: true, tooltip: "Ordered list" },
'ul': { before:" - ", newline: true, tooltip: "Unordered list" },
'bq': { before:"> ", newline: true, tooltip: "Quote" },
'link': { before:"[", after: "]()", tooltip: "Link"},
'hr': { before:"\n---\n\n", tooltip: "Horizontal rule" }
}
return {
init: function() {
$(template).appendTo(document.body);
},
var definition = {
show: function(options) {
var value = options.value;
var onComplete = options.complete;
@@ -112,7 +94,8 @@ RED.editor.types._markdown = (function() {
expressionEditor = RED.editor.createEditor({
id: 'node-input-markdown',
value: value,
mode:"ace/mode/markdown"
mode:"ace/mode/markdown",
expandable: false
});
var changeTimer;
expressionEditor.getSession().on("change", function() {
@@ -138,11 +121,10 @@ RED.editor.types._markdown = (function() {
}
});
panels.ratio(1);
var toolbar = RED.editor.types._markdown.buildToolbar($("#node-input-markdown-toolbar"), expressionEditor);
$('<span class="button-group" style="float:right">'+
'<button id="node-btn-markdown-preview" class="editor-button toggle single"><i class="fa fa-eye"></i></button>'+
'</span>').appendTo(toolbar);
'</span>').appendTo(expressionEditor.toolbar);
$("#node-btn-markdown-preview").click(function(e) {
e.preventDefault();
@@ -154,7 +136,7 @@ RED.editor.types._markdown = (function() {
panels.ratio(0.5);
}
});
RED.popover.tooltip($("#node-btn-markdown-preview"),"Toggle preview");
RED.popover.tooltip($("#node-btn-markdown-preview"), RED._("markdownEditor.toggle-preview"));
if (options.cursor) {
expressionEditor.gotoLine(options.cursor.row+1,options.cursor.column,false);
@@ -174,6 +156,19 @@ RED.editor.types._markdown = (function() {
},
buildToolbar: function(container, editor) {
var styleActions = {
'h1': { newline: true, before:"# ", tooltip:RED._("markdownEditor.heading1")},
'h2': { newline: true, before:"## ", tooltip:RED._("markdownEditor.heading2")},
'h3': { newline: true, before:"### ", tooltip:RED._("markdownEditor.heading3")},
'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")},
'ul': { before:" - ", newline: true, tooltip: RED._("markdownEditor.unordered-list")},
'bq': { before:"> ", newline: true, tooltip: RED._("markdownEditor.quote")},
'link': { before:"[", after: "]()", tooltip: RED._("markdownEditor.link")},
'hr': { before:"\n---\n\n", tooltip: RED._("markdownEditor.horizontal-rule")}
}
var toolbar = $(toolbarTemplate).appendTo(container);
toolbar.find('button[data-style]').each(function(el) {
var style = styleActions[$(this).data('style')];
@@ -212,4 +207,6 @@ RED.editor.types._markdown = (function() {
return toolbar;
}
}
$(template).appendTo(document.body);
RED.editor.registerTypeEditor("_markdown", definition);
})();

View File

@@ -458,7 +458,10 @@ RED.library = (function() {
click: function() {
//TODO: move this to RED.library
var flowName = $("#node-input-library-filename").val();
if (!/^\s*$/.test(flowName)) {
flowName = flowName.trim();
if(flowName === "" || flowName.endsWith("/")) {
RED.notify(RED._("library.invalidFilename"),"warning");
} else {
$.ajax({
url:'library/flows/'+flowName,
type: "POST",

View File

@@ -56,6 +56,11 @@ RED.notifications = (function() {
type = options.type;
}
if (options.id && persistentNotifications.hasOwnProperty(options.id)) {
persistentNotifications[options.id].update(msg,options);
return persistentNotifications[options.id];
}
if (options.modal) {
$("#full-shade").show();
}
@@ -181,7 +186,9 @@ RED.notifications = (function() {
if (typeof options === 'number') {
timeout = options;
} else if (options !== undefined) {
timeout = options.timeout;
if (!options.fixed) {
timeout = options.timeout || 5000;
}
if (options.buttons) {
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(nn)
options.buttons.forEach(function(buttonDef) {
@@ -203,6 +210,11 @@ RED.notifications = (function() {
}
if (nn.hidden) {
nn.showNotification();
} else if (!options || !options.silent){
$(nn).addClass("notification-shake-horizontal");
setTimeout(function() {
$(nn).removeClass("notification-shake-horizontal");
},300);
}
}
@@ -221,7 +233,9 @@ RED.notifications = (function() {
currentNotifications.push(n);
if (options.id) {
persistentNotifications[options.id] = n;
notificationButtonWrapper.show();
if (options.fixed) {
notificationButtonWrapper.show();
}
}
c+=1;
return n;

View File

@@ -289,7 +289,7 @@ RED.palette.editor = (function() {
}
if (moduleInfo.pending_version) {
nodeEntry.versionSpan.html(moduleInfo.version+' <i class="fa fa-long-arrow-right"></i> '+moduleInfo.pending_version).appendTo(nodeEntry.metaRow)
nodeEntry.updateButton.text(RED._('palette.editor.updated')).addClass('disabled').show();
nodeEntry.updateButton.text(RED._('palette.editor.updated')).addClass('disabled').css('display', 'inline-block');
} else if (loadedIndex.hasOwnProperty(module)) {
if (semVerCompare(loadedIndex[module].version,moduleInfo.version) === 1) {
nodeEntry.updateButton.show();

View File

@@ -21,7 +21,7 @@ RED.projects = (function() {
var activeProject;
function reportUnexpectedError(error) {
var notification;
if (error.error === 'git_missing_user') {
if (error.code === 'git_missing_user') {
notification = RED.notify("<p>"+RED._("projects.errors.no-username-email")+"</p>",{
fixed: true,
type:'error',
@@ -43,7 +43,7 @@ RED.projects = (function() {
})
} else {
console.log(error);
notification = RED.notify("<p>"+RED._("projects.errors.unexpected")+":</p><p>"+error.message+"</p><small>"+RED._("projects.errors.code")+": "+error.error+"</small>",{
notification = RED.notify("<p>"+RED._("projects.errors.unexpected")+":</p><p>"+error.message+"</p><small>"+RED._("projects.errors.code")+": "+error.code+"</small>",{
fixed: true,
modal: true,
type: 'error',
@@ -546,7 +546,7 @@ RED.projects = (function() {
subrow = $('<div style="text-align: center">').appendTo(sshwarningRow);
$('<button class="editor-button">'+RED._("projects.clone-project.ssh-key-add")+'</button>').appendTo(subrow).click(function(e) {
e.preventDefault();
$('#projects-dialog-cancel').click();
dialog.dialog( "close" );
RED.userSettings.show('gitconfig');
setTimeout(function() {
$("#user-settings-gitconfig-add-key").click();
@@ -1507,7 +1507,7 @@ RED.projects = (function() {
return switchProject(selectedProject.name,function(err,data) {
dialog.dialog( "close" );
if (err) {
if (err.error !== 'credentials_load_failed') {
if (err.code !== 'credentials_load_failed') {
console.log(RED._("projects.create.unexpected_error"),err)
}
}
@@ -1893,7 +1893,6 @@ RED.projects = (function() {
function sendRequest(options,body) {
// dialogBody.hide();
// console.log(options.url,body);
if (options.requireCleanWorkspace && RED.nodes.dirty()) {
var thenCallback;
var alwaysCallback;
@@ -1952,7 +1951,7 @@ RED.projects = (function() {
resultCallback = responses;
resultCallbackArgs = {error:responses.statusText};
return;
} else if (options.handleAuthFail !== false && xhr.responseJSON.error === 'git_auth_failed') {
} else if (options.handleAuthFail !== false && xhr.responseJSON.code === 'git_auth_failed') {
var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch;
var message = $('<div>'+
@@ -2040,8 +2039,8 @@ RED.projects = (function() {
]
});
return;
} else if (responses[xhr.responseJSON.error]) {
resultCallback = responses[xhr.responseJSON.error];
} else if (responses[xhr.responseJSON.code]) {
resultCallback = responses[xhr.responseJSON.code];
resultCallbackArgs = xhr.responseJSON;
return;
} else if (responses['*']) {
@@ -2050,10 +2049,12 @@ RED.projects = (function() {
return;
}
}
console.log(responses)
console.log(RED._("projects.send-req.unhandled")+":");
console.log(xhr);
console.log(textStatus);
console.log(err);
console.log(stack);
}).always(function() {
var delta = Date.now() - start;
delta = Math.max(0,500-delta);

View File

@@ -140,11 +140,12 @@ RED.tray = (function() {
// tray.body.parent().width(Math.min($("#editor-stack").position().left-8,tray.width));
$("#main-container").scrollLeft(0);
el.css({
right: -(el.width()+10)+"px",
transition: "right 0.25s ease"
});
$("#workspace").scrollLeft(0);
handleWindowResize();
openingTray = true;
setTimeout(function() {

View File

@@ -200,7 +200,7 @@ RED.typeSearch = (function() {
dialog.hide();
searchResultsDiv.hide();
}
refreshTypeList();
refreshTypeList(opts);
addCallback = opts.add;
closeCallback = opts.close;
RED.events.emit("type-search:open");
@@ -254,21 +254,29 @@ RED.typeSearch = (function() {
return 1;
}
}
function refreshTypeList() {
function applyFilter(filter,type,def) {
return !filter ||
(
(!filter.type || type === filter.type) &&
(!filter.input || def.inputs > 0) &&
(!filter.output || def.outputs > 0)
)
}
function refreshTypeList(opts) {
var i;
searchResults.editableList('empty');
searchInput.searchBox('value','');
selected = -1;
var common = [
'inject','debug','function','change','switch'
];
].filter(function(t) { return applyFilter(opts.filter,t,RED.nodes.getType(t)); });
var recentlyUsed = Object.keys(typesUsed);
recentlyUsed.sort(function(a,b) {
return typesUsed[b]-typesUsed[a];
});
recentlyUsed = recentlyUsed.filter(function(t) {
return common.indexOf(t) === -1;
return applyFilter(opts.filter,t,RED.nodes.getType(t)) && common.indexOf(t) === -1;
});
var items = [];
@@ -313,8 +321,10 @@ RED.typeSearch = (function() {
searchResults.editableList('addItem', item);
}
for (i=0;i<items.length;i++) {
items[i].i = index++;
searchResults.editableList('addItem', items[i]);
if (applyFilter(opts.filter,items[i].type,items[i].def)) {
items[i].i = index++;
searchResults.editableList('addItem', items[i]);
}
}
setTimeout(function() {
selected = 0;

View File

@@ -42,6 +42,7 @@ RED.view = (function() {
var activeNodes = [];
var activeLinks = [];
var activeFlowLinks = [];
var activeLinkNodes = {};
var selected_link = null,
mousedown_link = null,
@@ -61,7 +62,8 @@ RED.view = (function() {
clickElapsed = 0,
scroll_position = [],
quickAddActive = false,
quickAddLink = null;
quickAddLink = null,
showAllLinkPorts = -1;
var clipboard = "";
@@ -263,19 +265,41 @@ RED.view = (function() {
"stroke-width" : "1px"
});
}
var linkLayer = vis.append("g");
var dragGroup = vis.append("g");
var nodeLayer = vis.append("g");
var drag_lines = [];
function showDragLines(nodes) {
showAllLinkPorts = -1;
for (var i=0;i<nodes.length;i++) {
var node = nodes[i];
node.el = dragGroup.append("svg:path").attr("class", "drag_line");
if ((node.node.type === "link out" && node.portType === PORT_TYPE_OUTPUT) ||
(node.node.type === "link in" && node.portType === PORT_TYPE_INPUT)) {
node.el.attr("class","link_link drag_line");
node.virtualLink = true;
showAllLinkPorts = (node.portType === PORT_TYPE_OUTPUT)?PORT_TYPE_INPUT:PORT_TYPE_OUTPUT;
}
drag_lines.push(node);
}
if (showAllLinkPorts !== -1) {
activeNodes.forEach(function(n) {
if (n.type === "link in" || n.type === "link out") {
n.dirty = true;
}
})
}
}
function hideDragLines() {
if (showAllLinkPorts !== -1) {
activeNodes.forEach(function(n) {
if (n.type === "link in" || n.type === "link out") {
n.dirty = true;
}
})
}
showAllLinkPorts = -1;
while(drag_lines.length) {
var line = drag_lines.pop();
if (line.el) {
@@ -657,10 +681,21 @@ RED.view = (function() {
mouse_mode = RED.state.QUICK_JOINING;
$(window).on('keyup',disableQuickJoinEventHandler);
}
var filter = undefined;
if (drag_lines.length > 0) {
if (drag_lines[0].virtualLink) {
filter = {type:drag_lines[0].node.type === 'link in'?'link out':'link in'}
} else if (drag_lines[0].portType === PORT_TYPE_OUTPUT) {
filter = {input:true}
} else {
filter = {output:true}
}
}
quickAddActive = true;
RED.typeSearch.show({
x:d3.event.clientX-mainPos.left-node_width/2,
y:d3.event.clientY-mainPos.top-node_height/2,
filter: filter,
cancel: function() {
quickAddActive = false;
resetMouseVars();
@@ -685,19 +720,54 @@ RED.view = (function() {
if (quickAddLink || drag_lines.length > 0) {
var drag_line = quickAddLink||drag_lines[0];
var src = null,dst,src_port;
if (drag_line.portType === PORT_TYPE_OUTPUT && nn.inputs > 0) {
if (drag_line.portType === PORT_TYPE_OUTPUT && (nn.inputs > 0 || drag_line.virtualLink) ) {
src = drag_line.node;
src_port = drag_line.port;
dst = nn;
} else if (drag_line.portType === PORT_TYPE_INPUT && nn.outputs > 0) {
} else if (drag_line.portType === PORT_TYPE_INPUT && (nn.outputs > 0 || drag_line.virtualLink)) {
src = nn;
dst = drag_line.node;
src_port = 0;
}
if (src !== null) {
var link = {source: src, sourcePort:src_port, target: dst};
RED.nodes.addLink(link);
historyEvent.links = [link];
if (drag_line.virtualLink) {
historyEvent = {
t:'multi',
events: [historyEvent]
}
var oldSrcLinks = $.extend(true,{},{v:src.links}).v
var oldDstLinks = $.extend(true,{},{v:dst.links}).v
src.links.push(dst.id);
dst.links.push(src.id);
src.dirty = true;
dst.dirty = true;
historyEvent.events.push({
t:'edit',
node: src,
dirty: RED.nodes.dirty(),
changed: src.changed,
changes: {
links:oldSrcLinks
}
});
historyEvent.events.push({
t:'edit',
node: dst,
dirty: RED.nodes.dirty(),
changed: dst.changed,
changes: {
links:oldDstLinks
}
});
src.changed = true;
dst.changed = true;
} else {
var link = {source: src, sourcePort:src_port, target: dst};
RED.nodes.addLink(link);
historyEvent.links = [link];
}
hideDragLines();
if (!quickAddLink && drag_line.portType === PORT_TYPE_OUTPUT && nn.outputs > 0) {
showDragLines([{node:nn,port:0,portType:PORT_TYPE_OUTPUT}]);
@@ -1216,9 +1286,15 @@ RED.view = (function() {
var currentLinks = activeLinks;
var addedLinkLinks = {};
activeFlowLinks = [];
var activeLinkNodeIds = Object.keys(activeLinkNodes);
activeLinkNodeIds.forEach(function(n) {
activeLinkNodes[n].dirty = true;
})
activeLinkNodes = {};
for (var i=0;i<moving_set.length;i++) {
if (moving_set[i].n.type === "link out" || moving_set[i].n.type === "link in") {
var linkNode = moving_set[i].n;
activeLinkNodes[linkNode.id] = linkNode;
var offFlowLinks = {};
linkNode.links.forEach(function(id) {
var target = RED.nodes.node(id);
@@ -1233,6 +1309,9 @@ RED.view = (function() {
link: true
});
addedLinkLinks[linkNode.id+":"+target.id] = true;
activeLinkNodes[target.id] = target;
target.dirty = true;
}
} else {
offFlowLinks[target.z] = offFlowLinks[target.z]||[];
@@ -1248,6 +1327,8 @@ RED.view = (function() {
link: true
});
addedLinkLinks[target.id+":"+linkNode.id] = true;
activeLinkNodes[target.id] = target;
target.dirty = true;
}
} else {
offFlowLinks[target.z] = offFlowLinks[target.z]||[];
@@ -1269,6 +1350,13 @@ RED.view = (function() {
}
}
}
if (activeFlowLinks.length === 0 && selected_link !== null && selected_link.link) {
activeLinks.push(selected_link);
activeLinkNodes[selected_link.source.id] = selected_link.source;
selected_link.source.dirty = true;
activeLinkNodes[selected_link.target.id] = selected_link.target;
selected_link.target.dirty = true;
}
} else {
selection.flows = workspaceSelection;
}
@@ -1445,22 +1533,63 @@ RED.view = (function() {
RED.nodes.dirty(true);
}
}
if (selected_link) {
RED.nodes.removeLink(selected_link);
removedLinks.push(selected_link);
var historyEvent;
if (selected_link && selected_link.link) {
var sourceId = selected_link.source.id;
var targetId = selected_link.target.id;
var sourceIdIndex = selected_link.target.links.indexOf(sourceId);
var targetIdIndex = selected_link.source.links.indexOf(targetId);
historyEvent = {
t:"multi",
events: [
{
t: "edit",
node: selected_link.source,
changed: selected_link.source.changed,
changes: {
links: $.extend(true,{},{v:selected_link.source.links}).v
}
},
{
t: "edit",
node: selected_link.target,
changed: selected_link.target.changed,
changes: {
links: $.extend(true,{},{v:selected_link.target.links}).v
}
}
],
dirty:RED.nodes.dirty()
}
selected_link.source.changed = true;
selected_link.target.changed = true;
selected_link.target.links.splice(sourceIdIndex,1);
selected_link.source.links.splice(targetIdIndex,1);
selected_link.source.dirty = true;
selected_link.target.dirty = true;
} else {
if (selected_link) {
RED.nodes.removeLink(selected_link);
removedLinks.push(selected_link);
}
RED.nodes.dirty(true);
historyEvent = {
t:"delete",
nodes:removedNodes,
links:removedLinks,
subflowOutputs:removedSubflowOutputs,
subflowInputs:removedSubflowInputs,
subflow: {
instances: subflowInstances
},
dirty:startDirty
};
}
var historyEvent = {
t:"delete",
nodes:removedNodes,
links:removedLinks,
subflowOutputs:removedSubflowOutputs,
subflowInputs:removedSubflowInputs,
subflow: {
instances: subflowInstances
},
dirty:startDirty
};
RED.history.push(historyEvent);
selected_link = null;
@@ -1507,7 +1636,7 @@ RED.view = (function() {
}
}
clipboard = JSON.stringify(nns);
RED.notify(RED._("clipboard.nodeCopied",{count:nns.length}));
RED.notify(RED._("clipboard.nodeCopied",{count:nns.length}),{id:"clipboard"});
}
}
@@ -1574,6 +1703,7 @@ RED.view = (function() {
if (d3.event.ctrlKey || d3.event.metaKey) {
mouse_mode = RED.state.QUICK_JOINING;
showDragLines([{node:mousedown_node,port:mousedown_port_index,portType:mousedown_port_type}]);
quickAddLink = null;
$(window).on('keyup',disableQuickJoinEventHandler);
}
}
@@ -1584,7 +1714,16 @@ RED.view = (function() {
function portMouseUp(d,portType,portIndex) {
var i;
if (mouse_mode === RED.state.QUICK_JOINING && drag_lines.length > 0) {
if (drag_lines[0].node===d) {
if (drag_lines[0].node === d) {
// Cannot quick-join to self
return
}
if (drag_lines[0].virtualLink &&
(
(drag_lines[0].node.type === 'link in' && d.type !== 'link out') ||
(drag_lines[0].node.type === 'link out' && d.type !== 'link in')
)
) {
return
}
}
@@ -1608,12 +1747,17 @@ RED.view = (function() {
}
var addedLinks = [];
var removedLinks = [];
var modifiedNodes = []; // joining link nodes
var select_link = null;
for (i=0;i<drag_lines.length;i++) {
if (drag_lines[i].link) {
removedLinks.push(drag_lines[i].link)
}
}
var linkEditEvents = [];
for (i=0;i<drag_lines.length;i++) {
if (portType != drag_lines[i].portType && mouseup_node !== drag_lines[i].node) {
var drag_line = drag_lines[i];
@@ -1627,21 +1771,75 @@ RED.view = (function() {
dst = drag_line.node;
src_port = portIndex;
}
var existingLink = RED.nodes.filterLinks({source:src,target:dst,sourcePort: src_port}).length !== 0;
if (!existingLink) {
var link = {source: src, sourcePort:src_port, target: dst};
RED.nodes.addLink(link);
addedLinks.push(link);
var link = {source: src, sourcePort:src_port, target: dst};
if (drag_line.virtualLink) {
if (/^link (in|out)$/.test(src.type) && /^link (in|out)$/.test(dst.type)) {
if (src.links.indexOf(dst.id) === -1 && dst.links.indexOf(src.id) === -1) {
var oldSrcLinks = $.extend(true,{},{v:src.links}).v
var oldDstLinks = $.extend(true,{},{v:dst.links}).v
src.links.push(dst.id);
dst.links.push(src.id);
src.dirty = true;
dst.dirty = true;
modifiedNodes.push(src);
modifiedNodes.push(dst);
link.link = true;
activeLinks.push(link);
activeLinkNodes[src.id] = src;
activeLinkNodes[dst.id] = dst;
select_link = link;
linkEditEvents.push({
t:'edit',
node: src,
dirty: RED.nodes.dirty(),
changed: src.changed,
changes: {
links:oldSrcLinks
}
});
linkEditEvents.push({
t:'edit',
node: dst,
dirty: RED.nodes.dirty(),
changed: dst.changed,
changes: {
links:oldDstLinks
}
});
src.changed = true;
dst.changed = true;
}
}
} else {
var existingLink = RED.nodes.filterLinks({source:src,target:dst,sourcePort: src_port}).length !== 0;
if (!existingLink) {
RED.nodes.addLink(link);
addedLinks.push(link);
}
}
}
}
if (addedLinks.length > 0 || removedLinks.length > 0) {
var historyEvent = {
t:"add",
links:addedLinks,
removedLinks: removedLinks,
dirty:RED.nodes.dirty()
};
if (addedLinks.length > 0 || removedLinks.length > 0 || modifiedNodes.length > 0) {
// console.log(addedLinks);
// console.log(removedLinks);
// console.log(modifiedNodes);
var historyEvent;
if (modifiedNodes.length > 0) {
historyEvent = {
t:"multi",
events: linkEditEvents,
dirty:RED.nodes.dirty()
};
} else {
historyEvent = {
t:"add",
links:addedLinks,
removedLinks: removedLinks,
dirty:RED.nodes.dirty()
};
}
if (activeSubflow) {
var subflowRefresh = RED.subflow.refresh(true);
if (subflowRefresh) {
@@ -1657,7 +1855,7 @@ RED.view = (function() {
RED.nodes.dirty(true);
}
if (mouse_mode === RED.state.QUICK_JOINING) {
if (addedLinks.length > 0) {
if (addedLinks.length > 0 || modifiedNodes.length > 0) {
hideDragLines();
if (portType === PORT_TYPE_INPUT && d.outputs > 0) {
showDragLines([{node:d,port:0,portType:PORT_TYPE_OUTPUT}]);
@@ -1666,6 +1864,11 @@ RED.view = (function() {
} else {
resetMouseVars();
}
selected_link = select_link;
mousedown_link = select_link;
if (select_link) {
updateSelection();
}
}
redraw();
return;
@@ -1673,7 +1876,11 @@ RED.view = (function() {
resetMouseVars();
hideDragLines();
selected_link = null;
selected_link = select_link;
mousedown_link = select_link;
if (select_link) {
updateSelection();
}
redraw();
}
}
@@ -1772,9 +1979,20 @@ RED.view = (function() {
});
return tooltip;
}
function portMouseOver(port,d,portType,portIndex) {
clearTimeout(portLabelHoverTimeout);
var active = (mouse_mode!=RED.state.JOINING || (drag_lines.length > 0 && drag_lines[0].portType !== portType));
var active = (mouse_mode!=RED.state.JOINING && mouse_mode != RED.state.QUICK_JOINING) || // Not currently joining - all ports active
(
drag_lines.length > 0 && // Currently joining
drag_lines[0].portType !== portType && // INPUT->OUTPUT OUTPUT->INPUT
(
!drag_lines[0].virtualLink || // Not a link wire
(drag_lines[0].node.type === 'link in' && d.type === 'link out') ||
(drag_lines[0].node.type === 'link out' && d.type === 'link in')
)
)
if (active && ((portType === PORT_TYPE_INPUT && ((d._def && d._def.inputLabels)||d.inputLabels)) || (portType === PORT_TYPE_OUTPUT && ((d._def && d._def.outputLabels)||d.outputLabels)))) {
portLabelHoverTimeout = setTimeout(function() {
var tooltip = getPortLabel(d,portType,portIndex);
@@ -1815,7 +2033,24 @@ RED.view = (function() {
return;
}
var direction = d._def? (d.inputs > 0 ? 1: 0) : (d.direction == "in" ? 0: 1)
var wasJoining = false;
if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) {
wasJoining = true;
if (drag_lines.length > 0) {
if (drag_lines[0].virtualLink) {
if (d.type === 'link in') {
direction = 1;
} else if (d.type === 'link out') {
direction = 0;
}
}
}
}
portMouseUp(d, direction, 0);
if (wasJoining) {
d3.selectAll(".port_hovered").classed("port_hovered",false);
}
}
function nodeMouseDown(d) {
@@ -2033,12 +2268,12 @@ RED.view = (function() {
outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor);
// Don't bother redrawing nodes if we're drawing links
if (mouse_mode != RED.state.JOINING) {
if (showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) {
var dirtyNodes = {};
if (activeSubflow) {
var subflowOutputs = vis.selectAll(".subflowoutput").data(activeSubflow.out,function(d,i){ return d.id;});
var subflowOutputs = nodeLayer.selectAll(".subflowoutput").data(activeSubflow.out,function(d,i){ return d.id;});
subflowOutputs.exit().remove();
var outGroup = subflowOutputs.enter().insert("svg:g").attr("class","node subflowoutput").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"});
outGroup.each(function(d,i) {
@@ -2081,7 +2316,7 @@ RED.view = (function() {
outGroup.append("svg:text").attr("class","port_label").attr("x",20).attr("y",8).style("font-size","10px").text("output");
outGroup.append("svg:text").attr("class","port_label port_index").attr("x",20).attr("y",24).text(function(d,i){ return i+1});
var subflowInputs = vis.selectAll(".subflowinput").data(activeSubflow.in,function(d,i){ return d.id;});
var subflowInputs = nodeLayer.selectAll(".subflowinput").data(activeSubflow.in,function(d,i){ return d.id;});
subflowInputs.exit().remove();
var inGroup = subflowInputs.enter().insert("svg:g").attr("class","node subflowinput").attr("transform",function(d) { return "translate("+(d.x-20)+","+(d.y-20)+")"});
inGroup.each(function(d,i) {
@@ -2143,11 +2378,11 @@ RED.view = (function() {
}
});
} else {
vis.selectAll(".subflowoutput").remove();
vis.selectAll(".subflowinput").remove();
nodeLayer.selectAll(".subflowoutput").remove();
nodeLayer.selectAll(".subflowinput").remove();
}
var node = vis.selectAll(".nodegroup").data(activeNodes,function(d){return d.id});
var node = nodeLayer.selectAll(".nodegroup").data(activeNodes,function(d){return d.id});
node.exit().remove();
var nodeEnter = node.enter().insert("svg:g")
@@ -2157,10 +2392,11 @@ RED.view = (function() {
nodeEnter.each(function(d,i) {
var node = d3.select(this);
var isLink = d.hasOwnProperty('l')?!d.l : (d.type === "link in" || d.type === "link out")
var isLink = (d.type === "link in" || d.type === "link out")
var hideLabel = d.hasOwnProperty('l')?!d.l : isLink;
node.attr("id",d.id);
var l = RED.utils.getNodeLabel(d);
if (isLink) {
if (hideLabel) {
d.w = node_height;
} else {
d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0))/20)) );
@@ -2268,6 +2504,19 @@ RED.view = (function() {
}
},500);
}
} else if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) {
if (drag_lines.length > 0) {
var selectClass;
var portType;
if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) {
selectClass = ".port_input .port";
portType = PORT_TYPE_INPUT;
} else {
selectClass = ".port_output .port";
portType = PORT_TYPE_OUTPUT;
}
portMouseOver(d3.select(this.parentNode).selectAll(selectClass),d,portType,0);
}
}
})
.on("mouseout",function(d) {
@@ -2278,6 +2527,20 @@ RED.view = (function() {
portLabelHover.remove();
portLabelHover = null;
}
if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) {
if (drag_lines.length > 0) {
var selectClass;
var portType;
if ((drag_lines[0].virtualLink && drag_lines[0].portType === PORT_TYPE_INPUT) || drag_lines[0].portType === PORT_TYPE_OUTPUT) {
selectClass = ".port_input .port";
portType = PORT_TYPE_INPUT;
} else {
selectClass = ".port_output .port";
portType = PORT_TYPE_OUTPUT;
}
portMouseOut(d3.select(this.parentNode).selectAll(selectClass),d,portType,0);
}
}
});
//node.append("rect").attr("class", "node-gradient-top").attr("rx", 6).attr("ry", 6).attr("height",30).attr("stroke","none").attr("fill","url(#gradient-top)").style("pointer-events","none");
@@ -2332,7 +2595,7 @@ RED.view = (function() {
.attr("x", 38)
.attr("dy", ".35em")
.attr("text-anchor","start")
.classed("hidden",isLink);
.classed("hidden",hideLabel);
if (d._def.align) {
text.attr("class","node_label node_label_"+d._def.align);
@@ -2360,13 +2623,14 @@ RED.view = (function() {
node.each(function(d,i) {
if (d.dirty) {
var isLink = d.hasOwnProperty('l')?!d.l : (d.type === "link in" || d.type === "link out")
var isLink = (d.type === "link in" || d.type === "link out")
var hideLabel = d.hasOwnProperty('l')?!d.l : isLink;
dirtyNodes[d.id] = d;
//if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette
if (/*!isLink &&*/ d.resize) {
if (d.resize) {
var l = RED.utils.getNodeLabel(d);
var ow = d.w;
if (isLink) {
if (hideLabel) {
d.w = node_height;
} else {
d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "node_label", 50)+(d._def.inputs>0?7:0))/20)) );
@@ -2396,13 +2660,24 @@ RED.view = (function() {
//thisNode.selectAll(".node_icon_shade_border_right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)});
var inputPorts = thisNode.selectAll(".port_input");
if (d.inputs === 0 && !inputPorts.empty()) {
if (isLink && showAllLinkPorts === -1 && !activeLinkNodes[d.id] && d.inputs === 0 && !inputPorts.empty()) {
inputPorts.remove();
//nodeLabel.attr("x",30);
} else if (d.inputs === 1 && inputPorts.empty()) {
} else if (((isLink && (showAllLinkPorts===PORT_TYPE_INPUT||activeLinkNodes[d.id]))|| d.inputs === 1) && inputPorts.empty()) {
var inputGroup = thisNode.append("g").attr("class","port_input");
inputGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
.on("mousedown",function(d){portMouseDown(d,PORT_TYPE_INPUT,0);})
var inputGroupPorts;
if (d.type === "link in") {
inputGroupPorts = inputGroup.append("circle")
.attr("cx",-1).attr("cy",5)
.attr("r",5)
.attr("class","port link_port")
// inputGroupPorts = inputGroup.append("path")
// .attr("d","M 4 -1 h -3 a 6 6 0 1 0 0 12 h 3")
// .attr("class","port link_port")
} else {
inputGroupPorts = inputGroup.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
}
inputGroupPorts.on("mousedown",function(d){portMouseDown(d,PORT_TYPE_INPUT,0);})
.on("touchstart",function(d){portMouseDown(d,PORT_TYPE_INPUT,0);})
.on("mouseup",function(d){portMouseUp(d,PORT_TYPE_INPUT,0);} )
.on("touchend",function(d){portMouseUp(d,PORT_TYPE_INPUT,0);} )
@@ -2411,13 +2686,38 @@ RED.view = (function() {
}
var numOutputs = d.outputs;
if (isLink && d.type === "link out") {
if (showAllLinkPorts===PORT_TYPE_OUTPUT || activeLinkNodes[d.id]) {
d.ports = [0];
numOutputs = 1;
} else {
d.ports = [];
numOutputs = 0;
}
}
var y = (d.h/2)-((numOutputs-1)/2)*13;
d.ports = d.ports || d3.range(numOutputs);
d._ports = thisNode.selectAll(".port_output").data(d.ports);
var output_group = d._ports.enter().append("g").attr("class","port_output");
var output_group_ports;
output_group.append("rect").attr("class","port").attr("rx",3).attr("ry",3).attr("width",10).attr("height",10)
.on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,PORT_TYPE_OUTPUT,i);}})() )
if (d.type === "link out") {
output_group_ports = output_group.append("circle")
.attr("cx",11).attr("cy",5)
.attr("r",5)
.attr("class","port link_port")
// output_group_ports = output_group.append("path")
// .attr("d","M 6 -1 h 3 a 6 6 0 1 1 0 12 h -3")
// .attr("class","port link_port")
} else {
output_group_ports = output_group.append("rect")
.attr("class","port")
.attr("rx",3).attr("ry",3)
.attr("width",10)
.attr("height",10)
}
output_group_ports.on("mousedown",(function(){var node = d; return function(d,i){portMouseDown(node,PORT_TYPE_OUTPUT,i);}})() )
.on("touchstart",(function(){var node = d; return function(d,i){portMouseDown(node,PORT_TYPE_OUTPUT,i);}})() )
.on("mouseup",(function(){var node = d; return function(d,i){portMouseUp(node,PORT_TYPE_OUTPUT,i);}})() )
.on("touchend",(function(){var node = d; return function(d,i){portMouseUp(node,PORT_TYPE_OUTPUT,i);}})() )
@@ -2464,7 +2764,7 @@ RED.view = (function() {
}
return "node_label"+
(d._def.align?" node_label_"+d._def.align:"")+s;
}).classed("hidden",isLink);
}).classed("hidden",hideLabel);
if (d._def.icon) {
var icon = thisNode.select(".node_icon");
var faIcon = thisNode.select(".fa-lg");
@@ -2592,7 +2892,7 @@ RED.view = (function() {
}
});
var link = vis.selectAll(".link").data(
var link = linkLayer.selectAll(".link").data(
activeLinks,
function(d) {
return d.source.id+":"+d.sourcePort+":"+d.target.id+":"+d.target.i;
@@ -2638,7 +2938,7 @@ RED.view = (function() {
});
link.exit().remove();
var links = vis.selectAll(".link_path");
var links = linkLayer.selectAll(".link_path");
links.each(function(d) {
var link = d3.select(this);
if (d.added || d===selected_link || d.selected || dirtyNodes[d.source.id] || dirtyNodes[d.target.id]) {
@@ -2666,7 +2966,7 @@ RED.view = (function() {
delete d.added;
return d.target.type == "unknown" || d.source.type == "unknown"
});
var offLinks = vis.selectAll(".link_flow_link_g").data(
var offLinks = linkLayer.selectAll(".link_flow_link_g").data(
activeFlowLinks,
function(d) {
return d.node.id+":"+d.refresh
@@ -2697,10 +2997,13 @@ RED.view = (function() {
var y = -(flows.length-1)*h/2;
var linkGroups = g.selectAll(".link_group").data(flows);
var enterLinkGroups = linkGroups.enter().append("g").attr("class","link_group")
.on('mouseover', function() { d3.select(this).classed('link_group_active',true)})
.on('mouseout', function() { d3.select(this).classed('link_group_active',false)})
.on('mouseover', function() { if (mouse_mode !== 0) { return } d3.select(this).classed('link_group_active',true)})
.on('mouseout', function() {if (mouse_mode !== 0) { return } d3.select(this).classed('link_group_active',false)})
.on('mousedown', function() { d3.event.preventDefault(); d3.event.stopPropagation(); })
.on('mouseup', function(f) {
if (mouse_mode !== 0) {
return
}
d3.event.stopPropagation();
var targets = d.links[f];
RED.workspaces.show(f);
@@ -2772,7 +3075,7 @@ RED.view = (function() {
linkGroups.exit().remove();
});
offLinks.exit().remove();
offLinks = vis.selectAll(".link_flow_link_g");
offLinks = linkLayer.selectAll(".link_flow_link_g");
offLinks.each(function(d) {
var s = 1;
if (d.node.type === "link in") {
@@ -2785,7 +3088,7 @@ RED.view = (function() {
} else {
// JOINING - unselect any selected links
vis.selectAll(".link_selected").data(
linkLayer.selectAll(".link_selected").data(
activeLinks,
function(d) {
return d.source.id+":"+d.sourcePort+":"+d.target.id+":"+d.target.i;
@@ -2952,7 +3255,7 @@ RED.view = (function() {
}
if (counts.length > 0) {
var countList = "<ul><li>"+counts.join("</li><li>")+"</li></ul>";
RED.notify("<p>"+RED._("clipboard.nodesImported")+"</p>"+countList);
RED.notify("<p>"+RED._("clipboard.nodesImported")+"</p>"+countList,{id:"clipboard"});
}
}

View File

@@ -146,7 +146,6 @@ RED.workspaces = (function() {
height -= $(rows[i]).outerHeight(true);
}
height -= (parseInt($("#dialog-form").css("marginTop"))+parseInt($("#dialog-form").css("marginBottom")));
height -= 28;
$(".node-text-editor").css("height",height+"px");
tabflowEditor.resize();
},
@@ -166,7 +165,6 @@ RED.workspaces = (function() {
var row = $('<div class="form-row node-text-editor-row">'+
'<label for="node-input-info" data-i18n="editor:workspace.info" style="width:300px;"></label>'+
'<div class="node-text-editor-toolbar"></div>'+
'<div style="min-height:250px;" class="node-text-editor" id="node-input-info"></div>'+
'</div>').appendTo(dialogForm);
tabflowEditor = RED.editor.createEditor({
@@ -175,10 +173,6 @@ RED.workspaces = (function() {
value: ""
});
var toolbar = RED.editor.types._markdown.buildToolbar(row.find(".node-text-editor-toolbar"),tabflowEditor);
$('<button id="node-info-input-info-expand" class="editor-button" style="float: right;"><i class="fa fa-expand"></i></button>').appendTo(toolbar);
$('#node-info-input-info-expand').click(function(e) {
e.preventDefault();
var value = tabflowEditor.getValue();

View File

@@ -567,9 +567,15 @@ ul.node-dialog-configm-deploy-list {
td.lineno {
font-family: monospace;
text-align: right;
color: #aaa;
color: #999;
background: #f6f6f6;
padding: 1px 5px;
&.added {
background: #c0f6c0;
}
&.removed {
background: #ffcccc;
}
}
td.lineno:nth-child(3) {
border-left: 1px solid $secondary-border-color;
@@ -578,12 +584,20 @@ ul.node-dialog-configm-deploy-list {
font-family: monospace;
white-space: pre-wrap;
padding: 1px 5px;
border-left: 1px solid #ccc;
span.prefix {
width: 30px;
display: inline-block;
text-align: center;
color: #999;
}
&.added {
border-left-color: #aaeeaa
}
&.removed {
border-left-color: #eebbbb
}
}
td.blank {
background: #f6f6f6;
@@ -592,7 +606,7 @@ ul.node-dialog-configm-deploy-list {
background: #eefaee;
}
td.removed {
background: #fadddd;
background: #ffecec;
}
tr.mergeHeader td {
color: #800080;

View File

@@ -209,11 +209,28 @@
}
.node-text-editor {
position: relative;
.node-text-editor-help {
position: absolute;
bottom: 0px;
right: 1px;
border-bottom-right-radius: 5px;
z-Index: 8;
border-bottom: none;
border-right: none;
}
}
.node-text-editor-container {
border:1px solid #ccc;
border-radius:5px;
overflow: hidden;
font-size: 14px !important;
font-family: Menlo, Consolas, 'DejaVu Sans Mono', Courier, monospace !important;
height: 100%;
&.node-text-editor-container-toolbar {
height: calc(100% - 40px);
}
}
.editor-button {
@@ -333,7 +350,7 @@
padding: 10px;
border:1px solid #ccc;
border-radius:5px;
height: calc(100% - 31px);
height: calc(100% - 21px);
overflow-y: scroll;
background: #fff;
}

View File

@@ -194,8 +194,8 @@
}
.port_hovered {
stroke: $port-selected-color;
fill: $port-selected-color;
stroke: $port-selected-color !important;
fill: $port-selected-color !important;
}
.port_quick_link {
@@ -211,7 +211,7 @@
}
.drag_line {
stroke: $node-selected-color;
stroke: $node-selected-color !important;
stroke-width: 3;
fill: none;
pointer-events: none;
@@ -236,10 +236,10 @@
stroke: $link-link-color;
fill: none;
stroke-dasharray: 15,2;
pointer-events: none;
// pointer-events: none;
}
.link_port {
fill: #fff;
fill: #eee;
stroke: $link-link-color;
stroke-width: 1;
}

View File

@@ -54,3 +54,65 @@
.notification-error {
border-color: #AD1625;
}
.notification-shake-horizontal {
-webkit-animation: notification-shake-horizontal 0.3s steps(2, end) both;
animation: notification-shake-horizontal 0.3s steps(2, end) both;
}
@-webkit-keyframes notification-shake-horizontal {
0%,
100% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
10%,
30%,
50%,
70% {
-webkit-transform: translateX(-1px);
transform: translateX(-1px);
}
20%,
40%,
60% {
-webkit-transform: translateX(1px);
transform: translateX(1px);
}
// 80% {
// -webkit-transform: translateX(1px);
// transform: translateX(1px);
// }
// 90% {
// -webkit-transform: translateX(-1px);
// transform: translateX(-1px);
// }
}
@keyframes notification-shake-horizontal {
0%,
100% {
-webkit-transform: translateX(0);
transform: translateX(0);
}
10%,
30%,
50%,
70% {
-webkit-transform: translateX(-1px);
transform: translateX(-1px);
}
20%,
40%,
60% {
-webkit-transform: translateX(1px);
transform: translateX(1px);
}
// 80% {
// -webkit-transform: translateX(1px);
// transform: translateX(1px);
// }
// 90% {
// -webkit-transform: translateX(-1px);
// transform: translateX(-1px);
// }
}

View File

@@ -82,7 +82,6 @@
@include component-footer-button;
}
.palette-category {
border-bottom: 1px solid #ccc;
}
@@ -101,6 +100,9 @@
padding-left: 30px;
overflow: hidden;
user-select: none;
&:hover {
background: $palette-header-background !important;
}
}
.palette-header > i {
position: absolute;

View File

@@ -42,11 +42,13 @@
.red-ui-panels.red-ui-panels-horizontal {
height: 100%;
.red-ui-panel {
vertical-align: top;
display: inline-block;
height: 100%;
width: calc(50% - 4px);
}
.red-ui-panels-separator {
vertical-align: top;
border-top: none;
border-bottom: none;
border-left: 1px solid $secondary-border-color;

View File

@@ -58,6 +58,7 @@
@import "ui/common/nodeList";
@import "ui/common/checkboxSet";
@import "ui/common/stack";
@import "ui/common/treeList";
@import "dragdrop";

View File

@@ -0,0 +1,108 @@
/**
* 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-ui-treeList {
}
.red-ui-treeList-container {
width: 100%;
height: 100%;
position: relative;
background: #f9f9f9;
border: 1px solid $form-input-border-color;
border-radius: 4px;
box-sizing: border-box;
.red-ui-editableList-border {
border: none;
}
.red-ui-editableList-container {
padding: 0px;
}
.red-ui-editableList-container li {
padding: 0;
border-bottom: none;
.red-ui-editableList-container {
// margin-left: 15px;
}
}
.red-ui-editableList-item-content {
& > .red-ui-treeList-label .fa-angle-right {
transition: transform 0.1s ease-in-out;
}
.red-ui-editableList {
display: none;
}
&.expanded {
& > .red-ui-treeList-label .fa-angle-right {
transform: rotate(90deg)
}
& > .red-ui-editableList {
display: block
}
& > .red-ui-treeList-spinner {
display: block;
}
}
}
}
label.red-ui-treeList-label {
display: block;
width: auto;
}
.red-ui-treeList-label {
@include disable-selection;
padding: 6px 0;
display: block;
color: $form-text-color;
text-decoration: none;
cursor: pointer;
vertical-align: middle;
margin: 0;
&:hover {
background: #f9f9f9;
color: $form-text-color;
text-decoration: none;
}
&:focus {
outline: none;
color: $form-text-color;
text-decoration: none;
}
input {
margin: 0;
}
}
.red-ui-treeList-label-text {
margin-left: 4px;
}
.red-ui-treeList-icon {
display: inline-block;
width: 20px;
text-align: center;
}
.red-ui-treeList-spinner {
display: none;
height: 32px;
background: url(images/spin.svg) 50% 50% no-repeat;
background-size: auto 20px;
}

View File

@@ -118,11 +118,13 @@
'$count':{ args:[ 'array' ]},
'$each':{ args:[ 'object', 'function' ]},
'$env': { args:[ 'arg' ]},
'$eval': { args: ['expr', 'context']},
'$exists':{ args:[ 'arg' ]},
'$filter':{ args:[ 'array', 'function' ]},
'$floor':{ args:[ 'number' ]},
'$flowContext': {args:['string']},
'$formatBase': {args:['number','radix']},
'$formatInteger': {args:['number', 'picture']},
'$formatNumber': {args:['number', 'picture', 'options']},
'$fromMillis': {args:['number']},
'$globalContext': {args:['string']},
@@ -141,6 +143,7 @@
'$now':{ args:[ ]},
'$number':{ args:[ 'arg' ]},
'$pad': {args:['str', 'width','char']},
'$parseInteger': {args:['string', 'picture']},
'$power':{ args:[ 'base', 'exponent' ]},
'$random':{ args:[ ]},
'$reduce':{ args:[ 'array', 'function' , 'init' ]},

View File

@@ -483,20 +483,17 @@
var key = RED.utils.parseContextKey(payload);
payload = this.payloadType+"."+key.key;
}
var label = (this.name||payload);
var label = this._def.label.call(this);
if (label.length > 30) {
label = label.substring(0,50)+"...";
}
label = label.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
if (this.payloadType === "date") { label = this._("inject.timestamp"); }
if (this.payloadType === "none") { label = this._("inject.blank"); }
var node = this;
$.ajax({
url: "inject/"+this.id,
type:"POST",
success: function(resp) {
RED.notify(node._("inject.success",{label:label}),"success");
RED.notify(node._("inject.success",{label:label}),{type:"success",id:"inject"});
},
error: function(jqXHR,textStatus,errorThrown) {
if (jqXHR.status == 404) {

View File

@@ -4,7 +4,9 @@
<label for="node-input-typed-complete"><i class="fa fa-list"></i> <span data-i18n="debug.output"></span></label>
<input id="node-input-typed-complete" type="text" style="width: 70%">
<input id="node-input-complete" type="hidden">
<input id="node-input-targetType" type="hidden">
</div>
<div class="form-row">
<label for="node-input-tosidebar"><i class="fa fa-random"></i> <span data-i18n="debug.to"></span></label>
<label for="node-input-tosidebar" style="width:70%">
@@ -42,11 +44,15 @@
tosidebar: {value:true},
console: {value:false},
tostatus: {value:false},
complete: {value:"false", required:true}
complete: {value:"false", required:true},
targetType: {value:undefined}
},
label: function() {
var suffix = "";
if (this.console === true || this.console === "true") { suffix = " ⇲"; }
if (this.targetType === "jsonata") {
return (this.name || "JSONata") + suffix;
}
if (this.complete === true || this.complete === "true") {
return (this.name||"msg") + suffix;
} else {
@@ -245,6 +251,11 @@
delete RED._debug;
},
oneditprepare: function() {
var none = {
value: "none",
label: RED._("node-red:debug.none"),
hasValue: false
};
if (this.tosidebar === undefined) {
this.tosidebar = true;
$("#node-input-tosidebar").prop('checked', true);
@@ -254,8 +265,21 @@
$("#node-input-console").prop('checked', this.console);
$("#node-input-tosidebar").prop('checked', true);
}
$("#node-input-typed-complete").typedInput({types:['msg', {value:"full",label:RED._("node-red:debug.msgobj"),hasValue:false}]});
if (this.complete === "true" || this.complete === true) {
var fullType = {
value: "full",
label: RED._("node-red:debug.msgobj"),
hasValue: false
};
$("#node-input-typed-complete").typedInput({
default: "msg",
types:['msg', fullType, "jsonata"],
typeField: $("#node-input-targetType")
});
if (this.targetType === "jsonata") {
var property = this.complete || "";
$("#node-input-typed-complete").typedInput('type','jsonata');
$("#node-input-typed-complete").typedInput('value',property);
} else if ((this.targetType === "full") || this.complete === "true" || this.complete === true) {
// show complete message object
$("#node-input-typed-complete").typedInput('type','full');
} else {

View File

@@ -1,4 +1,3 @@
module.exports = function(RED) {
"use strict";
var util = require("util");
@@ -9,9 +8,11 @@ module.exports = function(RED) {
util.inspect.styles.boolean = "red";
function DebugNode(n) {
var is_edit = (n.targetType === "jsonata");
var edit_exp = is_edit ? n.complete : null;
RED.nodes.createNode(this,n);
this.name = n.name;
this.complete = (n.complete||"payload").toString();
this.complete = is_edit ? null : (n.complete||"payload").toString();
if (this.complete === "false") { this.complete = "payload"; }
this.console = ""+(n.console || false);
this.tostatus = n.tostatus || false;
@@ -43,8 +44,48 @@ module.exports = function(RED) {
"50": "green",
"60": "blue"
};
var edit = null;
if (edit_exp) {
try {
edit = RED.util.prepareJSONataExpression(edit_exp, this);
}
catch (e) {
node.error(RED._("debug.invalid-exp", {error: edit_exp}));
return;
}
}
function editValue(exp, val) {
return new Promise((resolve, reject) => {
if (exp) {
RED.util.evaluateJSONataExpression(exp, val, (err, value) => {
if (err) {
reject(RED._("debug.invalid-exp", {error: edit_exp}));
} else {
resolve(value);
}
});
}
else {
resolve(val);
}
});
}
this.on("input",function(msg) {
function output_e(msg) {
editValue(edit, msg).then(val => {
if (this.console === "true") {
node.log("\n"+util.inspect(val, {colors:useColors, depth:10}));
}
if (this.active && this.tosidebar) {
sendDebug({id:node.id, name:node.name, topic:val.topic, msg:val, _path:val._path});
}
}).catch(err => {
node.error(err);
});
}
function output(msg) {
if (this.complete === "true") {
// debug complete msg object
if (this.console === "true") {
@@ -87,7 +128,9 @@ module.exports = function(RED) {
}
}
}
});
}
this.on("input", (edit_exp ? output_e : output));
}
RED.nodes.registerType("debug",DebugNode, {

View File

@@ -14,164 +14,81 @@
<div class="form-row node-input-link-row"></div>
</script>
<style>
#node-input-link-container {
position: relative;
}
#node-input-link-container li {
padding: 2px 5px;
background: none;
font-size: 0.8em;
margin:0;
white-space: nowrap;
}
#node-input-link-container li label {
margin-bottom: 0;
width: 100%;
}
#node-input-link-container li label input {
vertical-align: top;
width:15px;
margin-right: 10px;
}
#node-input-link-container li:hover,
#node-input-link-container li:hover .node-input-target-node-sublabel {
background: #f0f0f0;
}
.node-input-link-node-sublabel {
position:absolute;
right: 0px;
padding-right: 10px;
padding-left: 10px;
font-size: 0.8em;
}
</style>
<script type="text/javascript">
(function() {
function sortNodeList(nodeList,sortOn,sortOnSecond) {
var currentSort = nodeList.data('currentSort');
var currentSortOrder = nodeList.data('currentSortOrder');
var treeList;
if (!currentSort) {
currentSort = sortOn;
currentSortOrder = 'a';
} else {
if (currentSort === sortOn) {
currentSortOrder = (currentSortOrder === 'a'?'d':'a');
} else {
currentSortOrder = 'a';
}
currentSort = sortOn;
}
nodeList.data('currentSort',currentSort);
nodeList.data('currentSortOrder',currentSortOrder);
$("#node-input-link-container-div .fa").hide();
$(".node-input-link-sort-"+currentSort+"-"+currentSortOrder).show();
var items = nodeList.find("li").get();
items.sort(function(a,b) {
var labelA = $(a).find(".node-input-link-node-"+currentSort).text().toLowerCase();
var labelB = $(b).find(".node-input-link-node-"+currentSort).text().toLowerCase();
if (labelA < labelB) { return currentSortOrder==='a'?-1:1; }
if (labelA > labelB) { return currentSortOrder==='a'?1:-1; }
if (sortOnSecond) {
labelA = $(a).find(".node-input-link-node-"+sortOnSecond).text().toLowerCase();
labelB = $(b).find(".node-input-link-node-"+sortOnSecond).text().toLowerCase();
if (labelA < labelB) { return currentSortOrder==='a'?-1:1; }
if (labelA > labelB) { return currentSortOrder==='a'?1:-1; }
}
return 0;
});
$.each(items, function(i, li) {
nodeList.append(li);
});
}
function onEditPrepare(node,targetType) {
if (!node.links) {
node.links = [];
}
node.oldLinks = [];
$('<div id="node-input-link-container-div" style="min-height: 100px;position: relative; box-sizing: border-box; border-radius: 2px; height: 180px; border: 1px solid #ccc;overflow:hidden; ">'+
' <div style="box-sizing: border-box; line-height: 20px; font-size: 0.8em; border-bottom: 1px solid #ddd; height: 20px;">'+
' <div style="display: inline-block;margin-left: 5px;"><a id="node-input-link-sort-label" href="#" data-i18n="[title]node-red:link.label.sortByLabel"><span data-i18n="node-red:link.label.node">name</span> <i class="node-input-link-sort-label-a fa fa-caret-down"></i><i class="node-input-link-sort-label-d fa fa-caret-up"></i></a></div>'+
' <div style="position: absolute; right: 10px; width: 50px; display: inline-block; text-align: right;"><a id="node-input-link-sort-type" href="#" data-i18n="[title]node-red:link.label.sortByFlow"><i class="node-input-link-sort-sublabel-a fa fa-caret-down"></i><i class="node-input-link-sort-sublabel-d fa fa-caret-up"></i> <span data-i18n="node-red:link.label.type">flow</span></a></div>'+
' </div>'+
' <div style="background: #fbfbfb; box-sizing: border-box; position:absolute; top:20px;bottom:0;left:0px;right:0px; overflow-y: scroll; overflow-x: hidden;">'+
' <ul id="node-input-link-container" style=" list-style-type:none; margin: 0;"></ul>'+
' </div>'+
'</div>').appendTo('.node-input-link-row');
var activeSubflow = RED.nodes.subflow(node.z);
var nodeList = $("#node-input-link-container");
treeList = $("<div>")
.css({width: "100%", height: "100%"})
.appendTo(".node-input-link-row")
.treeList({})
.on('treelistitemmouseover',function(e,item) {
if (item.node) {
item.node.highlighted = true;
item.node.dirty = true;
RED.view.redraw();
}
})
.on('treelistitemmouseout',function(e,item) {
if (item.node) {
item.node.highlighted = false;
item.node.dirty = true;
RED.view.redraw();
}
});
var candidateNodes = RED.nodes.filterNodes({type:targetType});
var inSubflow = !!RED.nodes.subflow(node.z);
var flows = [];
var flowMap = {};
if (activeSubflow) {
flowMap[activeSubflow.id] = {
id: activeSubflow.id,
class: 'palette-header',
label: "Subflow : "+(activeSubflow.name || activeSubflow.id),
expanded: true,
children: []
};
flows.push(flowMap[activeSubflow.id])
} else {
RED.nodes.eachWorkspace(function(ws) {
flowMap[ws.id] = {
id: ws.id,
class: 'palette-header',
label: (ws.label || ws.id),
expanded: ws.id === node.z,
children: []
}
flows.push(flowMap[ws.id])
})
}
candidateNodes.forEach(function(n) {
if (inSubflow) {
if (n.z !== node.z) {
return;
}
} else {
if (!!RED.nodes.subflow(n.z)) {
return;
if (flowMap[n.z]) {
var isChecked = false;
isChecked = (node.links.indexOf(n.id) !== -1) || (n.links||[]).indexOf(node.id) !== -1;
if (isChecked) {
node.oldLinks.push(n.id);
}
flowMap[n.z].children.push({
id: n.id,
node: n,
label: n.name||n.id,
selected: isChecked
})
}
var isChecked = false;
isChecked = (node.links.indexOf(n.id) !== -1) || (n.links||[]).indexOf(node.id) !== -1;
if (isChecked) {
node.oldLinks.push(n.id);
}
var container = $('<li/>',{class:"node-input-link-node"});
var row = $('<label/>',{for:"node-input-link-node-"+n.id}).appendTo(container);
$('<input>',{type:"checkbox",class:"node-input-link-node-checkbox",id:"node-input-link-node-"+n.id})
.data('node-id',n.id)
.prop('checked', isChecked)
.appendTo(row);
container.on('mouseover',function(e) {
n.highlighted = true;
n.dirty = true;
RED.view.redraw();
});
container.on('mouseout',function(e) {
n.highlighted = false;
n.dirty = true;
RED.view.redraw();
});
var labelSpan = $('<span>');
var label = n.name||n.id;
var sublabel;
var tab = RED.nodes.workspace(n.z);
if (tab) {
sublabel = tab.label||tab.id;
} else {
tab = RED.nodes.subflow(n.z);
sublabel = "subflow : "+tab.name;
}
$('<span>',{class:"node-input-link-node-label",style:"white-space:nowrap"}).text(label).appendTo(row);
if (sublabel) {
$('<span>',{class:"node-input-link-node-sublabel"}).text(sublabel).appendTo(row);
}
container.appendTo(nodeList);
});
sortNodeList(nodeList,'sublabel','label');
$("#node-input-link-sort-label").click(function(e) {
e.preventDefault();
sortNodeList(nodeList,'label');
});
$("#node-input-link-sort-type").click(function(e) {
e.preventDefault();
sortNodeList(nodeList,'sublabel');
});
flows = flows.filter(function(f) { return f.children.length > 0 })
treeList.treeList('data',flows)
}
function resizeNodeList() {
@@ -182,16 +99,19 @@
}
var editorRow = $("#dialog-form>div.node-input-link-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
$("#node-input-link-container-div").css("height",height+"px");
$(".node-input-link-row").css("height",height+"px");
}
function onEditSave(node) {
var flows = treeList.treeList('data');
node.links = [];
$(".node-input-link-node-checkbox").each(function(n) {
if ($(this).prop("checked")) {
node.links.push($(this).data('node-id'));
}
});
flows.forEach(function(f) {
f.children.forEach(function(n) {
if (n.selected) {
node.links.push(n.id);
}
})
})
node.oldLinks.sort();
node.links.sort();
var nodeMap = {};

View File

@@ -1,17 +1,13 @@
<script type="text/x-red" data-template-name="comment">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-comment"></i> <span data-i18n="comment.label.title"></span></label>
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name">
</div>
<div class="form-row" style="margin-bottom: 0px;">
<label for="node-input-info" style="width: 100% !important;"><i class="fa fa-comments"></i> <span data-i18n="comment.label.body"></span></label>
<input type="hidden" id="node-input-info" autofocus="autofocus">
</div>
<div class="form-row node-text-editor-row">
<input type="hidden" id="node-input-info" autofocus="autofocus">
<div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-info-editor"></div>
</div>
<div class="form-tips" data-i18n="[html]comment.tip"></div>
</script>
<script type="text/javascript">
@@ -32,7 +28,7 @@
return this.name?"node_label_italic":"";
},
info: function() {
return (this.name?"# "+this.name+"\n":"")+(this.info||"");
return this.name?"# "+this.name+"\n\n---\n\n":"";
},
oneditprepare: function() {
var that = this;

View File

@@ -18,6 +18,23 @@ module.exports = function(RED) {
"use strict";
var ws = require("ws");
var inspect = require("util").inspect;
var url = require("url");
var serverUpgradeAdded = false;
function handleServerUpgrade(request, socket, head) {
const pathname = url.parse(request.url).pathname;
if (listenerNodes.hasOwnProperty(pathname)) {
listenerNodes[pathname].server.handleUpgrade(request, socket, head, function done(ws) {
listenerNodes[pathname].server.emit('connection', ws, request);
});
} else {
// Don't destroy the socket as other listeners may want to handle the
// event.
}
}
var listenerNodes = {};
var activeListenerNodes = 0;
// A node red node that sets up a local websocket server
function WebSocketListenerNode(n) {
@@ -53,13 +70,22 @@ module.exports = function(RED) {
function handleConnection(/*socket*/socket) {
var id = (1+Math.random()*4294967295).toString(16);
if (node.isServer) { node._clients[id] = socket; node.emit('opened',Object.keys(node._clients).length); }
if (node.isServer) {
node._clients[id] = socket;
node.emit('opened',Object.keys(node._clients).length);
}
socket.on('open',function() {
if (!node.isServer) { node.emit('opened',''); }
if (!node.isServer) {
node.emit('opened','');
}
});
socket.on('close',function() {
if (node.isServer) { delete node._clients[id]; node.emit('closed',Object.keys(node._clients).length); }
else { node.emit('closed'); }
if (node.isServer) {
delete node._clients[id];
node.emit('closed',Object.keys(node._clients).length);
} else {
node.emit('closed');
}
if (!node.closing && !node.isServer) {
clearTimeout(node.tout);
node.tout = setTimeout(function() { startconn(); }, 3000); // try to reconnect every 3 secs... bit fast ?
@@ -78,34 +104,29 @@ module.exports = function(RED) {
}
if (node.isServer) {
var path = RED.settings.httpNodeRoot || "/";
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
// Workaround https://github.com/einaros/ws/pull/253
// Listen for 'newListener' events from RED.server
node._serverListeners = {};
var storeListener = function(/*String*/event,/*function*/listener) {
if (event == "error" || event == "upgrade" || event == "listening") {
node._serverListeners[event] = listener;
}
activeListenerNodes++;
if (!serverUpgradeAdded) {
RED.server.on('upgrade', handleServerUpgrade);
serverUpgradeAdded = true
}
RED.server.addListener('newListener',storeListener);
var path = RED.settings.httpNodeRoot || "/";
path = path + (path.slice(-1) == "/" ? "":"/") + (node.path.charAt(0) == "/" ? node.path.substring(1) : node.path);
node.fullPath = path;
if (listenerNodes.hasOwnProperty(path)) {
node.error(RED._("websocket.errors.duplicate-path",{path: node.path}));
return;
}
listenerNodes[node.fullPath] = node;
var serverOptions = {
server:RED.server,
path:path
noServer: true
}
if (RED.settings.webSocketNodeVerifyClient) {
serverOptions.verifyClient = RED.settings.webSocketNodeVerifyClient;
}
// Create a WebSocket Server
node.server = new ws.Server(serverOptions);
// Workaround https://github.com/einaros/ws/pull/253
// Stop listening for new listener events
RED.server.removeListener('newListener',storeListener);
node.server.setMaxListeners(0);
node.server.on('connection', handleConnection);
}
@@ -115,21 +136,17 @@ module.exports = function(RED) {
}
node.on("close", function() {
// Workaround https://github.com/einaros/ws/pull/253
// Remove listeners from RED.server
if (node.isServer) {
var listener = null;
for (var event in node._serverListeners) {
if (node._serverListeners.hasOwnProperty(event)) {
listener = node._serverListeners[event];
if (typeof listener === "function") {
RED.server.removeListener(event,listener);
}
}
}
node._serverListeners = {};
delete listenerNodes[node.fullPath];
node.server.close();
node._inputNodes = [];
activeListenerNodes--;
if (activeListenerNodes === 0 && serverUpgradeAdded) {
RED.server.removeListener('upgrade', handleServerUpgrade);
serverUpgradeAdded = false;
}
}
else {
node.closing = true;
@@ -177,11 +194,12 @@ module.exports = function(RED) {
}
WebSocketListenerNode.prototype.broadcast = function(data) {
var i;
try {
if (this.isServer) {
for (i = 0; i < this.server.clients.length; i++) {
this.server.clients[i].send(data);
for (let client in this._clients) {
if (this._clients.hasOwnProperty(client)) {
this._clients[client].send(data);
}
}
}
else {
@@ -215,8 +233,11 @@ module.exports = function(RED) {
this.serverConfig.on('opened', function(n) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); });
this.serverConfig.on('erro', function() { node.status({fill:"red",shape:"ring",text:"common.status.error"}); });
this.serverConfig.on('closed', function(n) {
if (n > 0) { node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})}); }
else { node.status({fill:"red",shape:"ring",text:"common.status.disconnected"}); }
if (n > 0) {
node.status({fill:"green",shape:"dot",text:RED._("websocket.status.connected",{count:n})});
} else {
node.status({fill:"red",shape:"ring",text:"common.status.disconnected"});
}
});
} else {
this.error(RED._("websocket.errors.missing-conf"));

View File

@@ -91,206 +91,117 @@ module.exports = function(RED) {
return _maxKeptCount;
}
function getProperty(node,msg) {
if (node.useAsyncRules) {
return new Promise((resolve,reject) => {
if (node.propertyType === 'jsonata') {
RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
function getProperty(node,msg,done) {
if (node.propertyType === 'jsonata') {
RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => {
if (err) {
done(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
done(undefined,value);
}
});
} else {
if (node.propertyType === 'jsonata') {
try {
return RED.util.evaluateJSONataExpression(node.property,msg);
} catch(err) {
throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => {
if (err) {
done(undefined,undefined);
} else {
done(undefined,value);
}
} else {
try {
return RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
} catch(err) {
return undefined;
}
}
});
}
}
function getV1(node,msg,rule,hasParts) {
if (node.useAsyncRules) {
return new Promise( (resolve,reject) => {
if (rule.vt === 'prev') {
resolve(node.previousValue);
} else if (rule.vt === 'jsonata') {
var exp = rule.v;
if (rule.t === 'jsonata_exp') {
if (hasParts) {
exp.assign("I", msg.parts.index);
exp.assign("N", msg.parts.count);
}
}
RED.util.evaluateJSONataExpression(exp,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
} else if (rule.vt === 'json') {
resolve("json"); // TODO: ?! invalid case
} else if (rule.vt === 'null') {
resolve("null");
function getV1(node,msg,rule,hasParts,done) {
if (rule.vt === 'prev') {
return done(undefined,node.previousValue);
} else if (rule.vt === 'jsonata') {
var exp = rule.v;
if (rule.t === 'jsonata_exp') {
if (hasParts) {
exp.assign("I", msg.parts.index);
exp.assign("N", msg.parts.count);
}
}
RED.util.evaluateJSONataExpression(exp,msg,(err,value) => {
if (err) {
done(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
done(undefined, value);
}
});
} else if (rule.vt === 'json') {
done(undefined,"json"); // TODO: ?! invalid case
} else if (rule.vt === 'null') {
done(undefined,"null");
} else {
RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) {
if (err) {
done(undefined, undefined);
} else {
done(undefined, value);
}
});
}
}
function getV2(node,msg,rule,done) {
var v2 = rule.v2;
if (rule.v2t === 'prev') {
return done(undefined,node.previousValue);
} else if (rule.v2t === 'jsonata') {
RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => {
if (err) {
done(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
done(undefined,value);
}
});
} else if (typeof v2 !== 'undefined') {
RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) {
if (err) {
done(undefined,undefined);
} else {
done(undefined,value);
}
});
} else {
if (rule.vt === 'prev') {
return node.previousValue;
} else if (rule.vt === 'jsonata') {
var exp = rule.v;
if (rule.t === 'jsonata_exp') {
if (hasParts) {
exp.assign("I", msg.parts.index);
exp.assign("N", msg.parts.count);
}
}
try {
return RED.util.evaluateJSONataExpression(exp,msg);
} catch(err) {
throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
}
} else if (rule.vt === 'json') {
return "json"; // TODO: ?! invalid case
} else if (rule.vt === 'null') {
return "null";
} else {
try {
return RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
} catch(err) {
return undefined;
}
}
done(undefined,v2);
}
}
function getV2(node,msg,rule) {
if (node.useAsyncRules) {
return new Promise((resolve,reject) => {
var v2 = rule.v2;
if (rule.v2t === 'prev') {
resolve(node.previousValue);
} else if (rule.v2t === 'jsonata') {
RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
} else if (typeof v2 !== 'undefined') {
RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
function applyRule(node, msg, property, state, done) {
var rule = node.rules[state.currentRule];
var v1,v2;
getV1(node,msg,rule,state.hasParts, (err,value) => {
if (err) {
return done(err);
}
v1 = value;
getV2(node,msg,rule, (err,value) => {
if (err) {
return done(err);
}
v2 = value;
if (rule.t == "else") {
property = state.elseflag;
state.elseflag = true;
}
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
state.onward.push(msg);
state.elseflag = false;
if (node.checkall == "false") {
return done(undefined,false);
}
} else {
resolve(v2);
state.onward.push(null);
}
})
} else {
var v2 = rule.v2;
if (rule.v2t === 'prev') {
return node.previousValue;
} else if (rule.v2t === 'jsonata') {
try {
return RED.util.evaluateJSONataExpression(rule.v2,msg);
} catch(err) {
throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
}
} else if (typeof v2 !== 'undefined') {
try {
return RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
} catch(err) {
return undefined;
}
} else {
return v2;
}
}
done(undefined, state.currentRule < node.rules.length - 1);
});
});
}
function applyRule(node, msg, property, state) {
if (node.useAsyncRules) {
return new Promise((resolve,reject) => {
var rule = node.rules[state.currentRule];
var v1,v2;
getV1(node,msg,rule,state.hasParts).then(value => {
v1 = value;
}).then(()=>getV2(node,msg,rule)).then(value => {
v2 = value;
}).then(() => {
if (rule.t == "else") {
property = state.elseflag;
state.elseflag = true;
}
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
state.onward.push(msg);
state.elseflag = false;
if (node.checkall == "false") {
return resolve(false);
}
} else {
state.onward.push(null);
}
resolve(state.currentRule < node.rules.length - 1);
});
})
} else {
var rule = node.rules[state.currentRule];
var v1 = getV1(node,msg,rule,state.hasParts);
var v2 = getV2(node,msg,rule);
if (rule.t == "else") {
property = state.elseflag;
state.elseflag = true;
}
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
state.onward.push(msg);
state.elseflag = false;
if (node.checkall == "false") {
return false;
}
} else {
state.onward.push(null);
}
return state.currentRule < node.rules.length - 1
}
}
function applyRules(node, msg, property,state) {
function applyRules(node, msg, property,state,done) {
if (!state) {
state = {
currentRule: 0,
@@ -301,26 +212,18 @@ module.exports = function(RED) {
msg.parts.hasOwnProperty("index")
}
}
if (node.useAsyncRules) {
return applyRule(node,msg,property,state).then(hasMore => {
if (hasMore) {
state.currentRule++;
return applyRules(node,msg,property,state);
} else {
node.previousValue = property;
return state.onward;
}
});
} else {
var hasMore = applyRule(node,msg,property,state);
applyRule(node,msg,property,state,(err,hasMore) => {
if (err) {
return done(err);
}
if (hasMore) {
state.currentRule++;
return applyRules(node,msg,property,state);
applyRules(node,msg,property,state,done);
} else {
node.previousValue = property;
return state.onward;
done(undefined,state.onward);
}
}
});
}
@@ -345,13 +248,6 @@ module.exports = function(RED) {
var valid = true;
var repair = n.repair;
var needsCount = repair;
this.useAsyncRules = (
this.propertyType === 'flow' ||
this.propertyType === 'global' || (
this.propertyType === 'jsonata' &&
/\$(flow|global)Context/.test(this.property)
)
);
for (var i=0; i<this.rules.length; i+=1) {
var rule = this.rules[i];
@@ -363,13 +259,6 @@ module.exports = function(RED) {
rule.vt = 'str';
}
}
this.useAsyncRules = this.useAsyncRules || (
rule.vt === 'flow' ||
rule.vt === 'global' || (
rule.vt === 'jsonata' &&
/\$(flow|global)Context/.test(rule.v)
)
);
if (rule.vt === 'num') {
if (!isNaN(Number(rule.v))) {
rule.v = Number(rule.v);
@@ -382,9 +271,6 @@ module.exports = function(RED) {
valid = false;
}
}
if (rule.vt === 'flow' || rule.vt === 'global' || rule.vt === 'jsonata') {
this.useAsyncRules = true;
}
if (typeof rule.v2 !== 'undefined') {
if (!rule.v2t) {
if (!isNaN(Number(rule.v2))) {
@@ -393,13 +279,6 @@ module.exports = function(RED) {
rule.v2t = 'str';
}
}
this.useAsyncRules = this.useAsyncRules || (
rule.v2t === 'flow' ||
rule.v2t === 'global' || (
rule.v2t === 'jsonata' &&
/\$(flow|global)Context/.test(rule.v2)
)
);
if (rule.v2t === 'num') {
rule.v2 = Number(rule.v2);
} else if (rule.v2t === 'jsonata') {
@@ -444,26 +323,38 @@ module.exports = function(RED) {
return group;
}
function addMessageToPending(msg) {
function drainMessageGroup(msgs,count,done) {
var msg = msgs.shift();
msg.parts.count = count;
processMessage(msg,false, err => {
if (err) {
done(err);
} else {
if (msgs.length === 0) {
done()
} else {
drainMessageGroup(msgs,count,done);
}
}
})
}
function addMessageToPending(msg,done) {
var parts = msg.parts;
// We've already checked the msg.parts has the require bits
var group = addMessageToGroup(parts.id, msg, parts);
var msgs = group.msgs;
var count = group.count;
if (count === msgs.length) {
var msgsCount = msgs.length;
if (count === msgsCount) {
// We have a complete group - send the individual parts
return msgs.reduce((promise, msg) => {
return promise.then((result) => {
msg.parts.count = count;
return processMessage(msg, false);
})
}, Promise.resolve()).then( () => {
pendingCount -= group.msgs.length;
drainMessageGroup(msgs,count,err => {
pendingCount -= msgsCount;
delete pendingIn[parts.id];
});
done();
})
return;
}
return Promise.resolve();
done();
}
function sendGroup(onwards, port_count) {
@@ -529,43 +420,33 @@ module.exports = function(RED) {
}
}
function processMessage(msg, checkParts) {
function processMessage(msg, checkParts, done) {
var hasParts = msg.hasOwnProperty("parts") &&
msg.parts.hasOwnProperty("id") &&
msg.parts.hasOwnProperty("index");
if (needsCount && checkParts && hasParts) {
return addMessageToPending(msg);
}
if (node.useAsyncRules) {
return getProperty(node,msg)
.then(property => applyRules(node,msg,property))
.then(onward => {
if (!repair || !hasParts) {
node.send(onward);
}
else {
sendGroupMessages(onward, msg);
}
}).catch(err => {
node.warn(err);
});
addMessageToPending(msg,done);
} else {
try {
var property = getProperty(node,msg);
var onward = applyRules(node,msg,property);
if (!repair || !hasParts) {
node.send(onward);
getProperty(node,msg,(err,property) => {
if (err) {
node.warn(err);
done();
} else {
sendGroupMessages(onward, msg);
applyRules(node,msg,property,undefined,(err,onward) => {
if (err) {
node.warn(err);
} else {
if (!repair || !hasParts) {
node.send(onward);
} else {
sendGroupMessages(onward, msg);
}
}
done();
});
}
} catch(err) {
node.warn(err);
}
});
}
}
@@ -578,12 +459,13 @@ module.exports = function(RED) {
}
var pendingMessages = [];
var activeMessagePromise = null;
var handlingMessage = false;
var processMessageQueue = function(msg) {
if (msg) {
// A new message has arrived - add it to the message queue
pendingMessages.push(msg);
if (activeMessagePromise !== null) {
if (handlingMessage) {
// The node is currently processing a message, so do nothing
// more with this message
return;
@@ -592,27 +474,24 @@ module.exports = function(RED) {
if (pendingMessages.length === 0) {
// There are no more messages to process, clear the active flag
// and return
activeMessagePromise = null;
handlingMessage = false;
return;
}
// There are more messages to process. Get the next message and
// start processing it. Recurse back in to check for any more
var nextMsg = pendingMessages.shift();
activeMessagePromise = processMessage(nextMsg,true)
.then(processMessageQueue)
.catch((err) => {
handlingMessage = true;
processMessage(nextMsg,true,err => {
if (err) {
node.error(err,nextMsg);
return processMessageQueue();
});
}
processMessageQueue()
});
}
this.on('input', function(msg) {
if (node.useAsyncRules) {
processMessageQueue(msg);
} else {
processMessage(msg,true);
}
processMessageQueue(msg);
});
this.on('close', function() {

View File

@@ -98,7 +98,7 @@ module.exports = function(RED) {
}
}
function getToValue(msg,rule) {
function getToValue(msg,rule,done) {
var value = rule.to;
if (rule.tot === 'json') {
value = JSON.parse(rule.to);
@@ -107,222 +107,235 @@ module.exports = function(RED) {
}
if (rule.tot === "msg") {
value = RED.util.getMessageProperty(msg,rule.to);
} else if ((rule.tot === 'flow') ||
(rule.tot === 'global')) {
return new Promise((resolve,reject) => {
RED.util.evaluateNodeProperty(rule.to, rule.tot, node, msg, (err,value) => {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
} else if ((rule.tot === 'flow') || (rule.tot === 'global')) {
RED.util.evaluateNodeProperty(rule.to, rule.tot, node, msg, (err,value) => {
if (err) {
done(undefined,undefined);
} else {
done(undefined,value);
}
});
return
} else if (rule.tot === 'date') {
value = Date.now();
} else if (rule.tot === 'jsonata') {
return new Promise((resolve,reject) => {
RED.util.evaluateJSONataExpression(rule.to,msg, (err, value) => {
if (err) {
reject(RED._("change.errors.invalid-expr",{error:err.message}))
} else {
resolve(value);
}
});
RED.util.evaluateJSONataExpression(rule.to,msg, (err, value) => {
if (err) {
done(RED._("change.errors.invalid-expr",{error:err.message}))
} else {
done(undefined, value);
}
});
return;
}
return Promise.resolve(value);
done(undefined,value);
}
function getFromValue(msg,rule) {
function getFromValueType(fromValue, done) {
var fromType;
var fromRE;
if (typeof fromValue === 'number' || fromValue instanceof Number) {
fromType = 'num';
} else if (typeof fromValue === 'boolean') {
fromType = 'bool'
} else if (fromValue instanceof RegExp) {
fromType = 're';
fromRE = fromValue;
} else if (typeof fromValue === 'string') {
fromType = 'str';
fromRE = fromValue.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
try {
fromRE = new RegExp(fromRE, "g");
} catch (e) {
done(new Error(RED._("change.errors.invalid-from",{error:e.message})));
}
} else {
done(new Error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)})));
}
done(undefined,{
fromType,
fromValue,
fromRE
});
}
function getFromValue(msg,rule, done) {
var fromValue;
var fromType;
var fromRE;
if (rule.t === 'change') {
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {
return new Promise((resolve,reject) => {
if (rule.fromt === "msg") {
resolve(RED.util.getMessageProperty(msg,rule.from));
} else if (rule.fromt === 'flow' || rule.fromt === 'global') {
var contextKey = RED.util.parseContextStore(rule.from);
node.context()[rule.fromt].get(contextKey.key, contextKey.store, (err,fromValue) => {
if (err) {
reject(err);
} else {
resolve(fromValue);
}
});
}
}).then(fromValue => {
if (typeof fromValue === 'number' || fromValue instanceof Number) {
fromType = 'num';
} else if (typeof fromValue === 'boolean') {
fromType = 'bool'
} else if (fromValue instanceof RegExp) {
fromType = 're';
fromRE = fromValue;
} else if (typeof fromValue === 'string') {
fromType = 'str';
fromRE = fromValue.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
try {
fromRE = new RegExp(fromRE, "g");
} catch (e) {
throw new Error(RED._("change.errors.invalid-from",{error:e.message}));
if (rule.fromt === "msg") {
return getFromValueType(RED.util.getMessageProperty(msg,rule.from),done);
} else if (rule.fromt === 'flow' || rule.fromt === 'global') {
var contextKey = RED.util.parseContextStore(rule.from);
node.context()[rule.fromt].get(contextKey.key, contextKey.store, (err,fromValue) => {
if (err) {
done(err)
} else {
getFromValueType(fromValue,done);
}
} else {
throw new Error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)}));
}
return {
fromType,
fromValue,
fromRE
}
});
});
return;
}
} else {
fromType = rule.fromt;
fromValue = rule.from;
fromRE = rule.fromRE;
}
}
return Promise.resolve({
done(undefined, {
fromType,
fromValue,
fromRE
});
}
function applyRule(msg,rule) {
var property = rule.p;
var current;
var fromValue;
var fromType;
var fromRE;
try {
return getToValue(msg,rule).then(value => {
return getFromValue(msg,rule).then(fromParts => {
fromValue = fromParts.fromValue;
fromType = fromParts.fromType;
fromRE = fromParts.fromRE;
if (rule.pt === 'msg') {
try {
if (rule.t === 'delete') {
RED.util.setMessageProperty(msg,property,undefined);
} else if (rule.t === 'set') {
RED.util.setMessageProperty(msg,property,value);
} else if (rule.t === 'change') {
current = RED.util.getMessageProperty(msg,property);
if (typeof current === 'string') {
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
// str representation of exact from number/boolean
// only replace if they match exactly
function applyRule(msg,rule,done) {
var property = rule.p;
var current;
var fromValue;
var fromType;
var fromRE;
try {
getToValue(msg,rule,(err,value) => {
if (err) {
node.error(err, msg);
return done(undefined,null);
} else {
getFromValue(msg,rule,(err,fromParts) => {
if (err) {
node.error(err, msg);
return done(undefined,null);
} else {
fromValue = fromParts.fromValue;
fromType = fromParts.fromType;
fromRE = fromParts.fromRE;
if (rule.pt === 'msg') {
try {
if (rule.t === 'delete') {
RED.util.setMessageProperty(msg,property,undefined);
} else if (rule.t === 'set') {
RED.util.setMessageProperty(msg,property,value);
} else {
current = current.replace(fromRE,value);
RED.util.setMessageProperty(msg,property,current);
} else if (rule.t === 'change') {
current = RED.util.getMessageProperty(msg,property);
if (typeof current === 'string') {
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
// str representation of exact from number/boolean
// only replace if they match exactly
RED.util.setMessageProperty(msg,property,value);
} else {
current = current.replace(fromRE,value);
RED.util.setMessageProperty(msg,property,current);
}
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
if (current == Number(fromValue)) {
RED.util.setMessageProperty(msg,property,value);
}
} else if (typeof current === 'boolean' && fromType === 'bool') {
if (current.toString() === fromValue) {
RED.util.setMessageProperty(msg,property,value);
}
}
}
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
if (current == Number(fromValue)) {
RED.util.setMessageProperty(msg,property,value);
}
} else if (typeof current === 'boolean' && fromType === 'bool') {
if (current.toString() === fromValue) {
RED.util.setMessageProperty(msg,property,value);
}
}
}
} catch(err) {}
return msg;
} else if (rule.pt === 'flow' || rule.pt === 'global') {
var contextKey = RED.util.parseContextStore(property);
return new Promise((resolve,reject) => {
var target = node.context()[rule.pt];
var callback = err => {
if (err) {
reject(err);
} else {
resolve(msg);
}
}
if (rule.t === 'delete') {
target.set(contextKey.key,undefined,contextKey.store,callback);
} else if (rule.t === 'set') {
target.set(contextKey.key,value,contextKey.store,callback);
} else if (rule.t === 'change') {
target.get(contextKey.key,contextKey.store,(err,current) => {
} catch(err) {}
return done(undefined,msg);
} else if (rule.pt === 'flow' || rule.pt === 'global') {
var contextKey = RED.util.parseContextStore(property);
var target = node.context()[rule.pt];
var callback = err => {
if (err) {
reject(err);
return;
node.error(err, msg);
return done(undefined,null);
} else {
done(undefined,msg);
}
if (typeof current === 'string') {
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
// str representation of exact from number/boolean
// only replace if they match exactly
target.set(contextKey.key,value,contextKey.store,callback);
} else {
current = current.replace(fromRE,value);
target.set(contextKey.key,current,contextKey.store,callback);
}
if (rule.t === 'delete') {
target.set(contextKey.key,undefined,contextKey.store,callback);
} else if (rule.t === 'set') {
target.set(contextKey.key,value,contextKey.store,callback);
} else if (rule.t === 'change') {
target.get(contextKey.key,contextKey.store,(err,current) => {
if (err) {
node.error(err, msg);
return done(undefined,null);
}
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
if (current == Number(fromValue)) {
target.set(contextKey.key,value,contextKey.store,callback);
if (typeof current === 'string') {
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
// str representation of exact from number/boolean
// only replace if they match exactly
target.set(contextKey.key,value,contextKey.store,callback);
} else {
current = current.replace(fromRE,value);
target.set(contextKey.key,current,contextKey.store,callback);
}
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
if (current == Number(fromValue)) {
target.set(contextKey.key,value,contextKey.store,callback);
}
} else if (typeof current === 'boolean' && fromType === 'bool') {
if (current.toString() === fromValue) {
target.set(contextKey.key,value,contextKey.store,callback);
}
}
} else if (typeof current === 'boolean' && fromType === 'bool') {
if (current.toString() === fromValue) {
target.set(contextKey.key,value,contextKey.store,callback);
}
}
});
});
}
}
});
}
});
}).catch(err => {
node.error(err, msg);
return null;
}
})
}
});
} catch(err) {
return Promise.resolve(msg);
// This is an okay error
done(undefined,msg);
}
}
function applyRules(msg, currentRule) {
function completeApplyingRules(msg,currentRule,done) {
if (!msg) {
return done();
} else if (currentRule === node.rules.length - 1) {
return done(undefined, msg);
} else {
applyRules(msg, currentRule+1,done);
}
}
function applyRules(msg, currentRule, done) {
if (currentRule >= node.rules.length) {
return Promise.resolve(msg);
return done(undefined,msg);
}
var r = node.rules[currentRule];
var rulePromise;
if (r.t === "move") {
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) {
rulePromise = applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt}).then(
msg => applyRule(msg,{t:"delete", p:r.p, pt:r.pt})
);
}
else { // 2 step move if we are moving from a child
rulePromise = applyRule(msg,{t:"set", p:"_temp_move", pt:r.tot, to:r.p, tot:r.pt}).then(
msg => applyRule(msg,{t:"delete", p:r.p, pt:r.pt})
).then(
msg => applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:"_temp_move", tot:r.pt})
).then(
msg => applyRule(msg,{t:"delete", p:"_temp_move", pt:r.pt})
)
applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt},(err,msg) => {
applyRule(msg,{t:"delete", p:r.p, pt:r.pt}, (err,msg) => {
completeApplyingRules(msg,currentRule,done);
})
});
} else { // 2 step move if we are moving from a child
applyRule(msg,{t:"set", p:"_temp_move", pt:r.tot, to:r.p, tot:r.pt},(err,msg)=> {
applyRule(msg,{t:"delete", p:r.p, pt:r.pt},(err,msg)=> {
applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:"_temp_move", tot:r.pt},(err,msg)=> {
applyRule(msg,{t:"delete", p:"_temp_move", pt:r.pt},(err,msg)=> {
completeApplyingRules(msg,currentRule,done);
});
});
});
});
}
} else {
rulePromise = applyRule(msg,r);
applyRule(msg,r,(err,msg)=> { completeApplyingRules(msg,currentRule,done); });
}
return rulePromise.then(
msg => {
if (!msg) {
return
} else if (currentRule === node.rules.length - 1) {
return msg;
} else {
return applyRules(msg, currentRule+1);
}
}
);
}
if (valid) {
this.on('input', function(msg) {
applyRules(msg, 0)
.then( msg => { if (msg) { node.send(msg) }} )
.catch( err => node.error(err, msg))
applyRules(msg, 0, (err,msg) => {
if (err) {
node.error(err,msg);
} else if (msg) {
node.send(msg);
}
})
});
}
}

View File

@@ -233,48 +233,26 @@ module.exports = function(RED) {
RED.nodes.registerType("split",SplitNode);
var _max_kept_msgs_count;
var _maxKeptMsgsCount;
function max_kept_msgs_count(node) {
if (_max_kept_msgs_count === undefined) {
function maxKeptMsgsCount(node) {
if (_maxKeptMsgsCount === undefined) {
var name = "nodeMessageBufferMaxLength";
if (RED.settings.hasOwnProperty(name)) {
_max_kept_msgs_count = RED.settings[name];
_maxKeptMsgsCount = RED.settings[name];
}
else {
_max_kept_msgs_count = 0;
_maxKeptMsgsCount = 0;
}
}
return _max_kept_msgs_count;
return _maxKeptMsgsCount;
}
function apply_r(exp, accum, msg, index, count) {
function applyReduce(exp, accum, msg, index, count, done) {
exp.assign("I", index);
exp.assign("N", count);
exp.assign("A", accum);
return new Promise((resolve,reject) => {
RED.util.evaluateJSONataExpression(exp, msg, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
function apply_f(exp, accum, count) {
exp.assign("N", count);
exp.assign("A", accum);
return new Promise((resolve,reject) => {
return RED.util.evaluateJSONataExpression(exp, {}, (err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
RED.util.evaluateJSONataExpression(exp, msg, done);
}
function exp_or_undefined(exp) {
@@ -285,39 +263,68 @@ module.exports = function(RED) {
return exp
}
function reduceAndSendGroup(node, group) {
function reduceMessageGroup(node,msgs,exp,fixup,count,accumulator,done) {
var msg = msgs.shift();
exp.assign("I", msg.parts.index);
exp.assign("N", count);
exp.assign("A", accumulator);
RED.util.evaluateJSONataExpression(exp, msg, (err,result) => {
if (err) {
return done(err);
}
if (msgs.length === 0) {
if (fixup) {
fixup.assign("N", count);
fixup.assign("A", result);
RED.util.evaluateJSONataExpression(fixup, {}, (err, result) => {
if (err) {
return done(err);
}
node.send({payload: result});
done();
});
} else {
node.send({payload: result});
done();
}
} else {
reduceMessageGroup(node,msgs,exp,fixup,count,result,done);
}
});
}
function reduceAndSendGroup(node, group, done) {
var is_right = node.reduce_right;
var flag = is_right ? -1 : 1;
var msgs = group.msgs;
return getInitialReduceValue(node, node.exp_init, node.exp_init_type).then(accum => {
var reduce_exp = node.reduce_exp;
var reduce_fixup = node.reduce_fixup;
var count = group.count;
msgs.sort(function(x,y) {
var ix = x.parts.index;
var iy = y.parts.index;
if (ix < iy) {return -flag;}
if (ix > iy) {return flag;}
return 0;
});
return msgs.reduce((promise, msg) => promise.then(accum => apply_r(reduce_exp, accum, msg, msg.parts.index, count)), Promise.resolve(accum))
.then(accum => {
if(reduce_fixup !== undefined) {
return apply_f(reduce_fixup, accum, count).then(accum => {
node.send({payload: accum});
});
} else {
node.send({payload: accum});
}
try {
RED.util.evaluateNodeProperty(node.exp_init, node.exp_init_type, node, {}, (err,accum) => {
var reduceExpression = node.reduceExpression;
var fixupExpression = node.fixupExpression;
var count = group.count;
msgs.sort(function(x,y) {
var ix = x.parts.index;
var iy = y.parts.index;
if (ix < iy) {return -flag;}
if (ix > iy) {return flag;}
return 0;
});
}).catch(err => {
throw new Error(RED._("join.errors.invalid-expr",{error:err.message}));
});
reduceMessageGroup(node, msgs,reduceExpression,fixupExpression,count,accum,(err,result) => {
if (err) {
done(err);
return;
} else {
done();
}
})
});
} catch(err) {
done(new Error(RED._("join.errors.invalid-expr",{error:err.message})));
}
}
function reduce_msg(node, msg) {
var promise;
function reduceMessage(node, msg, done) {
if (msg.hasOwnProperty('parts')) {
var parts = msg.parts;
var pending = node.pending;
@@ -335,51 +342,37 @@ module.exports = function(RED) {
}
var group = pending[gid];
var msgs = group.msgs;
if(parts.hasOwnProperty('count') && (group.count === undefined)) {
if (parts.hasOwnProperty('count') && (group.count === undefined)) {
group.count = parts.count;
}
msgs.push(msg);
pending_count++;
var completeProcess = function() {
var completeProcess = function(err) {
if (err) {
return done(err);
}
node.pending_count = pending_count;
var max_msgs = max_kept_msgs_count(node);
var max_msgs = maxKeptMsgsCount(node);
if ((max_msgs > 0) && (pending_count > max_msgs)) {
node.pending = {};
node.pending_count = 0;
var promise = Promise.reject(RED._("join.too-many"));
promise.catch(()=>{});
return promise;
done(RED._("join.too-many"));
return;
}
return Promise.resolve();
return done();
}
if(msgs.length === group.count) {
if (msgs.length === group.count) {
delete pending[gid];
pending_count -= msgs.length;
promise = reduceAndSendGroup(node, group).then(completeProcess);
reduceAndSendGroup(node, group, completeProcess)
} else {
promise = completeProcess();
completeProcess();
}
} else {
node.send(msg);
done();
}
if (!promise) {
promise = Promise.resolve();
}
return promise;
}
function getInitialReduceValue(node, exp, exp_type) {
return new Promise((resolve, reject) => {
RED.util.evaluateNodeProperty(exp, exp_type, node, {},
(err, result) => {
if(err) {
return reject(err);
}
else {
return resolve(result);
}
});
});
}
function JoinNode(n) {
@@ -404,8 +397,8 @@ module.exports = function(RED) {
var exp_fixup = exp_or_undefined(n.reduceFixup);
this.reduce_right = n.reduceRight;
try {
this.reduce_exp = RED.util.prepareJSONataExpression(exp_reduce, this);
this.reduce_fixup = (exp_fixup !== undefined) ? RED.util.prepareJSONataExpression(exp_fixup, this) : undefined;
this.reduceExpression = RED.util.prepareJSONataExpression(exp_reduce, this);
this.fixupExpression = (exp_fixup !== undefined) ? RED.util.prepareJSONataExpression(exp_fixup, this) : undefined;
} catch(e) {
this.error(RED._("join.errors.invalid-expr",{error:e.message}));
return;
@@ -426,9 +419,6 @@ module.exports = function(RED) {
this.build = n.build || "array";
this.accumulate = n.accumulate || "false";
this.topics = (n.topics || []).map(function(x) { return x.topic; });
this.merge_on_change = n.mergeOnChange || false;
this.topic_counts = undefined;
this.output = n.output || "stream";
this.pending = {};
this.pending_count = 0;
@@ -493,7 +483,7 @@ module.exports = function(RED) {
}
var pendingMessages = [];
var activeMessagePromise = null;
var activeMessage = null;
// In reduce mode, we must process messages fully in order otherwise
// groups may overlap and cause unexpected results. The use of JSONata
// means some async processing *might* occur if flow/global context is
@@ -502,7 +492,7 @@ module.exports = function(RED) {
if (msg) {
// A new message has arrived - add it to the message queue
pendingMessages.push(msg);
if (activeMessagePromise !== null) {
if (activeMessage !== null) {
// The node is currently processing a message, so do nothing
// more with this message
return;
@@ -511,19 +501,21 @@ module.exports = function(RED) {
if (pendingMessages.length === 0) {
// There are no more messages to process, clear the active flag
// and return
activeMessagePromise = null;
activeMessage = null;
return;
}
// There are more messages to process. Get the next message and
// start processing it. Recurse back in to check for any more
var nextMsg = pendingMessages.shift();
activeMessagePromise = reduce_msg(node, nextMsg)
.then(processReduceMessageQueue)
.catch((err) => {
activeMessage = true;
reduceMessage(node, nextMsg, err => {
if (err) {
node.error(err,nextMsg);
return processReduceMessageQueue();
});
}
activeMessage = null;
processReduceMessageQueue();
})
}
this.on("input", function(msg) {

View File

@@ -56,6 +56,7 @@ module.exports = function(RED) {
RED.util.setMessageProperty(msg,node.property,JSON.parse(value));
if (validate) {
if (this.compiledSchema(msg[node.property])) {
delete msg.schema;
node.send(msg);
} else {
msg.schemaError = this.compiledSchema.errors;
@@ -70,6 +71,7 @@ module.exports = function(RED) {
// If node.action is str and value is str
if (validate) {
if (this.compiledSchema(JSON.parse(msg[node.property]))) {
delete msg.schema;
node.send(msg);
} else {
msg.schemaError = this.compiledSchema.errors;
@@ -87,6 +89,7 @@ module.exports = function(RED) {
if (validate) {
if (this.compiledSchema(value)) {
RED.util.setMessageProperty(msg,node.property,JSON.stringify(value,null,node.indent));
delete msg.schema;
node.send(msg);
} else {
msg.schemaError = this.compiledSchema.errors;
@@ -104,6 +107,7 @@ module.exports = function(RED) {
// If node.action is obj and value is object
if (validate) {
if (this.compiledSchema(value)) {
delete msg.schema;
node.send(msg);
} else {
msg.schemaError = this.compiledSchema.errors;

View File

@@ -15,7 +15,7 @@
-->
<script type="text/x-red" data-help-name="debug">
<p>Displays selected message properties in the debug sidebar tab and optionally the runtime log. By default it displays <code>msg.payload</code>.</p>
<p>Displays selected message properties in the debug sidebar tab and optionally the runtime log. By default it displays <code>msg.payload</code>, but can be configured to display any property, the full message or the result of a JSONata expression.</p>
<h3>Details</h3>
<p>The debug sidebar provides a structured view of the messages it is sent, making it easier to understand their structure.</p>
<p>JavaScript objects and arrays can be collapsed and expanded as required. Buffer objects can be displayed as raw data or as a string if possible.</p>

View File

@@ -103,6 +103,8 @@
},
"debug": {
"output": "Output",
"none": "None",
"invalid-exp": "Invalid JSONata expression: __error__",
"msgprop": "message property",
"msgobj": "complete msg object",
"to": "To",
@@ -136,14 +138,7 @@
},
"link": {
"linkIn": "link in",
"linkOut": "link out",
"label": {
"event": "Event name",
"node": "name",
"type": "flow",
"sortByFlow":"Sort by flow",
"sortByLabel": "Sort by name"
}
"linkOut": "link out"
},
"tls": {
"tls": "TLS configuration",
@@ -307,12 +302,7 @@
}
},
"comment": {
"comment": "comment",
"label": {
"title": "Title",
"body": "Body"
},
"tip": "Tip: The body text can be styled as <a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_blank\">GitHub flavoured Markdown</a>"
"comment": "comment"
},
"unknown": {
"label": {
@@ -442,7 +432,8 @@
"errors": {
"connect-error": "An error occured on the ws connection: ",
"send-error": "An error occurred while sending: ",
"missing-conf": "Missing server configuration"
"missing-conf": "Missing server configuration",
"duplicate-path": "Cannot have two WebSocket listeners on the same path: __path__"
}
},
"watch": {

View File

@@ -21,7 +21,8 @@
<dt>payload<span class="property-type">object | string</span></dt>
<dd>A JavaScript object or JSON string.</dd>
<dt>schema<span class="property-type">object</span></dt>
<dd>An optional JSON Schema object to validate the payload against.</dd>
<dd>An optional JSON Schema object to validate the payload against.
The property will be deleted before the <code>msg</code> is sent to the next node.</dd>
</dl>
<h3>Outputs</h3>
<dl class="message-properties">

View File

@@ -15,7 +15,7 @@
-->
<script type="text/x-red" data-help-name="debug">
<p>サイドバーのデバッグタブに選択したメッセージプロパティの値を表示します設定によりランタイムログへの出力も可能ですデフォルトの表示対象は<code>msg.payload</code></p>
<p>サイドバーのデバッグタブに選択したメッセージプロパティの値を表示します設定によりランタイムログへの出力も可能ですデフォルトの表示対象は<code>msg.payload</code>JSONata</p>
<h3>詳細</h3>
<p>デバッグサイドバーは受け取ったメッセージの階層構造を表示する機能を備えますこの機能によりメッセージの構造を容易に理解できます</p>
<p>JavaScriptオブジェクトと配列は必要に応じて折り畳んだり展開したりできますバッファオブジェクトを生データとして表示したり表現可能な場合に文字列として表示したりすることも可能です</p>

View File

@@ -103,6 +103,8 @@
},
"debug": {
"output": "対象",
"none": "無し",
"invalid-exp": "JSONata式が不正: __error__",
"msgprop": "メッセージプロパティ",
"msgobj": "msgオブジェクト全体",
"to": "出力先",
@@ -136,14 +138,7 @@
},
"link": {
"linkIn": "link in",
"linkOut": "link out",
"label": {
"event": "イベント名",
"node": "名前",
"type": "フロー",
"sortByFlow": "フロー名で並べ替え",
"sortByLabel": "名前で並べ替え"
}
"linkOut": "link out"
},
"tls": {
"tls": "TLS設定",
@@ -307,12 +302,7 @@
}
},
"comment": {
"comment": "comment",
"label": {
"title": "タイトル",
"body": "本文"
},
"tip": "注釈: 本文は<a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_blank\">GitHubのMarkdown形式</a>として整形されます。"
"comment": "comment"
},
"unknown": {
"label": {

View File

@@ -135,14 +135,7 @@
},
"link": {
"linkIn": "输入",
"linkOut": "输出",
"label": {
"event": "事件名称",
"node": "节点名称",
"type": "流程",
"sortByFlow":"根据流程排序",
"sortByLabel": "根据名称排序"
}
"linkOut": "输出"
},
"tls": {
"tls": "TLS设置",
@@ -297,11 +290,6 @@
}
},
"comment": {
"label": {
"title": "标题",
"body": "主体"
},
"tip": "提示: 主题内容可被格式化为 <a href=\"https://help.github.com/articles/markdown-basics/\" target=\"_blank\">GitHub风格的Markdown</a>"
},
"unknown": {
"label": {

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/nodes",
"version": "0.20.0-beta.2",
"version": "0.20.0-beta.3",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -15,20 +15,20 @@
}
],
"dependencies": {
"ajv": "6.6.1",
"ajv": "6.6.2",
"body-parser": "1.18.3",
"cheerio": "0.22.0",
"cookie-parser": "1.4.3",
"cookie": "0.3.1",
"cors": "2.8.5",
"cron": "1.5.1",
"cron": "1.6.0",
"denque": "1.4.0",
"fs-extra": "7.0.1",
"fs.notify": "0.0.4",
"hash-sum": "1.0.2",
"https-proxy-agent": "2.2.1",
"is-utf8": "0.2.1",
"js-yaml": "3.12.0",
"js-yaml": "3.12.1",
"media-typer": "1.0.1",
"mqtt": "2.18.8",
"multer": "1.4.1",
@@ -37,7 +37,7 @@
"raw-body": "2.3.3",
"request": "2.88.0",
"sentiment": "2.1.0",
"ws": "1.1.5",
"ws": "6.1.2",
"xml2js": "0.4.19"
}
}

View File

@@ -270,6 +270,10 @@ function getNodeInfo(typeOrId) {
if (config.hasOwnProperty("loaded")) {
info.loaded = config.loaded;
}
if (module.pending_version) {
info.pending_version = module.pending_version;
}
info.version = module.version;
return info;
}
@@ -342,6 +346,9 @@ function getModuleInfo(module) {
path: moduleConfigs[module].path,
nodes: []
};
if (moduleConfigs[module] && moduleConfigs[module].pending_version) {
m.pending_version = moduleConfigs[module].pending_version;
}
for (var i = 0; i < nodes.length; ++i) {
var nodeInfo = filterNodeInfo(moduleConfigs[module].nodes[nodes[i]]);
nodeInfo.version = m.version;

View File

@@ -14,7 +14,9 @@
* limitations under the License.
**/
var path = require("path");
var i18n = require("@node-red/util").i18n;
var registry;
var runtime;
function copyObjectProperties(src,dst,copyList,blockList) {
@@ -105,6 +107,7 @@ function createNodeApi(node) {
module.exports = {
init: function(_runtime) {
runtime = _runtime;
registry = require("@node-red/registry/lib");
},
createNodeApi: createNodeApi
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/registry",
"version": "0.20.0-beta.2",
"version": "0.20.0-beta.3",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,7 +16,7 @@
}
],
"dependencies": {
"@node-red/util": "0.20.0-beta.2",
"@node-red/util": "0.20.0-beta.3",
"semver": "5.6.0",
"uglify-js": "3.4.9",
"when": "3.7.8"

View File

@@ -209,6 +209,7 @@ LocalFileSystem.prototype.open = function(){
}
});
} else {
self._flushPendingWrites = function() { }
return fs.ensureDir(self.storageBaseDir);
}
}
@@ -219,6 +220,12 @@ LocalFileSystem.prototype.close = function(){
clearTimeout(this._pendingWriteTimeout);
delete this._pendingWriteTimeout;
this.flushInterval = 0;
self.writePromise = self.writePromise.then(function(){
return self._flushPendingWrites.call(self).catch(function(err) {
log.error(log._("context.localfilesystem.error-write",{message:err.toString()}));
});
});
}
return this.writePromise;
}

View File

@@ -151,8 +151,8 @@ function reportNodeStateChange(info,enabled) {
}
function installModule(module,version) {
var ex_module = registry.getModuleInfo(module);
var isUpgrade = !!ex_module;
var existingModule = registry.getModuleInfo(module);
var isUpgrade = !!existingModule;
return registry.installModule(module,version).then(function(info) {
if (isUpgrade) {
events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:module,version:version}});

View File

@@ -34,7 +34,7 @@ function getFileMeta(root,path) {
var read = 0;
var length = 10;
var remaining = "";
var buffer = Buffer(length);
var buffer = Buffer.alloc(length);
while(read < size) {
read+=fs.readSync(fd,buffer,0,length);
var data = remaining+buffer.toString();
@@ -63,7 +63,7 @@ function getFileBody(root,path) {
var read = 0;
var length = 50;
var remaining = "";
var buffer = Buffer(length);
var buffer = Buffer.alloc(length);
while(read < size) {
var thisRead = fs.readSync(fd,buffer,0,length);
read += thisRead;

View File

@@ -137,17 +137,21 @@ function init(_settings, _runtime) {
saveSettings = true;
} else {
// if it resolves to a dir - use it
var stat = fs.statSync(fspath.join(projectsDir,settings.flowFile));
if (stat && stat.isDirectory()) {
activeProject = settings.flowFile;
globalSettings.projects.activeProject = activeProject;
// Now check for a credentialSecret
if (settings.credentialSecret !== undefined) {
globalSettings.projects.projects[settings.flowFile] = {
credentialSecret: settings.credentialSecret
try {
var stat = fs.statSync(fspath.join(projectsDir,settings.flowFile));
if (stat && stat.isDirectory()) {
activeProject = settings.flowFile;
globalSettings.projects.activeProject = activeProject;
// Now check for a credentialSecret
if (settings.credentialSecret !== undefined) {
globalSettings.projects.projects[settings.flowFile] = {
credentialSecret: settings.credentialSecret
}
saveSettings = true;
}
saveSettings = true;
}
} catch(err) {
// Doesn't exist, handle as a flow file to be created
}
}
}

3
packages/node_modules/@node-red/runtime/locales/ja/runtime.json vendored Normal file → Executable file
View File

@@ -12,7 +12,8 @@
"loading": "パレットノードのロード",
"palette-editor": {
"disabled": "パレットエディタを無効化 : ユーザ設定",
"npm-not-found": "レットエディタを無効化 : npmコマンドが見つかりません"
"npm-not-found": "レットエディタを無効化 : npmコマンドが見つかりません",
"npm-too-old": "パレットエディタを無効化 : npmのバージョンが古過ぎます。npm 3.x以上が必要です"
},
"errors": "__count__ 個のノードの登録に失敗しました",
"errors_plural": "__count__ 個のノードの登録に失敗しました",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/runtime",
"version": "0.20.0-beta.2",
"version": "0.20.0-beta.3",
"license": "Apache-2.0",
"main": "./lib/index.js",
"repository": {
@@ -16,8 +16,8 @@
}
],
"dependencies": {
"@node-red/registry": "0.20.0-beta.2",
"@node-red/util": "0.20.0-beta.2",
"@node-red/registry": "0.20.0-beta.3",
"@node-red/util": "0.20.0-beta.3",
"clone": "2.1.2",
"express": "4.16.4",
"fs-extra": "7.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "@node-red/util",
"version": "0.20.0-beta.2",
"version": "0.20.0-beta.3",
"license": "Apache-2.0",
"repository": {
"type": "git",
@@ -16,9 +16,9 @@
],
"dependencies": {
"clone": "2.1.2",
"i18next": "12.1.0",
"i18next": "13.1.0",
"json-stringify-safe": "5.0.1",
"jsonata": "1.5.4",
"jsonata": "1.6.3",
"when": "3.7.8"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "node-red",
"version": "0.20.0-beta.2",
"version": "0.20.0-beta.3",
"description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org",
"license": "Apache-2.0",
@@ -31,10 +31,10 @@
"flow"
],
"dependencies": {
"@node-red/editor-api": "0.20.0-beta.2",
"@node-red/runtime": "0.20.0-beta.2",
"@node-red/util": "0.20.0-beta.2",
"@node-red/nodes": "0.20.0-beta.2",
"@node-red/editor-api": "0.20.0-beta.3",
"@node-red/runtime": "0.20.0-beta.3",
"@node-red/util": "0.20.0-beta.3",
"@node-red/nodes": "0.20.0-beta.3",
"basic-auth": "2.0.1",
"bcryptjs": "2.4.3",
"express": "4.16.4",

View File

@@ -247,7 +247,7 @@ function basicAuthMiddleware(user,pass) {
}
var requestUser = basicAuth(req);
if (!requestUser || requestUser.name !== user || !checkPasswordAndCache(requestUser.pass)) {
res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
res.set('WWW-Authenticate', 'Basic realm="Authorization Required"');
return res.sendStatus(401);
}
next();

View File

@@ -64,6 +64,7 @@ if (require.main === module) {
verifyDependencies().then(failures => {
if (failures.length > 0) {
failures.forEach(f => console.log(` - ${f}`));
console.log("Run with --fix option to fix up versions")
process.exit(1);
}
}).catch(e => {

View File

@@ -18,6 +18,8 @@ var util = require("util");
var nodePage = require("../../node_page");
var keyPage = require("../../../util/key_page");
function debugNode(id) {
nodePage.call(this, id);
}
@@ -32,7 +34,7 @@ debugNode.prototype.setOutput = function(complete) {
browser.clickWithWait('//div[@class="red-ui-typedInput-options"][1]/a[1]');
// Input the path in msg.
browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-input")]/input');
browser.keys(['Control', 'a', 'Control']);
browser.keys(keyPage.selectAll());
browser.keys(['Delete']);
browser.setValue('//*[contains(@class, "red-ui-typedInput-input")]/input', complete);
} else {

View File

@@ -18,6 +18,8 @@ var util = require("util");
var nodePage = require("../../node_page");
var keyPage = require("../../../util/key_page");
function functionNode(id) {
nodePage.call(this, id);
}
@@ -25,13 +27,13 @@ function functionNode(id) {
util.inherits(functionNode, nodePage);
functionNode.prototype.setFunction = function(func) {
browser.click('#node-input-func-editor');
browser.keys(['Control', 'Home', 'Control']);
browser.clickWithWait('#node-input-func-editor');
browser.keys(keyPage.selectAll());
for (var i = 0; i < func.length; i++) {
browser.keys([func.charAt(i)]);
}
// Delete the unnecessary code that ace editor does the autocompletion.
browser.keys(['Control', 'Shift', 'End', 'Shift', 'Control']);
browser.keys(keyPage.selectToEnd());
browser.keys(['Delete']);
// Need to wait until ace editor correctly checks the syntax.
browser.pause(300);

View File

@@ -18,6 +18,8 @@ var util = require("util");
var nodePage = require("../../node_page");
var keyPage = require("../../../util/key_page");
function templateNode(id) {
nodePage.call(this, id);
}
@@ -33,13 +35,13 @@ templateNode.prototype.setFormat = function(format) {
}
templateNode.prototype.setTemplate = function(template) {
browser.click('#node-input-template-editor');
browser.keys(['Control', 'a', 'Control']); // call twice to release the keys.
browser.clickWithWait('#node-input-template-editor');
browser.keys(keyPage.selectAll());
// Need to add a character one by one since some words such as 'Control' are treated as a special word.
for (var i = 0; i < template.length; i++) {
browser.keys([template.charAt(i)]);
}
browser.keys(['Control', 'Shift', 'End', 'Shift', 'Control']);
browser.keys(keyPage.selectToEnd());
browser.keys(['Delete']);
// Need to wait until ace editor correctly checks the syntax.
browser.pause(300);

View File

@@ -0,0 +1,50 @@
/**
* 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.
**/
var os = require("os");
var shortCutKeyMap = {
"selectAll": ['Control', 'a', 'Control'],
"selectToEnd": ['Control', 'Shift', 'End', 'Shift', 'Control'],
};
var shortCutKeyMapForMac = {
"selectAll": ['Command', 'a', 'Command'],
"selectToEnd": ['Command', 'Shift', 'ArrowDown', 'Shift', 'Command'],
};
function getShortCutKey(type) {
if (os.type() === "Darwin") {
return shortCutKeyMapForMac[type];
} else {
return shortCutKeyMap[type];
}
}
function selectAll() {
var key = getShortCutKey('selectAll');
return key;
}
function selectToEnd() {
var key = getShortCutKey('selectToEnd');
return key;
}
module.exports = {
selectAll: selectAll,
selectToEnd: selectToEnd,
};

View File

@@ -513,7 +513,7 @@ describe('cookbook', function() {
var changeNode = workspace.addNode("change", 400);
var httpinNodeClear = workspace.addNode("httpin", 0, 200);
var functionNodeClear = workspace.addNode("function", 240);
var functionNodeClear = workspace.addNode("function", 250);
httpinNodeFormat.edit();
httpinNodeFormat.setMethod("get");

View File

@@ -356,6 +356,22 @@ describe('debug node', function() {
});
});
it('should publish complete message with edit', function(done) {
var flow = [{id:"n1", type:"debug", name:"Debug", complete: "true",
targetType: "jsonata", complete: '"<" & payload & ">"'}];
helper.load(debugNode, flow, function() {
var n1 = helper.getNode("n1");
websocket_test(function() {
n1.emit("input", {payload:"test"});
}, function(msg) {
JSON.parse(msg).should.eql([{
topic:"debug",data:{id:"n1",name:"Debug",msg:"<test>",
format:"string[6]"}
}]);
}, done);
});
});
it('should truncate a long message', function(done) {
var flow = [{id:"n1", type:"debug" }];
helper.load(debugNode, flow, function() {

View File

@@ -433,4 +433,36 @@ describe('JSON node', function() {
}
});
});
it('msg.schema property should be deleted before sending to next node (string input)', function(done) {
var flow = [{id:"jn1",type:"json",action:"str",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
should.equal(msg.schema, undefined);
done();
});
var jsonString = '{"number":3,"string":"allo"}';
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
jn1.receive({payload:jsonString, schema:schema});
});
});
it('msg.schema property should be deleted before sending to next node (object input)', function(done) {
var flow = [{id:"jn1",type:"json",action:"str",wires:[["jn2"]]},
{id:"jn2", type:"helper"}];
helper.load(jsonNode, flow, function() {
var jn1 = helper.getNode("jn1");
var jn2 = helper.getNode("jn2");
jn2.on("input", function(msg) {
should.equal(msg.schema, undefined);
done();
});
var jsonObject = {"number":3,"string":"allo"};
var schema = {title: "testSchema", type: "object", properties: {number: {type: "number"}, string: {type: "string" }}};
jn1.receive({payload:jsonObject, schema:schema});
});
});
});

View File

@@ -74,7 +74,7 @@ describe('api/editor/credentials', function() {
request(app)
.get("/credentials/unknown-type/n2")
.expect("Content-Type",/json/)
.expect(500)
.expect(400)
.end(function(err,res) {
if (err) {
done(err);

View File

@@ -102,7 +102,7 @@ describe("api/editor/sshkeys", function() {
mockRuntime.settings.getUserKeys.returns(p);
request(app)
.get("/settings/user/keys")
.expect(500)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
@@ -139,7 +139,7 @@ describe("api/editor/sshkeys", function() {
mockRuntime.settings.getUserKeys.returns(p)
request(app)
.get("/settings/user/keys")
.expect(500)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
@@ -179,7 +179,7 @@ describe("api/editor/sshkeys", function() {
mockRuntime.settings.getUserKey.returns(p);
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(500)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
@@ -200,7 +200,7 @@ describe("api/editor/sshkeys", function() {
mockRuntime.settings.getUserKey.returns(p);
request(app)
.get("/settings/user/keys/" + key_file_name)
.expect(500)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
@@ -238,7 +238,7 @@ describe("api/editor/sshkeys", function() {
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(500)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
@@ -260,7 +260,7 @@ describe("api/editor/sshkeys", function() {
request(app)
.post("/settings/user/keys")
.send({ name: key_file_name })
.expect(500)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
@@ -297,7 +297,7 @@ describe("api/editor/sshkeys", function() {
mockRuntime.settings.removeUserKey.returns(p);
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(500)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
@@ -318,7 +318,7 @@ describe("api/editor/sshkeys", function() {
mockRuntime.settings.removeUserKey.returns(p);
request(app)
.delete("/settings/user/keys/" + key_file_name)
.expect(500)
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);