diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9a278b223..b7239546c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,6 @@ name: PublishDockerImage - +env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: true on: release: types: [published] @@ -27,9 +28,6 @@ jobs: with: node-version: '12' - run: node ./node-red/.github/scripts/update-node-red-docker.js - with: - env: - ACTIONS_ALLOW_UNSECURE_COMMANDS: true - name: Create Docker Pull Request uses: peter-evans/create-pull-request@v2 with: diff --git a/.travis.yml b/.travis.yml index 315eb07a8..4cd0dffe1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,5 +11,11 @@ matrix: before_script: - npm install -g coveralls - node_js: "12" + script: + - ./node_modules/.bin/grunt no-coverage - node_js: "10" + script: + - ./node_modules/.bin/grunt no-coverage - node_js: "8" + script: + - ./node_modules/.bin/grunt no-coverage diff --git a/API.md b/API.md index f349ea8fe..692a34723 100644 --- a/API.md +++ b/API.md @@ -10,6 +10,6 @@ Module | Description [@node-red/editor-api](@node-red_editor-api.html) | an Express application that serves the Node-RED editor and provides the Admin HTTP API [@node-red/runtime](@node-red_runtime.html) | the core runtime of Node-RED [@node-red/util](@node-red_util.html) | common utilities for the Node-RED runtime and editor modules -@node-red/registry | the internal node registry +[@node-red/registry](@node-red_registry.html) | the internal node registry @node-red/nodes | the default set of core nodes @node-red/editor-client | the client-side resources of the Node-RED editor application diff --git a/CHANGELOG.md b/CHANGELOG.md index e9d7ba010..7af2084c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,65 @@ +### 1.2.8: Maintenance Release + +Editor + + - Ensure subflow help is picked up for palette tooltip Fixes #2834 + - Improve Ru locale (#2826) @alexk111 + - Fix scrollbars (#2825) @alexk111 + +Runtime + + - Restrict project file access to inside the project directory + - Validate user-provided language parameter before passing to i18n + - Fix grunt release mkdir issue on Node.js 14 (#2827) @alexk111 + - Prevent crash when coreNodesDir is empty (#2831) @hardillb + +Nodes + + - Batch node: Fixing minor typo in node's documentation (#2848) @matthiasradde + - Split node: Handle out of order messages as long as one of the messages has msg.parts.count set to the proper value (#2748) @s4ke + +### 1.2.7: Maintenance Release + +Editor + + - Ensure subflow-scoped config nodes do not get moved on import Fixes #2789 + - Allow TypedInput to be disabled (#2752) @bartbutenaers + - Allow userMenu to be explicitly enabled (#2805) @tfmf + - Improvements to DE translation (#2192) @ketzu + + +Runtime + + - Handle `undefined` error passed to node.error (#2781) @johnwang71 + - Disable nyc coverage reporting on older node versions + - Improve Editor API unit test coverage (#2777) @aaronmyatt + + +Nodes + + - Trigger: ensure timestamp option sends .now() at point of sending + + +### 1.2.6: Maintenance Release + + +Editor + + - Update Japanese translations for 1.2.5 (#2764) @kazuhitoyokoi + - Library: properly handle symlinked folders (#2768) @natcl + +Runtime + + - Support Windows paths when installing tarball by path name Fixes #2769 + - Fix unsecure command usage in GH Action + +Nodes + + - Update MQTT to latest to fix Node 8 URL breakage + + + + ### 1.2.5: Maintenance Release Editor diff --git a/Gruntfile.js b/Gruntfile.js index 6272e9818..23481f793 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -142,6 +142,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/text/bidi.js", "packages/node_modules/@node-red/editor-client/src/js/text/format.js", "packages/node_modules/@node-red/editor-client/src/js/ui/state.js", + "packages/node_modules/@node-red/editor-client/src/js/plugins.js", "packages/node_modules/@node-red/editor-client/src/js/nodes.js", "packages/node_modules/@node-red/editor-client/src/js/font-awesome.js", "packages/node_modules/@node-red/editor-client/src/js/history.js", @@ -461,7 +462,8 @@ module.exports = function(grunt) { 'packages/node_modules/@node-red/runtime/lib/hooks.js', 'packages/node_modules/@node-red/util/**/*.js', 'packages/node_modules/@node-red/editor-api/lib/index.js', - 'packages/node_modules/@node-red/editor-api/lib/auth/index.js' + 'packages/node_modules/@node-red/editor-api/lib/auth/index.js', + 'packages/node_modules/@node-red/registry/lib/index.js' ], options: { destination: 'docs', @@ -623,6 +625,11 @@ module.exports = function(grunt) { 'Builds editor content then runs code style checks and unit tests on all components', ['build','verifyPackageDependencies','jshint:editor','nyc:all']); + grunt.registerTask('no-coverage', + 'Builds editor content then runs code style checks and unit tests on all components without code coverage', + ['build','verifyPackageDependencies','jshint:editor','simplemocha:all']); + + grunt.registerTask('test-core', 'Runs code style check and unit tests on core runtime code', ['build','nyc:core']); diff --git a/package.json b/package.json index 15abf0507..2f3dfa520 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ ], "dependencies": { "ajv": "6.12.6", - "async-mutex": "0.2.4", + "async-mutex": "0.2.6", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "body-parser": "1.19.0", @@ -38,7 +38,7 @@ "cookie-parser": "1.4.5", "cors": "2.8.5", "cron": "1.7.2", - "denque": "1.4.1", + "denque": "1.5.0", "express": "4.17.1", "express-session": "1.17.1", "fs-extra": "8.1.0", @@ -54,11 +54,11 @@ "lodash.clonedeep": "^4.5.0", "media-typer": "1.1.0", "memorystore": "1.6.4", - "mime": "2.4.6", + "mime": "2.4.7", "moment-timezone": "0.5.32", - "mqtt": "4.2.5", + "mqtt": "4.2.6", "multer": "1.4.2", - "mustache": "4.0.1", + "mustache": "4.1.0", "node-red-admin": "^0.2.6", "node-red-node-rbe": "^0.2.9", "node-red-node-sentiment": "^0.1.6", @@ -73,8 +73,7 @@ "request": "2.88.0", "semver": "6.3.0", "tar": "6.0.5", - "uglify-js": "3.11.6", - "when": "3.7.8", + "uglify-js": "3.12.4", "ws": "6.2.1", "xml2js": "0.4.23" }, @@ -82,7 +81,7 @@ "bcrypt": "3.0.8" }, "devDependencies": { - "dompurify": "2.2.2", + "dompurify": "2.2.6", "grunt": "1.3.0", "grunt-chmod": "~1.1.1", "grunt-cli": "~1.3.2", @@ -97,17 +96,17 @@ "grunt-jsdoc": "2.4.1", "grunt-jsdoc-to-markdown": "5.0.0", "grunt-jsonlint": "2.1.3", - "grunt-mkdir": "~1.0.0", + "grunt-mkdir": "~1.1.0", "grunt-npm-command": "~0.1.2", "grunt-sass": "~3.1.0", "grunt-simple-mocha": "~0.4.1", "grunt-simple-nyc": "^3.0.1", "http-proxy": "1.18.1", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", - "marked": "1.2.4", + "marked": "1.2.7", "minami": "1.2.3", "mocha": "^5.2.0", - "node-red-node-test-helper": "^0.2.5", + "node-red-node-test-helper": "^0.2.6", "node-sass": "^4.14.1", "nodemon": "2.0.6", "should": "13.2.3", diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/index.js b/packages/node_modules/@node-red/editor-api/lib/admin/index.js index ff32111f5..cf3505439 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/index.js @@ -22,6 +22,7 @@ var flow = require("./flow"); var context = require("./context"); var auth = require("../auth"); var info = require("./settings"); +var plugins = require("./plugins"); var apiUtil = require("../util"); @@ -32,6 +33,7 @@ module.exports = { nodes.init(runtimeAPI); context.init(runtimeAPI); info.init(settings,runtimeAPI); + plugins.init(runtimeAPI); var needsPermission = auth.needsPermission; @@ -50,12 +52,14 @@ module.exports = { // Nodes adminApp.get("/nodes",needsPermission("nodes.read"),nodes.getAll,apiUtil.errorHandler); - if (!settings.editorTheme || !settings.editorTheme.palette || settings.editorTheme.palette.upload !== false) { - const multer = require('multer'); - const upload = multer({ storage: multer.memoryStorage() }); - adminApp.post("/nodes",needsPermission("nodes.write"),upload.single("tarball"),nodes.post,apiUtil.errorHandler); - } else { - adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler); + if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowInstall !== false) { + if (!settings.externalModules || !settings.externalModules.palette || settings.externalModules.palette.allowUpload !== false) { + const multer = require('multer'); + const upload = multer({ storage: multer.memoryStorage() }); + adminApp.post("/nodes",needsPermission("nodes.write"),upload.single("tarball"),nodes.post,apiUtil.errorHandler); + } else { + adminApp.post("/nodes",needsPermission("nodes.write"),nodes.post,apiUtil.errorHandler); + } } adminApp.get(/^\/nodes\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalogs,apiUtil.errorHandler); adminApp.get(/^\/nodes\/((@[^\/]+\/)?[^\/]+\/[^\/]+)\/messages/,needsPermission("nodes.read"),nodes.getModuleCatalog,apiUtil.errorHandler); @@ -78,6 +82,10 @@ module.exports = { adminApp.get("/settings",needsPermission("settings.read"),info.runtimeSettings,apiUtil.errorHandler); + // Plugins + adminApp.get("/plugins", needsPermission("plugins.read"), plugins.getAll, apiUtil.errorHandler); + adminApp.get("/plugins/messages", needsPermission("plugins.read"), plugins.getCatalogs, apiUtil.errorHandler); + return adminApp; } } diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js b/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js index 187bd823f..058053a29 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/nodes.js @@ -60,6 +60,7 @@ module.exports = { runtimeAPI.nodes.addModule(opts).then(function(info) { res.json(info); }).catch(function(err) { + console.log(err.stack); apiUtils.rejectHandler(req,res,err); }) }, diff --git a/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js b/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js new file mode 100644 index 000000000..3d49a7e8a --- /dev/null +++ b/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js @@ -0,0 +1,46 @@ +var apiUtils = require("../util"); + +var runtimeAPI; + +module.exports = { + init: function(_runtimeAPI) { + runtimeAPI = _runtimeAPI; + }, + getAll: function(req,res) { + var opts = { + user: req.user, + req: apiUtils.getRequestLogObject(req) + } + if (req.get("accept") == "application/json") { + runtimeAPI.plugins.getPluginList(opts).then(function(list) { + res.json(list); + }) + } else { + opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages()); + if (/[^a-z\-\*]/i.test(opts.lang)) { + res.json({}); + return; + } + runtimeAPI.plugins.getPluginConfigs(opts).then(function(configs) { + res.send(configs); + }) + } + }, + getCatalogs: function(req,res) { + var opts = { + user: req.user, + lang: req.query.lng, + req: apiUtils.getRequestLogObject(req) + } + if (/[^a-z\-\*]/i.test(opts.lang)) { + res.json({}); + return; + } + runtimeAPI.plugins.getPluginCatalogs(opts).then(function(result) { + res.json(result); + }).catch(function(err) { + console.log(err.stack); + apiUtils.rejectHandler(req,res,err); + }) + } +}; diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/index.js b/packages/node_modules/@node-red/editor-api/lib/editor/index.js index 71876eaa6..2e8333f3f 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/index.js @@ -76,7 +76,7 @@ module.exports = { editorApp.get("/icons/:scope/:module/:icon",ui.icon); var theme = require("./theme"); - theme.init(settings); + theme.init(settings, runtimeAPI); editorApp.use("/theme",theme.app()); editorApp.use("/",ui.editorResources); diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/library.js b/packages/node_modules/@node-red/editor-api/lib/editor/library.js index 47a41bb7b..89b92fd11 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/library.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/library.js @@ -17,7 +17,6 @@ var apiUtils = require("../util"); var fs = require('fs'); var fspath = require('path'); -var when = require('when'); var runtimeAPI; diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/locales.js b/packages/node_modules/@node-red/editor-api/lib/editor/locales.js index a7f300cd3..f9453f55b 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/locales.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/locales.js @@ -39,9 +39,12 @@ module.exports = { }, get: function(req,res) { var namespace = req.params[0]; - var lngs = req.query.lng; namespace = namespace.replace(/\.json$/,""); var lang = req.query.lng || i18n.defaultLang; //apiUtil.determineLangFromHeaders(req.acceptsLanguages() || []); + if (/[^a-z\-\*]/i.test(lang)) { + res.json({}); + return; + } var prevLang = i18n.i.language; // Trigger a load from disk of the language if it is not the default i18n.i.changeLanguage(lang, function(){ diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js index 17dbbafea..52f7974ec 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js @@ -41,6 +41,10 @@ var theme = null; var themeContext = clone(defaultContext); var themeSettings = null; +var activeTheme = null; +var activeThemeInitialised = false; + +var runtimeAPI; var themeApp; function serveFile(app,baseUrl,file) { @@ -58,7 +62,7 @@ function serveFile(app,baseUrl,file) { } } -function serveFilesFromTheme(themeValue, themeApp, directory) { +function serveFilesFromTheme(themeValue, themeApp, directory, baseDirectory) { var result = []; if (themeValue) { var array = themeValue; @@ -67,7 +71,14 @@ function serveFilesFromTheme(themeValue, themeApp, directory) { } for (var i=0;i theme.id); res.json(themeContext); }) @@ -185,10 +200,38 @@ module.exports = { themeSettings.projects = theme.projects; } - + if (theme.theme) { + themeSettings.theme = theme.theme; + } return themeApp; }, - context: function() { + context: async function() { + if (activeTheme && !activeThemeInitialised) { + const themePlugin = await runtimeAPI.plugins.getPlugin({ + id:activeTheme + }); + if (themePlugin) { + if (themePlugin.css) { + const cssFiles = serveFilesFromTheme( + themePlugin.css, + themeApp, + "/css/", + themePlugin.path + ); + themeContext.page.css = cssFiles.concat(themeContext.page.css || []) + } + if (themePlugin.scripts) { + const scriptFiles = serveFilesFromTheme( + themePlugin.scripts, + themeApp, + "/scripts/", + themePlugin.path + ) + themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || []) + } + } + activeThemeInitialised = true; + } return themeContext; }, settings: function() { diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/ui.js b/packages/node_modules/@node-red/editor-api/lib/editor/ui.js index 24b819fec..a4812a668 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/ui.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/ui.js @@ -68,8 +68,8 @@ module.exports = { apiUtils.rejectHandler(req,res,err); }) }, - editor: function(req,res) { - res.send(Mustache.render(editorTemplate,theme.context())); + editor: async function(req,res) { + res.send(Mustache.render(editorTemplate,await theme.context())); }, editorResources: express.static(path.join(editorClientDir,'public')) }; diff --git a/packages/node_modules/@node-red/editor-api/lib/index.js b/packages/node_modules/@node-red/editor-api/lib/index.js index 457b99cc9..f66e9094b 100644 --- a/packages/node_modules/@node-red/editor-api/lib/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/index.js @@ -28,7 +28,6 @@ var express = require("express"); var bodyParser = require("body-parser"); var util = require('util'); var passport = require('passport'); -var when = require('when'); var cors = require('cors'); var auth = require("./auth"); @@ -60,8 +59,8 @@ function init(settings,_server,storage,runtimeAPI) { adminApp.use(corsHandler); if (settings.httpAdminMiddleware) { - if (typeof settings.httpAdminMiddleware === "function") { - adminApp.use(settings.httpAdminMiddleware) + if (typeof settings.httpAdminMiddleware === "function" || Array.isArray(settings.httpAdminMiddleware)) { + adminApp.use(settings.httpAdminMiddleware); } } @@ -111,11 +110,9 @@ function init(settings,_server,storage,runtimeAPI) { * @return {Promise} resolves when the application is ready to handle requests * @memberof @node-red/editor-api */ -function start() { +async function start() { if (editor) { return editor.start(); - } else { - return when.resolve(); } } @@ -124,11 +121,10 @@ function start() { * @return {Promise} resolves when the application is stopped * @memberof @node-red/editor-api */ -function stop() { +async function stop() { if (editor) { editor.stop(); } - return when.resolve(); } module.exports = { init: init, diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 6885aaabb..0b9ebb224 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -25,14 +25,13 @@ "express-session": "1.17.1", "express": "4.17.1", "memorystore": "1.6.4", - "mime": "2.4.6", + "mime": "2.4.7", "multer": "1.4.2", - "mustache": "4.0.1", + "mustache": "4.1.0", "oauth2orize": "1.11.0", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", "passport": "0.4.1", - "when": "3.7.8", "ws": "6.2.1" }, "optionalDependencies": { diff --git a/packages/node_modules/@node-red/editor-client/locales/de/editor.json b/packages/node_modules/@node-red/editor-client/locales/de/editor.json index 0c8dd42eb..7c408d742 100755 --- a/packages/node_modules/@node-red/editor-client/locales/de/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/de/editor.json @@ -32,7 +32,7 @@ "label" : { "view" : { "view" : "Ansicht", - "grid" : "Gitter", + "grid" : "Raster", "showGrid" : "Raster anzeigen", "snapGrid" : "Am Raster ausrichten", "gridSize" : "Rastergröße", diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 0b92fc18c..99eb34a3f 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -38,6 +38,7 @@ } }, "event": { + "loadPlugins": "Loading Plugins", "loadPalette": "Loading Palette", "loadNodeCatalogs": "Loading Node catalogs", "loadNodes": "Loading Nodes __count__", @@ -337,8 +338,21 @@ "output": "outputs:", "status": "status node", "deleteSubflow": "delete subflow", + "confirmDelete": "Are you sure you want to delete this subflow?", "info": "Description", "category": "Category", + "module": "Module", + "license": "License", + "licenseNone": "none", + "licenseOther": "Other", + "type": "Node Type", + "version": "Version", + "versionPlaceholder": "x.y.z", + "keys": "Keywords", + "keysPlaceholder": "Comma-separated keywords", + "author": "Author", + "authorPlaceholder": "Your Name ", + "desc": "Description", "env": { "restore": "Restore to subflow default", "remove": "Remove environment variable" @@ -385,6 +399,7 @@ "icon": "Icon", "inputType": "Input type", "selectType": "select types...", + "loadCredentials": "Loading node credentials", "inputs" : { "input": "input", "select": "select", @@ -419,7 +434,8 @@ }, "errors": { "scopeChange": "Changing the scope will make it unavailable to nodes in other flows that use it", - "invalidProperties": "Invalid properties:" + "invalidProperties": "Invalid properties:", + "credentialLoadFailed": "Failed to load node credentials" } }, "keyboard": { @@ -1079,6 +1095,7 @@ "editor-tab": { "properties": "Properties", "envProperties": "Environment Variables", + "module": "Module Properties", "description": "Description", "appearance": "Appearance", "preview": "UI Preview", diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json index 57ebb00a7..282dfe551 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json @@ -243,19 +243,19 @@ "args": "array, function", "desc": "Returns the one and only value in the `array` parameter that satisfies the `function` predicate (i.e. the `function` returns Boolean `true` when passed the value). Throws an exception if the number of matching values is not exactly one.\n\nThe function should be supplied in the following signature: `function(value [, index [, array]])` where value is each input of the array, index is the position of that value and the whole array is passed as the third argument" }, - "$encodeUrl": { + "$encodeUrlComponent": { "args": "str", "desc": "Encodes a Uniform Resource Locator (URL) component by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character.\n\nExample: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" }, - "$encodeUrlComponent": { + "$encodeUrl": { "args": "str", "desc": "Encodes a Uniform Resource Locator (URL) by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character. \n\nExample: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`" }, - "$decodeUrl": { + "$decodeUrlComponent": { "args": "str", "desc": "Decodes a Uniform Resource Locator (URL) component previously created by encodeUrlComponent. \n\nExample: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`" }, - "$decodeUrlComponent": { + "$decodeUrl": { "args": "str", "desc": "Decodes a Uniform Resource Locator (URL) previously created by encodeUrl. \n\nExample: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`" }, diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 723bee1cd..5419d2a93 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -38,6 +38,7 @@ } }, "event": { + "loadPlugins": "プラグインを読み込み中", "loadPalette": "パレットを読み込み中", "loadNodeCatalogs": "ノードカタログを読み込み中", "loadNodes": "ノードを読み込み中 __count__", @@ -85,7 +86,7 @@ "userSettings": "ユーザ設定", "nodes": "ノード", "displayStatus": "ノードのステータスを表示", - "displayConfig": "ノードの設定", + "displayConfig": "設定ノード", "import": "読み込み", "export": "書き出し", "search": "ノードを検索", @@ -203,8 +204,8 @@ "replacedNodes_plural": "置換された __count__ 個のノード", "pasteNodes": "JSON形式のフローデータを貼り付け", "selectFile": "読み込むファイルを選択", - "importNodes": "フローをクリップボードから読み込み", - "exportNodes": "フローをクリップボードへ書き出し", + "importNodes": "フローを読み込み", + "exportNodes": "フローを書き出し", "download": "ダウンロード", "importUnrecognised": "認識できない型が読み込まれました:", "importUnrecognised_plural": "認識できない型が読み込まれました:", @@ -266,7 +267,7 @@ "successfulDeploy": "デプロイが成功しました", "successfulRestart": "フローの再起動が成功しました", "deployFailed": "デプロイが失敗しました: __message__", - "unusedConfigNodes": "使われていない「ノードの設定」があります。", + "unusedConfigNodes": "使われていない設定ノードがあります。", "unusedConfigNodesLink": "設定を参照する", "errors": { "noResponse": "サーバの応答がありません" @@ -337,8 +338,21 @@ "output": "出力:", "status": "ステータスノード", "deleteSubflow": "サブフローを削除", + "confirmDelete": "このサブフローを削除しても良いですか?", "info": "詳細", "category": "カテゴリ", + "module": "モジュール", + "license": "ライセンス", + "licenseNone": "なし", + "licenseOther": "その他", + "type": "ノードの型", + "version": "バージョン", + "versionPlaceholder": "x.y.z", + "keys": "キーワード", + "keysPlaceholder": "カンマ区切りのキーワード", + "author": "作者", + "authorPlaceholder": "名前 ", + "desc": "説明", "env": { "restore": "デフォルト値に戻す", "remove": "環境変数を削除" @@ -362,9 +376,9 @@ "configDelete": "削除", "nodesUse": "__count__ 個のノードが、この設定を使用しています", "nodesUse_plural": "__count__ 個のノードが、この設定を使用しています", - "addNewConfig": "新規に __type__ ノードの設定を追加", + "addNewConfig": "新規に __type__ 設定ノードを追加", "editNode": "__type__ ノードを編集", - "editConfig": "__type__ ノードの設定を編集", + "editConfig": "__type__ 設定ノードを編集", "addNewType": "新規に __type__ を追加...", "nodeProperties": "プロパティ", "label": "ラベル", @@ -385,6 +399,7 @@ "icon": "記号", "inputType": "入力形式", "selectType": "形式選択...", + "loadCredentials": "ノードの認証情報を読み込み中", "inputs": { "input": "入力", "select": "メニュー", @@ -419,7 +434,8 @@ }, "errors": { "scopeChange": "スコープの変更は、他のフローで使われているノードを無効にします", - "invalidProperties": "プロパティが不正です:" + "invalidProperties": "プロパティが不正です:", + "credentialLoadFailed": "ノードの認証情報の読み込みに失敗" } }, "keyboard": { @@ -637,8 +653,8 @@ "noHelp": "ヘルプのトピックが未選択" }, "config": { - "name": "ノードの設定を表示", - "label": "ノードの設定", + "name": "設定ノードを表示", + "label": "設定ノード", "global": "全てのフロー上", "none": "なし", "subflows": "サブフロー", @@ -1079,6 +1095,7 @@ "editor-tab": { "properties": "プロパティ", "envProperties": "環境変数", + "module": "モジュールプロパティ", "description": "説明", "appearance": "外観", "preview": "UIプレビュー", @@ -1089,6 +1106,7 @@ "en-US": "英語", "ja": "日本語", "ko": "韓国語", + "ru": "ロシア語", "zh-CN": "中国語(簡体)", "zh-TW": "中国語(繁体)" } diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/ja/jsonata.json index 02973a69a..7f96c747a 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/jsonata.json @@ -243,19 +243,19 @@ "args": "array, function", "desc": "`array`の要素のうち、条件判定関数`function`を満たす(`function`に与えた場合に真偽値`true`を返す)要素が1つのみである場合、それを返します。マッチする要素が1つのみでない場合、例外を送出します。\n\n指定する関数は`function(value [, index [, array]])`というシグネチャでなければなりません。ここで、`value`は`array`の要素値、`index`は要素の添字、第三引数には配列全体を渡します。" }, - "$encodeUrl": { + "$encodeUrlComponent": { "args": "str", "desc": "Uniform Resource Locator (URL)を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" }, - "$encodeUrlComponent": { + "$encodeUrl": { "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\"`" }, - "$decodeUrl": { + "$decodeUrlComponent": { "args": "str", "desc": "encodeUrlComponentで置換したUniform Resource Locator (URL)をデコードします。\n\n例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`" }, - "$decodeUrlComponent": { + "$decodeUrl": { "args": "str", "desc": "encodeUrlで置換したUniform Resource Locator (URL)要素をデコードします。 \n\n例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`" }, diff --git a/packages/node_modules/@node-red/editor-client/locales/ru/editor.json b/packages/node_modules/@node-red/editor-client/locales/ru/editor.json index 1f91d4b0b..04ff361ba 100755 --- a/packages/node_modules/@node-red/editor-client/locales/ru/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ru/editor.json @@ -246,8 +246,8 @@ "import": { "import": "Импортировать в", "importSelected": "Импортировать выбранные", - "importCopy": "Импортировать копию", - "viewNodes": "Посмотреть узлы...", + "importCopy": "Импортировать копии", + "viewNodes": "Показать узлы...", "newFlow": "новый поток", "replace": "заменить", "errors": { @@ -257,7 +257,7 @@ "missingType": "Недопустимый поток - у элемента __index__ отсутствует свойство 'type'" }, "conflictNotification1": "Некоторые импортируемые Вами узлы уже существуют в рабочей области.", - "conflictNotification2": "Выберите, какие узлы импортировать и следует ли заменить существующие узлы или импортировать их копию." + "conflictNotification2": "Выберите, какие узлы импортировать и следует ли заменить ими существующие узлы или импортировать их копии." }, "copyMessagePath": "Путь скопирован", "copyMessageValue": "Значение скопировано", @@ -373,12 +373,12 @@ "configAdd": "Добавить", "configUpdate": "Обновить", "configDelete": "Удалить", - "nodesUse": "__count__ узел использует эту конфигурацию", - "nodesUse_plural_2": "__count__ узла используют эту конфигурацию", - "nodesUse_plural_5": "__count__ узлов используют эту конфигурацию", - "addNewConfig": "Добавить новый конфигурационный узел __type__", + "nodesUse": "__count__ узел использует этот конфиг", + "nodesUse_plural_2": "__count__ узла используют этот конфиг", + "nodesUse_plural_5": "__count__ узлов используют этот конфиг", + "addNewConfig": "Добавить новый конфиг узел __type__", "editNode": "Изменить узел __type__", - "editConfig": "Изменить конфигурационный узел __type__", + "editConfig": "Изменить конфиг узел __type__", "addNewType": "Добавить новый __type__...", "nodeProperties": "свойства узла", "label": "Метка", @@ -615,7 +615,7 @@ "info": { "name": "Информация", "tabName": "Имя", - "label": "сведения", + "label": "инфо", "node": "Узел", "type": "Тип", "group": "Группа", @@ -648,8 +648,8 @@ "showTips":"Вы можете открыть советы из панели настроек", "outline": "Структура", "empty": "пусто", - "globalConfig": "Узлы глобальной конфигурации", - "triggerAction": "Запустить действие", + "globalConfig": "Глобальные конфиг узлы", + "triggerAction": "Вызвать действие", "find": "Найти в рабочей области", "search": { "configNodes": "Узлы конфигурации", @@ -671,8 +671,8 @@ }, "config": { "name": "Узлы конфигураций", - "label": "конфигурация", - "global": "На всех потока", + "label": "конфиг", + "global": "На всех потоках", "none": "нет", "subflows": "подпотоки", "flows": "потоки", @@ -690,8 +690,8 @@ "none": "ничего не выбрано", "refresh": "обновите, чтобы загрузить", "empty": "пусто", - "node": "Узел", - "flow": "Поток", + "node": "Узловой", + "flow": "Потоковый", "global": "Глобальный", "deleteConfirm": "Вы уверены, что хотите удалить этот элемент?", "autoRefresh": "Обновить при изменении выбора", @@ -877,7 +877,7 @@ "bool": "логический тип", "json": "JSON", "bin": "буфер", - "date": "отметка времени", + "date": "метка времени", "jsonata": "выражение", "env": "переменная среды", "cred": "учетные данные" diff --git a/packages/node_modules/@node-red/editor-client/locales/ru/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/ru/jsonata.json index fd0ecf24c..920042c25 100755 --- a/packages/node_modules/@node-red/editor-client/locales/ru/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/ru/jsonata.json @@ -243,19 +243,19 @@ "args": "array, function", "desc": "Возвращает одно-единственное значение из массива `array`, которое удовлетворяет предикату `function` (то есть когда `function` возвращает логическое `true` при передаче значения). Выдает исключение, если число подходящих значений не одно.\n\nФункция должна соответствовать следующей сигнатуре: `function(value [, index [, array]])` где value - элемент массива, index - позиция этого значения, а весь массив передается в качестве третьего аргумента" }, - "$encodeUrl": { + "$encodeUrlComponent": { "args": "str", "desc": "Кодирует компонент Uniform Resource Locator (URL), заменяя каждый экземпляр определенных символов одной, двумя, тремя или четырьмя escape-последовательностями, представляющими кодировку UTF-8 символа.\n\nПример: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" }, - "$encodeUrlComponent": { + "$encodeUrl": { "args": "str", "desc": "Кодирует Uniform Resource Locator (URL), заменяя каждый экземпляр определенных символов одной, двумя, тремя или четырьмя escape-последовательностями, представляющими кодировку UTF-8 символа.\n\nПример: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`" }, - "$decodeUrl": { + "$decodeUrlComponent": { "args": "str", "desc": "Декодирует компонент Uniform Resource Locator (URL), ранее созданный с помощью encodeUrlComponent.\n\nПример: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`" }, - "$decodeUrlComponent": { + "$decodeUrl": { "args": "str", "desc": "Декодирует компонент Uniform Resource Locator (URL), ранее созданный с помощью encodeUrl. \n\nПример: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`" }, diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json index a9e6a7b1f..b4403e318 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json @@ -243,19 +243,19 @@ "args": "array, function", "desc": "返回满足参数function谓语的array参数中的唯一值 (比如:传递值时,函数返回布尔值“true”)。如果匹配值的数量不唯一时,则抛出异常。\n\n应在以下签名中提供函数: `function(value [,index [,array []]])` 其中value是数组的每个输入,index是该值的位置,整个数组作为第三个参数传递。" }, - "$encodeUrl": { + "$encodeUrlComponent": { "args": "str", "desc": "通过用表示字符的UTF-8编码的一个,两个,三个或四个转义序列替换某些字符的每个实例,对统一资源定位符(URL)组件进行编码。\n\n示例: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" }, - "$encodeUrlComponent": { + "$encodeUrl": { "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": { + "$decodeUrlComponent": { "args": "str", "desc": "解码以前由encodeUrlComponent创建的统一资源定位器(URL)组件。 \n\n示例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`" }, - "$decodeUrlComponent": { + "$decodeUrl": { "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=шеллы\"`" }, diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json index 3765ab3dd..2b47c1af7 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json @@ -243,19 +243,19 @@ "args": "array, function", "desc": "返回滿足參數function謂語的array參數中的唯一值 (比如:傳遞值時,函數返回布林值“true”)。如果匹配值的數量不唯一時,則拋出異常。\n\n應在以下簽名中提供函數:`function(value [,index [,array []]])`其中value是數組的每個輸入,index是該值的位置,整個數組作為第三個參數傳遞。" }, - "$encodeUrl": { + "$encodeUrlComponent": { "args": "str", "desc": "通過用表示字符的UTF-8編碼的一個,兩個,三個或四個轉義序列替換某些字符的每個實例,對統一資源定位符(URL)組件進行編碼。\n\n示例:`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" }, - "$encodeUrlComponent": { + "$encodeUrl": { "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": { + "$decodeUrlComponent": { "args": "str", "desc": "解碼以前由encodeUrlComponent創建的統一資源定位器(URL)組件。 \n\n示例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`" }, - "$decodeUrlComponent": { + "$decodeUrl": { "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=шеллы\"`" }, diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index a3392fd26..7fedf84be 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -343,17 +343,29 @@ RED.history = (function() { if (ev.changes.hasOwnProperty(i)) { inverseEv.changes[i] = ev.node[i]; if (ev.node._def.defaults && ev.node._def.defaults[i] && ev.node._def.defaults[i].type) { - // This is a config node property - var currentConfigNode = RED.nodes.node(ev.node[i]); - if (currentConfigNode) { - currentConfigNode.users.splice(currentConfigNode.users.indexOf(ev.node),1); - RED.events.emit("nodes:change",currentConfigNode); + // This property is a reference to another node or nodes. + var nodeList = ev.node[i]; + if (!Array.isArray(nodeList)) { + nodeList = [nodeList]; } - var newConfigNode = RED.nodes.node(ev.changes[i]); - if (newConfigNode) { - newConfigNode.users.push(ev.node); - RED.events.emit("nodes:change",newConfigNode); + nodeList.forEach(function(id) { + var currentConfigNode = RED.nodes.node(id); + if (currentConfigNode && currentConfigNode._def.category === "config") { + currentConfigNode.users.splice(currentConfigNode.users.indexOf(ev.node),1); + RED.events.emit("nodes:change",currentConfigNode); + } + }); + nodeList = ev.changes[i]; + if (!Array.isArray(nodeList)) { + nodeList = [nodeList]; } + nodeList.forEach(function(id) { + var newConfigNode = RED.nodes.node(id); + if (newConfigNode && newConfigNode._def.category === "config") { + newConfigNode.users.push(ev.node); + RED.events.emit("nodes:change",newConfigNode); + } + }); } ev.node[i] = ev.changes[i]; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/i18n.js b/packages/node_modules/@node-red/editor-client/src/js/i18n.js index f1d0edfaf..ac439ecbc 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/i18n.js +++ b/packages/node_modules/@node-red/editor-client/src/js/i18n.js @@ -108,6 +108,31 @@ RED.i18n = (function() { } }); }) + }, + + loadPluginCatalogs: function(done) { + var languageList = i18n.functions.toLanguages(localStorage.getItem("editor-language")||i18n.detectLanguage()); + var toLoad = languageList.length; + + languageList.forEach(function(lang) { + $.ajax({ + headers: { + "Accept":"application/json" + }, + cache: false, + url: apiRootUrl+'plugins/messages?lng='+lang, + success: function(data) { + var namespaces = Object.keys(data); + namespaces.forEach(function(ns) { + i18n.addResourceBundle(lang,ns,data[ns]); + }); + toLoad--; + if (toLoad === 0) { + done(); + } + } + }); + }) } } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index ddeea65e6..236f7fa57 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -164,6 +164,21 @@ RED.nodes = (function() { // TODO: too tightly coupled into palette UI } + if (def.defaults) { + for (var d in def.defaults) { + if (def.defaults.hasOwnProperty(d)) { + if (def.defaults[d].type) { + try { + def.defaults[d]._type = parseNodePropertyTypeString(def.defaults[d].type) + } catch(err) { + console.warn(err); + } + } + } + } + } + + RED.events.emit("registry:node-type-added",nt); }, removeNodeType: function(nt) { @@ -193,6 +208,59 @@ RED.nodes = (function() { return (1+Math.random()*4294967295).toString(16); } + function parseNodePropertyTypeString(typeString) { + typeString = typeString.trim(); + var c; + var pos = 0; + var isArray = /\[\]$/.test(typeString); + if (isArray) { + typeString = typeString.substring(0,typeString.length-2); + } + + var l = typeString.length; + var inBrackets = false; + var inToken = false; + var currentToken = ""; + var types = []; + while (pos < l) { + c = typeString[pos]; + if (inToken) { + if (c === "|") { + types.push(currentToken.trim()) + currentToken = ""; + inToken = false; + } else if (c === ")") { + types.push(currentToken.trim()) + currentToken = ""; + inBrackets = false; + inToken = false; + } else { + currentToken += c; + } + } else { + if (c === "(") { + if (inBrackets) { + throw new Error("Invalid character '"+c+"' at position "+pos) + } + inBrackets = true; + } else if (c !== " ") { + inToken = true; + currentToken = c; + } + } + pos++; + } + currentToken = currentToken.trim(); + if (currentToken.length > 0) { + types.push(currentToken) + } + return { + types: types, + array: isArray + } + } + + function addNode(n) { if (n.type.indexOf("subflow") !== 0) { n["_"] = n._def._; @@ -670,6 +738,7 @@ RED.nodes = (function() { node.in = []; node.out = []; node.env = n.env; + node.meta = n.meta; if (exportCreds) { var credentialSet = {}; @@ -780,6 +849,12 @@ RED.nodes = (function() { subflowSet.push(n); } }); + RED.nodes.eachConfig(function(n) { + if (n.z == subflowId) { + subflowSet.push(n); + exportedConfigNodes[n.id] = true; + } + }); var exportableSubflow = createExportableNodeSet(subflowSet, exportedIds, exportedSubflows, exportedConfigNodes); nns = exportableSubflow.concat(nns); } @@ -787,16 +862,29 @@ RED.nodes = (function() { if (node.type !== "subflow") { var convertedNode = RED.nodes.convertNode(node); for (var d in node._def.defaults) { - if (node._def.defaults[d].type && node[d] in configNodes) { - var confNode = configNodes[node[d]]; - var exportable = registry.getNodeType(node._def.defaults[d].type).exportable; - if ((exportable == null || exportable)) { - if (!(node[d] in exportedConfigNodes)) { - exportedConfigNodes[node[d]] = true; - set.push(confNode); + if (node._def.defaults[d].type) { + var nodeList = node[d]; + if (!Array.isArray(nodeList)) { + nodeList = [nodeList]; + } + nodeList = nodeList.filter(function(id) { + if (id in configNodes) { + var confNode = configNodes[id]; + if (confNode._def.exportable !== false) { + if (!(id in exportedConfigNodes)) { + exportedConfigNodes[id] = true; + set.push(confNode); + return true; + } + } + return false; } + return true; + }) + if (nodeList.length === 0) { + convertedNode[d] = Array.isArray(node[d])?[]:"" } else { - convertedNode[d] = ""; + convertedNode[d] = Array.isArray(node[d])?nodeList:nodeList[0] } } } @@ -1360,7 +1448,7 @@ RED.nodes = (function() { } } } else { - if (n.z && !workspaces[n.z]) { + if (n.z && !workspaces[n.z] && !subflow_map[n.z]) { n.z = activeWorkspace; } } @@ -1587,15 +1675,6 @@ RED.nodes = (function() { } } } - // TODO: make this a part of the node definition so it doesn't have to - // be hardcoded here - var nodeTypeArrayReferences = { - "catch":"scope", - "status":"scope", - "complete": "scope", - "link in":"links", - "link out":"links" - } // Remap all wires and config node references for (i=0;i)/); + var totalCount = configs.length; + var stepConfig = function() { + // loader.reportProgress(RED._("event.loadNodes",{count:(totalCount-configs.length)+"/"+totalCount}), 30 + ((totalCount-configs.length)/totalCount)*40 ) + if (configs.length === 0) { + done(); + } else { + var config = configs.shift(); + appendPluginConfig(config,stepConfig); + } + } + stepConfig(); + } + }); + } + + function appendConfig(config, moduleIdMatch, targetContainer, done) { done = done || function(){}; - var m = //.exec(nodeConfig.trim()); var moduleId; - if (m) { - moduleId = m[1]; + if (moduleIdMatch) { + moduleId = moduleIdMatch[1]; + RED._loadingModule = moduleId; } else { moduleId = "unknown"; } try { var hasDeferred = false; - - var nodeConfigEls = $("
"+nodeConfig+"
"); + var nodeConfigEls = $("
"+config+"
"); var scripts = nodeConfigEls.find("script"); var scriptCount = scripts.length; scripts.each(function(i,el) { @@ -38,14 +84,15 @@ var RED = (function() { newScript.onload = function() { scriptCount--; if (scriptCount === 0) { - $("#red-ui-editor-node-configs").append(nodeConfigEls); + $(targetContainer).append(nodeConfigEls); + delete RED._loadingModule; done() } } if ($(el).attr('type') === "module") { newScript.type = "module"; } - $("#red-ui-editor-node-configs").append(newScript); + $(targetContainer).append(newScript); newScript.src = RED.settings.apiRootUrl+srcUrl; hasDeferred = true; } else { @@ -61,7 +108,8 @@ var RED = (function() { } }) if (!hasDeferred) { - $("#red-ui-editor-node-configs").append(nodeConfigEls); + $(targetContainer).append(nodeConfigEls); + delete RED._loadingModule; done(); } } catch(err) { @@ -70,9 +118,27 @@ var RED = (function() { timeout: 10000 }); console.log("["+moduleId+"] "+err.toString()); + delete RED._loadingModule; done(); } } + function appendPluginConfig(pluginConfig,done) { + appendConfig( + pluginConfig, + //.exec(pluginConfig.trim()), + "#red-ui-editor-plugin-configs", + done + ); + } + + function appendNodeConfig(nodeConfig,done) { + appendConfig( + nodeConfig, + //.exec(nodeConfig.trim()), + "#red-ui-editor-node-configs", + done + ); + } function loadNodeList() { loader.reportProgress(RED._("event.loadPalette"), 20) @@ -186,6 +252,7 @@ var RED = (function() { RED.workspaces.show(currentHash.substring(6)); } } catch(err) { + console.warn(err); RED.notify( RED._("event.importError", {message: err.message}), { @@ -269,7 +336,7 @@ var RED = (function() { } } ] - // } else if (RED.settings.theme('palette.editable') !== false) { + // } else if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) { } else { options.buttons = [ { @@ -509,7 +576,7 @@ var RED = (function() { ]}); menuOptions.push(null); - if (RED.settings.theme('palette.editable') !== false) { + if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) { menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"}); menuOptions.push(null); } @@ -544,7 +611,7 @@ var RED = (function() { RED.palette.init(); RED.eventLog.init(); - if (RED.settings.theme('palette.editable') !== false) { + if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) { RED.palette.editor.init(); } else { console.log("Palette editor disabled"); @@ -579,7 +646,7 @@ var RED = (function() { RED.actions.add("core:show-about", showAbout); - loadNodeList(); + loadPluginList(); } @@ -595,6 +662,7 @@ var RED = (function() { '
'+ '
'+ '').appendTo(options.target); + $('
').appendTo(options.target); $('
').appendTo(options.target); $('
').appendTo(options.target); @@ -613,9 +681,12 @@ var RED = (function() { $('').html(theme.header.title).appendTo(logo); } } + if (theme.themes) { + knownThemes = theme.themes; + } }); } - + var knownThemes = null; var initialised = false; function init(options) { @@ -635,7 +706,13 @@ var RED = (function() { buildEditor(options); RED.i18n.init(options, function() { - RED.settings.init(options, loadEditor); + RED.settings.init(options, function() { + if (knownThemes) { + RED.settings.editorTheme = RED.settings.editorTheme || {}; + RED.settings.editorTheme.themes = knownThemes; + } + loadEditor(); + }); }) } diff --git a/packages/node_modules/@node-red/editor-client/src/js/settings.js b/packages/node_modules/@node-red/editor-client/src/js/settings.js index 760165581..109554418 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/settings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/settings.js @@ -57,12 +57,11 @@ RED.settings = (function () { return JSON.parse(localStorage.getItem(key)); } else { var v; - try { - v = RED.utils.getMessageProperty(userSettings,key); - if (v === undefined) { - v = defaultIfUndefined; - } - } catch(err) { + try { v = RED.utils.getMessageProperty(userSettings,key); } catch(err) {} + if (v === undefined) { + try { v = RED.utils.getMessageProperty(RED.settings,key); } catch(err) {} + } + if (v === undefined) { v = defaultIfUndefined; } return v; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js index e46c88b4f..bb82ecea3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js @@ -30,6 +30,27 @@ RED.clipboard = (function() { var pendingImportConfig; + + function downloadData(file, data) { + if (window.navigator.msSaveBlob) { + // IE11 workaround + // IE does not support data uri scheme for downloading data + var blob = new Blob([data], { + type: "data:text/plain;charset=utf-8" + }); + navigator.msSaveBlob(blob, file); + } + else { + var element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(data)); + element.setAttribute('download', file); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + } + } + function setupDialogs() { dialog = $('
') .appendTo("#red-ui-editor") @@ -56,13 +77,8 @@ RED.clipboard = (function() { class: "primary", text: RED._("clipboard.download"), click: function() { - var element = document.createElement('a'); - element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent($("#red-ui-clipboard-dialog-export-text").val())); - element.setAttribute('download', "flows.json"); - element.style.display = 'none'; - document.body.appendChild(element); - element.click(); - document.body.removeChild(element); + var data = $("#red-ui-clipboard-dialog-export-text").val(); + downloadData("flows.json", data); $( this ).dialog( "close" ); } }, @@ -72,9 +88,7 @@ RED.clipboard = (function() { text: RED._("clipboard.export.copy"), click: function() { if (activeTab === "red-ui-clipboard-dialog-export-tab-clipboard") { - $("#red-ui-clipboard-dialog-export-text").select(); - document.execCommand("copy"); - document.getSelection().removeAllRanges(); + copyText($("#red-ui-clipboard-dialog-export-text").val()); RED.notify(RED._("clipboard.nodesExported"),{id:"clipboard"}); $( this ).dialog( "close" ); } else { @@ -222,14 +236,22 @@ RED.clipboard = (function() { ''+ '
'+ '
'+ - '
'+ - ''+ + '
'+ + '
    '+ '
    '+ - '
    '+ - ''+ - ''+ - ''+ - ''+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + '
    '+ + ''+ + '
    '+ + '
    '+ + ''+ + ''+ + ''+ + ''+ + '
    '+ '
    '+ '
    '+ '
    '+ @@ -592,6 +614,30 @@ RED.clipboard = (function() { }) loadFlowLibrary(libraryBrowser,"local",RED._("library.types.local")); + var clipboardTabs = RED.tabs.create({ + id: "red-ui-clipboard-dialog-export-tab-clipboard-tabs", + onchange: function(tab) { + $(".red-ui-clipboard-dialog-export-tab-clipboard-tab").hide(); + $("#" + tab.id).show(); + } + }); + + clipboardTabs.addTab({ + id: "red-ui-clipboard-dialog-export-tab-clipboard-preview", + label: RED._("clipboard.exportNodes") + }); + + clipboardTabs.addTab({ + id: "red-ui-clipboard-dialog-export-tab-clipboard-json", + label: RED._("editor.types.json") + }); + + + var previewList = $("#red-ui-clipboard-dialog-export-tab-clipboard-preview-list").css({position:"absolute",top:0,right:0,bottom:0,left:0}).treeList({ + data: [] + }) + refreshExportPreview(); + $("#red-ui-clipboard-dialog-tab-library-name").val("flows.json").select(); dialogContainer.i18n(); @@ -630,10 +676,10 @@ RED.clipboard = (function() { } $(this).parent().children().removeClass('selected'); $(this).addClass('selected'); - var type = $(this).attr('id'); + var type = $(this).attr('id').substring("red-ui-clipboard-dialog-export-rng-".length); var flow = ""; var nodes = null; - if (type === 'red-ui-clipboard-dialog-export-rng-selected') { + if (type === 'selected') { var selection = RED.workspaces.selection(); if (selection.length > 0) { nodes = []; @@ -647,14 +693,14 @@ RED.clipboard = (function() { } // Don't include the subflow meta-port nodes in the exported selection 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 === 'flow') { var activeWorkspace = RED.workspaces.active(); nodes = RED.nodes.groups(activeWorkspace); nodes = nodes.concat(RED.nodes.filterNodes({z:activeWorkspace})); var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace); nodes.unshift(parentNode); nodes = RED.nodes.createExportableNodeSet(nodes); - } else if (type === 'red-ui-clipboard-dialog-export-rng-full') { + } else if (type === 'full') { nodes = RED.nodes.createCompleteNodeSet(false); } if (nodes !== null) { @@ -670,8 +716,10 @@ RED.clipboard = (function() { $("#red-ui-clipboard-dialog-export").addClass('disabled'); } $("#red-ui-clipboard-dialog-export-text").val(flow); - setTimeout(function() { $("#red-ui-clipboard-dialog-export-text").scrollTop(0); },50); - $("#red-ui-clipboard-dialog-export-text").trigger("focus"); + setTimeout(function() { + $("#red-ui-clipboard-dialog-export-text").scrollTop(0); + refreshExportPreview(type); + },50); }) $("#red-ui-clipboard-dialog-ok").hide(); @@ -717,6 +765,93 @@ RED.clipboard = (function() { } + function refreshExportPreview(type) { + + var flowData = $("#red-ui-clipboard-dialog-export-text").val() || "[]"; + var flow = JSON.parse(flowData); + var flows = {}; + var subflows = {}; + var nodes = []; + var nodesByZ = {}; + + var treeFlows = []; + var treeSubflows = []; + + flow.forEach(function(node) { + if (node.type === "tab") { + flows[node.id] = { + element: getFlowLabel(node,false), + deferBuild: type !== "flow", + expanded: type === "flow", + children: [] + }; + treeFlows.push(flows[node.id]) + } else if (node.type === "subflow") { + subflows[node.id] = { + element: getNodeLabel(node,false), + deferBuild: true, + children: [] + }; + treeSubflows.push(subflows[node.id]) + } else { + nodes.push(node); + } + }); + + var globalNodes = []; + var parentlessNodes = []; + + nodes.forEach(function(node) { + var treeNode = { + element: getNodeLabel(node, false, false) + }; + if (node.z) { + if (!flows[node.z] && !subflows[node.z]) { + parentlessNodes.push(treeNode) + } else if (flows[node.z]) { + flows[node.z].children.push(treeNode) + } else if (subflows[node.z]) { + subflows[node.z].children.push(treeNode) + } + } else { + globalNodes.push(treeNode); + } + }); + var treeData = []; + + if (parentlessNodes.length > 0) { + treeData = treeData.concat(parentlessNodes); + } + if (type === "flow") { + treeData = treeData.concat(treeFlows); + } else if (treeFlows.length > 0) { + treeData.push({ + label: RED._("menu.label.flows"), + deferBuild: treeFlows.length > 20, + expanded: treeFlows.length <= 20, + children: treeFlows + }) + } + if (treeSubflows.length > 0) { + treeData.push({ + label: RED._("menu.label.subflows"), + deferBuild: treeSubflows.length > 10, + expanded: treeSubflows.length <= 10, + children: treeSubflows + }) + } + if (globalNodes.length > 0) { + treeData.push({ + label: RED._("sidebar.info.globalConfig"), + deferBuild: globalNodes.length > 10, + expanded: globalNodes.length <= 10, + children: globalNodes + }) + } + + $("#red-ui-clipboard-dialog-export-tab-clipboard-preview-list").treeList('data',treeData); + } + function loadFlowLibrary(browser,library,label) { // if (includeExamples) { // listing.push({ @@ -756,6 +891,7 @@ RED.clipboard = (function() { } function copyText(value,element,msg) { var truncated = false; + var currentFocus = document.activeElement; if (typeof value !== "string" ) { value = JSON.stringify(value, function(key,value) { if (value !== null && typeof value === 'object') { @@ -787,7 +923,7 @@ RED.clipboard = (function() { if (truncated) { msg += "_truncated"; } - $("#red-ui-clipboard-hidden").val(value).select(); + $("#red-ui-clipboard-hidden").val(value).focus().select(); var result = document.execCommand("copy"); if (result && element) { var popover = RED.popover.create({ @@ -801,6 +937,10 @@ RED.clipboard = (function() { },1000); popover.open(); } + $("#red-ui-clipboard-hidden").val(""); + if (currentFocus) { + $(currentFocus).focus(); + } return result; } @@ -1103,7 +1243,7 @@ RED.clipboard = (function() { init: function() { setupDialogs(); - $('').appendTo("#red-ui-editor"); + $('