1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge branch 'dev' into master

This commit is contained in:
Nick O'Leary 2020-04-01 20:22:25 +01:00 committed by GitHub
commit e0f3e94e2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
404 changed files with 14036 additions and 2635 deletions

View File

@ -1,6 +1,6 @@
--- ---
name: Bug report name: Bug report
about: Reproducable software issues in the core of Node-RED about: Reproducible software issues in the core of Node-RED
title: '' title: ''
labels: '' labels: ''
assignees: '' assignees: ''

View File

@ -29,6 +29,6 @@ the [forum](https://discourse.nodered.org) or
<!-- Put an `x` in the boxes that apply --> <!-- Put an `x` in the boxes that apply -->
- [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md) - [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md)
- [ ] For non-bugfix PRs, I have discussed this change on the mailing list/slack team. - [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team.
- [ ] I have run `grunt` to verify the unit tests pass - [ ] I have run `grunt` to verify the unit tests pass
- [ ] I have added suitable unit tests to cover the new/changed functionality - [ ] I have added suitable unit tests to cover the new/changed functionality

File diff suppressed because it is too large Load Diff

View File

@ -26,9 +26,13 @@ module.exports = function(grunt) {
nodemonArgs.push(flowFile); nodemonArgs.push(flowFile);
} }
var browserstack = grunt.option('browserstack');
if (browserstack) {
process.env.BROWSERSTACK = true;
}
var nonHeadless = grunt.option('non-headless'); var nonHeadless = grunt.option('non-headless');
if (nonHeadless) { if (nonHeadless) {
process.env.NODE_RED_NON_HEADLESS = 'true'; process.env.NODE_RED_NON_HEADLESS = true;
} }
grunt.initConfig({ grunt.initConfig({
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
@ -80,20 +84,20 @@ module.exports = function(grunt) {
//"loopfunc": true, // allow functions to be defined in loops //"loopfunc": true, // allow functions to be defined in loops
//"sub": true // don't warn that foo['bar'] should be written as foo.bar //"sub": true // don't warn that foo['bar'] should be written as foo.bar
}, },
all: [ // all: [
'Gruntfile.js', // 'Gruntfile.js',
'red.js', // 'red.js',
'packages/**/*.js' // 'packages/**/*.js'
], // ],
core: { // core: {
files: { // files: {
src: [ // src: [
'Gruntfile.js', // 'Gruntfile.js',
'red.js', // 'red.js',
'packages/**/*.js', // 'packages/**/*.js',
] // ]
} // }
}, // },
nodes: { nodes: {
files: { files: {
src: [ 'nodes/core/*/*.js' ] src: [ 'nodes/core/*/*.js' ]
@ -101,7 +105,7 @@ module.exports = function(grunt) {
}, },
editor: { editor: {
files: { files: {
src: [ 'editor/js/**/*.js' ] src: [ 'packages/node_modules/@node-red/editor-client/src/js/**/*.js' ]
} }
}, },
tests: { tests: {
@ -147,6 +151,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/toggleButton.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/toggleButton.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/actions.js", "packages/node_modules/@node-red/editor-client/src/js/ui/actions.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js", "packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/diff.js", "packages/node_modules/@node-red/editor-client/src/js/ui/diff.js",
@ -173,6 +178,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js", "packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js", "packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js", "packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/group.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js", "packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js", "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js", "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js",
@ -189,7 +195,8 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.0.1.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.0.1.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js", "node_modules/marked/marked.min.js",
"node_modules/dompurify/dist/purify.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js",
"packages/node_modules/@node-red/editor-client/src/vendor/i18next/i18next.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/i18next/i18next.min.js",
"node_modules/jsonata/jsonata-es5.min.js", "node_modules/jsonata/jsonata-es5.min.js",

View File

@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "1.0.3", "version": "1.0.4",
"description": "Low-code programming for event-driven applications", "description": "Low-code programming for event-driven applications",
"homepage": "http://nodered.org", "homepage": "http://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",
@ -24,7 +24,7 @@
} }
], ],
"dependencies": { "dependencies": {
"ajv": "6.10.2", "ajv": "6.12.0",
"basic-auth": "2.0.1", "basic-auth": "2.0.1",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.19.0", "body-parser": "1.19.0",
@ -34,48 +34,50 @@
"cookie": "0.4.0", "cookie": "0.4.0",
"cookie-parser": "1.4.4", "cookie-parser": "1.4.4",
"cors": "2.8.5", "cors": "2.8.5",
"cron": "1.7.2", "cron": "1.8.2",
"denque": "1.4.1", "denque": "1.4.1",
"express": "4.17.1", "express": "4.17.1",
"express-session": "1.17.0", "express-session": "1.17.0",
"fs-extra": "8.1.0", "fs-extra": "8.1.0",
"fs.notify": "0.0.4", "fs.notify": "0.0.4",
"hash-sum": "2.0.0", "hash-sum": "2.0.0",
"https-proxy-agent": "2.2.4", "https-proxy-agent": "5.0.0",
"i18next": "15.1.2", "i18next": "15.1.2",
"iconv-lite": "0.5.0", "iconv-lite": "0.5.1",
"is-utf8": "0.2.1", "is-utf8": "0.2.1",
"js-yaml": "3.13.1", "js-yaml": "3.13.1",
"json-stringify-safe": "5.0.1", "json-stringify-safe": "5.0.1",
"jsonata": "1.7.0", "jsonata": "1.8.1",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"media-typer": "1.1.0", "media-typer": "1.1.0",
"memorystore": "1.6.1", "memorystore": "1.6.2",
"mime": "2.4.4", "mime": "2.4.4",
"mqtt": "2.18.8", "mqtt": "2.18.8",
"multer": "1.4.2", "multer": "1.4.2",
"mustache": "3.0.2", "mustache": "4.0.0",
"node-red-node-rbe": "^0.2.5", "node-red-node-rbe": "^0.2.6",
"node-red-node-sentiment": "^0.1.4", "node-red-node-sentiment": "^0.1.6",
"node-red-node-tail": "^0.0.3", "node-red-node-tail": "^0.1.0",
"nopt": "4.0.1", "nopt": "4.0.1",
"oauth2orize": "1.11.0", "oauth2orize": "1.11.0",
"on-headers": "1.0.2", "on-headers": "1.0.2",
"passport": "0.4.0", "passport": "0.4.1",
"passport-http-bearer": "1.0.1", "passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",
"raw-body": "2.4.1", "raw-body": "2.4.1",
"request": "2.88.0", "request": "2.88.0",
"semver": "6.3.0", "semver": "6.3.0",
"uglify-js": "3.6.9", "uglify-js": "3.8.0",
"when": "3.7.8", "when": "3.7.8",
"ws": "6.2.1", "ws": "6.2.1",
"xml2js": "0.4.22" "xml2js": "0.4.23"
}, },
"optionalDependencies": { "optionalDependencies": {
"bcrypt": "3.0.6" "bcrypt": "3.0.6"
}, },
"devDependencies": { "devDependencies": {
"marked": "0.8.0",
"dompurify": "2.0.8",
"grunt": "~1.0.4", "grunt": "~1.0.4",
"grunt-chmod": "~1.1.1", "grunt-chmod": "~1.1.1",
"grunt-cli": "~1.3.2", "grunt-cli": "~1.3.2",
@ -103,7 +105,7 @@
"mocha": "^5.2.0", "mocha": "^5.2.0",
"mosca": "^2.8.3", "mosca": "^2.8.3",
"node-red-node-test-helper": "^0.2.3", "node-red-node-test-helper": "^0.2.3",
"node-sass": "^4.13.0", "node-sass": "^4.13.1",
"should": "^8.4.0", "should": "^8.4.0",
"sinon": "1.17.7", "sinon": "1.17.7",
"stoppable": "^1.1.0", "stoppable": "^1.1.0",

View File

@ -44,6 +44,7 @@ module.exports = {
user: req.user, user: req.user,
module: req.body.module, module: req.body.module,
version: req.body.version, version: req.body.version,
url: req.body.url,
req: apiUtils.getRequestLogObject(req) req: apiUtils.getRequestLogObject(req)
} }
runtimeAPI.nodes.addModule(opts).then(function(info) { runtimeAPI.nodes.addModule(opts).then(function(info) {

View File

@ -36,6 +36,7 @@ var log = require("@node-red/util").log; // TODO: separate module
passport.use(strategies.bearerStrategy.BearerStrategy); passport.use(strategies.bearerStrategy.BearerStrategy);
passport.use(strategies.clientPasswordStrategy.ClientPasswordStrategy); passport.use(strategies.clientPasswordStrategy.ClientPasswordStrategy);
passport.use(strategies.anonymousStrategy); passport.use(strategies.anonymousStrategy);
passport.use(strategies.tokensStrategy);
var server = oauth2orize.createServer(); var server = oauth2orize.createServer();
@ -60,7 +61,7 @@ function init(_settings,storage) {
function needsPermission(permission) { function needsPermission(permission) {
return function(req,res,next) { return function(req,res,next) {
if (settings && settings.adminAuth) { if (settings && settings.adminAuth) {
return passport.authenticate(['bearer','anon'],{ session: false })(req,res,function() { return passport.authenticate(['bearer','tokens','anon'],{ session: false })(req,res,function() {
if (!req.user) { if (!req.user) {
return next(); return next();
} }
@ -100,7 +101,10 @@ function login(req,res) {
} }
} else if (mergedAdminAuth.type === "strategy") { } else if (mergedAdminAuth.type === "strategy") {
var urlPrefix = (settings.httpAdminRoot==='/')?"":settings.httpAdminRoot; var urlPrefix = (settings.httpAdminRoot||"").replace(/\/$/,"");
if (urlPrefix.length > 0) {
urlPrefix += "/";
}
response = { response = {
"type":"strategy", "type":"strategy",
"prompts":[{type:"button",label:mergedAdminAuth.strategy.label, url: urlPrefix + "auth/strategy"}] "prompts":[{type:"button",label:mergedAdminAuth.strategy.label, url: urlPrefix + "auth/strategy"}]

View File

@ -123,9 +123,38 @@ AnonymousStrategy.prototype.authenticate = function(req) {
}); });
} }
function TokensStrategy() {
passport.Strategy.call(this);
this.name = 'tokens';
}
util.inherits(TokensStrategy, passport.Strategy);
TokensStrategy.prototype.authenticate = function(req) {
var self = this;
var token = null;
if (Users.tokenHeader() === 'authorization') {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
token = req.headers.authorization.split(' ')[1];
}
} else {
token = req.headers[Users.tokenHeader()];
}
if (token) {
Users.tokens(token).then(function(admin) {
if (admin) {
self.success(admin,{scope:admin.permissions});
} else {
self.fail(401);
}
});
} else {
self.fail(401);
}
}
module.exports = { module.exports = {
bearerStrategy: bearerStrategy, bearerStrategy: bearerStrategy,
clientPasswordStrategy: clientPasswordStrategy, clientPasswordStrategy: clientPasswordStrategy,
passwordTokenExchange: passwordTokenExchange, passwordTokenExchange: passwordTokenExchange,
anonymousStrategy: new AnonymousStrategy() anonymousStrategy: new AnonymousStrategy(),
tokensStrategy: new TokensStrategy()
} }

View File

@ -59,7 +59,9 @@ function getDefaultUser() {
var api = { var api = {
get: get, get: get,
authenticate: authenticate, authenticate: authenticate,
default: getDefaultUser default: getDefaultUser,
tokens: getDefaultUser,
tokenHeader: "authorization"
} }
function init(config) { function init(config) {
@ -105,6 +107,12 @@ function init(config) {
} else { } else {
api.default = getDefaultUser; api.default = getDefaultUser;
} }
if (config.tokens && typeof config.tokens === "function") {
api.tokens = config.tokens;
if (config.tokenHeader && typeof config.tokenHeader === "string") {
api.tokenHeader = config.tokenHeader.toLowerCase();
}
}
} }
function cleanUser(user) { function cleanUser(user) {
if (user && user.hasOwnProperty('password')) { if (user && user.hasOwnProperty('password')) {
@ -118,5 +126,7 @@ module.exports = {
init: init, init: init,
get: function(username) { return api.get(username).then(cleanUser)}, get: function(username) { return api.get(username).then(cleanUser)},
authenticate: function() { return api.authenticate.apply(null, arguments) }, authenticate: function() { return api.authenticate.apply(null, arguments) },
default: function() { return api.default(); } default: function() { return api.default(); },
tokens: function(token) { return api.tokens(token); },
tokenHeader: function() { return api.tokenHeader }
}; };

View File

@ -88,13 +88,13 @@ module.exports = {
// Locales // Locales
var locales = require("./locales"); var locales = require("./locales");
locales.init(runtimeAPI); locales.init(runtimeAPI);
editorApp.get(/locales\/(.+)\/?$/,locales.get,apiUtil.errorHandler); editorApp.get(/^\/locales\/(.+)\/?$/,locales.get,apiUtil.errorHandler);
// Library // Library
var library = require("./library"); var library = require("./library");
library.init(runtimeAPI); library.init(runtimeAPI);
editorApp.get(/library\/([^\/]+)\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry); editorApp.get(/^\/library\/([^\/]+)\/([^\/]+)(?:$|\/(.*))/,needsPermission("library.read"),library.getEntry);
editorApp.post(/library\/([^\/]+)\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry); editorApp.post(/^\/library\/([^\/]+)\/([^\/]+)\/(.*)/,needsPermission("library.write"),library.saveEntry);
// Credentials // Credentials

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-api", "name": "@node-red/editor-api",
"version": "1.0.3", "version": "1.0.4",
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {
@ -16,21 +16,21 @@
} }
], ],
"dependencies": { "dependencies": {
"@node-red/util": "1.0.3", "@node-red/util": "1.0.4",
"@node-red/editor-client": "1.0.3", "@node-red/editor-client": "1.0.4",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "1.19.0", "body-parser": "1.19.0",
"clone": "2.1.2", "clone": "2.1.2",
"cors": "2.8.5", "cors": "2.8.5",
"express-session": "1.17.0", "express-session": "1.17.0",
"express": "4.17.1", "express": "4.17.1",
"memorystore": "1.6.1", "memorystore": "1.6.2",
"mime": "2.4.4", "mime": "2.4.4",
"mustache": "3.0.2", "mustache": "4.0.0",
"oauth2orize": "1.11.0", "oauth2orize": "1.11.0",
"passport-http-bearer": "1.0.1", "passport-http-bearer": "1.0.1",
"passport-oauth2-client-password": "0.1.2", "passport-oauth2-client-password": "0.1.2",
"passport": "0.4.0", "passport": "0.4.1",
"when": "3.7.8", "when": "3.7.8",
"ws": "6.2.1" "ws": "6.2.1"
}, },

View File

@ -34,11 +34,11 @@
"view" : "Ansicht", "view" : "Ansicht",
"grid" : "Gitter", "grid" : "Gitter",
"showGrid" : "Raster anzeigen", "showGrid" : "Raster anzeigen",
"snapGrid" : "Einrasten am Raster", "snapGrid" : "Am Raster ausrichten",
"gridSize" : "Rastergröße", "gridSize" : "Rastergröße",
"textDir" : "Textrichtung", "textDir" : "Textrichtung",
"defaultDir" : "Standard", "defaultDir" : "Standard",
"ltr" : "Links-nach-rechts", "ltr" : "Von links nach rechts",
"rtl" : "Von rechts nach links", "rtl" : "Von rechts nach links",
"auto" : "Kontextuell" "auto" : "Kontextuell"
}, },
@ -53,15 +53,15 @@
"import" : "Import", "import" : "Import",
"export" : "Exportieren", "export" : "Exportieren",
"search" : "Flows durchsuchen", "search" : "Flows durchsuchen",
"searchInput" : "durchsuchen Sie Ihre Flows", "searchInput" : "Flows durchsuchen",
"subflows" : "Subflow", "subflows" : "Subflow",
"createSubflow" : "Subflow erstellen", "createSubflow" : "Subflow erstellen",
"selectionToSubflow" : "Auswahl für Subflow", "selectionToSubflow" : "Auswahl zu Subflow",
"flows" : "Flows", "flows" : "Flows",
"add" : "Hinzufügen", "add" : "Hinzufügen",
"rename" : "Umbenennen", "rename" : "Umbenennen",
"delete" : "Löschen", "delete" : "Löschen",
"keyboardShortcuts" : "Tastaturkurzbefehle", "keyboardShortcuts" : "Tastenkürzel",
"login" : "Anmelden", "login" : "Anmelden",
"logout" : "Abmelden", "logout" : "Abmelden",
"editPalette" : "Palette verwalten", "editPalette" : "Palette verwalten",
@ -217,7 +217,7 @@
"remote" : "Ferne Änderungen", "remote" : "Ferne Änderungen",
"reviewChanges" : "Änderungen prüfen", "reviewChanges" : "Änderungen prüfen",
"noBinaryFileShowed" : "Der Inhalt der Binärdatei kann nicht angezeigt", "noBinaryFileShowed" : "Der Inhalt der Binärdatei kann nicht angezeigt",
"viewCommitDiff" : "Änderungen festschreiben", "viewCommitDiff" : "Änderungen committen",
"compareChanges" : "Änderungen vergleichen", "compareChanges" : "Änderungen vergleichen",
"saveConflict" : "Konfliktlösung speichern", "saveConflict" : "Konfliktlösung speichern",
"conflictHeader" : "<span> __resolved__ </span> von <span> __unresolved__ </span> -Konflikten behoben", "conflictHeader" : "<span> __resolved__ </span> von <span> __unresolved__ </span> -Konflikten behoben",
@ -226,8 +226,8 @@
"newVersionError" : "Neue Version enthält keine gültige JSON-Datei:" "newVersionError" : "Neue Version enthält keine gültige JSON-Datei:"
}, },
"subflow" : { "subflow" : {
"editSubflow" : "Flowschablone bearbeiten: __name__", "editSubflow" : "Subflow bearbeiten: __name__",
"edit" : "Flowsschablone bearbeiten", "edit" : "Subflow bearbeiten",
"subflowInstances" : "Es ist __count__ Instanz dieser Subflow-Vorlage vorhanden.", "subflowInstances" : "Es ist __count__ Instanz dieser Subflow-Vorlage vorhanden.",
"subflowInstances_plural" : "Es gibt __count__ Instanzen dieser Subflow-Vorlage.", "subflowInstances_plural" : "Es gibt __count__ Instanzen dieser Subflow-Vorlage.",
"editSubflowProperties" : "Eigenschaften bearbeiten", "editSubflowProperties" : "Eigenschaften bearbeiten",
@ -266,7 +266,7 @@
} }
}, },
"keyboard" : { "keyboard" : {
"title" : "Tastaturkurzbefehle", "title" : "Tastenkürzel",
"keyboard" : "Tastatur", "keyboard" : "Tastatur",
"filterActions" : "Filteraktionen", "filterActions" : "Filteraktionen",
"shortcut" : "Direktaufruf", "shortcut" : "Direktaufruf",
@ -283,7 +283,7 @@
"exportNode" : "Node exportieren", "exportNode" : "Node exportieren",
"nudgeNode" : "Ausgewählte Nodes verschieben (1px)", "nudgeNode" : "Ausgewählte Nodes verschieben (1px)",
"moveNode" : "Ausgewählte Nodes verschieben (20px)", "moveNode" : "Ausgewählte Nodes verschieben (20px)",
"toggleSidebar" : "Seitenleiste ein-/ausschalten", "toggleSidebar" : "Seitenleiste ein-/ausblenden",
"copyNode" : "Ausgewählte Nodes kopieren", "copyNode" : "Ausgewählte Nodes kopieren",
"cutNode" : "Ausgewählte Nodes ausschneiden", "cutNode" : "Ausgewählte Nodes ausschneiden",
"pasteNode" : "Node einfügen", "pasteNode" : "Node einfügen",
@ -308,7 +308,7 @@
}, },
"palette" : { "palette" : {
"noInfo" : "Keine Informationen verfügbar", "noInfo" : "Keine Informationen verfügbar",
"filter" : "Filter Nodes", "filter" : "Nodes filtern",
"search" : "Suchmodule", "search" : "Suchmodule",
"addCategory" : "Neu hinzufügen ...", "addCategory" : "Neu hinzufügen ...",
"label" : { "label" : {
@ -366,11 +366,11 @@
"remove" : "entfernen", "remove" : "entfernen",
"update" : "Update auf __version__", "update" : "Update auf __version__",
"updated" : "aktualisiert", "updated" : "aktualisiert",
"install" : "installieren", "install" : "Installieren",
"installed" : "installiert", "installed" : "Installiert",
"loading" : "Kataloge werden geladen ...", "loading" : "Kataloge werden geladen ...",
"tab-nodes" : "Nodes", "tab-nodes" : "Nodes",
"tab-install" : "installieren", "tab-install" : "Installieren",
"sort" : "Sortierung:", "sort" : "Sortierung:",
"sortAZ" : "a-z", "sortAZ" : "a-z",
"sortRecent" : "kürzlich", "sortRecent" : "kürzlich",
@ -452,7 +452,7 @@
"name" : "Kontextdaten", "name" : "Kontextdaten",
"label" : "Kontext", "label" : "Kontext",
"none" : "keine ausgewählt", "none" : "keine ausgewählt",
"refresh" : "Aktualisierung zum Laden", "refresh" : "Zum Aktualisieren neu laden",
"empty" : "leer", "empty" : "leer",
"node" : "Node", "node" : "Node",
"flow" : "Flow", "flow" : "Flow",
@ -477,7 +477,7 @@
"none" : "Keine", "none" : "Keine",
"install" : "installieren", "install" : "installieren",
"removeFromProject" : "Aus Projekt entfernen", "removeFromProject" : "Aus Projekt entfernen",
"addToProject" : "zu Projekt hinzufügen", "addToProject" : "Zu Projekt hinzufügen",
"files" : "Dateien", "files" : "Dateien",
"flow" : "Flow", "flow" : "Flow",
"credentials" : "Berechtigungsnachweis", "credentials" : "Berechtigungsnachweis",
@ -510,7 +510,7 @@
}, },
"userSettings" : { "userSettings" : {
"committerDetail" : "Committer-Details", "committerDetail" : "Committer-Details",
"committerTip" : "Leer Wert für Systemstandardwert belassen", "committerTip" : "Leer lassen für Systemstandard",
"userName" : "Benutzername", "userName" : "Benutzername",
"email" : "E-Mail", "email" : "E-Mail",
"sshKeys" : "SSH-Schlüssel", "sshKeys" : "SSH-Schlüssel",
@ -544,7 +544,7 @@
"revertChanges" : "Änderungen zurücksetzen", "revertChanges" : "Änderungen zurücksetzen",
"localChanges" : "Lokale Änderungen", "localChanges" : "Lokale Änderungen",
"none" : "Keine", "none" : "Keine",
"conflictResolve" : "Alle Konflikte wurden aufgelöst. Festschreiben der Änderungen, um den Mischvorgang abzuschließen.", "conflictResolve" : "Alle Konflikte wurden aufgelöst. Committe die Änderungen, um den Merge Request abzuschließen.",
"localFiles" : "Lokale Dateien", "localFiles" : "Lokale Dateien",
"all" : "alle", "all" : "alle",
"unmergedChanges" : "Nicht zusammengeführte Änderungen", "unmergedChanges" : "Nicht zusammengeführte Änderungen",

View File

@ -14,7 +14,11 @@
"back": "Back", "back": "Back",
"next": "Next", "next": "Next",
"clone": "Clone project", "clone": "Clone project",
"cont": "Continue" "cont": "Continue",
"line": "Outline",
"fill": "Fill",
"color": "Color",
"position": "Position"
}, },
"type": { "type": {
"string": "string", "string": "string",
@ -91,7 +95,12 @@
"projects-new": "New", "projects-new": "New",
"projects-open": "Open", "projects-open": "Open",
"projects-settings": "Project Settings", "projects-settings": "Project Settings",
"showNodeLabelDefault": "Show label of newly added nodes" "showNodeLabelDefault": "Show label of newly added nodes",
"groups": "Groups",
"groupSelection": "Group selection",
"ungroupSelection": "Ungroup selection",
"groupMergeSelection": "Merge selection",
"groupRemoveSelection": "Remove from group"
} }
}, },
"actions": { "actions": {
@ -171,6 +180,8 @@
"node_plural": "__count__ nodes", "node_plural": "__count__ nodes",
"configNode": "__count__ configuration node", "configNode": "__count__ configuration node",
"configNode_plural": "__count__ configuration nodes", "configNode_plural": "__count__ configuration nodes",
"group": "__count__ group",
"group_plural": "__count__ groups",
"flow": "__count__ flow", "flow": "__count__ flow",
"flow_plural": "__count__ flows", "flow_plural": "__count__ flows",
"subflow": "__count__ subflow", "subflow": "__count__ subflow",
@ -186,6 +197,9 @@
"nodesImported": "Imported:", "nodesImported": "Imported:",
"nodeCopied": "__count__ node copied", "nodeCopied": "__count__ node copied",
"nodeCopied_plural": "__count__ nodes copied", "nodeCopied_plural": "__count__ nodes copied",
"groupCopied": "__count__ group copied",
"groupCopied_plural": "__count__ groups copied",
"groupStyleCopied": "Group style copied",
"invalidFlow": "Invalid flow: __message__", "invalidFlow": "Invalid flow: __message__",
"export": { "export": {
"selected":"selected nodes", "selected":"selected nodes",
@ -308,6 +322,13 @@
"multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection" "multipleInputsToSelection": "<strong>Cannot create subflow</strong>: multiple inputs to selection"
} }
}, },
"group": {
"editGroup": "Edit group: __name__",
"errors": {
"cannotCreateDiffGroups": "Cannot create group using nodes from different groups",
"cannotAddSubflowPorts": "Cannot add subflow ports to a group"
}
},
"editor": { "editor": {
"configEdit": "Edit", "configEdit": "Edit",
"configAdd": "Add", "configAdd": "Add",
@ -351,7 +372,8 @@
"bool": "bool", "bool": "bool",
"json": "JSON", "json": "JSON",
"bin": "buffer", "bin": "buffer",
"env": "env variable" "env": "env variable",
"cred": "credential"
}, },
"menu": { "menu": {
"input": "input", "input": "input",
@ -538,6 +560,7 @@
"label": "info", "label": "info",
"node": "Node", "node": "Node",
"type": "Type", "type": "Type",
"group": "Group",
"module": "Module", "module": "Module",
"id": "ID", "id": "ID",
"status": "Status", "status": "Status",
@ -613,7 +636,6 @@
"removeFromProject": "remove from project", "removeFromProject": "remove from project",
"addToProject": "add to project", "addToProject": "add to project",
"files": "Files", "files": "Files",
"package": "Package",
"flow": "Flow", "flow": "Flow",
"credentials": "Credentials", "credentials": "Credentials",
"package":"Package", "package":"Package",
@ -757,7 +779,8 @@
"bin": "buffer", "bin": "buffer",
"date": "timestamp", "date": "timestamp",
"jsonata": "expression", "jsonata": "expression",
"env": "env variable" "env": "env variable",
"cred": "credential"
} }
}, },
"editableList": { "editableList": {
@ -977,7 +1000,8 @@
"passphrase": "Passphrase", "passphrase": "Passphrase",
"retry": "Retry", "retry": "Retry",
"update-failed": "Failed to update auth", "update-failed": "Failed to update auth",
"unhandled": "Unhandled error response" "unhandled": "Unhandled error response",
"host-key-verify-failed": "<p>Host key verification failed.</p><p>The repository host key could not be verified. Please update your <code>known_hosts</code> file and try again."
}, },
"create-branch-list": { "create-branch-list": {
"invalid": "Invalid branch", "invalid": "Invalid branch",
@ -1008,6 +1032,7 @@
"en-US": "English", "en-US": "English",
"ja": "Japanese", "ja": "Japanese",
"ko": "Korean", "ko": "Korean",
"zh-CN": "Chinese(Simplified)" "zh-CN": "Chinese(Simplified)",
"zh-TW": "Chinese(Traditional)"
} }
} }

View File

@ -262,5 +262,9 @@
"$distinct": { "$distinct": {
"args": "array", "args": "array",
"desc": "Returns an array with duplicate values removed from `array`" "desc": "Returns an array with duplicate values removed from `array`"
},
"$type": {
"args": "value",
"desc": "Returns the type of `value` as a string. If `value` is undefined, this will return `undefined`"
} }
} }

View File

@ -351,7 +351,8 @@
"bool": "真偽", "bool": "真偽",
"json": "JSON", "json": "JSON",
"bin": "バッファ", "bin": "バッファ",
"env": "環境変数" "env": "環境変数",
"cred": "認証情報"
}, },
"menu": { "menu": {
"input": "入力", "input": "入力",
@ -1007,6 +1008,7 @@
"en-US": "英語", "en-US": "英語",
"ja": "日本語", "ja": "日本語",
"ko": "韓国語", "ko": "韓国語",
"zh-CN": "中国語(簡体)" "zh-CN": "中国語(簡体)",
"zh-TW": "中国語(繁体)"
} }
} }

View File

@ -245,11 +245,11 @@
}, },
"$encodeUrl": { "$encodeUrl": {
"args": "str", "args": "str",
"desc": "Uniform Resource Locator (URL)を構成する文字を1、、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" "desc": "Uniform Resource Locator (URL)を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
}, },
"$encodeUrlComponent": { "$encodeUrlComponent": {
"args": "str", "args": "str",
"desc": "Uniform Resource Locator (URL)要素を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`" "desc": "Uniform Resource Locator (URL)要素を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
}, },
"$decodeUrl": { "$decodeUrl": {
"args": "str", "args": "str",
@ -262,5 +262,9 @@
"$distinct": { "$distinct": {
"args": "array", "args": "array",
"desc": "配列`array`から重複要素を削除した配列を返します。" "desc": "配列`array`から重複要素を削除した配列を返します。"
},
"$type": {
"args": "value",
"desc": "`value` の型を文字列として返します。もし `value` が未定義の場合、 `undefined` が返されます。"
} }
} }

View File

