mirror of
				https://github.com/node-red/node-red.git
				synced 2025-03-01 10:36:34 +00:00 
			
		
		
		
	Compare commits
	
		
			92 Commits
		
	
	
		
			4334-conte
			...
			fix-nls
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 2f1565fbc9 | ||
|  | 7fd0ecf721 | ||
|  | 37d1539fda | ||
|  | 1e518396d6 | ||
|  | 617b98ed49 | ||
|  | 6d2a870812 | ||
|  | 2963f3f1b8 | ||
|  | 17e4bdbff1 | ||
|  | f3dd5770d9 | ||
|  | eebab4a921 | ||
|  | b06494c5be | ||
|  | a0562bef81 | ||
|  | eff063a748 | ||
|  | 94abaaff1e | ||
|  | 03732869e4 | ||
|  | 41868e2652 | ||
|  | 81bfba3cea | ||
|  | 8a04eb2e29 | ||
|  | 1777fc749d | ||
|  | fd32ee09ff | ||
|  | 4bb2157cab | ||
|  | 47f20cc86a | ||
|  | 5fc4526c70 | ||
|  | 9fe653f821 | ||
|  | 55da21ed15 | ||
|  | 7ebf84f38c | ||
|  | d42e75ebd0 | ||
|  | 28825049fe | ||
|  | 1c3644e338 | ||
|  | 2dfabb523b | ||
|  | 3e2d20e536 | ||
|  | ee7ee083b0 | ||
|  | fb54c05d9f | ||
|  | 21f807aa66 | ||
|  | 6b088bda12 | ||
|  | a32ee869ae | ||
|  | a2d7772958 | ||
|  | 6ec052be18 | ||
|  | 9c71d52d69 | ||
|  | 171c146ec5 | ||
|  | bc6afa2164 | ||
|  | 3c036257ef | ||
|  | 6633730bf1 | ||
|  | 2964a4da5e | ||
|  | a55554193b | ||
|  | d2a8338d4a | ||
|  | e945deeab6 | ||
|  | 722fe02933 | ||
|  | 3dec609459 | ||
|  | e73b9f646d | ||
|  | 3cea6400d1 | ||
|  | c6a8eee73d | ||
|  | 74d431ea36 | ||
|  | 409a559a13 | ||
|  | 6829535350 | ||
|  | 6488111f79 | ||
|  | 923339c1d8 | ||
|  | 3f4d96f4cd | ||
|  | c52985d245 | ||
|  | 88e6c71aa0 | ||
|  | 4d08e297c4 | ||
|  | ad2b30691f | ||
|  | 369bad01b8 | ||
|  | b6ecc6d9ea | ||
|  | 0117df0960 | ||
|  | 6ac905f264 | ||
|  | e5307f6604 | ||
|  | 8d9b6dd859 | ||
|  | 01821ead0f | ||
|  | 1451fb9e2f | ||
|  | 245751bb23 | ||
|  | f07519c705 | ||
|  | 33a978a246 | ||
|  | 861dc0c383 | ||
|  | 60593fed4a | ||
|  | fae3a5c26a | ||
|  | 1a52c0adfc | ||
|  | 44c0bbc61e | ||
|  | 5ac50fae3a | ||
|  | ee48a2f2bf | ||
|  | 680d5b8216 | ||
|  | c9320c190d | ||
|  | 566c667c5d | ||
|  | ec6e42e655 | ||
|  | bba6b6f71d | ||
|  | c261f6625a | ||
|  | a489b270d1 | ||
|  | 51cb61940d | ||
|  | 6635ff9a69 | ||
|  | 41797f8cef | ||
|  | 797cea5394 | ||
|  | 2880d4120e | 
							
								
								
									
										6
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -27,12 +27,12 @@ jobs: | ||||
|         with: | ||||
|             repository: 'node-red/node-red.github.io' | ||||
|             path: 'node-red.github.io' | ||||
|       - uses: actions/setup-node@v3 | ||||
|       - uses: actions/setup-node@v4 | ||||
|         with: | ||||
|             node-version: '16' | ||||
|       - run: node ./node-red/.github/scripts/update-node-red-docker.js | ||||
|       - name: Create Docker Pull Request | ||||
|         uses: peter-evans/create-pull-request@v2 | ||||
|         uses: peter-evans/create-pull-request@v5 | ||||
|         with: | ||||
|           token: ${{ secrets.NR_REPO_TOKEN }} | ||||
|           committer: GitHub <noreply@github.com> | ||||
| @@ -48,7 +48,7 @@ jobs: | ||||
|             This PR was auto-generated by a GitHub Action. Any questions, speak to @knolleary | ||||
|       - run: node ./node-red/.github/scripts/update-node-red-website.js | ||||
|       - name: Create Website Pull Request | ||||
|         uses: peter-evans/create-pull-request@v2 | ||||
|         uses: peter-evans/create-pull-request@v5 | ||||
|         with: | ||||
|           token: ${{ secrets.NR_REPO_TOKEN }} | ||||
|           committer: GitHub <noreply@github.com> | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/tests.yml
									
									
									
									
										vendored
									
									
								
							| @@ -21,7 +21,7 @@ jobs: | ||||
