diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 92a0abb8a..d673df676 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -30,5 +30,5 @@ the [forum](https://discourse.nodered.org) or - [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md) - [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team. -- [ ] I have run `grunt` to verify the unit tests pass +- [ ] I have run `npm run test` to verify the unit tests pass - [ ] I have added suitable unit tests to cover the new/changed functionality diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..ad3a4ca7a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "monthly" + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 85fc1f92a..5c34dee54 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,25 +14,25 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out node-red repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: path: 'node-red' - name: Check out node-red-docker repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: repository: 'node-red/node-red-docker' path: 'node-red-docker' - name: Check out node-red.github.io repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: repository: 'node-red/node-red.github.io' path: 'node-red.github.io' - - uses: actions/setup-node@v1 + - 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 @@ -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 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cdba11d2a..a20b8ae14 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,9 +19,9 @@ jobs: matrix: node-version: [16, 18, 20] steps: - - uses: actions/checkout@v3 + - 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 diff --git a/.gitignore b/.gitignore index d4c991688..6a2ebfaa1 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ docs .vscode .nyc_output sync.ffs_db +package-lock.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 26ec431bc..c01956782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,57 @@ +#### 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 95287d81f..f8fb04304 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,9 @@ behavior to the project's core team at team@nodered.org. Please raise any bug reports on the relevant project's issue tracker. Be sure to search the list to see if your issue has already been raised. +If your issue is more of a question on how to do something with Node-RED, please +consider using the [community forum](https://discourse.nodered.org/). + A good bug report is one that make it easy for us to understand what you were trying to do and what went wrong. @@ -35,14 +38,18 @@ For feature requests, please raise them on the [forum](https://discourse.nodered ## Pull-Requests If you want to raise a pull-request with a new feature, or a refactoring -of existing code, it may well get rejected if you haven't discussed it on -the [forum](https://discourse.nodered.org) first. +of existing code, please come and discuss it with us first. We prefer to +do it that way to make sure your time and effort is well spent on something +that fits with our goals. + +If you've got a bug-fix or similar for us, then you are most welcome to +get it raised - just make sure you link back to the issue it's fixing and +try to include some tests! All contributors need to sign the OpenJS Foundation's Contributor License Agreement. It is an online process and quick to do. If you raise a pull-request without having signed the CLA, you will be prompted to do so automatically. - ### Code Branches When raising a PR for a fix or a new feature, it is important to target the right branch. diff --git a/Gruntfile.js b/Gruntfile.js index 44f4c97f6..09b057837 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -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", diff --git a/package.json b/package.json index 2af24212d..2bd4091cc 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js index c3e8f975e..e5c3904c7 100644 --- a/packages/node_modules/@node-red/editor-api/lib/editor/theme.js +++ b/packages/node_modules/@node-red/editor-api/lib/editor/theme.js @@ -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; } diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 8742a6c6f..c0317c90e 100644 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -1215,11 +1215,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", diff --git a/packages/node_modules/@node-red/editor-client/locales/fr/editor.json b/packages/node_modules/@node-red/editor-client/locales/fr/editor.json index 46d85daa5..274cadb2a 100644 --- a/packages/node_modules/@node-red/editor-client/locales/fr/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/fr/editor.json @@ -119,7 +119,7 @@ "searchInput": "Rechercher vos flux", "subflows": "Sous-flux", "createSubflow": "Créer un sous-flux", - "selectionToSubflow": "Selection d'un sous-flux", + "selectionToSubflow": "Convertir en sous-flux", "flows": "Flux", "add": "Ajouter", "rename": "Renommer", @@ -274,23 +274,23 @@ "recoveredNodesInfo": "Les noeuds importés sur ce flux contiennent un mauvais identifiant de flux. Ces noeuds ont été ajoutés à ce flux afin que vous puissiez les restaurer ou les supprimer.", "recoveredNodesNotification": "

Noeuds importés sans identifiant de flux valide

Ils ont été ajoutés à un nouveau flux appelé '__flowName__'.

", "export": { - "selected": "noeuds sélectionnés", - "current": "flux actuel", + "selected": "les noeuds sélectionnés", + "current": "le flux actuel", "all": "tous les flux", - "compact": "condensé", - "formatted": "formaté", + "compact": "Condensé", + "formatted": "Formaté", "copy": "Copier dans le presse-papier", "export": "Exporter vers la bibliothèque", - "exportAs": "Exporter en tant que", + "exportAs": "Exporter comme", "overwrite": "Remplacer", "exists": "

\"__file__\" existe déjà.

Voulez-vous le remplacer ?

" }, "import": { "import": "Importer vers", - "importSelected": "Importation sélectionnée", + "importSelected": "Importer la sélection", "importCopy": "Importer une copie", - "viewNodes": "Afficher les noeuds...", - "newFlow": "Nouveau flux", + "viewNodes": "Vérifier ces noeuds", + "newFlow": "un nouveau flux", "replace": "Remplacer", "errors": { "notArray": "L'entrée n'est pas un tableau JSON", @@ -299,7 +299,7 @@ "missingType": "L'entrée n'est pas un flux valide - l'élément '__index__' n'a pas de propriété 'type'" }, "conflictNotification1": "Certains des noeuds que vous avez importés existent déjà dans votre espace de travail.", - "conflictNotification2": "Sélectionner les noeuds à importer et choisir s'il faut remplacer les noeuds existants ou en importer une copie." + "conflictNotification2": "Sélectionnez les noeuds à importer et choisissez s'il faut remplacer les noeuds existants ou en importer une copie." }, "copyMessagePath": "Chemin copié", "copyMessageValue": "Valeur copiée", @@ -391,10 +391,10 @@ "subflowInstances": "Il existe __count__ instance de ce modèle de sous-flux", "subflowInstances_plural": "Il existe __count__ instances de ce modèle de sous-flux", "editSubflowProperties": "modifier les propriétés", - "input": "entrées:", - "output": "sorties:", - "status": "statut du noeud", - "deleteSubflow": "supprimer le sous-flux", + "input": "Entrées:", + "output": "Sorties:", + "status": "Statut du noeud", + "deleteSubflow": "Supprimer le sous-flux", "confirmDelete": "Voulez-vous vraiment supprimer ce sous-flux ?", "info": "Description", "category": "Catégorie", @@ -416,6 +416,7 @@ }, "errors": { "noNodesSelected": "Impossible de créer un sous-flux : aucun noeud sélectionné", + "acrossMultipleGroups": "Impossible de créer un sous-flux sur plusieurs groupes", "multipleInputsToSelection": "Impossible de créer un sous-flux : plusieurs entrées pour la sélection" } }, @@ -447,8 +448,8 @@ "default": "Par défaut", "noDefaultLabel": "Aucune", "defaultLabel": "Utiliser l'étiquette par défaut", - "searchIcons": "Icônes de recherche", - "useDefault": "Utilisation par défaut", + "searchIcons": "Rechercher une icône", + "useDefault": "Icône par défaut", "description": "Description", "show": "Afficher", "hide": "Masquer", @@ -498,13 +499,13 @@ "keyboard": { "title": "Raccourcis clavier", "keyboard": "Clavier", - "filterActions": "Actions de filtrage", - "shortcut": "raccourci", - "scope": "portée", + "filterActions": "Rechercher l'action", + "shortcut": "Raccourci", + "scope": "Portée", "unassigned": "Non attribué", - "global": "global", - "workspace": "espace de travail", - "editor": "boîte de dialogue d'édition", + "global": "Global", + "workspace": "Espace de travail", + "editor": "Boîte de dialogue d'édition", "selectAll": "Tout sélectionner", "selectNone": "Ne rien sélectionner", "selectAllConnected": "Sélectionner tous les éléments connectés", @@ -550,22 +551,22 @@ }, "palette": { "noInfo": "Pas d'information disponible", - "filter": "Filtrer les noeuds", + "filter": "Rechercher le noeud", "search": "Rechercher les modules", "addCategory": "Ajouter un nouveau...", "label": { - "subflows": "sous-flux", - "network": "réseau", - "common": "commun", - "input": "entrée", - "output": "sortie", - "function": "fonction", - "sequence": "séquence", - "parser": "analyseur", - "social": "social", - "storage": "stockage", - "analysis": "analyse", - "advanced": "avancé" + "subflows": "Sous-flux", + "network": "Réseau", + "common": "Commun", + "input": "Entrée", + "output": "Sortie", + "function": "Fonction", + "sequence": "Séquence", + "parser": "Analyseur", + "social": "Social", + "storage": "Stockage", + "analysis": "Analyse", + "advanced": "Avancé" }, "actions": { "collapse-all": "Réduire toutes les catégories", @@ -586,6 +587,7 @@ "editor": { "title": "Gérer la palette", "palette": "Palette", + "allCatalogs": "Tous les catalogues", "times": { "seconds": "il y a quelques secondes", "minutes": "il y a quelques minutes", @@ -609,24 +611,25 @@ "nodeCount_plural": "__label__ noeuds", "moduleCount": "__count__ module disponible", "moduleCount_plural": "__count__ modules disponibles", - "inuse": "en cours d'utilisation", - "enableall": "activer tout", - "disableall": "désactiver tout", - "enable": "activer", - "disable": "désactiver", - "remove": "supprimer", - "update": "mettre à jour vers __version__", - "updated": "mis à jour", - "install": "installer", - "installed": "installé", - "conflict": "conflit", + "inuse": "En cours d'utilisation", + "enableall": "Activer tout", + "disableall": "Désactiver tout", + "enable": "Activer", + "disable": "Désactiver", + "remove": "Supprimer", + "update": "Mettre à jour vers __version__", + "updated": "Mis à jour", + "install": "Installer", + "installed": "Installé", + "conflict": "Conflit", "conflictTip": "

Ce module ne peut pas être installé car il inclut un
type de noeud qui a déjà été installé

Conflits avec __module__

", "loading": "Chargement des catalogues...", "tab-nodes": "Noeuds", "tab-install": "Installer", - "sort": "trier:", - "sortAZ": "a-z", - "sortRecent": "récent", + "sort": "Trier:", + "sortRelevance": "Pertinence", + "sortAZ": "A-Z", + "sortRecent": "Récent", "more": "+ __count__ en plus", "upload": "Charger le fichier tgz du module", "refresh": "Actualiser la liste des modules", @@ -667,7 +670,7 @@ "info": { "name": "Information", "tabName": "Nom", - "label": "info", + "label": "Info", "node": "Noeud", "type": "Type", "group": "Groupe", @@ -681,10 +684,10 @@ "properties": "Propriétés", "info": "Information", "desc": "Description", - "blank": "vide", - "null": "nul", - "showMore": "afficher en plus", - "showLess": "afficher en moins", + "blank": "Vide", + "null": "Nul", + "showMore": "Afficher en plus", + "showLess": "Afficher en moins", "flow": "Flux", "selection": "Sélection", "nodes": "__count__ noeuds", @@ -695,7 +698,7 @@ "arrayItems": "__count__ éléments", "showTips": "Vous pouvez ouvrir les astuces à partir du panneau des paramètres", "outline": "Plan", - "empty": "vide", + "empty": "Vide", "globalConfig": "Noeuds de configuration globale", "triggerAction": "Déclencher une action", "find": "Rechercher dans l'espace de travail", @@ -706,7 +709,7 @@ }, "help": { "name": "Aide", - "label": "aide", + "label": "Aide", "search": "Aide à la recherche", "nodeHelp": "Aide sur les noeuds", "showHelp": "Afficher l'aide", @@ -717,23 +720,23 @@ }, "config": { "name": "Noeuds de configuration", - "label": "configuration", + "label": "Configuration", "global": "Tous les flux", - "none": "aucun", - "subflows": "sous-flux", - "flows": "flux", - "filterAll": "tout", + "none": "Aucun", + "subflows": "Sous-flux", + "flows": "Flux", + "filterAll": "Tout", "showAllConfigNodes": "Afficher tous les noeuds de configuration", - "filterUnused": "inutilisé", + "filterUnused": "Inutilisé", "showAllUnusedConfigNodes": "Afficher tous les noeuds de configuration inutilisés", "filtered": "__count__ caché(s)" }, "context": { "name": "Données contextuelles", - "label": "contexte", - "none": "aucune sélection", - "refresh": "actualiser pour charger", - "empty": "vide", + "label": "Contexte", + "none": "Aucune sélection", + "refresh": "Actualiser pour charger", + "empty": "Vide", "node": "Noeud", "flow": "Flux", "global": "Global", @@ -744,10 +747,10 @@ }, "palette": { "name": "Gestion des palettes", - "label": "palette" + "label": "Palette" }, "project": { - "label": "projet", + "label": "Projet", "name": "Projet", "description": "Description", "dependencies": "Dépendances", @@ -760,11 +763,11 @@ "showProjectSettings": "Afficher les paramètres du projet", "projectSettings": { "title": "Paramètres du projet", - "edit": "modifier", + "edit": "Modifier", "none": "Vide", - "install": "installer", - "removeFromProject": "supprimer du projet", - "addToProject": "ajouter au projet", + "install": "Installer", + "removeFromProject": "Supprimer du projet", + "addToProject": "Ajouter au projet", "files": "Fichiers", "flow": "Flux", "credentials": "Identifiants", @@ -812,7 +815,7 @@ "workflowAutoTip": "Les modifications sont validées automatiquement à chaque déploiement", "sshKeys": "Clés SSH", "sshKeysTip": "Vous permet de créer des connexions sécurisées aux référentiels Git distants.", - "add": "ajouter une clé", + "add": "Ajouter une clé", "addSshKey": "Ajouter une clé SSH", "addSshKeyTip": "Générer une nouvelle paire de clés publique/privée", "name": "Nom", @@ -848,7 +851,7 @@ "none": "Vide", "conflictResolve": "Tous les conflits ont été résolus. Valider les modifications pour terminer la fusion.", "localFiles": "Fichiers locaux", - "all": "tout", + "all": "Tout", "unmergedChanges": "Modifications non fusionnées", "abortMerge": "Abandonner la fusion", "commit": "Valider", @@ -1097,9 +1100,9 @@ "desc8": "Le fichier contenant les identifiants ne sera pas crypté et son contenu sera facilement lisible", "create-project-files": "Créer des fichiers de projet", "create-project": "Créer un projet", - "already-exists": "existe déjà", + "already-exists": "Existe déjà", "git-error": "Erreur Git", - "git-auth-error": "erreur d'authentification Git" + "git-auth-error": "Erreur d'authentification Git" }, "create-success": { "success": "Vous avez créé avec succès votre premier projet !", @@ -1135,8 +1138,8 @@ "desc2": "Avant de pouvoir cloner un référentiel sur ssh, vous devez ajouter une clé SSH pour y accéder.", "add-ssh-key": "Ajouter une clé ssh", "credentials-encryption-key": "Clé de chiffrement des identifiants", - "already-exists-2": "existe déjà", - "git-error": "erreur git", + "already-exists-2": "Existe déjà", + "git-error": "Erreur git", "con-failed": "La connexion a échoué", "not-git": "Ce n'est pas un dépôt git", "no-resource": "Référentiel introuvable", @@ -1148,8 +1151,8 @@ "confirm": "Voulez-vous vraiment supprimer ce projet ?" }, "create-project-list": { - "search": "rechercher vos projets", - "current": "actuel" + "search": "Rechercher vos projets", + "current": "Actuel" }, "require-clean": { "confirm": "

Vous avez des modifications non déployées qui seront perdues.

Voulez-vous continuer ?

" @@ -1212,11 +1215,8 @@ "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-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", diff --git a/packages/node_modules/@node-red/editor-client/locales/fr/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/fr/jsonata.json index ea7aff815..fca57953a 100644 --- a/packages/node_modules/@node-red/editor-client/locales/fr/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/fr/jsonata.json @@ -270,5 +270,9 @@ "$moment": { "args": "[str]", "desc": "Obtient un objet de date à l'aide de la bibliothèque Moment." + }, + "$clone": { + "args": "valeur", + "desc": "Cloner un objet en toute sécurité." } } diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json index 42257f6b0..ceb001a10 100644 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -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__: プロパティが未設定", diff --git a/packages/node_modules/@node-red/editor-client/locales/pt-BR/editor.json b/packages/node_modules/@node-red/editor-client/locales/pt-BR/editor.json index 6e20bc32d..f65ec62e9 100644 --- a/packages/node_modules/@node-red/editor-client/locales/pt-BR/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/pt-BR/editor.json @@ -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", diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json index e55240cc5..afc63e4cb 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json @@ -1199,11 +1199,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__: 缺少属性值", diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 2a14a0244..bf1faf0a4 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -797,8 +797,8 @@ RED.nodes = (function() { if (node && node._def.onremove) { // Deprecated: never documented but used by some early nodes - console.log("Deprecated API warning: node type ",node.type," has an onremove function - should be oneditremove - please report"); - node._def.onremove.call(n); + console.log("Deprecated API warning: node type ",node.type," has an onremove function - should be oneditdelete - please report"); + node._def.onremove.call(node); } return {links:removedLinks,nodes:removedNodes}; } @@ -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); diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 353c2effd..3878965e3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -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 = ""; - 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 = ""; + RED.notify(RED._("palette.event.nodeRemoved", {count:pendingNodeRemovedNotifications.length})+typeList,"success"); + pendingNodeRemovedNotifications = [] + }, 200) } } loadIconList(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js index 7440b464e..9aa27c710 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/typedInput.js @@ -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", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js index b63cdc1a2..f53e7458e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/contextMenu.js @@ -168,8 +168,8 @@ RED.contextMenu = (function () { menuItems.push( null, - { onselect: 'core:undo', disabled: RED.history.list().length === 0 }, - { onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 }, + { onselect: 'core:undo', label: RED._("keyboard.undoChange"), disabled: RED.history.list().length === 0 }, + { onselect: 'core:redo', label: RED._("keyboard.redoChange"), disabled: RED.history.listRedo().length === 0 }, null, { 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 }, @@ -177,7 +177,7 @@ RED.contextMenu = (function () { { onselect: 'core:delete-selection', 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' }, + { onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") }, ) } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js b/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js index b6a069ab5..3f73e29aa 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js @@ -989,9 +989,10 @@ RED.diff = (function() { } if (localNode && remoteNode && typeof localNode[d] === "string") { if (/\n/.test(localNode[d]) || /\n/.test(remoteNode[d])) { - $('').on("click", function() { + var textDiff = $('').on("click", function() { showTextDiff(localNode[d],remoteNode[d]); }).appendTo(propertyNameCell); + RED.popover.tooltip(textDiff, RED._("diff.compareChanges")); } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js index 0ef3892e7..4908373c7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editor.js @@ -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_") { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/buffer.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/buffer.js index f47ec508b..03a50e60b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/buffer.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/buffer.js @@ -121,7 +121,7 @@ var i=0,l=bufferBinValue.length; var c = 0; for(i=0;i 255)) { valid = false; break; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js index b18e01fbb..eedc99c88 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/code-editors/monaco.js @@ -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 diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js index bd7a11b3f..c4d7bf26d 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/markdown.js @@ -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", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js new file mode 100644 index 000000000..0c1597919 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js @@ -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) { + $('
').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, + }; +})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js index e1e6b2ba3..d6dd5112d 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/panes/appearance.js @@ -196,7 +196,7 @@ } $('
'+ - ''+ + ''+ ''+ ''+ '
').appendTo(dialogForm); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/mermaid.js b/packages/node_modules/@node-red/editor-client/src/js/ui/mermaid.js deleted file mode 100644 index d126cf188..000000000 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/mermaid.js +++ /dev/null @@ -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, - }; -})(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js index b8f1558a6..37a5ef1a8 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js @@ -166,7 +166,7 @@ RED.projects.settings = (function() { var description = addTargetToExternalLinks($(''+desc+'')).appendTo(container); description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "" ); setTimeout(function () { - mermaid.init(); + RED.editor.mermaid.render() }, 200); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js index 5512926be..afdec2273 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/tab-versionControl.js @@ -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); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js index ee4bad2d8..bf66611b1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-help.js @@ -383,6 +383,7 @@ RED.sidebar.help = (function() { $(this).toggleClass('expanded',!isExpanded); }) helpSection.parent().scrollTop(0); + RED.editor.mermaid.render() } function set(html,title) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index e38b0b67e..f72a7b3f2 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -464,7 +464,7 @@ RED.sidebar.info = (function() { } $(this).toggleClass('expanded',!isExpanded); }); - mermaid.init(); + RED.editor.mermaid.render() } var tips = (function() { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js index 4f47d4674..872169828 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/typeSearch.js @@ -323,7 +323,7 @@ RED.typeSearch = (function() { } } function applyFilter(filter,type,def) { - return !filter || + return !def || !filter || ( (!filter.spliceMultiple) && (!filter.type || type === filter.type) && diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index 4d8ccdd9d..6475b19f5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -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 `
${code}
`; - } - else { - return `
${RED._("markdownEditor.mermaid.summary")}
${code}
`; - } - } - else { + return `
${code}
`; + } else { return "
" +code +"
"; } }; @@ -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 } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 66c87b1a9..56a29e421 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -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; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index 1a71577ce..2fa78f679 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js @@ -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); diff --git a/packages/node_modules/@node-red/editor-client/src/js/validators.js b/packages/node_modules/@node-red/editor-client/src/js/validators.js index 4fb384cbf..43ac90270 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/validators.js +++ b/packages/node_modules/@node-red/editor-client/src/js/validators.js @@ -40,46 +40,29 @@ 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 + } + 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 + } } -}; +}; \ No newline at end of file diff --git a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss index bd83e3eff..723e8ac79 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/flow.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/flow.scss @@ -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; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss index a32fd53b4..f6bd57375 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/projects.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/projects.scss @@ -825,6 +825,7 @@ div.red-ui-projects-dialog-ssh-public-key { margin-top: 0 !important; padding: 5px 10px; margin-bottom: 10px; + border-radius: 3px 3px 0px 0px; } } diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.html b/packages/node_modules/@node-red/nodes/core/common/20-inject.html index 0fafa9df0..d50725d51 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.html +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.html @@ -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; diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.js b/packages/node_modules/@node-red/nodes/core/common/20-inject.js index cde9b2141..6973ebf1b 100644 --- a/packages/node_modules/@node-red/nodes/core/common/20-inject.js +++ b/packages/node_modules/@node-red/nodes/core/common/20-inject.js @@ -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) { diff --git a/packages/node_modules/@node-red/nodes/core/common/21-debug.html b/packages/node_modules/@node-red/nodes/core/common/21-debug.html index 88d51b283..1c33ad848 100644 --- a/packages/node_modules/@node-red/nodes/core/common/21-debug.html +++ b/packages/node_modules/@node-red/nodes/core/common/21-debug.html @@ -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); diff --git a/packages/node_modules/@node-red/nodes/core/common/60-link.html b/packages/node_modules/@node-red/nodes/core/common/60-link.html index c57827149..73ce86ccf 100644 --- a/packages/node_modules/@node-red/nodes/core/common/60-link.html +++ b/packages/node_modules/@node-red/nodes/core/common/60-link.html @@ -275,7 +275,7 @@ value: [], type: "link in[]", validate: function (v, opt) { - if ((this.linkType === "static" && v.length > 0) + if (((this.linkType || "static") === "static" && v.length > 0) || this.linkType === "dynamic") { return true; } diff --git a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js index 70dc33605..a4cd4c68d 100644 --- a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js +++ b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js @@ -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 } })(); diff --git a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug.js b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug.js index 792d9895a..96f31bc81 100644 --- a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug.js +++ b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug.js @@ -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) { diff --git a/packages/node_modules/@node-red/nodes/core/function/10-switch.html b/packages/node_modules/@node-red/nodes/core/function/10-switch.html index c10a53827..01ec78e19 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-switch.html +++ b/packages/node_modules/@node-red/nodes/core/function/10-switch.html @@ -103,7 +103,6 @@ } else if (type === "istype") { r.v = rule.find(".node-input-rule-type-value").typedInput('type'); r.vt = rule.find(".node-input-rule-type-value").typedInput('type'); - r.vt = (r.vt === "number") ? "num" : "str"; } else if (type === "jsonata_exp") { r.v = rule.find(".node-input-rule-exp-value").typedInput('value'); r.vt = rule.find(".node-input-rule-exp-value").typedInput('type'); @@ -168,7 +167,33 @@ 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 0) { var lastRule = $("#node-input-rule-container").editableList('getItemAt',i-1); var exportedRule = exportRule(lastRule.element); - opt.r.vt = exportedRule.vt; + if (exportedRule.t === "istype") { + opt.r.vt = (exportedRule.vt === "number") ? "num" : "str"; + } else { + opt.r.vt = exportedRule.vt; + } opt.r.v = ""; // We could copy the value over as well and preselect it (see the 'activeElement' code below) // But not sure that feels right. Is copying over the last value 'expected' behaviour? diff --git a/packages/node_modules/@node-red/nodes/core/function/15-change.html b/packages/node_modules/@node-red/nodes/core/function/15-change.html index e53f522d9..d6106be1c 100644 --- a/packages/node_modules/@node-red/nodes/core/function/15-change.html +++ b/packages/node_modules/@node-red/nodes/core/function/15-change.html @@ -19,71 +19,42 @@ diff --git a/packages/node_modules/@node-red/nodes/locales/fr/messages.json b/packages/node_modules/@node-red/nodes/locales/fr/messages.json index 7890ce9a2..c56cc28f1 100644 --- a/packages/node_modules/@node-red/nodes/locales/fr/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/fr/messages.json @@ -11,11 +11,11 @@ "expand": "Développer" }, "status": { - "connected": "connecté", - "not-connected": "pas connecté", - "disconnected": "déconnecté", - "connecting": "connexion", - "error": "erreur", + "connected": "Connecté", + "not-connected": "Pas connecté", + "disconnected": "Déconnecté", + "connecting": "Connexion", + "error": "Erreur", "ok": "OK" }, "notification": { @@ -32,7 +32,7 @@ }, "inject": { "inject": "Injecter", - "injectNow": "injecter maintenant", + "injectNow": "Injecter maintenant", "repeat": "répéter = __repeat__", "crontab": "crontab = __crontab__", "stopped": "arrêté", @@ -98,7 +98,7 @@ "catchUncaught": "catch : non capturé", "label": { "source": "Détecter les erreurs de", - "selectAll": "tout sélectionner", + "selectAll": "Tout sélectionner", "uncaught": "Ignorer les erreurs gérées par les autres noeuds Catch" }, "scope": { @@ -112,7 +112,7 @@ "statusNodes": "statut : __number__", "label": { "source": "Signaler l'état de", - "sortByType": "trier par type" + "sortByType": "Trier par type" }, "scope": { "all": "tous les noeuds", @@ -148,23 +148,23 @@ "deactivated": "Désactivé avec succès : __label__" }, "sidebar": { - "label": "débogage", + "label": "Débogage", "name": "Messages de débogage", - "filterAll": "tous les noeuds", - "filterSelected": "noeuds sélectionnés", - "filterCurrent": "flux actuel", + "filterAll": "Tous les noeuds", + "filterSelected": "Noeuds sélectionnés", + "filterCurrent": "Flux actuel", "debugNodes": "noeuds de débogage", - "clearLog": "Effacer les messages", - "clearFilteredLog": "Effacer les messages filtrés", + "clearLog": "Tous les messages", + "clearFilteredLog": "Les messages filtrés", "filterLog": "Filtrer les messages", "openWindow": "Ouvrir dans une nouvelle fenêtre", "copyPath": "Copier le chemin", "copyPayload": "Copier la valeur", "pinPath": "Épingler le chemin", - "selectAll": "tout sélectionner", - "selectNone": "ne rien sélectionner", - "all": "tout", - "filtered": "filtré" + "selectAll": "Tout sélectionner", + "selectNone": "Ne rien sélectionner", + "all": "Tout", + "filtered": "Filtrés" }, "messageMenu": { "collapseAll": "Réduire tous les chemins", @@ -177,11 +177,11 @@ "linkIn": "Lien entrant", "linkOut": "Lien sortant", "linkCall": "Appel de lien", - "linkOutReturn": "retour de lien", + "linkOutReturn": "Retour de lien", "outMode": "Mode", "sendToAll": "Envoyer à tous les noeuds de liaison connectés", "returnToCaller": "Retour au noeud de liaison appelant", - "timeout": "temps mort", + "timeout": "Temps mort", "linkCallType": "Type de liaison", "staticLinkCall": "Lien fixe", "dynamicLinkCall": "Lien dynamique (msg.target)", @@ -225,7 +225,7 @@ "command": "Commande", "append": "Joindre", "timeout": "Temps mort", - "timeoutplace": "facultatif", + "timeoutplace": "Facultatif", "return": "Sortie", "seconds": "secondes", "stdout": "stdout", @@ -234,7 +234,7 @@ "winHide": "Masquer la console" }, "placeholder": { - "extraparams": "paramètres d'entrée supplémentaires" + "extraparams": "Paramètres d'entrée supplémentaires" }, "opt": { "exec": "lorsque la commande est terminée - mode exec", @@ -319,7 +319,7 @@ "queuemsg": "Mettre en file d'attente les messages intermédiaires", "dropmsg": "Supprimer les messages intermédiaires", "sendmsg": "Envoyer les messages intermédiaires sur la 2ème sortie", - "allowrate": "autoriser msg.rate (en ms) à remplacer le débit", + "allowrate": "Autoriser msg.rate (en ms) à remplacer le débit", "label": { "delay": "retard", "variable": "variable", @@ -349,7 +349,7 @@ } }, "errors": { - "too-many": "trop de messages en attente dans le noeud 'Delay'", + "too-many": "Trop de messages en attente dans le noeud 'Delay'", "invalid-timeout": "Valeur de délai invalide", "invalid-rate": "Valeur de taux invalide", "invalid-rate-unit": "Valeur de débit invalide", @@ -359,8 +359,8 @@ }, "trigger": { "send": "Envoyer", - "then": "puis", - "then-send": "puis envoyer", + "then": "Puis", + "then-send": "Puis envoyer", "output": { "string": "la chaîne", "number": "le nombre", @@ -381,9 +381,9 @@ "m": "Minutes", "h": "Heures" }, - "extend": " prolonger le délai si un nouveau message arrive", - "override": "remplacer le délai avec msg.delay", - "second": " envoyer un deuxième message à une sortie séparée", + "extend": " Prolonger le délai si un nouveau message arrive", + "override": "Remplacer le délai avec msg.delay", + "second": " Envoyer un deuxième message à une sortie séparée", "label": { "trigger": "déclencher", "trigger-block": "déclencher et bloquer", @@ -408,7 +408,7 @@ "mqtt": { "label": { "broker": "Serveur", - "example": "par exemple. localhost", + "example": "expl. localhost", "output": "Sortie", "qos": "QoS", "retain": "Conserver", @@ -438,7 +438,7 @@ "sessionExpiry": "Expiration de la session (secondes)", "topicAlias": "Alias", "payloadFormatIndicator": "Formater", - "payloadFormatIndicatorFalse": "octets non spécifiés (par défaut)", + "payloadFormatIndicatorFalse": "Octets non spécifiés (par défaut)", "payloadFormatIndicatorTrue": "Charge utile encodée en UTF-8", "protocolVersion": "Protocole", "protocolVersion3": "MQTT V3.1 (hérité)", @@ -493,8 +493,8 @@ "false": "faux", "tip": "Conseil : laisser le sujet, le qos ou le contenu vide si vous souhaitez les définir via les propriétés du msg.", "errors": { - "not-defined": "sujet non défini", - "missing-config": "configuration du courtier manquante", + "not-defined": "Sujet non défini", + "missing-config": "Configuration du courtier manquante", "invalid-topic": "Sujet invalide spécifié", "nonclean-missingclientid": "Aucun ID client défini, utilisation d'une session propre", "invalid-json-string": "Chaîne JSON invalide", @@ -514,7 +514,7 @@ "upload": "Accepter les téléchargements de fichiers ?", "status": "Code d'état", "headers": "En-têtes", - "other": "autre", + "other": "Autre", "paytoqs": { "ignore": "Ignorer", "query": "Joindre aux paramètres de chaîne de requête", @@ -625,7 +625,7 @@ "chars": "caractères", "close": "Fermer", "optional": "(facultatif)", - "reattach": "rattacher le délimiteur" + "reattach": "Rattacher le délimiteur" }, "type": { "listen": "Écoute sur", @@ -633,8 +633,8 @@ "reply": "Répondre sur TCP" }, "output": { - "stream": "flux de", - "single": "unique", + "stream": "Flux de", + "single": "Unique", "buffer": "Tampon", "string": "Chaîne", "base64": "Chaîne en Base64" @@ -657,15 +657,15 @@ "connections_plural": "__count__ connexions" }, "errors": { - "connection-lost": "connexion perdue avec __host__:__port__", - "timeout": "délai d'expiration du port __port__ du socket fermé", - "cannot-listen": "impossible d'écouter sur le port __port__, erreur : __error__", - "error": "erreur : __error__", - "socket-error": "erreur de courtier depuis __host__:__port__", + "connection-lost": "Connexion perdue avec __host__:__port__", + "timeout": "Délai d'expiration du port __port__ du socket fermé", + "cannot-listen": "Impossible d'écouter sur le port __port__, erreur : __error__", + "error": "Erreur : __error__", + "socket-error": "Erreur de courtier depuis __host__:__port__", "no-host": "Hôte et/ou port non défini", - "connect-timeout": "délai de connexion", - "connect-fail": "la connexion a échoué", - "bad-string": "échec de la conversion en chaîne", + "connect-timeout": "Délai de connexion", + "connect-fail": "La connexion a échoué", + "bad-string": "Échec de la conversion en chaîne", "invalid-host": "Hôte invalide", "invalid-port": "Port invalide" } @@ -722,7 +722,7 @@ }, "errors": { "access-error": "Erreur d'accès UDP, vous aurez peut-être besoin d'un accès root pour les ports inférieurs à 1024", - "error": "erreur : __erreur__", + "error": "Erreur : __erreur__", "bad-mcaddress": "Mauvaise adresse de multidiffusion", "interface": "Doit être l'adresse IP de l'interface requise", "ip-notset": "udp : adresse IP non définie", @@ -730,7 +730,7 @@ "port-invalid": "udp : numéro de port non valide", "alreadyused": "udp : port __port__ déjà utilisé", "ifnotfound": "udp : interface __iface__ introuvable", - "invalid-group": "groupe de multidiffusion invalide" + "invalid-group": "Groupe de multidiffusion invalide" } }, "switch": { @@ -738,15 +738,15 @@ "label": { "property": "Propriété", "rule": "règle", - "repair": "recréer des séquences du messages", - "value-rules": "règles de valeur", - "sequence-rules": "règles de séquence" + "repair": "Recréer des séquences du messages", + "value-rules": "Règles de valeur", + "sequence-rules": "Règles de séquence" }, "previous": "valeur précédente", "and": "et", - "checkall": "vérifier toutes les règles", - "stopfirst": "arrêter après la première concordance", - "ignorecase": "ignorer la casse", + "checkall": "Vérifier toutes les règles", + "stopfirst": "Arrêter après la première concordance", + "ignorecase": "Ignorer la casse", "rules": { "btwn": "est entre", "cont": "contient", @@ -767,7 +767,7 @@ }, "errors": { "invalid-expr": "Expression JSONata non valide : __error__", - "too-many": "trop de messages en attente dans le noeud de commutation" + "too-many": "Trop de messages en attente dans le noeud de commutation" } }, "change": { @@ -840,13 +840,13 @@ "entrée": "Entrée", "skip-s": "Passer en premier", "skip-e": "lignes", - "firstrow": "la première ligne contient les noms des colonnes", + "firstrow": "La première ligne contient les noms des colonnes", "output": "Sortie", - "includerow": "inclure la ligne du nom de la colonne", + "includerow": "Inclure la ligne du nom de la colonne", "newline": "Nouvelle ligne", - "usestrings": "analyser les valeurs numériques", - "include_empty_strings": "inclure les chaînes vides", - "include_null_values": "inclure les valeurs nulles" + "usestrings": "Analyser les valeurs numériques", + "include_empty_strings": "Inclure les chaînes vides", + "include_null_values": "Inclure les valeurs nulles" }, "placeholder": { "columns": "noms de colonnes séparés par des virgules" @@ -936,8 +936,8 @@ }, "file": { "label": { - "write": "écrire le fichier", - "read": "lire le fichier", + "write": "Écrire le fichier", + "read": "Lire le fichier", "filename": "Nom du fichier", "path": "chemin", "action": "Action", @@ -972,8 +972,8 @@ "appendedfile": "ajouté au fichier : __file__" }, "encoding": { - "none": "par défaut", - "setbymsg": "défini par msg.encoding", + "none": "Par défaut", + "setbymsg": "Défini par msg.encoding", "native": "Natif", "unicode": "Unicode", "japanese": "Japonais", @@ -990,10 +990,10 @@ "errors": { "nofilename": "Aucun nom de fichier spécifié", "invaliddelete": "Attention : suppression non valide. Veuiller utiliser une option de suppression spécifique dans la boîte de dialogue de configuration.", - "deletefail": "échec de la suppression du fichier : __error__", - "writefail": "échec de l'écriture dans le fichier : __error__", - "appendfail": "échec de l'ajout au fichier : __error__", - "createfail": "échec de la création du fichier : __error__" + "deletefail": "Échec de la suppression du fichier : __error__", + "writefail": "Échec de l'écriture dans le fichier : __error__", + "appendfail": "Échec de l'ajout au fichier : __error__", + "createfail": "Échec de la création du fichier : __error__" }, "tip": "Astuce : Le nom du fichier doit être un chemin absolu, sinon il sera relatif au répertoire de travail du processus Node-RED." }, @@ -1018,9 +1018,9 @@ "reduce": "réduire la séquence", "custom": "manuel" }, - "combine": "Combine each", + "combine": "Combiner chaque", "completeMessage": "message complet", - "create": "créer", + "create": "Créer", "type": { "string": "une Chaîne", "array": "un Tableau", @@ -1028,13 +1028,13 @@ "object": "un Objet clé/valeur", "merged": "un Objet fusionné" }, - "using": "en utilisant la valeur de", - "key": "comme la clé", + "using": "En utilisant la valeur du", + "key": "comme clé", "joinedUsing": "joint en utilisant", "send": "Envoyer le message :", - "afterCount": "Après un certain nombre de parties du message", - "count": "compter", - "subsequent": "et tous les messages suivants.", + "afterCount": "Après un nombre de parties du message", + "count": "nombre", + "subsequent": "Et tous les messages suivants.", "afterTimeout": "Après un délai d'attente après le premier message", "seconds": "secondes", "complete": "Après un message avec la propriété msg.complete définie", @@ -1068,10 +1068,10 @@ "order": "Sens", "ascending": "croissant", "descending": "descendant", - "as-number": "comme nombre", + "as-number": "Comme nombre", "invalid-exp": "Expression JSONata invalide dans le noeud sort: __message__", "too-many": "Trop de messages en attente dans le noeud sort", - "clear": "effacer le message en attente dans le noeud sort" + "clear": "Effacer le message en attente dans le noeud sort" }, "batch": { "batch": "Regrouper", @@ -1084,21 +1084,21 @@ "count": { "label": "Nombre de messages", "overlap": "Chevauchement", - "count": "compter", + "count": "nombre", "invalid": "Comptage et chevauchement invalides" }, "interval": { "label": "Intervalle", "seconds": "secondes", - "empty": "envoyer un message vide lorsqu'aucun message n'arrive" + "empty": "Envoyer un message vide lorsqu'aucun message n'arrive" }, "concat": { "topics-label": "Sujets", "topic": "sujet" }, - "too-many": "trop de messages en attente dans le noeud batch", - "unexpected": "mode inattendu", - "no-parts": "aucune propriété de pièces dans le message", + "too-many": "Trop de messages en attente dans le noeud batch", + "unexpected": "Mode inattendu", + "no-parts": "Aucune propriété de pièces dans le message", "error": { "invalid-count": "Compte invalide", "invalid-overlap": "Recouvrement invalide", @@ -1132,7 +1132,7 @@ "out": "par rapport à la dernière valeur de sortie valide" }, "warn": { - "nonumber": "aucun numéro trouvé dans la charge utile" + "nonumber": "Aucun numéro trouvé dans la charge utile" } }, "global-config": { diff --git a/packages/node_modules/@node-red/registry/lib/externalModules.js b/packages/node_modules/@node-red/registry/lib/externalModules.js index 476f3dfbf..7cedadb05 100644 --- a/packages/node_modules/@node-red/registry/lib/externalModules.js +++ b/packages/node_modules/@node-red/registry/lib/externalModules.js @@ -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; diff --git a/packages/node_modules/@node-red/registry/lib/loader.js b/packages/node_modules/@node-red/registry/lib/loader.js index d125156ab..27783be7f 100644 --- a/packages/node_modules/@node-red/registry/lib/loader.js +++ b/packages/node_modules/@node-red/registry/lib/loader.js @@ -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) { diff --git a/packages/node_modules/@node-red/runtime/lib/api/settings.js b/packages/node_modules/@node-red/runtime/lib/api/settings.js index 2399a3152..13f2cec4e 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/api/settings.js @@ -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)) { diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js index ce2dab9ef..2833341c6 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Flow.js @@ -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 } } } diff --git a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js index 5d52beeb7..f94e1016d 100644 --- a/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/flows/Subflow.js @@ -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) { @@ -237,7 +248,7 @@ class Subflow extends Flow { for (j=0;j 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 } diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js index 7a7445a92..5a60cf6a0 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js @@ -42,6 +42,7 @@ function Node(n) { this._closeCallbacks = []; this._inputCallback = null; this._inputCallbacks = null; + this._expectedDoneCount = 0; if (n.name) { this.name = n.name; @@ -159,6 +160,9 @@ Node.prototype.on = function(event, callback) { if (event == "close") { this._closeCallbacks.push(callback); } else if (event === "input") { + if (callback.length === 3) { + this._expectedDoneCount++ + } if (this._inputCallback) { this._inputCallbacks = [this._inputCallback, callback]; this._inputCallback = null; @@ -218,19 +222,17 @@ Node.prototype._emitInput = function(arg) { } else if (node._inputCallbacks) { // Multiple callbacks registered. Call each one, tracking eventual completion var c = node._inputCallbacks.length; + let doneCount = 0 for (var i=0;i { + 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() diff --git a/test/unit/@node-red/runtime/lib/nodes/Node_spec.js b/test/unit/@node-red/runtime/lib/nodes/Node_spec.js index 3fe676f1e..8aa9e6e28 100644 --- a/test/unit/@node-red/runtime/lib/nodes/Node_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/Node_spec.js @@ -183,6 +183,35 @@ describe('Node', function() { n.receive(message); }); + + it('calls parent flow handleComplete when multiple callbacks provided', function(done) { + var n = new RedNode({id:'123',type:'abc', _flow: { + handleComplete: function(node,msg) { + try { + doneCount.should.equal(2) + msg.should.deepEqual(message); + done(); + } catch(err) { + done(err); + } + } + }}); + + var message = {payload:"hello world"}; + let doneCount = 0 + n.on('input',function(msg, nodeSend, nodeDone) { + doneCount++ + nodeDone(); + }); + // Include a callback without explicit done signature + n.on('input',function(msg) { }); + n.on('input',function(msg, nodeSend, nodeDone) { + doneCount++ + nodeDone(); + }); + n.receive(message); + }); + it('triggers onComplete hook when done callback provided', function(done) { var handleCompleteCalled = false; var hookCalled = false;