@ -10,7 +10,22 @@
"load": "读取", "load": "读取",
"save": "保存", "save": "保存",
"import": "导入", "import": "导入",
"export": "导出" "export": "导出",
"back": "后退",
"next": "下一个",
"clone": "克隆项目",
"cont": "继续"
},
"type": {
"string": "字符串",
"number": "数字",
"boolean": "布尔值",
"array": "数组",
"buffer": "buffer",
"object": "对象",
"jsonString": "JSON字符串",
"undefined": "未定义",
"null": "空"
} }
}, },
"workspace": { "workspace": {
@ -19,10 +34,13 @@
"confirmDelete": "确认删除", "confirmDelete": "确认删除",
"delete": "你确定想删除 '__label__'?", "delete": "你确定想删除 '__label__'?",
"dropFlowHere": "把流程放到这里", "dropFlowHere": "把流程放到这里",
"addFlow": "添加流程",
"listFlows": "流程一览",
"status": "状态", "status": "状态",
"enabled": "有效", "enabled": "有效",
"disabled": "无效", "disabled": "无效",
"info": "详细描述" "info": "详细描述",
"selectNodes": "点击节点来选择"
}, },
"menu": { "menu": {
"label": { "label": {
@ -36,11 +54,16 @@
"defaultDir": "默认方向", "defaultDir": "默认方向",
"ltr": "从左到右", "ltr": "从左到右",
"rtl": "从右到左", "rtl": "从右到左",
"auto": "上下文" "auto": "上下文",
"language": "语言",
"browserDefault": "浏览器默认"
}, },
"sidebar": { "sidebar": {
"show": "显示侧边栏" "show": "显示侧边栏"
}, },
"palette": {
"show": "显示控制板"
},
"settings": "设置", "settings": "设置",
"userSettings": "用户设置", "userSettings": "用户设置",
"nodes": "节点", "nodes": "节点",
@ -62,9 +85,21 @@
"logout": "退出", "logout": "退出",
"editPalette": "节点管理", "editPalette": "节点管理",
"other": "其他", "other": "其他",
"showTips": "显示小提示" "showTips": "显示小提示",
"help": "Node-RED网页",
"projects": "项目",
"projects-new": "新建",
"projects-open": "打开",
"projects-settings": "项目设定",
"showNodeLabelDefault": "显示新添加的节点的标签"
} }
}, },
"actions": {
"toggle-navigator": "切换导航器",
"zoom-out": "缩小",
"zoom-reset": "重设缩放",
"zoom-in": "放大"
},
"user": { "user": {
"loggedInAs": "作为__name__登陆", "loggedInAs": "作为__name__登陆",
"username": "账号", "username": "账号",
@ -82,29 +117,73 @@
"warning": "<strong>警告</strong>: __message__", "warning": "<strong>警告</strong>: __message__",
"warnings": { "warnings": {
"undeployedChanges": "节点中存在未部署的更改", "undeployedChanges": "节点中存在未部署的更改",
"nodeActionDisabled": "节点操作已禁用",
"nodeActionDisabledSubflow": "节点动作在子流程中被禁用", "nodeActionDisabledSubflow": "节点动作在子流程中被禁用",
"missing-types": "流程由于缺少节点类型而停止。请检查日志的详细信息", "missing-types": "流程由于缺少节点类型而停止。请检查日志的详细信息",
"restartRequired": "Node-RED必须重新启动以启用升级的模块" "safe-mode": "<p>流程以安全模式停止。</p><p>您可以修改流程并部署更改以重新启动。</p>",
"restartRequired": "Node-RED必须重新启动以启用升级的模块",
"credentials_load_failed": "<p>由于无法解密凭据,因此流程停止。</p><p>流程凭据文件已加密,但是项目的加密密钥丢失或无效。</p>",
"credentials_load_failed_reset": "<p>凭据无法解密</p><p>流凭据文件已加密,但是项目的加密密钥丢失或无效。</p><p>流凭据文件将在下一次部署时重置。任何现有的流凭证将被清除。</p>",
"missing_flow_file": "<p>找不到项目流程文件。</p><p>该项目未配置流程文件。</p>",
"missing_package_file": "<p>找不到项目包文件。</p><p>项目缺少package.json文件。</p>",
"project_empty": "<p>该项目为空。</p><p>是否要创建一组默认的项目文件?<br/>否则,您将必须在编辑器外部手动将文件添加到项目中。</p>",
"project_not_found": "<p>未找到项目'__project__'。</p>",
"git_merge_conflict": "<p>自动合并更改失败。</p><p>修复未合并的冲突,然后提交结果。</p>"
}, },
"error": "<strong>Error</strong>: __message__", "error": "<strong>错误</strong>: __message__",
"errors": { "errors": {
"lostConnection": "丢失与服务器的连接,重新连接...", "lostConnection": "丢失与服务器的连接,重新连接...",
"lostConnectionReconnect": "丢失与服务器的连接__time__秒后重新连接", "lostConnectionReconnect": "丢失与服务器的连接__time__秒后重新连接",
"lostConnectionTry": "现在尝试", "lostConnectionTry": "现在尝试",
"cannotAddSubflowToItself": "无法向其自身添加子流程", "cannotAddSubflowToItself": "无法向其自身添加子流程",
"cannotAddCircularReference": "无法添加子流程 - 循环引用", "cannotAddCircularReference": "无法添加子流程 - 循环引用",
"unsupportedVersion": "您正在使用不受支持的Node.js版本<br/>请升级到最新版本的Node.js LTS" "unsupportedVersion": "您正在使用不受支持的Node.js版本<br/>请升级到最新版本的Node.js LTS",
"failedToAppendNode": "<p>'__module__'加载失败</p><p>__error__</p>"
},
"project": {
"change-branch": "转到本地分支'__project__'",
"merge-abort": "Git合并中止",
"loaded": "项目'__project__'已加载",
"updated": "项目'__project__'已更新",
"pull": "项目'__project__'已重新加载",
"revert": "项目 '__project__'已还原",
"merge-complete": "Git合并完成",
"setupCredentials": "设定证书",
"setupProjectFiles": "设置项目文件",
"no": "不了,谢谢",
"createDefault": "创建默认项目文件",
"mergeConflict": "显示合并冲突"
},
"label": {
"manage-project-dep": "管理项目依赖性",
"setup-cred": "设定证书",
"setup-project": "设置项目文件",
"create-default-package": "创建默认的包文件",
"no-thanks": "不了,谢谢",
"create-default-project": "创建默认项目文件",
"show-merge-conflicts": "显示合并冲突"
} }
}, },
"clipboard": { "clipboard": {
"clipboard": "剪贴板", "clipboard": "剪贴板",
"nodes": "节点", "nodes": "节点",
"node": "__count__节点",
"node_plural": "__count__节点",
"configNode": "__count__配置节点",
"configNode_plural": "__count__配置节点",
"flow": "__count__流程",
"flow_plural": "__count__流程",
"subflow": "__count__子流程",
"subflow_plural": "__count__子流程",
"pasteNodes": "在这里粘贴节点", "pasteNodes": "在这里粘贴节点",
"selectFile": "选择要导入的文件",
"importNodes": "导入节点", "importNodes": "导入节点",
"exportNodes": "导出节点至剪贴板", "exportNodes": "导出节点至剪贴板",
"download": "下载",
"importUnrecognised": "导入了无法识别的类型:", "importUnrecognised": "导入了无法识别的类型:",
"importUnrecognised_plural": "导入了无法识别的类型:", "importUnrecognised_plural": "导入了无法识别的类型:",
"nodesExported": "节点导出到了剪贴板", "nodesExported": "节点导出到了剪贴板",
"nodesImported": "导入:",
"nodeCopied": "已复制__count__个节点", "nodeCopied": "已复制__count__个节点",
"nodeCopied_plural": "已复制__count__个节点", "nodeCopied_plural": "已复制__count__个节点",
"invalidFlow": "无效的流程: __message__", "invalidFlow": "无效的流程: __message__",
@ -114,11 +193,21 @@
"all": "所有流程", "all": "所有流程",
"compact": "紧凑", "compact": "紧凑",
"formatted": "已格式化", "formatted": "已格式化",
"copy": "导出到剪贴板" "copy": "导出到剪贴板",
"export": "到处到库",
"exportAs": "导出为",
"overwrite": "替换",
"exists": "<p><b>\"__file__\"</b>已存在</p><p>是否要替换它?</p>"
}, },
"import": { "import": {
"import": "导入到", "import": "导入到",
"newFlow": "新流程" "newFlow": "新流程",
"errors": {
"notArray": "输入的不是JSON数组",
"itemNotObject": "输入的流无效 - 项目__index__不是节点对象",
"missingId": "输入的流无效-项 __index__ 缺少'id'属性",
"missingType": "输入的流程无效-项__index__缺少'类型'属性"
}
}, },
"copyMessagePath": "已复制路径", "copyMessagePath": "已复制路径",
"copyMessageValue": "已复制数值", "copyMessageValue": "已复制数值",
@ -132,7 +221,10 @@
"modifiedFlowsDesc": "只部署包含已更改节点的流", "modifiedFlowsDesc": "只部署包含已更改节点的流",
"modifiedNodes": "已更改的节点", "modifiedNodes": "已更改的节点",
"modifiedNodesDesc": "只部署已经更改的节点", "modifiedNodesDesc": "只部署已经更改的节点",
"restartFlows": "重启流程",
"restartFlowsDesc": "重新启动当前部署的流程",
"successfulDeploy": "部署成功", "successfulDeploy": "部署成功",
"successfulRestart": "成功重启流程",
"deployFailed": "部署失败: __message__", "deployFailed": "部署失败: __message__",
"unusedConfigNodes": "您有一些未使用的配置节点", "unusedConfigNodes": "您有一些未使用的配置节点",
"unusedConfigNodesLink": "点击此处查看它们", "unusedConfigNodesLink": "点击此处查看它们",
@ -152,16 +244,24 @@
"improperlyConfigured": "工作区包含一些未正确配置的节点:", "improperlyConfigured": "工作区包含一些未正确配置的节点:",
"unknown": "工作区包含一些未知的节点类型:", "unknown": "工作区包含一些未知的节点类型:",
"confirm": "你确定要部署吗?", "confirm": "你确定要部署吗?",
"doNotWarn": "不要再对此发出警告",
"conflict": "服务器正在运行较新的一组流程。", "conflict": "服务器正在运行较新的一组流程。",
"backgroundUpdate": "服务器上的流程已更新。", "backgroundUpdate": "服务器上的流程已更新。",
"conflictChecking": "检查是否可以自动合并更改", "conflictChecking": "检查是否可以自动合并更改",
"conflictAutoMerge": "此更改不包括冲突,可以自动合并", "conflictAutoMerge": "此更改不包括冲突,可以自动合并",
"conflictManualMerge": "这些更改包括了在部署之前必须解决的冲突。" "conflictManualMerge": "这些更改包括了在部署之前必须解决的冲突。",
"plusNMore": "+ __count__更多"
} }
}, },
"eventLog": {
"title": "事件记录日志",
"view": "查看日志"
},
"diff": { "diff": {
"unresolvedCount": "__count__个未解决的冲突", "unresolvedCount": "__count__个未解决的冲突",
"unresolvedCount_plural": "__count__个未解决的冲突", "unresolvedCount_plural": "__count__个未解决的冲突",
"globalNodes": "全局节点",
"flowProperties": "流程属性",
"type": { "type": {
"added": "已添加", "added": "已添加",
"changed": "已更改", "changed": "已更改",
@ -175,9 +275,19 @@
"nodeCount": "__count__个节点", "nodeCount": "__count__个节点",
"nodeCount_plural": "__count__个节点", "nodeCount_plural": "__count__个节点",
"local": "本地", "local": "本地",
"remote": "远程" "remote": "远程",
"reviewChanges": "查看变更",
"noBinaryFileShowed": "无法显示二进制文件内容",
"viewCommitDiff": "查看提交更改",
"compareChanges": "比较变更",
"saveConflict": "保存冲突解决",
"conflictHeader": "已解决<span>__unresolved__</span>中的<span>__resolved__</span>个冲突",
"commonVersionError": "通用版本不包含有效的JSON",
"oldVersionError": "旧版本不包含有效的JSON",
"newVersionError": "新版本不包含有效的JSON"
}, },
"subflow": { "subflow": {
"editSubflowInstance": "编辑子流实例__name__",
"editSubflow": "编辑流程模板: __name__", "editSubflow": "编辑流程模板: __name__",
"edit": "编辑流程模板", "edit": "编辑流程模板",
"subflowInstances": "这个子流程模板有__count__个实例", "subflowInstances": "这个子流程模板有__count__个实例",
@ -185,8 +295,14 @@
"editSubflowProperties": "编辑属性", "editSubflowProperties": "编辑属性",
"input": "输入:", "input": "输入:",
"output": "输出:", "output": "输出:",
"status": "状态节点",
"deleteSubflow": "删除子流程", "deleteSubflow": "删除子流程",
"info": "详细描述", "info": "详细描述",
"category": "类别",
"env": {
"restore": "恢复为默认子流",
"remove": "删除环境变量"
},
"errors": { "errors": {
"noNodesSelected": "<strong>无法创建子流程</strong>: 未选择节点", "noNodesSelected": "<strong>无法创建子流程</strong>: 未选择节点",
"multipleInputsToSelection": "<strong>无法创建子流程</strong>: 多个输入到了选择" "multipleInputsToSelection": "<strong>无法创建子流程</strong>: 多个输入到了选择"
@ -204,18 +320,68 @@
"editConfig": "编辑__type__配置", "editConfig": "编辑__type__配置",
"addNewType": "添加新的__type__节点", "addNewType": "添加新的__type__节点",
"nodeProperties": "节点属性", "nodeProperties": "节点属性",
"label": "标签",
"color": "颜色",
"portLabels": "端口标签", "portLabels": "端口标签",
"labelInputs": "输入", "labelInputs": "输入",
"labelOutputs": "输出", "labelOutputs": "输出",
"settingIcon": "图标",
"default": "默认",
"noDefaultLabel": "无", "noDefaultLabel": "无",
"defaultLabel": "使用默认标签", "defaultLabel": "使用默认标签",
"searchIcons": "搜索图标",
"useDefault": "使用默认",
"description": "描述",
"show": "显示",
"hide": "隐藏",
"locale": "选择界面语言",
"icon": "图标",
"inputType": "输入类型",
"inputs": {
"input": "输入",
"select": "选择",
"checkbox": "复选框",
"spinner": "微调器",
"none": "空",
"hidden": "隐藏属性"
},
"types": {
"str": "字符串",
"num": "数字",
"bool": "布尔",
"json": "JSON",
"bin": "buffer",
"env": "环境变量"
},
"menu": {
"input": "输入",
"select": "选择",
"checkbox": "复选框",
"spinner": "微调器",
"hidden": "仅标签"
},
"select": {
"label": "标签",
"value": "值"
},
"spinner": {
"min": "最小值",
"max": "最大值"
},
"errors": { "errors": {
"scopeChange": "更改范围将使其他流中的节点无法使用" "scopeChange": "更改范围将使其他流中的节点无法使用",
"invalidProperties": "无效的属性:"
} }
}, },
"keyboard": { "keyboard": {
"title": "键盘快捷键", "title": "键盘快捷键",
"keyboard": "键盘",
"filterActions": "筛选动作",
"shortcut": "快捷键",
"scope": "范围",
"unassigned": "未分配", "unassigned": "未分配",
"global": "全局",
"workspace": "工作组",
"selectAll": "选择所有节点", "selectAll": "选择所有节点",
"selectAllConnected": "选择所有连接的节点", "selectAllConnected": "选择所有连接的节点",
"addRemoveNode": "从选择中添加/删除节点", "addRemoveNode": "从选择中添加/删除节点",
@ -226,12 +392,14 @@
"nudgeNode": "移动所选节点(1px)", "nudgeNode": "移动所选节点(1px)",
"moveNode": "移动所选节点(20px)", "moveNode": "移动所选节点(20px)",
"toggleSidebar": "切换侧边栏", "toggleSidebar": "切换侧边栏",
"togglePalette": "切换控制板",
"copyNode": "复制所选节点", "copyNode": "复制所选节点",
"cutNode": "剪切所选节点", "cutNode": "剪切所选节点",
"pasteNode": "粘贴节点", "pasteNode": "粘贴节点",
"undoChange": "撤消上次执行的更改", "undoChange": "撤消上次执行的更改",
"searchBox": "打开搜索框", "searchBox": "打开搜索框",
"managePalette": "管理面板" "managePalette": "管理面板",
"actionList": "动作列表"
}, },
"library": { "library": {
"library": "库", "library": "库",
@ -239,30 +407,42 @@
"saveToLibrary": "保存到库...", "saveToLibrary": "保存到库...",
"typeLibrary": "__type__类型库", "typeLibrary": "__type__类型库",
"unnamedType": "无名__type__", "unnamedType": "无名__type__",
"exportToLibrary": "节点导出到库", "exportedToLibrary": "节点导出到库",
"dialogSaveOverwrite": "一个叫做__libraryName__的__libraryType__已经存在您需要覆盖么", "dialogSaveOverwrite": "一个叫做__libraryName__的__libraryType__已经存在您需要覆盖么",
"invalidFilename": "无效的文件名", "invalidFilename": "无效的文件名",
"savedNodes": "保存的节点", "savedNodes": "保存的节点",
"savedType": "已保存__type__", "savedType": "已保存__type__",
"saveFailed": "保存失败: __message__", "saveFailed": "保存失败: __message__",
"newFolder": "新文件夹",
"types": { "types": {
"local": "本地的",
"examples": "例子" "examples": "例子"
} },
"exportToLibrary": "将节点导出到库"
}, },
"palette": { "palette": {
"noInfo": "无可用信息", "noInfo": "无可用信息",
"filter": "过滤节点", "filter": "过滤节点",
"search": "搜索模块", "search": "搜索模块",
"addCategory": "添加新的...",
"label": { "label": {
"subflows": "子流程", "subflows": "子流程",
"network": "网络",
"common": "共通",
"input": "输入", "input": "输入",
"output": "输出", "output": "输出",
"function": "功能", "function": "功能",
"sequence": "序列",
"parser": "解析",
"social": "社交", "social": "社交",
"storage": "存储", "storage": "存储",
"analysis": "分析", "analysis": "分析",
"advanced": "高级" "advanced": "高级"
}, },
"actions": {
"collapse-all": "收起所有类别",
"expand-all": "展开所有类别"
},
"event": { "event": {
"nodeAdded": "添加到面板中的节点:", "nodeAdded": "添加到面板中的节点:",
"nodeAdded_plural": "添加到面板中的多个节点", "nodeAdded_plural": "添加到面板中的多个节点",
@ -276,6 +456,7 @@
}, },
"editor": { "editor": {
"title": "面板管理", "title": "面板管理",
"palette": "控制板",
"times": { "times": {
"seconds": "秒前", "seconds": "秒前",
"minutes": "分前", "minutes": "分前",
@ -309,6 +490,8 @@
"updated": "已更新", "updated": "已更新",
"install": "安装", "install": "安装",
"installed": "已安装", "installed": "已安装",
"conflict": "冲突",
"conflictTip": "<p>无法安装此模块,因为它包含已安装的<br/>节点类型</p><p>与<code>__module__</code>冲突</p>",
"loading": "加载目录...", "loading": "加载目录...",
"tab-nodes": "节点", "tab-nodes": "节点",
"tab-install": "安装", "tab-install": "安装",
@ -356,6 +539,7 @@
"label": "信息", "label": "信息",
"node": "节点", "node": "节点",
"type": "类型", "type": "类型",
"module": "模组",
"id": "ID", "id": "ID",
"status": "状态", "status": "状态",
"enabled": "启用", "enabled": "启用",
@ -364,6 +548,7 @@
"instances": "实例", "instances": "实例",
"properties": "属性", "properties": "属性",
"info": "信息", "info": "信息",
"desc": "描述",
"blank": "空白", "blank": "空白",
"null": "空", "null": "空",
"showMore": "展开", "showMore": "展开",
@ -386,9 +571,25 @@
"subflows": "子流程", "subflows": "子流程",
"flows": "流程", "flows": "流程",
"filterAll": "所有", "filterAll": "所有",
"showAllConfigNodes": "显示所有配置节点",
"filterUnused": "未使用", "filterUnused": "未使用",
"showAllUnusedConfigNodes": "显示所有未使用的配置节点",
"filtered": "__count__ 个隐藏" "filtered": "__count__ 个隐藏"
}, },
"context": {
"name": "上下文数据",
"label": "上下午",
"none": "未选择",
"refresh": "刷新以加载",
"empty": "空",
"node": "节点",
"flow": "流程",
"global": "全局",
"deleteConfirm": "你确定要删除这个项目吗?",
"autoRefresh": "刷新选择更改",
"refrsh": "刷新",
"delete": "删除"
},
"palette": { "palette": {
"name": "节点管理", "name": "节点管理",
"label": "节点" "label": "节点"
@ -399,8 +600,151 @@
"description": "描述", "description": "描述",
"dependencies": "依赖", "dependencies": "依赖",
"settings": "设置", "settings": "设置",
"noSummaryAvailable": "无可用摘要",
"editDescription": "编辑项目描述", "editDescription": "编辑项目描述",
"editDependencies": "编辑项目依赖" "editDependencies": "编辑项目依赖",
"noDescriptionAvailable": "没有可用的描述",
"editReadme": "编辑README.md",
"showProjectSettings": "显示项目设置",
"projectSettings": {
"title": "项目设置",
"edit": "编辑",
"none": "空",
"install": "安装",
"removeFromProject": "从项目中删除",
"addToProject": "添加到项目",
"files": "文件",
"package": "包",
"flow": "流程",
"credentials": "证书",
"packageCreate": "保存更改后将创建文件",
"fileNotExist": "文件不存在",
"selectFile": "选择文件",
"invalidEncryptionKey": "无效的加密密钥",
"encryptionEnabled": "启用加密",
"encryptionDisabled": "加密已禁用",
"setTheEncryptionKey": "设置加密密钥",
"resetTheEncryptionKey": "重置加密密钥",
"changeTheEncryptionKey": "更改加密密钥",
"currentKey": "当前密钥",
"newKey": "新密钥",
"credentialsAlert": "这将删除所有现有凭证",
"versionControl": "版本控制",
"branches": "分支",
"noBranches": "没有分支",
"deleteConfirm": "您确定要删除本地分支'__name__'吗? 这不能被撤消。",
"unmergedConfirm": "本地分支'__name__'具有未合并的更改,这些更改将丢失。你确定要删除吗?",
"deleteUnmergedBranch": "删除未合并的分支",
"gitRemotes": "Git远程仓库",
"addRemote": "添加远程仓库",
"addRemote2": "添加远程仓库",
"remoteName": "远程仓库名",
"nameRule": "只能包含A-Z 0-9 _ -",
"url": "URL",
"urlRule": "https://, ssh:// or file://",
"urlRule2": "网址中不能包含用户名/密码",
"noRemotes": "没有远程仓库",
"deleteRemoteConfrim": "您确定要删除远程仓库'__name__'吗?",
"deleteRemote": "删除远程仓库"
},
"userSettings": {
"committerDetail": "提交者详细信息",
"committerTip": "保留空白以使用系统默认值",
"userName": "用户名",
"email": "电子邮件",
"sshKeys": "SSH密钥",
"sshKeysTip": "允许您创建到远程git存储库的安全连接。",
"add": "添加密钥",
"addSshKey": "添加SSH密钥",
"addSshKeyTip": "生成新的公钥/私钥对",
"name": "名字",
"nameRule": "只能包含A-Z 0-9 _ -",
"passphrase": "密码短语",
"passphraseShort": "密码短语过短",
"optional": "可选的",
"cancel": "取消",
"generate": "生成密钥",
"noSshKeys": "没有SSH密钥",
"copyPublicKey": "将公钥复制到剪贴板",
"delete": "删除密钥",
"gitConfig": "Git配置",
"deleteConfirm": "您确定要删除SSH密钥__name__吗这不能被撤消。"
},
"versionControl": {
"unstagedChanges": "未暂存的变更",
"stagedChanges": "暂存的变更",
"unstageChange": "取消变更的暂存",
"stageChange": "暂存变更",
"unstageAllChange": "取消所有变更的暂存",
"stageAllChange": "暂存所有变更",
"commitChanges": "提交变更",
"resolveConflicts": "解决冲突",
"head": "HEAD",
"staged": "暂存的",
"unstaged": "未暂存的",
"local": "本地的",
"remote": "远程的",
"revert": "您确定要将更改恢复为'__file__'吗?这不能被撤消。",
"revertChanges": "还原变更",
"localChanges": "本地变更",
"none": "None",
"conflictResolve": "解决所有冲突。提交更改以完成合并。",
"localFiles": "本地文件",
"all": "所有的",
"unmergedChanges": "未合并的更改",
"abortMerge": "中止合并",
"commit": "提交",
"changeToCommit": "提交变更",
"commitPlaceholder": "输入您的提交信息",
"cancelCapital": "取消",
"commitCapital": "提交",
"commitHistory": "提交历史",
"branch": "分支:",
"moreCommits": "更多提交",
"changeLocalBranch": "变更本地分支",
"createBranchPlaceholder": "查找或创建分支",
"upstream": "上游",
"localOverwrite": "切换分支会覆盖您现有的本地更改。您必须先提交或撤消那些更改。",
"manageRemoteBranch": "管理远程分支",
"unableToAccess": "无法访问远程存储库",
"retry": "重试",
"setUpstreamBranch": "设置为上游分支",
"createRemoteBranchPlaceholder": "查找或创建远程分支",
"trackedUpstreamBranch": "创建的分支将被设置为跟踪的上游分支。",
"selectUpstreamBranch": "分支将被创建。 在下面选择以将其设置为被跟踪的上游分支。",
"pushFailed": "推送失败,因为远程具有更多的最新提交。请先拉取并合并,然后再尝试推送。",
"push": "推送",
"pull": "拉取",
"unablePull": "<p>无法提取远程更改;您未暂存的本地更改将被覆盖。</p><p>请先提交更改,然后重试。</p>",
"showUnstagedChanges": "显示未暂存的更改",
"connectionFailed": "无法连接到远程存储库:",
"pullUnrelatedHistory": "<p>远程有无关的提交历史</p><p>您确定要将这些更改拉入本地仓库吗?</p>",
"pullChanges": "拉取更改",
"history": "历史",
"projectHistory": "项目历史",
"daysAgo": "__count__天前",
"daysAgo_plural": "__count__天前",
"hoursAgo": "__count__小时前",
"hoursAgo_plural": "__count__小时前",
"minsAgo": "__count__分钟前",
"minsAgo_plural": "__count__分钟前",
"secondsAgo": "秒前",
"notTracking": "您的本地分支当前未跟踪一个远程分支。",
"statusUnmergedChanged": "您的仓库中有未合并的更改。您需要解决冲突并提交结果。",
"repositoryUpToDate": "您的仓库是最新的。",
"commitsAhead": "您的存储库领先远程仓库__count__次提交。您现在可以推送这些提交。",
"commitsAhead_plural": "您的存储库领先远程仓库__count__次提交。您现在可以推送这些提交。",
"commitsBehind": "您的存储库落后远程仓库__count__次提交。您现在可以拉取这些提交。",
"commitsBehind_plural": "您的存储库落后远程仓库__count__次提交。您现在可以拉取这些提交。",
"commitsAheadAndBehind1": "您的存储库落后远程仓库__count__次提交",
"commitsAheadAndBehind1_plural": "您的存储库落后远程仓库__count__次提交",
"commitsAheadAndBehind2": "领先远程仓库__count__次提交。",
"commitsAheadAndBehind2_plural": "领先远程仓库__count__次提交。",
"commitsAheadAndBehind3": "您必须先拉取远程提交,然后才能进行推送。",
"commitsAheadAndBehind3_plural": "您必须先拉取远程提交,然后才能进行推送。",
"refreshCommitHistory": "刷新提交历史",
"refreshChanges": "刷新更改"
}
} }
}, },
"typedInput": { "typedInput": {
@ -408,10 +752,12 @@
"str": "文字列", "str": "文字列",
"num": "数字", "num": "数字",
"re": "正则表达式", "re": "正则表达式",
"bool": "布尔", "bool": "布尔",
"json": "JSON", "json": "JSON",
"bin": "二进制流", "bin": "二进制流",
"date": "时间戳" "date": "时间戳",
"jsonata": "表达式",
"env": "环境变量"
} }
}, },
"editableList": { "editableList": {
@ -423,8 +769,10 @@
}, },
"expressionEditor": { "expressionEditor": {
"functions": "功能", "functions": "功能",
"functionReference": "功能reference",
"insert": "插入", "insert": "插入",
"title": "JSONata表达式编辑器", "title": "JSONata表达式编辑器",
"test": "测试",
"data": "示例消息", "data": "示例消息",
"result": "结果", "result": "结果",
"format": "格式表达方法", "format": "格式表达方法",
@ -438,14 +786,229 @@
"eval": "评估表达式错误:\n __message__" "eval": "评估表达式错误:\n __message__"
} }
}, },
"jsEditor": {
"title": "JavaScript编辑器"
},
"textEditor": {
"title": "文本编辑器"
},
"jsonEditor": { "jsonEditor": {
"title": "JSON编辑器", "title": "JSON编辑器",
"format": "格式化JSON" "format": "格式化JSON",
"rawMode": "编辑 JSON",
"uiMode": "Visual编辑器",
"insertAbove": "在上方插入",
"insertBelow": "在下方插入",
"addItem": "添加项目",
"copyPath": "复制路径到项目",
"expandItems": "展开项目",
"collapseItems": "收合项目",
"duplicate": "重复",
"error": {
"invalidJSON": "无效的JSON: "
}
},
"markdownEditor": {
"title": "Markdown编辑器",
"expand": "展开",
"format": "格式化为markdown",
"heading1": "标题 1",
"heading2": "标题 2",
"heading3": "标题 3",
"bold": "粗体",
"italic": "斜体",
"code": "代码",
"ordered-list": "排序的列表",
"unordered-list": "非排序的列表",
"quote": "引用",
"link": "链接",
"horizontal-rule": "水平线",
"toggle-preview": "切换预览"
}, },
"bufferEditor": { "bufferEditor": {
"title": "缓冲区编辑器", "title": "缓冲区编辑器",
"modeString": "作为UTF-8字符串处理", "modeString": "作为UTF-8字符串处理",
"modeArray": "作为JSON数组处理", "modeArray": "作为JSON数组处理",
"modeDesc": "<h3>缓冲区编辑器</h3><p>缓冲区类型被存储为字节值的JSON数组。编辑器将尝试将输入的数值解析为JSON数组。如果它不是有效的JSON它将被视为UTF-8字符串并被转换为单个字符代码点的数组。</p><p>例如,<code>Hello World</code>的值会被转换为JSON数组<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>" "modeDesc": "<h3>缓冲区编辑器</h3><p>缓冲区类型被存储为字节值的JSON数组。编辑器将尝试将输入的数值解析为JSON数组。如果它不是有效的JSON它将被视为UTF-8字符串并被转换为单个字符代码点的数组。</p><p>例如,<code>Hello World</code>的值会被转换为JSON数组<pre>[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]</pre></p>"
},
"projects": {
"config-git": "配置Git客户端",
"welcome": {
"hello": "你好! 我们已经将“项目”引入了Node-RED。",
"desc0": "这是一种用于管理流程文件的新方法,并且包括对流程的版本控制。",
"desc1": "首先您可以创建您的第一个项目或从git存储库克隆现有项目。",
"desc2": "如果不确定,可以暂时跳过此步骤。您仍然可以随时通过“项目”菜单创建第一个项目。",
"create": "建立专案",
"clone": "克隆仓库",
"openExistingProject": "打开现有项目",
"not-right-now": "不是现在"
},
"git-config": {
"setup": "设置您的版本控制客户端",
"desc0": "Node-RED使用开源工具Git进行版本控制。它跟踪对项目文件的更改并允许您将其推送到远程存储库。",
"desc1": "提交一组更改时Git会使用用户名和电子邮件地址记录谁进行了更改。用户名可以是您想要的任何名称-不必是您的真实姓名。",
"desc2": "您的Git客户端已经配置了以下详细信息。",
"desc3": "您可以稍后在设置对话框的'Git config'标签下更改这些设置。",
"username": "用户名",
"email": "电子邮件"
},
"project-details": {
"create": "创建你的项目",
"desc0": "项目被维护为Git仓库。与他人一起共享您的流程",
"desc1": "您可以创建多个项目,并通过编辑器在它们之间快速切换。",
"desc2": "首先,您的项目需要一个名称和一个可选的描述。",
"already-exists": "项目已存在",
"must-contain": "只能包含A-Z 0-9 _ -",
"project-name": "项目名",
"desc": "描述",
"opt": "可选的"
},
"clone-project": {
"clone": "克隆一个项目",
"desc0": "如果您已经有一个包含项目的git仓库则可以对其进行克隆以开始使用。",
"already-exists": "项目已存在",
"must-contain": "只能包含A-Z 0-9 _ -",
"project-name": "项目名",
"no-info-in-url": "网址中不要包含用户名/密码",
"git-url": "Git仓库的url",
"protocols": "https://, ssh:// or file://",
"auth-failed": "认证失败",
"username": "用户名",
"passwd": "秘密啊",
"ssh-key": "SSH密钥",
"passphrase": "密码短语",
"ssh-key-desc": "在通过ssh克隆仓库之前必须添加SSH密钥才能访问它。",
"ssh-key-add": "添加一个ssh密钥",
"credential-key": "证书加密密钥",
"cant-get-ssh-key": "错误! 无法获取所选的SSH密钥路径。",
"already-exists2": "已存在",
"git-error": "git错误",
"connection-failed": "连接失败",
"not-git-repo": "不是一个git仓库",
"repo-not-found": "未发现仓库"
},
"default-files": {
"create": "创建您的项目文件",
"desc0": "一个包含您的流程文件Readme文件和package.json文件的项目。",
"desc1": "它可以包含您要在Git仓库中维护的任何其他文件。",
"desc2": "您现有的流程和凭证文件将被复制到项目中。",
"flow-file": "流程文件",
"credentials-file": "证书文件"
},
"encryption-config": {
"setup": "设置证书文件的加密",
"desc0": "您的流程证书文件可以被加密以确保其内容安全。",
"desc1": "如果要将这些证书存储在公共Git存储库中则必须通过提供密钥短语来对它们进行加密。",
"desc2": "您的流程证书文件当前未加密。",
"desc3": "这意味着任何有权访问该文件的人都可以读取其内容,例如密码和访问令牌。",
"desc4": "如果要将这些证书存储在公共Git仓库中则必须通过提供密钥短语来对它们进行加密。",
"desc5": "当前使用设置文件中的credentialSecret属性作为密钥来加密流程证书文件。",
"desc6": "您的流程证书文件当前使用系统生成的密钥加密。您应该为此项目提供一个新的密钥。",
"desc7": "密钥将与项目文件分开存储。您将需要提供在另一个Node-RED实例中使用该项目的密钥。",
"credentials": "证书",
"enable": "启用加密",
"disable": "禁用加密",
"disabled": "禁用的",
"copy": "复制现有密钥",
"use-custom": "使用自定义密钥",
"desc8": "证书文件不会被加密,其内容很容易阅读",
"create-project-files": "创建项目文件",
"create-project": "创建项目",
"already-exists": "已存在",
"git-error": "git错误",
"git-auth-error": "git认证错误"
},
"create-success": {
"success": "您已经成功创建了第一个项目!",
"desc0": "现在您可以像往常一样继续使用Node-RED。",
"desc1": "侧栏中的“信息”标签显示了您当前的活动项目。名称旁边的按钮可用于访问项目设置视图。",
"desc2": "侧栏中的“历史记录”标签可用于查看项目中已更改的文件并提交。它向您显示了提交的完整历史记录,并允许您将更改推送到远程存储库。"
},
"create": {
"projects": "项目",
"already-exists": "项目已存在",
"must-contain": "只能包含A-Z 0-9 _ -",
"no-info-in-url": "网址中不要包含用户名/密码",
"open": "打开项目",
"create": "创建项目",
"clone": "克隆仓库",
"project-name": "项目名",
"desc": "描述",
"opt": "可选的",
"flow-file": "流程文件",
"credentials": "证书",
"enable-encryption": "启用加密",
"disable-encryption": "禁用加密",
"encryption-key": "加密密钥",
"desc0": "用来保护您的凭证的短语",
"desc1": "凭证文件不会被加密,其内容很容易阅读",
"git-url": "Git存储库URL",
"protocols": "https://, ssh:// or file://",
"auth-failed": "验证失败",
"username": "用户名",
"password": "密码",
"ssh-key": "SSH密钥",
"passphrase": "密码短语",
"desc2": "在通过ssh克隆存储库之前必须添加SSH密钥才能访问它。",
"add-ssh-key": "添加一个ssh密钥",
"credentials-encryption-key": "证书加密密钥",
"already-exists-2": "已存在",
"git-error": "git错误",
"con-failed": "连接失败",
"not-git": "不是git仓库",
"no-resource": "找不到存储库",
"cant-get-ssh-key-path": "错误无法获取所选的SSH密钥路径。",
"unexpected_error": "意外的错误"
},
"delete": {
"confirm": "您确定要删除此项目吗?"
},
"create-project-list": {
"search": "搜索您的项目",
"current": "当前的"
},
"require-clean": {
"confirm": "<p>您有未部署的更改,这些更改将丢失。</p><p>您要继续吗?</p>"
},
"send-req": {
"auth-req": "存储库需要认证",
"username": "用户名",
"password": "秘密",
"passphrase": "密码短语",
"retry": "重试",
"update-failed": "无法更新身份验证",
"unhandled": "未处理的错误响应"
},
"create-branch-list": {
"invalid": "无效的分支",
"create": "创建分支",
"current": "当前的"
},
"create-default-file-set": {
"no-active": "没有活动项目就无法创建默认文件集",
"no-empty": "无法在非空项目上创建默认文件集",
"git-error": "git错误"
},
"errors": {
"no-username-email": "您的Git客户端未配置用户名/电子邮件。",
"unexpected": "发生了一个意料之外的问题",
"code": "代码"
}
},
"editor-tab": {
"properties": "属性",
"envProperties": "环境变量",
"description": "描述",
"appearance": "外观",
"preview": "UI预览",
"defaultValue": "默认值"
},
"languages": {
"de": "德语",
"en-US": "英文",
"ja": "日语",
"ko": "韩文",
"zh-CN": "简体中文",
"zh-TW": "繁体中文"
} }
} }

View File

