diff --git a/.travis.yml b/.travis.yml index 4cffa0389..323f0948a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,9 @@ addons: language: node_js matrix: include: + - node_js: "16" + script: + - ./node_modules/.bin/grunt no-coverage - node_js: "14" script: - ./node_modules/.bin/grunt && ( cat coverage/lcov.info | $(npm get prefix)/bin/coveralls || true ) && rm -rf coverage @@ -13,6 +16,7 @@ matrix: - node_js: "12" script: - ./node_modules/.bin/grunt no-coverage + allow_failures: - node_js: "16" script: - ./node_modules/.bin/grunt no-coverage diff --git a/API.md b/API.md index 692a34723..97789082f 100644 --- a/API.md +++ b/API.md @@ -1,8 +1,12 @@ -Node-RED Modules ---- +Node-RED consists of 6 node modules under the `@node-red` scope, which are pulled together +by the top-level `node-red` module. The typical scenario is where you are embedding Node-RED into your +own application, in which case you would use the `node-red` module rather than any of the +internal modules directly. + +```javascript +let RED = require("node-red"); +``` -Node-RED provides a set of node modules that implement different parts of the -application. Module | Description -------|------- @@ -11,5 +15,5 @@ Module | Description [@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](@node-red_registry.html) | the internal node registry -@node-red/nodes | the default set of core nodes +@node-red/nodes | the default set of core nodes. This module only contains the Node-RED nodes - it does not expose any APIs. @node-red/editor-client | the client-side resources of the Node-RED editor application diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cb1464cc..78ce7d480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,101 @@ +### 1.3.5 Maintenance Release + +Editor + + - Open subflow tab next to active tab rather than at the end + - Shrink default notification box + - Support mousewheel scroll in tab bar + - Revert some of #2967 to fix treeList gutter width calculation + - Prevent unknown node from breaking editor + - Stop module with missing types from preventing editor load + - Handle sidebar tab that no longer exists when setting first active + - Fix plugin loading when browser sends unrecognised lang + - Prevent error whilst drag/drop importing from leaving dropTarget visible Fixes #2982 + - Fix scaling issues when dragging nodes into scaled workspace + - Fix incorrect shortcut keys in info tips (#2980) @kazuhitoyokoi + - Reduce code duplication around node/label generation + - Fix theme handling when no editorTheme.page setting + - Fix jshint error in treeList + +Runtime + + - Fix error handling in runtime/lib/api/nodes + - Add Node 16 with sass fixed + - Migrate from node-sass to sass (#2984) + - Fix "installRetry" was declared a constant and changed (#2974) @aheissenberger + +Nodes + + - Function: Fix 'SyntaxError' in Function node when last line of on-stop is a comment + - Function: Fix Function tab label names in the node help text Closes #2978 + - Function: Update Japanese info text of function node (#2985) @HiroyasuNishiyama + +### 1.3.4 Maintenance Release + +Editor + - Allow nodes to access resolved theme files Fixes #2968 + - Fix importing node to currently flow rather than match its old z value + - Don't let 'escape' whilst moving nodes interrupt things Fixes #2960 + - Sort context stores in TypedInput and ensure default first Fixes #2954 + - Fix margin between nodes on palette (#2947) @kazuhitoyokoi + - Ensure typedInput option is selected in dropdown menu Part of #2945 + - Ensure typedInput without value has focus class removed Closes #2945 + - TreeList: Fix remove item when depth=0 and wrong gutter calc (#2967) @hanc2006 + +Runtime + - Handle subflow modules that contain subflows + - Timeout http upgrade requests that are not otherwise handled Fixes #2956 + - Fix error on auto commit for no flow change (#2957) @HiroyasuNishiyama + +Nodes + + - CSV: Fix CSV handling of special chars as separators + - Delay: Give delay node random mina nd max more space so you can see complete value + - Exec: fix grunt fail on exec node test (#2964) @HiroyasuNishiyama + - Function: Ensure function expand button is above vertical scrollbar Fixes #2955 + - Inject: Fix inject node output tooltip extra property count + + +### 1.3.3: Maintenance Release + +Editor + + - Fix package semver comparison to allow >1 version increment + - Prevent TypedInput label overflowing element Fixes #2941 + - Remove TypedInput from tab focus when only one type available + - Make typedInput.disable more consistent in behaviour Fixes #2942 + - Fix project credential secret reset handling Part of #2868 + +Runtime + + - Export package version in Grunt file so docs template can access + +Nodes + + - CSV: ensure CSV node can send false as string + - HTTPIn: handle application/x-protobuf as Buffer type (#2935 #2938) @hardillb + - MQTT: Ensure mqtt-close message is published when closing mqtt nodes + + +### 1.3.2: Maintenance Release + +Runtime + - Handle package.json without dependencies section + +Editor + + - Fix variable reference error in editableList Fixes #2933 + - Fix handling of user-provided keymap Fixes #2926 + - Ensure theme login image is passed through to api response Fixes #2929 + - Add Japanese translations for Node-RED v1.3.1 (#2930) @kazuhitoyokoi + +Nodes + + - CSV: Fix CSV parsing with other than , separator + - File out: Fix timing of msg.send to be after close + - Function: describe `node.outputCount` in help text + - MQTT: Fix MQTT Broker TLS config row layout Fixes #2927 + - Split: add comment to info re $N being number of messages arriving ### 1.3.1: Maintenance Release diff --git a/Gruntfile.js b/Gruntfile.js index c52f05115..5d9b8ba5c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,7 +16,7 @@ var path = require("path"); var fs = require("fs-extra"); -var sass = require("node-sass"); +var sass = require("sass"); module.exports = function(grunt) { @@ -40,8 +40,10 @@ module.exports = function(grunt) { if (nonHeadless) { process.env.NODE_RED_NON_HEADLESS = true; } + const pkg = grunt.file.readJSON('package.json'); + process.env.NODE_RED_PACKAGE_VERSION = pkg.version; grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), + pkg: pkg, paths: { dist: ".dist" }, @@ -135,6 +137,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/jquery-addons.js", "packages/node_modules/@node-red/editor-client/src/js/red.js", "packages/node_modules/@node-red/editor-client/src/js/events.js", + "packages/node_modules/@node-red/editor-client/src/js/hooks.js", "packages/node_modules/@node-red/editor-client/src/js/i18n.js", "packages/node_modules/@node-red/editor-client/src/js/settings.js", "packages/node_modules/@node-red/editor-client/src/js/user.js", @@ -179,6 +182,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editor.js", "packages/node_modules/@node-red/editor-client/src/js/ui/editors/*.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/*.js", "packages/node_modules/@node-red/editor-client/src/js/ui/event-log.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tray.js", "packages/node_modules/@node-red/editor-client/src/js/ui/clipboard.js", @@ -283,7 +287,9 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/public/index.html", "packages/node_modules/@node-red/editor-client/public/favicon.ico", "packages/node_modules/@node-red/editor-client/public/icons", - "packages/node_modules/@node-red/editor-client/public/vendor" + "packages/node_modules/@node-red/editor-client/public/vendor", + "packages/node_modules/@node-red/editor-client/public/types/node", + "packages/node_modules/@node-red/editor-client/public/types/node-red", ] }, release: { @@ -373,11 +379,24 @@ module.exports = function(grunt) { src: [ 'ace/**', 'jquery/css/base/**', - 'font-awesome/**' + 'font-awesome/**', + 'monaco/dist/**', + 'monaco/types/extraLibs.js', + 'monaco/style.css', + 'monaco/monaco-bootstrap.js' ], expand: true, dest: 'packages/node_modules/@node-red/editor-client/public/vendor/' }, + { + cwd: 'packages/node_modules/@node-red/editor-client/src', + src: [ + 'types/node/*.ts', + 'types/node-red/*.ts', + ], + expand: true, + dest: 'packages/node_modules/@node-red/editor-client/public/' + }, { cwd: 'packages/node_modules/@node-red/editor-client/src/icons', src: '**', @@ -469,7 +488,8 @@ module.exports = function(grunt) { ], options: { destination: 'docs', - configure: './jsdoc.json' + configure: './jsdoc.json', + fred: "hi there" } }, _editor: { diff --git a/package.json b/package.json index 3906ebbef..c114af177 100644 --- a/package.json +++ b/package.json @@ -26,30 +26,32 @@ } ], "dependencies": { - "ajv": "6.12.6", + "acorn": "8.3.0", + "acorn-walk": "8.1.0", + "ajv": "8.6.0", "async-mutex": "0.3.1", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "body-parser": "1.19.0", - "cheerio": "0.22.0", + "cheerio": "1.0.0-rc.10", "clone": "2.1.2", "content-type": "1.0.4", "cookie": "0.4.1", "cookie-parser": "1.4.5", "cors": "2.8.5", - "cron": "1.7.2", + "cronosjs": "1.7.1", "denque": "1.5.0", "express": "4.17.1", - "express-session": "1.17.1", + "express-session": "1.17.2", "form-data": "4.0.0", - "fs-extra": "9.1.0", + "fs-extra": "10.0.0", "fs.notify": "0.0.4", "got": "11.8.2", "hash-sum": "2.0.0", "hpagent": "0.1.1", "https-proxy-agent": "5.0.0", - "i18next": "20.2.1", - "iconv-lite": "0.6.2", + "i18next": "20.3.1", + "iconv-lite": "0.6.3", "is-utf8": "0.2.1", "js-yaml": "3.14.0", "json-stringify-safe": "5.0.1", @@ -62,7 +64,7 @@ "mqtt": "4.2.6", "multer": "1.4.2", "mustache": "4.2.0", - "node-red-admin": "^0.2.6", + "node-red-admin": "0.2.7", "nopt": "5.0.0", "oauth2orize": "1.11.0", "on-headers": "1.0.2", @@ -74,19 +76,19 @@ "semver": "7.3.5", "tar": "6.1.0", "tough-cookie": "4.0.0", - "uglify-js": "3.13.3", + "uglify-js": "3.13.9", "uuid": "8.3.2", - "ws": "6.2.1", + "ws": "7.4.6", "xml2js": "0.4.23" }, "optionalDependencies": { "bcrypt": "5.0.1" }, "devDependencies": { - "dompurify": "2.2.7", - "grunt": "1.3.0", + "dompurify": "2.2.9", + "grunt": "1.4.1", "grunt-chmod": "~1.1.1", - "grunt-cli": "~1.4.2", + "grunt-cli": "~1.4.3", "grunt-concurrent": "3.0.0", "grunt-contrib-clean": "~2.0.0", "grunt-contrib-compress": "2.0.0", @@ -103,18 +105,18 @@ "grunt-sass": "~3.1.0", "grunt-simple-mocha": "~0.4.1", "grunt-simple-nyc": "^3.0.1", - "i18next-http-backend": "1.2.1", + "i18next-http-backend": "1.2.6", "jquery-i18next": "1.2.1", "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", - "marked": "2.0.1", + "marked": "2.0.7", "minami": "1.2.3", - "mocha": "8.3.2", + "mocha": "8.4.0", "node-red-node-test-helper": "^0.2.7", - "node-sass": "^5.0.0", "nodemon": "2.0.7", "proxy": "^1.0.2", + "sass": "1.34.1", "should": "13.2.3", - "sinon": "10.0.1", + "sinon": "11.1.1", "stoppable": "^1.1.0", "supertest": "6.1.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 cf3505439..be9bb5317 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 @@ -39,6 +39,15 @@ module.exports = { var adminApp = express(); + var defaultServerSettings = { + "x-powered-by": false + } + var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{}); + for (var eOption in serverSettings) { + adminApp.set(eOption, serverSettings[eOption]); + } + + // Flows adminApp.get("/flows",needsPermission("flows.read"),flows.get,apiUtil.errorHandler); adminApp.post("/flows",needsPermission("flows.write"),flows.post,apiUtil.errorHandler); 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 index 3d49a7e8a..ac6c6f701 100644 --- a/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js +++ b/packages/node_modules/@node-red/editor-api/lib/admin/plugins.js @@ -17,9 +17,8 @@ module.exports = { }) } else { opts.lang = apiUtils.determineLangFromHeaders(req.acceptsLanguages()); - if (/[^a-z\-\*]/i.test(opts.lang)) { - res.json({}); - return; + if (/[^0-9a-z=\-\*]/i.test(opts.lang)) { + opts.lang = "en-US"; } runtimeAPI.plugins.getPluginConfigs(opts).then(function(configs) { res.send(configs); @@ -32,9 +31,8 @@ module.exports = { lang: req.query.lng, req: apiUtils.getRequestLogObject(req) } - if (/[^a-z\-\*]/i.test(opts.lang)) { - res.json({}); - return; + if (/[^0-9a-z=\-\*]/i.test(opts.lang)) { + opts.lang = "en-US"; } runtimeAPI.plugins.getPluginCatalogs(opts).then(function(result) { res.json(result); diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js index d4ec10f08..fb95ede7b 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js @@ -90,7 +90,7 @@ function getToken(req,res,next) { return server.token()(req,res,next); } -function login(req,res) { +async function login(req,res) { var response = {}; if (settings.adminAuth) { var mergedAdminAuth = Object.assign({}, settings.adminAuth, settings.adminAuth.module); @@ -116,8 +116,9 @@ function login(req,res) { response.prompts[0].image = theme.serveFile('/login/',mergedAdminAuth.strategy.image); } } - if (theme.context().login && theme.context().login.image) { - response.image = theme.context().login.image; + let themeContext = await theme.context(); + if (themeContext.login && themeContext.login.image) { + response.image = themeContext.login.image; } } res.json(response); 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 6943027c2..f210d90fe 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 @@ -64,10 +64,12 @@ module.exports = { } }); } - if (settings.httpServerOptions) { - for (var eOption in settings.httpServerOptions) { - editorApp.set(eOption, settings.httpServerOptions[eOption]); - } + var defaultServerSettings = { + "x-powered-by": false + } + var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{}); + for (var eOption in serverSettings) { + editorApp.set(eOption, serverSettings[eOption]); } editorApp.get("/",ensureRuntimeStarted,ui.ensureSlash,ui.editor); 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 3ea4ede73..0953a0adc 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 @@ -33,7 +33,7 @@ var defaultContext = { asset: { red: (process.env.NODE_ENV == "development")? "red/red.js":"red/red.min.js", main: (process.env.NODE_ENV == "development")? "red/main.js":"red/main.min.js", - + vendorMonaco: "" } }; @@ -93,6 +93,7 @@ module.exports = { themeContext = clone(defaultContext); themeSettings = null; theme = settings.editorTheme || {}; + themeContext.asset.vendorMonaco = ((theme.codeEditor || {}).lib === "monaco") ? "vendor/monaco/monaco-bootstrap.js" : ""; activeTheme = theme.theme; }, @@ -129,6 +130,14 @@ module.exports = { } themeContext.page.title = theme.page.title || themeContext.page.title; + + // Store the resolved urls to these resources so nodes (such as Debug) + // can access them + theme.page._ = { + css: themeContext.page.css, + scripts: themeContext.page.scripts, + favicon: themeContext.page.favicon + } } if (theme.header) { @@ -223,6 +232,8 @@ module.exports = { themePlugin.path ); themeContext.page.css = cssFiles.concat(themeContext.page.css || []) + theme.page = theme.page || {_:{}} + theme.page._.css = cssFiles.concat(theme.page._.css || []) } if (themePlugin.scripts) { const scriptFiles = serveFilesFromTheme( @@ -232,6 +243,8 @@ module.exports = { themePlugin.path ) themeContext.page.scripts = scriptFiles.concat(themeContext.page.scripts || []) + theme.page = theme.page || {_:{}} + theme.page._.scripts = cssFiles.concat(theme.page._.scripts || []) } } activeThemeInitialised = true; 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 f66e9094b..258e6e514 100644 --- a/packages/node_modules/@node-red/editor-api/lib/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/index.js @@ -64,6 +64,14 @@ function init(settings,_server,storage,runtimeAPI) { } } + var defaultServerSettings = { + "x-powered-by": false + } + var serverSettings = Object.assign({},defaultServerSettings,settings.httpServerOptions||{}); + for (var eOption in serverSettings) { + adminApp.set(eOption, serverSettings[eOption]); + } + auth.init(settings,storage); var maxApiRequestSize = settings.apiMaxLength || '5mb'; diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 3cd87897f..e773d7347 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -22,7 +22,7 @@ "body-parser": "1.19.0", "clone": "2.1.2", "cors": "2.8.5", - "express-session": "1.17.1", + "express-session": "1.17.2", "express": "4.17.1", "memorystore": "1.6.6", "mime": "2.5.2", @@ -32,7 +32,7 @@ "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", "passport": "0.4.1", - "ws": "6.2.1" + "ws": "7.4.6" }, "optionalDependencies": { "bcrypt": "5.0.1" 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 466eda94d..3f1e32588 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 @@ -111,6 +111,7 @@ "projects-open": "Open", "projects-settings": "Project Settings", "showNodeLabelDefault": "Show label of newly added nodes", + "codeEditor": "Code Editor", "groups": "Groups", "groupSelection": "Group selection", "ungroupSelection": "Ungroup selection", @@ -885,6 +886,9 @@ "eval": "Error evaluating expression:\n __message__" } }, + "monaco": { + "setTheme": "Set theme" + }, "jsEditor": { "title": "JavaScript editor" }, 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 282dfe551..1e8956254 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 @@ -52,8 +52,8 @@ "desc": "Finds occurrences of `pattern` within `str` and replaces them with `replacement`.\n\nThe optional `limit` parameter is the maximum number of replacements." }, "$now": { - "args":"", - "desc":"Generates a timestamp in ISO 8601 compatible format and returns it as a string." + "args":"$[picture [, timezone]]", + "desc":"Generates a timestamp in ISO 8601 compatible format and returns it as a string. If the optional picture and timezone parameters are supplied, then the current timestamp is formatted as described by the `$fromMillis()` function" }, "$base64encode": { "args":"string", 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 6a7edb202..3870c9336 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 @@ -524,8 +524,8 @@ "title": "パレットの管理", "palette": "パレット", "times": { - "seconds": "秒前", - "minutes": "分前", + "seconds": "数秒前", + "minutes": "数分前", "minutesV": "__count__ 分前", "hoursV": "__count__ 時間前", "hoursV_plural": "__count__ 時間前", diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json index e123aef1e..792f6471a 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json @@ -648,7 +648,7 @@ }, "context": { "name": "上下文数据", - "label": "上下午", + "label": "上下文", "none": "未选择", "refresh": "刷新以加载", "empty": "空", 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 7fedf84be..338d955e1 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 @@ -66,12 +66,14 @@ RED.history = (function() { var importedResult = RED.nodes.import(ev.config,{importMap: importMap}) inverseEv = { t: 'replace', - config: importedResult.removedNodes + config: importedResult.removedNodes, + dirty: RED.nodes.dirty() } } } else if (ev.t == 'add') { inverseEv = { t: "delete", + dirty: RED.nodes.dirty() }; if (ev.nodes) { inverseEv.nodes = []; @@ -158,7 +160,8 @@ RED.history = (function() { } else if (ev.t == "delete") { inverseEv = { - t: "add" + t: "add", + dirty: RED.nodes.dirty() }; if (ev.workspaces) { inverseEv.workspaces = []; @@ -300,11 +303,12 @@ RED.history = (function() { } else if (ev.t == "move") { inverseEv = { t: 'move', - nodes: [] + nodes: [], + dirty: RED.nodes.dirty() }; for (i=0;i -1) { - label = label.substring(0,newlineIndex)+"..."; - } - return label; - } - function getFlowLabel(n) { n = JSON.parse(JSON.stringify(n)); n._def = RED.nodes.getType(n.type) || {}; @@ -1227,16 +1211,8 @@ RED.clipboard = (function() { if (n._def) { n._ = n._def._; } - var div = $('
',{class:"red-ui-info-outline-item"}); - RED.utils.createNodeIcon(n).appendTo(div); - var contentDiv = $('
',{class:"red-ui-search-result-description"}).appendTo(div); - var labelText = getNodeLabelText(n); - var label = $('
',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv); - if (labelText) { - label.text(labelText) - } else { - label.html(n.type) - } + var div = $('
',{class:"red-ui-node-list-item"}); + RED.utils.createNodeIcon(n,true).appendTo(div); return div; } @@ -1284,22 +1260,27 @@ RED.clipboard = (function() { hideDropTarget(); }) .on("drop",function(event) { - if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { - var data = event.originalEvent.dataTransfer.getData("text/plain"); - data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1); - importNodes(data); - } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) { - var files = event.originalEvent.dataTransfer.files; - if (files.length === 1) { - var file = files[0]; - var reader = new FileReader(); - reader.onload = (function(theFile) { - return function(e) { - importNodes(e.target.result); - }; - })(file); - reader.readAsText(file); + try { + if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { + var data = event.originalEvent.dataTransfer.getData("text/plain"); + data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1); + importNodes(data); + } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) { + var files = event.originalEvent.dataTransfer.files; + if (files.length === 1) { + var file = files[0]; + var reader = new FileReader(); + reader.onload = (function(theFile) { + return function(e) { + importNodes(e.target.result); + }; + })(file); + reader.readAsText(file); + } } + } catch(err) { + // Ensure any errors throw above doesn't stop the drop target from + // being hidden. } hideDropTarget(); event.preventDefault(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js index 7f823289e..ea1938e5c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js @@ -18,7 +18,7 @@ /** * options: * - addButton : boolean|string - text for add label, default 'add' - * - buttons : array - list of custom buttons (objects with fields 'label', 'icon', 'title', 'click') + * - buttons : array - list of custom buttons (objects with fields 'id', 'label', 'icon', 'title', 'click') * - height : number|'auto' * - resize : function - called when list as a whole is resized * - resizeItem : function(item) - called to resize individual item @@ -71,7 +71,7 @@ var buttons = this.options.buttons || []; if (this.options.addButton !== false) { - var addLabel, addTittle; + var addLabel, addTitle; if (typeof this.options.addButton === 'string') { addLabel = this.options.addButton } else { @@ -94,7 +94,7 @@ } buttons.forEach(function(button) { - var element = $('') + var element = $('') .appendTo(that.topContainer) .on("click", function(evt) { evt.preventDefault(); @@ -102,7 +102,10 @@ button.click(evt); } }); - + + if (button.id) { + element.attr("id", button.id); + } if (button.title) { element.attr("title", button.title); } @@ -113,7 +116,7 @@ element.append($("").text(" " + button.label)); } }); - + if (this.element.css("position") === "absolute") { ["top","left","bottom","right"].forEach(function(s) { var v = that.element.css(s); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js index baaac7a8b..ee6ea1960 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js @@ -100,7 +100,22 @@ RED.tabs = (function() { if (options.scrollable) { wrapper.addClass("red-ui-tabs-scrollable"); scrollContainer.addClass("red-ui-tabs-scroll-container"); - scrollContainer.on("scroll",updateScroll); + scrollContainer.on("scroll",function(evt) { + // Generated by trackpads - not mousewheel + updateScroll(evt); + }); + scrollContainer.on("wheel", function(evt) { + if (evt.originalEvent.deltaX === 0) { + // Prevent the scroll event from firing + evt.preventDefault(); + + // Assume this is wheel event which might not trigger + // the scroll event, so do things manually + var sl = scrollContainer.scrollLeft(); + sl -= evt.originalEvent.deltaY; + scrollContainer.scrollLeft(sl); + } + }) scrollLeft = $('
').appendTo(wrapper).find("a"); scrollLeft.on('mousedown',function(evt) { scrollEventHandler(evt,'-=150') }).on('click',function(evt){ evt.preventDefault();}); scrollRight = $('
').appendTo(wrapper).find("a"); @@ -770,6 +785,9 @@ RED.tabs = (function() { count: function() { return ul.find("li.red-ui-tab").length; }, + activeIndex: function() { + return ul.find("li.active").index() + }, contains: function(id) { return ul.find("a[href='#"+id+"']").length > 0; }, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js index a6db02ffb..6619ad76e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js @@ -312,6 +312,7 @@ } if (child.depth !== parent.depth+1) { child.depth = parent.depth+1; + // var labelPaddingWidth = ((child.gutter ? child.gutter[0].offsetWidth + 2 : 0) + (child.depth * 20)); var labelPaddingWidth = ((child.gutter?child.gutter.width()+2:0)+(child.depth*20)); child.treeList.labelPadding.width(labelPaddingWidth+'px'); if (child.element) { @@ -348,6 +349,18 @@ that._selected.delete(item); delete item.treeList; delete that._items[item.id]; + if(item.depth === 0) { + for(var key in that._items) { + if (that._items.hasOwnProperty(key)) { + var child = that._items[key]; + if(child.parent && child.parent.id === item.id) { + delete that._items[key].treeList; + delete that._items[key]; + } + } + } + that._data = that._data.filter(function(data) { return data.id !== item.id}) + } } item.treeList.insertChildAt = function(newItem,position,select) { newItem.parent = item; @@ -480,7 +493,10 @@ if (item.treeList.container) { $(item.element).remove(); $(element).appendTo(item.treeList.label); - var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(item.depth*20); + // using the JQuery Object, the gutter width will + // be wrong when the element is reattached the second time + var labelPaddingWidth = (item.gutter ? item.gutter[0].offsetWidth + 2 : 0) + (item.depth * 20); + $(element).css({ width: "calc(100% - "+(labelPaddingWidth+20+(item.icon?20:0))+"px)" }) @@ -517,6 +533,7 @@ } var labelPaddingWidth = (item.gutter?item.gutter.width()+2:0)+(depth*20); + // var labelPaddingWidth = (item.gutter ? item.gutter[0].offsetWidth + 2 : 0) + (depth * 20) item.treeList.labelPadding = $('').css({ display: "inline-block", width: labelPaddingWidth+'px' diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index 1b5af5f13..0401be1b9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -15,7 +15,7 @@ **/ (function($) { var contextParse = function(v,defaultStore) { - var parts = RED.utils.parseContextKey(v, defaultStore); + var parts = RED.utils.parseContextKey(v, defaultStore&&defaultStore.value); return { option: parts.store, value: parts.key @@ -279,6 +279,14 @@ var contextStores = RED.settings.context.stores; var contextOptions = contextStores.map(function(store) { return {value:store,label: store, icon:''} + }).sort(function(A,B) { + if (A.value === RED.settings.context.default) { + return -1; + } else if (B.value === RED.settings.context.default) { + return 1; + } else { + return A.value.localeCompare(B.value); + } }) if (contextOptions.length < 2) { allOptions.flow.options = []; @@ -389,6 +397,11 @@ evt.stopPropagation(); }).on('focus', function() { that.uiSelect.addClass('red-ui-typedInput-focus'); + }).on('blur', function() { + var opt = that.typeMap[that.propertyType]; + if (opt.hasValue === false) { + that.uiSelect.removeClass('red-ui-typedInput-focus'); + } }) // explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline' @@ -438,7 +451,11 @@ }); this._showMenu(this.optionMenu,this.optionSelectTrigger); - var selectedOption = this.optionMenu.find("[value='"+this.optionValue+"']"); + var targetValue = this.optionValue; + if (this.optionValue === null || this.optionValue === undefined) { + targetValue = this.value(); + } + var selectedOption = this.optionMenu.find("[value='"+targetValue+"']"); if (selectedOption.length === 0) { selectedOption = this.optionMenu.children(":first"); } @@ -669,6 +686,11 @@ that.typeMap[result.value] = result; return result; }); + if (this.typeList.length < 2) { + this.selectTrigger.attr("tabindex", -1) + } else { + this.selectTrigger.attr("tabindex", 0) + } this.selectTrigger.toggleClass("disabled", this.typeList.length === 1); this.selectTrigger.find(".fa-caret-down").toggle(this.typeList.length > 1) if (this.menu) { @@ -768,6 +790,11 @@ if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) { this.selectLabel.text(opt.label); } + if (opt.label) { + this.selectTrigger.attr("title",opt.label); + } else { + this.selectTrigger.attr("title",""); + } if (opt.hasValue === false) { this.selectTrigger.addClass("red-ui-typedInput-full-width"); } else { @@ -1004,16 +1031,17 @@ this.uiSelect.hide(); }, disable: function(val) { - if(val === true) { + if(val === undefined || !!val ) { this.uiSelect.attr("disabled", "disabled"); - } else if (val === false) { - this.uiSelect.attr("disabled", null); //remove attr } else { - this.uiSelect.attr("disabled", val); //user value + this.uiSelect.attr("disabled", null); //remove attr } }, + enable: function() { + this.uiSelect.attr("disabled", null); //remove attr + }, disabled: function() { - return this.uiSelect.attr("disabled"); + return this.uiSelect.attr("disabled") === "disabled"; } }); })(jQuery); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 9c3274441..98b3dd450 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -1525,6 +1525,7 @@ var buildingEditDialog = false; console.log("oneditresize",editing_node.id,editing_node.type,err.toString()); } } + if (nodeInfoEditor) {nodeInfoEditor.resize();} //markdown editor doesnt size up without this - idkw!? }, open: function(tray, done) { if (editing_node.hasOwnProperty('outputs')) { @@ -2759,98 +2760,11 @@ var buildingEditDialog = false; } } - function createEditor(options) { - var el = options.element || $("#"+options.id)[0]; - var toolbarRow = $("
").appendTo(el); - el = $("
").appendTo(el).addClass("red-ui-editor-text-container")[0]; - var editor = ace.edit(el); - editor.setTheme("ace/theme/tomorrow"); - var session = editor.getSession(); - session.on("changeAnnotation", function () { - var annotations = session.getAnnotations() || []; - var i = annotations.length; - var len = annotations.length; - while (i--) { - if (/doctype first\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); } - else if (/Unexpected End of file\. Expected/.test(annotations[i].text)) { annotations.splice(i, 1); } - } - if (len > annotations.length) { session.setAnnotations(annotations); } - }); - if (options.mode) { - session.setMode(options.mode); - } - if (options.foldStyle) { - session.setFoldStyle(options.foldStyle); - } else { - session.setFoldStyle('markbeginend'); - } - if (options.options) { - editor.setOptions(options.options); - } else { - editor.setOptions({ - enableBasicAutocompletion:true, - enableSnippets:true, - tooltipFollowsMouse: false - }); - } - if (options.readOnly) { - editor.setOption('readOnly',options.readOnly); - editor.container.classList.add("ace_read-only"); - } - if (options.hasOwnProperty('lineNumbers')) { - editor.renderer.setOption('showGutter',options.lineNumbers); - } - editor.$blockScrolling = Infinity; - if (options.value) { - session.setValue(options.value,-1); - } - if (options.globals) { - setTimeout(function() { - if (!!session.$worker) { - session.$worker.send("setOptions", [{globals: options.globals, maxerr:1000}]); - } - },100); - } - if (options.mode === 'ace/mode/markdown') { - $(el).addClass("red-ui-editor-text-container-toolbar"); - editor.toolbar = customEditTypes['_markdown'].buildToolbar(toolbarRow,editor); - if (options.expandable !== false) { - var expandButton = $('').appendTo(editor.toolbar); - RED.popover.tooltip(expandButton, RED._("markdownEditor.expand")); - expandButton.on("click", function(e) { - e.preventDefault(); - var value = editor.getValue(); - RED.editor.editMarkdown({ - value: value, - width: "Infinity", - cursor: editor.getCursorPosition(), - complete: function(v,cursor) { - editor.setValue(v, -1); - editor.gotoLine(cursor.row+1,cursor.column,false); - setTimeout(function() { - editor.focus(); - },300); - } - }) - }); - } - var helpButton = $('').appendTo($(el).parent()); - RED.popover.create({ - target: helpButton, - trigger: 'click', - size: "small", - direction: "left", - content: RED._("markdownEditor.format"), - autoClose: 50 - }); - session.setUseWrapMode(true); - } - return editor; - } + return { init: function() { - ace.config.set('basePath', 'vendor/ace'); + if(window.ace) { window.ace.config.set('basePath', 'vendor/ace'); } RED.tray.init(); RED.actions.add("core:confirm-edit-tray", function() { $(document.activeElement).blur(); @@ -2862,6 +2776,8 @@ var buildingEditDialog = false; $("#node-dialog-cancel").trigger("click"); $("#node-config-dialog-cancel").trigger("click"); }); + //console.log("packages/node_modules/@node-red/editor-client/src/js/ui/editor.js ? init()") //TODO: Remove + RED.editor.codeEditor.init(); }, edit: showEditDialog, editConfig: showEditConfigNodeDialog, @@ -2908,9 +2824,14 @@ var buildingEditDialog = false; /** * Create a editor ui component * @param {object} options - the editor options - * @function + * @returs The code editor * @memberof RED.editor */ - createEditor: createEditor + createEditor: function(options) { + return RED.editor.codeEditor.create(options); + }, + get customEditTypes() { + return customEditTypes; + } } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js new file mode 100644 index 000000000..13ed25611 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editor.js @@ -0,0 +1,107 @@ +/** + * 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. + **/ + +/** + * @namespace RED.editor.codeEditor + */ + RED.editor.codeEditor = (function() { + + const MONACO = "monaco"; + const ACE = "ace"; + const defaultEditor = ACE; + const DEFAULT_SETTINGS = { lib: defaultEditor, options: {} }; + var selectedCodeEditor = null; + var initialised = false; + + function init() { + var codeEditorSettings = RED.editor.codeEditor.settings; + var editorChoice = codeEditorSettings.lib === MONACO ? MONACO : ACE; + try { + var browser = RED.utils.getBrowserInfo(); + selectedCodeEditor = RED.editor.codeEditor[editorChoice]; + //fall back to default code editor if there are any issues + if (!selectedCodeEditor || (editorChoice === MONACO && (browser.ie || !window.monaco))) { + selectedCodeEditor = RED.editor.codeEditor[defaultEditor]; + } + initialised = selectedCodeEditor.init(); + } catch (error) { + selectedCodeEditor = null; + console.warn("Problem initialising '" + editorChoice + "' code editor", error); + } + if(!initialised) { + selectedCodeEditor = RED.editor.codeEditor[defaultEditor]; + initialised = selectedCodeEditor.init(); + } + } + + function create(options) { + //TODO: (quandry - for consideration) + // Below, I had to create a hidden element if options.id || options.element is not in the DOM + // I have seen 1 node calling `this.editor = RED.editor.createEditor()` with an + // invalid (non existing html element selector) (e.g. node-red-contrib-components does this) + // This causes monaco to throw an error when attempting to hook up its events to the dom & the rest of the 'oneditperapre' + // code is thus skipped. + // In ACE mode, creating an ACE editor (with an invalid ID) allows the editor to be created (but obviously there is no UI) + // Because one (or more) contrib nodes have left this bad code in place, how would we handle this? + // For compatibility, I have decided to create a hidden element so that at least an editor is created & errors do not occur. + // IMO, we should warn and exit as it is a coding error by the contrib author. + + if (!options) { + console.warn("createEditor() options are missing"); + options = {}; + } + + if (this.editor.type === MONACO) { + // compatibility (see above note) + if (!options.element && !options.id) { + options.id = 'node-backwards-compatability-dummy-editor'; + } + options.element = options.element || $("#" + options.id)[0]; + if (!options.element) { + console.warn("createEditor() options.element or options.id is not valid", options); + $("#dialog-form").append('