|     steps: | ||||
|     - uses: actions/checkout@v4 | ||||
|     - name: Use Node.js ${{ matrix.node-version }} | ||||
|       uses: actions/setup-node@v3 | ||||
|       uses: actions/setup-node@v4 | ||||
|       with: | ||||
|         node-version: ${{ matrix.node-version }} | ||||
|     - name: Install Dependencies | ||||
|   | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -27,3 +27,4 @@ docs | ||||
| .vscode | ||||
| .nyc_output | ||||
| sync.ffs_db | ||||
| package-lock.json | ||||
|   | ||||
							
								
								
									
										68
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,71 @@ | ||||
| #### 3.1.2: Maintenance Release | ||||
|  | ||||
| Editor  | ||||
|  | ||||
|  - Relax some node validators to allow undefined value (#4471) @knolleary | ||||
|  - Fix switch validation of typeof field (#4465) @knolleary | ||||
|  - Use move cursor when hovering on group border (#4467) @knolleary | ||||
|  - Added action list Chinese (Simplified and Traditional) translation + v3.1.1 changes (#4470) @wangyiyi2056 | ||||
|  - Add French translation of `action-list` + v3.1.1 changes (#4466) @GogoVega | ||||
|   | ||||
|  Runtime | ||||
|  | ||||
|  - Ensure nested groups inside subflows have their g props remapped (#4472) @knolleary | ||||
|   | ||||
| #### 3.1.1: Maintenance Release | ||||
|  | ||||
| Editor | ||||
|  | ||||
|  - Fix debug filter (#4461) @knolleary | ||||
|  - Fix various issues with debug pop-out window (#4459) @knolleary | ||||
|  - Ensure subflow instances keep track of their groups (#4457) @knolleary | ||||
|  - Fix `validateNodeProperty` without validator provided (#4455) @GogoVega | ||||
|  - Debounce node-removed notifications (#4453) @knolleary | ||||
|  - Don't try to load the parents of the first commit (#4448) @bonanitech | ||||
|  - Allow a theme to specifiy which theme mermaid should use (#4441) @knolleary | ||||
|  - Update browser title with flow name if set (#4427) @knolleary | ||||
|  - Ensure typeSearch handles undefined node definitions (#4423) @knolleary | ||||
|  - Ensure group w/h are imported if present (#4426) @knolleary | ||||
|  - Hide node status background when there is no status to show (#4425) @knolleary | ||||
|  - Add a close button to the restart-required notification (#4407) @knolleary | ||||
|  - Extend typedInput "num" type validity check to NaN, binary, octal & hex (#4371) @ralphwetzel | ||||
|  - Fix unintended new line in node name (#4399) @kazuhitoyokoi | ||||
|  - Ctrl-Enter does not close tray (Monaco) #4377 (#4382) @hazymat | ||||
|  - fix buffer viewer to handle 0b style binary (#4393) @dceejay | ||||
|  - Rework mermaid integration to support off-DOM rendering (#4364) @knolleary | ||||
|  - Add missing nls labels to context menu (#4365) @knolleary | ||||
|  | ||||
| Runtime | ||||
|  | ||||
|  - Bump the github-actions group with 2 updates (#4404) @app/dependabot | ||||
|  - Handle unknown node reference inside subflow module (#4460) @knolleary | ||||
|  - Add modules.install audit event when external module installed (#4452) @knolleary | ||||
|  - Allow import of modules with subpath in specifier (#4451) @knolleary | ||||
|  - Update node-red-admin version (#4438) @knolleary | ||||
|  - Handle false-like env vars properly (#4411) @knolleary | ||||
|  - Only save settings once during node load process (#4409) @knolleary | ||||
|  - Ensure global-config nodes lookup cred values properly (#4405) @knolleary | ||||
|  - Handle credential env var evaluation when no value set (#4362) @knolleary | ||||
|  - Don't commit package-lock.json (#4354) @bonanitech | ||||
|  - Fix env evaluation when one env references another in the same object (#4361) @knolleary | ||||
|  - Add dependabot for Github Actions (#4312) @Rotzbua | ||||
|  - Update outdated Github Actions (#4311) @Rotzbua | ||||
|  - github: Request `npm run test` in PR template (#4348) @ZJvandeWeg | ||||
|  - Add French translation of v3.1.0-beta.4 changes + slight improvements (#4329) @GogoVega | ||||
|  - Handle nodes with multiple input handlers properly (#4332) @knolleary | ||||
|  - Soften the language around unrequited PRs (#4351) @knolleary | ||||
|  | ||||
| Nodes | ||||
|  | ||||
|  - CSV: make CSV export way faster by not re-allocating and handling huge string (#4349) @Fadoli | ||||
|  - Delay: Fix regression in delay node to not pass on msg.reset (#4350) @dceejay | ||||
|  - Link Call: Handle undefined linkType value for existing link-call nodes (#4331) @knolleary | ||||
|  - MQTT: Guard against node.broker being undefined (#4454) @knolleary | ||||
|  - MQTT: check topic length > 0 before publish (#4416) @dceejay | ||||
|  - Switch/Change: Improve validation of switch/change node rules (#4368) @knolleary | ||||
|  - Template: Fix height of description editor in template node (#4346) @kazuhitoyokoi | ||||
|  - Various: Add validators to any fields using msg-typed Input (#4440) @knolleary | ||||
|  | ||||
| #### 3.1.0: Milestone Release | ||||
|  | ||||
| Editor | ||||
|   | ||||
| @@ -151,7 +151,6 @@ module.exports = function(grunt) { | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/font-awesome.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/history.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/validators.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/mermaid.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/utils.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/common/editableList.js", | ||||
|                     "packages/node_modules/@node-red/editor-client/src/js/ui/common/treeList.js", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "node-red", | ||||
|     "version": "3.1.0", | ||||
|     "version": "3.1.2", | ||||
|     "description": "Low-code programming for event-driven applications", | ||||
|     "homepage": "https://nodered.org", | ||||
|     "license": "Apache-2.0", | ||||
| @@ -64,7 +64,7 @@ | ||||
|         "mqtt": "4.3.7", | ||||
|         "multer": "1.4.5-lts.1", | ||||
|         "mustache": "4.2.0", | ||||
|         "node-red-admin": "^3.1.0", | ||||
|         "node-red-admin": "^3.1.1", | ||||
|         "node-watch": "0.7.4", | ||||
|         "nopt": "5.0.0", | ||||
|         "oauth2orize": "1.11.1", | ||||
| @@ -109,7 +109,7 @@ | ||||
|         "jquery-i18next": "1.2.1", | ||||
|         "jsdoc-nr-template": "github:node-red/jsdoc-nr-template", | ||||
|         "marked": "4.3.0", | ||||
|         "mermaid": "^9.4.3", | ||||
|         "mermaid": "^10.4.0", | ||||
|         "minami": "1.2.3", | ||||
|         "mocha": "9.2.2", | ||||
|         "node-red-node-test-helper": "^0.3.2", | ||||
|   | ||||
| @@ -339,6 +339,8 @@ module.exports = { | ||||
|                 } | ||||
|                 theme.codeEditor = theme.codeEditor || {} | ||||
|                 theme.codeEditor.options = Object.assign({}, themePlugin.monacoOptions, theme.codeEditor.options); | ||||
|  | ||||
|                 theme.mermaid = Object.assign({}, themePlugin.mermaid, theme.mermaid) | ||||
|             } | ||||
|             activeThemeInitialised = true; | ||||
|         } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/editor-api", | ||||
|     "version": "3.1.0", | ||||
|     "version": "3.1.2", | ||||
|     "license": "Apache-2.0", | ||||
|     "main": "./lib/index.js", | ||||
|     "repository": { | ||||
| @@ -16,8 +16,8 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/util": "3.1.0", | ||||
|         "@node-red/editor-client": "3.1.0", | ||||
|         "@node-red/util": "3.1.2", | ||||
|         "@node-red/editor-client": "3.1.2", | ||||
|         "bcryptjs": "2.4.3", | ||||
|         "body-parser": "1.20.2", | ||||
|         "clone": "2.1.2", | ||||
|   | ||||
| @@ -113,7 +113,7 @@ | ||||
|             "displayStatus": "Show node status", | ||||
|             "displayConfig": "Configuration nodes", | ||||
|             "import": "Import", | ||||
|             "importExample": "Import Example Flow", | ||||
|             "importExample": "Import example flow", | ||||
|             "export": "Export", | ||||
|             "search": "Search flows", | ||||
|             "searchInput": "search your flows", | ||||
| @@ -130,6 +130,11 @@ | ||||
|             "editPalette": "Manage palette", | ||||
|             "other": "Other", | ||||
|             "showTips": "Show tips", | ||||
|             "showNodeHelp": "Show node help", | ||||
|             "enableSelectedNodes": "Enable selected nodes", | ||||
|             "disableSelectedNodes": "Disable selected nodes", | ||||
|             "showSelectedNodeLabels": "Show selected node labels", | ||||
|             "hideSelectedNodeLabels": "Hide selected node labels", | ||||
|             "showWelcomeTours": "Show guided tours for new versions", | ||||
|             "help": "Node-RED website", | ||||
|             "projects": "Projects", | ||||
| @@ -511,8 +516,8 @@ | ||||
|         "selectAllConnected": "Select connected", | ||||
|         "addRemoveNode": "Add/remove node from selection", | ||||
|         "editSelected": "Edit selected node", | ||||
|         "deleteSelected": "Delete selected nodes or link", | ||||
|         "deleteReconnect": "Delete and Reconnect", | ||||
|         "deleteSelected": "Delete selection", | ||||
|         "deleteReconnect": "Delete and reconnect", | ||||
|         "importNode": "Import nodes", | ||||
|         "exportNode": "Export nodes", | ||||
|         "nudgeNode": "Move selected nodes (1px)", | ||||
| @@ -1215,11 +1220,9 @@ | ||||
|     "validator": { | ||||
|         "errors": { | ||||
|             "invalid-json": "Invalid JSON data: __error__", | ||||
|             "invalid-json-prop": "__prop__: invalid JSON data: __error__", | ||||
|             "invalid-expr": "Invalid JSONata expression: __error__", | ||||
|             "invalid-prop": "Invalid property expression", | ||||
|             "invalid-prop-prop": "__prop__: invalid property expression", | ||||
|             "invalid-num": "Invalid number", | ||||
|             "invalid-num-prop": "__prop__: invalid number", | ||||
|             "invalid-regexp": "Invalid input pattern", | ||||
|             "invalid-regex-prop": "__prop__: invalid input pattern", | ||||
|             "missing-required-prop": "__prop__: property value missing", | ||||
| @@ -1229,6 +1232,7 @@ | ||||
|         } | ||||
|     }, | ||||
|     "contextMenu": { | ||||
|         "showActionList": "Show action list", | ||||
|         "insert": "Insert", | ||||
|         "node": "Node", | ||||
|         "junction": "Junction", | ||||
|   | ||||
| @@ -1215,11 +1215,9 @@ | ||||
|   "validator": { | ||||
|     "errors": { | ||||
|       "invalid-json": "Données JSON invalides : __error__", | ||||
|       "invalid-json-prop": "__prop__: données JSON invalides : __error__", | ||||
|       "invalid-prop": "Expression de propriété non valide", | ||||
|       "invalid-prop-prop": "__prop__: expression de propriété invalide", | ||||
|       "invalid-expr": "Expression JSONata invalide : __error__", | ||||
|       "invalid-prop": "Expression de propriété invalide", | ||||
|       "invalid-num": "Numéro invalide", | ||||
|       "invalid-num-prop": "__prop__: numéro invalide", | ||||
|       "invalid-regexp": "Modèle d'entrée non valide", | ||||
|       "invalid-regex-prop": "__prop__: modèle d'entrée non valide", | ||||
|       "missing-required-prop": "__prop__: valeur de la propriété manquante", | ||||
| @@ -1238,5 +1236,159 @@ | ||||
|     "environment": "Environment", | ||||
|     "header": "Variables d'environnement globales", | ||||
|     "revert": "Rétablir" | ||||
|   }, | ||||
|   "action-list": { | ||||
|     "toggle-show-tips": "Basculer l'affichage des astuces", | ||||
|     "show-about": "Afficher la description de Node-RED", | ||||
|     "show-welcome-tour": "Afficher la visite de bienvenue", | ||||
|     "show-next-tab": "Afficher l'onglet suivant", | ||||
|     "show-previous-tab": "Afficher l'onglet précédent", | ||||
|     "add-flow": "Ajouter un flux", | ||||
|     "add-flow-to-right": "Ajouter un flux à droite", | ||||
|     "edit-flow": "Modifier le flux", | ||||
|     "remove-flow": "Supprimer le flux", | ||||
|     "enable-flow": "Activer le flux", | ||||
|     "disable-flow": "Désactiver le flux", | ||||
|     "hide-flow": "Masquer le flux", | ||||
|     "hide-other-flows": "Masquer les autres flux", | ||||
|     "hide-all-flows": "Masquer tous les flux", | ||||
|     "show-all-flows": "Afficher tous les flux", | ||||
|     "show-last-hidden-flow": "Afficher le dernier flux masqué", | ||||
|     "list-modified-nodes": "Afficher les flux modifiés", | ||||
|     "list-hidden-flows": "Afficher les flux cachés", | ||||
|     "list-flows": "Lister les flux", | ||||
|     "list-subflows": "Liste les sous-flux", | ||||
|     "go-to-previous-location": "Aller à l'emplacement précédent", | ||||
|     "go-to-next-location": "Aller à l'emplacement suivant", | ||||
|     "copy-selection-to-internal-clipboard": "Copier la sélection dans le presse-papiers", | ||||
|     "cut-selection-to-internal-clipboard": "Couper la sélection dans le presse-papiers", | ||||
|     "paste-from-internal-clipboard": "Coller depuis le presse-papiers", | ||||
|     "detach-selected-nodes": "Détacher les noeuds sélectionnés", | ||||
|     "delete-selection": "Supprimer la sélection", | ||||
|     "delete-selection-and-reconnect": "Supprimer la sélection et reconnecter", | ||||
|     "edit-selected-node": "Modifier le noeud sélectionné", | ||||
|     "go-to-selection": "Aller à la sélection", | ||||
|     "undo": "Annuler les modifications", | ||||
|     "redo": "Rétablir les modifications", | ||||
|     "select-all-nodes": "Sélectionner tous les noeuds", | ||||
|     "select-none": "Sélectionner un noeud", | ||||
|     "enable-selected-nodes": "Activer les noeuds sélectionnés", | ||||
|     "disable-selected-nodes": "Désactiver les noeuds sélectionnés", | ||||
|     "toggle-show-grid": "Basculer l'affichage de la grille", | ||||
|     "toggle-snap-grid": "Basculer l'aide au placement des noeuds", | ||||
|     "toggle-status": "Commuter l'état", | ||||
|     "show-selected-node-labels": "Afficher les étiquettes des noeuds sélectionnés", | ||||
|     "hide-selected-node-labels": "Masquer les étiquettes des noeuds sélectionnés", | ||||
|     "scroll-view-up": "Faire défiler vers le haut", | ||||
|     "scroll-view-right": "Faire défiler vers la droite", | ||||
|     "scroll-view-down": "Faire défiler vers le bas", | ||||
|     "scroll-view-left": "Faire défiler vers la gauche", | ||||
|     "step-view-up": "Faire défiler d'une unité vers le haut", | ||||
|     "step-view-right": "Faire défiler d'une unité vers la droite", | ||||
|     "step-view-down": "Faire défiler d'une unité vers le bas", | ||||
|     "step-view-left": "Faire défiler d'une unité vers la gauche", | ||||
|     "move-selection-up": "Déplacer la sélection vers le haut", | ||||
|     "move-selection-right": "Déplacer la sélection vers la droite", | ||||
|     "move-selection-down": "Déplacer la sélection vers le bas", | ||||
|     "move-selection-left": "Déplacer la sélection vers la gauche", | ||||
|     "move-selection-forwards": "Avancer la sélection", | ||||
|     "move-selection-backwards": "Reculer la sélection", | ||||
|     "move-selection-to-front": "Déplacer la sélection vers l'avant", | ||||
|     "move-selection-to-back": "Déplacer la sélection vers l'arrière", | ||||
|     "step-selection-up": "Déplacer la sélection d'une unité vers le haut", | ||||
|     "step-selection-right": "Déplacer la sélection d'une unité vers la droite", | ||||
|     "step-selection-down": "Déplacer la sélection d'une unité vers le bas", | ||||
|     "step-selection-left": "Déplacer la sélection d'une unité vers la gauche", | ||||
|     "select-connected-nodes": "Sélectionner les noeuds connectés", | ||||
|     "select-downstream-nodes": "Sélectionner les noeuds connectés en aval", | ||||
|     "select-upstream-nodes": "Sélectionner les noeuds connectés en amont", | ||||
|     "go-to-next-node": "Aller au noeud suivant", | ||||
|     "go-to-previous-node": "Aller au noeud précédent", | ||||
|     "go-to-next-sibling": "Aller au noeud frère suivant", | ||||
|     "go-to-previous-sibling": "Aller au noeud frère précédent", | ||||
|     "go-to-nearest-node-on-left": "Aller au noeud gauche le plus proche", | ||||
|     "go-to-nearest-node-on-right": "Aller au noeud droit le plus proche", | ||||
|     "go-to-nearest-node-above": "Aller au noeud supérieur le plus proche", | ||||
|     "go-to-nearest-node-below": "Aller au noeud le plus proche ci-dessous", | ||||
|     "align-selection-to-grid": "Aligner la sélection", | ||||
|     "align-selection-to-left": "Aligner la sélection à gauche", | ||||
|     "align-selection-to-right": "Aligner la sélection à droite", | ||||
|     "align-selection-to-top": "Aligner la sélection en haut", | ||||
|     "align-selection-to-bottom": "Aligner la sélection vers le bas", | ||||
|     "align-selection-to-middle": "Aligner la sélection au centre verticalement", | ||||
|     "align-selection-to-center": "Aligner la sélection au centre horizontalement", | ||||
|     "distribute-selection-horizontally": "Distribuer la sélection horizontalement", | ||||
|     "distribute-selection-vertical": "Distribuer la sélection verticalement", | ||||
|     "wire-series-of-nodes": "Connecter les noeuds en série", | ||||
|     "wire-node-to-multiple": "Connecter les noeuds à plusieurs", | ||||
|     "wire-multiple-to-node": "Connecter plusieurs au noeud", | ||||
|     "split-wire-with-link-nodes": "Diviser le fil avec des noeuds de liaison", | ||||
|     "generate-node-names": "Générer les noms de noeuds", | ||||
|     "show-user-settings": "Afficher les paramètres utilisateur", | ||||
|     "show-help": "Afficher l'aide", | ||||
|     "toggle-palette": "Basculer l'affichage de la palette", | ||||
|     "show-event-log": "Afficher le journal des événements", | ||||
|     "manage-palette": "Gérer la palette", | ||||
|     "toggle-sidebar": "Basculer l'affichage de la barre latérale", | ||||
|     "show-info-tab": "Afficher l'onglet d'informations sur le noeud", | ||||
|     "show-help-tab": "Afficher l'onglet d'aide du noeud", | ||||
|     "show-config-tab": "Afficher l'onglet du noeud de configuration", | ||||
|     "select-all-config-nodes": "Sélectionner tous les noeuds de configuration", | ||||
|     "delete-config-selection": "Supprimer le noeud de configuration sélectionné", | ||||
|     "show-context-tab": "Afficher l'onglet des données contextuelles", | ||||
|     "create-subflow": "Créer un sous-flux", | ||||
|     "convert-to-subflow": "Convertir la sélection en sous-flux", | ||||
|     "group-selection": "Grouper la sélection", | ||||
|     "ungroup-selection": "Dissocier la sélection", | ||||
|     "merge-selection-to-group": "Fusionner la sélection dans le groupe", | ||||
|     "remove-selection-from-group": "Supprimer la sélection du groupe", | ||||
|     "copy-group-style": "Copier le style du groupe", | ||||
|     "paste-group-style": "Coller le style du groupe", | ||||
|     "show-export-dialog": "Afficher la boîte de dialogue d'exportation", | ||||
|     "show-import-dialog": "Afficher la boîte de dialogue d'importation", | ||||
|     "show-library-export-dialog": "Afficher la boîte de dialogue d'exportation de la bibliothèque", | ||||
|     "show-library-import-dialog": "Afficher la boîte de dialogue d'importation de bibliothèque", | ||||
|     "show-examples-import-dialog": "Afficher la boîte de dialogue d'importation d'exemples", | ||||
|     "search": "Rechercher", | ||||
|     "search-previous": "Recherche précédente", | ||||
|     "search-next": "Recherche suivante", | ||||
|     "show-action-list": "Afficher la liste d'actions", | ||||
|     "confirm-edit-tray": "Confirmer la modification", | ||||
|     "cancel-edit-tray": "Annuler la modification", | ||||
|     "show-remote-diff": "Afficher les différences avec les modifications distantes", | ||||
|     "deploy-flows": "Déployer des flux", | ||||
|     "restart-flows": "Redémarrer les flux", | ||||
|     "set-deploy-type-to-full": "Définir le déploiement sur 'tout'", | ||||
|     "set-deploy-type-to-modified-flows": "Définir le déploiement sur 'flux modifiés'", | ||||
|     "set-deploy-type-to-modified-nodes": "Définir le déploiement sur 'noeuds modifiés'", | ||||
|     "show-debug-tab": "Afficher l'onglet de débogage", | ||||
|     "clear-debug-messages": "Supprimer les messages de débogage", | ||||
|     "clear-filtered-debug-messages": "Supprimer les messages de débogage filtrés", | ||||
|     "activate-selected-debug-nodes": "Activer les noeuds de débogage sélectionnés", | ||||
|     "activate-all-debug-nodes": "Activer tous les noeuds de débogage", | ||||
|     "activate-all-flow-debug-nodes": "Activer tous les noeuds de débogage dans un flux", | ||||
|     "deactivate-selected-debug-nodes": "Désactiver les noeuds de débogage sélectionnés", | ||||
|     "deactivate-all-debug-nodes": "Désactiver tous les noeuds de débogage", | ||||
|     "deactivate-all-flow-debug-nodes": "Désactiver tous les noeuds de débogage dans un flux", | ||||
|     "zoom-in": "Zoomer", | ||||
|     "zoom-out": "Dézoomer", | ||||
|     "zoom-reset": "Réinitialiser le zoom", | ||||
|     "toggle-navigator": "Basculer l'affichage du navigateur", | ||||
|     "show-system-info": "Afficher les informations système", | ||||
|     "split-wires-with-junctions": "Diviser les fils avec des jonctions", | ||||
|     "new-project": "Nouveau projet", | ||||
|     "open-project": "Ouvrir le projet", | ||||
|     "show-project-settings": "Afficher les paramètres du projet", | ||||
|     "show-version-control-tab": "Afficher l'onglet de contrôle de version", | ||||
|     "start-flows": "Démarrer les flux", | ||||
|     "stop-flows": "Arrêter les flux", | ||||
|     "copy-item-url": "Copier l'URL de l'élément", | ||||
|     "copy-item-edit-url": "Copier l'URL de modification de l'élément", | ||||
|     "move-flow-to-start": "Déplacer le flux jusqu'au début", | ||||
|     "move-flow-to-end": "Déplacer le flux jusqu'à la fin", | ||||
|     "show-global-env": "Afficher les variables d'environnement globales", | ||||
|     "lock-flow": "Verrouiller le flux", | ||||
|     "unlock-flow": "Déverrouiller le flux", | ||||
|     "show-node-help": "Afficher l'aide du noeud" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1215,11 +1215,8 @@ | ||||
|     "validator": { | ||||
|         "errors": { | ||||
|             "invalid-json": "JSONデータが不正: __error__", | ||||
|             "invalid-json-prop": "__prop__: JSONデータが不正: __error__", | ||||
|             "invalid-prop": "プロパティ式が不正", | ||||
|             "invalid-prop-prop": "__prop__: プロパティ式が不正", | ||||
|             "invalid-num": "数値が不正", | ||||
|             "invalid-num-prop": "__prop__: 数値が不正", | ||||
|             "invalid-regexp": "入力パターンが不正", | ||||
|             "invalid-regex-prop": "__prop__: 入力パターンが不正", | ||||
|             "missing-required-prop": "__prop__: プロパティが未設定", | ||||
|   | ||||
| @@ -1186,11 +1186,8 @@ | ||||
|     "validator": { | ||||
|         "errors": { | ||||
|             "invalid-json": "Dados JSON inválidos: __error__", | ||||
|             "invalid-json-prop": "__prop__: dados JSON inválidos: __error__", | ||||
|             "invalid-prop": "Expressão de propriedade inválida", | ||||
|             "invalid-prop-prop": "__prop__: expressão de propriedade inválida", | ||||
|             "invalid-num": "Número inválido", | ||||
|             "invalid-num-prop": "__prop__: número inválido", | ||||
|             "invalid-regexp": "Padrão de entrada inválido", | ||||
|             "invalid-regex-prop": "__prop__: Padrão de entrada inválido", | ||||
|             "missing-required-prop": "__prop__: valor de propriedade ausente", | ||||
|   | ||||
| @@ -23,7 +23,11 @@ | ||||
|       "position": "位置", | ||||
|       "enable": "启用", | ||||
|       "disable": "禁用", | ||||
|       "upload": "上传" | ||||
|       "upload": "上传", | ||||
|       "lock": "锁定", | ||||
|       "unlock": "解锁", | ||||
|       "locked": "锁定", | ||||
|       "unlocked": "解锁" | ||||
|     }, | ||||
|     "type": { | ||||
|       "string": "字符串", | ||||
| @@ -68,7 +72,13 @@ | ||||
|     "enabled": "有效", | ||||
|     "disabled": "无效", | ||||
|     "info": "详细描述", | ||||
|     "selectNodes": "点击节点来选择" | ||||
|     "selectNodes": "点击节点来选择", | ||||
|     "enableFlow": "启用流程", | ||||
|     "disableFlow": "禁用流程", | ||||
|     "lockFlow": "锁定流程", | ||||
|     "unlockFlow": "解除锁定", | ||||
|     "moveToStart": "移动到起始", | ||||
|     "moveToEnd": "移动到末尾" | ||||
|   }, | ||||
|   "menu": { | ||||
|     "label": { | ||||
| @@ -101,6 +111,7 @@ | ||||
|       "displayStatus": "显示节点状态", | ||||
|       "displayConfig": "修改节点配置", | ||||
|       "import": "导入", | ||||
|       "importExample": "导入示例流程", | ||||
|       "export": "导出", | ||||
|       "search": "查找流程", | ||||
|       "searchInput": "查找流程", | ||||
| @@ -142,7 +153,12 @@ | ||||
|       "moveToBack": "置于底层", | ||||
|       "moveToFront": "置于顶层", | ||||
|       "moveBackwards": "向后移动", | ||||
|       "moveForwards": "向前移动" | ||||
|       "moveForwards": "向前移动", | ||||
|       "showNodeHelp":"显示节点帮助", | ||||
|       "enableSelectedNodes":"启用当前选中节点", | ||||
|       "disableSelectedNodes":"禁用当前选中节点", | ||||
|       "showSelectedNodeLabels":"显示选中的节点标签", | ||||
|       "hideSelectedNodeLabels":"隐藏选中的节点标签" | ||||
|     } | ||||
|   }, | ||||
|   "actions": { | ||||
| @@ -403,6 +419,7 @@ | ||||
|     }, | ||||
|     "errors": { | ||||
|       "noNodesSelected": "<strong>无法创建子流程</strong>: 未选择节点", | ||||
|       "acrossMultipleGroups": "无法跨多个组创建子流", | ||||
|       "multipleInputsToSelection": "<strong>无法创建子流程</strong>: 多个输入到了选择" | ||||
|     } | ||||
|   }, | ||||
| @@ -491,12 +508,14 @@ | ||||
|     "unassigned": "未分配", | ||||
|     "global": "全局", | ||||
|     "workspace": "工作区", | ||||
|     "editor": "编辑对话框", | ||||
|     "selectAll": "选择所有节点", | ||||
|     "selectNone": "取消所有选择", | ||||
|     "selectAllConnected": "选择所有连接的节点", | ||||
|     "addRemoveNode": "从选择中添加/删除节点", | ||||
|     "editSelected": "编辑选定节点", | ||||
|     "deleteSelected": "删除选定节点或链接", | ||||
|     "deleteReconnect": "删除并重新连接", | ||||
|     "importNode": "导入节点", | ||||
|     "exportNode": "导出节点", | ||||
|     "nudgeNode": "移动所选节点(1px)", | ||||
| @@ -571,6 +590,7 @@ | ||||
|     "editor": { | ||||
|       "title": "面板管理", | ||||
|       "palette": "控制板", | ||||
|       "allCatalogs": "所有目录", | ||||
|       "times": { | ||||
|         "seconds": "秒前", | ||||
|         "minutes": "分前", | ||||
| @@ -610,6 +630,7 @@ | ||||
|       "tab-nodes": "节点", | ||||
|       "tab-install": "安装", | ||||
|       "sort": "排序:", | ||||
|       "sortRelevance": "关联", | ||||
|       "sortAZ": "a-z顺序", | ||||
|       "sortRecent": "日期顺序", | ||||
|       "more": "增加 __count__ 个", | ||||
| @@ -683,7 +704,11 @@ | ||||
|       "empty": "空的", | ||||
|       "globalConfig": "全局配置节点", | ||||
|       "triggerAction": "触发动作", | ||||
|       "find": "在工作区中查找" | ||||
|       "find": "在工作区中查找", | ||||
|       "copyItemUrl": "复制地址", | ||||
| 	    "copyURL2Clipboard": "复制地址到剪贴板", | ||||
|       "showFlow": "显示流程", | ||||
|       "hideFlow": "隐藏流程" | ||||
|     }, | ||||
|     "help": { | ||||
|       "name": "帮助", | ||||
| @@ -984,7 +1009,10 @@ | ||||
|     "quote": "引用", | ||||
|     "link": "链接", | ||||
|     "horizontal-rule": "水平线", | ||||
|     "toggle-preview": "切换预览" | ||||
|     "toggle-preview": "切换预览", | ||||
|     "mermaid": { | ||||
|       "summary": "美人鱼图" | ||||
|     } | ||||
|   }, | ||||
|   "bufferEditor": { | ||||
|     "title": "Buffer 编辑器", | ||||
| @@ -1147,17 +1175,6 @@ | ||||
|       "create": "创建分支", | ||||
|       "current": "当前的" | ||||
|     }, | ||||
|     "languages": { | ||||
|         "de": "德语", | ||||
|         "en-US": "英文", | ||||
|         "fr": "法语", | ||||
|         "ja": "日语", | ||||
|         "ko": "韩文", | ||||
|         "pt-BR":"葡萄牙语", | ||||
|         "ru":"俄語", | ||||
|         "zh-CN": "简体中文", | ||||
|         "zh-TW": "繁体中文" | ||||
|     }, | ||||
|     "create-default-file-set": { | ||||
|       "no-active": "没有活动项目就无法创建默认文件集", | ||||
|       "no-empty": "无法在非空项目上创建默认文件集", | ||||
| @@ -1188,22 +1205,22 @@ | ||||
|     "title": "系统信息" | ||||
|   }, | ||||
|   "languages": { | ||||
|     "de": "德语-Deutsch", | ||||
|     "en-US": "英文-English", | ||||
|     "ja": "日语-日本", | ||||
|     "ko": "韩文-한국인", | ||||
|     "ru": "俄语-Русский", | ||||
|     "de": "德语", | ||||
|     "en-US": "英文", | ||||
|     "fr": "法语", | ||||
|     "ja": "日语", | ||||
|     "ko": "韩文", | ||||
|     "pt-BR":"葡萄牙语", | ||||
|     "ru":"俄語", | ||||
|     "zh-CN": "简体中文", | ||||
|     "zh-TW": "繁體中文" | ||||
|     "zh-TW": "繁体中文" | ||||
|   }, | ||||
|   "validator": { | ||||
|     "errors": { | ||||
|       "invalid-json": "无效的 JSON 数据: __error__", | ||||
|       "invalid-json-prop": "__prop__: 无效的 JSON 数据: __error__", | ||||
|       "invalid-expr": "无效的 JSONata 表达式: __error__", | ||||
|       "invalid-prop": "无效的属性表达式", | ||||
|       "invalid-prop-prop": "__prop__: 无效的属性表达式", | ||||
|       "invalid-num": "无效的数字", | ||||
|       "invalid-num-prop": "__prop__: 无效的数字", | ||||
|       "invalid-regexp": "输入格式无效", | ||||
|       "invalid-regex-prop": "__prop__: 输入格式无效", | ||||
|       "missing-required-prop": "__prop__: 缺少属性值", | ||||
| @@ -1213,9 +1230,15 @@ | ||||
|     } | ||||
|   }, | ||||
|   "contextMenu": { | ||||
|     "showActionList":"显示动作列表", | ||||
|     "insert": "插入", | ||||
|     "node": "节点", | ||||
|     "junction": "连接点", | ||||
|     "linkNodes": "链接节点" | ||||
|   }, | ||||
|   "env-var": { | ||||
|     "environment": "环境配置", | ||||
|     "header": "全局环境变量", | ||||
|     "revert": "重置" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -270,5 +270,9 @@ | ||||
|     "$moment": { | ||||
|         "args": "[str]", | ||||
|         "desc": "使用Moment库获取日期对象。" | ||||
|     }, | ||||
|     "$clone": { | ||||
|         "args": "value", | ||||
|         "desc": "安全克隆对象." | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -23,7 +23,11 @@ | ||||
|             "position": "位置", | ||||
|             "enable": "啟用", | ||||
|             "disable": "禁用", | ||||
|             "upload": "上傳" | ||||
|             "upload": "上傳", | ||||
|             "lock": "鎖定", | ||||
|             "unlock": "解鎖", | ||||
|             "locked": "鎖定", | ||||
|             "unlocked": "解鎖" | ||||
|         }, | ||||
|         "type": { | ||||
|             "string": "字符串", | ||||
| @@ -38,11 +42,14 @@ | ||||
|         } | ||||
|     }, | ||||
|     "event": { | ||||
|         "loadPlugins": "加載插件", | ||||
|         "loadPalette": "加載控制板", | ||||
|         "loadNodeCatalogs": "加載節點目錄", | ||||
|         "loadNodes": "加載 __count__ 個節點", | ||||
|         "loadFlows": "加載流程", | ||||
|         "importFlows": "往工作區中加載流程" | ||||
|         "importFlows": "往工作區中加載流程", | ||||
|         "importError": "<p>加載流程錯誤</p><p>__message__</p>", | ||||
|         "loadingProject": "加載項目" | ||||
|     }, | ||||
|     "workspace": { | ||||
|         "defaultName": "流程__number__", | ||||
| @@ -51,18 +58,35 @@ | ||||
|         "delete": "確定想要刪除 '__label__'?", | ||||
|         "dropFlowHere": "把流程放到這裡", | ||||
|         "addFlow": "新增流程", | ||||
|         "listFlows": "流程列表", | ||||
|         "addFlowToRight": "在右側新增流程", | ||||
|         "hideFlow": "隱藏流程", | ||||
|         "hideOtherFlows": "隱藏其它流程", | ||||
|         "showAllFlows": "顯示所有流程", | ||||
|         "hideAllFlows": "隱藏所有流程", | ||||
|         "hiddenFlows": "列出 __count__ 個隱藏流程", | ||||
|         "hiddenFlows_plural": "列出 __count__ 個隱藏流程", | ||||
|         "showLastHiddenFlow": "顯示最後一個隱藏流程", | ||||
|         " ": "流程列表", | ||||
|         "listSubflows": "列出子流程", | ||||
|         "status": "狀態", | ||||
|         "enabled": "有效", | ||||
|         "disabled": "無效", | ||||
|         "info": "詳細描述", | ||||
|         "selectNodes": "點擊節點用於選擇" | ||||
|         "selectNodes": "點擊節點用於選擇", | ||||
|         "enableFlow": "啟用流程", | ||||
|         "disableFlow": "禁用流程", | ||||
|         "lockFlow": "鎖定流程", | ||||
|         "unlockFlow": "解除鎖定", | ||||
|         "moveToStart": "移動到起始", | ||||
|         "moveToEnd": "移動到末尾" | ||||
|     }, | ||||
|     "menu": { | ||||
|         "label": { | ||||
|             "view": { | ||||
|                 "view": "顯示", | ||||
|                 "grid": "格線", | ||||
|                 "storeZoom": "加載時還原縮放尺寸", | ||||
|                 "storePosition": "加載時還原滾動位置", | ||||
|                 "showGrid": "顯示格線", | ||||
|                 "snapGrid": "對齊格線", | ||||
|                 "gridSize": "格線尺寸", | ||||
| @@ -80,12 +104,14 @@ | ||||
|             "palette": { | ||||
|                 "show": "顯示控制板" | ||||
|             }, | ||||
|             "edit": "編輯", | ||||
|             "settings": "設置", | ||||
|             "userSettings": "使用者設置", | ||||
|             "nodes": "節點", | ||||
|             "displayStatus": "顯示節點狀態", | ||||
|             "displayConfig": "修改節點配置", | ||||
|             "import": "匯入", | ||||
|             "importExample": "導入示例流程", | ||||
|             "export": "匯出", | ||||
|             "search": "搜尋流程", | ||||
|             "searchInput": "搜尋流程", | ||||
| @@ -102,24 +128,48 @@ | ||||
|             "editPalette": "節點管理", | ||||
|             "other": "其他", | ||||
|             "showTips": "顯示小提示", | ||||
|             "help": "Node-RED website", | ||||
|             "showWelcomeTours": "顯示新版本向導", | ||||
|             "help": "Node-RED 文檔主頁", | ||||
|             "projects": "專案", | ||||
|             "projects-new": "新專案", | ||||
|             "projects-open": "開啟專案", | ||||
|             "projects-settings": "專案設定", | ||||
|             "showNodeLabelDefault": "顯示新添加節點的標籤", | ||||
|             "codeEditor": "代碼編輯器", | ||||
|             "groups": "組", | ||||
|             "groupSelection": "選擇組", | ||||
|             "ungroupSelection": "取消選擇組", | ||||
|             "groupMergeSelection": "合并選擇", | ||||
|             "groupRemoveSelection": "從組中移除" | ||||
|             "groupRemoveSelection": "從組中移除", | ||||
|             "arrange": "布局", | ||||
|             "alignLeft": "左對齊", | ||||
|             "alignCenter": "居中對齊", | ||||
|             "alignRight": "右對齊", | ||||
|             "alignTop": "頂部對齊", | ||||
|             "alignMiddle": "垂直居中對齊", | ||||
|             "alignBottom": "底部對齊", | ||||
|             "distributeHorizontally": "横向分布", | ||||
|             "distributeVertically": "垂直分布", | ||||
|             "moveToBack": "置於底層", | ||||
|             "moveToFront": "置於頂層", | ||||
|             "moveBackwards": "向後移動", | ||||
|             "moveForwards": "向前移動", | ||||
|             "showNodeHelp":"顯示節點幫助", | ||||
|             "enableSelectedNodes":"啟用當前選中節點", | ||||
|             "disableSelectedNodes":"禁用當前選中節點", | ||||
|             "showSelectedNodeLabels":"顯示選中的節點標簽", | ||||
|             "hideSelectedNodeLabels":"隱藏選中的節點標簽" | ||||
|         } | ||||
|     }, | ||||
|     "actions": { | ||||
|         "toggle-navigator": "切換導航器", | ||||
|         "zoom-out": "縮小", | ||||
|         "zoom-reset": "重置縮放", | ||||
|         "zoom-in": "放大" | ||||
|         "zoom-in": "放大", | ||||
|         "search-flows": "搜索流程", | ||||
|         "search-prev": "上一個", | ||||
|         "search-next": "下一個", | ||||
|         "search-counter": "\"__term__\" __result__ of __count__" | ||||
|     }, | ||||
|     "user": { | ||||
|         "loggedInAs": "作為 __name__ 登入", | ||||
| @@ -135,12 +185,17 @@ | ||||
|         } | ||||
|     }, | ||||
|     "notification": { | ||||
|         "state": { | ||||
|           "flowsStopped": "流程已停止", | ||||
|           "flowsStarted": "流程已啟動" | ||||
|         }, | ||||
|         "warning": "<strong>警告</strong>: __message__", | ||||
|         "warnings": { | ||||
|             "undeployedChanges": "節點中存在未部署的更改", | ||||
|             "nodeActionDisabled": "節點動作在子流程中被禁用", | ||||
|             "nodeActionDisabledSubflow": "子流程中禁用了節點操作", | ||||
|             "missing-types": "流程由於缺少節點類型而停止。請檢查日誌的詳細資訊", | ||||
|             "missing-modules": "<p>流程因缺少模塊而停止。</p>", | ||||
|             "safe-mode": "<p>流程在安全模式下停止。</p><p>您可以修改流程並部署更改以重新啟動。</p>", | ||||
|             "restartRequired": "Node-RED必須重新啟動,以啟用升級的模組", | ||||
|             "credentials_load_failed": "<p>流程由於無法解密證書而停止。</p> <p>流程證書文件已加密,但是項目的加密密鑰丟失或無效。</p>", | ||||
| @@ -151,7 +206,7 @@ | ||||
|             "project_not_found": "<p>找不到項目的'__project__'</p>", | ||||
|             "git_merge_conflict": "<p>自動合併更改失敗。</p><p>修復未合併的衝突,然後提交結果。</p>" | ||||
|         }, | ||||
|         "error": "<strong>Error</strong>: __message__", | ||||
|         "error": "<strong>錯誤</strong>: __message__", | ||||
|         "errors": { | ||||
|             "lostConnection": "丟失與伺服器的連接,重新連接...", | ||||
|             "lostConnectionReconnect": "丟失與伺服器的連接,__time__ 秒後重新連接", | ||||
| @@ -208,6 +263,8 @@ | ||||
|         "download": "下載", | ||||
|         "importUnrecognised": "匯入了無法識別的類型:", | ||||
|         "importUnrecognised_plural": "匯入了無法識別的類型:", | ||||
|         "importDuplicate": "導入了重復節點:", | ||||
|         "importDuplicate_plural": "導入了重復節點:", | ||||
|         "nodesExported": "節點匯出到了剪貼簿", | ||||
|         "nodesImported": "已匯入:", | ||||
|         "nodeCopied": "已複製 __count__ 個節點", | ||||
| @@ -259,6 +316,10 @@ | ||||
|         "modifiedFlowsDesc": "只部署包含已更改節點的流程", | ||||
|         "modifiedNodes": "已更改的節點", | ||||
|         "modifiedNodesDesc": "只部署已經更改的節點", | ||||
|         "startFlows": "啟動", | ||||
|         "startFlowsDesc": "啟動流程", | ||||
|         "stopFlows": "停止", | ||||
|         "stopFlowsDesc": "停止流程", | ||||
|         "restartFlows": "重新啟動流程", | ||||
|         "restartFlowsDesc": "重新啟動當前部署的流程", | ||||
|         "successfulDeploy": "部署成功", | ||||
| @@ -337,14 +398,28 @@ | ||||
|         "output": "輸出:", | ||||
|         "status": "狀態節點", | ||||
|         "deleteSubflow": "刪除子流程", | ||||
|         "confirmDelete": "您確定要刪除此子流程?", | ||||
|         "info": "詳細描述", | ||||
|         "category": "類別", | ||||
|         "module": "模塊", | ||||
|         "license": "許可", | ||||
|         "licenseNone": "無", | ||||
|         "licenseOther": "其它", | ||||
|         "type": "節點類型", | ||||
|         "version": "版本", | ||||
|         "versionPlaceholder": "x.y.z", | ||||
|         "keys": "關鍵字", | ||||
|         "keysPlaceholder": "使用英文逗號分隔關鍵字", | ||||
|         "author": "作者", | ||||
|         "authorPlaceholder": "名字 <email@example.com>", | ||||
|         "desc": "描述", | ||||
|         "env": { | ||||
|             "restore": "恢復為默認子流程", | ||||
|             "remove": "類別刪除環境變量" | ||||
|         }, | ||||
|         "errors": { | ||||
|             "noNodesSelected": "<strong>無法創建子流程</strong>: 未選擇節點", | ||||
|             "acrossMultipleGroups": "無法跨多個組創建子流", | ||||
|             "multipleInputsToSelection": "<strong>無法創建子流程</strong>: 多個輸入到了選擇" | ||||
|         } | ||||
|     }, | ||||
| @@ -367,12 +442,12 @@ | ||||
|         "editConfig": "編輯 __type__ 配置", | ||||
|         "addNewType": "添加新的 __type__ 節點", | ||||
|         "nodeProperties": "節點屬性", | ||||
|         "label": "Label", | ||||
|         "label": "標簽", | ||||
|         "color": "顏色", | ||||
|         "portLabels": "埠標籤", | ||||
|         "labelInputs": "輸入", | ||||
|         "labelOutputs": "輸出", | ||||
|         "settingIcon": "Icon", | ||||
|         "settingIcon": "圖標", | ||||
|         "default": "默認", | ||||
|         "noDefaultLabel": "無", | ||||
|         "defaultLabel": "使用默認標籤", | ||||
| @@ -385,6 +460,7 @@ | ||||
|         "icon": "圖標", | ||||
|         "inputType": "輸入類型", | ||||
|         "selectType": "選擇類型...", | ||||
|         "loadCredentials": "加載節點憑證", | ||||
|         "inputs": { | ||||
|             "input": "輸入", | ||||
|             "select": "選擇", | ||||
| @@ -419,7 +495,8 @@ | ||||
|         }, | ||||
|         "errors": { | ||||
|             "scopeChange": "更改範圍將使其他流程中的節點無法使用", | ||||
|             "invalidProperties": "無效的屬性:" | ||||
|             "invalidProperties": "無效的屬性:", | ||||
|             "credentialLoadFailed": "無法加載節點憑據" | ||||
|         } | ||||
|     }, | ||||
|     "keyboard": { | ||||
| @@ -431,11 +508,14 @@ | ||||
|         "unassigned": "未分配", | ||||
|         "global": "全局", | ||||
|         "workspace": "工作區", | ||||
|         "editor": "編輯對話框", | ||||
|         "selectAll": "選擇所有節點", | ||||
|         "selectNone": "取消所有選擇", | ||||
|         "selectAllConnected": "選擇所有連接的節點", | ||||
|         "addRemoveNode": "從選擇中添加/刪除節點", | ||||
|         "editSelected": "編輯選定節點", | ||||
|         "deleteSelected": "刪除選定節點或連結", | ||||
|         "deleteReconnect": "刪除並重新連接", | ||||
|         "importNode": "匯入節點", | ||||
|         "exportNode": "匯出節點", | ||||
|         "nudgeNode": "移動所選節點(1px)", | ||||
| @@ -445,10 +525,14 @@ | ||||
|         "copyNode": "複製所選節點", | ||||
|         "cutNode": "剪切所選節點", | ||||
|         "pasteNode": "粘貼節點", | ||||
|         "copyGroupStyle": "復製組樣式", | ||||
|         "pasteGroupStyle": "粘貼組樣式", | ||||
|         "undoChange": "撤銷上次執行的更改", | ||||
|         "redoChange": "重做", | ||||
|         "searchBox": "打開搜尋框", | ||||
|         "managePalette": "管理面板", | ||||
|         "actionList": "動作列表" | ||||
|         "actionList": "動作列表", | ||||
|         "splitWireWithLinks": "使用Link節點拆分已選項" | ||||
|     }, | ||||
|     "library": { | ||||
|         "library": "庫", | ||||
| @@ -466,12 +550,11 @@ | ||||
|         "types": { | ||||
|             "local": "本地", | ||||
|             "examples": "例子" | ||||
|         }, | ||||
|         "exportToLibrary": "將節點匯出到庫" | ||||
|         } | ||||
|     }, | ||||
|     "palette": { | ||||
|         "noInfo": "無可用資訊", | ||||
|         "filter": "過濾節點", | ||||
|         "filter": "過濾已安裝模組", | ||||
|         "search": "搜尋模組", | ||||
|         "addCategory": "添加新的...", | ||||
|         "label": { | ||||
| @@ -501,11 +584,13 @@ | ||||
|             "nodeEnabled_plural": "啟用多個節點:", | ||||
|             "nodeDisabled": "禁用節點:", | ||||
|             "nodeDisabled_plural": "禁用多個節點:", | ||||
|             "nodeUpgraded": "節點模組__module__升級到__version__版本" | ||||
|             "nodeUpgraded": "節點模組__module__升級到__version__版本", | ||||
|             "unknownNodeRegistered": "加載節點錯誤: <ul><li>__type__<br>__error__</li></ul>" | ||||
|         }, | ||||
|         "editor": { | ||||
|             "title": "面板管理", | ||||
|             "palette": "Palette", | ||||
|             "palette": "控製板", | ||||
|             "allCatalogs": "所有目錄", | ||||
|             "times": { | ||||
|                 "seconds": "秒前", | ||||
|                 "minutes": "分前", | ||||
| @@ -545,10 +630,12 @@ | ||||
|             "tab-nodes": "節點", | ||||
|             "tab-install": "安裝", | ||||
|             "sort": "排序:", | ||||
|             "sortRelevance": "關聯", | ||||
|             "sortAZ": "a-z順序", | ||||
|             "sortRecent": "日期順序", | ||||
|             "more": "增加 __count__ 個", | ||||
|             "upload": "上傳模塊tgz文件", | ||||
|             "refresh": "更新模塊列表", | ||||
|             "errors": { | ||||
|                 "catalogLoadFailed": "無法載入節點目錄。<br>查看瀏覽器控制臺瞭解更多資訊", | ||||
|                 "installFailed": "無法安裝: __module__<br>__message__<br>查看日誌瞭解更多資訊", | ||||
| @@ -617,7 +704,11 @@ | ||||
|             "empty": "空的", | ||||
|             "globalConfig": "全局配置節點", | ||||
|             "triggerAction": "觸發動作", | ||||
|             "find": "在工作區中查找" | ||||
|             "find": "在工作區中查找", | ||||
|             "copyItemUrl": "復製地址", | ||||
|             "copyURL2Clipboard": "復製地址到剪貼板", | ||||
|             "showFlow": "顯示流程", | ||||
|             "hideFlow": "隱藏流程" | ||||
|         }, | ||||
|         "help": { | ||||
|             "name": "幫助", | ||||
| @@ -627,7 +718,8 @@ | ||||
|             "showHelp": "顯示幫助", | ||||
|             "showInOutline": "在大綱中顯示", | ||||
|             "showTopics": "顯示主題", | ||||
|             "noHelp": "未選擇幫助主題" | ||||
|             "noHelp": "未選擇幫助主題", | ||||
|             "changeLog": "更新日誌" | ||||
|         }, | ||||
|         "config": { | ||||
|             "name": "配置節點", | ||||
| @@ -828,31 +920,37 @@ | ||||
|             "json": "JSON", | ||||
|             "bin": "二進位流", | ||||
|             "date": "時間戳記", | ||||
|             "jsonata": "expression", | ||||
|             "env": "env variable", | ||||
|             "jsonata": "表達式", | ||||
|             "env": "環境變量", | ||||
|             "cred": "證書" | ||||
|         } | ||||
|     }, | ||||
|     "editableList": { | ||||
|         "add": "添加" | ||||
|         "add": "添加", | ||||
|         "addTitle": "添加項" | ||||
|     }, | ||||
|     "search": { | ||||
|         "empty": "找不到匹配", | ||||
|         "history": "搜索歷史", | ||||
|         "clear": "清除所有", | ||||
|         "empty": "找不到匹配項", | ||||
|         "addNode": "添加一個節點...", | ||||
|         "options": { | ||||
|             "configNodes": "配置節點", | ||||
|             "unusedConfigNodes": "未使用的配置節點", | ||||
|             "invalidNodes": "無效的節點", | ||||
|             "uknownNodes": "未知的節點", | ||||
|             "unusedSubflows": "未使用的子流程" | ||||
|             "unusedSubflows": "未使用的子流程", | ||||
|             "hiddenFlows": "隱藏的流程", | ||||
|             "modifiedNodes": "已修改的節點或流程", | ||||
|             "thisFlow": "當前流程" | ||||
|         } | ||||
|     }, | ||||
|     "expressionEditor": { | ||||
|         "functions": "功能", | ||||
|         "functionReference": "Function reference", | ||||
|         "functionReference": "功能參考", | ||||
|         "insert": "插入", | ||||
|         "title": "JSONata運算式編輯器", | ||||
|         "test": "Test", | ||||
|         "test": "測試", | ||||
|         "data": "示例消息", | ||||
|         "result": "結果", | ||||
|         "format": "格式表達方法", | ||||
| @@ -863,20 +961,28 @@ | ||||
|             "invalid-expr": "無效的JSONata運算式:\n  __message__", | ||||
|             "invalid-msg": "無效的示例JSON消息:\n  __message__", | ||||
|             "context-unsupported": "無法測試上下文函數\n $flowContext 或 $globalContext", | ||||
|             "env-unsupported": "無法測試 $env 函數", | ||||
|             "moment-unsupported": "無法測試 $moment 函數", | ||||
|             "clone-unsupported": "無法測試 $clone 函數", | ||||
|             "eval": "評估運算式錯誤:\n  __message__" | ||||
|         } | ||||
|     }, | ||||
|     "monaco": { | ||||
|       "setTheme": "設置主題" | ||||
|     }, | ||||
|     "jsEditor": { | ||||
|         "title": "JavaScript 編輯器" | ||||
|     }, | ||||
|     "textEditor": { | ||||
|         "title": "Text 編輯器" | ||||
|         "title": "文本編輯器" | ||||
|     }, | ||||
|     "jsonEditor": { | ||||
|         "title": "JSON編輯器", | ||||
|         "format": "格式化JSON", | ||||
|         "rawMode": "編輯 JSON", | ||||
|         "uiMode": "Visual編輯器", | ||||
|         "uiMode": "可視化編輯器", | ||||
|         "rawMode-readonly": "原始JSON", | ||||
|         "uiMode-readonly": "可視化", | ||||
|         "insertAbove": "在上方插入", | ||||
|         "insertBelow": "在下方插入", | ||||
|         "addItem": "添加項目", | ||||
| @@ -892,9 +998,9 @@ | ||||
|         "title": "Markdown 編輯器", | ||||
|         "expand": "展開", | ||||
|         "format": "F使用markdown格式化", | ||||
|         "heading1": "Heading 1", | ||||
|         "heading2": "Heading 2", | ||||
|         "heading3": "Heading 3", | ||||
|         "heading1": "標題 1", | ||||
|         "heading2": "標題 2", | ||||
|         "heading3": "標題 3", | ||||
|         "bold": "粗體", | ||||
|         "italic": "斜體", | ||||
|         "code": "程式碼", | ||||
| @@ -903,7 +1009,10 @@ | ||||
|         "quote": "引用", | ||||
|         "link": "連結", | ||||
|         "horizontal-rule": "分隔線", | ||||
|         "toggle-preview": "預覽" | ||||
|         "toggle-preview": "切換預覽", | ||||
|         "mermaid": { | ||||
|           "summary": "美人魚圖" | ||||
|         } | ||||
|     }, | ||||
|     "bufferEditor": { | ||||
|         "title": "緩衝區編輯器", | ||||
| @@ -1038,7 +1147,8 @@ | ||||
|             "not-git": "不是git倉庫", | ||||
|             "no-resource": "找不到存儲庫", | ||||
|             "cant-get-ssh-key-path": "錯誤! 無法獲取所選的SSH密鑰路徑。", | ||||
|             "unexpected_error": "意外的錯誤" | ||||
|             "unexpected_error": "意外的錯誤", | ||||
|             "clearContext": "更改項目時清除上下文" | ||||
|         }, | ||||
|         "delete": { | ||||
|             "confirm": "您確定要刪除此項目嗎?" | ||||
| @@ -1068,7 +1178,7 @@ | ||||
|         "create-default-file-set": { | ||||
|             "no-active": "沒有活動項目就無法創建默認文件集", | ||||
|             "no-empty": "無法在非空項目上創建默認文件集", | ||||
|             "git-error": "git error" | ||||
|             "git-error": "git錯誤" | ||||
|         }, | ||||
|         "errors": { | ||||
|             "no-username-email": "您的Git客戶端未配置用戶名/電子郵件。", | ||||
| @@ -1079,11 +1189,20 @@ | ||||
|     "editor-tab": { | ||||
|         "properties": "屬性", | ||||
|         "envProperties": "環境變量", | ||||
|         "module": "模塊屬性", | ||||
|         "description": "描述", | ||||
|         "appearance": "外觀", | ||||
|         "preview": "UI預覽", | ||||
|         "defaultValue": "默認值", | ||||
|         "env": "環境變量" | ||||
|         "defaultValue": "默認值" | ||||
|     }, | ||||
|     "tourGuide": { | ||||
|       "takeATour": "查看更新內容", | ||||
|       "start": "開始", | ||||
|       "next": "下一個", | ||||
|       "welcomeTours": "歡迎使用 Node-RED" | ||||
|     }, | ||||
|     "diagnostics": { | ||||
|       "title": "系统信息" | ||||
|     }, | ||||
|     "languages": { | ||||
|         "de": "德語", | ||||
| @@ -1095,5 +1214,31 @@ | ||||
|         "ru":"俄語", | ||||
|         "zh-CN": "簡體中文", | ||||
|         "zh-TW": "繁體中文" | ||||
|     }, | ||||
|     "validator": { | ||||
|       "errors": { | ||||
|         "invalid-json": "無效的 JSON 數據: __error__", | ||||
|         "invalid-expr": "無效的 JSONata 表達式: __error__", | ||||
|         "invalid-prop": "無效的屬性表達式", | ||||
|         "invalid-num": "無效的數字", | ||||
|         "invalid-regexp": "輸入格式無效", | ||||
|         "invalid-regex-prop": "__prop__: 輸入格式無效", | ||||
|         "missing-required-prop": "__prop__: 缺少屬性值", | ||||
|         "invalid-config": "__prop__: 無效的配置節點", | ||||
|         "missing-config": "__prop__: 缺少配置節點", | ||||
|         "validation-error": "__prop__: 驗證錯誤: __node__, __id__: __error__" | ||||
|       } | ||||
|     }, | ||||
|     "contextMenu": { | ||||
|       "showActionList":"顯示動作列表", | ||||
|       "insert": "插入", | ||||
|       "node": "節點", | ||||
|       "junction": "連接點", | ||||
|       "linkNodes": "鏈接節點" | ||||
|     }, | ||||
|     "env-var": { | ||||
|       "environment": "環境配置", | ||||
|       "header": "全局環境變量", | ||||
|       "revert": "重置" | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -270,5 +270,9 @@ | ||||
|     "$moment": { | ||||
|         "args": "[str]", | ||||
|         "desc": "使用Moment庫獲取日期對象。" | ||||
|     }, | ||||
|     "$clone": { | ||||
|       "args": "value", | ||||
|       "desc": "安全克隆對象." | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/editor-client", | ||||
|     "version": "3.1.0", | ||||
|     "version": "3.1.2", | ||||
|     "license": "Apache-2.0", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|   | ||||
| @@ -2198,6 +2198,12 @@ RED.nodes = (function() { | ||||
|                         } | ||||
|                         node._config.x = node.x; | ||||
|                         node._config.y = node.y; | ||||
|                         if (n.hasOwnProperty('w')) { | ||||
|                             node.w = n.w | ||||
|                         } | ||||
|                         if (n.hasOwnProperty('h')) { | ||||
|                             node.h = n.h | ||||
|                         } | ||||
|                     } else if (n.type.substring(0,7) === "subflow") { | ||||
|                         var parentId = n.type.split(":")[1]; | ||||
|                         var subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId); | ||||
|   | ||||
| @@ -498,6 +498,15 @@ var RED = (function() { | ||||
|                             ] | ||||
|                         } | ||||
|                     } | ||||
|                 } else if (notificationId === 'restart-required') { | ||||
|                     options.buttons = [ | ||||
|                         { | ||||
|                             text: RED._("common.label.close"), | ||||
|                             click: function() { | ||||
|                                 persistentNotifications[notificationId].hideNotification(); | ||||
|                             } | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|                 if (!persistentNotifications.hasOwnProperty(notificationId)) { | ||||
|                     persistentNotifications[notificationId] = RED.notify(text,options); | ||||
| @@ -525,6 +534,10 @@ var RED = (function() { | ||||
|                 RED.view.redrawStatus(node); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         let pendingNodeRemovedNotifications = [] | ||||
|         let pendingNodeRemovedTimeout | ||||
|  | ||||
|         RED.comms.subscribe("notification/node/#",function(topic,msg) { | ||||
|             var i,m; | ||||
|             var typeList; | ||||
| @@ -562,8 +575,15 @@ var RED = (function() { | ||||
|                     m = msg[i]; | ||||
|                     info = RED.nodes.removeNodeSet(m.id); | ||||
|                     if (info.added) { | ||||
|                         typeList = "<ul><li>"+m.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>"; | ||||
|                         RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success"); | ||||
|                         pendingNodeRemovedNotifications = pendingNodeRemovedNotifications.concat(m.types.map(RED.utils.sanitize)) | ||||
|                         if (pendingNodeRemovedTimeout) { | ||||
|                             clearTimeout(pendingNodeRemovedTimeout) | ||||
|                         } | ||||
|                         pendingNodeRemovedTimeout = setTimeout(function () { | ||||
|                             typeList = "<ul><li>"+pendingNodeRemovedNotifications.join("</li><li>")+"</li></ul>"; | ||||
|                             RED.notify(RED._("palette.event.nodeRemoved", {count:pendingNodeRemovedNotifications.length})+typeList,"success"); | ||||
|                             pendingNodeRemovedNotifications = [] | ||||
|                         }, 200) | ||||
|                     } | ||||
|                 } | ||||
|                 loadIconList(); | ||||
|   | ||||
| @@ -182,7 +182,9 @@ | ||||
|             valueLabel: contextLabel | ||||
|         }, | ||||
|         str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"}, | ||||
|         num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/}, | ||||
|         num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate: function(v) { | ||||
|             return (true === RED.utils.validateTypedProperty(v, "num")); | ||||
|         } }, | ||||
|         bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.svg",options:["true","false"]}, | ||||
|         json: { | ||||
|             value:"json", | ||||
|   | ||||
| @@ -44,7 +44,7 @@ RED.contextMenu = (function () { | ||||
|             } | ||||
|  | ||||
|             menuItems.push( | ||||
|                 { onselect: 'core:show-action-list', onpostselect: function () { } } | ||||
|                 { onselect: 'core:show-action-list', label: RED._("contextMenu.showActionList"), onpostselect: function () { } } | ||||
|             ) | ||||
|  | ||||
|             const insertOptions = [] | ||||
| @@ -108,16 +108,16 @@ RED.contextMenu = (function () { | ||||
|                 const nodeOptions = [] | ||||
|                 if (!hasMultipleSelection && !isGroup) { | ||||
|                     nodeOptions.push( | ||||
|                         { onselect: 'core:show-node-help' }, | ||||
|                         { onselect: 'core:show-node-help', label: RED._('menu.label.showNodeHelp') }, | ||||
|                         null | ||||
|                     ) | ||||
|                 } | ||||
|                 nodeOptions.push( | ||||
|                     { onselect: 'core:enable-selected-nodes' }, | ||||
|                     { onselect: 'core:disable-selected-nodes' }, | ||||
|                     { onselect: 'core:enable-selected-nodes', label: RED._('menu.label.enableSelectedNodes') }, | ||||
|                     { onselect: 'core:disable-selected-nodes', label: RED._('menu.label.disableSelectedNodes') }, | ||||
|                     null, | ||||
|                     { onselect: 'core:show-selected-node-labels' }, | ||||
|                     { onselect: 'core:hide-selected-node-labels' } | ||||
|                     { onselect: 'core:show-selected-node-labels', label: RED._('menu.label.showSelectedNodeLabels') }, | ||||
|                     { onselect: 'core:hide-selected-node-labels', label: RED._('menu.label.hideSelectedNodeLabels') } | ||||
|                 ) | ||||
|                 menuItems.push({ | ||||
|                     label: RED._('sidebar.info.node'), | ||||
| @@ -126,8 +126,8 @@ RED.contextMenu = (function () { | ||||
|                 menuItems.push({ | ||||
|                     label: RED._('sidebar.info.group'), | ||||
|                     options: [ | ||||
|                         { onselect: 'core:group-selection' }, | ||||
|                         { onselect: 'core:ungroup-selection', disabled: !hasGroup }, | ||||
|                         { onselect: 'core:group-selection', label: RED._("menu.label.groupSelection") }, | ||||
|                         { onselect: 'core:ungroup-selection', label: RED._("menu.label.ungroupSelection"), disabled: !hasGroup }, | ||||
|                     ] | ||||
|                 }) | ||||
|                 if (hasGroup) { | ||||
| @@ -143,8 +143,8 @@ RED.contextMenu = (function () { | ||||
|                 } | ||||
|                 menuItems[menuItems.length - 1].options.push( | ||||
|                     null, | ||||
|                     { onselect: 'core:copy-group-style', disabled: !hasGroup }, | ||||
|                     { onselect: 'core:paste-group-style', disabled: !hasGroup} | ||||
|                     { onselect: 'core:copy-group-style', label: RED._("keyboard.copyGroupStyle"), disabled: !hasGroup }, | ||||
|                     { onselect: 'core:paste-group-style', label: RED._("keyboard.pasteGroupStyle"), disabled: !hasGroup} | ||||
|                 ) | ||||
|             } | ||||
|             if (canEdit && hasMultipleSelection) { | ||||
| @@ -174,7 +174,7 @@ RED.contextMenu = (function () { | ||||
|                 { onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !canEdit || !hasSelection }, | ||||
|                 { onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection }, | ||||
|                 { onselect: 'core:paste-from-internal-clipboard', label: RED._("keyboard.pasteNode"), disabled: !canEdit || !RED.view.clipboard() }, | ||||
|                 { onselect: 'core:delete-selection', disabled: !canEdit || !canDelete }, | ||||
|                 { onselect: 'core:delete-selection', label: RED._('keyboard.deleteSelected'), disabled: !canEdit || !canDelete }, | ||||
|                 { onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete }, | ||||
|                 { onselect: 'core:show-export-dialog', label: RED._("menu.label.export") }, | ||||
|                 { onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }, | ||||
|   | ||||
| @@ -115,8 +115,9 @@ RED.editor = (function() { | ||||
|                 var valid = validateNodeProperty(node, definition, prop, properties[prop]); | ||||
|                 if ((typeof valid) === "string") { | ||||
|                     result.push(valid); | ||||
|                 } | ||||
|                 else if(!valid) { | ||||
|                 } else if (Array.isArray(valid)) { | ||||
|                     result = result.concat(valid) | ||||
|                 } else if(!valid) { | ||||
|                     result.push(prop); | ||||
|                 } | ||||
|             } | ||||
| @@ -165,7 +166,7 @@ RED.editor = (function() { | ||||
|                 // If the validator takes two arguments, it is a 3.x validator that | ||||
|                 // can return a String to mean 'invalid' and provide a reason | ||||
|                 if ((definition[property].validate.length === 2) && | ||||
|                     ((typeof valid) === "string")) { | ||||
|                     ((typeof valid) === "string") || Array.isArray(valid)) { | ||||
|                     return valid; | ||||
|                 } else { | ||||
|                     // Otherwise, a 2.x returns a truth-like/false-like value that | ||||
| @@ -181,6 +182,17 @@ RED.editor = (function() { | ||||
|                     error: err.message | ||||
|                 }); | ||||
|             } | ||||
|         } else if (valid) { | ||||
|             // If the validator is not provided in node property => Check if the input has a validator | ||||
|             if ("category" in node._def) { | ||||
|                 const isConfig = node._def.category === "config"; | ||||
|                 const prefix = isConfig ? "node-config-input" : "node-input"; | ||||
|                 const input = $("#"+prefix+"-"+property); | ||||
|                 const isTypedInput = input.length > 0 && input.next(".red-ui-typedInput-container").length > 0; | ||||
|                 if (isTypedInput) { | ||||
|                     valid = input.typedInput("validate"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) { | ||||
|             if (!value || value == "_ADD_") { | ||||
|   | ||||
| @@ -121,7 +121,7 @@ | ||||
|                         var i=0,l=bufferBinValue.length; | ||||
|                         var c = 0; | ||||
|                         for(i=0;i<l;i++) { | ||||
|                             var d = parseInt(bufferBinValue[i]); | ||||
|                             var d = parseInt(Number(bufferBinValue[i])); | ||||
|                             if (!isString && (isNaN(d) || d < 0 || d > 255)) { | ||||
|                                 valid = false; | ||||
|                                 break; | ||||
|   | ||||
| @@ -966,12 +966,10 @@ RED.editor.codeEditor.monaco = (function() { | ||||
|  | ||||
|         //Unbind ctrl-Enter (default action is to insert a newline in editor) This permits the shortcut to close the tray. | ||||
|         try { | ||||
|             ed._standaloneKeybindingService.addDynamicKeybinding( | ||||
|                 '-editor.action.insertLineAfter', // command ID prefixed by '-' | ||||
|                 null, // keybinding | ||||
|                 () => {} // need to pass an empty handler | ||||
|             ); | ||||
|         } catch (error) { } | ||||
|             monaco.editor.addKeybindingRule({keybinding: 0, command: "-editor.action.insertLineAfter"}); | ||||
|         } catch (error) { | ||||
|             console.warn(error) | ||||
|         } | ||||
|  | ||||
|         ed.nodered = { | ||||
|             refreshModuleLibs: refreshModuleLibs //expose this for function node externalModules refresh | ||||
|   | ||||
| @@ -169,7 +169,7 @@ | ||||
|                             var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop(); | ||||
|                             $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue())); | ||||
|                             $(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop); | ||||
|                             mermaid.init(); | ||||
|                             RED.editor.mermaid.render() | ||||
|                         },200); | ||||
|                     }) | ||||
|                     if (options.header) { | ||||
| @@ -178,7 +178,7 @@ | ||||
|  | ||||
|                     if (value) { | ||||
|                         $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue())); | ||||
|                         mermaid.init(); | ||||
|                         RED.editor.mermaid.render() | ||||
|                     } | ||||
|                     panels = RED.panels.create({ | ||||
|                         id:"red-ui-editor-type-markdown-panels", | ||||
|   | ||||
							
								
								
									
										54
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| RED.editor.mermaid = (function () { | ||||
|     let initializing = false | ||||
|     let loaded = false | ||||
|     let pendingEvals = [] | ||||
|     let diagramIds = 0 | ||||
|  | ||||
|     function render(selector = '.mermaid') { | ||||
|         // $(selector).hide() | ||||
|         if (!loaded) { | ||||
|             pendingEvals.push(selector) | ||||
|              | ||||
|             if (!initializing) { | ||||
|                 initializing = true | ||||
|                 $.getScript( | ||||
|                     'vendor/mermaid/mermaid.min.js', | ||||
|                     function (data, stat, jqxhr) { | ||||
|                         mermaid.initialize({ | ||||
|                             startOnLoad: false, | ||||
|                             theme: RED.settings.get('mermaid', {}).theme | ||||
|                         }) | ||||
|                         loaded = true | ||||
|                         while(pendingEvals.length > 0) { | ||||
|                             const pending = pendingEvals.shift() | ||||
|                             render(pending) | ||||
|                         } | ||||
|                     } | ||||
|                 ) | ||||
|             } | ||||
|         } else { | ||||
|             const nodes = document.querySelectorAll(selector) | ||||
|  | ||||
|             nodes.forEach(async node => { | ||||
|                 if (!node.getAttribute('mermaid-processed')) { | ||||
|                     const mermaidContent = node.innerText | ||||
|                     node.setAttribute('mermaid-processed', true) | ||||
|                     try { | ||||
|                         const { svg } = await mermaid.render('mermaid-render-'+Date.now()+'-'+(diagramIds++), mermaidContent); | ||||
|                         node.innerHTML = svg | ||||
|                     } catch (err) { | ||||
|                         $('<div>').css({ | ||||
|                             fontSize: '0.8em', | ||||
|                             border: '1px solid var(--red-ui-border-color-error)', | ||||
|                             padding: '5px', | ||||
|                             marginBottom: '10px', | ||||
|                         }).text(err.toString()).prependTo(node) | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|     }   | ||||
|     return { | ||||
|         render: render, | ||||
|     }; | ||||
| })(); | ||||
| @@ -1,46 +0,0 @@ | ||||
| // Mermaid diagram stub library for on-demand dynamic loading | ||||
| // Will be overwritten after script loading by $.getScript | ||||
| var mermaid = (function () { | ||||
|     var enabled /* = undefined */; | ||||
|      | ||||
|     var initializing = false; | ||||
|     var initCalled = false; | ||||
|  | ||||
|     function initialize(opt) { | ||||
|         if (enabled === undefined) { | ||||
|             if (RED.settings.markdownEditor && | ||||
|                 RED.settings.markdownEditor.mermaid) { | ||||
|                 enabled = RED.settings.markdownEditor.mermaid.enabled; | ||||
|             } | ||||
|             else { | ||||
|                 enabled = true; | ||||
|             } | ||||
|         } | ||||
|         if (enabled) { | ||||
|             initializing = true; | ||||
|             $.getScript("vendor/mermaid/mermaid.min.js", | ||||
|                         function (data, stat, jqxhr) { | ||||
|                             $(".mermaid").show(); | ||||
|                             // invoke loaded mermaid API | ||||
|                             initializing = false;  | ||||
|                             mermaid.initialize(opt); | ||||
|                             if (initCalled) { | ||||
|                                 mermaid.init(); | ||||
|                                 initCalled = false; | ||||
|                             } | ||||
|                         }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function init() { | ||||
|         if (initializing) { | ||||
|             $(".mermaid").hide(); | ||||
|             initCalled = true; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     return { | ||||
|         initialize: initialize, | ||||
|         init: init, | ||||
|     }; | ||||
| })(); | ||||
| @@ -166,7 +166,7 @@ RED.projects.settings = (function() { | ||||
|         var description = addTargetToExternalLinks($('<span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(desc)+'">'+desc+'</span>')).appendTo(container); | ||||
|         description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" ); | ||||
|         setTimeout(function () { | ||||
|             mermaid.init(); | ||||
|             RED.editor.mermaid.render() | ||||
|         }, 200); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -647,9 +647,9 @@ RED.sidebar.versionControl = (function() { | ||||
|                             $.getJSON("projects/"+activeProject.name+"/commits/"+entry.sha,function(result) { | ||||
|                                 result.project = activeProject; | ||||
|                                 result.parents = entry.parents; | ||||
|                                 result.oldRev = entry.sha+"~1"; | ||||
|                                 result.oldRev = entry.parents[0].length !== 0 ? entry.sha+"~1" : entry.sha; | ||||
|                                 result.newRev = entry.sha; | ||||
|                                 result.oldRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7)+"~1"; | ||||
|                                 result.oldRevTitle = entry.parents[0].length !== 0 ? RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7)+"~1" : " "; | ||||
|                                 result.newRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7); | ||||
|                                 result.date = humanizeSinceDate(parseInt(entry.date)); | ||||
|                                 RED.diff.showCommitDiff(result); | ||||
|   | ||||
| @@ -383,6 +383,7 @@ RED.sidebar.help = (function() { | ||||
|                 $(this).toggleClass('expanded',!isExpanded); | ||||
|             }) | ||||
|         helpSection.parent().scrollTop(0); | ||||
|         RED.editor.mermaid.render() | ||||
|     } | ||||
|  | ||||
|     function set(html,title) { | ||||
|   | ||||
| @@ -464,7 +464,7 @@ RED.sidebar.info = (function() { | ||||
|                 } | ||||
|                 $(this).toggleClass('expanded',!isExpanded); | ||||
|             }); | ||||
|         mermaid.init(); | ||||
|         RED.editor.mermaid.render() | ||||
|     } | ||||
|  | ||||
|     var tips = (function() { | ||||
|   | ||||
| @@ -323,7 +323,7 @@ RED.typeSearch = (function() { | ||||
|         } | ||||
|     } | ||||
|     function applyFilter(filter,type,def) { | ||||
|         return !filter || | ||||
|         return !def || !filter || | ||||
|             ( | ||||
|                 (!filter.spliceMultiple) && | ||||
|                 (!filter.type || type === filter.type) && | ||||
|   | ||||
| @@ -101,28 +101,8 @@ RED.utils = (function() { | ||||
|  | ||||
|     renderer.code = function (code, lang) { | ||||
|         if(lang === "mermaid") { | ||||
|             // mermaid diagram rendering  | ||||
|             if (mermaidIsEnabled === undefined) { | ||||
|                 if (RED.settings.markdownEditor && | ||||
|                     RED.settings.markdownEditor.mermaid) { | ||||
|                     mermaidIsEnabled = RED.settings.markdownEditor.mermaid.enabled; | ||||
|                 } | ||||
|                 else { | ||||
|                     mermaidIsEnabled = true; | ||||
|                 } | ||||
|             } | ||||
|             if (mermaidIsEnabled) { | ||||
|                 if (!mermaidIsInitialized) { | ||||
|                     mermaidIsInitialized = true; | ||||
|                     mermaid.initialize({startOnLoad:false}); | ||||
|                 } | ||||
|                 return `<pre class='mermaid'>${code}</pre>`; | ||||
|             } | ||||
|             else { | ||||
|                 return `<details><summary>${RED._("markdownEditor.mermaid.summary")}</summary><pre><code>${code}</code></pre></details>`; | ||||
|             } | ||||
|         } | ||||
|         else { | ||||
|             return `<pre class='mermaid'>${code}</pre>`; | ||||
|         } else { | ||||
|             return "<pre><code>" +code +"</code></pre>"; | ||||
|         } | ||||
|     }; | ||||
| @@ -917,6 +897,51 @@ RED.utils = (function() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Checks a typed property is valid according to the type. | ||||
|      * Returns true if valid. | ||||
|      * Return String error message if invalid | ||||
|      * @param {*} propertyType  | ||||
|      * @param {*} propertyValue  | ||||
|      * @returns true if valid, String if invalid | ||||
|      */ | ||||
|     function validateTypedProperty(propertyValue, propertyType, opt) { | ||||
|  | ||||
|         let error | ||||
|         if (propertyType === 'json') { | ||||
|             try { | ||||
|                 JSON.parse(propertyValue); | ||||
|             } catch(err) { | ||||
|                 error = RED._("validator.errors.invalid-json", { | ||||
|                     error: err.message | ||||
|                 }) | ||||
|             } | ||||
|         } else if (propertyType === 'msg' || propertyType === 'flow' || propertyType === 'global' ) { | ||||
|             if (!RED.utils.validatePropertyExpression(propertyValue)) { | ||||
|                 error = RED._("validator.errors.invalid-prop") | ||||
|             } | ||||
|         } else if (propertyType === 'num') { | ||||
|             if (!/^NaN$|^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$|^[+-]?(0b|0B)[01]+$|^[+-]?(0o|0O)[0-7]+$|^[+-]?(0x|0X)[0-9a-fA-F]+$/.test(propertyValue)) { | ||||
|                 error = RED._("validator.errors.invalid-num") | ||||
|             } | ||||
|         } else if (propertyType === 'jsonata') { | ||||
|             try {  | ||||
|                 jsonata(propertyValue) | ||||
|             } catch(err) { | ||||
|                 error = RED._("validator.errors.invalid-expr", { | ||||
|                     error: err.message | ||||
|                 }) | ||||
|             } | ||||
|         } | ||||
|         if (error) { | ||||
|             if (opt && opt.label) { | ||||
|                 return opt.label+': '+error | ||||
|             } | ||||
|             return error | ||||
|         } | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     function getMessageProperty(msg,expr) { | ||||
|         var result = null; | ||||
|         var msgPropParts; | ||||
| @@ -1451,6 +1476,7 @@ RED.utils = (function() { | ||||
|         getDarkerColor: getDarkerColor, | ||||
|         parseModuleList: parseModuleList, | ||||
|         checkModuleAllowed: checkModuleAllowed, | ||||
|         getBrowserInfo: getBrowserInfo | ||||
|         getBrowserInfo: getBrowserInfo, | ||||
|         validateTypedProperty: validateTypedProperty | ||||
|     } | ||||
| })(); | ||||
|   | ||||
| @@ -4187,7 +4187,7 @@ RED.view = (function() { | ||||
|                 nodeEl.__statusGroup__.style.display = "none"; | ||||
|             } else { | ||||
|                 nodeEl.__statusGroup__.style.display = "inline"; | ||||
|                 let backgroundWidth = 12 | ||||
|                 let backgroundWidth = 15 | ||||
|                 var fill = status_colours[d.status.fill]; // Only allow our colours for now | ||||
|                 if (d.status.shape == null && fill == null) { | ||||
|                     backgroundWidth = 0 | ||||
| @@ -4207,7 +4207,11 @@ RED.view = (function() { | ||||
|                     nodeEl.__statusLabel__.textContent = ""; | ||||
|                 } | ||||
|                 const textSize = nodeEl.__statusLabel__.getBBox() | ||||
|                 nodeEl.__statusBackground__.setAttribute('width', backgroundWidth + textSize.width + 6) | ||||
|                 backgroundWidth += textSize.width | ||||
|                 if (backgroundWidth > 0 && textSize.width > 0) { | ||||
|                     backgroundWidth += 6 | ||||
|                 } | ||||
|                 nodeEl.__statusBackground__.setAttribute('width', backgroundWidth) | ||||
|             } | ||||
|             delete d.dirtyStatus; | ||||
|         } | ||||
| @@ -4619,8 +4623,8 @@ RED.view = (function() { | ||||
|                 statusBackground.setAttribute("y",-1); | ||||
|                 statusBackground.setAttribute("width",200); | ||||
|                 statusBackground.setAttribute("height",13); | ||||
|                 statusBackground.setAttribute("rx",1); | ||||
|                 statusBackground.setAttribute("ry",1); | ||||
|                 statusBackground.setAttribute("rx",2); | ||||
|                 statusBackground.setAttribute("ry",2); | ||||
|                  | ||||
|                 statusEl.appendChild(statusBackground); | ||||
|                 node[0][0].__statusBackground__ = statusBackground; | ||||
|   | ||||
| @@ -17,6 +17,8 @@ | ||||
|  | ||||
| RED.workspaces = (function() { | ||||
|  | ||||
|     const documentTitle = document.title; | ||||
|  | ||||
|     var activeWorkspace = 0; | ||||
|     var workspaceIndex = 0; | ||||
|  | ||||
| @@ -339,12 +341,18 @@ RED.workspaces = (function() { | ||||
|                     $("#red-ui-workspace-chart").show(); | ||||
|                     activeWorkspace = tab.id; | ||||
|                     window.location.hash = 'flow/'+tab.id; | ||||
|                     if (tab.label) { | ||||
|                         document.title = `${documentTitle} : ${tab.label}` | ||||
|                     } else { | ||||
|                         document.title = documentTitle | ||||
|                     } | ||||
|                     $("#red-ui-workspace").toggleClass("red-ui-workspace-disabled", !!tab.disabled); | ||||
|                     $("#red-ui-workspace").toggleClass("red-ui-workspace-locked", !!tab.locked); | ||||
|                 } else { | ||||
|                     $("#red-ui-workspace-chart").hide(); | ||||
|                     activeWorkspace = 0; | ||||
|                     window.location.hash = ''; | ||||
|                     document.title = documentTitle | ||||
|                 } | ||||
|                 event.workspace = activeWorkspace; | ||||
|                 RED.events.emit("workspace:change",event); | ||||
|   | ||||
| @@ -40,46 +40,32 @@ RED.validators = { | ||||
|             return opt ? RED._("validator.errors.invalid-regexp") : false; | ||||
|         }; | ||||
|     }, | ||||
|     typedInput: function(ptypeName,isConfig,mopt) { | ||||
|     typedInput: function(ptypeName, isConfig, mopt) { | ||||
|         let options = ptypeName | ||||
|         if (typeof ptypeName === 'string' ) { | ||||
|             options = {} | ||||
|             options.typeField = ptypeName | ||||
|             options.isConfig = isConfig | ||||
|             options.allowBlank = false | ||||
|         } | ||||
|         return function(v, opt) { | ||||
|             var ptype = $("#node-"+(isConfig?"config-":"")+"input-"+ptypeName).val() || this[ptypeName]; | ||||
|             if (ptype === 'json') { | ||||
|                 try { | ||||
|                     JSON.parse(v); | ||||
|                     return true; | ||||
|                 } catch(err) { | ||||
|                     if (opt && opt.label) { | ||||
|                         return RED._("validator.errors.invalid-json-prop", { | ||||
|                             error: err.message, | ||||
|                             prop: opt.label, | ||||
|                         }); | ||||
|                     } | ||||
|                     return opt ? RED._("validator.errors.invalid-json", { | ||||
|                         error: err.message | ||||
|                     }) : false; | ||||
|                 } | ||||
|             } else if (ptype === 'msg' || ptype === 'flow' || ptype === 'global' ) { | ||||
|                 if (RED.utils.validatePropertyExpression(v)) { | ||||
|                     return true; | ||||
|                 } | ||||
|                 if (opt && opt.label) { | ||||
|                     return RED._("validator.errors.invalid-prop-prop", { | ||||
|                         prop: opt.label | ||||
|                     }); | ||||
|                 } | ||||
|                 return opt ? RED._("validator.errors.invalid-prop") : false; | ||||
|             } else if (ptype === 'num') { | ||||
|                 if (/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v)) { | ||||
|                     return true; | ||||
|                 } | ||||
|                 if (opt && opt.label) { | ||||
|                     return RED._("validator.errors.invalid-num-prop", { | ||||
|                         prop: opt.label | ||||
|                     }); | ||||
|                 } | ||||
|                 return opt ? RED._("validator.errors.invalid-num") : false; | ||||
|             let ptype = options.type | ||||
|             if (!ptype && options.typeField) { | ||||
|                 ptype = $("#node-"+(options.isConfig?"config-":"")+"input-"+options.typeField).val() || this[options.typeField]; | ||||
|             } | ||||
|             return true; | ||||
|         }; | ||||
|             if (options.allowBlank && v === '') { | ||||
|                 return true | ||||
|             } | ||||
|             if (options.allowUndefined && v === undefined) { | ||||
|                 return true | ||||
|             } | ||||
|             const result = RED.utils.validateTypedProperty(v, ptype, opt) | ||||
|             if (result === true || opt) { | ||||
|                 // Valid, or opt provided - return result as-is | ||||
|                 return result | ||||
|             } | ||||
|             // No opt - need to return false for backwards compatibilty | ||||
|             return false | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| }; | ||||
| @@ -114,6 +114,7 @@ | ||||
|     pointer-events: stroke; | ||||
| } | ||||
| .red-ui-flow-group-outline-select { | ||||
|     cursor: move; | ||||
|     fill: none; | ||||
|     stroke: var(--red-ui-node-selected-color); | ||||
|     pointer-events: none; | ||||
|   | ||||
| @@ -320,7 +320,7 @@ | ||||
|             } | ||||
|             // but replace with repeat one if set to repeat | ||||
|             if ((this.repeat && this.repeat != 0) || this.crontab) { | ||||
|                 suffix = " ↻"; | ||||
|                 suffix = "\t↻"; | ||||
|             } | ||||
|             if (this.name) { | ||||
|                 return this.name+suffix; | ||||
|   | ||||
| @@ -109,9 +109,8 @@ module.exports = function(RED) { | ||||
|                 } | ||||
|                 const p = props.shift() | ||||
|                 const property = p.p; | ||||
|                 const value = p.v ? p.v : ''; | ||||
|                 const valueType = p.vt ? p.vt : 'str'; | ||||
|  | ||||
|                 const value = p.v !== undefined ? p.v : ''; | ||||
|                 const valueType = p.vt !== undefined ? p.vt : 'str'; | ||||
|                 if (property) { | ||||
|                     if (valueType === "jsonata") { | ||||
|                         if (p.v) { | ||||
|   | ||||
| @@ -86,7 +86,7 @@ | ||||
|         }, | ||||
|         label: function() { | ||||
|             var suffix = ""; | ||||
|             if (this.console === true || this.console === "true") { suffix = " ⇲"; } | ||||
|             if (this.console === true || this.console === "true") { suffix = "\t⇲"; } | ||||
|             if (this.targetType === "jsonata") { | ||||
|                 return (this.name || "JSONata") + suffix; | ||||
|             } | ||||
| @@ -195,6 +195,119 @@ | ||||
|                         node.dirty = true; | ||||
|                     }); | ||||
|                     RED.view.redraw(); | ||||
|                 }, | ||||
|                 requestDebugNodeList: function(filteredNodes) { | ||||
|                     var workspaceOrder = RED.nodes.getWorkspaceOrder(); | ||||
|                     var workspaceOrderMap = {}; | ||||
|                     workspaceOrder.forEach(function(ws,i) { | ||||
|                         workspaceOrderMap[ws] = i; | ||||
|                     }); | ||||
|  | ||||
|                     var candidateNodes = []; | ||||
|                     var candidateSFs = []; | ||||
|                     var subflows = {}; | ||||
|                     RED.nodes.eachNode(function (n) { | ||||
|                         var nt = n.type; | ||||
|                         if (nt === "debug") { | ||||
|                             if (n.z in workspaceOrderMap) { | ||||
|                                 candidateNodes.push(n); | ||||
|                             } | ||||
|                             else { | ||||
|                                 var sf = RED.nodes.subflow(n.z); | ||||
|                                 if (sf) { | ||||
|                                     subflows[sf.id] = { | ||||
|                                         debug: true, | ||||
|                                         subflows: {} | ||||
|                                     }; | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                         else if(nt.substring(0, 8) === "subflow:") { | ||||
|                             if (n.z in workspaceOrderMap) { | ||||
|                                 candidateSFs.push(n); | ||||
|                             } | ||||
|                             else { | ||||
|                                 var psf = RED.nodes.subflow(n.z); | ||||
|                                 if (psf) { | ||||
|                                     var sid = nt.substring(8); | ||||
|                                     var item = subflows[psf.id]; | ||||
|                                     if (!item) { | ||||
|                                         item = { | ||||
|                                             debug: undefined, | ||||
|                                             subflows: {} | ||||
|                                         }; | ||||
|                                         subflows[psf.id] = item; | ||||
|                                     } | ||||
|                                     item.subflows[sid] = true; | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     }); | ||||
|                     candidateSFs.forEach(function (sf) { | ||||
|                         var sid = sf.type.substring(8); | ||||
|                         if (containsDebug(sid, subflows)) { | ||||
|                             candidateNodes.push(sf); | ||||
|                         } | ||||
|                     }); | ||||
|  | ||||
|                     candidateNodes.sort(function(A,B) { | ||||
|                         var wsA = workspaceOrderMap[A.z]; | ||||
|                         var wsB = workspaceOrderMap[B.z]; | ||||
|                         if (wsA !== wsB) { | ||||
|                             return wsA-wsB; | ||||
|                         } | ||||
|                         var labelA = RED.utils.getNodeLabel(A,A.id); | ||||
|                         var labelB = RED.utils.getNodeLabel(B,B.id); | ||||
|                         return labelA.localeCompare(labelB); | ||||
|                     }); | ||||
|                     var currentWs = null; | ||||
|                     var data = []; | ||||
|                     var currentFlow; | ||||
|                     var currentSelectedCount = 0; | ||||
|                     candidateNodes.forEach(function(node) { | ||||
|                         if (currentWs !== node.z) { | ||||
|                             if (currentFlow && currentFlow.checkbox) { | ||||
|                                 currentFlow.selected = currentSelectedCount === currentFlow.children.length | ||||
|                             } | ||||
|                             currentSelectedCount = 0; | ||||
|                             currentWs = node.z; | ||||
|                             var parent = RED.nodes.workspace(currentWs) || RED.nodes.subflow(currentWs); | ||||
|                             currentFlow = { | ||||
|                                 label: RED.utils.getNodeLabel(parent, currentWs), | ||||
|                             } | ||||
|                             if (!parent.disabled) { | ||||
|                                 currentFlow.children = []; | ||||
|                                 currentFlow.checkbox = true; | ||||
|                             } else { | ||||
|                                 currentFlow.class = "disabled" | ||||
|                             } | ||||
|                             data.push(currentFlow); | ||||
|                         } | ||||
|                         if (currentFlow.children) { | ||||
|                             if (!filteredNodes[node.id]) { | ||||
|                                 currentSelectedCount++; | ||||
|                             } | ||||
|                             currentFlow.children.push({ | ||||
|                                 label: RED.utils.getNodeLabel(node,node.id), | ||||
|                                 node: { | ||||
|                                     id: node.id | ||||
|                                 }, | ||||
|                                 checkbox: true, | ||||
|                                 selected: !filteredNodes[node.id] | ||||
|                             }); | ||||
|                         } | ||||
|                     }); | ||||
|                     if (currentFlow && currentFlow.checkbox) { | ||||
|                         currentFlow.selected = currentSelectedCount === currentFlow.children.length | ||||
|                     } | ||||
|                     if (subWindow) { | ||||
|                         try { | ||||
|                             subWindow.postMessage({event:"refreshDebugNodeList", nodes:data},"*"); | ||||
|                         } catch(err) { | ||||
|                             console.log(err); | ||||
|                         } | ||||
|                     } | ||||
|                     RED.debug.refreshDebugNodeList(data) | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
| @@ -396,6 +509,26 @@ | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             function containsDebug(sid, map) { | ||||
|                 var item = map[sid]; | ||||
|                 if (item) { | ||||
|                     if (item.debug === undefined) { | ||||
|                         var sfs = Object.keys(item.subflows); | ||||
|                         var contain = false; | ||||
|                         for (var i = 0; i < sfs.length; i++) { | ||||
|                             var sf = sfs[i]; | ||||
|                             if (containsDebug(sf, map)) { | ||||
|                                 contain = true; | ||||
|                                 break; | ||||
|                             } | ||||
|                         } | ||||
|                         item.debug = contain; | ||||
|                     } | ||||
|                     return item.debug; | ||||
|                 } | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             $("#red-ui-sidebar-debug-open").on("click", function(e) { | ||||
|                 e.preventDefault(); | ||||
|                 subWindow = window.open(document.location.toString().replace(/[?#].*$/,"")+"debug/view/view.html"+document.location.search,"nodeREDDebugView","menubar=no,location=no,toolbar=no,chrome,height=500,width=600"); | ||||
| @@ -427,6 +560,8 @@ | ||||
|                     options.messageSourceClick(msg.id,msg._alias,msg.path); | ||||
|                 } else if (msg.event === "clear") { | ||||
|                     options.clear(); | ||||
|                 } else if (msg.event === "requestDebugNodeList") { | ||||
|                     options.requestDebugNodeList(msg.filteredNodes) | ||||
|                 } | ||||
|             }; | ||||
|             window.addEventListener('message',this.handleWindowMessage); | ||||
|   | ||||
| @@ -167,19 +167,13 @@ RED.debug = (function() { | ||||
|             var menu = RED.popover.menu({ | ||||
|                 options: options, | ||||
|                 onselect: function(item) { | ||||
|                     if (item.value !== filterType) { | ||||
|                         filterType = item.value; | ||||
|                         $('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.'+filterType)); | ||||
|                         refreshMessageList(); | ||||
|                         RED.settings.set("debug.filter",filterType) | ||||
|                     } | ||||
|                     setFilterType(item.value) | ||||
|                     if (filterType === 'filterSelected') { | ||||
|                         refreshDebugNodeList(); | ||||
|                         config.requestDebugNodeList(filteredNodes); | ||||
|                         filterDialog.slideDown(200); | ||||
|                         filterDialogShown = true; | ||||
|                         debugNodeTreeList.focus(); | ||||
|                     } | ||||
|  | ||||
|                 } | ||||
|             }); | ||||
|             menu.show({ | ||||
| @@ -254,131 +248,7 @@ RED.debug = (function() { | ||||
|  | ||||
|     } | ||||
|  | ||||
|  | ||||
|     function containsDebug(sid, map) { | ||||
|         var item = map[sid]; | ||||
|         if (item) { | ||||
|             if (item.debug === undefined) { | ||||
|                 var sfs = Object.keys(item.subflows); | ||||
|                 var contain = false; | ||||
|                 for (var i = 0; i < sfs.length; i++) { | ||||
|                     var sf = sfs[i]; | ||||
|                     if (containsDebug(sf, map)) { | ||||
|                         contain = true; | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|                 item.debug = contain; | ||||
|             } | ||||
|             return item.debug; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     function refreshDebugNodeList() { | ||||
|         var workspaceOrder = RED.nodes.getWorkspaceOrder(); | ||||
|         var workspaceOrderMap = {}; | ||||
|         workspaceOrder.forEach(function(ws,i) { | ||||
|             workspaceOrderMap[ws] = i; | ||||
|         }); | ||||
|  | ||||
|         var candidateNodes = []; | ||||
|         var candidateSFs = []; | ||||
|         var subflows = {}; | ||||
|         RED.nodes.eachNode(function (n) { | ||||
|             var nt = n.type; | ||||
|             if (nt === "debug") { | ||||
|                 if (n.z in workspaceOrderMap) { | ||||
|                     candidateNodes.push(n); | ||||
|                 } | ||||
|                 else { | ||||
|                     var sf = RED.nodes.subflow(n.z); | ||||
|                     if (sf) { | ||||
|                         subflows[sf.id] = { | ||||
|                             debug: true, | ||||
|                             subflows: {} | ||||
|                         }; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else if(nt.substring(0, 8) === "subflow:") { | ||||
|                 if (n.z in workspaceOrderMap) { | ||||
|                     candidateSFs.push(n); | ||||
|                 } | ||||
|                 else { | ||||
|                     var psf = RED.nodes.subflow(n.z); | ||||
|                     if (psf) { | ||||
|                         var sid = nt.substring(8); | ||||
|                         var item = subflows[psf.id]; | ||||
|                         if (!item) { | ||||
|                             item = { | ||||
|                                 debug: undefined, | ||||
|                                 subflows: {} | ||||
|                             }; | ||||
|                             subflows[psf.id] = item; | ||||
|                         } | ||||
|                         item.subflows[sid] = true; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         candidateSFs.forEach(function (sf) { | ||||
|             var sid = sf.type.substring(8); | ||||
|             if (containsDebug(sid, subflows)) { | ||||
|                 candidateNodes.push(sf); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         candidateNodes.sort(function(A,B) { | ||||
|             var wsA = workspaceOrderMap[A.z]; | ||||
|             var wsB = workspaceOrderMap[B.z]; | ||||
|             if (wsA !== wsB) { | ||||
|                 return wsA-wsB; | ||||
|             } | ||||
|             var labelA = RED.utils.getNodeLabel(A,A.id); | ||||
|             var labelB = RED.utils.getNodeLabel(B,B.id); | ||||
|             return labelA.localeCompare(labelB); | ||||
|         }); | ||||
|         var currentWs = null; | ||||
|         var data = []; | ||||
|         var currentFlow; | ||||
|         var currentSelectedCount = 0; | ||||
|         candidateNodes.forEach(function(node) { | ||||
|             if (currentWs !== node.z) { | ||||
|                 if (currentFlow && currentFlow.checkbox) { | ||||
|                     currentFlow.selected = currentSelectedCount === currentFlow.children.length | ||||
|                 } | ||||
|                 currentSelectedCount = 0; | ||||
|                 currentWs = node.z; | ||||
|                 var parent = RED.nodes.workspace(currentWs) || RED.nodes.subflow(currentWs); | ||||
|                 currentFlow = { | ||||
|                     label: RED.utils.getNodeLabel(parent, currentWs), | ||||
|                 } | ||||
|                 if (!parent.disabled) { | ||||
|                     currentFlow.children = []; | ||||
|                     currentFlow.checkbox = true; | ||||
|                 } else { | ||||
|                     currentFlow.class = "disabled" | ||||
|                 } | ||||
|                 data.push(currentFlow); | ||||
|             } | ||||
|             if (currentFlow.children) { | ||||
|                 if (!filteredNodes[node.id]) { | ||||
|                     currentSelectedCount++; | ||||
|                 } | ||||
|                 currentFlow.children.push({ | ||||
|                     label: RED.utils.getNodeLabel(node,node.id), | ||||
|                     node: node, | ||||
|                     checkbox: true, | ||||
|                     selected: !filteredNodes[node.id] | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|         if (currentFlow && currentFlow.checkbox) { | ||||
|             currentFlow.selected = currentSelectedCount === currentFlow.children.length | ||||
|         } | ||||
|  | ||||
|     function refreshDebugNodeList(data) { | ||||
|         debugNodeTreeList.treeList("data", data); | ||||
|     } | ||||
|  | ||||
| @@ -401,7 +271,7 @@ RED.debug = (function() { | ||||
|         },200); | ||||
|     } | ||||
|     function _refreshMessageList(_activeWorkspace) { | ||||
|         if (_activeWorkspace) { | ||||
|         if (typeof _activeWorkspace === 'string') { | ||||
|             activeWorkspace = _activeWorkspace.replace(/\./g,"_"); | ||||
|         } | ||||
|         if (filterType === "filterAll") { | ||||
| @@ -479,12 +349,12 @@ RED.debug = (function() { | ||||
|                         filteredNodes[n.id] = true; | ||||
|                     }); | ||||
|                     delete filteredNodes[sourceId]; | ||||
|                     $("#red-ui-sidebar-debug-filterSelected").trigger("click"); | ||||
|                     RED.settings.set('debug.filteredNodes',Object.keys(filteredNodes)) | ||||
|                     setFilterType('filterSelected') | ||||
|                     refreshMessageList(); | ||||
|                 }}, | ||||
|                 {id:"red-ui-debug-msg-menu-item-clear-filter",label:RED._("node-red:debug.messageMenu.clearFilter"),onselect:function(){ | ||||
|                     $("#red-ui-sidebar-debug-filterAll").trigger("click"); | ||||
|                     clearFilterSettings() | ||||
|                     refreshMessageList(); | ||||
|                 }} | ||||
|             ); | ||||
| @@ -713,9 +583,17 @@ RED.debug = (function() { | ||||
|         if (!!clearFilter) { | ||||
|             clearFilterSettings(); | ||||
|         } | ||||
|         refreshDebugNodeList(); | ||||
|         config.requestDebugNodeList(filteredNodes); | ||||
|     } | ||||
|  | ||||
|     function setFilterType(type) { | ||||
|         if (type !== filterType) { | ||||
|             filterType = type; | ||||
|             $('#red-ui-sidebar-debug-filter span').text(RED._('node-red:debug.sidebar.'+filterType)); | ||||
|             refreshMessageList(); | ||||
|             RED.settings.set("debug.filter",filterType) | ||||
|         } | ||||
|     } | ||||
|     function clearFilterSettings() { | ||||
|         filteredNodes = {}; | ||||
|         filterType = 'filterAll'; | ||||
| @@ -728,6 +606,7 @@ RED.debug = (function() { | ||||
|         init: init, | ||||
|         refreshMessageList:refreshMessageList, | ||||
|         handleDebugMessage: handleDebugMessage, | ||||
|         clearMessageList: clearMessageList | ||||
|         clearMessageList: clearMessageList, | ||||
|         refreshDebugNodeList: refreshDebugNodeList | ||||
|     } | ||||
| })(); | ||||
|   | ||||
| @@ -12,6 +12,9 @@ $(function() { | ||||
|             }, | ||||
|             clear: function() { | ||||
|                 window.opener.postMessage({event:"clear"},'*'); | ||||
|             }, | ||||
|             requestDebugNodeList: function(filteredNodes) { | ||||
|                 window.opener.postMessage({event: 'requestDebugNodeList', filteredNodes},'*') | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -26,6 +29,8 @@ $(function() { | ||||
|                     RED.debug.refreshMessageList(evt.data.activeWorkspace); | ||||
|                 } else if (evt.data.event === "projectChange") { | ||||
|                     RED.debug.clearMessageList(true); | ||||
|                 } else if (evt.data.event === "refreshDebugNodeList") { | ||||
|                     RED.debug.refreshDebugNodeList(evt.data.nodes) | ||||
|                 } | ||||
|             },false); | ||||
|         } catch(err) { | ||||
|   | ||||
| @@ -167,7 +167,35 @@ | ||||
|                        label:RED._("node-red:common.label.payload"), | ||||
|                        validate: RED.validators.typedInput("propertyType", false)}, | ||||
|             propertyType: { value:"msg" }, | ||||
|             rules: {value:[{t:"eq", v:"", vt:"str"}]}, | ||||
|             rules: { | ||||
|                 value:[{t:"eq", v:"", vt:"str"}], | ||||
|                 validate: function (rules, opt) { | ||||
|                     let msg; | ||||
|                     const errors = [] | ||||
|                     if (!rules || rules.length === 0) { return true } | ||||
|                     for (var i=0;i<rules.length;i++) { | ||||
|                         const opt = { label: RED._('node-red:switch.label.rule')+' '+(i+1) } | ||||
|                         const r = rules[i]; | ||||
|                         if (r.t !== 'istype') { | ||||
|                             if (r.hasOwnProperty('v')) { | ||||
|                                 if ((msg = RED.utils.validateTypedProperty(r.v,r.vt,opt)) !== true) { | ||||
|                                     errors.push(msg) | ||||
|                                 } | ||||
|                             } | ||||
|                             if (r.hasOwnProperty('v2')) { | ||||
|                                 if ((msg = RED.utils.validateTypedProperty(r.v2,r.v2t,opt)) !== true) { | ||||
|                                     errors.push(msg) | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     if (errors.length) { | ||||
|                         console.log(errors) | ||||
|                         return errors | ||||
|                     } | ||||
|                     return true; | ||||
|                 } | ||||
|             }, | ||||
|             checkall: {value:"true", required:true}, | ||||
|             repair: {value:false}, | ||||
|             outputs: {value:1} | ||||
|   | ||||
| @@ -19,71 +19,42 @@ | ||||
|  | ||||
| <script type="text/javascript"> | ||||
| (function() { | ||||
|     function isInvalidProperty(v,vt) { | ||||
|         if (/msg|flow|global/.test(vt)) { | ||||
|             if (!RED.utils.validatePropertyExpression(v)) { | ||||
|                 return RED._("node-red:change.errors.invalid-prop", { | ||||
|                     property: v | ||||
|                 }); | ||||
|             } | ||||
|         } else if (vt === "jsonata") { | ||||
|             try{ jsonata(v); } catch(e) { | ||||
|                 return RED._("node-red:change.errors.invalid-expr", { | ||||
|                     error: e.message | ||||
|                 }); | ||||
|             } | ||||
|         } else if (vt === "json") { | ||||
|             try{ JSON.parse(v); } catch(e) { | ||||
|                 return RED._("node-red:change.errors.invalid-json-data", { | ||||
|                     error: e.message | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     RED.nodes.registerType('change', { | ||||
|         color: "#E2D96E", | ||||
|         category: 'function', | ||||
|         defaults: { | ||||
|             name: {value:""}, | ||||
|             rules:{value:[{t:"set",p:"payload",pt:"msg",to:"",tot:"str"}],validate: function(rules, opt) { | ||||
|                 var msg; | ||||
|                 if (!rules || rules.length === 0) { return true } | ||||
|                 for (var i=0;i<rules.length;i++) { | ||||
|                     var r = rules[i]; | ||||
|                     if (r.t === 'set') { | ||||
|                         if (msg = isInvalidProperty(r.p,r.pt)) { | ||||
|                             return msg; | ||||
|             rules:{ | ||||
|                 value:[{t:"set",p:"payload",pt:"msg",to:"",tot:"str"}], | ||||
|                 validate: function(rules, opt) { | ||||
|                     let msg; | ||||
|                     const errors = [] | ||||
|                     if (!rules || rules.length === 0) { return true } | ||||
|                     for (var i=0;i<rules.length;i++) { | ||||
|                         const opt = { label: RED._('node-red:change.label.rule')+' '+(i+1) } | ||||
|                         const r = rules[i]; | ||||
|                         if (r.t === 'set' || r.t === 'change' || r.t === 'delete' || r.t === 'move') { | ||||
|                             if ((msg = RED.utils.validateTypedProperty(r.p,r.pt,opt)) !== true) { | ||||
|                                 errors.push(msg) | ||||
|                             } | ||||
|                         } | ||||
|                         if (msg = isInvalidProperty(r.to,r.tot)) { | ||||
|                             return msg; | ||||
|                         if (r.t === 'set' || r.t === 'change' || r.t === 'move') { | ||||
|                             if ((msg = RED.utils.validateTypedProperty(r.to,r.tot,opt)) !== true) { | ||||
|                                 errors.push(msg) | ||||
|                             } | ||||
|                         } | ||||
|                     } else if (r.t === 'change') { | ||||
|                         if (msg = isInvalidProperty(r.p,r.pt)) { | ||||
|                             return msg; | ||||
|                         } | ||||
|                         if(msg = isInvalidProperty(r.from,r.fromt)) { | ||||
|                             return msg; | ||||
|                         } | ||||
|                         if(msg = isInvalidProperty(r.to,r.tot)) { | ||||
|                             return msg; | ||||
|                         } | ||||
|                     } else if (r.t === 'delete') { | ||||
|                         if (msg = isInvalidProperty(r.p,r.pt)) { | ||||
|                             return msg; | ||||
|                         } | ||||
|                     } else if (r.t === 'move') { | ||||
|                         if (msg = isInvalidProperty(r.p,r.pt)) { | ||||
|                             return msg; | ||||
|                         } | ||||
|                         if (msg = isInvalidProperty(r.to,r.tot)) { | ||||
|                             return msg; | ||||
|                         if (r.t === 'change') { | ||||
|                             if ((msg = RED.utils.validateTypedProperty(r.from,r.fromt,opt)) !== true) { | ||||
|                                 errors.push(msg) | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     if (errors.length) { | ||||
|                         return errors | ||||
|                     } | ||||
|                     return true; | ||||
|                 } | ||||
|                 return true; | ||||
|             }}, | ||||
|             }, | ||||
|             // legacy | ||||
|             action: {value:""}, | ||||
|             property: {value:""}, | ||||
|   | ||||
| @@ -57,7 +57,9 @@ | ||||
|             action: {value:"scale"}, | ||||
|             round: {value:false}, | ||||
|             property: {value:"payload",required:true, | ||||
|                        label:RED._("node-red:common.label.property")}, | ||||
|                        label:RED._("node-red:common.label.property"), | ||||
|                        validate: RED.validators.typedInput({ type: 'msg', allowBlank: true }) | ||||
|                     }, | ||||
|             name: {value:""} | ||||
|         }, | ||||
|         inputs: 1, | ||||
|   | ||||
| @@ -56,7 +56,7 @@ | ||||
|         color:"darksalmon", | ||||
|         defaults: { | ||||
|             command: {value:""}, | ||||
|             addpay: {value:""}, | ||||
|             addpay: {value:"", validate: RED.validators.typedInput({ type: 'msg', allowBlank: true })}, | ||||
|             append: {value:""}, | ||||
|             useSpawn: {value:"false"}, | ||||
|             timer: {value:""}, | ||||
|   | ||||
| @@ -56,9 +56,11 @@ | ||||
|             inout: {value:"out"}, | ||||
|             septopics: {value:true}, | ||||
|             property: {value:"payload", required:true, | ||||
|                        label:RED._("node-red:rbe.label.property")}, | ||||
|                        label:RED._("node-red:rbe.label.property"), | ||||
|                        validate: RED.validators.typedInput({ type: 'msg', allowUndefined: true })}, | ||||
|             topi: {value:"topic", required:true, | ||||
|                    label:RED._("node-red:rbe.label.topic")} | ||||
|                    label:RED._("node-red:rbe.label.topic"), | ||||
|                    validate: RED.validators.typedInput({ type: 'msg', allowUndefined: true })} | ||||
|         }, | ||||
|         inputs:1, | ||||
|         outputs:1, | ||||
|   | ||||
| @@ -104,6 +104,7 @@ module.exports = function(RED) { | ||||
|      * @returns `true` if it is a valid topic | ||||
|      */ | ||||
|     function isValidPublishTopic(topic) { | ||||
|         if (topic.length === 0) return false; | ||||
|         return !/[\+#\b\f\n\r\t\v\0]/.test(topic); | ||||
|     } | ||||
|  | ||||
| @@ -219,8 +220,8 @@ module.exports = function(RED) { | ||||
|      */ | ||||
|     function subscriptionHandler(node, datatype ,topic, payload, packet) { | ||||
|         const msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain}; | ||||
|         const v5 = (node && node.brokerConn)  | ||||
|             ? node.brokerConn.v5()  | ||||
|         const v5 = (node && node.brokerConn) | ||||
|             ? node.brokerConn.v5() | ||||
|             : Object.prototype.hasOwnProperty.call(packet, "properties"); | ||||
|         if(v5 && packet.properties) { | ||||
|             setStrProp(packet.properties, msg, "responseTopic"); | ||||
| @@ -451,7 +452,7 @@ module.exports = function(RED) { | ||||
|  | ||||
|     /** | ||||
|      * Perform the disconnect action | ||||
|      * @param {MQTTInNode|MQTTOutNode} node  | ||||
|      * @param {MQTTInNode|MQTTOutNode} node | ||||
|      * @param {Function} done | ||||
|      */ | ||||
|     function handleDisconnectAction(node, done) { | ||||
| @@ -611,7 +612,7 @@ module.exports = function(RED) { | ||||
|                 node.brokerurl = node.url; | ||||
|             } else { | ||||
|                 // if the broker is ws:// or wss:// or tcp:// | ||||
|                 if (node.broker.indexOf("://") > -1) { | ||||
|                 if ((typeof node.broker  === 'string') && node.broker.indexOf("://") > -1) { | ||||
|                     node.brokerurl = node.broker; | ||||
|                     // Only for ws or wss, check if proxy env var for additional configuration | ||||
|                     if (node.brokerurl.indexOf("wss://") > -1 || node.brokerurl.indexOf("ws://") > -1) { | ||||
| @@ -865,7 +866,7 @@ module.exports = function(RED) { | ||||
|              * Call end and wait for the client to end (or timeout) | ||||
|              * @param {mqtt.MqttClient} client The broker client | ||||
|              * @param {number} ms The time to wait for the client to end | ||||
|              * @returns  | ||||
|              * @returns | ||||
|              */ | ||||
|             let waitEnd = (client, ms) => { | ||||
|                 return new Promise( (resolve, reject) => { | ||||
| @@ -905,7 +906,7 @@ module.exports = function(RED) { | ||||
|         node.subid = 1; | ||||
|  | ||||
|         //typedef for subscription object: | ||||
|         /**  | ||||
|         /** | ||||
|          * @typedef {Object} Subscription | ||||
|          * @property {String} topic - topic to subscribe to | ||||
|          * @property {Object} [options] - options object | ||||
| @@ -933,7 +934,7 @@ module.exports = function(RED) { | ||||
|             const ref = _ref || 0; | ||||
|             let options | ||||
|             let qos = 1 // default to QoS 1 (AWS and several other brokers don't support QoS 2) | ||||
|              | ||||
|  | ||||
|             // if options is an object, then clone it | ||||
|             if (typeof _options == "object") { | ||||
|                 options = RED.util.cloneMessage(_options || {}) | ||||
| @@ -947,7 +948,7 @@ module.exports = function(RED) { | ||||
|             if (typeof qos === "number" && qos >= 0 && qos <= 2) { | ||||
|                 options.qos = qos; | ||||
|             } | ||||
|              | ||||
|  | ||||
|             subscription.topic = _topic; | ||||
|             subscription.qos = qos; | ||||
|             subscription.options = RED.util.cloneMessage(options); | ||||
| @@ -957,16 +958,16 @@ module.exports = function(RED) { | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * If topic is a subscription object, then use that, otherwise look up the topic in  | ||||
|          * If topic is a subscription object, then use that, otherwise look up the topic in | ||||
|          * the  subscriptions object.  If the topic is not found, then create a new subscription | ||||
|          * object and add it to the subscriptions object. | ||||
|          * @param {Subscription|String} topic  | ||||
|          * @param {*} options  | ||||
|          * @param {*} callback  | ||||
|          * @param {*} ref  | ||||
|          * @param {Subscription|String} topic | ||||
|          * @param {*} options | ||||
|          * @param {*} callback | ||||
|          * @param {*} ref | ||||
|          */ | ||||
|         node.subscribe = function (topic, options, callback, ref) { | ||||
|             /** @type {Subscription} */  | ||||
|             /** @type {Subscription} */ | ||||
|             let subscription | ||||
|             let doCompare = false | ||||
|             let changesFound = false | ||||
| @@ -1004,7 +1005,7 @@ module.exports = function(RED) { | ||||
|                     _brokerConn.unsubscribe(sub.topic, sub.ref, true) | ||||
|                 } | ||||
|             }) | ||||
|              | ||||
|  | ||||
|             // if subscription is found (or sent in as a parameter), then check for changes. | ||||
|             // if there are any changes requested, tidy up the old subscription | ||||
|             if (subscription) { | ||||
| @@ -1091,7 +1092,7 @@ module.exports = function(RED) { | ||||
|                         delete sub[ref] | ||||
|                     } | ||||
|                 } | ||||
|                 // if instructed to remove the actual MQTT client subscription  | ||||
|                 // if instructed to remove the actual MQTT client subscription | ||||
|                 if (unsub) { | ||||
|                     // if there are no more subscriptions for the topic, then remove the topic | ||||
|                     if (Object.keys(sub).length === 0) { | ||||
|   | ||||
| @@ -41,8 +41,8 @@ | ||||
|         color:"#DEBD5C", | ||||
|         defaults: { | ||||
|             name: {value:""}, | ||||
|             property: {value:"payload"}, | ||||
|             outproperty: {value:"payload"}, | ||||
|             property: {value:"payload", validate: RED.validators.typedInput({ type: 'msg', allowUndefined: true }) }, | ||||
|             outproperty: {value:"payload", validate: RED.validators.typedInput({ type: 'msg', allowUndefined: true }) }, | ||||
|             tag: {value:""}, | ||||
|             ret: {value:"html"}, | ||||
|             as: {value:"single"} | ||||
|   | ||||
| @@ -32,6 +32,7 @@ | ||||
|         defaults: { | ||||
|             name: {value:""}, | ||||
|             property: {value:"payload",required:true, | ||||
|                        validate: RED.validators.typedInput({ type: 'msg', allowUndefined: true}), | ||||
|                        label:RED._("node-red:json.label.property")}, | ||||
|             action: {value:""}, | ||||
|             pretty: {value:false} | ||||
|   | ||||
| @@ -27,7 +27,8 @@ | ||||
|         defaults: { | ||||
|             name: {value:""}, | ||||
|             property: {value:"payload",required:true, | ||||
|                        label:RED._("node-red:common.label.property")}, | ||||
|                        label:RED._("node-red:common.label.property"), | ||||
|                        validate: RED.validators.typedInput({ type: 'msg', allowUndefined: true })}, | ||||
|             attr: {value:""}, | ||||
|             chr: {value:""} | ||||
|         }, | ||||
|   | ||||
| @@ -16,6 +16,7 @@ | ||||
|         color:"#DEBD5C", | ||||
|         defaults: { | ||||
|             property: {value:"payload",required:true, | ||||
|                        validate: RED.validators.typedInput({ type: 'msg', allowUndefined: true }), | ||||
|                        label:RED._("node-red:common.label.property")}, | ||||
|             name: {value:""} | ||||
|         }, | ||||
|   | ||||
| @@ -57,7 +57,7 @@ | ||||
|             arraySplt: {value:1}, | ||||
|             arraySpltType: {value:"len"}, | ||||
|             stream: {value:false}, | ||||
|             addname: {value:""} | ||||
|             addname: {value:"", validate: RED.validators.typedInput({ type: 'msg', allowBlank: true })} | ||||
|         }, | ||||
|         inputs:1, | ||||
|         outputs:1, | ||||
| @@ -208,7 +208,22 @@ | ||||
|                 validate:RED.validators.typedInput("propertyType", false) | ||||
|             }, | ||||
|             propertyType: { value:"msg"}, | ||||
|             key: {value:"topic"}, | ||||
|             key: {value:"topic", validate: (function () { | ||||
|                 const typeValidator = RED.validators.typedInput({ type: 'msg' }) | ||||
|                 return function(v, opt) { | ||||
|                     const joinMode = $("#node-input-mode").val() || this.mode | ||||
|                     if (joinMode !== 'custom') { | ||||
|                         return true | ||||
|                     } | ||||
|                     const buildType = $("#node-input-build").val() || this.build | ||||
|                     if (buildType !== 'object') { | ||||
|                         return true | ||||
|                     } else { | ||||
|                         return typeValidator(v, opt) | ||||
|                     } | ||||
|                 } | ||||
|                 })() | ||||
|             }, | ||||
|             joiner: { value:"\\n"}, | ||||
|             joinerType: { value:"str"}, | ||||
|             accumulate: { value:"false" }, | ||||
|   | ||||
| @@ -198,7 +198,7 @@ | ||||
|         category: 'storage', | ||||
|         defaults: { | ||||
|             name: {value:""}, | ||||
|             filename: {value:""}, | ||||
|             filename: {value:"", validate: RED.validators.typedInput({ typeField: 'filenameType' })}, | ||||
|             filenameType: {value:"str"}, | ||||
|             appendNewline: {value:true}, | ||||
|             createDir: {value:false}, | ||||
| @@ -297,7 +297,7 @@ | ||||
|         category: 'storage', | ||||
|         defaults: { | ||||
|             name: {value:""}, | ||||
|             filename: {value:""}, | ||||
|             filename: {value:"", validate: RED.validators.typedInput({ typeField: 'filenameType' }) }, | ||||
|             filenameType: {value:"str"}, | ||||
|             format: {value:"utf8"}, | ||||
|             chunk: {value:false}, | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/nodes", | ||||
|     "version": "3.1.0", | ||||
|     "version": "3.1.2", | ||||
|     "license": "Apache-2.0", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|   | ||||
| @@ -98,7 +98,7 @@ function requireModule(module) { | ||||
|     const parsedModule = parseModuleName(module); | ||||
|  | ||||
|     if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) { | ||||
|         return require(parsedModule.module); | ||||
|         return require(parsedModule.module + parsedModule.subpath); | ||||
|     } | ||||
|     if (!knownExternalModules[parsedModule.module]) { | ||||
|         const e = new Error("Module not allowed"); | ||||
| @@ -131,7 +131,7 @@ function importModule(module) { | ||||
|     const parsedModule = parseModuleName(module); | ||||
|  | ||||
|     if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) { | ||||
|         return import(parsedModule.module); | ||||
|         return import(parsedModule.module + parsedModule.subpath); | ||||
|     } | ||||
|     if (!knownExternalModules[parsedModule.module]) { | ||||
|         const e = new Error("Module not allowed"); | ||||
| @@ -152,12 +152,13 @@ function importModule(module) { | ||||
| } | ||||
|  | ||||
| function parseModuleName(module) { | ||||
|     var match = /((?:@[^/]+\/)?[^/@]+)(?:@([\s\S]+))?/.exec(module); | ||||
|     var match = /((?:@[^/]+\/)?[^/@]+)(\/[^/@]+)?(?:@([\s\S]+))?/.exec(module); | ||||
|     if (match) { | ||||
|         return { | ||||
|             spec: module, | ||||
|             module: match[1], | ||||
|             version: match[2], | ||||
|             subpath: match[2] || '', | ||||
|             version: match[3], | ||||
|             builtin: BUILTIN_MODULES.indexOf(match[1]) !== -1, | ||||
|             known: !!knownExternalModules[match[1]] | ||||
|         } | ||||
| @@ -283,6 +284,7 @@ async function installModule(moduleDetails) { | ||||
|             const runtimeInstalledModules = settings.get("modules") || {}; | ||||
|             runtimeInstalledModules[moduleDetails.module] = moduleDetails; | ||||
|             settings.set("modules",runtimeInstalledModules) | ||||
|             log.audit({event: "modules.install",module:moduleDetails.module, version:moduleDetails.version}); | ||||
|         }).catch(result => { | ||||
|             var output = result.stderr || result.toString(); | ||||
|             var e; | ||||
|   | ||||
| @@ -143,6 +143,12 @@ function loadModuleFiles(modules) { | ||||
|         return loadNodeSetList(pluginList); | ||||
|     }).then(function() { | ||||
|         return loadNodeSetList(nodeList); | ||||
|     }).then(function () { | ||||
|         if (settings.available()) { | ||||
|             return registry.saveNodeList(); | ||||
|         } else { | ||||
|             return | ||||
|         } | ||||
|     }) | ||||
| } | ||||
|  | ||||
| @@ -436,7 +442,7 @@ async function loadPlugin(plugin) { | ||||
|         return plugin; | ||||
|     } | ||||
| } | ||||
|  | ||||
| let invocation = 0 | ||||
| function loadNodeSetList(nodes) { | ||||
|     var promises = []; | ||||
|     nodes.forEach(function(node) { | ||||
| @@ -451,13 +457,7 @@ function loadNodeSetList(nodes) { | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     return Promise.all(promises).then(function() { | ||||
|         if (settings.available()) { | ||||
|             return registry.saveNodeList(); | ||||
|         } else { | ||||
|             return; | ||||
|         } | ||||
|     }); | ||||
|     return Promise.all(promises) | ||||
| } | ||||
|  | ||||
| function addModule(module) { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/registry", | ||||
|     "version": "3.1.0", | ||||
|     "version": "3.1.2", | ||||
|     "license": "Apache-2.0", | ||||
|     "main": "./lib/index.js", | ||||
|     "repository": { | ||||
| @@ -16,7 +16,7 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/util": "3.1.0", | ||||
|         "@node-red/util": "3.1.2", | ||||
|         "clone": "2.1.2", | ||||
|         "fs-extra": "11.1.1", | ||||
|         "semver": "7.5.4", | ||||
|   | ||||
| @@ -99,6 +99,9 @@ var api = module.exports = { | ||||
|                     safeSettings.markdownEditor = runtime.settings.editorTheme.markdownEditor || {}; | ||||
|                     safeSettings.markdownEditor.mermaid = safeSettings.markdownEditor.mermaid || { enabled: true }; | ||||
|                 } | ||||
|                 if (runtime.settings.editorTheme.mermaid) { | ||||
|                     safeSettings.mermaid = runtime.settings.editorTheme.mermaid | ||||
|                 } | ||||
|             } | ||||
|             safeSettings.libraries = runtime.library.getLibraries(); | ||||
|             if (util.isArray(runtime.settings.paletteCategories)) { | ||||
|   | ||||
| @@ -161,7 +161,8 @@ class Flow { | ||||
|             for (let i = 0; i < configNodes.length; i++) { | ||||
|                 const node = this.flow.configs[configNodes[i]] | ||||
|                 if (node.type === 'global-config' && node.env) { | ||||
|                     const nodeEnv = await flowUtil.evaluateEnvProperties(this, node.env, credentials.get(node.id)) | ||||
|                     const globalCreds = credentials.get(node.id)?.map || {} | ||||
|                     const nodeEnv = await flowUtil.evaluateEnvProperties(this, node.env, globalCreds) | ||||
|                     this._env = { ...this._env, ...nodeEnv } | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -73,9 +73,20 @@ class Subflow extends Flow { | ||||
|             id: subflowInstance.id, | ||||
|             configs: {}, | ||||
|             nodes: {}, | ||||
|             groups: {}, | ||||
|             subflows: {} | ||||
|         } | ||||
|  | ||||
|         if (subflowDef.groups) { | ||||
|             // Clone all of the subflow group definitions and give them new IDs | ||||
|             for (i in subflowDef.groups) { | ||||
|                 if (subflowDef.groups.hasOwnProperty(i)) { | ||||
|                     node = createNodeInSubflow(subflowInstance.id,subflowDef.groups[i]); | ||||
|                     node_map[node._alias] = node; | ||||
|                     subflowInternalFlowConfig.groups[node.id] = node; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (subflowDef.configs) { | ||||
|             // Clone all of the subflow config node definitions and give them new IDs | ||||
|             for (i in subflowDef.configs) { | ||||
| @@ -101,6 +112,7 @@ class Subflow extends Flow { | ||||
|  | ||||
|         remapSubflowNodes(subflowInternalFlowConfig.configs,node_map); | ||||
|         remapSubflowNodes(subflowInternalFlowConfig.nodes,node_map); | ||||
|         remapSubflowNodes(subflowInternalFlowConfig.groups,node_map); | ||||
|  | ||||
|         // console.log("Instance config\n",JSON.stringify(subflowInternalFlowConfig,"",2)); | ||||
|  | ||||
| @@ -237,7 +249,7 @@ class Subflow extends Flow { | ||||
|                     for (j=0;j<wires.length;j++) { | ||||
|                         if (wires[j].id != self.subflowDef.id) { | ||||
|                             node = self.node_map[wires[j].id]; | ||||
|                             if (node._originalWires) { | ||||
|                             if (node && node._originalWires) { | ||||
|                                 node.wires = clone(node._originalWires); | ||||
|                             } | ||||
|                         } | ||||
| @@ -254,8 +266,10 @@ class Subflow extends Flow { | ||||
|                             subflowInstanceModified = true; | ||||
|                         } else { | ||||
|                             node = self.node_map[wires[j].id]; | ||||
|                             node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]); | ||||
|                             modifiedNodes[node.id] = node; | ||||
|                             if (node) { | ||||
|                                 node.wires[wires[j].port] = node.wires[wires[j].port].concat(newWires[i]); | ||||
|                                 modifiedNodes[node.id] = node; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| @@ -283,10 +297,14 @@ class Subflow extends Flow { | ||||
|                         this.node._updateWires(subflowInstanceConfig.wires); | ||||
|                     } else { | ||||
|                         var node = self.node_map[wires[j].id]; | ||||
|                         if (!node._originalWires) { | ||||
|                             node._originalWires = clone(node.wires); | ||||
|                         if (node) { | ||||
|                             if (!node._originalWires) { | ||||
|                                 node._originalWires = clone(node.wires); | ||||
|                             } | ||||
|                             node.wires[wires[j].port] = (node.wires[wires[j].port]||[]).concat(this.subflowInstance.wires[i]); | ||||
|                         } else { | ||||
|                             this.error("Unknown node referenced inside subflow: " + wires[j].id) | ||||
|                         } | ||||
|                         node.wires[wires[j].port] = (node.wires[wires[j].port]||[]).concat(this.subflowInstance.wires[i]); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -302,11 +320,15 @@ class Subflow extends Flow { | ||||
|                     this.node._updateWires(subflowInstanceConfig.wires); | ||||
|                 } else { | ||||
|                     var node = self.node_map[wires[j].id]; | ||||
|                     if (!node._originalWires) { | ||||
|                         node._originalWires = clone(node.wires); | ||||
|                     if (node) { | ||||
|                         if (!node._originalWires) { | ||||
|                             node._originalWires = clone(node.wires); | ||||
|                         } | ||||
|                         node.wires[wires[j].port] = (node.wires[wires[j].port]||[]); | ||||
|                         node.wires[wires[j].port].push(subflowStatusId); | ||||
|                     } else { | ||||
|                         this.error("Unknown node referenced inside subflow: " + wires[j].id) | ||||
|                     } | ||||
|                     node.wires[wires[j].port] = (node.wires[wires[j].port]||[]); | ||||
|                     node.wires[wires[j].port].push(subflowStatusId); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -57,18 +57,20 @@ var EnvVarPropertyRE = /^\${(\S+)}$/; | ||||
|  | ||||
|  | ||||
| function mapEnvVarProperties(obj,prop,flow,config) { | ||||
|     var v = obj[prop]; | ||||
|     const v = obj[prop]; | ||||
|     if (Buffer.isBuffer(v)) { | ||||
|         return; | ||||
|     } else if (Array.isArray(v)) { | ||||
|         for (var i=0;i<v.length;i++) { | ||||
|         for (let i=0;i<v.length;i++) { | ||||
|             mapEnvVarProperties(v,i,flow,config); | ||||
|         } | ||||
|     } else if (typeof obj[prop] === 'string') { | ||||
|         if (obj[prop][0] === "$" && (EnvVarPropertyRE_old.test(v) || EnvVarPropertyRE.test(v)) ) { | ||||
|             var envVar = v.substring(2,v.length-1); | ||||
|             var r = redUtil.getSetting(config, envVar, flow); | ||||
|             obj[prop] = r ? r : obj[prop]; | ||||
|             const envVar = v.substring(2,v.length-1); | ||||
|             const r = redUtil.getSetting(config, envVar, flow); | ||||
|             if (r !== undefined && r !== '') { | ||||
|                 obj[prop] = r | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         for (var p in v) { | ||||
| @@ -80,6 +82,7 @@ function mapEnvVarProperties(obj,prop,flow,config) { | ||||
| } | ||||
|  | ||||
| async function evaluateEnvProperties(flow, env, credentials) { | ||||
|     credentials = credentials || {} | ||||
|     const pendingEvaluations = [] | ||||
|     const evaluatedEnv = {} | ||||
|     const envTypes = [] | ||||
| @@ -112,6 +115,7 @@ async function evaluateEnvProperties(flow, env, credentials) { | ||||
|     if (pendingEvaluations.length > 0) { | ||||
|         await Promise.all(pendingEvaluations) | ||||
|     } | ||||
|     // Now loop over the env types and evaluate them properly | ||||
|     for (let i = 0; i < envTypes.length; i++) { | ||||
|         let { name, value, type } = envTypes[i] | ||||
|         // If an env-var wants to lookup itself, delegate straight to the parent | ||||
| @@ -122,7 +126,17 @@ async function evaluateEnvProperties(flow, env, credentials) { | ||||
|         if (evaluatedEnv.hasOwnProperty(value)) { | ||||
|             value = evaluatedEnv[value] | ||||
|         } else { | ||||
|             value = redUtil.evaluateNodeProperty(value, type, {_flow: flow}, null, null); | ||||
|             value = redUtil.evaluateNodeProperty(value, type, {_flow: { | ||||
|                 // Provide a hook so when it tries to look up a flow setting, | ||||
|                 // we can insert the just-evaluated value which hasn't yet | ||||
|                 // been set on the flow object - otherwise delegate up to the flow | ||||
|                 getSetting: function(name) { | ||||
|                     if (evaluatedEnv.hasOwnProperty(name)){ | ||||
|                         return evaluatedEnv[name] | ||||
|                     } | ||||
|                     return flow.getSetting(name) | ||||
|                 } | ||||
|             }}, null, null); | ||||
|         } | ||||
|         evaluatedEnv[name] = value | ||||
|     } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/runtime", | ||||
|     "version": "3.1.0", | ||||
|     "version": "3.1.2", | ||||
|     "license": "Apache-2.0", | ||||
|     "main": "./lib/index.js", | ||||
|     "repository": { | ||||
| @@ -16,8 +16,8 @@ | ||||
|         } | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/registry": "3.1.0", | ||||
|         "@node-red/util": "3.1.0", | ||||
|         "@node-red/registry": "3.1.2", | ||||
|         "@node-red/util": "3.1.2", | ||||
|         "async-mutex": "0.4.0", | ||||
|         "clone": "2.1.2", | ||||
|         "express": "4.18.2", | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "@node-red/util", | ||||
|     "version": "3.1.0", | ||||
|     "version": "3.1.2", | ||||
|     "license": "Apache-2.0", | ||||
|     "repository": { | ||||
|         "type": "git", | ||||
|   | ||||
							
								
								
									
										12
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								packages/node_modules/node-red/package.json
									
									
									
									
										vendored
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|     "name": "node-red", | ||||
|     "version": "3.1.0", | ||||
|     "version": "3.1.2", | ||||
|     "description": "Low-code programming for event-driven applications", | ||||
|     "homepage": "https://nodered.org", | ||||
|     "license": "Apache-2.0", | ||||
| @@ -31,15 +31,15 @@ | ||||
|         "flow" | ||||
|     ], | ||||
|     "dependencies": { | ||||
|         "@node-red/editor-api": "3.1.0", | ||||
|         "@node-red/runtime": "3.1.0", | ||||
|         "@node-red/util": "3.1.0", | ||||
|         "@node-red/nodes": "3.1.0", | ||||
|         "@node-red/editor-api": "3.1.2", | ||||
|         "@node-red/runtime": "3.1.2", | ||||
|         "@node-red/util": "3.1.2", | ||||
|         "@node-red/nodes": "3.1.2", | ||||
|         "basic-auth": "2.0.1", | ||||
|         "bcryptjs": "2.4.3", | ||||
|         "express": "4.18.2", | ||||
|         "fs-extra": "11.1.1", | ||||
|         "node-red-admin": "^3.1.0", | ||||
|         "node-red-admin": "^3.1.1", | ||||
|         "nopt": "5.0.0", | ||||
|         "semver": "7.5.4" | ||||
|     }, | ||||
|   | ||||
| @@ -26,6 +26,7 @@ var flowUtils = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/util"); | ||||
| var Flow = NR_TEST_UTILS.require("@node-red/runtime/lib/flows/Flow"); | ||||
| var flows = NR_TEST_UTILS.require("@node-red/runtime/lib/flows"); | ||||
| var Node = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/Node"); | ||||
| var credentials = NR_TEST_UTILS.require("@node-red/runtime/lib/nodes/credentials"); | ||||
| var hooks = NR_TEST_UTILS.require("@node-red/util/lib/hooks"); | ||||
| var typeRegistry = NR_TEST_UTILS.require("@node-red/registry"); | ||||
|  | ||||
| @@ -61,6 +62,7 @@ describe('Flow', function() { | ||||
|         this.scope = n.scope; | ||||
|         var node = this; | ||||
|         this.foo = n.foo; | ||||
|         this.bar = n.bar; | ||||
|         this.handled = 0; | ||||
|         this.stopped = false; | ||||
|         currentNodes[node.id] = node; | ||||
| @@ -1235,11 +1237,12 @@ describe('Flow', function() { | ||||
|     }) | ||||
|  | ||||
|     describe("#env", function () { | ||||
|         afterEach(() => { | ||||
|             delete process.env.V0; | ||||
|             delete process.env.V1; | ||||
|             credentials.get.restore?.() | ||||
|         }) | ||||
|         it("can instantiate a node with environment variable property values of group and tab", async function () { | ||||
|             after(function() { | ||||
|                 delete process.env.V0; | ||||
|                 delete process.env.V1; | ||||
|             }) | ||||
|             process.env.V0 = "gv0"; | ||||
|             process.env.V1 = "gv1"; | ||||
|             process.env.V3 = "gv3"; | ||||
| @@ -1283,10 +1286,6 @@ describe('Flow', function() { | ||||
|         }); | ||||
|  | ||||
|         it("can access environment variable property using $parent", async function () { | ||||
|             after(function() { | ||||
|                 delete process.env.V0; | ||||
|                 delete process.env.V1; | ||||
|             }) | ||||
|             process.env.V0 = "gv0"; | ||||
|             process.env.V1 = "gv1"; | ||||
|             var config = flowUtils.parseConfig([ | ||||
| @@ -1321,9 +1320,6 @@ describe('Flow', function() { | ||||
|         }); | ||||
|  | ||||
|         it("can define environment variable using JSONata", async function () { | ||||
|             after(function() { | ||||
|                 delete process.env.V0; | ||||
|             }) | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab",env:[ | ||||
|                     {"name": "V0", value: "1+2", type: "jsonata"} | ||||
| @@ -1346,9 +1342,6 @@ describe('Flow', function() { | ||||
|         }); | ||||
|  | ||||
|         it("can access global environment variables defined as JSONata values", async function () { | ||||
|             after(function() { | ||||
|                 delete process.env.V0; | ||||
|             }) | ||||
|             var config = flowUtils.parseConfig([ | ||||
|                 {id:"t1",type:"tab",env:[ | ||||
|                     {"name": "V0", value: "1+2", type: "jsonata"} | ||||
| @@ -1370,15 +1363,21 @@ describe('Flow', function() { | ||||
|             await flow.stop() | ||||
|         }); | ||||
|         it("global flow can access global-config defined environment variables", async function () { | ||||
|             after(function() { | ||||
|                 delete process.env.V0; | ||||
|             sinon.stub(credentials,"get").callsFake(function(id) { | ||||
|                 if (id === 'gc') { | ||||
|                     return { map: { GC_CRED: 'gc_cred' }} | ||||
|                 } | ||||
|                 return null | ||||
|             }) | ||||
|  | ||||
|             const config = flowUtils.parseConfig([ | ||||
|                 {id:"gc", type:"global-config", env:[ | ||||
|                     {"name": "GC0", value: "3+4", type: "jsonata"} | ||||
|                     {"name": "GC0", value: "3+4", type: "jsonata"}, | ||||
|                     {"name": "GC_CRED", type: "cred"}, | ||||
|                      | ||||
|                 ]}, | ||||
|                 {id:"t1",type:"tab" }, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"${GC0}",wires:[]}, | ||||
|                 {id:"1",x:10,y:10,z:"t1",type:"test",foo:"${GC0}", bar:"${GC_CRED}", wires:[]}, | ||||
|             ]); | ||||
|             // Two-arg call - makes this the global flow that handles global-config nodes | ||||
|             const globalFlow = Flow.create({getSetting:v=>process.env[v]},config); | ||||
| @@ -1390,6 +1389,7 @@ describe('Flow', function() { | ||||
|  | ||||
|             var activeNodes = flow.getActiveNodes(); | ||||
|             activeNodes["1"].foo.should.equal(7); | ||||
|             activeNodes["1"].bar.should.equal('gc_cred'); | ||||
|  | ||||
|             await flow.stop() | ||||
|             await globalFlow.stop() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user