@ -214,5 +214,57 @@
"$toMillis": { "$toMillis": {
"args": "timestamp", "args": "timestamp",
"desc": "将ISO 8601格式的字符串`timestamp`转换为从UNIX时间 (1970年1月1日 UTC/GMT的午夜开始到现在的毫秒数。如果该字符串的格式不正确则抛出错误。" "desc": "将ISO 8601格式的字符串`timestamp`转换为从UNIX时间 (1970年1月1日 UTC/GMT的午夜开始到现在的毫秒数。如果该字符串的格式不正确则抛出错误。"
},
"$env": {
"args": "arg",
"desc": "返回环境变量的值。\n\n这是Node-RED定义的函数。"
},
"$eval": {
"args": "expr [, context]",
"desc": "使用当前上下文来作为评估依据,分析并评估字符串`expr`其中包含文字JSON或JSONata表达式。"
},
"$formatInteger": {
"args": "number, picture",
"desc": "将“数字”转换为字符串并将其格式化为“图片”字符串指定的整数表示形式。图片字符串参数定义了数字的格式并具有与XPath F&O 3.1 规范中的fnformat-integer相同的语法。"
},
"$parseInteger": {
"args": "string, picture",
"desc": "使用“图片”字符串指定的格式将“字符串”参数的内容解析为整数作为JSON数字。图片字符串参数与$formatInteger格式相同。."
},
"$error": {
"args": "[str]",
"desc": "引发错误并显示一条消息。 可选的`str`将替代$error()函数评估的默认消息。"
},
"$assert": {
"args": "arg, str",
"desc": "如果`arg`为真,则该函数返回。 如果arg为假则抛出带有str的异常作为异常消息。"
},
"$single": {
"args": "array, function",
"desc": "返回满足参数function谓语的array参数中的唯一值 (比如传递值时函数返回布尔值“true”)。如果匹配值的数量不唯一时,则抛出异常。\n\n应在以下签名中提供函数`functionvalue [index [array []]]`其中value是数组的每个输入index是该值的位置整个数组作为第三个参数传递。"
},
"$encodeUrl": {
"args": "str",
"desc": "通过用表示字符的UTF-8编码的一个两个三个或四个转义序列替换某些字符的每个实例对统一资源定位符URL组件进行编码。\n\n示例`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"args": "str",
"desc": "通过用表示字符的UTF-8编码的一个两个三个或四个转义序列替换某些字符的每个实例对统一资源定位符URL进行编码。\n\n示例 `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"args": "str",
"desc": "解码以前由encodeUrlComponent创建的统一资源定位器URL组件。 \n\n示例 `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"args": "str",
"desc": "解码先前由encodeUrl创建的统一资源定位符URL。 \n\n示例 `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},
"$distinct": {
"args": "array",
"desc": "返回一个数组,其中重复的值已从`数组`中删除"
},
"$type": {
"args": "value",
"desc": "以字符串形式返回`值`的类型。 如果该`值`未定义,则将返回`未定义`"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
{
"info": {
"tip0" : "您可以用 {{core:delete-selection}} 刪除選擇的節點或連結。",
"tip1" : "{{core:search}} 可以在流程內搜索節點。",
"tip2": "{{core:toggle-sidebar}} 可以顯示或隱藏邊側欄。",
"tip3": "您可以在 {{core:manage-palette}} 中管理節點的控制台。",
"tip4": "側邊欄中會列出流程中所有的配置節點。您可以通過功能表或者 {{core:show-config-tab}} 來訪問這些節點。",
"tip5": "您可以在設定中選擇顯示或隱藏這些提示。",
"tip6": "您可以用[left] [up] [down] [right]鍵來移動被選中的節點。按住[shift]可以更快地移動節點。",
"tip7": "把節點拖到連接上可以向連接中插入節點。",
"tip8": "您可以用 {{core:show-export-dialog}} 來匯出被選中的節點或標籤頁中的流程。",
"tip9": "您可以將流程的json文字檔拖入編輯方塊或 {{core:show-import-dialog}} 來導入流程。",
"tip10": "按住[shift]後按一下並拖動節點可以將該節點的多個連接一併移動到其他節點的埠。",
"tip11": "{{core:show-info-tab}} 可以顯示「資訊」標籤頁。 {{core:show-debug-tab}} 可以顯示「調試」標籤頁。",
"tip12": "按住[ctrl]的同時點擊工作介面可以在節點的對話欄中快速添加節點。",
"tip13": "按住[ctrl]的同時點擊節點的埠或後續節點可以快速連接多個節點。",
"tip14": "按住[shift]的同時點擊節點會選中所有被連接的節點。",
"tip15": "按住[ctrl]的同時點擊節點可以在選中或取消選中節點。",
"tip16": "{{core:show-previous-tab}} 和 {{core:show-next-tab}} 可以切換標籤頁。",
"tip17": "您可以在節點的屬性配置畫面中通過 {{core:confirm-edit-tray}} 來更改設置,或者用 {{core:cancel-edit-tray}} 來取消更改。",
"tip18": "您可以通過點擊 {{core:edit-selected-node}} 來顯示被選中節點的屬性設置畫面。"
}
}

View File

@ -0,0 +1,270 @@
{
"$string": {
"args": "arg",
"desc": "通過以下的類型轉換規則將參數*arg*轉換成字串:\n\n - 字串不轉換。\n -函數轉換成空的字串。\n - JSON的值無法用數字表示所以用無限大或者NaN非數表示。\n - 用JSON.stringify函數將其他值轉換成JSON字串。"
},
"$length": {
"args": "str",
"desc": "輸出字串str的字數。如果str不是字串拋出錯誤。"
},
"$substring": {
"args": "str, start[, length]",
"desc": "輸出`start`位置後的的首次出現的包括`str`的子字串。 如果`length`被指定,那麼的字串中將只包括前`length`個文字。如果`start`是負數則輸出從`str`末尾開始的`length`個文字"
},
"$substringBefore": {
"args": "str, chars",
"desc": "輸出str中首次出現的chars之前的子字串如果str中不包括chars則輸出str。"
},
"$substringAfter": {
"args": "str, chars",
"desc": "輸出str中首次出現的chars之後的子字串如果str中不包括chars則輸出str。"
},
"$uppercase": {
"args": "str",
"desc": "`將str中的所有字母變為大寫後輸出。"
},
"$lowercase": {
"args": "str",
"desc": "將str中的所有字母變為小寫後輸出。"
},
"$trim": {
"args": "str",
"desc": "將以下步驟應用於`str`來去除所有空白文字並實現標準化。\n\n 將全部tab定位字元、Enter鍵、換行字元用空白代替。\n- 將連續的空白文字變成一個空白文字。\n- 消除開頭和末尾的空白文字。\n\n如果`str`沒有被指定(即在無輸入參數的情況下調用本函數),將上下文的值作為`str`來使用。 如果`str` 不是字串則拋出錯誤。"
},
"$contains": {
"args": "str, pattern",
"desc": "字串`str` 和 `pattern`匹配的話輸出`true`,不匹配的情況下輸出 `false`。 不指定`str`的情況下(比如用一個參數調用本函數時)、將上下文的值作為`str`來使用。參數 `pattern`可以為字串或正則表達。"
},
"$split": {
"args": "str[, separator][, limit]",
"desc": "將參數`str`分解成由子字串組成的陣列。 如果`str`不是字串拋出錯誤。可以省略的參數 `separator`中指定字串`str`的分隔符號。分隔符號可以是文字或規則運算式。在不指定`separator`的情況下、將分隔符號看作空的字串並把`str`拆分成由單個字母組成的陣列。如果`separator`不是字串則拋出錯誤。在可省略的參數`limit`中指定分割後的子字串的最大個數。超出個數的子字串將被捨棄。如果`limit`沒有被指定,`str` 將不考慮子字串的個數而將字串完全分隔。如果`limit`是負數則拋出錯誤。"
},
"$join": {
"args": "array[, separator]",
"desc": "用可以省略的參數 `separator`來把多個字元串連接。如果`array`不是字串則拋出錯誤。 如果沒有指定`separator`,則用空字串來連接字元(即字串之間沒有`separator`)。 如果`separator`不是字元則拋出錯誤。"
},
"$match": {
"args": "str, pattern [, limit]",
"desc": "對字串`str`使用規則運算式`pattern`並輸出與`str`相匹配的部分資訊。"
},
"$replace": {
"args": "str, pattern, replacement [, limit]",
"desc": "在字串`str`中搜索`pattern`並用`replacement`來替換。\n\n可選參數`limit`用來指定替換次數的上限。"
},
"$now": {
"args": "",
"desc": "生成ISO 8601互換格式的時刻並作為字串輸出。"
},
"$base64encode": {
"args": "string",
"desc": "將ASCII格式的字串轉換為Base 64格式。將字串中的文字視作二進位形式的資料處理。包含URI編碼在內的字串文字必須在0x00到0xFF的範圍內否則不會被支持。"
},
"$base64decode": {
"args": "string",
"desc": "用UTF-8內碼表將Base 64形式二進位值轉換為字串。"
},
"$number": {
"args": "arg",
"desc": "用下述的規則將參數 `arg`轉換為數值。:\n\n 數值不做轉換。\n 將字串中合法的JSON數値表示轉換成數値。\n 其他形式的值則拋出錯誤。"
},
"$abs": {
"args": "number",
"desc": "輸出參數`number`的絕對值。"
},
"$floor": {
"args": "number",
"desc": "輸出比`number`的值小的最大整數。"
},
"$ceil": {
"args": "number",
"desc": "輸出比`number`的值大的最小整數。"
},
"$round": {
"args": "number [, precision]",
"desc": "輸出四捨五入後的參數`number`。可省略的參數 `precision`指定四捨五入後小數點下的位數。"
},
"$power": {
"args": "base, exponent",
"desc": "輸出底數`base`的`exponent`次冪。"
},
"$sqrt": {
"args": "number",
"desc": "輸出參數 `number`的平方根。"
},
"$random": {
"args": "",
"desc": "輸出比0大比1小的偽亂數。"
},
"$millis": {
"args": "",
"desc": "返回從UNIX時間 (1970年1月1日 UTC/GMT的午夜開始到現在的毫秒數。在同一個運算式的測試中所有對`$millis()`的調用將會返回相同的值。"
},
"$sum": {
"args": "array",
"desc": "輸出陣列`array`的總和。如果`array`不是數值則拋出錯誤。"
},
"$max": {
"args": "array",
"desc": "輸出陣列`array`的最大值。如果`array`不是數值則拋出錯誤。"
},
"$min": {
"args": "array",
"desc": "輸出陣列`array`的最小值。如果`array`不是數值則拋出錯誤。。"
},
"$average": {
"args": "array",
"desc": "輸出陣列`array`的平均數。如果`array`不是數值則拋出錯誤。。"
},
"$boolean": {
"args": "arg",
"desc": "用下述規則將資料轉換成布林值。:\n\n - 不轉換布林值`Boolean`。\n 將空的字串`string`轉換為`false`\n 將不為空的字串`string`轉換為`true`\n 將為0的數位`number`轉換成`false`\n 將不為0的數位`number`轉換成`true`\n –將`null`轉換成`false`\n –將空的陣列`array`轉換成`false`\n –如果陣列`array`中含有可以轉換成`true`的要素則轉換成`true`\n –如果`array`中沒有可轉換成`true`的要素則轉換成`false`\n 空的物件`object`轉換成`false`\n 非空的物件`object`轉換成`true`\n –將函數`function`轉換成`false`"
},
"$not": {
"args": "arg",
"desc": "輸出做反轉運算後的布林值。首先將`arg`轉換為布林值。"
},
"$exists": {
"args": "arg",
"desc": "如果算式`arg`的值存在則輸出`true`。如果算式的值不存在(比如指向不存在區域的引用)則輸出`false`。"
},
"$count": {
"args": "array",
"desc": "輸出陣列中的元素數。"
},
"$append": {
"args": "array, array",
"desc": "將兩個陣列連接。"
},
"$sort": {
"args": "array [, function]",
"desc": "輸出排序後的陣列`array`。\n\n如果使用了比較函數`function`,則下述兩個參數需要被指定。\n\n`function(left, right)`\n\n該比較函數是為了比較left和right兩個值而被排序演算法調用的。如果使用者希望left的值被置於right的值之後那麼該函數必須輸出布林值`true`來表示位置交換。而在不需要位置交換時函數必須輸出`false`。"
},
"$reverse": {
"args": "array",
"desc": "輸出倒序後的陣列`array`。"
},
"$shuffle": {
"args": "array",
"desc": "輸出隨機排序後的陣列 `array`。"
},
"$zip": {
"args": "array, ...",
"desc": "將陣列中的值按索引順序打包後輸出。"
},
"$keys": {
"args": "object",
"desc": "輸出由物件內的鍵組成的陣列。如果參數是物件的陣列則輸出由所有物件中的鍵去重後組成的佇列。"
},
"$lookup": {
"args": "object, key",
"desc": "輸出對象中與參數`key`對應的值。如果第一個參數`object`是陣列,那麼陣列中所有的物件都將被搜索並輸出這些物件中與參數`key`對應的值。"
},
"$spread": {
"args": "object",
"desc": "將物件中的鍵值對分隔成每個要素中只含有一個鍵值對的陣列。如果參數`object`是陣列,那麼返回值的陣列中包含所有物件中的鍵值對。"
},
"$merge": {
"args": "array&lt;object&gt;",
"desc": "將輸入陣列`objects`中所有的鍵值對合併到一個`object`中並返回。如果輸入陣列的要素中含有重複的鍵,則返回的`object`中將只包含陣列中最後出現要素的值。如果輸入陣列中包括物件以外的元素,則拋出錯誤。"
},
"$sift": {
"args": "object, function",
"desc": "輸出參數`object`中符合`function`的鍵值對。\n\n`function`必須含有下述參數。\n\n`function(value [, key [, object]])`"
},
"$each": {
"args": "object, function",
"desc": "將函數`function`應用於`object`中的所有鍵值對並輸出由所有返回值組成的陣列。"
},
"$map": {
"args": "array, function",
"desc": "將函數`function`應用於陣列`array`中所有的值並輸出由返回值組成的陣列。\n\n`function`中必須含有下述參數。\n\n`function(value [, index [, array]])`"
},
"$filter": {
"args": "array, function",
"desc": "輸出陣列`array`中符合函數`function`條件的值組成的陣列。\n\n`function`必須包括下述參數。\n\n`function(value [, index [, array]])`"
},
"$reduce": {
"args": "array, function [, init]",
"desc": "將`function`依次應用於陣列中的各要素值。 其中,前一個要素值的計算結果將參與到下一次的函數運算中。。\n\n函數`function`接受兩個參數並作為中綴標記法中的操作符。\n\n可省略的參數`init`將作為運算的初始值。"
},
"$flowContext": {
"args": "string",
"desc": "獲取流上下文(流等級的上下文,可以讓所有節點共用)的屬性。"
},
"$globalContext": {
"args": "string",
"desc": "獲取全域上下文的屬性。"
},
"$pad": {
"args": "string, width [, char]",
"desc": "根據需要,向字串`string`的副本中填充文字使該字串的字數達到`width`的絕對值並返回填充文字後的字串。\n\n如果`width`的值為正,則向字串`string`的右側填充文字,如果`width`為負,則向字串`string`的左側填充文字。\n\n可選參數`char`用來指定填充的文字。如果未指定該參數,則填充空白文字。"
},
"$fromMillis": {
"args": "number",
"desc": "將表示從UNIX時間 (1970年1月1日 UTC/GMT的午夜開始到現在的毫秒數的數值轉換成ISO 8601形式時間戳記的字串。"
},
"$formatNumber": {
"args": "number, picture [, options]",
"desc": "將`number`轉換成具有`picture`所指定的數值格式的字串。\n\n此函數的功能與XPath F&O 3.1規格中定義的XPath/XQuery函數的fn:format-number功能相一致。參數`picture`用於指定數值的轉換格式其語法與fn:format-number中的定義一致。\n\n可選的第三參數`options`用來覆蓋預設的局部環境格式如小數點分隔符號。如果指定該參數那麼該參數必須是包含name/value對的物件並且name/value對必須符合XPath F&O 3.1規格中記述的數值格式。"
},
"$formatBase": {
"args": "number [, radix]",
"desc": "將`number`變換為以參數`radix`的值為基數形式的字串。如果不指定`radix`的值則默認基數為10。指定的`radix`值必須在236之間否則拋出錯誤。"
},
"$toMillis": {
"args": "timestamp",
"desc": "將ISO 8601格式的字串`timestamp`轉換為從UNIX時間 (1970年1月1日 UTC/GMT的午夜開始到現在的毫秒數。如果該字串的格式不正確則拋出錯誤。"
},
"$env": {
"args": "arg",
"desc": "返回環境變量的值。\n\n這是Node-RED定義的函數。"
},
"$eval": {
"args": "expr [, context]",
"desc": "使用當前上下文來作為評估依據,分析並評估字符串`expr`其中包含文字JSON或JSONata表達式。"
},
"$formatInteger": {
"args": "number, picture",
"desc": "將“數字”轉換為字符串並將其格式化為“圖片”字符串指定的整數表示形式。圖片字符串參數定義了數字的格式並具有與XPath F&O 3.1 規範中的fnformat-integer相同的語法。"
},
"$parseInteger": {
"args": "string, picture",
"desc": "使用“圖片”字符串指定的格式將“字符串”參數的內容解析為整數作為JSON數字。圖片字符串參數與$formatInteger格式相同。."
},
"$error": {
"args": "[str]",
"desc": "引發錯誤並顯示一條消息。 可選的`str`將替代$error()函數評估的默認消息。"
},
"$assert": {
"args": "arg, str",
"desc": "如果`arg`為真,則該函數返回。 如果arg為假則拋出帶有str的異常作為異常消息。"
},
"$single": {
"args": "array, function",
"desc": "返回滿足參數function謂語的array參數中的唯一值 (比如傳遞值時函數返回布林值“true”)。如果匹配值的數量不唯一時,則拋出異常。\n\n應在以下簽名中提供函數`functionvalue [index [array []]]`其中value是數組的每個輸入index是該值的位置整個數組作為第三個參數傳遞。"
},
"$encodeUrl": {
"args": "str",
"desc": "通過用表示字符的UTF-8編碼的一個兩個三個或四個轉義序列替換某些字符的每個實例對統一資源定位符URL組件進行編碼。\n\n示例`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`"
},
"$encodeUrlComponent": {
"args": "str",
"desc": "通過用表示字符的UTF-8編碼的一個兩個三個或四個轉義序列替換某些字符的每個實例對統一資源定位符URL進行編碼。\n\n示例 `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`"
},
"$decodeUrl": {
"args": "str",
"desc": "解碼以前由encodeUrlComponent創建的統一資源定位器URL組件。 \n\n示例 `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`"
},
"$decodeUrlComponent": {
"args": "str",
"desc": "解碼先前由encodeUrl創建的統一資源定位符URL。 \n\n示例 `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`"
},
"$distinct": {
"args": "array",
"desc": "返回一個數組,其中重復的值已從`數組`中刪除"
},
"$type": {
"args": "value",
"desc": "以字符串形式返回`值`的類型。 如果該`值`未定義,則將返回`未定義`"
}
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@node-red/editor-client", "name": "@node-red/editor-client",
"version": "1.0.3", "version": "1.0.4",
"license": "Apache-2.0", "license": "Apache-2.0",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,4 +1,4 @@
ace.define("ace/snippets/nrjavascript",[],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="nrjavascript"}); ace.define("ace/snippets/nrjavascript",[],function(e,t,n){"use strict";t.snippetText='# Prototype\nsnippet proto\n ${1:class_name}.prototype.${2:method_name} = function(${3:first_argument}) {\n ${4:// body...}\n };\n# Function\nsnippet fun\n function ${1?:function_name}(${2:argument}) {\n ${3:// body...}\n }\n# Anonymous Function\nregex /((=)\\s*|(:)\\s*|(\\()|\\b)/f/(\\))?/\nsnippet f\n function${M1?: ${1:functionName}}($2) {\n ${0:$TM_SELECTED_TEXT}\n }${M2?;}${M3?,}${M4?)}\n# Immediate function\ntrigger \\(?f\\(\nendTrigger \\)?\nsnippet f(\n (function(${1}) {\n ${0:${TM_SELECTED_TEXT:/* code */}}\n }(${1}));\n# if\nsnippet if\n if (${1:true}) {\n ${0}\n }\n# if ... else\nsnippet ife\n if (${1:true}) {\n ${2}\n } else {\n ${0}\n }\n# tertiary conditional\nsnippet ter\n ${1:/* condition */} ? ${2:a} : ${3:b}\n# switch\nsnippet switch\n switch (${1:expression}) {\n case \'${3:case}\':\n ${4:// code}\n break;\n ${5}\n default:\n ${2:// code}\n }\n# case\nsnippet case\n case \'${1:case}\':\n ${2:// code}\n break;\n ${3}\n\n# while (...) {...}\nsnippet wh\n while (${1:/* condition */}) {\n ${0:/* code */}\n }\n# try\nsnippet try\n try {\n ${0:/* code */}\n } catch (e) {}\n# do...while\nsnippet do\n do {\n ${2:/* code */}\n } while (${1:/* condition */});\n# Object Method\nsnippet :f\nregex /([,{[])|^\\s*/:f/\n ${1:method_name}: function(${2:attribute}) {\n ${0}\n }${3:,}\n# setTimeout function\nsnippet setTimeout\nregex /\\b/st|timeout|setTimeo?u?t?/\n setTimeout(function() {${3:$TM_SELECTED_TEXT}}, ${1:10});\n# console.log (Firebug)\nsnippet cl\n console.log(${1});\n# return\nsnippet ret\n return ${1:result}\n# for (property in object ) { ... }\nsnippet fori\n for (var ${1:prop} in ${2:Things}) {\n ${0:$2[$1]}\n }\n# hasOwnProperty\nsnippet has\n hasOwnProperty(${1})\n# docstring\nsnippet /**\n /**\n * ${1:description}\n *\n */\nsnippet @par\nregex /^\\s*\\*\\s*/@(para?m?)?/\n @param {${1:type}} ${2:name} ${3:description}\nsnippet @ret\n @return {${1:type}} ${2:description}\n# JSON.parse\nsnippet jsonp\n JSON.parse(${1:jstr});\n# JSON.stringify\nsnippet jsons\n JSON.stringify(${1:object});\n# self-defining function\nsnippet sdf\n var ${1:function_name} = function(${2:argument}) {\n ${3:// initial code ...}\n\n $1 = function($2) {\n ${4:// main code}\n };\n }\n# \nsnippet for-\n for (var ${1:i} = ${2:Things}.length; ${1:i}--; ) {\n ${0:${2:Things}[${1:i}];}\n }\n# for (...) {...}\nsnippet for\n for (var ${1:i} = 0; $1 < ${2:Things}.length; $1++) {\n ${3:$2[$1]}$0\n }\n# for (...) {...} (Improved Native For-Loop)\nsnippet forr\n for (var ${1:i} = ${2:Things}.length - 1; $1 >= 0; $1--) {\n ${3:$2[$1]}$0\n }\n# Node-RED Specific Funcs\nsnippet nodes\n node.send(${1:msg})\nsnippet clone\n RED.util.cloneMessage(${1:msg})\nsnippet nodel\n node.log($1)\nsnippet nodew\n node.warn($1)\nsnippet nodee\n node.error($1)\nsnippet noded\n node.debug($1)\nsnippet done\n node.done($1)\nsnippet flowg\n flow.get($1)\nsnippet flows\n flow.set($1, $2)\nsnippet globalg\n global.get($1)\nsnippet globals\n global.set($1, $2)\n',t.scope="nrjavascript"});
(function() { (function() {
ace.require(["ace/snippets/nrjavascript"], function(m) { ace.require(["ace/snippets/nrjavascript"], function(m) {
if (typeof module == "object" && typeof exports == "object" && module) { if (typeof module == "object" && typeof exports == "object" && module) {
@ -6,4 +6,3 @@ ace.define("ace/snippets/nrjavascript",[],function(e,t,n){"use strict";t.snippet
} }
}); });
})(); })();

View File

@ -21,6 +21,7 @@ RED.history = (function() {
var i; var i;
var len; var len;
var node; var node;
var group;
var subflow; var subflow;
var modifiedTabs = {}; var modifiedTabs = {};
var inverseEv; var inverseEv;
@ -74,6 +75,15 @@ RED.history = (function() {
RED.nodes.removeLink(ev.links[i]); RED.nodes.removeLink(ev.links[i]);
} }
} }
if (ev.groups) {
inverseEv.groups = [];
for (i=0;i<ev.groups.length;i++) {
group = ev.groups[i];
modifiedTabs[group.z] = true;
inverseEv.groups.push(group);
RED.nodes.removeGroup(group);
}
}
if (ev.workspaces) { if (ev.workspaces) {
inverseEv.workspaces = []; inverseEv.workspaces = [];
for (i=0;i<ev.workspaces.length;i++) { for (i=0;i<ev.workspaces.length;i++) {
@ -193,12 +203,35 @@ RED.history = (function() {
n.dirty = true; n.dirty = true;
}); });
} }
if (ev.groups) {
inverseEv.groups = [];
var groupsToAdd = new Set(ev.groups.map(function(g) { return g.id }));
for (i=0;i<ev.groups.length;i++) {
RED.nodes.addGroup(ev.groups[i])
modifiedTabs[ev.groups[i].z] = true;
inverseEv.groups.push(ev.groups[i]);
if (ev.groups[i].g && !groupsToAdd.has(ev.groups[i].g)) {
group = RED.nodes.group(ev.groups[i].g);
if (group.nodes.indexOf(ev.groups[i]) === -1) {
group.nodes.push(ev.groups[i]);
}
RED.group.markDirty(ev.groups[i])
}
}
}
if (ev.nodes) { if (ev.nodes) {
inverseEv.nodes = []; inverseEv.nodes = [];
for (i=0;i<ev.nodes.length;i++) { for (i=0;i<ev.nodes.length;i++) {
RED.nodes.add(ev.nodes[i]); RED.nodes.add(ev.nodes[i]);
modifiedTabs[ev.nodes[i].z] = true; modifiedTabs[ev.nodes[i].z] = true;
inverseEv.nodes.push(ev.nodes[i].id); inverseEv.nodes.push(ev.nodes[i].id);
if (ev.nodes[i].g) {
group = RED.nodes.group(ev.nodes[i].g);
if (group.nodes.indexOf(ev.nodes[i]) === -1) {
group.nodes.push(ev.nodes[i]);
}
RED.group.markDirty(group)
}
} }
} }
if (ev.links) { if (ev.links) {
@ -260,6 +293,13 @@ RED.history = (function() {
RED.nodes.addLink(ev.removedLinks[i]); RED.nodes.addLink(ev.removedLinks[i]);
} }
} }
if (ev.addToGroup) {
RED.group.removeFromGroup(ev.addToGroup,ev.nodes.map(function(n) { return n.n }),true);
inverseEv.removeFromGroup = ev.addToGroup;
} else if (ev.removeFromGroup) {
RED.group.addToGroup(ev.removeFromGroup,ev.nodes.map(function(n) { return n.n }));
inverseEv.addToGroup = ev.removeFromGroup;
}
} else if (ev.t == "edit") { } else if (ev.t == "edit") {
inverseEv = { inverseEv = {
t: "edit", t: "edit",
@ -370,7 +410,9 @@ RED.history = (function() {
if (ev.nodes) { if (ev.nodes) {
inverseEv.movedNodes = []; inverseEv.movedNodes = [];
var z = ev.activeWorkspace; var z = ev.activeWorkspace;
RED.nodes.filterNodes({z:ev.subflow.subflow.id}).forEach(function(n) { var fullNodeList = RED.nodes.filterNodes({z:ev.subflow.subflow.id});
fullNodeList = fullNodeList.concat(RED.nodes.groups(ev.subflow.subflow.id))
fullNodeList.forEach(function(n) {
n.x += ev.subflow.offsetX; n.x += ev.subflow.offsetX;
n.y += ev.subflow.offsetY; n.y += ev.subflow.offsetY;
n.dirty = true; n.dirty = true;
@ -411,6 +453,9 @@ RED.history = (function() {
if (ev.subflow) { if (ev.subflow) {
RED.nodes.addSubflow(ev.subflow.subflow); RED.nodes.addSubflow(ev.subflow.subflow);
inverseEv.subflow = ev.subflow; inverseEv.subflow = ev.subflow;
if (ev.subflow.subflow.g) {
RED.group.addToGroup(RED.nodes.group(ev.subflow.subflow.g),ev.subflow.subflow);
}
} }
if (ev.subflows) { if (ev.subflows) {
inverseEv.nodes = []; inverseEv.nodes = [];
@ -422,6 +467,9 @@ RED.history = (function() {
if (ev.movedNodes) { if (ev.movedNodes) {
ev.movedNodes.forEach(function(nid) { ev.movedNodes.forEach(function(nid) {
nn = RED.nodes.node(nid); nn = RED.nodes.node(nid);
if (!nn) {
nn = RED.nodes.group(nid);
}
nn.x -= ev.subflow.offsetX; nn.x -= ev.subflow.offsetX;
nn.y -= ev.subflow.offsetY; nn.y -= ev.subflow.offsetY;
nn.dirty = true; nn.dirty = true;
@ -450,6 +498,55 @@ RED.history = (function() {
if (ev.order) { if (ev.order) {
RED.workspaces.order(ev.order); RED.workspaces.order(ev.order);
} }
} else if (ev.t == "createGroup") {
inverseEv = {
t: "ungroup",
dirty: RED.nodes.dirty(),
groups: []
}
if (ev.groups) {
for (i=0;i<ev.groups.length;i++) {
inverseEv.groups.push(ev.groups[i]);
RED.group.ungroup(ev.groups[i]);
}
}
} else if (ev.t == "ungroup") {
inverseEv = {
t: "createGroup",
dirty: RED.nodes.dirty(),
groups: []
}
if (ev.groups) {
for (i=0;i<ev.groups.length;i++) {
inverseEv.groups.push(ev.groups[i]);
var nodes = ev.groups[i].nodes.slice();
ev.groups[i].nodes = [];
RED.nodes.addGroup(ev.groups[i]);
RED.group.addToGroup(ev.groups[i],nodes);
}
}
} else if (ev.t == "addToGroup") {
inverseEv = {
t: "removeFromGroup",
dirty: RED.nodes.dirty(),
group: ev.group,
nodes: ev.nodes,
reparent: ev.reparent
}
if (ev.nodes) {
RED.group.removeFromGroup(ev.group,ev.nodes,(ev.hasOwnProperty('reparent')&&ev.hasOwnProperty('reparent')!==undefined)?ev.reparent:true);
}
} else if (ev.t == "removeFromGroup") {
inverseEv = {
t: "addToGroup",
dirty: RED.nodes.dirty(),
group: ev.group,
nodes: ev.nodes,
reparent: ev.reparent
}
if (ev.nodes) {
RED.group.addToGroup(ev.group,ev.nodes);
}
} }
Object.keys(modifiedTabs).forEach(function(id) { Object.keys(modifiedTabs).forEach(function(id) {
@ -460,8 +557,8 @@ RED.history = (function() {
}); });
RED.nodes.dirty(ev.dirty); RED.nodes.dirty(ev.dirty);
RED.view.updateActive();
RED.view.select(null); RED.view.select(null);
RED.view.redraw(true);
RED.palette.refresh(); RED.palette.refresh();
RED.workspaces.refresh(); RED.workspaces.refresh();
RED.sidebar.config.refresh(); RED.sidebar.config.refresh();
@ -482,6 +579,9 @@ RED.history = (function() {
list: function() { list: function() {
return undoHistory; return undoHistory;
}, },
listRedo: function() {
return redoHistory;
},
depth: function() { depth: function() {
return undoHistory.length; return undoHistory.length;
}, },

View File

@ -44,6 +44,14 @@
"ctrl-y": "core:redo", "ctrl-y": "core:redo",
"ctrl-a": "core:select-all-nodes", "ctrl-a": "core:select-all-nodes",
"shift-?": "core:show-help", "shift-?": "core:show-help",
"w": "core:scroll-view-up",
"d": "core:scroll-view-right",
"s": "core:scroll-view-down",
"a": "core:scroll-view-left",
"shift-w": "core:step-view-up",
"shift-d": "core:step-view-right",
"shift-s": "core:step-view-down",
"shift-a": "core:step-view-left",
"up": "core:move-selection-up", "up": "core:move-selection-up",
"right": "core:move-selection-right", "right": "core:move-selection-right",
"down": "core:move-selection-down", "down": "core:move-selection-down",
@ -53,6 +61,10 @@
"shift-down": "core:step-selection-down", "shift-down": "core:step-selection-down",
"shift-left": "core:step-selection-left", "shift-left": "core:step-selection-left",
"ctrl-shift-j": "core:show-previous-tab", "ctrl-shift-j": "core:show-previous-tab",
"ctrl-shift-k": "core:show-next-tab" "ctrl-shift-k": "core:show-next-tab",
"ctrl-shift-g": "core:group-selection",
"ctrl-shift-u": "core:ungroup-selection",
"ctrl-shift-c": "core:copy-group-style",
"ctrl-shift-v": "core:paste-group-style"
} }
} }

View File

@ -27,6 +27,9 @@ RED.nodes = (function() {
var subflows = {}; var subflows = {};
var loadedFlowVersion = null; var loadedFlowVersion = null;
var groups = {};
var groupsByZ = {};
var initialLoad; var initialLoad;
var dirty = false; var dirty = false;
@ -302,6 +305,10 @@ RED.nodes = (function() {
} }
function moveNodeToTab(node, z) { function moveNodeToTab(node, z) {
if (node.type === "group") {
moveGroupToTab(node,z);
return;
}
if (nodeTabMap[node.z]) { if (nodeTabMap[node.z]) {
delete nodeTabMap[node.z][node.id]; delete nodeTabMap[node.z][node.id];
} }
@ -311,6 +318,13 @@ RED.nodes = (function() {
nodeTabMap[z][node.id] = node; nodeTabMap[z][node.id] = node;
node.z = z; node.z = z;
} }
function moveGroupToTab(group, z) {
var index = groupsByZ[group.z].indexOf(group);
groupsByZ[group.z].splice(index,1);
groupsByZ[z] = groupsByZ[z] || [];
groupsByZ[z].push(group);
group.z = z;
}
function removeLink(l) { function removeLink(l) {
var index = links.indexOf(l); var index = links.indexOf(l);
@ -340,6 +354,7 @@ RED.nodes = (function() {
var removedNodes = []; var removedNodes = [];
var removedLinks = []; var removedLinks = [];
var removedGroups = [];
var n; var n;
var node; var node;
for (n=0;n<nodes.length;n++) { for (n=0;n<nodes.length;n++) {
@ -356,11 +371,17 @@ RED.nodes = (function() {
} }
} }
} }
removedGroups = groupsByZ[id] || [];
removedGroups.forEach(function(g) {
delete groups[g.id]
})
delete groupsByZ[id];
for (n=0;n<removedNodes.length;n++) { for (n=0;n<removedNodes.length;n++) {
var result = removeNode(removedNodes[n].id); var result = removeNode(removedNodes[n].id);
removedLinks = removedLinks.concat(result.links); removedLinks = removedLinks.concat(result.links);
} }
return {nodes:removedNodes,links:removedLinks}; return {nodes:removedNodes,links:removedLinks, groups: removedGroups};
} }
function addSubflow(sf, createNewIds) { function addSubflow(sf, createNewIds) {
@ -398,6 +419,10 @@ RED.nodes = (function() {
paletteLabel: function() { return RED.nodes.subflow(sf.id).name }, paletteLabel: function() { return RED.nodes.subflow(sf.id).name },
inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null }, inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null },
outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null }, outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null },
oneditprepare: function() {
RED.subflow.buildEditForm("subflow",this);
RED.subflow.buildPropertiesForm(this);
},
oneditresize: function(size) { oneditresize: function(size) {
// var rows = $(".dialog-form>div:not(.node-input-env-container-row)"); // var rows = $(".dialog-form>div:not(.node-input-env-container-row)");
var height = size.height; var height = size.height;
@ -493,6 +518,9 @@ RED.nodes = (function() {
if (n.d === true) { if (n.d === true) {
node.d = true; node.d = true;
} }
if (n.g) {
node.g = n.g;
}
if (node.type == "unknown") { if (node.type == "unknown") {
for (var p in n._orig) { for (var p in n._orig) {
if (n._orig.hasOwnProperty(p)) { if (n._orig.hasOwnProperty(p)) {
@ -505,9 +533,22 @@ RED.nodes = (function() {
node[d] = n[d]; node[d] = n[d];
} }
} }
if(exportCreds && n.credentials) { if (exportCreds) {
var credentialSet = {}; var credentialSet = {};
if (/^subflow:/.test(node.type) && n.credentials) {
// A subflow instance node can have arbitrary creds
for (var sfCred in n.credentials) {
if (n.credentials.hasOwnProperty(sfCred)) {
if (!n.credentials._ ||
n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] ||
(n.credentials["has_"+sfCred] && n.credentials[sfCred])) {
credentialSet[sfCred] = n.credentials[sfCred];
}
}
}
} else if (n.credentials) {
node.credentials = {}; node.credentials = {};
// All other nodes have a well-defined list of possible credentials
for (var cred in n._def.credentials) { for (var cred in n._def.credentials) {
if (n._def.credentials.hasOwnProperty(cred)) { if (n._def.credentials.hasOwnProperty(cred)) {
if (n._def.credentials[cred].type == 'password') { if (n._def.credentials[cred].type == 'password') {
@ -521,11 +562,19 @@ RED.nodes = (function() {
} }
} }
} }
}
if (Object.keys(credentialSet).length > 0) { if (Object.keys(credentialSet).length > 0) {
node.credentials = credentialSet; node.credentials = credentialSet;
} }
} }
} }
if (n.type === "group") {
node.x = n.x;
node.y = n.y;
node.w = n.w;
node.h = n.h;
node.nodes = node.nodes.map(function(n) { return n.id });
}
if (n._def.category != "config") { if (n._def.category != "config") {
node.x = n.x; node.x = n.x;
node.y = n.y; node.y = n.y;
@ -568,7 +617,7 @@ RED.nodes = (function() {
return node; return node;
} }
function convertSubflow(n) { function convertSubflow(n, exportCreds) {
var node = {}; var node = {};
node.id = n.id; node.id = n.id;
node.type = n.type; node.type = n.type;
@ -578,6 +627,24 @@ RED.nodes = (function() {
node.in = []; node.in = [];
node.out = []; node.out = [];
node.env = n.env; node.env = n.env;
if (exportCreds) {
var credentialSet = {};
// A subflow node can have arbitrary creds
for (var sfCred in n.credentials) {
if (n.credentials.hasOwnProperty(sfCred)) {
if (!n.credentials._ ||
n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] ||
(n.credentials["has_"+sfCred] && n.credentials[sfCred])) {
credentialSet[sfCred] = n.credentials[sfCred];
}
}
}
if (Object.keys(credentialSet).length > 0) {
node.credentials = credentialSet;
}
}
node.color = n.color; node.color = n.color;
n.in.forEach(function(p) { n.in.forEach(function(p) {
@ -633,8 +700,18 @@ RED.nodes = (function() {
/** /**
* Converts the current node selection to an exportable JSON Object * Converts the current node selection to an exportable JSON Object
**/ **/
function createExportableNodeSet(set, exportedSubflows, exportedConfigNodes) { function createExportableNodeSet(set, exportedIds, exportedSubflows, exportedConfigNodes) {
var nns = []; var nns = [];
exportedIds = exportedIds || {};
set = set.filter(function(n) {
if (exportedIds[n.id]) {
return false;
}
exportedIds[n.id] = true;
return true;
})
exportedConfigNodes = exportedConfigNodes || {}; exportedConfigNodes = exportedConfigNodes || {};
exportedSubflows = exportedSubflows || {}; exportedSubflows = exportedSubflows || {};
for (var n=0;n<set.length;n++) { for (var n=0;n<set.length;n++) {
@ -650,11 +727,11 @@ RED.nodes = (function() {
subflowSet.push(n); subflowSet.push(n);
} }
}); });
var exportableSubflow = createExportableNodeSet(subflowSet, exportedSubflows, exportedConfigNodes); var exportableSubflow = createExportableNodeSet(subflowSet, exportedIds, exportedSubflows, exportedConfigNodes);
nns = exportableSubflow.concat(nns); nns = exportableSubflow.concat(nns);
} }
} }
if (node.type != "subflow") { if (node.type !== "subflow") {
var convertedNode = RED.nodes.convertNode(node); var convertedNode = RED.nodes.convertNode(node);
for (var d in node._def.defaults) { for (var d in node._def.defaults) {
if (node._def.defaults[d].type && node[d] in configNodes) { if (node._def.defaults[d].type && node[d] in configNodes) {
@ -671,6 +748,9 @@ RED.nodes = (function() {
} }
} }
nns.push(convertedNode); nns.push(convertedNode);
if (node.type === "group") {
nns = nns.concat(createExportableNodeSet(node.nodes, exportedIds, exportedSubflows, exportedConfigNodes));
}
} else { } else {
var convertedSubflow = convertSubflow(node); var convertedSubflow = convertSubflow(node);
nns.push(convertedSubflow); nns.push(convertedSubflow);
@ -693,7 +773,12 @@ RED.nodes = (function() {
} }
for (i in subflows) { for (i in subflows) {
if (subflows.hasOwnProperty(i)) { if (subflows.hasOwnProperty(i)) {
nns.push(convertSubflow(subflows[i])); nns.push(convertSubflow(subflows[i], exportCredentials));
}
}
for (i in groups) {
if (groups.hasOwnProperty(i)) {
nns.push(convertNode(groups[i]));
} }
} }
for (i in configNodes) { for (i in configNodes) {
@ -822,6 +907,7 @@ RED.nodes = (function() {
if (n.type != "workspace" && if (n.type != "workspace" &&
n.type != "tab" && n.type != "tab" &&
n.type != "subflow" && n.type != "subflow" &&
n.type != "group" &&
!registry.getNodeType(n.type) && !registry.getNodeType(n.type) &&
n.type.substring(0,8) != "subflow:" && n.type.substring(0,8) != "subflow:" &&
unknownTypes.indexOf(n.type)==-1) { unknownTypes.indexOf(n.type)==-1) {
@ -871,6 +957,7 @@ RED.nodes = (function() {
var node_map = {}; var node_map = {};
var new_nodes = []; var new_nodes = [];
var new_links = []; var new_links = [];
var new_groups = [];
var nid; var nid;
var def; var def;
var configNode; var configNode;
@ -1038,20 +1125,25 @@ RED.nodes = (function() {
y:parseFloat(n.y || 0), y:parseFloat(n.y || 0),
z:n.z, z:n.z,
type:0, type:0,
wires:n.wires||[],
inputLabels: n.inputLabels,
outputLabels: n.outputLabels,
icon: n.icon,
info: n.info, info: n.info,
changed:false, changed:false,
_config:{} _config:{}
}; }
if (n.type !== "group") {
node.wires = n.wires||[];
node.inputLabels = n.inputLabels;
node.outputLabels = n.outputLabels;
node.icon = n.icon;
}
if (n.hasOwnProperty('l')) { if (n.hasOwnProperty('l')) {
node.l = n.l; node.l = n.l;
} }
if (n.hasOwnProperty('d')) { if (n.hasOwnProperty('d')) {
node.d = n.d; node.d = n.d;
} }
if (n.hasOwnProperty('g')) {
node.g = n.g;
}
if (createNewIds) { if (createNewIds) {
if (subflow_blacklist[n.z]) { if (subflow_blacklist[n.z]) {
continue; continue;
@ -1088,7 +1180,17 @@ RED.nodes = (function() {
} }
node.type = n.type; node.type = n.type;
node._def = def; node._def = def;
if (n.type.substring(0,7) === "subflow") { if (node.type === "group") {
node._def = RED.group.def;
for (d in node._def.defaults) {
if (node._def.defaults.hasOwnProperty(d) && d !== 'inputs' && d !== 'outputs') {
node[d] = n[d];
node._config[d] = JSON.stringify(n[d]);
}
}
node._config.x = node.x;
node._config.y = node.y;
} else if (n.type.substring(0,7) === "subflow") {
var parentId = n.type.split(":")[1]; var parentId = n.type.split(":")[1];
var subflow = subflow_blacklist[parentId]||subflow_map[parentId]||getSubflow(parentId); var subflow = subflow_blacklist[parentId]||subflow_map[parentId]||getSubflow(parentId);
if (createNewIds) { if (createNewIds) {
@ -1178,13 +1280,19 @@ RED.nodes = (function() {
} }
} }
} }
if (node.type !== "group") {
addNode(node); addNode(node);
RED.editor.validateNode(node); RED.editor.validateNode(node);
} else {
addGroup(node);
}
node_map[n.id] = node; node_map[n.id] = node;
// If an 'unknown' config node, it will not have been caught by the // If an 'unknown' config node, it will not have been caught by the
// proper config node handling, so needs adding to new_nodes here // proper config node handling, so needs adding to new_nodes here
if (node.type === "unknown" || node._def.category !== "config") { if (node.type === "unknown" || node._def.category !== "config") {
new_nodes.push(node); new_nodes.push(node);
} else if (node.type === "group") {
new_groups.push(node);
} }
} }
} }
@ -1219,6 +1327,11 @@ RED.nodes = (function() {
} }
delete n.wires; delete n.wires;
} }
if (n.g && node_map[n.g]) {
n.g = node_map[n.g].id;
} else {
delete n.g
}
for (var d3 in n._def.defaults) { for (var d3 in n._def.defaults) {
if (n._def.defaults.hasOwnProperty(d3)) { if (n._def.defaults.hasOwnProperty(d3)) {
if (n._def.defaults[d3].type && node_map[n[d3]]) { if (n._def.defaults[d3].type && node_map[n[d3]]) {
@ -1287,9 +1400,20 @@ RED.nodes = (function() {
delete n.status.wires; delete n.status.wires;
} }
} }
for (i=0;i<new_groups.length;i++) {
n = new_groups[i];
if (n.g && node_map[n.g]) {
n.g = node_map[n.g].id;
} else {
delete n.g;
}
n.nodes = n.nodes.map(function(id) {
return node_map[id];
})
}
RED.workspaces.refresh(); RED.workspaces.refresh();
return [new_nodes,new_links,new_workspaces,new_subflows,missingWorkspace]; return [new_nodes,new_links,new_groups,new_workspaces,new_subflows,missingWorkspace];
} }
// TODO: supports filter.z|type // TODO: supports filter.z|type
@ -1380,6 +1504,9 @@ RED.nodes = (function() {
nodeTabMap = {}; nodeTabMap = {};
configNodes = {}; configNodes = {};
workspacesOrder = []; workspacesOrder = [];
groups = {};
groupsByZ = {};
var subflowIds = Object.keys(subflows); var subflowIds = Object.keys(subflows);
subflowIds.forEach(function(id) { subflowIds.forEach(function(id) {
RED.subflow.removeSubflow(id) RED.subflow.removeSubflow(id)
@ -1408,6 +1535,27 @@ RED.nodes = (function() {
// var loadedFlowVersion = null; // var loadedFlowVersion = null;
} }
function addGroup(group) {
groupsByZ[group.z] = groupsByZ[group.z] || [];
groupsByZ[group.z].push(group);
groups[group.id] = group;
}
function removeGroup(group) {
var i = groupsByZ[group.z].indexOf(group);
groupsByZ[group.z].splice(i,1);
if (group.g) {
if (groups[group.g]) {
var index = groups[group.g].nodes.indexOf(group);
groups[group.g].nodes.splice(index,1);
}
}
RED.group.markDirty(group);
delete groups[group.id];
}
return { return {
init: function() { init: function() {
RED.events.on("registry:node-type-added",function(type) { RED.events.on("registry:node-type-added",function(type) {
@ -1434,6 +1582,9 @@ RED.nodes = (function() {
delete configNodes[n.id]; delete configNodes[n.id];
} else { } else {
nodes.splice(nodes.indexOf(n),1); nodes.splice(nodes.indexOf(n),1);
if (nodeTabMap[n.z]) {
delete nodeTabMap[n.z][n.id];
}
} }
reimportList.push(convertNode(n)); reimportList.push(convertNode(n));
}); });
@ -1448,8 +1599,9 @@ RED.nodes = (function() {
}); });
removeLinks.forEach(removeLink); removeLinks.forEach(removeLink);
// Force the redraw to be synchronous so the view updates
RED.view.redraw(true); // *now* and removes the unknown node
RED.view.redraw(true, true);
var result = importNodes(reimportList,false); var result = importNodes(reimportList,false);
var newNodeMap = {}; var newNodeMap = {};
result[0].forEach(function(n) { result[0].forEach(function(n) {
@ -1503,6 +1655,11 @@ RED.nodes = (function() {
subflow: getSubflow, subflow: getSubflow,
subflowContains: subflowContains, subflowContains: subflowContains,
addGroup: addGroup,
removeGroup: removeGroup,
group: function(id) { return groups[id] },
groups: function(z) { return groupsByZ[z]||[] },
eachNode: function(cb) { eachNode: function(cb) {
for (var n=0;n<nodes.length;n++) { for (var n=0;n<nodes.length;n++) {
if (cb(nodes[n]) === false) { if (cb(nodes[n]) === false) {

View File

@ -418,8 +418,6 @@ var RED = (function() {
RED.notify(RED._("palette.event.nodeUpgraded", {module:msg.module,version:msg.version}),"success"); RED.notify(RED._("palette.event.nodeUpgraded", {module:msg.module,version:msg.version}),"success");
RED.nodes.registry.setModulePendingUpdated(msg.module,msg.version); RED.nodes.registry.setModulePendingUpdated(msg.module,msg.version);
} }
// Refresh flow library to ensure any examples are updated
RED.library.loadFlowLibrary();
}); });
RED.comms.subscribe("event-log/#", function(topic,payload) { RED.comms.subscribe("event-log/#", function(topic,payload) {
var id = topic.substring(9); var id = topic.substring(9);
@ -433,7 +431,7 @@ var RED = (function() {
'<img width="50px" src="red/images/node-red-icon.svg" />'+ '<img width="50px" src="red/images/node-red-icon.svg" />'+
'</div>'; '</div>';
RED.sidebar.info.set(aboutHeader+marked(data)); RED.sidebar.info.set(aboutHeader+RED.utils.renderMarkdown(data));
RED.sidebar.info.show(); RED.sidebar.info.show();
}); });
} }
@ -474,6 +472,14 @@ var RED = (function() {
{id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"}, {id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"},
{id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"}, {id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"},
]}); ]});
menuOptions.push({id:"menu-item-group",label:RED._("menu.label.groups"), options: [
{id:"menu-item-group-group",label:RED._("menu.label.groupSelection"),disabled:true,onselect:"core:group-selection"},
{id:"menu-item-group-ungroup",label:RED._("menu.label.ungroupSelection"),disabled:true,onselect:"core:ungroup-selection"},
null,
{id:"menu-item-group-merge",label:RED._("menu.label.groupMergeSelection"),disabled:true,onselect:"core:merge-selection-to-group"},
{id:"menu-item-group-remove",label:RED._("menu.label.groupRemoveSelection"),disabled:true,onselect:"core:remove-selection-from-group"}
]});
menuOptions.push(null); menuOptions.push(null);
if (RED.settings.theme('palette.editable') !== false) { if (RED.settings.theme('palette.editable') !== false) {
menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"}); menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"});
@ -526,6 +532,7 @@ var RED = (function() {
} }
RED.subflow.init(); RED.subflow.init();
RED.group.init();
RED.clipboard.init(); RED.clipboard.init();
RED.search.init(); RED.search.init();
RED.actionList.init(); RED.actionList.init();

View File

@ -56,8 +56,9 @@ RED.settings = (function () {
if (key === "auth-tokens") { if (key === "auth-tokens") {
return JSON.parse(localStorage.getItem(key)); return JSON.parse(localStorage.getItem(key));
} else { } else {
var v;
try { try {
var v = RED.utils.getMessageProperty(userSettings,key); v = RED.utils.getMessageProperty(userSettings,key);
if (v === undefined) { if (v === undefined) {
v = defaultIfUndefined; v = defaultIfUndefined;
} }

View File

@ -583,6 +583,7 @@ RED.clipboard = (function() {
nodes = []; nodes = [];
selection.forEach(function(n) { selection.forEach(function(n) {
nodes.push(n); nodes.push(n);
nodes = nodes.concat(RED.nodes.groups(n.id));
nodes = nodes.concat(RED.nodes.filterNodes({z:n.id})); nodes = nodes.concat(RED.nodes.filterNodes({z:n.id}));
}); });
} else { } else {
@ -592,7 +593,8 @@ RED.clipboard = (function() {
nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'})); nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'}));
} else if (type === 'red-ui-clipboard-dialog-export-rng-flow') { } else if (type === 'red-ui-clipboard-dialog-export-rng-flow') {
var activeWorkspace = RED.workspaces.active(); var activeWorkspace = RED.workspaces.active();
nodes = RED.nodes.filterNodes({z:activeWorkspace}); nodes = RED.nodes.groups(activeWorkspace);
nodes = nodes.concat(RED.nodes.filterNodes({z:activeWorkspace}));
var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace); var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace);
nodes.unshift(parentNode); nodes.unshift(parentNode);
nodes = RED.nodes.createExportableNodeSet(nodes); nodes = RED.nodes.createExportableNodeSet(nodes);

View File

@ -0,0 +1,223 @@
RED.colorPicker = (function() {
function getDarkerColor(c) {
var r,g,b;
if (/^#[a-f0-9]{6}$/i.test(c)) {
r = parseInt(c.substring(1, 3), 16);
g = parseInt(c.substring(3, 5), 16);
b = parseInt(c.substring(5, 7), 16);
} else if (/^#[a-f0-9]{3}$/i.test(c)) {
r = parseInt(c.substring(1, 2)+c.substring(1, 2), 16);
g = parseInt(c.substring(2, 3)+c.substring(2, 3), 16);
b = parseInt(c.substring(3, 4)+c.substring(3, 4), 16);
} else {
return c;
}
var l = 0.3 * r/255 + 0.59 * g/255 + 0.11 * b/255 ;
r = Math.max(0,r-50);
g = Math.max(0,g-50);
b = Math.max(0,b-50);
return '#'+((r<<16) + (g<<8) + b).toString(16).padStart(6,'0')
}
function create(options) {
var color = options.value;
var id = options.id;
var colorPalette = options.palette || [];
var width = options.cellWidth || 30;
var height = options.cellHeight || 30;
var margin = options.cellMargin || 2;
var perRow = options.cellPerRow || 6;
var container = $("<div>",{style:"display:inline-block"});
var colorHiddenInput = $("<input/>", { id: id, type: "hidden", value: color }).appendTo(container);
var opacityHiddenInput = $("<input/>", { id: id+"-opacity", type: "hidden", value: options.hasOwnProperty('opacity')?options.opacity:"1" }).appendTo(container);
var colorButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(container);
$('<i class="fa fa-caret-down"></i>').appendTo(colorButton);
var colorDispContainer = $('<div>',{class:"red-ui-search-result-node"}).appendTo(colorButton);
$('<div>',{class:"red-ui-color-picker-cell-none"}).appendTo(colorDispContainer);
var colorDisp = $('<div>',{class:"red-ui-color-picker-swatch"}).appendTo(colorDispContainer);
var refreshDisplay = function(color) {
if (color === "none") {
colorDisp.addClass('red-ui-color-picker-cell-none').css({
"background-color": "",
opacity: 1
});
colorDispContainer.css({
"border-color":""
})
} else {
var opacity = parseFloat(opacityHiddenInput.val())
colorDisp.removeClass('red-ui-color-picker-cell-none').css({
"background-color": color,
"opacity": opacity
});
var border = getDarkerColor(color);
if (border[0] === '#') {
border += Math.round(255*Math.floor(opacity*100)/100).toString(16);
} else {
border = "";
}
colorDispContainer.css({
"border-color": border
})
}
if (options.hasOwnProperty('opacity')) {
$(".red-ui-color-picker-opacity-slider-overlay").css({
"background-image": "linear-gradient(90deg, transparent 0%, "+color+" 100%)"
})
}
}
colorButton.on("click", function (e) {
var numColors = colorPalette.length;
var picker = $("<div/>", {
class: "red-ui-color-picker"
}).css({
width: ((width+margin+margin)*perRow)+"px",
height: Math.ceil(numColors/perRow)*(height+margin+margin)+"+px"
});
var count = 0;
var row = null;
row = $("<div/>").appendTo(picker);
var colorInput = $('<input>',{
type:"text",
value:colorHiddenInput.val()
}).appendTo(row);
colorInput.on("change", function (e) {
var color = colorInput.val();
colorHiddenInput.val(color).trigger('change');
refreshDisplay(color);
});
// if (options.hasOwnProperty('opacity')) {
// var sliderContainer = $("<div>",{class:"red-ui-color-picker-opacity-slider"
// }
if (options.none) {
row = $("<div/>").appendTo(picker);
var button = $("<button/>", {
class:"red-ui-color-picker-cell red-ui-color-picker-cell-none"
}).css({
width: width+"px",
height: height+"px",
margin: margin+"px"
}).appendTo(row);
button.on("click", function (e) {
e.preventDefault();
colorInput.val("none");
colorInput.trigger("change");
});
}
colorPalette.forEach(function (col) {
if ((count % perRow) == 0) {
row = $("<div/>").appendTo(picker);
}
var button = $("<button/>", {
class:"red-ui-color-picker-cell"
}).css({
width: width+"px",
height: height+"px",
margin: margin+"px",
backgroundColor: col,
"border-color": getDarkerColor(col)
}).appendTo(row);
button.on("click", function (e) {
e.preventDefault();
// colorPanel.hide();
colorInput.val(col);
colorInput.trigger("change");
});
count++;
});
if (options.none || options.hasOwnProperty('opacity')) {
row = $("<div/>").appendTo(picker);
// if (options.none) {
// var button = $("<button/>", {
// class:"red-ui-color-picker-cell red-ui-color-picker-cell-none"
// }).css({
// width: width+"px",
// height: height+"px",
// margin: margin+"px"
// }).appendTo(row);
// button.on("click", function (e) {
// e.preventDefault();
// colorPanel.hide();
// selector.val("none");
// selector.trigger("change");
// });
// }
if (options.hasOwnProperty('opacity')) {
var sliderContainer = $("<div>",{class:"red-ui-color-picker-opacity-slider"}).appendTo(row);
sliderContainer.on("mousedown", function(evt) {
if (evt.target === sliderHandle[0]) {
return;
}
var v = evt.offsetX/sliderContainer.width();
sliderHandle.css({
left: ( v*(sliderContainer.width() - sliderHandle.outerWidth()))+"px"
});
v = Math.floor(100*v)
opacityHiddenInput.val(v/100)
opacityLabel.text(v+"%");
refreshDisplay(colorHiddenInput.val());
})
$("<div>",{class:"red-ui-color-picker-opacity-slider-overlay"}).appendTo(sliderContainer);
var sliderHandle = $("<div>",{class:"red-ui-color-picker-opacity-slider-handle red-ui-button red-ui-button-small"}).appendTo(sliderContainer).draggable({
containment: "parent",
axis: "x",
drag: function( event, ui ) {
var v = Math.max(0,ui.position.left/($(this).parent().width()-$(this).outerWidth()));
// Odd bug that if it is loaded with a non-0 value, the first time
// it is dragged it ranges -1 to 99. But every other time, its 0 to 100.
// The Math.max above makes the -1 disappear. The follow hack ensures
// it always maxes out at a 100, at the cost of not allowing 99% exactly.
v = Math.floor(100*v)
if ( v === 99 ) {
v = 100;
}
// console.log("uip",ui.position.left);
opacityHiddenInput.val(v/100)
opacityLabel.text(v+"%");
refreshDisplay(colorHiddenInput.val());
}
});
var opacityLabel = $('<small></small>').appendTo(row);
setTimeout(function() {
sliderHandle.css({
left: (parseFloat(opacityHiddenInput.val())*(sliderContainer.width() - sliderHandle.outerWidth()))+"px"
})
opacityLabel.text(Math.floor(opacityHiddenInput.val()*100)+"%");
},50);
}
}
var colorPanel = RED.popover.panel(picker);
setTimeout(function() {
refreshDisplay(colorHiddenInput.val())
},50);
colorPanel.show({
target: colorButton
})
});
setTimeout(function() {
refreshDisplay(colorHiddenInput.val())
},50);
return container;
}
return {
create: create
}
})();

View File

@ -38,7 +38,10 @@
this.element.addClass("red-ui-searchBox-input"); this.element.addClass("red-ui-searchBox-input");
this.uiContainer = this.element.wrap("<div>").parent(); this.uiContainer = this.element.wrap("<div>").parent();
this.uiContainer.addClass("red-ui-searchBox-container"); this.uiContainer.addClass("red-ui-searchBox-container");
if (this.element.parents("form").length === 0) {
var form = this.element.wrap("<form>").parent();
form.addClass("red-ui-searchBox-form");
}
$('<i class="fa fa-search"></i>').prependTo(this.uiContainer); $('<i class="fa fa-search"></i>').prependTo(this.uiContainer);
this.clearButton = $('<a href="#"><i class="fa fa-times"></i></a>').appendTo(this.uiContainer); this.clearButton = $('<a href="#"><i class="fa fa-times"></i></a>').appendTo(this.uiContainer);
this.clearButton.on("click",function(e) { this.clearButton.on("click",function(e) {

View File

@ -410,7 +410,7 @@
return; return;
} }
if (container.hasClass("expanded")) { if (container.hasClass("expanded")) {
done && done(); if (done) { done() }
return; return;
} }
if (!container.hasClass("built") && (item.deferBuild || typeof item.children === 'function')) { if (!container.hasClass("built") && (item.deferBuild || typeof item.children === 'function')) {
@ -435,7 +435,7 @@
spinner.remove(); spinner.remove();
} }
} }
done && done(); if (done) { done() }
that._trigger("childrenloaded",null,item) that._trigger("childrenloaded",null,item)
} }
if (typeof item.children === 'function') { if (typeof item.children === 'function') {
@ -457,7 +457,7 @@
} else { } else {
item.treeList.childList.slideDown('fast'); item.treeList.childList.slideDown('fast');
} }
done && done(); if (done) { done() }
} }
container.addClass("expanded"); container.addClass("expanded");
} }

View File

@ -164,6 +164,84 @@
} }
}) })
} }
},
cred:{
value:"cred",
label:"credential",
icon:"fa fa-lock",
inputType: "password",
valueLabel: function(container,value) {
var that = this;
container.css("pointer-events","none");
this.elementDiv.hide();
var buttons = $('<div>').css({
position: "absolute",
right:"6px",
top: "6px",
"pointer-events":"all"
}).appendTo(container);
var eyeButton = $('<button type="button" class="red-ui-button red-ui-button-small"></button>').css({
width:"20px"
}).appendTo(buttons).on("click", function(evt) {
evt.preventDefault();
var currentType = that.input.attr("type");
if (currentType === "text") {
that.input.attr("type","password");
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
setTimeout(function() {
that.input.focus();
},50);
} else {
that.input.attr("type","text");
eyeCon.removeClass("fa-eye").addClass("fa-eye-slash");
setTimeout(function() {
that.input.focus();
},50);
}
}).hide();
var eyeCon = $('<i class="fa fa-eye"></i>').css("margin-left","-1px").appendTo(eyeButton);
if (value === "__PWRD__") {
var innerContainer = $('<div><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i><i class="fa fa-asterisk"></i></div>').css({
padding:"6px 6px",
borderRadius:"4px"
}).addClass("red-ui-typedInput-value-label-inactive").appendTo(container);
var editButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-pencil"></i></button>').appendTo(buttons).on("click", function(evt) {
evt.preventDefault();
innerContainer.hide();
container.css("background","none");
container.css("pointer-events","none");
that.input.val("");
that.element.val("");
that.elementDiv.show();
editButton.hide();
cancelButton.show();
eyeButton.show();
setTimeout(function() {
that.input.focus();
},50);
});
var cancelButton = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-times"></i></button>').css("margin-left","3px").appendTo(buttons).on("click", function(evt) {
evt.preventDefault();
innerContainer.show();
container.css("background","");
that.input.val("__PWRD__");
that.element.val("__PWRD__");
that.elementDiv.hide();
editButton.show();
cancelButton.hide();
eyeButton.hide();
that.input.attr("type","password");
eyeCon.removeClass("fa-eye-slash").addClass("fa-eye");
}).hide();
} else {
container.css("background","none");
container.css("pointer-events","none");
this.elementDiv.show();
eyeButton.show();
}
}
} }
}; };
var nlsd = false; var nlsd = false;
@ -220,6 +298,8 @@
that.input.attr(d,m); that.input.attr(d,m);
}); });
this.defaultInputType = this.input.attr('type');
this.uiSelect.addClass("red-ui-typedInput-container"); this.uiSelect.addClass("red-ui-typedInput-container");
this.element.attr('type','hidden'); this.element.attr('type','hidden');
@ -687,7 +767,7 @@
$('<img>',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel); $('<img>',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel);
} }
else { else {
$('<i>',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel); $('<i>',{class:"red-ui-typedInput-icon "+opt.icon,style:"min-width: 13px; margin-right: 4px;"}).prependTo(this.selectLabel);
} }
} }
if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) { if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) {
@ -822,6 +902,11 @@
if (this.optionSelectTrigger) { if (this.optionSelectTrigger) {
this.optionSelectTrigger.hide(); this.optionSelectTrigger.hide();
} }
if (opt.inputType) {
this.input.attr('type',opt.inputType)
} else {
this.input.attr('type',this.defaultInputType)
}
if (opt.hasValue === false) { if (opt.hasValue === false) {
this.oldValue = this.input.val(); this.oldValue = this.input.val();
this.input.val(""); this.input.val("");
@ -830,8 +915,8 @@
} else if (opt.valueLabel) { } else if (opt.valueLabel) {
this.valueLabelContainer.show(); this.valueLabelContainer.show();
this.valueLabelContainer.empty(); this.valueLabelContainer.empty();
opt.valueLabel.call(this,this.valueLabelContainer,this.input.val());
this.elementDiv.hide(); this.elementDiv.hide();
opt.valueLabel.call(this,this.valueLabelContainer,this.input.val());
} else { } else {
if (this.oldValue !== undefined) { if (this.oldValue !== undefined) {
this.input.val(this.oldValue); this.input.val(this.oldValue);

View File

@ -334,8 +334,7 @@ RED.deploy = (function() {
var invalidNodes = []; var invalidNodes = [];
RED.nodes.eachNode(function(node) { RED.nodes.eachNode(function(node) {
hasInvalid = hasInvalid || !node.valid; if (!node.valid && !node.d) {
if (!node.valid) {
invalidNodes.push(getNodeInfo(node)); invalidNodes.push(getNodeInfo(node));
} }
if (node.type === "unknown") { if (node.type === "unknown") {
@ -345,6 +344,7 @@ RED.deploy = (function() {
} }
}); });
hasUnknown = unknownNodes.length > 0; hasUnknown = unknownNodes.length > 0;
hasInvalid = invalidNodes.length > 0;
var unusedConfigNodes = []; var unusedConfigNodes = [];
RED.nodes.eachConfig(function(node) { RED.nodes.eachConfig(function(node) {

View File

@ -1029,9 +1029,9 @@ RED.diff = (function() {
} }
var localSelectDiv = $('<label>',{class:"red-ui-diff-selectbox",for:safeNodeId+"-local"}).on("click", function(e) { e.stopPropagation();}).appendTo(localDiv); var localSelectDiv = $('<label>',{class:"red-ui-diff-selectbox",for:safeNodeId+"-local"}).on("click", function(e) { e.stopPropagation();}).appendTo(localDiv);
var localRadio = $('<input>',{class:"red-ui-diff-selectbox-input",id:safeNodeId+"-local",type:'radio',value:"local",name:safeNodeId,class:className+"-local"}).data('node-id',node.id).on("change", changeHandler).appendTo(localSelectDiv); var localRadio = $('<input>',{class:"red-ui-diff-selectbox-input "+className+"-local",id:safeNodeId+"-local",type:'radio',value:"local",name:safeNodeId}).data('node-id',node.id).on("change", changeHandler).appendTo(localSelectDiv);
var remoteSelectDiv = $('<label>',{class:"red-ui-diff-selectbox",for:safeNodeId+"-remote"}).on("click", function(e) { e.stopPropagation();}).appendTo(remoteDiv); var remoteSelectDiv = $('<label>',{class:"red-ui-diff-selectbox",for:safeNodeId+"-remote"}).on("click", function(e) { e.stopPropagation();}).appendTo(remoteDiv);
var remoteRadio = $('<input>',{class:"red-ui-diff-selectbox-input",id:safeNodeId+"-remote",type:'radio',value:"remote",name:safeNodeId,class:className+"-remote"}).data('node-id',node.id).on("change", changeHandler).appendTo(remoteSelectDiv); var remoteRadio = $('<input>',{class:"red-ui-diff-selectbox-input "+className+"-remote",id:safeNodeId+"-remote",type:'radio',value:"remote",name:safeNodeId}).data('node-id',node.id).on("change", changeHandler).appendTo(remoteSelectDiv);
if (state === 'local') { if (state === 'local') {
localRadio.prop('checked',true); localRadio.prop('checked',true);
} else if (state === 'remote') { } else if (state === 'remote') {

View File

@ -490,8 +490,7 @@ RED.editor = (function() {
done(); done();
} }
} }
if (definition.credentials || /^subflow:/.test(definition.type)) {
if (definition.credentials) {
if (node.credentials) { if (node.credentials) {
populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); populateCredentialsInputs(node, definition.credentials, node.credentials, prefix);
completePrepare(); completePrepare();
@ -499,7 +498,9 @@ RED.editor = (function() {
$.getJSON(getCredentialsURL(node.type, node.id), function (data) { $.getJSON(getCredentialsURL(node.type, node.id), function (data) {
node.credentials = data; node.credentials = data;
node.credentials._ = $.extend(true,{},data); node.credentials._ = $.extend(true,{},data);
if (!/^subflow:/.test(definition.type)) {
populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); populateCredentialsInputs(node, definition.credentials, node.credentials, prefix);
}
completePrepare(); completePrepare();
}); });
} }
@ -513,7 +514,9 @@ RED.editor = (function() {
for (var i=editStack.length-1;i<editStack.length;i++) { for (var i=editStack.length-1;i<editStack.length;i++) {
var node = editStack[i]; var node = editStack[i];
label = node.type; label = node.type;
if (node.type === '_expression') { if (node.type === 'group') {
label = RED._("group.editGroup",{name:RED.utils.sanitize(node.name||node.id)});
} else if (node.type === '_expression') {
label = RED._("expressionEditor.title"); label = RED._("expressionEditor.title");
} else if (node.type === '_js') { } else if (node.type === '_js') {
label = RED._("jsEditor.title"); label = RED._("jsEditor.title");
@ -576,17 +579,21 @@ RED.editor = (function() {
$(this).attr("data-i18n",keys.join(";")); $(this).attr("data-i18n",keys.join(";"));
}); });
if (type === "subflow-template" || type === "subflow") { if (type === "subflow-template") {
RED.subflow.buildEditForm(dialogForm,type,node); // This is the 'edit properties' dialog for a subflow template
// TODO: this needs to happen later in the dialog open sequence
// so that credentials can be loaded prior to building the form
RED.subflow.buildEditForm(type,node);
} }
// Add dummy fields to prevent 'Enter' submitting the form in some // Add dummy fields to prevent 'Enter' submitting the form in some
// cases, and also prevent browser auto-fill of password // cases, and also prevent browser auto-fill of password
// Add in reverse order as they are prepended... // - the elements cannot be hidden otherwise Chrome will ignore them.
$('<input type="password" style="display: none;" />').prependTo(dialogForm); // - the elements need to have id's that imply password/username
$('<input type="text" style="display: none;" />').prependTo(dialogForm); $('<span style="position: absolute; top: -2000px;"><input id="red-ui-trap-password" type="password"/></span>').prependTo(dialogForm);
$('<span style="position: absolute; top: -2000px;"><input id="red-ui-trap-username" type="text"/></span>').prependTo(dialogForm);
dialogForm.on("submit", function(e) { e.preventDefault();}); dialogForm.on("submit", function(e) { e.preventDefault();});
dialogForm.find('input').attr("autocomplete","disable"); dialogForm.find('input').attr("autocomplete","off");
return dialogForm; return dialogForm;
} }
@ -818,99 +825,6 @@ RED.editor = (function() {
searchInput.trigger("focus"); searchInput.trigger("focus");
} }
function createColorPicker(colorRow, color) {
var colorButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(colorRow);
$('<i class="fa fa-caret-down"></i>').appendTo(colorButton);
var colorDisp = $('<div>',{class:"red-ui-search-result-node"}).appendTo(colorButton);
var selector = $("<input/>", {
id: "red-ui-editor-node-color",
type: "text",
value: color
}).css({
marginLeft: "10px",
width: "150px",
}).appendTo(colorRow);
selector.on("change", function (e) {
var color = selector.val();
$(".red-ui-editor-node-appearance-button .red-ui-search-result-node").css({
"background-color": color
});
});
selector.trigger("change");
colorButton.on("click", function (e) {
var recommendedColors = [
"#DDAA99",
"#3FADB5", "#87A980", "#A6BBCF",
"#AAAA66", "#C0C0C0", "#C0DEED",
"#C7E9C0", "#D7D7A0", "#D8BFD8",
"#DAC4B4", "#DEB887", "#DEBD5C",
"#E2D96E", "#E6E0F8", "#E7E7AE",
"#E9967A", "#F3B567", "#FDD0A2",
"#FDF0C2", "#FFAAAA", "#FFCC66",
"#FFF0F0", "#FFFFFF"
].map(function(c) {
var r = parseInt(c.substring(1, 3), 16) / 255;
var g = parseInt(c.substring(3, 5), 16) / 255;
var b = parseInt(c.substring(5, 7), 16) / 255;
return {
hex: c,
r: r,
g: g,
b: b,
l: 0.3 * r + 0.59 * g + 0.11 * b
}
});
// Sort by luminosity.
recommendedColors.sort(function (a, b) {
return a.l - b.l;
});
var numColors = recommendedColors.length;
var width = 30;
var height = 30;
var margin = 2;
var perRow = 6;
var picker = $("<div/>", {
class: "red-ui-color-picker"
}).css({
width: ((width+margin+margin)*perRow)+"px",
height: Math.ceil(numColors/perRow)*(height+margin+margin)+"+px"
});
var count = 0;
var row = null;
recommendedColors.forEach(function (col) {
if ((count % perRow) == 0) {
row = $("<div/>").appendTo(picker);
}
var button = $("<button/>", {
}).css({
width: width+"px",
height: height+"px",
margin: margin+"px",
backgroundColor: col.hex,
"border-style": "solid",
"border-width": "1px",
"border-color": col.luma<0.92?col.hex:'#ccc'
}).appendTo(row);
button.on("click", function (e) {
e.preventDefault();
colorPanel.hide();
selector.val(col.hex);
selector.trigger("change");
});
count++;
});
var colorPanel = RED.popover.panel(picker);
colorPanel.show({
target: colorButton
})
});
}
function buildAppearanceForm(container,node) { function buildAppearanceForm(container,node) {
var dialogForm = $('<form class="dialog-form form-horizontal" autocomplete="off"></form>').appendTo(container); var dialogForm = $('<form class="dialog-form form-horizontal" autocomplete="off"></form>').appendTo(container);
@ -996,7 +910,30 @@ RED.editor = (function() {
class: "form-row" class: "form-row"
}).appendTo(dialogForm); }).appendTo(dialogForm);
$("<label/>").text(RED._("editor.color")).appendTo(colorRow); $("<label/>").text(RED._("editor.color")).appendTo(colorRow);
createColorPicker(colorRow, color);
var recommendedColors = [
"#DDAA99",
"#3FADB5", "#87A980", "#A6BBCF",
"#AAAA66", "#C0C0C0", "#C0DEED",
"#C7E9C0", "#D7D7A0", "#D8BFD8",
"#DAC4B4", "#DEB887", "#DEBD5C",
"#E2D96E", "#E6E0F8", "#E7E7AE",
"#E9967A", "#F3B567", "#FDD0A2",
"#FDF0C2", "#FFAAAA", "#FFCC66",
"#FFF0F0", "#FFFFFF"
]
RED.colorPicker.create({
id: "red-ui-editor-node-color",
value: color,
palette: recommendedColors,
sortPalette: function (a, b) {return a.l - b.l;}
}).appendTo(colorRow);
$("#red-ui-editor-node-color").on('change', function(ev) {
// Horribly out of scope...
nodeDiv.css('backgroundColor',$(this).val());
})
} }
@ -1320,11 +1257,13 @@ RED.editor = (function() {
newValue = parseInt(newValue); newValue = parseInt(newValue);
} }
} }
if (editing_node[d] != newValue) {
if (editing_node._def.defaults[d].type) { if (editing_node._def.defaults[d].type) {
if (newValue == "_ADD_") { if (newValue == "_ADD_") {
newValue = ""; newValue = "";
} }
}
if (editing_node[d] != newValue) {
if (editing_node._def.defaults[d].type) {
// Change to a related config node // Change to a related config node
var configNode = RED.nodes.node(editing_node[d]); var configNode = RED.nodes.node(editing_node[d]);
if (configNode) { if (configNode) {
@ -1468,6 +1407,19 @@ RED.editor = (function() {
if (type === "subflow") { if (type === "subflow") {
var old_env = editing_node.env; var old_env = editing_node.env;
var new_env = RED.subflow.exportSubflowInstanceEnv(editing_node); var new_env = RED.subflow.exportSubflowInstanceEnv(editing_node);
if (new_env && new_env.length > 0) {
new_env.forEach(function(prop) {
if (prop.type === "cred") {
editing_node.credentials = editing_node.credentials || {_:{}};
editing_node.credentials[prop.name] = prop.value;
editing_node.credentials['has_'+prop.name] = (prop.value !== "");
if (prop.value !== '__PWRD__') {
changed = true;
}
delete prop.value;
}
});
}
if (!isSameObj(old_env, new_env)) { if (!isSameObj(old_env, new_env)) {
editing_node.env = new_env; editing_node.env = new_env;
changes.env = editing_node.env; changes.env = editing_node.env;
@ -1596,12 +1548,13 @@ RED.editor = (function() {
id: "editor-subflow-envProperties", id: "editor-subflow-envProperties",
label: RED._("editor-tab.envProperties"), label: RED._("editor-tab.envProperties"),
name: RED._("editor-tab.envProperties"), name: RED._("editor-tab.envProperties"),
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), content: $('<div>', {id:"editor-subflow-envProperties-content",class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-list" iconClass: "fa fa-list"
}; };
RED.subflow.buildPropertiesForm(subflowPropertiesTab.content,node);
editorTabs.addTab(subflowPropertiesTab); editorTabs.addTab(subflowPropertiesTab);
// This tab is populated by the oneditprepare function of this
// subflow. That ensures it is done *after* any credentials
// have been loaded for the instance.
} }
if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) { if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) {
@ -2249,6 +2202,21 @@ RED.editor = (function() {
var old_env = editing_node.env; var old_env = editing_node.env;
var new_env = RED.subflow.exportSubflowTemplateEnv($("#node-input-env-container").editableList("items")); var new_env = RED.subflow.exportSubflowTemplateEnv($("#node-input-env-container").editableList("items"));
if (new_env && new_env.length > 0) {
new_env.forEach(function(prop) {
if (prop.type === "cred") {
editing_node.credentials = editing_node.credentials || {_:{}};
editing_node.credentials[prop.name] = prop.value;
editing_node.credentials['has_'+prop.name] = (prop.value !== "");
if (prop.value !== '__PWRD__') {
changed = true;
}
delete prop.value;
}
});
}
if (!isSameObj(old_env, new_env)) { if (!isSameObj(old_env, new_env)) {
editing_node.env = new_env; editing_node.env = new_env;
changes.env = editing_node.env; changes.env = editing_node.env;
@ -2308,7 +2276,7 @@ RED.editor = (function() {
$("#node-input-env-container").editableList('height',height-95); $("#node-input-env-container").editableList('height',height-95);
} }
}, },
open: function(tray) { open: function(tray, done) {
var trayFooter = tray.find(".red-ui-tray-footer"); var trayFooter = tray.find(".red-ui-tray-footer");
var trayFooterLeft = $("<div/>", { var trayFooterLeft = $("<div/>", {
class: "red-ui-tray-footer-left" class: "red-ui-tray-footer-left"
@ -2359,7 +2327,6 @@ RED.editor = (function() {
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-cog" iconClass: "fa fa-cog"
}; };
buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node);
editorTabs.addTab(nodePropertiesTab); editorTabs.addTab(nodePropertiesTab);
var descriptionTab = { var descriptionTab = {
@ -2388,11 +2355,19 @@ RED.editor = (function() {
buildAppearanceForm(appearanceTab.content,editing_node); buildAppearanceForm(appearanceTab.content,editing_node);
editorTabs.addTab(appearanceTab); editorTabs.addTab(appearanceTab);
buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node);
trayBody.i18n();
$.getJSON(getCredentialsURL("subflow", subflow.id), function (data) {
subflow.credentials = data;
subflow.credentials._ = $.extend(true,{},data);
$("#subflow-input-name").val(subflow.name); $("#subflow-input-name").val(subflow.name);
RED.text.bidi.prepareInput($("#subflow-input-name")); RED.text.bidi.prepareInput($("#subflow-input-name"));
trayBody.i18n();
finishedBuilding = true; finishedBuilding = true;
done();
});
}, },
close: function() { close: function() {
if (RED.view.state() != RED.state.IMPORT_DRAGGING) { if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
@ -2411,6 +2386,249 @@ RED.editor = (function() {
RED.tray.show(trayOptions); RED.tray.show(trayOptions);
} }
function showEditGroupDialog(group) {
var editing_node = group;
editStack.push(group);
RED.view.state(RED.state.EDITING);
var nodeInfoEditor;
var finishedBuilding = false;
var trayOptions = {
title: getEditStackTitle(),
buttons: [
{
id: "node-dialog-cancel",
text: RED._("common.label.cancel"),
click: function() {
RED.tray.close();
}
},
{
id: "node-dialog-ok",
class: "primary",
text: RED._("common.label.done"),
click: function() {
var changes = {};
var changed = false;
var wasDirty = RED.nodes.dirty();
var d;
var outputMap;
if (editing_node._def.oneditsave) {
var oldValues = {};
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
if (typeof editing_node[d] === "string" || typeof editing_node[d] === "number") {
oldValues[d] = editing_node[d];
} else {
oldValues[d] = $.extend(true,{},{v:editing_node[d]}).v;
}
}
}
try {
var rc = editing_node._def.oneditsave.call(editing_node);
if (rc === true) {
changed = true;
}
} catch(err) {
console.log("oneditsave",editing_node.id,editing_node.type,err.toString());
}
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
if (oldValues[d] === null || typeof oldValues[d] === "string" || typeof oldValues[d] === "number") {
if (oldValues[d] !== editing_node[d]) {
changes[d] = oldValues[d];
changed = true;
}
} else {
if (JSON.stringify(oldValues[d]) !== JSON.stringify(editing_node[d])) {
changes[d] = oldValues[d];
changed = true;
}
}
}
}
}
var newValue;
if (editing_node._def.defaults) {
for (d in editing_node._def.defaults) {
if (editing_node._def.defaults.hasOwnProperty(d)) {
var input = $("#node-input-"+d);
if (input.attr('type') === "checkbox") {
newValue = input.prop('checked');
} else if (input.prop("nodeName") === "select" && input.attr("multiple") === "multiple") {
// An empty select-multiple box returns null.
// Need to treat that as an empty array.
newValue = input.val();
if (newValue == null) {
newValue = [];
}
} else if ("format" in editing_node._def.defaults[d] && editing_node._def.defaults[d].format !== "" && input[0].nodeName === "DIV") {
newValue = input.text();
} else {
newValue = input.val();
}
if (newValue != null) {
if (editing_node._def.defaults[d].type) {
if (newValue == "_ADD_") {
newValue = "";
}
}
if (editing_node[d] != newValue) {
if (editing_node._def.defaults[d].type) {
// Change to a related config node
var configNode = RED.nodes.node(editing_node[d]);
if (configNode) {
var users = configNode.users;
users.splice(users.indexOf(editing_node),1);
}
configNode = RED.nodes.node(newValue);
if (configNode) {
configNode.users.push(editing_node);
}
}
changes[d] = editing_node[d];
editing_node[d] = newValue;
changed = true;
}
}
}
}
}
var oldInfo = editing_node.info;
if (nodeInfoEditor) {
var newInfo = nodeInfoEditor.getValue();
if (!!oldInfo) {
// Has existing info property
if (newInfo.trim() === "") {
// New value is blank - remove the property
changed = true;
changes.info = oldInfo;
delete editing_node.info;
} else if (newInfo !== oldInfo) {
// New value is different
changed = true;
changes.info = oldInfo;
editing_node.info = newInfo;
}
} else {
// No existing info
if (newInfo.trim() !== "") {
// New value is not blank
changed = true;
changes.info = undefined;
editing_node.info = newInfo;
}
}
}
if (changed) {
var wasChanged = editing_node.changed;
editing_node.changed = true;
RED.nodes.dirty(true);
var historyEvent = {
t:'edit',
node:editing_node,
changes:changes,
dirty:wasDirty,
changed:wasChanged
};
RED.history.push(historyEvent);
}
editing_node.dirty = true;
RED.tray.close();
RED.view.redraw(true);
}
}
],
resize: function(size) {
editTrayWidthCache['group'] = size.width;
$(".red-ui-tray-content").height(size.height - 50);
// var form = $(".red-ui-tray-content form").height(dimensions.height - 50 - 40);
// if (editing_node && editing_node._def.oneditresize) {
// try {
// editing_node._def.oneditresize.call(editing_node,{width:form.width(),height:form.height()});
// } catch(err) {
// console.log("oneditresize",editing_node.id,editing_node.type,err.toString());
// }
// }
},
open: function(tray, done) {
var trayFooter = tray.find(".red-ui-tray-footer");
var trayFooterLeft = $("<div/>", {
class: "red-ui-tray-footer-left"
}).appendTo(trayFooter)
var trayBody = tray.find('.red-ui-tray-body');
trayBody.parent().css('overflow','hidden');
var editorTabEl = $('<ul></ul>').appendTo(trayBody);
var editorContent = $('<div></div>').appendTo(trayBody);
var editorTabs = RED.tabs.create({
element:editorTabEl,
onchange:function(tab) {
editorContent.children().hide();
if (tab.onchange) {
tab.onchange.call(tab);
}
tab.content.show();
if (finishedBuilding) {
RED.tray.resize();
}
},
collapsible: true,
menu: false
});
var nodePropertiesTab = {
id: "editor-tab-properties",
label: RED._("editor-tab.properties"),
name: RED._("editor-tab.properties"),
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-cog"
};
buildEditForm(nodePropertiesTab.content,"dialog-form","group","node-red",group);
editorTabs.addTab(nodePropertiesTab);
var descriptionTab = {
id: "editor-tab-description",
label: RED._("editor-tab.description"),
name: RED._("editor-tab.description"),
content: $('<div>', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(),
iconClass: "fa fa-file-text-o",
onchange: function() {
nodeInfoEditor.focus();
}
};
editorTabs.addTab(descriptionTab);
nodeInfoEditor = buildDescriptionForm(descriptionTab.content,editing_node);
prepareEditDialog(group,group._def,"node-input", function() {
trayBody.i18n();
finishedBuilding = true;
done();
});
},
close: function() {
if (RED.view.state() != RED.state.IMPORT_DRAGGING) {
RED.view.state(RED.state.DEFAULT);
}
nodeInfoEditor.destroy();
nodeInfoEditor = null;
editStack.pop();
editing_node = null;
},
show: function() {
}
}
if (editTrayWidthCache.hasOwnProperty('group')) {
trayOptions.width = editTrayWidthCache['group'];
}
RED.tray.show(trayOptions);
}
function showTypeEditor(type, options) { function showTypeEditor(type, options) {
if (customEditTypes.hasOwnProperty(type)) { if (customEditTypes.hasOwnProperty(type)) {
if (editStack.length > 0) { if (editStack.length > 0) {
@ -2534,6 +2752,7 @@ RED.editor = (function() {
edit: showEditDialog, edit: showEditDialog,
editConfig: showEditConfigNodeDialog, editConfig: showEditConfigNodeDialog,
editSubflow: showEditSubflowDialog, editSubflow: showEditSubflowDialog,
editGroup: showEditGroupDialog,
editJavaScript: function(options) { showTypeEditor("_js",options) }, editJavaScript: function(options) { showTypeEditor("_js",options) },
editExpression: function(options) { showTypeEditor("_expression", options) }, editExpression: function(options) { showTypeEditor("_expression", options) },
editJSON: function(options) { showTypeEditor("_json", options) }, editJSON: function(options) { showTypeEditor("_json", options) },

View File

@ -102,7 +102,7 @@
var f = $(this).val(); var f = $(this).val();
var args = RED._('jsonata:'+f+".args",{defaultValue:''}); var args = RED._('jsonata:'+f+".args",{defaultValue:''});
var title = "<h5>"+f+"("+args+")</h5>"; var title = "<h5>"+f+"("+args+")</h5>";
var body = marked(RED._('jsonata:'+f+'.desc',{defaultValue:''})); var body = RED.utils.renderMarkdown(RED._('jsonata:'+f+'.desc',{defaultValue:''}));
$("#red-ui-editor-type-expression-help").html(title+"<p>"+body+"</p>"); $("#red-ui-editor-type-expression-help").html(title+"<p>"+body+"</p>");
}) })

View File

@ -106,7 +106,7 @@
options.push({id:"red-ui-editor-type-json-menu-duplicate", icon:"fa fa-copy", label:RED._("jsonEditor.duplicate"),onselect:function(){ options.push({id:"red-ui-editor-type-json-menu-duplicate", icon:"fa fa-copy", label:RED._("jsonEditor.duplicate"),onselect:function(){
var newKey = item.key; var newKey = item.key;
if (item.parent.type === 'array') { if (item.parent.type === 'array') {
newKey = parent.children.length; newKey = item.parent.children.length;
} else { } else {
var m = /^(.*?)(-(\d+))?$/.exec(newKey); var m = /^(.*?)(-(\d+))?$/.exec(newKey);
var usedKeys = {}; var usedKeys = {};
@ -301,9 +301,9 @@
var val = $('<input type="text" class="red-ui-editor-type-json-editor-value">').css({width:w+"px"}).val(""+valValue).insertAfter(valueLabel).typedInput({ var val = $('<input type="text" class="red-ui-editor-type-json-editor-value">').css({width:w+"px"}).val(""+valValue).insertAfter(valueLabel).typedInput({
types:[ types:[
'str','num','bool', 'str','num','bool',
{value:"null",label:"null",hasValue:false}, {value:"null",label:RED._("common.type.null"),hasValue:false},
{value:"array",label:RED._("common.type.array"),hasValue:false}, {value:"array",label:RED._("common.type.array"),hasValue:false,icon:"red/images/typedInput/json.png"},
{value:"object",label:RED._("common.type.object"),hasValue:false} {value:"object",label:RED._("common.type.object"),hasValue:false,icon:"red/images/typedInput/json.png"}
], ],
default: valType default: valType
}); });
@ -327,10 +327,10 @@
item.value = valValue; item.value = valValue;
var valClass; var valClass;
switch(valType) { switch(valType) {
case 'str': item.children && (orphanedChildren = item.children); item.treeList.makeLeaf(true); item.type = "string"; valClass = "red-ui-debug-msg-type-string"; valValue = '"'+valValue+'"'; break; case 'str': if (item.children) { orphanedChildren = item.children } item.treeList.makeLeaf(true); item.type = "string"; valClass = "red-ui-debug-msg-type-string"; valValue = '"'+valValue+'"'; break;
case 'num': item.children && (orphanedChildren = item.children); item.treeList.makeLeaf(true); item.type = "number"; valClass = "red-ui-debug-msg-type-number"; break; case 'num': if (item.children) { orphanedChildren = item.children } item.treeList.makeLeaf(true); item.type = "number"; valClass = "red-ui-debug-msg-type-number"; break;
case 'bool': item.children && (orphanedChildren = item.children); item.treeList.makeLeaf(true); item.type = "boolean"; valClass = "red-ui-debug-msg-type-other"; item.value = (valValue === "true"); break; case 'bool': if (item.children) { orphanedChildren = item.children } item.treeList.makeLeaf(true); item.type = "boolean"; valClass = "red-ui-debug-msg-type-other"; item.value = (valValue === "true"); break;
case 'null': item.children && (orphanedChildren = item.children); item.treeList.makeLeaf(true); item.type = "null"; valClass = "red-ui-debug-msg-type-null"; item.value = valValue = "null"; break; case 'null': if (item.children) { orphanedChildren = item.children } item.treeList.makeLeaf(true); item.type = "null"; valClass = "red-ui-debug-msg-type-null"; item.value = valValue = "null"; break;
case 'object': case 'object':
item.treeList.makeParent(orphanedChildren); item.treeList.makeParent(orphanedChildren);
item.type = "object"; item.type = "object";
@ -485,7 +485,7 @@
} else if (activeTab === "json-raw") { } else if (activeTab === "json-raw") {
result = expressionEditor.getValue(); result = expressionEditor.getValue();
} }
onComplete && onComplete(result); if (onComplete) { onComplete(result) }
RED.tray.close(); RED.tray.close();
} }
} }

View File

@ -32,7 +32,7 @@
'<button type="button" class="red-ui-button" data-style="bq"><i class="fa fa-quote-left"></i></button>'+ '<button type="button" class="red-ui-button" data-style="bq"><i class="fa fa-quote-left"></i></button>'+
'<button type="button" class="red-ui-button" data-style="hr"><i class="fa fa-minus"></i></button>'+ '<button type="button" class="red-ui-button" data-style="hr"><i class="fa fa-minus"></i></button>'+
'<button type="button" class="red-ui-button" data-style="link"><i class="fa fa-link"></i></button>'+ '<button type="button" class="red-ui-button" data-style="link"><i class="fa fa-link"></i></button>'+
'</span>' '</span>'+
'</div>'; '</div>';
var template = '<script type="text/x-red" data-template-name="_markdown">'+ var template = '<script type="text/x-red" data-template-name="_markdown">'+
@ -107,7 +107,7 @@
clearTimeout(changeTimer); clearTimeout(changeTimer);
changeTimer = setTimeout(function() { changeTimer = setTimeout(function() {
var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop(); var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop();
$(".red-ui-editor-type-markdown-panel-preview").html(marked(expressionEditor.getValue())); $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
$(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop); $(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop);
},200); },200);
}) })
@ -116,7 +116,7 @@
} }
if (value) { if (value) {
$(".red-ui-editor-type-markdown-panel-preview").html(marked(expressionEditor.getValue())); $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
} }
panels = RED.panels.create({ panels = RED.panels.create({
id:"red-ui-editor-type-markdown-panels", id:"red-ui-editor-type-markdown-panels",

View File

@ -0,0 +1,633 @@
/**
* 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.group = (function() {
var _groupEditTemplate = '<script type="text/x-red" data-template-name="group">'+
'<div class="form-row">'+
'<label for="node-input-name" data-i18n="[append]editor:common.label.name"><i class="fa fa-tag"></i> </label>'+
'<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">'+
'</div>'+
// '<div class="node-input-group-style-tools"><span class="button-group"><button class="red-ui-button red-ui-button-small">Use default style</button><button class="red-ui-button red-ui-button-small">Set as default style</button></span></div>'+
'<div class="form-row" id="node-input-row-style-stroke">'+
'<label>Style</label>'+
'<label style="width: 70px;margin-right:10px" for="node-input-style-stroke" data-i18n="editor:common.label.line"></label>'+
'</div>'+
'<div class="form-row" style="padding-left: 100px;" id="node-input-row-style-fill">'+
'<label style="width: 70px;margin-right: 10px " for="node-input-style-fill" data-i18n="editor:common.label.fill"></label>'+
'</div>'+
'<div class="form-row">'+
'<label for="node-input-style-label">Label</label>'+
'<input type="checkbox" id="node-input-style-label"/>'+
'</div>'+
'<div class="form-row" id="node-input-row-style-label-options">'+
'<div style="margin-left: 100px; display: inline-block">'+
'<div class="form-row">'+
'<span style="display: inline-block; min-width: 140px" id="node-input-row-style-label-color">'+
'<label style="width: 70px;margin-right: 10px" for="node-input-style-fill" data-i18n="editor:common.label.color"></label>'+
'</span>'+
'</div>'+
'<div class="form-row">'+
'<span style="display: inline-block; min-width: 140px;" id="node-input-row-style-label-position">'+
'<label style="width: 70px;margin-right: 10px " for="node-input-style-label-position" data-i18n="editor:common.label.position"></label>'+
'</span>'+
'</div>'+
'</div>'+
'</div>'+
'</script>';
var colorPalette = [
"#ff0000",
"#ffC000",
"#ffff00",
"#92d04f",
"#0070c0",
"#001f60",
"#6f2fa0",
"#000000",
"#777777"
]
var colorSteps = 3;
var colorCount = colorPalette.length;
for (var i=0,len=colorPalette.length*colorSteps;i<len;i++) {
var ci = i%colorCount;
var j = Math.floor(i/colorCount)+1;
var c = colorPalette[ci];
var r = parseInt(c.substring(1, 3), 16);
var g = parseInt(c.substring(3, 5), 16);
var b = parseInt(c.substring(5, 7), 16);
var dr = (255-r)/(colorSteps+((ci===colorCount-1) ?0:1));
var dg = (255-g)/(colorSteps+((ci===colorCount-1) ?0:1));
var db = (255-b)/(colorSteps+((ci===colorCount-1) ?0:1));
r = Math.min(255,Math.floor(r+j*dr));
g = Math.min(255,Math.floor(g+j*dg));
b = Math.min(255,Math.floor(b+j*db));
colorPalette.push('#'+((r<<16) + (g<<8) + b).toString(16).padStart(6,'0'));
}
var defaultGroupStyle = {};
var groupDef = {
defaults:{
name:{value:""},
style:{value:{}},
nodes:{value:[]}
},
category: "config",
oneditprepare: function() {
var style = this.style || {};
RED.colorPicker.create({
id:"node-input-style-stroke",
value: style.stroke || "#a4a4a4",
palette: colorPalette,
cellPerRow: colorCount,
cellWidth: 16,
cellHeight: 16,
cellMargin: 3,
none: true,
opacity: style['stroke-opacity'] || 1.0
}).appendTo("#node-input-row-style-stroke");
RED.colorPicker.create({
id:"node-input-style-fill",
value: style.fill || "none",
palette: colorPalette,
cellPerRow: colorCount,
cellWidth: 16,
cellHeight: 16,
cellMargin: 3,
none: true,
opacity: style['fill-opacity'] || 1.0
}).appendTo("#node-input-row-style-fill");
createLayoutPicker({
id:"node-input-style-label-position",
value:style["label-position"] || "nw"
}).appendTo("#node-input-row-style-label-position");
RED.colorPicker.create({
id:"node-input-style-color",
value: style.color || "#a4a4a4",
palette: colorPalette,
cellPerRow: colorCount,
cellWidth: 16,
cellHeight: 16,
cellMargin: 3
}).appendTo("#node-input-row-style-label-color");
$("#node-input-style-label").toggleButton({
enabledLabel: RED._("editor.show"),
disabledLabel: RED._("editor.hide")
})
$("#node-input-style-label").on("change", function(evt) {
$("#node-input-row-style-label-options").toggle($(this).prop("checked"));
})
$("#node-input-style-label").prop("checked", this.style.label)
$("#node-input-style-label").trigger("change");
},
oneditresize: function(size) {
},
oneditsave: function() {
this.style.stroke = $("#node-input-style-stroke").val();
this.style.fill = $("#node-input-style-fill").val();
this.style["stroke-opacity"] = $("#node-input-style-stroke-opacity").val();
this.style["fill-opacity"] = $("#node-input-style-fill-opacity").val();
this.style.label = $("#node-input-style-label").prop("checked");
if (this.style.label) {
this.style["label-position"] = $("#node-input-style-label-position").val();
this.style.color = $("#node-input-style-color").val();
} else {
delete this.style["label-position"];
delete this.style.color;
}
if (this.style["stroke-opacity"] === "1") {
delete this.style["stroke-opacity"]
}
if (this.style["fill-opacity"] === "1") {
delete this.style["fill-opacity"]
}
this.resize = true;
},
set:{
module: "node-red"
}
}
function init() {
RED.events.on("view:selection-changed",function(selection) {
RED.menu.setDisabled("menu-item-group-group",!!!selection.nodes);
RED.menu.setDisabled("menu-item-group-ungroup",!!!selection.nodes || selection.nodes.filter(function(n) { return n.type==='group'}).length === 0);
RED.menu.setDisabled("menu-item-group-merge",!!!selection.nodes);
RED.menu.setDisabled("menu-item-group-remove",!!!selection.nodes || selection.nodes.filter(function(n) { return !!n.g }).length === 0);
});
RED.actions.add("core:group-selection", function() { groupSelection() })
RED.actions.add("core:ungroup-selection", function() { ungroupSelection() })
RED.actions.add("core:merge-selection-to-group", function() { mergeSelection() })
RED.actions.add("core:remove-selection-from-group", function() { removeSelection() })
RED.actions.add("core:copy-group-style", function() { copyGroupStyle() });
RED.actions.add("core:paste-group-style", function() { pasteGroupStyle() });
$(_groupEditTemplate).appendTo("#red-ui-editor-node-configs");
var groupStyleDiv = $("<div>",{
class:"red-ui-flow-group-body",
style: "position: absolute; top: -1000px;"
}).appendTo(document.body);
var groupStyle = getComputedStyle(groupStyleDiv[0]);
defaultGroupStyle = {
stroke: convertColorToHex(groupStyle.stroke),
"stroke-opacity": groupStyle.strokeOpacity,
fill: convertColorToHex(groupStyle.fill),
"fill-opacity": groupStyle.fillOpacity
}
groupStyleDiv.remove();
}
function convertColorToHex(c) {
var m = /^rgb\((\d+), (\d+), (\d+)\)$/.exec(c);
if (m) {
return "#"+(((parseInt(m[1])<<16) + (parseInt(m[2])<<8) + parseInt(m[3])).toString(16).padStart(6,'0'))
}
return c;
}
var groupStyleClipboard;
function copyGroupStyle() {
var selection = RED.view.selection();
if (selection.nodes && selection.nodes.length === 1 && selection.nodes[0].type === 'group') {
groupStyleClipboard = JSON.parse(JSON.stringify(selection.nodes[0].style));
RED.notify(RED._("clipboard.groupStyleCopied"),{id:"clipboard"})
}
}
function pasteGroupStyle() {
if (groupStyleClipboard) {
var selection = RED.view.selection();
if (selection.nodes) {
var historyEvent = {
t:'multi',
events:[],
dirty: RED.nodes.dirty()
}
selection.nodes.forEach(function(n) {
if (n.type === 'group') {
historyEvent.events.push({
t: "edit",
node: n,
changes: {
style: JSON.parse(JSON.stringify(n.style))
},
dirty: RED.nodes.dirty()
});
n.style = JSON.parse(JSON.stringify(groupStyleClipboard));
n.dirty = true;
}
})
if (historyEvent.events.length > 0) {
RED.history.push(historyEvent);
RED.nodes.dirty(true);
RED.view.redraw();
}
}
}
}
function groupSelection() {
var selection = RED.view.selection();
if (selection.nodes) {
var group = createGroup(selection.nodes);
if (group) {
var historyEvent = {
t:"createGroup",
groups: [ group ],
dirty: RED.nodes.dirty()
}
RED.history.push(historyEvent);
RED.view.select({nodes:[group]});
RED.nodes.dirty(true);
}
}
}
function ungroupSelection() {
var selection = RED.view.selection();
if (selection.nodes) {
var newSelection = [];
groups = selection.nodes.filter(function(n) { return n.type === "group" });
var historyEvent = {
t:"ungroup",
groups: [ ],
dirty: RED.nodes.dirty()
}
RED.history.push(historyEvent);
groups.forEach(function(g) {
newSelection = newSelection.concat(ungroup(g))
historyEvent.groups.push(g);
})
RED.history.push(historyEvent);
RED.view.select({nodes:newSelection})
RED.nodes.dirty(true);
}
}
function ungroup(g) {
var nodes = [];
var parentGroup = RED.nodes.group(g.g);
g.nodes.forEach(function(n) {
nodes.push(n);
if (parentGroup) {
// Move nodes to parent group
n.g = parentGroup.id;
parentGroup.nodes.push(n);
parentGroup.dirty = true;
n.dirty = true;
} else {
delete n.g;
}
})
RED.nodes.removeGroup(g);
return nodes;
}
function mergeSelection() {
// TODO: this currently creates an entirely new group. Need to merge properties
// of any existing group
var selection = RED.view.selection();
if (selection.nodes) {
var nodes = [];
var historyEvent = {
t: "multi",
events: []
}
var ungroupHistoryEvent = {
t: "ungroup",
groups: []
}
var n;
var parentGroup;
// First pass, check they are all in the same parent
// TODO: DRY mergeSelection,removeSelection,...
for (var i=0; i<selection.nodes.length; i++) {
n = selection.nodes[i];
if (i === 0) {
parentGroup = n.g;
} else if (n.g !== parentGroup) {
RED.notify(RED._("group.errors.cannotCreateDiffGroups"),"error");
return;
}
}
// Second pass, ungroup any groups in the selection and add their contents
// to the selection
for (var i=0; i<selection.nodes.length; i++) {
n = selection.nodes[i];
if (n.type === "group") {
ungroupHistoryEvent.groups.push(n);
nodes = nodes.concat(ungroup(n));
} else {
nodes.push(n);
}
n.dirty = true;
}
if (ungroupHistoryEvent.groups.length > 0) {
historyEvent.events.push(ungroupHistoryEvent);
}
// Finally, create the new group
var group = createGroup(nodes);
if (group) {
RED.view.select({nodes:[group]})
}
historyEvent.events.push({
t:"createGroup",
groups: [ group ],
dirty: RED.nodes.dirty()
});
RED.history.push(historyEvent);
RED.nodes.dirty(true);
}
}
function removeSelection() {
var selection = RED.view.selection();
if (selection.nodes) {
var nodes = [];
var n;
var parentGroup = RED.nodes.group(selection.nodes[0].g);
if (parentGroup) {
try {
removeFromGroup(parentGroup,selection.nodes,true);
var historyEvent = {
t: "removeFromGroup",
dirty: RED.nodes.dirty(),
group: parentGroup,
nodes: selection.nodes
}
RED.history.push(historyEvent);
RED.nodes.dirty(true);
} catch(err) {
RED.notify(err,"error");
return;
}
}
RED.view.select({nodes:selection.nodes})
}
}
function createGroup(nodes) {
if (nodes.length === 0) {
return;
}
if (nodes.filter(function(n) { return n.type === "subflow" }).length > 0) {
RED.notify(RED._("group.errors.cannotAddSubflowPorts"),"error");
return;
}
// nodes is an array
// each node must be on the same tab (z)
var group = {
id: RED.nodes.id(),
type: 'group',
nodes: [],
style: JSON.parse(JSON.stringify(defaultGroupStyle)),
x: Number.POSITIVE_INFINITY,
y: Number.POSITIVE_INFINITY,
w: 0,
h: 0,
_def: RED.group.def
}
try {
addToGroup(group,nodes);
} catch(err) {
RED.notify(err,"error");
return;
}
group.z = nodes[0].z;
RED.nodes.addGroup(group);
return group;
}
function addToGroup(group,nodes) {
if (!Array.isArray(nodes)) {
nodes = [nodes];
}
var i,n,z;
var g;
// First pass - validate we can safely add these nodes to the group
for (i=0;i<nodes.length;i++) {
n = nodes[i]
if (!n.z) {
throw new Error("Cannot add node without a z property to a group")
}
if (!z) {
z = n.z;
} else if (z !== n.z) {
throw new Error("Cannot add nooes with different z properties")
}
if (n.g) {
// This is already in a group.
// - check they are all in the same group
if (!g) {
if (i!==0) {
// TODO: this might be ok when merging groups
throw new Error(RED._("group.errors.cannotCreateDiffGroups"))
}
g = n.g
}
}
if (g !== n.g) {
throw new Error(RED._("group.errors.cannotCreateDiffGroups"))
}
}
// The nodes are already in a group. The assumption is they should be
// wrapped in the newly provided group, and that group added to in their
// place to the existing containing group.
if (g) {
g = RED.nodes.group(g);
g.nodes.push(group);
g.dirty = true;
group.g = g.id;
}
// Second pass - add them to the group
for (i=0;i<nodes.length;i++) {
n = nodes[i];
if (n.type !== "subflow") {
if (g && n.g === g.id) {
var ni = g.nodes.indexOf(n);
if (ni > -1) {
g.nodes.splice(ni,1)
}
}
n.g = group.id;
n.dirty = true;
group.nodes.push(n);
group.x = Math.min(group.x,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0));
group.y = Math.min(group.y,n.y-n.h/2-25);
group.w = Math.max(group.w,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0) - group.x);
group.h = Math.max(group.h,n.y+n.h/2+25-group.y);
}
}
markDirty(group);
}
function removeFromGroup(group, nodes, reparent) {
if (!Array.isArray(nodes)) {
nodes = [nodes];
}
var n;
// First pass, check they are all in the same parent
// TODO: DRY mergeSelection,removeSelection,...
for (var i=0; i<nodes.length; i++) {
if (nodes[i].g !== group.id) {
return;
}
}
var parentGroup = RED.nodes.group(group.g);
for (var i=0; i<nodes.length; i++) {
n = nodes[i];
n.dirty = true;
var index = group.nodes.indexOf(n);
group.nodes.splice(index,1);
if (reparent && group.g) {
n.g = group.g
parentGroup.nodes.push(n);
} else {
delete n.g;
}
}
markDirty(group);
}
function getNodes(group,recursive) {
var nodes = [];
group.nodes.forEach(function(n) {
nodes.push(n);
if (recursive && n.type === 'group') {
nodes = nodes.concat(getNodes(n,recursive))
}
})
return nodes;
}
function groupContains(group,item) {
if (item.g === group.id) {
return true;
}
for (var i=0;i<group.nodes.length;i++) {
if (group.nodes[i].type === "group") {
if (groupContains(group.nodes[i],item)) {
return true;
}
}
}
return false;
}
function getRootGroup(group) {
if (!group.g) {
return group;
}
return getRootGroup(RED.nodes.group(group.g))
}
function createLayoutPicker(options) {
var container = $("<div>",{style:"display:inline-block"});
var layoutHiddenInput = $("<input/>", { id: options.id, type: "hidden", value: options.value }).appendTo(container);
var layoutButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(container);
$('<i class="fa fa-caret-down"></i>').appendTo(layoutButton);
var layoutDispContainer = $('<div>',{class:"red-ui-search-result-node"}).appendTo(layoutButton);
var layoutDisp = $('<div>',{class:"red-ui-group-layout-picker-cell-text red-ui-group-layout-text-pos-"}).appendTo(layoutDispContainer);
var refreshDisplay = function() {
var val = layoutHiddenInput.val();
layoutDisp.removeClass().addClass("red-ui-group-layout-picker-cell-text red-ui-group-layout-text-pos-"+val)
}
layoutButton.on("click", function(e) {
var picker = $("<div/>", {
class: "red-ui-group-layout-picker"
}).css({
width: "126px"
});
var row = null;
row = $("<div/>").appendTo(picker);
for (var y=0;y<2;y++) { //red-ui-group-layout-text-pos
var yComponent= "ns"[y];
row = $("<div/>").appendTo(picker);
for (var x=0;x<3;x++) {
var xComponent = ["w","","e"][x];
var val = yComponent+xComponent;
var button = $("<button/>", { class:"red-ui-search-result-node","data-pos":val }).appendTo(row);
button.on("click", function (e) {
e.preventDefault();
layoutHiddenInput.val($(this).data("pos"));
layoutPanel.hide()
refreshDisplay();
});
$('<div>',{class:"red-ui-group-layout-picker-cell-text red-ui-group-layout-text-pos-"+val}).appendTo(button);
}
}
refreshDisplay();
var layoutPanel = RED.popover.panel(picker);
layoutPanel.show({
target: layoutButton
})
})
refreshDisplay();
return container;
}
function markDirty(group) {
group.dirty = true;
while(group) {
group.dirty = true;
group = RED.nodes.group(group.g);
}
}
return {
def: groupDef,
init: init,
createGroup: createGroup,
ungroup: ungroup,
addToGroup: addToGroup,
removeFromGroup: removeFromGroup,
getNodes: getNodes,
contains: groupContains,
markDirty: markDirty
}
})();

View File

@ -524,12 +524,12 @@ RED.keyboard = (function() {
var pane = $('<div id="red-ui-settings-tab-keyboard"></div>'); var pane = $('<div id="red-ui-settings-tab-keyboard"></div>');
$('<div class="keyboard-shortcut-entry keyboard-shortcut-list-header">'+ $('<div class="keyboard-shortcut-entry keyboard-shortcut-list-header">'+
'<div class="keyboard-shortcut-entry-key keyboard-shortcut-entry-text"><input id="red-ui-settings-tab-keyboard-filter" type="text" data-i18n="[placeholder]keyboard.filterActions"></div>'+ '<div class="keyboard-shortcut-entry-key keyboard-shortcut-entry-text"><input autocomplete="off" name="keyboard-filter" id="red-ui-settings-tab-keyboard-filter" type="text" data-i18n="[placeholder]keyboard.filterActions"></div>'+
'<div class="keyboard-shortcut-entry-key" data-i18n="keyboard.shortcut"></div>'+ '<div class="keyboard-shortcut-entry-key" data-i18n="keyboard.shortcut"></div>'+
'<div class="keyboard-shortcut-entry-scope" data-i18n="keyboard.scope"></div>'+ '<div class="keyboard-shortcut-entry-scope" data-i18n="keyboard.scope"></div>'+
'</div>').appendTo(pane); '</div>').appendTo(pane);
pane.find("input").searchBox({ pane.find("#red-ui-settings-tab-keyboard-filter").searchBox({
delay: 100, delay: 100,
change: function() { change: function() {
var filterValue = $(this).val().trim(); var filterValue = $(this).val().trim();

View File

@ -75,13 +75,16 @@ RED.palette.editor = (function() {
}); });
}) })
} }
function installNodeModule(id,version,callback) { function installNodeModule(id,version,url,callback) {
var requestBody = { var requestBody = {
module: id module: id
}; };
if (version) { if (version) {
requestBody.version = version; requestBody.version = version;
} }
if (url) {
requestBody.url = url;
}
$.ajax({ $.ajax({
url:"nodes", url:"nodes",
type: "POST", type: "POST",
@ -381,6 +384,7 @@ RED.palette.editor = (function() {
handleCatalogResponse(null,catalog,index,v); handleCatalogResponse(null,catalog,index,v);
refreshNodeModuleList(); refreshNodeModuleList();
}).fail(function(jqxhr, textStatus, error) { }).fail(function(jqxhr, textStatus, error) {
console.warn("Error loading catalog",catalog,":",error);
handleCatalogResponse(jqxhr,catalog,index); handleCatalogResponse(jqxhr,catalog,index);
}).always(function() { }).always(function() {
handled++; handled++;
@ -622,7 +626,7 @@ RED.palette.editor = (function() {
if ($(this).hasClass('disabled')) { if ($(this).hasClass('disabled')) {
return; return;
} }
update(entry,loadedIndex[entry.name].version,container,function(err){}); update(entry,loadedIndex[entry.name].version,loadedIndex[entry.name].pkg_url,container,function(err){});
}) })
@ -872,7 +876,7 @@ RED.palette.editor = (function() {
$('<div id="red-ui-palette-module-install-shade" class="red-ui-palette-module-shade hide"><div class="red-ui-palette-module-shade-status"></div><img src="red/images/spin.svg" class="red-ui-palette-spinner"/></div>').appendTo(installTab); $('<div id="red-ui-palette-module-install-shade" class="red-ui-palette-module-shade hide"><div class="red-ui-palette-module-shade-status"></div><img src="red/images/spin.svg" class="red-ui-palette-spinner"/></div>').appendTo(installTab);
} }
function update(entry,version,container,done) { function update(entry,version,url,container,done) {
if (RED.settings.theme('palette.editable') === false) { if (RED.settings.theme('palette.editable') === false) {
done(new Error('Palette not editable')); done(new Error('Palette not editable'));
return; return;
@ -898,7 +902,7 @@ RED.palette.editor = (function() {
RED.actions.invoke("core:show-event-log"); RED.actions.invoke("core:show-event-log");
}); });
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.name+" "+version); RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.name+" "+version);
installNodeModule(entry.name,version,function(xhr) { installNodeModule(entry.name,version,url,function(xhr) {
spinner.remove(); spinner.remove();
if (xhr) { if (xhr) {
if (xhr.responseJSON) { if (xhr.responseJSON) {
@ -1023,7 +1027,7 @@ RED.palette.editor = (function() {
RED.actions.invoke("core:show-event-log"); RED.actions.invoke("core:show-event-log");
}); });
RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version); RED.eventLog.startEvent(RED._("palette.editor.confirm.button.install")+" : "+entry.id+" "+entry.version);
installNodeModule(entry.id,entry.version,function(xhr) { installNodeModule(entry.id,entry.version,entry.pkg_url,function(xhr) {
spinner.remove(); spinner.remove();
if (xhr) { if (xhr) {
if (xhr.responseJSON) { if (xhr.responseJSON) {

View File

@ -92,8 +92,11 @@ RED.palette = (function() {
var lineHeight = 20; var lineHeight = 20;
var portHeight = 10; var portHeight = 10;
el.attr("data-palette-label",label);
label = RED.utils.sanitize(label); label = RED.utils.sanitize(label);
var words = label.split(/[ -]/); var words = label.split(/[ -]/);
var displayLines = []; var displayLines = [];
@ -212,16 +215,14 @@ RED.palette = (function() {
} }
$('<div/>', { $('<div/>', {
class: "red-ui-palette-label" class: "red-ui-palette-label"+(((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " red-ui-palette-label-right" : "")
+ (((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " red-ui-palette-label-right" : "")
}).appendTo(d); }).appendTo(d);
if (def.icon) { if (def.icon) {
var icon_url = RED.utils.getNodeIcon(def); var icon_url = RED.utils.getNodeIcon(def);
var iconContainer = $('<div/>', { var iconContainer = $('<div/>', {
class: "red-ui-palette-icon-container" class: "red-ui-palette-icon-container"+(((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " red-ui-palette-icon-container-right" : "")
+ (((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " red-ui-palette-icon-container-right" : "")
}).appendTo(d); }).appendTo(d);
RED.utils.createIconElement(icon_url, iconContainer, true); RED.utils.createIconElement(icon_url, iconContainer, true);
} }
@ -268,7 +269,7 @@ RED.palette = (function() {
RED.view.focus(); RED.view.focus();
var helpText; var helpText;
if (nt.indexOf("subflow:") === 0) { if (nt.indexOf("subflow:") === 0) {
helpText = marked(RED.nodes.subflow(nt.substring(8)).info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>'); helpText = RED.utils.renderMarkdown(RED.nodes.subflow(nt.substring(8)).info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
} else { } else {
helpText = $("script[data-help-name='"+d.attr("data-palette-type")+"']").html()||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>'); helpText = $("script[data-help-name='"+d.attr("data-palette-type")+"']").html()||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
} }
@ -283,6 +284,9 @@ RED.palette = (function() {
var mouseX; var mouseX;
var mouseY; var mouseY;
var spliceTimer; var spliceTimer;
var groupTimer;
var activeGroup;
var hoverGroup;
var paletteWidth; var paletteWidth;
var paletteTop; var paletteTop;
$(d).draggable({ $(d).draggable({
@ -294,16 +298,53 @@ RED.palette = (function() {
start: function() { start: function() {
paletteWidth = $("#red-ui-palette").width(); paletteWidth = $("#red-ui-palette").width();
paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top; paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top;
hoverGroup = null;
activeGroup = RED.view.getActiveGroup();
if (activeGroup) {
document.getElementById("group_select_"+activeGroup.id).classList.add("red-ui-flow-group-active-hovered");
}
RED.view.focus(); RED.view.focus();
}, },
stop: function() { d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null;}}, stop: function() {
d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false);
if (hoverGroup) {
document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
}
if (activeGroup) {
document.getElementById("group_select_"+activeGroup.id).classList.remove("red-ui-flow-group-active-hovered");
}
if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; }
if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; }
},
drag: function(e,ui) { drag: function(e,ui) {
var paletteNode = getPaletteNode(nt); var paletteNode = getPaletteNode(nt);
ui.originalPosition.left = paletteNode.offset().left; ui.originalPosition.left = paletteNode.offset().left;
if (def.inputs > 0 && def.outputs > 0) {
mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft();
mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop(); mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop();
if (!groupTimer) {
groupTimer = setTimeout(function() {
mouseX /= RED.view.scale();
mouseY /= RED.view.scale();
var group = RED.view.getGroupAtPoint(mouseX,mouseY);
if (group !== hoverGroup) {
if (hoverGroup) {
document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered");
}
if (group) {
document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered");
}
hoverGroup = group;
if (hoverGroup) {
$(ui.helper).data('group',hoverGroup);
} else {
$(ui.helper).removeData('group');
}
}
groupTimer = null;
},200)
}
if (def.inputs > 0 && def.outputs > 0) {
if (!spliceTimer) { if (!spliceTimer) {
spliceTimer = setTimeout(function() { spliceTimer = setTimeout(function() {
var nodes = []; var nodes = [];
@ -369,7 +410,7 @@ RED.palette = (function() {
RED.workspaces.show(nt.substring(8)); RED.workspaces.show(nt.substring(8));
e.preventDefault(); e.preventDefault();
}); });
nodeInfo = marked(def.info||""); nodeInfo = RED.utils.renderMarkdown(def.info||"");
} }
setLabel(nt,d,label,nodeInfo); setLabel(nt,d,label,nodeInfo);
@ -419,14 +460,10 @@ RED.palette = (function() {
var portOutput = paletteNode.find(".red-ui-palette-port-output"); var portOutput = paletteNode.find(".red-ui-palette-port-output");
var paletteLabel = paletteNode.find(".red-ui-palette-label"); var paletteLabel = paletteNode.find(".red-ui-palette-label");
paletteLabel.attr("class","red-ui-palette-label" paletteLabel.attr("class","red-ui-palette-label" + (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-label-right" : ""));
+ (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-label-right" : "")
);
var paletteIconContainer = paletteNode.find(".red-ui-palette-icon-container"); var paletteIconContainer = paletteNode.find(".red-ui-palette-icon-container");
paletteIconContainer.attr("class","red-ui-palette-icon-container" paletteIconContainer.attr("class","red-ui-palette-icon-container" + (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-icon-container-right" : ""));
+ (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-icon-container-right" : "")
);
if (portInput.length === 0 && sf.in.length > 0) { if (portInput.length === 0 && sf.in.length > 0) {
var portIn = document.createElement("div"); var portIn = document.createElement("div");
@ -443,7 +480,7 @@ RED.palette = (function() {
} else if (portOutput.length !== 0 && sf.out.length === 0) { } else if (portOutput.length !== 0 && sf.out.length === 0) {
portOutput.remove(); portOutput.remove();
} }
setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||"")); setLabel(sf.type+":"+sf.id,paletteNode,sf.name,RED.utils.renderMarkdown(sf.info||""));
setIcon(paletteNode,sf); setIcon(paletteNode,sf);
var currentCategory = paletteNode.data('category'); var currentCategory = paletteNode.data('category');
@ -475,7 +512,7 @@ RED.palette = (function() {
function filterChange(val) { function filterChange(val) {
var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i'); var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i');
$("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) { $("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) {
var currentLabel = $(el).find(".red-ui-palette-label").text(); var currentLabel = $(el).attr("data-palette-label");
var type = $(el).attr("data-palette-type"); var type = $(el).attr("data-palette-type");
if (val === "" || re.test(type) || re.test(currentLabel)) { if (val === "" || re.test(type) || re.test(currentLabel)) {
$(this).show(); $(this).show();

View File

@ -158,7 +158,7 @@ RED.projects.settings = (function() {
container.empty(); container.empty();
var desc; var desc;
if (activeProject.description) { if (activeProject.description) {
desc = marked(activeProject.description); desc = RED.utils.renderMarkdown(activeProject.description);
} else { } else {
desc = '<span class="red-ui-help-info-none">' + RED._("sidebar.project.noDescriptionAvailable") + '</span>'; desc = '<span class="red-ui-help-info-none">' + RED._("sidebar.project.noDescriptionAvailable") + '</span>';
} }

View File

@ -30,13 +30,13 @@ RED.projects.userSettings = (function() {
$('<div class="red-ui-settings-section-description"></div>').appendTo(gitconfigContainer).text(RED._("editor:sidebar.project.userSettings.committerTip")); $('<div class="red-ui-settings-section-description"></div>').appendTo(gitconfigContainer).text(RED._("editor:sidebar.project.userSettings.committerTip"));
var row = $('<div class="red-ui-settings-row"></div>').appendTo(gitconfigContainer); var row = $('<div class="red-ui-settings-row"></div>').appendTo(gitconfigContainer);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row); $('<label for="user-settings-gitconfig-username"></label>').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row);
gitUsernameInput = $('<input type="text">').appendTo(row); gitUsernameInput = $('<input type="text" id="user-settings-gitconfig-username">').appendTo(row);
gitUsernameInput.val(currentGitSettings.user.name||""); gitUsernameInput.val(currentGitSettings.user.name||"");
row = $('<div class="red-ui-settings-row"></div>').appendTo(gitconfigContainer); row = $('<div class="red-ui-settings-row"></div>').appendTo(gitconfigContainer);
$('<label for=""></label>').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row); $('<label for="user-settings-gitconfig-email"></label>').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row);
gitEmailInput = $('<input type="text">').appendTo(row); gitEmailInput = $('<input type="text" id="user-settings-gitconfig-email">').appendTo(row);
gitEmailInput.val(currentGitSettings.user.email||""); gitEmailInput.val(currentGitSettings.user.email||"");
} }

View File

@ -1938,13 +1938,16 @@ RED.projects = (function() {
resultCallbackArgs = data; resultCallbackArgs = data;
} }
}).fail(function(xhr,textStatus,err) { }).fail(function(xhr,textStatus,err) {
var responses;
if (options.responses && options.responses[xhr.status]) { if (options.responses && options.responses[xhr.status]) {
var responses = options.responses[xhr.status]; responses = options.responses[xhr.status];
if (typeof responses === 'function') { if (typeof responses === 'function') {
resultCallback = responses; resultCallback = responses;
resultCallbackArgs = {error:responses.statusText}; resultCallbackArgs = {error:responses.statusText};
return; return;
} else if (options.handleAuthFail !== false && xhr.responseJSON.code === 'git_auth_failed') { } else if (options.handleAuthFail !== false && (xhr.responseJSON.code === 'git_auth_failed' || xhr.responseJSON.code === 'git_host_key_verification_failed')) {
if (xhr.responseJSON.code === 'git_auth_failed') {
var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch; var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch;
var message = $('<div>'+ var message = $('<div>'+
@ -2032,6 +2035,25 @@ RED.projects = (function() {
] ]
}); });
return; return;
} else if (xhr.responseJSON.code === 'git_host_key_verification_failed') {
var message = $('<div>'+
'<div class="form-row">'+RED._("projects.send-req.host-key-verify-failed")+'</div>'+
'</div>');
var notification = RED.notify(message,{
type:"error",
fixed: true,
modal: true,
buttons: [
{
text: RED._("common.label.close"),
click: function() {
notification.close();
}
}
]
});
return;
}
} else if (responses[xhr.responseJSON.code]) { } else if (responses[xhr.responseJSON.code]) {
resultCallback = responses[xhr.responseJSON.code]; resultCallback = responses[xhr.responseJSON.code];
resultCallbackArgs = xhr.responseJSON; resultCallbackArgs = xhr.responseJSON;

View File

@ -336,7 +336,7 @@ RED.sidebar.versionControl = (function() {
var unstagedContent = $('<div class="red-ui-sidebar-vc-change-container"></div>').appendTo(localChanges.content); var unstagedContent = $('<div class="red-ui-sidebar-vc-change-container"></div>').appendTo(localChanges.content);
var header = $('<div class="red-ui-sidebar-vc-change-header">'+RED._("sidebar.project.versionControl.localFiles")+'</div>').appendTo(unstagedContent); var header = $('<div class="red-ui-sidebar-vc-change-header">'+RED._("sidebar.project.versionControl.localFiles")+'</div>').appendTo(unstagedContent);
stageAllButton = $('<button class="red-ui-button red-ui-button-small" style="float: right"><i class="fa fa-plus"></i> '+RED._("sidebar.project.versionControl.all")+'</button>') stageAllButton = $('<button class="red-ui-button red-ui-button-small" style="position: absolute; right: 5px; top: 5px;"><i class="fa fa-plus"></i> '+RED._("sidebar.project.versionControl.all")+'</button>')
.appendTo(header) .appendTo(header)
.on("click", function(evt) { .on("click", function(evt) {
evt.preventDefault(); evt.preventDefault();
@ -368,7 +368,7 @@ RED.sidebar.versionControl = (function() {
unmergedContent = $('<div class="red-ui-sidebar-vc-change-container"></div>').appendTo(localChanges.content); unmergedContent = $('<div class="red-ui-sidebar-vc-change-container"></div>').appendTo(localChanges.content);
header = $('<div class="red-ui-sidebar-vc-change-header">'+RED._("sidebar.project.versionControl.unmergedChanges")+'</div>').appendTo(unmergedContent); header = $('<div class="red-ui-sidebar-vc-change-header">'+RED._("sidebar.project.versionControl.unmergedChanges")+'</div>').appendTo(unmergedContent);
bg = $('<div style="float: right"></div>').appendTo(header); bg = $('<div style="position: absolute; right: 5px; top: 5px;"></div>').appendTo(header);
var abortMergeButton = $('<button class="red-ui-button red-ui-button-small" style="margin-right: 5px;">'+RED._("sidebar.project.versionControl.abortMerge")+'</button>') var abortMergeButton = $('<button class="red-ui-button red-ui-button-small" style="margin-right: 5px;">'+RED._("sidebar.project.versionControl.abortMerge")+'</button>')
.appendTo(bg) .appendTo(bg)
.on("click", function(evt) { .on("click", function(evt) {
@ -433,7 +433,7 @@ RED.sidebar.versionControl = (function() {
header = $('<div class="red-ui-sidebar-vc-change-header">'+RED._("sidebar.project.versionControl.changeToCommit")+'</div>').appendTo(stagedContent); header = $('<div class="red-ui-sidebar-vc-change-header">'+RED._("sidebar.project.versionControl.changeToCommit")+'</div>').appendTo(stagedContent);
bg = $('<div style="float: right"></div>').appendTo(header); bg = $('<div style="position: absolute; right: 5px; top: 5px;"></div>').appendTo(header);
var showCommitBox = function() { var showCommitBox = function() {
commitMessage.val(""); commitMessage.val("");
submitCommitButton.prop("disabled",true); submitCommitButton.prop("disabled",true);

View File

@ -25,5 +25,7 @@ RED.state = {
IMPORT_DRAGGING: 8, IMPORT_DRAGGING: 8,
QUICK_JOINING: 9, QUICK_JOINING: 9,
PANNING: 10, PANNING: 10,
SELECTING_NODE: 11 SELECTING_NODE: 11,
GROUP_DRAGGING: 12,
GROUP_RESIZE: 13
} }

View File

@ -567,6 +567,34 @@ RED.subflow = (function() {
return; return;
} }
var i,n; var i,n;
var nodeList = new Set();
var tmplist = selection.nodes.slice();
var includedGroups = new Set();
while(tmplist.length > 0) {
n = tmplist.shift();
if (n.type === "group") {
includedGroups.add(n.id);
tmplist = tmplist.concat(n.nodes);
}
nodeList.add(n);
}
nodeList = Array.from(nodeList);
var containingGroup = nodeList[0].g;
var nodesMovedFromGroup = [];
for (i=0; i<nodeList.length;i++) {
if (nodeList[i].g && !includedGroups.has(nodeList[i].g)) {
if (containingGroup !== nodeList[i].g) {
RED.notify("Cannot create subflow across multiple groups","error");
return;
}
}
}
if (containingGroup) {
containingGroup = RED.nodes.group(containingGroup);
}
var nodes = {}; var nodes = {};
var new_links = []; var new_links = [];
var removedLinks = []; var removedLinks = [];
@ -575,13 +603,13 @@ RED.subflow = (function() {
var candidateOutputs = []; var candidateOutputs = [];
var candidateInputNodes = {}; var candidateInputNodes = {};
var boundingBox = [selection.nodes[0].x, var boundingBox = [nodeList[0].x,
selection.nodes[0].y, nodeList[0].y,
selection.nodes[0].x, nodeList[0].x,
selection.nodes[0].y]; nodeList[0].y];
for (i=0;i<selection.nodes.length;i++) { for (i=0;i<nodeList.length;i++) {
n = selection.nodes[i]; n = nodeList[i];
nodes[n.id] = {n:n,outputs:{}}; nodes[n.id] = {n:n,outputs:{}};
boundingBox = [ boundingBox = [
Math.min(boundingBox[0],n.x), Math.min(boundingBox[0],n.x),
@ -690,6 +718,20 @@ RED.subflow = (function() {
RED.editor.validateNode(subflowInstance); RED.editor.validateNode(subflowInstance);
RED.nodes.add(subflowInstance); RED.nodes.add(subflowInstance);
if (containingGroup) {
RED.group.addToGroup(containingGroup, subflowInstance);
nodeList.forEach(function(nl) {
if (nl.g === containingGroup.id) {
delete nl.g;
var index = containingGroup.nodes.indexOf(nl);
containingGroup.nodes.splice(index,1);
nodesMovedFromGroup.push(nl);
}
})
containingGroup.dirty = true;
}
candidateInputs.forEach(function(l) { candidateInputs.forEach(function(l) {
var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance}; var link = {source:l.source, sourcePort:l.sourcePort, target: subflowInstance};
new_links.push(link); new_links.push(link);
@ -723,8 +765,8 @@ RED.subflow = (function() {
RED.nodes.removeLink(removedLinks[i]); RED.nodes.removeLink(removedLinks[i]);
} }
for (i=0;i<selection.nodes.length;i++) { for (i=0;i<nodeList.length;i++) {
n = selection.nodes[i]; n = nodeList[i];
if (/^link /.test(n.type)) { if (/^link /.test(n.type)) {
n.links = n.links.filter(function(id) { n.links = n.links.filter(function(id) {
var isLocalLink = nodes.hasOwnProperty(id); var isLocalLink = nodes.hasOwnProperty(id);
@ -745,7 +787,8 @@ RED.subflow = (function() {
RED.nodes.moveNodeToTab(n, subflow.id); RED.nodes.moveNodeToTab(n, subflow.id);
} }
RED.history.push({
var historyEvent = {
t:'createSubflow', t:'createSubflow',
nodes:[subflowInstance.id], nodes:[subflowInstance.id],
links:new_links, links:new_links,
@ -759,18 +802,36 @@ RED.subflow = (function() {
removedLinks: removedLinks, removedLinks: removedLinks,
dirty:RED.nodes.dirty() dirty:RED.nodes.dirty()
}); }
RED.view.select(null); if (containingGroup) {
historyEvent = {
t:'multi',
events: [ historyEvent ]
}
historyEvent.events.push({
t:'addToGroup',
group: containingGroup,
nodes: [subflowInstance]
})
historyEvent.events.push({
t:'removeFromGroup',
group: containingGroup,
nodes: nodesMovedFromGroup,
reparent: false
})
}
RED.history.push(historyEvent);
RED.editor.validateNode(subflow); RED.editor.validateNode(subflow);
RED.nodes.dirty(true); RED.nodes.dirty(true);
RED.view.redraw(true); RED.view.updateActive();
RED.view.select(null);
} }
/** /**
* Create interface for controlling env var UI definition * Create interface for controlling env var UI definition
*/ */
function buildEnvControl(envList) { function buildEnvControl(envList,node) {
var tabs = RED.tabs.create({ var tabs = RED.tabs.create({
id: "subflow-env-tabs", id: "subflow-env-tabs",
@ -779,7 +840,7 @@ RED.subflow = (function() {
var inputContainer = $("#subflow-input-ui"); var inputContainer = $("#subflow-input-ui");
var list = envList.editableList("items"); var list = envList.editableList("items");
var exportedEnv = exportEnvList(list, true); var exportedEnv = exportEnvList(list, true);
buildEnvUI(inputContainer, exportedEnv); buildEnvUI(inputContainer, exportedEnv,node);
} }
$("#subflow-env-tabs-content").children().hide(); $("#subflow-env-tabs-content").children().hide();
$("#" + tab.id).show(); $("#" + tab.id).show();
@ -831,6 +892,9 @@ RED.subflow = (function() {
}); });
} }
var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env'];
var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred'];
/** /**
* Create env var edit interface * Create env var edit interface
* @param container - container * @param container - container
@ -841,7 +905,7 @@ RED.subflow = (function() {
var isTemplateNode = (node.type === "subflow"); var isTemplateNode = (node.type === "subflow");
if (isTemplateNode) { if (isTemplateNode) {
buildEnvControl(envContainer); buildEnvControl(envContainer, node);
} }
envContainer envContainer
.css({ .css({
@ -851,6 +915,9 @@ RED.subflow = (function() {
.editableList({ .editableList({
header: isTemplateNode?$('<div><div><div></div><div data-i18n="common.label.name"></div><div data-i18n="editor-tab.defaultValue"></div><div></div></div></div>'):undefined, header: isTemplateNode?$('<div><div><div></div><div data-i18n="common.label.name"></div><div data-i18n="editor-tab.defaultValue"></div><div></div></div></div>'):undefined,
addItem: function(container, i, opt) { addItem: function(container, i, opt) {
// If this is an instance node, these are properties unique to
// this instance - ie opt.parent will not be defined.
if (isTemplateNode) { if (isTemplateNode) {
container.addClass("red-ui-editor-subflow-env-editable") container.addClass("red-ui-editor-subflow-env-editable")
} }
@ -859,9 +926,6 @@ RED.subflow = (function() {
var nameField = null; var nameField = null;
var valueField = null; var valueField = null;
// if (opt.parent) {
// buildEnvUIRow(envRow,opt,opt.parent.ui||{})
// } else {
nameField = $('<input/>', { nameField = $('<input/>', {
class: "node-input-env-name", class: "node-input-env-name",
type: "text", type: "text",
@ -872,16 +936,26 @@ RED.subflow = (function() {
class: "node-input-env-value", class: "node-input-env-value",
type: "text", type: "text",
}).attr("autocomplete","disable").appendTo(envRow) }).attr("autocomplete","disable").appendTo(envRow)
valueField.typedInput({default:'str',types:['str','num','bool','json','bin','env']}); valueField.typedInput({default:'str',types:isTemplateNode?DEFAULT_ENV_TYPE_LIST:DEFAULT_ENV_TYPE_LIST_INC_CRED});
valueField.typedInput('type', opt.parent?(opt.type||opt.parent.type):opt.type); valueField.typedInput('type', opt.type);
valueField.typedInput('value', opt.parent?((opt.value !== undefined)?opt.value:opt.parent.value):opt.value); if (opt.type === "cred") {
// } if (opt.value) {
valueField.typedInput('value', opt.value);
} else if (node.credentials && node.credentials[opt.name]) {
valueField.typedInput('value', node.credentials[opt.name]);
} else if (node.credentials && node.credentials['has_'+opt.name]) {
valueField.typedInput('value', "__PWRD__");
} else {
valueField.typedInput('value', "");
}
} else {
valueField.typedInput('value', opt.value);
}
opt.nameField = nameField; opt.nameField = nameField;
opt.valueField = valueField; opt.valueField = valueField;
if (!opt.parent) {
var actionButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow); var actionButton = $('<a/>',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow);
$('<i/>',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); $('<i/>',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton);
var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove"));
@ -893,18 +967,23 @@ RED.subflow = (function() {
envContainer.editableList('removeItem',opt); envContainer.editableList('removeItem',opt);
}); });
}); });
}
if (isTemplateNode) { if (isTemplateNode) {
// Add the UI customisation row // Add the UI customisation row
// if `opt.ui` does not exist, then apply defaults. If these // if `opt.ui` does not exist, then apply defaults. If these
// defaults do not change then they will get stripped off // defaults do not change then they will get stripped off
// before saving. // before saving.
if (opt.type === 'cred') {
opt.ui = opt.ui || {
icon: "",
type: "cred"
}
} else {
opt.ui = opt.ui || { opt.ui = opt.ui || {
icon: "", icon: "",
label: {},
type: "input", type: "input",
opts: {types:['str','num','bool','json','bin','env']} opts: {types:DEFAULT_ENV_TYPE_LIST}
}
} }
opt.ui.label = opt.ui.label || {}; opt.ui.label = opt.ui.label || {};
opt.ui.type = opt.ui.type || "input"; opt.ui.type = opt.ui.type || "input";
@ -995,11 +1074,11 @@ RED.subflow = (function() {
var row = $('<div></div>').appendTo(container); var row = $('<div></div>').appendTo(container);
$('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row); $('<div><i class="red-ui-editableList-item-handle fa fa-bars"></i></div>').appendTo(row);
var typeOptions = { var typeOptions = {
'input': {types:['str','num','bool','json','bin','env']}, 'input': {types:DEFAULT_ENV_TYPE_LIST},
'select': {opts:[]}, 'select': {opts:[]},
'spinner': {} 'spinner': {},
'cred': {}
}; };
if (ui.opts) { if (ui.opts) {
typeOptions[ui.type] = ui.opts; typeOptions[ui.type] = ui.opts;
@ -1082,9 +1161,10 @@ RED.subflow = (function() {
{value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"}, {value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"},
{value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"}, {value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"},
{value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"}, {value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"},
{value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"} {value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"},
{value: "cred",label: RED._("editor.types.cred"),icon: "fa fa-lock"}
], ],
default: ['str','num','bool','json','bin','env'], default: DEFAULT_ENV_TYPE_LIST,
valueLabel: function(container,value) { valueLabel: function(container,value) {
container.css("padding",0); container.css("padding",0);
var innerContainer = $('<div>').css({ var innerContainer = $('<div>').css({
@ -1097,7 +1177,12 @@ RED.subflow = (function() {
$('<span><i class="fa fa-i-cursor"></i></span>').appendTo(input); $('<span><i class="fa fa-i-cursor"></i></span>').appendTo(input);
if (value.length) { if (value.length) {
value.forEach(function(v) { value.forEach(function(v) {
$('<img>',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 3px"}).appendTo(input); if (!/^fa /.test(v.icon)) {
$('<img>',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input);
} else {
var s = $('<span>',{style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input);
$("<i>",{class: v.icon}).appendTo(s);
}
}) })
} else { } else {
$("<span>").css({ $("<span>").css({
@ -1107,6 +1192,21 @@ RED.subflow = (function() {
} }
} }
}, },
{
value: "cred",
label: RED._("typedInput.type.cred"), icon:"fa fa-lock", showLabel: false,
valueLabel: function(container,value) {
container.css("padding",0);
var innerContainer = $('<div>').css({
"background":"white",
"height":"100%",
"box-sizing": "border-box",
"border-top-right-radius": "4px",
"border-bottom-right-radius": "4px"
}).appendTo(container);
$('<div class="placeholder-input">').html("&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;").appendTo(innerContainer);
}
},
{ {
value:"select", value:"select",
label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false, label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false,
@ -1162,7 +1262,7 @@ RED.subflow = (function() {
// and open the type editor. // and open the type editor.
// - but it leaves the popout panel over the top. // - but it leaves the popout panel over the top.
// - there is no way to get back to the popout panel after closing the type editor // - there is no way to get back to the popout panel after closing the type editor
//.typedInput({default:itemData.t||'str', types:['str','num','bool','json','bin','env']}); //.typedInput({default:itemData.t||'str', types:DEFAULT_ENV_TYPE_LIST});
itemData.input.on('keydown', function(evt) { itemData.input.on('keydown', function(evt) {
if (evt.keyCode === 13) { if (evt.keyCode === 13) {
// Enter or Tab // Enter or Tab
@ -1335,10 +1435,13 @@ RED.subflow = (function() {
valueField.typedInput('types',['bool']); valueField.typedInput('types',['bool']);
break; break;
case 'spinner': case 'spinner':
valueField.typedInput('types',['num']) valueField.typedInput('types',['num']);
break;
case 'cred':
valueField.typedInput('types',['cred']);
break; break;
default: default:
valueField.typedInput('types',['str','num','bool','json','bin','env']) valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST)
} }
if (ui.type === 'checkbox') { if (ui.type === 'checkbox') {
valueField.typedInput('type','bool'); valueField.typedInput('type','bool');
@ -1365,11 +1468,14 @@ RED.subflow = (function() {
inputCellInput.typedInput('type',ui.type) inputCellInput.typedInput('type',ui.type)
} }
function buildEnvUIRow(row, tenv, ui) { function buildEnvUIRow(row, tenv, ui, node) {
ui.label = ui.label||{}; ui.label = ui.label||{};
if (!ui.type) { if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) {
ui.type = "cred";
ui.opts = {};
} else if (!ui.type) {
ui.type = "input"; ui.type = "input";
ui.opts = {types:['str','num','bool','json','bin','env']} ui.opts = {types:DEFAULT_ENV_TYPE_LIST}
} else { } else {
if (!ui.opts) { if (!ui.opts) {
ui.opts = (ui.type === "select") ? {opts:[]} : {}; ui.opts = (ui.type === "select") ? {opts:[]} : {};
@ -1467,6 +1573,24 @@ RED.subflow = (function() {
input.spinner(spinnerOpts).parent().width('70%'); input.spinner(spinnerOpts).parent().width('70%');
input.val(val.value); input.val(val.value);
break; break;
case "cred":
input = $('<input type="password">').css('width','70%').appendTo(row);
if (node.credentials) {
if (node.credentials[tenv.name]) {
input.val(node.credentials[tenv.name]);
} else if (node.credentials['has_'+tenv.name]) {
input.val("__PWRD__")
} else {
input.val("");
}
} else {
input.val("");
}
input.typedInput({
types: ['cred'],
default: 'cred'
})
break;
} }
if (input) { if (input) {
input.attr('id',getSubflowEnvPropertyName(tenv.name)) input.attr('id',getSubflowEnvPropertyName(tenv.name))
@ -1478,7 +1602,7 @@ RED.subflow = (function() {
* @param uiContainer - container for UI * @param uiContainer - container for UI
* @param envList - env var definitions of template * @param envList - env var definitions of template
*/ */
function buildEnvUI(uiContainer, envList) { function buildEnvUI(uiContainer, envList,node) {
uiContainer.empty(); uiContainer.empty();
var elementID = 0; var elementID = 0;
for (var i = 0; i < envList.length; i++) { for (var i = 0; i < envList.length; i++) {
@ -1487,7 +1611,7 @@ RED.subflow = (function() {
continue; continue;
} }
var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer); var row = $("<div/>", { class: "form-row" }).appendTo(uiContainer);
buildEnvUIRow(row,tenv, tenv.ui || {}); buildEnvUIRow(row,tenv, tenv.ui || {}, node);
// console.log(ui); // console.log(ui);
} }
@ -1525,7 +1649,7 @@ RED.subflow = (function() {
// icon: "", // icon: "",
// label: {}, // label: {},
// type: "input", // type: "input",
// opts: {types:['str','num','bool','json','bin','env']} // opts: {types:DEFAULT_ENV_TYPE_LIST}
// } // }
if (!ui.icon) { if (!ui.icon) {
delete ui.icon; delete ui.icon;
@ -1535,13 +1659,19 @@ RED.subflow = (function() {
} }
switch (ui.type) { switch (ui.type) {
case "input": case "input":
if (JSON.stringify(ui.opts) === JSON.stringify({types:['str','num','bool','json','bin','env']})) { if (JSON.stringify(ui.opts) === JSON.stringify({types:DEFAULT_ENV_TYPE_LIST})) {
// This is the default input config. Delete it as it will // This is the default input config. Delete it as it will
// be applied automatically // be applied automatically
delete ui.type; delete ui.type;
delete ui.opts; delete ui.opts;
} }
break; break;
case "cred":
if (envItem.type === "cred") {
delete ui.type;
}
delete ui.opts;
break;
case "select": case "select":
if (ui.opts && $.isEmptyObject(ui.opts.opts)) { if (ui.opts && $.isEmptyObject(ui.opts.opts)) {
// This is the default select config. // This is the default select config.
@ -1585,7 +1715,7 @@ RED.subflow = (function() {
type: env.type, type: env.type,
value: env.value value: env.value
}, },
ui: env.ui ui: $.extend(true,{},env.ui)
} }
envList.push(item); envList.push(item);
parentEnv[env.name] = item; parentEnv[env.name] = item;
@ -1621,13 +1751,17 @@ RED.subflow = (function() {
var item; var item;
var ui = data.ui || {}; var ui = data.ui || {};
if (!ui.type) { if (!ui.type) {
if (data.parent && data.parent.type === "cred") {
ui.type = "cred";
} else {
ui.type = "input"; ui.type = "input";
ui.opts = {types:['str','num','bool','json','bin','env']} ui.opts = {types:DEFAULT_ENV_TYPE_LIST}
}
} else { } else {
ui.opts = ui.opts || {}; ui.opts = ui.opts || {};
} }
var input = $("#"+getSubflowEnvPropertyName(data.name)); var input = $("#"+getSubflowEnvPropertyName(data.name));
if (input.length) { if (input.length || ui.type === "cred") {
item = { name: data.name }; item = { name: data.name };
switch(ui.type) { switch(ui.type) {
case "input": case "input":
@ -1639,6 +1773,10 @@ RED.subflow = (function() {
item.type = 'str'; item.type = 'str';
} }
break; break;
case "cred":
item.value = input.val();
item.type = 'cred';
break;
case "spinner": case "spinner":
item.value = input.val(); item.value = input.val();
item.type = 'num'; item.type = 'num';
@ -1652,7 +1790,7 @@ RED.subflow = (function() {
item.value = ""+input.prop("checked"); item.value = ""+input.prop("checked");
break; break;
} }
if (item.type !== data.parent.type || item.value !== data.parent.value) { if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) {
env.push(item); env.push(item);
} }
} }
@ -1702,14 +1840,17 @@ RED.subflow = (function() {
return defaultLabel; return defaultLabel;
} }
function buildEditForm(container,type,node) { function buildEditForm(type,node) {
if (type === "subflow-template") { if (type === "subflow-template") {
buildPropertiesList($('#node-input-env-container'), node); buildPropertiesList($('#node-input-env-container'), node);
} else if (type === "subflow") { } else if (type === "subflow") {
buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node)); // This gets called by the subflow type `oneditprepare` function
// registered in nodes.js#addSubflow()
buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node), node);
} }
} }
function buildPropertiesForm(container, node) { function buildPropertiesForm(node) {
var container = $('#editor-subflow-envProperties-content');
var form = $('<form class="dialog-form form-horizontal"></form>').appendTo(container); var form = $('<form class="dialog-form form-horizontal"></form>').appendTo(container);
var listContainer = $('<div class="form-row node-input-env-container-row"></div>').appendTo(form); var listContainer = $('<div class="form-row node-input-env-container-row"></div>').appendTo(form);
var list = $('<ol id="red-ui-editor-subflow-env-list" class="red-ui-editor-subflow-env-list"></ol>').appendTo(listContainer); var list = $('<ol id="red-ui-editor-subflow-env-list" class="red-ui-editor-subflow-env-list"></ol>').appendTo(listContainer);

View File

@ -231,7 +231,8 @@ RED.sidebar.context = (function() {
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), { RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
typeHint: data.format, typeHint: data.format,
sourceId: id+"."+k, sourceId: id+"."+k,
tools: tools tools: tools,
path: ""
}).appendTo(propRow.children()[1]); }).appendTo(propRow.children()[1]);
} }
}) })
@ -275,7 +276,8 @@ RED.sidebar.context = (function() {
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), { RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
typeHint: data.format, typeHint: data.format,
sourceId: id+"."+k, sourceId: id+"."+k,
tools: tools tools: tools,
path: ""
}).appendTo(propRow.children()[1]); }).appendTo(propRow.children()[1]);
} }
}); });
@ -295,7 +297,8 @@ RED.sidebar.context = (function() {
RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), { RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), {
typeHint: v.format, typeHint: v.format,
sourceId: id+"."+k, sourceId: id+"."+k,
tools: tools tools: tools,
path: ""
}).appendTo(propRow.children()[1]); }).appendTo(propRow.children()[1]);
if (contextStores.length > 1) { if (contextStores.length > 1) {
$("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0])) $("<span>",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0]))

View File

@ -15,17 +15,6 @@
**/ **/
RED.sidebar.info = (function() { RED.sidebar.info = (function() {
marked.setOptions({
renderer: new marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
sanitize: true,
smartLists: true,
smartypants: false
});
var content; var content;
var sections; var sections;
var propertiesSection; var propertiesSection;
@ -163,7 +152,8 @@ RED.sidebar.info = (function() {
var types = { var types = {
nodes:0, nodes:0,
flows:0, flows:0,
subflows:0 subflows:0,
groups: 0
} }
node.forEach(function(n) { node.forEach(function(n) {
if (n.type === 'tab') { if (n.type === 'tab') {
@ -171,6 +161,8 @@ RED.sidebar.info = (function() {
types.nodes += RED.nodes.filterNodes({z:n.id}).length; types.nodes += RED.nodes.filterNodes({z:n.id}).length;
} else if (n.type === 'subflow') { } else if (n.type === 'subflow') {
types.subflows++; types.subflows++;
} else if (n.type === 'group') {
types.groups++;
} else { } else {
types.nodes++; types.nodes++;
} }
@ -190,6 +182,9 @@ RED.sidebar.info = (function() {
if (types.nodes > 0) { if (types.nodes > 0) {
$('<div>').text(RED._("clipboard.node",{count:types.nodes})).appendTo(counts); $('<div>').text(RED._("clipboard.node",{count:types.nodes})).appendTo(counts);
} }
if (types.groups > 0) {
$('<div>').text(RED._("clipboard.group",{count:types.groups})).appendTo(counts);
}
} else { } else {
// A single 'thing' selected. // A single 'thing' selected.
@ -220,6 +215,36 @@ RED.sidebar.info = (function() {
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.status")+'</td><td></td></tr>').appendTo(tableBody); propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.status")+'</td><td></td></tr>').appendTo(tableBody);
$(propRow.children()[1]).text((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled")) $(propRow.children()[1]).text((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled"))
} }
} else if (node.type === "group") {
// An actual node is selected in the editor - build up its properties table
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.group")+"</td><td></td></tr>").appendTo(tableBody);
RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]);
if (node.name) {
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("common.label.name")+'</td><td></td></tr>').appendTo(tableBody);
$('<span class="red-ui-text-bidi-aware" dir="'+RED.text.bidi.resolveBaseTextDir(node.name)+'"></span>').text(node.name).appendTo(propRow.children()[1]);
}
propRow = $('<tr class="red-ui-help-info-row"><td>&nbsp;</td><td></td></tr>').appendTo(tableBody);
var typeCounts = {
nodes:0,
groups: 0
}
var allNodes = RED.group.getNodes(node,true);
allNodes.forEach(function(n) {
if (n.type === "group") {
typeCounts.groups++;
} else {
typeCounts.nodes++
}
});
var counts = $('<div>').appendTo($(propRow.children()[1]));
if (typeCounts.nodes > 0) {
$('<div>').text(RED._("clipboard.node",{count:typeCounts.nodes})).appendTo(counts);
}
if (typeCounts.groups > 0) {
$('<div>').text(RED._("clipboard.group",{count:typeCounts.groups})).appendTo(counts);
}
} else { } else {
// An actual node is selected in the editor - build up its properties table // An actual node is selected in the editor - build up its properties table
propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.node")+"</td><td></td></tr>").appendTo(tableBody); propRow = $('<tr class="red-ui-help-info-row"><td>'+RED._("sidebar.info.node")+"</td><td></td></tr>").appendTo(tableBody);
@ -236,7 +261,7 @@ RED.sidebar.info = (function() {
} }
} }
var count = 0; var count = 0;
if (!m && node.type != "subflow") { if (!m && node.type != "subflow" && node.type != "group") {
var defaults; var defaults;
if (node.type === 'unknown') { if (node.type === 'unknown') {
defaults = {}; defaults = {};
@ -314,7 +339,7 @@ RED.sidebar.info = (function() {
if (subflowNode && node.type !== "subflow") { if (subflowNode && node.type !== "subflow") {
// Selected a subflow instance node. // Selected a subflow instance node.
// - The subflow template info goes into help // - The subflow template info goes into help
helpText = (marked(subflowNode.info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>')); helpText = (RED.utils.renderMarkdown(subflowNode.info||"")||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>'));
} else { } else {
helpText = $("script[data-help-name='"+node.type+"']").html()||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>'); helpText = $("script[data-help-name='"+node.type+"']").html()||('<span class="red-ui-help-info-none">'+RED._("sidebar.info.none")+'</span>');
} }
@ -326,10 +351,10 @@ RED.sidebar.info = (function() {
if (node._def && node._def.info) { if (node._def && node._def.info) {
var info = node._def.info; var info = node._def.info;
var textInfo = (typeof info === "function" ? info.call(node) : info); var textInfo = (typeof info === "function" ? info.call(node) : info);
infoText = infoText + marked(textInfo); infoText = infoText + RED.utils.renderMarkdown(textInfo);
} }
if (node.info) { if (node.info) {
infoText = infoText + marked(node.info || "") infoText = infoText + RED.utils.renderMarkdown(node.info || "")
} }
setInfoText(infoText, infoSection.content); setInfoText(infoText, infoSection.content);

View File

@ -16,6 +16,28 @@
RED.utils = (function() { RED.utils = (function() {
window._marked = window.marked;
window.marked = function(txt) {
console.warn("Use of 'marked()' is deprecated. Use RED.utils.renderMarkdown() instead");
return renderMarkdown(txt);
}
_marked.setOptions({
renderer: new _marked.Renderer(),
gfm: true,
tables: true,
breaks: false,
pedantic: false,
smartLists: true,
smartypants: false
});
function renderMarkdown(txt) {
var rendered = _marked(txt);
var cleaned = DOMPurify.sanitize(rendered, {SAFE_FOR_JQUERY: true})
return cleaned;
}
function formatString(str) { function formatString(str) {
return str.replace(/\r?\n/g,"&crarr;").replace(/\t/g,"&rarr;"); return str.replace(/\r?\n/g,"&crarr;").replace(/\t/g,"&rarr;");
} }
@ -1053,6 +1075,7 @@ RED.utils = (function() {
decodeObject: decodeObject, decodeObject: decodeObject,
parseContextKey: parseContextKey, parseContextKey: parseContextKey,
createIconElement: createIconElement, createIconElement: createIconElement,
sanitize: sanitize sanitize: sanitize,
renderMarkdown: renderMarkdown
} }
})(); })();

View File

@ -67,9 +67,16 @@ RED.view.tools = (function() {
function moveSelection(dx,dy) { function moveSelection(dx,dy) {
if (moving_set === null) { if (moving_set === null) {
moving_set = [];
var selection = RED.view.selection(); var selection = RED.view.selection();
if (selection.nodes) { if (selection.nodes) {
moving_set = selection.nodes.map(function(n) { return {n:n}}); while (selection.nodes.length > 0) {
var n = selection.nodes.shift();
moving_set.push({n:n});
if (n.type === "group") {
selection.nodes = selection.nodes.concat(n.nodes);
}
}
} }
} }
if (moving_set && moving_set.length > 0) { if (moving_set && moving_set.length > 0) {
@ -93,6 +100,9 @@ RED.view.tools = (function() {
node.n.x += dx; node.n.x += dx;
node.n.y += dy; node.n.y += dy;
node.n.dirty = true; node.n.dirty = true;
if (node.n.type === "group") {
RED.group.markDirty(node.n);
}
minX = Math.min(node.n.x-node.n.w/2-5,minX); minX = Math.min(node.n.x-node.n.w/2-5,minX);
minY = Math.min(node.n.y-node.n.h/2-5,minY); minY = Math.min(node.n.y-node.n.h/2-5,minY);
} }
@ -105,6 +115,8 @@ RED.view.tools = (function() {
} }
} }
RED.view.redraw(); RED.view.redraw();
} else {
RED.view.scroll(dx*10,dy*10);
} }
} }
@ -112,6 +124,16 @@ RED.view.tools = (function() {
init: function() { init: function() {
RED.actions.add("core:align-selection-to-grid", alignToGrid); RED.actions.add("core:align-selection-to-grid", alignToGrid);
RED.actions.add("core:scroll-view-up", function() { RED.view.scroll(0,-RED.view.gridSize());});
RED.actions.add("core:scroll-view-right", function() { RED.view.scroll(RED.view.gridSize(),0);});
RED.actions.add("core:scroll-view-down", function() { RED.view.scroll(0,RED.view.gridSize());});
RED.actions.add("core:scroll-view-left", function() { RED.view.scroll(-RED.view.gridSize(),0);});
RED.actions.add("core:step-view-up", function() { RED.view.scroll(0,-5*RED.view.gridSize());});
RED.actions.add("core:step-view-right", function() { RED.view.scroll(5*RED.view.gridSize(),0);});
RED.actions.add("core:step-view-down", function() { RED.view.scroll(0,5*RED.view.gridSize());});
RED.actions.add("core:step-view-left", function() { RED.view.scroll(-5*RED.view.gridSize(),0);});
RED.actions.add("core:move-selection-up", function() { moveSelection(0,-1);}); RED.actions.add("core:move-selection-up", function() { moveSelection(0,-1);});
RED.actions.add("core:move-selection-right", function() { moveSelection(1,0);}); RED.actions.add("core:move-selection-right", function() { moveSelection(1,0);});
RED.actions.add("core:move-selection-down", function() { moveSelection(0,1);}); RED.actions.add("core:move-selection-down", function() { moveSelection(0,1);});

File diff suppressed because it is too large Load Diff

View File

@ -9,19 +9,15 @@
color: transparent !important; color: transparent !important;
} }
} }
.ace_gutter { .ace_gutter {
background: $text-editor-gutter-background;
border-top-left-radius: 4px; border-top-left-radius: 4px;
border-bottom-left-radius: 4px; border-bottom-left-radius: 4px;
} }
.ace_scroller { .ace_scroller {
background: $text-editor-background;
border-top-right-radius: 4px; border-top-right-radius: 4px;
border-bottom-right-radius: 4px; border-bottom-right-radius: 4px;
}
.ace_scroller {
background: $text-editor-background;
color: $text-editor-color; color: $text-editor-color;
} }
.ace_marker-layer .ace_active-line { .ace_marker-layer .ace_active-line {
@ -37,9 +33,6 @@
.ace_gutter-active-line { .ace_gutter-active-line {
background: $text-editor-gutter-active-line-background; background: $text-editor-gutter-active-line-background;
} }
.ace_gutter {
background: $text-editor-gutter-background;
}
.ace_tooltip { .ace_tooltip {
font-family: $primary-font; font-family: $primary-font;
line-height: 1.4em; line-height: 1.4em;
@ -52,6 +45,10 @@
@include component-shadow; @include component-shadow;
border-color: $popover-background; border-color: $popover-background;
} }
textarea.ace_text-input {
overflow: hidden;
padding: 0px 1px !important;
}
#red-ui-event-log-editor { #red-ui-event-log-editor {
.ace_scroller { .ace_scroller {

View File

@ -284,3 +284,8 @@ $debug-message-border: #eee;
$debug-message-border-hover: #999; $debug-message-border-hover: #999;
$debug-message-border-warning: #ffdf9d; $debug-message-border-warning: #ffdf9d;
$debug-message-border-error: #f99; $debug-message-border-error: #f99;
$group-default-fill: none;
$group-default-fill-opacity: 1;
$group-default-stroke: #999;
$group-default-stroke-opacity: 1;

View File

@ -160,6 +160,7 @@
.red-ui-debug-msg-element { .red-ui-debug-msg-element {
color: $debug-message-text-color; color: $debug-message-text-color;
line-height: 1.3em; line-height: 1.3em;
overflow-wrap: break-word;
} }
.red-ui-debug-msg-object-key { .red-ui-debug-msg-object-key {
color: $debug-message-text-color-object-key; color: $debug-message-text-color-object-key;

View File

@ -411,6 +411,133 @@ button.red-ui-button.red-ui-editor-node-appearance-button {
} }
} }
.red-ui-group-layout-picker {
padding: 5px;
background: $primary-background;
}
.red-ui-group-layout-picker-cell-text {
position: absolute;
width: 14px;
height: 2px;
border-top: 2px solid $secondary-text-color;
border-bottom: 2px solid $secondary-text-color;
margin: 2px;
&.red-ui-group-layout-text-pos-nw { top: 0; left: 0; }
&.red-ui-group-layout-text-pos-n { top: 0; left: calc(50% - 9px); }
&.red-ui-group-layout-text-pos-ne { top: 0; right: 0; }
&.red-ui-group-layout-text-pos-sw { bottom: 0; left: 0; }
&.red-ui-group-layout-text-pos-s { bottom: 0; left: calc(50% - 9px); }
&.red-ui-group-layout-text-pos-se { bottom: 0; right: 0; }
&.red-ui-group-layout-text-pos- {
width: 100%;
height: 100%;
border-radius: 5px;
margin: 0;
background-color: #FFF;
background-size: 100% 100%;
background-position: 0 0, 50% 50%;
background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent);
border: none;
}
}
.red-ui-group-layout-picker button.red-ui-search-result-node {
float: none;
position: relative;
padding: 0;
margin: 5px;
width: 32px;
height: 27px;
}
button.red-ui-group-layout-picker-none {
width: 100%;
}
.red-ui-color-picker {
input[type="text"] {
border-radius:0;
width: 100%;
margin-bottom: 0;
border: none;
border-bottom: 1px solid $form-input-border-color;
}
small {
color: $secondary-text-color;
margin-left: 5px;
margin-right: 4px;
display: inline-block;
min-width: 35px;
text-align: right;
}
background: $primary-background;
}
.red-ui-editor-node-appearance-button {
.red-ui-search-result-node {
overflow: hidden
}
}
.red-ui-color-picker-cell {
padding: 0;
border-style: solid;
border-width: 1px;
border-color: $secondary-border-color;
}
.red-ui-color-picker-swatch {
position: absolute;
top:-1px;right:-1px;left:-1px;bottom:-1px;
border-radius: 4px;
}
.red-ui-color-picker-cell-none {
height: 100%;
background-color: #FFF;
background-size: 100% 100%;
background-position: 0 0, 50% 50%;
background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent)
}
.red-ui-search-result-node .red-ui-color-picker-cell-none {
border-radius: 4px;
background-size: 50% 50%;
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
}
.red-ui-color-picker-opacity-slider {
position:relative;
vertical-align: middle;
display: inline-block;
width: calc(100% - 50px);
height: 14px;
margin: 6px 3px 8px;
box-sizing: border-box;
background-color: white;
background-image:
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 25%),
linear-gradient(-45deg, #eee 25%, transparent 25%, transparent 75%, #eee 25%);
background-size: 6px 6px;
}
.red-ui-color-picker-opacity-slider-overlay {
position: absolute;
top:0;right:0;left:0;bottom:0;
background-image:linear-gradient(90deg, transparent 0%, #f00 100%);
background-size: 100% 100%;
border: 1px solid $primary-border-color;
}
div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle {
z-Index: 10;
top: -4px;
cursor: pointer;
min-width: 0;
width: 10px;
height: 22px;
padding: 0;
border: 1px solid $primary-border-color;
border-radius: 1px;
background: $secondary-background;
box-sizing: border-box;
}
.red-ui-icon-picker { .red-ui-icon-picker {
select { select {
box-sizing: border-box; box-sizing: border-box;

View File

@ -71,6 +71,48 @@
} }
} }
.red-ui-flow-group {
&.red-ui-flow-group-hovered {
.red-ui-flow-group-outline-select {
stroke-opacity: 0.8 !important;
stroke-dasharray: 10 4 !important;
}
}
&.red-ui-flow-group-active-hovered:not(.red-ui-flow-group-hovered) {
.red-ui-flow-group-outline-select {
stroke: $link-link-color;
}
}
}
.red-ui-flow-group-outline {
fill: none;
stroke: $node-selected-color;
stroke-opacity: 0;
stroke-width: 12;
pointer-events: stroke;
}
.red-ui-flow-group-outline-select {
fill: none;
stroke: $node-selected-color;
pointer-events: stroke;
stroke-opacity: 0;
stroke-width: 3;
}
.red-ui-flow-group-body {
pointer-events: none;
fill: $group-default-fill;
fill-opacity: $group-default-fill-opacity;
stroke-width: 2;
stroke: $group-default-stroke;
stroke-opacity: $group-default-stroke-opacity;
}
.red-ui-flow-group-label {
@include disable-selection;
}
.red-ui-flow-node-unknown { .red-ui-flow-node-unknown {
stroke-dasharray:10,4; stroke-dasharray:10,4;
stroke: $node-border-unknown; stroke: $node-border-unknown;
@ -145,8 +187,8 @@ g.red-ui-flow-node-selected {
border-color: $node-selected-color !important; border-color: $node-selected-color !important;
border-style: dashed !important; border-style: dashed !important;
stroke: $node-selected-color; stroke: $node-selected-color;
stroke-width: 2; stroke-width: 3;
stroke-dasharray: 8, 3; stroke-dasharray: 8, 4;
} }
.red-ui-flow-subflow .red-ui-flow-node { .red-ui-flow-subflow .red-ui-flow-node {
@ -248,6 +290,7 @@ g.red-ui-flow-node-selected {
.red-ui-flow-link-outline { .red-ui-flow-link-outline {
stroke: $view-background; stroke: $view-background;
stroke-opacity: 0.4;
stroke-width: 5; stroke-width: 5;
cursor: crosshair; cursor: crosshair;
fill: none; fill: none;

View File

@ -27,9 +27,22 @@
display: none; display: none;
} }
} }
.red-ui-info-table {
table-layout: fixed;
}
table.red-ui-info-table tr:not(.blank) td:first-child {
width: 30%;
}
table.red-ui-info-table tr:not(.blank) td:last-child {
vertical-align: top;
}
} }
.red-ui-sidebar-context-property { .red-ui-sidebar-context-property {
overflow-wrap: break-word;
position: relative; position: relative;
.red-ui-debug-msg-tools { .red-ui-debug-msg-tools {
right: 0px; right: 0px;

View File

@ -32,6 +32,9 @@
right: 5px; right: 5px;
top: 9px; top: 9px;
} }
form.red-ui-searchBox-form {
margin: 0;
}
input.red-ui-searchBox-input { input.red-ui-searchBox-input {
border-radius: 0; border-radius: 0;
border: none; border: none;

View File

@ -56,7 +56,10 @@
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
.red-ui-typedInput-value-label-inactive {
background: $secondary-background-disabled;
color: $secondary-text-color-disabled;
}
} }
} }
.red-ui-typedInput-options { .red-ui-typedInput-options {
@ -123,7 +126,7 @@ button.red-ui-typedInput-option-trigger
} }
&.disabled { &.disabled {
cursor: default; cursor: default;
i.red-ui-typedInput-icon { > i.red-ui-typedInput-icon {
color: $secondary-text-color-disabled; color: $secondary-text-color-disabled;
} }
} }

View File

@ -112,7 +112,7 @@
position: absolute; position: absolute;
bottom: 0; bottom: 0;
right:0; right:0;
zIndex: 101; z-index: 101;
border-left: 1px solid $primary-border-color; border-left: 1px solid $primary-border-color;
border-top: 1px solid $primary-border-color; border-top: 1px solid $primary-border-color;
background: $view-navigator-background; background: $view-navigator-background;
@ -122,7 +122,7 @@
stroke-dasharray: 5,5; stroke-dasharray: 5,5;
pointer-events: none; pointer-events: none;
stroke: $secondary-border-color; stroke: $secondary-border-color;
strokeWidth: 1; stroke-width: 1;
fill: $view-background; fill: $view-background;
} }

View File

@ -11,6 +11,7 @@
var length = str.length; var length = str.length;
var start = 0; var start = 0;
var inString = false; var inString = false;
var inRegex = false;
var inBox = false; var inBox = false;
var quoteChar; var quoteChar;
var list = []; var list = [];
@ -24,8 +25,13 @@
} }
for (var i=0;i<length;i++) { for (var i=0;i<length;i++) {
var c = str[i]; var c = str[i];
if (!inString) { if (!inString && !inRegex) {
if (c === "'" || c === '"') { if (c === "/") {
inRegex = true;
frame = {type:"regex",pos:i};
list.push(frame);
stack.push(frame);
} else if (c === "'" || c === '"') {
inString = true; inString = true;
quoteChar = c; quoteChar = c;
frame = {type:"string",pos:i}; frame = {type:"string",pos:i};
@ -37,6 +43,9 @@
} else if (c === ",") { } else if (c === ",") {
frame = {type:",",pos:i}; frame = {type:",",pos:i};
list.push(frame); list.push(frame);
} else if (c === "&") {
frame = {type:"&",pos:i};
list.push(frame);
} else if (/[\(\[\{]/.test(c)) { } else if (/[\(\[\{]/.test(c)) {
frame = {type:"open-block",char:c,pos:i}; frame = {type:"open-block",char:c,pos:i};
list.push(frame); list.push(frame);
@ -45,6 +54,7 @@
var oldFrame = stack.pop(); var oldFrame = stack.pop();
if (matchingBrackets[oldFrame.char] !== c) { if (matchingBrackets[oldFrame.char] !== c) {
// console.log("Stack frame mismatch",c,"at",i,"expected",matchingBrackets[oldFrame.char],"from",oldFrame.pos); // console.log("Stack frame mismatch",c,"at",i,"expected",matchingBrackets[oldFrame.char],"from",oldFrame.pos);
// console.log(list);
return str; return str;
} }
//console.log("Closing",c,"at",i,"compare",oldFrame.type,oldFrame.pos); //console.log("Closing",c,"at",i,"compare",oldFrame.type,oldFrame.pos);
@ -53,19 +63,32 @@
list.push(frame); list.push(frame);
} }
} else { } else {
if (c === "\\") {
// an escaped char - stay in current mode and skip the next char
i++;
}
if (inString) {
if (c === quoteChar) { if (c === quoteChar) {
// Next char must be a ] // Next char must be a ]
inString = false; inString = false;
stack.pop(); var f = stack.pop();
f.end = i;
}
} else if (inRegex) {
if (c === "/") {
inRegex = false;
var f = stack.pop();
f.end = i;
} }
} }
}
}
// console.log("list",list);
}
// console.log(stack);
var result = str; var result = str;
var indent = 0; var indent = 0;
var offset = 0; var offset = 0;
var pre,post,indented; var pre,post,indented,hasNewline;
var longStack = []; var longStack = [];
list.forEach(function(f) { list.forEach(function(f) {
if (f.type === ";" || f.type === ",") { if (f.type === ";" || f.type === ",") {
@ -73,30 +96,52 @@
pre = result.substring(0,offset+f.pos+1); pre = result.substring(0,offset+f.pos+1);
post = result.substring(offset+f.pos+1); post = result.substring(offset+f.pos+1);
indented = indentLine(post,indent); indented = indentLine(post,indent);
result = pre+"\n"+indented; hasNewline = /\n$/.test(pre);
offset += indented.length-post.length+1; // console.log("A§"+pre+"§\n§"+indented+"§",hasNewline);
result = pre+(hasNewline?"":"\n")+indented;
offset += indented.length-post.length+(hasNewline?0:1);
} }
} else if (f.type === "&") {
pre = result.substring(0,offset+f.pos+1);
var lastLineBreak = pre.lastIndexOf("\n");
var lineLength = pre.length - lastLineBreak;
if (lineLength > 70) {
post = result.substring(offset+f.pos+1);
if (!/^\n/.test(post)) {
indented = indentLine(post,indent);
hasNewline = /\n$/.test(pre);
result = pre+(hasNewline?"":"\n")+indented;
offset += indented.length-post.length+(hasNewline?0:1);
}
}
} else if (f.type === "open-block") { } else if (f.type === "open-block") {
if (f.width > 30) { if (f.width > 40) {
longStack.push(true); longStack.push(true);
indent += 4; indent += 4;
pre = result.substring(0,offset+f.pos+1); pre = result.substring(0,offset+f.pos+1);
post = result.substring(offset+f.pos+1); post = result.substring(offset+f.pos+1);
hasNewline = /\n$/.test(pre);
indented = indentLine(post,indent); indented = indentLine(post,indent);
result = pre+"\n"+indented; result = pre+(hasNewline?"":"\n")+indented;
offset += indented.length-post.length+1; offset += indented.length-post.length+(hasNewline?0:1);
} else { } else {
longStack.push(false); longStack.push(false);
} }
} else if (f.type === "close-block") { } else if (f.type === "close-block") {
if (f.width > 30) { if (f.width > 40) {
indent -= 4; indent -= 4;
pre = result.substring(0,offset+f.pos); pre = result.substring(0,offset+f.pos);
post = result.substring(offset+f.pos); post = result.substring(offset+f.pos);
indented = indentLine(post,indent); indented = indentLine(post,indent);
hasNewline = /\n *$/.test(pre);
if (hasNewline) {
result = pre + post;
} else {
result = pre+"\n"+indented; result = pre+"\n"+indented;
offset += indented.length-post.length+1; offset += indented.length-post.length+1;
} }
}
longStack.pop(); longStack.pop();
} }
}) })
@ -171,6 +216,7 @@
'$sum':{ args:[ 'array' ]}, '$sum':{ args:[ 'array' ]},
'$toMillis':{args:['timestamp']}, // <------------- '$toMillis':{args:['timestamp']}, // <-------------
'$trim':{ args:[ 'str' ]}, '$trim':{ args:[ 'str' ]},
'$type':{ args:['value']},
'$uppercase':{ args:[ 'str' ]}, '$uppercase':{ args:[ 'str' ]},
'$zip':{ args:[ 'array1' ]} '$zip':{ args:[ 'array1' ]}
} }

View File

@ -28,6 +28,11 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m
}, "identifier"); }, "identifier");
this.$rules = { this.$rules = {
"start" : [ "start" : [
{
token: "string.regexp",
regex: "\\/",
next: "regex"
},
{ {
token : "string", token : "string",
regex : "'(?=.)", regex : "'(?=.)",
@ -46,7 +51,8 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m
token : "constant.numeric", // float token : "constant.numeric", // float
regex : /[+-]?\d[\d_]*(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/ regex : /[+-]?\d[\d_]*(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/
}, },
{ token: "keyword", {
token: "keyword",
regex: /λ/ regex: /λ/
}, },
{ {
@ -86,7 +92,8 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m
token : "string", token : "string",
regex : '"|$', regex : '"|$',
next : "start" next : "start"
}, { },
{
defaultToken: "string" defaultToken: "string"
} }
], ],
@ -95,9 +102,24 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m
token : "string", token : "string",
regex : "'|$", regex : "'|$",
next : "start" next : "start"
}, { },
{
defaultToken: "string" defaultToken: "string"
} }
],
"regex" : [
{
token: "string.regexp",
regex: "\\\\/"
},
{
token: "string.regexp",
regex: "/[sxngimy]*",
next: "start"
},
{
defaultToken: "string.regexp"
}
] ]
}; };
}; };

File diff suppressed because one or more lines are too long

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="inject"> <script type="text/html" data-template-name="inject">
<div class="form-row"> <div class="form-row">
<label for="node-input-payload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label> <label for="node-input-payload"><i class="fa fa-envelope"></i> <span data-i18n="common.label.payload"></span></label>
<input type="text" id="node-input-payload" style="width:70%"> <input type="text" id="node-input-payload" style="width:70%">

View File

@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="debug"> <script type="text/html" data-template-name="debug">
<div class="form-row"> <div class="form-row">
<label for="node-input-typed-complete"><i class="fa fa-list"></i> <span data-i18n="debug.output"></span></label> <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-typed-complete" type="text" style="width: 70%">
@ -131,8 +131,32 @@
RED.view.redraw(); RED.view.redraw();
} }
}, },
messageSourceClick: function(sourceId) { messageSourceClick: function(sourceId, aliasId, path) {
RED.view.reveal(sourceId); // Get all of the nodes that could have logged this message
var candidateNodes = [RED.nodes.node(sourceId)]
if (path) {
for (var i=2;i<path.length;i++) {
candidateNodes.push(RED.nodes.node(path[i]))
}
}
if (aliasId) {
candidateNodes.push(RED.nodes.node(aliasId));
}
if (candidateNodes.length > 1) {
// The node is in a subflow. Check to see if the active
// workspace is a subflow in the node's parentage. If
// so, reveal the relevant subflow instance node.
var ws = RED.workspaces.active();
for (var i=0;i<candidateNodes.length;i++) {
if (candidateNodes[i].z === ws) {
RED.view.reveal(candidateNodes[i].id);
return
}
}
// The active workspace is unrelated to the node. So
// fall back to revealing the top most node
}
RED.view.reveal(candidateNodes[0].id);
}, },
clear: function() { clear: function() {
RED.nodes.eachNode(function(node) { RED.nodes.eachNode(function(node) {
@ -179,9 +203,44 @@
RED.events.on("workspace:change", this.refreshMessageList); RED.events.on("workspace:change", this.refreshMessageList);
this.handleDebugMessage = function(t,o) { this.handleDebugMessage = function(t,o) {
var sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z); // console.log("->",o.id,o.z,o._alias);
//
// sourceNode should be the top-level node - one that is on a flow.
var sourceNode;
var pathParts;
if (o.path) {
// Path is a `/`-separated list of ids that identifies the
// complete parentage of the node that generated this message.
// flow-id/subflow-A-instance/subflow-A-type/subflow-B-instance/subflow-B-type/node-id
// If it has one id, that is a top level flow
// each subsequent id is the instance id of a subflow node
//
pathParts = o.path.split("/");
if (pathParts.length === 1) {
// The source node is on a flow - so can use its id to find
sourceNode = RED.nodes.node(o.id);
} else if (pathParts.length > 1) {
// Highlight the subflow instance node.
sourceNode = RED.nodes.node(pathParts[1]);
}
} else {
// This is probably redundant...
sourceNode = RED.nodes.node(o.id) || RED.nodes.node(o.z);
}
if (sourceNode) { if (sourceNode) {
o._source = {id:sourceNode.id,z:sourceNode.z,name:sourceNode.name,type:sourceNode.type,_alias:o._alias}; o._source = {
id:sourceNode.id,
z:sourceNode.z,
name:sourceNode.name,
type:sourceNode.type,
// _alias identifies the actual logging node. This is
// not necessarily the same as sourceNode, which will be
// the top-level subflow instance node.
// This means the node's name is displayed in the sidebar.
_alias:o._alias,
path: pathParts
};
} }
RED.debug.handleDebugMessage(o); RED.debug.handleDebugMessage(o);
if (subWindow) { if (subWindow) {
@ -235,7 +294,7 @@
} else if (msg.event === "mouseLeave") { } else if (msg.event === "mouseLeave") {
options.messageMouseLeave(msg.id); options.messageMouseLeave(msg.id);
} else if (msg.event === "mouseClick") { } else if (msg.event === "mouseClick") {
options.messageSourceClick(msg.id); options.messageSourceClick(msg.id,msg._alias,msg.path);
} else if (msg.event === "clear") { } else if (msg.event === "clear") {
options.clear(); options.clear();
} }

View File

@ -62,7 +62,7 @@ module.exports = function(RED) {
if (err) { if (err) {
done(RED._("debug.invalid-exp", {error: editExpression})); done(RED._("debug.invalid-exp", {error: editExpression}));
} else { } else {
done(null,{id:node.id, z:node.z, name:node.name, topic:msg.topic, msg:value, _path:msg._path}); done(null,{id:node.id, z:node.z, _alias: node._alias, path:node._flow.path, name:node.name, topic:msg.topic, msg:value});
} }
}); });
} else { } else {
@ -77,7 +77,7 @@ module.exports = function(RED) {
output = undefined; output = undefined;
} }
} }
done(null,{id:node.id, z:node.z, name:node.name, topic:msg.topic, property:property, msg:output, _path:msg._path}); done(null,{id:node.id, z:node.z, _alias: node._alias, path:node._flow.path, name:node.name, topic:msg.topic, property:property, msg:output});
} }
} }
@ -88,7 +88,7 @@ module.exports = function(RED) {
node.log("\n"+util.inspect(msg, {colors:useColors, depth:10})); node.log("\n"+util.inspect(msg, {colors:useColors, depth:10}));
} }
if (this.active && this.tosidebar) { if (this.active && this.tosidebar) {
sendDebug({id:node.id, z:node.z, name:node.name, topic:msg.topic, msg:msg, _path:msg._path}); sendDebug({id:node.id, z:node.z, _alias: node._alias, path:node._flow.path, name:node.name, topic:msg.topic, msg:msg});
} }
done(); done();
} else { } else {

View File

@ -1,4 +1,4 @@
<script type="text/x-red" data-template-name="complete"> <script type="text/html" data-template-name="complete">
<div class="form-row node-input-target-row"> <div class="form-row node-input-target-row">
<button id="node-input-complete-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button> <button id="node-input-complete-target-select" class="red-ui-button" data-i18n="common.label.selectNodes"></button>
</div> </div>

View File

@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="catch"> <script type="text/html" data-template-name="catch">
<div class="form-row"> <div class="form-row">
<label style="width: auto" for="node-input-scope" data-i18n="catch.label.source"></label> <label style="width: auto" for="node-input-scope" data-i18n="catch.label.source"></label>
<select id="node-input-scope-select"> <select id="node-input-scope-select">

View File

@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="status"> <script type="text/html" data-template-name="status">
<div class="form-row"> <div class="form-row">
<label style="width: auto" for="node-input-scope" data-i18n="status.label.source"></label> <label style="width: auto" for="node-input-scope" data-i18n="status.label.source"></label>
<select id="node-input-scope-select"> <select id="node-input-scope-select">

View File

@ -1,12 +1,12 @@
<script type="text/x-red" data-template-name="link in"> <script type="text/html" data-template-name="link in">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></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" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
</div> </div>
<div class="form-row node-input-link-row"></div> <div class="form-row node-input-link-row"></div>
</script> </script>
<script type="text/x-red" data-template-name="link out"> <script type="text/html" data-template-name="link out">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></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" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">

View File

@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="comment"> <script type="text/html" data-template-name="comment">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></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" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">

View File

@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="unknown"> <script type="text/html" data-template-name="unknown">
<div class="form-tips"><span data-i18n="[html]unknown.tip"></span></div> <div class="form-tips"><span data-i18n="[html]unknown.tip"></span></div>
</script> </script>

View File

@ -406,10 +406,16 @@ RED.debug = (function() {
msg.on("mouseenter", function() { msg.on("mouseenter", function() {
msg.addClass('red-ui-debug-msg-hover'); msg.addClass('red-ui-debug-msg-hover');
if (o._source) { if (o._source) {
// highlight the top-level node (could be subflow instance)
config.messageMouseEnter(o._source.id); config.messageMouseEnter(o._source.id);
if (o._source._alias) { if (o._source._alias) {
// this is inside a subflow - highlight the node itself
config.messageMouseEnter(o._source._alias); config.messageMouseEnter(o._source._alias);
} }
// if path.length > 2, we are nested - highlight subflow instances
for (var i=2;i<o._source.path.length;i++) {
config.messageMouseEnter(o._source.path[i]);
}
} }
}); });
msg.on("mouseleave", function() { msg.on("mouseleave", function() {
@ -419,6 +425,9 @@ RED.debug = (function() {
if (o._source._alias) { if (o._source._alias) {
config.messageMouseLeave(o._source._alias); config.messageMouseLeave(o._source._alias);
} }
for (var i=2;i<o._source.path.length;i++) {
config.messageMouseLeave(o._source.path[i]);
}
} }
}); });
var name = sanitize(((o.name?o.name:o.id)||"").toString()); var name = sanitize(((o.name?o.name:o.id)||"").toString());
@ -448,11 +457,11 @@ RED.debug = (function() {
var metaRow = $('<div class="red-ui-debug-msg-meta"></div>').appendTo(msg); var metaRow = $('<div class="red-ui-debug-msg-meta"></div>').appendTo(msg);
$('<span class="red-ui-debug-msg-date">'+ getTimestamp()+'</span>').appendTo(metaRow); $('<span class="red-ui-debug-msg-date">'+ getTimestamp()+'</span>').appendTo(metaRow);
if (sourceNode) { if (sourceNode) {
$('<a>',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+sanitize(o.name||sourceNode.name||sourceNode.id)) $('<a>',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+(o.name||sourceNode.name||sourceNode.id))
.appendTo(metaRow) .appendTo(metaRow)
.on("click", function(evt) { .on("click", function(evt) {
evt.preventDefault(); evt.preventDefault();
config.messageSourceClick(sourceNode.id); config.messageSourceClick(sourceNode.id, sourceNode._alias, sourceNode.path);
}); });
} else if (name) { } else if (name) {
$('<span class="red-ui-debug-msg-name">'+name+'</span>').appendTo(metaRow); $('<span class="red-ui-debug-msg-name">'+name+'</span>').appendTo(metaRow);

View File

@ -7,8 +7,8 @@ $(function() {
messageMouseLeave: function(sourceId) { messageMouseLeave: function(sourceId) {
window.opener.postMessage({event:"mouseLeave",id:sourceId},'*'); window.opener.postMessage({event:"mouseLeave",id:sourceId},'*');
}, },
messageSourceClick: function(sourceId) { messageSourceClick: function(sourceId, aliasId, path) {
window.opener.postMessage({event:"mouseClick",id:sourceId},'*'); window.opener.postMessage({event:"mouseClick",id:sourceId, _alias: aliasId, path: path},'*');
}, },
clear: function() { clear: function() {
window.opener.postMessage({event:"clear"},'*'); window.opener.postMessage({event:"clear"},'*');

View File

@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="function"> <script type="text/html" data-template-name="function">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div> <div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="switch"> <script type="text/html" data-template-name="switch">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></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" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@ -173,7 +173,7 @@
} }
} }
$("#node-input-rule-container").css('min-height','250px').css('min-width','450px').editableList({ $("#node-input-rule-container").css('min-height','150px').css('min-width','450px').editableList({
addItem: function(container,i,opt) { addItem: function(container,i,opt) {
if (!opt.hasOwnProperty('r')) { if (!opt.hasOwnProperty('r')) {
opt.r = {}; opt.r = {};
@ -453,6 +453,7 @@
} }
var editorRow = $("#dialog-form>div.node-input-rule-container-row"); var editorRow = $("#dialog-form>div.node-input-rule-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
height += 16;
$("#node-input-rule-container").editableList('height',height); $("#node-input-rule-container").editableList('height',height);
} }
}); });

View File

@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="change"> <script type="text/html" data-template-name="change">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></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" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@ -81,7 +81,7 @@
rule.find('.red-ui-typedInput').typedInput("width",newWidth-130); rule.find('.red-ui-typedInput').typedInput("width",newWidth-130);
} }
$('#node-input-rule-container').css('min-height','300px').css('min-width','450px').editableList({ $('#node-input-rule-container').css('min-height','150px').css('min-width','450px').editableList({
addItem: function(container,i,opt) { addItem: function(container,i,opt) {
var rule = opt; var rule = opt;
if (!rule.hasOwnProperty('t')) { if (!rule.hasOwnProperty('t')) {
@ -259,7 +259,7 @@
} }
var editorRow = $("#dialog-form>div.node-input-rule-container-row"); var editorRow = $("#dialog-form>div.node-input-rule-container-row");
height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom"))); height -= (parseInt(editorRow.css("marginTop"))+parseInt(editorRow.css("marginBottom")));
height += 16;
$("#node-input-rule-container").editableList('height',height); $("#node-input-rule-container").editableList('height',height);
} }
}); });

View File

@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="range"> <script type="text/html" data-template-name="range">
<div class="form-row"> <div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label> <label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:calc(70% - 1px)"/> <input type="text" id="node-input-property" style="width:calc(70% - 1px)"/>

View File

@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="template"> <script type="text/html" data-template-name="template">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div> <div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"></div>

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="delay"> <script type="text/html" data-template-name="delay">
<div class="form-row"> <div class="form-row">
<label for="node-input-delay-action"><i class="fa fa-tasks"></i> <span data-i18n="delay.action"></span></label> <label for="node-input-delay-action"><i class="fa fa-tasks"></i> <span data-i18n="delay.action"></span></label>
<select id="node-input-delay-action" style="width:270px !important"> <select id="node-input-delay-action" style="width:270px !important">

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="trigger"> <script type="text/html" data-template-name="trigger">
<div class="form-row"> <div class="form-row">
<label data-i18n="trigger.send" for="node-input-op1"></label> <label data-i18n="trigger.send" for="node-input-op1"></label>
<input type="hidden" id="node-input-op1type"> <input type="hidden" id="node-input-op1type">
@ -109,9 +109,11 @@
$(".node-type-duration").hide(); $(".node-type-duration").hide();
} }
else if ($(this).val() == "loop") { else if ($(this).val() == "loop") {
if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); }
$(".node-type-wait").hide(); $(".node-type-wait").hide();
$(".node-type-duration").show(); $(".node-type-duration").show();
} else { } else {
if ($("#node-input-duration").val() == 0) { $("#node-input-duration").val(250); }
$(".node-type-wait").show(); $(".node-type-wait").show();
$(".node-type-duration").show(); $(".node-type-duration").show();
} }
@ -176,8 +178,6 @@
if ($("#node-then-type").val() == "loop") { if ($("#node-then-type").val() == "loop") {
$("#node-input-duration").val($("#node-input-duration").val() * -1); $("#node-input-duration").val($("#node-input-duration").val() * -1);
} }
} }
}); });
</script> </script>

View File

@ -76,6 +76,7 @@ module.exports = function(RED) {
var node = this; var node = this;
node.topics = {}; node.topics = {};
var npay = {};
var pendingMessages = []; var pendingMessages = [];
var activeMessagePromise = null; var activeMessagePromise = null;
var processMessageQueue = function(msg) { var processMessageQueue = function(msg) {
@ -106,7 +107,9 @@ module.exports = function(RED) {
}); });
} }
var npay;
this.on('input', function(msg) { this.on('input', function(msg) {
if (node.op2type === "payl") { npay = RED.util.cloneMessage(msg); }
processMessageQueue(msg); processMessageQueue(msg);
}); });
@ -122,9 +125,10 @@ module.exports = function(RED) {
node.status({}); node.status({});
} }
else { else {
if (node.op2type === "payl") { npay[topic] = RED.util.cloneMessage(msg); }
if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) { if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) {
promise = Promise.resolve(); promise = Promise.resolve();
if (node.op2type === "pay" || node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } if (node.op2type === "pay") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); } else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); }
else if (node.op2type !== "nul") { else if (node.op2type !== "nul") {
promise = new Promise((resolve,reject) => { promise = new Promise((resolve,reject) => {
@ -186,9 +190,17 @@ module.exports = function(RED) {
}); });
} }
promise.then(() => { promise.then(() => {
if (node.op2type === "payl") {
node.send(npay[topic]);
delete npay[topic];
}
else {
msg2.payload = node.topics[topic].m2; msg2.payload = node.topics[topic].m2;
delete node.topics[topic];
node.send(msg2); node.send(msg2);
}
delete node.topics[topic];
if (node.op2type === "payl") { node.send(npay); }
else { node.send(msg2); }
node.status({}); node.status({});
}).catch(err => { }).catch(err => {
node.error(err); node.error(err);
@ -244,9 +256,9 @@ module.exports = function(RED) {
}); });
}, node.duration); }, node.duration);
} }
else { // else {
if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } // if (node.op2type === "payl") {node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
} // }
} }
return Promise.resolve(); return Promise.resolve();
} }

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="exec"> <script type="text/html" data-template-name="exec">
<div class="form-row"> <div class="form-row">
<label for="node-input-command"><i class="fa fa-file"></i> <span data-i18n="exec.label.command"></span></label> <label for="node-input-command"><i class="fa fa-file"></i> <span data-i18n="exec.label.command"></span></label>
<input type="text" id="node-input-command" data-i18n="[placeholder]exec.label.command"> <input type="text" id="node-input-command" data-i18n="[placeholder]exec.label.command">

View File

@ -145,7 +145,7 @@ module.exports = function(RED) {
if (error.signal) { msg3.payload.signal = error.signal; } if (error.signal) { msg3.payload.signal = error.signal; }
if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); } if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); }
else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); } else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); }
node.log('error:' + error); if (RED.settings.verbose) { node.log('error:' + error); }
} }
else if (node.oldrc === "false") { else if (node.oldrc === "false") {
msg3 = RED.util.cloneMessage(msg); msg3 = RED.util.cloneMessage(msg);

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="tls-config"> <script type="text/html" data-template-name="tls-config">
<div class="form-row" class="hide" id="node-config-row-uselocalfiles"> <div class="form-row" class="hide" id="node-config-row-uselocalfiles">
<input type="checkbox" id="node-config-input-uselocalfiles" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-config-input-uselocalfiles" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-config-input-uselocalfiles" style="width: 70%;"><span data-i18n="tls.label.use-local-files"></label> <label for="node-config-input-uselocalfiles" style="width: 70%;"><span data-i18n="tls.label.use-local-files"></label>

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="http proxy"> <script type="text/html" data-template-name="http proxy">
<div class="form-row"> <div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-config-input-name"> <input type="text" id="node-config-input-name">

View File

@ -11,7 +11,7 @@
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="mqtt in"> <script type="text/html" data-template-name="mqtt in">
<div class="form-row"> <div class="form-row">
<label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label> <label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
<input type="text" id="node-input-broker"> <input type="text" id="node-input-broker">
@ -75,7 +75,7 @@
}); });
</script> </script>
<script type="text/x-red" data-template-name="mqtt out"> <script type="text/html" data-template-name="mqtt out">
<div class="form-row"> <div class="form-row">
<label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label> <label for="node-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>
<input type="text" id="node-input-broker"> <input type="text" id="node-input-broker">
@ -129,7 +129,7 @@
}); });
</script> </script>
<script type="text/x-red" data-template-name="mqtt-broker"> <script type="text/html" data-template-name="mqtt-broker">
<div class="form-row"> <div class="form-row">
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-config-input-name" data-i18n="[placeholder]common.label.name">
@ -137,7 +137,7 @@
<div class="form-row"> <div class="form-row">
<ul style="min-width: 600px; margin-bottom: 20px;" id="node-config-mqtt-broker-tabs"></ul> <ul style="min-width: 600px; margin-bottom: 20px;" id="node-config-mqtt-broker-tabs"></ul>
</div> </div>
<div id="node-config-mqtt-broker-tabs-content" style="min-height: 170px;"> <div id="node-config-mqtt-broker-tabs-content" style="min-height:150px;">
<div id="mqtt-broker-tab-connection" style="display:none"> <div id="mqtt-broker-tab-connection" style="display:none">
<div class="form-row node-input-broker"> <div class="form-row node-input-broker">
<label for="node-config-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label> <label for="node-config-input-broker"><i class="fa fa-globe"></i> <span data-i18n="mqtt.label.broker"></span></label>

View File

@ -153,7 +153,12 @@ module.exports = function(RED) {
this.brokerurl="mqtt://"; this.brokerurl="mqtt://";
} }
if (this.broker !== "") { if (this.broker !== "") {
//Check for an IPv6 address
if (/(?:^|(?<=\s))(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(?=\s|$)/.test(this.broker)) {
this.brokerurl = this.brokerurl+"["+this.broker+"]:";
} else {
this.brokerurl = this.brokerurl+this.broker+":"; this.brokerurl = this.brokerurl+this.broker+":";
}
// port now defaults to 1883 if unset. // port now defaults to 1883 if unset.
if (!this.port){ if (!this.port){
this.brokerurl = this.brokerurl+"1883"; this.brokerurl = this.brokerurl+"1883";
@ -464,6 +469,7 @@ module.exports = function(RED) {
this.broker = n.broker; this.broker = n.broker;
this.brokerConn = RED.nodes.getNode(this.broker); this.brokerConn = RED.nodes.getNode(this.broker);
var node = this; var node = this;
var chk = /[\+#]/;
if (this.brokerConn) { if (this.brokerConn) {
this.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); this.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"});
@ -482,6 +488,7 @@ module.exports = function(RED) {
} }
if ( msg.hasOwnProperty("payload")) { if ( msg.hasOwnProperty("payload")) {
if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist
if (chk.test(msg.topic)) { node.warn(RED._("mqtt.errors.invalid-topic")); }
this.brokerConn.publish(msg, done); // send the message this.brokerConn.publish(msg, done); // send the message
} else { } else {
node.warn(RED._("mqtt.errors.invalid-topic")); node.warn(RED._("mqtt.errors.invalid-topic"));

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="http in"> <script type="text/html" data-template-name="http in">
<div class="form-row"> <div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label> <label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
<select type="text" id="node-input-method" style="width:70%;"> <select type="text" id="node-input-method" style="width:70%;">
@ -45,7 +45,7 @@
<div id="node-input-tip" class="form-tips"><span data-i18n="httpin.tip.in"></span><code><span id="node-input-path"></span></code>.</div> <div id="node-input-tip" class="form-tips"><span data-i18n="httpin.tip.in"></span><code><span id="node-input-path"></span></code>.</div>
</script> </script>
<script type="text/x-red" data-template-name="http response"> <script type="text/html" data-template-name="http response">
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></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" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="http request"> <script type="text/html" data-template-name="http request">
<div class="form-row"> <div class="form-row">
<label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label> <label for="node-input-method"><i class="fa fa-tasks"></i> <span data-i18n="httpin.label.method"></span></label>
<select type="text" id="node-input-method" style="width:70%;"> <select type="text" id="node-input-method" style="width:70%;">
@ -22,6 +22,7 @@
<option value="POST">POST</option> <option value="POST">POST</option>
<option value="PUT">PUT</option> <option value="PUT">PUT</option>
<option value="DELETE">DELETE</option> <option value="DELETE">DELETE</option>
<option value="HEAD">HEAD</option>
<option value="use" data-i18n="httpin.setby"></option> <option value="use" data-i18n="httpin.setby"></option>
</select> </select>
</div> </div>

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<!-- WebSocket Input Node --> <!-- WebSocket Input Node -->
<script type="text/x-red" data-template-name="websocket in"> <script type="text/html" data-template-name="websocket in">
<div class="form-row"> <div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label> <label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
<select id="node-input-mode"> <select id="node-input-mode">
@ -163,7 +163,7 @@
if (root === "") { if (root === "") {
$("#node-config-ws-tip").hide(); $("#node-config-ws-tip").hide();
} else { } else {
$("#node-config-ws-path").html(root); $("#node-config-ws-path").html(RED._("node-red:websocket.tip.path2", { path: root }));
$("#node-config-ws-tip").show(); $("#node-config-ws-tip").show();
} }
} }
@ -198,7 +198,7 @@
</script> </script>
<!-- WebSocket out Node --> <!-- WebSocket out Node -->
<script type="text/x-red" data-template-name="websocket out"> <script type="text/html" data-template-name="websocket out">
<div class="form-row"> <div class="form-row">
<label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label> <label for="node-input-mode"><i class="fa fa-dot-circle-o"></i> <span data-i18n="websocket.label.type"></span></label>
<select id="node-input-mode"> <select id="node-input-mode">
@ -221,7 +221,7 @@
</script> </script>
<!-- WebSocket Server configuration node --> <!-- WebSocket Server configuration node -->
<script type="text/x-red" data-template-name="websocket-listener"> <script type="text/html" data-template-name="websocket-listener">
<div class="form-row"> <div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label> <label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.path"></span></label>
<input id="node-config-input-path" type="text" placeholder="/ws/example"> <input id="node-config-input-path" type="text" placeholder="/ws/example">
@ -235,12 +235,12 @@
</div> </div>
<div class="form-tips"> <div class="form-tips">
<span data-i18n="[html]websocket.tip.path1"></span> <span data-i18n="[html]websocket.tip.path1"></span>
<p id="node-config-ws-tip"><span data-i18n="[html]websocket.tip.path2"></span><code><span id="node-config-ws-path"></span></code>.</p> <p id="node-config-ws-tip"><span id="node-config-ws-path"></span></p>
</div> </div>
</script> </script>
<!-- WebSocket Client configuration node --> <!-- WebSocket Client configuration node -->
<script type="text/x-red" data-template-name="websocket-client"> <script type="text/html" data-template-name="websocket-client">
<div class="form-row"> <div class="form-row">
<label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label> <label for="node-config-input-path"><i class="fa fa-bookmark"></i> <span data-i18n="websocket.label.url"></span></label>
<input id="node-config-input-path" type="text" placeholder="ws://example.com/ws"> <input id="node-config-input-path" type="text" placeholder="ws://example.com/ws">

View File

@ -14,7 +14,7 @@
limitations under the License. limitations under the License.
--> -->
<script type="text/x-red" data-template-name="tcp in"> <script type="text/html" data-template-name="tcp in">
<div class="form-row"> <div class="form-row">
<label for="node-input-server"><i class="fa fa-dot-circle-o"></i> <span data-i18n="tcpin.label.type"></span></label> <label for="node-input-server"><i class="fa fa-dot-circle-o"></i> <span data-i18n="tcpin.label.type"></span></label>
<select id="node-input-server" style="width:120px; margin-right:5px;"> <select id="node-input-server" style="width:120px; margin-right:5px;">
@ -108,7 +108,7 @@
</script> </script>
<script type="text/x-red" data-template-name="tcp out"> <script type="text/html" data-template-name="tcp out">
<div class="form-row"> <div class="form-row">
<label for="node-input-beserver"><i class="fa fa-dot-circle-o"></i> <span data-i18n="tcpin.label.type"></span></label> <label for="node-input-beserver"><i class="fa fa-dot-circle-o"></i> <span data-i18n="tcpin.label.type"></span></label>
<select id="node-input-beserver" style="width:150px; margin-right:5px;"> <select id="node-input-beserver" style="width:150px; margin-right:5px;">
@ -188,7 +188,7 @@
</script> </script>
<script type="text/x-red" data-template-name="tcp request"> <script type="text/html" data-template-name="tcp request">
<div class="form-row"> <div class="form-row">
<label for="node-input-server"><i class="fa fa-globe"></i> <span data-i18n="tcpin.label.server"></span></label> <label for="node-input-server"><i class="fa fa-globe"></i> <span data-i18n="tcpin.label.server"></span></label>
<input type="text" id="node-input-server" placeholder="ip.address" style="width:45%"> <input type="text" id="node-input-server" placeholder="ip.address" style="width:45%">

View File

@ -15,7 +15,7 @@
--> -->
<!-- The Input Node --> <!-- The Input Node -->
<script type="text/x-red" data-template-name="udp in"> <script type="text/html" data-template-name="udp in">
<div class="form-row"> <div class="form-row">
<label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.listen"></span></label> <label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.listen"></span></label>
<select id="node-input-multicast" style='width:70%'> <select id="node-input-multicast" style='width:70%'>
@ -115,7 +115,7 @@
<!-- The Output Node --> <!-- The Output Node -->
<script type="text/x-red" data-template-name="udp out"> <script type="text/html" data-template-name="udp out">
<div class="form-row"> <div class="form-row">
<label for="node-input-port"><i class="fa fa-envelope"></i> <span data-i18n="udp.label.send"></span></label> <label for="node-input-port"><i class="fa fa-envelope"></i> <span data-i18n="udp.label.send"></span></label>
<select id="node-input-multicast" style="width:40%"> <select id="node-input-multicast" style="width:40%">

View File

@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="csv"> <script type="text/html" data-template-name="csv">
<div class="form-row"> <div class="form-row">
<label for="node-input-temp"><i class="fa fa-list"></i> <span data-i18n="csv.label.columns"></span></label> <label for="node-input-temp"><i class="fa fa-list"></i> <span data-i18n="csv.label.columns"></span></label>
<input type="text" id="node-input-temp" data-i18n="[placeholder]csv.placeholder.columns"> <input type="text" id="node-input-temp" data-i18n="[placeholder]csv.placeholder.columns">
@ -33,6 +33,10 @@
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-hdrin"><label style="width:auto; margin-top:7px;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></label><br/> <input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-hdrin"><label style="width:auto; margin-top:7px;" for="node-input-hdrin"><span data-i18n="csv.label.firstrow"></span></label><br/>
<label>&nbsp;</label> <label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-strings"><label style="width:auto; margin-top:7px;" for="node-input-strings"><span data-i18n="csv.label.usestrings"></span></label><br/> <input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-strings"><label style="width:auto; margin-top:7px;" for="node-input-strings"><span data-i18n="csv.label.usestrings"></span></label><br/>
<label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_empty_strings"><label style="width:auto; margin-top:7px;" for="node-input-include_empty_strings"><span data-i18n="csv.label.include_empty_strings"></span></label><br/>
<label>&nbsp;</label>
<input style="width:20px; vertical-align:baseline; margin-right:5px;" type="checkbox" id="node-input-include_null_values"><label style="width:auto; margin-top:7px;" for="node-input-include_null_values"><span data-i18n="csv.label.include_null_values"></span></label><br/>
</div> </div>
<div class="form-row" style="padding-left:20px;"> <div class="form-row" style="padding-left:20px;">
<label><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label> <label><i class="fa fa-sign-out"></i> <span data-i18n="csv.label.output"></span></label>
@ -74,7 +78,9 @@
ret: {value:'\\n'}, ret: {value:'\\n'},
temp: {value:""}, temp: {value:""},
skip: {value:"0"}, skip: {value:"0"},
strings: {value:true} strings: {value:true},
include_empty_strings: {value:""},
include_null_values: {value:""}
}, },
inputs:1, inputs:1,
outputs:1, outputs:1,

View File

@ -31,6 +31,8 @@ module.exports = function(RED) {
this.skip = parseInt(n.skip || 0); this.skip = parseInt(n.skip || 0);
this.store = []; this.store = [];
this.parsestrings = n.strings; this.parsestrings = n.strings;
this.include_empty_strings = n.include_empty_strings || false;
this.include_null_values = n.include_null_values || false;
if (this.parsestrings === undefined) { this.parsestrings = true; } if (this.parsestrings === undefined) { this.parsestrings = true; }
var tmpwarn = true; var tmpwarn = true;
var node = this; var node = this;
@ -173,20 +175,29 @@ module.exports = function(RED) {
} }
else if ((line[i] === node.sep) && f) { // if it is the end of the line then finish else if ((line[i] === node.sep) && f) { // if it is the end of the line then finish
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); } if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "" ) ) { if ( node.template[j] && (node.template[j] !== "") ) {
if ( (node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); } // if no value between separators ('1,,"3"...') or if the line beings with separator (',1,"2"...') treat value as null
o[node.template[j]] = k[j]; if (line[i-1] === node.sep || line[i-1].includes('\n','\r')) k[j] = null;
if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); }
if (node.include_null_values && k[j] === null) o[node.template[j]] = k[j];
if (node.include_empty_strings && k[j] === "") o[node.template[j]] = k[j];
if (k[j] !== null && k[j] !== "") o[node.template[j]] = k[j];
} }
j += 1; j += 1;
k[j] = ""; // if separator is last char in processing string line (without end of line), add null value at the end - example: '1,2,3\n3,"3",'
k[j] = line.length - 1 === i ? null : "";
} }
else if ((line[i] === "\n") || (line[i] === "\r")) { // handle multiple lines else if ((line[i] === "\n") || (line[i] === "\r")) { // handle multiple lines
//console.log(j,k,o,k[j]); //console.log(j,k,o,k[j]);
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); } if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) { if ( node.template[j] && (node.template[j] !== "") ) {
if ( (node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); } // if separator before end of line, set null value ie. '1,2,"3"\n1,2,\n1,2,3'
else { k[j].replace(/\r$/,''); } if (line[i-1] === node.sep) k[j] = null;
o[node.template[j]] = k[j]; if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); }
else { if (k[j] !== null) k[j].replace(/\r$/,''); }
if (node.include_null_values && k[j] === null) o[node.template[j]] = k[j];
if (node.include_empty_strings && k[j] === "") o[node.template[j]] = k[j];
if (k[j] !== null && k[j] !== "") o[node.template[j]] = k[j];
} }
if (JSON.stringify(o) !== "{}") { // don't send empty objects if (JSON.stringify(o) !== "{}") { // don't send empty objects
a.push(o); // add to the array a.push(o); // add to the array
@ -204,10 +215,13 @@ module.exports = function(RED) {
// Finished so finalize and send anything left // Finished so finalize and send anything left
//console.log(j,k,o,k[j]); //console.log(j,k,o,k[j]);
if (!node.goodtmpl) { node.template[j] = "col"+(j+1); } if (!node.goodtmpl) { node.template[j] = "col"+(j+1); }
if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) {
if ( (node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); } if ( node.template[j] && (node.template[j] !== "") ) {
else { k[j].replace(/\r$/,''); } if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); }
o[node.template[j]] = k[j]; else { if (k[j] !== null) k[j].replace(/\r$/,''); }
if (node.include_null_values && k[j] === null) o[node.template[j]] = k[j];
if (node.include_empty_strings && k[j] === "") o[node.template[j]] = k[j];
if (k[j] !== null && k[j] !== "") o[node.template[j]] = k[j];
} }
if (JSON.stringify(o) !== "{}") { // don't send empty objects if (JSON.stringify(o) !== "{}") { // don't send empty objects
a.push(o); // add to the array a.push(o); // add to the array

View File

@ -1,7 +1,7 @@
<script type="text/x-red" data-template-name="html"> <script type="text/html" data-template-name="html">
<div class="form-row"> <div class="form-row">
<label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="node-red:common.label.property"></span></label> <label for="node-input-property"><i class="fa fa-ellipsis-h"></i> <span data-i18n="common.label.property"></span></label>
<input type="text" id="node-input-property" style="width:70%"> <input type="text" id="node-input-property" style="width:70%">
</div> </div>
<div class="form-row"> <div class="form-row">

View File

@ -1,5 +1,5 @@
<script type="text/x-red" data-template-name="json"> <script type="text/html" data-template-name="json">
<div class="form-row"> <div class="form-row">
<label for="node-input-action"><i class="fa fa-dot-circle-o"></i> <span data-i18n="json.label.action"></span></label> <label for="node-input-action"><i class="fa fa-dot-circle-o"></i> <span data-i18n="json.label.action"></span></label>
<select style="width:70%" id="node-input-action"> <select style="width:70%" id="node-input-action">

Some files were not shown because too many files have changed in this diff Show More