diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 07efaf18e..bdb978517 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -32,3 +32,4 @@ To help us understand the issue, please fill-in as much of the following informa - [ ] npm version: - [ ] Platform/OS: - [ ] Browser: +- [ ] running in Docker: diff --git a/.github/ISSUE_TEMPLATE/--bug_report.md b/.github/ISSUE_TEMPLATE/--bug_report.md index 63923455e..7cdc9caad 100644 --- a/.github/ISSUE_TEMPLATE/--bug_report.md +++ b/.github/ISSUE_TEMPLATE/--bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: Reproducable software issues in the core of Node-RED +about: Reproducible software issues in the core of Node-RED title: '' labels: '' assignees: '' diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 05fdacd51..92a0abb8a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -29,6 +29,6 @@ the [forum](https://discourse.nodered.org) or - [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md) -- [ ] For non-bugfix PRs, I have discussed this change on the mailing list/slack team. +- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team. - [ ] I have run `grunt` to verify the unit tests pass - [ ] I have added suitable unit tests to cover the new/changed functionality diff --git a/.gitignore b/.gitignore index c3fa9624a..7b1f1f219 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ packages/node_modules/@node-red/editor-client/public !test/**/node_modules docs !packages/node_modules/**/docs +.vscode \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index a101eecb0..5a8eebb8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ sudo: false language: node_js matrix: include: + - node_js: "14" - node_js: "12" - node_js: "10" script: diff --git a/CHANGELOG.md b/CHANGELOG.md index b7b75d30f..9d24243fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,113 @@ +#### 1.0.6: Maintenance Release + +Runtime + + - Update to JSONata 1.8.3 + - #2536 Handle clone of null in utils + +Editor + + - Prevent button label wrapping in typedInput + - Handle error objects when reporting in palette manager + +Nodes + + - Inject: Revert to cron 1.7.2 + - UDP: when reusing input socket honour the broadcast mode. + +#### 1.0.5: Maintenance Release + +Runtime + + - #2500 Support for context stores using JSONata and evaluateNodeProperty() + - Add better handling of host-key-verify error with projects + - #2517 Handle false values in $env() properly + - #2514 Ensure complete node scope is remapped in subflows + - #2513 Flows/subflows must preinitialise their context objects + - Clear node.close timeout to avoid unnecessary work on restart + - #2532 Set flow.disabled when disabled property is false + - #2522 Ensure file context does not write 'undefined' to store + +Editor + + - #2489 Fix XPath in UI tests + - #2504 Fix paletteCategories order + - #2501 Add page objects for UI testing + - #2494 Check node props when deciding if pasted node can splice links + - #2521 Don't double-sanitize node name in debug sidebar + - #2519 German i18n updates + - #2523 Update nodeTabMap when replacing unknown nodes + - Update TypedInput to use flexbox and remove resizing code + - Handle nodes with no wires array + - Do not collapse whitespace in Debug string messages + +Nodes + + - File: Remove old legacy wording from file node info to stop confusing users. + - Join: Ensure join node handles missing buffer joiner when not in string mode + - Exec: make exec node logging consistent with itself. (only be verbose when in verbose mode) + - Trigger: reset default timeout value when switching away from wait for reset + - Join: Fix join to not crash on appending invalid types to buffer. + - MQTT out: Add warning if topic contains + or # + - #2502 WebSocket i18n update + - #2508 Add Japanese translation for join node + - TCP out: tidy up select of which rows to display + + +#### 1.0.4: Maintenance Release + +Runtime + + - Update all dependencies to latest fix versions + - Update JSONata to 1.8.1 + - #2473 Handle httpAdminRoot missing ending slash with login strategy Fixes + - #2470 Update https-proxy-agent + - #2461 Allow credentials to be provided as part of /flows api + - #2444 Move receive metric position to better reflect async changes Fixes + - #2406 Improve file store error when cache disabled and sync api used Closes + - #2399 cloneMessage should handle undefined without throwing err Fixes + - #2418 Fix the library api routes to prevent too broad matching of regex URLs + - #2417 Remove undefined loadFlowLibrary call + +Editor + + - #2465 Add better regex highlighting in jsonata edit mode Fixes + - Add regex awareness to jsonata formatter + - #2472 Avoid adding extra newlines when formating jsonata Fixes + - #2475 Add UI test case for error handling + - Avoid adding extra divs to edit form to avoid size miscalculation + - Upgrade to latest marked and dompurify libs + - Ensure catalog load errors are logged to the console + - #2460 Track context sidebar element paths to track formatting changes Fixes + - Battling Chrome Autocomplete, part 31: Wrap search input with form + - #2445 Trick chrome into autofilling dummy username/password inputs Fixes + - #2457 Fix garbled characters in library + - #2409 Filter palette using raw label not html formatted label Fixes + - #2400 Wrap long context values when displaying in sidebar Fixes + - Fix duplicating array item in visual json editor + - #2338 Modify history sidebar button positioning to handle long labels Fixes + - #2438 Add some auto-complete snippets to the nrjavascript mode Close + - #2430 Ignore disabled nodes when checking for invalid configs on deploy Closes + - #2442 #2458 #2453 Update zh-CN translations + - #2235 Add initial zh-TW translation + - Re-enable jshint on editor and fixup issues + - #2431 Remove unnecessary namespaces for i18n + - #2440 Support BrowserStack in UI testing + - #2358 Add path property to debug messages Fixes + - Fix false change detection when no config node selected + - Fix IME bug in text editor + - Make node highlighting a bit more obvious for busy flows + - #2392 Add icons and support i18n in typedInput of JSON editor + +Nodes + - #2462 MQTT: Ensure IPv6 broker names are wrapped in brackets Fixes + - Join node - check existance before clearing timeout + - Trigger: Complete 2nd msg when set to send latest + - TCP: clarify text regarding blank parameters. + - #2449 HTTP Request: Add HEAD as Method + - Make min-height for change, switch, batch and mqtt consistent + + #### 1.0.3: Maintenance Release Runtime @@ -44,7 +154,7 @@ Runtime Editor - Ensure node status is refreshed whenever node is edited - - #2315 #2316 Ensure z property included in full message debug payload + - #2316 Ensure z property included in full message debug payload #2315 - #2321 Fixed editor.json (JA nls) - #2313 Fix element to collapse items in visual JSON editor - #2314 Insert divider in menu by calling RED.menu.addItem('id', null); @@ -113,9 +223,9 @@ Editor - Move flow-status button to footer for consistency - Fix node hover effect to prevent jumping position - Filter quick-add properly when splicing a wire - - Mark workspace dirty when deleting link node link Fixes #2274 + - #2274 Mark workspace dirty when deleting link node link Fixes - Add red-ui-button class to strategy login button - - Fix padding of subflow locale select Closes #2276 + - #2276 Fix padding of subflow locale select Closes - Update info text of complete node & add JP text - Add class red-ui-button to cancel button - Add css class to login submit button (#2275) @@ -138,12 +248,12 @@ Runtime - [FEATURE] Add Node Done API - make message passing async - Ensure the subflow stop promise is waiting for before restarting - Limit the regex for the /nodes/ api end points - - Add error event handler to ssh-keygen child_process Fixes #2255 - - Fix default value handling on context array access Fixes #2252 + - #2255 Add error event handler to ssh-keygen child_process Fixes + - #2252 Fix default value handling on context array access Fixes - Remove all ui test dependencies from package.json - Add req back to audit log events and extend to Projects api - - Ensure 2nd arg to node.error is an object Fixes #2228 - - Use a more atomic process for writing context files Fixes #2271 + - #2228 Ensure 2nd arg to node.error is an object Fixes + - #2271 Use a more atomic process for writing context files Fixes Editor @@ -162,25 +272,25 @@ Editor - [FEATURE] add support for specifying subflow template color - [FEATURE] Use ctrl-click on wire to splice node in place - [FEATURE] Allow search results to show more than 25 results - - [FEATURE] Allow a node to change if it has an input port Closes #2268 - - Revealing node position needs to account for zoom level Fixes #2172 - - Fix typedInput option selection Fixes #2174 - - Fix palette node id handling so search works Fixes #2173 + - #2268 [FEATURE] Allow a node to change if it has an input port Closes + - #2172 Revealing node position needs to account for zoom level Fixes + - #2174 Fix typedInput option selection Fixes + - #2173 Fix palette node id handling so search works Fixes - Add popover tooltips to debug sidebar,function and template - Add popovers to context sidebar mini buttons - Ensure node status icon is shown when value set - Revert treeList children function signature change - Restore tray component css for compatibility. Mark as deprecated - fix function name & string compare function - - Handle empty list of example flows Fixes #2171 + - #2171 Handle empty list of example flows Fixes - Ensure library list has an item selected when opened - Ensure tooltip popover doesn't replace normal popover - Fix clipboard export download button - Ensure input box has focus on repeated quick add - Fix width calculation of typedInput - Remove some hardcoded css colors - - Fix display of node help when clicking in palette Fixes #2194 - - Ensure node help is loaded in the right language Fixes #2195 + - #2194 Fix display of node help when clicking in palette Fixes + - #2195 Ensure node help is loaded in the right language Fixes - Do not allow tab focus on clipboard hidden element - Fix undefined error on typedInput due to valueLabel used before being added - Fix undo of flow disable state change @@ -193,7 +303,7 @@ Editor - Update all node icons to SVG - Handle png/svg fallback for def.icon values. Remove old pngs - Ignore empty examples directories (don't add to import menu) - - better handle example file at any depth - #2222 + - #2222 better handle example file at any depth - - Properly escape node types in palette - Ensure session expiry timeout doesn't exceed limit - Use node/tab map to make filterNodes more efficient @@ -201,22 +311,22 @@ Editor - Handle undefined node.\_def in edit stack title. - fix converting selection to subflow - Fix inserting new subflow node to existing wire between nodes - - Support displaying falsey node status values Fixes #2246 + - #2246 Support displaying falsey node status values Fixes - Remove tab menu from node property UI for subflow and config nodes - - Mark workspace dirty when shift-click-drag detaches wires Fixes #2260 + - #2260 Mark workspace dirty when shift-click-drag detaches wires Fixes - Fix subflow category change on palette Nodes - Remove pi gpi, twitter, email and feedparser nodes from core - - Fix error handling in Websocket broadcast function Fixes #2182 + - #2182 Fix error handling in Websocket broadcast function Fixes - Handle websocket item being parseable but not an object better - stop join tripping up if last message of buffer is blank. - Add support for env var propety in switch node - Improve handling of file upload in request node - Add "has key" rule to switch node + tests - Optimise generation of switch node edit dialog - - Add keep-alive option to HTTP Request - #2261 + - #2261 Add keep-alive option to HTTP Request - #### 1.0.0-beta.2: Beta Release @@ -228,7 +338,7 @@ Runtime Runtime - Update runtime apis to support multiple libraries - Add Node 12 to travis (allow_failures) - - Bump all dependencies Fixes #2152 + - #2152 Bump all dependencies Fixes Editor - [BREAKING] complete overhaul of editor DOM/CSS structure @@ -240,17 +350,17 @@ Editor - Allow script tags with src to reference esm modules - Upgrade to jq 3.4.1 / jq-ui 1.12.1 - Allow editor language to be chosen in editor settings - - Only NLS status text that starts with a letter Fixes #2128 - - Fix display of link node list within subflow Fixes #2140 - - Blur the active element when closing edit dialog via action Fixes #2097 - - Trigger change evnt on typedInput when type changes and options present Fixes #2160 + - #2128 Only NLS status text that starts with a letter Fixes + - #2140 Fix display of link node list within subflow Fixes + - #2097 Blur the active element when closing edit dialog via action Fixes + - #2160 Trigger change evnt on typedInput when type changes and options present Fixes - Move library import/export to single dialog - Move type-library dialogs to new style dialog - Fix node drag and drop animation - let status be simple text if wanted - Add workspace statusBar - Complete refresh of German translations - - Fix memory leak in Debug sidebar #2163 + - #2163 Fix memory leak in Debug sidebar - Introduce toggleButton and move flow-disabled to use it - Allow RED.settings.get/set to use full property desc - Add auto-refresh toggle to context sidebar @@ -269,26 +379,26 @@ Nodes #### 0.20.8: Maintenance Release - Sanitize tab name in edit dialog - - Pass httpServer to runtime even when httpAdmin disabled Fixes #2272 + - #2272 Pass httpServer to runtime even when httpAdmin disabled Fixes #### 0.20.7: Maintenance Release - - Update jsonata to 1.6.5 which should fix #2183 + - #2183 Update jsonata to 1.6.5 which should fix - Ensure the subflow stop promise is waiting for before restarting - Properly escape node types in palette #### 0.20.6: Maintenance Release - - Revealing node position needs to account for zoom level Fixes #2172 + - #2172 Revealing node position needs to account for zoom level Fixes - stop join tripping up if last message of buffer is blank. - Improve handling of file upload in request node - - Handle subflow internal node wired to a non-existant node Fixes #2202 + - #2202 Handle subflow internal node wired to a non-existant node Fixes - Do not save subflow env vars with blank names - Don't allow a link node virtual wire to connect to normal port - - Clear HTTP Request node authType when auth disabled Fixes #2215 - - Fix parsing of content-type header Fixes #2216 + - #2215 Clear HTTP Request node authType when auth disabled Fixes + - #2216 Fix parsing of content-type header Fixes - Fix join node reset issue with merging objects - - Copy data-i18n attribute on TypedInput Fixes #2211 + - #2211 Copy data-i18n attribute on TypedInput Fixes #### 0.20.5: Maintenance Release @@ -325,27 +435,27 @@ Nodes #### 0.20.1: Maintenance Release - - Ensure all subflow instances are stopped when flow stopping Fixes #2095 - - modify name of korean locale forders #2091 + - #2095 Ensure all subflow instances are stopped when flow stopping Fixes + - #2091 modify name of korean locale forders - Ensure node names are sanitized before being presented - - Subflow status node must pass status to parent flow Fixes #2087 - - fix problem on displaying option label on Firefox #2090 + - #2087 Subflow status node must pass status to parent flow Fixes + - #2090 fix problem on displaying option label on Firefox #### 0.20.0: Milestone Release Runtime - Pass complete status to Status node and filter to editor - - Ensure flows wait for all nodes to close before restarting Fixes #2067 + - #2067 Ensure flows wait for all nodes to close before restarting Fixes - Fix git clone with password protected key - Allow a project to be located below the root of repo - Detect the cloning of an empty git repo properly - Fix use of custom auth strategy plugins - - Remove remnants of when library in git/index Fixes #2057 + - #2057 Remove remnants of when library in git/index Fixes - Clear subflow status on close - Add exportGlobalContextKeys to prevent exposing functionGlobalContext keys - Add --no-audit and --no-update-notifier flags to npm commands to reduce workload - Add envVarExcludes setting to block named env vars - - Update settings.js docs on userDir to match reality Fixes #2082 + - #2082 Update settings.js docs on userDir to match reality Fixes - Add Korean Language @@ -389,16 +499,16 @@ Editor - Add env type to subflow env var types - Display parent subflow properties in edit dialog - Fix direction value of subflow output - - Add Status Node to Subflow to allow subflow-specific status Closes #597 - - Better handling of multiple flow merges Fixes #2039 + - #597 Add Status Node to Subflow to allow subflow-specific status Closes + - #2039 Better handling of multiple flow merges Fixes Nodes - Various translation updates - - Catch: Add 'catch uncaught only' mode. Closes #1747 + - #1747 Catch: Add 'catch uncaught only' mode. Closes - Link: scroll to current flow in node list - HTTPRequest: add option to urlencode cookies - - HTTPRequest: option to use msg.payload as query params on GET. #1981 + - #1981 HTTPRequest: option to use msg.payload as query params on GET. - Debug: Add local time display option to numerics in debug window - MQTT: Add parsed JSON output option @@ -416,9 +526,9 @@ Editor - German translation - Change default dropdown appearance and sidebar tab menu handling - - Handle multiple-select box when nothing selected Fixes #2021 - - Handle i18n properly when key is a valid sub-identifier Fixes #2028 - - Avoid duplicate links when missing node type installed Fixes #2032 + - #2021 Handle multiple-select box when nothing selected Fixes + - #2028 Handle i18n properly when key is a valid sub-identifier Fixes + - #2032 Avoid duplicate links when missing node type installed Fixes - Add View Tools - Don't collapse version control header when clicking refresh - Add fast entry via keyboard for string of nodes @@ -438,15 +548,15 @@ Editor - Update palette manager view properly when module updated - Add TreeList common widget - - Fix visual jump when opening Comment editor on Safari Part of #2008 - - Fix vertical align of markdown editor in Safari Fixes #2008 - - Avoid marking node as changed if label state is default Fixes #2009 + - #2008 Fix visual jump when opening Comment editor on Safari Part of + - #2008 Fix vertical align of markdown editor in Safari Fixes + - #2009 Avoid marking node as changed if label state is default Fixes - Highlight port on node hover while joining - Support drag-wiring of link nodes - Allow TypeSearch to include a filter option - Improve diff colouring - Allow sections to toggle in 2-element stack - - Add support for ${} env var syntax when skipping validation Closes #1980 + - #1980 Add support for ${} env var syntax when skipping validation Closes - i18 support for markdown editor tooltip - Add RED.editor.registerTypeEditor for custom type editors - Tidy up markdown toolbar handling across all editors @@ -456,15 +566,15 @@ Editor Runtime - - Bump JSONata to 1.6.4 Fixes #2023 + - #2023 Bump JSONata to 1.6.4 Fixes - Add audit logging to admin api - - Fix failure of RED.require #2010 - - Allow oauth strategy callback method to be customised Closes #1998 - - Ensure fs context cache is flushed on close Fixes #2001 + - #2010 Fix failure of RED.require + - #1998 Allow oauth strategy callback method to be customised Closes + - #2001 Ensure fs context cache is flushed on close Fixes - Fix library Buffer( to Buffer.alloc( for node 10 - Catch file-not-found on startup when non-existant flow file specified - Actively expire login sesssions and notify user - - Add quotation marks for basic auth challenge #1976 + - #1976 Add quotation marks for basic auth challenge Nodes @@ -482,7 +592,7 @@ Nodes Editor - Allow the editor to use a custom admin api url root - - Improve performance of Flow Diff dialog - @TothiViseo #1989 + - #1989 Improve performance of Flow Diff dialog - @TothiViseo - Add 'open project' option to Projects Welcome dialog - Add 'type already registered' check in palette editor - Handle missing tab.disabled property @@ -500,11 +610,11 @@ Editor - Allow left-hand node button to act as toggle - Support dbl-click in tab bar to add new flow in position - Fix duplicate subflow detection on import - - Add import notification with info on what has been imported Closes #1862 + - #1862 Add import notification with info on what has been imported Closes - Show error details when trying to import invalid json - Show default icon when non-existent font-awesome icon was specified - Add configurable option for showing node label - - Avoid http redirects as Safari doesn't reuse Auth header Fixes #1903 + - #1903 Avoid http redirects as Safari doesn't reuse Auth header Fixes - Tidy up ace tooltip styling - Add event log to editor - Add tooltips to multiple editor elements @@ -532,30 +642,30 @@ Editor Runtime - Allow a project to be loaded from cmdline - - Handle lookup of undefined property in Global context Fixes #1978 + - #1978 Handle lookup of undefined property in Global context Fixes - Refuse to enable Manage Palette if npm too old - Remove restriction on upgrading non-local modules - - Remove deprecated Buffer constructor usage Fixes #1709 + - #1709 Remove deprecated Buffer constructor usage Fixes - Update httpServerOptions doc in settings.js - Exclude non-testable .js files from the unit tests - Add --safe mode flag to allow starting without flows running - - Add setting-defined accessToken for automated access to the adminAPI - #1789 + - #1789 Add setting-defined accessToken for automated access to the adminAPI - Nodes - - Move all core node EN help to their own locale files - #1990 + - #1990 Move all core node EN help to their own locale files - - CSV: better regex for number detection - Debug: hide button if not configured to send to sidebar - Delay: report queue activity when in by-topic mode - Delay: add msg.flush mode - Exec: Preserve existing properties on msg object - File: remove CR/LF from incoming filename - - Function: create custom ace javascript mode to handle ES6 Fixes #1911 + - #1911 Function: create custom ace javascript mode to handle ES6 Fixes - Function: add env.get - - HTTP Request: Add http-proxy config #1913 + - #1913 HTTP Request: Add http-proxy config - HTTP Request: add msg.redirectList to output - - HTTP Request: add msg.requestTimeout option for per-message setting - @natcl #1959 - - MQTT: add auto-detect and base64 output to mqtt node Fixes #1912 - @DurandA + - #1959 HTTP Request: add msg.requestTimeout option for per-message setting - @natcl + - #1912 - @DurandA MQTT: add auto-detect and base64 output to mqtt node Fixes - MQTT: only unsubscribe node that is being removed - Sentiment: move to node-red-node-sentiment - Switch: add missing edit dialog icon @@ -570,48 +680,48 @@ Nodes #### 0.19.6: Maintenance Release - - Fix encoding of file node from binary to utf8 - #2051 + - #2051 Fix encoding of file node from binary to utf8 - #### 0.19.5: Maintenance Release - Recognize pip installs of RPi.GPIO (#1934) - - Merge pull request #1941 from node-red-hitachi/master-batch - - Merge pull request #1931 from node-red-hitachi/master-typedinput + - #1941 from node-red-hitachi/master-batch Merge pull request + - #1931 from node-red-hitachi/master-typedinput Merge pull request - Set min value of properties and spinners for batch - Fix that unnecessary optionMenu remains - - Merge pull request #1894 from node-red-hitachi/fix-overlapping-file-node-execution - - Merge pull request #1924 from imZack/patch-1 + - #1894 from node-red-hitachi/fix-overlapping-file-node-execution Merge pull request + - #1924 from imZack/patch-1 Merge pull request - Add missing comma - - Do not disable context sidebar during node edit Fixes #1921 - - Don't allow virtual links to be spliced Fixes #1920 + - #1921 Do not disable context sidebar during node edit Fixes + - #1920 Don't allow virtual links to be spliced Fixes - Merge project package changes to avoid overwritten changes - - Handle manually added project deps that are unused Fixes #1908 + - #1908 Handle manually added project deps that are unused Fixes - update close & input handling of File node - make close handler argument only one - - Merge pull request #1907 from amilajack/patch-2 + - #1907 from amilajack/patch-2 Merge pull request - Change repo badge to point to master branch - invoke callbacks if async handler is specified - - Merge pull request #1891 from camlow325/resolve-example-path-for-windows-support - - Merge pull request #1900 from kazuhitoyokoi/master-addtestcases4settings.js + - #1891 from camlow325/resolve-example-path-for-windows-support Merge pull request + - #1900 from kazuhitoyokoi/master-addtestcases4settings.js Merge pull request - wait closing while pending messages exist - Add test cases for red/api/editor/settings.js - - Ensure all palette categories are opened properly Closes #1893 + - #1893 Ensure all palette categories are opened properly Closes - Resolve path when sending example file for Windows support - fix multiple input message processing of file node #### 0.19.4: Maintenance Release - - Fix race condition in non-cache lfs context Fixes #1888 + - #1888 Fix race condition in non-cache lfs context Fixes - LocalFileSystem Context: Remove extra flush code - Prevent race condition in caching mode of lfs context (#1889) - Allow context store name to be provided in the key - Switch node: only use promises when absolutely necessary - Fix dbl-click handling on webkit-based browsers - Ensure context.flow/global cannot be deleted or enumerated - - Handle context.get with multiple levels of unknown key Fixes #1883 + - #1883 Handle context.get with multiple levels of unknown key Fixes - Fix global.get("foo.bar") for functionGlobalContext set values - Fix node color bug (#1877) - - Merge pull request #1857 from cclauss/patch-1 + - #1857 from cclauss/patch-1 Merge pull request - Define raw_input() in Python 3 & fix time.sleep() #### 0.19.3: Maintenance Release @@ -626,14 +736,14 @@ Nodes - TCP-request node - only write payload - JSON schema: perform validation when obj -> obj or str -> str - JSON schema: add draft-06 support (via $schema keyword) - - Mqtt proxy configuration for websocket connection, #1651. + - #1651. Mqtt proxy configuration for websocket connection, - Allows MQTT Shared Subscriptions for MQTT-In core node - Fix use of HTML tag or CSS class specification as icon of typedInput #### 0.19.2: Maintenance Release - Ensure node default colour is used if palette.theme has no match - - fix lost messages / properties in TCPRequest Node; closes #1863 (#1864) + - #1863 (#1864) fix lost messages / properties in TCPRequest Node; closes - Fix typo in template.html - Improve error reporting from context plugin loading - Prevent no-op edit of node marking as changed due to icon @@ -653,8 +763,8 @@ Nodes Editor - Add editorTheme.palette.theme to allow overriding colours - - Index all node properties when searching Fixes #1446 - - Handle NaN and Infinity properly in debug sidebar Fixes #1778 #1779 + - #1446 Index all node properties when searching Fixes + - #1779 Handle NaN and Infinity properly in debug sidebar Fixes #1778 - Prevent horizontal scroll when palette name cannot wrap - Ignore middle-click on node/ports to enable panning - Better wire layout when looping back @@ -668,7 +778,7 @@ Editor - Only edit nodes on dbl click on primary button with no modifiers - Allow subflows to be put in any palette category - Add flow navigator widget - - Cache flow library result to improve response time Fixes #1753 + - #1753 Cache flow library result to improve response time Fixes - Add middle-button-drag to pan the workspace - allow multi-line category name in editor - Redesign sidebar tabs @@ -676,20 +786,20 @@ Editor Nodes - - Change: Ensure runtime errors in Change node can be caught Fixes #1769 + - #1769 Change: Ensure runtime errors in Change node can be caught Fixes - File: Add output to File Out node - Function: add expandable JavaScript editor pane - Function: allow id and name reference in function node code (#1731) - HTTP Request: Move to request module - - HTTP: Ensure apiMaxLength applies to HTTP Nodes Fixes #1278 + - #1278 HTTP: Ensure apiMaxLength applies to HTTP Nodes Fixes - Join: accumulate top level properties - Join: allow environment variable as reduce init value - JSON: add JSON schema validation via msg.schema - Pi: Let nrgpio code work with python 3 - Pi: let Pi nodes be visible/editable on all platforms - Switch: add isEmpty rule - - TCP: queue messages while connecting; closes #1414 - - TLS: Add servername option to TLS config node for SNI Fixes #1805 + - #1414 TCP: queue messages while connecting; closes + - #1805 TLS: Add servername option to TLS config node for SNI Fixes - UDP: Don't accidentally re-use udp port when set to not do so Persistent Context @@ -704,8 +814,8 @@ Persistent Context Runtime - Support flow.disabled and .info in /flow API - - Node errors should be Strings not Errors Fixes #1781 - - Add detection of connection timeout in git communication Fixes #1770 + - #1781 Node errors should be Strings not Errors Fixes + - #1770 Add detection of connection timeout in git communication Fixes - Handle loading empty nodesDir - Add 'private' property to userDir generated package.json - Add RED.require to allow nodes to access other modules @@ -715,7 +825,7 @@ Runtime Editor Fixes - - Do not trim wires if node declares outputs in defaults but misses value Fixes #1737 + - #1737 Do not trim wires if node declares outputs in defaults but misses value Fixes Node Fixes @@ -725,16 +835,16 @@ Node Fixes - typo fix in node help (#1735) Other Fixes - - Tidy up default grunt task and fixup test break due to reorder Fixes #1738 + - #1738 Tidy up default grunt task and fixup test break due to reorder Fixes - Bump jsonata version #### 0.18.6: Maintenance Release Editor Fixes - - Handle a node having wires in the editor on ports it no longer has Fixes #1724 + - #1724 Handle a node having wires in the editor on ports it no longer has Fixes - Add missing ACE snippet files - - Fix wireClippedNodes is not defined Fixes #1726 + - #1726 Fix wireClippedNodes is not defined Fixes - Split node html to isolate bad nodes when loading - Avoid unnecessary use of .html() where .text() will do @@ -761,32 +871,32 @@ New Features Editor Fixes - - Highlight subflow node when log msg comes from inside Fixes #1698 - - Ensure node wires array is not longer than outputs value Fixes #1678 - - Allow importing an unknown config node to be undone Fixes #1681 - - Ensure keyboard shortcuts get saved in runtime settings Fixes #1696 + - #1698 Highlight subflow node when log msg comes from inside Fixes + - #1678 Ensure node wires array is not longer than outputs value Fixes + - #1681 Allow importing an unknown config node to be undone Fixes + - #1696 Ensure keyboard shortcuts get saved in runtime settings Fixes - Don't mark a subflow changed when actually modified nothing (#1665) Node Fixes - bind to correct port when doing udp broadcast/multicast (#1686) - Provide full error stack in Function node log message (#1700) - - Fix http request doc type Fixes #1690 + - #1690 Fix http request doc type Fixes - Make debug slightly larger to pass WCAG AA rating - - Make core nodes labels more consistent, to close #1673 - - Allow template node to be updated more than once Fixes #1671 + - #1673 Make core nodes labels more consistent, to close + - #1671 Allow template node to be updated more than once Fixes - Fix the problem that output labels of switch node sometimes disappear (#1664) - Chinese translations for core nodes (#1607) Runtime Fixes - - Handle and display for invalid flow credentials when project is disabled #1689 (#1694) + - #1689 (#1694) Handle and display for invalid flow credentials when project is disabled - node-red-pi: fix behavior with old bash version (#1713) - Fix ENOENT error on first start when no user dir (#1711) - - Handle null error object in Flow.handleError Fixes #1721 + - #1721 Handle null error object in Flow.handleError Fixes - update settings comments to describe how to setup for ipv6 (#1675) - - Remove credential props after diffing flow to prevent future false positives Fixes #1359 - - Log error if settings unavailable when saving user settings Fixes #1645 + - #1359 Remove credential props after diffing flow to prevent future false positives Fixes + - #1645 Log error if settings unavailable when saving user settings Fixes - Keep backup of .config.json - Add warning if using \_credentialSecret from .config.json - Filter req.user in /settings to prevent potentially leaking info @@ -812,7 +922,7 @@ Editor Fixes - Fix merging a remote diff - Fixed the problems when using a node without defaults - Disable user defined icon for subflow - - getDefaultNodeIcon should handle subflow instance nodes Fixes #1635 + - #1635 getDefaultNodeIcon should handle subflow instance nodes Fixes - Add Japanese info text for core nodes - Fix message lookup for core nodes in case of i18 locales directory exists - Prevent the last tab from being deleted @@ -843,7 +953,7 @@ Editor Fixes - Fix offset calculation when dragging node from palette - Allow a library entry to use non-default node-input- prefixes - - Change remote-diff shortcut and add it to keymap Fixes #1628 + - #1628 Change remote-diff shortcut and add it to keymap Fixes #### 0.18.2: Maintenance Release @@ -882,7 +992,7 @@ Projects - Handle more repo clone error cases - Relax validation of git urls - Revalidate project name on return to project-details view - - Avoid unnecessary project refresh on branch-switch Fixes #1597 + - #1597 Avoid unnecessary project refresh on branch-switch Fixes - Add support for file:// git urls - Handle project first-run without existing flow file - Handle delete of last remote in project settings @@ -893,9 +1003,9 @@ Projects Node Fixes - Trigger node migration - ensure bytopic not blank - - Add HEAD to list of methods with no body in http req node #1598 - - Do not include payload in GET requests Fixes #1598 - - Update sort/batch docs Fixes #1601 + - #1598 Add HEAD to list of methods with no body in http req node + - #1598 Do not include payload in GET requests Fixes + - #1601 Update sort/batch docs Fixes - Don't assume node has defaults when exporting icon property @@ -908,11 +1018,11 @@ Runtime - Better error reporting when module provides duplicate type - Update jsonata to 1.5.0 - add express-session memorystore without leaks (#1435) - - Allow adminAuth.user to be a Function Fixes #1461 + - #1461 Allow adminAuth.user to be a Function Fixes - Ensure RED.server is set even if admin api disabled - - Ensure strategy login button uses relative URL Fixes #1481 + - #1481 Ensure strategy login button uses relative URL Fixes - ignore `_msgid` when merging full objects - - Move node install to spawn to allow for big stdout Fixes #1488 + - #1488 Move node install to spawn to allow for big stdout Fixes - SIGINT handler should wait for stop to complete before exit Editor @@ -920,12 +1030,12 @@ Editor - allow a node's icon to be set dynamically (#1490) - Batch messages sent over comms to increase throughput - Migrate deploy confirmations to notifications - - `oneditdelete` should be available to all node types Closes #1346 + - #1346 `oneditdelete` should be available to all node types Closes - Sort typeSearch results based on position of match - Update ACE to test and add python highlighter (#1373) - - Clear mouse state when typeSearch cancelled Fixes #1517 + - #1517 Clear mouse state when typeSearch cancelled Fixes - Handle scoped modules via palette editor - - TypedInput: handle user defined value/labels options Fixes #1549 + - #1549 TypedInput: handle user defined value/labels options Fixes Nodes @@ -940,7 +1050,7 @@ Nodes - Add support for rejectUnauthorized msg property - Add TLS options to WebSocket client - Added parsed YAML support for template node (#1443) - - Allow delay node in rate-limit mode to be reset Fixes #1360 + - #1360 Allow delay node in rate-limit mode to be reset Fixes - Allow setTimeout in Function node to be promisified in node 8 - Debug to status option (#1499) - enable template config via msg.template for stored or generated templates (#1503) @@ -959,13 +1069,13 @@ Nodes - MQTT node - if Server/URL config contains '//' use it as a complete url; enabled ws:// and wss:// - clone messages before delayed send (#1474) - Decrement connected client count rather than show disconnected - - Don't end mqtt client on first error Fixes #1566 - - File out - create dirs synchronously to ensure they exist Fixes #1489 + - #1566 Don't end mqtt client on first error Fixes + - #1489 File out - create dirs synchronously to ensure they exist Fixes - Fix debug message format for Buffer (#1444) - Fix global.keys() bug in function node (#1417) - Handle escape characters in template node which uses Mustache format and JSON output mode (#1377) - - Move all node.send to end of timer functions in trigger node (issue #1527) (#1539) - - Publish null/undefined to mqtt as blank not toString Fixes #1521 + - #1527) (#1539) Move all node.send to end of timer functions in trigger node (issue + - #1521 Publish null/undefined to mqtt as blank not toString Fixes - remove inject node at specific time spinner - restrict inject interval to less that 2^31 millisecs - tag UDP ports in use properly so they get closed correctly (#1508) @@ -974,10 +1084,10 @@ Nodes - Add express-session missing dependency for oauth - Fix improper type tests is core test cases - - File node: recreate write stream when file deleted Fixes #1351 + - #1351 File node: recreate write stream when file deleted Fixes - Add flow stopping trace messages - Fix userDir test case when .config.json exists (#1350) - - Do not try to send msg after http request error handled Fixes #1344 + - #1344 Do not try to send msg after http request error handled Fixes - Fix boundary problem in range node (#1338) - Modify messages in node properties to refer messages.json (#1339) - Fix settings.js replacing webSocketVerifyClient by webSocketNodeVerifyClient (#1343) @@ -988,16 +1098,16 @@ Nodes - Add request node test case for POSTing 0 - Allow false and 0 in payload for httprequest (#1334) - Add file extension into flow name of library automatically (#1331) - - Fix accessing global context from jsonata expressions Fixes #1335 - - Disable editor whilst a deploy is inflight Fixes #1332 + - #1335 Fix accessing global context from jsonata expressions Fixes + - #1332 Disable editor whilst a deploy is inflight Fixes - Replace Unknown nodes with their real versions when node loaded - Retry auto-install of modules that fail - Fix column name in link nodes to refer language file (#1330) - - Use namespaces with link node title attributes i18n name Fixes #1329 - - Tidy up GPIO pin table presentation Fixes #1328 + - #1329 Use namespaces with link node title attributes i18n name Fixes + - #1328 Tidy up GPIO pin table presentation Fixes - Join: count of 0 should not send on every msg - Handle importing only one end of a link node pair - - Make sending to Debug synchronous again Fixes #1323 + - #1323 Make sending to Debug synchronous again Fixes - Make send-error behaviour optional in file node - Restore File In node behaviour of sending msg on error - Expose context.keys within Function node @@ -1011,10 +1121,10 @@ Nodes - Fix missing icons for some nodes (#1321) - Add reformat button to JSONata test data editor - Update delay node status without spawning unnecessary intervals - - Avoid stringify ServerResponse and Socket in Debug node Fixes #1311 + - #1311 Avoid stringify ServerResponse and Socket in Debug node Fixes - Fix creating userDir other than system drive on Windows (#1317) - - Trigger node not handling a duration of 0 as block mode Fixes #1316 - - Unable to config GPIO Pin 13 Fixes #1314 + - #1316 Trigger node not handling a duration of 0 as block mode Fixes + - #1314 Unable to config GPIO Pin 13 Fixes #### 0.17.2: Maintenance Release @@ -1023,7 +1133,7 @@ Nodes #### 0.17.1: Maintenance Release - Fix PI gpio to use BCM - - Prevent event thread contention when sending to Debug node Closes #1311 + - #1311 Prevent event thread contention when sending to Debug node Closes - Fix Bug: Can not display node icon when npm package has scope (#1305) (#1309) - Clear moved flag when nodes are deployed @@ -1031,7 +1141,7 @@ Nodes Runtime - - Return flow rev on reload api when api v2 enabled Closes #1273 + - #1273 Return flow rev on reload api when api v2 enabled Closes - Provide single endpoint to load all node message catalogs - Add .trace and .debug to Node prototype - Rename oauth auth scheme to strategy as it works for openid @@ -1039,16 +1149,16 @@ Runtime - Add support for oauth adminAuth configs - Cache auth details to save needlessly recalculating hashes - Add context.keys function to list top-level keys - - Strip BOM character from JSON files if present Fixes #1239 + - #1239 Strip BOM character from JSON files if present Fixes - Version check no meta (#1243) - - Ensure all nodes have access to global context Fixes #1230 - - Don't process subscription for unauthenticated comms link Fixes #851 - - Clone credentials when passing to node Fixes #1198 + - #1230 Ensure all nodes have access to global context Fixes + - #851 Don't process subscription for unauthenticated comms link Fixes + - #1198 Clone credentials when passing to node Fixes - Resolve dir argument of getLocalNodeFiles function (#1216) - Add wait for writing a library entry into a file. (#1186) - Use correct Buffer.from method rather than constructor - update core nodes to use newer Buffer syntax - - Treat missing msg properties as undefined rather than throw error Fixes #1167 + - #1167 Treat missing msg properties as undefined rather than throw error Fixes - Allows flows to be enabled/disabled in the runtime - add off option to logging settings comment - Log error stack traces if verbose flag is set @@ -1081,8 +1191,8 @@ Nodes - Fix wrong number of double quotes in CSV parsing - let csv node handle ip addresses without trying to parse - Update debug node to register the settings it uses - - Handle IncomingMessage/ServerResponse object types in debug Fixes #1202 - - Toggling debug node enabled/disabled state should set state dirty Fixes #1203 + - #1202 Handle IncomingMessage/ServerResponse object types in debug Fixes + - #1203 Toggling debug node enabled/disabled state should set state dirty Fixes - redo delay node status messages to be interval based - Update delay node ui - Add new msg.delay option to delay node @@ -1118,16 +1228,16 @@ Nodes - First pass of new node-info style - MQTT new style info - Fix empty extra node help content issue - - Handle HTTP In url that is missing its leading / Fixes #1218 + - #1218 Handle HTTP In url that is missing its leading / Fixes - Add file upload support to HTTP In node - HTTP Request node: add info on how to do form encoding - - Prevent unmodified msg.headers from breaking HTTP Request flows Closed #1015 + - #1015 Prevent unmodified msg.headers from breaking HTTP Request flows Closed - Add cookie handling to HTTP Request node - Add guard against the http-request buffer fix being reverted - Multipart streaming - Add http-request node unit tests - http request node add transport validity check and warn. - - Update follow_redirects to fix http_proxy handling Fixes #1172 + - #1172 Update follow_redirects to fix http_proxy handling Fixes - Allow statusCode/headers to be set directly within HTTP Response node - let inject "between time" also fire at start - Plus new info - remove repeat symbol from inject if repeat is 0 @@ -1176,7 +1286,7 @@ Nodes - Move udp sock error listener to only be instantiated once. - Let watch node recurse into subdirectories - Misconfigured WebSocket nodes should not register msg handlers - - Add websocketVerifyClient option to enable custom websocket auth Fixes #1127 + - #1127 Add websocketVerifyClient option to enable custom websocket auth Fixes Editor @@ -1198,11 +1308,11 @@ Editor - Remove unused modified flag on debug messages - Add copy path/value buttons to debug messages - dont match only part of the node type (#1242) - - Add editorTheme.logout.redirect to allow redirect on logout Closes #1213 - - Handle logging out and already logged-out editor Fixes #1288 + - #1213 Add editorTheme.logout.redirect to allow redirect on logout Closes + - #1288 Handle logging out and already logged-out editor Fixes - Fix bug: Export Subflows (#1282) - destroy editor to ensure fully removed on close (function, template, comment) - - Don't try to nls status text starting with '.' Fixes #1258 + - #1258 Don't try to nls status text starting with '.' Fixes - Add note of removed flows in diffConfig (#1253) - Add description to flow same as subflow - Allow tabs to be enabled/disabled in the editor @@ -1274,7 +1384,7 @@ Editor - Allow RED.validators.number to allow blank values as valid - Support dropping json files into the editor - NLS Expression/JSON editor and fix their height calculation - - Update JSONata to 1.2.4 Closes #1275 + - #1275 Update JSONata to 1.2.4 Closes - Remember test expression data on a per-node basis - NLS jsonata test messages - Add JSONata expr tester and improved feedback @@ -1291,13 +1401,13 @@ Other #### 0.16.2: Maintenance Release - - Ensure custom mustache context parent set in Template node fixes #1126 + - #1126 Ensure custom mustache context parent set in Template node fixes - Display debug node name in debug panel if its known - Ensure auth-tokens are removed when no user is specified in settings - Ensure all a tags have blank target in info sidebar - Ensure links do not span tabs in the editor - Avoid creating multiple reconnect timers in websocket node - - Fix inner reference in install fail message catalog entry Fixes #1120 + - #1120 Fix inner reference in install fail message catalog entry Fixes - Display buffer data properly for truncated buffers under Object property #### 0.16.1: Maintenance Release @@ -1305,9 +1415,9 @@ Other - Add colour swatches to debug when hex colour matched - Nodes with hasUsers set to false should not appear unused - Change hard error to verbose warning if using old node.js level - - Don't filter debug properties starting with _ Fixes #1117 - - Node logged errors not displayed properly in debug pane Fixes #1116 - - Do not look for existing nodes when checking for wires on paste Fixes #1114 + - #1117 Don't filter debug properties starting with _ Fixes + - #1116 Node logged errors not displayed properly in debug pane Fixes + - #1114 Do not look for existing nodes when checking for wires on paste Fixes - -v option not enabling verbose mode properly - Add node.js version check on startup @@ -1319,10 +1429,10 @@ Runtime Nodes - - Add option to colourise debug console output Closes #1103 + - #1103 Add option to colourise debug console output Closes - Add property validation to nodes using typedInput - - Add common validator for typedInput fields Closes #1104 - - Update debug node console logging indicator icon Closes #1094 + - #1104 Add common validator for typedInput fields Closes + - #1094 Update debug node console logging indicator icon Closes - Let exec node (spawn) handle commands with spaces in path - Add symbol to debug node to indicate debugging also to console.log - Change file node to use node 4 syntax (drops support for 0.8) @@ -1334,9 +1444,9 @@ Nodes Editor - - Add install/remove dialog to increase friction Closes #1109 - - Report node catalogue load errors Closes #1009 - - Properly report module remove errors in palette editor Fixes #1043 + - #1109 Add install/remove dialog to increase friction Closes + - #1009 Report node catalogue load errors Closes + - #1043 Properly report module remove errors in palette editor Fixes - Update rather than hide install button after success install - Tweak search box styling - Display info tips slightly longer @@ -1357,36 +1467,36 @@ Editor - Focus tray body when edit dialog opened - Hit enter to edit first node in selection - Add node delete button to edit dialog - - Add notification when runtime stopped due to missing types Part of #832 + - #832 Add notification when runtime stopped due to missing types Part of Fixes - - Do not tie debug src loading to needsPermission Fixes #1111 - - Initialise nodeApp regardless of httpAdmin setting Closes #1096 #1095 + - #1111 Do not tie debug src loading to needsPermission Fixes + - #1095 Initialise nodeApp regardless of httpAdmin setting Closes #1096 - Speed up reveal of search dialogs - - Ensure flows exist before delegating status/error events Fixes #1069 + - #1069 Ensure flows exist before delegating status/error events Fixes - Update package dependencies - Update MQTT to latest 2.2.1 - Node status not being refreshed properly in the editor - - Try to prevent auto-fill of password fields in node edit tray Fixes #1081 + - #1081 Try to prevent auto-fill of password fields in node edit tray Fixes - Fix whitespace in localfilesystem - fix bug where savesettings did not honor local settings variables (#1073) - - Tidy up unused/duplicate editor messages Closes #922 + - #922 Tidy up unused/duplicate editor messages Closes - Property expressions must not be blank - Tidy up merge commit of validatePropertyExpression - add port if wires array > number of ports declared. - - Allow quoted property expressions Fixes #1101 + - #1101 Allow quoted property expressions Fixes - Index all node properties for node search - Remove node 0.10 from travis config - update welcome message to use logger so it can be turned off/on if required (#1083) - Fix dynamically loading multiple node-sets from palette editor - - Allow a node to reorder its outputs and maintain links Fixes #1031 + - #1031 Allow a node to reorder its outputs and maintain links Fixes #### 0.15.3: Maintenance Release - Tcpgetfix: Another small check (#1070) - TCPGet: Ensure done() is called only once (#1068) - - Allow $ and _ at start of property identifiers Fixes #1063 + - #1063 Allow $ and _ at start of property identifiers Fixes - TCPGet: Separated the node.connected property for each instance (#1062) - Corrected 'overide' typo in XML node help (#1061) - TCPGet: Last property check (hopefully) (#1059) @@ -1419,11 +1529,11 @@ Fixes #### 0.15.2: Maintenance Release - - Revert bidi changes to nodes and hide menu option until fixed Fixes #1024 + - #1024 Revert bidi changes to nodes and hide menu option until fixed Fixes - Let xml node set options both ways - Bump serialport to use version 4 - gpio node handle multiple bits of data returned in one go - - HTTP In should pass application/octet-stream as buffer not string Fixes #1023 + - #1023 HTTP In should pass application/octet-stream as buffer not string Fixes - Handle missing httpNodeRoot setting properly - Config sidebar not handling node definition error properly - Add minimum show time to deploy spinner to avoid flicker @@ -1431,24 +1541,24 @@ Fixes - Add log.removeHandler function - Add Crtl/Shift/p shortcut for manage palette - Add spinner to deploy button - - Status messages from nodes in subflows not delegated properly Fixes #1016 + - #1016 Status messages from nodes in subflows not delegated properly Fixes - fix spelling in join node info - Speed up tab scrolling - - Update delay burst test to be more tolerant of timing Fixes #1013 + - #1013 Update delay burst test to be more tolerant of timing Fixes #### 0.15.1: Maintenance Release - Update default palette catalogue to use https - Disable palette editor if npm not found - and fix for Windows - - Searching package catalogue should be case-insensitive Fixes #1010 - - contenteditable fields not handled in config nodes Fixes #1011 + - #1010 Searching package catalogue should be case-insensitive Fixes + - #1011 contenteditable fields not handled in config nodes Fixes - Change html link refs from `_new` to `_blank` to be standards compliant #### 0.15.0: Milestone Release Runtime - - Increase default apiMaxLength to 5mb and add to default settings Closes #1001 + - #1001 Increase default apiMaxLength to 5mb and add to default settings Closes - Add v2 /flows api and deploy-overwrite protection - Encrypt credentials by default - Ensure errors thrown by RED.events handlers don't percolate up @@ -1457,7 +1567,7 @@ Editor - Mark nodes as changed when they are moved - Added parent containment option for draggable. (#1006) - - Ignore bidi event handling on non-existent and non-Input elements Closes #999 + - #999 Ignore bidi event handling on non-existent and non-Input elements Closes - Remove list of flows from menu - Allow nodes to be imported with their credentials - Add workspace search option @@ -1469,7 +1579,7 @@ Editor - Add import-to-new-tab option - Add new options to export-nodes dialog - Stop nodes being added beyond the outer bounds of the workspace - - Default config nodes to global scope unless in a subflow Closes #972 + - #972 Default config nodes to global scope unless in a subflow Closes - Bidi support for Text Direction and Structured Text (#961) - Fix jQuery selector, selecting more than one help pane/popover and displaying incorrectly. (#970) - Fixes removeItem not passing row data to callback. (#965) @@ -1483,7 +1593,7 @@ Nodes - Clean up status on close for several core nodes. - Change node: re-parse JSON set value each time to avoid pass-by-ref - Better handle HTTP Request header capitalisation - - Enable ES6 parsing in Function editor by default Fixes #985 + - #985 Enable ES6 parsing in Function editor by default Fixes - Update debug sidebar to use RED.view.reveal to show debug nodes - Add full path tip to file node, And tidy up Pi node tips - Remove WebSocket node maxlistener warning @@ -1502,7 +1612,7 @@ Nodes Other - - Add npm build/test scripts Closes #946 #660 + - #660 Add npm build/test scripts Closes #946 - Move travis to node 6 and 7 - drop 5 and 0.12 @@ -1510,15 +1620,15 @@ Other Fixes - - Tell ace about Function node globals. Closes #927 - - Tidy up mqtt nodes - linting and done handling. Closes #935 + - #927 Tell ace about Function node globals. Closes + - #935 Tidy up mqtt nodes - linting and done handling. Closes - Fix invalid html in TCP and HTML node edit templates - Add proper help text to link nodes - Handle importing old mqtt-broker configs that lack properties - Update ace to 1.2.4 - Allow config nodes to provide a sort function for their select list - Add log warning if node module required version cannot be satisfied - - Handle empty credentials file. Closes #937 + - #937 Handle empty credentials file. Closes - Add RPi.GPIO lib test for ArchLinux #### 0.14.5: Maintenance Release @@ -1528,8 +1638,8 @@ Fixes - Cannot clear cookies with http nodes - let HTML parse node allow msg.select set select - Validate nodes on import after any references have been remapped - - Debug node handles objects without constructor property Fixes #933 - - Ensure 'false' property values are displayed in info panel Fixes #940 + - #933 Debug node handles objects without constructor property Fixes + - #940 Ensure 'false' property values are displayed in info panel Fixes - Fix node enable/disable over restart - load configs after settings init #### 0.14.4: Maintenance Release @@ -1538,20 +1648,20 @@ Nodes - Update trigger node ui to use typedInputs - Better handling of quotes in CSV node - - Clarify the MQTT node sends msg.payload - closes #929 - - Inject node should reuse the message it is triggered with Closes #914 + - #929 Clarify the MQTT node sends msg.payload - closes + - #914 Inject node should reuse the message it is triggered with Closes - Stop trigger node re-using old message - Allow node.status text to be 'falsey' values Fixes - - Handle DOMException when embedded in an iframe of different origin Fixes #932 + - #932 Handle DOMException when embedded in an iframe of different origin Fixes - Fix double firing of menu actions - - Fix select box handling in Safari - fixes #928 - - Clear context in node test helper Fixes #858 - - Allow node properties to be same as existing object functions Fixes #880 + - #928 Fix select box handling in Safari - fixes + - #858 Clear context in node test helper Fixes + - #880 Allow node properties to be same as existing object functions Fixes - Handle comms link closing whilst completing the initial connect - - Protect against node type names that clash with Object property names Fixes #917 + - #917 Protect against node type names that clash with Object property names Fixes - Clone default node properties to avoid reference leakage - Strip tab node definition when exporting - Check for null config properties in editor before over-writing them @@ -1561,16 +1671,16 @@ Editor - Add sql mode to ace editor - Keyboard shortcuts dialog update (#923) - - Ensure importing link nodes to a subflow doesn't add outbound links Fixes #921 + - #921 Ensure importing link nodes to a subflow doesn't add outbound links Fixes - Add updateConfigNodeUsers function to editor - Scroll to bottom when item added to editableList - - Form input widths behave more consistently when resizing Fixes #919 #920 + - #920 Form input widths behave more consistently when resizing Fixes #919 #### 0.14.3: Maintenance Release Fixes - - Create default setting.js in user-specified directory. Fixes #908 + - #908 Create default setting.js in user-specified directory. Fixes - MQTT In subscription qos not defaulting properly - Let exec node handle 0 as well as "0" @@ -1578,7 +1688,7 @@ Fixes Fixes - - Cannot add new twitter credentials. Fixes #913 + - #913 Cannot add new twitter credentials. Fixes - Support array references in Debug property field #### 0.14.1: Maintenance Release @@ -1586,8 +1696,8 @@ Fixes Fixes - Handle undefined property that led to missing wires in the editor - - Remove duplicate 'Delete' entry in keyboard shortcut window. Closes #911 - - Add 'exec' to node-red-pi launch script. Closes #910 + - #911 Remove duplicate 'Delete' entry in keyboard shortcut window. Closes + - #910 Add 'exec' to node-red-pi launch script. Closes #### 0.14.0: Milestone Release @@ -1609,9 +1719,9 @@ Editor Runtime - Always log node warnings on start without requiring -v - - Add support for loading scoped node modules. Closes #885 + - #885 Add support for loading scoped node modules. Closes - Add process.env.PORT to settings.js - - Clear node context on deploy. Closes #870 + - #870 Clear node context on deploy. Closes - Enable finer grained permissions in adminAuth Nodes @@ -1619,9 +1729,9 @@ Nodes - Enable config nodes to reference other config nodes - Add Split/Join nodes - Add Link nodes - - Add support to HTTP In node for PATCH requests. Closes #904 + - #904 Add support to HTTP In node for PATCH requests. Closes - Add cookie handling to HTTP In and HTTP Response nodes - - Add repeat indicator to inject node label. Closes #887 + - #887 Add repeat indicator to inject node label. Closes - Add javascript highlighter to template node - Add optional timeout to exec node - Add TLS node and update MQTT/HTTP nodes to use it @@ -1633,19 +1743,19 @@ Nodes - Update Serial node to support custom baud rates - Add support for array-syntax in typedInput msg properties - Add RED.util to Function node sandbox - - Capture error stack on node.error. Closes #879 + - #879 Capture error stack on node.error. Closes Fixes - Add error handling to all node definition api calls - Handle null return from Function node in array of messages - - Defer loading of token sessions until they are accessed. Fixes #895 + - #895 Defer loading of token sessions until they are accessed. Fixes - set pi gpio pin status correctly if set on start - - Prevent parent window scrolling when view is focused. Fixes #635 + - #635 Prevent parent window scrolling when view is focused. Fixes - Handle missing tab nodes in a loaded flow config - Ensure typedInput dropdown doesn't fall off the page - - Protect against node types with reserved names such as toString. Fixes #880 + - #880 Protect against node types with reserved names such as toString. Fixes - Do not rely on the HTML file to identify where nodes are registered from - Preserve node properties on import - Fix regression in delay node. topic based queue was emptying all the time instead of spreading out messages. @@ -1661,22 +1771,22 @@ Fixes #### 0.13.4: Maintenance Release - Add timed release mode to delay node - - Enable link splicing for when import_dragging nodes. Closes #811 + - #811 Enable link splicing for when import_dragging nodes. Closes - Fix uncaught exception on deploy whilst node sending messages - Deprecate old mqtt client and connection pool modules - - Change node: add bool/num types to change mode Closes #835 - - Validate fields that are `$(env-vars)` Closes #825 + - #835 Change node: add bool/num types to change mode Closes + - #825 Validate fields that are `$(env-vars)` Closes - Handle missing config nodes when validating node properties - Pi node - don't try to send data if closing - Load node message catalog when added dynamically - Split palette labels on spaces and hyphens when laying out - - Warn if editor routes are accessed but runtime not started Closes #816 - - Better handling of zero-length flow files Closes #819 + - #816 Warn if editor routes are accessed but runtime not started Closes + - #819 Better handling of zero-length flow files Closes - Allow runtime calls to RED._ to specify other namespace - Better right alignment of numerics in delay and trigger nodes - Allow node modules to include example flows - Create node_modules in userDir - - Ensure errors in node def functions don't break view rendering Fixes #815 + - #815 Ensure errors in node def functions don't break view rendering Fixes - Updated Inject node info with instructions for flow and global options @@ -1700,13 +1810,13 @@ Fixes - Make jquery spinner element css consistent with other inputs - tcp node add reply (to all) capability - Allow the template node to be treated as plain text - - Validate MQTT In topics Fixes #792 - - httpNodeAuth should not block http options requests Fixes #793 + - #792 Validate MQTT In topics Fixes + - #793 httpNodeAuth should not block http options requests Fixes - Disable perMessageDeflate on WS servers - fixes 'zlib binding closed' error - Clear trigger status icon on re-deploy - Don't default inject payload to blank string - Trigger node, add configurable reset - - Allow function properties in settings Fixes #790 - fixes use of httpNodeMiddleware + - #790 - fixes use of httpNodeMiddleware Allow function properties in settings Fixes - Fix order of config dialog calls to save/creds/validate - Add debounce to Pi GPIO node @@ -1722,7 +1832,7 @@ Fixes - Add 'previous value' option to Switch node - Allow existing nodes to splice into links on drag - - CORS not properly configured on multiple http routes Fixes #783 + - #783 CORS not properly configured on multiple http routes Fixes - Restore shift-drag to snap/unsnap to grid - Moving nodes with keyboard should flag workspace dirty - Notifications flagged as fixed should not be click-closable diff --git a/Gruntfile.js b/Gruntfile.js index cbf70f4dc..32556f0a4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -125,6 +125,7 @@ module.exports = function(grunt) { src: [ // Ensure editor source files are concatenated in // the right order + "packages/node_modules/@node-red/editor-client/src/js/polyfills.js", "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", @@ -151,6 +152,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/common/stack.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js", "packages/node_modules/@node-red/editor-client/src/js/ui/common/toggleButton.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js", "packages/node_modules/@node-red/editor-client/src/js/ui/actions.js", "packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js", "packages/node_modules/@node-red/editor-client/src/js/ui/diff.js", @@ -163,6 +165,8 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js", "packages/node_modules/@node-red/editor-client/src/js/ui/palette.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js", "packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js", "packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js", @@ -177,6 +181,7 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js", "packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js", "packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js", + "packages/node_modules/@node-red/editor-client/src/js/ui/group.js", "packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js", "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js", "packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js", @@ -189,11 +194,12 @@ module.exports = function(grunt) { vendor: { files: { "packages/node_modules/@node-red/editor-client/public/vendor/vendor.js": [ - "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.4.1.min.js", - "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.0.1.min.js", + "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.5.1.min.js", + "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js", - "packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js", + "node_modules/marked/marked.min.js", + "node_modules/dompurify/dist/purify.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/i18next/i18next.min.js", "node_modules/jsonata/jsonata-es5.min.js", diff --git a/package.json b/package.json index a6c4c5091..abb22e118 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.0.3", + "version": "1.1.0", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -24,7 +24,7 @@ } ], "dependencies": { - "ajv": "6.10.2", + "ajv": "6.12.0", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "body-parser": "1.19.0", @@ -32,7 +32,7 @@ "clone": "2.1.2", "content-type": "1.0.4", "cookie": "0.4.0", - "cookie-parser": "1.4.4", + "cookie-parser": "1.4.5", "cors": "2.8.5", "cron": "1.7.2", "denque": "1.4.1", @@ -41,40 +41,43 @@ "fs-extra": "8.1.0", "fs.notify": "0.0.4", "hash-sum": "2.0.0", - "https-proxy-agent": "2.2.4", + "https-proxy-agent": "5.0.0", "i18next": "15.1.2", - "iconv-lite": "0.5.0", + "iconv-lite": "0.5.1", "is-utf8": "0.2.1", "js-yaml": "3.13.1", "json-stringify-safe": "5.0.1", - "jsonata": "1.8.0", + "jsonata": "1.8.3", + "lodash.clonedeep": "^4.5.0", "media-typer": "1.1.0", - "memorystore": "1.6.1", + "memorystore": "1.6.2", "mime": "2.4.4", "mqtt": "2.18.8", "multer": "1.4.2", - "mustache": "3.0.2", + "mustache": "4.0.1", "node-red-node-rbe": "^0.2.6", "node-red-node-sentiment": "^0.1.6", "node-red-node-tail": "^0.1.0", - "nopt": "4.0.1", + "nopt": "4.0.3", "oauth2orize": "1.11.0", "on-headers": "1.0.2", - "passport": "0.4.0", + "passport": "0.4.1", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", "raw-body": "2.4.1", "request": "2.88.0", "semver": "6.3.0", - "uglify-js": "3.6.9", + "uglify-js": "3.8.1", "when": "3.7.8", "ws": "6.2.1", - "xml2js": "0.4.22" + "xml2js": "0.4.23" }, "optionalDependencies": { - "bcrypt": "3.0.6" + "bcrypt": "3.0.8" }, "devDependencies": { + "marked": "0.8.2", + "dompurify": "2.0.8", "grunt": "~1.0.4", "grunt-chmod": "~1.1.1", "grunt-cli": "~1.3.2", @@ -101,8 +104,8 @@ "minami": "1.2.3", "mocha": "^5.2.0", "mosca": "^2.8.3", - "node-red-node-test-helper": "^0.2.3", - "node-sass": "^4.13.0", + "node-red-node-test-helper": "^0.2.5", + "node-sass": "^4.13.1", "should": "^8.4.0", "sinon": "1.17.7", "stoppable": "^1.1.0", 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 a7ee2e7f3..d4ec10f08 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 @@ -36,6 +36,7 @@ var log = require("@node-red/util").log; // TODO: separate module passport.use(strategies.bearerStrategy.BearerStrategy); passport.use(strategies.clientPasswordStrategy.ClientPasswordStrategy); passport.use(strategies.anonymousStrategy); +passport.use(strategies.tokensStrategy); var server = oauth2orize.createServer(); @@ -60,7 +61,7 @@ function init(_settings,storage) { function needsPermission(permission) { return function(req,res,next) { if (settings && settings.adminAuth) { - return passport.authenticate(['bearer','anon'],{ session: false })(req,res,function() { + return passport.authenticate(['bearer','tokens','anon'],{ session: false })(req,res,function() { if (!req.user) { return next(); } @@ -100,7 +101,10 @@ function login(req,res) { } } else if (mergedAdminAuth.type === "strategy") { - var urlPrefix = (settings.httpAdminRoot==='/')?"":settings.httpAdminRoot; + var urlPrefix = (settings.httpAdminRoot||"").replace(/\/$/,""); + if (urlPrefix.length > 0) { + urlPrefix += "/"; + } response = { "type":"strategy", "prompts":[{type:"button",label:mergedAdminAuth.strategy.label, url: urlPrefix + "auth/strategy"}] diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js index b17bf1473..87023a487 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js @@ -123,9 +123,38 @@ AnonymousStrategy.prototype.authenticate = function(req) { }); } +function TokensStrategy() { + passport.Strategy.call(this); + this.name = 'tokens'; +} +util.inherits(TokensStrategy, passport.Strategy); +TokensStrategy.prototype.authenticate = function(req) { + var self = this; + var token = null; + if (Users.tokenHeader() === 'authorization') { + if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { + token = req.headers.authorization.split(' ')[1]; + } + } else { + token = req.headers[Users.tokenHeader()]; + } + if (token) { + Users.tokens(token).then(function(admin) { + if (admin) { + self.success(admin,{scope:admin.permissions}); + } else { + self.fail(401); + } + }); + } else { + self.fail(401); + } +} + module.exports = { bearerStrategy: bearerStrategy, clientPasswordStrategy: clientPasswordStrategy, passwordTokenExchange: passwordTokenExchange, - anonymousStrategy: new AnonymousStrategy() + anonymousStrategy: new AnonymousStrategy(), + tokensStrategy: new TokensStrategy() } diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/users.js b/packages/node_modules/@node-red/editor-api/lib/auth/users.js index 24a762958..f032332db 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/users.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/users.js @@ -59,7 +59,9 @@ function getDefaultUser() { var api = { get: get, authenticate: authenticate, - default: getDefaultUser + default: getDefaultUser, + tokens: getDefaultUser, + tokenHeader: "authorization" } function init(config) { @@ -105,6 +107,12 @@ function init(config) { } else { api.default = getDefaultUser; } + if (config.tokens && typeof config.tokens === "function") { + api.tokens = config.tokens; + if (config.tokenHeader && typeof config.tokenHeader === "string") { + api.tokenHeader = config.tokenHeader.toLowerCase(); + } + } } function cleanUser(user) { if (user && user.hasOwnProperty('password')) { @@ -118,5 +126,7 @@ module.exports = { init: init, get: function(username) { return api.get(username).then(cleanUser)}, authenticate: function() { return api.authenticate.apply(null, arguments) }, - default: function() { return api.default(); } + default: function() { return api.default(); }, + tokens: function(token) { return api.tokens(token); }, + tokenHeader: function() { return api.tokenHeader } }; 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 0dc06ab71..534a77869 100644 --- a/packages/node_modules/@node-red/editor-api/lib/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/index.js @@ -59,6 +59,12 @@ function init(settings,_server,storage,runtimeAPI) { }); adminApp.use(corsHandler); + if (settings.httpAdminMiddleware) { + if (typeof settings.httpAdminMiddleware === "function") { + adminApp.use(settings.httpAdminMiddleware) + } + } + auth.init(settings,storage); var maxApiRequestSize = settings.apiMaxLength || '5mb'; diff --git a/packages/node_modules/@node-red/editor-api/lib/util.js b/packages/node_modules/@node-red/editor-api/lib/util.js index 0cef96bbb..950e3d9cc 100644 --- a/packages/node_modules/@node-red/editor-api/lib/util.js +++ b/packages/node_modules/@node-red/editor-api/lib/util.js @@ -43,10 +43,16 @@ module.exports = { rejectHandler: function(req,res,err) { //TODO: why this when errorHandler also?! log.audit({event: "api.error",error:err.code||"unexpected_error",message:err.message||err.toString()},req); - res.status(err.status||400).json({ + var response = { code: err.code||"unexpected_error", message: err.message||err.toString() - }); + }; + // Handle auth failures on a specific remote + // TODO: don't hardcode this here - allow users of rejectHandler to identify extra props to send + if (err.remote) { + response.remote = err.remote; + } + res.status(err.status||400).json(response); }, getRequestLogObject: function(req) { return { diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 824f4df5b..936fe3edc 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "1.0.3", + "version": "1.1.0", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,21 +16,21 @@ } ], "dependencies": { - "@node-red/util": "1.0.3", - "@node-red/editor-client": "1.0.3", + "@node-red/util": "1.1.0", + "@node-red/editor-client": "1.1.0", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "clone": "2.1.2", "cors": "2.8.5", "express-session": "1.17.0", "express": "4.17.1", - "memorystore": "1.6.1", + "memorystore": "1.6.2", "mime": "2.4.4", - "mustache": "3.0.2", + "mustache": "4.0.1", "oauth2orize": "1.11.0", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", - "passport": "0.4.0", + "passport": "0.4.1", "when": "3.7.8", "ws": "6.2.1" }, 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 c7394c347..0c8dd42eb 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 @@ -34,11 +34,11 @@ "view" : "Ansicht", "grid" : "Gitter", "showGrid" : "Raster anzeigen", - "snapGrid" : "Einrasten am Raster", + "snapGrid" : "Am Raster ausrichten", "gridSize" : "Rastergröße", "textDir" : "Textrichtung", "defaultDir" : "Standard", - "ltr" : "Links-nach-rechts", + "ltr" : "Von links nach rechts", "rtl" : "Von rechts nach links", "auto" : "Kontextuell" }, @@ -53,15 +53,15 @@ "import" : "Import", "export" : "Exportieren", "search" : "Flows durchsuchen", - "searchInput" : "durchsuchen Sie Ihre Flows", + "searchInput" : "Flows durchsuchen", "subflows" : "Subflow", "createSubflow" : "Subflow erstellen", - "selectionToSubflow" : "Auswahl für Subflow", + "selectionToSubflow" : "Auswahl zu Subflow", "flows" : "Flows", "add" : "Hinzufügen", "rename" : "Umbenennen", "delete" : "Löschen", - "keyboardShortcuts" : "Tastaturkurzbefehle", + "keyboardShortcuts" : "Tastenkürzel", "login" : "Anmelden", "logout" : "Abmelden", "editPalette" : "Palette verwalten", @@ -217,7 +217,7 @@ "remote" : "Ferne Änderungen", "reviewChanges" : "Änderungen prüfen", "noBinaryFileShowed" : "Der Inhalt der Binärdatei kann nicht angezeigt", - "viewCommitDiff" : "Änderungen festschreiben", + "viewCommitDiff" : "Änderungen committen", "compareChanges" : "Änderungen vergleichen", "saveConflict" : "Konfliktlösung speichern", "conflictHeader" : " __resolved__ von __unresolved__ -Konflikten behoben", @@ -226,8 +226,8 @@ "newVersionError" : "Neue Version enthält keine gültige JSON-Datei:" }, "subflow" : { - "editSubflow" : "Flowschablone bearbeiten: __name__", - "edit" : "Flowsschablone bearbeiten", + "editSubflow" : "Subflow bearbeiten: __name__", + "edit" : "Subflow bearbeiten", "subflowInstances" : "Es ist __count__ Instanz dieser Subflow-Vorlage vorhanden.", "subflowInstances_plural" : "Es gibt __count__ Instanzen dieser Subflow-Vorlage.", "editSubflowProperties" : "Eigenschaften bearbeiten", @@ -266,7 +266,7 @@ } }, "keyboard" : { - "title" : "Tastaturkurzbefehle", + "title" : "Tastenkürzel", "keyboard" : "Tastatur", "filterActions" : "Filteraktionen", "shortcut" : "Direktaufruf", @@ -283,7 +283,7 @@ "exportNode" : "Node exportieren", "nudgeNode" : "Ausgewählte Nodes verschieben (1px)", "moveNode" : "Ausgewählte Nodes verschieben (20px)", - "toggleSidebar" : "Seitenleiste ein-/ausschalten", + "toggleSidebar" : "Seitenleiste ein-/ausblenden", "copyNode" : "Ausgewählte Nodes kopieren", "cutNode" : "Ausgewählte Nodes ausschneiden", "pasteNode" : "Node einfügen", @@ -308,7 +308,7 @@ }, "palette" : { "noInfo" : "Keine Informationen verfügbar", - "filter" : "Filter Nodes", + "filter" : "Nodes filtern", "search" : "Suchmodule", "addCategory" : "Neu hinzufügen ...", "label" : { @@ -366,11 +366,11 @@ "remove" : "entfernen", "update" : "Update auf __version__", "updated" : "aktualisiert", - "install" : "installieren", - "installed" : "installiert", + "install" : "Installieren", + "installed" : "Installiert", "loading" : "Kataloge werden geladen ...", "tab-nodes" : "Nodes", - "tab-install" : "installieren", + "tab-install" : "Installieren", "sort" : "Sortierung:", "sortAZ" : "a-z", "sortRecent" : "kürzlich", @@ -452,7 +452,7 @@ "name" : "Kontextdaten", "label" : "Kontext", "none" : "keine ausgewählt", - "refresh" : "Aktualisierung zum Laden", + "refresh" : "Zum Aktualisieren neu laden", "empty" : "leer", "node" : "Node", "flow" : "Flow", @@ -477,7 +477,7 @@ "none" : "Keine", "install" : "installieren", "removeFromProject" : "Aus Projekt entfernen", - "addToProject" : "zu Projekt hinzufügen", + "addToProject" : "Zu Projekt hinzufügen", "files" : "Dateien", "flow" : "Flow", "credentials" : "Berechtigungsnachweis", @@ -510,7 +510,7 @@ }, "userSettings" : { "committerDetail" : "Committer-Details", - "committerTip" : "Leer Wert für Systemstandardwert belassen", + "committerTip" : "Leer lassen für Systemstandard", "userName" : "Benutzername", "email" : "E-Mail", "sshKeys" : "SSH-Schlüssel", @@ -544,7 +544,7 @@ "revertChanges" : "Änderungen zurücksetzen", "localChanges" : "Lokale Änderungen", "none" : "Keine", - "conflictResolve" : "Alle Konflikte wurden aufgelöst. Festschreiben der Änderungen, um den Mischvorgang abzuschließen.", + "conflictResolve" : "Alle Konflikte wurden aufgelöst. Committe die Änderungen, um den Merge Request abzuschließen.", "localFiles" : "Lokale Dateien", "all" : "alle", "unmergedChanges" : "Nicht zusammengeführte Änderungen", 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 7977a5752..f71bd2577 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 @@ -14,7 +14,13 @@ "back": "Back", "next": "Next", "clone": "Clone project", - "cont": "Continue" + "cont": "Continue", + "style": "Style", + "line": "Outline", + "fill": "Fill", + "label": "Label", + "color": "Color", + "position": "Position" }, "type": { "string": "string", @@ -28,6 +34,13 @@ "null": "null" } }, + "event": { + "loadPalette": "Loading Palette", + "loadNodeCatalogs": "Loading Node catalogs", + "loadNodes": "Loading Nodes __count__", + "loadFlows": "Loading Flows", + "importFlows": "Adding Flows to workspace" + }, "workspace": { "defaultName": "Flow __number__", "editFlow": "Edit flow: __name__", @@ -91,7 +104,12 @@ "projects-new": "New", "projects-open": "Open", "projects-settings": "Project Settings", - "showNodeLabelDefault": "Show label of newly added nodes" + "showNodeLabelDefault": "Show label of newly added nodes", + "groups": "Groups", + "groupSelection": "Group selection", + "ungroupSelection": "Ungroup selection", + "groupMergeSelection": "Merge selection", + "groupRemoveSelection": "Remove from group" } }, "actions": { @@ -171,6 +189,8 @@ "node_plural": "__count__ nodes", "configNode": "__count__ configuration node", "configNode_plural": "__count__ configuration nodes", + "group": "__count__ group", + "group_plural": "__count__ groups", "flow": "__count__ flow", "flow_plural": "__count__ flows", "subflow": "__count__ subflow", @@ -186,6 +206,9 @@ "nodesImported": "Imported:", "nodeCopied": "__count__ node copied", "nodeCopied_plural": "__count__ nodes copied", + "groupCopied": "__count__ group copied", + "groupCopied_plural": "__count__ groups copied", + "groupStyleCopied": "Group style copied", "invalidFlow": "Invalid flow: __message__", "export": { "selected":"selected nodes", @@ -308,6 +331,13 @@ "multipleInputsToSelection": "Cannot create subflow: multiple inputs to selection" } }, + "group": { + "editGroup": "Edit group: __name__", + "errors": { + "cannotCreateDiffGroups": "Cannot create group using nodes from different groups", + "cannotAddSubflowPorts": "Cannot add subflow ports to a group" + } + }, "editor": { "configEdit": "Edit", "configAdd": "Add", @@ -351,7 +381,8 @@ "bool": "bool", "json": "JSON", "bin": "buffer", - "env": "env variable" + "env": "env variable", + "cred": "credential" }, "menu": { "input": "input", @@ -538,6 +569,7 @@ "label": "info", "node": "Node", "type": "Type", + "group": "Group", "module": "Module", "id": "ID", "status": "Status", @@ -560,7 +592,20 @@ "nodeHelp": "Node Help", "none":"None", "arrayItems": "__count__ items", - "showTips":"You can open the tips from the settings panel" + "showTips":"You can open the tips from the settings panel", + "outline": "Outline", + "empty": "empty", + "globalConfig": "Global Configuration Nodes" + }, + "help": { + "name": "Help", + "label": "help", + "search": "Search help", + "nodeHelp": "Node Help", + "showHelp": "Show help", + "showInOutline": "Show in outline", + "showTopics": "Show topics", + "noHelp": "No help topic selected" }, "config": { "name": "Configuration nodes", @@ -613,7 +658,6 @@ "removeFromProject": "remove from project", "addToProject": "add to project", "files": "Files", - "package": "Package", "flow": "Flow", "credentials": "Credentials", "package":"Package", @@ -805,9 +849,9 @@ "expandItems": "Expand items", "collapseItems": "Collapse items", "duplicate": "Duplicate", - "error": { - "invalidJSON": "Invalid JSON: " - } + "error": { + "invalidJSON": "Invalid JSON: " + } }, "markdownEditor": { "title": "Markdown editor", @@ -978,7 +1022,8 @@ "passphrase": "Passphrase", "retry": "Retry", "update-failed": "Failed to update auth", - "unhandled": "Unhandled error response" + "unhandled": "Unhandled error response", + "host-key-verify-failed": "

Host key verification failed.

The repository host key could not be verified. Please update your known_hosts file and try again.

" }, "create-branch-list": { "invalid": "Invalid branch", 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 old mode 100755 new mode 100644 index cf04ced1d..9a0c23cc5 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -14,7 +14,13 @@ "back": "戻る", "next": "進む", "clone": "プロジェクトをクローン", - "cont": "続ける" + "cont": "続ける", + "style": "形式", + "line": "線", + "fill": "塗りつぶし", + "label": "ラベル", + "color": "色", + "position": "配置" }, "type": { "string": "文字列", @@ -28,6 +34,13 @@ "null": "null" } }, + "event": { + "loadPalette": "パレットを読み込み中", + "loadNodeCatalogs": "ノードカタログを読み込み中", + "loadNodes": "ノードを読み込み中 __count__", + "loadFlows": "フローを読み込み中", + "importFlows": "ワークスペースにフローを追加中" + }, "workspace": { "defaultName": "フロー __number__", "editFlow": "フローを編集: __name__", @@ -67,7 +80,7 @@ "settings": "設定", "userSettings": "ユーザ設定", "nodes": "ノード", - "displayStatus": "ノードの状態を表示", + "displayStatus": "ノードのステータスを表示", "displayConfig": "ノードの設定", "import": "読み込み", "export": "書き出し", @@ -91,7 +104,12 @@ "projects-new": "新規", "projects-open": "開く", "projects-settings": "設定", - "showNodeLabelDefault": "追加したノードのラベルを表示" + "showNodeLabelDefault": "追加したノードのラベルを表示", + "groups": "グループ", + "groupSelection": "選択部分をグループ化", + "ungroupSelection": "選択部分をグループ解除", + "groupMergeSelection": "選択部分をマージ", + "groupRemoveSelection": "グループから削除" } }, "actions": { @@ -171,6 +189,8 @@ "node_plural": "__count__ 個のノード", "configNode": "__count__ 個の設定ノード", "configNode_plural": "__count__ 個の設定ノード", + "group": "__count__ 個のグループ", + "group_plural": "__count__ 個のグループ", "flow": "__count__ 個のフロー", "flow_plural": "__count__ 個のフロー", "subflow": "__count__ 個のサブフロー", @@ -186,6 +206,9 @@ "nodesImported": "読み込みました:", "nodeCopied": "__count__ 個のノードをコピーしました", "nodeCopied_plural": "__count__ 個のノードをコピーしました", + "groupCopied": "__count__ 個のグループをコピーしました", + "groupCopied_plural": "__count__ 個のグループをコピーしました", + "groupStyleCopied": "グループの形式をコピーしました", "invalidFlow": "不正なフロー: __message__", "export": { "selected": "選択したフロー", @@ -308,6 +331,13 @@ "multipleInputsToSelection": "サブフローを作成できません: 複数の入力が選択されています" } }, + "group": { + "editGroup": "__name__ グループを編集", + "errors": { + "cannotCreateDiffGroups": "異なるグループのノードを使用してグループを作成することはできません", + "cannotAddSubflowPorts": "グループにサブフローの端子を追加できません" + } + }, "editor": { "configEdit": "編集", "configAdd": "追加", @@ -351,7 +381,8 @@ "bool": "真偽", "json": "JSON", "bin": "バッファ", - "env": "環境変数" + "env": "環境変数", + "cred": "認証情報" }, "menu": { "input": "入力", @@ -538,6 +569,7 @@ "label": "情報", "node": "ノード", "type": "型", + "group": "グループ", "module": "モジュール", "id": "ID", "status": "状態", @@ -560,7 +592,20 @@ "nodeHelp": "ノードのヘルプ", "none": "なし", "arrayItems": "__count__ 要素", - "showTips": "設定からヒントを表示できます" + "showTips": "設定からヒントを表示できます", + "outline": "アウトライン", + "empty": "空", + "globalConfig": "グローバル設定ノード" + }, + "help": { + "name": "ヘルプ", + "label": "ヘルプ", + "search": "ヘルプを検索", + "nodeHelp": "ノードヘルプ", + "showHelp": "ヘルプを表示", + "showInOutline": "アウトラインに表示", + "showTopics": "トピックを表示", + "noHelp": "ヘルプのトピックが未選択" }, "config": { "name": "ノードの設定を表示", @@ -613,9 +658,9 @@ "removeFromProject": "プロジェクトから削除", "addToProject": "プロジェクトへ追加", "files": "ファイル", - "package": "パッケージ", "flow": "フロー", "credentials": "認証情報", + "package": "パッケージ", "packageCreate": "変更が保存された時にファイルが作成されます", "fileNotExist": "ファイルが存在しません", "selectFile": "ファイルを選択", @@ -756,7 +801,8 @@ "bin": "バッファ", "date": "日時", "jsonata": "JSONata式", - "env": "環境変数" + "env": "環境変数", + "cred": "認証情報" } }, "editableList": { @@ -976,7 +1022,8 @@ "passphrase": "パスフレーズ", "retry": "リトライ", "update-failed": "認証の更新に失敗しました", - "unhandled": "エラー応答が処理されませんでした" + "unhandled": "エラー応答が処理されませんでした", + "host-key-verify-failed": "

ホストキーの検証に失敗

リポジトリのホストキーを検証できませんでした。known_hostsファイルを更新して、もう一度試してください。

" }, "create-branch-list": { "invalid": "不正なブランチ", 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 3695eb263..95d3fa5ee 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 @@ -24,7 +24,7 @@ "buffer": "buffer", "object": "对象", "jsonString": "JSON字符串", - "undefined": "为定义", + "undefined": "未定义", "null": "空" } }, @@ -1008,6 +1008,7 @@ "en-US": "英文", "ja": "日语", "ko": "韩文", - "zh-CN": "简体中文" + "zh-CN": "简体中文", + "zh-TW": "繁体中文" } } 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 56ede1b4e..f27ec1f51 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 @@ -262,5 +262,9 @@ "$distinct": { "args": "array", "desc": "返回一个数组,其中重复的值已从`数组`中删除" + }, + "$type": { + "args": "value", + "desc": "以字符串形式返回`值`的类型。 如果该`值`未定义,则将返回`未定义`" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json b/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json index 492fce58c..897599bc7 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json @@ -15,6 +15,17 @@ "next": "下一步", "clone": "複製專案", "cont": "Continue" + }, + "type": { + "string": "字符串", + "number": "數值", + "boolean": "布林", + "array": "數組", + "buffer": "buffer", + "object": "對象", + "jsonString": "JSON字符串", + "undefined": "未定義", + "null": "空" } }, "workspace": { @@ -29,8 +40,7 @@ "enabled": "有效", "disabled": "無效", "info": "詳細描述", - "selectNodes": "點擊節點用於選擇", - "tip": "詳細描述支援Markdown羽量級標記語言,並將出現在資訊標籤中。" + "selectNodes": "點擊節點用於選擇" }, "menu": { "label": { @@ -45,14 +55,14 @@ "ltr": "從左到右", "rtl": "從右到左", "auto": "上下文", - "language": "Language", - "browserDefault": "Browser default" + "language": "語言", + "browserDefault": "瀏覽器默認" }, "sidebar": { "show": "顯示側邊欄" }, "palette": { - "show": "Show palette" + "show": "顯示控制板" }, "settings": "設置", "userSettings": "使用者設置", @@ -81,10 +91,7 @@ "projects-new": "新專案", "projects-open": "開啟專案", "projects-settings": "專案設定", - "showNodeLabelDefault": "顯示新添加節點的標籤", - "clipboard": "剪貼簿", - "library": "庫", - "examples": "範例" + "showNodeLabelDefault": "顯示新添加節點的標籤" } }, "actions": { @@ -204,8 +211,7 @@ }, "copyMessagePath": "已複製路徑", "copyMessageValue": "已複製數值", - "copyMessageValue_truncated": "已複製捨棄的數值", - "selectNodes": "選擇上面的文本並複製到剪貼簿" + "copyMessageValue_truncated": "已複製捨棄的數值" }, "deploy": { "deploy": "部署", @@ -237,7 +243,7 @@ "undeployedChanges": "您有未部署的更改。\n\n離開此頁面將丟失這些更改。", "improperlyConfigured": "工作區包含一些未正確配置的節點:", "unknown": "工作區包含一些未知的節點類型:", - "confirm": "你確定要部署嗎?", + "confirm": "確定要部署嗎?", "doNotWarn": "不要再對此發出警告", "conflict": "伺服器正在運行較新的一組流程。", "backgroundUpdate": "伺服器上的流程已更新。", @@ -300,8 +306,7 @@ "errors": { "noNodesSelected": "無法創建子流程: 未選擇節點", "multipleInputsToSelection": "無法創建子流程: 多個輸入到了選擇" - }, - "format": "標記格式" + } }, "editor": { "configEdit": "編輯", @@ -316,17 +321,53 @@ "addNewType": "添加新的__type__節點", "nodeProperties": "節點屬性", "label": "Label", + "color": "顏色", "portLabels": "埠標籤", "labelInputs": "輸入", "labelOutputs": "輸出", "settingIcon": "Icon", + "default": "默認", "noDefaultLabel": "無", "defaultLabel": "使用默認標籤", - "searchIcons": "搜尋 icons", + "searchIcons": "搜尋圖標", "useDefault": "使用默認", "description": "描述", "show": "顯示", "hide": "隱藏", + "locale": "選擇界面語言", + "icon": "圖標", + "inputType": "輸入類型", + "inputs": { + "input": "輸入", + "select": "選擇", + "checkbox": "復選框", + "spinner": "微調器", + "none": "空", + "hidden": "隱藏屬性" + }, + "types": { + "str": "字符串", + "num": "數字", + "bool": "布爾", + "json": "JSON", + "bin": "buffer", + "env": "環境變量" + }, + "menu": { + "input": "輸入", + "select": "選擇", + "checkbox": "復選框", + "spinner": "微調器", + "hidden": "僅標簽" + }, + "select": { + "label": "標簽", + "value": "值" + }, + "spinner": { + "min": "最小值", + "max": "最大值" + }, "errors": { "scopeChange": "更改範圍將使其他流程中的節點無法使用", "invalidProperties": "無效的屬性:" @@ -356,8 +397,9 @@ "cutNode": "剪切所選節點", "pasteNode": "粘貼節點", "undoChange": "撤銷上次執行的更改", - "searchBox": "打開搜索框", - "managePalette": "管理面板" + "searchBox": "打開搜尋框", + "managePalette": "管理面板", + "actionList": "動作列表" }, "library": { "library": "庫", @@ -371,28 +413,27 @@ "savedNodes": "保存的節點", "savedType": "已保存__type__", "saveFailed": "保存失敗: __message__", + "newFolder": "新文件夾", "types": { "local": "本地", "examples": "例子" }, - "exportToLibrary": "將節點匯出到庫", - "filename": "檔案名", - "folder": "資料夾", - "filenamePlaceholder": "文件", - "fullFilenamePlaceholder": "a/b/文件", - "folderPlaceholder": "a/b", - "breadcrumb": "庫" + "exportToLibrary": "將節點匯出到庫" }, "palette": { "noInfo": "無可用資訊", "filter": "過濾節點", - "search": "搜索模組", + "search": "搜尋模組", "addCategory": "添加新的...", "label": { "subflows": "子流程", + "network": "網絡", + "common": "共通", "input": "輸入", "output": "輸出", "function": "功能", + "sequence": "序列", + "parser": "解析", "social": "社交", "storage": "存儲", "analysis": "分析", @@ -459,7 +500,7 @@ "sortRecent": "日期順序", "more": "增加__count__個", "errors": { - "catalogLoadFailed": "無法載入節點目錄。
查看瀏覽器控制台瞭解更多資訊", + "catalogLoadFailed": "無法載入節點目錄。
查看瀏覽器控制臺瞭解更多資訊", "installFailed": "無法安裝: __module__
__message__
查看日誌瞭解更多資訊", "removeFailed": "無法刪除: __module__
__message__
查看日誌瞭解更多資訊", "updateFailed": "無法更新: __module__
__message__
查看日誌瞭解更多資訊", @@ -529,8 +570,10 @@ "none": "無", "subflows": "子流程", "flows": "流程", - "filterUnused": "未使用", "filterAll": "所有", + "showAllConfigNodes": "顯示所有配置節點", + "filterUnused": "未使用", + "showAllUnusedConfigNodes": "顯示所有未使用的配置節點", "filtered": "__count__ 個隱藏" }, "context": { @@ -543,7 +586,9 @@ "flow": "流程", "global": "全局的", "deleteConfirm": "你確定要刪除這個項目嗎?", - "autoRefresh": "自動刷新" + "autoRefresh": "自動刷新", + "refrsh": "刷新", + "delete": "刪除" }, "palette": { "name": "節點管理", @@ -558,6 +603,7 @@ "noSummaryAvailable": "無可用摘要", "editDescription": "編輯專案描述", "editDependencies": "編輯項目依賴", + "noDescriptionAvailable": "沒有可用的描述", "editReadme": "Edit README.md", "showProjectSettings": "顯示項目設置", "projectSettings": { @@ -657,15 +703,15 @@ "moreCommits": "更多提交", "changeLocalBranch": "變更當地分支", "createBranchPlaceholder": "查找或創建分支", - "upstream": "上游的", - "localOverwrite": "您有可通过切换分支覆盖的本地更改。您必须先提交或撤销那些更改。", + "upstream": "上遊的", + "localOverwrite": "您有可通過切換分支覆蓋的本地更改。您必須先提交或撤銷那些更改。", "manageRemoteBranch": "管理遠程分支", "unableToAccess": "無法訪問遠程存儲庫", "retry": "重試", - "setUpstreamBranch": "設置為上游分支", + "setUpstreamBranch": "設置為上遊分支", "createRemoteBranchPlaceholder": "查找或創建遠程分支", - "trackedUpstreamBranch": "創建的分支將被設置為跟踪的上游分支。", - "selectUpstreamBranch": "分支將被創建。 在下面選擇以將其設置為被跟踪的上游分支。", + "trackedUpstreamBranch": "創建的分支將被設置為跟蹤的上遊分支。", + "selectUpstreamBranch": "分支將被創建。 在下面選擇以將其設置為被跟蹤的上遊分支。", "pushFailed": "Push失敗,因為遠程具有更多的最新提交。請先進行pull與merge,然後再嘗試push。", "push": "push", "pull": "pull", @@ -683,7 +729,7 @@ "minsAgo": "__count__分鐘前", "minsAgo_plural": "__count__分鐘前", "secondsAgo": "秒前", - "notTracking": "您的本地分支當前未跟踪遠程分支。", + "notTracking": "您的本地分支當前未跟蹤遠程分支。", "statusUnmergedChanged": "您的存儲庫中有未合併的更改。您需要解決衝突並提交結果。", "repositoryUpToDate": "您的存儲庫是最新的。", "commitsAhead": "您的倉庫領先遠程倉庫__count__次提交。您現在可以push這些提交。", @@ -748,10 +794,23 @@ }, "jsonEditor": { "title": "JSON編輯器", - "format": "格式化JSON" + "format": "格式化JSON", + "rawMode": "編輯 JSON", + "uiMode": "Visual編輯器", + "insertAbove": "在上方插入", + "insertBelow": "在下方插入", + "addItem": "添加項目", + "copyPath": "復制路徑到項目", + "expandItems": "展開項目", + "collapseItems": "收合項目", + "duplicate": "重復", + "error": { + "invalidJSON": "無效的JSON: " + } }, "markdownEditor": { "title": "Markdown 編輯器", + "expand": "展開", "format": "F使用markdown格式化", "heading1": "Heading 1", "heading2": "Heading 2", @@ -786,7 +845,7 @@ }, "git-config": { "setup": "設置您的版本控制客戶端", - "desc0": "Node-RED使用開源工具Git進行版本控制。 它跟踪對項目文件的更改,並允許您將其推送到遠程存儲庫。", + "desc0": "Node-RED使用開源工具Git進行版本控制。 它跟蹤對項目文件的更改,並允許您將其推送到遠程存儲庫。", "desc1": "提交一組更改時,Git會使用用戶名和電子郵件地址記錄誰進行了更改。 用戶名可以是您想要的任何名稱-不必是您的真實姓名。", "desc2": "您的Git客戶端已經配置了以下詳細信息。", "desc3": "您可以稍後在設置對話框的“ Git config”標籤下更改這些設置。", @@ -905,7 +964,7 @@ "confirm": "您確定要刪除此項目嗎?" }, "create-project-list": { - "search": "搜索您的項目", + "search": "搜尋您的項目", "current": "當前的" }, "require-clean": { @@ -938,8 +997,19 @@ }, "editor-tab": { "properties": "屬性", + "envProperties": "環境變量", "description": "描述", "appearance": "外觀", + "preview": "UI預覽", + "defaultValue": "默認值", "env": "環境變量" + }, + "languages": { + "de": "德語", + "en-US": "英語", + "ja": "日語", + "ko": "韓語", + "zh-CN": "簡體中文", + "zh-TW": "繁體中文" } } 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 ae62fe068..6d99ffc6a 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 @@ -214,5 +214,57 @@ "$toMillis": { "args": "timestamp", "desc": "將ISO 8601格式的字串`timestamp`轉換為從UNIX時間 (1970年1月1日 UTC/GMT的午夜)開始到現在的毫秒數。如果該字串的格式不正確,則拋出錯誤。" + }, + "$env": { + "args": "arg", + "desc": "返回環境變量的值。\n\n這是Node-RED定義的函數。" + }, + "$eval": { + "args": "expr [, context]", + "desc": "使用當前上下文來作為評估依據,分析並評估字符串`expr`,其中包含文字JSON或JSONata表達式。" + }, + "$formatInteger": { + "args": "number, picture", + "desc": "將“數字”轉換為字符串,並將其格式化為“圖片”字符串指定的整數表示形式。圖片字符串參數定義了數字的格式,並具有與XPath F&O 3.1 規範中的fn:format-integer相同的語法。" + }, + "$parseInteger": { + "args": "string, picture", + "desc": "使用“圖片”字符串指定的格式將“字符串”參數的內容解析為整數(作為JSON數字)。圖片字符串參數與$formatInteger格式相同。." + }, + "$error": { + "args": "[str]", + "desc": "引發錯誤並顯示一條消息。 可選的`str`將替代$error()函數評估的默認消息。" + }, + "$assert": { + "args": "arg, str", + "desc": "如果`arg`為真,則該函數返回。 如果arg為假,則拋出帶有str的異常作為異常消息。" + }, + "$single": { + "args": "array, function", + "desc": "返回滿足參數function謂語的array參數中的唯一值 (比如:傳遞值時,函數返回布林值“true”)。如果匹配值的數量不唯一時,則拋出異常。\n\n應在以下簽名中提供函數:`function(value [,index [,array []]])`其中value是數組的每個輸入,index是該值的位置,整個數組作為第三個參數傳遞。" + }, + "$encodeUrl": { + "args": "str", + "desc": "通過用表示字符的UTF-8編碼的一個,兩個,三個或四個轉義序列替換某些字符的每個實例,對統一資源定位符(URL)組件進行編碼。\n\n示例:`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" + }, + "$encodeUrlComponent": { + "args": "str", + "desc": "通過用表示字符的UTF-8編碼的一個,兩個,三個或四個轉義序列替換某些字符的每個實例,對統一資源定位符(URL)進行編碼。\n\n示例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`" + }, + "$decodeUrl": { + "args": "str", + "desc": "解碼以前由encodeUrlComponent創建的統一資源定位器(URL)組件。 \n\n示例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`" + }, + "$decodeUrlComponent": { + "args": "str", + "desc": "解碼先前由encodeUrl創建的統一資源定位符(URL)。 \n\n示例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`" + }, + "$distinct": { + "args": "array", + "desc": "返回一個數組,其中重復的值已從`數組`中刪除" + }, + "$type": { + "args": "value", + "desc": "以字符串形式返回`值`的類型。 如果該`值`未定義,則將返回`未定義`" } } diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 7f8061c6d..6774df246 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "1.0.3", + "version": "1.1.0", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/editor-client/src/js/events.js b/packages/node_modules/@node-red/editor-client/src/js/events.js index f79cc864f..41e669aba 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/events.js +++ b/packages/node_modules/@node-red/editor-client/src/js/events.js @@ -32,11 +32,16 @@ } } } - function emit(evt,arg) { + function emit() { + var evt = arguments[0] + var args = Array.prototype.slice.call(arguments,1); + if (RED.events.DEBUG) { + console.log(evt,args); + } if (handlers[evt]) { for (var i=0;i 1) { + throw new Error("Node-RED's IE11 Array.from polyfill doesn't support multiple arguments"); + } + var arrayLike = arguments[0] + var result = []; + if (arrayLike.forEach) { + arrayLike.forEach(function(i) { + result.push(i); + }) + } else { + for (var 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) { $("#red-ui-editor").i18n(); $("#red-ui-palette > .red-ui-palette-spinner").hide(); $(".red-ui-palette-scroll").removeClass("hide"); $("#red-ui-palette-search").removeClass("hide"); - loadFlows(function() { - if (RED.settings.theme("projects.enabled",false)) { - RED.projects.refresh(function(activeProject) { + if (RED.settings.theme("projects.enabled",false)) { + RED.projects.refresh(function(activeProject) { + loadFlows(function() { RED.sidebar.info.refresh() if (!activeProject) { // Projects enabled but no active project @@ -140,12 +147,14 @@ var RED = (function() { } completeLoad(); }); - } else { + }); + } else { + loadFlows(function() { // Projects disabled by the user RED.sidebar.info.refresh() completeLoad(); - } - }); + }); + } } else { var config = configs.shift(); appendNodeConfig(config,stepConfig); @@ -157,6 +166,7 @@ var RED = (function() { } function loadFlows(done) { + loader.reportProgress(RED._("event.loadFlows"),80 ) $.ajax({ headers: { "Accept":"application/json", @@ -167,6 +177,7 @@ var RED = (function() { if (nodes) { var currentHash = window.location.hash; RED.nodes.version(nodes.rev); + loader.reportProgress(RED._("event.importFlows"),90 ) RED.nodes.import(nodes.flows); RED.nodes.dirty(false); RED.view.redraw(true); @@ -193,6 +204,7 @@ var RED = (function() { return; } if (notificationId === "project-update") { + loader.start("Loading project",0) RED.nodes.clear(); RED.history.clear(); RED.view.redraw(true); @@ -208,6 +220,7 @@ var RED = (function() { "revert": RED._("notification.project.revert", {project: msg.project}), "merge-complete": RED._("notification.project.merge-complete") }[msg.action]; + loader.end() RED.notify("

"+message+"

"); RED.sidebar.info.refresh() }); @@ -423,6 +436,12 @@ var RED = (function() { var id = topic.substring(9); RED.eventLog.log(id,payload); }); + + $(".red-ui-header-toolbar").show(); + + setTimeout(function() { + loader.end(); + },100); } function showAbout() { @@ -431,8 +450,7 @@ var RED = (function() { ''+ ''; - RED.sidebar.info.set(aboutHeader+marked(data)); - RED.sidebar.info.show(); + RED.sidebar.help.set(aboutHeader+RED.utils.renderMarkdown(data)); }); } @@ -472,6 +490,14 @@ var RED = (function() { {id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"}, {id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"}, ]}); + menuOptions.push({id:"menu-item-group",label:RED._("menu.label.groups"), options: [ + {id:"menu-item-group-group",label:RED._("menu.label.groupSelection"),disabled:true,onselect:"core:group-selection"}, + {id:"menu-item-group-ungroup",label:RED._("menu.label.ungroupSelection"),disabled:true,onselect:"core:ungroup-selection"}, + null, + {id:"menu-item-group-merge",label:RED._("menu.label.groupMergeSelection"),disabled:true,onselect:"core:merge-selection-to-group"}, + {id:"menu-item-group-remove",label:RED._("menu.label.groupRemoveSelection"),disabled:true,onselect:"core:remove-selection-from-group"} + ]}); + menuOptions.push(null); if (RED.settings.theme('palette.editable') !== false) { menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"}); @@ -497,7 +523,6 @@ var RED = (function() { } function loadEditor() { - RED.workspaces.init(); RED.statusBar.init(); RED.view.init(); @@ -524,6 +549,7 @@ var RED = (function() { } RED.subflow.init(); + RED.group.init(); RED.clipboard.init(); RED.search.init(); RED.actionList.init(); @@ -539,13 +565,14 @@ var RED = (function() { RED.comms.connect(); $("#red-ui-main-container").show(); - $(".red-ui-header-toolbar").show(); + RED.actions.add("core:show-about", showAbout); loadNodeList(); } + function buildEditor(options) { var header = $('
').appendTo(options.target); var logo = $('').appendTo(header); @@ -560,6 +587,10 @@ var RED = (function() { '').appendTo(options.target); $('
').appendTo(options.target); $('
').appendTo(options.target); + + loader.init().appendTo("#red-ui-main-container"); + loader.start("...",0); + $.getJSON(options.apiRootUrl+"theme", function(theme) { if (theme.header) { if (theme.header.url) { @@ -592,12 +623,39 @@ var RED = (function() { options.target.addClass("red-ui-editor"); buildEditor(options); + RED.i18n.init(options, function() { RED.settings.init(options, loadEditor); }) } + var loader = { + init: function() { + var wrapper = $('
').hide(); + var container = $('
').appendTo(wrapper); + var label = $('
',{class:"red-ui-loading-bar-label"}).appendTo(container); + var bar = $('
',{class:"red-ui-loading-bar"}).appendTo(container); + var fill =$('').appendTo(bar); + return wrapper; + }, + start: function(text, prcnt) { + if (text) { + loader.reportProgress(text,prcnt) + } + $("#red-ui-loading-progress").show(); + }, + reportProgress: function(text, prcnt) { + $(".red-ui-loading-bar-label").text(text); + $(".red-ui-loading-bar span").width(prcnt+"%") + }, + end: function() { + $("#red-ui-loading-progress").hide(); + loader.reportProgress("",0); + } + } + return { - init: init + init: init, + loader: loader } })(); 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 cdff62919..33b306752 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 @@ -184,7 +184,7 @@ RED.clipboard = (function() { '
'+ '
'+ '
'+ - '
'+ + '
'+ ''+ '
'+ '
'+ @@ -216,7 +216,7 @@ RED.clipboard = (function() { ' '+ ''+ '
'+ - '
'+ + '
'+ ''+ '
'+ '
'+ @@ -474,6 +474,12 @@ RED.clipboard = (function() { },100) } + var dialogHeight = 400; + var winHeight = $(window).height(); + if (winHeight < 600) { + dialogHeight = 400 - (600 - winHeight); + } + $(".red-ui-clipboard-dialog-box").height(dialogHeight); dialog.dialog("option","title",RED._("clipboard.importNodes")).dialog("open"); popover = RED.popover.create({ @@ -583,6 +589,7 @@ RED.clipboard = (function() { nodes = []; selection.forEach(function(n) { nodes.push(n); + nodes = nodes.concat(RED.nodes.groups(n.id)); nodes = nodes.concat(RED.nodes.filterNodes({z:n.id})); }); } else { @@ -592,7 +599,8 @@ RED.clipboard = (function() { nodes = RED.nodes.createExportableNodeSet(nodes.filter(function(n) { return n.type !== 'subflow'})); } else if (type === 'red-ui-clipboard-dialog-export-rng-flow') { var activeWorkspace = RED.workspaces.active(); - nodes = RED.nodes.filterNodes({z:activeWorkspace}); + nodes = RED.nodes.groups(activeWorkspace); + nodes = nodes.concat(RED.nodes.filterNodes({z:activeWorkspace})); var parentNode = RED.nodes.workspace(activeWorkspace)||RED.nodes.subflow(activeWorkspace); nodes.unshift(parentNode); nodes = RED.nodes.createExportableNodeSet(nodes); @@ -637,6 +645,14 @@ RED.clipboard = (function() { $("#red-ui-clipboard-dialog-export-fmt-mini").trigger("click"); } tabs.activateTab("red-ui-clipboard-dialog-export-tab-"+mode); + + var dialogHeight = 400; + var winHeight = $(window).height(); + if (winHeight < 600) { + dialogHeight = 400 - (600 - winHeight); + } + $(".red-ui-clipboard-dialog-box").height(dialogHeight); + dialog.dialog("option","title",RED._("clipboard.exportNodes")).dialog( "open" ); $("#red-ui-clipboard-dialog-export-text").trigger("focus"); @@ -738,6 +754,8 @@ RED.clipboard = (function() { RED.actions.add("core:show-library-export-dialog",function() { exportNodes('library') }); RED.actions.add("core:show-library-import-dialog",function() { importNodes('library') }); + RED.actions.add("core:show-examples-import-dialog",function() { importNodes('examples') }); + RED.events.on("editor:open",function() { disabled = true; }); RED.events.on("editor:close",function() { disabled = false; }); RED.events.on("search:open",function() { disabled = true; }); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js new file mode 100644 index 000000000..3739cd970 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js @@ -0,0 +1,203 @@ +RED.colorPicker = (function() { + + function create(options) { + var color = options.value; + var id = options.id; + var colorPalette = options.palette || []; + var width = options.cellWidth || 30; + var height = options.cellHeight || 30; + var margin = options.cellMargin || 2; + var perRow = options.cellPerRow || 6; + + var container = $("
",{style:"display:inline-block"}); + var colorHiddenInput = $("", { id: id, type: "hidden", value: color }).appendTo(container); + var opacityHiddenInput = $("", { id: id+"-opacity", type: "hidden", value: options.hasOwnProperty('opacity')?options.opacity:"1" }).appendTo(container); + + var colorButton = $('').appendTo(popOverContent) $('

',{style:"font-size: 0.8em"}).text(metaData).appendTo(popOverContent); } } catch(err) { @@ -181,7 +182,11 @@ RED.palette = (function() { function setIcon(element,sf) { var icon_url = RED.utils.getNodeIcon(sf._def); var iconContainer = element.find(".red-ui-palette-icon-container"); - RED.utils.createIconElement(icon_url, iconContainer, true); + var currentIcon = iconContainer.attr("data-palette-icon"); + if (currentIcon !== icon_url) { + iconContainer.attr("data-palette-icon", icon_url); + RED.utils.createIconElement(icon_url, iconContainer, true); + } } function getPaletteNode(type) { @@ -224,6 +229,7 @@ RED.palette = (function() { var iconContainer = $('

', { class: "red-ui-palette-icon-container"+(((!def.align && def.inputs !== 0 && def.outputs === 0) || "right" === def.align) ? " red-ui-palette-icon-container-right" : "") }).appendTo(d); + iconContainer.attr("data-palette-icon", icon_url); RED.utils.createIconElement(icon_url, iconContainer, true); } @@ -250,6 +256,7 @@ RED.palette = (function() { var popover = RED.popover.create({ target:d, trigger: "hover", + interactive: true, width: "300px", content: "hi", delay: { show: 750, hide: 50 } @@ -265,25 +272,28 @@ RED.palette = (function() { // html: true, // container:'body' // }); - d.on("click", function() { - RED.view.focus(); - var helpText; - if (nt.indexOf("subflow:") === 0) { - helpText = marked(RED.nodes.subflow(nt.substring(8)).info||"")||(''+RED._("sidebar.info.none")+''); - } else { - helpText = $("script[data-help-name='"+d.attr("data-palette-type")+"']").html()||(''+RED._("sidebar.info.none")+''); - } - // Don't look too closely. RED.sidebar.info.set will set the 'Description' - // section of the sidebar. Pass in the title of the Help section so it looks - // right. - RED.sidebar.info.set(helpText,RED._("sidebar.info.nodeHelp")); - }); + // d.on("click", function() { + // RED.view.focus(); + // var helpText; + // if (nt.indexOf("subflow:") === 0) { + // helpText = RED.utils.renderMarkdown(RED.nodes.subflow(nt.substring(8)).info||"")||(''+RED._("sidebar.info.none")+''); + // } else { + // helpText = $("script[data-help-name='"+d.attr("data-palette-type")+"']").html()||(''+RED._("sidebar.info.none")+''); + // } + // // Don't look too closely. RED.sidebar.info.set will set the 'Description' + // // section of the sidebar. Pass in the title of the Help section so it looks + // // right. + // RED.sidebar.type.show(helpText,RED._("sidebar.info.nodeHelp")); + // }); var chart = $("#red-ui-workspace-chart"); var chartSVG = $("#red-ui-workspace-chart>svg").get(0); var activeSpliceLink; var mouseX; var mouseY; var spliceTimer; + var groupTimer; + var activeGroup; + var hoverGroup; var paletteWidth; var paletteTop; $(d).draggable({ @@ -295,16 +305,53 @@ RED.palette = (function() { start: function() { paletteWidth = $("#red-ui-palette").width(); paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top; + hoverGroup = null; + activeGroup = RED.view.getActiveGroup(); + if (activeGroup) { + document.getElementById("group_select_"+activeGroup.id).classList.add("red-ui-flow-group-active-hovered"); + } RED.view.focus(); }, - stop: function() { d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null;}}, + stop: function() { + d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); + if (hoverGroup) { + document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); + } + if (activeGroup) { + document.getElementById("group_select_"+activeGroup.id).classList.remove("red-ui-flow-group-active-hovered"); + } + if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; } + if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; } + }, drag: function(e,ui) { var paletteNode = getPaletteNode(nt); ui.originalPosition.left = paletteNode.offset().left; + mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); + mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop(); + if (!groupTimer) { + groupTimer = setTimeout(function() { + mouseX /= RED.view.scale(); + mouseY /= RED.view.scale(); + var group = RED.view.getGroupAtPoint(mouseX,mouseY); + if (group !== hoverGroup) { + if (hoverGroup) { + document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); + } + if (group) { + document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered"); + } + hoverGroup = group; + if (hoverGroup) { + $(ui.helper).data('group',hoverGroup); + } else { + $(ui.helper).removeData('group'); + } + } + groupTimer = null; + },200) + } if (def.inputs > 0 && def.outputs > 0) { - mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); - mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop(); if (!spliceTimer) { spliceTimer = setTimeout(function() { var nodes = []; @@ -370,7 +417,7 @@ RED.palette = (function() { RED.workspaces.show(nt.substring(8)); e.preventDefault(); }); - nodeInfo = marked(def.info||""); + nodeInfo = RED.utils.renderMarkdown(def.info||""); } setLabel(nt,d,label,nodeInfo); @@ -412,61 +459,67 @@ RED.palette = (function() { categoryNode.show(); paletteNode.show(); } - function refreshNodeTypes() { - RED.nodes.eachSubflow(function(sf) { - var paletteNode = getPaletteNode('subflow:'+sf.id); - var portInput = paletteNode.find(".red-ui-palette-port-input"); - var portOutput = paletteNode.find(".red-ui-palette-port-output"); + RED.nodes.eachSubflow(refreshSubflow) + } + function refreshSubflow(sf) { + var paletteNode = getPaletteNode('subflow:'+sf.id); + var portInput = paletteNode.find(".red-ui-palette-port-input"); + var portOutput = paletteNode.find(".red-ui-palette-port-output"); - var paletteLabel = paletteNode.find(".red-ui-palette-label"); - paletteLabel.attr("class","red-ui-palette-label" + (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-label-right" : "")); + var paletteLabel = paletteNode.find(".red-ui-palette-label"); + paletteLabel.attr("class","red-ui-palette-label" + (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-label-right" : "")); - var paletteIconContainer = paletteNode.find(".red-ui-palette-icon-container"); - paletteIconContainer.attr("class","red-ui-palette-icon-container" + (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-icon-container-right" : "")); + var paletteIconContainer = paletteNode.find(".red-ui-palette-icon-container"); + paletteIconContainer.attr("class","red-ui-palette-icon-container" + (((!sf._def.align && sf.in.length !== 0 && sf.out.length === 0) || "right" === sf._def.align) ? " red-ui-palette-icon-container-right" : "")); - if (portInput.length === 0 && sf.in.length > 0) { - var portIn = document.createElement("div"); - portIn.className = "red-ui-palette-port red-ui-palette-port-input"; - paletteNode.append(portIn); - } else if (portInput.length !== 0 && sf.in.length === 0) { - portInput.remove(); + if (portInput.length === 0 && sf.in.length > 0) { + var portIn = document.createElement("div"); + portIn.className = "red-ui-palette-port red-ui-palette-port-input"; + paletteNode.append(portIn); + } else if (portInput.length !== 0 && sf.in.length === 0) { + portInput.remove(); + } + + if (portOutput.length === 0 && sf.out.length > 0) { + var portOut = document.createElement("div"); + portOut.className = "red-ui-palette-port red-ui-palette-port-output"; + paletteNode.append(portOut); + } else if (portOutput.length !== 0 && sf.out.length === 0) { + portOutput.remove(); + } + var currentLabel = paletteNode.attr("data-palette-label"); + var currentInfo = paletteNode.attr("data-palette-info"); + + if (currentLabel !== sf.name || currentInfo !== sf.info) { + paletteNode.attr("data-palette-info",sf.info); + setLabel(sf.type+":"+sf.id,paletteNode,sf.name,RED.utils.renderMarkdown(sf.info||"")); + } + setIcon(paletteNode,sf); + + var currentCategory = paletteNode.data('category'); + var newCategory = (sf.category||"subflows"); + if (currentCategory !== newCategory) { + var category = escapeCategory(newCategory); + createCategory(newCategory,category,category,"node-red"); + + var currentCategoryNode = paletteNode.closest(".red-ui-palette-category"); + var newCategoryNode = $("#red-ui-palette-"+category); + newCategoryNode.append(paletteNode); + if (newCategoryNode.find(".red-ui-palette-node").length === 1) { + categoryContainers[category].open(); } - if (portOutput.length === 0 && sf.out.length > 0) { - var portOut = document.createElement("div"); - portOut.className = "red-ui-palette-port red-ui-palette-port-output"; - paletteNode.append(portOut); - } else if (portOutput.length !== 0 && sf.out.length === 0) { - portOutput.remove(); - } - setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||"")); - setIcon(paletteNode,sf); - - var currentCategory = paletteNode.data('category'); - var newCategory = (sf.category||"subflows"); - if (currentCategory !== newCategory) { - var category = escapeCategory(newCategory); - createCategory(newCategory,category,category,"node-red"); - - var currentCategoryNode = paletteNode.closest(".red-ui-palette-category"); - var newCategoryNode = $("#red-ui-palette-"+category); - newCategoryNode.append(paletteNode); - if (newCategoryNode.find(".red-ui-palette-node").length === 1) { - categoryContainers[category].open(); - } - - paletteNode.data('category',newCategory); - if (currentCategoryNode.find(".red-ui-palette-node").length === 0) { - if (currentCategoryNode.find("i").hasClass("expanded")) { - currentCategoryNode.find(".red-ui-palette-content").slideToggle(); - currentCategoryNode.find("i").toggleClass("expanded"); - } + paletteNode.data('category',newCategory); + if (currentCategoryNode.find(".red-ui-palette-node").length === 0) { + if (currentCategoryNode.find("i").hasClass("expanded")) { + currentCategoryNode.find(".red-ui-palette-content").slideToggle(); + currentCategoryNode.find("i").toggleClass("expanded"); } } + } - paletteNode.css("backgroundColor", sf.color); - }); + paletteNode.css("backgroundColor", sf.color); } function filterChange(val) { @@ -504,6 +557,8 @@ RED.palette = (function() { $('').appendTo("#red-ui-palette"); $('
').appendTo("#red-ui-palette"); + $("#red-ui-palette > .red-ui-palette-spinner").show(); + RED.events.on('registry:node-type-added', function(nodeType) { var def = RED.nodes.getType(nodeType); @@ -545,7 +600,8 @@ RED.palette = (function() { } }); - $("#red-ui-palette > .red-ui-palette-spinner").show(); + RED.events.on("subflows:change",refreshSubflow); + $("#red-ui-palette-search input").searchBox({ diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js index 2d377ba54..f0944c879 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js @@ -158,7 +158,7 @@ RED.projects.settings = (function() { container.empty(); var desc; if (activeProject.description) { - desc = marked(activeProject.description); + desc = RED.utils.renderMarkdown(activeProject.description); } else { desc = '' + RED._("sidebar.project.noDescriptionAvailable") + ''; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js index 4219b189f..170aacea0 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js @@ -30,13 +30,13 @@ RED.projects.userSettings = (function() { $('
').appendTo(gitconfigContainer).text(RED._("editor:sidebar.project.userSettings.committerTip")); var row = $('
').appendTo(gitconfigContainer); - $('').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row); - gitUsernameInput = $('').appendTo(row); + $('').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row); + gitUsernameInput = $('').appendTo(row); gitUsernameInput.val(currentGitSettings.user.name||""); row = $('
').appendTo(gitconfigContainer); - $('').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row); - gitEmailInput = $('').appendTo(row); + $('').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row); + gitEmailInput = $('').appendTo(row); gitEmailInput.val(currentGitSettings.user.email||""); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js index 0c297c82a..462ad7575 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js @@ -685,6 +685,8 @@ RED.projects = (function() { } } },projectData).then(function() { + RED.menu.setDisabled('menu-item-projects-open',false); + RED.menu.setDisabled('menu-item-projects-settings',false); RED.events.emit("project:change", {name:name}); }).always(function() { setTimeout(function() { @@ -1495,7 +1497,6 @@ RED.projects = (function() { } } else if (projectType === 'open') { return switchProject(selectedProject.name,function(err,data) { - dialog.dialog( "close" ); if (err) { if (err.code !== 'credentials_load_failed') { console.log(RED._("projects.create.unexpected_error"),err) @@ -1604,6 +1605,7 @@ RED.projects = (function() { }, } },{active:true}).then(function() { + dialog.dialog( "close" ); RED.events.emit("project:change", {name:name}); }).always(function() { setTimeout(function() { @@ -1671,16 +1673,27 @@ RED.projects = (function() { if (typeof buttons === 'function') { buttons = buttons(options||{}); } + + + dialog.dialog('option','buttons',buttons); dialogBody.append(container); + + + var dialogHeight = 590; + var winHeight = $(window).height(); + if (winHeight < 750) { + dialogHeight = 590 - (750 - winHeight); + } + $(".red-ui-projects-dialog-box").height(dialogHeight); + $(".red-ui-projects-dialog-project-list-inner-container").height(Math.max(500,dialogHeight) - 180); dialog.dialog('option','title',screen.title||""); dialog.dialog("open"); - dialog.dialog({position: { 'my': 'center top', 'at': 'center top+20', 'of': window }}); } function createProjectList(options) { options = options||{}; - var height = options.height || "300px"; + var height = options.height || "200px"; var container = $('
',{class:"red-ui-projects-dialog-project-list-container" }); var filterTerm = ""; @@ -1939,100 +1952,121 @@ RED.projects = (function() { } }).fail(function(xhr,textStatus,err) { var responses; + if (options.responses && options.responses[xhr.status]) { responses = options.responses[xhr.status]; if (typeof responses === 'function') { resultCallback = responses; resultCallbackArgs = {error:responses.statusText}; return; - } else if (options.handleAuthFail !== false && xhr.responseJSON.code === 'git_auth_failed') { - var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch; + } else if (options.handleAuthFail !== false && (xhr.responseJSON.code === 'git_auth_failed' || xhr.responseJSON.code === 'git_host_key_verification_failed')) { + if (xhr.responseJSON.code === 'git_auth_failed') { + var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch; - var message = $('
'+ + var message = $('
'+ '
'+RED._("projects.send-req.auth-req")+':
'+ '
'+url+'
'+ '
'); - var isSSH = false; - if (/^https?:\/\//.test(url)) { - $('
'+ - '
').appendTo(message); - } else if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(url)) { - isSSH = true; - var row = $('
').appendTo(message); - $('').appendTo(row); - var projectRepoSSHKeySelect = $('
'+ + '
').appendTo(message); + } else if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(url)) { + isSSH = true; + var row = $('
').appendTo(message); + $('').appendTo(row); + var projectRepoSSHKeySelect = $('').appendTo(row); - } + row = $('
').appendTo(message); + $('').appendTo(row); + $('').appendTo(row); + } - var notification = RED.notify(message,{ - type:"error", - fixed: true, - modal: true, - buttons: [ - { - //id: "node-dialog-delete", - //class: 'leftButton', - text: RED._("common.label.cancel"), - click: function() { - notification.close(); - } - },{ - text: ' ' +RED._("projects.send-req.retry") +'', - click: function() { - body = body || {}; - var authBody = {}; - if (isSSH) { - authBody.keyFile = $('#projects-user-auth-key').val(); - authBody.passphrase = $('#projects-user-auth-passphrase').val(); - } else { - authBody.username = $('#projects-user-auth-username').val(); - authBody.password = $('#projects-user-auth-password').val(); + var notification = RED.notify(message,{ + type:"error", + fixed: true, + modal: true, + buttons: [ + { + //id: "node-dialog-delete", + //class: 'leftButton', + text: RED._("common.label.cancel"), + click: function() { + notification.close(); } - var done = function(err) { - if (err) { - console.log(RED._("projects.send-req.update-failed")); - console.log(err); + },{ + text: ' ' +RED._("projects.send-req.retry") +'', + click: function() { + body = body || {}; + var authBody = {}; + if (isSSH) { + authBody.keyFile = $('#projects-user-auth-key').val(); + authBody.passphrase = $('#projects-user-auth-passphrase').val(); } else { - sendRequest(options,body); - notification.close(); + authBody.username = $('#projects-user-auth-username').val(); + authBody.password = $('#projects-user-auth-password').val(); } + var done = function(err) { + if (err) { + console.log(RED._("projects.send-req.update-failed")); + console.log(err); + } else { + sendRequest(options,body); + notification.close(); + } - } - sendRequest({ - url: "projects/"+activeProject.name+"/remotes/"+(xhr.responseJSON.remote||options.remote||'origin'), - type: "PUT", - responses: { - 0: function(error) { - done(error,null); - }, - 200: function(data) { - done(null,data); - }, - 400: { - 'unexpected_error': function(error) { - done(error,null); - } - }, } - },{auth:authBody}); + sendRequest({ + url: "projects/"+activeProject.name+"/remotes/"+(xhr.responseJSON.remote||options.remote||'origin'), + type: "PUT", + responses: { + 0: function(error) { + done(error,null); + }, + 200: function(data) { + done(null,data); + }, + 400: { + 'unexpected_error': function(error) { + done(error,null); + } + }, + } + },{auth:authBody}); + } } - } - ] - }); - return; + ] + }); + return; + } else if (xhr.responseJSON.code === 'git_host_key_verification_failed') { + var message = $('
'+ + '
'+RED._("projects.send-req.host-key-verify-failed")+'
'+ + '
'); + var notification = RED.notify(message,{ + type:"error", + fixed: true, + modal: true, + buttons: [ + { + text: RED._("common.label.close"), + click: function() { + notification.close(); + } + } + ] + }); + return; + } } else if (responses[xhr.responseJSON.code]) { resultCallback = responses[xhr.responseJSON.code]; resultCallbackArgs = xhr.responseJSON; @@ -2066,6 +2100,8 @@ RED.projects = (function() { var branchFilterTerm = ""; var branchFilterCreateItem; var branches = []; + var branchNames = new Set(); + var remotes = []; var branchPrefix = ""; var container = $('
').appendTo(options.container); @@ -2073,18 +2109,30 @@ RED.projects = (function() { delay: 200, change: function() { branchFilterTerm = $(this).val(); - if (/(\.\.|\/\.|[?*[~^: \\]|\/\/|\/.$|\/$)/.test(branchFilterTerm)) { + // if there is a / then + // - check what preceeds it is a known remote + + var valid = false; + var hasRemote = false; + var m = /^([^/]+)\/[^/.~*?\[]/.exec(branchFilterTerm); + if (m && remotes.indexOf(m[1]) > -1) { + valid = true; + hasRemote = true; + } + + if (!valid && /(\.\.|\/\.|[?*[~^: \\]|\/\/|\/.$|\/$)/.test(branchFilterTerm)) { if (!branchFilterCreateItem.hasClass("input-error")) { branchFilterCreateItem.addClass("input-error"); branchFilterCreateItem.find("i").addClass("fa-warning").removeClass("fa-code-fork"); } - branchFilterCreateItem.find("span").text(RED._("projects.create-branch-list.invalid")+": "+branchPrefix+branchFilterTerm); + branchFilterCreateItem.find("span").text(RED._("projects.create-branch-list.invalid")+": "+(hasRemote?"":branchPrefix)+branchFilterTerm); } else { if (branchFilterCreateItem.hasClass("input-error")) { branchFilterCreateItem.removeClass("input-error"); branchFilterCreateItem.find("i").removeClass("fa-warning").addClass("fa-code-fork"); } - branchFilterCreateItem.find(".red-ui-sidebar-vc-branch-list-entry-create-name").text(branchPrefix+branchFilterTerm); + branchFilterCreateItem.find("span").text(RED._("projects.create-branch-list.create")+":"); + branchFilterCreateItem.find(".red-ui-sidebar-vc-branch-list-entry-create-name").text((hasRemote?"":branchPrefix)+branchFilterTerm); } branchList.editableList("filter"); } @@ -2117,8 +2165,12 @@ RED.projects = (function() { if (!entry.hasOwnProperty('commit')) { body.name = branchFilter.val(); body.create = true; - if (options.remote) { - body.name = options.remote()+"/"+body.name; + + if (options.remotes) { + var m = /^([^/]+)\/[^/.~*?\[]/.exec(body.name); + if (!m || remotes.indexOf(m[1]) === -1) { + body.name = remotes[0]+"/"+body.name; + } } } else { if ($(this).hasClass('selected')) { @@ -2133,11 +2185,17 @@ RED.projects = (function() { }, filter: function(data) { var isCreateEntry = (!data.hasOwnProperty('commit')); + var filterTerm = branchFilterTerm; + if (remotes.length > 0) { + var m = /^([^/]+)\/[^/.~*?\[]/.exec(filterTerm); + if (filterTerm !== "" && (!m || remotes.indexOf(m[1]) == -1)) { + filterTerm = remotes[0]+"/"+filterTerm; + } + } return ( isCreateEntry && ( - branchFilterTerm !== "" && - branches.indexOf(branchPrefix+branchFilterTerm) === -1 + filterTerm !== "" && !branchNames.has(filterTerm) ) ) || ( @@ -2152,12 +2210,14 @@ RED.projects = (function() { branchList.editableList('empty'); var start = Date.now(); var spinner = addSpinnerOverlay(container).addClass("red-ui-component-spinner-contain"); - if (options.remote) { - branchPrefix = options.remote()+"/"; + if (options.remotes) { + remotes = options.remotes(); + branchPrefix = remotes[0]+"/"; } else { branchPrefix = ""; + remotes = []; } - + branchNames = new Set(); sendRequest({ url: url, type: "GET", @@ -2169,6 +2229,7 @@ RED.projects = (function() { branches = result.branches; result.branches.forEach(function(b) { branchList.editableList('addItem',b); + branchNames.add(b.name); }); branchList.editableList('addItem',{}); setTimeout(function() { @@ -2204,7 +2265,7 @@ RED.projects = (function() { } function init() { - dialog = $('
') + dialog = $('
') .appendTo("#red-ui-editor") .dialog({ modal: true, @@ -2291,6 +2352,7 @@ RED.projects = (function() { if (data.active) { $.getJSON("projects/"+data.active, function(project) { activeProject = project; + RED.events.emit("projects:load",activeProject); RED.sidebar.versionControl.refresh(true); if (done) { done(activeProject); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js index e367e9c95..7d20fe094 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js @@ -830,10 +830,9 @@ RED.sidebar.versionControl = (function() { var remoteBranchList = utils.createBranchList({ placeholder: RED._("sidebar.project.versionControl.createRemoteBranchPlaceholder"), currentLabel: RED._("sidebar.project.versionControl.upstream"), - remote: function() { + remotes: function() { var project = RED.projects.getActiveProject(); - var remotes = Object.keys(project.git.remotes); - return remotes[0]; + return Object.keys(project.git.remotes); }, container: remoteBranchSubRow, onselect: function(body) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index 9bb4cefb0..7be689c4a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -23,8 +23,7 @@ RED.search = (function() { var visible = false; var index = {}; - var keys = []; - var results = []; + var currentResults = []; var previousActiveElement; @@ -66,23 +65,9 @@ RED.search = (function() { } } - function indexWorkspace() { - index = {}; - RED.nodes.eachWorkspace(indexNode); - RED.nodes.eachSubflow(indexNode); - RED.nodes.eachConfig(indexNode); - RED.nodes.eachNode(indexNode); - keys = Object.keys(index); - keys.sort(); - keys.forEach(function(key) { - index[key] = Object.keys(index[key]).map(function(id) { - return index[key][id]; - }) - }) - } - function search(val) { - searchResults.editableList('empty'); + var results = []; + var keys = Object.keys(index); var typeFilter; var m = /(?:^| )type:([^ ]+)/.exec(val); if (m) { @@ -92,8 +77,7 @@ RED.search = (function() { val = val.trim(); - selected = -1; - results = []; + if (val.length > 0 || typeFilter) { val = val.toLowerCase(); var i; @@ -104,10 +88,14 @@ RED.search = (function() { var key = keys[i]; var kpos = keys[i].indexOf(val); if (kpos > -1) { - for (j=0;j 0) { - for (i=0;i 25) { - searchResults.editableList('addItem', { - more: { - results: results, - start: 25 - } - }) - } - } else { - searchResults.editableList('addItem',{}); - } } + return results; } function ensureSelectedIsVisible() { @@ -161,13 +135,37 @@ RED.search = (function() { searchInput = $('').appendTo(searchDiv).searchBox({ delay: 200, change: function() { - search($(this).val()); + searchResults.editableList('empty'); + selected = -1; + currentResults = search($(this).val()); + if (currentResults.length > 0) { + for (i=0;i 25) { + searchResults.editableList('addItem', { + more: { + results: currentResults, + start: 25 + } + }) + } + } else { + searchResults.editableList('addItem',{}); + } + + } }); + var copySearchContainer = $('').appendTo(searchDiv).on('click', function(evt) { + evt.preventDefault(); + RED.sidebar.info.outliner.search(searchInput.val()) + hide(); + }); searchInput.on('keydown',function(evt) { var children; - if (results.length > 0) { + if (currentResults.length > 0) { if (evt.keyCode === 40) { // Down children = searchResults.children(); @@ -199,21 +197,21 @@ RED.search = (function() { var object = $(children[selected]).find(".red-ui-editableList-item-content").data('data'); if (object) { searchResults.editableList('removeItem',object); - for (i=object.more.start;i object.more.start+25) { + if (currentResults.length > object.more.start+25) { searchResults.editableList('addItem', { more: { - results: results, + results: currentResults, start: object.more.start+25 } }) } } } else { - if (results.length > 0) { - reveal(results[Math.max(0,selected)].node); + if (currentResults.length > 0) { + reveal(currentResults[Math.max(0,selected)].node); } } } @@ -234,13 +232,13 @@ RED.search = (function() { div.on("click", function(evt) { evt.preventDefault(); searchResults.editableList('removeItem',object); - for (i=object.more.start;i object.more.start+25) { + if (currentResults.length > object.more.start+25) { searchResults.editableList('addItem', { more: { - results: results, + results: currentResults, start: object.more.start+25 } }) @@ -253,17 +251,7 @@ RED.search = (function() { var def = node._def; div = $('',{href:'#',class:"red-ui-search-result"}).appendTo(container); - var nodeDiv = $('
',{class:"red-ui-search-result-node"}).appendTo(div); - var colour = RED.utils.getNodeColor(node.type,def); - var icon_url = RED.utils.getNodeIcon(def,node); - if (node.type === 'tab') { - colour = "#C0DEED"; - } - nodeDiv.css('backgroundColor',colour); - - var iconContainer = $('
',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); - RED.utils.createIconElement(icon_url, iconContainer, true); - + RED.utils.createNodeIcon(node).appendTo(div); var contentDiv = $('
',{class:"red-ui-search-result-node-description"}).appendTo(div); if (node.z) { var workspace = RED.nodes.workspace(node.z); @@ -308,7 +296,7 @@ RED.search = (function() { $("#red-ui-palette-shade").show(); $("#red-ui-sidebar-shade").show(); $("#red-ui-sidebar-separator").hide(); - indexWorkspace(); + if (dialog === null) { createDialog(); } @@ -342,6 +330,28 @@ RED.search = (function() { } } + function clearIndex() { + index = {}; + } + + function addItemToIndex(item) { + indexNode(item); + } + function removeItemFromIndex(item) { + var keys = Object.keys(index); + for (var i=0,l=keys.length;i 0) { + n = tmplist.shift(); + if (n.type === "group") { + includedGroups.add(n.id); + tmplist = tmplist.concat(n.nodes); + } + nodeList.add(n); + } + + nodeList = Array.from(nodeList); + + var containingGroup = nodeList[0].g; + var nodesMovedFromGroup = []; + + for (i=0; i 1) { $("",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0])) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js new file mode 100644 index 000000000..4c469425b --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js @@ -0,0 +1,332 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ +RED.sidebar.help = (function() { + + var content; + var toolbar; + var helpSection; + var panels; + var panelRatio; + var helpTopics = []; + var treeList; + var tocPanel; + var helpIndex = {}; + + + function resizeStack() { + var h = $(content).parent().height() - toolbar.outerHeight(); + panels.resize(h) + } + + function init() { + + content = document.createElement("div"); + content.className = "red-ui-sidebar-info" + + toolbar = $("
", {class:"red-ui-sidebar-header red-ui-info-toolbar"}).appendTo(content); + $('').appendTo(toolbar) + var showTOCButton = toolbar.find('#red-ui-sidebar-help-show-toc') + RED.popover.tooltip(showTOCButton,RED._("sidebar.help.showTopics")); + showTOCButton.on("click",function(e) { + e.preventDefault(); + if ($(this).hasClass('selected')) { + hideTOC(); + } else { + showTOC(); + } + }); + + var stackContainer = $("
",{class:"red-ui-sidebar-help-stack"}).appendTo(content); + + tocPanel = $("
", {class: "red-ui-sidebar-help-toc"}).appendTo(stackContainer); + var helpPanel = $("
").css({ + "overflow-y": "scroll" + }).appendTo(stackContainer); + + panels = RED.panels.create({ + container: stackContainer + }) + panels.ratio(0.3); + + helpSearch = $('').appendTo(toolbar).searchBox({ + delay: 100, + change: function() { + var val = $(this).val().toLowerCase(); + if (val) { + showTOC(); + var c = treeList.treeList('filter',function(item) { + if (item.depth === 0) { + return true; + } + return (item.nodeType && item.nodeType.indexOf(val) > -1) || + (item.subflowLabel && item.subflowLabel.indexOf(val) > -1) + },true) + } else { + treeList.treeList('filter',null); + var selected = treeList.treeList('selected'); + if (selected.id) { + treeList.treeList('show',selected.id); + } + + } + } + }) + + helpSection = $("
",{class:"red-ui-help"}).css({ + "padding":"6px", + }).appendTo(helpPanel) + + $(''+RED._("sidebar.help.noHelp")+'').appendTo(helpSection); + + treeList = $("
").css({width: "100%"}).appendTo(tocPanel).treeList({data: []}) + treeList.on('treelistselect', function(e,item) { + if (item.nodeType) { + showHelp(item.nodeType); + } + }) + + RED.sidebar.addTab({ + id: "help", + label: RED._("sidebar.help.label"), + name: RED._("sidebar.help.name"), + iconClass: "fa fa-book", + action:"core:show-help-tab", + content: content, + pinned: true, + enableOnEdit: true, + onchange: function() { + resizeStack() + } + }); + + $(window).on("resize", resizeStack); + $(window).on("focus", resizeStack); + + RED.events.on('registry:node-type-added', queueRefresh); + RED.events.on('registry:node-type-removed', queueRefresh); + RED.events.on('subflows:change', refreshSubflow); + + RED.actions.add("core:show-help-tab",show); + + } + + var refreshTimer; + function queueRefresh() { + if (!refreshTimer) { + refreshTimer = setTimeout(function() { + refreshTimer = null; + refreshHelpIndex(); + },500); + } + } + + function refreshSubflow(sf) { + var item = treeList.treeList('get',"node-type:subflow:"+sf.id); + item.subflowLabel = sf._def.label().toLowerCase(); + item.treeList.replaceElement(getNodeLabel({_def:sf._def,type:sf._def.label()})); + } + + function hideTOC() { + var tocButton = $('#red-ui-sidebar-help-show-toc') + if (tocButton.hasClass('selected')) { + tocButton.removeClass('selected'); + panelRatio = panels.ratio(); + tocPanel.css({"transition":"height 0.2s"}) + panels.ratio(0) + setTimeout(function() { + tocPanel.css({"transition":""}) + },250); + } + } + function showTOC() { + var tocButton = $('#red-ui-sidebar-help-show-toc') + if (!tocButton.hasClass('selected')) { + tocButton.addClass('selected'); + tocPanel.css({"transition":"height 0.2s"}) + panels.ratio(Math.max(0.3,Math.min(panelRatio,0.7))); + setTimeout(function() { + tocPanel.css({"transition":""}) + var selected = treeList.treeList('selected'); + if (selected.id) { + treeList.treeList('show',selected); + } + },250); + } + } + + function refreshHelpIndex() { + helpTopics = []; + var modules = RED.nodes.registry.getModuleList(); + var moduleNames = Object.keys(modules); + moduleNames.sort(); + + var helpData = [{ + label: RED._("sidebar.help.nodeHelp"), + children: [], + expanded: true + }] + + var subflows = RED.nodes.registry.getNodeTypes().filter(function(t) {return /subflow/.test(t)}); + if (subflows.length > 0) { + helpData[0].children.push({ + label: RED._("menu.label.subflows"), + children: [] + }) + subflows.forEach(function(nodeType) { + var sf = RED.nodes.getType(nodeType); + helpData[0].children[0].children.push({ + id:"node-type:"+nodeType, + nodeType: nodeType, + subflowLabel: sf.label().toLowerCase(), + element: getNodeLabel({_def:sf,type:sf.label()}) + }) + }) + } + + + moduleNames.forEach(function(moduleName) { + var module = modules[moduleName]; + var nodeTypes = []; + + var setNames = Object.keys(module.sets); + setNames.forEach(function(setName) { + module.sets[setName].types.forEach(function(nodeType) { + if ($("script[data-help-name='"+nodeType+"']").length) { + nodeTypes.push({ + id: "node-type:"+nodeType, + nodeType: nodeType, + element:getNodeLabel({_def:RED.nodes.getType(nodeType),type:nodeType}) + }) + } + }) + }) + if (nodeTypes.length > 0) { + nodeTypes.sort(function(A,B) { + return A.nodeType.localeCompare(B.nodeType) + }) + helpData[0].children.push({ + id: moduleName, + icon: "fa fa-cube", + label: moduleName, + children: nodeTypes + }) + } + }); + treeList.treeList("data",helpData); + } + + function getNodeLabel(n) { + var div = $('
',{class:"red-ui-info-outline-item"}); + RED.utils.createNodeIcon(n).appendTo(div); + var contentDiv = $('
',{class:"red-ui-search-result-description"}).appendTo(div); + $('
',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).text(n.name||n.type).appendTo(contentDiv); + return div; + } + + function showHelp(nodeType) { + helpSection.empty(); + var helpText; + var title; + var m = /^subflow(:(.+))?$/.exec(nodeType); + if (m && m[2]) { + var subflowNode = RED.nodes.subflow(m[2]); + helpText = (RED.utils.renderMarkdown(subflowNode.info||"")||(''+RED._("sidebar.info.none")+'')); + title = subflowNode.name || nodeType; + } else { + helpText = $("script[data-help-name='"+nodeType+"']").html()||(''+RED._("sidebar.info.none")+''); + title = nodeType; + } + setInfoText(title, helpText, helpSection); + + var ratio = panels.ratio(); + if (ratio > 0.7) { + panels.ratio(0.7) + } + treeList.treeList("show","node-type:"+nodeType) + treeList.treeList("select","node-type:"+nodeType, false); + + } + + function show(type) { + RED.sidebar.show("help"); + if (type) { + hideTOC(); + showHelp(type); + } + } + + // TODO: DRY - projects.js + function addTargetToExternalLinks(el) { + $(el).find("a").each(function(el) { + var href = $(this).attr('href'); + if (/^https?:/.test(href)) { + $(this).attr('target','_blank'); + } + }); + return el; + } + + function setInfoText(title, infoText,target) { + if (title) { + $("

",{class:"red-ui-help-title"}).text(title).appendTo(target); + } + var info = addTargetToExternalLinks($('
'+infoText+'
')).appendTo(target); + info.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "" ); + var foldingHeader = "H3"; + info.find(foldingHeader).wrapInner('') + .find("a").prepend('').on("click", function(e) { + e.preventDefault(); + var isExpanded = $(this).hasClass('expanded'); + var el = $(this).parent().next(); + while(el.length === 1 && el[0].nodeName !== foldingHeader) { + el.toggle(!isExpanded); + el = el.next(); + } + $(this).toggleClass('expanded',!isExpanded); + }) + target.parent().scrollTop(0); + } + + function set(html,title) { + $(helpSection).empty(); + setInfoText(title,html,helpSection); + hideTOC(); + show(); + } + + function refreshSelection(selection) { + if (selection === undefined) { + selection = RED.view.selection(); + } + if (selection.nodes) { + if (selection.nodes.length == 1) { + var node = selection.nodes[0]; + if (node.type === "subflow" && node.direction) { + // ignore subflow virtual ports + } else if (node.type !== 'group'){ + showHelp(node.type); + } + } + } + } + RED.events.on("view:selection-changed",refreshSelection); + + return { + init: init, + show: show, + set: set + } +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js new file mode 100644 index 000000000..ccecfd043 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js @@ -0,0 +1,475 @@ +RED.sidebar.info.outliner = (function() { + + var treeList; + var searchInput; + var projectInfo; + var projectInfoLabel; + var flowList; + var subflowList; + var globalConfigNodes; + + var objects = {}; + var missingParents = {}; + + function getFlowData() { + var flowData = [ + { + label: RED._("menu.label.flows"), + expanded: true, + children: [] + }, + { + label: RED._("menu.label.subflows"), + children: [] + }, + { + id: "__global__", + label: RED._("sidebar.info.globalConfig"), + children: [] + } + ] + flowList = flowData[0]; + subflowList = flowData[1]; + globalConfigNodes = flowData[2]; + + return flowData; + } + + function getProjectLabel(p) { + var div = $('
',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"}); + div.css("width", "calc(100% - 40px)"); + var contentDiv = $('
',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div); + contentDiv.text(p.name); + var controls = $('
',{class:"red-ui-info-outline-item-controls"}).appendTo(div); + var editProjectButton = $('') + .appendTo(controls) + .on("click", function(evt) { + evt.preventDefault(); + RED.projects.editProject(); + }); + RED.popover.tooltip(editProjectButton,RED._('sidebar.project.showProjectSettings')); + return div; + } + + var empties = {}; + function getEmptyItem(id) { + var item = { + empty: true, + element: $('
').text(RED._("sidebar.info.empty")), + } + empties[id] = item; + return item; + } + + function getNodeLabelText(n) { + var label = n.name || n.type+": "+n.id; + if (n._def.label) { + try { + label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||""; + } catch(err) { + console.log("Definition error: "+type+".label",err); + } + } + var newlineIndex = label.indexOf("\\n"); + if (newlineIndex > -1) { + label = label.substring(0,newlineIndex)+"..."; + } + return label; + } + + function getNodeLabel(n) { + 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(" ") + } + + addControls(n, div); + + return div; + } + + function getFlowLabel(n) { + var div = $('
',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"}); + var contentDiv = $('
',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div); + var label = (typeof n === "string")? n : n.label; + var newlineIndex = label.indexOf("\\n"); + if (newlineIndex > -1) { + label = label.substring(0,newlineIndex)+"..."; + } + contentDiv.text(label); + addControls(n, div); + return div; + } + + function getSubflowLabel(n) { + + 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(" ") + } + + addControls(n, div); + + return div; + + + // var div = $('
',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"}); + // var contentDiv = $('
',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div); + // contentDiv.text(n.name || n.id); + // addControls(n, div); + // return div; + } + + function addControls(n,div) { + var controls = $('
',{class:"red-ui-info-outline-item-controls red-ui-info-outline-item-hover-controls"}).appendTo(div); + if (n._def.button) { + $('').appendTo(controls).on("click",function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + RED.view.clickNodeButton(n); + }) + } + // $('').appendTo(controls).on("click",function(evt) { + // evt.preventDefault(); + // evt.stopPropagation(); + // RED.view.reveal(n.id); + // }) + if (n.type !== 'group' && n.type !== 'subflow') { + $('').appendTo(controls).on("click",function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + if (n.type === 'tab') { + if (n.disabled) { + RED.workspaces.enable(n.id) + } else { + RED.workspaces.disable(n.id) + } + } else { + // TODO: this ought to be a utility function in RED.nodes + var historyEvent = { + t: "edit", + node: n, + changed: n.changed, + changes: { + d: n.d + }, + dirty:RED.nodes.dirty() + } + if (n.d) { + delete n.d; + } else { + n.d = true; + } + n.dirty = true; + n.changed = true; + RED.events.emit("nodes:change",n); + RED.nodes.dirty(true) + RED.view.redraw(); + } + }); + } else { + $('
').appendTo(controls) + } + controls.find("button").on("dblclick", function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + }) + } + + function onProjectLoad(activeProject) { + objects = {}; + var newFlowData = getFlowData(); + projectInfoLabel.empty(); + getProjectLabel(activeProject).appendTo(projectInfoLabel); + projectInfo.show(); + treeList.treeList('data',newFlowData); + } + + function build() { + var container = $("
", {class:"red-ui-info-outline"}).css({'height': '100%'}); + var toolbar = $("
", {class:"red-ui-sidebar-header red-ui-info-toolbar"}).appendTo(container); + + searchInput = $('').appendTo(toolbar).searchBox({ + delay: 300, + change: function() { + var val = $(this).val(); + var searchResults = RED.search.search(val); + if (val) { + var resultMap = {}; + for (var i=0,l=searchResults.length;i
').hide().appendTo(container) + projectInfoLabel = $('').appendTo(projectInfo); + + //
Space Monkey
').appendTo(container) + + treeList = $("
").css({width: "100%"}).appendTo(container).treeList({ + data:getFlowData() + }) + treeList.on('treelistselect', function(e,item) { + var node = RED.nodes.node(item.id) || RED.nodes.group(item.id); + if (node) { + if (node.type === 'group' || node._def.category !== "config") { + RED.view.select({nodes:[node]}) + } else { + RED.view.select({nodes:[]}) + } + } + }) + treeList.on('treelistconfirm', function(e,item) { + var node = RED.nodes.node(item.id); + if (node) { + if (node._def.category === "config") { + RED.editor.editConfig("", node.type, node.id); + } else { + RED.editor.edit(node); + } + } + }) + + RED.events.on("projects:load", onProjectLoad) + + RED.events.on("flows:add", onFlowAdd) + RED.events.on("flows:remove", onObjectRemove) + RED.events.on("flows:change", onFlowChange) + RED.events.on("flows:reorder", onFlowsReorder) + + RED.events.on("subflows:add", onSubflowAdd) + RED.events.on("subflows:remove", onObjectRemove) + RED.events.on("subflows:change", onSubflowChange) + + RED.events.on("nodes:add",onNodeAdd); + RED.events.on("nodes:remove",onObjectRemove); + RED.events.on("nodes:change",onNodeChange); + + RED.events.on("groups:add",onNodeAdd); + RED.events.on("groups:remove",onObjectRemove); + RED.events.on("groups:change",onNodeChange); + + RED.events.on("view:selection-changed", onSelectionChanged); + + RED.events.on("workspace:clear", onWorkspaceClear) + + return container; + } + function onWorkspaceClear() { + treeList.treeList('data',getFlowData()); + } + function onFlowAdd(ws) { + objects[ws.id] = { + id: ws.id, + element: getFlowLabel(ws), + children:[], + deferBuild: true, + icon: "red-ui-icons red-ui-icons-flow", + gutter: getGutter(ws) + } + if (missingParents[ws.id]) { + objects[ws.id].children = missingParents[ws.id]; + delete missingParents[ws.id] + } else { + objects[ws.id].children.push(getEmptyItem(ws.id)); + } + flowList.treeList.addChild(objects[ws.id]) + objects[ws.id].element.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled) + objects[ws.id].treeList.container.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled) + + + } + function onFlowChange(n) { + var existingObject = objects[n.id]; + + var label = n.label || n.id; + var newlineIndex = label.indexOf("\\n"); + if (newlineIndex > -1) { + label = label.substring(0,newlineIndex)+"..."; + } + existingObject.element.find(".red-ui-info-outline-item-label").text(label); + existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled) + existingObject.treeList.container.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled) + } + function onFlowsReorder(order) { + var indexMap = {}; + order.forEach(function(id,index) { + indexMap[id] = index; + }) + + flowList.treeList.sortChildren(function(A,B) { + if (A.id === "__global__") { return -1 } + if (B.id === "__global__") { return 1 } + return indexMap[A.id] - indexMap[B.id] + }) + } + function onSubflowAdd(sf) { + objects[sf.id] = { + id: sf.id, + element: getNodeLabel(sf), + children:[], + deferBuild: true, + gutter: getGutter(sf) + } + if (missingParents[sf.id]) { + objects[sf.id].children = missingParents[sf.id]; + delete missingParents[sf.id] + } else { + objects[sf.id].children.push(getEmptyItem(sf.id)); + } + subflowList.treeList.addChild(objects[sf.id]) + } + function onSubflowChange(sf) { + var existingObject = objects[sf.id]; + existingObject.treeList.replaceElement(getNodeLabel(sf)); + // existingObject.element.find(".red-ui-info-outline-item-label").text(n.name || n.id); + RED.nodes.eachNode(function(n) { + if (n.type == "subflow:"+sf.id) { + var sfInstance = objects[n.id]; + sfInstance.treeList.replaceElement(getNodeLabel(n)); + } + }); + } + + function onNodeChange(n) { + var existingObject = objects[n.id]; + var parent = n.g||n.z; + + var nodeLabelText = getNodeLabelText(n); + if (nodeLabelText) { + existingObject.element.find(".red-ui-info-outline-item-label").text(nodeLabelText); + } else { + existingObject.element.find(".red-ui-info-outline-item-label").html(" "); + } + + if (parent !== existingObject.parent.id) { + existingObject.treeList.remove(); + if (!parent) { + globalConfigNodes.treeList.addChild(existingObject); + } else { + if (empties[parent]) { + empties[parent].treeList.remove(); + delete empties[parent]; + } + objects[parent].treeList.addChild(existingObject) + } + } + existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.d) + } + function onObjectRemove(n) { + var existingObject = objects[n.id]; + existingObject.treeList.remove(); + delete objects[n.id] + var parent = existingObject.parent; + if (parent.children.length === 0) { + parent.treeList.addChild(getEmptyItem(parent.id)); + } + if (existingObject.children && (existingObject.children.length > 0)) { + existingObject.children.forEach(function (nc) { + if (!nc.empty) { + var childObject = objects[nc.id]; + nc.parent = parent; + objects[parent.id].treeList.addChild(childObject); + } + }); + } + } + function getGutter(n) { + var span = $("",{class:"red-ui-info-outline-gutter"}); + $('').appendTo(span).on("click",function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + RED.view.reveal(n.id); + }) + return span; + } + function onNodeAdd(n) { + objects[n.id] = { + id: n.id, + element: getNodeLabel(n), + gutter: getGutter(n) + } + if (n.type === "group") { + objects[n.id].children = []; + objects[n.id].deferBuild = true; + if (missingParents[n.id]) { + objects[n.id].children = missingParents[n.id]; + delete missingParents[n.id] + } + } + var parent = n.g||n.z; + if (parent) { + if (objects[parent]) { + if (empties[parent]) { + empties[parent].treeList.remove(); + delete empties[parent]; + } + if (objects[parent].treeList) { + objects[parent].treeList.addChild(objects[n.id]); + } else { + objects[parent].children.push(objects[n.id]) + } + } else { + missingParents[parent] = missingParents[parent]||[]; + missingParents[parent].push(objects[n.id]) + } + } else { + // No parent - add to Global flow list + globalConfigNodes.treeList.addChild(objects[n.id]) + } + objects[n.id].element.toggleClass("red-ui-info-outline-item-disabled", !!n.d) + } + + function onSelectionChanged(selection) { + // treeList.treeList('clearSelection'); + } + + return { + build: build, + search: function(val) { + searchInput.searchBox('value',val) + }, + select: function(node) { + if (node) { + if (Array.isArray(node)) { + treeList.treeList('select', node.map(function(n) { return objects[n.id] }), false) + } else { + treeList.treeList('select', objects[node.id], false) + + } + } else { + treeList.treeList('clearSelection') + } + }, + reveal: function(node) { + treeList.treeList('show', objects[node.id]) + } + } +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index b8e3b150b..418939a72 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -15,28 +15,33 @@ **/ RED.sidebar.info = (function() { - marked.setOptions({ - renderer: new marked.Renderer(), - gfm: true, - tables: true, - breaks: false, - pedantic: false, - sanitize: true, - smartLists: true, - smartypants: false - }); - var content; - var sections; - var propertiesSection; + var panels; var infoSection; - var helpSection; + + var propertiesPanelContent; + var propertiesPanelHeader; + var propertiesPanelHeaderIcon; + var propertiesPanelHeaderLabel; + var propertiesPanelHeaderReveal; + var propertiesPanelHeaderHelp; + + var selectedObject; + + var tipContainer; var tipBox; + // TODO: remove this var expandedSections = { "property": false }; + function resizeStack() { + if (panels) { + var h = $(content).parent().height() - tipContainer.outerHeight(); + panels.resize(h) + } + } function init() { content = document.createElement("div"); @@ -46,31 +51,79 @@ RED.sidebar.info = (function() { var stackContainer = $("
",{class:"red-ui-sidebar-info-stack"}).appendTo(content); - sections = RED.stack.create({ - container: stackContainer - }).hide(); + var outlinerPanel = $("
").css({ + "overflow": "hidden", + "height": "calc(70%)" + }).appendTo(stackContainer); + var propertiesPanel = $("
").css({ + "overflow":"hidden", + "height":"100%", + "display": "flex", + "flex-direction": "column" + }).appendTo(stackContainer); + propertiesPanelHeader = $("
", {class:"red-ui-palette-header red-ui-info-header"}).css({ + "flex":"0 0 auto" + }).appendTo(propertiesPanel); - propertiesSection = sections.add({ - title: RED._("sidebar.info.info"), - collapsible: true + propertiesPanelHeaderIcon = $("").appendTo(propertiesPanelHeader); + propertiesPanelHeaderLabel = $("").appendTo(propertiesPanelHeader); + propertiesPanelHeaderHelp = $('').css({ + position: 'absolute', + top: '12px', + right: '32px' + }).on("click", function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + if (selectedObject) { + RED.sidebar.help.show(selectedObject.type); + } + }).appendTo(propertiesPanelHeader); + RED.popover.tooltip(propertiesPanelHeaderHelp,RED._("sidebar.help.showHelp")); + + + propertiesPanelHeaderReveal = $('').css({ + position: 'absolute', + top: '12px', + right: '8px' + }).on("click", function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + if (selectedObject) { + RED.sidebar.info.outliner.reveal(selectedObject); + RED.view.reveal(selectedObject.id); + } + }).appendTo(propertiesPanelHeader); + RED.popover.tooltip(propertiesPanelHeaderReveal,RED._("sidebar.help.showInOutline")); + + + propertiesPanelContent = $("
").css({ + "flex":"1 1 auto", + "overflow-y":"scroll", + }).appendTo(propertiesPanel); + + + panels = RED.panels.create({container: stackContainer}) + panels.ratio(0.6); + RED.sidebar.info.outliner.build().appendTo(outlinerPanel); + + + RED.sidebar.addTab({ + id: "info", + label: RED._("sidebar.info.label"), + name: RED._("sidebar.info.name"), + iconClass: "fa fa-info", + action:"core:show-info-tab", + content: content, + pinned: true, + enableOnEdit: true }); - propertiesSection.expand(); - infoSection = sections.add({ - title: RED._("sidebar.info.desc"), - collapsible: true - }); - infoSection.expand(); - infoSection.content.css("padding","6px"); + $(window).on("resize", resizeStack); + $(window).on("focus", resizeStack); - helpSection = sections.add({ - title: RED._("sidebar.info.nodeHelp"), - collapsible: true - }); - helpSection.expand(); - helpSection.content.css("padding","6px"); - var tipContainer = $('
').appendTo(content); + // Tip Box + tipContainer = $('
').appendTo(content); tipBox = $('
').appendTo(tipContainer); var tipButtons = $('
').appendTo(tipContainer); @@ -86,17 +139,6 @@ RED.sidebar.info = (function() { RED.actions.invoke("core:toggle-show-tips"); RED.notify(RED._("sidebar.info.showTips")); }); - - RED.sidebar.addTab({ - id: "info", - label: RED._("sidebar.info.label"), - name: RED._("sidebar.info.name"), - iconClass: "fa fa-info", - action:"core:show-info-tab", - content: content, - pinned: true, - enableOnEdit: true - }); if (tips.enabled()) { tips.start(); } else { @@ -124,46 +166,36 @@ RED.sidebar.info = (function() { refreshSelection(); return; } - sections.show(); - $(propertiesSection.content).empty(); - $(infoSection.content).empty(); - $(helpSection.content).empty(); - infoSection.title.text(RED._("sidebar.info.desc")); + $(propertiesPanelContent).empty(); var propRow; - var table = $('
').appendTo(propertiesSection.content); + var table = $('
').appendTo(propertiesPanelContent); var tableBody = $('').appendTo(table); var subflowNode; var subflowUserCount; - var activeProject = RED.projects.getActiveProject(); - if (activeProject) { - propRow = $(''+ RED._("sidebar.project.name") + '').appendTo(tableBody); - $(propRow.children()[1]).text(activeProject.name||""); - $('').appendTo(tableBody); - var editProjectButton = $('') - .appendTo(propRow.children()[1]) - .on("click", function(evt) { - evt.preventDefault(); - RED.projects.editProject(); - }); - RED.popover.tooltip(editProjectButton,RED._('sidebar.project.showProjectSettings')); - } - propertiesSection.container.show(); - infoSection.container.show(); - helpSection.container.show(); if (node === null) { + RED.sidebar.info.outliner.select(null); return; } else if (Array.isArray(node)) { // Multiple things selected // - hide help and info sections + RED.sidebar.info.outliner.select(node); + + propertiesPanelHeaderIcon.empty(); + RED.utils.createNodeIcon({type:"_selection_"}).appendTo(propertiesPanelHeaderIcon); + propertiesPanelHeaderLabel.text("Selection"); + propertiesPanelHeaderReveal.hide(); + propertiesPanelHeaderHelp.hide(); + selectedObject = null; var types = { nodes:0, flows:0, - subflows:0 + subflows:0, + groups: 0 } node.forEach(function(n) { if (n.type === 'tab') { @@ -171,12 +203,13 @@ RED.sidebar.info = (function() { types.nodes += RED.nodes.filterNodes({z:n.id}).length; } else if (n.type === 'subflow') { types.subflows++; + } else if (n.type === 'group') { + types.groups++; } else { types.nodes++; } }); - helpSection.container.hide(); - infoSection.container.hide(); + // infoSection.container.hide(); // - show the count of selected nodes propRow = $(''+RED._("sidebar.info.selection")+"").appendTo(tableBody); @@ -190,14 +223,19 @@ RED.sidebar.info = (function() { if (types.nodes > 0) { $('
').text(RED._("clipboard.node",{count:types.nodes})).appendTo(counts); } + if (types.groups > 0) { + $('
').text(RED._("clipboard.group",{count:types.groups})).appendTo(counts); + } } else { // A single 'thing' selected. + RED.sidebar.info.outliner.select(node); + // Check to see if this is a subflow or subflow instance - var m = /^subflow(:(.+))?$/.exec(node.type); - if (m) { - if (m[2]) { - subflowNode = RED.nodes.subflow(m[2]); + var subflowRegex = /^subflow(:(.+))?$/.exec(node.type); + if (subflowRegex) { + if (subflowRegex[2]) { + subflowNode = RED.nodes.subflow(subflowRegex[2]); } else { subflowNode = node; } @@ -210,33 +248,76 @@ RED.sidebar.info = (function() { } }); } + + propertiesPanelHeaderIcon.empty(); + RED.utils.createNodeIcon(node).appendTo(propertiesPanelHeaderIcon); + var objectLabel = RED.utils.getNodeLabel(node, node.type+": "+node.id) + var newlineIndex = objectLabel.indexOf("\\n"); + if (newlineIndex > -1) { + objectLabel = objectLabel.substring(0,newlineIndex)+"..."; + } + propertiesPanelHeaderLabel.text(objectLabel); + propertiesPanelHeaderReveal.show(); + selectedObject = node; + + propRow = $('').appendTo(tableBody); + var objectType = "node"; + if (node.type === "subflow" || subflowRegex) { + objectType = "subflow"; + } else if (node.type === "tab") { + objectType = "flow"; + }else if (node.type === "group") { + objectType = "group"; + } + $(propRow.children()[0]).text(RED._("sidebar.info."+objectType)) + RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]); + if (node.type === "tab" || node.type === "subflow") { // If nothing is selected, but we're on a flow or subflow tab. - propRow = $(''+RED._("sidebar.info."+(node.type==='tab'?'flow':'subflow'))+'').appendTo(tableBody); - RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]); - propRow = $(''+RED._("sidebar.info.tabName")+"").appendTo(tableBody); - $(propRow.children()[1]).text(node.label||node.name||""); - if (node.type === "tab") { - propRow = $(''+RED._("sidebar.info.status")+'').appendTo(tableBody); - $(propRow.children()[1]).text((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled")) + propertiesPanelHeaderHelp.hide(); + + } else if (node.type === "group") { + propertiesPanelHeaderHelp.hide(); + + propRow = $(' ').appendTo(tableBody); + + var typeCounts = { + nodes:0, + groups: 0 } + var allNodes = RED.group.getNodes(node,true); + allNodes.forEach(function(n) { + if (n.type === "group") { + typeCounts.groups++; + } else { + typeCounts.nodes++ + } + }); + var counts = $('
').appendTo($(propRow.children()[1])); + if (typeCounts.nodes > 0) { + $('
').text(RED._("clipboard.node",{count:typeCounts.nodes})).appendTo(counts); + } + if (typeCounts.groups > 0) { + $('
').text(RED._("clipboard.group",{count:typeCounts.groups})).appendTo(counts); + } + + } else { - // An actual node is selected in the editor - build up its properties table - propRow = $(''+RED._("sidebar.info.node")+"").appendTo(tableBody); - RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]); - if (node.type !== "subflow" && node.type !== "unknown" && node.name) { - propRow = $(''+RED._("common.label.name")+'').appendTo(tableBody); - $('').text(node.name).appendTo(propRow.children()[1]); - } - if (!m) { - propRow = $(''+RED._("sidebar.info.type")+"").appendTo(tableBody); + propertiesPanelHeaderHelp.show(); + + if (!subflowRegex) { + propRow = $(''+RED._("sidebar.info.type")+'').appendTo(tableBody); $(propRow.children()[1]).text((node.type === "unknown")?node._orig.type:node.type); if (node.type === "unknown") { $('').prependTo($(propRow.children()[1])) } } + var count = 0; - if (!m && node.type != "subflow") { + if (!subflowRegex && node.type != "subflow" && node.type != "group") { + + var blankRow = $('').appendTo(tableBody); + var defaults; if (node.type === 'unknown') { defaults = {}; @@ -251,7 +332,6 @@ RED.sidebar.info = (function() { $(propRow.children()[1]).text(RED.nodes.getType(node.type).set.module); count++; } - $('').appendTo(tableBody); if (defaults) { for (var n in defaults) { @@ -259,7 +339,8 @@ RED.sidebar.info = (function() { var val = node[n]; var type = typeof val; count++; - propRow = $(''+n+"").appendTo(tableBody); + propRow = $('').appendTo(tableBody); + $(propRow.children()[0]).text(n); if (defaults[n].type) { var configNode = RED.nodes.node(val); if (!configNode) { @@ -289,49 +370,66 @@ RED.sidebar.info = (function() { } } if (count > 0) { - $(''+RED._("sidebar.info.showMore")+''+RED._("sidebar.info.showLess")+' ').appendTo(tableBody); + $(''+RED._("sidebar.info.showMore")+''+RED._("sidebar.info.showLess")+' ').appendTo(blankRow.children()[0]); } } if (node.type !== 'tab') { - if (m) { + if (subflowRegex) { $(''+RED._("sidebar.info.subflow")+'').appendTo(tableBody); $(''+RED._("common.label.name")+''+RED.utils.sanitize(subflowNode.name)+'').appendTo(tableBody); } } } - if (m) { + if (subflowRegex) { propRow = $(''+RED._("subflow.category")+'').appendTo(tableBody); var category = subflowNode.category||"subflows"; $(propRow.children()[1]).text(RED._("palette.label."+category,{defaultValue:category})) $(''+RED._("sidebar.info.instances")+""+subflowUserCount+'').appendTo(tableBody); } - var helpText = ""; - if (node.type === "tab" || node.type === "subflow") { - $(helpSection.container).hide(); - } else { - $(helpSection.container).show(); - if (subflowNode && node.type !== "subflow") { - // Selected a subflow instance node. - // - The subflow template info goes into help - helpText = (marked(subflowNode.info||"")||(''+RED._("sidebar.info.none")+'')); - } else { - helpText = $("script[data-help-name='"+node.type+"']").html()||(''+RED._("sidebar.info.none")+''); - } - setInfoText(helpText, helpSection.content); - } + // var helpText = ""; + // if (node.type === "tab" || node.type === "subflow") { + // } else { + // if (subflowNode && node.type !== "subflow") { + // // Selected a subflow instance node. + // // - The subflow template info goes into help + // helpText = (RED.utils.renderMarkdown(subflowNode.info||"")||(''+RED._("sidebar.info.none")+'')); + // } else { + // helpText = $("script[data-help-name='"+node.type+"']").html()||(''+RED._("sidebar.info.none")+''); + // } + // setInfoText(helpText, helpSection.content); + // } var infoText = ""; if (node._def && node._def.info) { var info = node._def.info; var textInfo = (typeof info === "function" ? info.call(node) : info); - infoText = infoText + marked(textInfo); + infoText = infoText + RED.utils.renderMarkdown(textInfo); } if (node.info) { - infoText = infoText + marked(node.info || "") + infoText = infoText + RED.utils.renderMarkdown(node.info || "") } - setInfoText(infoText, infoSection.content); + var infoSectionContainer = $("
").css("padding","0 6px 6px").appendTo(propertiesPanelContent) + + // var editInfo = $('').appendTo(infoSectionContainer).on("click", function(evt) { + // //.text(RED._("sidebar.info.editDescription")) + // evt.preventDefault(); + // evt.stopPropagation(); + // if (node.type === 'tab') { + // + // } else if (node.type === 'subflow') { + // + // } else if (node.type === 'group') { + // + // } else if (node._def.category !== 'config') { + // RED.editor.edit(node,"editor-tab-description"); + // } else { + // + // } + // }) + + setInfoText(infoText, infoSectionContainer); $(".red-ui-sidebar-info-stack").scrollTop(0); $(".node-info-property-header").on("click", function(e) { @@ -347,7 +445,7 @@ RED.sidebar.info = (function() { // propRow = $('Actions').appendTo(tableBody); // var actionBar = $(propRow.children()[1]); // - // // var actionBar = $('
',{style:"background: #fefefe; padding: 3px;"}).appendTo(propertiesSection.content); + // // var actionBar = $('
',{style:"background: #fefefe; padding: 3px;"}).appendTo(propertiesPanel); // $('').appendTo(actionBar); // $('').appendTo(actionBar); // $('').appendTo(actionBar); @@ -422,6 +520,7 @@ RED.sidebar.info = (function() { } function startTips() { $(".red-ui-sidebar-info").addClass('show-tips'); + resizeStack(); if (enabled) { if (!startTimeout && !refreshTimeout) { if (tipCount === -1) { @@ -435,6 +534,7 @@ RED.sidebar.info = (function() { } function stopTips() { $(".red-ui-sidebar-info").removeClass('show-tips'); + resizeStack(); clearInterval(refreshTimeout); clearTimeout(startTimeout); refreshTimeout = null; @@ -459,15 +559,8 @@ RED.sidebar.info = (function() { } function set(html,title) { - // tips.stop(); - // sections.show(); - refresh(null); - propertiesSection.container.hide(); - helpSection.container.hide(); - infoSection.container.show(); - infoSection.title.text(title||RED._("sidebar.info.desc")); - setInfoText(html,infoSection.content); - $(".red-ui-sidebar-info-stack").scrollTop(0); + console.warn("Deprecated use of RED.sidebar.info.set - use RED.sidebar.help.set instead") + RED.sidebar.help.set(html,title); } function refreshSelection(selection) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index d35d1f1ba..e8306bd0f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -16,6 +16,28 @@ RED.utils = (function() { + window._marked = window.marked; + window.marked = function(txt) { + console.warn("Use of 'marked()' is deprecated. Use RED.utils.renderMarkdown() instead"); + return renderMarkdown(txt); + } + + _marked.setOptions({ + renderer: new _marked.Renderer(), + gfm: true, + tables: true, + breaks: false, + pedantic: false, + smartLists: true, + smartypants: false + }); + + function renderMarkdown(txt) { + var rendered = _marked(txt); + var cleaned = DOMPurify.sanitize(rendered, {SAFE_FOR_JQUERY: true}) + return cleaned; + } + function formatString(str) { return str.replace(/\r?\n/g,"↵").replace(/\t/g,"→"); } @@ -784,9 +806,9 @@ RED.utils = (function() { function separateIconPath(icon) { var result = {module: "", file: ""}; if (icon) { - var index = icon.indexOf('icons/'); - if (index !== -1) { - icon = icon.substring(index+6); + var index = icon.indexOf(RED.settings.apiRootUrl+'icons/'); + if (index === 0) { + icon = icon.substring((RED.settings.apiRootUrl+'icons/').length); } index = icon.indexOf('/'); if (index !== -1) { @@ -837,10 +859,15 @@ RED.utils = (function() { } function getNodeIcon(def,node) { - if (def.category === 'config') { + if (node && node.type === '_selection_') { + return "font-awesome/fa-object-ungroup"; + } else if (node && node.type === 'group') { + return "font-awesome/fa-object-group" + } else if (def.category === 'config') { return RED.settings.apiRootUrl+"icons/node-red/cog.svg" } else if (node && node.type === 'tab') { - return RED.settings.apiRootUrl+"icons/node-red/subflow.svg" + return "red-ui-icons/red-ui-icons-flow" + // return RED.settings.apiRootUrl+"images/subflow_tab.svg" } else if (node && node.type === 'unknown') { return RED.settings.apiRootUrl+"icons/node-red/alert.svg" } else if (node && node.icon) { @@ -899,6 +926,8 @@ RED.utils = (function() { var l; if (node.type === 'tab') { l = node.label || defaultLabel + } else if (node.type === 'group') { + l = node.name || defaultLabel } else { l = node._def.label; try { @@ -1032,11 +1061,63 @@ RED.utils = (function() { } // If the specified name is not defined in font-awesome, show arrow-in icon. iconUrl = RED.settings.apiRootUrl+"icons/node-red/arrow-in.svg" + } else if (iconPath.module === "red-ui-icons") { + var redIconElement = $('').appendTo(iconContainer); + redIconElement.addClass("red-ui-palette-icon red-ui-icons " + iconPath.file); + return; } var imageIconElement = $('
',{class:"red-ui-palette-icon"}).appendTo(iconContainer); imageIconElement.css("backgroundImage", "url("+iconUrl+")"); } + function createNodeIcon(node) { + var def = node._def; + var nodeDiv = $('
',{class:"red-ui-search-result-node"}) + if (node.type === "_selection_") { + nodeDiv.addClass("red-ui-palette-icon-selection"); + } else if (node.type === "group") { + nodeDiv.addClass("red-ui-palette-icon-group"); + } else if (node.type === 'tab') { + nodeDiv.addClass("red-ui-palette-icon-flow"); + } else { + var colour = RED.utils.getNodeColor(node.type,def); + // if (node.type === 'tab') { + // colour = "#C0DEED"; + // } + nodeDiv.css('backgroundColor',colour); + var borderColor = getDarkerColor(colour); + if (borderColor !== colour) { + nodeDiv.css('border-color',borderColor) + } + } + + var icon_url = RED.utils.getNodeIcon(def,node); + var iconContainer = $('
',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv); + RED.utils.createIconElement(icon_url, iconContainer, true); + return nodeDiv; + } + + function getDarkerColor(c) { + var r,g,b; + if (/^#[a-f0-9]{6}$/i.test(c)) { + r = parseInt(c.substring(1, 3), 16); + g = parseInt(c.substring(3, 5), 16); + b = parseInt(c.substring(5, 7), 16); + } else if (/^#[a-f0-9]{3}$/i.test(c)) { + r = parseInt(c.substring(1, 2)+c.substring(1, 2), 16); + g = parseInt(c.substring(2, 3)+c.substring(2, 3), 16); + b = parseInt(c.substring(3, 4)+c.substring(3, 4), 16); + } else { + return c; + } + var l = 0.3 * r/255 + 0.59 * g/255 + 0.11 * b/255 ; + r = Math.max(0,r-50); + g = Math.max(0,g-50); + b = Math.max(0,b-50); + var s = ((r<<16) + (g<<8) + b).toString(16); + return '#'+'000000'.slice(0, 6-s.length)+s; + } + return { createObjectElement: buildMessageElement, getMessageProperty: getMessageProperty, @@ -1053,6 +1134,9 @@ RED.utils = (function() { decodeObject: decodeObject, parseContextKey: parseContextKey, createIconElement: createIconElement, - sanitize: sanitize + sanitize: sanitize, + renderMarkdown: renderMarkdown, + createNodeIcon: createNodeIcon, + getDarkerColor: getDarkerColor } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 539fb763d..631e7ec1e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -67,9 +67,16 @@ RED.view.tools = (function() { function moveSelection(dx,dy) { if (moving_set === null) { + moving_set = []; var selection = RED.view.selection(); if (selection.nodes) { - moving_set = selection.nodes.map(function(n) { return {n:n}}); + while (selection.nodes.length > 0) { + var n = selection.nodes.shift(); + moving_set.push({n:n}); + if (n.type === "group") { + selection.nodes = selection.nodes.concat(n.nodes); + } + } } } if (moving_set && moving_set.length > 0) { @@ -93,6 +100,9 @@ RED.view.tools = (function() { node.n.x += dx; node.n.y += dy; node.n.dirty = true; + if (node.n.type === "group") { + RED.group.markDirty(node.n); + } minX = Math.min(node.n.x-node.n.w/2-5,minX); minY = Math.min(node.n.y-node.n.h/2-5,minY); } @@ -110,8 +120,69 @@ RED.view.tools = (function() { } } + function setSelectedNodeLabelState(labelShown) { + var selection = RED.view.selection(); + var historyEvents = []; + var nodes = []; + if (selection.nodes) { + selection.nodes.forEach(function(n) { + if (n.type !== 'subflow' && n.type !== 'group') { + nodes.push(n); + } else if (n.type === 'group') { + nodes = nodes.concat( RED.group.getNodes(n,true)); + } + }); + } + nodes.forEach(function(n) { + var modified = false; + var oldValue = n.l === undefined?true:n.l; + var isLink = /^link (in|out)$/.test(n._def.type); + + if (labelShown) { + if (n.l === false || (isLink && !n.hasOwnProperty('l'))) { + n.l = true; + modified = true; + } + } else { + if ((!isLink && (!n.hasOwnProperty('l') || n.l === true)) || (isLink && n.l === true) ) { + n.l = false; + modified = true; + } + } + if (modified) { + historyEvents.push({ + t: "edit", + node: n, + changed: n.changed, + changes: { + l: oldValue + } + }) + n.changed = true; + n.dirty = true; + n.resize = true; + } + }) + + if (historyEvents.length > 0) { + RED.history.push({ + t: "multi", + events: historyEvents, + dirty: RED.nodes.dirty() + }) + RED.nodes.dirty(true); + } + + RED.view.redraw(); + + + } + return { init: function() { + RED.actions.add("core:show-selected-node-labels", function() { setSelectedNodeLabelState(true); }) + RED.actions.add("core:hide-selected-node-labels", function() { setSelectedNodeLabelState(false); }) + RED.actions.add("core:align-selection-to-grid", alignToGrid); RED.actions.add("core:scroll-view-up", function() { RED.view.scroll(0,-RED.view.gridSize());}); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index de9465340..7f541204e 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -21,12 +21,15 @@ * \- .red-ui-workspace-chart-event-layer "eventLayer" * |- .red-ui-workspace-chart-background * |- .red-ui-workspace-chart-grid "gridLayer" + * |- "groupLayer" + * |- "groupSelectLayer" * |- "linkLayer" * |- "dragGroupLayer" - * \- "nodeLayer" + * |- "nodeLayer" */ RED.view = (function() { + var DEBUG_EVENTS = false; var space_width = 5000, space_height = 5000, lineCurveScale = 0.75, @@ -48,34 +51,42 @@ RED.view = (function() { var activeSpliceLink; var spliceActive = false; var spliceTimer; + var groupHoverTimer; var activeSubflow = null; var activeNodes = []; var activeLinks = []; var activeFlowLinks = []; var activeLinkNodes = {}; + var activeGroup = null; + var activeHoverGroup = null; + var activeGroups = []; + var dirtyGroups = {}; - var selected_link = null, - mousedown_link = null, - mousedown_node = null, - mousedown_port_type = null, - mousedown_port_index = 0, - mouseup_node = null, - mouse_offset = [0,0], - mouse_position = null, - mouse_mode = 0, - moving_set = [], - lasso = null, - ghostNode = null, - showStatus = false, - lastClickNode = null, - dblClickPrimed = null, - clickTime = 0, - clickElapsed = 0, - scroll_position = [], - quickAddActive = false, - quickAddLink = null, - showAllLinkPorts = -1; + var selected_link = null; + var mousedown_link = null; + var mousedown_node = null; + var mousedown_group = null; + var mousedown_port_type = null; + var mousedown_port_index = 0; + var mouseup_node = null; + var mouse_offset = [0,0]; + var mouse_position = null; + var mouse_mode = 0; + var mousedown_group_handle = null; + var moving_set = []; + var lasso = null; + var ghostNode = null; + var showStatus = false; + var lastClickNode = null; + var dblClickPrimed = null; + var clickTime = 0; + var clickElapsed = 0; + var scroll_position = []; + var quickAddActive = false; + var quickAddLink = null; + var showAllLinkPorts = -1; + var groupNodeSelectPrimed = false; var selectNodesOptions; @@ -100,7 +111,9 @@ RED.view = (function() { var gridLayer; var linkLayer; var dragGroupLayer; + var groupSelectLayer; var nodeLayer; + var groupLayer; var drag_lines; function init() { @@ -253,9 +266,12 @@ RED.view = (function() { gridLayer = eventLayer.append("g").attr("class","red-ui-workspace-chart-grid"); updateGrid(); + groupLayer = eventLayer.append("g"); + groupSelectLayer = eventLayer.append("g"); linkLayer = eventLayer.append("g"); dragGroupLayer = eventLayer.append("g"); nodeLayer = eventLayer.append("g"); + drag_lines = []; RED.events.on("workspace:change",function(event) { @@ -377,14 +393,36 @@ RED.view = (function() { historyEvent.removedLinks = [spliceLink]; } - RED.history.push(historyEvent); RED.nodes.add(nn); + + var group = $(ui.helper).data("group"); + if (group) { + RED.group.addToGroup(group, nn); + historyEvent = { + t: 'multi', + events: [historyEvent], + + } + historyEvent.events.push({ + t: "addToGroup", + group: group, + nodes: nn + }) + } + + RED.history.push(historyEvent); RED.editor.validateNode(nn); RED.nodes.dirty(true); // auto select dropped node - so info shows (if visible) + exitActiveGroup(); clearSelection(); nn.selected = true; moving_set.push({n:nn}); + if (group) { + selectGroup(group,false); + enterActiveGroup(group); + activeGroup = group; + } updateActiveNodes(); updateSelection(); redraw(); @@ -517,6 +555,53 @@ RED.view = (function() { source:{z:activeWorkspace}, target:{z:activeWorkspace} }); + + activeGroups = RED.nodes.groups(activeWorkspace)||[]; + activeGroups.forEach(function(g) { + if (g.g) { + g._root = g.g; + g._depth = 1; + } else { + g._root = g.id; + g._depth = 0; + } + }); + var changed = false; + do { + changed = false; + activeGroups.forEach(function(g) { + if (g.g) { + var parentGroup = RED.nodes.group(g.g); + if (parentGroup) { + var parentDepth = parentGroup._depth; + if (g._depth !== parentDepth + 1) { + g._depth = parentDepth + 1; + changed = true; + } + if (g._root !== parentGroup._root) { + g._root = parentGroup._root; + changed = true; + } + } + } + }); + } while (changed) + activeGroups.sort(function(a,b) { + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._root.localeCompare(b._root); + } + }); + + var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); + group.sort(function(a,b) { + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._root.localeCompare(b._root); + } + }) } function generateLinkPath(origX,origY, destX, destY, sc) { @@ -671,6 +756,7 @@ RED.view = (function() { } function canvasMouseDown() { +if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); } var point; if (mouse_mode === RED.state.SELECTING_NODE) { d3.event.stopPropagation(); @@ -684,7 +770,7 @@ RED.view = (function() { scroll_position = [chart.scrollLeft(),chart.scrollTop()]; return; } - if (!mousedown_node && !mousedown_link) { + if (!mousedown_node && !mousedown_link && !mousedown_group) { selected_link = null; updateSelection(); } @@ -697,7 +783,13 @@ RED.view = (function() { if (mouse_mode === 0 || mouse_mode === RED.state.QUICK_JOINING) { if (d3.event.metaKey || d3.event.ctrlKey) { d3.event.stopPropagation(); - showQuickAddDialog(d3.mouse(this)); + clearSelection(); + point = d3.mouse(this); + var clickedGroup = getGroupAt(point[0],point[1]); + if (drag_lines.length > 0) { + clickedGroup = clickedGroup || RED.nodes.group(drag_lines[0].node.g) + } + showQuickAddDialog(point, null, clickedGroup); } } if (mouse_mode === 0 && !(d3.event.metaKey || d3.event.ctrlKey)) { @@ -718,7 +810,13 @@ RED.view = (function() { } } - function showQuickAddDialog(point,spliceLink) { + function showQuickAddDialog(point, spliceLink, targetGroup) { + if (targetGroup && !targetGroup.active) { + selectGroup(targetGroup,false); + enterActiveGroup(targetGroup); + RED.view.redraw(); + } + var ox = point[0]; var oy = point[1]; @@ -946,6 +1044,22 @@ RED.view = (function() { } } } + if (targetGroup) { + RED.group.addToGroup(targetGroup, nn); + if (historyEvent.t !== "multi") { + historyEvent = { + t:'multi', + events: [historyEvent] + } + } + historyEvent.events.push({ + t: "addToGroup", + group: targetGroup, + nodes: nn + }) + + } + if (spliceLink) { resetMouseVars(); // TODO: DRY - droppable/nodeMouseDown/canvasMouseUp/showQuickAddDialog @@ -972,6 +1086,10 @@ RED.view = (function() { // auto select dropped node - so info shows (if visible) clearSelection(); nn.selected = true; + if (targetGroup) { + selectGroup(targetGroup,false); + enterActiveGroup(targetGroup); + } moving_set.push({n:nn}); updateActiveNodes(); updateSelection(); @@ -1076,11 +1194,23 @@ RED.view = (function() { return; } - if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && selected_link == null) { + if (mouse_mode != RED.state.QUICK_JOINING && mouse_mode != RED.state.IMPORT_DRAGGING && !mousedown_node && !mousedown_group && selected_link == null) { return; } var mousePos; + // if (mouse_mode === RED.state.GROUP_RESIZE) { + // mousePos = mouse_position; + // var nx = mousePos[0] + mousedown_group.dx; + // var ny = mousePos[1] + mousedown_group.dy; + // switch(mousedown_group.activeHandle) { + // case 0: mousedown_group.pos.x0 = nx; mousedown_group.pos.y0 = ny; break; + // case 1: mousedown_group.pos.x1 = nx; mousedown_group.pos.y0 = ny; break; + // case 2: mousedown_group.pos.x1 = nx; mousedown_group.pos.y1 = ny; break; + // case 3: mousedown_group.pos.x0 = nx; mousedown_group.pos.y1 = ny; break; + // } + // mousedown_group.dirty = true; + // } if (mouse_mode == RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { // update drag line if (drag_lines.length === 0 && mousedown_port_type !== null) { @@ -1182,10 +1312,18 @@ RED.view = (function() { node.n.x = mousePos[0]+node.dx; node.n.y = mousePos[1]+node.dy; node.n.dirty = true; - minX = Math.min(node.n.x-node.n.w/2-5,minX); - minY = Math.min(node.n.y-node.n.h/2-5,minY); - maxX = Math.max(node.n.x+node.n.w/2+5,maxX); - maxY = Math.max(node.n.y+node.n.h/2+5,maxY); + if (node.n.type === "group") { + RED.group.markDirty(node.n); + minX = Math.min(node.n.x-5,minX); + minY = Math.min(node.n.y-5,minY); + maxX = Math.max(node.n.x+node.n.w+5,maxX); + maxY = Math.max(node.n.y+node.n.h+5,maxY); + } else { + minX = Math.min(node.n.x-node.n.w/2-5,minX); + minY = Math.min(node.n.y-node.n.h/2-5,minY); + maxX = Math.max(node.n.x+node.n.w/2+5,maxX); + maxY = Math.max(node.n.y+node.n.h/2+5,maxY); + } } if (minX !== 0 || minY !== 0) { for (i = 0; i 0) { - var gridOffset = [0,0]; - node = moving_set[0]; - gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2); - gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize)); + var i = 0; + + // Prefer to snap nodes to the grid if there is one in the selection + do { + node = moving_set[i++]; + } while(i x && n.x < x2 && n.y > y && n.y < y2); - if (n.selected) { - n.dirty = true; - moving_set.push({n:n}); + activeGroups.forEach(function(g) { + if (!g.selected) { + if (g.x > x && g.x+g.w < x2 && g.y > y && g.y+g.h < y2) { + while (g.g) { + g = RED.nodes.group(g.g); + } + if (!g.selected) { + selectGroup(g,true); + } + } + } + }) + + activeNodes.forEach(function(n) { + if (!n.selected) { + if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { + if (n.g) { + var group = RED.nodes.group(n.g); + while (group.g) { + group = RED.nodes.group(group.g); + } + if (!group.selected) { + selectGroup(group,true); + } + } else { + n.selected = true; + n.dirty = true; + moving_set.push({n:n}); + } } } }); + + + + // var selectionChanged = false; + // do { + // selectionChanged = false; + // selectedGroups.forEach(function(g) { + // if (g.g && g.selected && RED.nodes.group(g.g).selected) { + // g.selected = false; + // selectionChanged = true; + // } + // }) + // } while(selectionChanged); + if (activeSubflow) { activeSubflow.in.forEach(function(n) { n.selected = (n.x > x && n.x < x2 && n.y > y && n.y < y2); @@ -1355,6 +1568,21 @@ RED.view = (function() { } if (mouse_mode == RED.state.MOVING_ACTIVE) { if (moving_set.length > 0) { + var addedToGroup = null; + if (activeHoverGroup) { + for (var j=0;j 0) { historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}; if (activeSpliceLink) { @@ -1386,12 +1615,31 @@ RED.view = (function() { historyEvent.removedLinks = [spliceLink]; updateActiveNodes(); } + if (addedToGroup) { + historyEvent.addToGroup = addedToGroup; + } RED.nodes.dirty(true); RED.history.push(historyEvent); } } } + // if (mouse_mode === RED.state.MOVING && mousedown_node && mousedown_node.g) { + // if (mousedown_node.gSelected) { + // delete mousedown_node.gSelected + // } else { + // if (!d3.event.ctrlKey && !d3.event.metaKey) { + // clearSelection(); + // } + // RED.nodes.group(mousedown_node.g).selected = true; + // mousedown_node.selected = true; + // mousedown_node.dirty = true; + // moving_set.push({n:mousedown_node}); + // } + // } if (mouse_mode == RED.state.MOVING || mouse_mode == RED.state.MOVING_ACTIVE) { + // if (mousedown_node) { + // delete mousedown_node.gSelected; + // } for (i=0;i 0) { - selection.nodes = moving_set.map(function(n) { return n.n;}); - } - if (selected_link != null) { - selection.link = selected_link; - } + selection = getSelection(); activeLinks = RED.nodes.filterLinks({ source:{z:activeWorkspace}, target:{z:activeWorkspace} @@ -1606,6 +1872,8 @@ RED.view = (function() { var node = moving_set[0].n; if (node.type === "subflow") { RED.editor.editSubflow(activeSubflow); + } else if (node.type === "group") { + RED.editor.editGroup(node); } else { RED.editor.edit(node); } @@ -1632,6 +1900,7 @@ RED.view = (function() { dirty: RED.nodes.dirty(), nodes: [], links: [], + groups: [], workspaces: [], subflows: [] } @@ -1651,6 +1920,7 @@ RED.view = (function() { } historyEvent.nodes = historyEvent.nodes.concat(subEvent.nodes); historyEvent.links = historyEvent.links.concat(subEvent.links); + historyEvent.groups = historyEvent.groups.concat(subEvent.groups); } RED.history.push(historyEvent); RED.nodes.dirty(true); @@ -1661,6 +1931,7 @@ RED.view = (function() { var result; var removedNodes = []; var removedLinks = []; + var removedGroups = []; var removedSubflowOutputs = []; var removedSubflowInputs = []; var removedSubflowStatus; @@ -1668,11 +1939,14 @@ RED.view = (function() { var startDirty = RED.nodes.dirty(); var startChanged = false; + var selectedGroups = []; if (moving_set.length > 0) { for (var i=0;i 0) { + var g = selectedGroups.shift(); + if (removedGroups.indexOf(g) === -1) { + removedGroups.push(g); + g.nodes.forEach(function(n) { + if (n.type === "group") { + selectedGroups.push(n); + } + }) + RED.nodes.removeGroup(g); + } + } if (removedSubflowOutputs.length > 0) { result = RED.subflow.removeOutput(removedSubflowOutputs); if (result) { @@ -1716,7 +2009,7 @@ RED.view = (function() { subflowInstances = instances.instances; } moving_set = []; - if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus) { + if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0) { RED.nodes.dirty(true); } } @@ -1769,6 +2062,7 @@ RED.view = (function() { t:"delete", nodes:removedNodes, links:removedLinks, + groups: removedGroups, subflowOutputs:removedSubflowOutputs, subflowInputs:removedSubflowInputs, subflow: { @@ -1801,20 +2095,36 @@ RED.view = (function() { selection.forEach(function(n) { if (n.type === 'tab') { nodes.push(n); + nodes = nodes.concat(RED.nodes.groups(n.id)); nodes = nodes.concat(RED.nodes.filterNodes({z:n.id})); } }); - } else if (moving_set.length > 0) { - nodes = moving_set.map(function(n) { return n.n }); + } else { + selection = RED.view.selection(); + if (selection.nodes) { + selection.nodes.forEach(function(n) { + nodes.push(n); + if (n.type === 'group') { + nodes = nodes.concat(RED.group.getNodes(n,true)); + } + }) + } } if (nodes.length > 0) { var nns = []; + var nodeCount = 0; + var groupCount = 0; for (var n=0;n 0) { + RED.notify(RED._("clipboard.nodeCopied",{count:nodeCount}),{id:"clipboard"}); + } else if (groupCount > 0) { + RED.notify(RED._("clipboard.groupCopied",{count:groupCount}),{id:"clipboard"}); + } } } function calculateTextWidth(str, className, offset) { - return calculateTextDimensions(str,className,offset,0)[0]; + var result=convertLineBreakCharacter(str); + var width = 0; + for (var i=0;i 0 ? 1: 0) : (d.direction == "in" ? 0: 1) var wasJoining = false; if (mouse_mode === RED.state.JOINING || mouse_mode === RED.state.QUICK_JOINING) { @@ -2276,7 +2666,24 @@ RED.view = (function() { } } + function prepareDrag(mouse) { + mouse_mode = RED.state.MOVING; + // Called when moving_set should be prepared to be dragged + for (i=0;i 0 && clickElapsed < 750) { + mouse_mode = RED.state.DEFAULT; + RED.editor.editGroup(g); + d3.event.stopPropagation(); + return; + } + + } + + function groupMouseDown(g) { + var mouse = d3.touches(this.parentNode)[0]||d3.mouse(this.parentNode); + // if (! (mouse[0] < g.x+10 || mouse[0] > g.x+g.w-10 || mouse[1] < g.y+10 || mouse[1] > g.y+g.h-10) ) { + // return + // } + + focusView(); + if (d3.event.button === 1) { + return; + } + if (mouse_mode == RED.state.IMPORT_DRAGGING) { + RED.keyboard.remove("escape"); + } else if (mouse_mode == RED.state.QUICK_JOINING) { + d3.event.stopPropagation(); + return; + } else if (mouse_mode === RED.state.SELECTING_NODE) { + d3.event.stopPropagation(); + return; + } + + mousedown_group = g; + + var now = Date.now(); + clickElapsed = now-clickTime; + clickTime = now; + + dblClickPrimed = ( + lastClickNode == g && + d3.event.button === 0 && + !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey + ); + lastClickNode = g; + + if (g.selected && (d3.event.ctrlKey||d3.event.metaKey)) { + if (g === activeGroup) { + exitActiveGroup(); + } + deselectGroup(g); + d3.event.stopPropagation(); + } else { + if (!g.selected) { + if (!d3.event.ctrlKey && !d3.event.metaKey) { + clearSelection(); + } + if (activeGroup) { + if (!RED.group.contains(activeGroup,g)) { + // Clicked on a group that is outside the activeGroup + exitActiveGroup(); + } else { + } + } + selectGroup(g,true);//!wasSelected); + } else { + exitActiveGroup(); + } + + + if (d3.event.button != 2) { + var d = g.nodes[0]; + prepareDrag(mouse); + mousedown_group.dx = mousedown_group.x - mouse[0]; + mousedown_group.dy = mousedown_group.y - mouse[1]; + } + } + + updateSelection(); + redraw(); + d3.event.stopPropagation(); + } + + function selectGroup(g, includeNodes) { + if (!g.selected) { + g.selected = true; + g.dirty = true; + } + moving_set.push({n:g}); + if (includeNodes) { + var currentSet = new Set(moving_set.map(function(n) { return n.n })); + var allNodes = RED.group.getNodes(g,true); + allNodes.forEach(function(n) { + if (!currentSet.has(n)) { + moving_set.push({n:n}) + // n.selected = true; + } + n.dirty = true; + }) + } + } + function enterActiveGroup(group) { + if (activeGroup) { + exitActiveGroup(); + } + group.active = true; + group.dirty = true; + activeGroup = group; + for (var i = moving_set.length-1; i >= 0; i -= 1) { + if (moving_set[i].n === group) { + moving_set.splice(i,1); + break; + } + } + } + function exitActiveGroup() { + if (activeGroup) { + activeGroup.active = false; + activeGroup.dirty = true; + deselectGroup(activeGroup); + selectGroup(activeGroup,true); + activeGroup = null; + } + } + function deselectGroup(g) { + if (g.selected) { + g.selected = false; + g.dirty = true; + } + var nodeSet = new Set(g.nodes); + nodeSet.add(g); + for (var i = moving_set.length-1; i >= 0; i -= 1) { + if (nodeSet.has(moving_set[i].n) || moving_set[i].n === g) { + moving_set[i].n.selected = false; + moving_set[i].n.dirty = true; + moving_set.splice(i,1); + } + } + } + function getGroupAt(x,y) { + var candidateGroups = {}; + for (var i=0;i= g.x && x <= g.x + g.w && y >= g.y && y <= g.y + g.h) { + candidateGroups[g.id] = g; + } + } + var ids = Object.keys(candidateGroups); + if (ids.length > 1) { + ids.forEach(function(id) { + if (candidateGroups[id] && candidateGroups[id].g) { + delete candidateGroups[candidateGroups[id].g] + } + }) + ids = Object.keys(candidateGroups); + } + if (ids.length === 0) { + return null; + } else { + return candidateGroups[ids[ids.length-1]] + } + } + function isButtonEnabled(d) { var buttonEnabled = true; var ws = RED.nodes.workspace(RED.workspaces.active()); @@ -2424,7 +3066,9 @@ RED.view = (function() { function nodeButtonClicked(d) { if (mouse_mode === RED.state.SELECTING_NODE) { - d3.event.stopPropagation(); + if (d3.event) { + d3.event.stopPropagation(); + } return; } var activeWorkspace = RED.workspaces.active(); @@ -2451,7 +3095,9 @@ RED.view = (function() { RED.notify(RED._("notification.warning", {message:RED._("notification.warnings.nodeActionDisabled")}),"warning"); } } - d3.event.preventDefault(); + if (d3.event) { + d3.event.preventDefault(); + } } function showTouchMenu(obj,pos) { @@ -2523,6 +3169,10 @@ RED.view = (function() { } function redraw() { + requestAnimationFrame(_redraw); + } + + function _redraw() { eventLayer.attr("transform","scale("+scaleFactor+")"); outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor); @@ -2702,23 +3352,26 @@ RED.view = (function() { var nodeEnter = node.enter().insert("svg:g") .attr("class", "red-ui-flow-node red-ui-flow-node-group") - .classed("red-ui-flow-subflow",function(d) { return activeSubflow != null; }); + .classed("red-ui-flow-subflow", activeSubflow != null); nodeEnter.each(function(d,i) { var node = d3.select(this); var isLink = (d.type === "link in" || d.type === "link out") var hideLabel = d.hasOwnProperty('l')?!d.l : isLink; node.attr("id",d.id); - var l = RED.utils.getNodeLabel(d); + var labelWidth = calculateTextWidth(RED.utils.getNodeLabel(d), "red-ui-flow-node-label", 50); if (d.resize || d.w === undefined) { if (hideLabel) { d.w = node_height; } else { - d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) ); + d.w = Math.max(node_width,20*(Math.ceil((labelWidth+(d._def.inputs>0?7:0))/20)) ); } } - d.h = Math.max(node_height,(d.outputs||0) * 15); - + if (hideLabel) { + d.h = Math.max(node_height,(d.outputs || 0) * 15); + } else { + d.h = Math.max(6+24*separateTextByLineBreak.length, (d.outputs || 0) * 15, 30); + } // if (d._def.badge) { // var badge = node.append("svg:g").attr("class","node_badge_group"); // var badgeRect = badge.append("rect").attr("class","node_badge").attr("rx",5).attr("ry",5).attr("width",40).attr("height",15); @@ -2765,10 +3418,10 @@ RED.view = (function() { var mainRect = node.append("rect") .attr("class", "red-ui-flow-node") - .classed("red-ui-flow-node-unknown",function(d) { return d.type == "unknown"; }) + .classed("red-ui-flow-node-unknown", d.type == "unknown") .attr("rx", 5) .attr("ry", 5) - .attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def); /*d._def.color;*/}) + .attr("fill", RED.utils.getNodeColor(d.type,d._def)) .on("mouseup",nodeMouseUp) .on("mousedown",nodeMouseDown) .on("touchstart",function(d) { @@ -2876,17 +3529,17 @@ RED.view = (function() { .attr("x",0).attr("y",0) .attr("class","red-ui-flow-node-icon-shade") .attr("width","30") - .attr("height",function(d){return Math.min(50,d.h-4);}); + .attr("height", Math.min(50,d.h-4)); createIconAttributes(icon_url, icon_group, d); var icon_shade_border = icon_group.append("path") - .attr("d",function(d) { return "M 30 1 l 0 "+(d.h-2)}) + .attr("d","M 30 1 l 0 "+(d.h-2)) .attr("class","red-ui-flow-node-icon-shade-border"); if ("right" == d._def.align) { icon_group.attr("class","red-ui-flow-node-icon-group red-ui-flow-node-icon-group-"+d._def.align); - icon_shade_border.attr("d",function(d) { return "M 0 1 l 0 "+(d.h-2)}) + icon_shade_border.attr("d", "M 0 1 l 0 "+(d.h-2)) //icon.attr("class","red-ui-flow-node-icon red-ui-flow-node-icon-"+d._def.align); //icon.attr("class","red-ui-flow-node-icon-shade red-ui-flow-node-icon-shade-"+d._def.align); //icon.attr("class","red-ui-flow-node-icon-shade-border red-ui-flow-node-icon-shade-border-"+d._def.align); @@ -2904,17 +3557,22 @@ RED.view = (function() { //icon.style("pointer-events","none"); icon_group.style("pointer-events","none"); } - var text = node.append("svg:text") + var labelLineNumber = (separateTextByLineBreak.length == 0)? 1:separateTextByLineBreak.length; + var labelId = d.id.replace(".","-"); + for(var i=0;i0?7:0))/20)) ); + d.w = Math.max(node_width,20*(Math.ceil((labelWidth+(d._def.inputs>0?7:0))/20)) ); } // d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) ); - d.h = Math.max(node_height,(d.outputs||0) * 15); d.x += (d.w-ow)/2; d.resize = false; } + if (hideLabel) { + d.h = Math.max(node_height,(d.outputs || 0) * 15); + } else { + d.h = Math.max(6+24*separateTextByLineBreak.length,(d.outputs || 0) * 15, 30); + } + var thisNode = d3.select(this); - thisNode.classed("red-ui-flow-node-disabled", function(d) { return d.d === true}); - thisNode.classed("red-ui-flow-subflow",function(d) { return activeSubflow != null; }) + thisNode.classed("red-ui-flow-node-disabled", d.d === true); + thisNode.classed("red-ui-flow-subflow", activeSubflow != null) //thisNode.selectAll(".centerDot").attr({"cx":function(d) { return d.w/2;},"cy":function(d){return d.h/2}}); - thisNode.attr("transform", function(d) { return "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"; }); + thisNode.attr("transform", "translate(" + (d.x-d.w/2) + "," + (d.y-d.h/2) + ")"); if (mouse_mode != RED.state.MOVING_ACTIVE) { - thisNode.classed("red-ui-flow-node-selected",function(d) { return d.selected }) + thisNode.classed("red-ui-flow-node-selected", d.selected ) thisNode.selectAll(".red-ui-flow-node") - .attr("width",function(d){return d.w}) - .attr("height",function(d){return d.h}) - .classed("red-ui-flow-node-highlighted",function(d) { return d.highlighted; }) + .attr("width", d.w) + .attr("height", d.h) + .classed("red-ui-flow-node-highlighted",d.highlighted ) ; + var l = ""; + if (d._def.label) { + l = d._def.label; + try { + l = (typeof l === "function" ? l.call(d) : l)||""; + l = RED.text.bidi.enforceTextDirectionWithUCC(l); + } catch(err) { + console.log("Definition error: "+d.type+".label",err); + l = d.type; + } + } + var sa = convertLineBreakCharacter(l); + var sn = sa.length; + var st = ""; + var yp = d.h / 2 - (sn / 2) * 24 + 16 + var labelId = d.id.replace(".","-"); + if(labelLineNumber0?5:0);}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-right").attr("x",function(d){return d.w-30;}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-border-right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)}); @@ -3067,35 +3790,6 @@ RED.view = (function() { port.attr("transform", function(d) { return "translate("+x+","+((y+13*i)-5)+")";}); }); } - thisNode.selectAll("text.red-ui-flow-node-label").text(function(d,i){ - var l = ""; - if (d._def.label) { - l = d._def.label; - try { - l = (typeof l === "function" ? l.call(d) : l)||""; - l = RED.text.bidi.enforceTextDirectionWithUCC(l); - } catch(err) { - console.log("Definition error: "+d.type+".label",err); - l = d.type; - } - } - return l; - }) - .attr("y", function(d){return (d.h/2)-1;}) - .attr("class",function(d){ - var s = ""; - if (d._def.labelStyle) { - s = d._def.labelStyle; - try { - s = (typeof s === "function" ? s.call(d) : s)||""; - } catch(err) { - console.log("Definition error: "+d.type+".labelStyle",err); - s = ""; - } - s = " "+s; - } - return "red-ui-flow-node-label"+(d._def.align?" red-ui-flow-node-label-"+d._def.align:"")+s; - }).classed("hide",hideLabel); if (d._def.icon) { var icon = thisNode.select(".red-ui-flow-node-icon"); var faIcon = thisNode.select(".fa-lg"); @@ -3118,12 +3812,12 @@ RED.view = (function() { } thisNode.selectAll(".red-ui-flow-node-changed") - .attr("transform",function(d){return "translate("+(d.w-10)+", -2)"}) - .classed("hide",function(d) { return !(d.changed||d.moved); }); + .attr("transform", "translate("+(d.w-10)+", -2)") + .classed("hide", !(d.changed||d.moved)); thisNode.selectAll(".red-ui-flow-node-error") - .attr("transform",function(d){ return "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)"}) - .classed("hide",function(d) { return d.valid; }); + .attr("transform", "translate("+(d.w-10-((d.changed||d.moved)?14:0))+", -2)") + .classed("hide", d.valid); thisNode.selectAll(".red-ui-flow-port-input").each(function(d,i) { var port = d3.select(this); @@ -3131,18 +3825,14 @@ RED.view = (function() { }); thisNode.selectAll(".red-ui-flow-node-icon").attr("y",function(d){return (d.h-d3.select(this).attr("height"))/2;}); - thisNode.selectAll(".red-ui-flow-node-icon-shade").attr("height",function(d){return d.h;}); - thisNode.selectAll(".red-ui-flow-node-icon-shade-border").attr("d", function (d) { - return "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2); - }); - thisNode.selectAll(".fa-lg").attr("y",function(d){return (d.h+13)/2;}); + thisNode.selectAll(".red-ui-flow-node-icon-shade").attr("height", d.h ); + thisNode.selectAll(".red-ui-flow-node-icon-shade-border").attr("d", + "M " + (((!d._def.align && d.inputs !== 0 && d.outputs === 0) || "right" === d._def.align) ? 0 : 30) + " 1 l 0 " + (d.h - 2) + ); + thisNode.selectAll(".fa-lg").attr("y",(d.h+13)/2); - thisNode.selectAll(".red-ui-flow-node-button").attr("opacity",function(d) { - return (!isButtonEnabled(d))?0.4:1 - }); - thisNode.selectAll(".red-ui-flow-node-button-button").attr("cursor",function(d) { - return (!isButtonEnabled(d))?"":"pointer"; - }); + thisNode.selectAll(".red-ui-flow-node-button").attr("opacity", function(d2) { return !isButtonEnabled(d2)?0.4:1 }); + thisNode.selectAll(".red-ui-flow-node-button-button").attr("cursor",function(d2) { return isButtonEnabled(d2)?"":"pointer"}); thisNode.selectAll(".red-ui-flow-node-button").attr("transform",function(d){ var x = d._def.align == "right"?d.w-6:-25; if (d._def.button.toggle && !d[d._def.button.toggle]) { @@ -3208,6 +3898,15 @@ RED.view = (function() { } d.dirty = false; + if (d.g) { + if (!dirtyGroups[d.g]) { + var gg = d.g; + while (gg && !dirtyGroups[gg]) { + dirtyGroups[gg] = RED.nodes.group(gg); + gg = dirtyGroups[gg].g; + } + } + } } }); @@ -3238,7 +3937,9 @@ RED.view = (function() { d3.event.stopPropagation(); if (d3.event.metaKey || d3.event.ctrlKey) { l.classed("red-ui-flow-link-splice",true); - showQuickAddDialog(d3.mouse(this), selected_link); + var point = d3.mouse(this); + var clickedGroup = getGroupAt(point[0],point[1]); + showQuickAddDialog(point, selected_link, clickedGroup); } }) .on("touchstart",function(d) { @@ -3423,6 +4124,194 @@ RED.view = (function() { }) + + var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); + group.exit().each(function(d,i) { + document.getElementById("group_select_"+d.id).remove() + }).remove(); + var groupEnter = group.enter().insert("svg:g").attr("class", "red-ui-flow-group") + + var addedGroups = false; + groupEnter.each(function(d,i) { + addedGroups = true; + var g = d3.select(this); + g.attr("id",d.id); + + var groupBorderRadius = 4; + + var selectGroup = groupSelectLayer.append('g').attr("class", "red-ui-flow-group").attr("id","group_select_"+d.id); + selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) + .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius) + .attr("x",-4) + .attr("y",-4) + .style("stroke","rgba(255,255,255,0.8)") + .style("stroke-width",6) + + selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) + .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius) + .attr("x",-4) + .attr("y",-4) + selectGroup.on("mousedown", function() {groupMouseDown.call(g[0][0],d)}); + selectGroup.on("mouseup", function() {groupMouseUp.call(g[0][0],d)}); + + g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5); + + g.append('rect').classed("red-ui-flow-group-body",true) + .attr('rx',groupBorderRadius).attr('ry',groupBorderRadius).style({ + "fill":d.fill||"none", + "stroke": d.stroke||"none", + }) + g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp) + g.append('svg:text').attr("class","red-ui-flow-group-label"); + d.dirty = true; + }); + if (addedGroups) { + group.sort(function(a,b) { + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._root.localeCompare(b._root); + } + }) + } + group[0].reverse(); + + group.each(function(d,i) { + if (d.resize) { + d.minWidth = 0; + delete d.resize; + } + if (d.dirty || dirtyGroups[d.id]) { + var g = d3.select(this); + if (d.nodes.length > 0) { + var minX = Number.POSITIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxX = 0; + var maxY = 0; + d.nodes.forEach(function(n) { + if (n.type !== "group") { + minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); + minY = Math.min(minY,n.y-n.h/2-25); + maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0)); + maxY = Math.max(maxY,n.y+n.h/2+25); + } else { + minX = Math.min(minX,n.x-25) + minY = Math.min(minY,n.y-25) + maxX = Math.max(maxX,n.x+n.w+25) + maxY = Math.max(maxY,n.y+n.h+25) + } + }); + + d.x = minX; + d.y = minY; + d.w = maxX - minX; + d.h = maxY - minY; + } else { + d.w = 40; + d.h = 40; + } + if (!d.minWidth) { + if (d.style.label && d.name) { + d.minWidth = calculateTextWidth(d.name||"","red-ui-flow-group-label",8); + d.labels = separateTextByLineBreak; + } else { + d.minWidth = 40; + } + } + d.w = Math.max(d.minWidth,d.w); + if (d.style.label && d.labels) { + var h = (d.labels.length -1) * 16; + var labelPos = d.style["label-position"] || "nw"; + d.h += h; + if (labelPos[0] === "n") { + d.y -= h; + } + } + + g.attr("transform","translate("+d.x+","+d.y+")") + g.selectAll(".red-ui-flow-group-outline") + .attr("width",d.w) + .attr("height",d.h) + + + var selectGroup = document.getElementById("group_select_"+d.id); + selectGroup.setAttribute("transform","translate("+d.x+","+d.y+")"); + if (d.hovered) { + selectGroup.classList.add("red-ui-flow-group-hovered") + } else { + selectGroup.classList.remove("red-ui-flow-group-hovered") + } + var selectGroupRect = selectGroup.children[0]; + selectGroupRect.setAttribute("width",d.w+8) + selectGroupRect.setAttribute("height",d.h+8) + selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0; + selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; + selectGroupRect = selectGroup.children[1]; + selectGroupRect.setAttribute("width",d.w+8) + selectGroupRect.setAttribute("height",d.h+8) + selectGroupRect.style.strokeOpacity = (d.selected || d.highlighted)?0.8:0; + selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; + + if (d.highlighted) { + selectGroup.classList.add("red-ui-flow-node-highlighted"); + } else { + selectGroup.classList.remove("red-ui-flow-node-highlighted"); + } + + + g.selectAll(".red-ui-flow-group-body") + .attr("width",d.w) + .attr("height",d.h) + .style("stroke", d.style.stroke || "") + .style("stroke-opacity", d.style.hasOwnProperty('stroke-opacity') ? d.style['stroke-opacity'] : "") + .style("fill", d.style.fill || "") + .style("fill-opacity", d.style.hasOwnProperty('fill-opacity') ? d.style['fill-opacity'] : "") + + var label = g.selectAll(".red-ui-flow-group-label"); + label.classed("hide",!!!d.style.label) + if (d.style.label) { + var labelPos = d.style["label-position"] || "nw"; + var labelX = 0; + var labelY = 0; + + if (labelPos[0] === 'n') { + labelY = 0+15; // Allow for font-height + } else { + labelY = d.h - 5 -(d.labels.length -1) * 16; + } + if (labelPos[1] === 'w') { + labelX = 5; + labelAnchor = "start" + } else if (labelPos[1] === 'e') { + labelX = d.w-5; + labelAnchor = "end" + } else { + labelX = d.w/2; + labelAnchor = "middle" + } + label + .style("fill", d.style.hasOwnProperty('color')?d.style.color:"#999") + .attr("transform","translate("+labelX+","+labelY+")") + .attr("text-anchor",labelAnchor); + if (d.labels) { + var ypos = 0; + g.selectAll(".red-ui-flow-group-label-text").remove(); + d.labels.forEach(function (name) { + label.append("tspan") + .classed("red-ui-flow-group-label-text", true) + .text(name) + .attr("x", 0) + .attr("y", ypos); + ypos += 16; + }); + } + } + + delete dirtyGroups[d.id]; + delete d.dirty; + } + }) + } else { // JOINING - unselect any selected links linkLayer.selectAll(".red-ui-flow-link-selected").data( @@ -3436,7 +4325,6 @@ RED.view = (function() { if (d3.event) { d3.event.preventDefault(); } - } function focusView() { @@ -3474,29 +4362,36 @@ RED.view = (function() { if (result) { var new_nodes = result[0]; var new_links = result[1]; - var new_workspaces = result[2]; - var new_subflows = result[3]; - var new_default_workspace = result[4]; + var new_groups = result[2]; + var new_workspaces = result[3]; + var new_subflows = result[4]; + var new_default_workspace = result[5]; if (addNewFlow && new_default_workspace) { RED.workspaces.show(new_default_workspace.id); } var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }).map(function(n) { return {n:n};}); + new_ms = new_ms.concat(new_groups.filter(function(g) { return g.z === RED.workspaces.active()}).map(function(g) { return {n:g}})) var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); - // TODO: pick a more sensible root node if (new_ms.length > 0) { - var root_node = new_ms[0].n; - var dx = root_node.x; - var dy = root_node.y; + if (mouse_position == null) { mouse_position = [0,0]; } + var dx = mouse_position[0]; + var dy = mouse_position[1]; + if (new_ms.length > 0) { + var root_node = new_ms[0].n; + dx = root_node.x; + dy = root_node.y; + } + var minX = 0; var minY = 0; var i; - var node; + var node,group; for (i=0;i 0 && - node.n._def.outputs > 0; + ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && + ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) + + } } RED.keyboard.add("*","escape",function(){ @@ -3549,6 +4454,7 @@ RED.view = (function() { t:"add", nodes:new_node_ids, links:new_links, + groups:new_groups, workspaces:new_workspaces, subflows:new_subflows, dirty:RED.nodes.dirty() @@ -3581,12 +4487,16 @@ RED.view = (function() { newConfigNodeCount++; } }) + var newGroupCount = new_groups.length; if (new_workspaces.length > 0) { counts.push(RED._("clipboard.flow",{count:new_workspaces.length})); } if (newNodeCount > 0) { counts.push(RED._("clipboard.node",{count:newNodeCount})); } + if (newGroupCount > 0) { + counts.push(RED._("clipboard.group",{count:newGroupCount})); + } if (newConfigNodeCount > 0) { counts.push(RED._("clipboard.configNode",{count:newNodeCount})); } @@ -3638,22 +4548,25 @@ RED.view = (function() { var historyEvents = []; for (var i=0;i 0) { @@ -3668,7 +4581,40 @@ RED.view = (function() { RED.view.redraw(); } + function getSelection() { + var selection = {}; + var allNodes = new Set(); + + if (moving_set.length > 0) { + moving_set.forEach(function(n) { + if (n.n.type !== 'group') { + allNodes.add(n.n); + } + }); + } + var selectedGroups = activeGroups.filter(function(g) { return g.selected && !g.active }); + if (selectedGroups.length > 0) { + if (selectedGroups.length === 1 && selectedGroups[0].active) { + // Let nodes be nodes + } else { + selectedGroups.forEach(function(g) { + var groupNodes = RED.group.getNodes(g,true); + groupNodes.forEach(function(n) { + allNodes.delete(n); + }); + allNodes.add(g); + }); + } + } + if (allNodes.size > 0) { + selection.nodes = Array.from(allNodes); + } + if (selected_link != null) { + selection.link = selected_link; + } + return selection; + } return { init: init, state:function(state) { @@ -3679,12 +4625,17 @@ RED.view = (function() { } }, - redraw: function(updateActive) { + updateActive: updateActiveNodes, + redraw: function(updateActive, syncRedraw) { if (updateActive) { updateActiveNodes(); updateSelection(); } - redraw(); + if (syncRedraw) { + _redraw(); + } else { + redraw(); + } }, focus: focusView, importNodes: importNodes, @@ -3699,21 +4650,31 @@ RED.view = (function() { selectedNode.dirty = true; moving_set = [{n:selectedNode}]; } + } else if (selection) { + if (selection.nodes) { + updateActiveNodes(); + moving_set = []; + // TODO: this selection group span groups + // - if all in one group -> activate the group + // - if in multiple groups (or group/no-group) + // -> select the first 'set' of things in the same group/no-group + selection.nodes.forEach(function(n) { + if (n.type !== "group") { + n.selected = true; + n.dirty = true; + moving_set.push({n:n}); + } else { + selectGroup(n,true); + } + }) + } } } updateSelection(); - redraw(); - }, - selection: function() { - var selection = {}; - if (moving_set.length > 0) { - selection.nodes = moving_set.map(function(n) { return n.n;}); - } - if (selected_link != null) { - selection.link = selected_link; - } - return selection; + redraw(true); }, + selection: getSelection, + scale: function() { return scaleFactor; }, @@ -3728,47 +4689,57 @@ RED.view = (function() { } return result; }, - reveal: function(id) { + getGroupAtPoint: getGroupAt, + getActiveGroup: function() { return activeGroup }, + reveal: function(id,triggerHighlight) { if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) { RED.workspaces.show(id); } else { - var node = RED.nodes.node(id); - if (node._def.category !== 'config' && node.z) { - node.highlighted = true; - node.dirty = true; - RED.workspaces.show(node.z); + var node = RED.nodes.node(id) || RED.nodes.group(id); + if (node) { + if (node.z && (node.type === "group" || node._def.category !== 'config')) { + node.dirty = true; + RED.workspaces.show(node.z); - var screenSize = [chart.width()/scaleFactor,chart.height()/scaleFactor]; - var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor]; - - if (node.x < scrollPos[0] || node.y < scrollPos[1] || node.x > screenSize[0]+scrollPos[0] || node.y > screenSize[1]+scrollPos[1]) { - var deltaX = '-='+(((scrollPos[0] - node.x) + screenSize[0]/2)*scaleFactor); - var deltaY = '-='+(((scrollPos[1] - node.y) + screenSize[1]/2)*scaleFactor); - chart.animate({ - scrollLeft: deltaX, - scrollTop: deltaY - },200); - } - - if (!node._flashing) { - node._flashing = true; - var flash = 22; - var flashFunc = function() { - flash--; - node.dirty = true; - if (flash >= 0) { - node.highlighted = !node.highlighted; - setTimeout(flashFunc,100); - } else { - node.highlighted = false; - delete node._flashing; - } - RED.view.redraw(); + var screenSize = [chart.width()/scaleFactor,chart.height()/scaleFactor]; + var scrollPos = [chart.scrollLeft()/scaleFactor,chart.scrollTop()/scaleFactor]; + var cx = node.x; + var cy = node.y; + if (node.type === "group") { + cx += node.w/2; + cy += node.h/2; } - flashFunc(); + if (cx < scrollPos[0] || cy < scrollPos[1] || cx > screenSize[0]+scrollPos[0] || cy > screenSize[1]+scrollPos[1]) { + var deltaX = '-='+(((scrollPos[0] - cx) + screenSize[0]/2)*scaleFactor); + var deltaY = '-='+(((scrollPos[1] - cy) + screenSize[1]/2)*scaleFactor); + chart.animate({ + scrollLeft: deltaX, + scrollTop: deltaY + },200); + } + if (triggerHighlight !== false) { + node.highlighted = true; + if (!node._flashing) { + node._flashing = true; + var flash = 22; + var flashFunc = function() { + flash--; + node.dirty = true; + if (flash >= 0) { + node.highlighted = !node.highlighted; + setTimeout(flashFunc,100); + } else { + node.highlighted = false; + delete node._flashing; + } + RED.view.redraw(); + } + flashFunc(); + } + } + } else if (node._def.category === 'config') { + RED.sidebar.config.show(id); } - } else if (node._def.category === 'config') { - RED.sidebar.config.show(id); } } }, @@ -3793,7 +4764,6 @@ RED.view = (function() { mouse_mode = RED.state.SELECTING_NODE; clearSelection(); if (options.selected) { - console.log(options.selected); options.selected.forEach(function(id) { var n = RED.nodes.node(id); if (n) { @@ -3850,6 +4820,11 @@ RED.view = (function() { scroll: function(x,y) { chart.scrollLeft(chart.scrollLeft()+x); chart.scrollTop(chart.scrollTop()+y) + }, + clickNodeButton: function(n) { + if (n._def.button) { + nodeButtonClicked(n); + } } }; })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index 1f361d0f7..290083ea6 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js @@ -128,10 +128,6 @@ RED.workspaces = (function() { RED.history.push(historyEvent); RED.nodes.dirty(true); RED.sidebar.config.refresh(); - var selection = RED.view.selection(); - if (!selection.nodes && !selection.links) { - RED.sidebar.info.refresh(workspace); - } if (changes.hasOwnProperty('disabled')) { RED.nodes.eachNode(function(n) { if (n.z === workspace.id) { @@ -140,6 +136,7 @@ RED.workspaces = (function() { }); RED.view.redraw(); } + RED.events.emit("flows:change",workspace); } RED.tray.close(); } @@ -219,7 +216,10 @@ RED.workspaces = (function() { if (RED.view.state() != RED.state.IMPORT_DRAGGING) { RED.view.state(RED.state.DEFAULT); } - RED.sidebar.info.refresh(workspace); + var selection = RED.view.selection(); + if (!selection.nodes && !selection.links && workspace.id === activeWorkspace) { + RED.sidebar.info.refresh(workspace); + } tabflowEditor.destroy(); } } @@ -371,7 +371,9 @@ RED.workspaces = (function() { var changes = { disabled: workspace.disabled }; workspace.disabled = disabled; $("#red-ui-tab-"+(workspace.id.replace(".","-"))).toggleClass('red-ui-workspace-disabled',!!workspace.disabled); - $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled); + if (id === activeWorkspace) { + $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled",!!workspace.disabled); + } var historyEvent = { t: "edit", changes:changes, @@ -380,10 +382,11 @@ RED.workspaces = (function() { } workspace.changed = true; RED.history.push(historyEvent); + RED.events.emit("flows:change",workspace); RED.nodes.dirty(true); RED.sidebar.config.refresh(); var selection = RED.view.selection(); - if (!selection.nodes && !selection.links) { + if (!selection.nodes && !selection.links && workspace.id === activeWorkspace) { RED.sidebar.info.refresh(workspace); } if (changes.hasOwnProperty('disabled')) { @@ -412,9 +415,14 @@ RED.workspaces = (function() { } function setWorkspaceOrder(order) { - RED.nodes.setWorkspaceOrder(order.filter(function(id) { + var newOrder = order.filter(function(id) { return RED.nodes.workspace(id) !== undefined; - })); + }) + var currentOrder = RED.nodes.getWorkspaceOrder(); + if (JSON.stringify(newOrder) !== JSON.stringify(currentOrder)) { + RED.nodes.setWorkspaceOrder(newOrder); + RED.events.emit("flows:reorder",newOrder); + } workspace_tabs.order(order); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss index fb6eaf8eb..adcaa3458 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss @@ -9,19 +9,15 @@ color: transparent !important; } } - - .ace_gutter { + background: $text-editor-gutter-background; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } .ace_scroller { + background: $text-editor-background; border-top-right-radius: 4px; border-bottom-right-radius: 4px; - } - - .ace_scroller { - background: $text-editor-background; color: $text-editor-color; } .ace_marker-layer .ace_active-line { @@ -37,9 +33,6 @@ .ace_gutter-active-line { background: $text-editor-gutter-active-line-background; } - .ace_gutter { - background: $text-editor-gutter-background; - } .ace_tooltip { font-family: $primary-font; line-height: 1.4em; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss index 8411343c5..96be7b5ab 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss @@ -23,7 +23,7 @@ $primary-background: #f3f3f3;//#0ff; $secondary-background: #fff;//#ff0; $secondary-background-selected: #efefef;//#e9e900; $secondary-background-inactive: #f0f0f0;//#f0f000; -$secondary-background-hover: #ddd;//#dd0; +$secondary-background-hover: #e6e6e6;//#dd0; $secondary-background-disabled: #f9f9f9;//#fafa0; $tertiary-background: #f7f7f7;//#f0f; @@ -94,7 +94,7 @@ $list-item-secondary-color: $secondary-text-color; $list-item-background: $secondary-background; $list-item-background-disabled: $secondary-background-inactive; $list-item-background-hover: $secondary-background-hover; -$list-item-background-selected: $secondary-background-selected; +$list-item-background-selected: #ffebc7; // #fff1e5; $list-item-border-selected: $secondary-text-color-selected; $tab-text-color-active: $header-text-color; @@ -284,3 +284,8 @@ $debug-message-border: #eee; $debug-message-border-hover: #999; $debug-message-border-warning: #ffdf9d; $debug-message-border-error: #f99; + +$group-default-fill: none; +$group-default-fill-opacity: 1; +$group-default-stroke: #999; +$group-default-stroke-opacity: 1; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss index c92c43320..1e77e46e1 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss @@ -217,6 +217,10 @@ .red-ui-debug-msg-type-number { color: $debug-message-text-color-msg-type-number; }; .red-ui-debug-msg-type-number-toggle { cursor: pointer;} +.red-ui-debug-msg-type-string { + white-space: pre-wrap; +} + .red-ui-debug-msg-row { display: block; padding: 4px 2px 2px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss index b13941f22..d8ef3e89c 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss @@ -304,9 +304,6 @@ button.red-ui-button-small &:first-child { padding: 20px 20px 0; } - &:last-child { - padding-bottom: 20px; - } } } .red-ui-editor-type-expression-tab-content { @@ -411,6 +408,133 @@ button.red-ui-button.red-ui-editor-node-appearance-button { } } +.red-ui-group-layout-picker { + padding: 5px; + background: $primary-background; +} +.red-ui-group-layout-picker-cell-text { + position: absolute; + width: 14px; + height: 2px; + border-top: 2px solid $secondary-text-color; + border-bottom: 2px solid $secondary-text-color; + margin: 2px; + + &.red-ui-group-layout-text-pos-nw { top: 0; left: 0; } + &.red-ui-group-layout-text-pos-n { top: 0; left: calc(50% - 9px); } + &.red-ui-group-layout-text-pos-ne { top: 0; right: 0; } + &.red-ui-group-layout-text-pos-sw { bottom: 0; left: 0; } + &.red-ui-group-layout-text-pos-s { bottom: 0; left: calc(50% - 9px); } + &.red-ui-group-layout-text-pos-se { bottom: 0; right: 0; } + &.red-ui-group-layout-text-pos- { + width: 100%; + height: 100%; + border-radius: 5px; + margin: 0; + background-color: #FFF; + background-size: 100% 100%; + background-position: 0 0, 50% 50%; + background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent); + border: none; + } +} + +.red-ui-group-layout-picker button.red-ui-search-result-node { + float: none; + position: relative; + padding: 0; + margin: 5px; + width: 32px; + height: 27px; +} + +button.red-ui-group-layout-picker-none { + width: 100%; +} + +.red-ui-color-picker { + input[type="text"] { + border-radius:0; + width: 100%; + margin-bottom: 0; + border: none; + border-bottom: 1px solid $form-input-border-color; + } + small { + color: $secondary-text-color; + margin-left: 5px; + margin-right: 4px; + display: inline-block; + min-width: 35px; + text-align: right; + } + background: $primary-background; +} +.red-ui-editor-node-appearance-button { + .red-ui-search-result-node { + overflow: hidden + } +} +.red-ui-color-picker-cell { + padding: 0; + border-style: solid; + border-width: 1px; + border-color: $secondary-border-color; +} +.red-ui-color-picker-swatch { + position: absolute; + top:-1px;right:-1px;left:-1px;bottom:-1px; + border-radius: 4px; +} + +.red-ui-color-picker-cell-none { + height: 100%; + background-color: #FFF; + background-size: 100% 100%; + background-position: 0 0, 50% 50%; + background-image: linear-gradient(45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent),linear-gradient(-45deg, transparent 45%, $secondary-border-color 45%, $secondary-border-color 55%, transparent 55%, transparent) +} +.red-ui-search-result-node .red-ui-color-picker-cell-none { + border-radius: 4px; + background-size: 50% 50%; + background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee); +} + +.red-ui-color-picker-opacity-slider { + position:relative; + vertical-align: middle; + display: inline-block; + width: calc(100% - 50px); + height: 14px; + margin: 6px 3px 8px; + box-sizing: border-box; + background-color: white; + background-image: + linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 25%), + linear-gradient(-45deg, #eee 25%, transparent 25%, transparent 75%, #eee 25%); + background-size: 6px 6px; +} +.red-ui-color-picker-opacity-slider-overlay { + position: absolute; + top:0;right:0;left:0;bottom:0; + background-image:linear-gradient(90deg, transparent 0%, #f00 100%); + background-size: 100% 100%; + border: 1px solid $primary-border-color; +} + +div.red-ui-button-small.red-ui-color-picker-opacity-slider-handle { + z-Index: 10; + top: -4px; + cursor: pointer; + min-width: 0; + width: 10px; + height: 22px; + padding: 0; + border: 1px solid $primary-border-color; + border-radius: 1px; + background: $secondary-background; + box-sizing: border-box; +} .red-ui-icon-picker { select { box-sizing: border-box; @@ -633,7 +757,7 @@ button.red-ui-toggleButton.toggle { .red-ui-typedInput-value-label,.red-ui-typedInput-option-label { select,.placeholder-input { margin: 3px; - height: 26px; + height: 24px; width: calc(100% - 10px); padding-left: 3px; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss index d92d1e964..60de209cc 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss @@ -71,6 +71,48 @@ } } +.red-ui-flow-group { + &.red-ui-flow-group-hovered { + .red-ui-flow-group-outline-select { + stroke-opacity: 0.8 !important; + stroke-dasharray: 10 4 !important; + } + } + &.red-ui-flow-group-active-hovered:not(.red-ui-flow-group-hovered) { + .red-ui-flow-group-outline-select { + stroke: $link-link-color; + } + } +} + +.red-ui-flow-group-outline { + fill: none; + stroke: $node-selected-color; + stroke-opacity: 0; + stroke-width: 12; + pointer-events: stroke; +} +.red-ui-flow-group-outline-select { + fill: none; + stroke: $node-selected-color; + pointer-events: stroke; + stroke-opacity: 0; + stroke-width: 3; +} +.red-ui-flow-group-body { + pointer-events: none; + fill: $group-default-fill; + fill-opacity: $group-default-fill-opacity; + stroke-width: 2; + stroke: $group-default-stroke; + stroke-opacity: $group-default-stroke-opacity; +} +.red-ui-flow-group-label { + @include disable-selection; +} + + + .red-ui-flow-node-unknown { stroke-dasharray:10,4; stroke: $node-border-unknown; @@ -166,6 +208,9 @@ g.red-ui-flow-node-selected { fill-opacity: 1; stroke-dasharray: none; } + .red-ui-flow-group, .red-ui-flow-group-body { + stroke-dasharray: 8, 3; + } } .red-ui-flow-node-disabled { &.red-ui-flow-node, .red-ui-flow-node { @@ -248,6 +293,7 @@ g.red-ui-flow-node-selected { .red-ui-flow-link-outline { stroke: $view-background; + stroke-opacity: 0.4; stroke-width: 5; cursor: crosshair; fill: none; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/library.scss b/packages/node_modules/@node-red/editor-client/src/sass/library.scss index 60014b2e6..5cff53f44 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/library.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/library.scss @@ -37,7 +37,7 @@ border-radius: 4px; font-family: $monospace-font !important; font-size: 13px !important; - height: 300px; + height: 100%; line-height: 1.3em; padding: 6px 10px; background: $clipboard-textarea-background; @@ -62,6 +62,7 @@ background: $form-input-background; &>div { height: 100%; + box-sizing: border-box; } } .red-ui-clipboard-dialog-box { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss index ea4b06c52..922a31e33 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss @@ -186,6 +186,21 @@ background-size: contain; background-repeat: no-repeat; } +.red-ui-search-result-node { + &.red-ui-palette-icon-flow, + &.red-ui-palette-icon-group, + &.red-ui-palette-icon-selection { + background: none; + border-color: transparent; + .red-ui-palette-icon-container { + background: none; + } + .red-ui-palette-icon-fa { + color: $secondary-text-color; + font-size: 18px; + } + } +} .red-ui-palette-icon-fa { color: white; position: absolute; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/panels.scss b/packages/node_modules/@node-red/editor-client/src/sass/panels.scss index 9f99db5d4..455aab891 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/panels.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/panels.scss @@ -22,9 +22,19 @@ // border: 1px solid red; box-sizing: border-box; } + display: flex; + flex-direction: column; + + >.red-ui-panel:first-child { + flex: 0 0 auto; + } + >.red-ui-panel:last-child { + flex: 1 1 auto; + } } .red-ui-panels-separator { + flex: 0 0 auto; border-top: 1px solid $secondary-border-color; border-bottom: 1px solid $secondary-border-color; height: 7px; @@ -37,10 +47,13 @@ .red-ui-panel { overflow: auto; height: calc(50% - 4px); + position: relative; } .red-ui-panels.red-ui-panels-horizontal { height: 100%; + flex-direction: row; + &>.red-ui-panel { vertical-align: top; display: inline-block; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/popover.scss b/packages/node_modules/@node-red/editor-client/src/sass/popover.scss index 95097a30e..872f32024 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/popover.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/popover.scss @@ -150,6 +150,16 @@ .red-ui-popover a.red-ui-button, .red-ui-popover button.red-ui-button { + &:not(.primary) { + border-color: $popover-button-border-color; + background: $popover-background; + color: $popover-color !important; + } + &:not(.primary):not(.disabled):not(.ui-button-disabled):hover { + border-color: $popover-button-border-color-hover; + } + + &.primary { border-color: $popover-button-border-color; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss index 9b1005f22..e08551297 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss @@ -18,7 +18,12 @@ .red-ui-editableList-container { padding: 0px; } - + padding: 0; + .red-ui-projects-dialog-box { + box-sizing: border-box; + overflow-y: auto; + padding: 25px 25px 10px 25px; + } } #red-ui-project-settings-tab-settings { overflow-y: scroll; @@ -99,6 +104,7 @@ .red-ui-projects-dialog-screen-create { min-height: 500px; button.red-ui-projects-dialog-screen-create-type { + position: relative; height: auto; padding: 10px; } @@ -169,9 +175,14 @@ .red-ui-projects-dialog-project-list-container { border: 1px solid $secondary-border-color; border-radius: 2px; + display: flex; + flex-direction: column; + .red-ui-search-container { + flex-grow: 0; + } } .red-ui-projects-dialog-project-list-inner-container { - height: 300px; + flex-grow: 1 ; overflow-y: scroll; position:relative; .red-ui-editableList-border { @@ -254,6 +265,9 @@ } } } +.red-ui-projects-dialog-screen-create-type { + position: relative; +} .red-ui-projects-dialog-screen-create-type.red-ui-button.toggle.selected:not(.disabled):not(:disabled) { color: $secondary-text-color-active !important; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/search.scss b/packages/node_modules/@node-red/editor-client/src/sass/search.scss index 0ec8b6525..27ebb1a04 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/search.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/search.scss @@ -24,6 +24,13 @@ top: 0px; border: 1px solid $primary-border-color; box-shadow: 0 0 10px $shadow; + background: $secondary-background; + + .red-ui-searchBox-container { + display: inline-block; + margin-right: 6px; + width: calc(100% - 30px); + } } .red-ui-type-search { @@ -87,6 +94,8 @@ } .red-ui-palette-icon { width: 15px; + position:relative; + left: -1px; } .red-ui-search-result-description { margin-left:28px; @@ -153,7 +162,7 @@ width: 30px; float:left; height: 25px; - border-radius: 5px; + border-radius: 3px; border: 1px solid $node-border; background-position: 5% 50%; background-repeat: no-repeat; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/style.scss b/packages/node_modules/@node-red/editor-client/src/sass/style.scss index 088e5c1b8..ca572ea46 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/style.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/style.scss @@ -42,6 +42,7 @@ @import "tab-config"; @import "tab-context"; @import "tab-info"; +@import "tab-help"; @import "popover"; @import "flow"; @import "palette-editor"; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss new file mode 100644 index 000000000..fe4f9fb84 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-help.scss @@ -0,0 +1,27 @@ +.red-ui-sidebar-help-stack { + // height: calc(100% - 39px); +} +.red-ui-help-search { + border-bottom: 1px solid $secondary-border-color; +} + +.red-ui-sidebar-help-toc { + .red-ui-treeList-label { + font-size: 13px; + padding: 2px 0; + overflow: hidden; + white-space: nowrap; + } + +} +#red-ui-sidebar-help-show-toc { + i.fa-angle-right { + transition: all 0.2s ease-in-out; + } + &.selected { + i.fa-angle-right { + transform: rotate(90deg); + } + } + +} diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss index bc72f7532..c98bdb6ad 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss @@ -14,9 +14,25 @@ * limitations under the License. **/ +.red-ui-sidebar-info { + height: 100%; +} .red-ui-sidebar-info hr { margin: 10px 0; } +.red-ui-info-header { + padding-left: 9px; + line-height: 21px; + cursor: default; + > * { + vertical-align: middle + } + > span { + display: inline-block; + margin-left: 5px; + } + border-bottom: 1px solid $secondary-border-color; +} table.red-ui-info-table { font-size: 14px; margin: 0 0 10px; @@ -125,6 +141,9 @@ div.red-ui-info-table { font-size: 1.296em; line-height: 1.3em; margin: 8px auto; + &.red-ui-help-title { + border-bottom: 1px solid $tertiary-border-color; + } } h2 { font-weight: 500; @@ -214,12 +233,13 @@ div.red-ui-info-table { } .red-ui-sidebar-info-stack { - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - overflow-y: scroll; + height: 100%; + // position: absolute; + // top: 0; + // bottom: 0; + // left: 0; + // right: 0; + // overflow-y: scroll; } .red-ui-help-tips { display: none; @@ -227,20 +247,23 @@ div.red-ui-info-table { left:0; right:0; bottom: 0; - height: 150px; + height: 0; + transition: height 0.2s, padding 0.2s; box-sizing: border-box; border-top: 1px solid $secondary-border-color; background-color: $secondary-background; - padding: 20px; + padding: 0; box-shadow: 0 5px 20px 0px $shadow; overflow-y: auto; } .red-ui-sidebar-info.show-tips { .red-ui-sidebar-info-stack { - bottom: 150px; + height: calc(100% - 150px); } .red-ui-help-tips { display: block; + height: 150px; + padding: 20px; } } @@ -279,3 +302,220 @@ div.red-ui-info-table { border-radius: 4px; padding: 2px 4px 2px; } + +.red-ui-info-outline,.red-ui-sidebar-help-toc { + display: flex; + flex-direction: column; + + .red-ui-treeList { + flex-grow: 1; + position: relative; + } + .red-ui-treeList-container { + position: absolute; + top: 0; + bottom: 0; + } + + .red-ui-treeList-container,.red-ui-editableList-border { + border: none; + border-radius: 0; + } + .red-ui-treeList-label { + font-size: 13px; + padding: 2px 0; + overflow: hidden; + } + .red-ui-info-outline-project { + border-bottom: 1px solid $secondary-border-color; + } + + .red-ui-info-outline-item { + display: inline-block; + padding: 0; + font-size: 13px; + border: none; + .red-ui-palette-icon-fa { + position: relative; + top: 1px; + left: 0px; + } + &:hover { + background: inherit + } + + &.red-ui-info-outline-item-flow { + .red-ui-search-result-description { + margin-left: 4px; + } + } + &.red-ui-info-outline-item-group .red-ui-search-result-node { + background: none; + border-color: transparent; + .red-ui-palette-icon-container { + background: none; + } + .red-ui-palette-icon-fa { + color: $secondary-text-color; + font-size: 18px; + } + } + &.red-ui-info-outline-item-empty { + font-style: italic; + color: $form-placeholder-color; + } + } + + .red-ui-search-result-node { + width: 24px; + height: 20px; + margin-top: 1px; + } + + .red-ui-palette-icon-container { + width: 24px; + } + .red-ui-palette-icon { + width: 20px; + } + .red-ui-search-result-description { + margin-left: 32px; + line-height: 22px; + white-space: nowrap; + } + .red-ui-search-result-node-label { + color: $secondary-text-color; + } +} +.red-ui-info-outline-item-control-spacer { + display: inline-block; + width: 23px; +} +.red-ui-info-outline-gutter { + display:none; + button { + position: absolute; + top: 1px; + left: 2px; + } + .red-ui-treeList-label:hover & { + display: inline; + } +} +.red-ui-info-outline-item-controls { + position: absolute; + top:0; + bottom: 0; + right: 0px; + padding: 2px 3px 0 1px; + text-align: right; + background: $list-item-background; + + .red-ui-treeList-label:hover & { + background: $list-item-background-hover; + } + .red-ui-treeList-label.selected & { + background: $list-item-background-selected; + } + + + &.red-ui-info-outline-item-hover-controls button { + min-width: 23px; + } + + .red-ui-treeList-label:not(:hover) &.red-ui-info-outline-item-hover-controls { + button { + border: none; + background: none; + } + } + .red-ui-info-outline-item-control-reveal, + .red-ui-info-outline-item-control-action { + display: none; + } + .red-ui-treeList-label:hover & { + .red-ui-info-outline-item-control-reveal, + .red-ui-info-outline-item-control-action { + display: inline-block; + } + } + + .fa-ban { + display: none; + } + .red-ui-info-outline-item.red-ui-info-outline-item-disabled & { + .fa-ban { + display: inline-block; + } + .fa-circle-thin { + display: none; + } + } + button { + margin-right: 3px + } +} +.red-ui-info-outline-item-disabled { + .red-ui-search-result-node { + opacity: 0.4; + } + .red-ui-info-outline-item-label { + font-style: italic; + color: $secondary-text-color-disabled; + } + .red-ui-icons-flow { + opacity: 0.4; + } +} + + + +.red-ui-icons { + display: inline-block; + width: 18px; + &:before { + white-space: pre; + content: ' ' + } + +} + +.red-ui-icons-flow { + background-image: url('images/subflow_tab.svg'); + background-repeat: no-repeat; + background-size: contain; + filter: brightness(2.5); +} + +.red-ui-info-toolbar { + min-height: 39px; + height: 39px; + box-sizing: border-box; + text-align: left; + // padding-left: 9px; + // box-sizing: border-box; + // background: $palette-header-background; + // border-bottom: 1px solid $secondary-border-color; + + .red-ui-searchBox-container { + position: absolute; + top: 6px; + right: 8px; + width: calc(100% - 150px); + max-width: 250px; + background: $palette-header-background; + input.red-ui-searchBox-input { + border: 1px solid $secondary-border-color; + border-radius: 3px; + font-size: 12px; + height: 26px; + } + input:focus.red-ui-searchBox-input { + border: 1px solid $secondary-border-color; + } + i.fa-search, i.fa-times { + top: 7px; + } + } + +} diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss index 2f0740d61..d0cd631fc 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss @@ -32,6 +32,9 @@ right: 5px; top: 9px; } + form.red-ui-searchBox-form { + margin: 0; + } input.red-ui-searchBox-input { border-radius: 0; border: none; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index 598d4e7f9..ec865b116 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -18,7 +18,7 @@ border: 1px solid $form-input-border-color; border-radius: 4px; height: 34px; - display: inline-block; + display: inline-flex; padding: 0; margin: 0; vertical-align: middle; @@ -26,12 +26,7 @@ overflow:visible; position: relative; .red-ui-typedInput-input-wrap { - position: absolute; - left:0; - right:0; - top:0; - bottom:0; - outline: red; + flex-grow: 1; } input.red-ui-typedInput-input { width: 100%; @@ -49,7 +44,7 @@ border-color: $form-input-focus-color !important; } .red-ui-typedInput-value-label { - position: absolute; + flex-grow: 1; display: inline-block; height: 32px; box-sizing: border-box; @@ -107,18 +102,17 @@ button.red-ui-typedInput-option-trigger { text-align: left; border: none; - position: absolute; + flex-basis: auto; box-sizing: border-box; border-top-left-radius: 4px; border-bottom-left-radius: 4px; padding: 0 1px 0 5px; - display:inline-block; background: $form-button-background; height: 32px; line-height: 30px; - min-width: 23px; vertical-align: middle; color: $form-text-color; + white-space: nowrap; i.red-ui-typedInput-icon { margin-left: 1px; margin-right: 2px; @@ -154,7 +148,7 @@ button.red-ui-typedInput-option-trigger text-decoration: none; } &.red-ui-typedInput-full-width { - width: 100%; + flex-grow: 1; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } @@ -170,7 +164,6 @@ button.red-ui-typedInput-option-expand { border-bottom-right-radius: 4px; border-top-left-radius: 0; border-bottom-left-radius: 0; - right: 0; } button.red-ui-typedInput-option-trigger { @@ -179,27 +172,23 @@ button.red-ui-typedInput-option-trigger { border-top-right-radius: 4px; border-bottom-right-radius: 4px; padding: 0 0 0 0; - position:absolute; - right: 0; + position:relative; + flex-grow: 1; + line-height: 32px; + display: inline-flex; .red-ui-typedInput-option-label { background:$form-button-background; color: $form-text-color; - position:absolute; - left:0; - right:23px; - top: 0; - padding: 0 5px 0 8px; - i.red-ui-typedInput-icon { - margin-right: 4px; - } + flex-grow: 1; + padding: 0 0 0 8px; + display:inline-block; } .red-ui-typedInput-option-caret { - top: 0; - position: absolute; - right: 0; - bottom: 0; - width: 17px; - padding-left: 5px; + flex-grow: 0; + display:inline-block; + width: 23px; + text-align: center; + height: 100%; &:before { content:''; display: inline-block; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss index f6255eacf..2adfb89ba 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss @@ -112,7 +112,7 @@ position: absolute; bottom: 0; right:0; - zIndex: 101; + z-index: 101; border-left: 1px solid $primary-border-color; border-top: 1px solid $primary-border-color; background: $view-navigator-background; @@ -122,7 +122,7 @@ stroke-dasharray: 5,5; pointer-events: none; stroke: $secondary-border-color; - strokeWidth: 1; + stroke-width: 1; fill: $view-background; } @@ -172,3 +172,44 @@ button.red-ui-footer-button-toggle { margin-right: 0; } } + + +#red-ui-loading-progress { + position: absolute; + background: $primary-background; + top: 0; + bottom: 0; + right: 0; + left: 0; + z-index: 200; + & > div { + position: absolute; + top: 30%; + left: 50%; + transform: translate(-50%, -50%); + width: 300px; + height:80px; + text-align: center; + color: $secondary-text-color; + + } +} +.red-ui-loading-bar { + box-sizing: border-box; + width: 300px; + height: 30px; + border: 2px solid $primary-border-color; + border-radius: 4px; + + > span { + display: block; + height: 100%; + background: $secondary-border-color; + transition: width 0.2s; + width: 10%; + } +} +.red-ui-loading-bar-label { + font-size: 13px; + margin-bottom: 2px; +} diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.4.1.min.js b/packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.4.1.min.js deleted file mode 100644 index a1c07fd80..000000000 --- a/packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-3.4.1.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.4.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.4.1",k=function(e,t){return new k.fn.init(e,t)},p=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function d(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0= 0; - } - - var newValue = oldIsNumeric( val ), - oldValue = isNumeric2( val ); - - if ( newValue !== oldValue ) { - migrateWarn( "jQuery.isNumeric() should not be called on constructed objects" ); - } - - return oldValue; -}; - -migrateWarnFunc( jQuery, "holdReady", jQuery.holdReady, - "jQuery.holdReady is deprecated" ); - -migrateWarnFunc( jQuery, "unique", jQuery.uniqueSort, - "jQuery.unique is deprecated; use jQuery.uniqueSort" ); - -// Now jQuery.expr.pseudos is the standard incantation -migrateWarnProp( jQuery.expr, "filters", jQuery.expr.pseudos, - "jQuery.expr.filters is deprecated; use jQuery.expr.pseudos" ); -migrateWarnProp( jQuery.expr, ":", jQuery.expr.pseudos, - "jQuery.expr[':'] is deprecated; use jQuery.expr.pseudos" ); - - -var oldAjax = jQuery.ajax; - -jQuery.ajax = function( ) { - var jQXHR = oldAjax.apply( this, arguments ); - - // Be sure we got a jQXHR (e.g., not sync) - if ( jQXHR.promise ) { - migrateWarnFunc( jQXHR, "success", jQXHR.done, - "jQXHR.success is deprecated and removed" ); - migrateWarnFunc( jQXHR, "error", jQXHR.fail, - "jQXHR.error is deprecated and removed" ); - migrateWarnFunc( jQXHR, "complete", jQXHR.always, - "jQXHR.complete is deprecated and removed" ); - } - - return jQXHR; -}; - - -var oldRemoveAttr = jQuery.fn.removeAttr, - oldToggleClass = jQuery.fn.toggleClass, - rmatchNonSpace = /\S+/g; - -jQuery.fn.removeAttr = function( name ) { - var self = this; - - jQuery.each( name.match( rmatchNonSpace ), function( i, attr ) { - if ( jQuery.expr.match.bool.test( attr ) ) { - migrateWarn( "jQuery.fn.removeAttr no longer sets boolean properties: " + attr ); - self.prop( attr, false ); - } - } ); - - return oldRemoveAttr.apply( this, arguments ); -}; - -jQuery.fn.toggleClass = function( state ) { - - // Only deprecating no-args or single boolean arg - if ( state !== undefined && typeof state !== "boolean" ) { - return oldToggleClass.apply( this, arguments ); - } - - migrateWarn( "jQuery.fn.toggleClass( boolean ) is deprecated" ); - - // Toggle entire class name of each element - return this.each( function() { - var className = this.getAttribute && this.getAttribute( "class" ) || ""; - - if ( className ) { - jQuery.data( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || state === false ? - "" : - jQuery.data( this, "__className__" ) || "" - ); - } - } ); -}; - - -var internalSwapCall = false; - -// If this version of jQuery has .swap(), don't false-alarm on internal uses -if ( jQuery.swap ) { - jQuery.each( [ "height", "width", "reliableMarginRight" ], function( _, name ) { - var oldHook = jQuery.cssHooks[ name ] && jQuery.cssHooks[ name ].get; - - if ( oldHook ) { - jQuery.cssHooks[ name ].get = function() { - var ret; - - internalSwapCall = true; - ret = oldHook.apply( this, arguments ); - internalSwapCall = false; - return ret; - }; - } - } ); -} - -jQuery.swap = function( elem, options, callback, args ) { - var ret, name, - old = {}; - - if ( !internalSwapCall ) { - migrateWarn( "jQuery.swap() is undocumented and deprecated" ); - } - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.apply( elem, args || [] ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - -var oldData = jQuery.data; - -jQuery.data = function( elem, name, value ) { - var curData; - - // Name can be an object, and each entry in the object is meant to be set as data - if ( name && typeof name === "object" && arguments.length === 2 ) { - curData = jQuery.hasData( elem ) && oldData.call( this, elem ); - var sameKeys = {}; - for ( var key in name ) { - if ( key !== jQuery.camelCase( key ) ) { - migrateWarn( "jQuery.data() always sets/gets camelCased names: " + key ); - curData[ key ] = name[ key ]; - } else { - sameKeys[ key ] = name[ key ]; - } - } - - oldData.call( this, elem, sameKeys ); - - return name; - } - - // If the name is transformed, look for the un-transformed name in the data object - if ( name && typeof name === "string" && name !== jQuery.camelCase( name ) ) { - curData = jQuery.hasData( elem ) && oldData.call( this, elem ); - if ( curData && name in curData ) { - migrateWarn( "jQuery.data() always sets/gets camelCased names: " + name ); - if ( arguments.length > 2 ) { - curData[ name ] = value; - } - return curData[ name ]; - } - } - - return oldData.apply( this, arguments ); -}; - -var oldTweenRun = jQuery.Tween.prototype.run; -var linearEasing = function( pct ) { - return pct; - }; - -jQuery.Tween.prototype.run = function( ) { - if ( jQuery.easing[ this.easing ].length > 1 ) { - migrateWarn( - "'jQuery.easing." + this.easing.toString() + "' should use only one argument" - ); - - jQuery.easing[ this.easing ] = linearEasing; - } - - oldTweenRun.apply( this, arguments ); -}; - -jQuery.fx.interval = jQuery.fx.interval || 13; - -// Support: IE9, Android <=4.4 -// Avoid false positives on browsers that lack rAF -if ( window.requestAnimationFrame ) { - migrateWarnProp( jQuery.fx, "interval", jQuery.fx.interval, - "jQuery.fx.interval is deprecated" ); -} - -var oldLoad = jQuery.fn.load, - oldEventAdd = jQuery.event.add, - originalFix = jQuery.event.fix; - -jQuery.event.props = []; -jQuery.event.fixHooks = {}; - -migrateWarnProp( jQuery.event.props, "concat", jQuery.event.props.concat, - "jQuery.event.props.concat() is deprecated and removed" ); - -jQuery.event.fix = function( originalEvent ) { - var event, - type = originalEvent.type, - fixHook = this.fixHooks[ type ], - props = jQuery.event.props; - - if ( props.length ) { - migrateWarn( "jQuery.event.props are deprecated and removed: " + props.join() ); - while ( props.length ) { - jQuery.event.addProp( props.pop() ); - } - } - - if ( fixHook && !fixHook._migrated_ ) { - fixHook._migrated_ = true; - migrateWarn( "jQuery.event.fixHooks are deprecated and removed: " + type ); - if ( ( props = fixHook.props ) && props.length ) { - while ( props.length ) { - jQuery.event.addProp( props.pop() ); - } - } - } - - event = originalFix.call( this, originalEvent ); - - return fixHook && fixHook.filter ? fixHook.filter( event, originalEvent ) : event; -}; - -jQuery.event.add = function( elem, types ) { - - // This misses the multiple-types case but that seems awfully rare - if ( elem === window && types === "load" && window.document.readyState === "complete" ) { - migrateWarn( "jQuery(window).on('load'...) called after load event occurred" ); - } - return oldEventAdd.apply( this, arguments ); -}; - -jQuery.each( [ "load", "unload", "error" ], function( _, name ) { - - jQuery.fn[ name ] = function() { - var args = Array.prototype.slice.call( arguments, 0 ); - - // If this is an ajax load() the first arg should be the string URL; - // technically this could also be the "Anything" arg of the event .load() - // which just goes to show why this dumb signature has been deprecated! - // jQuery custom builds that exclude the Ajax module justifiably die here. - if ( name === "load" && typeof args[ 0 ] === "string" ) { - return oldLoad.apply( this, args ); - } - - migrateWarn( "jQuery.fn." + name + "() is deprecated" ); - - args.splice( 0, 0, name ); - if ( arguments.length ) { - return this.on.apply( this, args ); - } - - // Use .triggerHandler here because: - // - load and unload events don't need to bubble, only applied to window or image - // - error event should not bubble to window, although it does pre-1.7 - // See http://bugs.jquery.com/ticket/11820 - this.triggerHandler.apply( this, args ); - return this; - }; - -} ); - -jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup contextmenu" ).split( " " ), - function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - migrateWarn( "jQuery.fn." + name + "() event shorthand is deprecated" ); - return arguments.length > 0 ? - this.on( name, null, data, fn ) : - this.trigger( name ); - }; -} ); - -// Trigger "ready" event only once, on document ready -jQuery( function() { - jQuery( window.document ).triggerHandler( "ready" ); -} ); - -jQuery.event.special.ready = { - setup: function() { - if ( this === window.document ) { - migrateWarn( "'ready' event is deprecated" ); - } - } -}; - -jQuery.fn.extend( { - - bind: function( types, data, fn ) { - migrateWarn( "jQuery.fn.bind() is deprecated" ); - return this.on( types, null, data, fn ); - }, - unbind: function( types, fn ) { - migrateWarn( "jQuery.fn.unbind() is deprecated" ); - return this.off( types, null, fn ); - }, - delegate: function( selector, types, data, fn ) { - migrateWarn( "jQuery.fn.delegate() is deprecated" ); - return this.on( types, selector, data, fn ); - }, - undelegate: function( selector, types, fn ) { - migrateWarn( "jQuery.fn.undelegate() is deprecated" ); - return arguments.length === 1 ? - this.off( selector, "**" ) : - this.off( types, selector || "**", fn ); - }, - hover: function( fnOver, fnOut ) { - migrateWarn( "jQuery.fn.hover() is deprecated" ); - return this.on( "mouseenter", fnOver ).on( "mouseleave", fnOut || fnOver ); - } -} ); - - -var oldOffset = jQuery.fn.offset; - -jQuery.fn.offset = function() { - var docElem, - elem = this[ 0 ], - origin = { top: 0, left: 0 }; - - if ( !elem || !elem.nodeType ) { - migrateWarn( "jQuery.fn.offset() requires a valid DOM element" ); - return origin; - } - - docElem = ( elem.ownerDocument || window.document ).documentElement; - if ( !jQuery.contains( docElem, elem ) ) { - migrateWarn( "jQuery.fn.offset() requires an element connected to a document" ); - return origin; - } - - return oldOffset.apply( this, arguments ); -}; - - -var oldParam = jQuery.param; - -jQuery.param = function( data, traditional ) { - var ajaxTraditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional; - - if ( traditional === undefined && ajaxTraditional ) { - - migrateWarn( "jQuery.param() no longer uses jQuery.ajaxSettings.traditional" ); - traditional = ajaxTraditional; - } - - return oldParam.call( this, data, traditional ); -}; - -var oldSelf = jQuery.fn.andSelf || jQuery.fn.addBack; - -jQuery.fn.andSelf = function() { - migrateWarn( "jQuery.fn.andSelf() is deprecated and removed, use jQuery.fn.addBack()" ); - return oldSelf.apply( this, arguments ); -}; - - -var oldDeferred = jQuery.Deferred, - tuples = [ - - // Action, add listener, callbacks, .then handlers, final state - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), "rejected" ], - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ) ] - ]; - -jQuery.Deferred = function( func ) { - var deferred = oldDeferred(), - promise = deferred.promise(); - - deferred.pipe = promise.pipe = function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - migrateWarn( "deferred.pipe() is deprecated" ); - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; - - // Deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .done( newDefer.resolve ) - .fail( newDefer.reject ) - .progress( newDefer.notify ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this === promise ? newDefer.promise() : this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - - }; - - if ( func ) { - func.call( deferred, deferred ); - } - - return deferred; -}; - -// Preserve handler of uncaught exceptions in promise chains -jQuery.Deferred.exceptionHook = oldDeferred.exceptionHook; - -return jQuery; -} ); diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.0.1.min.js b/packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.0.1.min.js deleted file mode 100644 index cd6d6c853..000000000 --- a/packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.0.1.min.js +++ /dev/null @@ -1,215 +0,0 @@ -/*! jQuery Migrate v3.0.1 | (c) jQuery Foundation and other contributors | jquery.org/license */ - -void 0 === jQuery.migrateMute && (jQuery.migrateMute = !0), function(e) { - "function" == typeof define && define.amd ? define([ "jquery" ], window, e) : "object" == typeof module && module.exports ? module.exports = e(require("jquery"), window) : e(jQuery, window); -}(function(e, t) { - "use strict"; - function r(r) { - var n = t.console; - o[r] || (o[r] = !0, e.migrateWarnings.push(r), n && n.warn && !e.migrateMute && (n.warn("JQMIGRATE: " + r), - e.migrateTrace && n.trace && n.trace())); - } - function n(e, t, n, a) { - Object.defineProperty(e, t, { - configurable: !0, - enumerable: !0, - get: function() { - return r(a), n; - }, - set: function(e) { - r(a), n = e; - } - }); - } - function a(e, t, n, a) { - e[t] = function() { - return r(a), n.apply(this, arguments); - }; - } - e.migrateVersion = "3.0.1", function() { - var r = /^[12]\./; - t.console && t.console.log && (e && !r.test(e.fn.jquery) || t.console.log("JQMIGRATE: jQuery 3.0.0+ REQUIRED"), - e.migrateWarnings && t.console.log("JQMIGRATE: Migrate plugin loaded multiple times"), - t.console.log("JQMIGRATE: Migrate is installed" + (e.migrateMute ? "" : " with logging active") + ", version " + e.migrateVersion)); - }(); - var o = {}; - e.migrateWarnings = [], void 0 === e.migrateTrace && (e.migrateTrace = !0), e.migrateReset = function() { - o = {}, e.migrateWarnings.length = 0; - }, "BackCompat" === t.document.compatMode && r("jQuery is not compatible with Quirks Mode"); - var i = e.fn.init, s = e.isNumeric, u = e.find, c = /\[(\s*[-\w]+\s*)([~|^$*]?=)\s*([-\w#]*?#[-\w#]*)\s*\]/, l = /\[(\s*[-\w]+\s*)([~|^$*]?=)\s*([-\w#]*?#[-\w#]*)\s*\]/g; - e.fn.init = function(e) { - var t = Array.prototype.slice.call(arguments); - return "string" == typeof e && "#" === e && (r("jQuery( '#' ) is not a valid selector"), - t[0] = []), i.apply(this, t); - }, e.fn.init.prototype = e.fn, e.find = function(e) { - var n = Array.prototype.slice.call(arguments); - if ("string" == typeof e && c.test(e)) try { - t.document.querySelector(e); - } catch (a) { - e = e.replace(l, function(e, t, r, n) { - return "[" + t + r + '"' + n + '"]'; - }); - try { - t.document.querySelector(e), r("Attribute selector with '#' must be quoted: " + n[0]), - n[0] = e; - } catch (e) { - r("Attribute selector with '#' was not fixed: " + n[0]); - } - } - return u.apply(this, n); - }; - var d; - for (d in u) Object.prototype.hasOwnProperty.call(u, d) && (e.find[d] = u[d]); - e.fn.size = function() { - return r("jQuery.fn.size() is deprecated and removed; use the .length property"), - this.length; - }, e.parseJSON = function() { - return r("jQuery.parseJSON is deprecated; use JSON.parse"), JSON.parse.apply(null, arguments); - }, e.isNumeric = function(t) { - var n = s(t), a = function(t) { - var r = t && t.toString(); - return !e.isArray(t) && r - parseFloat(r) + 1 >= 0; - }(t); - return n !== a && r("jQuery.isNumeric() should not be called on constructed objects"), - a; - }, a(e, "holdReady", e.holdReady, "jQuery.holdReady is deprecated"), a(e, "unique", e.uniqueSort, "jQuery.unique is deprecated; use jQuery.uniqueSort"), - n(e.expr, "filters", e.expr.pseudos, "jQuery.expr.filters is deprecated; use jQuery.expr.pseudos"), - n(e.expr, ":", e.expr.pseudos, "jQuery.expr[':'] is deprecated; use jQuery.expr.pseudos"); - var p = e.ajax; - e.ajax = function() { - var e = p.apply(this, arguments); - return e.promise && (a(e, "success", e.done, "jQXHR.success is deprecated and removed"), - a(e, "error", e.fail, "jQXHR.error is deprecated and removed"), a(e, "complete", e.always, "jQXHR.complete is deprecated and removed")), - e; - }; - var f = e.fn.removeAttr, y = e.fn.toggleClass, m = /\S+/g; - e.fn.removeAttr = function(t) { - var n = this; - return e.each(t.match(m), function(t, a) { - e.expr.match.bool.test(a) && (r("jQuery.fn.removeAttr no longer sets boolean properties: " + a), - n.prop(a, !1)); - }), f.apply(this, arguments); - }, e.fn.toggleClass = function(t) { - return void 0 !== t && "boolean" != typeof t ? y.apply(this, arguments) : (r("jQuery.fn.toggleClass( boolean ) is deprecated"), - this.each(function() { - var r = this.getAttribute && this.getAttribute("class") || ""; - r && e.data(this, "__className__", r), this.setAttribute && this.setAttribute("class", r || !1 === t ? "" : e.data(this, "__className__") || ""); - })); - }; - var h = !1; - e.swap && e.each([ "height", "width", "reliableMarginRight" ], function(t, r) { - var n = e.cssHooks[r] && e.cssHooks[r].get; - n && (e.cssHooks[r].get = function() { - var e; - return h = !0, e = n.apply(this, arguments), h = !1, e; - }); - }), e.swap = function(e, t, n, a) { - var o, i, s = {}; - h || r("jQuery.swap() is undocumented and deprecated"); - for (i in t) s[i] = e.style[i], e.style[i] = t[i]; - o = n.apply(e, a || []); - for (i in t) e.style[i] = s[i]; - return o; - }; - var g = e.data; - e.data = function(t, n, a) { - var o; - if (n && "object" == typeof n && 2 === arguments.length) { - o = e.hasData(t) && g.call(this, t); - var i = {}; - for (var s in n) s !== e.camelCase(s) ? (r("jQuery.data() always sets/gets camelCased names: " + s), - o[s] = n[s]) : i[s] = n[s]; - return g.call(this, t, i), n; - } - return n && "string" == typeof n && n !== e.camelCase(n) && (o = e.hasData(t) && g.call(this, t)) && n in o ? (r("jQuery.data() always sets/gets camelCased names: " + n), - arguments.length > 2 && (o[n] = a), o[n]) : g.apply(this, arguments); - }; - var v = e.Tween.prototype.run, j = function(e) { - return e; - }; - e.Tween.prototype.run = function() { - e.easing[this.easing].length > 1 && (r("'jQuery.easing." + this.easing.toString() + "' should use only one argument"), - e.easing[this.easing] = j), v.apply(this, arguments); - }, e.fx.interval = e.fx.interval || 13, t.requestAnimationFrame && n(e.fx, "interval", e.fx.interval, "jQuery.fx.interval is deprecated"); - var Q = e.fn.load, b = e.event.add, w = e.event.fix; - e.event.props = [], e.event.fixHooks = {}, n(e.event.props, "concat", e.event.props.concat, "jQuery.event.props.concat() is deprecated and removed"), - e.event.fix = function(t) { - var n, a = t.type, o = this.fixHooks[a], i = e.event.props; - if (i.length) for (r("jQuery.event.props are deprecated and removed: " + i.join()); i.length; ) e.event.addProp(i.pop()); - if (o && !o._migrated_ && (o._migrated_ = !0, r("jQuery.event.fixHooks are deprecated and removed: " + a), - (i = o.props) && i.length)) for (;i.length; ) e.event.addProp(i.pop()); - return n = w.call(this, t), o && o.filter ? o.filter(n, t) : n; - }, e.event.add = function(e, n) { - return e === t && "load" === n && "complete" === t.document.readyState && r("jQuery(window).on('load'...) called after load event occurred"), - b.apply(this, arguments); - }, e.each([ "load", "unload", "error" ], function(t, n) { - e.fn[n] = function() { - var e = Array.prototype.slice.call(arguments, 0); - return "load" === n && "string" == typeof e[0] ? Q.apply(this, e) : (r("jQuery.fn." + n + "() is deprecated"), - e.splice(0, 0, n), arguments.length ? this.on.apply(this, e) : (this.triggerHandler.apply(this, e), - this)); - }; - }), e.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "), function(t, n) { - e.fn[n] = function(e, t) { - return r("jQuery.fn." + n + "() event shorthand is deprecated"), arguments.length > 0 ? this.on(n, null, e, t) : this.trigger(n); - }; - }), e(function() { - e(t.document).triggerHandler("ready"); - }), e.event.special.ready = { - setup: function() { - this === t.document && r("'ready' event is deprecated"); - } - }, e.fn.extend({ - bind: function(e, t, n) { - return r("jQuery.fn.bind() is deprecated"), this.on(e, null, t, n); - }, - unbind: function(e, t) { - return r("jQuery.fn.unbind() is deprecated"), this.off(e, null, t); - }, - delegate: function(e, t, n, a) { - return r("jQuery.fn.delegate() is deprecated"), this.on(t, e, n, a); - }, - undelegate: function(e, t, n) { - return r("jQuery.fn.undelegate() is deprecated"), 1 === arguments.length ? this.off(e, "**") : this.off(t, e || "**", n); - }, - hover: function(e, t) { - return r("jQuery.fn.hover() is deprecated"), this.on("mouseenter", e).on("mouseleave", t || e); - } - }); - var x = e.fn.offset; - e.fn.offset = function() { - var n, a = this[0], o = { - top: 0, - left: 0 - }; - return a && a.nodeType ? (n = (a.ownerDocument || t.document).documentElement, e.contains(n, a) ? x.apply(this, arguments) : (r("jQuery.fn.offset() requires an element connected to a document"), - o)) : (r("jQuery.fn.offset() requires a valid DOM element"), o); - }; - var k = e.param; - e.param = function(t, n) { - var a = e.ajaxSettings && e.ajaxSettings.traditional; - return void 0 === n && a && (r("jQuery.param() no longer uses jQuery.ajaxSettings.traditional"), - n = a), k.call(this, t, n); - }; - var A = e.fn.andSelf || e.fn.addBack; - e.fn.andSelf = function() { - return r("jQuery.fn.andSelf() is deprecated and removed, use jQuery.fn.addBack()"), - A.apply(this, arguments); - }; - var S = e.Deferred, q = [ [ "resolve", "done", e.Callbacks("once memory"), e.Callbacks("once memory"), "resolved" ], [ "reject", "fail", e.Callbacks("once memory"), e.Callbacks("once memory"), "rejected" ], [ "notify", "progress", e.Callbacks("memory"), e.Callbacks("memory") ] ]; - return e.Deferred = function(t) { - var n = S(), a = n.promise(); - return n.pipe = a.pipe = function() { - var t = arguments; - return r("deferred.pipe() is deprecated"), e.Deferred(function(r) { - e.each(q, function(o, i) { - var s = e.isFunction(t[o]) && t[o]; - n[i[1]](function() { - var t = s && s.apply(this, arguments); - t && e.isFunction(t.promise) ? t.promise().done(r.resolve).fail(r.reject).progress(r.notify) : r[i[0] + "With"](this === a ? r.promise() : this, s ? [ t ] : arguments); - }); - }), t = null; - }).promise(); - }, t && t.call(n, n), n; - }, e.Deferred.exceptionHook = S.exceptionHook, e; -}); \ No newline at end of file diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js b/packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js new file mode 100644 index 000000000..411afbd8a --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.3.0.min.js @@ -0,0 +1,2 @@ +/*! jQuery Migrate v3.3.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +"undefined"==typeof jQuery.migrateMute&&(jQuery.migrateMute=!0),function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],function(e){return t(e,window)}):"object"==typeof module&&module.exports?module.exports=t(require("jquery"),window):t(jQuery,window)}(function(s,n){"use strict";function e(e){return 0<=function(e,t){var r,n=/^(\d+)\.(\d+)\.(\d+)/,i=n.exec(e)||[],o=n.exec(t)||[];for(r=1;r<=3;r++){if(+i[r]>+o[r])return 1;if(+i[r]<+o[r])return-1}return 0}(s.fn.jquery,e)}s.migrateVersion="3.3.0",n.console&&n.console.log&&(s&&e("3.0.0")||n.console.log("JQMIGRATE: jQuery 3.0.0+ REQUIRED"),s.migrateWarnings&&n.console.log("JQMIGRATE: Migrate plugin loaded multiple times"),n.console.log("JQMIGRATE: Migrate is installed"+(s.migrateMute?"":" with logging active")+", version "+s.migrateVersion));var r={};function u(e){var t=n.console;s.migrateDeduplicateWarnings&&r[e]||(r[e]=!0,s.migrateWarnings.push(e),t&&t.warn&&!s.migrateMute&&(t.warn("JQMIGRATE: "+e),s.migrateTrace&&t.trace&&t.trace()))}function t(e,t,r,n){Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:function(){return u(n),r},set:function(e){u(n),r=e}})}function i(e,t,r,n){e[t]=function(){return u(n),r.apply(this,arguments)}}s.migrateDeduplicateWarnings=!0,s.migrateWarnings=[],void 0===s.migrateTrace&&(s.migrateTrace=!0),s.migrateReset=function(){r={},s.migrateWarnings.length=0},"BackCompat"===n.document.compatMode&&u("jQuery is not compatible with Quirks Mode");var o,a={},c=s.fn.init,d=s.find,l=/\[(\s*[-\w]+\s*)([~|^$*]?=)\s*([-\w#]*?#[-\w#]*)\s*\]/,p=/\[(\s*[-\w]+\s*)([~|^$*]?=)\s*([-\w#]*?#[-\w#]*)\s*\]/g,f=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;for(o in s.fn.init=function(e){var t=Array.prototype.slice.call(arguments);return"string"==typeof e&&"#"===e&&(u("jQuery( '#' ) is not a valid selector"),t[0]=[]),c.apply(this,t)},s.fn.init.prototype=s.fn,s.find=function(t){var r=Array.prototype.slice.call(arguments);if("string"==typeof t&&l.test(t))try{n.document.querySelector(t)}catch(e){t=t.replace(p,function(e,t,r,n){return"["+t+r+'"'+n+'"]'});try{n.document.querySelector(t),u("Attribute selector with '#' must be quoted: "+r[0]),r[0]=t}catch(e){u("Attribute selector with '#' was not fixed: "+r[0])}}return d.apply(this,r)},d)Object.prototype.hasOwnProperty.call(d,o)&&(s.find[o]=d[o]);if(i(s.fn,"size",function(){return this.length},"jQuery.fn.size() is deprecated and removed; use the .length property"),i(s,"parseJSON",function(){return JSON.parse.apply(null,arguments)},"jQuery.parseJSON is deprecated; use JSON.parse"),i(s,"holdReady",s.holdReady,"jQuery.holdReady is deprecated"),i(s,"unique",s.uniqueSort,"jQuery.unique is deprecated; use jQuery.uniqueSort"),t(s.expr,"filters",s.expr.pseudos,"jQuery.expr.filters is deprecated; use jQuery.expr.pseudos"),t(s.expr,":",s.expr.pseudos,"jQuery.expr[':'] is deprecated; use jQuery.expr.pseudos"),e("3.1.1")&&i(s,"trim",function(e){return null==e?"":(e+"").replace(f,"")},"jQuery.trim is deprecated; use String.prototype.trim"),e("3.2.0")&&i(s,"nodeName",function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},"jQuery.nodeName is deprecated"),e("3.3.0")&&(i(s,"isNumeric",function(e){var t=typeof e;return("number"==t||"string"==t)&&!isNaN(e-parseFloat(e))},"jQuery.isNumeric() is deprecated"),s.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){a["[object "+t+"]"]=t.toLowerCase()}),i(s,"type",function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?a[Object.prototype.toString.call(e)]||"object":typeof e},"jQuery.type is deprecated"),i(s,"isFunction",function(e){return"function"==typeof e},"jQuery.isFunction() is deprecated"),i(s,"isWindow",function(e){return null!=e&&e===e.window},"jQuery.isWindow() is deprecated"),i(s,"isArray",Array.isArray,"jQuery.isArray is deprecated; use Array.isArray")),s.ajax){var y=s.ajax;s.ajax=function(){var e=y.apply(this,arguments);return e.promise&&(i(e,"success",e.done,"jQXHR.success is deprecated and removed"),i(e,"error",e.fail,"jQXHR.error is deprecated and removed"),i(e,"complete",e.always,"jQXHR.complete is deprecated and removed")),e}}var m=s.fn.removeAttr,g=s.fn.toggleClass,h=/\S+/g;function v(e){return e.replace(/-([a-z])/g,function(e,t){return t.toUpperCase()})}s.fn.removeAttr=function(e){var r=this;return s.each(e.match(h),function(e,t){s.expr.match.bool.test(t)&&(u("jQuery.fn.removeAttr no longer sets boolean properties: "+t),r.prop(t,!1))}),m.apply(this,arguments)};var j,Q=!(s.fn.toggleClass=function(t){return void 0!==t&&"boolean"!=typeof t?g.apply(this,arguments):(u("jQuery.fn.toggleClass( boolean ) is deprecated"),this.each(function(){var e=this.getAttribute&&this.getAttribute("class")||"";e&&s.data(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===t?"":s.data(this,"__className__")||"")}))}),b=/^[a-z]/,w=/^(?:Border(?:Top|Right|Bottom|Left)?(?:Width|)|(?:Margin|Padding)?(?:Top|Right|Bottom|Left)?|(?:Min|Max)?(?:Width|Height))$/;s.swap&&s.each(["height","width","reliableMarginRight"],function(e,t){var r=s.cssHooks[t]&&s.cssHooks[t].get;r&&(s.cssHooks[t].get=function(){var e;return Q=!0,e=r.apply(this,arguments),Q=!1,e})}),s.swap=function(e,t,r,n){var i,o,a={};for(o in Q||u("jQuery.swap() is undocumented and deprecated"),t)a[o]=e.style[o],e.style[o]=t[o];for(o in i=r.apply(e,n||[]),t)e.style[o]=a[o];return i},e("3.4.0")&&"undefined"!=typeof Proxy&&(s.cssProps=new Proxy(s.cssProps||{},{set:function(){return u("JQMIGRATE: jQuery.cssProps is deprecated"),Reflect.set.apply(this,arguments)}})),s.cssNumber||(s.cssNumber={}),j=s.fn.css,s.fn.css=function(e,t){var r=this;return"string"!=typeof e&&s.each(e,function(e,t){s.fn.css.call(r,e,t)}),"number"!=typeof t||function(e){return b.test(e)&&w.test(e[0].toUpperCase()+e.slice(1))}(v(e))||u("Use of number-typed values is deprecated in jQuery.fn.css"),j.apply(this,arguments)};var x=s.data;if(s.data=function(e,t,r){var n,i,o;if(t&&"object"==typeof t&&2===arguments.length){for(o in n=s.hasData(e)&&x.call(this,e),i={},t)o!==v(o)?(u("jQuery.data() always sets/gets camelCased names: "+o),n[o]=t[o]):i[o]=t[o];return x.call(this,e,i),t}return t&&"string"==typeof t&&t!==v(t)&&(n=s.hasData(e)&&x.call(this,e))&&t in n?(u("jQuery.data() always sets/gets camelCased names: "+t),2");t!==e&&C(e)!==C(t)&&u("HTML tags must be properly nested and closed: "+e)}var P=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,q=s.htmlPrefilter;s.UNSAFE_restoreLegacyHtmlPrefilter=function(){s.htmlPrefilter=function(e){return T(e),e.replace(P,"<$1>")}},s.htmlPrefilter=function(e){return T(e),q(e)};var D=s.fn.offset;if(s.fn.offset=function(){var e,t=this[0];if(t&&t.nodeType)return e=(t.ownerDocument||n.document).documentElement,s.contains(e,t)?D.apply(this,arguments):(u("jQuery.fn.offset() requires an element connected to a document"),{top:0,left:0});u("jQuery.fn.offset() requires a valid DOM element")},s.ajax){var E=s.param;s.param=function(e,t){var r=s.ajaxSettings&&s.ajaxSettings.traditional;return void 0===t&&r&&(u("jQuery.param() no longer uses jQuery.ajaxSettings.traditional"),t=r),E.call(this,e,t)}}var _=s.fn.andSelf||s.fn.addBack;if(s.fn.andSelf=function(){return u("jQuery.fn.andSelf() is deprecated and removed, use jQuery.fn.addBack()"),_.apply(this,arguments)},s.Deferred){var F=s.Deferred,O=[["resolve","done",s.Callbacks("once memory"),s.Callbacks("once memory"),"resolved"],["reject","fail",s.Callbacks("once memory"),s.Callbacks("once memory"),"rejected"],["notify","progress",s.Callbacks("memory"),s.Callbacks("memory")]];s.Deferred=function(e){var o=F(),a=o.promise();return o.pipe=a.pipe=function(){var i=arguments;return u("deferred.pipe() is deprecated"),s.Deferred(function(n){s.each(O,function(e,t){var r="function"==typeof i[e]&&i[e];o[t[1]](function(){var e=r&&r.apply(this,arguments);e&&"function"==typeof e.promise?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[t[0]+"With"](this===a?n.promise():this,r?[e]:arguments)})}),i=null}).promise()},e&&e.call(o,o),o},s.Deferred.exceptionHook=F.exceptionHook}return s}); diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js index 382537be3..49a02c206 100644 --- a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js +++ b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js @@ -11,6 +11,7 @@ var length = str.length; var start = 0; var inString = false; + var inRegex = false; var inBox = false; var quoteChar; var list = []; @@ -24,8 +25,13 @@ } for (var i=0;i 70) { + post = result.substring(offset+f.pos+1); + if (!/^\n/.test(post)) { + indented = indentLine(post,indent); + hasNewline = /\n$/.test(pre); + result = pre+(hasNewline?"":"\n")+indented; + offset += indented.length-post.length+(hasNewline?0:1); + } + } + } else if (f.type === "open-block") { - if (f.width > 30) { + if (f.width > 40) { longStack.push(true); indent += 4; pre = result.substring(0,offset+f.pos+1); post = result.substring(offset+f.pos+1); + hasNewline = /\n$/.test(pre); indented = indentLine(post,indent); - result = pre+"\n"+indented; - offset += indented.length-post.length+1; + result = pre+(hasNewline?"":"\n")+indented; + offset += indented.length-post.length+(hasNewline?0:1); } else { longStack.push(false); } } else if (f.type === "close-block") { - if (f.width > 30) { + if (f.width > 40) { indent -= 4; pre = result.substring(0,offset+f.pos); post = result.substring(offset+f.pos); indented = indentLine(post,indent); - result = pre+"\n"+indented; - offset += indented.length-post.length+1; + hasNewline = /\n *$/.test(pre); + if (hasNewline) { + result = pre + post; + } else { + result = pre+"\n"+indented; + offset += indented.length-post.length+1; + } } longStack.pop(); } diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js index 2a90e4ce7..48358517e 100644 --- a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js +++ b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js @@ -28,6 +28,11 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m }, "identifier"); this.$rules = { "start" : [ + { + token: "string.regexp", + regex: "\\/", + next: "regex" + }, { token : "string", regex : "'(?=.)", @@ -46,34 +51,35 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m token : "constant.numeric", // float regex : /[+-]?\d[\d_]*(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/ }, - { token: "keyword", - regex: /λ/ - }, - { - token: "keyword", - regex: jsonataFunctions - }, - { - token : keywordMapper, - regex : "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*" - }, - { - token : "punctuation.operator", - regex : /[.](?![.])/ - }, - { - token : "keyword.operator", - regex : /\|\||<=|>=|\.\.|\*\*|!=|:=|[=<>`!$%&*+\-~\/^]/, - next : "start" - }, - { - token : "punctuation.operator", - regex : /[?:,;.]/, - next : "start" - }, - { - token : "paren.lparen", - regex : /[\[({]/, + { + token: "keyword", + regex: /λ/ + }, + { + token: "keyword", + regex: jsonataFunctions + }, + { + token : keywordMapper, + regex : "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*" + }, + { + token : "punctuation.operator", + regex : /[.](?![.])/ + }, + { + token : "keyword.operator", + regex : /\|\||<=|>=|\.\.|\*\*|!=|:=|[=<>`!$%&*+\-~\/^]/, + next : "start" + }, + { + token : "punctuation.operator", + regex : /[?:,;.]/, + next : "start" + }, + { + token : "paren.lparen", + regex : /[\[({]/, next : "start" }, { @@ -86,7 +92,8 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m token : "string", regex : '"|$', next : "start" - }, { + }, + { defaultToken: "string" } ], @@ -95,9 +102,24 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m token : "string", regex : "'|$", next : "start" - }, { + }, + { defaultToken: "string" } + ], + "regex" : [ + { + token: "string.regexp", + regex: "\\\\/" + }, + { + token: "string.regexp", + regex: "/[sxngimy]*", + next: "start" + }, + { + defaultToken: "string.regexp" + } ] }; }; diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js b/packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js deleted file mode 100644 index 555c1dc1d..000000000 --- a/packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * marked - a markdown parser - * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) - * https://github.com/chjj/marked - */ -(function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]||""});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&(cap[1]==="pre"||cap[1]==="script"||cap[1]==="style"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out="",l=text.length,i=0,ch;for(;i.5){ch="x"+ch.toString(16)}out+="&#"+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return"
"+(escaped?code:escape(code,true))+"\n
"}return'
'+(escaped?code:escape(code,true))+"\n
\n"};Renderer.prototype.blockquote=function(quote){return"
\n"+quote+"
\n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"'+text+"\n"};Renderer.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+"\n"};Renderer.prototype.listitem=function(text){return"
  • "+text+"
  • \n"};Renderer.prototype.paragraph=function(text){return"

    "+text+"

    \n"};Renderer.prototype.table=function(header,body){return"\n"+"\n"+header+"\n"+"\n"+body+"\n"+"
    \n"};Renderer.prototype.tablerow=function(content){return"\n"+content+"\n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+"\n"};Renderer.prototype.strong=function(text){return""+text+""};Renderer.prototype.em=function(text){return""+text+""};Renderer.prototype.codespan=function(text){return""+text+""};Renderer.prototype.br=function(){return this.options.xhtml?"
    ":"
    "};Renderer.prototype.del=function(text){return""+text+""};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0||prot.indexOf("vbscript:")===0){return""}}var out='
    ";return out};Renderer.prototype.image=function(href,title,text){var out=''+text+'":">";return out};Renderer.prototype.text=function(text){return text};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out="";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type==="text"){body+="\n"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case"space":{return""}case"hr":{return this.renderer.hr()}case"heading":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case"code":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case"table":{var header="",body="",i,row,cell,flags,j;cell="";for(i=0;i/g,">").replace(/"/g,""").replace(/'/g,"'")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;iAn error occured:

    "+escape(e.message+"",true)+"
    "}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}()); \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.html b/packages/node_modules/@node-red/nodes/core/common/20-inject.html index 77ced7d66..8aa3dc7b2 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.html +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.html @@ -16,14 +16,12 @@ diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.js b/packages/node_modules/@node-red/nodes/core/common/20-inject.js index c0d9e0c2f..54715e131 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.js +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.js @@ -20,9 +20,32 @@ module.exports = function(RED) { function InjectNode(n) { RED.nodes.createNode(this,n); - this.topic = n.topic; - this.payload = n.payload; - this.payloadType = n.payloadType; + + /* Handle legacy */ + if(!Array.isArray(n.props)){ + n.props = []; + n.props.push({ + p:'payload', + v:n.payload, + vt:n.payloadType + }); + n.props.push({ + p:'topic', + v:n.topic, + vt:'str' + }); + } else { + for (var i=0,l=n.props.length; i 2147483) { node.error(RED._("inject.errors.toolong", this)); delete node.repeat; } node.repeaterSetup = function () { - if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) { - this.repeat = this.repeat * 1000; - if (RED.settings.verbose) { - this.log(RED._("inject.repeat", this)); + if (this.repeat && !isNaN(this.repeat) && this.repeat > 0) { + this.repeat = this.repeat * 1000; + if (RED.settings.verbose) { + this.log(RED._("inject.repeat", this)); + } + this.interval_id = setInterval(function() { + node.emit("input", {}); + }, this.repeat); + } else if (this.crontab) { + if (RED.settings.verbose) { + this.log(RED._("inject.crontab", this)); + } + this.cronjob = new cron.CronJob(this.crontab, function() { node.emit("input", {}); }, null, true); } - this.interval_id = setInterval(function() { - node.emit("input", {}); - }, this.repeat); - } else if (this.crontab) { - if (RED.settings.verbose) { - this.log(RED._("inject.crontab", this)); - } - this.cronjob = new cron.CronJob(this.crontab, function() { node.emit("input", {}); }, null, true); - } }; if (this.once) { this.onceTimeout = setTimeout( function() { - node.emit("input",{}); - node.repeaterSetup(); + node.emit("input",{}); + node.repeaterSetup(); }, this.onceDelay); } else { - node.repeaterSetup(); + node.repeaterSetup(); } - this.on("input",function(msg) { - msg.topic = this.topic; - if (this.payloadType !== 'flow' && this.payloadType !== 'global') { - try { - if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") { - msg.payload = Date.now(); - } else if (this.payloadType == null) { - msg.payload = this.payload; - } else if (this.payloadType === 'none') { - msg.payload = ""; - } else { - msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg); - } - this.send(msg); - msg = null; - } catch(err) { - this.error(err,msg); - } - } else { - RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg, function(err,res) { - if (err) { - node.error(err,msg); - } else { - msg.payload = res; - node.send(msg); - } + this.on("input", function(msg) { + var errors = []; - }); + this.props.forEach(p => { + var property = p.p; + var value = p.v ? p.v : ''; + var valueType = p.vt ? p.vt : 'str'; + + if (!property) return; + + if (valueType === "jsonata") { + if (p.exp) { + try { + var val = RED.util.evaluateJSONataExpression(p.exp, msg); + RED.util.setMessageProperty(msg, property, val, true); + } + catch (err) { + errors.push(err.message); + } + } + return; + } + try { + RED.util.setMessageProperty(msg,property,RED.util.evaluateNodeProperty(value, valueType, this, msg),true); + } catch (err) { + errors.push(err.toString()); + } + }); + + if (errors.length) { + node.error(errors.join('; '), msg); + } else { + node.send(msg); } }); } diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.html b/packages/node_modules/@node-red/nodes/core/common/21-debug.html index 6a87cbf21..d12279c74 100644 --- a/packages/node_modules/@node-red/nodes/core/common/21-debug.html +++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.html @@ -6,25 +6,30 @@
    -
    -
    +
    +
    + + + + +
    @@ -36,6 +41,36 @@ diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.js b/packages/node_modules/@node-red/nodes/core/function/10-function.js index 65a1b4a61..a6573a787 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.js +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.js @@ -57,22 +57,55 @@ module.exports = function(RED) { } } + function createVMOpt(node, kind) { + var opt = { + filename: 'Function node'+kind+':'+node.id+(node.name?' ['+node.name+']':''), // filename for stack traces + displayErrors: true + // Using the following options causes node 4/6 to not include the line number + // in the stack output. So don't use them. + // lineOffset: -11, // line number offset to be used for stack traces + // columnOffset: 0, // column number offset to be used for stack traces + }; + return opt; + } + + function updateErrorInfo(err) { + if (err.stack) { + var stack = err.stack.toString(); + var m = /^([^:]+):([^:]+):(\d+).*/.exec(stack); + if (m) { + var line = parseInt(m[3]) -1; + var kind = "body:"; + if (/setup/.exec(m[1])) { + kind = "setup:"; + } + if (/cleanup/.exec(m[1])) { + kind = "cleanup:"; + } + err.message += " ("+kind+"line "+line+")"; + } + } + } + function FunctionNode(n) { RED.nodes.createNode(this,n); var node = this; - this.name = n.name; - this.func = n.func; + node.name = n.name; + node.func = n.func; + node.ini = n.initialize ? n.initialize : ""; + node.fin = n.finalize ? n.finalize : ""; var handleNodeDoneCall = true; + // Check to see if the Function appears to call `node.done()`. If so, // we will assume it is well written and does actually call node.done(). // Otherwise, we will call node.done() after the function returns regardless. - if (/node\.done\s*\(\s*\)/.test(this.func)) { + if (/node\.done\s*\(\s*\)/.test(node.func)) { handleNodeDoneCall = false; } var functionText = "var results = null;"+ - "results = (function(msg,__send__,__done__){ "+ + "results = (async function(msg,__send__,__done__){ "+ "var __msgid__ = msg._msgid;"+ "var node = {"+ "id:__node__.id,"+ @@ -87,11 +120,13 @@ module.exports = function(RED) { "send:function(msgs,cloneMsg){ __node__.send(__send__,__msgid__,msgs,cloneMsg);},"+ "done:__done__"+ "};\n"+ - this.func+"\n"+ + node.func+"\n"+ "})(msg,send,done);"; - this.topic = n.topic; - this.outstandingTimers = []; - this.outstandingIntervals = []; + var finScript = null; + var finOpt = null; + node.topic = n.topic; + node.outstandingTimers = []; + node.outstandingIntervals = []; var sandbox = { console:console, util:util, @@ -182,12 +217,12 @@ module.exports = function(RED) { arguments[0] = function() { sandbox.clearTimeout(timerId); try { - func.apply(this,arguments); + func.apply(node,arguments); } catch(err) { node.error(err,{}); } }; - timerId = setTimeout.apply(this,arguments); + timerId = setTimeout.apply(node,arguments); node.outstandingTimers.push(timerId); return timerId; }, @@ -203,12 +238,12 @@ module.exports = function(RED) { var timerId; arguments[0] = function() { try { - func.apply(this,arguments); + func.apply(node,arguments); } catch(err) { node.error(err,{}); } }; - timerId = setInterval.apply(this,arguments); + timerId = setInterval.apply(node,arguments); node.outstandingIntervals.push(timerId); return timerId; }, @@ -226,37 +261,48 @@ module.exports = function(RED) { sandbox.setTimeout(function(){ resolve(value); }, after); }); }; + sandbox.promisify = util.promisify; } var context = vm.createContext(sandbox); try { - this.script = vm.createScript(functionText, { - filename: 'Function node:'+this.id+(this.name?' ['+this.name+']':''), // filename for stack traces - displayErrors: true - // Using the following options causes node 4/6 to not include the line number - // in the stack output. So don't use them. - // lineOffset: -11, // line number offset to be used for stack traces - // columnOffset: 0, // column number offset to be used for stack traces - }); - this.on("input", function(msg,send,done) { - try { - var start = process.hrtime(); - context.msg = msg; - context.send = send; - context.done = done; + var iniScript = null; + var iniOpt = null; + if (node.ini && (node.ini !== "")) { + var iniText = "(async function () {\n"+node.ini +"\n})();"; + iniOpt = createVMOpt(node, " setup"); + iniScript = new vm.Script(iniText, iniOpt); + } + node.script = vm.createScript(functionText, createVMOpt(node, "")); + if (node.fin && (node.fin !== "")) { + var finText = "(function () {\n"+node.fin +"\n})();"; + finOpt = createVMOpt(node, " cleanup"); + finScript = new vm.Script(finText, finOpt); + } + var promise = Promise.resolve(); + if (iniScript) { + promise = iniScript.runInContext(context, iniOpt); + } - this.script.runInContext(context); - sendResults(this,send,msg._msgid,context.results,false); + function processMessage(msg, send, done) { + var start = process.hrtime(); + context.msg = msg; + context.send = send; + context.done = done; + + node.script.runInContext(context); + context.results.then(function(results) { + sendResults(node,send,msg._msgid,results,false); if (handleNodeDoneCall) { done(); } var duration = process.hrtime(start); var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100; - this.metric("duration", msg, converted); + node.metric("duration", msg, converted); if (process.env.NODE_RED_FUNCTION_TIME) { - this.status({fill:"yellow",shape:"dot",text:""+converted}); + node.status({fill:"yellow",shape:"dot",text:""+converted}); } - } catch(err) { + }).catch(err => { if ((typeof err === "object") && err.hasOwnProperty("stack")) { //remove unwanted part var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/); @@ -294,23 +340,67 @@ module.exports = function(RED) { else { done(JSON.stringify(err)); } + }); + } + + const RESOLVING = 0; + const RESOLVED = 1; + const ERROR = 2; + var state = RESOLVING; + var messages = []; + + node.on("input", function(msg,send,done) { + if(state === RESOLVING) { + messages.push({msg:msg, send:send, done:done}); + } + else if(state === RESOLVED) { + processMessage(msg, send, done); } }); - this.on("close", function() { + node.on("close", function() { + if (finScript) { + try { + finScript.runInContext(context, finOpt); + } + catch (err) { + node.error(err); + } + } while (node.outstandingTimers.length > 0) { clearTimeout(node.outstandingTimers.pop()); } while (node.outstandingIntervals.length > 0) { clearInterval(node.outstandingIntervals.pop()); } - this.status({}); + node.status({}); }); - } catch(err) { + + promise.then(function (v) { + var msgs = messages; + messages = []; + while (msgs.length > 0) { + msgs.forEach(function (s) { + processMessage(s.msg, s.send, s.done); + }); + msgs = messages; + messages = []; + } + state = RESOLVED; + }).catch((error) => { + messages = []; + state = ERROR; + node.error(error); + }); + + } + catch(err) { // eg SyntaxError - which v8 doesn't include line number information // so we can't do better than this - this.error(err); + updateErrorInfo(err); + node.error(err); } } RED.nodes.registerType("function",FunctionNode); RED.library.register("functions"); }; + diff --git a/packages/node_modules/@node-red/nodes/core/function/15-change.html b/packages/node_modules/@node-red/nodes/core/function/15-change.html index fc9fa8d84..59d4337c5 100644 --- a/packages/node_modules/@node-red/nodes/core/function/15-change.html +++ b/packages/node_modules/@node-red/nodes/core/function/15-change.html @@ -1,5 +1,10 @@ diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index 48a595ccc..cda1afadf 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -24,6 +24,8 @@ module.exports = function(RED) { this.op2 = n.op2 || "0"; this.op1type = n.op1type || "str"; this.op2type = n.op2type || "str"; + this.second = (n.outputs == 2) ? true : false; + this.topic = n.topic || "topic"; if (this.op1type === 'val') { if (this.op1 === 'true' || this.op1 === 'false') { @@ -76,6 +78,7 @@ module.exports = function(RED) { var node = this; node.topics = {}; + var npay = {}; var pendingMessages = []; var activeMessagePromise = null; var processMessageQueue = function(msg) { @@ -110,8 +113,15 @@ module.exports = function(RED) { processMessageQueue(msg); }); + var stat = function() { + var l = Object.keys(node.topics).length; + if (l === 0) { return {} } + else if (l === 1) { return {fill:"blue",shape:"dot"} } + else return {fill:"blue",shape:"dot",text:l}; + } + var processMessage = function(msg) { - var topic = msg.topic || "_none"; + var topic = RED.util.getMessageProperty(msg,node.topic) || "_none"; var promise; if (node.bytopic === "all") { topic = "_none"; } node.topics[topic] = node.topics[topic] || {}; @@ -119,12 +129,13 @@ module.exports = function(RED) { if (node.loop === true) { clearInterval(node.topics[topic].tout); } else { clearTimeout(node.topics[topic].tout); } delete node.topics[topic]; - node.status({}); + node.status(stat()); } else { + if (node.op2type === "payl") { npay[topic] = RED.util.cloneMessage(msg); } if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) { promise = Promise.resolve(); - if (node.op2type === "pay" || node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } + if (node.op2type === "pay") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); } else if (node.op2type !== "nul") { promise = new Promise((resolve,reject) => { @@ -186,22 +197,30 @@ module.exports = function(RED) { }); } promise.then(() => { - msg2.payload = node.topics[topic].m2; + if (node.op2type === "payl") { + if (node.second === true) { node.send([null,npay[topic]]); } + else { node.send(npay[topic]); } + delete npay[topic]; + } + else { + msg2.payload = node.topics[topic].m2; + if (node.second === true) { node.send([null,msg2]); } + else { node.send(msg2); } + } delete node.topics[topic]; - node.send(msg2); - node.status({}); + node.status(stat()); }).catch(err => { node.error(err); }); } else { delete node.topics[topic]; - node.status({}); + node.status(stat()); } }, node.duration); } } - node.status({fill:"blue",shape:"dot",text:" "}); + node.status(stat()); if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); } }); }); @@ -237,16 +256,17 @@ module.exports = function(RED) { } } delete node.topics[topic]; - node.status({}); - node.send(msg2); + node.status(stat()); + if (node.second === true) { node.send([null,msg2]); } + else { node.send(msg2); } }).catch(err => { node.error(err); }); }, node.duration); } - else { - if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } - } + // else { + // if (node.op2type === "payl") {node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } + // } } return Promise.resolve(); } @@ -259,7 +279,7 @@ module.exports = function(RED) { delete node.topics[t]; } } - node.status({}); + node.status(stat()); }); } RED.nodes.registerType("trigger",TriggerNode); diff --git a/packages/node_modules/@node-red/nodes/core/function/90-exec.js b/packages/node_modules/@node-red/nodes/core/function/90-exec.js index a5132230e..f5e7edd87 100644 --- a/packages/node_modules/@node-red/nodes/core/function/90-exec.js +++ b/packages/node_modules/@node-red/nodes/core/function/90-exec.js @@ -145,7 +145,7 @@ module.exports = function(RED) { if (error.signal) { msg3.payload.signal = error.signal; } if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); } else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); } - node.log('error:' + error); + if (RED.settings.verbose) { node.log('error:' + error); } } else if (node.oldrc === "false") { msg3 = RED.util.cloneMessage(msg); diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html index 0d16abdfc..c1b02e836 100644 --- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html +++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.html @@ -303,7 +303,7 @@ return this.name; } var b = this.broker; - if (b === "") { b = "undefined"; } + if (!b) { b = "undefined"; } var lab = ""; lab = (this.clientid?this.clientid+"@":"")+b; if (b.indexOf("://") === -1){ diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js index 6f682b279..7ab624222 100644 --- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js +++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js @@ -153,7 +153,12 @@ module.exports = function(RED) { this.brokerurl="mqtt://"; } if (this.broker !== "") { - this.brokerurl = this.brokerurl+this.broker+":"; + //Check for an IPv6 address + if (/(?:^|(?<=\s))(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(?=\s|$)/.test(this.broker)) { + this.brokerurl = this.brokerurl+"["+this.broker+"]:"; + } else { + this.brokerurl = this.brokerurl+this.broker+":"; + } // port now defaults to 1883 if unset. if (!this.port){ this.brokerurl = this.brokerurl+"1883"; @@ -464,6 +469,7 @@ module.exports = function(RED) { this.broker = n.broker; this.brokerConn = RED.nodes.getNode(this.broker); var node = this; + var chk = /[\+#]/; if (this.brokerConn) { this.status({fill:"red",shape:"ring",text:"node-red:common.status.disconnected"}); @@ -482,6 +488,7 @@ module.exports = function(RED) { } if ( msg.hasOwnProperty("payload")) { if (msg.hasOwnProperty("topic") && (typeof msg.topic === "string") && (msg.topic !== "")) { // topic must exist + if (chk.test(msg.topic)) { node.warn(RED._("mqtt.errors.invalid-topic")); } this.brokerConn.publish(msg, done); // send the message } else { node.warn(RED._("mqtt.errors.invalid-topic")); diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js index fc3fafd28..6cf243cf8 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httpin.js @@ -228,7 +228,7 @@ module.exports = function(RED) { var diff = process.hrtime(startAt); var ms = diff[0] * 1e3 + diff[1] * 1e-6; var metricResponseTime = ms.toFixed(3); - var metricContentLength = res._headers["content-length"]; + var metricContentLength = res.getHeader("content-length"); //assuming that _id has been set for res._metrics in HttpOut node! node.metric("response.time.millis", {_msgid:res._msgid} , metricResponseTime); node.metric("response.content-length.bytes", {_msgid:res._msgid} , metricContentLength); diff --git a/packages/node_modules/@node-red/nodes/core/network/22-websocket.html b/packages/node_modules/@node-red/nodes/core/network/22-websocket.html index e3d75e11f..523dc2629 100644 --- a/packages/node_modules/@node-red/nodes/core/network/22-websocket.html +++ b/packages/node_modules/@node-red/nodes/core/network/22-websocket.html @@ -163,7 +163,7 @@ if (root === "") { $("#node-config-ws-tip").hide(); } else { - $("#node-config-ws-path").html(root); + $("#node-config-ws-path").html(RED._("node-red:websocket.tip.path2", { path: root })); $("#node-config-ws-tip").show(); } } @@ -235,7 +235,7 @@
    -

    .

    +

    diff --git a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.html index e2786b9da..ae0eaedda 100644 --- a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.html @@ -170,15 +170,14 @@ $("#node-input-port-row").hide(); $("#node-input-host-row").hide(); $("#node-input-end-row").hide(); + } else if (sockettype == "client"){ + $("#node-input-port-row").show(); + $("#node-input-host-row").show(); + $("#node-input-end-row").show(); } else { $("#node-input-port-row").show(); - $("#node-input-end-row").show(); - } - - if (sockettype == "client") { - $("#node-input-host-row").show(); - } else { $("#node-input-host-row").hide(); + $("#node-input-end-row").show(); } }; updateOptions(); diff --git a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js index e52e9c382..655e72c1e 100644 --- a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js @@ -74,7 +74,7 @@ module.exports = function(RED) { buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : ""; node.connected = true; node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port})); - node.status({fill:"green",shape:"dot",text:"common.status.connected"}); + node.status({fill:"green",shape:"dot",text:"common.status.connected",_session:{type:"tcp",id:id}}); }); client.setKeepAlive(true,120000); connectionPool[id] = client; @@ -121,7 +121,7 @@ module.exports = function(RED) { client.on('close', function() { delete connectionPool[id]; node.connected = false; - node.status({fill:"red",shape:"ring",text:"common.status.disconnected"}); + node.status({fill:"red",shape:"ring",text:"common.status.disconnected",_session:{type:"tcp",id:id}}); if (!node.closing) { if (end) { // if we were asked to close then try to reconnect once very quick. end = false; diff --git a/packages/node_modules/@node-red/nodes/core/network/32-udp.js b/packages/node_modules/@node-red/nodes/core/network/32-udp.js index 60e9bec08..894c19d77 100644 --- a/packages/node_modules/@node-red/nodes/core/network/32-udp.js +++ b/packages/node_modules/@node-red/nodes/core/network/32-udp.js @@ -180,6 +180,10 @@ module.exports = function(RED) { node.tout = setTimeout(function() { if (udpInputPortsInUse[p]) { sock = udpInputPortsInUse[p]; + if (node.multicast != "false") { + sock.setBroadcast(true); + sock.setMulticastLoopback(false); + } node.log(RED._("udp.status.re-use",{outport:node.outport,host:node.addr,port:node.port})); if (node.iface) { node.status({text:n.iface+" : "+node.iface}); } } diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html index 2e4ca0dd7..80778ac1b 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html @@ -28,11 +28,15 @@
    -   
    +   


    + +
    + +
    @@ -45,8 +49,13 @@
    - -
    @@ -69,12 +78,14 @@ sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)}, //quo: {value:'"',required:true}, hdrin: {value:""}, - hdrout: {value:""}, + hdrout: {value:"none"}, multi: {value:"one",required:true}, ret: {value:'\\n'}, temp: {value:""}, skip: {value:"0"}, - strings: {value:true} + strings: {value:true}, + include_empty_strings: {value:""}, + include_null_values: {value:""} }, inputs:1, outputs:1, @@ -86,6 +97,8 @@ return this.name?"node_label_italic":""; }, oneditprepare: function() { + if (this.hdrout === false) { this.hdrout = "none"; $("#node-input-hdrout").val("none"); } + if (this.hdrout === true) { this.hdrout = "all"; $("#node-input-hdrout").val("all");} if (this.strings === undefined) { this.strings = true; $("#node-input-strings").prop('checked', true); } if (this.skip === undefined) { this.skip = 0; $("#node-input-skip").val("0");} $("#node-input-skip").spinner({ min:0 }); diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index 290b159a9..15c16da13 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -26,12 +26,16 @@ module.exports = function(RED) { this.lineend = "\n"; this.multi = n.multi || "one"; this.hdrin = n.hdrin || false; - this.hdrout = n.hdrout || false; + this.hdrout = n.hdrout || "none"; this.goodtmpl = true; this.skip = parseInt(n.skip || 0); this.store = []; this.parsestrings = n.strings; + this.include_empty_strings = n.include_empty_strings || false; + this.include_null_values = n.include_null_values || false; if (this.parsestrings === undefined) { this.parsestrings = true; } + if (this.hdrout === false) { this.hdrout = "none"; } + if (this.hdrout === true) { this.hdrout = "all"; } var tmpwarn = true; var node = this; @@ -49,14 +53,22 @@ module.exports = function(RED) { return col; } node.template = clean(node.template); + node.hdrSent = false; this.on("input", function(msg) { + if (msg.hasOwnProperty("reset")) { + node.hdrSent = false; + } if (msg.hasOwnProperty("payload")) { if (typeof msg.payload == "object") { // convert object to CSV string try { var ou = ""; - if (node.hdrout) { + if (node.hdrout !== "none" && node.hdrSent === false) { + if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) { + node.template = clean((msg.columns || "").split(",")); + } ou += node.template.join(node.sep) + node.ret; + if (node.hdrout === "once") { node.hdrSent = true; } } if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; } for (var s = 0; s < msg.payload.length; s++) { @@ -75,13 +87,15 @@ module.exports = function(RED) { ou += msg.payload[s].join(node.sep) + node.ret; } else { + if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) { + node.template = clean((msg.columns || "").split(",")); + } if ((node.template.length === 1) && (node.template[0] === '')) { /* istanbul ignore else */ if (tmpwarn === true) { // just warn about missing template once node.warn(RED._("csv.errors.obj_csv")); tmpwarn = false; } - ou = ""; for (var p in msg.payload[0]) { /* istanbul ignore else */ if (msg.payload[0].hasOwnProperty(p)) { @@ -125,6 +139,7 @@ module.exports = function(RED) { } } msg.payload = ou; + msg.columns = node.template.join(','); if (msg.payload !== '') { node.send(msg); } } catch(e) { node.error(e,msg); } @@ -173,20 +188,29 @@ module.exports = function(RED) { } else if ((line[i] === node.sep) && f) { // if it is the end of the line then finish if (!node.goodtmpl) { node.template[j] = "col"+(j+1); } - if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "" ) ) { - if ( (node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); } - o[node.template[j]] = k[j]; + if ( node.template[j] && (node.template[j] !== "") ) { + // if no value between separators ('1,,"3"...') or if the line beings with separator (',1,"2"...') treat value as null + if (line[i-1] === node.sep || line[i-1].includes('\n','\r')) k[j] = null; + if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); } + if (node.include_null_values && k[j] === null) o[node.template[j]] = k[j]; + if (node.include_empty_strings && k[j] === "") o[node.template[j]] = k[j]; + if (k[j] !== null && k[j] !== "") o[node.template[j]] = k[j]; } j += 1; - k[j] = ""; + // if separator is last char in processing string line (without end of line), add null value at the end - example: '1,2,3\n3,"3",' + k[j] = line.length - 1 === i ? null : ""; } - else if ((line[i] === "\n") || (line[i] === "\r")) { // handle multiple lines + else if (((line[i] === "\n") || (line[i] === "\r")) && f) { // handle multiple lines //console.log(j,k,o,k[j]); if (!node.goodtmpl) { node.template[j] = "col"+(j+1); } - if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) { - if ( (node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); } - else { k[j].replace(/\r$/,''); } - o[node.template[j]] = k[j]; + if ( node.template[j] && (node.template[j] !== "") ) { + // if separator before end of line, set null value ie. '1,2,"3"\n1,2,\n1,2,3' + if (line[i-1] === node.sep) k[j] = null; + if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); } + else { if (k[j] !== null) k[j].replace(/\r$/,''); } + if (node.include_null_values && k[j] === null) o[node.template[j]] = k[j]; + if (node.include_empty_strings && k[j] === "") o[node.template[j]] = k[j]; + if (k[j] !== null && k[j] !== "") o[node.template[j]] = k[j]; } if (JSON.stringify(o) !== "{}") { // don't send empty objects a.push(o); // add to the array @@ -202,17 +226,21 @@ module.exports = function(RED) { } } // Finished so finalize and send anything left - //console.log(j,k,o,k[j]); + if (f === false) { node.warn(RED._("csv.errors.bad_csv")); } if (!node.goodtmpl) { node.template[j] = "col"+(j+1); } - if ( node.template[j] && (node.template[j] !== "") && (k[j] !== "") ) { - if ( (node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); } - else { k[j].replace(/\r$/,''); } - o[node.template[j]] = k[j]; + + if ( node.template[j] && (node.template[j] !== "") ) { + if ( (k[j] !== null && node.parsestrings === true) && reg.test(k[j]) ) { k[j] = parseFloat(k[j]); } + else { if (k[j] !== null) k[j].replace(/\r$/,''); } + if (node.include_null_values && k[j] === null) o[node.template[j]] = k[j]; + if (node.include_empty_strings && k[j] === "") o[node.template[j]] = k[j]; + if (k[j] !== null && k[j] !== "") o[node.template[j]] = k[j]; } if (JSON.stringify(o) !== "{}") { // don't send empty objects a.push(o); // add to the array } var has_parts = msg.hasOwnProperty("parts"); + if (node.multi !== "one") { msg.payload = a; if (has_parts) { @@ -221,12 +249,14 @@ module.exports = function(RED) { } if (msg.parts.index + 1 === msg.parts.count) { msg.payload = node.store; + msg.columns = node.template.filter(val => val).join(','); delete msg.parts; node.send(msg); node.store = []; } } else { + msg.columns = node.template.filter(val => val).join(','); node.send(msg); // finally send the array } } @@ -234,6 +264,7 @@ module.exports = function(RED) { var len = a.length; for (var i = 0; i < len; i++) { var newMessage = RED.util.cloneMessage(msg); + newMessage.columns = node.template.filter(val => val).join(','); newMessage.payload = a[i]; if (!has_parts) { newMessage.parts = { @@ -259,7 +290,11 @@ module.exports = function(RED) { } else { node.warn(RED._("csv.errors.csv_js")); } } - else { node.send(msg); } // If no payload - just pass it on. + else { + if (!msg.hasOwnProperty("reset")) { + node.send(msg); // If no payload and not reset - just pass it on. + } + } }); } RED.nodes.registerType("csv",CSVNode); diff --git a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js index 5d93c8faf..ced637555 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js @@ -292,7 +292,6 @@ module.exports = function(RED) { reduceMessageGroup(node,msgs,exp,fixup,count,result,done); } }); - } function reduceAndSendGroup(node, group, done) { var is_right = node.reduce_right; @@ -331,7 +330,7 @@ module.exports = function(RED) { var pending_count = node.pending_count; var gid = msg.parts.id; var count; - if(!pending.hasOwnProperty(gid)) { + if (!pending.hasOwnProperty(gid)) { if(parts.hasOwnProperty('count')) { count = msg.parts.count; } @@ -361,7 +360,6 @@ module.exports = function(RED) { } return done(); } - if (msgs.length === group.count) { delete pending[gid]; pending_count -= msgs.length; @@ -408,7 +406,7 @@ module.exports = function(RED) { if (this.joinerType === "str") { this.joiner = this.joiner.replace(/\\n/g,"\n").replace(/\\r/g,"\r").replace(/\\t/g,"\t").replace(/\\e/g,"\e").replace(/\\f/g,"\f").replace(/\\0/g,"\0"); } else if (this.joinerType === "bin") { - var joinArray = JSON.parse(n.joiner) + var joinArray = JSON.parse(n.joiner || "[]"); if (Array.isArray(joinArray)) { this.joiner = Buffer.from(joinArray); } else { @@ -429,7 +427,7 @@ module.exports = function(RED) { var completeSend = function(partId) { var group = inflight[partId]; - clearTimeout(group.timeout); + if (group.timeout) { clearTimeout(group.timeout); } if ((node.accumulate !== true) || group.msg.hasOwnProperty("complete")) { delete inflight[partId]; } if (group.type === 'array' && group.arrayLen > 1) { var newArray = []; @@ -448,6 +446,9 @@ module.exports = function(RED) { buffers.push(joinBuffer); bufferLen += joinBuffer.length; } + if (!Buffer.isBuffer(group.payload[i])) { + group.payload[i] = Buffer.from(group.payload[i]); + } buffers.push(group.payload[i]); bufferLen += group.payload[i].length; } @@ -629,7 +630,13 @@ module.exports = function(RED) { var group = inflight[partId]; if (payloadType === 'buffer') { if (property !== undefined) { - inflight[partId].bufferLen += property.length; + if (Buffer.isBuffer(property) || (typeof property === "string") || Array.isArray(property)) { + inflight[partId].bufferLen += property.length; + } + else { + node.error(RED._("join.errors.invalid-type",{error:(typeof property)}),msg); + return; + } } } if (payloadType === 'object') { diff --git a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js index 495862af8..77574f222 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.js @@ -179,6 +179,11 @@ module.exports = function(RED) { } node.pending = []; this.on("input", function(msg) { + if (msg.hasOwnProperty("reset")) { + node.pending = []; + node.pending_count = 0; + return; + } var queue = node.pending; queue.push(msg); node.pending_count++; @@ -204,11 +209,26 @@ module.exports = function(RED) { var interval = Number(n.interval || "0") *1000; var allow_empty_seq = n.allowEmptySequence; node.pending = [] - var timer = setInterval(function() { + function msgHandler() { send_interval(node, allow_empty_seq); node.pending_count = 0; - }, interval); + } + var timer = undefined; + if (interval > 0) { + timer = setInterval(msgHandler, interval); + } this.on("input", function(msg) { + if (msg.hasOwnProperty("reset")) { + if (timer !== undefined) { + clearInterval(timer); + } + node.pending = []; + node.pending_count = 0; + if (interval > 0) { + timer = setInterval(msgHandler, interval); + } + return; + } node.pending.push(msg); node.pending_count++; var max_msgs = max_kept_msgs_count(node); @@ -219,7 +239,9 @@ module.exports = function(RED) { } }); this.on("close", function() { - clearInterval(timer); + if (timer !== undefined) { + clearInterval(timer); + } node.pending = []; node.pending_count = 0; }); @@ -230,6 +252,11 @@ module.exports = function(RED) { }); node.pending = {}; this.on("input", function(msg) { + if (msg.hasOwnProperty("reset")) { + node.pending = {}; + node.pending_count = 0; + return; + } concat_msg(node, msg); }); this.on("close", function() { diff --git a/packages/node_modules/@node-red/nodes/locales/de/messages.json b/packages/node_modules/@node-red/nodes/locales/de/messages.json index c36c309f3..58fce3a92 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/de/messages.json @@ -397,7 +397,7 @@ "message" : "gesamte Nachricht", "tip" : { "path1" : "Standardmäßig enthält Nutzdaten die Daten, die über einen Websocket gesendet oder von einem Websocket empfangen werden. Der Listener kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge sendet oder empfängt.", - "path2" : "Dieser Pfad ist relativ zu ", + "path2" : "Dieser Pfad ist relativ zu __path__.", "url1" : "URL sollte ws: / & #47; oder wss: / & #47; Schema verwenden und auf einen vorhandenen Websocket-Listener verweisen.", "url2" : "Standardmäßig enthält Nutzdaten die Daten, die über einen Websocket gesendet oder von einem Websocket empfangen werden. Der Client kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge sendet oder empfängt." }, diff --git a/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html b/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html index d69229820..0192c4e13 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html +++ b/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html @@ -23,13 +23,13 @@ @@ -37,30 +37,6 @@

    Dieser Konfigurations-Node erstellt einen WebSocket Server-Endpunkt unter Verwendung des angegebenen Pfades.

    - - - diff --git a/packages/node_modules/@node-red/nodes/locales/de/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/de/parsers/70-XML.html index 4740ea204..1842ed135 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/parsers/70-XML.html +++ b/packages/node_modules/@node-red/nodes/locales/de/parsers/70-XML.html @@ -20,6 +20,10 @@
    payloadObjekt | String
    Ein JavaScript Objekt oder ein XML String.
    +
    options Objekt
    +
    This optional property can be used to pass in any of the options supported by the underlying + library used to convert to and from XML. See the xml2js docs + for more information.

    Ausgaben

    @@ -30,10 +34,6 @@
  • Wenn die Eingabe ein JavaScript-Objekt ist, wird versucht ein XML-String zu erstellen.
  • -
    Optionen Objekt
    -
    This optional property can be used to pass in any of the options supported by the underlying - library used to convert to and from XML. See the xml2js docs - for more information.

    Details

    Bei der Konvertierung zwischen XML und einem Objekt werden standardmäßig alle XML-Attribute als Eigenschaft namens $ hinzugefügt. diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html index 28bc76f3a..93a1d6f51 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html @@ -15,12 +15,14 @@ --> diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index e1d7c6368..e7b5605a4 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -37,6 +37,7 @@ "stopped": "stopped", "failed": "Inject failed: __error__", "label": { + "properties": "Properties", "repeat": "Repeat", "flow": "flow context", "global": "global context", @@ -51,7 +52,7 @@ "string": "string", "boolean": "boolean", "number": "number", - "Array": "Array", + "Array": "array", "invalid": "Invalid JSON Object" }, "timestamp": "timestamp", @@ -79,11 +80,11 @@ "on": "on", "onstart": "Inject once after", "onceDelay": "seconds, then", - "tip": "Note: \"interval between times\" and \"at a specific time\" will use cron.
    \"interval\" should be 596 hours or less.
    See info box for details.", "success": "Successfully injected: __label__", "errors": { "failed": "inject failed, see log for details", - "toolong": "Interval too large" + "toolong": "Interval too large", + "invalid-expr": "Invalid JSONata expression: __error__" } }, "catch": { @@ -117,10 +118,12 @@ }, "debug": { "output": "Output", + "status": "status", "none": "None", "invalid-exp": "Invalid JSONata expression: __error__", "msgprop": "message property", "msgobj": "complete msg object", + "autostatus": "automatic", "to": "To", "debtab": "debug tab", "tabcon": "debug tab and console", @@ -207,8 +210,14 @@ "function": "", "label": { "function": "Function", + "initialize": "Setup", + "finalize": "Close", "outputs": "Outputs" }, + "text": { + "initialize": "// Code added here will be run once\n// whenever the node is deployed.\n", + "finalize": "// Code added here will be run when the\n// node is being stopped or re-deployed.\n" + }, "error": { "inputListener":"Cannot add listener to 'input' event within Function", "non-message-returned":"Function tried to send a message of type __type__" @@ -302,7 +311,7 @@ "wait-for": "wait for", "wait-loop": "resend it every", "for": "Handling", - "bytopics": "each msg.topic independently", + "bytopics": "each", "alltopics": "all messages", "duration": { "ms": "Milliseconds", @@ -311,6 +320,7 @@ "h": "Hours" }, "extend": " extend delay if new message arrives", + "second": " send second message to separate output", "label": { "trigger": "trigger", "trigger-block": "trigger & block", @@ -455,7 +465,7 @@ "message": "entire message", "tip": { "path1": "By default, payload will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.", - "path2": "This path will be relative to ", + "path2": "This path will be relative to __path__.", "url1": "URL should use ws:// or wss:// scheme and point to an existing websocket listener.", "url2": "By default, payload will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string." }, @@ -698,7 +708,9 @@ "output": "Output", "includerow": "include column name row", "newline": "Newline", - "usestrings": "parse numerical values" + "usestrings": "parse numerical values", + "include_empty_strings": "include empty strings", + "include_null_values": "include null values" }, "placeholder": { "columns": "comma-separated column names" @@ -721,9 +733,15 @@ "mac": "Mac (\\r)", "windows": "Windows (\\r\\n)" }, + "hdrout": { + "none": "never send column headers", + "all": "always send column headers", + "once": "send headers once, until msg.reset" + }, "errors": { "csv_js": "This node only handles CSV strings or js objects.", - "obj_csv": "No columns template specified for object -> CSV." + "obj_csv": "No columns template specified for object -> CSV.", + "bad_csv": "Malformed CSV data - output probably corrupt." } }, "html": { @@ -894,7 +912,8 @@ "fixup": "Fix-up exp" }, "errors": { - "invalid-expr": "Invalid JSONata expression: __error__" + "invalid-expr": "Invalid JSONata expression: __error__", + "invalid-type": "Cannot join __error__ to buffer" } }, "sort" : { diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html index 31bdc4907..173f003f7 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html @@ -41,5 +41,5 @@ wait a fixed timeout from first reply and then return, sit and wait for data, or send then close the connection immediately, without waiting for a reply.

    The response will be output in msg.payload as a buffer, so you may want to .toString() it.

    -

    If you leave tcp host or port blank they must be set by using the msg.host and msg.port properties.

    +

    If you leave tcp host or port blank they must be set by using the msg.host and msg.port properties in every message sent to the node.

    diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html index 5b0731cb1..6f16f5b72 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html @@ -38,8 +38,13 @@

    The column template can contain an ordered list of column names. When converting CSV to an object, the column names will be used as the property names. Alternatively, the column names can be taken from the first row of the CSV.

    When converting to CSV, the column template is used to identify which properties to extract from the object and in what order.

    +

    If the template is blank then the node can use a simple comma separated list of properties supplied in msg.columns to + determine what to extract. If that is not present then all the object properties are ouput in the order in which they are found.

    If the input is an array then the columns template is only used to optionally generate a row of column titles.

    -

    The node can accept a multi-part input as long as the parts property is set correctly.

    +

    If 'parse numerical values' option is checked, string numerical values will be returned as numbers, ie. middle value '1,"1.5",2'.

    +

    If 'include empty strings' option is checked, empty strings will be returned in result, ie. middle value '"1","",3'.

    +

    If 'include null values' option is checked, null values will be returned in result, ie. middle value '"1",,3'.

    +

    The node can accept a multi-part input as long as the parts property is set correctly, for example from a file-in node or split node.

    If outputting multiple messages they will have their parts property set and form a complete message sequence.

    Note: the column template must be comma separated - even if a different separator is chosen for the data.

    diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-XML.html index 07513907d..e378a6358 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-XML.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-XML.html @@ -20,6 +20,10 @@
    payloadobject | string
    A JavaScript object or XML string.
    +
    options object
    +
    This optional property can be used to pass in any of the options supported by the underlying + library used to convert to and from XML. See the xml2js docs + for more information.

    Outputs

    @@ -30,10 +34,6 @@
  • If the input is a JavaScript object it tries to build an XML string.
  • -
    options object
    -
    This optional property can be used to pass in any of the options supported by the underlying - library used to convert to and from XML. See the xml2js docs - for more information.

    Details

    When converting between XML and an object, any XML attributes are added as a property named $ by default. diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html index 118df70af..c9c3e3070 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html @@ -91,7 +91,8 @@

    complete
    -
    If set, the node will send its output message in its current state.
    +
    If set, the node will append the payload, and then send the output message in its current state. + If you don't wish to append the payload, delete it from the msg.

    Details

    diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/19-batch.html index 0564957fc..dbf00d287 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/19-batch.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/19-batch.html @@ -39,4 +39,5 @@

    This node will buffer messages internally in order to work across sequences. The runtime setting nodeMessageBufferMaxLength can be used to limit how many messages nodes will buffer.

    +

    If a message is received with the msg.reset property set, the buffered messages are deleted and not sent.

    diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html index 5933f9c2d..fe2bbc324 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html @@ -49,12 +49,6 @@
    The contents of the file as either a string or binary buffer.
    filename string
    If not configured in the node, this optional property sets the name of the file to be read.
    -
    error object
    -
    deprecated: If enabled in the node, when the node hits an error - reading the file, it will send a message with no payload - and this error property set to the error details. This - mode of behaviour is deprecated and not enabled by default for new - instances of the node. See below for more information.

    Details

    The filename should be an absolute path, otherwise it will be relative to @@ -65,11 +59,5 @@

    When split into multiple messages, each message will have a parts property set, forming a complete message sequence.

    Encoding of input data can be specified from list of encodings if output format is string.

    -

    Legacy error handling

    -

    Before Node-RED 0.17, if this node hit an error whilst reading the file, it would - send a message with no msg.payload and msg.error set to the - details of the error. This is a deprecated mode of behaviour for the node that new - instances will not do. If required, this mode can be re-enabled within the node - configuration.

    Errors should be caught and handled using a Catch node.

    diff --git a/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html index 2744cf53a..b348512df 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/function/10-function.html @@ -15,10 +15,11 @@ --> diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json old mode 100755 new mode 100644 index adc033390..cc88965dd --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -37,6 +37,7 @@ "stopped": "stopped", "failed": "inject失敗: __error__", "label": { + "properties": "プロパティ", "repeat": "繰り返し", "flow": "フローコンテクスト", "global": "グローバルコンテクスト", @@ -79,11 +80,11 @@ "on": "曜日", "onstart": "Node-RED起動の", "onceDelay": "秒後、以下を行う", - "tip": "注釈: 「指定した時間間隔、日時」と「指定した日時」はcronを使用します。
    「時間間隔」には596時間より小さな値を指定します。
    詳細はノードの「情報」を確認してください。", "success": "inject処理を実行しました: __label__", "errors": { "failed": "inject処理が失敗しました。詳細はログを確認してください。", - "toolong": "時間間隔が大き過ぎます" + "toolong": "時間間隔が大き過ぎます", + "invalid-expr": "JSONata式が不正: __error__" } }, "catch": { @@ -104,7 +105,7 @@ "status": "status: 全て", "statusNodes": "status: __number__", "label": { - "source": "状態取得元", + "source": "ステータス取得元", "sortByType": "型で並べ替え" }, "scope": { @@ -117,16 +118,18 @@ }, "debug": { "output": "対象", + "status": "ステータス", "none": "無し", "invalid-exp": "JSONata式が不正: __error__", "msgprop": "メッセージプロパティ", "msgobj": "msgオブジェクト全体", + "autostatus": "自動", "to": "出力先", "debtab": "デバッグタブ", "tabcon": "デバッグタブとコンソール", "toSidebar": "デバッグウィンドウ", "toConsole": "システムコンソール", - "toStatus": "ノード状態 (32 文字)", + "toStatus": "ノードステータス(32 文字)", "severity": "Level", "notification": { "activated": "有効化しました: __label__", @@ -207,8 +210,14 @@ "function": "", "label": { "function": "コード", + "initialize": "初期化処理", + "finalize": "終了処理", "outputs": "出力数" }, + "text": { + "initialize": "// ここに記述したコードは、ノードをデプロイした時に\n// 一度だけ実行されます。\n", + "finalize": "// ここに記述したコードは、ノードを停止したとき\n// もしくは再デプロイしたときに実行されます。\n" + }, "error": { "inputListener": "コード内で'input'イベントのリスナを設定できません", "non-message-returned": "Functionノードが __type__ 型のメッセージ送信を試みました" @@ -302,7 +311,7 @@ "wait-for": "指定した時間待機", "wait-loop": "指定した時間間隔毎に送信を繰り返す", "for": "処理対象", - "bytopics": "msg.topic毎", + "bytopics": "毎", "alltopics": "全メッセージ", "duration": { "ms": "ミリ秒", @@ -310,7 +319,8 @@ "m": "分", "h": "時間" }, - "extend": " 新たなメッセージを受け取った時に遅延を延長", + "extend": "新たなメッセージを受け取った時に遅延を延長", + "second": "2つ目の出力端子に2番目のメッセージを送信", "label": { "trigger": "trigger", "trigger-block": "trigger & block", @@ -395,7 +405,7 @@ "doc": "Docs", "return": "出力形式", "upload": "ファイルのアップロード", - "status": "状態コード", + "status": "ステータスコード", "headers": "ヘッダ", "other": "その他", "paytoqs": "msg.payloadをクエリパラメータに追加", @@ -455,7 +465,7 @@ "message": "メッセージ全体を送信/受信", "tip": { "path1": "標準では payload がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。", - "path2": "This path will be relative to ", + "path2": "このパスは __path__ の相対パスになります。", "url1": "URLには ws:// または wss:// スキーマを使用して、存在するwebsocketリスナを設定してください。", "url2": "標準では payload がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。" }, @@ -696,7 +706,9 @@ "output": "出力", "includerow": "1行目を列名とする", "newline": "改行コード", - "usestrings": "数値を変換する" + "usestrings": "数値を変換する", + "include_empty_strings": "空の文字を含む", + "include_null_values": "null値を含む" }, "placeholder": { "columns": "コンマ区切りで列名を入力" @@ -719,9 +731,15 @@ "mac": "Mac (\\r)", "windows": "Windows (\\r\\n)" }, + "hdrout": { + "none": "カラムヘッダを送信しない", + "all": "カラムヘッダを常に送信する", + "once": "ヘッダを一度だけ送信する(msg.resetの受け付けると再送)" + }, "errors": { "csv_js": "本ノードが処理できる形式は、CSV文字列またはJSONのみです", - "obj_csv": "オブジェクトをCSVへ変換する際の列名が設定されていません" + "obj_csv": "オブジェクトをCSVへ変換する際の列名が設定されていません", + "bad_csv": "不正なCSVデータ - 出力の修正を試みました" } }, "html": { @@ -892,7 +910,8 @@ "fixup": "最終調整式" }, "errors": { - "invalid-expr": "JSONata式が不正: __error__" + "invalid-expr": "JSONata式が不正: __error__", + "invalid-type": "__error__ をバッファに連結できません" } }, "sort": { diff --git a/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html index 1ab60b163..e63c93ae5 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/network/31-tcpin.html @@ -31,5 +31,5 @@

    シンプルなTCPリクエストノード。msg.payloadをサーバのTCPポートに送信し、レスポンスを待ちます。

    サーバに接続、"リクエスト"送信、"レスポンス"受信を行います。固定長の文字数、指定文字へのマッチ、最初のリプライの到着から指定した時間待つ、データの到着待ち、データ送信を行いリプライを待たず接続を即時解除、などから動作を選択できます。

    レスポンスはバッファ形式でmsg.payloadに出力されます。文字列として扱いには、.toString()を使用してください。

    -

    TCPホストのポート番号設定を空にした場合、msg.hostおよびmsg.portプロパティを設定しなくてはなりません。

    +

    TCPホストのポート番号設定を空にした場合、本ノードに送信される全てのメッセージにおいてmsg.hostおよびmsg.portプロパティを設定しなくてはなりません。

    diff --git a/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-CSV.html index 38286e438..f1dfaffcd 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-CSV.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-CSV.html @@ -26,8 +26,7 @@
    payloadオブジェクト | 配列 | 文字列
      -
    • 入力が文字列の場合、CSVとして解釈し、CSVの各行をキー/バリューとしたJavaScriptオブジェクトを生成します。 - 各行毎にメッセージを送信するかオブジェクトの配列からなる一つのメッセージを送信するかを選択できます。
    • +
    • 入力が文字列の場合、CSVとして解釈し、CSVの各行をキー/バリューとしたJavaScriptオブジェクトを生成します。各行毎にメッセージを送信するかオブジェクトの配列からなる一つのメッセージを送信するかを選択できます。
    • 入力がJavaScriptオブジェクトの場合、CSV文字列への変換を行います。
    • 入力が基本型の配列の場合、1行のCSV文字列へ変換します。
    • 入力が配列の配列、もしくは、オブジェクトの配列の場合、複数行のCSV文字列へ変換します。
    • @@ -37,8 +36,12 @@

      詳細

      「列名」にカラム名のリストを指定することができます。CSVからオブジェクトに変換を行う際、カラム名をプロパティ名として使用します。「列名」の代わりに、CSVデータの1行目にカラム名を含めることもできます。

      CSVへの変換を行う際には、オブジェクトから取り出すべきプロパティとその順序を「列名」を参照して決めます。

      +

      列名がない場合、本ノードはmsg.columnsプロパティの単純なコンマ区切りリストを使用して、何を抽出するかを決定します。もしそれが存在しない場合、すべてのオブジェクトプロパティを見つけた順序で出力します。

      入力が配列の場合には、「列名」はカラム名を表す行の出力指定がされた場合だけ用います。

      -

      partsプロパティが正しく設定されている場合、メッセージ列を入力として受け付けます。

      +

      「数値を変換する」オプションがチェックされている場合、文字列型の数値が数値として返されます。つまり「1,"1.5",2」の真ん中の値が数値になります。

      +

      「空の文字を含む」オプションがチェックされている場合、空の文字列が結果に返されます。つまり「"1","",3」の真ん中の値が空の文字列になります。

      +

      「null値を含む」オプションがチェックされている場合、null値が結果に返されます。つまり「"1",,3」の真ん中の値がnullになります。

      +

      file-inノードやsplitノードが出力するメッセージの様に、partsプロパティが正しく設定されている場合、メッセージ列を入力として受け付けます。

      CSVを複数のメッセージに変換して出力する場合、出力がメッセージ列となるようpartsプロパティを設定します。

      注: カンマ以外の区切り文字を設定した場合であっても、「列名」はカンマ区切りとしてください。

      diff --git a/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-XML.html index 5c882a2ec..1149a072a 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-XML.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/parsers/70-XML.html @@ -20,6 +20,8 @@
      payloadオブジェクト | 文字列
      JavaScriptオブジェクトもしくはXML文字列
      +
      options オブジェクト
      +
      内部で用いているXMLへの変換ライブラリに対してオプションを渡すことができます。詳しくはthe xml2js docsを参照してください。

      出力

      @@ -30,8 +32,6 @@
    • 入力がJavaScriptオブジェクトの場合、XML文字列に変換します。
    -
    options オブジェクト
    -
    内部で用いているXMLへの変換ライブラリに対してオプションを渡すことができます。詳しくはthe xml2js docsを参照してください。

    詳細

    XMLとオブジェクトの間での変換を行う場合、デフォルトでは、XML属性は$という名称のプロパティに追加します。 diff --git a/packages/node_modules/@node-red/nodes/locales/ja/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/ja/sequence/17-split.html index 7702704df..466960a39 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/sequence/17-split.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/sequence/17-split.html @@ -79,7 +79,7 @@

    complete
    -
    設定されている場合、保持しているメッセージを結合して送信します。
    +
    設定されている場合、本ノードはペイロードを追加し、保持しているメッセージを送信します。ペイロードを追加したくない場合は、msgから削除してください。

    詳細

    diff --git a/packages/node_modules/@node-red/nodes/locales/ja/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/locales/ja/sequence/19-batch.html index 03f2f3f17..1306bdfa7 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/sequence/19-batch.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/sequence/19-batch.html @@ -31,4 +31,6 @@

    メッセージの蓄積

    このノードの処理ではメッセージ列の処理のためメッセージを内部に蓄積します。settings.jsnodeMessageBufferMaxLengthを指定することで蓄積するメッセージの最大値を制限することができます。

    +

    メッセージがmsg.resetプロパティを持つ場合、蓄積したメッセージを削除し送信を行いません。

    + diff --git a/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html index bc971049e..21d4af9be 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html @@ -44,8 +44,6 @@
    ファイルの内容を文字列もしくはバッファで表現します
    filename 文字列
    読み出し対象のファイル名をノードに設定していない場合、このプロパティでファイルを指定します
    -
    error オブジェクト
    -
    非推奨: 設定で有効にした場合、ファイルの読み込み時にエラーが発生するとpayloadを持たずerrorプロパティにエラーの詳細情報を設定したメッセージを送信します。この動作モードは非推奨であり、新しいノード実装ではデフォルトでは無効としています。詳細については、以下を参照してください。

    詳細

    ファイルネームは絶対パスでの指定を推奨します。絶対パスを指定しない場合は、Node-REDプロセスのワーキングディレクトリからの相対パスとして扱います。

    @@ -53,7 +51,5 @@

    テキストファイルの場合、行毎に分割して各々メッセージを送信することができます。また、バイナリファイルの場合、小さな塊のバッファに分割して送信できます。バッファの分割単位はオペレーティングシステム依存ですが、一般に64k(Linux/Mac)もしくは41k(Windows)です。

    複数のメッセージに分割する場合、各メッセージにはpartsプロパティが設定され、メッセージ列を構成します。

    出力形式が文字列の場合、入力データのエンコーディングをエンコーディングリストから選択できます。

    -

    旧式のエラー処理

    -

    Node-RED 0.17より前の版では、ファイルの読み込み時にエラーが発生するとpayloadを持たずerrorプロパティにエラーの詳細情報を設定したメッセージを送信します。この動作モードは非推奨であり、新しいノード実装ではデフォルトでは無効としています。ノードの設定により、必要に応じてこのモードを有効にできます。

    エラーはcatchノードで補足して処理することを推奨します。

    diff --git a/packages/node_modules/@node-red/nodes/locales/ko/messages.json b/packages/node_modules/@node-red/nodes/locales/ko/messages.json index d87b2c0f5..8e3a4f325 100755 --- a/packages/node_modules/@node-red/nodes/locales/ko/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ko/messages.json @@ -446,7 +446,7 @@ "message": "메세지 전체를 송신/수신", "tip": { "path1": "표준으로는 payload 가 websocket에서 송신, 수신된 데이터를 기다립니다. 클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.", - "path2": "This path will be relative to ", + "path2": "This path will be relative to __path__.", "url1": "URL에는 ws:// 또는 wss:// 스키마를 사용하여, 존재하는 websocket리스너를 설정해 주세요.", "url2": "표준으로는 payload 가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다." }, diff --git a/packages/node_modules/@node-red/nodes/locales/ko/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/ko/parsers/70-XML.html index 7d601f6c1..cd7091049 100644 --- a/packages/node_modules/@node-red/nodes/locales/ko/parsers/70-XML.html +++ b/packages/node_modules/@node-red/nodes/locales/ko/parsers/70-XML.html @@ -20,6 +20,8 @@
    payload오브젝트 | 문자열
    JavaScript오브젝트 혹은 XML문자열
    +
    options 오브젝트
    +
    내부에서 사용중인 XML로의 변환 라이브러리에 대해 옵션을 전달할 수 있습니다. 자세한 사항은 the xml2js docs를 참조해 주세요.

    출력

    @@ -30,8 +32,6 @@
  • 입력이 JavaScript오브젝트인 경우, XML문자열로 변환합니다.
  • -
    options 오브젝트
    -
    내부에서 사용중인 XML로의 변환 라이브러리에 대해 옵션을 전달할 수 있습니다. 자세한 사항은 the xml2js docs를 참조해 주세요.

    상세

    XML와 오브젝트의 사이에서의 변환을 수행할 경우, 기본값으로는 XML속성은 $이라는 명칭의 프로퍼티에 추가합니다. diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json index 46560d181..934cc734d 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json @@ -455,7 +455,7 @@ "message": "完整信息", "tip": { "path1": "默认情况下,payload将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.", - "path2": "这条路径将相对于 ", + "path2": "这条路径将相对于 __path__.", "url1": "URL 应该使用ws://或者wss://方案并指向现有的websocket侦听器.", "url2": "默认情况下,payload 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象." }, @@ -698,7 +698,7 @@ "output": "输出", "includerow": "包含列名行", "newline": "换行符", - "usestrings": "parse numerical values" + "usestrings": "解析数值" }, "placeholder": { "columns": "用逗号分割列名" @@ -898,7 +898,7 @@ } }, "sort" : { - "sort": "sort", + "sort": "排序", "target" : "排序属性", "seq" : "信息队列", "key" : "键值", @@ -907,9 +907,9 @@ "ascending" : "升序", "descending" : "降序", "as-number" : "作为数值", - "invalid-exp" : "sort节点中存在无效的JSONata表达式", - "too-many" : "sort节点中有太多待定信息", - "clear" : "清空sort节点中的待定信息" + "invalid-exp" : "排序节点中存在无效的JSONata表达式", + "too-many" : "排序节点中有太多待定信息", + "clear" : "清空排序节点中的待定信息" }, "batch" : { "batch": "batch", diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-XML.html index 04a7783ef..969f410a0 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-XML.html +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-XML.html @@ -20,6 +20,8 @@

    payloadobject | 字符串
    JavaScript对象或XML字符串。
    +
    options object
    +
    可以将选项传递给内部使用的XML转换库。请参见 xml2js文档 来获取更多信息。

    输出

    @@ -30,8 +32,6 @@
  • 如果输入是JavaScript对象,它将尝试构建XML字符串。
  • -
    options object
    -
    可以将选项传递给内部使用的XML转换库。请参见 xml2js文档 来获取更多信息。

    详细

    在XML和对象之间进行转换时,默认情况下XML属性会添加到名为$的属性中。将文本内容添加到名为_的属性中。这些属性名称可以在节点设置中更改。

    diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html index 4ec78cdb4..eb38a5235 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html @@ -14,7 +14,7 @@ limitations under the License. --> - - diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html new file mode 100644 index 000000000..046eddd7e --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html @@ -0,0 +1,35 @@ + + + + \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html new file mode 100644 index 000000000..31f78e907 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html @@ -0,0 +1,25 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html new file mode 100644 index 000000000..862745310 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html @@ -0,0 +1,24 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html new file mode 100644 index 000000000..4e3db015d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html @@ -0,0 +1,36 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html new file mode 100644 index 000000000..d961c8e52 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html new file mode 100644 index 000000000..e7723c499 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html @@ -0,0 +1,31 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html new file mode 100644 index 000000000..d044f28db --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html @@ -0,0 +1,21 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html new file mode 100644 index 000000000..c3588def1 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html @@ -0,0 +1,24 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html new file mode 100644 index 000000000..9f8ddb43f --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html @@ -0,0 +1,51 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html new file mode 100644 index 000000000..5a65eff93 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html @@ -0,0 +1,37 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html new file mode 100644 index 000000000..91a320945 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html new file mode 100644 index 000000000..62eb63d0b --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html @@ -0,0 +1,40 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html new file mode 100644 index 000000000..874ae3801 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html @@ -0,0 +1,46 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html new file mode 100644 index 000000000..28c291de8 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html @@ -0,0 +1,32 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html new file mode 100644 index 000000000..6bbe72f4d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html new file mode 100644 index 000000000..27be34e00 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html @@ -0,0 +1,74 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json index 153a88300..5368bb993 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json @@ -6,7 +6,9 @@ "name": "名稱", "username": "使用者名稱", "password": "密碼", - "property": "屬性" + "property": "屬性", + "selectNodes": "選擇節點...", + "expand": "擴展" }, "status": { "connected": "已連接", @@ -35,7 +37,22 @@ "stopped": "停止", "failed": "注入失敗: __error__", "label": { - "repeat": "重複" + "repeat": "重複", + "flow": "流上下午", + "global": "全局上下文", + "str": "字符串", + "num": "數值", + "bool": "布爾值", + "json": "JSON對象", + "bin": "buffer", + "date": "時間戳", + "env": "環境變量", + "object": "對象", + "string": "字符串", + "boolean": "布爾值", + "number": "數值", + "Array": "數組", + "invalid": "無效的JSON對象" }, "timestamp": "時間戳記", "none": "無", @@ -72,13 +89,11 @@ "catch": { "catch": "監測所有節點", "catchNodes": "監測__number__個節點", + "catchUncaught": "捕獲:未捕獲", "label": { "source": "監測範圍", - "node": "節點", - "type": "類型", "selectAll": "全選", - "sortByLabel": "按名稱排序", - "sortByType": "按類型排序" + "uncaught": "忽略其他捕獲節點處理的錯誤" }, "scope": { "all": "所有節點", @@ -90,10 +105,6 @@ "statusNodes": "報告__number__個節點狀態", "label": { "source": "報告狀態範圍", - "node": "節點", - "type": "類型", - "selectAll": "全選", - "sortByLabel": "按名稱排序", "sortByType": "按類型排序" }, "scope": { @@ -101,8 +112,13 @@ "selected": "指定節點" } }, + "complete": { + "completeNodes": "完成: __number__個節點" + }, "debug": { "output": "輸出", + "none": "None", + "invalid-exp": "無效的JSONata表達式: __error__", "msgprop": "資訊屬性", "msgobj": "完整資訊", "to": "目標", @@ -124,7 +140,11 @@ "filterCurrent": "當前流程", "debugNodes": "除錯節點", "clearLog": "清空日誌", - "openWindow": "在新視窗打開" + "filterLog": "過濾日誌", + "openWindow": "在新視窗打開", + "copyPath": "復制路徑", + "copyPayload": "復制值", + "pinPath": "固定展開" }, "messageMenu": { "collapseAll": "折疊所有路徑", @@ -146,26 +166,33 @@ "key": "私密金鑰", "passphrase": "密碼", "ca": "CA證書", - "verify-server-cert":"驗證伺服器憑證" + "verify-server-cert": "驗證伺服器憑證", + "servername": "服務器名" }, "placeholder": { - "cert":"憑證路徑 (PEM 格式)", - "key":"私密金鑰路徑 (PEM 格式)", - "ca":"CA憑證路徑 (PEM 格式)", - "passphrase":"私密金鑰密碼 (可選)" + "cert": "憑證路徑 (PEM 格式)", + "key": "私密金鑰路徑 (PEM 格式)", + "ca": "CA憑證路徑 (PEM 格式)", + "passphrase": "私密金鑰密碼 (可選)", + "servername": "用於SNI" }, "error": { "missing-file": "未提供證書/金鑰檔案" } }, "exec": { + "exec": "exec", + "spawn": "spawn", "label": { "command": "命令", "append": "追加", "timeout": "超時", "timeoutplace": "可選填", "return": "輸出", - "seconds": "秒" + "seconds": "秒", + "stdout": "標準輸出", + "stderr": "標準錯誤輸出", + "retcode": "返回碼" }, "placeholder": { "extraparams": "額外的輸入參數" @@ -177,16 +204,18 @@ "oldrc": "使用舊式輸出 (相容模式)" }, "function": { + "function": "函數", "label": { "function": "函數", "outputs": "輸出" }, "error": { - "inputListener":"無法在函數中監聽對'注入'事件", - "non-message-returned":"函數節點嘗試返回類型為 __type__ 的資訊" + "inputListener": "無法在函數中監聽對'注入'事件", + "non-message-returned": "函數節點嘗試返回類型為 __type__ 的資訊" } }, "template": { + "template": "模板", "label": { "template": "模版", "property": "屬性", @@ -233,21 +262,21 @@ "limit": "限制", "limitTopic": "限制主題", "random": "隨機", - "units" : { + "units": { "second": { - "plural" : "秒", + "plural": "秒", "singular": "秒" }, "minute": { - "plural" : "分鐘", + "plural": "分鐘", "singular": "分鐘" }, "hour": { - "plural" : "小時", + "plural": "小時", "singular": "小時" }, "day": { - "plural" : "天", + "plural": "天", "singular": "天" } } @@ -272,6 +301,9 @@ "wait-reset": "等待被重置", "wait-for": "等待", "wait-loop": "週期性重發", + "for": "處理", + "bytopics": "每個msg.topic", + "alltopics": "所有消息", "duration": { "ms": "毫秒", "s": "秒", @@ -284,12 +316,13 @@ "trigger-block": "觸發並阻止", "trigger-loop": "週期性重發", "reset": "重置觸發節點條件 如果:", - "resetMessage":"msg.reset已設置", - "resetPayload":"msg.payload等於", + "resetMessage": "msg.reset已設置", + "resetPayload": "msg.payload等於", "resetprompt": "可選填" } }, "comment": { + "comment": "注釋" }, "unknown": { "label": { @@ -303,26 +336,32 @@ "example": "e.g. localhost", "output": "輸出", "qos": "QoS", + "retain": "保持", "clientid": "使用者端ID", "port": "埠", "keepalive": "Keepalive計時(秒)", "cleansession": "使用新的會話", "use-tls": "使用安全連接 (SSL/TLS)", - "tls-config":"TLS 設置", - "verify-server-cert":"驗證伺服器憑證", + "tls-config": "TLS 設置", + "verify-server-cert": "驗證伺服器憑證", "compatmode": "使用舊式MQTT 3.1支援" }, + "sections-label": { + "birth-message": "連接時發送的消息(出生消息)", + "will-message": "意外斷開連接時的發送消息(Will消息)", + "close-message": "斷開連接前發送的消息(關閉消息)" + }, "tabs-label": { "connection": "連接", "security": "安全", - "will": "Will信息", - "birth": "Birth信息" + "messages": "消息" }, "placeholder": { "clientid": "留白則自動隨機生成", - "clientid-nonclean":"如非新會話,必須設置使用者端ID", + "clientid-nonclean": "如非新會話,必須設置使用者端ID", "will-topic": "留白將禁止Will資訊", - "birth-topic": "留白將禁止Birth資訊" + "birth-topic": "留白將禁止Birth資訊", + "close-topic": "留白以禁用關閉消息" }, "state": { "connected": "已連接到服務端: __broker__", @@ -333,7 +372,9 @@ "output": { "buffer": "Buffer", "string": "字串", - "base64": "Base64編碼字串" + "base64": "Base64編碼字串", + "auto": "自動檢測 (字符串或buffer)", + "json": "解析的JSON對象" }, "true": "是", "false": "否", @@ -342,7 +383,9 @@ "not-defined": "主題未設置", "missing-config": "未設置服務端", "invalid-topic": "主題無效", - "nonclean-missingclientid": "使用者端ID未設定,使用新會話" + "nonclean-missingclientid": "使用者端ID未設定,使用新會話", + "invalid-json-string": "無效的JSON字符串", + "invalid-json-parse": "無法解析JSON字符串" } }, "httpin": { @@ -354,12 +397,26 @@ "upload": "接受檔案上傳?", "status": "狀態碼", "headers": "Header", - "other": "其他" + "other": "其他", + "paytoqs": "將msg.payload附加為查詢字符串參數", + "utf8String": "UTF8格式的字符串", + "binaryBuffer": "二進制buffer", + "jsonObject": "解析的JSON對象", + "authType": "類型", + "bearerToken": "Token" }, "setby": "- 用 msg.method 設定 -", "basicauth": "基本認證", "use-tls": "使用安全連接 (SSL/TLS) ", - "tls-config":"TLS 設置", + "tls-config": "TLS 設置", + "basic": "基本認證", + "digest": "摘要認證", + "bearer": "bearer認證", + "use-proxy": "使用代理服務器", + "persist": "對連接啟用keep-alive", + "proxy-config": "代理服務器設置", + "use-proxyauth": "使用代理身份驗證", + "noproxy-hosts": "代理例外", "utf8": "UTF-8 字串", "binary": "二進位資料", "json": "JSON對象", @@ -375,8 +432,11 @@ "no-response": "無響應物件", "json-error": "JSON 解析錯誤", "no-url": "未設定 URL", - "deprecated-call":"__method__方法已棄用", - "invalid-transport":"非HTTP傳輸請求" + "deprecated-call": "__method__方法已棄用", + "invalid-transport": "非HTTP傳輸請求", + "timeout-isnan": "超時值不是有效數字,忽略", + "timeout-isnegative": "超時值為負,忽略", + "invalid-payload": "無效的有效載荷" }, "status": { "requesting": "請求中" @@ -395,17 +455,23 @@ "message": "完整資訊", "tip": { "path1": "預設情況下,payload將包含要發送或從Websocket接收的資料。偵聽器可以配置為以JSON格式的字串發送或接收整個消息物件.", - "path2": "這條路徑將相對於 ", + "path2": "這條路徑將相對於 __path__.", "url1": "URL 應該使用ws://或者wss://方案並指向現有的websocket監聽器.", "url2": "預設情況下,payload 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件." }, + "status": { + "connected": "連接數 __count__", + "connected_plural": "連接數 __count__" + }, "errors": { "connect-error": "ws連接發生了錯誤: ", "send-error": "發送時發生了錯誤: ", - "missing-conf": "未設置伺服器" + "missing-conf": "未設置伺服器", + "duplicate-path": "同一路徑上不能有兩個WebSocket偵聽器: __path__" } }, "watch": { + "watch": "watch", "label": { "files": "文件", "recursive": "遞迴所有子資料夾" @@ -458,14 +524,12 @@ "connection-closed": "連接已關閉 __host__:__port__", "connections": "__count__ 個連接", "connections_plural": "__count__ 個連接" - }, "errors": { "connection-lost": "連接中斷 __host__:__port__", "timeout": "超時關閉通訊端連接,埠 __port__", "cannot-listen": "無法監聽埠 __port__, 錯誤: __error__", "error": "錯誤: __error__", - "socket-error": "通訊端連接錯誤來自 __host__:__port__", "no-host": "主機位址或埠未設定", "connect-timeout": "連接逾時", @@ -480,14 +544,15 @@ "output": "輸出", "group": "組", "interface": "本地IP", - "interfaceprompt": "(可選)本地 IP 綁定到", "send": "發送一個", "toport": "到埠", "address": "地址", - "decode-base64": "是否解碼Base64編碼的資訊?" + "decode-base64": "是否解碼Base64編碼的資訊?", + "interfaceprompt": "(可選)本地 IP 綁定到" }, "placeholder": { "interface": "(可選)eth0的IP地址", + "interfaceprompt": "(可選) 要綁定的本地接口或地址", "address": "目標IP位址" }, "udpmsgs": "udp信息", @@ -529,36 +594,43 @@ "ip-notset": "udp: IP地址未設定", "port-notset": "udp: 埠未設定", "port-invalid": "udp: 無效埠號碼", - "alreadyused": "udp: 埠已被佔用" + "alreadyused": "udp: 埠已被佔用", + "ifnotfound": "udp: 接口 __iface__ 未發現" } }, "switch": { + "switch": "switch", "label": { "property": "屬性", "rule": "規則", - "repair" : "重建資訊佇列" + "repair": "重建資訊佇列" }, + "previous": "先前值", "and": "與", "checkall": "全選所有規則", "stopfirst": "接受第一條匹配資訊後停止", "ignorecase": "忽略大小寫", "rules": { - "btwn":"在之間", - "cont":"包含", - "regex":"匹配規則運算式", - "true":"為真", - "false":"為假", - "null":"為空", - "nnull":"非空", - "head":"head", - "tail":"tail", - "index":"index between", - "exp":"JSONata運算式", - "else":"除此以外" + "btwn": "在之間", + "cont": "包含", + "regex": "匹配規則運算式", + "true": "為真", + "false": "為假", + "null": "為空", + "nnull": "非空", + "istype": "類型是", + "empty": "為空", + "nempty": "非空", + "head": "head", + "tail": "tail", + "index": "index between", + "exp": "JSONata運算式", + "else": "除此以外", + "hask": "擁有鍵" }, "errors": { "invalid-expr": "無效的JSONata運算式: __error__", - "too-many" : "Switch節點中有太多待定信息" + "too-many": "Switch節點中有太多待定信息" } }, "change": { @@ -588,6 +660,7 @@ } }, "range": { + "range": "range", "label": { "action": "操作", "inputrange": "映射輸入資料", @@ -623,7 +696,8 @@ "firstrow": "第一行包含列名", "output": "輸出", "includerow": "包含列名行", - "newline": "分行符號" + "newline": "分行符號", + "usestrings": "解析數值" }, "placeholder": { "columns": "用逗號分割列名" @@ -654,7 +728,8 @@ "html": { "label": { "select": "選取項", - "output": "輸出" + "output": "輸出", + "in": "in" }, "output": { "html": "選定元素的html內容", @@ -670,7 +745,9 @@ "errors": { "dropped-object": "忽略非物件格式的有效負載", "dropped": "忽略不支援格式的有效負載類型", - "dropped-error": "轉換有效負載失敗" + "dropped-error": "轉換有效負載失敗", + "schema-error": "JSON架構錯誤", + "schema-error-compile": "JSON架構錯誤: 未能編譯架構" }, "label": { "o2j": "對象至JSON", @@ -679,8 +756,8 @@ "property": "屬性", "actions": { "toggle": "JSON字串與物件互轉", - "str":"總是轉為JSON字串", - "obj":"總是轉為JS對象" + "str": "總是轉為JSON字串", + "obj": "總是轉為JS對象" } } }, @@ -702,76 +779,6 @@ "xml_js": "此節點僅處理XML字串或JS物件." } }, - "rpi-gpio": { - "label": { - "gpiopin": "GPIO", - "selectpin": "選擇引腳", - "resistor": "電阻?", - "readinitial": "在部署/重啟時讀取引腳的初始狀態?", - "type": "類型", - "initpin": "初始化引腳狀態?", - "debounce": "去抖動", - "freq": "頻率", - "button": "按鈕", - "pimouse": "Pi滑鼠", - "pikeyboard": "Pi鍵盤", - "left": "左", - "right": "右", - "middle": "中" - }, - "resistor": { - "none": "無", - "pullup": "上拉電阻", - "pulldown": "下拉電阻" - }, - "digout": "數位輸出", - "pwmout": "PWM輸出", - "servo": "伺服輸出", - "initpin0": "初始引腳電平 - 低(0)", - "initpin1": "初始引腳電平 - 高(1)", - "left": "左", - "right": "右", - "middle": "中", - "any": "任何", - "pinname": "引腳", - "alreadyuse": "已被使用", - "alreadyset": "已被設為", - "tip": { - "pin": "正在使用引腳: ", - "in": "提示: 僅接受數位輸入 - 輸出必須為0或1.", - "dig": "提示: 如用數位輸出 - 輸入必須為0或1.", - "pwm": "提示: 如用PWM輸出 - 輸入必須為0至100之間; 如用高頻率可能會比預期佔用更多CPU資源.", - "ser": "提示: 如用伺服輸出 - 輸入必須為0至100之間. 50為中間值." - }, - "types": { - "digout": "數位輸出", - "input": "輸入", - "pullup": "含有上拉電阻的輸入", - "pulldown": "含有下拉電阻的輸入", - "pwmout": "PWM輸出", - "servo": "伺服輸出" - }, - "status": { - "stopped": "已停止", - "closed": "已關閉", - "not-running": "不運行" - }, - "errors": { - "ignorenode": "忽略樹莓派的特定節點", - "version": "版本命令失敗", - "sawpitype": "查看Pi類型", - "libnotfound": "找不到樹莓派RPi.GPIO的python庫", - "alreadyset": "GPIO引腳 __pin__ 已經被設定為類型: __type__", - "invalidpin": "無效GPIO引腳", - "invalidinput": "無效輸入", - "needtobeexecutable": "__command__須為可運行命令", - "mustbeexecutable": "nrgpio須為可運行", - "commandnotfound": "nrgpio命令不存在", - "commandnotexecutable": "nrgpio命令不可運行", - "error": "錯誤: __error__", - "pythoncommandnotfound": "nrpgio python命令未處於運行狀態" - } - }, "file": { "label": { "filename": "檔案名", @@ -783,7 +790,10 @@ "breaklines": "分拆成行", "filelabel": "文件", "sendError": "發生錯誤時發送消息(傳統模式)", - "deletelabel": "刪除 __file__" + "deletelabel": "刪除 __file__", + "encoding": "編碼", + "utf8String": "UTF8字符串", + "binaryBuffer": "二進制buffer" }, "action": { "append": "追加至文件", @@ -801,6 +811,21 @@ "deletedfile": "刪除檔: __file__", "appendedfile": "追加至文件: __file__" }, + "encoding": { + "none": "默認", + "native": "Native", + "unicode": "Unicode", + "japanese": "日本", + "chinese": "中國", + "korean": "韓國", + "taiwan": "臺灣/香港", + "windows": "Windows代碼頁", + "iso": "ISO代碼頁", + "ibm": "IBM代碼頁", + "mac": "Mac代碼頁", + "koi8": "KOI8代碼頁", + "misc": "其它" + }, "errors": { "nofilename": "未指定檔案名", "invaliddelete": "警告:無效刪除。請在配置對話方塊中使用特定的刪除選項", @@ -812,50 +837,53 @@ "tip": "提示: 檔案名應該是絕對路徑,否則它將相對於Node-RED進程的工作目錄。" }, "split": { - "intro":"基於以下類型拆分msg.payload:", - "object":"對象", - "objectSend":"每個鍵值對作為單個消息發送", - "strBuff":"字串 / Buffer", - "array":"陣列", - "splitUsing":"拆分使用", - "splitLength":"固定長度", - "stream":"作為消息流處理", - "addname":" 複製鍵到 " + "split": "split", + "intro": "基於以下類型拆分msg.payload:", + "object": "對象", + "objectSend": "每個鍵值對作為單個消息發送", + "strBuff": "字串 / Buffer", + "array": "陣列", + "splitUsing": "拆分使用", + "splitLength": "固定長度", + "stream": "作為消息流處理", + "addname": " 複製鍵到 " }, - "join":{ - "mode":{ - "mode":"模式", - "auto":"自動", - "merge":"合併序列", - "reduce":"縮減序列", - "custom":"手動" + "join": { + "join": "join", + "mode": { + "mode": "模式", + "auto": "自動", + "merge": "合併序列", + "reduce": "縮減序列", + "custom": "手動" }, - "combine":"合併每個", - "create":"輸出為", - "type":{ - "string":"字串", - "array":"陣列", - "buffer":"Buffer", - "object":"鍵值對對象", - "merged":"合併對象" + "combine": "合併每個", + "completeMessage": "完整的消息", + "create": "輸出為", + "type": { + "string": "字串", + "array": "陣列", + "buffer": "Buffer", + "object": "鍵值對對象", + "merged": "合併對象" }, - "using":"使用此值", - "key":"作為鍵", - "joinedUsing":"合併符號", - "send":"發送資訊:", - "afterCount":"達到一定數量的資訊時", - "count":"數量", - "subsequent":"和每個後續的消息", - "afterTimeout":"第一條消息的若干時間後", - "seconds":"秒", - "complete":"在收到存在msg.complete的消息後", - "tip":"此模式假定此節點與split相連, 或者接收到的消息有正確配置的msg.parts屬性.", - "too-many" : "join節點中有太多待定信息", + "using": "使用此值", + "key": "作為鍵", + "joinedUsing": "合併符號", + "send": "發送資訊:", + "afterCount": "達到一定數量的資訊時", + "count": "數量", + "subsequent": "和每個後續的消息", + "afterTimeout": "第一條消息的若幹時間後", + "seconds": "秒", + "complete": "在收到存在msg.complete的消息後", + "tip": "此模式假定此節點與split相連, 或者接收到的消息有正確配置的msg.parts屬性.", + "too-many": "join節點中有太多待定信息", "merge": { - "topics-label":"合併主題", - "topics":"主題", - "topic" : "主題", - "on-change":"當收到一個新主題時發送已合併資訊" + "topics-label": "合併主題", + "topics": "主題", + "topic": "主題", + "on-change": "當收到一個新主題時發送已合併資訊" }, "reduce": { "exp": "Reduce運算式", @@ -868,43 +896,45 @@ "invalid-expr": "無效的JSONata運算式: __error__" } }, - "sort" : { - "target" : "排序屬性", - "seq" : "資訊佇列", - "key" : "鍵值", - "elem" : "元素值", - "order" : "順序", - "ascending" : "昇冪", - "descending" : "降冪", - "as-number" : "作為數值", - "invalid-exp" : "sort節點中存在無效的JSONata運算式", - "too-many" : "sort節點中有太多待定信息", - "clear" : "清空sort節點中的待定資訊" + "sort": { + "sort": "排序", + "target": "排序屬性", + "seq": "資訊佇列", + "key": "鍵值", + "elem": "元素值", + "order": "順序", + "ascending": "昇冪", + "descending": "降冪", + "as-number": "作為數值", + "invalid-exp": "排序節點中存在無效的JSONata運算式", + "too-many": "排序節點中有太多待定信息", + "clear": "清空排序節點中的待定資訊" }, - "batch" : { + "batch": { + "batch": "batch", "mode": { - "label" : "模式", - "num-msgs" : "按指定數量分組", - "interval" : "按時間間隔分組", - "concat" : "按主題分組" + "label": "模式", + "num-msgs": "按指定數量分組", + "interval": "按時間間隔分組", + "concat": "按主題分組" }, "count": { - "label" : "分組數量", - "overlap" : "隊末隊首重疊數量", - "count" : "數量", - "invalid" : "無效的分組數量或重疊數量" + "label": "分組數量", + "overlap": "隊末隊首重疊數量", + "count": "數量", + "invalid": "無效的分組數量或重疊數量" }, "interval": { - "label" : "時間間隔", - "seconds" : "秒", - "empty" : "無數據到達時發送空資訊" + "label": "時間間隔", + "seconds": "秒", + "empty": "無數據到達時發送空資訊" }, "concat": { "topics-label": "主題", - "topic" : "主題" + "topic": "主題" }, - "too-many" : "batch節點中有太多待定信息", - "unexpected" : "未知模式", - "no-parts" : "資訊中沒有parts屬性" + "too-many": "batch節點中有太多待定信息", + "unexpected": "未知模式", + "no-parts": "資訊中沒有parts屬性" } } diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html new file mode 100644 index 000000000..ee4b3bd95 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html @@ -0,0 +1,19 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html new file mode 100644 index 000000000..23f899627 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html @@ -0,0 +1,22 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html new file mode 100644 index 000000000..825bf218e --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html @@ -0,0 +1,70 @@ + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html new file mode 100644 index 000000000..0d44ce3b1 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html @@ -0,0 +1,81 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html new file mode 100644 index 000000000..71ed96087 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html @@ -0,0 +1,78 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html new file mode 100644 index 000000000..4bb2c7f4d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html new file mode 100644 index 000000000..2898ca718 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html @@ -0,0 +1,35 @@ + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html new file mode 100644 index 000000000..401af48e3 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html new file mode 100644 index 000000000..9a8638614 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html @@ -0,0 +1,43 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html new file mode 100644 index 000000000..b1559455f --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html new file mode 100644 index 000000000..1a46c3690 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html @@ -0,0 +1,43 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html new file mode 100644 index 000000000..e4e433fdb --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html @@ -0,0 +1,48 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html new file mode 100644 index 000000000..8ea4f87f8 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html @@ -0,0 +1,34 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html new file mode 100644 index 000000000..fd97b497a --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html @@ -0,0 +1,133 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html new file mode 100644 index 000000000..cb8e7fa21 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html @@ -0,0 +1,41 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html new file mode 100644 index 000000000..79879076e --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html @@ -0,0 +1,34 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html new file mode 100644 index 000000000..ce2531137 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html @@ -0,0 +1,55 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html new file mode 100644 index 000000000..d8e1b5807 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html @@ -0,0 +1,25 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index d2b3060ee..96e829eaf 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "1.0.3", + "version": "1.1.0", "license": "Apache-2.0", "repository": { "type": "git", @@ -15,11 +15,11 @@ } ], "dependencies": { - "ajv": "6.10.2", + "ajv": "6.12.0", "body-parser": "1.19.0", "cheerio": "0.22.0", "content-type": "1.0.4", - "cookie-parser": "1.4.4", + "cookie-parser": "1.4.5", "cookie": "0.4.0", "cors": "2.8.5", "cron": "1.7.2", @@ -27,18 +27,18 @@ "fs-extra": "8.1.0", "fs.notify": "0.0.4", "hash-sum": "2.0.0", - "https-proxy-agent": "2.2.4", + "https-proxy-agent": "5.0.0", "is-utf8": "0.2.1", "js-yaml": "3.13.1", "media-typer": "1.1.0", "mqtt": "2.18.8", "multer": "1.4.2", - "mustache": "3.0.2", + "mustache": "4.0.1", "on-headers": "1.0.2", "raw-body": "2.4.1", "request": "2.88.0", "ws": "6.2.1", - "xml2js": "0.4.22", - "iconv-lite": "0.5.0" + "xml2js": "0.4.23", + "iconv-lite": "0.5.1" } } diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index fefb24360..1ac6ce528 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "1.0.3", + "version": "1.1.0", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,9 +16,9 @@ } ], "dependencies": { - "@node-red/util": "1.0.3", + "@node-red/util": "1.1.0", "semver": "6.3.0", - "uglify-js": "3.6.9", + "uglify-js": "3.8.1", "when": "3.7.8" } } diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js index 76e645b92..277099ecc 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -57,6 +57,8 @@ var api = module.exports = { * Sets the current flow configuration * @param {Object} opts * @param {User} opts.user - the user calling the api + * @param {Object} opts.flows - the flow configuration: `{flows: [..], credentials: {}}` + * @param {Object} opts.deploymentType - the type of deployment - "full", "nodes", "flows", "reload" * @param {Object} opts.req - the request to log (optional) * @return {Promise} - the active flow configuration * @memberof @node-red/runtime_flows @@ -83,7 +85,7 @@ var api = module.exports = { return reject(err); } } - apiPromise = runtime.nodes.setFlows(flows.flows,deploymentType); + apiPromise = runtime.nodes.setFlows(flows.flows,flows.credentials,deploymentType); } apiPromise.then(function(flowId) { return resolve({rev:flowId}); diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js index c62895f7f..a834c8c34 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js @@ -193,6 +193,7 @@ Node.prototype.emit = function(event, ...args) { */ Node.prototype._emitInput = function(arg) { var node = this; + this.metric("receive", arg); if (node._inputCallback) { // Just one callback registered. try { @@ -448,7 +449,6 @@ Node.prototype.receive = function(msg) { if (!msg._msgid) { msg._msgid = redUtil.generateId(); } - this.metric("receive",msg); this.emit("input",msg); }; diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js index 322fa2868..c395c0615 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js @@ -420,15 +420,39 @@ function createRootContext() { Object.defineProperties(obj, { get: { value: function(key, storage, callback) { + if (!callback && typeof storage === 'function') { + callback = storage; + storage = undefined; + } + if (callback) { + callback() + return; + } return undefined; } }, set: { value: function(key, value, storage, callback) { + if (!callback && typeof storage === 'function') { + callback = storage; + storage = undefined; + } + if (callback) { + callback() + return + } } }, keys: { value: function(storage, callback) { + if (!callback && typeof storage === 'function') { + callback = storage; + storage = undefined; + } + if (callback) { + callback(); + return; + } return undefined; } } @@ -436,25 +460,48 @@ function createRootContext() { return obj; } -function getContext(localId,flowId,parent) { - var contextId = localId; +/** + * Get a flow-level context object. + * @param {string} flowId [description] + * @param {string} parentFlowId the id of the parent flow. undefined + * @return {object}} the context object + */ +function getFlowContext(flowId,parentFlowId) { + if (contexts.hasOwnProperty(flowId)) { + return contexts[flowId]; + } + var parentContext = contexts[parentFlowId]; + if (!parentContext) { + parentContext = createRootContext(); + contexts[parentFlowId] = parentContext; + // throw new Error("Flow "+flowId+" is missing parent context "+parentFlowId); + } + var newContext = createContext(flowId,undefined,parentContext); + contexts[flowId] = newContext; + return newContext; + +} + +function getContext(nodeId, flowId) { + var contextId = nodeId; if (flowId) { - contextId = localId+":"+flowId; + contextId = nodeId+":"+flowId; } if (contexts.hasOwnProperty(contextId)) { return contexts[contextId]; } - var newContext = createContext(contextId,undefined,parent); + var newContext = createContext(contextId); + if (flowId) { - var node = flows.get(flowId); - var parent = undefined; - if (node && node.type.startsWith("subflow:")) { - parent = node.context().flow; + var flowContext = contexts[flowId]; + if (!flowContext) { + // This is most likely due to a unit test for a node which doesn't + // initialise the flow properly. + // To keep things working, initialise the missing context. + // This *does not happen* in normal node-red operation + flowContext = createContext(flowId,undefined,createRootContext()); + contexts[flowId] = flowContext; } - else { - parent = createRootContext(); - } - var flowContext = getContext(flowId,undefined,parent); Object.defineProperty(newContext, 'flow', { value: flowContext }); @@ -466,6 +513,39 @@ function getContext(localId,flowId,parent) { return newContext; } +// +// function getContext(localId,flowId,parent) { +// var contextId = localId; +// if (flowId) { +// contextId = localId+":"+flowId; +// } +// console.log("getContext",localId,flowId,"known?",contexts.hasOwnProperty(contextId)); +// if (contexts.hasOwnProperty(contextId)) { +// return contexts[contextId]; +// } +// var newContext = createContext(contextId,undefined,parent); +// if (flowId) { +// var node = flows.get(flowId); +// console.log("flows,get",flowId,node&&node.type) +// var parent = undefined; +// if (node && node.type.startsWith("subflow:")) { +// parent = node.context().flow; +// } +// else { +// parent = createRootContext(); +// } +// var flowContext = getContext(flowId,undefined,parent); +// Object.defineProperty(newContext, 'flow', { +// value: flowContext +// }); +// } +// Object.defineProperty(newContext, 'global', { +// value: contexts['global'] +// }) +// contexts[contextId] = newContext; +// return newContext; +// } + function deleteContext(id,flowId) { if(!hasConfiguredStore){ // only delete context if there's no configured storage. @@ -517,6 +597,7 @@ module.exports = { load: load, listStores: listStores, get: getContext, + getFlowContext:getFlowContext, delete: deleteContext, clean: clean, close: close diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js b/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js index cf1769700..9755681d8 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js @@ -203,10 +203,10 @@ LocalFileSystem.prototype.open = function(){ var newContext = self.cache._export(); scopes.forEach(function(scope) { var storagePath = getStoragePath(self.storageBaseDir,scope); - var context = newContext[scope]; + var context = newContext[scope] || {}; var stringifiedContext = stringify(context); if (stringifiedContext.circular && !self.knownCircularRefs[scope]) { - log.warn(log._("error-circular",{scope:scope})); + log.warn(log._("context.localfilesystem.error-circular",{scope:scope})); self.knownCircularRefs[scope] = true; } else { delete self.knownCircularRefs[scope]; @@ -324,7 +324,7 @@ LocalFileSystem.prototype.set = function(scope, key, value, callback) { } var stringifiedContext = stringify(obj); if (stringifiedContext.circular && !self.knownCircularRefs[scope]) { - log.warn(log._("error-circular",{scope:scope})); + log.warn(log._("context.localfilesystem.error-circular",{scope:scope})); self.knownCircularRefs[scope] = true; } else { delete self.knownCircularRefs[scope]; diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js index db15e8eb5..df4e810fa 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js @@ -18,6 +18,7 @@ var clone = require("clone"); var redUtil = require("@node-red/util").util; var flowUtil = require("./util"); var events = require("../../events"); +const context = require('../context'); var Subflow; var Log; @@ -54,6 +55,8 @@ class Flow { this.catchNodes = []; this.statusNodes = []; this.path = this.id; + // Ensure a context exists for this flow + this.context = context.getFlowContext(this.id,this.parent.id); } /** @@ -568,15 +571,18 @@ function stopNode(node,removed) { Log.trace("Stopping node "+node.type+":"+node.id+(removed?" removed":"")); const start = Date.now(); const closePromise = node.close(removed); + let closeTimer = null; const closeTimeout = new Promise((resolve,reject) => { - setTimeout(() => { + closeTimer = setTimeout(() => { reject("Close timed out"); }, nodeCloseTimeout); }); return Promise.race([closePromise,closeTimeout]).then(() => { + clearTimeout(closeTimer); var delta = Date.now() - start; Log.trace("Stopped node "+node.type+":"+node.id+" ("+delta+"ms)" ); }).catch(err => { + clearTimeout(closeTimer); node.error(Log._("nodes.flows.stopping-error",{message:err})); Log.debug(err.stack); }) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js index c0bc63dd2..cca230d79 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js @@ -16,7 +16,7 @@ const clone = require("clone"); const Flow = require('./Flow').Flow; - +const context = require('../context'); const util = require("util"); const redUtil = require("@node-red/util").util; @@ -47,38 +47,6 @@ function evaluateInputValue(value, type, node) { return redUtil.evaluateNodeProperty(value, type, node, null, null); } -/** - * Compose information object for env var - */ -function composeInfo(info, val) { - var result = { - name: info.name, - type: info.type, - value: val, - }; - if (info.ui) { - var ui = info.ui; - result.ui = { - hasUI: ui.hasUI, - icon: ui.icon, - labels: ui.labels, - type: ui.type - }; - var retUI = result.ui; - if (ui.type === "input") { - retUI.inputTypes = ui.inputTypes; - } - if (ui.type === "select") { - retUI.menu = ui.menu; - } - if (ui.type === "spinner") { - retUI.spinner = ui.spinner; - } - } - return result; -} - - /** * This class represents a subflow - which is handled as a special type of Flow */ @@ -246,6 +214,10 @@ class Subflow extends Flow { this.node.on("input", function(msg) { this.send(msg);}); this.node.on("close", function() { this.status({}); }) this.node.status = status => this.parent.handleStatus(this.node,status); + // Create a context instance + // console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z) + this._context = context.get(this._alias||this.id,this.z); + this.node._updateWires = this.node.updateWires; @@ -345,15 +317,8 @@ class Subflow extends Flow { getSetting(name) { if (!/^\$parent\./.test(name)) { var env = this.env; - var is_info = name.endsWith("_info"); - var is_type = name.endsWith("_type"); - var ename = (is_info || is_type) ? name.substring(0, name.length -5) : name; // 5 = length of "_info"/"_type" - - if (env && env.hasOwnProperty(ename)) { - var val = env[ename]; - if (is_type) { - return val ? val.type : undefined; - } + if (env && env.hasOwnProperty(name)) { + var val = env[name]; // If this is an env type property we need to be careful not // to get into lookup loops. // 1. if the value to lookup is the same as this one, go straight to parent @@ -367,11 +332,7 @@ class Subflow extends Flow { value = value.replace(new RegExp("\\${"+name+"}","g"),"${$parent."+name+"}"); } try { - var ret = evaluateInputValue(value, type, this.node); - if (is_info) { - return composeInfo(val, ret); - } - return ret; + return evaluateInputValue(value, type, this.node); } catch (e) { this.error(e); @@ -483,7 +444,7 @@ function createNodeInSubflow(subflowInstanceId, def) { * properties in the nodes object to reference the new node ids. * This handles: * - node.wires, - * - node.scope of Catch and Status nodes, + * - node.scope of Complete, Catch and Status nodes, * - node.XYZ for any property where XYZ is recognised as an old property * @param {[type]} nodes [description] * @param {[type]} nodeMap [description] @@ -506,7 +467,7 @@ function remapSubflowNodes(nodes,nodeMap) { } } } - if ((node.type === 'catch' || node.type === 'status') && node.scope) { + if ((node.type === 'complete' || node.type === 'catch' || node.type === 'status') && node.scope) { node.scope = node.scope.map(function(id) { return nodeMap[id]?nodeMap[id].id:"" }) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js index 981ed7173..1a350ce7c 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js @@ -106,15 +106,20 @@ function load(forceStart) { // This is a force reload from the API - disable safeMode delete settings.safeMode; } - return setFlows(null,"load",false,forceStart); + return setFlows(null,null,"load",false,forceStart); } /* * _config - new node array configuration + * _credentials - new credentials configuration (optional) * type - full/nodes/flows/load (default full) * muteLog - don't emit the standard log messages (used for individual flow api) */ -function setFlows(_config,type,muteLog,forceStart) { +function setFlows(_config,_credentials,type,muteLog,forceStart) { + if (typeof _credentials === "string") { + type = _credentials; + _credentials = null; + } type = type||"full"; if (settings.safeMode) { if (type !== "load") { @@ -155,16 +160,27 @@ function setFlows(_config,type,muteLog,forceStart) { delete newFlowConfig.allNodes[id].credentials; } } + var credsDirty; - // Allow the credential store to remove anything no longer needed - credentials.clean(config); + if (_credentials) { + // A full set of credentials have been provided. Use those instead + configSavePromise = credentials.load(_credentials); + credsDirty = true; + } else { + // Allow the credential store to remove anything no longer needed + credentials.clean(config); - // Remember whether credentials need saving or not - var credsDirty = credentials.dirty(); + // Remember whether credentials need saving or not + var credsDirty = credentials.dirty(); + + configSavePromise = Promise.resolve(); + } // Get the latest credentials and ask storage to save them (if needed) // as well as the new flow configuration. - configSavePromise = credentials.export().then(function(creds) { + configSavePromise = configSavePromise.then(function() { + return credentials.export() + }).then(function(creds) { var saveConfig = { flows: config, credentialsDirty:credsDirty, @@ -515,7 +531,7 @@ function addFlow(flow) { var newConfig = clone(activeConfig.flows); newConfig = newConfig.concat(nodes); - return setFlows(newConfig,'flows',true).then(function() { + return setFlows(newConfig,null,'flows',true).then(function() { log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"})); return flow.id; }); @@ -537,7 +553,7 @@ function getFlow(id) { if (flow.label) { result.label = flow.label; } - if (flow.disabled) { + if (flow.hasOwnProperty('disabled')) { result.disabled = flow.disabled; } if (flow.hasOwnProperty('info')) { @@ -646,7 +662,7 @@ function updateFlow(id,newFlow) { } newConfig = newConfig.concat(nodes); - return setFlows(newConfig,'flows',true).then(function() { + return setFlows(newConfig,null,'flows',true).then(function() { log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"})); }) } @@ -668,7 +684,7 @@ function removeFlow(id) { return node.z !== id && node.id !== id; }); - return setFlows(newConfig,'flows',true).then(function() { + return setFlows(newConfig,null,'flows',true).then(function() { log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"})); }); } diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/util.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/util.js index b6074d792..e06cc5ede 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/util.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/util.js @@ -85,6 +85,7 @@ module.exports = { flow.subflows = {}; flow.configs = {}; flow.flows = {}; + flow.groups = {}; flow.missingTypes = []; config.forEach(function(n) { @@ -95,8 +96,12 @@ module.exports = { flow.flows[n.id].configs = {}; flow.flows[n.id].nodes = {}; } + if (n.type === 'group') { + flow.groups[n.id] = n; + } }); + // TODO: why a separate forEach? this can be merged with above config.forEach(function(n) { if (n.type === 'subflow') { flow.subflows[n.id] = n; @@ -108,7 +113,7 @@ module.exports = { var linkWires = {}; var linkOutNodes = []; config.forEach(function(n) { - if (n.type !== 'subflow' && n.type !== 'tab') { + if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { var subflowDetails = subflowInstanceRE.exec(n.type); if ( (subflowDetails && !flow.subflows[subflowDetails[1]]) || (!subflowDetails && !typeRegistry.get(n.type)) ) { @@ -163,7 +168,7 @@ module.exports = { var addedTabs = {}; config.forEach(function(n) { - if (n.type !== 'subflow' && n.type !== 'tab') { + if (n.type !== 'subflow' && n.type !== 'tab' && n.type !== 'group') { for (var prop in n) { if (n.hasOwnProperty(prop) && prop !== 'id' && prop !== 'wires' && prop !== 'type' && prop !== '_users' && flow.configs.hasOwnProperty(n[prop])) { // This property references a global config node diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js index da0e1f94f..04701321f 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js @@ -25,73 +25,66 @@ var settings; var libDir; var libFlowsDir; +function toSingleLine(text) { + var result = text.replace(/\\/g, "\\\\").replace(/\n/g, "\\n"); + return result; +} -function getFileMeta(root,path) { - var fn = fspath.join(root,path); - var fd = fs.openSync(fn,"r"); +function fromSingleLine(text) { + var result = text.replace(/\\[\\n]/g, function(s) { + return ((s === "\\\\") ? "\\" : "\n"); + }); + return result; +} + +function getFileMeta(root, path) { + var fn = fspath.join(root, path); + var fd = fs.openSync(fn, 'r'); var size = fs.fstatSync(fd).size; var meta = {}; var read = 0; var length = 10; - var remaining = ""; + var remaining = Buffer.alloc(0); var buffer = Buffer.alloc(length); - while(read < size) { - read+=fs.readSync(fd,buffer,0,length); - var data = remaining+buffer.toString(); - var parts = data.split("\n"); - remaining = parts.splice(-1); - for (var i=0;i 0?"\n":"")+parts[i]; - } - } - if (!scanning) { - body += remaining; - } - } else { - read += thisRead; - body += buffer.slice(0,thisRead).toString(); + for (var i = 0; i < parts.length; i++) { + if (! /^\/\/ \w+: /.test(parts[i]) || !scanning) { + body += (body.length > 0 ? '\n' : '') + parts[i]; + scanning = false; } } - fs.closeSync(fd); return body; } + function getLibraryEntry(type,path) { var root = fspath.join(libDir,type); var rootPath = fspath.join(libDir,type,path); @@ -172,7 +165,7 @@ module.exports = { var headers = ""; for (var i in meta) { if (meta.hasOwnProperty(i)) { - headers += "// "+i+": "+meta[i]+"\n"; + headers += "// "+i+": "+toSingleLine(meta[i])+"\n"; } } if (type === "flows" && settings.flowFilePretty) { diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js index 7ba3bd2f3..ad3c0bf26 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/Project.js @@ -706,7 +706,9 @@ Project.prototype.fetch = function(user,remoteName) { promise = promise.then(function() { return gitTools.fetch(project.path,remote,authCache.get(project.name,project.remotes[remote].fetch,username)) }).catch(function(err) { - err.remote = remote; + if (!err.remote) { + err.remote = remote; + } throw err; }) }); diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js index b9f114698..d812c05f2 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js @@ -41,6 +41,9 @@ function runGitCommand(args,cwd,env,emit) { err.code = "git_connection_failed"; } else if (/Connection timed out/i.test(stderr)) { err.code = "git_connection_failed"; + } else if(/Host key verification failed/i.test(stderr)) { + // TODO: handle host key verification errors separately + err.code = "git_host_key_verification_failed"; } else if (/fatal: could not read/i.test(stderr)) { // Username/Password err.code = "git_auth_failed"; @@ -48,9 +51,6 @@ function runGitCommand(args,cwd,env,emit) { err.code = "git_auth_failed"; } else if(/Permission denied \(publickey\)/i.test(stderr)) { err.code = "git_auth_failed"; - } else if(/Host key verification failed/i.test(stderr)) { - // TODO: handle host key verification errors separately - err.code = "git_auth_failed"; } else if (/commit your changes or stash/i.test(stderr)) { err.code = "git_local_overwrite"; } else if (/CONFLICT/.test(err.stdout)) { diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ask-pass.sh b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ask-pass.sh index 695b2a19e..b6fbf80f4 100755 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ask-pass.sh +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ask-pass.sh @@ -1 +1,2 @@ +#!/bin/sh "$NODE_RED_GIT_NODE_PATH" "$NODE_RED_GIT_ASKPASS_PATH" "$NODE_RED_GIT_SOCK_PATH" $@ diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ssh.sh b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ssh.sh index 26a232f83..b879b7384 100755 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ssh.sh +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/node-red-ssh.sh @@ -1 +1,2 @@ +#!/bin/sh ssh -i "$NODE_RED_KEY_FILE" -F /dev/null $@ diff --git a/packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json b/packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json new file mode 100644 index 000000000..b96d87a6e --- /dev/null +++ b/packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json @@ -0,0 +1,174 @@ +{ + "runtime": { + "welcome": "歡迎使用Node-RED", + "version": "__component__ 版本: __version__", + "unsupported_version": "__component__的不受支持的版本。要求: __requires__ 找到: __version__", + "paths": { + "settings": "設置文件 : __path__", + "httpStatic": "HTTP Static : __path__" + } + }, + + "server": { + "loading": "加載控制板節點", + "palette-editor": { + "disabled": "控制板編輯器已禁用:用戶設置", + "npm-not-found": "控制板編輯器已禁用:找不到npm命令", + "npm-too-old": "控制板編輯器已禁用: npm版本太舊。需要版本npm >= 3.x" + }, + "errors": "無法註冊__count__節點類型", + "errors_plural": "無法註冊__count__個節點類型", + "errors-help": "使用-v運行以獲取詳細信息", + "missing-modules": "缺少節點模組:", + "node-version-mismatch": "無法在此版本上加載節點模組。要求:__ version__", + "type-already-registered": "'__type__'已由模組__module__註冊", + "removing-modules": "從配置中刪除模組", + "added-types": "添加的節點類型:", + "removed-types": "刪除的節點類型:", + "install": { + "invalid": "無效的模組名稱", + "installing": "安裝模組:__ name__,版本:__ version__", + "installed": "已安裝的模組:__ name__", + "install-failed": "安裝失敗", + "install-failed-long": "模組__name__安裝失敗:", + "install-failed-not-found": "$t(server.install.install-failed-long) 模組未發現", + "upgrading": "更新模組: __name__ 至版本: __version__", + "upgraded": "更新模組: __name__。 重新啟動Node-RED以使用新版本", + "upgrade-failed-not-found": "$t(server.install.install-failed-long) 未發現版本", + "uninstalling": "卸載模組: __name__", + "uninstall-failed": "卸載失敗", + "uninstall-failed-long": "卸載模組__name__失敗:", + "uninstalled": "卸載模組: __name__" + }, + "unable-to-listen": "無法在__listenpath__上收聽", + "port-in-use": "錯誤: 端口正在使用中", + "uncaught-exception": "未捕獲的異常:", + "admin-ui-disabled": "管理員界面已禁用", + "now-running": "服務器現在在__listenpath__上運行", + "failed-to-start": "無法啟動服務器:", + "headless-mode": "在headless模式下運行", + "httpadminauth-deprecated": "不建議使用httpAdminAuth。請改用adminAuth" + }, + + "api": { + "flows": { + "error-save": "保存流程錯誤: __message__", + "error-reload": "重載流程錯誤: __message__" + }, + "library": { + "error-load-entry": "加載庫條目'__path__'時出錯:__message__", + "error-save-entry": "保存庫條目'__path__'時出錯:__ message__", + "error-load-flow": "加載流程'__path__'時出錯:__ message__", + "error-save-flow": "保存流'__path__'時出錯:__ message__" + }, + "nodes": { + "enabled": "啟用的節點類型:", + "disabled": "禁用的節點類型:", + "error-enable": "無法啟用節點:" + } + }, + + "comms": { + "error": "通訊渠道錯誤:__ message__", + "error-server": "通信服務器錯誤:__ message__", + "error-send": "通訊發送錯誤:__ message__" + }, + + "settings": { + "user-not-available": "無法保存用戶設置:__ message__", + "not-available": "設置不可用", + "property-read-only": "屬性“ __prop__”是只讀的" + }, + + "nodes": { + "credentials": { + "error":"加載證書時出錯:__ message__", + "error-saving":"保存證書時出錯:__ message__", + "not-registered": "證書類型'__type__'未註冊", + "system-key-warning": "\n\n---------------------------------------------------------------------\n您的流程證書文件是使用系統生成的密鑰加密的。\n\n如果系統生成的密鑰由於任何原因丟失,則您的證書文件將無法恢復,您將必須刪除它並重新輸入您的證書。\n\n您應該使用您的設置文件中的'credentialSecret'選項設置自己的密鑰。然後,下次部署更改時,Node-RED將使用選擇的密鑰重新加密您的證書文件。\n---------------------------------------------------------------------\n" + }, + "flows": { + "safe-mode": "流程在安全模式下停止。部署開始。", + "registered-missing": "缺少註冊的類型:__ type__", + "error": "錯誤加載流程:__ message__", + "starting-modified-nodes": "啟動修改的節點", + "starting-modified-flows": "啟動修改的流程", + "starting-flows": "啟動流程", + "started-modified-nodes": "修改的節點已啟動", + "started-modified-flows": "修改的流程已啟動", + "started-flows": "流程已啟動", + "stopping-modified-nodes": "停止修改的節點", + "stopping-modified-flows": "停止修改的流程", + "stopping-flows": "停止流程", + "stopped-modified-nodes": "修改的節點已停止", + "stopped-modified-flows": "修改的流程已停止", + "stopped-flows": "流程已停止", + "stopped": "已停止", + "stopping-error": "錯誤停止節點:__ message__", + "added-flow": "流程已添加: __label__", + "updated-flow": "流程已更新: __label__", + "removed-flow": "流程已移除: __label__", + "missing-types": "等待缺少的類型被註冊:", + "missing-type-provided": " - __type__ (由npm模組__module__提供)", + "missing-type-install-1": "要安裝所有缺少的模組,請運行:", + "missing-type-install-2": "在目錄中:" + }, + "flow": { + "unknown-type": "未知類型: __type__", + "missing-types": "缺少類型", + "error-loop": "郵件已超過最大捕獲數" + }, + "index": { + "unrecognised-id": "無法識別的ID: __id__", + "type-in-use": "使用中的類型: __msg__", + "unrecognised-module": "無法識別的模組: __module__" + }, + "registry": { + "localfilesystem": { + "module-not-found": "找不到模組:'__module__'" + } + } + }, + + "storage": { + "index": { + "forbidden-flow-name": "禁止流程名稱" + }, + "localfilesystem": { + "user-dir": "用戶目錄: __path__", + "flows-file": "流程文件: __path__", + "create": "創建新__type__文件", + "empty": "現有__type__文件為空", + "invalid": "現有__type__文件為無效json", + "restore": "恢復__type__文件備份:__path__", + "restore-fail": "恢復__type__文件備份失敗:__ message__", + "fsync-fail": "將文件__path__刷新到磁盤失敗:__message__", + "projects": { + "changing-project": "設置活動項目:__ project__", + "active-project": "活動項目:__ project__", + "project-not-found": "找不到項目:__ project__", + "no-active-project": "沒有活動的項目:使用默認流文件", + "disabled": "項目已禁用:editorTheme.projects.enabled = false", + "disabledNoFlag": "項目已禁用:設置editorTheme.projects.enabled = true來啟用", + "git-not-found": "項目已禁用:找不到git命令", + "git-version-old": "項目已禁用:不支持的git __version__。 需要的git版本為2.x", + "summary": "一個Node-RED項目", + "readme": "### 關於\n\n這是您項目的README.md文件。它可以幫助用戶了解您的項目,如何使用它以及他們可能需要知道的其他任何信息。" + } + } + }, + + "context": { + "log-store-init": "上下文儲存: '__name__' [__info__]", + "error-loading-module": "加載上下文存儲時出錯: __message__", + "error-loading-module2": "加載上下文存儲時出錯 '__module__': __message__", + "error-module-not-defined": "上下文存儲庫'__storage__'缺少“模組”選項", + "error-invalid-module-name": "無效的上下文存儲名稱:'__ name__'", + "error-invalid-default-module": "無效的默認的上下文存儲庫: '__storage__'", + "unknown-store": "指定了未知的上下文存儲庫'__name__'。 使用默認存儲庫。", + "localfilesystem": { + "error-circular": "上下文__scope__包含無法保留的循環引用", + "error-write": "編寫上下文時出錯:__ message__" + } + } +} diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 0d03b355b..93dc305f9 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "1.0.3", + "version": "1.1.0", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "1.0.3", - "@node-red/util": "1.0.3", + "@node-red/registry": "1.1.0", + "@node-red/util": "1.1.0", "clone": "2.1.2", "express": "4.17.1", "fs-extra": "8.1.0", diff --git a/packages/node_modules/@node-red/util/lib/util.js b/packages/node_modules/@node-red/util/lib/util.js index 45a33be14..8b4fd0c17 100644 --- a/packages/node_modules/@node-red/util/lib/util.js +++ b/packages/node_modules/@node-red/util/lib/util.js @@ -19,8 +19,7 @@ * @mixin @node-red/util_util */ - -const clone = require("clone"); +const clonedeep = require("lodash.clonedeep"); const jsonata = require("jsonata"); const safeJSONStringify = require("json-stringify-safe"); const util = require("util"); @@ -81,14 +80,14 @@ function ensureBuffer(o) { * @memberof @node-red/util_util */ function cloneMessage(msg) { - if (typeof msg !== "undefined") { + if (typeof msg !== "undefined" && msg !== null) { // Temporary fix for #97 // TODO: remove this http-node-specific fix somehow var req = msg.req; var res = msg.res; delete msg.req; delete msg.res; - var m = clone(msg); + var m = clonedeep(msg); if (req) { m.req = req; msg.req = req; @@ -558,15 +557,19 @@ function evaluateNodeProperty(value, type, node, msg, callback) { */ function prepareJSONataExpression(value,node) { var expr = jsonata(value); - expr.assign('flowContext',function(val) { - return node.context().flow.get(val); + expr.assign('flowContext',function(val, store) { + return node.context().flow.get(val, store); }); - expr.assign('globalContext',function(val) { - return node.context().global.get(val); + expr.assign('globalContext',function(val, store) { + return node.context().global.get(val, store); }); expr.assign('env', function(name) { var val = getSetting(node, name); - return (val ? val : ""); + if (typeof val !== 'undefined') { + return val; + } else { + return "" + } }) expr.registerFunction('clone', cloneMessage, '<(oa)-:o>'); expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value); diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 411c35a7c..16951b6ba 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "1.0.3", + "version": "1.1.0", "license": "Apache-2.0", "repository": { "type": "git", @@ -18,7 +18,8 @@ "clone": "2.1.2", "i18next": "15.1.2", "json-stringify-safe": "5.0.1", - "jsonata": "1.8.0", + "jsonata": "1.8.3", + "lodash.clonedeep": "^4.5.0", "when": "3.7.8" } } diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 618480400..5c7192a20 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.0.3", + "version": "1.1.0", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,17 +31,17 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "1.0.3", - "@node-red/runtime": "1.0.3", - "@node-red/util": "1.0.3", - "@node-red/nodes": "1.0.3", + "@node-red/editor-api": "1.1.0", + "@node-red/runtime": "1.1.0", + "@node-red/util": "1.1.0", + "@node-red/nodes": "1.1.0", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.17.1", "fs-extra": "8.1.0", "node-red-node-rbe": "^0.2.6", "node-red-node-tail": "^0.1.0", - "nopt": "4.0.1", + "nopt": "4.0.3", "semver": "6.3.0" }, "optionalDependencies": { diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index 960cb6d17..e42af525f 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -182,6 +182,17 @@ module.exports = { // next(); //}, + + // The following property can be used to add a custom middleware function + // in front of all admin http routes. For example, to set custom http + // headers + // httpAdminMiddleware: function(req,res,next) { + // // Set the X-Frame-Options header to limit where the editor + // // can be embedded + // //res.set('X-Frame-Options', 'sameorigin'); + // next(); + // }, + // The following property can be used to pass custom options to the Express.js // server used by Node-RED. For a full list of available options, refer // to http://expressjs.com/en/api.html#app.settings.table @@ -243,7 +254,7 @@ module.exports = { // palette. If a node's category is not in the list, the category will get // added to the end of the palette. // If not set, the following default order is used: - //paletteCategories: ['subflows','flow','input','output','function','parser','social','mobile','storage','analysis','advanced'], + //paletteCategories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], // Configure the logging output logging: { diff --git a/test/editor/pageobjects/editor/palette_page.js b/test/editor/pageobjects/editor/palette_page.js index a35859c5f..3b484a58c 100644 --- a/test/editor/pageobjects/editor/palette_page.js +++ b/test/editor/pageobjects/editor/palette_page.js @@ -15,22 +15,40 @@ **/ var idMap = { - // input + // common "inject": ".red-ui-palette-node[data-palette-type='inject']", - "httpIn": ".red-ui-palette-node[data-palette-type='http in']", - "mqttIn": ".red-ui-palette-node[data-palette-type='mqtt in']", - // output "debug": ".red-ui-palette-node[data-palette-type='debug']", - "httpResponse": ".red-ui-palette-node[data-palette-type='http response']", - "mqttOut": ".red-ui-palette-node[data-palette-type='mqtt out']", + "complete": ".red-ui-palette-node[data-palette-type='complete']", + "catch": ".red-ui-palette-node[data-palette-type='catch']", + "status": ".red-ui-palette-node[data-palette-type='status']", + "comment": ".red-ui-palette-node[data-palette-type='comment']", // function "function": ".red-ui-palette-node[data-palette-type='function']", - "template": ".red-ui-palette-node[data-palette-type='template']", + "switch": ".red-ui-palette-node[data-palette-type='switch']", "change": ".red-ui-palette-node[data-palette-type='change']", "range": ".red-ui-palette-node[data-palette-type='range']", + "template": ".red-ui-palette-node[data-palette-type='template']", + "delay": ".red-ui-palette-node[data-palette-type='delay']", + "trigger": ".red-ui-palette-node[data-palette-type='trigger']", + "exec": ".red-ui-palette-node[data-palette-type='exec']", + // network + "mqttIn": ".red-ui-palette-node[data-palette-type='mqtt in']", + "mqttOut": ".red-ui-palette-node[data-palette-type='mqtt out']", + "httpIn": ".red-ui-palette-node[data-palette-type='http in']", + "httpResponse": ".red-ui-palette-node[data-palette-type='http response']", "httpRequest": ".red-ui-palette-node[data-palette-type='http request']", + "websocketIn": ".red-ui-palette-node[data-palette-type='websocket in']", + "websocketOut": ".red-ui-palette-node[data-palette-type='websocket out']", + // sequence + "split": ".red-ui-palette-node[data-palette-type='split']", + "join": ".red-ui-palette-node[data-palette-type='join']", + "batch": ".red-ui-palette-node[data-palette-type='batch']", + // parser + "csv": ".red-ui-palette-node[data-palette-type='csv']", "html": ".red-ui-palette-node[data-palette-type='html']", "json": ".red-ui-palette-node[data-palette-type='json']", + "xml": ".red-ui-palette-node[data-palette-type='xml']", + "yaml": ".red-ui-palette-node[data-palette-type='yaml']", // storage "fileIn": ".red-ui-palette-node[data-palette-type='file in']", }; diff --git a/test/editor/pageobjects/editor/workspace_page.js b/test/editor/pageobjects/editor/workspace_page.js index ddf0a1c60..92a725dac 100644 --- a/test/editor/pageobjects/editor/workspace_page.js +++ b/test/editor/pageobjects/editor/workspace_page.js @@ -42,7 +42,7 @@ function addNode(type, x, y) { } } browser.waitForVisible('#red-ui-palette-search'); - browser.setValue('//*[@id="red-ui-palette-search"]/div/input', type.replace(/([A-Z])/g,' $1').toLowerCase()); + browser.setValue('//*[@id="red-ui-palette-search"]/div/form/input', type.replace(/([A-Z])/g, ' $1').toLowerCase()); browser.pause(300); browser.waitForVisible(palette.getId(type)); browser.moveToObject(palette.getId(type)); @@ -66,8 +66,8 @@ function deleteAllNodes() { function deploy() { browser.call(function () { - return when.promise(function(resolve, reject) { - events.on("runtime-event", function(event) { + return when.promise(function (resolve, reject) { + events.on("runtime-event", function (event) { if (event.id === 'runtime-deploy') { events.removeListener("runtime-event", arguments.callee); resolve(); diff --git a/test/editor/pageobjects/nodes/core/common/21-debug_page.js b/test/editor/pageobjects/nodes/core/common/21-debug_page.js index 602d4d542..990ed6cd9 100644 --- a/test/editor/pageobjects/nodes/core/common/21-debug_page.js +++ b/test/editor/pageobjects/nodes/core/common/21-debug_page.js @@ -18,8 +18,6 @@ var util = require("util"); var nodePage = require("../../node_page"); -var keyPage = require("../../../util/key_page"); - function debugNode(id) { nodePage.call(this, id); } @@ -33,6 +31,8 @@ debugNode.prototype.setOutput = function (complete) { // Select the "msg" type. browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options")][1]/a[1]'); // Input the path in msg. + browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-input")]/input'); + browser.keys(Array('payload'.length).fill('Backspace')); browser.setValue('//*[contains(@class, "red-ui-typedInput-input")]/input', complete); } else { // Select the "complete msg object" type. diff --git a/test/editor/pageobjects/nodes/core/common/24-complete_page.js b/test/editor/pageobjects/nodes/core/common/24-complete_page.js new file mode 100644 index 000000000..558ee709a --- /dev/null +++ b/test/editor/pageobjects/nodes/core/common/24-complete_page.js @@ -0,0 +1,47 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function completeNode(id) { + nodePage.call(this, id); +} + +util.inherits(completeNode, nodePage); + +completeNode.prototype.setScope = function (scope) { + if (scope) { + browser.clickWithWait('//*[@id="node-input-complete-target-select"]'); + browser.pause(1000); + if (Array.isArray(scope)) { + for (var i = 0; i < scope.length; i++) { + browser.moveToObject(scope[i].id); + browser.buttonDown(); + browser.buttonUp(); + } + } else { + browser.moveToObject(scope.id); + browser.buttonDown(); + browser.buttonUp(); + } + browser.clickWithWait('//*[contains(@class, "red-ui-notification")]/div/button[2]'); + browser.pause(1000); + } +} + +module.exports = completeNode; diff --git a/test/editor/pageobjects/nodes/core/common/25-catch_page.js b/test/editor/pageobjects/nodes/core/common/25-catch_page.js new file mode 100644 index 000000000..89bcb7f1e --- /dev/null +++ b/test/editor/pageobjects/nodes/core/common/25-catch_page.js @@ -0,0 +1,48 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function catchNode(id) { + nodePage.call(this, id); +} + +util.inherits(catchNode, nodePage); + +catchNode.prototype.setScope = function (scope) { + if (scope) { + browser.selectWithWait('#node-input-scope-select', 'target'); + browser.clickWithWait('//*[@id="node-input-catch-target-select"]'); + browser.pause(1000); + if (Array.isArray(scope)) { + for (var i = 0; i < scope.length; i++) { + browser.moveToObject(scope[i].id); + browser.buttonDown(); + browser.buttonUp(); + } + } else { + browser.moveToObject(scope.id); + browser.buttonDown(); + browser.buttonUp(); + } + browser.clickWithWait('//*[contains(@class, "red-ui-notification")]/div/button[2]'); + browser.pause(1000); + } +} + +module.exports = catchNode; diff --git a/test/editor/pageobjects/nodes/core/common/25-status_page.js b/test/editor/pageobjects/nodes/core/common/25-status_page.js new file mode 100644 index 000000000..6de0fcde7 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/common/25-status_page.js @@ -0,0 +1,48 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function statusNode(id) { + nodePage.call(this, id); +} + +util.inherits(statusNode, nodePage); + +statusNode.prototype.setScope = function (scope) { + if (scope) { + browser.selectWithWait('#node-input-scope-select', 'target'); + browser.clickWithWait('//*[@id="node-input-status-target-select"]'); + browser.pause(1000); + if (Array.isArray(scope)) { + for (var i = 0; i < scope.length; i++) { + browser.moveToObject(scope[i].id); + browser.buttonDown(); + browser.buttonUp(); + } + } else { + browser.moveToObject(scope.id); + browser.buttonDown(); + browser.buttonUp(); + } + browser.clickWithWait('//*[contains(@class, "red-ui-notification")]/div/button[2]'); + browser.pause(1000); + } +} + +module.exports = statusNode; diff --git a/test/editor/pageobjects/nodes/core/common/90-comment_page.js b/test/editor/pageobjects/nodes/core/common/90-comment_page.js new file mode 100644 index 000000000..2687dacfe --- /dev/null +++ b/test/editor/pageobjects/nodes/core/common/90-comment_page.js @@ -0,0 +1,27 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function commentNode(id) { + nodePage.call(this, id); +} + +util.inherits(commentNode, nodePage); + +module.exports = commentNode; diff --git a/test/editor/pageobjects/nodes/core/function/10-switch_page.js b/test/editor/pageobjects/nodes/core/function/10-switch_page.js new file mode 100644 index 000000000..a04014063 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/function/10-switch_page.js @@ -0,0 +1,234 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function switchNode(id) { + nodePage.call(this, id); +} + +util.inherits(switchNode, nodePage); + +var vtType = { + "msg": 1, + "flow": 2, + "global": 3, + "str": 4, + "num": 5, + "jsonata": 6, + "env": 7, + "prev": 8 +}; + +function setT(t, index) { + browser.selectWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/select', t); +} + +function setV(v, vt, index) { + if (vt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + vtType[vt] + ']'); + } + if (v) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', v); + } +} + +function setBetweenV(v, vt, v2, v2t, index) { + if (vt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + vtType[vt] + ']'); + } + if (v) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/div[1]/input', v); + } + if (v2t) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + vtType[v2t] + ']'); + } + if (v2) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div/div[1]/input', v2); + } +} + +function setSequenceV(v, vt, index) { + var sType = { + "flow": 1, + "global": 2, + "num": 3, + "jsonata": 4, + "env": 5, + }; + + if (vt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + sType[vt] + ']'); + } + if (v) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/div[1]/input', v); + } +} + +switchNode.prototype.ruleEqual = function (v, vt, index) { + index = index || 1; + setT('eq', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleNotEqual = function (v, vt, index) { + index = index || 1; + setT('neq', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleLessThan = function (v, vt, index) { + index = index || 1; + setT('lt', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleLessThanOrEqual = function (v, vt, index) { + index = index || 1; + setT('lte', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleGreaterThan = function (v, vt, index) { + index = index || 1; + setT('gt', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleGreaterThanOrEqual = function (v, vt, index) { + index = index || 1; + setT('gte', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleHasKey = function (v, vt, index) { + index = index || 1; + setT('hask', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleIsBetween = function (v, vt, v2, v2t, index) { + index = index || 1; + setT('btwn', index); + setBetweenV(v, vt, v2, v2t, index); +} + +switchNode.prototype.ruleContains = function (v, vt, index) { + index = index || 1; + setT('cont', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleMatchesRegex = function (v, vt, index) { + index = index || 1; + setT('regex', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleIsTrue = function (index) { + index = index || 1; + setT('true', index); +} + +switchNode.prototype.ruleIsFalse = function (index) { + index = index || 1; + setT('false', index); +} + +switchNode.prototype.ruleIsNull = function (index) { + index = index || 1; + setT('null', index); +} + +switchNode.prototype.ruleIsNotNull = function (index) { + index = index || 1; + setT('nnull', index); +} + +switchNode.prototype.ruleIsOfType = function (t, index) { + index = index || 1; + setT('istype', index); + + var tType = { + "str": 1, + "num": 2, + "boolean": 3, + "array": 4, + "buffer": 5, + "object": 6, + "json": 7, + "undefined": 8, + "null": 9 + }; + + if (t) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + tType[t] + ']'); + } +} + +switchNode.prototype.ruleIsEmpty = function (index) { + index = index || 1; + setT('empty', index); +} + +switchNode.prototype.ruleIsNotEmpty = function (index) { + index = index || 1; + setT('nempty', index); +} + +switchNode.prototype.ruleHead = function (v, vt, index) { + index = index || 1; + setT('head', index); + setSequenceV(v, vt, index); +} + +switchNode.prototype.ruleIndexBetween = function (v, vt, v2, v2t, index) { + index = index || 1; + setT('index', index); + setBetweenV(v, vt, v2, v2t, index); +} + +switchNode.prototype.ruleTail = function (v, vt, index) { + index = index || 1; + setT('tail', index); + setSequenceV(v, vt, index); +} + +switchNode.prototype.ruleJSONataExp = function (v, index) { + index = index || 1; + setT('jsonata_exp', index); + if (v) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/div[1]/input', v); + } +} + +switchNode.prototype.ruleOtherwise = function (index) { + index = index || 1; + setT('else', index); +} + +switchNode.prototype.addRule = function () { + browser.clickWithWait('//div[contains(@class, "red-ui-editableList")]/a'); +} + +module.exports = switchNode; diff --git a/test/editor/pageobjects/nodes/core/function/15-change_page.js b/test/editor/pageobjects/nodes/core/function/15-change_page.js index 8e72afe38..eb26f48aa 100644 --- a/test/editor/pageobjects/nodes/core/function/15-change_page.js +++ b/test/editor/pageobjects/nodes/core/function/15-change_page.js @@ -14,9 +14,9 @@ * limitations under the License. **/ -var util = require("util"); +var util = require('util'); -var nodePage = require("../../node_page"); +var nodePage = require('../../node_page'); function changeNode(id) { nodePage.call(this, id); @@ -51,41 +51,82 @@ function setT(t, index) { // It is better to create a function whose input value is the object type in the future, changeNode.prototype.ruleSet = function (p, pt, to, tot, index) { index = index || 1; - setT("set", index); + setT('set', index); if (pt) { browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); - var num = 5 * (index - 1) + 1; - var ptXPath = '//div[contains(@class, "red-ui-typedInput-options")][' + num + ']/a[' + ptType[pt] + ']'; - browser.clickWithWait(ptXPath); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']'); } if (p) { browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); } if (tot) { browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/button[1]'); - var num = 5 * (index - 1) + 2; - var totXPath = '//div[contains(@class, "red-ui-typedInput-options")][' + num + ']/a[' + totType[tot] + ']'; - browser.clickWithWait(totXPath); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[tot] + ']'); } if (to) { browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/div/input', to); } } -changeNode.prototype.ruleDelete = function (index) { +changeNode.prototype.ruleChange = function (p, pt, from, fromt, to, tot, index) { index = index || 1; - setT("delete", index); + setT('change', index); + if (pt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']'); + } + if (p) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); + } + if (fromt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[1]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[pt] + ']'); + } + if (from) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[1]/div[2]/div[1]/input', from); + } + if (tot) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[2]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[pt] + ']'); + } + if (to) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[2]/div[2]/div[1]/input', to); + } } -changeNode.prototype.ruleMove = function (p, to, index) { +changeNode.prototype.ruleDelete = function (p, pt, index) { index = index || 1; - setT("move", index); - browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); - browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/div/input', to); + setT('delete', index); + if (pt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']'); + } + if (p) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); + } +} + +changeNode.prototype.ruleMove = function (p, pt, to, tot, index) { + index = index || 1; + setT('move', index); + if (pt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']'); + } + if (p) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); + } + if (tot) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[pt] + ']'); + } + if (to) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/div/input', to); + } } changeNode.prototype.addRule = function () { - browser.clickWithWait('//*[@id="dialog-form"]/div[3]/div/a'); + browser.clickWithWait('//div[contains(@class, "red-ui-editableList")]/a'); } module.exports = changeNode; diff --git a/test/editor/pageobjects/nodes/core/network/10-mqttin_page.js b/test/editor/pageobjects/nodes/core/function/89-delay_page.js similarity index 71% rename from test/editor/pageobjects/nodes/core/network/10-mqttin_page.js rename to test/editor/pageobjects/nodes/core/function/89-delay_page.js index 31b909116..3604beb67 100644 --- a/test/editor/pageobjects/nodes/core/network/10-mqttin_page.js +++ b/test/editor/pageobjects/nodes/core/function/89-delay_page.js @@ -18,18 +18,14 @@ var util = require("util"); var nodePage = require("../../node_page"); -function mqttInNode(id) { +function delayNode(id) { nodePage.call(this, id); } -util.inherits(mqttInNode, nodePage); +util.inherits(delayNode, nodePage); -mqttInNode.prototype.setTopic = function (topic) { - browser.setValue('#node-input-topic', topic); +delayNode.prototype.setTimeout = function (timeout) { + browser.setValue('//*[@id="node-input-timeout"]', timeout); } -mqttInNode.prototype.setQoS = function (qos) { - browser.selectWithWait('#node-input-qos', qos); -} - -module.exports = mqttInNode; +module.exports = delayNode; diff --git a/test/editor/pageobjects/nodes/core/function/89-trigger_page.js b/test/editor/pageobjects/nodes/core/function/89-trigger_page.js new file mode 100644 index 000000000..5d24de380 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/function/89-trigger_page.js @@ -0,0 +1,83 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); + +var nodePage = require("../../node_page"); + +function triggerNode(id) { + nodePage.call(this, id); +} + +util.inherits(triggerNode, nodePage); + +triggerNode.prototype.setSend = function (send, sendt) { + var sendType = { + "flow": 1, + "global": 2, + "str": 3, + "num": 4, + "bool": 5, + "json": 6, + "bin": 7, + "date": 8, + "env": 9, + "pay": 10, + "nul": 11 + }; + + if (sendt) { + browser.clickWithWait('//*[@id="dialog-form"]/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + sendType[sendt] + ']'); + } + if (send) { + browser.setValue('//*[@id="dialog-form"]/div[1]/div/div[1]/input', send); + } +} + +triggerNode.prototype.setDuration = function (duration, units) { + browser.setValue('//*[@id="node-input-duration"]', duration); + if (units) { + browser.selectWithWait('//*[@id="node-input-units"]', units); + } +} + +triggerNode.prototype.setThenSend = function (thenSend, thenSendt) { + var thenSendType = { + "flow": 1, + "global": 2, + "str": 3, + "num": 4, + "bool": 5, + "json": 6, + "bin": 7, + "date": 8, + "env": 9, + "pay": 10, + "payl": 11, + "nul": 12 + }; + + if (thenSendt) { + browser.clickWithWait('//*[@id="dialog-form"]/div[5]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + thenSendType[thenSendt] + ']'); + } + if (thenSend) { + browser.setValue('//*[@id="dialog-form"]/div[5]/div/div[1]/input', thenSend); + } +} + +module.exports = triggerNode; diff --git a/test/editor/pageobjects/nodes/core/network/10-mqttout_page.js b/test/editor/pageobjects/nodes/core/function/90-exec_page.js similarity index 66% rename from test/editor/pageobjects/nodes/core/network/10-mqttout_page.js rename to test/editor/pageobjects/nodes/core/function/90-exec_page.js index 783d87b55..69b8b6c9a 100644 --- a/test/editor/pageobjects/nodes/core/network/10-mqttout_page.js +++ b/test/editor/pageobjects/nodes/core/function/90-exec_page.js @@ -18,18 +18,20 @@ var util = require("util"); var nodePage = require("../../node_page"); -function mqttOutNode(id) { +function execNode(id) { nodePage.call(this, id); } -util.inherits(mqttOutNode, nodePage); +util.inherits(execNode, nodePage); -mqttOutNode.prototype.setTopic = function(topic) { - browser.setValue('#node-input-topic', topic); +execNode.prototype.setCommand = function (command) { + browser.setValue('//*[@id="node-input-command"]', command); } -mqttOutNode.prototype.setRetain = function (retain) { - browser.selectWithWait('#node-input-retain', retain); +execNode.prototype.setAppend = function (checkbox) { + if (browser.isSelected('#node-input-addpay') !== checkbox) { + browser.click('#node-input-addpay'); + } } -module.exports = mqttOutNode; \ No newline at end of file +module.exports = execNode; diff --git a/test/editor/pageobjects/nodes/core/network/10-mqttconfig_page.js b/test/editor/pageobjects/nodes/core/network/10-mqtt_page.js similarity index 52% rename from test/editor/pageobjects/nodes/core/network/10-mqttconfig_page.js rename to test/editor/pageobjects/nodes/core/network/10-mqtt_page.js index c7cdc90c5..4bdd92336 100644 --- a/test/editor/pageobjects/nodes/core/network/10-mqttconfig_page.js +++ b/test/editor/pageobjects/nodes/core/network/10-mqtt_page.js @@ -14,27 +14,61 @@ * limitations under the License. **/ -function setServer(broker, port) { +var util = require("util"); + +var nodePage = require("../../node_page"); + +var mqttBrokerNode = {}; + +mqttBrokerNode.setServer = function (broker, port) { browser.setValue('#node-config-input-broker', broker); port = port ? port : 1883; browser.setValue('#node-config-input-port', port); -} +}; -function clickOk() { +mqttBrokerNode.clickOk = function () { browser.clickWithWait('#node-config-dialog-ok'); // Wait until an config dialog closes. browser.waitForVisible('#node-config-dialog-ok', 10000, true); -} +}; -function edit() { +mqttBrokerNode.edit = function () { browser.waitForVisible('#node-input-lookup-broker'); browser.click('#node-input-lookup-broker'); // Wait until a config dialog opens. browser.waitForVisible('#node-config-dialog-ok', 10000); +}; + +function mqttInNode(id) { + nodePage.call(this, id); } -module.exports = { - setServer: setServer, - clickOk: clickOk, - edit: edit +util.inherits(mqttInNode, nodePage); + +mqttInNode.prototype.setTopic = function (topic) { + browser.setValue('#node-input-topic', topic); }; + +mqttInNode.prototype.setQoS = function (qos) { + browser.selectWithWait('#node-input-qos', qos); +}; + +mqttInNode.prototype.mqttBrokerNode = mqttBrokerNode; +module.exports.mqttInNode = mqttInNode; + +function mqttOutNode(id) { + nodePage.call(this, id); +} + +util.inherits(mqttOutNode, nodePage); + +mqttOutNode.prototype.setTopic = function (topic) { + browser.setValue('#node-input-topic', topic); +}; + +mqttOutNode.prototype.setRetain = function (retain) { + browser.selectWithWait('#node-input-retain', retain); +}; + +mqttOutNode.prototype.mqttBrokerNode = mqttBrokerNode; +module.exports.mqttOutNode = mqttOutNode; diff --git a/test/editor/pageobjects/nodes/core/network/22-websocket_page.js b/test/editor/pageobjects/nodes/core/network/22-websocket_page.js new file mode 100644 index 000000000..8f7dc261e --- /dev/null +++ b/test/editor/pageobjects/nodes/core/network/22-websocket_page.js @@ -0,0 +1,93 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); + +var nodePage = require("../../node_page"); + +var websocketListenerNode = {}; + +websocketListenerNode.setPath = function (path) { + browser.setValue('#node-config-input-path', path); +}; + +websocketListenerNode.setSendReceive = function (wholemsg) { + browser.selectWithWait('#node-config-input-wholemsg', wholemsg); +}; + +websocketListenerNode.clickOk = function () { + browser.clickWithWait('#node-config-dialog-ok'); + // Wait until an config dialog closes. + browser.waitForVisible('#node-config-dialog-ok', 10000, true); +}; + +websocketListenerNode.edit = function () { + browser.waitForVisible('#node-input-lookup-server'); + browser.click('#node-input-lookup-server'); + // Wait until a config dialog opens. + browser.waitForVisible('#node-config-dialog-ok', 10000); +}; + +var websocketClientNode = {}; + +websocketClientNode.setPath = function (path) { + browser.setValue('#node-config-input-path', path); +}; + +websocketClientNode.setSendReceive = function (wholemsg) { + browser.selectWithWait('#node-config-input-wholemsg', wholemsg); +}; + +websocketClientNode.clickOk = function () { + browser.clickWithWait('#node-config-dialog-ok'); + // Wait until an config dialog closes. + browser.waitForVisible('#node-config-dialog-ok', 10000, true); +}; + +websocketClientNode.edit = function () { + browser.waitForVisible('#node-input-lookup-client'); + browser.click('#node-input-lookup-client'); + // Wait until a config dialog opens. + browser.waitForVisible('#node-config-dialog-ok', 10000); +}; + +function websocketInNode(id) { + nodePage.call(this, id); +} + +util.inherits(websocketInNode, nodePage); + +websocketInNode.prototype.setType = function (type) { + browser.selectWithWait('#node-input-mode', type); +}; + +websocketInNode.prototype.websocketListenerNode = websocketListenerNode; +websocketInNode.prototype.websocketClientNode = websocketClientNode; +module.exports.websocketInNode = websocketInNode; + +function websocketOutNode(id) { + nodePage.call(this, id); +} + +util.inherits(websocketOutNode, nodePage); + +websocketOutNode.prototype.setType = function (type) { + browser.selectWithWait('#node-input-mode', type); +}; + +websocketOutNode.prototype.websocketListenerNode = websocketListenerNode; +websocketOutNode.prototype.websocketClientNode = websocketClientNode; +module.exports.websocketOutNode = websocketOutNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js b/test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js new file mode 100644 index 000000000..e4bc9502c --- /dev/null +++ b/test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js @@ -0,0 +1,51 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function csvNode(id) { + nodePage.call(this, id); +} + +util.inherits(csvNode, nodePage); + +csvNode.prototype.setColumns = function (columns) { + browser.setValue('#node-input-temp', columns); +} + +csvNode.prototype.setSkipLines = function (skip) { + browser.setValue('#node-input-skip', skip); +} + +csvNode.prototype.setFirstRow4Names = function (checkbox) { + if (browser.isSelected('#node-input-hdrin') !== checkbox) { + browser.click('#node-input-hdrin'); + } +} + +csvNode.prototype.setOutput = function (output) { + browser.selectWithWait('#node-input-multi', output); +} + +csvNode.prototype.setIncludeRow = function (checkbox) { + if (browser.isSelected('#node-input-hdrout') !== checkbox) { + browser.click('#node-input-hdrout'); + } +} + +module.exports = csvNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js index 9c89bc8e2..243e4c905 100644 --- a/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js +++ b/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js @@ -14,9 +14,9 @@ * limitations under the License. **/ -var util = require("util"); +var util = require('util'); -var nodePage = require("../../node_page"); +var nodePage = require('../../node_page'); function htmlNode(id) { nodePage.call(this, id); @@ -24,7 +24,7 @@ function htmlNode(id) { util.inherits(htmlNode, nodePage); -htmlNode.prototype.setSelector = function(tag) { +htmlNode.prototype.setSelector = function (tag) { browser.setValue('#node-input-tag', tag); } diff --git a/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js index 10a7e648f..e0b31dd36 100644 --- a/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js +++ b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js @@ -14,9 +14,9 @@ * limitations under the License. **/ -var util = require("util"); +var util = require('util'); -var nodePage = require("../../node_page"); +var nodePage = require('../../node_page'); function jsonNode(id) { nodePage.call(this, id); @@ -28,8 +28,8 @@ jsonNode.prototype.setAction = function (action) { browser.setValue('node-input-action', action); } -jsonNode.prototype.setProperty = function(property) { - browser.setValue('//*[@id="dialog-form"]/div[2]/div/div/input', property); +jsonNode.prototype.setProperty = function (property) { + browser.setValue('//*[contains(@class, "red-ui-typedInput-container")]/div[1]/input', property); } module.exports = jsonNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js new file mode 100644 index 000000000..696ec59cb --- /dev/null +++ b/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js @@ -0,0 +1,35 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function xmlNode(id) { + nodePage.call(this, id); +} + +util.inherits(xmlNode, nodePage); + +xmlNode.prototype.setAction = function (action) { + browser.setValue('node-input-action', action); +} + +xmlNode.prototype.setProperty = function (property) { + browser.setValue('//*[contains(@class, "red-ui-typedInput-container")]/div[1]/input', property); +} + +module.exports = xmlNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js new file mode 100644 index 000000000..1002f3eb4 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js @@ -0,0 +1,35 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function yamlNode(id) { + nodePage.call(this, id); +} + +util.inherits(yamlNode, nodePage); + +yamlNode.prototype.setAction = function (action) { + browser.setValue('node-input-action', action); +} + +yamlNode.prototype.setProperty = function (property) { + browser.setValue('//*[contains(@class, "red-ui-typedInput-container")]/div[1]/input', property); +} + +module.exports = yamlNode; diff --git a/test/editor/pageobjects/nodes/core/sequence/17-split_page.js b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js new file mode 100644 index 000000000..8fc32ab1e --- /dev/null +++ b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js @@ -0,0 +1,47 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function splitNode(id) { + nodePage.call(this, id); +} + +util.inherits(splitNode, nodePage); + +splitNode.prototype.setSplitUsing = function (splt, spltt) { + var spltType = { + "str": 1, + "bin": 2, + "len": 3 + }; + + browser.clickWithWait('//*[@id="dialog-form"]/div[3]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + spltType[spltt] + ']'); + browser.setValue('//*[@id="dialog-form"]/div[3]/div/div[1]/input', splt); +}; + +module.exports.splitNode = splitNode; + +function joinNode(id) { + nodePage.call(this, id); +} + +util.inherits(joinNode, nodePage); + +module.exports.joinNode = joinNode; diff --git a/test/editor/pageobjects/nodes/core/sequence/19-batch_page.js b/test/editor/pageobjects/nodes/core/sequence/19-batch_page.js new file mode 100644 index 000000000..0d7b13028 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/sequence/19-batch_page.js @@ -0,0 +1,39 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function batchNode(id) { + nodePage.call(this, id); +} + +batchNode.prototype.setMode = function (mode) { + browser.selectWithWait('#node-input-mode', mode); +} + +batchNode.prototype.setCount = function (count) { + browser.setValue('#node-input-count', count); +} + +batchNode.prototype.setOverlap = function (overlap) { + browser.setValue('#node-input-overlap', overlap); +} + +util.inherits(batchNode, nodePage); + +module.exports = batchNode; diff --git a/test/editor/pageobjects/nodes/node_page.js b/test/editor/pageobjects/nodes/node_page.js index 5250250e7..03e734cab 100644 --- a/test/editor/pageobjects/nodes/node_page.js +++ b/test/editor/pageobjects/nodes/node_page.js @@ -35,10 +35,11 @@ Node.prototype.clickOk = function () { browser.pause(50); } -Node.prototype.connect = function (targetNode) { - var outputPort = this.id + '/*[@class="red-ui-flow-port-output"]'; +Node.prototype.connect = function (targetNode, port) { + port = port || 1; + var outputPort = this.id + '/*[@class="red-ui-flow-port-output"][' + port + ']'; var inputPort = targetNode.id + '/*[@class="red-ui-flow-port-input"]'; - browser.dragAndDrop(outputPort, inputPort) + browser.dragAndDrop(outputPort, inputPort); } Node.prototype.clickLeftButton = function () { diff --git a/test/editor/pageobjects/nodes/nodefactory_page.js b/test/editor/pageobjects/nodes/nodefactory_page.js index 744c1570d..008ecc625 100644 --- a/test/editor/pageobjects/nodes/nodefactory_page.js +++ b/test/editor/pageobjects/nodes/nodefactory_page.js @@ -16,37 +16,70 @@ var injectNode = require('./core/common/20-inject_page'); var debugNode = require('./core/common/21-debug_page'); +var completeNode = require('./core/common/24-complete_page'); +var catchNode = require('./core/common/25-catch_page'); +var statusNode = require('./core/common/25-status_page'); +var commentNode = require('./core/common/90-comment_page'); var functionNode = require('./core/function/10-function_page'); +var switchNode = require('./core/function/10-switch_page'); var changeNode = require('./core/function/15-change_page'); var rangeNode = require('./core/function/16-range_page'); var templateNode = require('./core/function/80-template_page'); -var mqttInNode = require('./core/network/10-mqttin_page'); -var mqttOutNode = require('./core/network/10-mqttout_page'); +var delayNode = require('./core/function/89-delay_page'); +var triggerNode = require('./core/function/89-trigger_page'); +var execNode = require('./core/function/90-exec_page'); +var mqttInNode = require('./core/network/10-mqtt_page').mqttInNode; +var mqttOutNode = require('./core/network/10-mqtt_page').mqttOutNode; var httpInNode = require('./core/network/21-httpin_page'); var httpResponseNode = require('./core/network/21-httpresponse_page'); var httpRequestNode = require('./core/network/21-httprequest_page'); +var websocketInNode = require('./core/network/22-websocket_page').websocketInNode; +var websocketOutNode = require('./core/network/22-websocket_page').websocketOutNode; +var splitNode = require('./core/sequence/17-split_page').splitNode; +var joinNode = require('./core/sequence/17-split_page').joinNode; +var batchNode = require('./core/sequence/19-batch_page'); +var csvNode = require('./core/parsers/70-CSV_page'); var htmlNode = require('./core/parsers/70-HTML_page'); var jsonNode = require('./core/parsers/70-JSON_page'); +var xmlNode = require('./core/parsers/70-XML_page'); +var yamlNode = require('./core/parsers/70-YAML_page'); var fileInNode = require('./core/storage/10-filein_page'); var nodeCatalog = { // common "inject": injectNode, "debug": debugNode, + "complete": completeNode, + "catch": catchNode, + "status": statusNode, + "comment": commentNode, // function "function": functionNode, + "switch": switchNode, "change": changeNode, "range": rangeNode, "template": templateNode, + "delay": delayNode, + "trigger": triggerNode, + "exec": execNode, // network "mqttIn": mqttInNode, "mqttOut": mqttOutNode, "httpIn": httpInNode, "httpResponse": httpResponseNode, "httpRequest": httpRequestNode, + "websocketIn": websocketInNode, + "websocketOut": websocketOutNode, + // sequence + "split": splitNode, + "join": joinNode, + "batch": batchNode, // parser + "csv": csvNode, "html": htmlNode, "json": jsonNode, + "xml": xmlNode, + "yaml": yamlNode, // storage "fileIn": fileInNode }; diff --git a/test/editor/pageobjects/util/util_page.js b/test/editor/pageobjects/util/util_page.js index 02508c831..3a764eb93 100644 --- a/test/editor/pageobjects/util/util_page.js +++ b/test/editor/pageobjects/util/util_page.js @@ -70,7 +70,7 @@ function init() { var ret = repeatUntilSuccess(function(args) { return browser.selectByValue(args[0], args[1]); - }, [selector, value]); + }, [selector, value.toString()]); return ret; } catch (e) { console.trace(); diff --git a/test/editor/specs/scenario/cookbook_dataformats_uispec.js b/test/editor/specs/scenario/cookbook_dataformats_uispec.js new file mode 100644 index 000000000..052481e91 --- /dev/null +++ b/test/editor/specs/scenario/cookbook_dataformats_uispec.js @@ -0,0 +1,364 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('working with data formats', function () { + it('convert to/from JSON', function () { + var injectNode1 = workspace.addNode('inject'); + var jsonNode1 = workspace.addNode('json'); + var debugNode1 = workspace.addNode('debug'); + + injectNode1.edit(); + injectNode1.setPayload('str', '{"a":1}'); + injectNode1.clickOk(); + + jsonNode1.edit(); + jsonNode1.setProperty('payload'); + jsonNode1.clickOk(); + + injectNode1.connect(jsonNode1); + jsonNode1.connect(debugNode1); + + var injectNode2 = workspace.addNode('inject'); + var jsonNode2 = workspace.addNode('json'); + var debugNode2 = workspace.addNode('debug'); + + injectNode2.edit(); + injectNode2.setPayload('json', '{"a":1}'); + injectNode2.clickOk(); + + jsonNode2.edit(); + jsonNode2.setProperty('payload'); + jsonNode2.clickOk(); + + injectNode2.connect(jsonNode2); + jsonNode2.connect(debugNode2); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage().should.eql('1'); + debugTab.clearMessage(); + injectNode2.clickLeftButton(); + debugTab.getMessage().should.eql('"{"a":1}"'); + }); + + it('convert to/from XML', function () { + var injectNode1 = workspace.addNode('inject', 0); + var templateNode1 = workspace.addNode('template', 200); + var xmlNode1 = workspace.addNode('xml', 400); + var debugNode1 = workspace.addNode('debug', 600); + + injectNode1.edit(); + injectNode1.setPayload('str', '{"a":1}'); + injectNode1.clickOk(); + + templateNode1.edit(); + templateNode1.setFormat('text'); + templateNode1.setSyntax('plain'); + templateNode1.setTemplate('' + + ' Nick' + + ' Dave' + + ' Reminder' + + ' Update the website' + + ''); + templateNode1.clickOk(); + + xmlNode1.edit(); + xmlNode1.setProperty('payload'); + xmlNode1.clickOk(); + + injectNode1.connect(templateNode1); + templateNode1.connect(xmlNode1); + xmlNode1.connect(debugNode1); + + var injectNode2 = workspace.addNode('inject'); + var xmlNode2 = workspace.addNode('xml'); + var debugNode2 = workspace.addNode('debug'); + + injectNode2.edit(); + injectNode2.setPayload('json', '{' + + ' "note": {' + + ' "$": { "priority": "high" },' + + ' "to": [ "Nick" ],' + + ' "from": [ "Dave" ],' + + ' "heading": [ "Reminder" ],' + + ' "body": [ "Update the website" ]' + + ' }' + + '}'); + injectNode2.clickOk(); + + xmlNode2.edit(); + xmlNode2.setProperty('payload'); + xmlNode2.clickOk(); + + injectNode2.connect(xmlNode2); + xmlNode2.connect(debugNode2); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage().should.eql('object'); + debugTab.clearMessage(); + injectNode2.clickLeftButton(); + debugTab.getMessage().should.eql('"' + + '' + + 'Nick' + + 'Dave' + + 'Reminder' + + 'Update the website' + + '"'); + }); + + it('convert to/from YAML', function () { + var injectNode1 = workspace.addNode('inject', 0); + var templateNode1 = workspace.addNode('template', 200); + var yamlNode1 = workspace.addNode('yaml', 400); + var debugNode1 = workspace.addNode('debug', 600); + + injectNode1.edit(); + injectNode1.setPayload('str', '{"a":1}'); + injectNode1.clickOk(); + + templateNode1.edit(); + templateNode1.setFormat('yaml'); + templateNode1.setSyntax('plain'); + templateNode1.setTemplate('a: 1\n' + + 'b:\n' + + ' - 1\n' + + '- 2\n' + + '- 3'); + templateNode1.clickOk(); + + yamlNode1.edit(); + yamlNode1.setProperty('payload'); + yamlNode1.clickOk(); + + injectNode1.connect(templateNode1); + templateNode1.connect(yamlNode1); + yamlNode1.connect(debugNode1); + + var injectNode2 = workspace.addNode('inject'); + var yamlNode2 = workspace.addNode('yaml'); + var debugNode2 = workspace.addNode('debug'); + + injectNode2.edit(); + injectNode2.setPayload('json', '{"a":1, "b":[1,2,3]}'); + injectNode2.clickOk(); + + yamlNode2.edit(); + yamlNode2.setProperty('payload'); + yamlNode2.clickOk(); + + injectNode2.connect(yamlNode2); + yamlNode2.connect(debugNode2); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage().should.eql([ '1', 'array[3]' ]); + debugTab.clearMessage(); + injectNode2.clickLeftButton(); + debugTab.getMessage().should.eql('"a: 1↵b:↵ - 1↵ - 2↵ - 3↵"'); + }); + + it('generate CSV output', function () { + var injectNode1 = workspace.addNode('inject', 0); + var changeNode1 = workspace.addNode('change', 200); + var csvNode1 = workspace.addNode('csv', 400); + var debugNode1 = workspace.addNode('debug', 600); + + changeNode1.edit(); + changeNode1.ruleSet('payload', 'msg', '{' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + '}', 'jsonata'); + changeNode1.clickOk(); + + csvNode1.edit(); + csvNode1.setColumns('a,b,c'); + csvNode1.clickOk(); + + injectNode1.connect(changeNode1); + changeNode1.connect(csvNode1); + csvNode1.connect(debugNode1); + + var injectNode2 = workspace.addNode('inject', 0, 80); + var changeNode2 = workspace.addNode('change', 200, 80); + var csvNode2 = workspace.addNode('csv', 400, 80); + var debugNode2 = workspace.addNode('debug', 600, 80); + + changeNode2.edit(); + changeNode2.ruleSet('payload', 'msg', '[' + + ' {' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + ' }, {' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + ' }, {' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + ' }, {' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + ' }' + + ']', 'jsonata'); + changeNode2.clickOk(); + + csvNode2.edit(); + csvNode2.setColumns('a,b,c'); + csvNode2.setIncludeRow(true); + csvNode2.clickOk(); + + injectNode2.connect(changeNode2); + changeNode2.connect(csvNode2); + csvNode2.connect(debugNode2); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage().should.match(/^"([1-9]?[0-9],){2}[1-9]?[0-9]↵"$/); + debugTab.clearMessage(); + injectNode2.clickLeftButton(); + debugTab.getMessage().should.match(/^"a,b,c↵(([1-9]?[0-9],){2}[1-9]?[0-9]↵){4}"$/); + }); + + it('parse CSV input', function () { + var injectNode = workspace.addNode('inject'); + var templateNode = workspace.addNode('template'); + var csvNode = workspace.addNode('csv'); + var debugNode = workspace.addNode('debug'); + + templateNode.edit(); + templateNode.setFormat('handlebars'); + templateNode.setSyntax('mustache'); + templateNode.setTemplate('# This is some random data\n' + + 'a,b,c\n' + + '80,18,2\n' + + '52,36,10\n' + + '91,18,61\n' + + '32,47,65'); + templateNode.clickOk(); + + csvNode.edit(); + csvNode.setSkipLines(1); + csvNode.setFirstRow4Names(true); + csvNode.setOutput('mult'); + csvNode.clickOk(); + + injectNode.connect(templateNode); + templateNode.connect(csvNode); + csvNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql([ 'object', 'object', 'object', 'object' ]); + }); + + it('simple GET request', function () { + var injectNode = workspace.addNode('inject'); + var httpRequestNode = workspace.addNode('httpRequest'); + var htmlNode = workspace.addNode('html'); + var debugNode = workspace.addNode('debug'); + + httpRequestNode.edit(); + httpRequestNode.setMethod('GET'); + httpRequestNode.setUrl('https://nodered.org'); + httpRequestNode.clickOk(); + + htmlNode.edit(); + htmlNode.setSelector('.node-red-latest-version'); + htmlNode.clickOk(); + + injectNode.connect(httpRequestNode); + httpRequestNode.connect(htmlNode); + htmlNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.match(/^"v[0-9]+\.[0-9]+\.[0-9]"$/); + }); + + it('split text into one message per line', function () { + var injectNode = workspace.addNode('inject'); + var templateNode = workspace.addNode('template'); + var splitNode = workspace.addNode('split'); + var changeNode = workspace.addNode('change'); + var joinNode = workspace.addNode('join'); + var debugNode = workspace.addNode('debug'); + + templateNode.edit(); + templateNode.setFormat('handlebars'); + templateNode.setSyntax('mustache'); + templateNode.setTemplate('one\ntwo\nthree\nfour\nfive'); + templateNode.clickOk(); + + changeNode.edit(); + changeNode.ruleSet('payload', 'msg', '(parts.index+1) & ": " & payload', 'jsonata'); + changeNode.clickOk(); + + injectNode.connect(templateNode); + templateNode.connect(splitNode); + splitNode.connect(changeNode); + changeNode.connect(joinNode); + joinNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"1: one↵2: two↵3: three↵4: four↵5: five"'); + }); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_errorhandling_uispec.js b/test/editor/specs/scenario/cookbook_errorhandling_uispec.js new file mode 100644 index 000000000..5285c5c0f --- /dev/null +++ b/test/editor/specs/scenario/cookbook_errorhandling_uispec.js @@ -0,0 +1,74 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('messages', function () { + it('trigger a flow when a node throws an error', function () { + var injectNode = workspace.addNode('inject'); + var functionNode = workspace.addNode('function'); + var catchNode = workspace.addNode('catch', 0 , 80); + var debugNode = workspace.addNode('debug'); + + functionNode.edit(); + functionNode.setFunction('node.error("an example error", msg);'); + functionNode.clickOk(); + + catchNode.edit(); + catchNode.setScope(functionNode); + catchNode.clickOk(); + + debugNode.edit(); + debugNode.setOutput('error'); + debugNode.clickOk(); + + injectNode.connect(functionNode); + catchNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql(['"an example error"', 'function']); + }); + + // skip this case since the flow outputs random results. + it.skip('automatically retry an action after an error'); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_flowcontrol_uispec.js b/test/editor/specs/scenario/cookbook_flowcontrol_uispec.js new file mode 100644 index 000000000..724d1c56f --- /dev/null +++ b/test/editor/specs/scenario/cookbook_flowcontrol_uispec.js @@ -0,0 +1,81 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('flow control', function () { + it('trigger a flow whenever Node-RED starts', function () { + var injectNode = workspace.addNode('inject'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', 'Started!'); + injectNode.setOnce(true); + injectNode.clickOk(); + injectNode.connect(debugNode); + + debugTab.open(); + workspace.deploy(); + debugTab.getMessage().should.eql('"Started!"'); + }); + + it('trigger a flow at regular intervals', function () { + var injectNode = workspace.addNode('inject'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setRepeat('interval'); + injectNode.setRepeatInterval(1); + injectNode.clickOk(); + injectNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + specUtil.pause(1000); + var t1 = Number(debugTab.getMessage(1)); + t1.should.within(1500000000000, 3000000000000); + specUtil.pause(1000); + debugTab.getMessage(2).should.within(t1 + 900, 3000000000000); + }); + + // skip this case since it needs up to one minite. + it.skip('trigger a flow at a specific time'); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_endpoint_uispec.js b/test/editor/specs/scenario/cookbook_httpendpoints_uispec.js similarity index 98% rename from test/editor/specs/scenario/cookbook_endpoint_uispec.js rename to test/editor/specs/scenario/cookbook_httpendpoints_uispec.js index 9a2ed0710..134498370 100644 --- a/test/editor/specs/scenario/cookbook_endpoint_uispec.js +++ b/test/editor/specs/scenario/cookbook_httpendpoints_uispec.js @@ -23,16 +23,16 @@ var workspace = require('../../pageobjects/editor/workspace_page'); var httpNodeRoot = "/api"; // https://cookbook.nodered.org/ -describe('cookbook', function() { - beforeEach(function() { +describe('cookbook', function () { + beforeEach(function () { workspace.init(); }); - before(function() { + before(function () { helper.startServer(); }); - after(function() { + after(function () { helper.stopServer(); }); @@ -359,7 +359,7 @@ describe('cookbook', function() { debugTab.getMessage().indexOf('Text file').should.not.eql(-1); }); - it('post raw data to a flow', function() { + it('post raw data to a flow', function () { var httpInNode = workspace.addNode("httpIn"); var templateNode = workspace.addNode("template"); var httpResponseNode = workspace.addNode("httpResponse"); @@ -383,7 +383,7 @@ describe('cookbook', function() { var httpRequestNode = workspace.addNode("httpRequest"); var debugNode = workspace.addNode("debug"); - injectNode.edit() + injectNode.edit(); injectNode.setPayload("str", "Nick"); injectNode.clickOk(); @@ -427,7 +427,7 @@ describe('cookbook', function() { var httpRequestNode = workspace.addNode("httpRequest"); var debugNode = workspace.addNode("debug"); - injectNode.edit() + injectNode.edit(); injectNode.setPayload("str", "name=Nick"); injectNode.clickOk(); @@ -451,7 +451,7 @@ describe('cookbook', function() { debugTab.getMessage().indexOf('Hello Nick!').should.not.eql(-1); }); - it('post JSON data to a flow', function() { + it('post JSON data to a flow', function () { var httpInNode = workspace.addNode("httpIn"); var templateNode = workspace.addNode("template"); var httpResponseNode = workspace.addNode("httpResponse"); @@ -476,7 +476,7 @@ describe('cookbook', function() { var httpRequestNode = workspace.addNode("httpRequest"); var debugNode = workspace.addNode("debug"); - injectNode.edit() + injectNode.edit(); injectNode.setPayload("json", '{"name":"Nick"}'); injectNode.clickOk(); diff --git a/test/editor/specs/scenario/cookbook_httprequests_uispec.js b/test/editor/specs/scenario/cookbook_httprequests_uispec.js new file mode 100644 index 000000000..797b06041 --- /dev/null +++ b/test/editor/specs/scenario/cookbook_httprequests_uispec.js @@ -0,0 +1,300 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('HTTP requests', function () { + it('simple get request', function () { + var injectNode = workspace.addNode('inject'); + var httpRequestNode = workspace.addNode('httpRequest'); + var htmlNode = workspace.addNode('html'); + var debugNode = workspace.addNode('debug'); + + httpRequestNode.edit(); + httpRequestNode.setMethod('GET'); + httpRequestNode.setUrl(helper.url()); + httpRequestNode.clickOk(); + + htmlNode.edit(); + htmlNode.setSelector('title'); + htmlNode.clickOk(); + + injectNode.connect(httpRequestNode); + httpRequestNode.connect(htmlNode); + htmlNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Node-RED"'); + }); + + it('set the URL of a request', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', helper.url()); + injectNode.clickOk(); + + changeNode.edit(); + changeNode.ruleSet('url', 'msg', 'payload', 'msg'); + changeNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.containEql('Node-RED'); + }); + + it('set the URL of a request using a template', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', 'settings'); + injectNode.clickOk(); + + changeNode.edit(); + changeNode.ruleSet('query', 'msg', 'payload', 'msg'); + changeNode.clickOk(); + + httpRequestNode.edit(); + httpRequestNode.setUrl(helper.url() + '/{{{query}}}'); + httpRequestNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.containEql('httpNodeRoot'); + }); + + it('set the query string parameters', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', 'Nick'); + injectNode.clickOk(); + + changeNode.edit(); + changeNode.ruleSet('query', 'msg', 'payload', 'msg'); + changeNode.clickOk(); + + httpRequestNode.edit(); + httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/set-query?q={{{query}}}'); + httpRequestNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + // The code for confirmation starts from here. + var httpInNode = workspace.addNode('httpIn', 0, 200); + var templateNode = workspace.addNode('template'); + var httpResponseNode = workspace.addNode('httpResponse'); + + httpInNode.edit(); + httpInNode.setMethod('get'); + httpInNode.setUrl('/set-query'); + httpInNode.clickOk(); + + templateNode.edit(); + templateNode.setSyntax('mustache'); + templateNode.setFormat('handlebars'); + templateNode.setTemplate('Hello {{req.query.q}}'); + templateNode.clickOk(); + + httpInNode.connect(templateNode); + templateNode.connect(httpResponseNode); + // The code for confirmation ends here. + + workspace.deploy(); + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Hello Nick"'); + }); + + it('get a parsed JSON response', function () { + var injectNode = workspace.addNode('inject'); + var changeNodeSetPost = workspace.addNode('change'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', 'json-response'); + injectNode.clickOk(); + + changeNodeSetPost.edit(); + changeNodeSetPost.ruleSet('post', 'msg', 'payload', 'msg'); + changeNodeSetPost.clickOk(); + + httpRequestNode.edit(); + httpRequestNode.setMethod('GET'); + var url = helper.url() + httpNodeRoot + '/{{post}}'; + httpRequestNode.setUrl(url); + httpRequestNode.setReturn('obj'); + httpRequestNode.clickOk(); + + debugNode.edit(); + debugNode.setOutput('payload.title'); + debugNode.clickOk(); + + injectNode.connect(changeNodeSetPost); + changeNodeSetPost.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + // The code for confirmation starts from here. + var httpInNode = workspace.addNode('httpIn', 0, 200); + var templateNode = workspace.addNode('template'); + var changeNodeSetHeader = workspace.addNode('change'); + var httpResponseNode = workspace.addNode('httpResponse'); + + httpInNode.edit(); + httpInNode.setMethod('get'); + httpInNode.setUrl('/json-response'); + httpInNode.clickOk(); + + templateNode.edit(); + templateNode.setSyntax('mustache'); + templateNode.setFormat('handlebars'); + templateNode.setTemplate('{"title": "Hello"}'); + templateNode.clickOk(); + + changeNodeSetHeader.edit(); + changeNodeSetHeader.ruleSet('headers', 'msg', '{"content-type":"application/json"}', 'json'); + changeNodeSetHeader.clickOk(); + + httpInNode.connect(templateNode); + templateNode.connect(changeNodeSetHeader); + changeNodeSetHeader.connect(httpResponseNode); + // The code for confirmation ends here. + + workspace.deploy(); + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Hello"'); + }); + + it('get a binary response', function () { + var injectNode = workspace.addNode('inject'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + httpRequestNode.edit(); + httpRequestNode.setMethod('GET'); + httpRequestNode.setUrl(helper.url() + '/settings'); + httpRequestNode.setReturn('bin'); + httpRequestNode.clickOk(); + + injectNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + + debugTab.getMessage().should.eql(['123', '34', '104', '116', '116', '112', '78', '111', '100', '101']); + }); + + it('set a request header', function () { + var injectNode = workspace.addNode('inject'); + var functionNode = workspace.addNode('function'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + functionNode.edit(); + functionNode.setFunction('msg.payload = "data to post";\nreturn msg;'); + functionNode.clickOk(); + + httpRequestNode.edit(); + httpRequestNode.setMethod('POST'); + var url = helper.url() + httpNodeRoot + '/set-header'; + httpRequestNode.setUrl(url); + httpRequestNode.clickOk(); + + injectNode.connect(functionNode); + functionNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + // The code for confirmation starts from here. + var httpInNode = workspace.addNode('httpIn', 0, 200); + var templateNode = workspace.addNode('template'); + var httpResponseNode = workspace.addNode('httpResponse'); + + httpInNode.edit(); + httpInNode.setMethod('post'); + httpInNode.setUrl('/set-header'); + httpInNode.clickOk(); + + templateNode.edit(); + templateNode.setSyntax('mustache'); + templateNode.setFormat('handlebars'); + templateNode.setTemplate('{{ payload }}'); + templateNode.clickOk(); + + httpInNode.connect(templateNode); + templateNode.connect(httpResponseNode); + // The code for confirmation ends here. + + workspace.deploy(); + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"data to post"'); + }); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_messages_uispec.js b/test/editor/specs/scenario/cookbook_messages_uispec.js new file mode 100644 index 000000000..78facbcda --- /dev/null +++ b/test/editor/specs/scenario/cookbook_messages_uispec.js @@ -0,0 +1,142 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('messages', function () { + it('set a message property to a fixed value', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var debugNode = workspace.addNode('debug'); + + changeNode.edit(); + changeNode.ruleSet('payload', 'msg', 'Hello World!'); + changeNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Hello World!"'); + }); + + it('delete a message property', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var debugNode = workspace.addNode('debug'); + + changeNode.edit(); + changeNode.ruleDelete(); + changeNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('undefined'); + }); + + it('move a message property', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setTopic('Hello'); + injectNode.clickOk(); + + changeNode.edit(); + changeNode.ruleMove('topic', 'msg', 'payload', 'msg'); + changeNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Hello"'); + }); + + it('map a property between different numeric ranges', function () { + var injectNode1 = workspace.addNode('inject'); + var injectNode2 = workspace.addNode('inject', 0, 100); + var injectNode3 = workspace.addNode('inject', 0, 200); + var rangeNode = workspace.addNode('range', 200, 100); + var debugNode = workspace.addNode('debug', 400); + + injectNode1.edit(); + injectNode1.setPayload('num', 0); + injectNode1.clickOk(); + injectNode2.edit(); + injectNode2.setPayload('num', 512); + injectNode2.clickOk(); + injectNode3.edit(); + injectNode3.setPayload('num', 1023); + injectNode3.clickOk(); + + rangeNode.edit(); + rangeNode.setAction('clamp'); + rangeNode.setRange(0, 1023, 0, 5); + rangeNode.clickOk(); + + injectNode1.connect(rangeNode); + injectNode2.connect(rangeNode); + injectNode3.connect(rangeNode); + rangeNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage(1).should.eql('0'); + injectNode2.clickLeftButton(); + debugTab.getMessage(2).should.eql('2.5024437927663734'); + injectNode3.clickLeftButton(); + debugTab.getMessage(3).should.eql('5'); + }); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_mqtt_uispec.js b/test/editor/specs/scenario/cookbook_mqtt_uispec.js index 655074750..b68170eb5 100644 --- a/test/editor/specs/scenario/cookbook_mqtt_uispec.js +++ b/test/editor/specs/scenario/cookbook_mqtt_uispec.js @@ -22,7 +22,6 @@ var helper = require("../../editor_helper"); var debugTab = require('../../pageobjects/editor/debugTab_page'); var workspace = require('../../pageobjects/editor/workspace_page'); var specUtil = require('../../pageobjects/util/spec_util_page'); -var mqttConfig = require('../../pageobjects/nodes/core/network/10-mqttconfig_page.js'); var httpNodeRoot = "/api"; @@ -73,9 +72,9 @@ describe('cookbook', function () { var mqttOutNode = workspace.addNode("mqttOut"); mqttOutNode.edit(); - mqttConfig.edit(); - mqttConfig.setServer("localhost", moscaSettings.port); - mqttConfig.clickOk(); + mqttOutNode.mqttBrokerNode.edit(); + mqttOutNode.mqttBrokerNode.setServer("localhost", moscaSettings.port); + mqttOutNode.mqttBrokerNode.clickOk(); mqttOutNode.clickOk(); workspace.deploy(); diff --git a/test/editor/specs/scenario/cookbook_uispec.js b/test/editor/specs/scenario/cookbook_uispec.js deleted file mode 100644 index 6e16ca8a7..000000000 --- a/test/editor/specs/scenario/cookbook_uispec.js +++ /dev/null @@ -1,441 +0,0 @@ -/** - * Copyright JS Foundation and other contributors, http://js.foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ - -var when = require('when'); -var should = require("should"); -var fs = require('fs-extra'); - -var helper = require("../../editor_helper"); -var debugTab = require('../../pageobjects/editor/debugTab_page'); -var workspace = require('../../pageobjects/editor/workspace_page'); -var specUtil = require('../../pageobjects/util/spec_util_page'); - -var httpNodeRoot = "/api"; - -// https://cookbook.nodered.org/ -describe('cookbook', function() { - beforeEach(function() { - workspace.init(); - }); - - before(function() { - helper.startServer(); - }); - - after(function() { - helper.stopServer(); - }); - - describe('messages', function() { - it('set a message property to a fixed value', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var debugNode = workspace.addNode("debug"); - - changeNode.edit(); - changeNode.ruleSet("payload", "msg", "Hello World!"); - changeNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Hello World!"'); - }); - - it('delete a message property', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var debugNode = workspace.addNode("debug"); - - changeNode.edit(); - changeNode.ruleDelete(); - changeNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql("undefined"); - }); - - it('move a message property', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setTopic("Hello"); - injectNode.clickOk(); - - changeNode.edit(); - changeNode.ruleMove("topic", "payload"); - changeNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Hello"'); - }); - - it('map a property between different numeric ranges', function() { - var injectNode1 = workspace.addNode("inject"); - var injectNode2 = workspace.addNode("inject", 0, 100); - var injectNode3 = workspace.addNode("inject", 0, 200); - var rangeNode = workspace.addNode("range", 200, 100); - var debugNode = workspace.addNode("debug", 400); - - injectNode1.edit(); - injectNode1.setPayload("num", 0); - injectNode1.clickOk(); - injectNode2.edit(); - injectNode2.setPayload("num", 512); - injectNode2.clickOk(); - injectNode3.edit(); - injectNode3.setPayload("num", 1023); - injectNode3.clickOk(); - - rangeNode.edit(); - rangeNode.setAction("clamp"); - rangeNode.setRange(0, 1023, 0, 5); - rangeNode.clickOk(); - - injectNode1.connect(rangeNode); - injectNode2.connect(rangeNode); - injectNode3.connect(rangeNode); - rangeNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode1.clickLeftButton(); - debugTab.getMessage(1).should.eql('0'); - injectNode2.clickLeftButton(); - debugTab.getMessage(2).should.eql('2.5024437927663734'); - injectNode3.clickLeftButton(); - debugTab.getMessage(3).should.eql('5'); - }); - }); - - describe('flow control', function() { - it('trigger a flow whenever Node-RED starts', function() { - var injectNode = workspace.addNode("inject"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", "Started!") - injectNode.setOnce(true); - injectNode.clickOk(); - injectNode.connect(debugNode); - - debugTab.open(); - workspace.deploy(); - debugTab.getMessage().should.eql('"Started!"'); - }); - - it('trigger a flow at regular intervals', function() { - var injectNode = workspace.addNode("inject"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setRepeat("interval"); - injectNode.setRepeatInterval(1); - injectNode.clickOk(); - injectNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - specUtil.pause(1000); - var t1 = Number(debugTab.getMessage(1)); - t1.should.within(1500000000000, 3000000000000); - specUtil.pause(1000); - debugTab.getMessage(2).should.within(t1 + 900, 3000000000000); - }); - - // skip this case since it needs up to one minite. - it.skip('trigger a flow at a specific time'); - }); - - describe('HTTP requests', function() { - it('simple get request', function() { - var injectNode = workspace.addNode("inject"); - var httpRequetNode = workspace.addNode("httpRequest"); - var htmlNode = workspace.addNode("html"); - var debugNode = workspace.addNode("debug"); - - httpRequetNode.edit(); - httpRequetNode.setMethod("GET"); - httpRequetNode.setUrl(helper.url()); - httpRequetNode.clickOk(); - - htmlNode.edit(); - htmlNode.setSelector("title"); - htmlNode.clickOk(); - - injectNode.connect(httpRequetNode); - httpRequetNode.connect(htmlNode); - htmlNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Node-RED"'); - }); - - it('set the URL of a request', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", helper.url()); - injectNode.clickOk(); - - changeNode.edit(); - changeNode.ruleSet("url", "msg", "payload", "msg"); - changeNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.containEql('Node-RED'); - }); - - it('set the URL of a request using a template', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", 'settings'); - injectNode.clickOk(); - - changeNode.edit(); - changeNode.ruleSet("query", "msg", "payload", "msg"); - changeNode.clickOk(); - - httpRequetNode.edit(); - httpRequetNode.setUrl(helper.url() + "/{{{query}}}"); - httpRequetNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.containEql('httpNodeRoot'); - }); - - it('set the query string parameters', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", 'Nick'); - injectNode.clickOk(); - - changeNode.edit(); - changeNode.ruleSet("query", "msg", "payload", "msg"); - changeNode.clickOk(); - - httpRequetNode.edit(); - httpRequetNode.setUrl(helper.url() + httpNodeRoot + '/set-query?q={{{query}}}'); - httpRequetNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - // The code for confirmation starts from here. - var httpInNode = workspace.addNode("httpIn", 0, 200); - var templateNode = workspace.addNode("template"); - var httpResponseNode = workspace.addNode("httpResponse"); - - httpInNode.edit(); - httpInNode.setMethod("get"); - httpInNode.setUrl("/set-query"); - httpInNode.clickOk(); - - templateNode.edit(); - templateNode.setSyntax("mustache"); - templateNode.setFormat("handlebars"); - templateNode.setTemplate("Hello {{req.query.q}}"); - templateNode.clickOk(); - - httpInNode.connect(templateNode); - templateNode.connect(httpResponseNode); - // The code for confirmation ends here. - - workspace.deploy(); - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Hello Nick"'); - }); - - it('get a parsed JSON response', function() { - var injectNode = workspace.addNode("inject"); - var changeNodeSetPost = workspace.addNode("change"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", "json-response"); - injectNode.clickOk(); - - changeNodeSetPost.edit(); - changeNodeSetPost.ruleSet("post", "msg", "payload", "msg"); - changeNodeSetPost.clickOk(); - - httpRequetNode.edit(); - httpRequetNode.setMethod("GET"); - var url = helper.url() + httpNodeRoot + "/{{post}}"; - httpRequetNode.setUrl(url); - httpRequetNode.setReturn("obj"); - httpRequetNode.clickOk(); - - debugNode.edit(); - debugNode.setOutput(".title"); - debugNode.clickOk(); - - injectNode.connect(changeNodeSetPost); - changeNodeSetPost.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - // The code for confirmation starts from here. - var httpInNode = workspace.addNode("httpIn", 0, 200); - var templateNode = workspace.addNode("template"); - var changeNodeSetHeader = workspace.addNode("change"); - var httpResponseNode = workspace.addNode("httpResponse"); - - httpInNode.edit(); - httpInNode.setMethod("get"); - httpInNode.setUrl("/json-response"); - httpInNode.clickOk(); - - templateNode.edit(); - templateNode.setSyntax("mustache"); - templateNode.setFormat("handlebars"); - templateNode.setTemplate('{"title": "Hello"}'); - templateNode.clickOk(); - - changeNodeSetHeader.edit(); - changeNodeSetHeader.ruleSet("headers", "msg", '{"content-type":"application/json"}', "json"); - changeNodeSetHeader.clickOk(); - - httpInNode.connect(templateNode); - templateNode.connect(changeNodeSetHeader); - changeNodeSetHeader.connect(httpResponseNode); - // The code for confirmation ends here. - - workspace.deploy(); - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Hello"'); - }); - - it('get a binary response', function() { - var injectNode = workspace.addNode("inject"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - httpRequetNode.edit(); - httpRequetNode.setMethod("GET"); - httpRequetNode.setUrl(helper.url() + "/settings"); - httpRequetNode.setReturn("bin"); - httpRequetNode.clickOk(); - - injectNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - - debugTab.getMessage().should.eql(['123', '34', '104', '116', '116', '112', '78', '111', '100', '101']); - }); - - it('set a request header', function() { - var injectNode = workspace.addNode("inject"); - var functionNode = workspace.addNode("function"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - functionNode.edit(); - functionNode.setFunction('msg.payload = "data to post";\nreturn msg;'); - functionNode.clickOk(); - - httpRequetNode.edit(); - httpRequetNode.setMethod("POST"); - var url = helper.url() + httpNodeRoot + "/set-header"; - httpRequetNode.setUrl(url); - httpRequetNode.clickOk(); - - injectNode.connect(functionNode); - functionNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - // The code for confirmation starts from here. - var httpInNode = workspace.addNode("httpIn", 0, 200); - var templateNode = workspace.addNode("template"); - var httpResponseNode = workspace.addNode("httpResponse"); - - httpInNode.edit(); - httpInNode.setMethod("post"); - httpInNode.setUrl("/set-header"); - httpInNode.clickOk(); - - templateNode.edit(); - templateNode.setSyntax("mustache"); - templateNode.setFormat("handlebars"); - templateNode.setTemplate("{{ payload }}"); - templateNode.clickOk(); - - httpInNode.connect(templateNode); - templateNode.connect(httpResponseNode); - // The code for confirmation ends here. - - workspace.deploy(); - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"data to post"'); - }); - }); -}); diff --git a/test/nodes/core/common/20-inject_spec.js b/test/nodes/core/common/20-inject_spec.js index ea78d0f53..ff5eb7f73 100644 --- a/test/nodes/core/common/20-inject_spec.js +++ b/test/nodes/core/common/20-inject_spec.js @@ -488,6 +488,77 @@ describe('inject node', function() { }); }); + + it('should inject multiple properties ', function (done) { + var flow = [{id: "n1", type: "inject", props: [{p:"topic", v:"t1", vt:"str"}, {p:"payload", v:"foo", vt:"str"}, {p:"x", v: 10, "vt":"num"}, {p:"y", v: "x+2", "vt":"jsonata"}], wires: [["n2"]], z: "flow"}, + {id: "n2", type: "helper"}]; + helper.load(injectNode, flow, function () { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function (msg) { + try { + msg.should.have.property("topic", "t1"); + msg.should.have.property("payload", "foo"); + msg.should.have.property("x", 10); + msg.should.have.property("y", 12); + done(); + } catch (err) { + done(err); + } + }); + n1.receive({}); + }); + }); + + it('should inject multiple properties using legacy props if needed', function (done) { + var flow = [{id: "n1", type: "inject", payload:"123", payloadType:"num", topic:"foo", props: [{p:"topic", vt:"str"}, {p:"payload"}], wires: [["n2"]], z: "flow"}, + {id: "n2", type: "helper"}]; + helper.load(injectNode, flow, function () { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function (msg) { + try { + msg.should.have.property("topic", "foo"); + msg.should.have.property("payload", 123); + done(); + } catch (err) { + done(err); + } + }); + n1.receive({}); + }); + }); + + + it('should report invalid JSONata expression', function (done) { + var flow = [{id: "n1", type: "inject", props: [{p:"topic", v:"t1", vt:"str"}, {p:"payload", v:"@", vt:"jsonata"}], wires: [["n2"]], z: "flow"}, + {id: "n2", type: "helper"}]; + helper.load(injectNode, flow, function () { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var count = 0; + n2.on("input", function (msg) { + try { + msg.should.have.property("topic", "t1"); + msg.should.not.have.property("payload"); + count++; + if (count == 2) { + done(); + } + } catch (err) { + done(err); + } + }); + n1.on("call:error", function(err) { + count++; + if (count == 2) { + done(); + } + }); + n1.receive({}); + }); + }); + describe('post', function() { it('should inject message', function(done) { helper.load(injectNode, diff --git a/test/nodes/core/common/21-debug_spec.js b/test/nodes/core/common/21-debug_spec.js index bdff7976c..990797ef3 100644 --- a/test/nodes/core/common/21-debug_spec.js +++ b/test/nodes/core/common/21-debug_spec.js @@ -603,6 +603,30 @@ describe('debug node', function() { .post('/debug/n99/enable') .expect(404).end(done); }); + + it('should return 400 for invalid bulk disable', function(done) { + var flow = [{id:"n1", type:"debug", active: true }]; + helper.load(debugNode, flow, function() { + helper.request() + .post('/debug/disable') + .send({}) + .set('Content-type', 'application/json') + .expect(400).end(done); + }); + + }) + + it('should return success for bulk disable', function(done) { + var flow = [{id:"n1", type:"debug", active: true }]; + helper.load(debugNode, flow, function() { + helper.request() + .post('/debug/disable') + .send({nodes:['n1']}) + .set('Content-type', 'application/json') + .expect(201).end(done); + }); + + }) }); describe('get', function() { diff --git a/test/nodes/core/function/10-function_spec.js b/test/nodes/core/function/10-function_spec.js index c13dd1972..ab7e4b486 100644 --- a/test/nodes/core/function/10-function_spec.js +++ b/test/nodes/core/function/10-function_spec.js @@ -53,7 +53,6 @@ describe('function node', function() { }); }); - it('should be loaded', function(done) { var flow = [{id:"n1", type:"function", name: "function" }]; helper.load(functionNode, flow, function() { @@ -1336,6 +1335,46 @@ describe('function node', function() { }); }); + it('should execute initialization', function(done) { + var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = global.get('X'); return msg;",initialize:"global.set('X','bar');"}, + {id:"n2", type:"helper"}]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property("payload", "bar"); + done(); + }); + n1.receive({payload: "foo"}); + }); + }); + + it('should wait completion of initialization', function(done) { + var flow = [{id:"n1",type:"function",wires:[["n2"]],func:"msg.payload = global.get('X'); return msg;",initialize:"global.set('X', '-'); return new Promise((resolve, reject) => setTimeout(() => { global.set('X','bar'); resolve(); }, 500));"}, + {id:"n2", type:"helper"}]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property("payload", "bar"); + done(); + }); + n1.receive({payload: "foo"}); + }); + }); + + it('should execute finalization', function(done) { + var flow = [{id:"n1",type:"function",wires:[],func:"return msg;",finalize:"global.set('X','bar');"}]; + helper.load(functionNode, flow, function() { + var n1 = helper.getNode("n1"); + var ctx = n1.context().global; + helper.unload().then(function () { + ctx.get('X').should.equal("bar"); + done(); + }); + }); + }); + describe('Logger', function () { it('should log an Info Message', function (done) { var flow = [{id: "n1", type: "function", wires: [["n2"]], func: "node.log('test');"}]; diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index 5f7bfc2d1..3377ee1f7 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -102,20 +102,20 @@ describe('trigger node', function() { function basicTest(type, val, rval) { it('should output 1st value when triggered ('+type+')', function(done) { var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:val, op1type:type, op2:"", op2type:"null", duration:"20", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; process.env[val] = rval; helper.load(triggerNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { try { - if (rval) { - msg.should.have.property("payload"); - should.deepEqual(msg.payload, rval); - } - else { - msg.should.have.property("payload", val); - } + if (rval) { + msg.should.have.property("payload"); + should.deepEqual(msg.payload, rval); + } + else { + msg.should.have.property("payload", val); + } delete process.env[val]; done(); } @@ -127,7 +127,7 @@ describe('trigger node', function() { it('should output 2st value when triggered ('+type+')', function(done) { var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"str", op2:val, op2type:type, duration:"20", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; process.env[val] = rval; helper.load(triggerNode, flow, function() { var n1 = helper.getNode("n1"); @@ -136,17 +136,17 @@ describe('trigger node', function() { n2.on("input", function(msg) { try { if (c === 0) { - msg.should.have.property("payload", "foo"); + msg.should.have.property("payload", "foo"); c++; } else { - if (rval) { - msg.should.have.property("payload"); - should.deepEqual(msg.payload, rval); - } - else { - msg.should.have.property("payload", val); - } + if (rval) { + msg.should.have.property("payload"); + should.deepEqual(msg.payload, rval); + } + else { + msg.should.have.property("payload", val); + } delete process.env[val]; done(); } @@ -378,6 +378,51 @@ describe('trigger node', function() { }); }); + it('should handle multiple other properties individually if asked to do so', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", topic:"foo", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + try { + c += 1; + if (c === 1) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("foo", "A"); + } + else if (c === 2) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("foo", "B"); + } + else if (c === 3) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("foo", "C"); + } + else if (c === 4) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("foo", "A"); + } + else if (c === 5) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("foo", "B"); + } + else if (c === 6) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("foo", "C"); + done(); + } + } catch(err) { + done(err); + } + }); + n1.emit("input", {payload:1,foo:"A"}); + n1.emit("input", {payload:2,foo:"B"}); + n1.emit("input", {payload:3,foo:"C"}); + }); + }); + it('should be able to return things from flow and global context variables', function(done) { var spy = sinon.stub(RED.util, 'evaluateNodeProperty', function(arg1, arg2, arg3, arg4, arg5) { if (arg5) { arg5(null, arg1) } else { return arg1; } } @@ -408,8 +453,8 @@ describe('trigger node', function() { it('should be able to return things from persistable flow and global context variables', function (done) { var flow = [{"id": "n1", "type": "trigger", "name": "triggerNode", "op1": "#:(memory1)::foo", "op1type": "flow", - "op2": "#:(memory1)::bar", "op2type": "global", "duration": "20", "wires": [["n2"]], "z": "flow" }, - {"id": "n2", "type": "helper"}]; + "op2": "#:(memory1)::bar", "op2type": "global", "duration": "20", "wires": [["n2"]], "z": "flow" }, + {"id": "n2", "type": "helper"}]; helper.load(triggerNode, flow, function () { initContext(function () { var n1 = helper.getNode("n1"); @@ -442,11 +487,11 @@ describe('trigger node', function() { it('should be able to return things from multiple persistable global context variables', function (done) { var flow = [{"id": "n1", "z": "flow", "type": "trigger", - "duration": "20", "wires": [["n2"]], - "op1": "#:(memory1)::val", "op1type": "global", - "op2": "#:(memory2)::val", "op2type": "global" - }, - {"id": "n2", "type": "helper"}]; + "duration": "20", "wires": [["n2"]], + "op1": "#:(memory1)::val", "op1type": "global", + "op2": "#:(memory2)::val", "op2type": "global" + }, + {"id": "n2", "type": "helper"}]; helper.load(triggerNode, flow, function () { initContext(function () { var n1 = helper.getNode("n1"); @@ -481,11 +526,11 @@ describe('trigger node', function() { it('should be able to return things from multiple persistable flow context variables', function (done) { var flow = [{"id": "n1", "z": "flow", "type": "trigger", - "duration": "20", "wires": [["n2"]], - "op1": "#:(memory1)::val", "op1type": "flow", - "op2": "#:(memory2)::val", "op2type": "flow" - }, - {"id": "n2", "type": "helper"}]; + "duration": "20", "wires": [["n2"]], + "op1": "#:(memory1)::val", "op1type": "flow", + "op2": "#:(memory2)::val", "op2type": "flow" + }, + {"id": "n2", "type": "helper"}]; helper.load(triggerNode, flow, function () { initContext(function () { var n1 = helper.getNode("n1"); @@ -520,11 +565,11 @@ describe('trigger node', function() { it('should be able to return things from multiple persistable flow & global context variables', function (done) { var flow = [{"id": "n1", "z": "flow", "type": "trigger", - "duration": "20", "wires": [["n2"]], - "op1": "#:(memory1)::val", "op1type": "flow", - "op2": "#:(memory2)::val", "op2type": "global" - }, - {"id": "n2", "type": "helper"}]; + "duration": "20", "wires": [["n2"]], + "op1": "#:(memory1)::val", "op1type": "flow", + "op2": "#:(memory2)::val", "op2type": "global" + }, + {"id": "n2", "type": "helper"}]; helper.load(triggerNode, flow, function () { initContext(function () { var n1 = helper.getNode("n1"); @@ -736,10 +781,12 @@ describe('trigger node', function() { try { if (c === 0) { msg.should.have.a.property("payload", "Goodbye"); + msg.should.have.a.property("topic", "test2"); c += 1; } else { msg.should.have.a.property("payload", "World"); + msg.should.have.a.property("topic", "test3"); (Date.now() - ss).should.be.greaterThan(70); done(); } @@ -747,16 +794,51 @@ describe('trigger node', function() { catch(err) { done(err); } }); var ss = Date.now(); - n1.emit("input", {payload:"Hello"}); + n1.emit("input", {payload:"Hello", topic:"test1"}); setTimeout( function() { - n1.emit("input", {payload:"Goodbye"}); + n1.emit("input", {payload:"Goodbye", topic:"test2"}); },20); setTimeout( function() { - n1.emit("input", {payload:"World"}); + n1.emit("input", {payload:"World", topic:"test3"}); },80); }); }); + it('should be able output the 2nd payload and handle multiple topics', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"false", op1type:"nul", op2type:"payl", op1:"false", op2:"true", duration:"80", bytopic:"topic", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + try { + if (c === 0) { + msg.should.have.a.property("payload", "Goodbye1"); + msg.should.have.a.property("topic", "test1"); + c += 1; + } + else { + msg.should.have.a.property("payload", "Goodbye2"); + msg.should.have.a.property("topic", "test2"); + done(); + } + } + catch(err) { done(err); } + }); + n1.emit("input", {payload:"Hello1", topic:"test1"}); + setTimeout( function() { + n1.emit("input", {payload:"Hello2", topic:"test2"}); + },20); + setTimeout( function() { + n1.emit("input", {payload:"Goodbye2", topic:"test2"}); + },20); + setTimeout( function() { + n1.emit("input", {payload:"Goodbye1", topic:"test1"}); + },20); + }); + }); + it('should be able to apply mustache templates to payloads', function(done) { var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"{{payload}}", op2:"{{topic}}", duration:"50", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; @@ -781,6 +863,40 @@ describe('trigger node', function() { }); }); + it('should be able to send 2nd message to a 2nd output', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"hello", op2:"world", duration:"50", outputs:2, wires:[["n2"],["n3"]] }, + {id:"n2", type:"helper"}, {id:"n3", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var n3 = helper.getNode("n3"); + var c = 0; + n2.on("input", function(msg) { + try { + if (c === 0) { + msg.should.have.a.property("payload", "hello"); + msg.should.have.a.property("topic", "test"); + c+=1; + } + else { done(err); } + } + catch(err) { done(err); } + }); + n3.on("input", function(msg) { + try { + if (c === 1) { + msg.should.have.a.property("payload", "world"); + msg.should.have.a.property("topic", "test"); + done(); + } + else { done(err); } + } + catch(err) { done(err); } + }); + n1.emit("input", {payload:"go",topic:"test"}); + }); + }); + it('should handle string null as null', function(done) { var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"pay", op1:"null", op2:"null", duration:"40", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js index f46afc24a..10e167b6d 100644 --- a/test/nodes/core/parsers/70-CSV_spec.js +++ b/test/nodes/core/parsers/70-CSV_spec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ /** * Copyright JS Foundation and other contributors, http://js.foundation * @@ -70,12 +71,13 @@ describe('CSV node', function() { it('should convert a simple csv string to a javascript object', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 }); + msg.should.have.property('columns', "a,b,c,d"); check_parts(msg, 0, 1); done(); }); @@ -86,7 +88,7 @@ describe('CSV node', function() { it('should remove quotes and whitespace from template', function(done) { var flow = [ { id:"n1", type:"csv", temp:'"a", "b" , " c "," d " ', wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -102,12 +104,13 @@ describe('CSV node', function() { it('should create column names if no template provided', function(done) { var flow = [ { id:"n1", type:"csv", temp:'', wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload', { col1: 1, col2: 2, col3: 3, col4: 4 }); + msg.should.have.property('columns', "col1,col2,col3,col4"); check_parts(msg, 0, 1); done(); }); @@ -118,12 +121,13 @@ describe('CSV node', function() { it('should allow dropping of fields from the template', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,,,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload', { a: 1, d: 4 }); + msg.should.have.property('columns', 'a,d'); check_parts(msg, 0, 1); done(); }); @@ -134,7 +138,7 @@ describe('CSV node', function() { it('should leave numbers starting with 0, e and + as strings (except 0.)', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -150,7 +154,7 @@ describe('CSV node', function() { it('should not parse numbers when told not to do so', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", strings:false, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -166,7 +170,7 @@ describe('CSV node', function() { it('should leave handle strings with scientific notation as numbers', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -181,44 +185,128 @@ describe('CSV node', function() { }); - it('should allow quotes in the input', function(done) { - var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + it('should allow quotes in the input (but drop blank strings)', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g,h", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { //console.log(msg); - msg.should.have.property('payload', { a: 1, b: -2, c: '+3', d: '04', e: '-05', f: 'ab"cd', g: 'with,a,comma' }); + msg.should.have.property('payload', { a:1, b:-2, c:'+3', d:'04', f:'-05', g:'ab"cd', h:'with,a,comma' }); check_parts(msg, 0, 1); done(); }); - var testString = '"1","-2","+3","04","-05","ab""cd","with,a,comma"'+String.fromCharCode(10); + var testString = '"1","-2","+3","04","","-05","ab""cd","with,a,comma"'+String.fromCharCode(10); + n1.emit("input", {payload:testString}); + }); + }); + + it('should allow blank strings in the input if selected', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", include_empty_strings:true, wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + //console.log(msg); + msg.should.have.property('payload', { a: 1, b: '', c: '', d: '', e: '-05', f: 'ab"cd', g: 'with,a,comma' }); + //check_parts(msg, 0, 1); + done(); + }); + var testString = '"1","","","","-05","ab""cd","with,a,comma"'+String.fromCharCode(10); + n1.emit("input", {payload:testString}); + }); + }); + + it('should allow missing columns (nulls) in the input if selected', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", include_null_values:true, wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + //console.log(msg); + msg.should.have.property('payload', { a: 1, b: null, c: '+3', d: null, e: '-05', f: 'ab"cd', g: 'with,a,comma' }); + //check_parts(msg, 0, 1); + done(); + }); + var testString = '"1",,"+3",,"-05","ab""cd","with,a,comma"'+String.fromCharCode(10); + n1.emit("input", {payload:testString}); + }); + }); + + it('should handle cr and lf in the input', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + //console.log(msg); + msg.should.have.property('payload', { a: "with a\nnew line", b: "and a\rcarriage return", c: "and why\r\nnot both"}); + check_parts(msg, 0, 1); + done(); + }); + var testString = '"with a'+String.fromCharCode(10)+'new line","and a'+String.fromCharCode(13)+'carriage return","and why\r\nnot both"'+String.fromCharCode(10); n1.emit("input", {payload:testString}); }); }); it('should recover from an odd number of quotes in the input', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); + var c = 0; n2.on("input", function(msg) { - //console.log(msg); - msg.should.have.property('payload', { a: "with,an", b: "odd,number", c: "ofquotes" }); - //msg.should.have.property('payload', { a: 1, b: -2, c: '+3', d: 4, e: -5, f: 'ab"cd', g: 'with,a,comma' }); - check_parts(msg, 0, 1); - done(); + if (c == 0) { + c = 1; + msg.should.have.property('payload', { a: "with,an", b: "odd,number", c: "ofquotes\n" }); + check_parts(msg, 0, 1); + } + else { + msg.should.have.property('payload', { a: "this is", b: "a normal", c: "line" }); + check_parts(msg, 0, 1); + done(); + } }); var testString = '"with,a"n,odd","num"ber","of"qu"ot"es"'+String.fromCharCode(10); n1.emit("input", {payload:testString}); + n1.emit("input", {payload:'"this is","a normal","line"'}); + }); + }); + + it('should recover from an odd number of quotes in the input (2)', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + //console.log(msg) + if (c == 0) { + c = 1; + msg.should.have.property('payload', { a: "with,an", b: "odd,number", c: "ofquotes\nthis is,a normal,line" }); + check_parts(msg, 0, 1); + } + else { + msg.should.have.property('payload', { a: "this is", b: "another", c: "line" }); + check_parts(msg, 0, 1); + done(); + } + }); + var testString = '"with,a"n,odd","num"ber","of"qu"ot"es"'+String.fromCharCode(10)+'"this is","a normal","line"'+String.fromCharCode(10); + n1.emit("input", {payload:testString}); + n1.emit("input", {payload:'"this is","another","line"'}); }); }); it('should be able to use the first line as a template', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrin:true, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -243,12 +331,13 @@ describe('CSV node', function() { it('should be able to output multiple lines as one array', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", multi:"yes", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload', [ { a: 1, b: 2, c: 3, d: 4 },{ a: 5, b: -6, c: '07', d: '+8' },{ a: 9, b: 0, c: 'a', d: 'b' },{ a: 'c', b: 'd', c: 'e', d: 'f' } ]); + msg.should.have.property('columns','a,b,c,d'); msg.should.not.have.property('parts'); done(); }); @@ -259,7 +348,7 @@ describe('CSV node', function() { it('should handle numbers in strings but not IP addresses', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -275,7 +364,7 @@ describe('CSV node', function() { it('should preserve parts property', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -319,7 +408,7 @@ describe('CSV node', function() { it('should skip several lines from start if requested', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -333,9 +422,9 @@ describe('CSV node', function() { }); }); - it('should skip several lines from start then use next line as a tempate', function(done) { + it('should skip several lines from start then use next line as a template', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrin:true, skip: 2, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -351,7 +440,7 @@ describe('CSV node', function() { it('should skip several lines from start and correct parts', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -383,11 +472,13 @@ describe('CSV node', function() { n2.on("input", function(msg) { if (c === 0) { msg.should.have.property('payload', { w: 1, x: 2, y: 3, z: 4 }); + msg.should.have.property('columns', 'w,x,y,z'); check_parts(msg, 0, 2); c += 1; } else { msg.should.have.property('payload', { w: 5, x: 6, y: 7, z: 8 }); + msg.should.have.property('columns', 'w,x,y,z'); check_parts(msg, 1, 2); done(); } @@ -411,7 +502,7 @@ describe('CSV node', function() { it('should convert a simple object back to a csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -429,7 +520,7 @@ describe('CSV node', function() { it('should convert a simple object back to a csv with no template', function(done) { var flow = [ { id:"n1", type:"csv", temp:" ", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -447,7 +538,7 @@ describe('CSV node', function() { it('should handle a template with spaces in the property names', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b o,c p,,e", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -465,7 +556,7 @@ describe('CSV node', function() { it('should convert an array of objects to a multi-line csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -483,7 +574,7 @@ describe('CSV node', function() { it('should convert a simple array back to a csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -501,7 +592,7 @@ describe('CSV node', function() { it('should convert an array of arrays back to a multi-line csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -519,7 +610,7 @@ describe('CSV node', function() { it('should be able to include column names as first row', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrout:true, ret:"\r\n", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -535,9 +626,36 @@ describe('CSV node', function() { }); }); + it('should be able to pass in column names', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"", hdrout:"once", ret:"\r\n", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var count = 0; + n2.on("input", function(msg) { + count += 1; + try { + if (count === 1) { + msg.should.have.property('payload', 'a,,b,a\r\n4,,3,4\r\n'); + } + if (count === 3) { + msg.should.have.property('payload', '4,,3,4\r\n'); + done() + } + } + catch(e) { done(e); } + }); + var testJson = [{ d: 1, b: 3, c: 2, a: 4 }]; + n1.emit("input", {payload:testJson, columns:"a,,b,a"}); + n1.emit("input", {payload:testJson}); + n1.emit("input", {payload:testJson}); + }); + }); + it('should handle quotes and sub-properties', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -557,7 +675,7 @@ describe('CSV node', function() { it('should just pass through if no payload provided', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -577,7 +695,7 @@ describe('CSV node', function() { it('should warn if provided a number or boolean', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); diff --git a/test/nodes/core/sequence/17-split_spec.js b/test/nodes/core/sequence/17-split_spec.js index 3d38a9ac8..2c45f0e02 100644 --- a/test/nodes/core/sequence/17-split_spec.js +++ b/test/nodes/core/sequence/17-split_spec.js @@ -517,6 +517,49 @@ describe('JOIN node', function() { }); }); + it('should join things into an array after a count with a buffer join set', function(done) { + var flow = [{id:"n1", type:"join", wires:[["n2"]], count:3, joinerType:"bin", joiner:"" ,mode:"custom"}, + {id:"n2", type:"helper"}]; + helper.load(joinNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property("payload"); + msg.payload.should.be.an.Array(); + msg.payload[0].should.equal(1); + msg.payload[1].should.equal(true); + //msg.payload[2].a.should.equal(1); + done(); + } + catch(e) {done(e);} + }); + n1.receive({payload:1}); + n1.receive({payload:true}); + n1.receive({payload:{a:1}}); + }); + }); + + it('should join strings into a buffer after a count', function(done) { + var flow = [{id:"n1", type:"join", wires:[["n2"]], count:2, build:"buffer", joinerType:"bin", joiner:"", mode:"custom"}, + {id:"n2", type:"helper"}]; + helper.load(joinNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property("payload"); + msg.payload.length.should.equal(10); + msg.payload.toString().should.equal("helloworld"); + done(); + } + catch(e) {done(e);} + }); + n1.receive({payload:"hello"}); + n1.receive({payload:"world"}); + }); + }); + it('should join things into an object after a count', function(done) { var flow = [{id:"n1", type:"join", wires:[["n2"]], count:5, build:"object", mode:"custom"}, {id:"n2", type:"helper"}]; diff --git a/test/nodes/core/sequence/19-batch_spec.js b/test/nodes/core/sequence/19-batch_spec.js index 3a40ebfb5..b2e1e6de2 100644 --- a/test/nodes/core/sequence/19-batch_spec.js +++ b/test/nodes/core/sequence/19-batch_spec.js @@ -107,13 +107,18 @@ describe('BATCH node', function() { } } - function delayed_send(receiver, index, count, delay) { + function delayed_send(receiver, index, count, delay, done) { if (index < count) { setTimeout(function() { receiver.receive({payload: index}); - delayed_send(receiver, index+1, count, delay); + delayed_send(receiver, index+1, count, delay, done); }, delay); } + else if(index === count) { + if (done) { + done(); + } + } } function check_interval(flow, results, delay, done) { @@ -198,10 +203,28 @@ describe('BATCH node', function() { }); }); + it('should handle reset', function(done) { + var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "count", count: 2, overlap: 0, interval: 0, allowEmptySequence: false, topics: [], wires:[["n2"]]}, + {id:"n2", type:"helper"}]; + helper.load(batchNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var results = [ + [0, 1], + [4, 5] + ]; + check_data(n1, n2, results, done); + n1.receive({payload:0}); + n1.receive({payload:1}); + n1.receive({payload:2}); + n1.receive({payload:3, reset: true}); + n1.receive({payload:4}); + n1.receive({payload:5}); + }); + }); }); describe('mode: interval', function() { - it('should create seq. with interval', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]}, {id:"n2", type:"helper"}]; @@ -265,10 +288,29 @@ describe('BATCH node', function() { }); }); + it('should handle reset', function(done) { + var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "interval", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [], wires:[["n2"]]}, + {id:"n2", type:"helper"}]; + helper.load(batchNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var results = [ + [0, 1], + [4, 5] + ]; + check_data(n1, n2, results, done); + delayed_send(n1, 0, 3, 400, function () { + setTimeout(function () { + n1.receive({payload: "3", reset: true}); + delayed_send(n1, 4, 7, 400); + }, 10); + }); + }); + }); + }); describe('mode: concat', function() { - it('should concat two seq. (series)', function(done) { var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]}, {id:"n2", type:"helper"}]; @@ -355,6 +397,58 @@ describe('BATCH node', function() { }); }); + it('should handle reset', function(done) { + var flow = [{id:"n1", type:"batch", name: "BatchNode", mode: "concat", count: 0, overlap: 0, interval: 1, allowEmptySequence: false, topics: [{topic: "TA"}, {topic: "TB"}], wires:[["n2"]]}, + {id:"n2", type:"helper"}]; + try { + helper.load(batchNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var results = [ + [2, 3, 0, 1] + ]; + check_data(n1, n2, results, done); + var inputs0 = [ + ["TB", 0, 0, 2], + ["TA", 1, 0, 2], + ]; + for(var data of inputs0) { + var msg = { + topic: data[0], + payload: data[1], + parts: { + id: data[0], + index: data[2], + count: data[3] + } + }; + n1.receive(msg); + } + n1.receive({payload: undefined, reset: true}); + var inputs1 = [ + ["TB", 0, 0, 2], + ["TB", 1, 1, 2], + ["TA", 2, 0, 2], + ["TA", 3, 1, 2] + ]; + for(var data of inputs1) { + var msg = { + topic: data[0], + payload: data[1], + parts: { + id: data[0], + index: data[2], + count: data[3] + } + }; + n1.receive(msg); + } + }); + } + catch (e) { + done(e); + } + }); }); }); diff --git a/test/nodes/subflow/subflow_spec.js b/test/nodes/subflow/subflow_spec.js index 39f5f6b8a..b60328296 100644 --- a/test/nodes/subflow/subflow_spec.js +++ b/test/nodes/subflow/subflow_spec.js @@ -447,117 +447,4 @@ describe('subflow', function() { }); }); - it('should access env var type of subflow instance', function(done) { - var flow = [ - {id:"t0", type:"tab", label:"", disabled:false, info:""}, - {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", - env: [ - {name: "K", type: "str", value: "V"} - ], - wires:[["n2"]]}, - {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, - // Subflow - {id:"s1", type:"subflow", name:"Subflow", info:"", - in:[{ - x:10, y:10, - wires:[ {id:"s1-n1"} ] - }], - out:[{ - x:10, y:10, - wires:[ {id:"s1-n1", port:0} ] - }] - }, - {id:"s1-n1", x:10, y:10, z:"s1", type:"function", - func:"msg.V = env.get('K_type'); return msg;", - wires:[]} - ]; - helper.load(functionNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property("V", "str"); - done(); - } - catch (e) { - console.log(e); - done(e); - } - }); - n1.receive({payload:"foo"}); - }); - }); - - it('should access env var info of subflow instance', function(done) { - var flow = [ - {id:"t0", type:"tab", label:"", disabled:false, info:""}, - {id:"n1", x:10, y:10, z:"t0", type:"subflow:s1", - env: [ - {name: "K", type: "str", value: "V"} - ], - wires:[["n2"]]}, - {id:"n2", x:10, y:10, z:"t0", type:"helper", wires:[]}, - // Subflow - {id:"s1", type:"subflow", name:"Subflow", info:"", - in:[{ - x:10, y:10, - wires:[ {id:"s1-n1"} ] - }], - out:[{ - x:10, y:10, - wires:[ {id:"s1-n1", port:0} ] - }], - env:[ - { - name: "K", type: "str", value: "", - ui: { - hasUI: true, - icon: "icon", - labels: { - "en-US": "label" - }, - type: "input", - inputTypes: { - str: true - } - } - } - ] - }, - {id:"s1-n1", x:10, y:10, z:"s1", type:"function", - func:"msg.V = env.get('K_info'); return msg;", - wires:[]} - ]; - helper.load(functionNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property("V"); - var v = msg.V; - v.should.have.property("name", "K"); - v.should.have.property("value", "V"); - v.should.have.property("type", "str"); - v.should.have.property("ui"); - var ui = v.ui; - ui.should.have.property("hasUI", true); - ui.should.have.property("icon", "icon"); - ui.should.have.property("type", "input"); - ui.should.have.property("labels"); - var labels = ui.labels; - labels.should.have.property("en-US", "label"); - ui.should.have.property("inputTypes"); - var types = ui.inputTypes; - types.should.have.property("str", true); - done(); - } - catch (e) { - console.log(e); - done(e); - } - }); - n1.receive({payload:"foo"}); - }); - }); - }); diff --git a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js index d30f6198a..848aaf99d 100644 --- a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js @@ -129,6 +129,61 @@ describe("api/auth/strategies", function() { }) }); + describe("Tokens Strategy", function() { + it('Succeeds if tokens user enabled custom header',function(done) { + var userTokens = sinon.stub(Users,"tokens",function(token) { + return when.resolve("tokens-"+token); + }); + var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { + return "x-test-token"; + }); + strategies.tokensStrategy._success = strategies.tokensStrategy.success; + strategies.tokensStrategy.success = function(user) { + user.should.equal("tokens-1234"); + strategies.tokensStrategy.success = strategies.tokensStrategy._success; + delete strategies.tokensStrategy._success; + done(); + }; + strategies.tokensStrategy.authenticate({headers:{"x-test-token":"1234"}}); + }); + it('Succeeds if tokens user enabled default header',function(done) { + var userTokens = sinon.stub(Users,"tokens",function(token) { + return when.resolve("tokens-"+token); + }); + var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { + return "authorization"; + }); + strategies.tokensStrategy._success = strategies.tokensStrategy.success; + strategies.tokensStrategy.success = function(user) { + user.should.equal("tokens-1234"); + strategies.tokensStrategy.success = strategies.tokensStrategy._success; + delete strategies.tokensStrategy._success; + done(); + }; + strategies.tokensStrategy.authenticate({headers:{"authorization":"Bearer 1234"}}); + }); + it('Fails if tokens user not enabled',function(done) { + var userTokens = sinon.stub(Users,"tokens",function() { + return when.resolve(null); + }); + var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { + return "authorization"; + }); + strategies.tokensStrategy._fail = strategies.tokensStrategy.fail; + strategies.tokensStrategy.fail = function(err) { + err.should.equal(401); + strategies.tokensStrategy.fail = strategies.tokensStrategy._fail; + delete strategies.tokensStrategy._fail; + done(); + }; + strategies.tokensStrategy.authenticate({headers:{"authorization":"Bearer 1234"}}); + }); + afterEach(function() { + Users.tokens.restore(); + Users.tokenHeader.restore(); + }) + }); + describe("Bearer Strategy", function() { it('Rejects invalid token',function(done) { var getToken = sinon.stub(Tokens,"get",function(token) { diff --git a/test/unit/@node-red/editor-api/lib/auth/users_spec.js b/test/unit/@node-red/editor-api/lib/auth/users_spec.js index 515d23034..228163684 100644 --- a/test/unit/@node-red/editor-api/lib/auth/users_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/users_spec.js @@ -227,4 +227,47 @@ describe("api/auth/users", function() { }); }); }); + + describe('Initialised with tokens set as function',function() { + before(function() { + Users.init({ + type:"strategy", + tokens: function(token) { return("Done-"+token); } + }); + }); + after(function() { + Users.init({}); + }); + describe('#tokens',function() { + it('handles api.tokens being a function',function(done) { + Users.should.have.property('tokens').which.is.a.Function(); + (Users.tokens("1234")).should.equal("Done-1234"); + (Users.tokenHeader()).should.equal("authorization"); + done(); + }); + }); + }); + + describe('Initialised with tokens set as function and tokenHeader set as token header name',function() { + before(function() { + Users.init({ + type:"strategy", + tokens: function(token) { return("Done-"+token); }, + tokenHeader: "X-TEST-TOKEN" + }); + }); + after(function() { + Users.init({}); + }); + describe('#tokens',function() { + it('handles api.tokens being a function and api.tokenHeader being a header name',function(done) { + Users.should.have.property('tokens').which.is.a.Function(); + (Users.tokens("1234")).should.equal("Done-1234"); + Users.should.have.property('tokenHeader').which.is.a.Function(); + (Users.tokenHeader()).should.equal("x-test-token"); + done(); + }); + }); + }); + }); diff --git a/test/unit/@node-red/runtime/lib/api/flows_spec.js b/test/unit/@node-red/runtime/lib/api/flows_spec.js index a7c85efa2..dafbbc69b 100644 --- a/test/unit/@node-red/runtime/lib/api/flows_spec.js +++ b/test/unit/@node-red/runtime/lib/api/flows_spec.js @@ -53,7 +53,7 @@ describe("runtime-api/flows", function() { var loadFlows; var reloadError = false; beforeEach(function() { - setFlows = sinon.spy(function(flows,type) { + setFlows = sinon.spy(function(flows,credentials,type) { if (flows[0] === "error") { var err = new Error("error"); err.code = "error"; @@ -91,7 +91,19 @@ describe("runtime-api/flows", function() { result.should.eql({rev:"newRev"}); setFlows.called.should.be.true(); setFlows.lastCall.args[0].should.eql([4,5,6]); - setFlows.lastCall.args[1].should.eql("full"); + setFlows.lastCall.args[2].should.eql("full"); + done(); + }).catch(done); + }); + it("includes credentials when part of the request", function(done) { + flows.setFlows({ + flows: {flows:[4,5,6], credentials: {$:"creds"}}, + }).then(function(result) { + result.should.eql({rev:"newRev"}); + setFlows.called.should.be.true(); + setFlows.lastCall.args[0].should.eql([4,5,6]); + setFlows.lastCall.args[1].should.eql({$:"creds"}); + setFlows.lastCall.args[2].should.eql("full"); done(); }).catch(done); }); @@ -103,7 +115,7 @@ describe("runtime-api/flows", function() { result.should.eql({rev:"newRev"}); setFlows.called.should.be.true(); setFlows.lastCall.args[0].should.eql([4,5,6]); - setFlows.lastCall.args[1].should.eql("nodes"); + setFlows.lastCall.args[2].should.eql("nodes"); done(); }).catch(done); }); @@ -125,7 +137,7 @@ describe("runtime-api/flows", function() { result.should.eql({rev:"newRev"}); setFlows.called.should.be.true(); setFlows.lastCall.args[0].should.eql([4,5,6]); - setFlows.lastCall.args[1].should.eql("nodes"); + setFlows.lastCall.args[2].should.eql("nodes"); done(); }).catch(done); }); diff --git a/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js b/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js index 3c9030b03..6fd76a421 100644 --- a/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js @@ -32,17 +32,20 @@ describe('context', function() { return Context.close(); }); it('stores local property',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); should.not.exist(context1.get("foo")); context1.set("foo","test"); context1.get("foo").should.equal("test"); }); it('stores local property - creates parent properties',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); context1.set("foo.bar","test"); context1.get("foo").should.eql({bar:"test"}); }); it('deletes local property',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); context1.set("foo.abc.bar1","test1"); context1.set("foo.abc.bar2","test2"); @@ -55,12 +58,14 @@ describe('context', function() { should.not.exist(context1.get("foo")); }); it('stores flow property',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); should.not.exist(context1.flow.get("foo")); context1.flow.set("foo","test"); context1.flow.get("foo").should.equal("test"); }); it('stores global property',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); should.not.exist(context1.global.get("foo")); context1.global.set("foo","test"); @@ -68,6 +73,7 @@ describe('context', function() { }); it('keeps local context local', function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); var context2 = Context.get("2","flowA"); @@ -79,6 +85,7 @@ describe('context', function() { should.not.exist(context2.get("foo")); }); it('flow context accessible to all flow nodes', function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); var context2 = Context.get("2","flowA"); @@ -91,6 +98,8 @@ describe('context', function() { }); it('flow context not shared to nodes on other flows', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB") var context1 = Context.get("1","flowA"); var context2 = Context.get("2","flowB"); @@ -103,6 +112,9 @@ describe('context', function() { }); it('global context shared to all nodes', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB") + var context1 = Context.get("1","flowA"); var context2 = Context.get("2","flowB"); @@ -115,6 +127,7 @@ describe('context', function() { }); it('context.flow/global are not enumerable', function() { + var flowContextA = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); Object.keys(context1).length.should.equal(0); Object.keys(context1.flow).length.should.equal(0); @@ -122,6 +135,7 @@ describe('context', function() { }) it('context.flow/global cannot be deleted', function() { + var flowContextA = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); delete context1.flow; should.exist(context1.flow); @@ -130,6 +144,7 @@ describe('context', function() { }) it('deletes context',function() { + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); should.not.exist(context.get("foo")); context.set("foo","abc"); @@ -142,6 +157,7 @@ describe('context', function() { }); it('enumerates context keys - sync', function() { + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); var keys = context.keys(); @@ -160,6 +176,7 @@ describe('context', function() { }); it('enumerates context keys - async', function(done) { + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); var keys = context.keys(function(err,keys) { @@ -183,6 +200,7 @@ describe('context', function() { it('should enumerate only context keys when GlobalContext was given - sync', function() { Context.init({functionGlobalContext: {foo:"bar"}}); Context.load().then(function(){ + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); context.global.set("foo2","bar2"); var keys = context.global.keys(); @@ -195,6 +213,7 @@ describe('context', function() { it('should enumerate only context keys when GlobalContext was given - async', function(done) { Context.init({functionGlobalContext: {foo:"bar"}}); Context.load().then(function(){ + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); context.global.set("foo2","bar2"); context.global.keys(function(err,keys) { @@ -210,6 +229,7 @@ describe('context', function() { it('returns functionGlobalContext value if store value undefined', function() { Context.init({functionGlobalContext: {foo:"bar"}}); return Context.load().then(function(){ + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); var v = context.global.get('foo'); v.should.equal('bar'); @@ -219,6 +239,7 @@ describe('context', function() { it('returns functionGlobalContext sub-value if store value undefined', function() { Context.init({functionGlobalContext: {foo:{bar:123}}}); return Context.load().then(function(){ + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); var v = context.global.get('foo.bar'); should.equal(v,123); @@ -227,40 +248,67 @@ describe('context', function() { describe("$parent", function() { it('should get undefined for $parent without key', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); + var context1 = Context.get("1","flowB"); var parent = context1.get("$parent"); should.equal(parent, undefined); }); it('should get undefined for $parent of root', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); - var parent = context1.get("$parent.$parent.K"); + var context1 = Context.get("1","flowB"); + var parent = context1.flow.get("$parent.$parent.K"); should.equal(parent, undefined); }); - it('should get value in $parent', function() { + it('should get undefined for $parent of root - callback', function(done) { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); - context0.set("K", "v"); - var v = context1.get("$parent.K"); + var context1 = Context.get("1","flowB"); + context1.flow.get("$parent.$parent.K", function(err, result) { + try { + should.equal(err, undefined); + should.equal(result, undefined); + done(); + } catch(err) { + done(err); + } + }); + + }); + + it('should get value in $parent', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") + var context0 = Context.get("0","flowA"); + var context1 = Context.get("1","flowB"); + flowContextA.set("K", "v"); + var v = context1.flow.get("$parent.K"); should.equal(v, "v"); }); it('should set value in $parent', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); - context1.set("$parent.K", "v"); - var v = context0.get("K"); + var context1 = Context.get("1","flowB"); + context1.flow.set("$parent.K", "v"); + var v = flowContextA.get("K"); should.equal(v, "v"); }); it('should not contain $parent in keys', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); + var context1 = Context.get("1","flowB"); var parent = context1.get("$parent"); - context0.set("K0", "v0"); + flowContextA.set("K0", "v0"); context1.set("K1", "v1"); var keys = context1.keys(); keys.should.have.length(1); @@ -366,6 +414,7 @@ describe('context', function() { it('should ignore reserved storage name `_`', function(done) { Context.init({contextStorage:{_:{module:testPlugin}}}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow") var context = Context.get("1","flow"); var cb = function(){} context.set("foo","bar","_",cb); @@ -452,6 +501,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); var cb = function(){done("An error occurred")} Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set("foo","bar","test",cb); context.get("foo","test",cb); @@ -465,6 +515,7 @@ describe('context', function() { it('should store flow property to external context storage',function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.flow.set("foo","bar","test",cb); @@ -479,6 +530,7 @@ describe('context', function() { it('should store global property to external context storage',function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.global.set("foo","bar","test",cb); @@ -493,6 +545,7 @@ describe('context', function() { it('should store data to the default context when non-existent context storage was specified', function(done) { Context.init({contextStorage:contextDefaultStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("foo","bar","nonexist",cb); @@ -510,6 +563,7 @@ describe('context', function() { it('should use the default context', function(done) { Context.init({contextStorage:contextDefaultStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("foo","bar","default",cb); @@ -527,6 +581,7 @@ describe('context', function() { it('should use the alias of default context', function(done) { Context.init({contextStorage:contextDefaultStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("foo","alias",cb); @@ -541,10 +596,11 @@ describe('context', function() { done(); }).catch(done); }); - + it('should allow the store name to be provide in the key', function(done) { Context.init({contextStorage:contextDefaultStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("#:(test)::foo","bar"); @@ -561,6 +617,7 @@ describe('context', function() { it('should use default as the alias of other context', function(done) { Context.init({contextStorage:contextAlias}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("foo","alias",cb); @@ -575,6 +632,7 @@ describe('context', function() { it('should not throw an error using undefined storage for local context', function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.get("local","nonexist",cb); @@ -584,6 +642,7 @@ describe('context', function() { it('should throw an error using undefined storage for flow context', function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.flow.get("flow","nonexist",cb); @@ -595,6 +654,7 @@ describe('context', function() { var fGC = { "foo": 456 }; Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC }); Context.load().then(function() { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); // Get foo - should be value from fGC var v = context.global.get("foo"); @@ -615,6 +675,7 @@ describe('context', function() { var fGC = { "foo": 456 }; Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC }); Context.load().then(function() { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); // Get foo - should be value from fGC context.global.get("foo", function(err, v) { @@ -647,6 +708,7 @@ describe('context', function() { it('should return multiple values if key is an array', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set("foo1","bar1","memory"); context.set("foo2","bar2","memory"); @@ -667,6 +729,7 @@ describe('context', function() { var fGC = { "foo1": 456, "foo2": {"bar":789} }; Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC }); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.global.get(["foo1","foo2.bar","foo3"], "memory", function(err,foo1,foo2,foo3){ if (err) { @@ -685,6 +748,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); stubGet.onFirstCall().callsArgWith(2, "error2", "bar1"); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow") var context = Context.get("1","flow"); context.global.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){ if (err === "error2") { @@ -702,6 +766,7 @@ describe('context', function() { stubGet.onSecondCall().callsArgWith(2, null, "bar2"); stubGet.onThirdCall().callsArgWith(2, "error3"); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){ if (err === "error1") { @@ -716,6 +781,7 @@ describe('context', function() { it('should store multiple properties if key and value are arrays', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){ if (err) { @@ -739,6 +805,7 @@ describe('context', function() { it('should deletes multiple properties', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){ if (err) { @@ -777,6 +844,7 @@ describe('context', function() { it('should use null for missing values if the value array is shorter than the key array', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2"], "memory", function(err){ if (err) { @@ -804,6 +872,7 @@ describe('context', function() { it('should use null for missing values if the value is not array', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], "bar1", "memory", function(err){ if (err) { @@ -831,6 +900,7 @@ describe('context', function() { it('should ignore the extra values if the value array is longer than the key array', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3","ignored"], "memory", function(err){ if (err) { @@ -859,6 +929,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); stubSet.onFirstCall().callsArgWith(3, "error2"); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){ if (err === "error2") { @@ -873,6 +944,7 @@ describe('context', function() { it('should throw an error if callback of context.get is not a function', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.get("foo", "memory", "callback"); done("should throw an error."); @@ -884,6 +956,7 @@ describe('context', function() { it('should not throw an error if callback of context.get is not specified', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.get("foo", "memory"); done(); @@ -893,6 +966,7 @@ describe('context', function() { it('should throw an error if callback of context.set is not a function', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.set("foo", "bar", "memory", "callback"); done("should throw an error."); @@ -904,6 +978,7 @@ describe('context', function() { it('should not throw an error if callback of context.set is not specified', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.set("foo", "bar", "memory"); done(); @@ -913,6 +988,7 @@ describe('context', function() { it('should throw an error if callback of context.keys is not a function', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.keys("memory", "callback"); done("should throw an error."); @@ -924,6 +1000,7 @@ describe('context', function() { it('should not throw an error if callback of context.keys is not specified', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.keys("memory"); done(); @@ -953,7 +1030,6 @@ describe('context', function() { }).catch(done); }); }); - describe('delete context',function(){ it('should not call delete() when external context storage is used', function(done) { Context.init({contextStorage:contextDefaultStorage}); diff --git a/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js b/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js index a37d88f78..8865c5d3f 100644 --- a/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js @@ -67,7 +67,10 @@ describe('flows/index', function() { }); return when.resolve(); }); - credentialsLoad = sinon.stub(credentials,"load",function() { + credentialsLoad = sinon.stub(credentials,"load",function(creds) { + if (creds && creds.hasOwnProperty("$") && creds['$'] === "fail") { + return when.reject("creds error"); + } return when.resolve(); }); flowCreate = sinon.stub(Flow,"create",function(parent, global, flow) { @@ -177,6 +180,23 @@ describe('flows/index', function() { }); }); + it('sets the full flow including credentials', function(done) { + var originalConfig = [ + {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, + {id:"t1",type:"tab"} + ]; + var credentials = {"t1-1":{"a":1}}; + + flows.init({log:mockLog, settings:{},storage:storage}); + flows.setFlows(originalConfig,credentials).then(function() { + credentialsClean.called.should.be.false(); + credentialsLoad.called.should.be.true(); + credentialsLoad.lastCall.args[0].should.eql(credentials); + flows.getFlows().flows.should.eql(originalConfig); + done(); + }); + }); + it('updates existing flows with partial deployment - nodes', function(done) { var originalConfig = [ {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, @@ -235,6 +255,20 @@ describe('flows/index', function() { }); }); + it('returns error if it cannot decrypt credentials', function(done) { + var originalConfig = [ + {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, + {id:"t1",type:"tab"} + ]; + var credentials = {"$":"fail"}; + + flows.init({log:mockLog, settings:{},storage:storage}); + flows.setFlows(originalConfig,credentials).then(function() { + done("Unexpected success when credentials couldn't be decrypted") + }).catch(function(err) { + done(); + }); + }); }); describe('#load', function() { diff --git a/test/unit/@node-red/runtime/lib/nodes/flows/util_spec.js b/test/unit/@node-red/runtime/lib/nodes/flows/util_spec.js index ac8efef79..c20d222e2 100644 --- a/test/unit/@node-red/runtime/lib/nodes/flows/util_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/flows/util_spec.js @@ -150,7 +150,7 @@ describe('flows/util', function() { {id:"t1",type:"tab"} ]; var parsedConfig = flowUtil.parseConfig(originalConfig); - var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"missingTypes":[]}; + var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"groups":{},"missingTypes":[]}; parsedConfig.should.eql(expectedConfig); }); @@ -161,7 +161,7 @@ describe('flows/util', function() { {id:"t1",type:"tab"} ]; var parsedConfig = flowUtil.parseConfig(originalConfig); - var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"missingTypes":[]}; + var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"},"t1":{"id":"t1","type":"tab"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"groups":{},"missingTypes":[]}; parsedConfig.should.eql(expectedConfig); }); @@ -173,7 +173,7 @@ describe('flows/util', function() { {id:"t2-1",x:10,y:10,z:"t2",type:"test",wires:[]} ]; var parsedConfig = flowUtil.parseConfig(originalConfig); - var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t2":{"id":"t2","type":"tab"},"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}},"t2":{"id":"t2","type":"tab","subflows":{},"configs":{},"nodes":{"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}}}},"missingTypes":[]}; + var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"t2":{"id":"t2","type":"tab"},"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}},"t2":{"id":"t2","type":"tab","subflows":{},"configs":{},"nodes":{"t2-1":{"id":"t2-1","x":10,"y":10,"z":"t2","type":"test","wires":[]}}}},"groups":{},"missingTypes":[]}; parsedConfig.should.eql(expectedConfig); }); @@ -185,7 +185,7 @@ describe('flows/util', function() { {id:"sf1-1",x:10,y:10,z:"sf1",type:"test",wires:[]} ]; var parsedConfig = flowUtil.parseConfig(originalConfig); - var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[]},"sf1":{"id":"sf1","type":"subflow"},"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"subflows":{"sf1":{"id":"sf1","type":"subflow","configs":{},"nodes":{"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"instances":[{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}]}},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}}}},"missingTypes":[]}; + var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[]},"sf1":{"id":"sf1","type":"subflow"},"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"subflows":{"sf1":{"id":"sf1","type":"subflow","configs":{},"nodes":{"sf1-1":{"id":"sf1-1","x":10,"y":10,"z":"sf1","type":"test","wires":[]}},"instances":[{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}]}},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"subflow:sf1","wires":[],"subflow":"sf1"}}}},"groups":{},"missingTypes":[]}; parsedConfig.should.eql(expectedConfig); }); @@ -197,7 +197,7 @@ describe('flows/util', function() { ]; var parsedConfig = flowUtil.parseConfig(originalConfig); parsedConfig.missingTypes.should.eql(['missing']); - var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},'t1-2': { id: 't1-2', x: 10, y: 10, z: 't1', type: 'missing', wires: [] }}}},"missingTypes":["missing"]}; + var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},"t1-2":{"id":"t1-2","x":10,"y":10,"z":"t1","type":"missing","wires":[]}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"sf1","wires":[]},'t1-2': { id: 't1-2', x: 10, y: 10, z: 't1', type: 'missing', wires: [] }}}},"groups":{},"missingTypes":["missing"]}; redUtil.compareObjects(parsedConfig,expectedConfig).should.be.true(); }); @@ -207,10 +207,20 @@ describe('flows/util', function() { {id:"cn",type:"test"}, ]; var parsedConfig = flowUtil.parseConfig(originalConfig); - var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"missingTypes":[]}; + var expectedConfig = {"allNodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]},"cn":{"id":"cn","type":"test"}},"subflows":{},"configs":{"cn":{"id":"cn","type":"test","_users":["t1-1"]}},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","foo":"cn","wires":[]}}}},"groups":{},"missingTypes":[]}; parsedConfig.should.eql(expectedConfig); }); + it('parses a flow including a group', function() { + var originalConfig = [ + {id:"t1",type:"tab"}, + {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, + {id:"g1",type:"group",z:"t1"} + ]; + var parsedConfig = flowUtil.parseConfig(originalConfig); + var expectedConfig = {"allNodes":{"t1":{"id":"t1","type":"tab"},"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]},"g1":{"id":"g1","type":"group","z":"t1"}},"subflows":{},"configs":{},"flows":{"t1":{"id":"t1","type":"tab","subflows":{},"configs":{},"nodes":{"t1-1":{"id":"t1-1","x":10,"y":10,"z":"t1","type":"test","wires":[]}}}},"groups":{"g1":{"id":"g1","type":"group","z":"t1"}},"missingTypes":[]} + parsedConfig.should.eql(expectedConfig); + }); }); diff --git a/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js b/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js index 70152ea1c..69b6e3da6 100644 --- a/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js +++ b/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js @@ -72,45 +72,52 @@ describe('storage/localfilesystem/library', function() { }); function createObjectLibrary(type) { - type = type ||"object"; - var objLib = path.join(userDir,"lib",type); + type = type || "object"; + var objLib = path.join(userDir, "lib", type); try { fs.mkdirSync(objLib); - } catch(err) { + } catch (err) { } - fs.mkdirSync(path.join(objLib,"A")); - fs.mkdirSync(path.join(objLib,"B")); - fs.mkdirSync(path.join(objLib,"B","C")); + fs.mkdirSync(path.join(objLib, "A")); + fs.mkdirSync(path.join(objLib, "B")); + fs.mkdirSync(path.join(objLib, "B", "C")); + fs.mkdirSync(path.join(objLib, "D")); if (type === "functions" || type === "object") { - fs.writeFileSync(path.join(objLib,"file1.js"),"// abc: def\n// not a metaline \n\n Hi",'utf8'); - fs.writeFileSync(path.join(objLib,"B","file2.js"),"// ghi: jkl\n// not a metaline \n\n Hi",'utf8'); + fs.writeFileSync(path.join(objLib, "file1.js"), "// abc: def\n// not a metaline \n\n Hi", 'utf8'); + fs.writeFileSync(path.join(objLib, "B", "file2.js"), "// ghi: jkl\n// not a metaline \n\n Hi", 'utf8'); + fs.writeFileSync(path.join(objLib, "D", "file3.js"), "// mno: 日本語テスト\n\nこんにちわ", 'utf8'); } if (type === "flows" || type === "object") { - fs.writeFileSync(path.join(objLib,"B","flow.json"),"Hi",'utf8'); + fs.writeFileSync(path.join(objLib, "B", "flow.json"), "Hi", 'utf8'); } } - it('should return a directory listing of library objects',function(done) { - localfilesystemLibrary.init({userDir:userDir}).then(function() { + it('should return a directory listing of library objects', function (done) { + localfilesystemLibrary.init({userDir: userDir}).then(function () { createObjectLibrary(); - localfilesystemLibrary.getLibraryEntry('object','').then(function(flows) { - flows.should.eql([ 'A', 'B', { abc: 'def', fn: 'file1.js' } ]); - localfilesystemLibrary.getLibraryEntry('object','B').then(function(flows) { - flows.should.eql([ 'C', { ghi: 'jkl', fn: 'file2.js' }, { fn: 'flow.json' } ]); - localfilesystemLibrary.getLibraryEntry('object','B/C').then(function(flows) { + localfilesystemLibrary.getLibraryEntry('object', '').then(function (flows) { + flows.should.eql([ 'A', 'B', 'D', { abc: 'def', fn: 'file1.js' }]); + localfilesystemLibrary.getLibraryEntry('object', 'B').then(function (flows) { + flows.should.eql([ 'C', { ghi: 'jkl', fn: 'file2.js' }, { fn: 'flow.json' }]); + localfilesystemLibrary.getLibraryEntry('object', 'B/C').then(function (flows) { flows.should.eql([]); - done(); - }).catch(function(err) { + localfilesystemLibrary.getLibraryEntry('object', 'D').then(function (flows) { + flows.should.eql([{ mno: '日本語テスト', fn: 'file3.js' }]); + done(); + }).catch(function (err) { + done(err); + }); + }).catch(function (err) { done(err); }); - }).catch(function(err) { + }).catch(function (err) { done(err); }); - }).catch(function(err) { + }).catch(function (err) { done(err); }); - }).catch(function(err) { + }).catch(function (err) { done(err); }); }); @@ -203,4 +210,35 @@ describe('storage/localfilesystem/library', function() { done(err); }); }); + + it('should return a newly saved library flow (multi-byte character)',function(done) { + localfilesystemLibrary.init({userDir:userDir}).then(function() { + createObjectLibrary("flows"); + localfilesystemLibrary.getLibraryEntry('flows','B').then(function(flows) { + flows.should.eql([ 'C', {fn:'flow.json'} ]); + var ft = path.join("B","D","file4"); + localfilesystemLibrary.saveLibraryEntry('flows',ft,{mno:'pqr'},"こんにちわこんにちわこんにちわ").then(function() { + setTimeout(function() { + localfilesystemLibrary.getLibraryEntry('flows',path.join("B","D")).then(function(flows) { + flows.should.eql([ { mno: 'pqr', fn: 'file4.json' } ]); + localfilesystemLibrary.getLibraryEntry('flows',ft+".json").then(function(body) { + body.should.eql("こんにちわこんにちわこんにちわ"); + done(); + }).catch(function(err) { + done(err); + }); + }).catch(function(err) { + done(err); + }) + }, 50); + }).catch(function(err) { + done(err); + }); + }).catch(function(err) { + done(err); + }); + }).catch(function(err) { + done(err); + }); + }); });