From d5176975644dc341196088ee0434c348c83d4c93 Mon Sep 17 00:00:00 2001 From: 3Anology Date: Mon, 15 Jul 2019 19:15:29 +0800 Subject: [PATCH 001/346] zh-TW MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 繁體中文(台灣) --- .../editor-client/locales/zh-TW/editor.json | 945 ++++++++++++++++++ .../editor-client/locales/zh-TW/infotips.json | 23 + .../editor-client/locales/zh-TW/jsonata.json | 218 ++++ 3 files changed, 1186 insertions(+) create mode 100644 packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json create mode 100644 packages/node_modules/@node-red/editor-client/locales/zh-TW/infotips.json create mode 100644 packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json b/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json new file mode 100644 index 000000000..95129acb3 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json @@ -0,0 +1,945 @@ +{ + "common": { + "label": { + "name": "名稱", + "ok": "確認", + "done": "完成", + "cancel": "取消", + "delete": "刪除", + "close": "關閉", + "load": "讀取", + "save": "保存", + "import": "匯入", + "export": "匯出", + "back": "返回", + "next": "下一步", + "clone": "複製專案", + "cont": "Continue" + } + }, + "workspace": { + "defaultName": "流程__number__", + "editFlow": "編輯流程: __name__", + "confirmDelete": "確認刪除", + "delete": "確定想要刪除 '__label__'?", + "dropFlowHere": "把流程放到這裡", + "addFlow": "新增流程", + "listFlows": "流程列表", + "status": "狀態", + "enabled": "有效", + "disabled": "無效", + "info": "詳細描述", + "selectNodes": "點擊節點用於選擇", + "tip": "詳細描述支援Markdown羽量級標記語言,並將出現在資訊標籤中。" + }, + "menu": { + "label": { + "view": { + "view": "顯示", + "grid": "格線", + "showGrid": "顯示格線", + "snapGrid": "對齊格線", + "gridSize": "格線尺寸", + "textDir": "文本方向", + "defaultDir": "默認方向", + "ltr": "從左到右", + "rtl": "從右到左", + "auto": "上下文", + "language": "Language", + "browserDefault": "Browser default" + }, + "sidebar": { + "show": "顯示側邊欄" + }, + "palette": { + "show": "Show palette" + }, + "settings": "設置", + "userSettings": "使用者設置", + "nodes": "節點", + "displayStatus": "顯示節點狀態", + "displayConfig": "修改節點配置", + "import": "匯入", + "export": "匯出", + "search": "搜尋流程", + "searchInput": "搜尋流程", + "subflows": "子流程", + "createSubflow": "新建子流程", + "selectionToSubflow": "將選擇部分更改為子流程", + "flows": "流程", + "add": "增加", + "rename": "重新命名", + "delete": "刪除", + "keyboardShortcuts": "鍵盤快速鍵", + "login": "登入", + "logout": "退出", + "editPalette": "節點管理", + "other": "其他", + "showTips": "顯示小提示", + "help": "Node-RED website", + "projects": "專案", + "projects-new": "新專案", + "projects-open": "開啟專案", + "projects-settings": "專案設定", + "showNodeLabelDefault": "顯示新添加節點的標籤", + "clipboard": "剪貼簿", + "library": "庫", + "examples": "範例" + } + }, + "actions": { + "toggle-navigator": "切換導航器", + "zoom-out": "縮小", + "zoom-reset": "重置縮放", + "zoom-in": "放大" + }, + "user": { + "loggedInAs": "作為__name__登入", + "username": "帳號", + "password": "密碼", + "login": "登入", + "loginFailed": "登入失敗", + "notAuthorized": "未授權", + "errors": { + "settings": "設置資訊需要登入後才能訪問", + "deploy": "改動需要登入後才能部署", + "notAuthorized": "此操作需要登入後才能執行" + } + }, + "notification": { + "warning": "警告: __message__", + "warnings": { + "undeployedChanges": "節點中存在未部署的更改", + "nodeActionDisabled": "節點動作在子流程中被禁用", + "nodeActionDisabledSubflow": "node actions disabled within subflow", + "missing-types": "流程由於缺少節點類型而停止。請檢查日誌的詳細資訊", + "safe-mode": "

Flows stopped in safe mode.

You can modify your flows and deploy the changes to restart.

", + "restartRequired": "Node-RED必須重新啟動,以啟用升級的模組", + "credentials_load_failed": "

Flows stopped as the credentials could not be decrypted.

The flow credential file is encrypted, but the project's encryption key is missing or invalid.

", + "credentials_load_failed_reset": "

Credentials could not be decrypted

The flow credential file is encrypted, but the project's encryption key is missing or invalid.

The flow credential file will be reset on the next deployment. Any existing flow credentials will be cleared.

", + "missing_flow_file": "

Project flow file not found.

The project is not configured with a flow file.

", + "missing_package_file": "

Project package file not found.

The project is missing a package.json file.

", + "project_empty": "

The project is empty.

Do you want to create a default set of project files?
Otherwise, you will have to manually add files to the project outside of the editor.

", + "project_not_found": "

Project '__project__' not found.

", + "git_merge_conflict": "

Automatic merging of changes failed.

Fix the unmerged conflicts then commit the results.

" + }, + "error": "Error: __message__", + "errors": { + "lostConnection": "丟失與伺服器的連接,重新連接...", + "lostConnectionReconnect": "丟失與伺服器的連接,__time__秒後重新連接", + "lostConnectionTry": "現在嘗試", + "cannotAddSubflowToItself": "無法向其自身添加子流程", + "cannotAddCircularReference": "無法添加子流程 - 迴圈引用", + "unsupportedVersion": "您正在使用不受支持的Node.js版本
請升級到最新版本的Node.js LTS", + "failedToAppendNode": "

Failed to load '__module__'

__error__

" + }, + "project": { + "change-branch": "Change to local branch '__project__'", + "merge-abort": "Git merge aborted", + "loaded": "Project '__project__' loaded", + "updated": "Project '__project__' updated", + "pull": "Project '__project__' reloaded", + "revert": "Project '__project__' reverted", + "merge-complete": "Git merge completed", + "setupCredentials": "Setup credentials", + "setupProjectFiles": "Setup project files", + "no": "No thanks", + "createDefault": "Create default project files", + "mergeConflict": "Show merge conflicts" + }, + "label": { + "manage-project-dep": "Manage project dependencies", + "setup-cred": "Setup credentials", + "setup-project": "Setup project files", + "create-default-package": "Create default package file", + "no-thanks": "No thanks", + "create-default-project": "Create default project files", + "show-merge-conflicts": "Show merge conflicts" + } + }, + "clipboard": { + "clipboard": "剪貼簿", + "nodes": "節點", + "node": "__count__ 節點", + "node_plural": "__count__ 多個節點", + "configNode": "__count__ 節點組態", + "configNode_plural": "__count__ 多節點組態", + "flow": "__count__ 流程", + "flow_plural": "__count__ 多流程", + "subflow": "__count__ 子流程", + "subflow_plural": "__count__ 多子流程", + "pasteNodes": "在這裡粘貼節點", + "selectFile": "匯入所選檔案", + "importNodes": "匯入節點", + "exportNodes": "匯出節點至剪貼簿", + "download": "下載", + "importUnrecognised": "匯入了無法識別的類型:", + "importUnrecognised_plural": "匯入了無法識別的類型:", + "nodesExported": "節點匯出到了剪貼簿", + "nodesImported": "已匯入:", + "nodeCopied": "已複製__count__個節點", + "nodeCopied_plural": "已複製__count__個節點", + "invalidFlow": "無效的流程: __message__", + "export": { + "selected": "已選擇的節點", + "current": "現在的節點", + "all": "所有流程", + "compact": "緊湊", + "formatted": "已格式化", + "copy": "匯出到剪貼簿", + "export": "匯出到庫", + "exportAs": "匯出為", + "overwrite": "取代", + "exists": "

\"__file__\" 已經存在.

是否要取代?

" + }, + "import": { + "import": "匯入到", + "newFlow": "新流程", + "errors": { + "notArray": "Input not a JSON Array", + "itemNotObject": "Input not a valid flow - item __index__ not a node object", + "missingId": "Input not a valid flow - item __index__ missing 'id' property", + "missingType": "Input not a valid flow - item __index__ missing 'type' property" + } + }, + "copyMessagePath": "已複製路徑", + "copyMessageValue": "已複製數值", + "copyMessageValue_truncated": "已複製捨棄的數值", + "selectNodes": "選擇上面的文本並複製到剪貼簿" + }, + "deploy": { + "deploy": "部署", + "full": "全面", + "fullDesc": "在工作區中部署所有內容", + "modifiedFlows": "已修改的流程", + "modifiedFlowsDesc": "只部署包含已更改節點的流", + "modifiedNodes": "已更改的節點", + "modifiedNodesDesc": "只部署已經更改的節點", + "restartFlows": "Restart Flows", + "restartFlowsDesc": "Restarts the current deployed flows", + "successfulDeploy": "部署成功", + "successfulRestart": "Successfully restarted flows", + "deployFailed": "部署失敗: __message__", + "unusedConfigNodes": "您有一些未使用的配置節點", + "unusedConfigNodesLink": "點擊此處查看它們", + "errors": { + "noResponse": "伺服器沒有回應" + }, + "confirm": { + "button": { + "ignore": "忽略", + "confirm": "確認部署", + "review": "查看更改", + "cancel": "取消", + "merge": "合併", + "overwrite": "忽略 & 部署" + }, + "undeployedChanges": "您有未部署的更改。\n\n離開此頁面將丟失這些更改。", + "improperlyConfigured": "工作區包含一些未正確配置的節點:", + "unknown": "工作區包含一些未知的節點類型:", + "confirm": "你確定要部署嗎?", + "doNotWarn": "do not warn about this again", + "conflict": "伺服器正在運行較新的一組流程。", + "backgroundUpdate": "伺服器上的流程已更新。", + "conflictChecking": "檢查是否可以自動合併更改", + "conflictAutoMerge": "此更改不包括衝突,可以自動合併", + "conflictManualMerge": "這些更改包括了在部署之前必須解決的衝突。", + "plusNMore": "+ __count__ more" + } + }, + "eventLog": { + "title": "事件日誌", + "view": "查看日誌" + }, + "diff": { + "unresolvedCount": "__count__個未解決的衝突", + "unresolvedCount_plural": "__count__個未解決的衝突", + "globalNodes": "Global nodes", + "flowProperties": "Flow Properties", + "type": { + "added": "已添加", + "changed": "已更改", + "unchanged": "未更改", + "deleted": "已刪除", + "flowDeleted": "已刪除流程", + "flowAdded": "已添加流程", + "movedTo": "移動至__id__", + "movedFrom": "從__id__移動" + }, + "nodeCount": "__count__個節點", + "nodeCount_plural": "__count__個節點", + "local": "本地", + "remote": "遠端", + "reviewChanges": "Review Changes", + "noBinaryFileShowed": "Cannot show binary file contents", + "viewCommitDiff": "View Commit Changes", + "compareChanges": "Compare Changes", + "saveConflict": "Save conflict resolution", + "conflictHeader": "__resolved__ of __unresolved__ conflicts resolved", + "commonVersionError": "Common Version doesn't contain valid JSON:", + "oldVersionError": "Old Version doesn't contain valid JSON:", + "newVersionError": "New Version doesn't contain valid JSON:" + }, + "subflow": { + "editSubflowInstance": "Edit subflow instance: __name__", + "editSubflow": "編輯流程範本: __name__", + "edit": "編輯流程範本", + "subflowInstances": "這個子流程範本有__count__個實例", + "subflowInstances_plural": "這個子流程範本有__count__個實例", + "editSubflowProperties": "編輯屬性", + "input": "輸入:", + "output": "輸出:", + "status": "status node", + "deleteSubflow": "刪除子流程", + "info": "詳細描述", + "category": "Category", + "env": { + "restore": "Restore to subflow default", + "remove": "Remove environment variable" + }, + "errors": { + "noNodesSelected": "無法創建子流程: 未選擇節點", + "multipleInputsToSelection": "無法創建子流程: 多個輸入到了選擇" + }, + "format": "標記格式" + }, + "editor": { + "configEdit": "編輯", + "configAdd": "添加", + "configUpdate": "更新", + "configDelete": "刪除", + "nodesUse": "__count__個節點使用此配置", + "nodesUse_plural": "__count__個節點使用此配置", + "addNewConfig": "添加新的__type__配置", + "editNode": "編輯__type__節點", + "editConfig": "編輯__type__配置", + "addNewType": "添加新的__type__節點", + "nodeProperties": "節點屬性", + "label": "Label", + "portLabels": "埠標籤", + "labelInputs": "輸入", + "labelOutputs": "輸出", + "settingIcon": "Icon", + "noDefaultLabel": "無", + "defaultLabel": "使用默認標籤", + "searchIcons": "搜尋 icons", + "useDefault": "use default", + "description": "Description", + "show": "Show", + "hide": "Hide", + "errors": { + "scopeChange": "更改範圍將使其他流中的節點無法使用", + "invalidProperties": "Invalid properties:" + } + }, + "keyboard": { + "title": "鍵盤快速鍵", + "keyboard": "Keyboard", + "filterActions": "filter actions", + "shortcut": "shortcut", + "scope": "scope", + "unassigned": "未分配", + "global": "global", + "workspace": "workspace", + "selectAll": "選擇所有節點", + "selectAllConnected": "選擇所有連接的節點", + "addRemoveNode": "從選擇中添加/刪除節點", + "editSelected": "編輯選定節點", + "deleteSelected": "刪除選定節點或連結", + "importNode": "匯入節點", + "exportNode": "匯出節點", + "nudgeNode": "移動所選節點(1px)", + "moveNode": "移動所選節點(20px)", + "toggleSidebar": "切換側邊欄", + "togglePalette": "Toggle palette", + "copyNode": "複製所選節點", + "cutNode": "剪切所選節點", + "pasteNode": "粘貼節點", + "undoChange": "撤銷上次執行的更改", + "searchBox": "打開搜索框", + "managePalette": "管理面板" + }, + "library": { + "library": "Library", + "openLibrary": "打開庫...", + "saveToLibrary": "保存到庫...", + "typeLibrary": "__type__型別程式庫", + "unnamedType": "無名__type__", + "exportedToLibrary": "Nodes exported to library", + "dialogSaveOverwrite": "一個叫做__libraryName__的__libraryType__已經存在,您需要覆蓋麼?", + "invalidFilename": "無效的檔案名", + "savedNodes": "保存的節點", + "savedType": "已保存__type__", + "saveFailed": "保存失敗: __message__", + "types": { + "local": "Local", + "examples": "Examples" + }, + "exportToLibrary": "將節點匯出到庫", + "filename": "檔案名", + "folder": "資料夾", + "filenamePlaceholder": "文件", + "fullFilenamePlaceholder": "a/b/文件", + "folderPlaceholder": "a/b", + "breadcrumb": "庫" + }, + "palette": { + "noInfo": "無可用資訊", + "filter": "過濾節點", + "search": "搜索模組", + "addCategory": "Add new...", + "label": { + "subflows": "子流程", + "input": "輸入", + "output": "輸出", + "function": "功能", + "social": "社交", + "storage": "存儲", + "analysis": "分析", + "advanced": "高級" + }, + "actions": { + "collapse-all": "Collapse all categories", + "expand-all": "Expand all categories" + }, + "event": { + "nodeAdded": "添加到面板中的節點:", + "nodeAdded_plural": "添加到面板中的多個節點", + "nodeRemoved": "從面板中刪除的節點:", + "nodeRemoved_plural": "從面板中刪除的多個節點:", + "nodeEnabled": "啟用節點:", + "nodeEnabled_plural": "啟用多個節點:", + "nodeDisabled": "禁用節點:", + "nodeDisabled_plural": "禁用多個節點:", + "nodeUpgraded": "節點模組__module__升級到__version__版本" + }, + "editor": { + "title": "面板管理", + "palette": "Palette", + "times": { + "seconds": "秒前", + "minutes": "分前", + "minutesV": "__count__分前", + "hoursV": "__count__小時前", + "hoursV_plural": "__count__小時前", + "daysV": "__count__天前", + "daysV_plural": "__count__天前", + "weeksV": "__count__周前", + "weeksV_plural": "__count__周前", + "monthsV": "__count__月前", + "monthsV_plural": "__count__月前", + "yearsV": "__count__年前", + "yearsV_plural": "__count__年前", + "yearMonthsV": "__y__年, __count__月前", + "yearMonthsV_plural": "__y__年, __count__月前", + "yearsMonthsV": "__y__年, __count__月前", + "yearsMonthsV_plural": "__y__年, __count__月前" + }, + "nodeCount": "__label__個節點", + "nodeCount_plural": "__label__個節點", + "moduleCount": "__count__個可用模組", + "moduleCount_plural": "__count__個可用模組", + "inuse": "使用中", + "enableall": "全部啟用", + "disableall": "全部禁用", + "enable": "啟用", + "disable": "禁用", + "remove": "移除", + "update": "更新至__version__版本", + "updated": "已更新", + "install": "安裝", + "installed": "已安裝", + "conflict": "conflict", + "conflictTip": "

This module cannot be installed as it includes a
node type that has already been installed

Conflicts with __module__

", + "loading": "載入目錄...", + "tab-nodes": "節點", + "tab-install": "安裝", + "sort": "排序:", + "sortAZ": "a-z順序", + "sortRecent": "日期順序", + "more": "增加__count__個", + "errors": { + "catalogLoadFailed": "無法載入節點目錄。
查看流覽器控制台瞭解更多資訊", + "installFailed": "無法安裝: __module__
__message__
查看日誌瞭解更多資訊", + "removeFailed": "無法刪除: __module__
__message__
查看日誌瞭解更多資訊", + "updateFailed": "無法更新: __module__
__message__
查看日誌瞭解更多資訊", + "enableFailed": "無法啟用: __module__
__message__
查看日誌瞭解更多資訊", + "disableFailed": "無法禁用: __module__
__message__
查看日誌瞭解更多資訊" + }, + "confirm": { + "install": { + "body": "在安裝之前,請閱讀節點的文檔,某些節點的依賴關係不能自動解決,可能需要重新啟動Node-RED。", + "title": "安裝節點" + }, + "remove": { + "body": "刪除節點將從Node-RED卸載它。節點可能會繼續使用資源,直到重新啟動Node-RED。", + "title": "刪除節點" + }, + "update": { + "body": "更新節點將需要重新啟動Node-RED來完成更新,該過程必須由手動完成。", + "title": "更新節點" + }, + "cannotUpdate": { + "body": "此節點的更新可用,但不會安裝在面板管理器可以更新的位置。

請參閱有關如何更新此節點的文檔。" + }, + "button": { + "review": "打開節點資訊", + "install": "安裝", + "remove": "刪除", + "update": "更新" + } + } + } + }, + "sidebar": { + "info": { + "name": "節點信息", + "tabName": "名稱", + "label": "信息", + "node": "節點", + "type": "類型", + "module": "Module", + "id": "ID", + "status": "狀態", + "enabled": "啟用", + "disabled": "禁用", + "subflow": "子流程", + "instances": "實例", + "properties": "屬性", + "info": "信息", + "desc": "Description", + "blank": "空白", + "null": "空", + "showMore": "展開", + "showLess": "收起", + "flow": "流程", + "selection": "選擇", + "nodes": "__count__ 個節點", + "flowDesc": "流程描述", + "subflowDesc": "子流程描述", + "nodeHelp": "節點幫助", + "none": "無", + "arrayItems": "__count__個項目", + "showTips": "您可以從設置面板啟用提示資訊" + }, + "config": { + "name": "配置節點", + "label": "配置", + "global": "所有流程", + "none": "無", + "subflows": "子流程", + "flows": "流程", + "filterUnused": "未使用", + "filterAll": "所有", + "filtered": "__count__ 個隱藏" + }, + "context": { + "name": "Context Data", + "label": "context", + "none": "none selected", + "refresh": "refresh to load", + "empty": "empty", + "node": "Node", + "flow": "Flow", + "global": "Global", + "deleteConfirm": "Are you sure you want to delete this item?", + "autoRefresh": "Auto-refresh" + }, + "palette": { + "name": "節點管理", + "label": "節點" + }, + "project": { + "label": "項目", + "name": "名稱", + "description": "描述", + "dependencies": "依賴", + "settings": "設置", + "noSummaryAvailable": "No summary available", + "editDescription": "編輯專案描述", + "editDependencies": "編輯項目依賴", + "editReadme": "Edit README.md", + "showProjectSettings": "Show project settings", + "projectSettings": { + "title": "Project Settings", + "edit": "edit", + "none": "None", + "install": "install", + "removeFromProject": "remove from project", + "addToProject": "add to project", + "files": "Files", + "package": "Package", + "flow": "Flow", + "credentials": "Credentials", + "packageCreate": "File will be created when changes are saved", + "fileNotExist": "File does not exist", + "selectFile": "Select File", + "invalidEncryptionKey": "Invalid encryption key", + "encryptionEnabled": "Encryption enabled", + "encryptionDisabled": "Encryption disabled", + "setTheEncryptionKey": "Set the encryption key", + "resetTheEncryptionKey": "Reset the encryption key", + "changeTheEncryptionKey": "Change the encryption key", + "currentKey": "Current key", + "newKey": "New key", + "credentialsAlert": "This will delete all existing credentials", + "versionControl": "Version Control", + "branches": "Branches", + "noBranches": "No branches", + "deleteConfirm": "Are you sure you want to delete the local branch '__name__'? This cannot be undone.", + "unmergedConfirm": "The local branch '__name__' has unmerged changes that will be lost. Are you sure you want to delete it?", + "deleteUnmergedBranch": "Delete unmerged branch", + "gitRemotes": "Git remotes", + "addRemote": "add remote", + "addRemote2": "Add remote", + "remoteName": "Remote name", + "nameRule": "Must contain only A-Z 0-9 _ -", + "url": "URL", + "urlRule": "https://, ssh:// or file://", + "urlRule2": "Do not include the username/password in the URL", + "noRemotes": "No remotes", + "deleteRemoteConfrim": "Are you sure you want to delete the remote '__name__'?", + "deleteRemote": "Delete remote" + }, + "userSettings": { + "committerDetail": "Committer Details", + "committerTip": "Leave blank to use system default", + "userName": "Username", + "email": "Email", + "sshKeys": "SSH Keys", + "sshKeysTip": "Allows you to create secure connections to remote git repositories.", + "add": "add key", + "addSshKey": "Add SSH Key", + "addSshKeyTip": "Generate a new public/private key pair", + "name": "Name", + "nameRule": "Must contain only A-Z 0-9 _ -", + "passphrase": "Passphrase", + "passphraseShort": "Passphrase too short", + "optional": "Optional", + "cancel": "Cancel", + "generate": "Generate key", + "noSshKeys": "No SSH keys", + "copyPublicKey": "Copy public key to clipboard", + "delete": "Delete key", + "gitConfig": "Git config", + "deleteConfirm": "Are you sure you want to delete the SSH key __name__? This cannot be undone." + }, + "versionControl": { + "unstagedChanges": "Unstaged changes", + "stagedChanges": "Staged changes", + "unstageChange": "Unstage change", + "stageChange": "Stage change", + "unstageAllChange": "Unstage all changes", + "stageAllChange": "Stage all changes", + "commitChanges": "Commit changes", + "resolveConflicts": "Resolve conflicts", + "head": "HEAD", + "staged": "Staged", + "unstaged": "Unstaged", + "local": "Local", + "remote": "Remote", + "revert": "Are you sure you want to revert the changes to '__file__'? This cannot be undone.", + "revertChanges": "Revert changes", + "localChanges": "Local Changes", + "none": "None", + "conflictResolve": "All conflicts resolved. Commit the changes to complete the merge.", + "localFiles": "Local files", + "all": "all", + "unmergedChanges": "Unmerged changes", + "abortMerge": "abort merge", + "commit": "commit", + "changeToCommit": "Changes to commit", + "commitPlaceholder": "Enter your commit message", + "cancelCapital": "Cancel", + "commitCapital": "Commit", + "commitHistory": "Commit History", + "branch": "Branch:", + "moreCommits": " more commit(s)", + "changeLocalBranch": "Change local branch", + "createBranchPlaceholder": "Find or create a branch", + "upstream": "upstream", + "localOverwrite": "You have local changes that would be overwritten by changing the branch. You must either commit or undo those changes first.", + "manageRemoteBranch": "Manage remote branch", + "unableToAccess": "Unable to access remote repository", + "retry": "Retry", + "setUpstreamBranch": "Set as upstream branch", + "createRemoteBranchPlaceholder": "Find or create a remote branch", + "trackedUpstreamBranch": "The created branch will be set as the tracked upstream branch.", + "selectUpstreamBranch": "The branch will be created. Select below to set it as the tracked upstream branch.", + "pushFailed": "Push failed as the remote has more recent commits. Pull and merge first, then push again.", + "push": "push", + "pull": "pull", + "unablePull": "

Unable to pull remote changes; your unstaged local changes would be overwritten.

Commit your changes and try again.

", + "showUnstagedChanges": "Show unstaged changes", + "connectionFailed": "Could not connect to remote repository: ", + "pullUnrelatedHistory": "

The remote has an unrelated history of commits.

Are you sure you want to pull the changes into your local repository?

", + "pullChanges": "Pull changes", + "history": "history", + "projectHistory": "Project History", + "daysAgo": "__count__ day ago", + "daysAgo_plural": "__count__ days ago", + "hoursAgo": "__count__ hour ago", + "hoursAgo_plural": "__count__ hours ago", + "minsAgo": "__count__ min ago", + "minsAgo_plural": "__count__ mins ago", + "secondsAgo": "Seconds ago", + "notTracking": "Your local branch is not currently tracking a remote branch.", + "statusUnmergedChanged": "Your repository has unmerged changes. You need to fix the conflicts and commit the result.", + "repositoryUpToDate": "Your repository is up to date.", + "commitsAhead": "Your repository is __count__ commit ahead of the remote. You can push this commit now.", + "commitsAhead_plural": "Your repository is __count__ commits ahead of the remote. You can push these commits now.", + "commitsBehind": "Your repository is __count__ commit behind of the remote. You can pull this commit now.", + "commitsBehind_plural": "Your repository is __count__ commits behind of the remote. You can pull these commits now.", + "commitsAheadAndBehind1": "Your repository is __count__ commit behind and ", + "commitsAheadAndBehind1_plural": "Your repository is __count__ commits behind and ", + "commitsAheadAndBehind2": "__count__ commit ahead of the remote. ", + "commitsAheadAndBehind2_plural": "__count__ commits ahead of the remote. ", + "commitsAheadAndBehind3": "You must pull the remote commit down before pushing.", + "commitsAheadAndBehind3_plural": "You must pull the remote commits down before pushing.", + "refreshCommitHistory": "Refresh commit history", + "refreshChanges": "Refresh changes" + } + } + }, + "typedInput": { + "type": { + "str": "文字列", + "num": "數字", + "re": "規則運算式", + "bool": "布林", + "json": "JSON", + "bin": "二進位流", + "date": "時間戳記", + "jsonata": "expression", + "env": "env variable" + } + }, + "editableList": { + "add": "添加" + }, + "search": { + "empty": "找不到匹配", + "addNode": "添加一個節點..." + }, + "expressionEditor": { + "functions": "功能", + "functionReference": "Function reference", + "insert": "插入", + "title": "JSONata運算式編輯器", + "test": "Test", + "data": "示例消息", + "result": "結果", + "format": "格式表達方法", + "compatMode": "相容模式啟用", + "compatModeDesc": "

JSONata的相容模式

目前的運算式仍然參考msg,所以將以相容性模式進行評估。請更新運算式,使其不使用msg,因為此模式將在將來刪除。

當JSONata支持首次添加到Node-RED時,它需要運算式引用msg物件。例如msg.payload將用於訪問有效負載。

這樣便不再需要運算式直接針對消息進行評估。要訪問有效負載,運算式應該只是payload.

", + "noMatch": "無匹配結果", + "errors": { + "invalid-expr": "無效的JSONata運算式:\n __message__", + "invalid-msg": "無效的示例JSON消息:\n __message__", + "context-unsupported": "無法測試上下文函數\n $flowContext 或 $globalContext", + "eval": "評估運算式錯誤:\n __message__" + } + }, + "jsEditor": { + "title": "JavaScript 編輯器" + }, + "textEditor": { + "title": "Text 編輯器" + }, + "jsonEditor": { + "title": "JSON編輯器", + "format": "格式化JSON" + }, + "markdownEditor": { + "title": "Markdown 編輯器", + "format": "F使用markdown格式化", + "heading1": "Heading 1", + "heading2": "Heading 2", + "heading3": "Heading 3", + "bold": "粗體", + "italic": "斜體", + "code": "程式碼", + "ordered-list": "編號清單", + "unordered-list": "符號清單", + "quote": "引用", + "link": "連結", + "horizontal-rule": "分隔線", + "toggle-preview": "預覽" + }, + "bufferEditor": { + "title": "緩衝區編輯器", + "modeString": "作為UTF-8字串處理", + "modeArray": "作為JSON陣列處理", + "modeDesc": "

緩衝區編輯器

緩衝區類型被存儲為位元組值的JSON陣列。編輯器將嘗試將輸入的數值解析為JSON陣列。如果它不是有效的JSON,它將被視為UTF-8字串,並被轉換為單個字元代碼點的陣列。

例如,Hello World的值會被轉換為JSON陣列:

[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]

" + }, + "projects": { + "config-git": "Configure Git client", + "welcome": { + "hello": "Hello! We have introduced 'projects' to Node-RED.", + "desc0": "This is a new way for you to manage your flow files and includes version control of your flows.", + "desc1": "To get started you can create your first project or clone an existing project from a git repository.", + "desc2": "If you are not sure, you can skip this for now. You will still be able to create your first project from the 'Projects' menu at any time.", + "create": "Create Project", + "clone": "Clone Repository", + "openExistingProject": "Open existing project", + "not-right-now": "Not right now" + }, + "git-config": { + "setup": "Setup your version control client", + "desc0": "Node-RED uses the open source tool Git for version control. It tracks changes to your project files and lets you push them to remote repositories.", + "desc1": "When you commit a set of changes, Git records who made the changes with a username and email address. The Username can be anything you want - it does not need to be your real name.", + "desc2": "Your Git client is already configured with the details below.", + "desc3": "You can change these settings later under the 'Git config' tab of the settings dialog.", + "username": "Username", + "email": "Email" + }, + "project-details": { + "create": "Create your project", + "desc0": "A project is maintained as a Git repository. It makes it much easier to share your flows with others and to collaborate on them.", + "desc1": "You can create multiple projects and quickly switch between them from the editor.", + "desc2": "To begin, your project needs a name and an optional description.", + "already-exists": "Project already exists", + "must-contain": "Must contain only A-Z 0-9 _ -", + "project-name": "Project name", + "desc": "描述", + "opt": "Optional" + }, + "clone-project": { + "clone": "Clone a project", + "desc0": "If you already have a git repository containing a project, you can clone it to get started.", + "already-exists": "Project already exists", + "must-contain": "Must contain only A-Z 0-9 _ -", + "project-name": "Project name", + "no-info-in-url": "Do not include the username/password in the url", + "git-url": "Git repository URL", + "protocols": "https://, ssh:// or file://", + "auth-failed": "Authentication failed", + "username": "Username", + "passwd": "Password", + "ssh-key": "SSH Key", + "passphrase": "Passphrase", + "ssh-key-desc": "Before you can clone a repository over ssh you must add an SSH key to access it.", + "ssh-key-add": "Add an ssh key", + "credential-key": "Credentials encryption key", + "cant-get-ssh-key": "Error! Can't get selected SSH key path.", + "already-exists2": "already exists", + "git-error": "git error", + "connection-failed": "Connection failed", + "not-git-repo": "Not a git repository", + "repo-not-found": "Repository not found" + }, + "default-files": { + "create": "Create your project files", + "desc0": "A project contains your flow files, a README file and a package.json file.", + "desc1": "It can contain any other files you want to maintain in the Git repository.", + "desc2": "Your existing flow and credential files will be copied into the project.", + "flow-file": "Flow file", + "credentials-file": "Credentials file" + }, + "encryption-config": { + "setup": "Setup encryption of your credentials file", + "desc0": "Your flow credentials file can be encrypted to keep its contents secure.", + "desc1": "If you want to store these credentials in a public Git repository, you must encrypt them by providing a secret key phrase.", + "desc2": "Your flow credentials file is not currently encrypted.", + "desc3": "That means its contents, such as passwords and access tokens, can be read by anyone with access to the file.", + "desc4": "If you want to store these credentials in a public Git repository, you must encrypt them by providing a secret key phrase.", + "desc5": "Your flow credentials file is currently encrypted using the credentialSecret property from your settings file as the key.", + "desc6": "Your flow credentials file is currently encrypted using a system-generated key. You should provide a new secret key for this project.", + "desc7": "The key will be stored separately from your project files. You will need to provide the key to use this project in another instance of Node-RED.", + "credentials": "Credentials", + "enable": "Enable encryption", + "disable": "Disable encryption", + "disabled": "disabled", + "copy": "Copy over existing key", + "use-custom": "Use custom key", + "desc8": "The credentials file will not be encrypted and its contents easily read", + "create-project-files": "Create project files", + "create-project": "Create project", + "already-exists": "already exists", + "git-error": "git error", + "git-auth-error": "git auth error" + }, + "create-success": { + "success": "You have successfully created your first project!", + "desc0": "You can now continue to use Node-RED just as you always have.", + "desc1": "The 'info' tab in the sidebar shows you what your current active project is. The button next to the name can be used to access the project settings view.", + "desc2": "The 'history' tab in the sidebar can be used to view files that have changed in your project and to commit them. It shows you a complete history of your commits and allows you to push your changes to a remote repository." + }, + "create": { + "projects": "Projects", + "already-exists": "Project already exists", + "must-contain": "Must contain only A-Z 0-9 _ -", + "no-info-in-url": "Do not include the username/password in the url", + "open": "Open Project", + "create": "Create Project", + "clone": "Clone Repository", + "project-name": "Project name", + "desc": "描述", + "opt": "Optional", + "flow-file": "Flow file", + "credentials": "Credentials", + "enable-encryption": "Enable encryption", + "disable-encryption": "Disable encryption", + "encryption-key": "Encryption key", + "desc0": "A phrase to secure your credentials with", + "desc1": "The credentials file will not be encrypted and its contents easily read", + "git-url": "Git repository URL", + "protocols": "https://, ssh:// or file://", + "auth-failed": "Authentication failed", + "username": "Username", + "password": "Password", + "ssh-key": "SSH Key", + "passphrase": "Passphrase", + "desc2": "Before you can clone a repository over ssh you must add an SSH key to access it.", + "add-ssh-key": "Add an ssh key", + "credentials-encryption-key": "Credentials encryption key", + "already-exists-2": "already exists", + "git-error": "git error", + "con-failed": "Connection failed", + "not-git": "Not a git repository", + "no-resource": "Repository not found", + "cant-get-ssh-key-path": "Error! Can't get selected SSH key path.", + "unexpected_error": "unexpected_error" + }, + "delete": { + "confirm": "Are you sure you want to delete this project?" + }, + "create-project-list": { + "search": "search your projects", + "current": "current" + }, + "require-clean": { + "confirm": "

You have undeployed changes that will be lost.

Do you want to continue?

" + }, + "send-req": { + "auth-req": "Authentication required for repository", + "username": "Username", + "password": "Password", + "passphrase": "Passphrase", + "retry": "Retry", + "update-failed": "Failed to update auth", + "unhandled": "Unhandled error response" + }, + "create-branch-list": { + "invalid": "Invalid branch", + "create": "Create branch", + "current": "current" + }, + "create-default-file-set": { + "no-active": "Cannot create default file set without an active project", + "no-empty": "Cannot create default file set on a non-empty project", + "git-error": "git error" + }, + "errors": { + "no-username-email": "Your Git client is not configured with a username/email.", + "unexpected": "An unexpected error occurred", + "code": "code" + } + }, + "editor-tab": { + "properties": "Properties", + "description": "描述", + "appearance": "Appearance", + "env": "Environment Variables" + } +} \ No newline at end of file diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-TW/infotips.json b/packages/node_modules/@node-red/editor-client/locales/zh-TW/infotips.json new file mode 100644 index 000000000..9e67a71cf --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/locales/zh-TW/infotips.json @@ -0,0 +1,23 @@ +{ + "info": { + "tip0" : "您可以用 {{core:delete-selection}} 刪除選擇的節點或連結。", + "tip1" : "{{core:search}} 可以在流程內搜索節點。", + "tip2": "{{core:toggle-sidebar}} 可以顯示或隱藏邊側欄。", + "tip3": "您可以在 {{core:manage-palette}} 中管理節點的控制台。", + "tip4": "側邊欄中會列出流程中所有的配置節點。您可以通過功能表或者 {{core:show-config-tab}} 來訪問這些節點。", + "tip5": "您可以在設定中選擇顯示或隱藏這些提示。", + "tip6": "您可以用[left] [up] [down] [right]鍵來移動被選中的節點。按住[shift]可以更快地移動節點。", + "tip7": "把節點拖到連接上可以向連接中插入節點。", + "tip8": "您可以用 {{core:show-export-dialog}} 來匯出被選中的節點或標籤頁中的流程。", + "tip9": "您可以將流程的json檔拖入編輯方塊或 {{core:show-import-dialog}} 來導入流程。", + "tip10": "按住[shift]後按一下並拖動節點可以將該節點的多個連接一併移動到其他節點的埠。", + "tip11": "{{core:show-info-tab}} 可以顯示「資訊」標籤頁。 {{core:show-debug-tab}} 可以顯示「調試」標籤頁。", + "tip12": "按住[ctrl]的同時點擊工作介面可以在節點的對話欄中快速添加節點。", + "tip13": "按住[ctrl]的同時點擊節點的埠或後續節點可以快速連接多個節點。", + "tip14": "按住[shift]的同時點擊節點會選中所有被連接的節點。", + "tip15": "按住[ctrl]的同時點擊節點可以在選中或取消選中節點。", + "tip16": "{{core:show-previous-tab}} 和 {{core:show-next-tab}} 可以切換標籤頁。", + "tip17": "您可以在節點的屬性配置畫面中通過 {{core:confirm-edit-tray}} 來更改設置,或者用 {{core:cancel-edit-tray}} 來取消更改。", + "tip18": "您可以通過點擊 {{core:edit-selected-node}} 來顯示被選中節點的屬性設置畫面。" + } +} diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json new file mode 100644 index 000000000..ae62fe068 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json @@ -0,0 +1,218 @@ +{ + "$string": { + "args": "arg", + "desc": "通過以下的類型轉換規則將參數*arg*轉換成字串:\n\n - 字串不轉換。\n -函數轉換成空的字串。\n - JSON的值無法用數字表示所以用無限大或者NaN(非數)表示。\n - 用’JSON.stringify’函數將其他值轉換成JSON字串。" + }, + "$length": { + "args": "str", + "desc": "輸出字串’str’的字數。如果’str’不是字串,拋出錯誤。" + }, + "$substring": { + "args": "str, start[, length]", + "desc": "輸出`start`位置後的的首次出現的包括`str`的子字串。 如果`length`被指定,那麼的字串中將只包括前`length`個文字。如果`start`是負數則輸出從`str`末尾開始的`length`個文字" + }, + "$substringBefore": { + "args": "str, chars", + "desc": "輸出’str’中首次出現的’chars’之前的子字串,如果’str’中不包括’chars’則輸出’str’。" + }, + "$substringAfter": { + "args": "str, chars", + "desc": "輸出’str’中首次出現的’chars’之後的子字串,如果’str’中不包括’chars’則輸出’str’。" + }, + "$uppercase": { + "args": "str", + "desc": "`將’str’中的所有字母變為大寫後輸出。" + }, + "$lowercase": { + "args": "str", + "desc": "將’str’中的所有字母變為小寫後輸出。" + }, + "$trim": { + "args": "str", + "desc": "將以下步驟應用於`str`來去除所有空白文字並實現標準化。\n\n – 將全部tab定位字元、Enter鍵、換行字元用空白代替。\n- 將連續的空白文字變成一個空白文字。\n- 消除開頭和末尾的空白文字。\n\n如果`str`沒有被指定(即在無輸入參數的情況下調用本函數),將上下文的值作為`str`來使用。 如果`str` 不是字串則拋出錯誤。" + }, + "$contains": { + "args": "str, pattern", + "desc": "字串`str` 和 `pattern`匹配的話輸出`true`,不匹配的情況下輸出 `false`。 不指定`str`的情況下(比如用一個參數調用本函數時)、將上下文的值作為`str`來使用。參數 `pattern`可以為字串或正則表達。" + }, + "$split": { + "args": "str[, separator][, limit]", + "desc": "將參數`str`分解成由子字串組成的陣列。 如果`str`不是字串拋出錯誤。可以省略的參數 `separator`中指定字串`str`的分隔符號。分隔符號可以是文字或規則運算式。在不指定`separator`的情況下、將分隔符號看作空的字串並把`str`拆分成由單個字母組成的陣列。如果`separator`不是字串則拋出錯誤。在可省略的參數`limit`中指定分割後的子字串的最大個數。超出個數的子字串將被捨棄。如果`limit`沒有被指定,`str` 將不考慮子字串的個數而將字串完全分隔。如果`limit`是負數則拋出錯誤。" + }, + "$join": { + "args": "array[, separator]", + "desc": "用可以省略的參數 `separator`來把多個字元串連接。如果`array`不是字串則拋出錯誤。 如果沒有指定`separator`,則用空字串來連接字元(即字串之間沒有`separator`)。 如果`separator`不是字元則拋出錯誤。" + }, + "$match": { + "args": "str, pattern [, limit]", + "desc": "對字串`str`使用規則運算式`pattern`並輸出與`str`相匹配的部分資訊。" + }, + "$replace": { + "args": "str, pattern, replacement [, limit]", + "desc": "在字串`str`中搜索`pattern`並用`replacement`來替換。\n\n可選參數`limit`用來指定替換次數的上限。" + }, + "$now": { + "args": "", + "desc": "生成ISO 8601互換格式的時刻,並作為字串輸出。" + }, + "$base64encode": { + "args": "string", + "desc": "將ASCII格式的字串轉換為Base 64格式。將字串中的文字視作二進位形式的資料處理。包含URI編碼在內的字串文字必須在0x00到0xFF的範圍內,否則不會被支持。" + }, + "$base64decode": { + "args": "string", + "desc": "用UTF-8內碼表將Base 64形式二進位值轉換為字串。" + }, + "$number": { + "args": "arg", + "desc": "用下述的規則將參數 `arg`轉換為數值。:\n\n – 數值不做轉換。\n – 將字串中合法的JSON數値表示轉換成數値。\n – 其他形式的值則拋出錯誤。" + }, + "$abs": { + "args": "number", + "desc": "輸出參數`number`的絕對值。" + }, + "$floor": { + "args": "number", + "desc": "輸出比`number`的值小的最大整數。" + }, + "$ceil": { + "args": "number", + "desc": "輸出比`number`的值大的最小整數。" + }, + "$round": { + "args": "number [, precision]", + "desc": "輸出四捨五入後的參數`number`。可省略的參數 `precision`指定四捨五入後小數點下的位數。" + }, + "$power": { + "args": "base, exponent", + "desc": "輸出底數`base`的`exponent`次冪。" + }, + "$sqrt": { + "args": "number", + "desc": "輸出參數 `number`的平方根。" + }, + "$random": { + "args": "", + "desc": "輸出比0大,比1小的偽亂數。" + }, + "$millis": { + "args": "", + "desc": "返回從UNIX時間 (1970年1月1日 UTC/GMT的午夜)開始到現在的毫秒數。在同一個運算式的測試中所有對`$millis()`的調用將會返回相同的值。" + }, + "$sum": { + "args": "array", + "desc": "輸出陣列`array`的總和。如果`array`不是數值則拋出錯誤。" + }, + "$max": { + "args": "array", + "desc": "輸出陣列`array`的最大值。如果`array`不是數值則拋出錯誤。" + }, + "$min": { + "args": "array", + "desc": "輸出陣列`array`的最小值。如果`array`不是數值則拋出錯誤。。" + }, + "$average": { + "args": "array", + "desc": "輸出陣列`array`的平均數。如果`array`不是數值則拋出錯誤。。" + }, + "$boolean": { + "args": "arg", + "desc": "用下述規則將資料轉換成布林值。:\n\n - 不轉換布林值`Boolean`。\n – 將空的字串`string`轉換為`false`\n – 將不為空的字串`string`轉換為`true`\n – 將為0的數位`number`轉換成`false`\n –將不為0的數位`number`轉換成`true`\n –將`null`轉換成`false`\n –將空的陣列`array`轉換成`false`\n –如果陣列`array`中含有可以轉換成`true`的要素則轉換成`true`\n –如果`array`中沒有可轉換成`true`的要素則轉換成`false`\n – 空的物件`object`轉換成`false`\n – 非空的物件`object`轉換成`true`\n –將函數`function`轉換成`false`" + }, + "$not": { + "args": "arg", + "desc": "輸出做反轉運算後的布林值。首先將`arg`轉換為布林值。" + }, + "$exists": { + "args": "arg", + "desc": "如果算式`arg`的值存在則輸出`true`。如果算式的值不存在(比如指向不存在區域的引用)則輸出`false`。" + }, + "$count": { + "args": "array", + "desc": "輸出陣列中的元素數。" + }, + "$append": { + "args": "array, array", + "desc": "將兩個陣列連接。" + }, + "$sort": { + "args": "array [, function]", + "desc": "輸出排序後的陣列`array`。\n\n如果使用了比較函數`function`,則下述兩個參數需要被指定。\n\n`function(left, right)`\n\n該比較函數是為了比較left和right兩個值而被排序演算法調用的。如果使用者希望left的值被置於right的值之後,那麼該函數必須輸出布林值`true`來表示位置交換。而在不需要位置交換時函數必須輸出`false`。" + }, + "$reverse": { + "args": "array", + "desc": "輸出倒序後的陣列`array`。" + }, + "$shuffle": { + "args": "array", + "desc": "輸出隨機排序後的陣列 `array`。" + }, + "$zip": { + "args": "array, ...", + "desc": "將陣列中的值按索引順序打包後輸出。" + }, + "$keys": { + "args": "object", + "desc": "輸出由物件內的鍵組成的陣列。如果參數是物件的陣列則輸出由所有物件中的鍵去重後組成的佇列。" + }, + "$lookup": { + "args": "object, key", + "desc": "輸出對象中與參數`key`對應的值。如果第一個參數`object`是陣列,那麼陣列中所有的物件都將被搜索並輸出這些物件中與參數`key`對應的值。" + }, + "$spread": { + "args": "object", + "desc": "將物件中的鍵值對分隔成每個要素中只含有一個鍵值對的陣列。如果參數`object`是陣列,那麼返回值的陣列中包含所有物件中的鍵值對。" + }, + "$merge": { + "args": "array<object>", + "desc": "將輸入陣列`objects`中所有的鍵值對合併到一個`object`中並返回。如果輸入陣列的要素中含有重複的鍵,則返回的`object`中將只包含陣列中最後出現要素的值。如果輸入陣列中包括物件以外的元素,則拋出錯誤。" + }, + "$sift": { + "args": "object, function", + "desc": "輸出參數`object`中符合`function`的鍵值對。\n\n`function`必須含有下述參數。\n\n`function(value [, key [, object]])`" + }, + "$each": { + "args": "object, function", + "desc": "將函數`function`應用於`object`中的所有鍵值對並輸出由所有返回值組成的陣列。" + }, + "$map": { + "args": "array, function", + "desc": "將函數`function`應用於陣列`array`中所有的值並輸出由返回值組成的陣列。\n\n`function`中必須含有下述參數。\n\n`function(value [, index [, array]])`" + }, + "$filter": { + "args": "array, function", + "desc": "輸出陣列`array`中符合函數`function`條件的值組成的陣列。\n\n`function`必須包括下述參數。\n\n`function(value [, index [, array]])`" + }, + "$reduce": { + "args": "array, function [, init]", + "desc": "將`function`依次應用於陣列中的各要素值。 其中,前一個要素值的計算結果將參與到下一次的函數運算中。。\n\n函數`function`接受兩個參數並作為中綴標記法中的操作符。\n\n可省略的參數`init`將作為運算的初始值。" + }, + "$flowContext": { + "args": "string", + "desc": "獲取流上下文(流等級的上下文,可以讓所有節點共用)的屬性。" + }, + "$globalContext": { + "args": "string", + "desc": "獲取全域上下文的屬性。" + }, + "$pad": { + "args": "string, width [, char]", + "desc": "根據需要,向字串`string`的副本中填充文字使該字串的字數達到`width`的絕對值並返回填充文字後的字串。\n\n如果`width`的值為正,則向字串`string`的右側填充文字,如果`width`為負,則向字串`string`的左側填充文字。\n\n可選參數`char`用來指定填充的文字。如果未指定該參數,則填充空白文字。" + }, + "$fromMillis": { + "args": "number", + "desc": "將表示從UNIX時間 (1970年1月1日 UTC/GMT的午夜)開始到現在的毫秒數的數值轉換成ISO 8601形式時間戳記的字串。" + }, + "$formatNumber": { + "args": "number, picture [, options]", + "desc": "將`number`轉換成具有`picture`所指定的數值格式的字串。\n\n此函數的功能與XPath F&O 3.1規格中定義的XPath/XQuery函數的fn:format-number功能相一致。參數`picture`用於指定數值的轉換格式,其語法與fn:format-number中的定義一致。\n\n可選的第三參數`options`用來覆蓋預設的局部環境格式,如小數點分隔符號。如果指定該參數,那麼該參數必須是包含name/value對的物件,並且name/value對必須符合XPath F&O 3.1規格中記述的數值格式。" + }, + "$formatBase": { + "args": "number [, radix]", + "desc": "將`number`變換為以參數`radix`的值為基數形式的字串。如果不指定`radix`的值,則默認基數為10。指定的`radix`值必須在2~36之間,否則拋出錯誤。" + }, + "$toMillis": { + "args": "timestamp", + "desc": "將ISO 8601格式的字串`timestamp`轉換為從UNIX時間 (1970年1月1日 UTC/GMT的午夜)開始到現在的毫秒數。如果該字串的格式不正確,則拋出錯誤。" + } +} From a42d7d867e83c4a5b65be7df59ca4efd844cd667 Mon Sep 17 00:00:00 2001 From: Yuma Matsuura Date: Fri, 26 Jul 2019 11:36:22 +0900 Subject: [PATCH 002/346] Add a libe break function --- .../@node-red/editor-client/src/js/ui/view.js | 80 ++++++++++++++++--- 1 file changed, 69 insertions(+), 11 deletions(-) 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 57cce2852..62282e624 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1805,7 +1805,15 @@ RED.view = (function() { } function calculateTextWidth(str, className, offset) { - return calculateTextDimensions(str,className,offset,0)[0]; + var result=convertLineBreakCharacter(str); + var width = 0; + for (var i=0;i0?7:0))/20)) ); } } + if (hideLabel) { + node_height = 30; + } else { + node_height = 6 + 24 * convertLineBreakCharacter(l).length; + } d.h = Math.max(node_height,(d.outputs||0) * 15); // if (d._def.badge) { @@ -2706,7 +2742,7 @@ RED.view = (function() { .attr("rx",5) .attr("ry",5) .attr("width",32) - .attr("height",node_height-4); + .attr("height",26); nodeButtonGroup.append("rect") .attr("class","red-ui-flow-node-button-button") .attr("x",function(d) { return d._def.align == "right"? 11:5}) @@ -2714,7 +2750,7 @@ RED.view = (function() { .attr("rx",4) .attr("ry",4) .attr("width",16) - .attr("height",node_height-12) + .attr("height",18) .attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def); /*d._def.color;*/}) .attr("cursor","pointer") .on("mousedown",function(d) {if (!lasso && isButtonEnabled(d)) {focusView();d3.select(this).attr("fill-opacity",0.2);d3.event.preventDefault(); d3.event.stopPropagation();}}) @@ -2925,19 +2961,25 @@ RED.view = (function() { var hideLabel = d.hasOwnProperty('l')?!d.l : isLink; dirtyNodes[d.id] = d; //if (d.x < -50) deleteSelection(); // Delete nodes if dragged back to palette + var l = RED.utils.getNodeLabel(d); if (d.resize) { - var l = RED.utils.getNodeLabel(d); var ow = d.w; if (hideLabel) { - d.w = node_height; + d.w = 30; } else { d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) ); } // d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) ); - d.h = Math.max(node_height,(d.outputs||0) * 15); d.x += (d.w-ow)/2; d.resize = false; } + if (hideLabel) { + node_height = 30; + } else { + node_height = 6 + 24 * convertLineBreakCharacter(l).length; + } + d.h = Math.max(node_height,(d.outputs || 0) * 15); + var thisNode = d3.select(this); thisNode.classed("red-ui-flow-node-disabled", function(d) { return d.d === true}); thisNode.classed("red-ui-flow-subflow",function(d) { return activeSubflow != null; }) @@ -2958,10 +3000,11 @@ RED.view = (function() { thisNode.selectAll(".red-ui-flow-node-icon-group").classed("red-ui-flow-node-icon-group-right", false); thisNode.selectAll(".red-ui-flow-node-label").classed("red-ui-flow-node-label-right", false).attr("text-anchor", "start"); } + var gx; thisNode.selectAll(".red-ui-flow-node-icon-group").attr("transform", function (d) { return "translate(0, 0)"; }); - thisNode.selectAll(".red-ui-flow-node-label").attr("x", function (d) { return 38; }); + thisNode.selectAll(".red-ui-flow-node-label").attr("x", function (d) { gx=38; return 38; }); thisNode.selectAll(".red-ui-flow-node-icon-group-right").attr("transform", function(d){return "translate("+(d.w-30)+",0)"}); - thisNode.selectAll(".red-ui-flow-node-label-right").attr("x", function(d){return d.w-38}); + thisNode.selectAll(".red-ui-flow-node-label-right").attr("x", function(d){ gx=d.w-38; return d.w-38}); //thisNode.selectAll(".red-ui-flow-node-icon-right").attr("x",function(d){return d.w-d3.select(this).attr("width")-1-(d.outputs>0?5:0);}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-right").attr("x",function(d){return d.w-30;}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-border-right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)}); @@ -3035,7 +3078,7 @@ RED.view = (function() { port.attr("transform", function(d) { return "translate("+x+","+((y+13*i)-5)+")";}); }); } - thisNode.selectAll("text.red-ui-flow-node-label").text(function(d,i){ + thisNode.selectAll("text.red-ui-flow-node-label").html(function(d,i){ var l = ""; if (d._def.label) { l = d._def.label; @@ -3047,7 +3090,22 @@ RED.view = (function() { l = d.type; } } - return l; + var sa = convertLineBreakCharacter(l); + var sn = sa.length; + var ic = 0; + var st = ""; + var yp = d.h/2-(sn/2)*24+16 + var yn = 0; + var dy = ".3px"; + for (ic=0; ic"+sa[ic]+""; + } + if (sn!=1) { + return st; + } else { + return sa[0]!=null ? sa[0]:l; + } }) .attr("y", function(d){return (d.h/2)-1;}) .attr("class",function(d){ From bc789c7f9f3967fa622ea4af9074adcd4fb855b2 Mon Sep 17 00:00:00 2001 From: 3Anology Date: Thu, 22 Aug 2019 23:50:44 +0800 Subject: [PATCH 003/346] Update infotips.json --- .../@node-red/editor-client/locales/zh-TW/infotips.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-TW/infotips.json b/packages/node_modules/@node-red/editor-client/locales/zh-TW/infotips.json index 9e67a71cf..f783f2e8b 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-TW/infotips.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-TW/infotips.json @@ -9,7 +9,7 @@ "tip6": "您可以用[left] [up] [down] [right]鍵來移動被選中的節點。按住[shift]可以更快地移動節點。", "tip7": "把節點拖到連接上可以向連接中插入節點。", "tip8": "您可以用 {{core:show-export-dialog}} 來匯出被選中的節點或標籤頁中的流程。", - "tip9": "您可以將流程的json檔拖入編輯方塊或 {{core:show-import-dialog}} 來導入流程。", + "tip9": "您可以將流程的json文字檔拖入編輯方塊或 {{core:show-import-dialog}} 來導入流程。", "tip10": "按住[shift]後按一下並拖動節點可以將該節點的多個連接一併移動到其他節點的埠。", "tip11": "{{core:show-info-tab}} 可以顯示「資訊」標籤頁。 {{core:show-debug-tab}} 可以顯示「調試」標籤頁。", "tip12": "按住[ctrl]的同時點擊工作介面可以在節點的對話欄中快速添加節點。", From bca9b5d8c06b049016a909bb6433490ee5648096 Mon Sep 17 00:00:00 2001 From: 3Anology Date: Fri, 23 Aug 2019 00:12:41 +0800 Subject: [PATCH 004/346] =?UTF-8?q?=E5=9F=BA=E6=9C=ACNode=E7=B9=81?= =?UTF-8?q?=E9=AB=94=E4=B8=AD=E6=96=87=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nodes/locales/zh-TW/messages.json | 910 ++++++++++++++++++ 1 file changed, 910 insertions(+) create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json new file mode 100644 index 000000000..153a88300 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json @@ -0,0 +1,910 @@ +{ + "common": { + "label": { + "payload": "內容", + "topic": "主題", + "name": "名稱", + "username": "使用者名稱", + "password": "密碼", + "property": "屬性" + }, + "status": { + "connected": "已連接", + "not-connected": "未連接", + "disconnected": "已斷開", + "connecting": "連接中", + "error": "錯誤", + "ok": "確定" + }, + "notification": { + "error": "錯誤: __message__", + "errors": { + "not-deployed": "節點未部署", + "no-response": "伺服器無反應", + "unexpected": "發生意外錯誤 (__status__) __message__" + } + }, + "errors": { + "nooverride": "警告: 資訊的屬性已經不可以改寫節點的屬性. 詳情參考 bit.ly/nr-override-msg-props" + } + }, + "inject": { + "inject": "注入", + "repeat": "重複 = __repeat__", + "crontab": "crontab = __crontab__", + "stopped": "停止", + "failed": "注入失敗: __error__", + "label": { + "repeat": "重複" + }, + "timestamp": "時間戳記", + "none": "無", + "interval": "週期性執行", + "interval-time": "指定時間段並週期性執行", + "time": "指定時間", + "seconds": "秒", + "minutes": "分鐘", + "hours": "小時", + "between": "介於", + "previous": "之前數值", + "at": "在", + "and": "至", + "every": "每隔", + "days": [ + "星期一", + "星期二", + "星期三", + "星期四", + "星期五", + "星期六", + "星期天" + ], + "on": "在", + "onstart": "立刻執行於", + "onceDelay": "秒後, 此後", + "tip": "注意: \"指定時間段並週期性執行\" 和 \"指定時間\" 會使用cron系統.
詳情查看信息頁.", + "success": "成功注入: __label__", + "errors": { + "failed": "注入失敗, 請查看日誌", + "toolong": "週期過長" + } + }, + "catch": { + "catch": "監測所有節點", + "catchNodes": "監測__number__個節點", + "label": { + "source": "監測範圍", + "node": "節點", + "type": "類型", + "selectAll": "全選", + "sortByLabel": "按名稱排序", + "sortByType": "按類型排序" + }, + "scope": { + "all": "所有節點", + "selected": "指定節點" + } + }, + "status": { + "status": "報告所有節點狀態", + "statusNodes": "報告__number__個節點狀態", + "label": { + "source": "報告狀態範圍", + "node": "節點", + "type": "類型", + "selectAll": "全選", + "sortByLabel": "按名稱排序", + "sortByType": "按類型排序" + }, + "scope": { + "all": "所有節點", + "selected": "指定節點" + } + }, + "debug": { + "output": "輸出", + "msgprop": "資訊屬性", + "msgobj": "完整資訊", + "to": "目標", + "debtab": "除錯窗口", + "tabcon": "除錯窗口及Console", + "toSidebar": "除錯窗口", + "toConsole": "Console", + "toStatus": "節點狀態 (32位元字元)", + "severity": "級別", + "notification": { + "activated": "成功啟動: __label__", + "deactivated": "成功取消: __label__" + }, + "sidebar": { + "label": "除錯窗口", + "name": "名稱", + "filterAll": "所有節點", + "filterSelected": "已選節點", + "filterCurrent": "當前流程", + "debugNodes": "除錯節點", + "clearLog": "清空日誌", + "openWindow": "在新視窗打開" + }, + "messageMenu": { + "collapseAll": "折疊所有路徑", + "clearPinned": "清空已固定路徑", + "filterNode": "過濾此節點", + "clearFilter": "清空過濾條件" + } + }, + "link": { + "linkIn": "輸入", + "linkOut": "輸出" + }, + "tls": { + "tls": "TLS設置", + "label": { + "use-local-files": "使用本地密鑰及證書檔", + "upload": "上傳", + "cert": "證書", + "key": "私密金鑰", + "passphrase": "密碼", + "ca": "CA證書", + "verify-server-cert":"驗證伺服器憑證" + }, + "placeholder": { + "cert":"憑證路徑 (PEM 格式)", + "key":"私密金鑰路徑 (PEM 格式)", + "ca":"CA憑證路徑 (PEM 格式)", + "passphrase":"私密金鑰密碼 (可選)" + }, + "error": { + "missing-file": "未提供證書/金鑰檔案" + } + }, + "exec": { + "label": { + "command": "命令", + "append": "追加", + "timeout": "超時", + "timeoutplace": "可選填", + "return": "輸出", + "seconds": "秒" + }, + "placeholder": { + "extraparams": "額外的輸入參數" + }, + "opt": { + "exec": "當命令完成時 - exec模式", + "spawn": "當命令進行時 - spawn模式" + }, + "oldrc": "使用舊式輸出 (相容模式)" + }, + "function": { + "label": { + "function": "函數", + "outputs": "輸出" + }, + "error": { + "inputListener":"無法在函數中監聽對'注入'事件", + "non-message-returned":"函數節點嘗試返回類型為 __type__ 的資訊" + } + }, + "template": { + "label": { + "template": "模版", + "property": "屬性", + "format": "語法高亮", + "syntax": "格式", + "output": "輸出為", + "mustache": "Mustache 模版", + "plain": "純文字", + "json": "JSON", + "yaml": "YAML", + "none": "無" + }, + "templatevalue": "This is the payload: {{payload}} !" + }, + "delay": { + "action": "行為設置", + "for": "時長", + "delaymsg": "延遲每一條資訊", + "delayfixed": "固定延遲時間", + "delayvarmsg": "允許msg.delay複寫延遲時長", + "randomdelay": "隨機延遲", + "limitrate": "限制資訊頻率", + "limitall": "所有資訊", + "limittopic": "每一個msg.topic", + "fairqueue": "依次發送每一個topic", + "timedqueue": "發所有topic", + "milisecs": "毫秒", + "secs": "秒", + "sec": "秒", + "mins": "分", + "min": "分", + "hours": "小時", + "hour": "小時", + "days": "天", + "day": "天", + "between": "介於", + "and": "至", + "rate": "速度", + "msgper": "信息 每", + "dropmsg": "不傳輸中間資訊", + "label": { + "delay": "延遲", + "variable": "變數", + "limit": "限制", + "limitTopic": "限制主題", + "random": "隨機", + "units" : { + "second": { + "plural" : "秒", + "singular": "秒" + }, + "minute": { + "plural" : "分鐘", + "singular": "分鐘" + }, + "hour": { + "plural" : "小時", + "singular": "小時" + }, + "day": { + "plural" : "天", + "singular": "天" + } + } + }, + "error": { + "buffer": "緩衝了超過 1000 條資訊", + "buffer1": "緩衝了超過 10000 條資訊" + } + }, + "trigger": { + "send": "發送", + "then": "然後", + "then-send": "然後發送", + "output": { + "string": "字串", + "number": "數字", + "existing": "現有資訊物件", + "original": "原本資訊物件", + "latest": "最新資訊物件", + "nothing": "無" + }, + "wait-reset": "等待被重置", + "wait-for": "等待", + "wait-loop": "週期性重發", + "duration": { + "ms": "毫秒", + "s": "秒", + "m": "分鐘", + "h": "小時" + }, + "extend": " 如有新資訊,延長延遲", + "label": { + "trigger": "觸發", + "trigger-block": "觸發並阻止", + "trigger-loop": "週期性重發", + "reset": "重置觸發節點條件 如果:", + "resetMessage":"msg.reset已設置", + "resetPayload":"msg.payload等於", + "resetprompt": "可選填" + } + }, + "comment": { + }, + "unknown": { + "label": { + "unknown": "未知" + }, + "tip": "

此節點是您安裝,但Node-RED所不知道的類型。

如果在此狀態下部署節點,那麼它的配置將被保留,但是流程將不會啟動,直到安裝缺少的節點。

有關更多説明,請參閱資訊側欄

" + }, + "mqtt": { + "label": { + "broker": "服務端", + "example": "e.g. localhost", + "output": "輸出", + "qos": "QoS", + "clientid": "使用者端ID", + "port": "埠", + "keepalive": "Keepalive計時(秒)", + "cleansession": "使用新的會話", + "use-tls": "使用安全連接 (SSL/TLS)", + "tls-config":"TLS 設置", + "verify-server-cert":"驗證伺服器憑證", + "compatmode": "使用舊式MQTT 3.1支援" + }, + "tabs-label": { + "connection": "連接", + "security": "安全", + "will": "Will信息", + "birth": "Birth信息" + }, + "placeholder": { + "clientid": "留白則自動隨機生成", + "clientid-nonclean":"如非新會話,必須設置使用者端ID", + "will-topic": "留白將禁止Will資訊", + "birth-topic": "留白將禁止Birth資訊" + }, + "state": { + "connected": "已連接到服務端: __broker__", + "disconnected": "已斷開與服務端 __broker__ 的連結", + "connect-failed": "與服務端 __broker__ 的連接失敗" + }, + "retain": "保留", + "output": { + "buffer": "Buffer", + "string": "字串", + "base64": "Base64編碼字串" + }, + "true": "是", + "false": "否", + "tip": "提示: 若希望通過msg屬性對topic(資訊), qos及retain(保留)進行設置, 則將上述項留白", + "errors": { + "not-defined": "主題未設置", + "missing-config": "未設置服務端", + "invalid-topic": "主題無效", + "nonclean-missingclientid": "使用者端ID未設定,使用新會話" + } + }, + "httpin": { + "label": { + "method": "請求方式", + "url": "URL", + "doc": "文字檔", + "return": "返回", + "upload": "接受檔案上傳?", + "status": "狀態碼", + "headers": "Header", + "other": "其他" + }, + "setby": "- 用 msg.method 設定 -", + "basicauth": "基本認證", + "use-tls": "使用安全連接 (SSL/TLS) ", + "tls-config":"TLS 設置", + "utf8": "UTF-8 字串", + "binary": "二進位資料", + "json": "JSON對象", + "tip": { + "in": "相對URL", + "res": "發送到此節點的消息必須來自http input節點", + "req": "提示:如果JSON解析失敗,則獲取的字串將按原樣返回." + }, + "httpreq": "http 請求", + "errors": { + "not-created": "當httpNodeRoot為否時,無法創建http-in節點", + "missing-path": "無路徑", + "no-response": "無響應物件", + "json-error": "JSON 解析錯誤", + "no-url": "未設定 URL", + "deprecated-call":"__method__方法已棄用", + "invalid-transport":"非HTTP傳輸請求" + }, + "status": { + "requesting": "請求中" + } + }, + "websocket": { + "label": { + "type": "類型", + "path": "路徑", + "url": "URL" + }, + "listenon": "監聽", + "connectto": "連接", + "sendrec": "發送/接受", + "payload": "有效載荷", + "message": "完整資訊", + "tip": { + "path1": "預設情況下,payload將包含要發送或從Websocket接收的資料。偵聽器可以配置為以JSON格式的字串發送或接收整個消息物件.", + "path2": "這條路徑將相對於 ", + "url1": "URL 應該使用ws://或者wss://方案並指向現有的websocket監聽器.", + "url2": "預設情況下,payload 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件." + }, + "errors": { + "connect-error": "ws連接發生了錯誤: ", + "send-error": "發送時發生了錯誤: ", + "missing-conf": "未設置伺服器" + } + }, + "watch": { + "label": { + "files": "文件", + "recursive": "遞迴所有子資料夾" + }, + "placeholder": { + "files": "逗號分開文件或資料夾" + }, + "tip": "在Windows上,請務必使用雙斜杠 \\\\ 來隔開資料夾名字" + }, + "tcpin": { + "label": { + "type": "類型", + "output": "輸出", + "port": "埠", + "host": "主機位址", + "payload": "的有效載荷", + "delimited": "分隔符號號", + "close-connection": "是否在成功發送每條資訊後斷開連接?", + "decode-base64": "用 Base64 解碼信息?", + "server": "伺服器", + "return": "返回", + "ms": "毫秒", + "chars": "字元" + }, + "type": { + "listen": "監聽", + "connect": "連接", + "reply": "回應 TCP" + }, + "output": { + "stream": "字串流", + "single": "單一", + "buffer": "Buffer", + "string": "字串", + "base64": "Base64 字串" + }, + "return": { + "timeout": "指定時間後", + "character": "當收到某個字元為", + "number": "指定字元數", + "never": "永不 - 保持連接", + "immed": "馬上 - 不需要等待回復" + }, + "status": { + "connecting": "正在連接到 __host__:__port__", + "connected": "已經連接到 __host__:__port__", + "listening-port": "監聽埠 __port__", + "stopped-listening": "已停止監聽埠", + "connection-from": "連接來自 __host__:__port__", + "connection-closed": "連接已關閉 __host__:__port__", + "connections": "__count__ 個連接", + "connections_plural": "__count__ 個連接" + + }, + "errors": { + "connection-lost": "連接中斷 __host__:__port__", + "timeout": "超時關閉通訊端連接,埠 __port__", + "cannot-listen": "無法監聽埠 __port__, 錯誤: __error__", + "error": "錯誤: __error__", + + "socket-error": "通訊端連接錯誤來自 __host__:__port__", + "no-host": "主機位址或埠未設定", + "connect-timeout": "連接逾時", + "connect-fail": "連接失敗" + } + }, + "udp": { + "label": { + "listen": "監聽", + "onport": "埠", + "using": "使用", + "output": "輸出", + "group": "組", + "interface": "本地IP", + "interfaceprompt": "(可選)本地 IP 綁定到", + "send": "發送一個", + "toport": "到埠", + "address": "地址", + "decode-base64": "是否解碼Base64編碼的資訊?" + }, + "placeholder": { + "interface": "(可選)eth0的IP地址", + "address": "目標IP位址" + }, + "udpmsgs": "udp信息", + "mcmsgs": "群播信息", + "udpmsg": "udp信息", + "bcmsg": "廣播資訊", + "mcmsg": "群播信息", + "output": { + "buffer": "Buffer", + "string": "字串", + "base64": "Base64編碼字串" + }, + "bind": { + "random": "綁定到任意本地埠", + "local": "綁定到本地埠", + "target": "綁定到目標埠" + }, + "tip": { + "in": "提示:確保您的防火牆將允許資料進入", + "out": "提示:如果要使用msg.ipmsg.port設置,請將位址和埠留空", + "port": "正在使用埠: " + }, + "status": { + "listener-at": "udp 監聽器正在監聽 __host__:__port__", + "mc-group": "udp 群播到 __group__", + "listener-stopped": "udp 監聽器已停止", + "output-stopped": "udp 輸出已停止", + "mc-ready": "udp 群播已準備好: __outport__ -> __host__:__port__", + "bc-ready": "udp 廣播已準備好: __outport__ -> __host__:__port__", + "ready": "udp 已準備好: __outport__ -> __host__:__port__", + "ready-nolocal": "udp 已準備好: __host__:__port__", + "re-use": "udp 重用通訊端: __outport__ -> __host__:__port__" + }, + "errors": { + "access-error": "UDP 訪問錯誤, 你可能需要root許可權才能接入1024以下的埠", + "error": "錯誤: __error__", + "bad-mcaddress": "無效的群播地址", + "interface": "必須是指定介面的IP位址", + "ip-notset": "udp: IP地址未設定", + "port-notset": "udp: 埠未設定", + "port-invalid": "udp: 無效埠號碼", + "alreadyused": "udp: 埠已被佔用" + } + }, + "switch": { + "label": { + "property": "屬性", + "rule": "規則", + "repair" : "重建資訊佇列" + }, + "and": "與", + "checkall": "全選所有規則", + "stopfirst": "接受第一條匹配資訊後停止", + "ignorecase": "忽略大小寫", + "rules": { + "btwn":"在之間", + "cont":"包含", + "regex":"匹配規則運算式", + "true":"為真", + "false":"為假", + "null":"為空", + "nnull":"非空", + "head":"head", + "tail":"tail", + "index":"index between", + "exp":"JSONata運算式", + "else":"除此以外" + }, + "errors": { + "invalid-expr": "無效的JSONata運算式: __error__", + "too-many" : "Switch節點中有太多待定信息" + } + }, + "change": { + "label": { + "rules": "規則", + "rule": "規則", + "set": "設定 __property__", + "change": "修改 __property__", + "delete": "刪除 __property__", + "move": "移動 __property__", + "changeCount": "修改: __count__條規矩", + "regex": "使用規則運算式" + }, + "action": { + "set": "設定", + "change": "修改", + "delete": "刪除", + "move": "轉移", + "to": "到", + "search": "搜索", + "replace": "替代為" + }, + "errors": { + "invalid-from": "無效的'from'屬性: __error__", + "invalid-json": "無效的'to'JSON 屬性", + "invalid-expr": "無效的JSONata運算式: __error__" + } + }, + "range": { + "label": { + "action": "操作", + "inputrange": "映射輸入資料", + "resultrange": "至目標範圍", + "from": "從", + "to": "到", + "roundresult": "取最接近整數?" + }, + "placeholder": { + "min": "e.g. 0", + "maxin": "e.g. 99", + "maxout": "e.g. 255" + }, + "scale": { + "payload": "按比例msg.payload", + "limit": "按比例並設定界限至目標範圍", + "wrap": "按比例並包含在目標範圍內" + }, + "tip": "提示: 此節點僅對數字有效", + "errors": { + "notnumber": "不是一個數字" + } + }, + "csv": { + "label": { + "columns": "列", + "separator": "分隔符號", + "c2o": "CSV至對象", + "o2c": "對象至CSV", + "input": "輸入", + "skip-s": "忽略前", + "skip-e": "行", + "firstrow": "第一行包含列名", + "output": "輸出", + "includerow": "包含列名行", + "newline": "分行符號" + }, + "placeholder": { + "columns": "用逗號分割列名" + }, + "separator": { + "comma": "逗號", + "tab": "Tab", + "space": "空格", + "semicolon": "分號", + "colon": "冒號", + "hashtag": "井號", + "other": "其他..." + }, + "output": { + "row": "每行一條信息", + "array": "僅一條資訊 [陣列]" + }, + "newline": { + "linux": "Linux (\\n)", + "mac": "Mac (\\r)", + "windows": "Windows (\\r\\n)" + }, + "errors": { + "csv_js": "此節點僅處理CSV字串或JS物件", + "obj_csv": "對象->CSV轉換未設定列模版" + } + }, + "html": { + "label": { + "select": "選取項", + "output": "輸出" + }, + "output": { + "html": "選定元素的html內容", + "text": "選定元素的純文字內容", + "attr": "包含選定元素的所有屬性的物件" + }, + "format": { + "single": "一條資訊 [陣列]", + "multi": "多條資訊,每條一個元素" + } + }, + "json": { + "errors": { + "dropped-object": "忽略非物件格式的有效負載", + "dropped": "忽略不支援格式的有效負載類型", + "dropped-error": "轉換有效負載失敗" + }, + "label": { + "o2j": "對象至JSON", + "pretty": "格式化JSON字串", + "action": "操作", + "property": "屬性", + "actions": { + "toggle": "JSON字串與物件互轉", + "str":"總是轉為JSON字串", + "obj":"總是轉為JS對象" + } + } + }, + "yaml": { + "errors": { + "dropped-object": "忽略非物件格式的有效負載", + "dropped": "忽略不支援格式的有效負載類型", + "dropped-error": "轉換有效負載失敗" + } + }, + "xml": { + "label": { + "represent": "XML標籤屬性的屬性名稱", + "prefix": "標籤文本內容的屬性名稱", + "advanced": "高級選項", + "x2o": "XML到物件選項" + }, + "errors": { + "xml_js": "此節點僅處理XML字串或JS物件." + } + }, + "rpi-gpio": { + "label": { + "gpiopin": "GPIO", + "selectpin": "選擇引腳", + "resistor": "電阻?", + "readinitial": "在部署/重啟時讀取引腳的初始狀態?", + "type": "類型", + "initpin": "初始化引腳狀態?", + "debounce": "去抖動", + "freq": "頻率", + "button": "按鈕", + "pimouse": "Pi滑鼠", + "pikeyboard": "Pi鍵盤", + "left": "左", + "right": "右", + "middle": "中" + }, + "resistor": { + "none": "無", + "pullup": "上拉電阻", + "pulldown": "下拉電阻" + }, + "digout": "數位輸出", + "pwmout": "PWM輸出", + "servo": "伺服輸出", + "initpin0": "初始引腳電平 - 低(0)", + "initpin1": "初始引腳電平 - 高(1)", + "left": "左", + "right": "右", + "middle": "中", + "any": "任何", + "pinname": "引腳", + "alreadyuse": "已被使用", + "alreadyset": "已被設為", + "tip": { + "pin": "正在使用引腳: ", + "in": "提示: 僅接受數位輸入 - 輸出必須為0或1.", + "dig": "提示: 如用數位輸出 - 輸入必須為0或1.", + "pwm": "提示: 如用PWM輸出 - 輸入必須為0至100之間; 如用高頻率可能會比預期佔用更多CPU資源.", + "ser": "提示: 如用伺服輸出 - 輸入必須為0至100之間. 50為中間值." + }, + "types": { + "digout": "數位輸出", + "input": "輸入", + "pullup": "含有上拉電阻的輸入", + "pulldown": "含有下拉電阻的輸入", + "pwmout": "PWM輸出", + "servo": "伺服輸出" + }, + "status": { + "stopped": "已停止", + "closed": "已關閉", + "not-running": "不運行" + }, + "errors": { + "ignorenode": "忽略樹莓派的特定節點", + "version": "版本命令失敗", + "sawpitype": "查看Pi類型", + "libnotfound": "找不到樹莓派RPi.GPIO的python庫", + "alreadyset": "GPIO引腳 __pin__ 已經被設定為類型: __type__", + "invalidpin": "無效GPIO引腳", + "invalidinput": "無效輸入", + "needtobeexecutable": "__command__須為可運行命令", + "mustbeexecutable": "nrgpio須為可運行", + "commandnotfound": "nrgpio命令不存在", + "commandnotexecutable": "nrgpio命令不可運行", + "error": "錯誤: __error__", + "pythoncommandnotfound": "nrpgio python命令未處於運行狀態" + } + }, + "file": { + "label": { + "filename": "檔案名", + "action": "行為", + "addnewline": "向每個有效載荷添加分行符號(\\n)?", + "createdir": "創建目錄(如果不存在)?", + "outputas": "輸出", + "breakchunks": "分拆成塊", + "breaklines": "分拆成行", + "filelabel": "文件", + "sendError": "發生錯誤時發送消息(傳統模式)", + "deletelabel": "刪除 __file__" + }, + "action": { + "append": "追加至文件", + "overwrite": "複寫文件", + "delete": "刪除檔" + }, + "output": { + "utf8": "一個utf8字串", + "buffer": "一個Buffer物件", + "lines": "每行一條信息", + "stream": "一個Buffer流" + }, + "status": { + "wrotefile": "寫入至文件: __file__", + "deletedfile": "刪除檔: __file__", + "appendedfile": "追加至文件: __file__" + }, + "errors": { + "nofilename": "未指定檔案名", + "invaliddelete": "警告:無效刪除。請在配置對話方塊中使用特定的刪除選項", + "deletefail": "無法刪除檔: __error__", + "writefail": "無法寫入文件: __error__", + "appendfail": "無法追加到文件: __error__", + "createfail": "檔創建失敗: __error__" + }, + "tip": "提示: 檔案名應該是絕對路徑,否則它將相對於Node-RED進程的工作目錄。" + }, + "split": { + "intro":"基於以下類型拆分msg.payload:", + "object":"對象", + "objectSend":"每個鍵值對作為單個消息發送", + "strBuff":"字串 / Buffer", + "array":"陣列", + "splitUsing":"拆分使用", + "splitLength":"固定長度", + "stream":"作為消息流處理", + "addname":" 複製鍵到 " + }, + "join":{ + "mode":{ + "mode":"模式", + "auto":"自動", + "merge":"合併序列", + "reduce":"縮減序列", + "custom":"手動" + }, + "combine":"合併每個", + "create":"輸出為", + "type":{ + "string":"字串", + "array":"陣列", + "buffer":"Buffer", + "object":"鍵值對對象", + "merged":"合併對象" + }, + "using":"使用此值", + "key":"作為鍵", + "joinedUsing":"合併符號", + "send":"發送資訊:", + "afterCount":"達到一定數量的資訊時", + "count":"數量", + "subsequent":"和每個後續的消息", + "afterTimeout":"第一條消息的若干時間後", + "seconds":"秒", + "complete":"在收到存在msg.complete的消息後", + "tip":"此模式假定此節點與split相連, 或者接收到的消息有正確配置的msg.parts屬性.", + "too-many" : "join節點中有太多待定信息", + "merge": { + "topics-label":"合併主題", + "topics":"主題", + "topic" : "主題", + "on-change":"當收到一個新主題時發送已合併資訊" + }, + "reduce": { + "exp": "Reduce運算式", + "exp-value": "exp", + "init": "初始值", + "right": "反向求值(從後往前)", + "fixup": "Fix-up exp" + }, + "errors": { + "invalid-expr": "無效的JSONata運算式: __error__" + } + }, + "sort" : { + "target" : "排序屬性", + "seq" : "資訊佇列", + "key" : "鍵值", + "elem" : "元素值", + "order" : "順序", + "ascending" : "昇冪", + "descending" : "降冪", + "as-number" : "作為數值", + "invalid-exp" : "sort節點中存在無效的JSONata運算式", + "too-many" : "sort節點中有太多待定信息", + "clear" : "清空sort節點中的待定資訊" + }, + "batch" : { + "mode": { + "label" : "模式", + "num-msgs" : "按指定數量分組", + "interval" : "按時間間隔分組", + "concat" : "按主題分組" + }, + "count": { + "label" : "分組數量", + "overlap" : "隊末隊首重疊數量", + "count" : "數量", + "invalid" : "無效的分組數量或重疊數量" + }, + "interval": { + "label" : "時間間隔", + "seconds" : "秒", + "empty" : "無數據到達時發送空資訊" + }, + "concat": { + "topics-label": "主題", + "topic" : "主題" + }, + "too-many" : "batch節點中有太多待定信息", + "unexpected" : "未知模式", + "no-parts" : "資訊中沒有parts屬性" + } +} From 17653761b9b2a31b2466e79802ed907f3edd3b94 Mon Sep 17 00:00:00 2001 From: Yuma Matsuura Date: Tue, 3 Sep 2019 18:43:46 +0900 Subject: [PATCH 005/346] Update a line break function --- .../@node-red/editor-client/src/js/ui/view.js | 192 ++++++++++-------- 1 file changed, 109 insertions(+), 83 deletions(-) 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 62282e624..ffd30af59 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1803,7 +1803,7 @@ RED.view = (function() { RED.notify(RED._("clipboard.nodeCopied",{count:nns.length}),{id:"clipboard"}); } } - + function calculateTextWidth(str, className, offset) { var result=convertLineBreakCharacter(str); var width = 0; @@ -1831,26 +1831,35 @@ RED.view = (function() { return [offsetW+w,offsetH+h]; } + var separateTextByLineBreak = []; function convertLineBreakCharacter(str) { var result = []; - var temp = str.split('\\n'); - var result_temp = null; - for (var i = 0;i < temp.length;i++) { - if (result_temp == null || result_temp == '') { - result_temp = temp[i]; - } - var chr = temp[i].charAt(temp[i].length-1); - if (i < temp.length - 1) { - if (chr == '\\') { - result_temp += 'n' + temp[i+1]; - } else { + var count = 0; + var result_temp = ''; + for (var i = 0;i < str.length;i++) { + if (str.charAt(i) == '\\') { + if (str.charAt(i+1) == '\\') { + result_temp += str.charAt(i); + i++; + } else if (str.charAt(i+1) == 'n') { result.push(result_temp); - result_temp = null; - } + if (i+1 == str.length-1) { + result.push(''); + } + result_temp = ''; + count = i+2; + i++; + } else { + result_temp += str.charAt(i); + } } else { - result.push(result_temp); + result_temp += str.charAt(i); } } + if (count == 0 || count < str.length) { + result.push(result_temp); + } + separateTextByLineBreak = result; return result; } @@ -2708,21 +2717,19 @@ RED.view = (function() { var isLink = (d.type === "link in" || d.type === "link out") var hideLabel = d.hasOwnProperty('l')?!d.l : isLink; node.attr("id",d.id); - var l = RED.utils.getNodeLabel(d); + var labelWidth = calculateTextWidth(RED.utils.getNodeLabel(d), "red-ui-flow-node-label", 50); if (d.resize || d.w === undefined) { if (hideLabel) { - d.w = 30; + d.w = node_height; } else { - d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) ); + d.w = Math.max(node_width,20*(Math.ceil((labelWidth+(d._def.inputs>0?7:0))/20)) ); } } if (hideLabel) { - node_height = 30; + d.h = Math.max(node_height,(d.outputs || 0) * 15); } else { - node_height = 6 + 24 * convertLineBreakCharacter(l).length; + d.h = Math.max(6+24*separateTextByLineBreak.length, (d.outputs || 0) * 15, 30); } - d.h = Math.max(node_height,(d.outputs||0) * 15); - // if (d._def.badge) { // var badge = node.append("svg:g").attr("class","node_badge_group"); // var badgeRect = badge.append("rect").attr("class","node_badge").attr("rx",5).attr("ry",5).attr("width",40).attr("height",15); @@ -2742,7 +2749,7 @@ RED.view = (function() { .attr("rx",5) .attr("ry",5) .attr("width",32) - .attr("height",26); + .attr("height",node_height-4); nodeButtonGroup.append("rect") .attr("class","red-ui-flow-node-button-button") .attr("x",function(d) { return d._def.align == "right"? 11:5}) @@ -2750,7 +2757,7 @@ RED.view = (function() { .attr("rx",4) .attr("ry",4) .attr("width",16) - .attr("height",18) + .attr("height",node_height-12) .attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def); /*d._def.color;*/}) .attr("cursor","pointer") .on("mousedown",function(d) {if (!lasso && isButtonEnabled(d)) {focusView();d3.select(this).attr("fill-opacity",0.2);d3.event.preventDefault(); d3.event.stopPropagation();}}) @@ -2908,17 +2915,21 @@ RED.view = (function() { //icon.style("pointer-events","none"); icon_group.style("pointer-events","none"); } - var text = node.append("svg:text") + var labelLineNumber = (separateTextByLineBreak.length == 0)? 1:separateTextByLineBreak.length; + for(var i=0;i0?7:0))/20)) ); + d.w = Math.max(node_width,20*(Math.ceil((labelWidth+(d._def.inputs>0?7:0))/20)) ); } // d.w = Math.max(node_width,20*(Math.ceil((calculateTextWidth(l, "red-ui-flow-node-label", 50)+(d._def.inputs>0?7:0))/20)) ); d.x += (d.w-ow)/2; d.resize = false; } if (hideLabel) { - node_height = 30; + d.h = Math.max(node_height,(d.outputs || 0) * 15); } else { - node_height = 6 + 24 * convertLineBreakCharacter(l).length; + d.h = Math.max(6+24*separateTextByLineBreak.length,(d.outputs || 0) * 15, 30); } - d.h = Math.max(node_height,(d.outputs || 0) * 15); var thisNode = d3.select(this); thisNode.classed("red-ui-flow-node-disabled", function(d) { return d.d === true}); @@ -2993,6 +3004,65 @@ RED.view = (function() { .attr("height",function(d){return d.h}) .classed("red-ui-flow-node-highlighted",function(d) { return d.highlighted; }) ; + var l = ""; + if (d._def.label) { + l = d._def.label; + try { + l = (typeof l === "function" ? l.call(d) : l)||""; + l = RED.text.bidi.enforceTextDirectionWithUCC(l); + } catch(err) { + console.log("Definition error: "+d.type+".label",err); + l = d.type; + } + } + var sa = convertLineBreakCharacter(l); + var sn = sa.length; + var st = ""; + var yp = d.h / 2 - (sn / 2) * 24 + 16 + if(labelLineNumber0?5:0);}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-right").attr("x",function(d){return d.w-30;}); //thisNode.selectAll(".red-ui-flow-node-icon-shade-border-right").attr("d",function(d){return "M "+(d.w-30)+" 1 l 0 "+(d.h-2)}); @@ -3078,50 +3148,6 @@ RED.view = (function() { port.attr("transform", function(d) { return "translate("+x+","+((y+13*i)-5)+")";}); }); } - thisNode.selectAll("text.red-ui-flow-node-label").html(function(d,i){ - var l = ""; - if (d._def.label) { - l = d._def.label; - try { - l = (typeof l === "function" ? l.call(d) : l)||""; - l = RED.text.bidi.enforceTextDirectionWithUCC(l); - } catch(err) { - console.log("Definition error: "+d.type+".label",err); - l = d.type; - } - } - var sa = convertLineBreakCharacter(l); - var sn = sa.length; - var ic = 0; - var st = ""; - var yp = d.h/2-(sn/2)*24+16 - var yn = 0; - var dy = ".3px"; - for (ic=0; ic"+sa[ic]+""; - } - if (sn!=1) { - return st; - } else { - return sa[0]!=null ? sa[0]:l; - } - }) - .attr("y", function(d){return (d.h/2)-1;}) - .attr("class",function(d){ - var s = ""; - if (d._def.labelStyle) { - s = d._def.labelStyle; - try { - s = (typeof s === "function" ? s.call(d) : s)||""; - } catch(err) { - console.log("Definition error: "+d.type+".labelStyle",err); - s = ""; - } - s = " "+s; - } - return "red-ui-flow-node-label"+(d._def.align?" red-ui-flow-node-label-"+d._def.align:"")+s; - }).classed("hide",hideLabel); if (d._def.icon) { var icon = thisNode.select(".red-ui-flow-node-icon"); var faIcon = thisNode.select(".fa-lg"); From 7957ec43692f00277f96ed23de509a7e841e86a5 Mon Sep 17 00:00:00 2001 From: Yuma Matsuura Date: Fri, 27 Sep 2019 19:17:17 +0900 Subject: [PATCH 006/346] Modify id --- .../@node-red/editor-client/src/js/ui/view.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 ffd30af59..9e2a7258f 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -2916,10 +2916,11 @@ RED.view = (function() { icon_group.style("pointer-events","none"); } var labelLineNumber = (separateTextByLineBreak.length == 0)? 1:separateTextByLineBreak.length; + var labelId = d.id.replace(".","-"); for(var i=0;i Date: Fri, 29 Nov 2019 18:50:30 -0700 Subject: [PATCH 007/346] Replace 'clone' with 'lodash.clonedeep' --- .gitignore | 1 + package.json | 1 + packages/node_modules/@node-red/util/lib/util.js | 5 +++-- packages/node_modules/@node-red/util/package.json | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c3fa9624a..7b1f1f219 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ packages/node_modules/@node-red/editor-client/public !test/**/node_modules docs !packages/node_modules/**/docs +.vscode \ No newline at end of file diff --git a/package.json b/package.json index b0b30df07..984f0f00d 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "js-yaml": "3.13.1", "json-stringify-safe": "5.0.1", "jsonata": "1.7.0", + "lodash.clonedeep": "^4.5.0", "media-typer": "1.1.0", "memorystore": "1.6.1", "mime": "2.4.4", diff --git a/packages/node_modules/@node-red/util/lib/util.js b/packages/node_modules/@node-red/util/lib/util.js index ef43739e6..e223fc84b 100644 --- a/packages/node_modules/@node-red/util/lib/util.js +++ b/packages/node_modules/@node-red/util/lib/util.js @@ -19,8 +19,8 @@ * @mixin @node-red/util_util */ - const clone = require("clone"); +const clonedeep = require("lodash.clonedeep"); const jsonata = require("jsonata"); const safeJSONStringify = require("json-stringify-safe"); const util = require("util"); @@ -87,7 +87,8 @@ function cloneMessage(msg) { var res = msg.res; delete msg.req; delete msg.res; - var m = clone(msg); + + var m = clonedeep(msg); if (req) { m.req = req; msg.req = req; diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 174db5bfd..b89559d1f 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -19,6 +19,7 @@ "i18next": "15.1.2", "json-stringify-safe": "5.0.1", "jsonata": "1.7.0", + "lodash.clonedeep": "^4.5.0", "when": "3.7.8" } } From d017dd75cd1f7394bd32f2a0e11ae946697ca7e3 Mon Sep 17 00:00:00 2001 From: Amo DelBello Date: Fri, 29 Nov 2019 20:15:56 -0700 Subject: [PATCH 008/346] Remove 'clone' from util --- packages/node_modules/@node-red/util/lib/util.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node_modules/@node-red/util/lib/util.js b/packages/node_modules/@node-red/util/lib/util.js index e223fc84b..387bc0728 100644 --- a/packages/node_modules/@node-red/util/lib/util.js +++ b/packages/node_modules/@node-red/util/lib/util.js @@ -19,7 +19,6 @@ * @mixin @node-red/util_util */ -const clone = require("clone"); const clonedeep = require("lodash.clonedeep"); const jsonata = require("jsonata"); const safeJSONStringify = require("json-stringify-safe"); From fe0d4f08f35e06412cc83f68a41c2f99fe07117b Mon Sep 17 00:00:00 2001 From: Vladimir Dronnikov Date: Wed, 25 Dec 2019 06:21:55 +0300 Subject: [PATCH 009/346] Allow to know particular session from status node The rationale is to keep own list of active sessions. As a workaround for https://discourse.nodered.org/t/tcp-connection-pool-better-separation/19432 TIA --- .../node_modules/@node-red/nodes/core/network/31-tcpin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js index e52e9c382..655e72c1e 100644 --- a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js @@ -74,7 +74,7 @@ module.exports = function(RED) { buffer = (node.datatype == 'buffer') ? Buffer.alloc(0) : ""; node.connected = true; node.log(RED._("tcpin.status.connected",{host:node.host,port:node.port})); - node.status({fill:"green",shape:"dot",text:"common.status.connected"}); + node.status({fill:"green",shape:"dot",text:"common.status.connected",_session:{type:"tcp",id:id}}); }); client.setKeepAlive(true,120000); connectionPool[id] = client; @@ -121,7 +121,7 @@ module.exports = function(RED) { client.on('close', function() { delete connectionPool[id]; node.connected = false; - node.status({fill:"red",shape:"ring",text:"common.status.disconnected"}); + node.status({fill:"red",shape:"ring",text:"common.status.disconnected",_session:{type:"tcp",id:id}}); if (!node.closing) { if (end) { // if we were asked to close then try to reconnect once very quick. end = false; From b49835c72ff176a0e990cce03b07e1352682430d Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 21 Jan 2020 09:49:19 +0900 Subject: [PATCH 010/346] Support BrowserStack in UI testing --- Gruntfile.js | 4 + scripts/install-ui-test-dependencies.sh | 6 +- test/editor/pageobjects/util/key_page.js | 6 ++ test/editor/wdio.conf.js | 108 ++++++++++++++++++----- 4 files changed, 101 insertions(+), 23 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 67739afe2..bbba61390 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -26,6 +26,10 @@ module.exports = function(grunt) { nodemonArgs.push(flowFile); } + var browserstack = grunt.option('browserstack'); + if (browserstack) { + process.env.BROWSERSTACK = true; + } var nonHeadless = grunt.option('non-headless'); if (nonHeadless) { process.env.NODE_RED_NON_HEADLESS = true; diff --git a/scripts/install-ui-test-dependencies.sh b/scripts/install-ui-test-dependencies.sh index 0e13f0b4f..155d2b275 100755 --- a/scripts/install-ui-test-dependencies.sh +++ b/scripts/install-ui-test-dependencies.sh @@ -3,5 +3,7 @@ npm install --no-save \ wdio-chromedriver-service@^0.1.5 \ wdio-mocha-framework@^0.6.4 \ wdio-spec-reporter@^0.1.5 \ - webdriverio@^4.14.1 \ - chromedriver@^78.0.1 + webdriverio@^4.14.4 \ + chromedriver@^79.0.0 \ + wdio-browserstack-service@^0.1.19 \ + browserstack-local@^1.4.4 diff --git a/test/editor/pageobjects/util/key_page.js b/test/editor/pageobjects/util/key_page.js index 509af9e22..497a8a141 100644 --- a/test/editor/pageobjects/util/key_page.js +++ b/test/editor/pageobjects/util/key_page.js @@ -27,6 +27,12 @@ var shortCutKeyMapForMac = { }; function getShortCutKey(type) { + if (process.env.BROWSERSTACK) { + if (browser.desiredCapabilities.os === 'OS X') { + return shortCutKeyMapForMac[type]; + } + return shortCutKeyMap[type]; + } if (os.type() === 'Darwin') { return shortCutKeyMapForMac[type]; } diff --git a/test/editor/wdio.conf.js b/test/editor/wdio.conf.js index 7bbfcbe24..4e5a602e0 100644 --- a/test/editor/wdio.conf.js +++ b/test/editor/wdio.conf.js @@ -14,6 +14,7 @@ * limitations under the License. **/ +var browserstack = require('browserstack-local'); exports.config = { // @@ -48,27 +49,20 @@ exports.config = { // and 30 processes will get spawned. The property handles how many capabilities // from the same test should run tests. // - maxInstances: 10, + // maxInstances: 10, // // If you have trouble getting all important capabilities together, check out the // Sauce Labs platform configurator - a great tool to configure your capabilities: // https://docs.saucelabs.com/reference/platforms-configurator // - capabilities: [{ + // capabilities: [{ // maxInstances can get overwritten per capability. So if you have an in-house Selenium // grid with only 5 firefox instances available you can make sure that not more than // 5 instances get started at a time. - maxInstances: 2, + // maxInstances: 5, // - browserName: 'chrome', - 'goog:chromeOptions': { - args: process.env.NODE_RED_NON_HEADLESS - // Runs tests with opening a browser. - ? ['--disable-gpu', '--no-sandbox'] - // Runs tests without opening a browser. - : ['--headless', '--disable-gpu', 'window-size=1920,1080', '--no-sandbox'] - }, - }], + // browserName: 'firefox' + // }], // // =================== // Test Configurations @@ -103,7 +97,7 @@ exports.config = { baseUrl: 'http://localhost', // // Default timeout for all waitFor* commands. - waitforTimeout: 10000, + waitforTimeout: 20000, // // Default timeout in milliseconds for request // if Selenium Grid doesn't send response @@ -134,9 +128,7 @@ exports.config = { // Services take over a specific job you don't want to take care of. They enhance // your test setup with almost no effort. Unlike plugins, they don't add new // commands. Instead, they hook themselves up into the test process. - port: 9515, - path: '/', - services: ['chromedriver'], + //services: ['chromedriver'], // // Framework you want to run your specs with. // The following are supported: Mocha, Jasmine, and Cucumber @@ -155,7 +147,7 @@ exports.config = { // Options to be passed to Mocha. // See the full list at http://mochajs.org/ mochaOpts: { - timeout: 100000, + timeout: 1000000, ui: 'bdd' }, // @@ -171,8 +163,44 @@ exports.config = { * @param {Object} config wdio configuration object * @param {Array.} capabilities list of capabilities details */ - // onPrepare: function (config, capabilities) { - // }, + onPrepare: function (config, capabilities) { + if (process.env.BROWSERSTACK) { + return new Promise(function (resolve, reject) { + var options = { key: exports.config.key }; + var proxy = process.env.http_proxy || process.env.HTTP_PROXY; + if (proxy) { + var proxyConfigs = proxy.match(/^(https?):\/\/(([^:@\/]+):([^:@\/]+)@)?([^:@\/]+)(:([^:@\/]+))?\/?$/); + if (proxyConfigs) { + var protocol = proxyConfigs[1]; + var user = proxyConfigs[3]; + var pass = proxyConfigs[4]; + var host = proxyConfigs[5]; + var port = proxyConfigs[7]; + if (!port) { + if (protocol === 'http') { + port = 80; + } else if (protocol === 'https') { + port = 443; + } + } + if (host) { options.proxyHost = host; } + if (port) { options.proxyPort = port; } + if (user) { options.proxyUser = user; } + if (pass) { options.proxyPass = pass; } + } else { + reject('error in parsing the environment variable, http_proxy'); + } + } + exports.bs_local = new browserstack.Local(); + exports.bs_local.start(options, function (error) { + if (error) { + return reject(error); + } + resolve(); + }); + }); + } + }, /** * Gets executed just before initialising the webdriver session and test framework. It allows you * to manipulate configurations depending on the capability or spec. @@ -267,6 +295,44 @@ exports.config = { * @param {Object} config wdio configuration object * @param {Array.} capabilities list of capabilities details */ - // onComplete: function(exitCode, config, capabilities) { - // } + onComplete: function(exitCode, config, capabilities) { + if (process.env.BROWSERSTACK) { + exports.bs_local.stop(function () {}); + } + } +}; + +if (process.env.BROWSERSTACK) { + exports.config.maxInstances = 1; + if (process.env.BROWSERSTACK_USERNAME && process.env.BROWSERSTACK_ACCESS_KEY) { + exports.config.user = process.env.BROWSERSTACK_USERNAME; + exports.config.key = process.env.BROWSERSTACK_ACCESS_KEY; + } else { + console.log('You need to set the following environment variables.'); + console.log('BROWSERSTACK_USERNAME='); + console.log('BROWSERSTACK_ACCESS_KEY='); + } + exports.config.services = ['browserstack']; + var capabilities = []; + capabilities.push({ os: 'Windows', os_version: '10', browser: 'Chrome', resolution: '1920x1080', 'browserstack.local': true }); + capabilities.push({ os: 'Windows', os_version: '10', browser: 'Firefox', resolution: '1920x1080', 'browserstack.local': true }); + capabilities.push({ os: 'OS X', os_version: 'Catalina', browser: 'Chrome', resolution: '1920x1080', 'browserstack.local': true }); + capabilities.push({ os: 'OS X', os_version: 'Catalina', browser: 'Firefox', resolution: '1920x1080', 'browserstack.local': true }); + exports.config.capabilities = capabilities; +} else { + exports.config.maxInstances = 10; + exports.config.port = 9515; + exports.config.path = '/'; + exports.config.services = ['chromedriver']; + exports.config.capabilities = [{ + maxInstances: 2, + browserName: 'chrome', + 'goog:chromeOptions': { + args: process.env.NODE_RED_NON_HEADLESS + // Runs tests with opening a browser. + ? ['--disable-gpu', '--no-sandbox'] + // Runs tests without opening a browser. + : ['--headless', '--disable-gpu', 'window-size=1920,1080', '--no-sandbox'] + } + }]; } From c16c119a7db3747142c9be34d722f15da7024c89 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 21 Jan 2020 20:13:34 +0900 Subject: [PATCH 011/346] Remove unnecessary namespaces for i18n --- .../@node-red/editor-client/locales/ja/editor.json | 6 +++--- .../node_modules/@node-red/nodes/core/parsers/70-HTML.html | 2 +- .../node_modules/@node-red/nodes/core/parsers/70-XML.html | 2 +- .../node_modules/@node-red/nodes/core/parsers/70-YAML.html | 2 +- .../node_modules/@node-red/nodes/core/sequence/18-sort.html | 4 ++-- .../@node-red/nodes/core/sequence/19-batch.html | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) 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 8290ca1c1..b8e790b66 100755 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -803,9 +803,9 @@ "expandItems": "要素を展開", "collapseItems": "要素を折り畳む", "duplicate": "複製", - "error": { - "invalidJSON": "不正なJSON: " - } + "error": { + "invalidJSON": "不正なJSON: " + } }, "markdownEditor": { "title": "マークダウンエディタ", diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-HTML.html b/packages/node_modules/@node-red/nodes/core/parsers/70-HTML.html index 043d60e2b..8fa714ccf 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-HTML.html +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-HTML.html @@ -1,7 +1,7 @@ diff --git a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.html index f3cb47a54..418ac605b 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/19-batch.html +++ b/packages/node_modules/@node-red/nodes/core/sequence/19-batch.html @@ -60,8 +60,8 @@
- - + +
From c3df1c6cdea0a5de0e3408d03f47df3377122ea2 Mon Sep 17 00:00:00 2001 From: Paul Wieland Date: Thu, 23 Jan 2020 08:55:50 -0500 Subject: [PATCH 012/346] Add support for user definable properties to inject node --- .../nodes/core/common/20-inject.html | 179 ++++++++++++++---- .../@node-red/nodes/core/common/20-inject.js | 67 ++++--- 2 files changed, 181 insertions(+), 65 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/common/20-inject.html b/packages/node_modules/@node-red/nodes/core/common/20-inject.html index 77ced7d66..c45be4f86 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 @@ -15,15 +15,12 @@ --> diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index 5dfe45fee..d0a350e6a 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -25,6 +25,7 @@ module.exports = function(RED) { this.op1type = n.op1type || "str"; this.op2type = n.op2type || "str"; this.second = n.second || false; + this.property = n.property || "topic"; if (this.op1type === 'val') { if (this.op1 === 'true' || this.op1 === 'false') { @@ -112,7 +113,7 @@ module.exports = function(RED) { }); var processMessage = function(msg) { - var topic = msg.topic || "_none"; + var topic = RED.util.getMessageProperty(msg,node.property) || "_none"; var promise; if (node.bytopic === "all") { topic = "_none"; } node.topics[topic] = node.topics[topic] || {}; diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/89-trigger.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/89-trigger.html index 836cabc6f..3caa0ab0a 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/function/89-trigger.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/89-trigger.html @@ -14,7 +14,7 @@ limitations under the License. --> - diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 5eb83f9d1..5774d5dac 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -302,7 +302,7 @@ "wait-for": "wait for", "wait-loop": "resend it every", "for": "Handling", - "bytopics": "each msg.topic independently", + "bytopics": "each", "alltopics": "all messages", "duration": { "ms": "Milliseconds", diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index adc033390..be98d50e3 100755 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -302,7 +302,7 @@ "wait-for": "指定した時間待機", "wait-loop": "指定した時間間隔毎に送信を繰り返す", "for": "処理対象", - "bytopics": "msg.topic毎", + "bytopics": "毎", "alltopics": "全メッセージ", "duration": { "ms": "ミリ秒", diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index ebf1c8db9..063fec47c 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -378,6 +378,51 @@ describe('trigger node', function() { }); }); + it('should handle multiple other properties individually if asked to do so', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", property:"foo", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + try { + c += 1; + if (c === 1) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("foo", "A"); + } + else if (c === 2) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("foo", "B"); + } + else if (c === 3) { + msg.should.have.a.property("payload", 1); + msg.should.have.a.property("foo", "C"); + } + else if (c === 4) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("foo", "A"); + } + else if (c === 5) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("foo", "B"); + } + else if (c === 6) { + msg.should.have.a.property("payload", 0); + msg.should.have.a.property("foo", "C"); + done(); + } + } catch(err) { + done(err); + } + }); + n1.emit("input", {payload:1,foo:"A"}); + n1.emit("input", {payload:2,foo:"B"}); + n1.emit("input", {payload:3,foo:"C"}); + }); + }); + it('should be able to return things from flow and global context variables', function(done) { var spy = sinon.stub(RED.util, 'evaluateNodeProperty', function(arg1, arg2, arg3, arg4, arg5) { if (arg5) { arg5(null, arg1) } else { return arg1; } } From 87aacb4270f1db52952e6257e115cc359f8350f9 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Thu, 30 Jan 2020 22:20:55 +0000 Subject: [PATCH 020/346] change property name to leave space if we want to also do main payload property --- .../@node-red/nodes/core/function/89-trigger.html | 10 +++++----- .../@node-red/nodes/core/function/89-trigger.js | 4 ++-- test/nodes/core/function/89-trigger_spec.js | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html index 5e250be28..187a87190 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html @@ -66,8 +66,8 @@ - - + +
@@ -93,7 +93,7 @@ reset: {value:""}, bytopic: {value:"all"}, outputs: {value:1}, - property: {value:"topic",required:true} + topic: {value:"topic",required:true} }, inputs:1, outputs:1, @@ -121,9 +121,9 @@ $("#node-input-bytopic").on("change", function() { console.log("BYT",$("#node-input-bytopic").val()); if ($("#node-input-bytopic").val() === "all") { - $("#node-trigger-property").hide(); + $("#node-stream-topic").hide(); } else { - $("#node-trigger-property").show(); + $("#node-stream-topic").show(); } }); diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index d0a350e6a..dab7a83ab 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -25,7 +25,7 @@ module.exports = function(RED) { this.op1type = n.op1type || "str"; this.op2type = n.op2type || "str"; this.second = n.second || false; - this.property = n.property || "topic"; + this.topic = n.topic || "topic"; if (this.op1type === 'val') { if (this.op1 === 'true' || this.op1 === 'false') { @@ -113,7 +113,7 @@ module.exports = function(RED) { }); var processMessage = function(msg) { - var topic = RED.util.getMessageProperty(msg,node.property) || "_none"; + var topic = RED.util.getMessageProperty(msg,node.topic) || "_none"; var promise; if (node.bytopic === "all") { topic = "_none"; } node.topics[topic] = node.topics[topic] || {}; diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index 063fec47c..582b47904 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -379,7 +379,7 @@ describe('trigger node', function() { }); it('should handle multiple other properties individually if asked to do so', function(done) { - var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", property:"foo", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] }, + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", bytopic:"topic", topic:"foo", op1:"1", op2:"0", op1type:"num", op2type:"num", duration:"30", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; helper.load(triggerNode, flow, function() { var n1 = helper.getNode("n1"); From 7ffd37d9cb0d1e9ad7a49dcc2a111cca8085f8f4 Mon Sep 17 00:00:00 2001 From: JIYE YU Date: Tue, 28 Jan 2020 13:39:41 +0900 Subject: [PATCH 021/346] add zn-CN translation for nodes:common,function,storage --- .../nodes/locales/zh-CN/common/20-inject.html | 34 +++++++++ .../nodes/locales/zh-CN/common/21-debug.html | 25 +++++++ .../locales/zh-CN/common/24-complete.html | 24 ++++++ .../nodes/locales/zh-CN/common/25-catch.html | 36 +++++++++ .../nodes/locales/zh-CN/common/25-status.html | 33 +++++++++ .../nodes/locales/zh-CN/common/60-link.html | 31 ++++++++ .../locales/zh-CN/common/90-comment.html | 21 ++++++ .../locales/zh-CN/common/98-unknown.html | 24 ++++++ .../locales/zh-CN/function/10-function.html | 51 +++++++++++++ .../locales/zh-CN/function/10-switch.html | 37 ++++++++++ .../locales/zh-CN/function/15-change.html | 33 +++++++++ .../locales/zh-CN/function/16-range.html | 40 ++++++++++ .../locales/zh-CN/function/80-template.html | 46 ++++++++++++ .../locales/zh-CN/function/89-delay.html | 32 ++++++++ .../locales/zh-CN/function/89-trigger.html | 33 +++++++++ .../nodes/locales/zh-CN/function/90-exec.html | 74 +++++++++++++++++++ .../nodes/locales/zh-CN/storage/10-file.html | 59 +++++++++++++++ .../nodes/locales/zh-CN/storage/23-watch.html | 25 +++++++ 18 files changed, 658 insertions(+) create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/common/20-inject.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/common/21-debug.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/common/24-complete.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-catch.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-status.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/common/60-link.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/common/90-comment.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/common/98-unknown.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-function.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-switch.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/function/15-change.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/function/16-range.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/function/80-template.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-delay.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-trigger.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/function/90-exec.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/storage/23-watch.html diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/20-inject.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/20-inject.html new file mode 100644 index 000000000..142b362fa --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/20-inject.html @@ -0,0 +1,34 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/21-debug.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/21-debug.html new file mode 100644 index 000000000..f96c74f6d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/21-debug.html @@ -0,0 +1,25 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/24-complete.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/24-complete.html new file mode 100644 index 000000000..e69ebc6a0 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/24-complete.html @@ -0,0 +1,24 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-catch.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-catch.html new file mode 100644 index 000000000..5b2b4c3b2 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-catch.html @@ -0,0 +1,36 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-status.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-status.html new file mode 100644 index 000000000..133bc93d5 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-status.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/60-link.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/60-link.html new file mode 100644 index 000000000..6f2bc5f99 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/60-link.html @@ -0,0 +1,31 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/90-comment.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/90-comment.html new file mode 100644 index 000000000..f98577ff4 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/90-comment.html @@ -0,0 +1,21 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/98-unknown.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/98-unknown.html new file mode 100644 index 000000000..108e19228 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/98-unknown.html @@ -0,0 +1,24 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-function.html new file mode 100644 index 000000000..035ccc81f --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-function.html @@ -0,0 +1,51 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-switch.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-switch.html new file mode 100644 index 000000000..7ad25c24f --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/10-switch.html @@ -0,0 +1,37 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/15-change.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/15-change.html new file mode 100644 index 000000000..fcba3fed4 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/15-change.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/16-range.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/16-range.html new file mode 100644 index 000000000..efda1d5a2 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/16-range.html @@ -0,0 +1,40 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/80-template.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/80-template.html new file mode 100644 index 000000000..938a77818 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/80-template.html @@ -0,0 +1,46 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-delay.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-delay.html new file mode 100644 index 000000000..690bddaea --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-delay.html @@ -0,0 +1,32 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-trigger.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-trigger.html new file mode 100644 index 000000000..606a08c2f --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-trigger.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/90-exec.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/90-exec.html new file mode 100644 index 000000000..ba5951a12 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/90-exec.html @@ -0,0 +1,74 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html new file mode 100644 index 000000000..7db958d94 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html @@ -0,0 +1,59 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/23-watch.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/23-watch.html new file mode 100644 index 000000000..eec611429 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/23-watch.html @@ -0,0 +1,25 @@ + + + From 6d3eb7bb4b63b2e6f0474ff9d941b1b127041635 Mon Sep 17 00:00:00 2001 From: JIYE YU Date: Wed, 29 Jan 2020 19:21:50 +0900 Subject: [PATCH 022/346] fix translated doc according to comments --- .../@node-red/nodes/locales/zh-CN/common/20-inject.html | 8 ++++---- .../@node-red/nodes/locales/zh-CN/common/21-debug.html | 4 ++-- .../@node-red/nodes/locales/zh-CN/common/25-status.html | 2 +- .../@node-red/nodes/locales/zh-CN/function/16-range.html | 2 +- .../nodes/locales/zh-CN/function/89-trigger.html | 6 +++--- .../@node-red/nodes/locales/zh-CN/function/90-exec.html | 4 ++-- .../@node-red/nodes/locales/zh-CN/storage/10-file.html | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/20-inject.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/20-inject.html index 142b362fa..78b218083 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/20-inject.html +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/20-inject.html @@ -15,20 +15,20 @@ --> diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/21-debug.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/21-debug.html index f96c74f6d..e3137c50d 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/21-debug.html +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/21-debug.html @@ -15,11 +15,11 @@ --> diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-status.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-status.html index 133bc93d5..7d9504c9f 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-status.html +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/common/25-status.html @@ -28,6 +28,6 @@
报告状态的节点的名称(如果已设置)。

详细

-

该节点不包含payload

+

该节点不包含有效荷载

默认情况下,节点会获取同一工作空间标签页上报告所有节点的状态。可以通过配置来设定目标节点。

diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/16-range.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/16-range.html index efda1d5a2..b5d2d033f 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/16-range.html +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/16-range.html @@ -19,7 +19,7 @@

输入

payload数值
-
payload一定得是一个数值. 否则则会映射失败。
+
有效荷载一定得是一个数值. 否则则会映射失败。

输出

diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-trigger.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-trigger.html index 606a08c2f..5f27a5002 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-trigger.html +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/89-trigger.html @@ -24,10 +24,10 @@

详细

-

该节点可用于在流中创建一个超时。 默认情况下,当它收到一条消息时,它将发送一条带有1的payload的消息。然后它将等待250毫秒,再发送第二条消息,其payload为0。这可以用于使连接到Raspberry Pi GPIO引脚的LED闪烁等例子上。

-

可以将发送的每个消息的payload配置为各种值,包括不发送任何内容的选项。例如,将初始消息设置为nothing,然后选择将计时器与每个收到的消息一起扩展的选项,则该节点将充当看门狗计时器;仅在设置的间隔内未收到任何消息时才发送消息。

+

该节点可用于在流中创建一个超时。 默认情况下,当它收到一条消息时,它将发送一条带有1的有效荷载的消息。然后它将等待250毫秒,再发送第二条消息,其有效荷载为0。这可以用于使连接到Raspberry Pi GPIO引脚的LED闪烁等例子上。

+

可以将发送的每个消息的有效荷载配置为各种值,包括不发送任何内容的选项。例如,将初始消息设置为nothing,然后选择将计时器与每个收到的消息一起扩展的选项,则该节点将充当看门狗计时器;仅在设置的间隔内未收到任何消息时才发送消息。

如果设置为字符串类型,则该节点支持mustache模板语法。

-

如果节点收到具有reset属性或与节点中配置的匹配的payload的消息,则将清除当前正在进行的任何超时或重复,并且不会触发任何消息。

+

如果节点收到具有reset属性或与节点中配置的匹配的有效荷载的消息,则将清除当前正在进行的任何超时或重复,并且不会触发任何消息。

可以将节点配置为以固定的时间间隔重新发送消息,直到被收到的消息重置为止。

(可选)可以将节点配置为将带有msg.topic的消息视为独立的流。

diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/90-exec.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/90-exec.html index ba5951a12..27c421160 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/function/90-exec.html +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/function/90-exec.html @@ -64,11 +64,11 @@

错误可能会在第三个端口msg.payload上返回额外的信息,例如message字符串,signal字符串。

运行的命令是在节点内定义的,带有附加msg.payload的选项和另外一组参数。

带空格的命令或参数应该用引号引起来:“这是一个参数”

-

返回的payload通常是字符串类型,除非检测到非UTF8字符,在这种情况下,它会是buffer类型。

+

返回的有效荷载通常是字符串类型,除非检测到非UTF8字符,在这种情况下,它会是buffer类型。

节点处于活动状态时,该节点的状态图标和PID将可见。对此更改可以通过Status节点读取。

杀死进程

发送msg.kill将杀死一个活动进程。msg.kill应该是包含要发送的信号类型的字符串,例如SIGINTSIGQUITSIGHUP。如果设置为空字符串,则默认为SIGTERM

如果节点有多个进程在运行,则还必须设置msg.pid并设置要杀死的PID的值。

-

如果Timeout字段提供了一个值,则如果在指定的秒数过去后进程尚未完成,则该进程将自动终止。

+

如果超时字段提供了一个值,则如果在指定的秒数过去后进程尚未完成,则该进程将自动终止。

提示:如果运行Python应用程序,则可能需要使用-u参数来停止对输出进行缓存。

diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html index 7db958d94..4ec78cdb4 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html @@ -24,7 +24,7 @@

输出

写入完成后,输入消息将发送到输出端口。

详细

-

每个消息payload将添加到文件的末尾,可以选择在每个消息之间添加一个换行符(\n)。

+

每个消息的有效荷载将添加到文件的末尾,可以选择在每个消息之间添加一个换行符(\n)。

如果使用msg.filename,则每次写入后文件都会关闭。为了获得最佳体验,请使用固定的文件名。

可以将其配置为覆盖整个文件,而不是在文件后添加段落。例如,在将二进制数据写入文件(例如图像)时,应使用此选项,并且应禁用添加换行符的选项。

可以从编码列表中指定写入文件的数据的编码。

@@ -45,7 +45,7 @@
filename 字符串
如果未在节点配置中设置,该属性可以选择要读取的文件名。
error object
-
已不推荐使用: 如果在节点中启用,则当节点在读取文件时遇到错误时,它将发送一条没有payload的消息,且将消息的error属性设置为错误的详细信息。在默认情况下,此行为模式已弃用且未启用。 请参阅下面的详细信息。
+
已不推荐使用: 如果在节点中启用,则当节点在读取文件时遇到错误时,它将发送一条没有有效荷载的消息,且将消息的error属性设置为错误的详细信息。在默认情况下,此行为模式已弃用且未启用。 请参阅下面的详细信息。

详细

文件名应该是绝对路径,否则将相对于Node-RED进程的工作目录。

From c03abdb5e756dd1d3df5bb0cf501707d2dd79ab2 Mon Sep 17 00:00:00 2001 From: JIYE YU Date: Fri, 31 Jan 2020 11:10:04 +0900 Subject: [PATCH 023/346] add zn-CN translation for nodes: network, parsers, sequence --- .../nodes/locales/zh-CN/network/05-tls.html | 19 +++ .../locales/zh-CN/network/06-httpproxy.html | 22 +++ .../nodes/locales/zh-CN/network/10-mqtt.html | 71 ++++++++++ .../locales/zh-CN/network/21-httpin.html | 81 +++++++++++ .../locales/zh-CN/network/21-httprequest.html | 78 ++++++++++ .../locales/zh-CN/network/22-websocket.html | 35 +++++ .../nodes/locales/zh-CN/network/31-tcpin.html | 35 +++++ .../nodes/locales/zh-CN/network/32-udp.html | 28 ++++ .../nodes/locales/zh-CN/parsers/70-CSV.html | 43 ++++++ .../nodes/locales/zh-CN/parsers/70-HTML.html | 33 +++++ .../nodes/locales/zh-CN/parsers/70-JSON.html | 43 ++++++ .../nodes/locales/zh-CN/parsers/70-XML.html | 48 +++++++ .../nodes/locales/zh-CN/parsers/70-YAML.html | 34 +++++ .../locales/zh-CN/sequence/17-split.html | 133 ++++++++++++++++++ .../nodes/locales/zh-CN/sequence/18-sort.html | 41 ++++++ .../locales/zh-CN/sequence/19-batch.html | 34 +++++ 16 files changed, 778 insertions(+) create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/network/05-tls.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/network/06-httpproxy.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/network/10-mqtt.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httpin.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httprequest.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/network/22-websocket.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/network/31-tcpin.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/network/32-udp.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-CSV.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-HTML.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-JSON.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-XML.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-YAML.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/17-split.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/18-sort.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/19-batch.html diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/05-tls.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/05-tls.html new file mode 100644 index 000000000..5a9603946 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/05-tls.html @@ -0,0 +1,19 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/06-httpproxy.html new file mode 100644 index 000000000..e84971973 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/06-httpproxy.html @@ -0,0 +1,22 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/10-mqtt.html new file mode 100644 index 000000000..520d7f4ef --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/10-mqtt.html @@ -0,0 +1,71 @@ + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httpin.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httpin.html new file mode 100644 index 000000000..0fb52efd1 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httpin.html @@ -0,0 +1,81 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httprequest.html new file mode 100644 index 000000000..3c7b163e2 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/21-httprequest.html @@ -0,0 +1,78 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/22-websocket.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/22-websocket.html new file mode 100644 index 000000000..d2ee29dfa --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/22-websocket.html @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/31-tcpin.html new file mode 100644 index 000000000..2f00ed5f5 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/31-tcpin.html @@ -0,0 +1,35 @@ + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/network/32-udp.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/32-udp.html new file mode 100644 index 000000000..1e01aa0a3 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/network/32-udp.html @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-CSV.html new file mode 100644 index 000000000..5657f4cd3 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-CSV.html @@ -0,0 +1,43 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-HTML.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-HTML.html new file mode 100644 index 000000000..73b365600 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-HTML.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-JSON.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-JSON.html new file mode 100644 index 000000000..2e574a0bf --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-JSON.html @@ -0,0 +1,43 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-XML.html new file mode 100644 index 000000000..04a7783ef --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-XML.html @@ -0,0 +1,48 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-YAML.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-YAML.html new file mode 100644 index 000000000..e65d1b87d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/parsers/70-YAML.html @@ -0,0 +1,34 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/17-split.html new file mode 100644 index 000000000..22f01832a --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/17-split.html @@ -0,0 +1,133 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/18-sort.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/18-sort.html new file mode 100644 index 000000000..226355a8c --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/18-sort.html @@ -0,0 +1,41 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/19-batch.html new file mode 100644 index 000000000..012f20816 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/sequence/19-batch.html @@ -0,0 +1,34 @@ + + + From 88e729664afa71b275c0f1ce38ede42adc81ef2b Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 31 Jan 2020 17:56:06 +0000 Subject: [PATCH 024/346] complete tidy up of trigger node remove unnecessary console.log --- .../@node-red/nodes/core/function/89-trigger.html | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html index 187a87190..79b022519 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html @@ -92,8 +92,8 @@ units: {value:"ms"}, reset: {value:""}, bytopic: {value:"all"}, - outputs: {value:1}, - topic: {value:"topic",required:true} + topic: {value:"topic",required:true}, + outputs: {value:1} }, inputs:1, outputs:1, @@ -114,12 +114,9 @@ }, oneditprepare: function() { var that = this; - if (this.property === undefined) { - $("#node-input-property").val("topic"); - } - $("#node-input-property").typedInput({default:'msg',types:['msg']}); + if (this.topic === undefined) { $("#node-input-topic").val("topic"); } + $("#node-input-topic").typedInput({default:'msg',types:['msg']}); $("#node-input-bytopic").on("change", function() { - console.log("BYT",$("#node-input-bytopic").val()); if ($("#node-input-bytopic").val() === "all") { $("#node-stream-topic").hide(); } else { From 663ed9833ac644a80977d4136be2739fbdc86453 Mon Sep 17 00:00:00 2001 From: Tscherno Date: Sat, 1 Feb 2020 17:21:33 +0100 Subject: [PATCH 025/346] Add HEAD as Method --- .../@node-red/nodes/core/network/21-httprequest.html | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html index cb97c68a5..03106dc61 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html @@ -22,6 +22,7 @@ +
From 36bf2a3c3897430021a9b134d1dddf5c6fbced27 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Mon, 3 Feb 2020 12:59:12 +0900 Subject: [PATCH 026/346] add support for examples of core nodes --- .../node_modules/@node-red/registry/lib/localfilesystem.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/node_modules/@node-red/registry/lib/localfilesystem.js b/packages/node_modules/@node-red/registry/lib/localfilesystem.js index 947972031..c66d7d080 100644 --- a/packages/node_modules/@node-red/registry/lib/localfilesystem.js +++ b/packages/node_modules/@node-red/registry/lib/localfilesystem.js @@ -286,6 +286,10 @@ function getNodeFiles(disableNodePathScan) { nodeFiles.forEach(function(node) { nodeList["node-red"].nodes[node.name] = node; }); + if (settings.coreNodesDir) { + var examplesDir = path.join(settings.coreNodesDir,"examples"); + nodeList["node-red"].examples = {path: examplesDir}; + } if (!disableNodePathScan) { var moduleFiles = scanTreeForNodesModules(); From 272fbc0cb0f3646f0294ad24bc549a32976f501f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 4 Feb 2020 09:45:23 +0900 Subject: [PATCH 027/346] add examples of batch node --- .../@node-red/nodes/examples/batch/1_number-mode.json | 1 + .../node_modules/@node-red/nodes/examples/batch/2_time-mode.json | 1 + .../@node-red/nodes/examples/batch/3_concatenate-mode.json | 1 + 3 files changed, 3 insertions(+) create mode 100644 packages/node_modules/@node-red/nodes/examples/batch/1_number-mode.json create mode 100644 packages/node_modules/@node-red/nodes/examples/batch/2_time-mode.json create mode 100644 packages/node_modules/@node-red/nodes/examples/batch/3_concatenate-mode.json diff --git a/packages/node_modules/@node-red/nodes/examples/batch/1_number-mode.json b/packages/node_modules/@node-red/nodes/examples/batch/1_number-mode.json new file mode 100644 index 000000000..b7b919c9e --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/batch/1_number-mode.json @@ -0,0 +1 @@ +[{"id":"bf16276d.2f1758","type":"tab","label":"Example: Number-based Group Mode","disabled":false,"info":"*Number-based Group mode* of batch node can be used to create new message sequences from incoming messages. Recently received *N*-messages are grouped to a sequence. Creating message sequences that has overwrap with adjacent message group is possible.\n"},{"id":"f5a82278.78d6c","type":"batch","z":"bf16276d.2f1758","name":"","mode":"count","count":"5","overlap":0,"interval":"5","allowEmptySequence":false,"topics":[],"x":370,"y":232,"wires":[["b1e514ed.44f328"]]},{"id":"43720065.2891d","type":"comment","z":"bf16276d.2f1758","name":"Group 5 consecutive messages","info":"","x":170,"y":60,"wires":[]},{"id":"b1e514ed.44f328","type":"join","z":"bf16276d.2f1758","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":510,"y":232,"wires":[["457e5970.8ceaa8"]]},{"id":"457e5970.8ceaa8","type":"debug","z":"bf16276d.2f1758","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":670,"y":232,"wires":[]},{"id":"8e3d3ceb.bd0fe","type":"comment","z":"bf16276d.2f1758","name":"↑ create message sequence with 5 messages","info":"","x":490,"y":272,"wires":[]},{"id":"fbe20ae3.cbb6f8","type":"comment","z":"bf16276d.2f1758","name":"↓ join sequence to array","info":"","x":560,"y":192,"wires":[]},{"id":"7ebafe58.2a112","type":"inject","z":"bf16276d.2f1758","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":152,"wires":[["589603c4.1cb5fc"]]},{"id":"589603c4.1cb5fc","type":"function","z":"bf16276d.2f1758","name":"send: 0-49","func":"for(var x = 0; x < 50; x++) {\n node.send({payload: x});\n}","outputs":1,"noerr":0,"x":310,"y":152,"wires":[["f5a82278.78d6c"]]},{"id":"9b59b72c.fcd8f8","type":"comment","z":"bf16276d.2f1758","name":"↓ send sequence: 0-49","info":"","x":340,"y":112,"wires":[]},{"id":"6421756f.6abd5c","type":"batch","z":"bf16276d.2f1758","name":"","mode":"count","count":"5","overlap":"1","interval":"5","allowEmptySequence":false,"topics":[],"x":370,"y":500,"wires":[["199cf232.743e7e"]]},{"id":"657b6a53.2a1fc4","type":"comment","z":"bf16276d.2f1758","name":"Group 5 consecutive messages with overlap of 1 msg","info":"","x":240,"y":328,"wires":[]},{"id":"199cf232.743e7e","type":"join","z":"bf16276d.2f1758","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":510,"y":500,"wires":[["91d29dda.d8a4f"]]},{"id":"91d29dda.d8a4f","type":"debug","z":"bf16276d.2f1758","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":670,"y":500,"wires":[]},{"id":"8bd4d407.aa2af8","type":"comment","z":"bf16276d.2f1758","name":"↑ create message sequence with 5 messages with overlap of 1 msg","info":"","x":560,"y":540,"wires":[]},{"id":"a49ea57d.8d2458","type":"comment","z":"bf16276d.2f1758","name":"↓ join sequence to array","info":"","x":560,"y":460,"wires":[]},{"id":"f689d1b3.90e4b","type":"inject","z":"bf16276d.2f1758","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":420,"wires":[["c021cf24.ad03e"]]},{"id":"c021cf24.ad03e","type":"function","z":"bf16276d.2f1758","name":"send: 0-49","func":"for(var x = 0; x < 50; x++) {\n node.send({payload: x});\n}","outputs":1,"noerr":0,"x":310,"y":420,"wires":[["6421756f.6abd5c"]]},{"id":"8da6e576.901a18","type":"comment","z":"bf16276d.2f1758","name":"↓ send sequence: 0-49","info":"","x":340,"y":380,"wires":[]}] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/batch/2_time-mode.json b/packages/node_modules/@node-red/nodes/examples/batch/2_time-mode.json new file mode 100644 index 000000000..87fe70f0a --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/batch/2_time-mode.json @@ -0,0 +1 @@ +[{"id":"82a01f29.86de","type":"tab","label":"Example: Time-based Group Mode","disabled":false,"info":"*Time-based Group mode* of batch node can be used to create new message sequences from incoming messages received within specified time range. \n"},{"id":"9a7f6539.6e36d8","type":"batch","z":"82a01f29.86de","name":"","mode":"interval","count":10,"overlap":0,"interval":"5","allowEmptySequence":false,"topics":[],"x":350,"y":260,"wires":[["e54a3b57.3677f8"]]},{"id":"71dc607e.98aab","type":"comment","z":"82a01f29.86de","name":"Group messages received within 5s","info":"","x":180,"y":80,"wires":[]},{"id":"e54a3b57.3677f8","type":"join","z":"82a01f29.86de","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":490,"y":260,"wires":[["1e263cac.6641f3"]]},{"id":"1e263cac.6641f3","type":"debug","z":"82a01f29.86de","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":650,"y":260,"wires":[]},{"id":"324c7b93.0db734","type":"comment","z":"82a01f29.86de","name":"↑ create message sequence received within 5s","info":"","x":480,"y":300,"wires":[]},{"id":"fb112bae.fbe428","type":"comment","z":"82a01f29.86de","name":"↓ join sequence to array","info":"","x":540,"y":220,"wires":[]},{"id":"34f8dda5.2864c2","type":"inject","z":"82a01f29.86de","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":160,"wires":[["c9e23ee4.ce535"]]},{"id":"c9e23ee4.ce535","type":"function","z":"82a01f29.86de","name":"send: 0-49","func":"for(var x = 0; x < 100; x++) {\n node.send({payload: x});\n}","outputs":1,"noerr":0,"x":310,"y":160,"wires":[["7026e0cc.4e3c3"]]},{"id":"7026e0cc.4e3c3","type":"delay","z":"82a01f29.86de","name":"","pauseType":"rate","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":490,"y":160,"wires":[["9a7f6539.6e36d8"]]},{"id":"40f8c766.6ed198","type":"comment","z":"82a01f29.86de","name":"↓ send sequence: 0-49","info":"","x":340,"y":120,"wires":[]}] \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/examples/batch/3_concatenate-mode.json b/packages/node_modules/@node-red/nodes/examples/batch/3_concatenate-mode.json new file mode 100644 index 000000000..819ae7966 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/examples/batch/3_concatenate-mode.json @@ -0,0 +1 @@ +[{"id":"845b226d.a4b18","type":"tab","label":"Example: Concatenate Mode","disabled":false,"info":"*Concatenate mode* of batch node can be used to combine input message sequences to create a new message sequence. Order of the sequences can be specified using message topic assigned to each message in a sequence. Message sequence can be specified multiple times.\n"},{"id":"72afe7b0.38b9d8","type":"inject","z":"845b226d.a4b18","name":"","topic":"SEQ","payload":"[1,2,3,4,5]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":100,"wires":[["6dea90dd.c442c"]]},{"id":"6dea90dd.c442c","type":"split","z":"845b226d.a4b18","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":330,"y":100,"wires":[["3ac93a4b.ddbbc6"]]},{"id":"3ac93a4b.ddbbc6","type":"batch","z":"845b226d.a4b18","name":"","mode":"concat","count":10,"overlap":0,"interval":10,"allowEmptySequence":false,"topics":[{"topic":"SEQ"},{"topic":"SEQ"}],"x":470,"y":100,"wires":[["48ec7040.56f5f"]]},{"id":"48ec7040.56f5f","type":"join","z":"845b226d.a4b18","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":610,"y":100,"wires":[["902613c4.769b5"]]},{"id":"902613c4.769b5","type":"debug","z":"845b226d.a4b18","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":770,"y":100,"wires":[]},{"id":"a84cf2e1.65adc","type":"comment","z":"845b226d.a4b18","name":"Duplicate","info":"","x":100,"y":60,"wires":[]},{"id":"3256f015.45c36","type":"inject","z":"845b226d.a4b18","name":"","topic":"SEQ","payload":"[1,-6,-8,7,2,-3]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":220,"wires":[["c308dcb2.621da"]]},{"id":"c308dcb2.621da","type":"split","z":"845b226d.a4b18","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":330,"y":220,"wires":[["2222098b.7fd036"]]},{"id":"247a5fab.239cc","type":"comment","z":"845b226d.a4b18","name":"Filter & Concat","info":"","x":120,"y":180,"wires":[]},{"id":"2222098b.7fd036","type":"switch","z":"845b226d.a4b18","name":"","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"num"},{"t":"else"}],"checkall":"true","repair":true,"outputs":2,"x":390,"y":280,"wires":[["56e3a974.2bfde8"],["86a1b43a.ff4cb8"]]},{"id":"cd00a796.e4e478","type":"comment","z":"845b226d.a4b18","name":"↑ Duplicate SEQ","info":"","x":500,"y":140,"wires":[]},{"id":"56e3a974.2bfde8","type":"change","z":"845b226d.a4b18","name":"Topic←POS","rules":[{"t":"set","p":"topic","pt":"msg","to":"POS","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":240,"wires":[["fe90d65d.a6b548"]]},{"id":"86a1b43a.ff4cb8","type":"change","z":"845b226d.a4b18","name":"Topic←NEG","rules":[{"t":"set","p":"topic","pt":"msg","to":"NEG","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":300,"wires":[["fe90d65d.a6b548"]]},{"id":"fe90d65d.a6b548","type":"batch","z":"845b226d.a4b18","name":"","mode":"concat","count":10,"overlap":0,"interval":10,"allowEmptySequence":false,"topics":[{"topic":"NEG"},{"topic":"POS"}],"x":710,"y":280,"wires":[["5b089f16.62b96"]]},{"id":"2f46c0af.bff71","type":"debug","z":"845b226d.a4b18","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":930,"y":220,"wires":[]},{"id":"5b089f16.62b96","type":"join","z":"845b226d.a4b18","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":770,"y":220,"wires":[["2f46c0af.bff71"]]},{"id":"e069eb28.6eb358","type":"comment","z":"845b226d.a4b18","name":"↑ Order sequence: negative→positive","info":"","x":810,"y":320,"wires":[]},{"id":"aeae162b.efd118","type":"comment","z":"845b226d.a4b18","name":"Filter pos/neg and make separate sequence↑ (but not a simple sort) ","info":"","x":320,"y":340,"wires":[]}] \ No newline at end of file From 5897045f24fa8cf5ed28d86c9380a8b802e27af0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 5 Feb 2020 14:26:55 +0000 Subject: [PATCH 028/346] Ignore disabled nodes when checking for invalid configs on deploy Closes #2430 --- .../node_modules/@node-red/editor-client/src/js/ui/deploy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index cdf84d02f..9e91410a9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -334,8 +334,7 @@ RED.deploy = (function() { var invalidNodes = []; RED.nodes.eachNode(function(node) { - hasInvalid = hasInvalid || !node.valid; - if (!node.valid) { + if (!node.valid && !node.d) { invalidNodes.push(getNodeInfo(node)); } if (node.type === "unknown") { @@ -345,6 +344,7 @@ RED.deploy = (function() { } }); hasUnknown = unknownNodes.length > 0; + hasInvalid = invalidNodes.length > 0; var unusedConfigNodes = []; RED.nodes.eachConfig(function(node) { From 3d9945b60cdeb86a43d8e3005e61ff5e5f165276 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 5 Feb 2020 14:44:39 +0000 Subject: [PATCH 029/346] Update to JSONata 1.8 --- package.json | 2 +- .../@node-red/editor-client/locales/en-US/jsonata.json | 4 ++++ .../@node-red/editor-client/src/vendor/jsonata/formatter.js | 1 + packages/node_modules/@node-red/util/package.json | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9aaeb8f9e..a6c4c5091 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "is-utf8": "0.2.1", "js-yaml": "3.13.1", "json-stringify-safe": "5.0.1", - "jsonata": "1.7.0", + "jsonata": "1.8.0", "media-typer": "1.1.0", "memorystore": "1.6.1", "mime": "2.4.4", diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json index 8f516a289..d777d1919 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/jsonata.json @@ -262,5 +262,9 @@ "$distinct": { "args": "array", "desc": "Returns an array with duplicate values removed from `array`" + }, + "$type": { + "args": "value", + "desc": "Returns the type of `value` as a string. If `value` is undefined, this will return `undefined`" } } diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js index 0cc69715a..382537be3 100644 --- a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js +++ b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js @@ -171,6 +171,7 @@ '$sum':{ args:[ 'array' ]}, '$toMillis':{args:['timestamp']}, // <------------- '$trim':{ args:[ 'str' ]}, + '$type':{ args:['value']}, '$uppercase':{ args:[ 'str' ]}, '$zip':{ args:[ 'array1' ]} } diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 174db5bfd..411c35a7c 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -18,7 +18,7 @@ "clone": "2.1.2", "i18next": "15.1.2", "json-stringify-safe": "5.0.1", - "jsonata": "1.7.0", + "jsonata": "1.8.0", "when": "3.7.8" } } From d771527f776a4d9282eafbcc97753123076f277b Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 5 Feb 2020 15:11:18 +0000 Subject: [PATCH 030/346] Add some auto-complete snippets to the nrjavascript mode Close #2438 --- .../editor-client/src/ace/bin/snippets/nrjavascript.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/ace/bin/snippets/nrjavascript.js b/packages/node_modules/@node-red/editor-client/src/ace/bin/snippets/nrjavascript.js index d11f3fc6a..c0f5c2e0d 100644 --- a/packages/node_modules/@node-red/editor-client/src/ace/bin/snippets/nrjavascript.js +++ b/packages/node_modules/@node-red/editor-client/src/ace/bin/snippets/nrjavascript.js @@ -1,4 +1,4 @@ -ace.define("ace/snippets/nrjavascript",[],function(e,t,n){"use strict";t.snippetText=undefined,t.scope="nrjavascript"}); +ace.define("ace/snippets/nrjavascript",[],function(e,t,n){"use strict";t.snippetText='# Prototype\nsnippet proto\n ${1:class_name}.prototype.${2:method_name} = function(${3:first_argument}) {\n ${4:// body...}\n };\n# Function\nsnippet fun\n function ${1?:function_name}(${2:argument}) {\n ${3:// body...}\n }\n# Anonymous Function\nregex /((=)\\s*|(:)\\s*|(\\()|\\b)/f/(\\))?/\nsnippet f\n function${M1?: ${1:functionName}}($2) {\n ${0:$TM_SELECTED_TEXT}\n }${M2?;}${M3?,}${M4?)}\n# Immediate function\ntrigger \\(?f\\(\nendTrigger \\)?\nsnippet f(\n (function(${1}) {\n ${0:${TM_SELECTED_TEXT:/* code */}}\n }(${1}));\n# if\nsnippet if\n if (${1:true}) {\n ${0}\n }\n# if ... else\nsnippet ife\n if (${1:true}) {\n ${2}\n } else {\n ${0}\n }\n# tertiary conditional\nsnippet ter\n ${1:/* condition */} ? ${2:a} : ${3:b}\n# switch\nsnippet switch\n switch (${1:expression}) {\n case \'${3:case}\':\n ${4:// code}\n break;\n ${5}\n default:\n ${2:// code}\n }\n# case\nsnippet case\n case \'${1:case}\':\n ${2:// code}\n break;\n ${3}\n\n# while (...) {...}\nsnippet wh\n while (${1:/* condition */}) {\n ${0:/* code */}\n }\n# try\nsnippet try\n try {\n ${0:/* code */}\n } catch (e) {}\n# do...while\nsnippet do\n do {\n ${2:/* code */}\n } while (${1:/* condition */});\n# Object Method\nsnippet :f\nregex /([,{[])|^\\s*/:f/\n ${1:method_name}: function(${2:attribute}) {\n ${0}\n }${3:,}\n# setTimeout function\nsnippet setTimeout\nregex /\\b/st|timeout|setTimeo?u?t?/\n setTimeout(function() {${3:$TM_SELECTED_TEXT}}, ${1:10});\n# console.log (Firebug)\nsnippet cl\n console.log(${1});\n# return\nsnippet ret\n return ${1:result}\n# for (property in object ) { ... }\nsnippet fori\n for (var ${1:prop} in ${2:Things}) {\n ${0:$2[$1]}\n }\n# hasOwnProperty\nsnippet has\n hasOwnProperty(${1})\n# docstring\nsnippet /**\n /**\n * ${1:description}\n *\n */\nsnippet @par\nregex /^\\s*\\*\\s*/@(para?m?)?/\n @param {${1:type}} ${2:name} ${3:description}\nsnippet @ret\n @return {${1:type}} ${2:description}\n# JSON.parse\nsnippet jsonp\n JSON.parse(${1:jstr});\n# JSON.stringify\nsnippet jsons\n JSON.stringify(${1:object});\n# self-defining function\nsnippet sdf\n var ${1:function_name} = function(${2:argument}) {\n ${3:// initial code ...}\n\n $1 = function($2) {\n ${4:// main code}\n };\n }\n# \nsnippet for-\n for (var ${1:i} = ${2:Things}.length; ${1:i}--; ) {\n ${0:${2:Things}[${1:i}];}\n }\n# for (...) {...}\nsnippet for\n for (var ${1:i} = 0; $1 < ${2:Things}.length; $1++) {\n ${3:$2[$1]}$0\n }\n# for (...) {...} (Improved Native For-Loop)\nsnippet forr\n for (var ${1:i} = ${2:Things}.length - 1; $1 >= 0; $1--) {\n ${3:$2[$1]}$0\n }\n# Node-RED Specific Funcs\nsnippet nodes\n node.send(${1:msg})\nsnippet clone\n RED.util.cloneMessage(${1:msg})\nsnippet nodel\n node.log($1)\nsnippet nodew\n node.warn($1)\nsnippet nodee\n node.error($1)\nsnippet noded\n node.debug($1)\nsnippet done\n node.done($1)\nsnippet flowg\n flow.get($1)\nsnippet flows\n flow.set($1, $2)\nsnippet globalg\n global.get($1)\nsnippet globals\n global.set($1, $2)\n',t.scope="nrjavascript"}); (function() { ace.require(["ace/snippets/nrjavascript"], function(m) { if (typeof module == "object" && typeof exports == "object" && module) { @@ -6,4 +6,3 @@ ace.define("ace/snippets/nrjavascript",[],function(e,t,n){"use strict";t.snippet } }); })(); - \ No newline at end of file From b6702a0c3b7f86b63b756fb681dcb0b7c7bd768a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 5 Feb 2020 19:45:57 +0000 Subject: [PATCH 031/346] Modify history sidebar button positioning to handle long labels Fixes #2338 --- .../editor-client/src/js/ui/projects/tab-versionControl.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 7069be6b7..e367e9c95 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 @@ -336,7 +336,7 @@ RED.sidebar.versionControl = (function() { var unstagedContent = $('
').appendTo(localChanges.content); var header = $('
'+RED._("sidebar.project.versionControl.localFiles")+'
').appendTo(unstagedContent); - stageAllButton = $('') + stageAllButton = $('') .appendTo(header) .on("click", function(evt) { evt.preventDefault(); @@ -368,7 +368,7 @@ RED.sidebar.versionControl = (function() { unmergedContent = $('
').appendTo(localChanges.content); header = $('
'+RED._("sidebar.project.versionControl.unmergedChanges")+'
').appendTo(unmergedContent); - bg = $('
').appendTo(header); + bg = $('
').appendTo(header); var abortMergeButton = $('') .appendTo(bg) .on("click", function(evt) { @@ -433,7 +433,7 @@ RED.sidebar.versionControl = (function() { header = $('
'+RED._("sidebar.project.versionControl.changeToCommit")+'
').appendTo(stagedContent); - bg = $('
').appendTo(header); + bg = $('
').appendTo(header); var showCommitBox = function() { commitMessage.val(""); submitCommitButton.prop("disabled",true); From 0bb77bfa7f2645f32cc8ad4a2a109d039e35d91c Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Thu, 6 Feb 2020 16:14:09 +0900 Subject: [PATCH 032/346] Add Japanese translations for i18n --- .../@node-red/editor-client/locales/en-US/editor.json | 3 ++- .../@node-red/editor-client/locales/ja/editor.json | 9 +++++---- .../@node-red/editor-client/locales/ja/jsonata.json | 8 ++++++-- 3 files changed, 13 insertions(+), 7 deletions(-) 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 72af1d38b..a5f232aa6 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -1008,6 +1008,7 @@ "en-US": "English", "ja": "Japanese", "ko": "Korean", - "zh-CN": "Chinese(Simplified)" + "zh-CN": "Chinese(Simplified)", + "zh-TW": "Chinese(Traditional)" } } 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 8290ca1c1..cf04ced1d 100755 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -803,9 +803,9 @@ "expandItems": "要素を展開", "collapseItems": "要素を折り畳む", "duplicate": "複製", - "error": { - "invalidJSON": "不正なJSON: " - } + "error": { + "invalidJSON": "不正なJSON: " + } }, "markdownEditor": { "title": "マークダウンエディタ", @@ -1007,6 +1007,7 @@ "en-US": "英語", "ja": "日本語", "ko": "韓国語", - "zh-CN": "中国語(簡体)" + "zh-CN": "中国語(簡体)", + "zh-TW": "中国語(繁体)" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/ja/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/ja/jsonata.json index ba873c448..659cf66df 100755 --- a/packages/node_modules/@node-red/editor-client/locales/ja/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/jsonata.json @@ -245,11 +245,11 @@ }, "$encodeUrl": { "args": "str", - "desc": "Uniform Resource Locator (URL)を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" + "desc": "Uniform Resource Locator (URL)を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" }, "$encodeUrlComponent": { "args": "str", - "desc": "Uniform Resource Locator (URL)要素を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`" + "desc": "Uniform Resource Locator (URL)要素を構成する文字を1、2、3、もしくは、4文字エスケープシーケンスのUTF-8文字エンコーディングで置換します。\n\n例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`" }, "$decodeUrl": { "args": "str", @@ -262,5 +262,9 @@ "$distinct": { "args": "array", "desc": "配列`array`から重複要素を削除した配列を返します。" + }, + "$type": { + "args": "value", + "desc": "`value` の型を文字列として返します。もし `value` が未定義の場合、 `undefined` が返されます。" } } From 0f1ca1c7cf200d436e0cb0feb6a308c6f4a56c03 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 6 Feb 2020 10:04:55 +0000 Subject: [PATCH 033/346] cloneMessage should handle undefined without throwing err Fixes #2399 --- .../node_modules/@node-red/util/lib/util.js | 33 ++++++++++--------- test/unit/@node-red/util/lib/util_spec.js | 14 +++++--- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/packages/node_modules/@node-red/util/lib/util.js b/packages/node_modules/@node-red/util/lib/util.js index ef43739e6..45a33be14 100644 --- a/packages/node_modules/@node-red/util/lib/util.js +++ b/packages/node_modules/@node-red/util/lib/util.js @@ -81,22 +81,25 @@ function ensureBuffer(o) { * @memberof @node-red/util_util */ function cloneMessage(msg) { - // Temporary fix for #97 - // TODO: remove this http-node-specific fix somehow - var req = msg.req; - var res = msg.res; - delete msg.req; - delete msg.res; - var m = clone(msg); - if (req) { - m.req = req; - msg.req = req; + if (typeof msg !== "undefined") { + // Temporary fix for #97 + // TODO: remove this http-node-specific fix somehow + var req = msg.req; + var res = msg.res; + delete msg.req; + delete msg.res; + var m = clone(msg); + if (req) { + m.req = req; + msg.req = req; + } + if (res) { + m.res = res; + msg.res = res; + } + return m; } - if (res) { - m.res = res; - msg.res = res; - } - return m; + return msg; } /** diff --git a/test/unit/@node-red/util/lib/util_spec.js b/test/unit/@node-red/util/lib/util_spec.js index cf59df55e..261dbc531 100644 --- a/test/unit/@node-red/util/lib/util_spec.js +++ b/test/unit/@node-red/util/lib/util_spec.js @@ -143,6 +143,10 @@ describe("@node-red/util/util", function() { cloned.req.should.equal(msg.req); cloned.res.should.equal(msg.res); }); + it('handles undefined values without throwing an error', function() { + var result = util.cloneMessage(undefined); + should.not.exist(result); + }) }); describe('getObjectProperty', function() { it('gets a property beginning with "msg."', function() { @@ -840,11 +844,11 @@ describe("@node-red/util/util", function() { }, } }; - + for (var i = 0; i < 1000; i++) { msg.msg.obj.big += 'some more string '; } - + var result = util.encodeObject(msg); result.format.should.eql("error"); var resultJson = JSON.parse(result.msg); @@ -862,7 +866,7 @@ describe("@node-red/util/util", function() { throw new Error('Exception in toString - should have been caught'); } msg.msg.constructor = { name: "strangeobj" }; - + var result = util.encodeObject(msg); var success = (result.msg.indexOf('[Type not printable]') >= 0); success.should.eql(true); @@ -872,11 +876,11 @@ describe("@node-red/util/util", function() { var msg = { msg: { mystrangeobj:"hello", - constructor: { + constructor: { get name(){ throw new Error('Exception in constructor name'); } - } + } }, }; var result = util.encodeObject(msg); From bbd471ad937d3c922d63d2ba2b8e23cf13f04370 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 6 Feb 2020 15:36:23 +0000 Subject: [PATCH 034/346] Trick chrome into autofilling dummy username/password inputs Fixes #2445 Continuing the arms race against Chrome's war on developers getting to choose if a form should be autocompleted or not. The honey-pot username/password fields we already had were being ignored. This is because they were hidden. This fix does three things: - unhides the honey-pot inputs, but moves them offscreen so they won't be seen - gives them dummy id's so Chrome thinks they are username/password fields - updates our autocomplete setting to be the standards-compliant 'off' for all the other browsers who adhere to the standard --- .../@node-red/editor-client/src/js/ui/editor.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 5f8379aa1..9fb29833a 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 @@ -582,11 +582,12 @@ RED.editor = (function() { // Add dummy fields to prevent 'Enter' submitting the form in some // cases, and also prevent browser auto-fill of password - // Add in reverse order as they are prepended... - $('').prependTo(dialogForm); - $('').prependTo(dialogForm); + // - the elements cannot be hidden otherwise Chrome will ignore them. + // - the elements need to have id's that imply password/username + $('
').prependTo(dialogForm); + $('
').prependTo(dialogForm); dialogForm.on("submit", function(e) { e.preventDefault();}); - dialogForm.find('input').attr("autocomplete","disable"); + dialogForm.find('input').attr("autocomplete","off"); return dialogForm; } From cd552ab2022d004132ad01233e6f21e74447f6e3 Mon Sep 17 00:00:00 2001 From: JIYE YU Date: Fri, 7 Feb 2020 17:57:37 +0900 Subject: [PATCH 035/346] wrap up ch-ZN translation for editor-client files --- .../editor-client/locales/zh-CN/editor.json | 608 +++++++++++++++++- .../editor-client/locales/zh-CN/jsonata.json | 48 ++ 2 files changed, 633 insertions(+), 23 deletions(-) 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 a45f37f66..3695eb263 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 @@ -10,7 +10,22 @@ "load": "读取", "save": "保存", "import": "导入", - "export": "导出" + "export": "导出", + "back": "后退", + "next": "下一个", + "clone": "克隆项目", + "cont": "继续" + }, + "type": { + "string": "字符串", + "number": "数字", + "boolean": "布尔值", + "array": "数组", + "buffer": "buffer", + "object": "对象", + "jsonString": "JSON字符串", + "undefined": "为定义", + "null": "空" } }, "workspace": { @@ -19,10 +34,13 @@ "confirmDelete": "确认删除", "delete": "你确定想删除 '__label__'?", "dropFlowHere": "把流程放到这里", + "addFlow": "添加流程", + "listFlows": "流程一览", "status": "状态", "enabled": "有效", "disabled": "无效", - "info": "详细描述" + "info": "详细描述", + "selectNodes": "点击节点来选择" }, "menu": { "label": { @@ -36,11 +54,16 @@ "defaultDir": "默认方向", "ltr": "从左到右", "rtl": "从右到左", - "auto": "上下文" + "auto": "上下文", + "language": "语言", + "browserDefault": "浏览器默认" }, "sidebar": { "show": "显示侧边栏" }, + "palette": { + "show": "显示控制板" + }, "settings": "设置", "userSettings": "用户设置", "nodes": "节点", @@ -60,11 +83,23 @@ "keyboardShortcuts": "键盘快捷方式", "login": "登陆", "logout": "退出", - "editPalette":"节点管理", + "editPalette": "节点管理", "other": "其他", - "showTips": "显示小提示" + "showTips": "显示小提示", + "help": "Node-RED网页", + "projects": "项目", + "projects-new": "新建", + "projects-open": "打开", + "projects-settings": "项目设定", + "showNodeLabelDefault": "显示新添加的节点的标签" } }, + "actions": { + "toggle-navigator": "切换导航器", + "zoom-out": "缩小", + "zoom-reset": "重设缩放", + "zoom-in": "放大" + }, "user": { "loggedInAs": "作为__name__登陆", "username": "账号", @@ -82,29 +117,73 @@ "warning": "警告: __message__", "warnings": { "undeployedChanges": "节点中存在未部署的更改", + "nodeActionDisabled": "节点操作已禁用", "nodeActionDisabledSubflow": "节点动作在子流程中被禁用", "missing-types": "流程由于缺少节点类型而停止。请检查日志的详细信息", - "restartRequired": "Node-RED必须重新启动,以启用升级的模块" + "safe-mode": "

流程以安全模式停止。

您可以修改流程并部署更改以重新启动。

", + "restartRequired": "Node-RED必须重新启动,以启用升级的模块", + "credentials_load_failed": "

由于无法解密凭据,因此流程停止。

流程凭据文件已加密,但是项目的加密密钥丢失或无效。

", + "credentials_load_failed_reset": "

凭据无法解密

流凭据文件已加密,但是项目的加密密钥丢失或无效。

流凭据文件将在下一次部署时重置。任何现有的流凭证将被清除。

", + "missing_flow_file": "

找不到项目流程文件。

该项目未配置流程文件。

", + "missing_package_file": "

找不到项目包文件。

项目缺少package.json文件。

", + "project_empty": "

该项目为空。

是否要创建一组默认的项目文件?
否则,您将必须在编辑器外部手动将文件添加到项目中。

", + "project_not_found": "

未找到项目'__project__'。

", + "git_merge_conflict": "

自动合并更改失败。

修复未合并的冲突,然后提交结果。

" }, - "error": "Error: __message__", + "error": "错误: __message__", "errors": { "lostConnection": "丢失与服务器的连接,重新连接...", "lostConnectionReconnect": "丢失与服务器的连接,__time__秒后重新连接", "lostConnectionTry": "现在尝试", "cannotAddSubflowToItself": "无法向其自身添加子流程", "cannotAddCircularReference": "无法添加子流程 - 循环引用", - "unsupportedVersion": "您正在使用不受支持的Node.js版本
请升级到最新版本的Node.js LTS" + "unsupportedVersion": "您正在使用不受支持的Node.js版本
请升级到最新版本的Node.js LTS", + "failedToAppendNode": "

'__module__'加载失败

__error__

" + }, + "project": { + "change-branch": "转到本地分支'__project__'", + "merge-abort": "Git合并中止", + "loaded": "项目'__project__'已加载", + "updated": "项目'__project__'已更新", + "pull": "项目'__project__'已重新加载", + "revert": "项目 '__project__'已还原", + "merge-complete": "Git合并完成", + "setupCredentials": "设定证书", + "setupProjectFiles": "设置项目文件", + "no": "不了,谢谢", + "createDefault": "创建默认项目文件", + "mergeConflict": "显示合并冲突" + }, + "label": { + "manage-project-dep": "管理项目依赖性", + "setup-cred": "设定证书", + "setup-project": "设置项目文件", + "create-default-package": "创建默认的包文件", + "no-thanks": "不了,谢谢", + "create-default-project": "创建默认项目文件", + "show-merge-conflicts": "显示合并冲突" } }, "clipboard": { "clipboard": "剪贴板", "nodes": "节点", + "node": "__count__节点", + "node_plural": "__count__节点", + "configNode": "__count__配置节点", + "configNode_plural": "__count__配置节点", + "flow": "__count__流程", + "flow_plural": "__count__流程", + "subflow": "__count__子流程", + "subflow_plural": "__count__子流程", "pasteNodes": "在这里粘贴节点", + "selectFile": "选择要导入的文件", "importNodes": "导入节点", "exportNodes": "导出节点至剪贴板", + "download": "下载", "importUnrecognised": "导入了无法识别的类型:", "importUnrecognised_plural": "导入了无法识别的类型:", "nodesExported": "节点导出到了剪贴板", + "nodesImported": "导入:", "nodeCopied": "已复制__count__个节点", "nodeCopied_plural": "已复制__count__个节点", "invalidFlow": "无效的流程: __message__", @@ -114,11 +193,21 @@ "all": "所有流程", "compact": "紧凑", "formatted": "已格式化", - "copy": "导出到剪贴板" + "copy": "导出到剪贴板", + "export": "到处到库", + "exportAs": "导出为", + "overwrite": "替换", + "exists": "

\"__file__\"已存在

是否要替换它?

" }, "import": { "import": "导入到", - "newFlow": "新流程" + "newFlow": "新流程", + "errors": { + "notArray": "输入的不是JSON数组", + "itemNotObject": "输入的流无效 - 项目__index__不是节点对象", + "missingId": "输入的流无效-项 __index__ 缺少'id'属性", + "missingType": "输入的流程无效-项__index__缺少'类型'属性" + } }, "copyMessagePath": "已复制路径", "copyMessageValue": "已复制数值", @@ -132,7 +221,10 @@ "modifiedFlowsDesc": "只部署包含已更改节点的流", "modifiedNodes": "已更改的节点", "modifiedNodesDesc": "只部署已经更改的节点", + "restartFlows": "重启流程", + "restartFlowsDesc": "重新启动当前部署的流程", "successfulDeploy": "部署成功", + "successfulRestart": "成功重启流程", "deployFailed": "部署失败: __message__", "unusedConfigNodes": "您有一些未使用的配置节点", "unusedConfigNodesLink": "点击此处查看它们", @@ -152,16 +244,24 @@ "improperlyConfigured": "工作区包含一些未正确配置的节点:", "unknown": "工作区包含一些未知的节点类型:", "confirm": "你确定要部署吗?", + "doNotWarn": "不要再对此发出警告", "conflict": "服务器正在运行较新的一组流程。", "backgroundUpdate": "服务器上的流程已更新。", "conflictChecking": "检查是否可以自动合并更改", "conflictAutoMerge": "此更改不包括冲突,可以自动合并", - "conflictManualMerge": "这些更改包括了在部署之前必须解决的冲突。" + "conflictManualMerge": "这些更改包括了在部署之前必须解决的冲突。", + "plusNMore": "+ __count__更多" } }, + "eventLog": { + "title": "事件记录日志", + "view": "查看日志" + }, "diff": { "unresolvedCount": "__count__个未解决的冲突", "unresolvedCount_plural": "__count__个未解决的冲突", + "globalNodes": "全局节点", + "flowProperties": "流程属性", "type": { "added": "已添加", "changed": "已更改", @@ -175,9 +275,19 @@ "nodeCount": "__count__个节点", "nodeCount_plural": "__count__个节点", "local": "本地", - "remote": "远程" + "remote": "远程", + "reviewChanges": "查看变更", + "noBinaryFileShowed": "无法显示二进制文件内容", + "viewCommitDiff": "查看提交更改", + "compareChanges": "比较变更", + "saveConflict": "保存冲突解决", + "conflictHeader": "已解决__unresolved__中的__resolved__个冲突", + "commonVersionError": "通用版本不包含有效的JSON:", + "oldVersionError": "旧版本不包含有效的JSON:", + "newVersionError": "新版本不包含有效的JSON:" }, "subflow": { + "editSubflowInstance": "编辑子流实例:__name__", "editSubflow": "编辑流程模板: __name__", "edit": "编辑流程模板", "subflowInstances": "这个子流程模板有__count__个实例", @@ -185,8 +295,14 @@ "editSubflowProperties": "编辑属性", "input": "输入:", "output": "输出:", + "status": "状态节点", "deleteSubflow": "删除子流程", "info": "详细描述", + "category": "类别", + "env": { + "restore": "恢复为默认子流", + "remove": "删除环境变量" + }, "errors": { "noNodesSelected": "无法创建子流程: 未选择节点", "multipleInputsToSelection": "无法创建子流程: 多个输入到了选择" @@ -204,18 +320,68 @@ "editConfig": "编辑__type__配置", "addNewType": "添加新的__type__节点", "nodeProperties": "节点属性", + "label": "标签", + "color": "颜色", "portLabels": "端口标签", "labelInputs": "输入", "labelOutputs": "输出", + "settingIcon": "图标", + "default": "默认", "noDefaultLabel": "无", "defaultLabel": "使用默认标签", + "searchIcons": "搜索图标", + "useDefault": "使用默认", + "description": "描述", + "show": "显示", + "hide": "隐藏", + "locale": "选择界面语言", + "icon": "图标", + "inputType": "输入类型", + "inputs": { + "input": "输入", + "select": "选择", + "checkbox": "复选框", + "spinner": "微调器", + "none": "空", + "hidden": "隐藏属性" + }, + "types": { + "str": "字符串", + "num": "数字", + "bool": "布尔", + "json": "JSON", + "bin": "buffer", + "env": "环境变量" + }, + "menu": { + "input": "输入", + "select": "选择", + "checkbox": "复选框", + "spinner": "微调器", + "hidden": "仅标签" + }, + "select": { + "label": "标签", + "value": "值" + }, + "spinner": { + "min": "最小值", + "max": "最大值" + }, "errors": { - "scopeChange": "更改范围将使其他流中的节点无法使用" + "scopeChange": "更改范围将使其他流中的节点无法使用", + "invalidProperties": "无效的属性:" } }, "keyboard": { "title": "键盘快捷键", + "keyboard": "键盘", + "filterActions": "筛选动作", + "shortcut": "快捷键", + "scope": "范围", "unassigned": "未分配", + "global": "全局", + "workspace": "工作组", "selectAll": "选择所有节点", "selectAllConnected": "选择所有连接的节点", "addRemoveNode": "从选择中添加/删除节点", @@ -226,12 +392,14 @@ "nudgeNode": "移动所选节点(1px)", "moveNode": "移动所选节点(20px)", "toggleSidebar": "切换侧边栏", + "togglePalette": "切换控制板", "copyNode": "复制所选节点", "cutNode": "剪切所选节点", "pasteNode": "粘贴节点", "undoChange": "撤消上次执行的更改", "searchBox": "打开搜索框", - "managePalette": "管理面板" + "managePalette": "管理面板", + "actionList": "动作列表" }, "library": { "library": "库", @@ -239,30 +407,42 @@ "saveToLibrary": "保存到库...", "typeLibrary": "__type__类型库", "unnamedType": "无名__type__", - "exportToLibrary": "将节点导出到库", + "exportedToLibrary": "节点导出到库", "dialogSaveOverwrite": "一个叫做__libraryName__的__libraryType__已经存在,您需要覆盖么?", "invalidFilename": "无效的文件名", "savedNodes": "保存的节点", "savedType": "已保存__type__", "saveFailed": "保存失败: __message__", + "newFolder": "新文件夹", "types": { + "local": "本地的", "examples": "例子" - } + }, + "exportToLibrary": "将节点导出到库" }, "palette": { "noInfo": "无可用信息", "filter": "过滤节点", "search": "搜索模块", + "addCategory": "添加新的...", "label": { "subflows": "子流程", + "network": "网络", + "common": "共通", "input": "输入", "output": "输出", "function": "功能", + "sequence": "序列", + "parser": "解析", "social": "社交", "storage": "存储", "analysis": "分析", "advanced": "高级" }, + "actions": { + "collapse-all": "收起所有类别", + "expand-all": "展开所有类别" + }, "event": { "nodeAdded": "添加到面板中的节点:", "nodeAdded_plural": "添加到面板中的多个节点", @@ -276,6 +456,7 @@ }, "editor": { "title": "面板管理", + "palette": "控制板", "times": { "seconds": "秒前", "minutes": "分前", @@ -309,6 +490,8 @@ "updated": "已更新", "install": "安装", "installed": "已安装", + "conflict": "冲突", + "conflictTip": "

无法安装此模块,因为它包含已安装的
节点类型

__module__冲突

", "loading": "加载目录...", "tab-nodes": "节点", "tab-install": "安装", @@ -356,6 +539,7 @@ "label": "信息", "node": "节点", "type": "类型", + "module": "模组", "id": "ID", "status": "状态", "enabled": "启用", @@ -364,17 +548,18 @@ "instances": "实例", "properties": "属性", "info": "信息", + "desc": "描述", "blank": "空白", "null": "空", "showMore": "展开", "showLess": "收起", "flow": "流程", - "selection":"选择", - "nodes":"__count__ 个节点", + "selection": "选择", + "nodes": "__count__ 个节点", "flowDesc": "流程描述", "subflowDesc": "子流程描述", "nodeHelp": "节点帮助", - "none":"无", + "none": "无", "arrayItems": "__count__个项目", "showTips": "您可以从设置面板启用提示信息" }, @@ -386,9 +571,25 @@ "subflows": "子流程", "flows": "流程", "filterAll": "所有", + "showAllConfigNodes": "显示所有配置节点", "filterUnused": "未使用", + "showAllUnusedConfigNodes": "显示所有未使用的配置节点", "filtered": "__count__ 个隐藏" }, + "context": { + "name": "上下文数据", + "label": "上下午", + "none": "未选择", + "refresh": "刷新以加载", + "empty": "空", + "node": "节点", + "flow": "流程", + "global": "全局", + "deleteConfirm": "你确定要删除这个项目吗?", + "autoRefresh": "刷新选择更改", + "refrsh": "刷新", + "delete": "删除" + }, "palette": { "name": "节点管理", "label": "节点" @@ -399,8 +600,151 @@ "description": "描述", "dependencies": "依赖", "settings": "设置", + "noSummaryAvailable": "无可用摘要", "editDescription": "编辑项目描述", - "editDependencies": "编辑项目依赖" + "editDependencies": "编辑项目依赖", + "noDescriptionAvailable": "没有可用的描述", + "editReadme": "编辑README.md", + "showProjectSettings": "显示项目设置", + "projectSettings": { + "title": "项目设置", + "edit": "编辑", + "none": "空", + "install": "安装", + "removeFromProject": "从项目中删除", + "addToProject": "添加到项目", + "files": "文件", + "package": "包", + "flow": "流程", + "credentials": "证书", + "packageCreate": "保存更改后将创建文件", + "fileNotExist": "文件不存在", + "selectFile": "选择文件", + "invalidEncryptionKey": "无效的加密密钥", + "encryptionEnabled": "启用加密", + "encryptionDisabled": "加密已禁用", + "setTheEncryptionKey": "设置加密密钥", + "resetTheEncryptionKey": "重置加密密钥", + "changeTheEncryptionKey": "更改加密密钥", + "currentKey": "当前密钥", + "newKey": "新密钥", + "credentialsAlert": "这将删除所有现有凭证", + "versionControl": "版本控制", + "branches": "分支", + "noBranches": "没有分支", + "deleteConfirm": "您确定要删除本地分支'__name__'吗? 这不能被撤消。", + "unmergedConfirm": "本地分支'__name__'具有未合并的更改,这些更改将丢失。你确定要删除吗?", + "deleteUnmergedBranch": "删除未合并的分支", + "gitRemotes": "Git远程仓库", + "addRemote": "添加远程仓库", + "addRemote2": "添加远程仓库", + "remoteName": "远程仓库名", + "nameRule": "只能包含A-Z 0-9 _ -", + "url": "URL", + "urlRule": "https://, ssh:// or file://", + "urlRule2": "网址中不能包含用户名/密码", + "noRemotes": "没有远程仓库", + "deleteRemoteConfrim": "您确定要删除远程仓库'__name__'吗?", + "deleteRemote": "删除远程仓库" + }, + "userSettings": { + "committerDetail": "提交者详细信息", + "committerTip": "保留空白以使用系统默认值", + "userName": "用户名", + "email": "电子邮件", + "sshKeys": "SSH密钥", + "sshKeysTip": "允许您创建到远程git存储库的安全连接。", + "add": "添加密钥", + "addSshKey": "添加SSH密钥", + "addSshKeyTip": "生成新的公钥/私钥对", + "name": "名字", + "nameRule": "只能包含A-Z 0-9 _ -", + "passphrase": "密码短语", + "passphraseShort": "密码短语过短", + "optional": "可选的", + "cancel": "取消", + "generate": "生成密钥", + "noSshKeys": "没有SSH密钥", + "copyPublicKey": "将公钥复制到剪贴板", + "delete": "删除密钥", + "gitConfig": "Git配置", + "deleteConfirm": "您确定要删除SSH密钥__name__吗?这不能被撤消。" + }, + "versionControl": { + "unstagedChanges": "未暂存的变更", + "stagedChanges": "暂存的变更", + "unstageChange": "取消变更的暂存", + "stageChange": "暂存变更", + "unstageAllChange": "取消所有变更的暂存", + "stageAllChange": "暂存所有变更", + "commitChanges": "提交变更", + "resolveConflicts": "解决冲突", + "head": "HEAD", + "staged": "暂存的", + "unstaged": "未暂存的", + "local": "本地的", + "remote": "远程的", + "revert": "您确定要将更改恢复为'__file__'吗?这不能被撤消。", + "revertChanges": "还原变更", + "localChanges": "本地变更", + "none": "None", + "conflictResolve": "解决所有冲突。提交更改以完成合并。", + "localFiles": "本地文件", + "all": "所有的", + "unmergedChanges": "未合并的更改", + "abortMerge": "中止合并", + "commit": "提交", + "changeToCommit": "提交变更", + "commitPlaceholder": "输入您的提交信息", + "cancelCapital": "取消", + "commitCapital": "提交", + "commitHistory": "提交历史", + "branch": "分支:", + "moreCommits": "更多提交", + "changeLocalBranch": "变更本地分支", + "createBranchPlaceholder": "查找或创建分支", + "upstream": "上游", + "localOverwrite": "切换分支会覆盖您现有的本地更改。您必须先提交或撤消那些更改。", + "manageRemoteBranch": "管理远程分支", + "unableToAccess": "无法访问远程存储库", + "retry": "重试", + "setUpstreamBranch": "设置为上游分支", + "createRemoteBranchPlaceholder": "查找或创建远程分支", + "trackedUpstreamBranch": "创建的分支将被设置为跟踪的上游分支。", + "selectUpstreamBranch": "分支将被创建。 在下面选择以将其设置为被跟踪的上游分支。", + "pushFailed": "推送失败,因为远程具有更多的最新提交。请先拉取并合并,然后再尝试推送。", + "push": "推送", + "pull": "拉取", + "unablePull": "

无法提取远程更改;您未暂存的本地更改将被覆盖。

请先提交更改,然后重试。

", + "showUnstagedChanges": "显示未暂存的更改", + "connectionFailed": "无法连接到远程存储库:", + "pullUnrelatedHistory": "

远程有无关的提交历史

您确定要将这些更改拉入本地仓库吗?

", + "pullChanges": "拉取更改", + "history": "历史", + "projectHistory": "项目历史", + "daysAgo": "__count__天前", + "daysAgo_plural": "__count__天前", + "hoursAgo": "__count__小时前", + "hoursAgo_plural": "__count__小时前", + "minsAgo": "__count__分钟前", + "minsAgo_plural": "__count__分钟前", + "secondsAgo": "秒前", + "notTracking": "您的本地分支当前未跟踪一个远程分支。", + "statusUnmergedChanged": "您的仓库中有未合并的更改。您需要解决冲突并提交结果。", + "repositoryUpToDate": "您的仓库是最新的。", + "commitsAhead": "您的存储库领先远程仓库__count__次提交。您现在可以推送这些提交。", + "commitsAhead_plural": "您的存储库领先远程仓库__count__次提交。您现在可以推送这些提交。", + "commitsBehind": "您的存储库落后远程仓库__count__次提交。您现在可以拉取这些提交。", + "commitsBehind_plural": "您的存储库落后远程仓库__count__次提交。您现在可以拉取这些提交。", + "commitsAheadAndBehind1": "您的存储库落后远程仓库__count__次提交", + "commitsAheadAndBehind1_plural": "您的存储库落后远程仓库__count__次提交", + "commitsAheadAndBehind2": "领先远程仓库__count__次提交。", + "commitsAheadAndBehind2_plural": "领先远程仓库__count__次提交。", + "commitsAheadAndBehind3": "您必须先拉取远程提交,然后才能进行推送。", + "commitsAheadAndBehind3_plural": "您必须先拉取远程提交,然后才能进行推送。", + "refreshCommitHistory": "刷新提交历史", + "refreshChanges": "刷新更改" + } } }, "typedInput": { @@ -408,10 +752,12 @@ "str": "文字列", "num": "数字", "re": "正则表达式", - "bool": "布尔", + "bool": "布尔值", "json": "JSON", "bin": "二进制流", - "date": "时间戳" + "date": "时间戳", + "jsonata": "表达式", + "env": "环境变量" } }, "editableList": { @@ -423,8 +769,10 @@ }, "expressionEditor": { "functions": "功能", + "functionReference": "功能reference", "insert": "插入", "title": "JSONata表达式编辑器", + "test": "测试", "data": "示例消息", "result": "结果", "format": "格式表达方法", @@ -438,14 +786,228 @@ "eval": "评估表达式错误:\n __message__" } }, + "jsEditor": { + "title": "JavaScript编辑器" + }, + "textEditor": { + "title": "文本编辑器" + }, "jsonEditor": { "title": "JSON编辑器", - "format": "格式化JSON" + "format": "格式化JSON", + "rawMode": "编辑 JSON", + "uiMode": "Visual编辑器", + "insertAbove": "在上方插入", + "insertBelow": "在下方插入", + "addItem": "添加项目", + "copyPath": "复制路径到项目", + "expandItems": "展开项目", + "collapseItems": "收合项目", + "duplicate": "重复", + "error": { + "invalidJSON": "无效的JSON: " + } + }, + "markdownEditor": { + "title": "Markdown编辑器", + "expand": "展开", + "format": "格式化为markdown", + "heading1": "标题 1", + "heading2": "标题 2", + "heading3": "标题 3", + "bold": "粗体", + "italic": "斜体", + "code": "代码", + "ordered-list": "排序的列表", + "unordered-list": "非排序的列表", + "quote": "引用", + "link": "链接", + "horizontal-rule": "水平线", + "toggle-preview": "切换预览" }, "bufferEditor": { "title": "缓冲区编辑器", "modeString": "作为UTF-8字符串处理", "modeArray": "作为JSON数组处理", "modeDesc": "

缓冲区编辑器

缓冲区类型被存储为字节值的JSON数组。编辑器将尝试将输入的数值解析为JSON数组。如果它不是有效的JSON,它将被视为UTF-8字符串,并被转换为单个字符代码点的数组。

例如,Hello World的值会被转换为JSON数组:

[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]

" + }, + "projects": { + "config-git": "配置Git客户端", + "welcome": { + "hello": "你好! 我们已经将“项目”引入了Node-RED。", + "desc0": "这是一种用于管理流程文件的新方法,并且包括对流程的版本控制。", + "desc1": "首先,您可以创建您的第一个项目或从git存储库克隆现有项目。", + "desc2": "如果不确定,可以暂时跳过此步骤。您仍然可以随时通过“项目”菜单创建第一个项目。", + "create": "建立专案", + "clone": "克隆仓库", + "openExistingProject": "打开现有项目", + "not-right-now": "不是现在" + }, + "git-config": { + "setup": "设置您的版本控制客户端", + "desc0": "Node-RED使用开源工具Git进行版本控制。它跟踪对项目文件的更改,并允许您将其推送到远程存储库。", + "desc1": "提交一组更改时,Git会使用用户名和电子邮件地址记录谁进行了更改。用户名可以是您想要的任何名称-不必是您的真实姓名。", + "desc2": "您的Git客户端已经配置了以下详细信息。", + "desc3": "您可以稍后在设置对话框的'Git config'标签下更改这些设置。", + "username": "用户名", + "email": "电子邮件" + }, + "project-details": { + "create": "创建你的项目", + "desc0": "项目被维护为Git仓库。与他人一起共享您的流程", + "desc1": "您可以创建多个项目,并通过编辑器在它们之间快速切换。", + "desc2": "首先,您的项目需要一个名称和一个可选的描述。", + "already-exists": "项目已存在", + "must-contain": "只能包含A-Z 0-9 _ -", + "project-name": "项目名", + "desc": "描述", + "opt": "可选的" + }, + "clone-project": { + "clone": "克隆一个项目", + "desc0": "如果您已经有一个包含项目的git仓库,则可以对其进行克隆以开始使用。", + "already-exists": "项目已存在", + "must-contain": "只能包含A-Z 0-9 _ -", + "project-name": "项目名", + "no-info-in-url": "网址中不要包含用户名/密码", + "git-url": "Git仓库的url", + "protocols": "https://, ssh:// or file://", + "auth-failed": "认证失败", + "username": "用户名", + "passwd": "秘密啊", + "ssh-key": "SSH密钥", + "passphrase": "密码短语", + "ssh-key-desc": "在通过ssh克隆仓库之前,必须添加SSH密钥才能访问它。", + "ssh-key-add": "添加一个ssh密钥", + "credential-key": "证书加密密钥", + "cant-get-ssh-key": "错误! 无法获取所选的SSH密钥路径。", + "already-exists2": "已存在", + "git-error": "git错误", + "connection-failed": "连接失败", + "not-git-repo": "不是一个git仓库", + "repo-not-found": "未发现仓库" + }, + "default-files": { + "create": "创建您的项目文件", + "desc0": "一个包含您的流程文件,Readme文件和package.json文件的项目。", + "desc1": "它可以包含您要在Git仓库中维护的任何其他文件。", + "desc2": "您现有的流程和凭证文件将被复制到项目中。", + "flow-file": "流程文件", + "credentials-file": "证书文件" + }, + "encryption-config": { + "setup": "设置证书文件的加密", + "desc0": "您的流程证书文件可以被加密以确保其内容安全。", + "desc1": "如果要将这些证书存储在公共Git存储库中,则必须通过提供密钥短语来对它们进行加密。", + "desc2": "您的流程证书文件当前未加密。", + "desc3": "这意味着任何有权访问该文件的人都可以读取其内容,例如密码和访问令牌。", + "desc4": "如果要将这些证书存储在公共Git仓库中,则必须通过提供密钥短语来对它们进行加密。", + "desc5": "当前,使用设置文件中的credentialSecret属性作为密钥来加密流程证书文件。", + "desc6": "您的流程证书文件当前使用系统生成的密钥加密。您应该为此项目提供一个新的密钥。", + "desc7": "密钥将与项目文件分开存储。您将需要提供在另一个Node-RED实例中使用该项目的密钥。", + "credentials": "证书", + "enable": "启用加密", + "disable": "禁用加密", + "disabled": "禁用的", + "copy": "复制现有密钥", + "use-custom": "使用自定义密钥", + "desc8": "证书文件不会被加密,其内容很容易阅读", + "create-project-files": "创建项目文件", + "create-project": "创建项目", + "already-exists": "已存在", + "git-error": "git错误", + "git-auth-error": "git认证错误" + }, + "create-success": { + "success": "您已经成功创建了第一个项目!", + "desc0": "现在,您可以像往常一样继续使用Node-RED。", + "desc1": "侧栏中的“信息”标签显示了您当前的活动项目。名称旁边的按钮可用于访问项目设置视图。", + "desc2": "侧栏中的“历史记录”标签可用于查看项目中已更改的文件并提交。它向您显示了提交的完整历史记录,并允许您将更改推送到远程存储库。" + }, + "create": { + "projects": "项目", + "already-exists": "项目已存在", + "must-contain": "只能包含A-Z 0-9 _ -", + "no-info-in-url": "网址中不要包含用户名/密码", + "open": "打开项目", + "create": "创建项目", + "clone": "克隆仓库", + "project-name": "项目名", + "desc": "描述", + "opt": "可选的", + "flow-file": "流程文件", + "credentials": "证书", + "enable-encryption": "启用加密", + "disable-encryption": "禁用加密", + "encryption-key": "加密密钥", + "desc0": "用来保护您的凭证的短语", + "desc1": "凭证文件不会被加密,其内容很容易阅读", + "git-url": "Git存储库URL", + "protocols": "https://, ssh:// or file://", + "auth-failed": "验证失败", + "username": "用户名", + "password": "密码", + "ssh-key": "SSH密钥", + "passphrase": "密码短语", + "desc2": "在通过ssh克隆存储库之前,必须添加SSH密钥才能访问它。", + "add-ssh-key": "添加一个ssh密钥", + "credentials-encryption-key": "证书加密密钥", + "already-exists-2": "已存在", + "git-error": "git错误", + "con-failed": "连接失败", + "not-git": "不是git仓库", + "no-resource": "找不到存储库", + "cant-get-ssh-key-path": "错误!无法获取所选的SSH密钥路径。", + "unexpected_error": "意外的错误" + }, + "delete": { + "confirm": "您确定要删除此项目吗?" + }, + "create-project-list": { + "search": "搜索您的项目", + "current": "当前的" + }, + "require-clean": { + "confirm": "

您有未部署的更改,这些更改将丢失。

您要继续吗?

" + }, + "send-req": { + "auth-req": "存储库需要认证", + "username": "用户名", + "password": "秘密", + "passphrase": "密码短语", + "retry": "重试", + "update-failed": "无法更新身份验证", + "unhandled": "未处理的错误响应" + }, + "create-branch-list": { + "invalid": "无效的分支", + "create": "创建分支", + "current": "当前的" + }, + "create-default-file-set": { + "no-active": "没有活动项目就无法创建默认文件集", + "no-empty": "无法在非空项目上创建默认文件集", + "git-error": "git错误" + }, + "errors": { + "no-username-email": "您的Git客户端未配置用户名/电子邮件。", + "unexpected": "发生了一个意料之外的问题", + "code": "代码" + } + }, + "editor-tab": { + "properties": "属性", + "envProperties": "环境变量", + "description": "描述", + "appearance": "外观", + "preview": "UI预览", + "defaultValue": "默认值" + }, + "languages": { + "de": "德语", + "en-US": "英文", + "ja": "日语", + "ko": "韩文", + "zh-CN": "简体中文" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json index 669a7683a..56ede1b4e 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json @@ -214,5 +214,53 @@ "$toMillis": { "args": "timestamp", "desc": "将ISO 8601格式的字符串`timestamp`转换为从UNIX时间 (1970年1月1日 UTC/GMT的午夜)开始到现在的毫秒数。如果该字符串的格式不正确,则抛出错误。" + }, + "$env": { + "args": "arg", + "desc": "返回环境变量的值。\n\n这是Node-RED定义的函数。" + }, + "$eval": { + "args": "expr [, context]", + "desc": "使用当前上下文来作为评估依据,分析并评估字符串`expr`,其中包含文字JSON或JSONata表达式。" + }, + "$formatInteger": { + "args": "number, picture", + "desc": "将“数字”转换为字符串,并将其格式化为“图片”字符串指定的整数表示形式。图片字符串参数定义了数字的格式,并具有与XPath F&O 3.1 规范中的fn:format-integer相同的语法。" + }, + "$parseInteger": { + "args": "string, picture", + "desc": "使用“图片”字符串指定的格式将“字符串”参数的内容解析为整数(作为JSON数字)。图片字符串参数与$formatInteger格式相同。." + }, + "$error": { + "args": "[str]", + "desc": "引发错误并显示一条消息。 可选的`str`将替代$error()函数评估的默认消息。" + }, + "$assert": { + "args": "arg, str", + "desc": "如果`arg`为真,则该函数返回。 如果arg为假,则抛出带有str的异常作为异常消息。" + }, + "$single": { + "args": "array, function", + "desc": "返回满足参数function谓语的array参数中的唯一值 (比如:传递值时,函数返回布尔值“true”)。如果匹配值的数量不唯一时,则抛出异常。\n\n应在以下签名中提供函数:`function(value [,index [,array []]])`其中value是数组的每个输入,index是该值的位置,整个数组作为第三个参数传递。" + }, + "$encodeUrl": { + "args": "str", + "desc": "通过用表示字符的UTF-8编码的一个,两个,三个或四个转义序列替换某些字符的每个实例,对统一资源定位符(URL)组件进行编码。\n\n示例:`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" + }, + "$encodeUrlComponent": { + "args": "str", + "desc": "通过用表示字符的UTF-8编码的一个,两个,三个或四个转义序列替换某些字符的每个实例,对统一资源定位符(URL)进行编码。\n\n示例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`" + }, + "$decodeUrl": { + "args": "str", + "desc": "解码以前由encodeUrlComponent创建的统一资源定位器(URL)组件。 \n\n示例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`" + }, + "$decodeUrlComponent": { + "args": "str", + "desc": "解码先前由encodeUrl创建的统一资源定位符(URL)。 \n\n示例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`" + }, + "$distinct": { + "args": "array", + "desc": "返回一个数组,其中重复的值已从`数组`中删除" } } From ad6104baeb0d68c098f89180227a9c8c10a12fe0 Mon Sep 17 00:00:00 2001 From: JIYE YU Date: Fri, 7 Feb 2020 17:58:20 +0900 Subject: [PATCH 036/346] wrap up ch-ZN translation for nodes message.json --- .../nodes/locales/zh-CN/messages.json | 179 ++++++++++++++---- 1 file changed, 140 insertions(+), 39 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json index 9202bb3a7..46560d181 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json @@ -6,7 +6,9 @@ "name": "名称", "username": "用户名", "password": "密码", - "property": "属性" + "property": "属性", + "selectNodes": "选择节点...", + "expand": "扩展" }, "status": { "connected": "已连接", @@ -35,7 +37,22 @@ "stopped": "停止", "failed": "注入失败: __error__", "label": { - "repeat": "重复" + "repeat": "重复", + "flow": "流上下午", + "global": "全局上下文", + "str": "字符串", + "num": "数值", + "bool": "布尔值", + "json": "JSON对象", + "bin": "buffer", + "date": "时间戳", + "env": "环境变量", + "object": "对象", + "string": "字符串", + "boolean": "布尔值", + "number": "数值", + "Array": "数组", + "invalid": "无效的JSON对象" }, "timestamp": "时间戳", "none": "无", @@ -70,15 +87,13 @@ } }, "catch": { - "catch": "监测所有节点", - "catchNodes": "监测__number__个节点", + "catch": "捕获:所有节点", + "catchNodes": "捕获:__number__个节点", + "catchUncaught": "捕获:未捕获", "label": { - "source": "监测范围", - "node": "节点", - "type": "类型", + "source": "捕获范围", "selectAll": "全选", - "sortByLabel": "按名称排序", - "sortByType": "按类型排序" + "uncaught": "忽略其他捕获节点处理的错误" }, "scope": { "all": "所有节点", @@ -90,10 +105,6 @@ "statusNodes": "报告__number__个节点状态", "label": { "source": "报告状态范围", - "node": "节点", - "type": "类型", - "selectAll": "全选", - "sortByLabel": "按名称排序", "sortByType": "按类型排序" }, "scope": { @@ -101,8 +112,13 @@ "selected": "指定节点" } }, + "complete": { + "completeNodes": "完成: __number__个节点" + }, "debug": { "output": "输出", + "none": "None", + "invalid-exp": "无效的JSONata表达式: __error__", "msgprop": "信息属性", "msgobj": "完整信息", "to": "目标", @@ -124,7 +140,11 @@ "filterCurrent": "当前流程", "debugNodes": "调试节点", "clearLog": "清空日志", - "openWindow": "在新窗口打开" + "filterLog": "过滤日志", + "openWindow": "在新窗口打开", + "copyPath": "复制路径", + "copyPayload": "复制值", + "pinPath": "固定展开" }, "messageMenu": { "collapseAll": "折叠所有路径", @@ -146,26 +166,33 @@ "key": "私钥", "passphrase": "密码", "ca": "CA证书", - "verify-server-cert":"验证服务器证书" + "verify-server-cert":"验证服务器证书", + "servername": "服务器名" }, "placeholder": { "cert":"证书路径 (PEM 格式)", "key":"私钥路径 (PEM 格式)", "ca":"CA证书路径 (PEM 格式)", - "passphrase":"私钥密码 (可选)" + "passphrase":"私钥密码 (可选)", + "servername":"用于SNI" }, "error": { "missing-file": "未提供证书/密钥文件" } }, "exec": { + "exec": "exec", + "spawn": "spawn", "label": { "command": "命令", "append": "追加", "timeout": "超时", "timeoutplace": "可选填", "return": "输出", - "seconds": "秒" + "seconds": "秒", + "stdout": "标准输出", + "stderr": "标准错误输出", + "retcode": "返回码" }, "placeholder": { "extraparams": "额外的输入参数" @@ -177,6 +204,7 @@ "oldrc": "使用旧式输出 (兼容模式)" }, "function": { + "function": "函数", "label": { "function": "函数", "outputs": "输出" @@ -187,6 +215,7 @@ } }, "template": { + "template": "模板", "label": { "template": "模版", "property": "属性", @@ -199,7 +228,7 @@ "yaml": "YAML", "none": "无" }, - "templatevalue": "This is the payload: {{payload}} !" + "templatevalue": "这是有效载荷: {{payload}} !" }, "delay": { "action": "行为设置", @@ -272,6 +301,9 @@ "wait-reset": "等待被重置", "wait-for": "等待", "wait-loop": "周期性重发", + "for": "处理", + "bytopics": "每个msg.topic", + "alltopics": "所有消息", "duration": { "ms": "毫秒", "s": "秒", @@ -290,6 +322,7 @@ } }, "comment": { + "comment": "注释" }, "unknown": { "label": { @@ -300,9 +333,10 @@ "mqtt": { "label": { "broker": "服务端", - "example": "e.g. localhost", + "example": "比如:本地主机", "output": "输出", "qos": "QoS", + "retain": "保持", "clientid": "客户端ID", "port": "端口", "keepalive": "Keepalive计时(秒)", @@ -312,17 +346,22 @@ "verify-server-cert":"验证服务器证书", "compatmode": "使用旧式MQTT 3.1支持" }, + "sections-label":{ + "birth-message": "连接时发送的消息(出生消息)", + "will-message":"意外断开连接时的发送消息(Will消息)", + "close-message":"断开连接前发送的消息(关闭消息)" + }, "tabs-label": { "connection": "连接", "security": "安全", - "will": "Will信息", - "birth": "Birth信息" + "messages": "消息" }, "placeholder": { "clientid": "留白则自动生成", "clientid-nonclean":"如非新会话,必须设置客户端ID", "will-topic": "留白将禁止Will信息", - "birth-topic": "留白将禁止Birth信息" + "birth-topic": "留白将禁止Birth信息", + "close-topic": "留白以禁用关闭消息" }, "state": { "connected": "已连接到服务端: __broker__", @@ -333,7 +372,9 @@ "output": { "buffer": "Buffer", "string": "字符串", - "base64": "Base64编码字符串" + "base64": "Base64编码字符串", + "auto": "自动检测 (字符串或buffer)", + "json": "解析的JSON对象" }, "true": "是", "false": "否", @@ -342,7 +383,9 @@ "not-defined": "主题未设置", "missing-config": "未设置服务端", "invalid-topic": "主题无效", - "nonclean-missingclientid": "客户端ID未设定,使用新会话" + "nonclean-missingclientid": "客户端ID未设定,使用新会话", + "invalid-json-string": "无效的JSON字符串", + "invalid-json-parse": "无法解析JSON字符串" } }, "httpin": { @@ -353,13 +396,27 @@ "return": "返回", "upload": "接受文件上传?", "status": "状态码", - "headers": "Header", - "other": "其他" + "headers": "头", + "other": "其他", + "paytoqs" : "将msg.payload附加为查询字符串参数", + "utf8String": "UTF8格式的字符串", + "binaryBuffer": "二进制buffer", + "jsonObject": "解析的JSON对象", + "authType": "类型", + "bearerToken": "Token" }, "setby": "- 用 msg.method 设定 -", "basicauth": "基本认证", "use-tls": "使用安全连接 (SSL/TLS) ", "tls-config":"TLS 设置", + "basic": "基本认证", + "digest": "摘要认证", + "bearer": "bearer认证", + "use-proxy": "使用代理服务器", + "persist": "对连接启用keep-alive", + "proxy-config": "代理服务器设置", + "use-proxyauth": "使用代理身份验证", + "noproxy-hosts": "代理例外", "utf8": "UTF-8 字符串", "binary": "二进制数据", "json": "JSON对象", @@ -376,7 +433,10 @@ "json-error": "JSON 解析错误", "no-url": "未设定 URL", "deprecated-call":"__method__方法已弃用", - "invalid-transport":"非HTTP传输请求" + "invalid-transport":"非HTTP传输请求", + "timeout-isnan": "超时值不是有效数字,忽略", + "timeout-isnegative": "超时值为负,忽略", + "invalid-payload": "无效的有效载荷" }, "status": { "requesting": "请求中" @@ -399,13 +459,19 @@ "url1": "URL 应该使用ws://或者wss://方案并指向现有的websocket侦听器.", "url2": "默认情况下,payload 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象." }, + "status": { + "connected": "连接数 __count__", + "connected_plural": "连接数 __count__" + }, "errors": { "connect-error": "ws连接发生了错误: ", "send-error": "发送时发生了错误: ", - "missing-conf": "未设置服务器" + "missing-conf": "未设置服务器", + "duplicate-path": "同一路径上不能有两个WebSocket侦听器: __path__" } }, "watch": { + "watch": "watch", "label": { "files": "文件", "recursive": "递归所有子文件夹" @@ -421,7 +487,7 @@ "output": "输出", "port": "端口", "host": "主机地址", - "payload": "的有效载荷", + "payload": "有效载荷", "delimited": "分隔符号", "close-connection": "是否在成功发送每条信息后断开连接?", "decode-base64": "用 Base64 解码信息?", @@ -480,7 +546,6 @@ "output": "输出", "group": "组", "interface": "本地IP", - "interfaceprompt": "(可选)本地 IP 绑定到", "send": "发送一个", "toport": "到端口", "address": "地址", @@ -488,6 +553,7 @@ }, "placeholder": { "interface": "(可选)eth0的IP地址", + "interfaceprompt": "(可选) 要绑定的本地接口或地址", "address": "目标IP地址" }, "udpmsgs": "udp信息", @@ -529,15 +595,18 @@ "ip-notset": "udp: IP地址未设定", "port-notset": "udp: 端口未设定", "port-invalid": "udp: 无效端口号码", - "alreadyused": "udp: 端口已被占用" + "alreadyused": "udp: 端口已被占用", + "ifnotfound": "udp: 接口 __iface__ 未发现" } }, "switch": { + "switch": "switch", "label": { "property": "属性", "rule": "规则", "repair" : "重建信息队列" }, + "previous": "先前值", "and": "与", "checkall": "全选所有规则", "stopfirst": "接受第一条匹配信息后停止", @@ -550,11 +619,15 @@ "false":"为假", "null":"为空", "nnull":"非空", - "head":"head", - "tail":"tail", - "index":"index between", + "istype": "类型是", + "empty": "为空", + "nempty": "非空", + "head":"头", + "tail":"尾", + "index":"索引在..中间", "exp":"JSONata表达式", - "else":"除此以外" + "else":"除此以外", + "hask": "拥有键" }, "errors": { "invalid-expr": "无效的JSONata表达式: __error__", @@ -588,6 +661,7 @@ } }, "range": { + "range": "range", "label": { "action": "操作", "inputrange": "映射输入数据", @@ -623,7 +697,8 @@ "firstrow": "第一行包含列名", "output": "输出", "includerow": "包含列名行", - "newline": "换行符" + "newline": "换行符", + "usestrings": "parse numerical values" }, "placeholder": { "columns": "用逗号分割列名" @@ -654,7 +729,8 @@ "html": { "label": { "select": "选取项", - "output": "输出" + "output": "输出", + "in": "in" }, "output": { "html": "选定元素的html内容", @@ -670,7 +746,9 @@ "errors": { "dropped-object": "忽略非对象格式的有效负载", "dropped": "忽略不支持格式的有效负载类型", - "dropped-error": "转换有效负载失败" + "dropped-error": "转换有效负载失败", + "schema-error": "JSON架构错误", + "schema-error-compile": "JSON架构错误: 未能编译架构" }, "label": { "o2j": "对象至JSON", @@ -713,7 +791,10 @@ "breaklines": "分拆成行", "filelabel": "文件", "sendError": "发生错误时发送消息(传统模式)", - "deletelabel": "删除 __file__" + "deletelabel": "删除 __file__", + "encoding": "编码", + "utf8String": "UTF8字符串", + "binaryBuffer": "二进制buffer" }, "action": { "append": "追加至文件", @@ -731,6 +812,21 @@ "deletedfile": "删除文件: __file__", "appendedfile": "追加至文件: __file__" }, + "encoding": { + "none": "默认", + "native": "Native", + "unicode": "Unicode", + "japanese": "日本", + "chinese": "中国", + "korean": "韩国", + "taiwan": "台湾/香港", + "windows": "Windows代码页", + "iso": "ISO代码页", + "ibm": "IBM代码页", + "mac": "Mac代码页", + "koi8": "KOI8代码页", + "misc": "其它" + }, "errors": { "nofilename": "未指定文件名", "invaliddelete": "警告:无效删除。请在配置对话框中使用特定的删除选项", @@ -742,6 +838,7 @@ "tip": "提示: 文件名应该是绝对路径,否则它将相对于Node-RED进程的工作目录。" }, "split": { + "split": "split", "intro":"基于以下类型拆分msg.payload:", "object":"对象", "objectSend":"每个键值对作为单个消息发送", @@ -753,6 +850,7 @@ "addname":" 复制键到 " }, "join":{ + "join": "join", "mode":{ "mode":"模式", "auto":"自动", @@ -761,6 +859,7 @@ "custom":"手动" }, "combine":"合并每个", + "completeMessage": "完整的消息", "create":"输出为", "type":{ "string":"字符串", @@ -799,6 +898,7 @@ } }, "sort" : { + "sort": "sort", "target" : "排序属性", "seq" : "信息队列", "key" : "键值", @@ -812,6 +912,7 @@ "clear" : "清空sort节点中的待定信息" }, "batch" : { + "batch": "batch", "mode": { "label" : "模式", "num-msgs" : "按指定数量分组", From 00eb474e0230159cc5335979261537b66fd47774 Mon Sep 17 00:00:00 2001 From: JIYE YU Date: Fri, 7 Feb 2020 18:27:06 +0900 Subject: [PATCH 037/346] new zh-CN translation for runtime --- .../runtime/locales/zh-CN/runtime.json | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 packages/node_modules/@node-red/runtime/locales/zh-CN/runtime.json diff --git a/packages/node_modules/@node-red/runtime/locales/zh-CN/runtime.json b/packages/node_modules/@node-red/runtime/locales/zh-CN/runtime.json new file mode 100644 index 000000000..d0813f476 --- /dev/null +++ b/packages/node_modules/@node-red/runtime/locales/zh-CN/runtime.json @@ -0,0 +1,174 @@ +{ + "runtime": { + "welcome": "欢迎使用Node-RED", + "version": "__component__ 版本: __version__", + "unsupported_version": "__component__的不受支持的版本。要求: __requires__ 找到: __version__", + "paths": { + "settings": "设置文件 : __path__", + "httpStatic": "HTTP Static : __path__" + } + }, + + "server": { + "loading": "加载控制板节点", + "palette-editor": { + "disabled": "控制板编辑器已禁用:用户设置", + "npm-not-found": "控制板编辑器已禁用:找不到npm命令", + "npm-too-old": "控制板编辑器已禁用: npm版本太旧。需要版本npm >= 3.x" + }, + "errors": "无法注册__count__节点类型", + "errors_plural": "无法注册__count__个节点类型", + "errors-help": "使用-v运行以获取详细信息", + "missing-modules": "缺少节点模块:", + "node-version-mismatch": "无法在此版本上加载节点模块。要求:__ version__", + "type-already-registered": "'__type__'已由模块__module__注册", + "removing-modules": "从配置中删除模块", + "added-types": "添加的节点类型:", + "removed-types": "删除的节点类型:", + "install": { + "invalid": "无效的模块名称", + "installing": "安装模块:__ name__,版本:__ version__", + "installed": "已安装的模块:__ name__", + "install-failed": "安装失败", + "install-failed-long": "模块__name__安装失败:", + "install-failed-not-found": "$t(server.install.install-failed-long) 模块未发现", + "upgrading": "更新模块: __name__ 至版本: __version__", + "upgraded": "更新模块: __name__。 重新启动Node-RED以使用新版本", + "upgrade-failed-not-found": "$t(server.install.install-failed-long) 未发现版本", + "uninstalling": "卸载模块: __name__", + "uninstall-failed": "卸载失败", + "uninstall-failed-long": "卸载模块__name__失败:", + "uninstalled": "卸载模块: __name__" + }, + "unable-to-listen": "无法在__listenpath__上收听", + "port-in-use": "错误: 端口正在使用中", + "uncaught-exception": "未捕获的异常:", + "admin-ui-disabled": "管理员界面已禁用", + "now-running": "服务器现在在__listenpath__上运行", + "failed-to-start": "无法启动服务器:", + "headless-mode": "在headless模式下运行", + "httpadminauth-deprecated": "不建议使用httpAdminAuth。请改用adminAuth" + }, + + "api": { + "flows": { + "error-save": "保存流程错误: __message__", + "error-reload": "重载流程错误: __message__" + }, + "library": { + "error-load-entry": "加载库条目'__path__'时出错:__message__", + "error-save-entry": "保存库条目'__path__'时出错:__ message__", + "error-load-flow": "加载流程'__path__'时出错:__ message__", + "error-save-flow": "保存流'__path__'时出错:__ message__" + }, + "nodes": { + "enabled": "启用的节点类型:", + "disabled": "禁用的节点类型:", + "error-enable": "无法启用节点:" + } + }, + + "comms": { + "error": "通讯渠道错误:__ message__", + "error-server": "通信服务器错误:__ message__", + "error-send": "通讯发送错误:__ message__" + }, + + "settings": { + "user-not-available": "无法保存用户设置:__ message__", + "not-available": "设置不可用", + "property-read-only": "属性“ __prop__”是只读的" + }, + + "nodes": { + "credentials": { + "error":"加载证书时出错:__ message__", + "error-saving":"保存证书时出错:__ message__", + "not-registered": "证书类型'__type__'未注册", + "system-key-warning": "\n\n---------------------------------------------------------------------\n您的流程证书文件是使用系统生成的密钥加密的。\n\n如果系统生成的密钥由于任何原因丢失,则您的证书文件将无法恢复,您将必须删除它并重新输入您的证书。\n\n您应该使用您的设置文件中的'credentialSecret'选项设置自己的密钥。然后,下次部署更改时,Node-RED将使用选择的密钥重新加密您的证书文件。\n---------------------------------------------------------------------\n" + }, + "flows": { + "safe-mode": "流程在安全模式下停止。部署开始。", + "registered-missing": "缺少注册的类型:__ type__", + "error": "错误加载流程:__ message__", + "starting-modified-nodes": "启动修改的节点", + "starting-modified-flows": "启动修改的流程", + "starting-flows": "启动流程", + "started-modified-nodes": "修改的节点已启动", + "started-modified-flows": "修改的流程已启动", + "started-flows": "流程已启动", + "stopping-modified-nodes": "停止修改的节点", + "stopping-modified-flows": "停止修改的流程", + "stopping-flows": "停止流程", + "stopped-modified-nodes": "修改的节点已停止", + "stopped-modified-flows": "修改的流程已停止", + "stopped-flows": "流程已停止", + "stopped": "已停止", + "stopping-error": "错误停止节点:__ message__", + "added-flow": "流程已添加: __label__", + "updated-flow": "流程已更新: __label__", + "removed-flow": "流程已移除: __label__", + "missing-types": "等待缺少的类型被注册:", + "missing-type-provided": " - __type__ (由npm模块__module__提供)", + "missing-type-install-1": "要安装所有缺少的模块,请运行:", + "missing-type-install-2": "在目录中:" + }, + "flow": { + "unknown-type": "未知类型: __type__", + "missing-types": "缺少类型", + "error-loop": "邮件已超过最大捕获数" + }, + "index": { + "unrecognised-id": "无法识别的ID: __id__", + "type-in-use": "使用中的类型: __msg__", + "unrecognised-module": "无法识别的模块: __module__" + }, + "registry": { + "localfilesystem": { + "module-not-found": "找不到模块:'__module__'" + } + } + }, + + "storage": { + "index": { + "forbidden-flow-name": "禁止流程名称" + }, + "localfilesystem": { + "user-dir": "用户目录: __path__", + "flows-file": "流程文件: __path__", + "create": "创建新__type__文件", + "empty": "现有__type__文件为空", + "invalid": "现有__type__文件为无效json", + "restore": "恢复__type__文件备份:__path__", + "restore-fail": "恢复__type__文件备份失败:__ message__", + "fsync-fail": "将文件__path__刷新到磁盘失败:__message__", + "projects": { + "changing-project": "设置活动项目:__ project__", + "active-project": "活动项目:__ project__", + "project-not-found": "找不到项目:__ project__", + "no-active-project": "没有活动的项目:使用默认流文件", + "disabled": "项目已禁用:editorTheme.projects.enabled = false", + "disabledNoFlag": "项目已禁用:设置editorTheme.projects.enabled = true来启用", + "git-not-found": "项目已禁用:找不到git命令", + "git-version-old": "项目已禁用:不支持的git __version__。 需要的git版本为2.x", + "summary": "一个Node-RED项目", + "readme": "### 关于\n\n这是您项目的README.md文件。它可以帮助用户了解您的项目,如何使用它以及他们可能需要知道的其他任何信息。" + } + } + }, + + "context": { + "log-store-init": "上下文储存: '__name__' [__info__]", + "error-loading-module": "加载上下文存储时出错: __message__", + "error-loading-module2": "加载上下文存储时出错 '__module__': __message__", + "error-module-not-defined": "上下文存储库'__storage__'缺少“模块”选项", + "error-invalid-module-name": "无效的上下文存储名称:'__ name__'", + "error-invalid-default-module": "无效的默认的上下文存储库: '__storage__'", + "unknown-store": "指定了未知的上下文存储库'__name__'。 使用默认存储库。", + "localfilesystem": { + "error-circular": "上下文__scope__包含无法保留的循环引用", + "error-write": "编写上下文时出错:__ message__" + } + } +} From 797da3bc8e616e42b52f00722b198189a0f01cb5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 7 Feb 2020 10:45:45 +0000 Subject: [PATCH 038/346] Fix duplicating array item in visual json editor --- .../@node-red/editor-client/src/js/ui/editors/json.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js index 3b83e3089..67b5850c4 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/json.js @@ -106,7 +106,7 @@ options.push({id:"red-ui-editor-type-json-menu-duplicate", icon:"fa fa-copy", label:RED._("jsonEditor.duplicate"),onselect:function(){ var newKey = item.key; if (item.parent.type === 'array') { - newKey = parent.children.length; + newKey = item.parent.children.length; } else { var m = /^(.*?)(-(\d+))?$/.exec(newKey); var usedKeys = {}; From 3db5f928eed13dca7ad749b2ecf38fdb0c997902 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 7 Feb 2020 13:59:08 +0000 Subject: [PATCH 039/346] Wrap long context values when displaying in sidebar Fixes #2400 --- .../@node-red/editor-client/src/sass/debug.scss | 1 + .../editor-client/src/sass/tab-context.scss | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss index d767aaed3..c92c43320 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/debug.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/debug.scss @@ -160,6 +160,7 @@ .red-ui-debug-msg-element { color: $debug-message-text-color; line-height: 1.3em; + overflow-wrap: break-word; } .red-ui-debug-msg-object-key { color: $debug-message-text-color-object-key; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss index 46b09de43..4be9761f7 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-context.scss @@ -27,9 +27,22 @@ display: none; } } + + .red-ui-info-table { + table-layout: fixed; + } + + table.red-ui-info-table tr:not(.blank) td:first-child { + width: 30%; + } + table.red-ui-info-table tr:not(.blank) td:last-child { + vertical-align: top; + } + } .red-ui-sidebar-context-property { + overflow-wrap: break-word; position: relative; .red-ui-debug-msg-tools { right: 0px; From 7d3263613376e40a5a76f3c2e2bb2f7df4c904ec Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 7 Feb 2020 14:26:30 +0000 Subject: [PATCH 040/346] Improve file store error when cache disabled and sync api used Closes #2406 --- .../@node-red/runtime/lib/nodes/context/localfilesystem.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js b/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js index 0f83573e8..cf1769700 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js @@ -245,7 +245,7 @@ LocalFileSystem.prototype.get = function(scope, key, callback) { return this.cache.get(scope,key,callback); } if(typeof callback !== "function"){ - throw new Error("Callback must be a function"); + throw new Error("File Store cache disabled - only asynchronous access supported"); } var storagePath = getStoragePath(this.storageBaseDir ,scope); loadFile(storagePath + ".json").then(function(data){ @@ -304,7 +304,7 @@ LocalFileSystem.prototype.set = function(scope, key, value, callback) { }, this.flushInterval); } } else if (callback && typeof callback !== 'function') { - throw new Error("Callback must be a function"); + throw new Error("File Store cache disabled - only asynchronous access supported"); } else { self.writePromise = self.writePromise.then(function() { return loadFile(storagePath + ".json") }).then(function(data){ var obj = data ? JSON.parse(data) : {} From a05589c5a66292c04fa4debdf6be427df90b4aca Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 7 Feb 2020 16:31:59 +0000 Subject: [PATCH 041/346] Filter palette using raw label not html formatted label Fixes #2409 --- .../@node-red/editor-client/src/js/ui/palette.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 9961cbf2b..967a61f51 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -92,8 +92,11 @@ RED.palette = (function() { var lineHeight = 20; var portHeight = 10; + el.attr("data-palette-label",label); + label = RED.utils.sanitize(label); + var words = label.split(/[ -]/); var displayLines = []; @@ -469,7 +472,7 @@ RED.palette = (function() { function filterChange(val) { var re = new RegExp(val.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),'i'); $("#red-ui-palette-container .red-ui-palette-node").each(function(i,el) { - var currentLabel = $(el).find(".red-ui-palette-label").text(); + var currentLabel = $(el).attr("data-palette-label"); var type = $(el).attr("data-palette-type"); if (val === "" || re.test(type) || re.test(currentLabel)) { $(this).show(); From 80d100f3f9cc41ed4489ee33424b7c6560d9c4f5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 7 Feb 2020 16:49:41 +0000 Subject: [PATCH 042/346] Move receive metric position to better reflect async changes Fixes #2444 --- packages/node_modules/@node-red/runtime/lib/nodes/Node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js index c62895f7f..a834c8c34 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js @@ -193,6 +193,7 @@ Node.prototype.emit = function(event, ...args) { */ Node.prototype._emitInput = function(arg) { var node = this; + this.metric("receive", arg); if (node._inputCallback) { // Just one callback registered. try { @@ -448,7 +449,6 @@ Node.prototype.receive = function(msg) { if (!msg._msgid) { msg._msgid = redUtil.generateId(); } - this.metric("receive",msg); this.emit("input",msg); }; From 389cbf4900c1d9b339c0323e7cb5628f7f8c9196 Mon Sep 17 00:00:00 2001 From: JIYE YU Date: Mon, 10 Feb 2020 11:31:37 +0900 Subject: [PATCH 043/346] complete traditional chinese translation --- .../editor-client/locales/zh-CN/editor.json | 5 +- .../editor-client/locales/zh-CN/jsonata.json | 4 + .../editor-client/locales/zh-TW/editor.json | 144 ++++-- .../editor-client/locales/zh-TW/jsonata.json | 52 +++ .../nodes/locales/zh-CN/messages.json | 10 +- .../nodes/locales/zh-TW/common/20-inject.html | 35 ++ .../nodes/locales/zh-TW/common/21-debug.html | 25 + .../locales/zh-TW/common/24-complete.html | 24 + .../nodes/locales/zh-TW/common/25-catch.html | 36 ++ .../nodes/locales/zh-TW/common/25-status.html | 33 ++ .../nodes/locales/zh-TW/common/60-link.html | 31 ++ .../locales/zh-TW/common/90-comment.html | 21 + .../locales/zh-TW/common/98-unknown.html | 24 + .../locales/zh-TW/function/10-function.html | 51 ++ .../locales/zh-TW/function/10-switch.html | 37 ++ .../locales/zh-TW/function/15-change.html | 33 ++ .../locales/zh-TW/function/16-range.html | 40 ++ .../locales/zh-TW/function/80-template.html | 46 ++ .../locales/zh-TW/function/89-delay.html | 32 ++ .../locales/zh-TW/function/89-trigger.html | 33 ++ .../nodes/locales/zh-TW/function/90-exec.html | 74 +++ .../nodes/locales/zh-TW/messages.json | 434 ++++++++++-------- .../nodes/locales/zh-TW/network/05-tls.html | 19 + .../locales/zh-TW/network/06-httpproxy.html | 22 + .../nodes/locales/zh-TW/network/10-mqtt.html | 70 +++ .../locales/zh-TW/network/21-httpin.html | 81 ++++ .../locales/zh-TW/network/21-httprequest.html | 78 ++++ .../locales/zh-TW/network/22-websocket.html | 35 ++ .../nodes/locales/zh-TW/network/31-tcpin.html | 35 ++ .../nodes/locales/zh-TW/network/32-udp.html | 28 ++ .../nodes/locales/zh-TW/parsers/70-CSV.html | 43 ++ .../nodes/locales/zh-TW/parsers/70-HTML.html | 33 ++ .../nodes/locales/zh-TW/parsers/70-JSON.html | 43 ++ .../nodes/locales/zh-TW/parsers/70-XML.html | 48 ++ .../nodes/locales/zh-TW/parsers/70-YAML.html | 34 ++ .../locales/zh-TW/sequence/17-split.html | 133 ++++++ .../nodes/locales/zh-TW/sequence/18-sort.html | 41 ++ .../locales/zh-TW/sequence/19-batch.html | 34 ++ .../nodes/locales/zh-TW/storage/10-file.html | 59 +++ .../nodes/locales/zh-TW/storage/23-watch.html | 25 + .../runtime/locales/zh-TW/runtime.json | 174 +++++++ 41 files changed, 2013 insertions(+), 246 deletions(-) create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html create mode 100644 packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html create mode 100644 packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json index 3695eb263..95d3fa5ee 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-CN/editor.json @@ -24,7 +24,7 @@ "buffer": "buffer", "object": "对象", "jsonString": "JSON字符串", - "undefined": "为定义", + "undefined": "未定义", "null": "空" } }, @@ -1008,6 +1008,7 @@ "en-US": "英文", "ja": "日语", "ko": "韩文", - "zh-CN": "简体中文" + "zh-CN": "简体中文", + "zh-TW": "繁体中文" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json index 56ede1b4e..f27ec1f51 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-CN/jsonata.json @@ -262,5 +262,9 @@ "$distinct": { "args": "array", "desc": "返回一个数组,其中重复的值已从`数组`中删除" + }, + "$type": { + "args": "value", + "desc": "以字符串形式返回`值`的类型。 如果该`值`未定义,则将返回`未定义`" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json b/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json index 492fce58c..897599bc7 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-TW/editor.json @@ -15,6 +15,17 @@ "next": "下一步", "clone": "複製專案", "cont": "Continue" + }, + "type": { + "string": "字符串", + "number": "數值", + "boolean": "布林", + "array": "數組", + "buffer": "buffer", + "object": "對象", + "jsonString": "JSON字符串", + "undefined": "未定義", + "null": "空" } }, "workspace": { @@ -29,8 +40,7 @@ "enabled": "有效", "disabled": "無效", "info": "詳細描述", - "selectNodes": "點擊節點用於選擇", - "tip": "詳細描述支援Markdown羽量級標記語言,並將出現在資訊標籤中。" + "selectNodes": "點擊節點用於選擇" }, "menu": { "label": { @@ -45,14 +55,14 @@ "ltr": "從左到右", "rtl": "從右到左", "auto": "上下文", - "language": "Language", - "browserDefault": "Browser default" + "language": "語言", + "browserDefault": "瀏覽器默認" }, "sidebar": { "show": "顯示側邊欄" }, "palette": { - "show": "Show palette" + "show": "顯示控制板" }, "settings": "設置", "userSettings": "使用者設置", @@ -81,10 +91,7 @@ "projects-new": "新專案", "projects-open": "開啟專案", "projects-settings": "專案設定", - "showNodeLabelDefault": "顯示新添加節點的標籤", - "clipboard": "剪貼簿", - "library": "庫", - "examples": "範例" + "showNodeLabelDefault": "顯示新添加節點的標籤" } }, "actions": { @@ -204,8 +211,7 @@ }, "copyMessagePath": "已複製路徑", "copyMessageValue": "已複製數值", - "copyMessageValue_truncated": "已複製捨棄的數值", - "selectNodes": "選擇上面的文本並複製到剪貼簿" + "copyMessageValue_truncated": "已複製捨棄的數值" }, "deploy": { "deploy": "部署", @@ -237,7 +243,7 @@ "undeployedChanges": "您有未部署的更改。\n\n離開此頁面將丟失這些更改。", "improperlyConfigured": "工作區包含一些未正確配置的節點:", "unknown": "工作區包含一些未知的節點類型:", - "confirm": "你確定要部署嗎?", + "confirm": "確定要部署嗎?", "doNotWarn": "不要再對此發出警告", "conflict": "伺服器正在運行較新的一組流程。", "backgroundUpdate": "伺服器上的流程已更新。", @@ -300,8 +306,7 @@ "errors": { "noNodesSelected": "無法創建子流程: 未選擇節點", "multipleInputsToSelection": "無法創建子流程: 多個輸入到了選擇" - }, - "format": "標記格式" + } }, "editor": { "configEdit": "編輯", @@ -316,17 +321,53 @@ "addNewType": "添加新的__type__節點", "nodeProperties": "節點屬性", "label": "Label", + "color": "顏色", "portLabels": "埠標籤", "labelInputs": "輸入", "labelOutputs": "輸出", "settingIcon": "Icon", + "default": "默認", "noDefaultLabel": "無", "defaultLabel": "使用默認標籤", - "searchIcons": "搜尋 icons", + "searchIcons": "搜尋圖標", "useDefault": "使用默認", "description": "描述", "show": "顯示", "hide": "隱藏", + "locale": "選擇界面語言", + "icon": "圖標", + "inputType": "輸入類型", + "inputs": { + "input": "輸入", + "select": "選擇", + "checkbox": "復選框", + "spinner": "微調器", + "none": "空", + "hidden": "隱藏屬性" + }, + "types": { + "str": "字符串", + "num": "數字", + "bool": "布爾", + "json": "JSON", + "bin": "buffer", + "env": "環境變量" + }, + "menu": { + "input": "輸入", + "select": "選擇", + "checkbox": "復選框", + "spinner": "微調器", + "hidden": "僅標簽" + }, + "select": { + "label": "標簽", + "value": "值" + }, + "spinner": { + "min": "最小值", + "max": "最大值" + }, "errors": { "scopeChange": "更改範圍將使其他流程中的節點無法使用", "invalidProperties": "無效的屬性:" @@ -356,8 +397,9 @@ "cutNode": "剪切所選節點", "pasteNode": "粘貼節點", "undoChange": "撤銷上次執行的更改", - "searchBox": "打開搜索框", - "managePalette": "管理面板" + "searchBox": "打開搜尋框", + "managePalette": "管理面板", + "actionList": "動作列表" }, "library": { "library": "庫", @@ -371,28 +413,27 @@ "savedNodes": "保存的節點", "savedType": "已保存__type__", "saveFailed": "保存失敗: __message__", + "newFolder": "新文件夾", "types": { "local": "本地", "examples": "例子" }, - "exportToLibrary": "將節點匯出到庫", - "filename": "檔案名", - "folder": "資料夾", - "filenamePlaceholder": "文件", - "fullFilenamePlaceholder": "a/b/文件", - "folderPlaceholder": "a/b", - "breadcrumb": "庫" + "exportToLibrary": "將節點匯出到庫" }, "palette": { "noInfo": "無可用資訊", "filter": "過濾節點", - "search": "搜索模組", + "search": "搜尋模組", "addCategory": "添加新的...", "label": { "subflows": "子流程", + "network": "網絡", + "common": "共通", "input": "輸入", "output": "輸出", "function": "功能", + "sequence": "序列", + "parser": "解析", "social": "社交", "storage": "存儲", "analysis": "分析", @@ -459,7 +500,7 @@ "sortRecent": "日期順序", "more": "增加__count__個", "errors": { - "catalogLoadFailed": "無法載入節點目錄。
查看瀏覽器控制台瞭解更多資訊", + "catalogLoadFailed": "無法載入節點目錄。
查看瀏覽器控制臺瞭解更多資訊", "installFailed": "無法安裝: __module__
__message__
查看日誌瞭解更多資訊", "removeFailed": "無法刪除: __module__
__message__
查看日誌瞭解更多資訊", "updateFailed": "無法更新: __module__
__message__
查看日誌瞭解更多資訊", @@ -529,8 +570,10 @@ "none": "無", "subflows": "子流程", "flows": "流程", - "filterUnused": "未使用", "filterAll": "所有", + "showAllConfigNodes": "顯示所有配置節點", + "filterUnused": "未使用", + "showAllUnusedConfigNodes": "顯示所有未使用的配置節點", "filtered": "__count__ 個隱藏" }, "context": { @@ -543,7 +586,9 @@ "flow": "流程", "global": "全局的", "deleteConfirm": "你確定要刪除這個項目嗎?", - "autoRefresh": "自動刷新" + "autoRefresh": "自動刷新", + "refrsh": "刷新", + "delete": "刪除" }, "palette": { "name": "節點管理", @@ -558,6 +603,7 @@ "noSummaryAvailable": "無可用摘要", "editDescription": "編輯專案描述", "editDependencies": "編輯項目依賴", + "noDescriptionAvailable": "沒有可用的描述", "editReadme": "Edit README.md", "showProjectSettings": "顯示項目設置", "projectSettings": { @@ -657,15 +703,15 @@ "moreCommits": "更多提交", "changeLocalBranch": "變更當地分支", "createBranchPlaceholder": "查找或創建分支", - "upstream": "上游的", - "localOverwrite": "您有可通过切换分支覆盖的本地更改。您必须先提交或撤销那些更改。", + "upstream": "上遊的", + "localOverwrite": "您有可通過切換分支覆蓋的本地更改。您必須先提交或撤銷那些更改。", "manageRemoteBranch": "管理遠程分支", "unableToAccess": "無法訪問遠程存儲庫", "retry": "重試", - "setUpstreamBranch": "設置為上游分支", + "setUpstreamBranch": "設置為上遊分支", "createRemoteBranchPlaceholder": "查找或創建遠程分支", - "trackedUpstreamBranch": "創建的分支將被設置為跟踪的上游分支。", - "selectUpstreamBranch": "分支將被創建。 在下面選擇以將其設置為被跟踪的上游分支。", + "trackedUpstreamBranch": "創建的分支將被設置為跟蹤的上遊分支。", + "selectUpstreamBranch": "分支將被創建。 在下面選擇以將其設置為被跟蹤的上遊分支。", "pushFailed": "Push失敗,因為遠程具有更多的最新提交。請先進行pull與merge,然後再嘗試push。", "push": "push", "pull": "pull", @@ -683,7 +729,7 @@ "minsAgo": "__count__分鐘前", "minsAgo_plural": "__count__分鐘前", "secondsAgo": "秒前", - "notTracking": "您的本地分支當前未跟踪遠程分支。", + "notTracking": "您的本地分支當前未跟蹤遠程分支。", "statusUnmergedChanged": "您的存儲庫中有未合併的更改。您需要解決衝突並提交結果。", "repositoryUpToDate": "您的存儲庫是最新的。", "commitsAhead": "您的倉庫領先遠程倉庫__count__次提交。您現在可以push這些提交。", @@ -748,10 +794,23 @@ }, "jsonEditor": { "title": "JSON編輯器", - "format": "格式化JSON" + "format": "格式化JSON", + "rawMode": "編輯 JSON", + "uiMode": "Visual編輯器", + "insertAbove": "在上方插入", + "insertBelow": "在下方插入", + "addItem": "添加項目", + "copyPath": "復制路徑到項目", + "expandItems": "展開項目", + "collapseItems": "收合項目", + "duplicate": "重復", + "error": { + "invalidJSON": "無效的JSON: " + } }, "markdownEditor": { "title": "Markdown 編輯器", + "expand": "展開", "format": "F使用markdown格式化", "heading1": "Heading 1", "heading2": "Heading 2", @@ -786,7 +845,7 @@ }, "git-config": { "setup": "設置您的版本控制客戶端", - "desc0": "Node-RED使用開源工具Git進行版本控制。 它跟踪對項目文件的更改,並允許您將其推送到遠程存儲庫。", + "desc0": "Node-RED使用開源工具Git進行版本控制。 它跟蹤對項目文件的更改,並允許您將其推送到遠程存儲庫。", "desc1": "提交一組更改時,Git會使用用戶名和電子郵件地址記錄誰進行了更改。 用戶名可以是您想要的任何名稱-不必是您的真實姓名。", "desc2": "您的Git客戶端已經配置了以下詳細信息。", "desc3": "您可以稍後在設置對話框的“ Git config”標籤下更改這些設置。", @@ -905,7 +964,7 @@ "confirm": "您確定要刪除此項目嗎?" }, "create-project-list": { - "search": "搜索您的項目", + "search": "搜尋您的項目", "current": "當前的" }, "require-clean": { @@ -938,8 +997,19 @@ }, "editor-tab": { "properties": "屬性", + "envProperties": "環境變量", "description": "描述", "appearance": "外觀", + "preview": "UI預覽", + "defaultValue": "默認值", "env": "環境變量" + }, + "languages": { + "de": "德語", + "en-US": "英語", + "ja": "日語", + "ko": "韓語", + "zh-CN": "簡體中文", + "zh-TW": "繁體中文" } } diff --git a/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json b/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json index ae62fe068..6d99ffc6a 100644 --- a/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json +++ b/packages/node_modules/@node-red/editor-client/locales/zh-TW/jsonata.json @@ -214,5 +214,57 @@ "$toMillis": { "args": "timestamp", "desc": "將ISO 8601格式的字串`timestamp`轉換為從UNIX時間 (1970年1月1日 UTC/GMT的午夜)開始到現在的毫秒數。如果該字串的格式不正確,則拋出錯誤。" + }, + "$env": { + "args": "arg", + "desc": "返回環境變量的值。\n\n這是Node-RED定義的函數。" + }, + "$eval": { + "args": "expr [, context]", + "desc": "使用當前上下文來作為評估依據,分析並評估字符串`expr`,其中包含文字JSON或JSONata表達式。" + }, + "$formatInteger": { + "args": "number, picture", + "desc": "將“數字”轉換為字符串,並將其格式化為“圖片”字符串指定的整數表示形式。圖片字符串參數定義了數字的格式,並具有與XPath F&O 3.1 規範中的fn:format-integer相同的語法。" + }, + "$parseInteger": { + "args": "string, picture", + "desc": "使用“圖片”字符串指定的格式將“字符串”參數的內容解析為整數(作為JSON數字)。圖片字符串參數與$formatInteger格式相同。." + }, + "$error": { + "args": "[str]", + "desc": "引發錯誤並顯示一條消息。 可選的`str`將替代$error()函數評估的默認消息。" + }, + "$assert": { + "args": "arg, str", + "desc": "如果`arg`為真,則該函數返回。 如果arg為假,則拋出帶有str的異常作為異常消息。" + }, + "$single": { + "args": "array, function", + "desc": "返回滿足參數function謂語的array參數中的唯一值 (比如:傳遞值時,函數返回布林值“true”)。如果匹配值的數量不唯一時,則拋出異常。\n\n應在以下簽名中提供函數:`function(value [,index [,array []]])`其中value是數組的每個輸入,index是該值的位置,整個數組作為第三個參數傳遞。" + }, + "$encodeUrl": { + "args": "str", + "desc": "通過用表示字符的UTF-8編碼的一個,兩個,三個或四個轉義序列替換某些字符的每個實例,對統一資源定位符(URL)組件進行編碼。\n\n示例:`$encodeUrlComponent(\"?x=test\")` => `\"%3Fx%3Dtest\"`" + }, + "$encodeUrlComponent": { + "args": "str", + "desc": "通過用表示字符的UTF-8編碼的一個,兩個,三個或四個轉義序列替換某些字符的每個實例,對統一資源定位符(URL)進行編碼。\n\n示例: `$encodeUrl(\"https://mozilla.org/?x=шеллы\")` => `\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\"`" + }, + "$decodeUrl": { + "args": "str", + "desc": "解碼以前由encodeUrlComponent創建的統一資源定位器(URL)組件。 \n\n示例: `$decodeUrlComponent(\"%3Fx%3Dtest\")` => `\"?x=test\"`" + }, + "$decodeUrlComponent": { + "args": "str", + "desc": "解碼先前由encodeUrl創建的統一資源定位符(URL)。 \n\n示例: `$decodeUrl(\"https://mozilla.org/?x=%D1%88%D0%B5%D0%BB%D0%BB%D1%8B\")` => `\"https://mozilla.org/?x=шеллы\"`" + }, + "$distinct": { + "args": "array", + "desc": "返回一個數組,其中重復的值已從`數組`中刪除" + }, + "$type": { + "args": "value", + "desc": "以字符串形式返回`值`的類型。 如果該`值`未定義,則將返回`未定義`" } } diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json index 46560d181..4fc5dc4d4 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json @@ -698,7 +698,7 @@ "output": "输出", "includerow": "包含列名行", "newline": "换行符", - "usestrings": "parse numerical values" + "usestrings": "解析数值" }, "placeholder": { "columns": "用逗号分割列名" @@ -898,7 +898,7 @@ } }, "sort" : { - "sort": "sort", + "sort": "排序", "target" : "排序属性", "seq" : "信息队列", "key" : "键值", @@ -907,9 +907,9 @@ "ascending" : "升序", "descending" : "降序", "as-number" : "作为数值", - "invalid-exp" : "sort节点中存在无效的JSONata表达式", - "too-many" : "sort节点中有太多待定信息", - "clear" : "清空sort节点中的待定信息" + "invalid-exp" : "排序节点中存在无效的JSONata表达式", + "too-many" : "排序节点中有太多待定信息", + "clear" : "清空排序节点中的待定信息" }, "batch" : { "batch": "batch", diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html new file mode 100644 index 000000000..046eddd7e --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/20-inject.html @@ -0,0 +1,35 @@ + + + + \ No newline at end of file diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html new file mode 100644 index 000000000..31f78e907 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/21-debug.html @@ -0,0 +1,25 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html new file mode 100644 index 000000000..862745310 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/24-complete.html @@ -0,0 +1,24 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html new file mode 100644 index 000000000..4e3db015d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-catch.html @@ -0,0 +1,36 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html new file mode 100644 index 000000000..d961c8e52 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/25-status.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html new file mode 100644 index 000000000..e7723c499 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/60-link.html @@ -0,0 +1,31 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html new file mode 100644 index 000000000..d044f28db --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/90-comment.html @@ -0,0 +1,21 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html new file mode 100644 index 000000000..c3588def1 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/common/98-unknown.html @@ -0,0 +1,24 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html new file mode 100644 index 000000000..9f8ddb43f --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-function.html @@ -0,0 +1,51 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html new file mode 100644 index 000000000..5a65eff93 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/10-switch.html @@ -0,0 +1,37 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html new file mode 100644 index 000000000..91a320945 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/15-change.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html new file mode 100644 index 000000000..62eb63d0b --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/16-range.html @@ -0,0 +1,40 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html new file mode 100644 index 000000000..874ae3801 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/80-template.html @@ -0,0 +1,46 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html new file mode 100644 index 000000000..28c291de8 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-delay.html @@ -0,0 +1,32 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html new file mode 100644 index 000000000..6bbe72f4d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/89-trigger.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html new file mode 100644 index 000000000..27be34e00 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/function/90-exec.html @@ -0,0 +1,74 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json index 153a88300..7ece3333d 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json @@ -6,7 +6,9 @@ "name": "名稱", "username": "使用者名稱", "password": "密碼", - "property": "屬性" + "property": "屬性", + "selectNodes": "選擇節點...", + "expand": "擴展" }, "status": { "connected": "已連接", @@ -35,7 +37,22 @@ "stopped": "停止", "failed": "注入失敗: __error__", "label": { - "repeat": "重複" + "repeat": "重複", + "flow": "流上下午", + "global": "全局上下文", + "str": "字符串", + "num": "數值", + "bool": "布爾值", + "json": "JSON對象", + "bin": "buffer", + "date": "時間戳", + "env": "環境變量", + "object": "對象", + "string": "字符串", + "boolean": "布爾值", + "number": "數值", + "Array": "數組", + "invalid": "無效的JSON對象" }, "timestamp": "時間戳記", "none": "無", @@ -72,13 +89,11 @@ "catch": { "catch": "監測所有節點", "catchNodes": "監測__number__個節點", + "catchUncaught": "捕獲:未捕獲", "label": { "source": "監測範圍", - "node": "節點", - "type": "類型", "selectAll": "全選", - "sortByLabel": "按名稱排序", - "sortByType": "按類型排序" + "uncaught": "忽略其他捕獲節點處理的錯誤" }, "scope": { "all": "所有節點", @@ -90,10 +105,6 @@ "statusNodes": "報告__number__個節點狀態", "label": { "source": "報告狀態範圍", - "node": "節點", - "type": "類型", - "selectAll": "全選", - "sortByLabel": "按名稱排序", "sortByType": "按類型排序" }, "scope": { @@ -101,8 +112,13 @@ "selected": "指定節點" } }, + "complete": { + "completeNodes": "完成: __number__個節點" + }, "debug": { "output": "輸出", + "none": "None", + "invalid-exp": "無效的JSONata表達式: __error__", "msgprop": "資訊屬性", "msgobj": "完整資訊", "to": "目標", @@ -124,7 +140,11 @@ "filterCurrent": "當前流程", "debugNodes": "除錯節點", "clearLog": "清空日誌", - "openWindow": "在新視窗打開" + "filterLog": "過濾日誌", + "openWindow": "在新視窗打開", + "copyPath": "復制路徑", + "copyPayload": "復制值", + "pinPath": "固定展開" }, "messageMenu": { "collapseAll": "折疊所有路徑", @@ -146,26 +166,33 @@ "key": "私密金鑰", "passphrase": "密碼", "ca": "CA證書", - "verify-server-cert":"驗證伺服器憑證" + "verify-server-cert": "驗證伺服器憑證", + "servername": "服務器名" }, "placeholder": { - "cert":"憑證路徑 (PEM 格式)", - "key":"私密金鑰路徑 (PEM 格式)", - "ca":"CA憑證路徑 (PEM 格式)", - "passphrase":"私密金鑰密碼 (可選)" + "cert": "憑證路徑 (PEM 格式)", + "key": "私密金鑰路徑 (PEM 格式)", + "ca": "CA憑證路徑 (PEM 格式)", + "passphrase": "私密金鑰密碼 (可選)", + "servername": "用於SNI" }, "error": { "missing-file": "未提供證書/金鑰檔案" } }, "exec": { + "exec": "exec", + "spawn": "spawn", "label": { "command": "命令", "append": "追加", "timeout": "超時", "timeoutplace": "可選填", "return": "輸出", - "seconds": "秒" + "seconds": "秒", + "stdout": "標準輸出", + "stderr": "標準錯誤輸出", + "retcode": "返回碼" }, "placeholder": { "extraparams": "額外的輸入參數" @@ -177,16 +204,18 @@ "oldrc": "使用舊式輸出 (相容模式)" }, "function": { + "function": "函數", "label": { "function": "函數", "outputs": "輸出" }, "error": { - "inputListener":"無法在函數中監聽對'注入'事件", - "non-message-returned":"函數節點嘗試返回類型為 __type__ 的資訊" + "inputListener": "無法在函數中監聽對'注入'事件", + "non-message-returned": "函數節點嘗試返回類型為 __type__ 的資訊" } }, "template": { + "template": "模板", "label": { "template": "模版", "property": "屬性", @@ -233,21 +262,21 @@ "limit": "限制", "limitTopic": "限制主題", "random": "隨機", - "units" : { + "units": { "second": { - "plural" : "秒", + "plural": "秒", "singular": "秒" }, "minute": { - "plural" : "分鐘", + "plural": "分鐘", "singular": "分鐘" }, "hour": { - "plural" : "小時", + "plural": "小時", "singular": "小時" }, "day": { - "plural" : "天", + "plural": "天", "singular": "天" } } @@ -272,6 +301,9 @@ "wait-reset": "等待被重置", "wait-for": "等待", "wait-loop": "週期性重發", + "for": "處理", + "bytopics": "每個msg.topic", + "alltopics": "所有消息", "duration": { "ms": "毫秒", "s": "秒", @@ -284,12 +316,13 @@ "trigger-block": "觸發並阻止", "trigger-loop": "週期性重發", "reset": "重置觸發節點條件 如果:", - "resetMessage":"msg.reset已設置", - "resetPayload":"msg.payload等於", + "resetMessage": "msg.reset已設置", + "resetPayload": "msg.payload等於", "resetprompt": "可選填" } }, "comment": { + "comment": "注釋" }, "unknown": { "label": { @@ -303,26 +336,32 @@ "example": "e.g. localhost", "output": "輸出", "qos": "QoS", + "retain": "保持", "clientid": "使用者端ID", "port": "埠", "keepalive": "Keepalive計時(秒)", "cleansession": "使用新的會話", "use-tls": "使用安全連接 (SSL/TLS)", - "tls-config":"TLS 設置", - "verify-server-cert":"驗證伺服器憑證", + "tls-config": "TLS 設置", + "verify-server-cert": "驗證伺服器憑證", "compatmode": "使用舊式MQTT 3.1支援" }, + "sections-label": { + "birth-message": "連接時發送的消息(出生消息)", + "will-message": "意外斷開連接時的發送消息(Will消息)", + "close-message": "斷開連接前發送的消息(關閉消息)" + }, "tabs-label": { "connection": "連接", "security": "安全", - "will": "Will信息", - "birth": "Birth信息" + "messages": "消息" }, "placeholder": { "clientid": "留白則自動隨機生成", - "clientid-nonclean":"如非新會話,必須設置使用者端ID", + "clientid-nonclean": "如非新會話,必須設置使用者端ID", "will-topic": "留白將禁止Will資訊", - "birth-topic": "留白將禁止Birth資訊" + "birth-topic": "留白將禁止Birth資訊", + "close-topic": "留白以禁用關閉消息" }, "state": { "connected": "已連接到服務端: __broker__", @@ -333,7 +372,9 @@ "output": { "buffer": "Buffer", "string": "字串", - "base64": "Base64編碼字串" + "base64": "Base64編碼字串", + "auto": "自動檢測 (字符串或buffer)", + "json": "解析的JSON對象" }, "true": "是", "false": "否", @@ -342,7 +383,9 @@ "not-defined": "主題未設置", "missing-config": "未設置服務端", "invalid-topic": "主題無效", - "nonclean-missingclientid": "使用者端ID未設定,使用新會話" + "nonclean-missingclientid": "使用者端ID未設定,使用新會話", + "invalid-json-string": "無效的JSON字符串", + "invalid-json-parse": "無法解析JSON字符串" } }, "httpin": { @@ -354,12 +397,26 @@ "upload": "接受檔案上傳?", "status": "狀態碼", "headers": "Header", - "other": "其他" + "other": "其他", + "paytoqs": "將msg.payload附加為查詢字符串參數", + "utf8String": "UTF8格式的字符串", + "binaryBuffer": "二進制buffer", + "jsonObject": "解析的JSON對象", + "authType": "類型", + "bearerToken": "Token" }, "setby": "- 用 msg.method 設定 -", "basicauth": "基本認證", "use-tls": "使用安全連接 (SSL/TLS) ", - "tls-config":"TLS 設置", + "tls-config": "TLS 設置", + "basic": "基本認證", + "digest": "摘要認證", + "bearer": "bearer認證", + "use-proxy": "使用代理服務器", + "persist": "對連接啟用keep-alive", + "proxy-config": "代理服務器設置", + "use-proxyauth": "使用代理身份驗證", + "noproxy-hosts": "代理例外", "utf8": "UTF-8 字串", "binary": "二進位資料", "json": "JSON對象", @@ -375,8 +432,11 @@ "no-response": "無響應物件", "json-error": "JSON 解析錯誤", "no-url": "未設定 URL", - "deprecated-call":"__method__方法已棄用", - "invalid-transport":"非HTTP傳輸請求" + "deprecated-call": "__method__方法已棄用", + "invalid-transport": "非HTTP傳輸請求", + "timeout-isnan": "超時值不是有效數字,忽略", + "timeout-isnegative": "超時值為負,忽略", + "invalid-payload": "無效的有效載荷" }, "status": { "requesting": "請求中" @@ -399,13 +459,19 @@ "url1": "URL 應該使用ws://或者wss://方案並指向現有的websocket監聽器.", "url2": "預設情況下,payload 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件." }, + "status": { + "connected": "連接數 __count__", + "connected_plural": "連接數 __count__" + }, "errors": { "connect-error": "ws連接發生了錯誤: ", "send-error": "發送時發生了錯誤: ", - "missing-conf": "未設置伺服器" + "missing-conf": "未設置伺服器", + "duplicate-path": "同一路徑上不能有兩個WebSocket偵聽器: __path__" } }, "watch": { + "watch": "watch", "label": { "files": "文件", "recursive": "遞迴所有子資料夾" @@ -458,14 +524,12 @@ "connection-closed": "連接已關閉 __host__:__port__", "connections": "__count__ 個連接", "connections_plural": "__count__ 個連接" - }, "errors": { "connection-lost": "連接中斷 __host__:__port__", "timeout": "超時關閉通訊端連接,埠 __port__", "cannot-listen": "無法監聽埠 __port__, 錯誤: __error__", "error": "錯誤: __error__", - "socket-error": "通訊端連接錯誤來自 __host__:__port__", "no-host": "主機位址或埠未設定", "connect-timeout": "連接逾時", @@ -480,14 +544,15 @@ "output": "輸出", "group": "組", "interface": "本地IP", - "interfaceprompt": "(可選)本地 IP 綁定到", "send": "發送一個", "toport": "到埠", "address": "地址", - "decode-base64": "是否解碼Base64編碼的資訊?" + "decode-base64": "是否解碼Base64編碼的資訊?", + "interfaceprompt": "(可選)本地 IP 綁定到" }, "placeholder": { "interface": "(可選)eth0的IP地址", + "interfaceprompt": "(可選) 要綁定的本地接口或地址", "address": "目標IP位址" }, "udpmsgs": "udp信息", @@ -529,36 +594,43 @@ "ip-notset": "udp: IP地址未設定", "port-notset": "udp: 埠未設定", "port-invalid": "udp: 無效埠號碼", - "alreadyused": "udp: 埠已被佔用" + "alreadyused": "udp: 埠已被佔用", + "ifnotfound": "udp: 接口 __iface__ 未發現" } }, "switch": { + "switch": "switch", "label": { "property": "屬性", "rule": "規則", - "repair" : "重建資訊佇列" + "repair": "重建資訊佇列" }, + "previous": "先前值", "and": "與", "checkall": "全選所有規則", "stopfirst": "接受第一條匹配資訊後停止", "ignorecase": "忽略大小寫", "rules": { - "btwn":"在之間", - "cont":"包含", - "regex":"匹配規則運算式", - "true":"為真", - "false":"為假", - "null":"為空", - "nnull":"非空", - "head":"head", - "tail":"tail", - "index":"index between", - "exp":"JSONata運算式", - "else":"除此以外" + "btwn": "在之間", + "cont": "包含", + "regex": "匹配規則運算式", + "true": "為真", + "false": "為假", + "null": "為空", + "nnull": "非空", + "istype": "類型是", + "empty": "為空", + "nempty": "非空", + "head": "head", + "tail": "tail", + "index": "index between", + "exp": "JSONata運算式", + "else": "除此以外", + "hask": "擁有鍵" }, "errors": { "invalid-expr": "無效的JSONata運算式: __error__", - "too-many" : "Switch節點中有太多待定信息" + "too-many": "Switch節點中有太多待定信息" } }, "change": { @@ -588,6 +660,7 @@ } }, "range": { + "range": "range", "label": { "action": "操作", "inputrange": "映射輸入資料", @@ -623,7 +696,8 @@ "firstrow": "第一行包含列名", "output": "輸出", "includerow": "包含列名行", - "newline": "分行符號" + "newline": "分行符號", + "usestrings": "解析數值" }, "placeholder": { "columns": "用逗號分割列名" @@ -654,7 +728,8 @@ "html": { "label": { "select": "選取項", - "output": "輸出" + "output": "輸出", + "in": "in" }, "output": { "html": "選定元素的html內容", @@ -670,7 +745,9 @@ "errors": { "dropped-object": "忽略非物件格式的有效負載", "dropped": "忽略不支援格式的有效負載類型", - "dropped-error": "轉換有效負載失敗" + "dropped-error": "轉換有效負載失敗", + "schema-error": "JSON架構錯誤", + "schema-error-compile": "JSON架構錯誤: 未能編譯架構" }, "label": { "o2j": "對象至JSON", @@ -679,8 +756,8 @@ "property": "屬性", "actions": { "toggle": "JSON字串與物件互轉", - "str":"總是轉為JSON字串", - "obj":"總是轉為JS對象" + "str": "總是轉為JSON字串", + "obj": "總是轉為JS對象" } } }, @@ -702,76 +779,6 @@ "xml_js": "此節點僅處理XML字串或JS物件." } }, - "rpi-gpio": { - "label": { - "gpiopin": "GPIO", - "selectpin": "選擇引腳", - "resistor": "電阻?", - "readinitial": "在部署/重啟時讀取引腳的初始狀態?", - "type": "類型", - "initpin": "初始化引腳狀態?", - "debounce": "去抖動", - "freq": "頻率", - "button": "按鈕", - "pimouse": "Pi滑鼠", - "pikeyboard": "Pi鍵盤", - "left": "左", - "right": "右", - "middle": "中" - }, - "resistor": { - "none": "無", - "pullup": "上拉電阻", - "pulldown": "下拉電阻" - }, - "digout": "數位輸出", - "pwmout": "PWM輸出", - "servo": "伺服輸出", - "initpin0": "初始引腳電平 - 低(0)", - "initpin1": "初始引腳電平 - 高(1)", - "left": "左", - "right": "右", - "middle": "中", - "any": "任何", - "pinname": "引腳", - "alreadyuse": "已被使用", - "alreadyset": "已被設為", - "tip": { - "pin": "正在使用引腳: ", - "in": "提示: 僅接受數位輸入 - 輸出必須為0或1.", - "dig": "提示: 如用數位輸出 - 輸入必須為0或1.", - "pwm": "提示: 如用PWM輸出 - 輸入必須為0至100之間; 如用高頻率可能會比預期佔用更多CPU資源.", - "ser": "提示: 如用伺服輸出 - 輸入必須為0至100之間. 50為中間值." - }, - "types": { - "digout": "數位輸出", - "input": "輸入", - "pullup": "含有上拉電阻的輸入", - "pulldown": "含有下拉電阻的輸入", - "pwmout": "PWM輸出", - "servo": "伺服輸出" - }, - "status": { - "stopped": "已停止", - "closed": "已關閉", - "not-running": "不運行" - }, - "errors": { - "ignorenode": "忽略樹莓派的特定節點", - "version": "版本命令失敗", - "sawpitype": "查看Pi類型", - "libnotfound": "找不到樹莓派RPi.GPIO的python庫", - "alreadyset": "GPIO引腳 __pin__ 已經被設定為類型: __type__", - "invalidpin": "無效GPIO引腳", - "invalidinput": "無效輸入", - "needtobeexecutable": "__command__須為可運行命令", - "mustbeexecutable": "nrgpio須為可運行", - "commandnotfound": "nrgpio命令不存在", - "commandnotexecutable": "nrgpio命令不可運行", - "error": "錯誤: __error__", - "pythoncommandnotfound": "nrpgio python命令未處於運行狀態" - } - }, "file": { "label": { "filename": "檔案名", @@ -783,7 +790,10 @@ "breaklines": "分拆成行", "filelabel": "文件", "sendError": "發生錯誤時發送消息(傳統模式)", - "deletelabel": "刪除 __file__" + "deletelabel": "刪除 __file__", + "encoding": "編碼", + "utf8String": "UTF8字符串", + "binaryBuffer": "二進制buffer" }, "action": { "append": "追加至文件", @@ -801,6 +811,21 @@ "deletedfile": "刪除檔: __file__", "appendedfile": "追加至文件: __file__" }, + "encoding": { + "none": "默認", + "native": "Native", + "unicode": "Unicode", + "japanese": "日本", + "chinese": "中國", + "korean": "韓國", + "taiwan": "臺灣/香港", + "windows": "Windows代碼頁", + "iso": "ISO代碼頁", + "ibm": "IBM代碼頁", + "mac": "Mac代碼頁", + "koi8": "KOI8代碼頁", + "misc": "其它" + }, "errors": { "nofilename": "未指定檔案名", "invaliddelete": "警告:無效刪除。請在配置對話方塊中使用特定的刪除選項", @@ -812,50 +837,53 @@ "tip": "提示: 檔案名應該是絕對路徑,否則它將相對於Node-RED進程的工作目錄。" }, "split": { - "intro":"基於以下類型拆分msg.payload:", - "object":"對象", - "objectSend":"每個鍵值對作為單個消息發送", - "strBuff":"字串 / Buffer", - "array":"陣列", - "splitUsing":"拆分使用", - "splitLength":"固定長度", - "stream":"作為消息流處理", - "addname":" 複製鍵到 " + "split": "split", + "intro": "基於以下類型拆分msg.payload:", + "object": "對象", + "objectSend": "每個鍵值對作為單個消息發送", + "strBuff": "字串 / Buffer", + "array": "陣列", + "splitUsing": "拆分使用", + "splitLength": "固定長度", + "stream": "作為消息流處理", + "addname": " 複製鍵到 " }, - "join":{ - "mode":{ - "mode":"模式", - "auto":"自動", - "merge":"合併序列", - "reduce":"縮減序列", - "custom":"手動" + "join": { + "join": "join", + "mode": { + "mode": "模式", + "auto": "自動", + "merge": "合併序列", + "reduce": "縮減序列", + "custom": "手動" }, - "combine":"合併每個", - "create":"輸出為", - "type":{ - "string":"字串", - "array":"陣列", - "buffer":"Buffer", - "object":"鍵值對對象", - "merged":"合併對象" + "combine": "合併每個", + "completeMessage": "完整的消息", + "create": "輸出為", + "type": { + "string": "字串", + "array": "陣列", + "buffer": "Buffer", + "object": "鍵值對對象", + "merged": "合併對象" }, - "using":"使用此值", - "key":"作為鍵", - "joinedUsing":"合併符號", - "send":"發送資訊:", - "afterCount":"達到一定數量的資訊時", - "count":"數量", - "subsequent":"和每個後續的消息", - "afterTimeout":"第一條消息的若干時間後", - "seconds":"秒", - "complete":"在收到存在msg.complete的消息後", - "tip":"此模式假定此節點與split相連, 或者接收到的消息有正確配置的msg.parts屬性.", - "too-many" : "join節點中有太多待定信息", + "using": "使用此值", + "key": "作為鍵", + "joinedUsing": "合併符號", + "send": "發送資訊:", + "afterCount": "達到一定數量的資訊時", + "count": "數量", + "subsequent": "和每個後續的消息", + "afterTimeout": "第一條消息的若幹時間後", + "seconds": "秒", + "complete": "在收到存在msg.complete的消息後", + "tip": "此模式假定此節點與split相連, 或者接收到的消息有正確配置的msg.parts屬性.", + "too-many": "join節點中有太多待定信息", "merge": { - "topics-label":"合併主題", - "topics":"主題", - "topic" : "主題", - "on-change":"當收到一個新主題時發送已合併資訊" + "topics-label": "合併主題", + "topics": "主題", + "topic": "主題", + "on-change": "當收到一個新主題時發送已合併資訊" }, "reduce": { "exp": "Reduce運算式", @@ -868,43 +896,45 @@ "invalid-expr": "無效的JSONata運算式: __error__" } }, - "sort" : { - "target" : "排序屬性", - "seq" : "資訊佇列", - "key" : "鍵值", - "elem" : "元素值", - "order" : "順序", - "ascending" : "昇冪", - "descending" : "降冪", - "as-number" : "作為數值", - "invalid-exp" : "sort節點中存在無效的JSONata運算式", - "too-many" : "sort節點中有太多待定信息", - "clear" : "清空sort節點中的待定資訊" + "sort": { + "sort": "排序", + "target": "排序屬性", + "seq": "資訊佇列", + "key": "鍵值", + "elem": "元素值", + "order": "順序", + "ascending": "昇冪", + "descending": "降冪", + "as-number": "作為數值", + "invalid-exp": "排序節點中存在無效的JSONata運算式", + "too-many": "排序節點中有太多待定信息", + "clear": "清空排序節點中的待定資訊" }, - "batch" : { + "batch": { + "batch": "batch", "mode": { - "label" : "模式", - "num-msgs" : "按指定數量分組", - "interval" : "按時間間隔分組", - "concat" : "按主題分組" + "label": "模式", + "num-msgs": "按指定數量分組", + "interval": "按時間間隔分組", + "concat": "按主題分組" }, "count": { - "label" : "分組數量", - "overlap" : "隊末隊首重疊數量", - "count" : "數量", - "invalid" : "無效的分組數量或重疊數量" + "label": "分組數量", + "overlap": "隊末隊首重疊數量", + "count": "數量", + "invalid": "無效的分組數量或重疊數量" }, "interval": { - "label" : "時間間隔", - "seconds" : "秒", - "empty" : "無數據到達時發送空資訊" + "label": "時間間隔", + "seconds": "秒", + "empty": "無數據到達時發送空資訊" }, "concat": { "topics-label": "主題", - "topic" : "主題" + "topic": "主題" }, - "too-many" : "batch節點中有太多待定信息", - "unexpected" : "未知模式", - "no-parts" : "資訊中沒有parts屬性" + "too-many": "batch節點中有太多待定信息", + "unexpected": "未知模式", + "no-parts": "資訊中沒有parts屬性" } } diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html new file mode 100644 index 000000000..ee4b3bd95 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/05-tls.html @@ -0,0 +1,19 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html new file mode 100644 index 000000000..23f899627 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/06-httpproxy.html @@ -0,0 +1,22 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html new file mode 100644 index 000000000..825bf218e --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/10-mqtt.html @@ -0,0 +1,70 @@ + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html new file mode 100644 index 000000000..0d44ce3b1 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httpin.html @@ -0,0 +1,81 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html new file mode 100644 index 000000000..71ed96087 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/21-httprequest.html @@ -0,0 +1,78 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html new file mode 100644 index 000000000..4bb2c7f4d --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/22-websocket.html @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html new file mode 100644 index 000000000..2898ca718 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/31-tcpin.html @@ -0,0 +1,35 @@ + + + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html new file mode 100644 index 000000000..401af48e3 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/network/32-udp.html @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html new file mode 100644 index 000000000..9a8638614 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-CSV.html @@ -0,0 +1,43 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html new file mode 100644 index 000000000..b1559455f --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-HTML.html @@ -0,0 +1,33 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html new file mode 100644 index 000000000..1a46c3690 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-JSON.html @@ -0,0 +1,43 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html new file mode 100644 index 000000000..4f0491291 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-XML.html @@ -0,0 +1,48 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html new file mode 100644 index 000000000..8ea4f87f8 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/parsers/70-YAML.html @@ -0,0 +1,34 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html new file mode 100644 index 000000000..fd97b497a --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/17-split.html @@ -0,0 +1,133 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html new file mode 100644 index 000000000..cb8e7fa21 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/18-sort.html @@ -0,0 +1,41 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html new file mode 100644 index 000000000..79879076e --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/sequence/19-batch.html @@ -0,0 +1,34 @@ + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html new file mode 100644 index 000000000..03705ea5c --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html @@ -0,0 +1,59 @@ + + + + + diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html new file mode 100644 index 000000000..d8e1b5807 --- /dev/null +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/23-watch.html @@ -0,0 +1,25 @@ + + + diff --git a/packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json b/packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json new file mode 100644 index 000000000..b96d87a6e --- /dev/null +++ b/packages/node_modules/@node-red/runtime/locales/zh-TW/runtime.json @@ -0,0 +1,174 @@ +{ + "runtime": { + "welcome": "歡迎使用Node-RED", + "version": "__component__ 版本: __version__", + "unsupported_version": "__component__的不受支持的版本。要求: __requires__ 找到: __version__", + "paths": { + "settings": "設置文件 : __path__", + "httpStatic": "HTTP Static : __path__" + } + }, + + "server": { + "loading": "加載控制板節點", + "palette-editor": { + "disabled": "控制板編輯器已禁用:用戶設置", + "npm-not-found": "控制板編輯器已禁用:找不到npm命令", + "npm-too-old": "控制板編輯器已禁用: npm版本太舊。需要版本npm >= 3.x" + }, + "errors": "無法註冊__count__節點類型", + "errors_plural": "無法註冊__count__個節點類型", + "errors-help": "使用-v運行以獲取詳細信息", + "missing-modules": "缺少節點模組:", + "node-version-mismatch": "無法在此版本上加載節點模組。要求:__ version__", + "type-already-registered": "'__type__'已由模組__module__註冊", + "removing-modules": "從配置中刪除模組", + "added-types": "添加的節點類型:", + "removed-types": "刪除的節點類型:", + "install": { + "invalid": "無效的模組名稱", + "installing": "安裝模組:__ name__,版本:__ version__", + "installed": "已安裝的模組:__ name__", + "install-failed": "安裝失敗", + "install-failed-long": "模組__name__安裝失敗:", + "install-failed-not-found": "$t(server.install.install-failed-long) 模組未發現", + "upgrading": "更新模組: __name__ 至版本: __version__", + "upgraded": "更新模組: __name__。 重新啟動Node-RED以使用新版本", + "upgrade-failed-not-found": "$t(server.install.install-failed-long) 未發現版本", + "uninstalling": "卸載模組: __name__", + "uninstall-failed": "卸載失敗", + "uninstall-failed-long": "卸載模組__name__失敗:", + "uninstalled": "卸載模組: __name__" + }, + "unable-to-listen": "無法在__listenpath__上收聽", + "port-in-use": "錯誤: 端口正在使用中", + "uncaught-exception": "未捕獲的異常:", + "admin-ui-disabled": "管理員界面已禁用", + "now-running": "服務器現在在__listenpath__上運行", + "failed-to-start": "無法啟動服務器:", + "headless-mode": "在headless模式下運行", + "httpadminauth-deprecated": "不建議使用httpAdminAuth。請改用adminAuth" + }, + + "api": { + "flows": { + "error-save": "保存流程錯誤: __message__", + "error-reload": "重載流程錯誤: __message__" + }, + "library": { + "error-load-entry": "加載庫條目'__path__'時出錯:__message__", + "error-save-entry": "保存庫條目'__path__'時出錯:__ message__", + "error-load-flow": "加載流程'__path__'時出錯:__ message__", + "error-save-flow": "保存流'__path__'時出錯:__ message__" + }, + "nodes": { + "enabled": "啟用的節點類型:", + "disabled": "禁用的節點類型:", + "error-enable": "無法啟用節點:" + } + }, + + "comms": { + "error": "通訊渠道錯誤:__ message__", + "error-server": "通信服務器錯誤:__ message__", + "error-send": "通訊發送錯誤:__ message__" + }, + + "settings": { + "user-not-available": "無法保存用戶設置:__ message__", + "not-available": "設置不可用", + "property-read-only": "屬性“ __prop__”是只讀的" + }, + + "nodes": { + "credentials": { + "error":"加載證書時出錯:__ message__", + "error-saving":"保存證書時出錯:__ message__", + "not-registered": "證書類型'__type__'未註冊", + "system-key-warning": "\n\n---------------------------------------------------------------------\n您的流程證書文件是使用系統生成的密鑰加密的。\n\n如果系統生成的密鑰由於任何原因丟失,則您的證書文件將無法恢復,您將必須刪除它並重新輸入您的證書。\n\n您應該使用您的設置文件中的'credentialSecret'選項設置自己的密鑰。然後,下次部署更改時,Node-RED將使用選擇的密鑰重新加密您的證書文件。\n---------------------------------------------------------------------\n" + }, + "flows": { + "safe-mode": "流程在安全模式下停止。部署開始。", + "registered-missing": "缺少註冊的類型:__ type__", + "error": "錯誤加載流程:__ message__", + "starting-modified-nodes": "啟動修改的節點", + "starting-modified-flows": "啟動修改的流程", + "starting-flows": "啟動流程", + "started-modified-nodes": "修改的節點已啟動", + "started-modified-flows": "修改的流程已啟動", + "started-flows": "流程已啟動", + "stopping-modified-nodes": "停止修改的節點", + "stopping-modified-flows": "停止修改的流程", + "stopping-flows": "停止流程", + "stopped-modified-nodes": "修改的節點已停止", + "stopped-modified-flows": "修改的流程已停止", + "stopped-flows": "流程已停止", + "stopped": "已停止", + "stopping-error": "錯誤停止節點:__ message__", + "added-flow": "流程已添加: __label__", + "updated-flow": "流程已更新: __label__", + "removed-flow": "流程已移除: __label__", + "missing-types": "等待缺少的類型被註冊:", + "missing-type-provided": " - __type__ (由npm模組__module__提供)", + "missing-type-install-1": "要安裝所有缺少的模組,請運行:", + "missing-type-install-2": "在目錄中:" + }, + "flow": { + "unknown-type": "未知類型: __type__", + "missing-types": "缺少類型", + "error-loop": "郵件已超過最大捕獲數" + }, + "index": { + "unrecognised-id": "無法識別的ID: __id__", + "type-in-use": "使用中的類型: __msg__", + "unrecognised-module": "無法識別的模組: __module__" + }, + "registry": { + "localfilesystem": { + "module-not-found": "找不到模組:'__module__'" + } + } + }, + + "storage": { + "index": { + "forbidden-flow-name": "禁止流程名稱" + }, + "localfilesystem": { + "user-dir": "用戶目錄: __path__", + "flows-file": "流程文件: __path__", + "create": "創建新__type__文件", + "empty": "現有__type__文件為空", + "invalid": "現有__type__文件為無效json", + "restore": "恢復__type__文件備份:__path__", + "restore-fail": "恢復__type__文件備份失敗:__ message__", + "fsync-fail": "將文件__path__刷新到磁盤失敗:__message__", + "projects": { + "changing-project": "設置活動項目:__ project__", + "active-project": "活動項目:__ project__", + "project-not-found": "找不到項目:__ project__", + "no-active-project": "沒有活動的項目:使用默認流文件", + "disabled": "項目已禁用:editorTheme.projects.enabled = false", + "disabledNoFlag": "項目已禁用:設置editorTheme.projects.enabled = true來啟用", + "git-not-found": "項目已禁用:找不到git命令", + "git-version-old": "項目已禁用:不支持的git __version__。 需要的git版本為2.x", + "summary": "一個Node-RED項目", + "readme": "### 關於\n\n這是您項目的README.md文件。它可以幫助用戶了解您的項目,如何使用它以及他們可能需要知道的其他任何信息。" + } + } + }, + + "context": { + "log-store-init": "上下文儲存: '__name__' [__info__]", + "error-loading-module": "加載上下文存儲時出錯: __message__", + "error-loading-module2": "加載上下文存儲時出錯 '__module__': __message__", + "error-module-not-defined": "上下文存儲庫'__storage__'缺少“模組”選項", + "error-invalid-module-name": "無效的上下文存儲名稱:'__ name__'", + "error-invalid-default-module": "無效的默認的上下文存儲庫: '__storage__'", + "unknown-store": "指定了未知的上下文存儲庫'__name__'。 使用默認存儲庫。", + "localfilesystem": { + "error-circular": "上下文__scope__包含無法保留的循環引用", + "error-write": "編寫上下文時出錯:__ message__" + } + } +} From d08e77cf36361f4c3747dd2ffc79bebb5a6a69da Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Nov 2019 14:13:36 +0000 Subject: [PATCH 044/346] Add credential type to TypedInput --- .../editor-client/locales/en-US/editor.json | 3 +- .../src/js/ui/common/typedInput.js | 82 ++++++++++++++++++- .../src/sass/ui/common/typedInput.scss | 7 +- 3 files changed, 87 insertions(+), 5 deletions(-) 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 a5f232aa6..7977a5752 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -757,7 +757,8 @@ "bin": "buffer", "date": "timestamp", "jsonata": "expression", - "env": "env variable" + "env": "env variable", + "cred": "credential" } }, "editableList": { 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 cb4de12d2..8ca601ec3 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 @@ -164,6 +164,77 @@ } }) } + }, + cred:{ + value:"cred", + label:"credential", + icon:"fa fa-lock", + inputType: "password", + valueLabel: function(container,value) { + var that = this; + container.css("pointer-events","none"); + var buttons = $('
').css({ + position: "absolute", + right:"6px", + top: "6px", + "pointer-events":"all" + }).appendTo(container); + var eyeButton = $('').css({ + width:"20px" + }).appendTo(buttons).on("click", function(evt) { + evt.preventDefault(); + var currentType = that.input.attr("type"); + if (currentType === "text") { + that.input.attr("type","password"); + eyeCon.removeClass("fa-eye-slash").addClass("fa-eye"); + } else { + that.input.attr("type","text"); + eyeCon.removeClass("fa-eye").addClass("fa-eye-slash"); + } + }).hide(); + var eyeCon = $('').css("margin-left","-1px").appendTo(eyeButton); + + if (value === "__PWRD__") { + var innerContainer = $('
').css({ + padding:"6px 6px", + borderRadius:"4px" + }).addClass("red-ui-typedInput-value-label-inactive").appendTo(container); + var editButton = $('').appendTo(buttons).on("click", function(evt) { + evt.preventDefault(); + innerContainer.hide(); + container.css("background","none"); + container.css("pointer-events","none"); + that.input.val(""); + that.element.val(""); + that.elementDiv.show(); + editButton.hide(); + cancelButton.show(); + eyeButton.show(); + setTimeout(function() { + that.input.focus(); + },50); + }); + var cancelButton = $('').css("margin-left","3px").appendTo(buttons).on("click", function(evt) { + evt.preventDefault(); + innerContainer.show(); + container.css("background",""); + that.input.val("__PWRD__"); + that.element.val("__PWRD__"); + that.elementDiv.hide(); + editButton.show(); + cancelButton.hide(); + eyeButton.hide(); + that.input.attr("type","password"); + eyeCon.removeClass("fa-eye-slash").addClass("fa-eye"); + + }).hide(); + } else { + container.css("background","none"); + container.css("pointer-events","none"); + this.elementDiv.show(); + eyeButton.show(); + } + } } }; var nlsd = false; @@ -220,6 +291,8 @@ that.input.attr(d,m); }); + this.defaultInputType = this.input.attr('type'); + this.uiSelect.addClass("red-ui-typedInput-container"); this.element.attr('type','hidden'); @@ -687,7 +760,7 @@ $('',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel); } else { - $('',{class:"red-ui-typedInput-icon "+opt.icon}).prependTo(this.selectLabel); + $('',{class:"red-ui-typedInput-icon "+opt.icon,style:"min-width: 13px; margin-right: 4px;"}).prependTo(this.selectLabel); } } if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) { @@ -822,6 +895,11 @@ if (this.optionSelectTrigger) { this.optionSelectTrigger.hide(); } + if (opt.inputType) { + this.input.attr('type',opt.inputType) + } else { + this.input.attr('type',this.defaultInputType) + } if (opt.hasValue === false) { this.oldValue = this.input.val(); this.input.val(""); @@ -830,8 +908,8 @@ } else if (opt.valueLabel) { this.valueLabelContainer.show(); this.valueLabelContainer.empty(); - opt.valueLabel.call(this,this.valueLabelContainer,this.input.val()); this.elementDiv.hide(); + opt.valueLabel.call(this,this.valueLabelContainer,this.input.val()); } else { if (this.oldValue !== undefined) { this.input.val(this.oldValue); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index be2d50674..f6c2c1dd7 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -56,7 +56,10 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - + .red-ui-typedInput-value-label-inactive { + backgroundColor: $secondary-background-disabled; + color: $secondary-text-color-disabled; + } } } .red-ui-typedInput-options { @@ -123,7 +126,7 @@ button.red-ui-typedInput-option-trigger } &.disabled { cursor: default; - i.red-ui-typedInput-icon { + > i.red-ui-typedInput-icon { color: $secondary-text-color-disabled; } } From 33cbb2ada81f56259533f7da0938e7a6e71248b5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Nov 2019 15:06:27 +0000 Subject: [PATCH 045/346] Fixup typedInput cred css --- .../@node-red/editor-client/src/js/ui/common/typedInput.js | 1 + .../@node-red/editor-client/src/sass/ui/common/typedInput.scss | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) 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 8ca601ec3..dc8d8e387 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 @@ -173,6 +173,7 @@ valueLabel: function(container,value) { var that = this; container.css("pointer-events","none"); + this.elementDiv.hide(); var buttons = $('
').css({ position: "absolute", right:"6px", diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index f6c2c1dd7..598d4e7f9 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -57,7 +57,7 @@ overflow: hidden; text-overflow: ellipsis; .red-ui-typedInput-value-label-inactive { - backgroundColor: $secondary-background-disabled; + background: $secondary-background-disabled; color: $secondary-text-color-disabled; } } From bffcaa1c17ab92f745b912eb9cc805123a8a74a1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 10 Feb 2020 11:16:19 +0000 Subject: [PATCH 046/346] Refocus credential typedInput when hide/show button clicked --- .../@node-red/editor-client/src/js/ui/common/typedInput.js | 6 ++++++ 1 file changed, 6 insertions(+) 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 dc8d8e387..a3f066223 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 @@ -188,9 +188,15 @@ if (currentType === "text") { that.input.attr("type","password"); eyeCon.removeClass("fa-eye-slash").addClass("fa-eye"); + setTimeout(function() { + that.input.focus(); + },50); } else { that.input.attr("type","text"); eyeCon.removeClass("fa-eye").addClass("fa-eye-slash"); + setTimeout(function() { + that.input.focus(); + },50); } }).hide(); var eyeCon = $('').css("margin-left","-1px").appendTo(eyeButton); From cd210d9fbf1aeefc5544ea61902f5c71d759cb5e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Nov 2019 14:14:48 +0000 Subject: [PATCH 047/346] Add support for credential-stored env var in subflow --- .../@node-red/editor-client/src/js/nodes.js | 59 +- .../editor-client/src/js/ui/editor.js | 67 +- .../editor-client/src/js/ui/subflow.js | 758 ++++++++++-------- .../@node-red/runtime/lib/api/flows.js | 26 +- .../runtime/lib/nodes/credentials.js | 69 +- .../runtime/lib/nodes/flows/Subflow.js | 23 +- 6 files changed, 606 insertions(+), 396 deletions(-) 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 5c0778fba..76dc4d194 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 @@ -398,6 +398,10 @@ RED.nodes = (function() { paletteLabel: function() { return RED.nodes.subflow(sf.id).name }, inputLabels: function(i) { return sf.inputLabels?sf.inputLabels[i]:null }, outputLabels: function(i) { return sf.outputLabels?sf.outputLabels[i]:null }, + oneditprepare: function() { + RED.subflow.buildEditForm("subflow",this); + RED.subflow.buildPropertiesForm(this); + }, oneditresize: function(size) { // var rows = $(".dialog-form>div:not(.node-input-env-container-row)"); var height = size.height; @@ -505,19 +509,33 @@ RED.nodes = (function() { node[d] = n[d]; } } - if(exportCreds && n.credentials) { + if (exportCreds) { var credentialSet = {}; - node.credentials = {}; - for (var cred in n._def.credentials) { - if (n._def.credentials.hasOwnProperty(cred)) { - if (n._def.credentials[cred].type == 'password') { + if (/^subflow:/.test(node.type) && n.credentials) { + // A subflow instance node can have arbitrary creds + for (var sfCred in n.credentials) { + if (n.credentials.hasOwnProperty(sfCred)) { if (!n.credentials._ || - n.credentials["has_"+cred] != n.credentials._["has_"+cred] || - (n.credentials["has_"+cred] && n.credentials[cred])) { + n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] || + (n.credentials["has_"+sfCred] && n.credentials[sfCred])) { + credentialSet[sfCred] = n.credentials[sfCred]; + } + } + } + } else if (n.credentials) { + node.credentials = {}; + // All other nodes have a well-defined list of possible credentials + for (var cred in n._def.credentials) { + if (n._def.credentials.hasOwnProperty(cred)) { + if (n._def.credentials[cred].type == 'password') { + if (!n.credentials._ || + n.credentials["has_"+cred] != n.credentials._["has_"+cred] || + (n.credentials["has_"+cred] && n.credentials[cred])) { + credentialSet[cred] = n.credentials[cred]; + } + } else if (n.credentials[cred] != null && (!n.credentials._ || n.credentials[cred] != n.credentials._[cred])) { credentialSet[cred] = n.credentials[cred]; } - } else if (n.credentials[cred] != null && (!n.credentials._ || n.credentials[cred] != n.credentials._[cred])) { - credentialSet[cred] = n.credentials[cred]; } } } @@ -568,7 +586,8 @@ RED.nodes = (function() { return node; } - function convertSubflow(n) { + function convertSubflow(n, exportCreds) { + exportCreds = true; var node = {}; node.id = n.id; node.type = n.type; @@ -578,6 +597,24 @@ RED.nodes = (function() { node.in = []; node.out = []; node.env = n.env; + + if (exportCreds) { + var credentialSet = {}; + // A subflow node can have arbitrary creds + for (var sfCred in n.credentials) { + if (n.credentials.hasOwnProperty(sfCred)) { + if (!n.credentials._ || + n.credentials["has_"+sfCred] != n.credentials._["has_"+sfCred] || + (n.credentials["has_"+sfCred] && n.credentials[sfCred])) { + credentialSet[sfCred] = n.credentials[sfCred]; + } + } + } + if (Object.keys(credentialSet).length > 0) { + node.credentials = credentialSet; + } + } + node.color = n.color; n.in.forEach(function(p) { @@ -693,7 +730,7 @@ RED.nodes = (function() { } for (i in subflows) { if (subflows.hasOwnProperty(i)) { - nns.push(convertSubflow(subflows[i])); + nns.push(convertSubflow(subflows[i], exportCredentials)); } } for (i in configNodes) { 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 9fb29833a..2f5c4fe11 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 @@ -490,8 +490,7 @@ RED.editor = (function() { done(); } } - - if (definition.credentials) { + if (definition.credentials || /^subflow:/.test(definition.type)) { if (node.credentials) { populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); completePrepare(); @@ -499,7 +498,9 @@ RED.editor = (function() { $.getJSON(getCredentialsURL(node.type, node.id), function (data) { node.credentials = data; node.credentials._ = $.extend(true,{},data); - populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); + if (!/^subflow:/.test(definition.type)) { + populateCredentialsInputs(node, definition.credentials, node.credentials, prefix); + } completePrepare(); }); } @@ -576,8 +577,11 @@ RED.editor = (function() { $(this).attr("data-i18n",keys.join(";")); }); - if (type === "subflow-template" || type === "subflow") { - RED.subflow.buildEditForm(dialogForm,type,node); + if (type === "subflow-template") { + // This is the 'edit properties' dialog for a subflow template + // TODO: this needs to happen later in the dialog open sequence + // so that credentials can be loaded prior to building the form + RED.subflow.buildEditForm(type,node); } // Add dummy fields to prevent 'Enter' submitting the form in some @@ -1471,6 +1475,19 @@ RED.editor = (function() { if (type === "subflow") { var old_env = editing_node.env; var new_env = RED.subflow.exportSubflowInstanceEnv(editing_node); + if (new_env && new_env.length > 0) { + new_env.forEach(function(prop) { + if (prop.type === "cred") { + editing_node.credentials = editing_node.credentials || {_:{}}; + editing_node.credentials[prop.name] = prop.value; + editing_node.credentials['has_'+prop.name] = (prop.value !== ""); + if (prop.value !== '__PWRD__') { + changed = true; + } + delete prop.value; + } + }); + } if (!isSameObj(old_env, new_env)) { editing_node.env = new_env; changes.env = editing_node.env; @@ -1599,12 +1616,13 @@ RED.editor = (function() { id: "editor-subflow-envProperties", label: RED._("editor-tab.envProperties"), name: RED._("editor-tab.envProperties"), - content: $('
', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), + content: $('
', {id:"editor-subflow-envProperties-content",class:"red-ui-tray-content"}).appendTo(editorContent).hide(), iconClass: "fa fa-list" }; - - RED.subflow.buildPropertiesForm(subflowPropertiesTab.content,node); editorTabs.addTab(subflowPropertiesTab); + // This tab is populated by the oneditprepare function of this + // subflow. That ensures it is done *after* any credentials + // have been loaded for the instance. } if (!node._def.defaults || !node._def.defaults.hasOwnProperty('info')) { @@ -2252,6 +2270,21 @@ RED.editor = (function() { var old_env = editing_node.env; var new_env = RED.subflow.exportSubflowTemplateEnv($("#node-input-env-container").editableList("items")); + + if (new_env && new_env.length > 0) { + new_env.forEach(function(prop) { + if (prop.type === "cred") { + editing_node.credentials = editing_node.credentials || {_:{}}; + editing_node.credentials[prop.name] = prop.value; + editing_node.credentials['has_'+prop.name] = (prop.value !== ""); + if (prop.value !== '__PWRD__') { + changed = true; + } + delete prop.value; + } + }); + } + if (!isSameObj(old_env, new_env)) { editing_node.env = new_env; changes.env = editing_node.env; @@ -2311,7 +2344,7 @@ RED.editor = (function() { $("#node-input-env-container").editableList('height',height-95); } }, - open: function(tray) { + open: function(tray, done) { var trayFooter = tray.find(".red-ui-tray-footer"); var trayFooterLeft = $("
", { class: "red-ui-tray-footer-left" @@ -2362,7 +2395,6 @@ RED.editor = (function() { content: $('
', {class:"red-ui-tray-content"}).appendTo(editorContent).hide(), iconClass: "fa fa-cog" }; - buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node); editorTabs.addTab(nodePropertiesTab); var descriptionTab = { @@ -2391,11 +2423,18 @@ RED.editor = (function() { buildAppearanceForm(appearanceTab.content,editing_node); editorTabs.addTab(appearanceTab); - $("#subflow-input-name").val(subflow.name); - RED.text.bidi.prepareInput($("#subflow-input-name")); + $.getJSON(getCredentialsURL("subflow", subflow.id), function (data) { + subflow.credentials = data; + subflow.credentials._ = $.extend(true,{},data); - trayBody.i18n(); - finishedBuilding = true; + buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node); + $("#subflow-input-name").val(subflow.name); + RED.text.bidi.prepareInput($("#subflow-input-name")); + + trayBody.i18n(); + finishedBuilding = true; + done(); + }); }, close: function() { if (RED.view.state() != RED.state.IMPORT_DRAGGING) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 9f1e7f3e0..01356c3f6 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -770,7 +770,7 @@ RED.subflow = (function() { /** * Create interface for controlling env var UI definition */ - function buildEnvControl(envList) { + function buildEnvControl(envList,node) { var tabs = RED.tabs.create({ id: "subflow-env-tabs", @@ -779,7 +779,7 @@ RED.subflow = (function() { var inputContainer = $("#subflow-input-ui"); var list = envList.editableList("items"); var exportedEnv = exportEnvList(list, true); - buildEnvUI(inputContainer, exportedEnv); + buildEnvUI(inputContainer, exportedEnv,node); } $("#subflow-env-tabs-content").children().hide(); $("#" + tab.id).show(); @@ -831,6 +831,9 @@ RED.subflow = (function() { }); } + var DEFAULT_ENV_TYPE_LIST = ['str','num','bool','json','bin','env']; + var DEFAULT_ENV_TYPE_LIST_INC_CRED = ['str','num','bool','json','bin','env','cred']; + /** * Create env var edit interface * @param container - container @@ -841,7 +844,7 @@ RED.subflow = (function() { var isTemplateNode = (node.type === "subflow"); if (isTemplateNode) { - buildEnvControl(envContainer); + buildEnvControl(envContainer, node); } envContainer .css({ @@ -851,6 +854,9 @@ RED.subflow = (function() { .editableList({ header: isTemplateNode?$('
'):undefined, addItem: function(container, i, opt) { + // If this is an instance node, these are properties unique to + // this instance - ie opt.parent will not be defined. + if (isTemplateNode) { container.addClass("red-ui-editor-subflow-env-editable") } @@ -859,52 +865,64 @@ RED.subflow = (function() { var nameField = null; var valueField = null; - // if (opt.parent) { - // buildEnvUIRow(envRow,opt,opt.parent.ui||{}) - // } else { - nameField = $('', { - class: "node-input-env-name", - type: "text", - placeholder: RED._("common.label.name") - }).attr("autocomplete","disable").appendTo(envRow).val(opt.name); - valueField = $('',{ - style: "width:100%", - class: "node-input-env-value", - type: "text", - }).attr("autocomplete","disable").appendTo(envRow) - valueField.typedInput({default:'str',types:['str','num','bool','json','bin','env']}); - valueField.typedInput('type', opt.parent?(opt.type||opt.parent.type):opt.type); - valueField.typedInput('value', opt.parent?((opt.value !== undefined)?opt.value:opt.parent.value):opt.value); - // } + nameField = $('', { + class: "node-input-env-name", + type: "text", + placeholder: RED._("common.label.name") + }).attr("autocomplete","disable").appendTo(envRow).val(opt.name); + valueField = $('',{ + style: "width:100%", + class: "node-input-env-value", + type: "text", + }).attr("autocomplete","disable").appendTo(envRow) + valueField.typedInput({default:'str',types:isTemplateNode?DEFAULT_ENV_TYPE_LIST:DEFAULT_ENV_TYPE_LIST_INC_CRED}); + valueField.typedInput('type', opt.type); + if (opt.type === "cred") { + if (opt.value) { + valueField.typedInput('value', opt.value); + } else if (node.credentials && node.credentials[opt.name]) { + valueField.typedInput('value', node.credentials[opt.name]); + } else if (node.credentials && node.credentials['has_'+opt.name]) { + valueField.typedInput('value', "__PWRD__"); + } else { + valueField.typedInput('value', ""); + } + } else { + valueField.typedInput('value', opt.value); + } opt.nameField = nameField; opt.valueField = valueField; - if (!opt.parent) { - var actionButton = $('',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow); - $('',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); - var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); - actionButton.on("click", function(evt) { - evt.preventDefault(); - removeTip.close(); - container.parent().addClass("red-ui-editableList-item-deleting") - container.fadeOut(300, function() { - envContainer.editableList('removeItem',opt); - }); + var actionButton = $('',{href:"#",class:"red-ui-editableList-item-remove red-ui-button red-ui-button-small"}).appendTo(envRow); + $('',{class:"fa "+(opt.parent?"fa-reply":"fa-remove")}).appendTo(actionButton); + var removeTip = RED.popover.tooltip(actionButton,RED._("subflow.env.remove")); + actionButton.on("click", function(evt) { + evt.preventDefault(); + removeTip.close(); + container.parent().addClass("red-ui-editableList-item-deleting") + container.fadeOut(300, function() { + envContainer.editableList('removeItem',opt); }); - } + }); if (isTemplateNode) { // Add the UI customisation row // if `opt.ui` does not exist, then apply defaults. If these // defaults do not change then they will get stripped off // before saving. - opt.ui = opt.ui || { - icon: "", - label: {}, - type: "input", - opts: {types:['str','num','bool','json','bin','env']} + if (opt.type === 'cred') { + opt.ui = opt.ui || { + icon: "", + type: "cred" + } + } else { + opt.ui = opt.ui || { + icon: "", + type: "input", + opts: {types:DEFAULT_ENV_TYPE_LIST} + } } opt.ui.label = opt.ui.label || {}; opt.ui.type = opt.ui.type || "input"; @@ -995,11 +1013,11 @@ RED.subflow = (function() { var row = $('
').appendTo(container); $('
').appendTo(row); - var typeOptions = { - 'input': {types:['str','num','bool','json','bin','env']}, + 'input': {types:DEFAULT_ENV_TYPE_LIST}, 'select': {opts:[]}, - 'spinner': {} + 'spinner': {}, + 'cred': {} }; if (ui.opts) { typeOptions[ui.type] = ui.opts; @@ -1054,7 +1072,7 @@ RED.subflow = (function() { } langs.forEach(function(l) { var row = $('
').appendTo(content); - $('').css({display:"inline-block",width:"120px"}).text(RED._("languages."+l)+(l===currentLocale?"*":"")).appendTo(row); + $('').css({display:"inline-block",width:"120px"}).text(RED._("languages."+l)+(l===currentLocale?"*":"")).appendTo(row); $('').text(ui.label[l]||"").appendTo(row); }); return content; @@ -1062,314 +1080,341 @@ RED.subflow = (function() { nameField.on('change',function(evt) { labelInput.attr("placeholder",$(this).val()) - }); + }); - var inputCell = $('
').appendTo(row); - var inputCellInput = $('').css("width","100%").appendTo(inputCell); - if (ui.type === "input") { - inputCellInput.val(ui.opts.types.join(",")); - } - var checkbox; - var selectBox; + var inputCell = $('
').appendTo(row); + var inputCellInput = $('').css("width","100%").appendTo(inputCell); + if (ui.type === "input") { + inputCellInput.val(ui.opts.types.join(",")); + } + var checkbox; + var selectBox; - inputCellInput.typedInput({ - types: [ - { - value:"input", - label:RED._("editor.inputs.input"), icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[ - {value:"str",label:RED._("editor.types.str"),icon:"red/images/typedInput/az.svg"}, - {value:"num",label:RED._("editor.types.num"),icon:"red/images/typedInput/09.svg"}, - {value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"}, - {value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"}, - {value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"}, - {value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"} - ], - default: ['str','num','bool','json','bin','env'], - valueLabel: function(container,value) { - container.css("padding",0); - var innerContainer = $('
').css({ - "background":"white", - "height":"100%", - "box-sizing": "border-box" - }).appendTo(container); + inputCellInput.typedInput({ + types: [ + { + value:"input", + label:RED._("editor.inputs.input"), icon:"fa fa-i-cursor",showLabel:false,multiple:true,options:[ + {value:"str",label:RED._("editor.types.str"),icon:"red/images/typedInput/az.svg"}, + {value:"num",label:RED._("editor.types.num"),icon:"red/images/typedInput/09.svg"}, + {value:"bool",label:RED._("editor.types.bool"),icon:"red/images/typedInput/bool.svg"}, + {value:"json",label:RED._("editor.types.json"),icon:"red/images/typedInput/json.svg"}, + {value: "bin",label: RED._("editor.types.bin"),icon: "red/images/typedInput/bin.svg"}, + {value: "env",label: RED._("editor.types.env"),icon: "red/images/typedInput/env.svg"}, + {value: "cred",label: RED._("editor.types.cred"),icon: "fa fa-lock"} + ], + default: DEFAULT_ENV_TYPE_LIST, + valueLabel: function(container,value) { + container.css("padding",0); + var innerContainer = $('
').css({ + "background":"white", + "height":"100%", + "box-sizing": "border-box" + }).appendTo(container); - var input = $('
').appendTo(innerContainer); - $('').appendTo(input); - if (value.length) { - value.forEach(function(v) { - $('',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 3px"}).appendTo(input); - }) - } else { - $("").css({ - "color":"#aaa", - "padding-left": "4px" - }).text("select types...").appendTo(input); - } - } - }, - { - value:"select", - label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false, - valueLabel: function(container,value) { - container.css("padding","0"); + var input = $('
').appendTo(innerContainer); + $('').appendTo(input); + if (value.length) { + value.forEach(function(v) { + if (!/^fa /.test(v.icon)) { + $('',{src:v.icon,style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input); + } else { + var s = $('',{style:"max-width:14px; padding: 0 3px; margin-top:-4px; margin-left: 1px"}).appendTo(input); + $("",{class: v.icon}).appendTo(s); + } + }) + } else { + $("").css({ + "color":"#aaa", + "padding-left": "4px" + }).text("select types...").appendTo(input); + } + } + }, + { + value: "cred", + label: RED._("typedInput.type.cred"), icon:"fa fa-lock", showLabel: false, + valueLabel: function(container,value) { + container.css("padding",0); + var innerContainer = $('
').css({ + "background":"white", + "height":"100%", + "box-sizing": "border-box", + "border-top-right-radius": "4px", + "border-bottom-right-radius": "4px" + }).appendTo(container); + $('
').html("••••••••").appendTo(innerContainer); + } + }, + { + value:"select", + label:RED._("editor.inputs.select"), icon:"fa fa-tasks",showLabel:false, + valueLabel: function(container,value) { + container.css("padding","0"); - selectBox = $('').appendTo(container); - if (ui.opts && Array.isArray(ui.opts.opts)) { - ui.opts.opts.forEach(function(o) { - var label = lookupLabel(o.l, o.l["en-US"]||o.v, currentLocale); - // $('
').appendTo(container); - var optList = $('
    ').appendTo(content).editableList({ - header:$("
    "+RED._("editor.select.label")+"
    "+RED._("editor.select.value")+"
    "), - addItem: function(row,index,itemData) { - var labelDiv = $('
    ').appendTo(row); - var label = lookupLabel(itemData.l, "", currentLocale); - itemData.label = $('').val(label).appendTo(labelDiv); - itemData.label.on('keydown', function(evt) { - if (evt.keyCode === 13) { - itemData.input.focus(); - evt.preventDefault(); - } - }); - var labelIcon = $('').appendTo(labelDiv); - RED.popover.tooltip(labelIcon,function() { - return currentLocale; - }) - itemData.input = $('').val(itemData.v).appendTo(row); + selectBox = $('').appendTo(container); + if (ui.opts && Array.isArray(ui.opts.opts)) { + ui.opts.opts.forEach(function(o) { + var label = lookupLabel(o.l, o.l["en-US"]||o.v, currentLocale); + // $('
    ').appendTo(container); + var optList = $('
      ').appendTo(content).editableList({ + header:$("
      "+RED._("editor.select.label")+"
      "+RED._("editor.select.value")+"
      "), + addItem: function(row,index,itemData) { + var labelDiv = $('
      ').appendTo(row); + var label = lookupLabel(itemData.l, "", currentLocale); + itemData.label = $('').val(label).appendTo(labelDiv); + itemData.label.on('keydown', function(evt) { + if (evt.keyCode === 13) { + itemData.input.focus(); + evt.preventDefault(); + } + }); + var labelIcon = $('').appendTo(labelDiv); + RED.popover.tooltip(labelIcon,function() { + return currentLocale; + }) + itemData.input = $('').val(itemData.v).appendTo(row); - // Problem using a TI here: - // - this is in a popout panel - // - clicking the expand button in the TI will close the parent edit tray - // and open the type editor. - // - but it leaves the popout panel over the top. - // - there is no way to get back to the popout panel after closing the type editor - //.typedInput({default:itemData.t||'str', types:['str','num','bool','json','bin','env']}); - itemData.input.on('keydown', function(evt) { - if (evt.keyCode === 13) { - // Enter or Tab - var index = optList.editableList('indexOf',itemData); - var length = optList.editableList('length'); - if (index + 1 === length) { - var newItem = {}; - optList.editableList('addItem',newItem); - setTimeout(function() { - if (newItem.label) { - newItem.label.focus(); - } - },100) - } else { - var nextItem = optList.editableList('getItemAt',index+1); - if (nextItem.label) { - nextItem.label.focus() - } - } - evt.preventDefault(); - } - }); - }, - sortable: true, - removable: true, - height: 160 - }) - if (ui.opts.opts.length > 0) { - ui.opts.opts.forEach(function(o) { - optList.editableList('addItem',$.extend(true,{},o)) - }) - } else { - optList.editableList('addItem',{}) - } - return { - onclose: function() { - var items = optList.editableList('items'); - var vals = []; - items.each(function (i,el) { - var data = el.data('data'); - var l = data.label.val().trim(); - var v = data.input.val(); - // var t = data.input.typedInput('type'); - // var v = data.input.typedInput('value'); - if (l.length > 0) { - data.l = data.l || {}; - data.l[currentLocale] = l; - } - data.v = v; + // Problem using a TI here: + // - this is in a popout panel + // - clicking the expand button in the TI will close the parent edit tray + // and open the type editor. + // - but it leaves the popout panel over the top. + // - there is no way to get back to the popout panel after closing the type editor + //.typedInput({default:itemData.t||'str', types:DEFAULT_ENV_TYPE_LIST}); + itemData.input.on('keydown', function(evt) { + if (evt.keyCode === 13) { + // Enter or Tab + var index = optList.editableList('indexOf',itemData); + var length = optList.editableList('length'); + if (index + 1 === length) { + var newItem = {}; + optList.editableList('addItem',newItem); + setTimeout(function() { + if (newItem.label) { + newItem.label.focus(); + } + },100) + } else { + var nextItem = optList.editableList('getItemAt',index+1); + if (nextItem.label) { + nextItem.label.focus() + } + } + evt.preventDefault(); + } + }); + }, + sortable: true, + removable: true, + height: 160 + }) + if (ui.opts.opts.length > 0) { + ui.opts.opts.forEach(function(o) { + optList.editableList('addItem',$.extend(true,{},o)) + }) + } else { + optList.editableList('addItem',{}) + } + return { + onclose: function() { + var items = optList.editableList('items'); + var vals = []; + items.each(function (i,el) { + var data = el.data('data'); + var l = data.label.val().trim(); + var v = data.input.val(); + // var t = data.input.typedInput('type'); + // var v = data.input.typedInput('value'); + if (l.length > 0) { + data.l = data.l || {}; + data.l[currentLocale] = l; + } + data.v = v; - if (l.length > 0 || v.length > 0) { - var val = {l:data.l,v:data.v}; - // if (t !== 'str') { - // val.t = t; - // } - vals.push(val); - } - }); - ui.opts.opts = vals; - inputCellInput.typedInput('value',Date.now()) - } - } - } - } - }, - { - value:"checkbox", - label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false, - valueLabel: function(container,value) { - container.css("padding",0); - checkbox = $('').appendTo(container); - checkbox.on('change', function(evt) { - valueField.typedInput('value',$(this).prop('checked')?"true":"false"); - }) - checkbox.prop('checked',valueField.typedInput('value')==="true"); - } - }, - { - value:"spinner", - label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false, - valueLabel: function(container,value) { - container.css("padding",0); - var innerContainer = $('
      ').css({ - "background":"white", - "height":"100%", - "box-sizing": "border-box" - }).appendTo(container); + if (l.length > 0 || v.length > 0) { + var val = {l:data.l,v:data.v}; + // if (t !== 'str') { + // val.t = t; + // } + vals.push(val); + } + }); + ui.opts.opts = vals; + inputCellInput.typedInput('value',Date.now()) + } + } + } + } + }, + { + value:"checkbox", + label:RED._("editor.inputs.checkbox"), icon:"fa fa-check-square-o",showLabel:false, + valueLabel: function(container,value) { + container.css("padding",0); + checkbox = $('').appendTo(container); + checkbox.on('change', function(evt) { + valueField.typedInput('value',$(this).prop('checked')?"true":"false"); + }) + checkbox.prop('checked',valueField.typedInput('value')==="true"); + } + }, + { + value:"spinner", + label:RED._("editor.inputs.spinner"), icon:"fa fa-sort-numeric-asc", showLabel:false, + valueLabel: function(container,value) { + container.css("padding",0); + var innerContainer = $('
      ').css({ + "background":"white", + "height":"100%", + "box-sizing": "border-box" + }).appendTo(container); - var input = $('
      ').appendTo(innerContainer); - $('').appendTo(input); + var input = $('
      ').appendTo(innerContainer); + $('').appendTo(input); - var min = ui.opts && ui.opts.min; - var max = ui.opts && ui.opts.max; - var label = ""; - if (min !== undefined && max !== undefined) { - label = Math.min(min,max)+" - "+Math.max(min,max); - } else if (min !== undefined) { - label = "> "+min; - } else if (max !== undefined) { - label = "< "+max; - } - $('').css("margin-left","15px").text(label).appendTo(input); - }, - expand: { - icon: "fa-caret-down", - content: function(container) { - var content = $('
      ').appendTo(container); - content.css("padding","8px 5px") - var min = ui.opts.min; - var max = ui.opts.max; - var minInput = $(''); - minInput.val(min); - var maxInput = $(''); - maxInput.val(max); - $('
      ').append(minInput).appendTo(content); - $('
      ').append(maxInput).appendTo(content); - return { - onclose: function() { - var min = minInput.val().trim(); - var max = maxInput.val().trim(); - if (min !== "") { - ui.opts.min = parseInt(min); - } else { - delete ui.opts.min; - } - if (max !== "") { - ui.opts.max = parseInt(max); - } else { - delete ui.opts.max; - } - inputCellInput.typedInput('value',Date.now()) - } - } - } - } - }, - { - value:"none", - label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false - }, - { - value:"hide", - label:RED._("editor.inputs.hidden"), icon:"fa fa-ban",hasValue:false - } - ], - default: 'none' - }).on("typedinputtypechange", function(evt,type) { - ui.type = $(this).typedInput("type"); - ui.opts = typeOptions[ui.type]; - if (ui.type === 'input') { - // In the case of 'input' type, the typedInput uses the multiple-option - // mode. Its value needs to be set to a comma-separately list of the - // selected options. - inputCellInput.typedInput('value',ui.opts.types.join(",")) - } else { - // No other type cares about `value`, but doing this will - // force a refresh of the label now that `ui.opts` has - // been updated. - inputCellInput.typedInput('value',Date.now()) - } + var min = ui.opts && ui.opts.min; + var max = ui.opts && ui.opts.max; + var label = ""; + if (min !== undefined && max !== undefined) { + label = Math.min(min,max)+" - "+Math.max(min,max); + } else if (min !== undefined) { + label = "> "+min; + } else if (max !== undefined) { + label = "< "+max; + } + $('').css("margin-left","15px").text(label).appendTo(input); + }, + expand: { + icon: "fa-caret-down", + content: function(container) { + var content = $('
      ').appendTo(container); + content.css("padding","8px 5px") + var min = ui.opts.min; + var max = ui.opts.max; + var minInput = $(''); + minInput.val(min); + var maxInput = $(''); + maxInput.val(max); + $('
      ').append(minInput).appendTo(content); + $('
      ').append(maxInput).appendTo(content); + return { + onclose: function() { + var min = minInput.val().trim(); + var max = maxInput.val().trim(); + if (min !== "") { + ui.opts.min = parseInt(min); + } else { + delete ui.opts.min; + } + if (max !== "") { + ui.opts.max = parseInt(max); + } else { + delete ui.opts.max; + } + inputCellInput.typedInput('value',Date.now()) + } + } + } + } + }, + { + value:"none", + label:RED._("editor.inputs.none"), icon:"fa fa-times",hasValue:false + }, + { + value:"hide", + label:RED._("editor.inputs.hidden"), icon:"fa fa-ban",hasValue:false + } + ], + default: 'none' + }).on("typedinputtypechange", function(evt,type) { + ui.type = $(this).typedInput("type"); + ui.opts = typeOptions[ui.type]; + if (ui.type === 'input') { + // In the case of 'input' type, the typedInput uses the multiple-option + // mode. Its value needs to be set to a comma-separately list of the + // selected options. + inputCellInput.typedInput('value',ui.opts.types.join(",")) + } else { + // No other type cares about `value`, but doing this will + // force a refresh of the label now that `ui.opts` has + // been updated. + inputCellInput.typedInput('value',Date.now()) + } - switch (ui.type) { - case 'input': + switch (ui.type) { + case 'input': valueField.typedInput('types',ui.opts.types); break; case 'select': - valueField.typedInput('types',['str']); - break; - case 'checkbox': + valueField.typedInput('types',['str']); + break; + case 'checkbox': valueField.typedInput('types',['bool']); break; case 'spinner': - valueField.typedInput('types',['num']) + valueField.typedInput('types',['num']); + break; + case 'cred': + valueField.typedInput('types',['cred']); break; default: - valueField.typedInput('types',['str','num','bool','json','bin','env']) - } - if (ui.type === 'checkbox') { - valueField.typedInput('type','bool'); - } else if (ui.type === 'spinner') { - valueField.typedInput('type','num'); - } - if (ui.type !== 'checkbox') { - checkbox = null; - } + valueField.typedInput('types',DEFAULT_ENV_TYPE_LIST) + } + if (ui.type === 'checkbox') { + valueField.typedInput('type','bool'); + } else if (ui.type === 'spinner') { + valueField.typedInput('type','num'); + } + if (ui.type !== 'checkbox') { + checkbox = null; + } - }).on("change", function(evt,type) { - if (ui.type === 'input') { - ui.opts.types = inputCellInput.typedInput('value').split(","); - valueField.typedInput('types',ui.opts.types); - } - }); - valueField.on("change", function(evt) { - if (checkbox) { - checkbox.prop('checked',$(this).typedInput('value')==="true") - } - }) - // Set the input to the right type. This will trigger the 'typedinputtypechange' - // event handler (just above ^^) to update the value if needed - inputCellInput.typedInput('type',ui.type) + }).on("change", function(evt,type) { + if (ui.type === 'input') { + ui.opts.types = inputCellInput.typedInput('value').split(","); + valueField.typedInput('types',ui.opts.types); + } + }); + valueField.on("change", function(evt) { + if (checkbox) { + checkbox.prop('checked',$(this).typedInput('value')==="true") + } + }) + // Set the input to the right type. This will trigger the 'typedinputtypechange' + // event handler (just above ^^) to update the value if needed + inputCellInput.typedInput('type',ui.type) } - function buildEnvUIRow(row, tenv, ui) { + function buildEnvUIRow(row, tenv, ui, node) { ui.label = ui.label||{}; - if (!ui.type) { + if ((tenv.type === "cred" || (tenv.parent && tenv.parent.type === "cred")) && !ui.type) { + ui.type = "cred"; + ui.opts = {}; + } else if (!ui.type) { ui.type = "input"; - ui.opts = {types:['str','num','bool','json','bin','env']} + ui.opts = {types:DEFAULT_ENV_TYPE_LIST} } else { if (!ui.opts) { ui.opts = (ui.type === "select") ? {opts:[]} : {}; @@ -1467,6 +1512,24 @@ RED.subflow = (function() { input.spinner(spinnerOpts).parent().width('70%'); input.val(val.value); break; + case "cred": + input = $('').css('width','70%').appendTo(row); + if (node.credentials) { + if (node.credentials[tenv.name]) { + input.val(node.credentials[tenv.name]); + } else if (node.credentials['has_'+tenv.name]) { + input.val("__PWRD__") + } else { + input.val(""); + } + } else { + input.val(""); + } + input.typedInput({ + types: ['cred'], + default: 'cred' + }) + break; } if (input) { input.attr('id',getSubflowEnvPropertyName(tenv.name)) @@ -1478,7 +1541,7 @@ RED.subflow = (function() { * @param uiContainer - container for UI * @param envList - env var definitions of template */ - function buildEnvUI(uiContainer, envList) { + function buildEnvUI(uiContainer, envList,node) { uiContainer.empty(); var elementID = 0; for (var i = 0; i < envList.length; i++) { @@ -1487,7 +1550,7 @@ RED.subflow = (function() { continue; } var row = $("
      ", { class: "form-row" }).appendTo(uiContainer); - buildEnvUIRow(row,tenv, tenv.ui || {}); + buildEnvUIRow(row,tenv, tenv.ui || {}, node); // console.log(ui); } @@ -1525,7 +1588,7 @@ RED.subflow = (function() { // icon: "", // label: {}, // type: "input", - // opts: {types:['str','num','bool','json','bin','env']} + // opts: {types:DEFAULT_ENV_TYPE_LIST} // } if (!ui.icon) { delete ui.icon; @@ -1535,13 +1598,19 @@ RED.subflow = (function() { } switch (ui.type) { case "input": - if (JSON.stringify(ui.opts) === JSON.stringify({types:['str','num','bool','json','bin','env']})) { + if (JSON.stringify(ui.opts) === JSON.stringify({types:DEFAULT_ENV_TYPE_LIST})) { // This is the default input config. Delete it as it will // be applied automatically delete ui.type; delete ui.opts; } break; + case "cred": + if (envItem.type === "cred") { + delete ui.type; + } + delete ui.opts; + break; case "select": if (ui.opts && $.isEmptyObject(ui.opts.opts)) { // This is the default select config. @@ -1585,7 +1654,7 @@ RED.subflow = (function() { type: env.type, value: env.value }, - ui: env.ui + ui: $.extend(true,{},env.ui) } envList.push(item); parentEnv[env.name] = item; @@ -1621,13 +1690,17 @@ RED.subflow = (function() { var item; var ui = data.ui || {}; if (!ui.type) { - ui.type = "input"; - ui.opts = {types:['str','num','bool','json','bin','env']} + if (data.parent && data.parent.type === "cred") { + ui.type = "cred"; + } else { + ui.type = "input"; + ui.opts = {types:DEFAULT_ENV_TYPE_LIST} + } } else { ui.opts = ui.opts || {}; } var input = $("#"+getSubflowEnvPropertyName(data.name)); - if (input.length) { + if (input.length || ui.type === "cred") { item = { name: data.name }; switch(ui.type) { case "input": @@ -1639,6 +1712,10 @@ RED.subflow = (function() { item.type = 'str'; } break; + case "cred": + item.value = input.val(); + item.type = 'cred'; + break; case "spinner": item.value = input.val(); item.type = 'num'; @@ -1652,7 +1729,7 @@ RED.subflow = (function() { item.value = ""+input.prop("checked"); break; } - if (item.type !== data.parent.type || item.value !== data.parent.value) { + if (ui.type === "cred" || item.type !== data.parent.type || item.value !== data.parent.value) { env.push(item); } } @@ -1702,14 +1779,17 @@ RED.subflow = (function() { return defaultLabel; } - function buildEditForm(container,type,node) { + function buildEditForm(type,node) { if (type === "subflow-template") { buildPropertiesList($('#node-input-env-container'), node); } else if (type === "subflow") { - buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node)); + // This gets called by the subflow type `oneditprepare` function + // registered in nodes.js#addSubflow() + buildEnvUI($("#subflow-input-ui"), getSubflowInstanceParentEnv(node), node); } } - function buildPropertiesForm(container, node) { + function buildPropertiesForm(node) { + var container = $('#editor-subflow-envProperties-content'); var form = $('
      ').appendTo(container); var listContainer = $('
      ').appendTo(form); var list = $('
        ').appendTo(listContainer); diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js index 1e6e72dc7..76e645b92 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -238,17 +238,25 @@ var api = module.exports = { if (!credentials) { return resolve({}); } - var definition = runtime.nodes.getCredentialDefinition(opts.type) || {}; - var sendCredentials = {}; - for (var cred in definition) { - if (definition.hasOwnProperty(cred)) { - if (definition[cred].type == "password") { - var key = 'has_' + cred; - sendCredentials[key] = credentials[cred] != null && credentials[cred] !== ''; - continue; + var cred; + if (/^subflow(:|$)/.test(opts.type)) { + for (cred in credentials) { + if (credentials.hasOwnProperty(cred)) { + sendCredentials['has_'+cred] = credentials[cred] != null && credentials[cred] !== ''; + } + } + } else { + var definition = runtime.nodes.getCredentialDefinition(opts.type) || {}; + for (cred in definition) { + if (definition.hasOwnProperty(cred)) { + if (definition[cred].type == "password") { + var key = 'has_' + cred; + sendCredentials[key] = credentials[cred] != null && credentials[cred] !== ''; + continue; + } + sendCredentials[cred] = credentials[cred] || ''; } - sendCredentials[cred] = credentials[cred] || ''; } } resolve(sendCredentials); diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js index 355bd9bc5..153ef4b06 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/credentials.js @@ -341,33 +341,62 @@ var api = module.exports = { extract: function(node) { var nodeID = node.id; var nodeType = node.type; + var cred; var newCreds = node.credentials; if (newCreds) { delete node.credentials; var savedCredentials = credentialCache[nodeID] || {}; - var dashedType = nodeType.replace(/\s+/g, '-'); - var definition = credentialsDef[dashedType]; - if (!definition) { - log.warn(log._("nodes.credentials.not-registered",{type:nodeType})); - return; - } + if (/^subflow(:|$)/.test(nodeType)) { + for (cred in newCreds) { + if (newCreds.hasOwnProperty(cred)) { + if (newCreds[cred] === "__PWRD__") { + continue; + } + if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) { + delete savedCredentials[cred]; + dirty = true; + continue; + } + if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) { + savedCredentials[cred] = newCreds[cred]; + dirty = true; + } - for (var cred in definition) { - if (definition.hasOwnProperty(cred)) { - if (newCreds[cred] === undefined) { - continue; } - if (definition[cred].type == "password" && newCreds[cred] == '__PWRD__') { - continue; + } + for (cred in savedCredentials) { + if (savedCredentials.hasOwnProperty(cred)) { + if (!newCreds.hasOwnProperty(cred)) { + delete savedCredentials[cred]; + dirty = true; + } } - if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) { - delete savedCredentials[cred]; - dirty = true; - continue; - } - if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) { - savedCredentials[cred] = newCreds[cred]; - dirty = true; + } + } else { + var dashedType = nodeType.replace(/\s+/g, '-'); + var definition = credentialsDef[dashedType]; + if (!definition) { + log.warn(log._("nodes.credentials.not-registered",{type:nodeType})); + return; + } + + for (cred in definition) { + if (definition.hasOwnProperty(cred)) { + if (newCreds[cred] === undefined) { + continue; + } + if (definition[cred].type == "password" && newCreds[cred] == '__PWRD__') { + continue; + } + if (0 === newCreds[cred].length || /^\s*$/.test(newCreds[cred])) { + delete savedCredentials[cred]; + dirty = true; + continue; + } + if (!savedCredentials.hasOwnProperty(cred) || JSON.stringify(savedCredentials[cred]) !== JSON.stringify(newCreds[cred])) { + savedCredentials[cred] = newCreds[cred]; + dirty = true; + } } } } diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js index a7bec1234..c0bc63dd2 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js @@ -22,6 +22,9 @@ const util = require("util"); const redUtil = require("@node-red/util").util; const flowUtil = require("./util"); + +const credentials = require("../credentials"); + var Log; /** @@ -38,6 +41,9 @@ function evaluateInputValue(value, type, node) { if (type === "bool") { return (value === "true") || (value === true); } + if (type === "cred") { + return value; + } return redUtil.evaluateNodeProperty(value, type, node, null, null); } @@ -142,10 +148,16 @@ class Subflow extends Flow { this.node_map = node_map; this.path = parent.path+"/"+(subflowInstance._alias||subflowInstance.id); + this.templateCredentials = credentials.get(subflowDef.id); + this.instanceCredentials = credentials.get(this.id); + var env = []; if (this.subflowDef.env) { this.subflowDef.env.forEach(e => { env[e.name] = e; + if (e.type === "cred") { + e.value = this.templateCredentials[e.name]; + } }); } if (this.subflowInstance.env) { @@ -154,7 +166,14 @@ class Subflow extends Flow { var ui = old ? old.ui : null; env[e.name] = e; if (ui) { - env[e.name].ui = ui; + env[e.name].ui = ui; + } + if (e.type === "cred") { + if (!old || this.instanceCredentials.hasOwnProperty(e.name) ) { + e.value = this.instanceCredentials[e.name]; + } else if (old) { + e.value = this.templateCredentials[e.name]; + } } }); } @@ -324,7 +343,6 @@ class Subflow extends Flow { * @return {Object} val value of env var */ getSetting(name) { - this.trace("getSetting:"+name); if (!/^\$parent\./.test(name)) { var env = this.env; var is_info = name.endsWith("_info"); @@ -333,7 +351,6 @@ class Subflow extends Flow { if (env && env.hasOwnProperty(ename)) { var val = env[ename]; - if (is_type) { return val ? val.type : undefined; } From cc177533e8dd0d99e991e9b962c4c1f854aa4156 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 1 Nov 2019 15:12:29 +0000 Subject: [PATCH 048/346] Dont export subflow template creds by default --- packages/node_modules/@node-red/editor-client/src/js/nodes.js | 1 - 1 file changed, 1 deletion(-) 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 76dc4d194..cdf2c8796 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 @@ -587,7 +587,6 @@ RED.nodes = (function() { } function convertSubflow(n, exportCreds) { - exportCreds = true; var node = {}; node.id = n.id; node.type = n.type; From 634a51635c5138dd61322369d23a7649d95e02a3 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 10 Feb 2020 18:55:03 +0000 Subject: [PATCH 049/346] Battling Chrome Autocomplete, part 31: Wrap search input with form --- .../@node-red/editor-client/src/js/ui/common/searchBox.js | 5 ++++- .../@node-red/editor-client/src/js/ui/keyboard.js | 4 ++-- .../src/js/ui/projects/projectUserSettings.js | 8 ++++---- .../editor-client/src/sass/ui/common/searchBox.scss | 3 +++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js index 214668485..b707ccbf5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/searchBox.js @@ -38,7 +38,10 @@ this.element.addClass("red-ui-searchBox-input"); this.uiContainer = this.element.wrap("
        ").parent(); this.uiContainer.addClass("red-ui-searchBox-container"); - + if (this.element.parents("form").length === 0) { + var form = this.element.wrap("
        ").parent(); + form.addClass("red-ui-searchBox-form"); + } $('').prependTo(this.uiContainer); this.clearButton = $('').appendTo(this.uiContainer); this.clearButton.on("click",function(e) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js index 524c1c16e..3e79f6eb1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/keyboard.js @@ -524,12 +524,12 @@ RED.keyboard = (function() { var pane = $('
        '); $('
        '+ - '
        '+ + '
        '+ '
        '+ '
        '+ '
        ').appendTo(pane); - pane.find("input").searchBox({ + pane.find("#red-ui-settings-tab-keyboard-filter").searchBox({ delay: 100, change: function() { var filterValue = $(this).val().trim(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js index 4219b189f..170aacea0 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectUserSettings.js @@ -30,13 +30,13 @@ RED.projects.userSettings = (function() { $('
        ').appendTo(gitconfigContainer).text(RED._("editor:sidebar.project.userSettings.committerTip")); var row = $('
        ').appendTo(gitconfigContainer); - $('').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row); - gitUsernameInput = $('').appendTo(row); + $('').text(RED._("editor:sidebar.project.userSettings.userName")).appendTo(row); + gitUsernameInput = $('').appendTo(row); gitUsernameInput.val(currentGitSettings.user.name||""); row = $('
        ').appendTo(gitconfigContainer); - $('').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row); - gitEmailInput = $('').appendTo(row); + $('').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row); + gitEmailInput = $('').appendTo(row); gitEmailInput.val(currentGitSettings.user.email||""); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss index 2f0740d61..d0cd631fc 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/searchBox.scss @@ -32,6 +32,9 @@ right: 5px; top: 9px; } + form.red-ui-searchBox-form { + margin: 0; + } input.red-ui-searchBox-input { border-radius: 0; border: none; From 5c199d3bb4430dba24f9d7ecd603b297c112f4a8 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Thu, 13 Feb 2020 01:35:33 +0900 Subject: [PATCH 050/346] Fix garbled characters in library (#2457) * update getFileBody * add suitable unit tests Co-authored-by: Hiroyuki Okada --- .../lib/storage/localfilesystem/library.js | 81 +++++++------------ .../storage/localfilesystem/library_spec.js | 80 +++++++++++++----- 2 files changed, 90 insertions(+), 71 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js index da0e1f94f..f72ce1e1e 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/library.js @@ -25,73 +25,54 @@ var settings; var libDir; var libFlowsDir; - -function getFileMeta(root,path) { - var fn = fspath.join(root,path); - var fd = fs.openSync(fn,"r"); +function getFileMeta(root, path) { + var fn = fspath.join(root, path); + var fd = fs.openSync(fn, 'r'); var size = fs.fstatSync(fd).size; var meta = {}; var read = 0; var length = 10; - var remaining = ""; + var remaining = Buffer.alloc(0); var buffer = Buffer.alloc(length); - while(read < size) { - read+=fs.readSync(fd,buffer,0,length); - var data = remaining+buffer.toString(); - var parts = data.split("\n"); - remaining = parts.splice(-1); - for (var i=0;i 0?"\n":"")+parts[i]; - } - } - if (!scanning) { - body += remaining; - } - } else { - read += thisRead; - body += buffer.slice(0,thisRead).toString(); + for (var i = 0; i < parts.length; i++) { + if (! /^\/\/ \w+: /.test(parts[i]) || !scanning) { + body += (body.length > 0 ? '\n' : '') + parts[i]; + scanning = false; } } - fs.closeSync(fd); return body; } + function getLibraryEntry(type,path) { var root = fspath.join(libDir,type); var rootPath = fspath.join(libDir,type,path); diff --git a/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js b/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js index 70152ea1c..69b6e3da6 100644 --- a/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js +++ b/test/unit/@node-red/runtime/lib/storage/localfilesystem/library_spec.js @@ -72,45 +72,52 @@ describe('storage/localfilesystem/library', function() { }); function createObjectLibrary(type) { - type = type ||"object"; - var objLib = path.join(userDir,"lib",type); + type = type || "object"; + var objLib = path.join(userDir, "lib", type); try { fs.mkdirSync(objLib); - } catch(err) { + } catch (err) { } - fs.mkdirSync(path.join(objLib,"A")); - fs.mkdirSync(path.join(objLib,"B")); - fs.mkdirSync(path.join(objLib,"B","C")); + fs.mkdirSync(path.join(objLib, "A")); + fs.mkdirSync(path.join(objLib, "B")); + fs.mkdirSync(path.join(objLib, "B", "C")); + fs.mkdirSync(path.join(objLib, "D")); if (type === "functions" || type === "object") { - fs.writeFileSync(path.join(objLib,"file1.js"),"// abc: def\n// not a metaline \n\n Hi",'utf8'); - fs.writeFileSync(path.join(objLib,"B","file2.js"),"// ghi: jkl\n// not a metaline \n\n Hi",'utf8'); + fs.writeFileSync(path.join(objLib, "file1.js"), "// abc: def\n// not a metaline \n\n Hi", 'utf8'); + fs.writeFileSync(path.join(objLib, "B", "file2.js"), "// ghi: jkl\n// not a metaline \n\n Hi", 'utf8'); + fs.writeFileSync(path.join(objLib, "D", "file3.js"), "// mno: 日本語テスト\n\nこんにちわ", 'utf8'); } if (type === "flows" || type === "object") { - fs.writeFileSync(path.join(objLib,"B","flow.json"),"Hi",'utf8'); + fs.writeFileSync(path.join(objLib, "B", "flow.json"), "Hi", 'utf8'); } } - it('should return a directory listing of library objects',function(done) { - localfilesystemLibrary.init({userDir:userDir}).then(function() { + it('should return a directory listing of library objects', function (done) { + localfilesystemLibrary.init({userDir: userDir}).then(function () { createObjectLibrary(); - localfilesystemLibrary.getLibraryEntry('object','').then(function(flows) { - flows.should.eql([ 'A', 'B', { abc: 'def', fn: 'file1.js' } ]); - localfilesystemLibrary.getLibraryEntry('object','B').then(function(flows) { - flows.should.eql([ 'C', { ghi: 'jkl', fn: 'file2.js' }, { fn: 'flow.json' } ]); - localfilesystemLibrary.getLibraryEntry('object','B/C').then(function(flows) { + localfilesystemLibrary.getLibraryEntry('object', '').then(function (flows) { + flows.should.eql([ 'A', 'B', 'D', { abc: 'def', fn: 'file1.js' }]); + localfilesystemLibrary.getLibraryEntry('object', 'B').then(function (flows) { + flows.should.eql([ 'C', { ghi: 'jkl', fn: 'file2.js' }, { fn: 'flow.json' }]); + localfilesystemLibrary.getLibraryEntry('object', 'B/C').then(function (flows) { flows.should.eql([]); - done(); - }).catch(function(err) { + localfilesystemLibrary.getLibraryEntry('object', 'D').then(function (flows) { + flows.should.eql([{ mno: '日本語テスト', fn: 'file3.js' }]); + done(); + }).catch(function (err) { + done(err); + }); + }).catch(function (err) { done(err); }); - }).catch(function(err) { + }).catch(function (err) { done(err); }); - }).catch(function(err) { + }).catch(function (err) { done(err); }); - }).catch(function(err) { + }).catch(function (err) { done(err); }); }); @@ -203,4 +210,35 @@ describe('storage/localfilesystem/library', function() { done(err); }); }); + + it('should return a newly saved library flow (multi-byte character)',function(done) { + localfilesystemLibrary.init({userDir:userDir}).then(function() { + createObjectLibrary("flows"); + localfilesystemLibrary.getLibraryEntry('flows','B').then(function(flows) { + flows.should.eql([ 'C', {fn:'flow.json'} ]); + var ft = path.join("B","D","file4"); + localfilesystemLibrary.saveLibraryEntry('flows',ft,{mno:'pqr'},"こんにちわこんにちわこんにちわ").then(function() { + setTimeout(function() { + localfilesystemLibrary.getLibraryEntry('flows',path.join("B","D")).then(function(flows) { + flows.should.eql([ { mno: 'pqr', fn: 'file4.json' } ]); + localfilesystemLibrary.getLibraryEntry('flows',ft+".json").then(function(body) { + body.should.eql("こんにちわこんにちわこんにちわ"); + done(); + }).catch(function(err) { + done(err); + }); + }).catch(function(err) { + done(err); + }) + }, 50); + }).catch(function(err) { + done(err); + }); + }).catch(function(err) { + done(err); + }); + }).catch(function(err) { + done(err); + }); + }); }); From d6b5494625f3eb67a6c6b3e2a0d9636125c89db1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 13 Feb 2020 16:44:48 +0000 Subject: [PATCH 051/346] Allow credentials to be provided as part of /flows api --- .../@node-red/runtime/lib/api/flows.js | 4 ++- .../runtime/lib/nodes/flows/index.js | 36 +++++++++++++------ .../@node-red/runtime/lib/api/flows_spec.js | 20 ++++++++--- .../runtime/lib/nodes/flows/index_spec.js | 36 ++++++++++++++++++- 4 files changed, 80 insertions(+), 16 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/api/flows.js b/packages/node_modules/@node-red/runtime/lib/api/flows.js index 1e6e72dc7..e58531190 100644 --- a/packages/node_modules/@node-red/runtime/lib/api/flows.js +++ b/packages/node_modules/@node-red/runtime/lib/api/flows.js @@ -57,6 +57,8 @@ var api = module.exports = { * Sets the current flow configuration * @param {Object} opts * @param {User} opts.user - the user calling the api + * @param {Object} opts.flows - the flow configuration: `{flows: [..], credentials: {}}` + * @param {Object} opts.deploymentType - the type of deployment - "full", "nodes", "flows", "reload" * @param {Object} opts.req - the request to log (optional) * @return {Promise} - the active flow configuration * @memberof @node-red/runtime_flows @@ -83,7 +85,7 @@ var api = module.exports = { return reject(err); } } - apiPromise = runtime.nodes.setFlows(flows.flows,deploymentType); + apiPromise = runtime.nodes.setFlows(flows.flows,flows.credentials,deploymentType); } apiPromise.then(function(flowId) { return resolve({rev:flowId}); diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js index 981ed7173..16166fbc1 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/index.js @@ -106,15 +106,20 @@ function load(forceStart) { // This is a force reload from the API - disable safeMode delete settings.safeMode; } - return setFlows(null,"load",false,forceStart); + return setFlows(null,null,"load",false,forceStart); } /* * _config - new node array configuration + * _credentials - new credentials configuration (optional) * type - full/nodes/flows/load (default full) * muteLog - don't emit the standard log messages (used for individual flow api) */ -function setFlows(_config,type,muteLog,forceStart) { +function setFlows(_config,_credentials,type,muteLog,forceStart) { + if (typeof _credentials === "string") { + type = _credentials; + _credentials = null; + } type = type||"full"; if (settings.safeMode) { if (type !== "load") { @@ -155,16 +160,27 @@ function setFlows(_config,type,muteLog,forceStart) { delete newFlowConfig.allNodes[id].credentials; } } + var credsDirty; - // Allow the credential store to remove anything no longer needed - credentials.clean(config); + if (_credentials) { + // A full set of credentials have been provided. Use those instead + configSavePromise = credentials.load(_credentials); + credsDirty = true; + } else { + // Allow the credential store to remove anything no longer needed + credentials.clean(config); - // Remember whether credentials need saving or not - var credsDirty = credentials.dirty(); + // Remember whether credentials need saving or not + var credsDirty = credentials.dirty(); + + configSavePromise = Promise.resolve(); + } // Get the latest credentials and ask storage to save them (if needed) // as well as the new flow configuration. - configSavePromise = credentials.export().then(function(creds) { + configSavePromise = configSavePromise.then(function() { + return credentials.export() + }).then(function(creds) { var saveConfig = { flows: config, credentialsDirty:credsDirty, @@ -515,7 +531,7 @@ function addFlow(flow) { var newConfig = clone(activeConfig.flows); newConfig = newConfig.concat(nodes); - return setFlows(newConfig,'flows',true).then(function() { + return setFlows(newConfig,null,'flows',true).then(function() { log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"})); return flow.id; }); @@ -646,7 +662,7 @@ function updateFlow(id,newFlow) { } newConfig = newConfig.concat(nodes); - return setFlows(newConfig,'flows',true).then(function() { + return setFlows(newConfig,null,'flows',true).then(function() { log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"})); }) } @@ -668,7 +684,7 @@ function removeFlow(id) { return node.z !== id && node.id !== id; }); - return setFlows(newConfig,'flows',true).then(function() { + return setFlows(newConfig,null,'flows',true).then(function() { log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"})); }); } diff --git a/test/unit/@node-red/runtime/lib/api/flows_spec.js b/test/unit/@node-red/runtime/lib/api/flows_spec.js index a7c85efa2..dafbbc69b 100644 --- a/test/unit/@node-red/runtime/lib/api/flows_spec.js +++ b/test/unit/@node-red/runtime/lib/api/flows_spec.js @@ -53,7 +53,7 @@ describe("runtime-api/flows", function() { var loadFlows; var reloadError = false; beforeEach(function() { - setFlows = sinon.spy(function(flows,type) { + setFlows = sinon.spy(function(flows,credentials,type) { if (flows[0] === "error") { var err = new Error("error"); err.code = "error"; @@ -91,7 +91,19 @@ describe("runtime-api/flows", function() { result.should.eql({rev:"newRev"}); setFlows.called.should.be.true(); setFlows.lastCall.args[0].should.eql([4,5,6]); - setFlows.lastCall.args[1].should.eql("full"); + setFlows.lastCall.args[2].should.eql("full"); + done(); + }).catch(done); + }); + it("includes credentials when part of the request", function(done) { + flows.setFlows({ + flows: {flows:[4,5,6], credentials: {$:"creds"}}, + }).then(function(result) { + result.should.eql({rev:"newRev"}); + setFlows.called.should.be.true(); + setFlows.lastCall.args[0].should.eql([4,5,6]); + setFlows.lastCall.args[1].should.eql({$:"creds"}); + setFlows.lastCall.args[2].should.eql("full"); done(); }).catch(done); }); @@ -103,7 +115,7 @@ describe("runtime-api/flows", function() { result.should.eql({rev:"newRev"}); setFlows.called.should.be.true(); setFlows.lastCall.args[0].should.eql([4,5,6]); - setFlows.lastCall.args[1].should.eql("nodes"); + setFlows.lastCall.args[2].should.eql("nodes"); done(); }).catch(done); }); @@ -125,7 +137,7 @@ describe("runtime-api/flows", function() { result.should.eql({rev:"newRev"}); setFlows.called.should.be.true(); setFlows.lastCall.args[0].should.eql([4,5,6]); - setFlows.lastCall.args[1].should.eql("nodes"); + setFlows.lastCall.args[2].should.eql("nodes"); done(); }).catch(done); }); diff --git a/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js b/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js index a37d88f78..8865c5d3f 100644 --- a/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/flows/index_spec.js @@ -67,7 +67,10 @@ describe('flows/index', function() { }); return when.resolve(); }); - credentialsLoad = sinon.stub(credentials,"load",function() { + credentialsLoad = sinon.stub(credentials,"load",function(creds) { + if (creds && creds.hasOwnProperty("$") && creds['$'] === "fail") { + return when.reject("creds error"); + } return when.resolve(); }); flowCreate = sinon.stub(Flow,"create",function(parent, global, flow) { @@ -177,6 +180,23 @@ describe('flows/index', function() { }); }); + it('sets the full flow including credentials', function(done) { + var originalConfig = [ + {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, + {id:"t1",type:"tab"} + ]; + var credentials = {"t1-1":{"a":1}}; + + flows.init({log:mockLog, settings:{},storage:storage}); + flows.setFlows(originalConfig,credentials).then(function() { + credentialsClean.called.should.be.false(); + credentialsLoad.called.should.be.true(); + credentialsLoad.lastCall.args[0].should.eql(credentials); + flows.getFlows().flows.should.eql(originalConfig); + done(); + }); + }); + it('updates existing flows with partial deployment - nodes', function(done) { var originalConfig = [ {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, @@ -235,6 +255,20 @@ describe('flows/index', function() { }); }); + it('returns error if it cannot decrypt credentials', function(done) { + var originalConfig = [ + {id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]}, + {id:"t1",type:"tab"} + ]; + var credentials = {"$":"fail"}; + + flows.init({log:mockLog, settings:{},storage:storage}); + flows.setFlows(originalConfig,credentials).then(function() { + done("Unexpected success when credentials couldn't be decrypted") + }).catch(function(err) { + done(); + }); + }); }); describe('#load', function() { From 569b9f3d0693f4feb60674bcd6899f3ddf8931a5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 13 Feb 2020 22:39:59 +0000 Subject: [PATCH 052/346] Track context sidebar element paths to track formatting changes Fixes #2460 --- .../@node-red/editor-client/src/js/ui/tab-context.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js index 3bc41b982..9994d5000 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js @@ -231,7 +231,8 @@ RED.sidebar.context = (function() { RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), { typeHint: data.format, sourceId: id+"."+k, - tools: tools + tools: tools, + path: "" }).appendTo(propRow.children()[1]); } }) @@ -275,7 +276,8 @@ RED.sidebar.context = (function() { RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), { typeHint: data.format, sourceId: id+"."+k, - tools: tools + tools: tools, + path: "" }).appendTo(propRow.children()[1]); } }); @@ -295,7 +297,8 @@ RED.sidebar.context = (function() { RED.utils.createObjectElement(RED.utils.decodeObject(payload,format), { typeHint: v.format, sourceId: id+"."+k, - tools: tools + tools: tools, + path: "" }).appendTo(propRow.children()[1]); if (contextStores.length > 1) { $("",{class:"red-ui-sidebar-context-property-storename"}).text(v.store).appendTo($(propRow.children()[0])) From e3dab3cf2056849cccde01d4a0550ca59dff8d26 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 14 Feb 2020 16:14:52 +0000 Subject: [PATCH 053/346] Ensure catalog load errors are logged to the console --- .../@node-red/editor-client/src/js/ui/palette-editor.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index ee5221554..b2dd38e35 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -381,6 +381,7 @@ RED.palette.editor = (function() { handleCatalogResponse(null,catalog,index,v); refreshNodeModuleList(); }).fail(function(jqxhr, textStatus, error) { + console.warn("Error loading catalog",catalog,":",error); handleCatalogResponse(jqxhr,catalog,index); }).always(function() { handled++; From 127b3619795970cb52f9378adddba46125e63d71 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 14 Feb 2020 20:13:37 -0500 Subject: [PATCH 054/346] change PR to only use a single property for the 2nd output --- .../@node-red/nodes/core/function/89-trigger.html | 4 +++- .../node_modules/@node-red/nodes/core/function/89-trigger.js | 2 +- test/nodes/core/function/89-trigger_spec.js | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html index 79b022519..8917f81d8 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html @@ -88,7 +88,6 @@ op2type: {value:"val"}, duration: {value:"250",required:true,validate:RED.validators.number()}, extend: {value:"false"}, - second: {value:false}, units: {value:"ms"}, reset: {value:""}, bytopic: {value:"all"}, @@ -123,6 +122,9 @@ $("#node-stream-topic").show(); } }); + + if (this.outputs == 2) { $("#node-input-second").prop('checked', true) } + else { $("#node-input-second").prop('checked', false) } $("#node-input-second").change(function() { if ($("#node-input-second").is(":checked")) { diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index dab7a83ab..e3fca00aa 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -24,7 +24,7 @@ module.exports = function(RED) { this.op2 = n.op2 || "0"; this.op1type = n.op1type || "str"; this.op2type = n.op2type || "str"; - this.second = n.second || false; + this.second = (n.outputs == 2) ? true : false; this.topic = n.topic || "topic"; if (this.op1type === 'val') { diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index 582b47904..f13e23a5e 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -827,7 +827,7 @@ describe('trigger node', function() { }); it('should be able to send 2nd message to a 2nd output', function(done) { - var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"hello", op2:"world", duration:"50", second:true, wires:[["n2"],["n3"]] }, + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"hello", op2:"world", duration:"50", outputs:2, wires:[["n2"],["n3"]] }, {id:"n2", type:"helper"}, {id:"n3", type:"helper"} ]; helper.load(triggerNode, flow, function() { var n1 = helper.getNode("n1"); From c0d007ffa93800f1318553744f223e053ae13dcb Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Sun, 16 Feb 2020 23:07:05 +0900 Subject: [PATCH 055/346] add option support for overwriting settiings.js --- packages/node_modules/node-red/red.js | 56 ++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/node-red/red.js b/packages/node_modules/node-red/red.js index 26b6d1fc8..5fb99bffa 100755 --- a/packages/node_modules/node-red/red.js +++ b/packages/node_modules/node-red/red.js @@ -39,7 +39,8 @@ var knownOpts = { "title": String, "userDir": [path], "verbose": Boolean, - "safe": Boolean + "safe": Boolean, + "define": [String, Array] }; var shortHands = { "?":["--help"], @@ -49,7 +50,8 @@ var shortHands = { // doesn't get treated as --title "t":["--help"], "u":["--userDir"], - "v":["--verbose"] + "v":["--verbose"], + "D":["--define"] }; nopt.invalidHandler = function(k,v,t) { // TODO: console.log(k,v,t); @@ -57,6 +59,28 @@ nopt.invalidHandler = function(k,v,t) { var parsedArgs = nopt(knownOpts,shortHands,process.argv,2) +/** + * Set property of specified object. + * @param {Object} obj - target object + * @param {string} path - "." separated property path + * @param {Object} val - value to be set + */ +function setProperty(obj, path, val) { + var paths = path.split("."); + if (paths.length > 0) { + var o = obj; + for (var i = 0; i < paths.length -1; i++) { + var path = paths[i]; + if (!o.hasOwnProperty(path)) { + o[path] = {}; + } + o = o[path]; + } + var key = paths[paths.length-1]; + o[key] = val; + } +} + if (parsedArgs.help) { console.log("Node-RED v"+RED.version()); console.log("Usage: node-red [-v] [-?] [--settings settings.js] [--userDir DIR]"); @@ -69,6 +93,7 @@ if (parsedArgs.help) { console.log(" -u, --userDir DIR use specified user directory"); console.log(" -v, --verbose enable verbose output"); console.log(" --safe enable safe mode"); + console.log(" -D, --define X=Y overwrite value in settings file"); console.log(" -?, --help show this help"); console.log(""); console.log("Documentation can be found at http://nodered.org"); @@ -130,6 +155,33 @@ try { process.exit(); } +if (parsedArgs.define) { + var defs = parsedArgs.define; + defs.forEach(function (def) { + try { + var match = /^(([^=]+)=(.+)|@(.*))$/.exec(def); + if (match) { + if (!match[4]) { + var val = JSON.parse(match[3]); + setProperty(settings, match[2], val); + } + else { + var obj = fs.readJsonSync(match[4]); + Object.entries(obj).forEach(([key, val]) => { + settings[key] = val; + }); + } + } + else { + throw new Error("Unexpected option: "+def); + } + } + catch (e) { + console.log(e); + } + }); +} + if (parsedArgs.verbose) { settings.verbose = true; } From 42b841cb7854d97bf774d5ddf27582ccd5eacbb2 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 17 Feb 2020 13:29:06 +0900 Subject: [PATCH 056/346] Update XPath to the latest --- test/editor/pageobjects/editor/workspace_page.js | 6 +++--- .../pageobjects/nodes/core/function/15-change_page.js | 6 +++--- .../editor/pageobjects/nodes/core/parsers/70-JSON_page.js | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/editor/pageobjects/editor/workspace_page.js b/test/editor/pageobjects/editor/workspace_page.js index ddf0a1c60..92a725dac 100644 --- a/test/editor/pageobjects/editor/workspace_page.js +++ b/test/editor/pageobjects/editor/workspace_page.js @@ -42,7 +42,7 @@ function addNode(type, x, y) { } } browser.waitForVisible('#red-ui-palette-search'); - browser.setValue('//*[@id="red-ui-palette-search"]/div/input', type.replace(/([A-Z])/g,' $1').toLowerCase()); + browser.setValue('//*[@id="red-ui-palette-search"]/div/form/input', type.replace(/([A-Z])/g, ' $1').toLowerCase()); browser.pause(300); browser.waitForVisible(palette.getId(type)); browser.moveToObject(palette.getId(type)); @@ -66,8 +66,8 @@ function deleteAllNodes() { function deploy() { browser.call(function () { - return when.promise(function(resolve, reject) { - events.on("runtime-event", function(event) { + return when.promise(function (resolve, reject) { + events.on("runtime-event", function (event) { if (event.id === 'runtime-deploy') { events.removeListener("runtime-event", arguments.callee); resolve(); diff --git a/test/editor/pageobjects/nodes/core/function/15-change_page.js b/test/editor/pageobjects/nodes/core/function/15-change_page.js index 8e72afe38..59ac4f2f1 100644 --- a/test/editor/pageobjects/nodes/core/function/15-change_page.js +++ b/test/editor/pageobjects/nodes/core/function/15-change_page.js @@ -14,9 +14,9 @@ * limitations under the License. **/ -var util = require("util"); +var util = require('util'); -var nodePage = require("../../node_page"); +var nodePage = require('../../node_page'); function changeNode(id) { nodePage.call(this, id); @@ -85,7 +85,7 @@ changeNode.prototype.ruleMove = function (p, to, index) { } changeNode.prototype.addRule = function () { - browser.clickWithWait('//*[@id="dialog-form"]/div[3]/div/a'); + browser.clickWithWait('//*[@id="dialog-form"]/div[5]/div/a'); } module.exports = changeNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js index 10a7e648f..875c3b013 100644 --- a/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js +++ b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js @@ -14,9 +14,9 @@ * limitations under the License. **/ -var util = require("util"); +var util = require('util'); -var nodePage = require("../../node_page"); +var nodePage = require('../../node_page'); function jsonNode(id) { nodePage.call(this, id); @@ -28,8 +28,8 @@ jsonNode.prototype.setAction = function (action) { browser.setValue('node-input-action', action); } -jsonNode.prototype.setProperty = function(property) { - browser.setValue('//*[@id="dialog-form"]/div[2]/div/div/input', property); +jsonNode.prototype.setProperty = function (property) { + browser.setValue('//*[@id="dialog-form"]/div[4]/div/div[1]/input', property); } module.exports = jsonNode; From 2e389995064625aa1ac132651c55415ebe88522d Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 17 Feb 2020 13:57:01 +0900 Subject: [PATCH 057/346] Add UI test cases for data formats --- .../editor/pageobjects/editor/palette_page.js | 21 +- .../nodes/core/parsers/70-CSV_page.js | 51 +++ .../nodes/core/parsers/70-HTML_page.js | 6 +- .../nodes/core/parsers/70-XML_page.js | 35 ++ .../nodes/core/parsers/70-YAML_page.js | 35 ++ .../nodes/core/sequence/17-join_page.js | 27 ++ .../nodes/core/sequence/17-split_page.js | 27 ++ .../pageobjects/nodes/nodefactory_page.js | 11 + .../scenario/cookbook_dataformats_uispec.js | 364 ++++++++++++++++++ 9 files changed, 567 insertions(+), 10 deletions(-) create mode 100644 test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js create mode 100644 test/editor/pageobjects/nodes/core/parsers/70-XML_page.js create mode 100644 test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js create mode 100644 test/editor/pageobjects/nodes/core/sequence/17-join_page.js create mode 100644 test/editor/pageobjects/nodes/core/sequence/17-split_page.js create mode 100644 test/editor/specs/scenario/cookbook_dataformats_uispec.js diff --git a/test/editor/pageobjects/editor/palette_page.js b/test/editor/pageobjects/editor/palette_page.js index a35859c5f..97077059b 100644 --- a/test/editor/pageobjects/editor/palette_page.js +++ b/test/editor/pageobjects/editor/palette_page.js @@ -15,22 +15,29 @@ **/ var idMap = { - // input + // common "inject": ".red-ui-palette-node[data-palette-type='inject']", - "httpIn": ".red-ui-palette-node[data-palette-type='http in']", - "mqttIn": ".red-ui-palette-node[data-palette-type='mqtt in']", - // output "debug": ".red-ui-palette-node[data-palette-type='debug']", - "httpResponse": ".red-ui-palette-node[data-palette-type='http response']", - "mqttOut": ".red-ui-palette-node[data-palette-type='mqtt out']", // function "function": ".red-ui-palette-node[data-palette-type='function']", - "template": ".red-ui-palette-node[data-palette-type='template']", "change": ".red-ui-palette-node[data-palette-type='change']", "range": ".red-ui-palette-node[data-palette-type='range']", + "template": ".red-ui-palette-node[data-palette-type='template']", + // network + "mqttIn": ".red-ui-palette-node[data-palette-type='mqtt in']", + "mqttOut": ".red-ui-palette-node[data-palette-type='mqtt out']", + "httpIn": ".red-ui-palette-node[data-palette-type='http in']", + "httpResponse": ".red-ui-palette-node[data-palette-type='http response']", "httpRequest": ".red-ui-palette-node[data-palette-type='http request']", + // sequence + "join": ".red-ui-palette-node[data-palette-type='join']", + "split": ".red-ui-palette-node[data-palette-type='split']", + // parser + "csv": ".red-ui-palette-node[data-palette-type='csv']", "html": ".red-ui-palette-node[data-palette-type='html']", "json": ".red-ui-palette-node[data-palette-type='json']", + "xml": ".red-ui-palette-node[data-palette-type='xml']", + "yaml": ".red-ui-palette-node[data-palette-type='yaml']", // storage "fileIn": ".red-ui-palette-node[data-palette-type='file in']", }; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js b/test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js new file mode 100644 index 000000000..e4bc9502c --- /dev/null +++ b/test/editor/pageobjects/nodes/core/parsers/70-CSV_page.js @@ -0,0 +1,51 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function csvNode(id) { + nodePage.call(this, id); +} + +util.inherits(csvNode, nodePage); + +csvNode.prototype.setColumns = function (columns) { + browser.setValue('#node-input-temp', columns); +} + +csvNode.prototype.setSkipLines = function (skip) { + browser.setValue('#node-input-skip', skip); +} + +csvNode.prototype.setFirstRow4Names = function (checkbox) { + if (browser.isSelected('#node-input-hdrin') !== checkbox) { + browser.click('#node-input-hdrin'); + } +} + +csvNode.prototype.setOutput = function (output) { + browser.selectWithWait('#node-input-multi', output); +} + +csvNode.prototype.setIncludeRow = function (checkbox) { + if (browser.isSelected('#node-input-hdrout') !== checkbox) { + browser.click('#node-input-hdrout'); + } +} + +module.exports = csvNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js index 9c89bc8e2..243e4c905 100644 --- a/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js +++ b/test/editor/pageobjects/nodes/core/parsers/70-HTML_page.js @@ -14,9 +14,9 @@ * limitations under the License. **/ -var util = require("util"); +var util = require('util'); -var nodePage = require("../../node_page"); +var nodePage = require('../../node_page'); function htmlNode(id) { nodePage.call(this, id); @@ -24,7 +24,7 @@ function htmlNode(id) { util.inherits(htmlNode, nodePage); -htmlNode.prototype.setSelector = function(tag) { +htmlNode.prototype.setSelector = function (tag) { browser.setValue('#node-input-tag', tag); } diff --git a/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js new file mode 100644 index 000000000..2a8be7d95 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js @@ -0,0 +1,35 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function xmlNode(id) { + nodePage.call(this, id); +} + +util.inherits(xmlNode, nodePage); + +xmlNode.prototype.setAction = function (action) { + browser.setValue('node-input-action', action); +} + +xmlNode.prototype.setProperty = function (property) { + browser.setValue('//*[@id="dialog-form"]/div[3]/div/div[1]/input', property); +} + +module.exports = xmlNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js new file mode 100644 index 000000000..16e4a678d --- /dev/null +++ b/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js @@ -0,0 +1,35 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function yamlNode(id) { + nodePage.call(this, id); +} + +util.inherits(yamlNode, nodePage); + +yamlNode.prototype.setAction = function (action) { + browser.setValue('node-input-action', action); +} + +yamlNode.prototype.setProperty = function (property) { + browser.setValue('//*[@id="dialog-form"]/div[3]/div/div[1]/input', property); +} + +module.exports = yamlNode; diff --git a/test/editor/pageobjects/nodes/core/sequence/17-join_page.js b/test/editor/pageobjects/nodes/core/sequence/17-join_page.js new file mode 100644 index 000000000..9426a64f9 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/sequence/17-join_page.js @@ -0,0 +1,27 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function joinNode(id) { + nodePage.call(this, id); +} + +util.inherits(joinNode, nodePage); + +module.exports = joinNode; diff --git a/test/editor/pageobjects/nodes/core/sequence/17-split_page.js b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js new file mode 100644 index 000000000..9426a64f9 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js @@ -0,0 +1,27 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function joinNode(id) { + nodePage.call(this, id); +} + +util.inherits(joinNode, nodePage); + +module.exports = joinNode; diff --git a/test/editor/pageobjects/nodes/nodefactory_page.js b/test/editor/pageobjects/nodes/nodefactory_page.js index 744c1570d..05ac8b4d4 100644 --- a/test/editor/pageobjects/nodes/nodefactory_page.js +++ b/test/editor/pageobjects/nodes/nodefactory_page.js @@ -25,8 +25,13 @@ var mqttOutNode = require('./core/network/10-mqttout_page'); var httpInNode = require('./core/network/21-httpin_page'); var httpResponseNode = require('./core/network/21-httpresponse_page'); var httpRequestNode = require('./core/network/21-httprequest_page'); +var splitNode = require('./core/sequence/17-split_page'); +var joinNode = require('./core/sequence/17-join_page'); +var csvNode = require('./core/parsers/70-CSV_page'); var htmlNode = require('./core/parsers/70-HTML_page'); var jsonNode = require('./core/parsers/70-JSON_page'); +var xmlNode = require('./core/parsers/70-XML_page'); +var yamlNode = require('./core/parsers/70-YAML_page'); var fileInNode = require('./core/storage/10-filein_page'); var nodeCatalog = { @@ -44,9 +49,15 @@ var nodeCatalog = { "httpIn": httpInNode, "httpResponse": httpResponseNode, "httpRequest": httpRequestNode, + // sequence + "split": splitNode, + "join": joinNode, // parser + "csv": csvNode, "html": htmlNode, "json": jsonNode, + "xml": xmlNode, + "yaml": yamlNode, // storage "fileIn": fileInNode }; diff --git a/test/editor/specs/scenario/cookbook_dataformats_uispec.js b/test/editor/specs/scenario/cookbook_dataformats_uispec.js new file mode 100644 index 000000000..23bb4fb1b --- /dev/null +++ b/test/editor/specs/scenario/cookbook_dataformats_uispec.js @@ -0,0 +1,364 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('working with data formats', function () { + it('convert to/from JSON', function () { + var injectNode1 = workspace.addNode('inject'); + var jsonNode1 = workspace.addNode('json'); + var debugNode1 = workspace.addNode('debug'); + + injectNode1.edit(); + injectNode1.setPayload('str', '{"a":1}'); + injectNode1.clickOk(); + + jsonNode1.edit(); + jsonNode1.setProperty('payload'); + jsonNode1.clickOk(); + + injectNode1.connect(jsonNode1); + jsonNode1.connect(debugNode1); + + var injectNode2 = workspace.addNode('inject'); + var jsonNode2 = workspace.addNode('json'); + var debugNode2 = workspace.addNode('debug'); + + injectNode2.edit(); + injectNode2.setPayload('json', '{"a":1}'); + injectNode2.clickOk(); + + jsonNode2.edit(); + jsonNode2.setProperty('payload'); + jsonNode2.clickOk(); + + injectNode2.connect(jsonNode2); + jsonNode2.connect(debugNode2); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage().should.eql('1'); + debugTab.clearMessage(); + injectNode2.clickLeftButton(); + debugTab.getMessage().should.eql('"{"a":1}"'); + }); + + it('convert to/from XML', function () { + var injectNode1 = workspace.addNode('inject', 0); + var templateNode1 = workspace.addNode('template', 200); + var xmlNode1 = workspace.addNode('xml', 400); + var debugNode1 = workspace.addNode('debug', 600); + + injectNode1.edit(); + injectNode1.setPayload('str', '{"a":1}'); + injectNode1.clickOk(); + + templateNode1.edit(); + templateNode1.setFormat('text'); + templateNode1.setSyntax('plain'); + templateNode1.setTemplate('' + + ' Nick' + + ' Dave' + + ' Reminder' + + ' Update the website' + + ''); + templateNode1.clickOk(); + + xmlNode1.edit(); + xmlNode1.setProperty('payload'); + xmlNode1.clickOk(); + + injectNode1.connect(templateNode1); + templateNode1.connect(xmlNode1); + xmlNode1.connect(debugNode1); + + var injectNode2 = workspace.addNode('inject'); + var xmlNode2 = workspace.addNode('xml'); + var debugNode2 = workspace.addNode('debug'); + + injectNode2.edit(); + injectNode2.setPayload('json', '{' + + ' "note": {' + + ' "$": { "priority": "high" },' + + ' "to": [ "Nick" ],' + + ' "from": [ "Dave" ],' + + ' "heading": [ "Reminder" ],' + + ' "body": [ "Update the website" ]' + + ' }' + + '}'); + injectNode2.clickOk(); + + xmlNode2.edit(); + xmlNode2.setProperty('payload'); + xmlNode2.clickOk(); + + injectNode2.connect(xmlNode2); + xmlNode2.connect(debugNode2); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage().should.eql('object'); + debugTab.clearMessage(); + injectNode2.clickLeftButton(); + debugTab.getMessage().should.eql('"' + + '' + + 'Nick' + + 'Dave' + + 'Reminder' + + 'Update the website' + + '"'); + }); + + it('convert to/from YAML', function () { + var injectNode1 = workspace.addNode('inject', 0); + var templateNode1 = workspace.addNode('template', 200); + var yamlNode1 = workspace.addNode('yaml', 400); + var debugNode1 = workspace.addNode('debug', 600); + + injectNode1.edit(); + injectNode1.setPayload('str', '{"a":1}'); + injectNode1.clickOk(); + + templateNode1.edit(); + templateNode1.setFormat('yaml'); + templateNode1.setSyntax('plain'); + templateNode1.setTemplate('a: 1\n' + + 'b:\n' + + ' - 1\n' + + '- 2\n' + + '- 3'); + templateNode1.clickOk(); + + yamlNode1.edit(); + yamlNode1.setProperty('payload'); + yamlNode1.clickOk(); + + injectNode1.connect(templateNode1); + templateNode1.connect(yamlNode1); + yamlNode1.connect(debugNode1); + + var injectNode2 = workspace.addNode('inject'); + var yamlNode2 = workspace.addNode('yaml'); + var debugNode2 = workspace.addNode('debug'); + + injectNode2.edit(); + injectNode2.setPayload('json', '{"a":1, "b":[1,2,3]}'); + injectNode2.clickOk(); + + yamlNode2.edit(); + yamlNode2.setProperty('payload'); + yamlNode2.clickOk(); + + injectNode2.connect(yamlNode2); + yamlNode2.connect(debugNode2); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage().should.eql([ '1', 'array[3]' ]); + debugTab.clearMessage(); + injectNode2.clickLeftButton(); + debugTab.getMessage().should.eql('"a: 1↵b:↵ - 1↵ - 2↵ - 3↵"'); + }); + + it('generate CSV output', function () { + var injectNode1 = workspace.addNode('inject', 0); + var changeNode1 = workspace.addNode('change', 200); + var csvNode1 = workspace.addNode('csv', 400); + var debugNode1 = workspace.addNode('debug', 600); + + changeNode1.edit(); + changeNode1.ruleSet('payload', 'msg', '{' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + '}', 'jsonata'); + changeNode1.clickOk(); + + csvNode1.edit(); + csvNode1.setColumns('a,b,c'); + csvNode1.clickOk(); + + injectNode1.connect(changeNode1); + changeNode1.connect(csvNode1); + csvNode1.connect(debugNode1); + + var injectNode2 = workspace.addNode('inject', 0, 80); + var changeNode2 = workspace.addNode('change', 200, 80); + var csvNode2 = workspace.addNode('csv', 400, 80); + var debugNode2 = workspace.addNode('debug', 600, 80); + + changeNode2.edit(); + changeNode2.ruleSet('payload', 'msg', '[' + + ' {' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + ' }, {' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + ' }, {' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + ' }, {' + + ' "a": $floor(100*$random()),' + + ' "b": $floor(100*$random()),' + + ' "c": $floor(100*$random())' + + ' }' + + ']', 'jsonata'); + changeNode2.clickOk(); + + csvNode2.edit(); + csvNode2.setColumns('a,b,c'); + csvNode2.setIncludeRow(true); + csvNode2.clickOk(); + + injectNode2.connect(changeNode2); + changeNode2.connect(csvNode2); + csvNode2.connect(debugNode2); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage().should.match(/^"([1-9]?[0-9],){2}[1-9]?[0-9]↵"$/); + debugTab.clearMessage(); + injectNode2.clickLeftButton(); + debugTab.getMessage().should.match(/^"a,b,c↵(([1-9]?[0-9],){2}[1-9]?[0-9]↵){4}"$/); + }); + + it('parse CSV input', function () { + var injectNode = workspace.addNode('inject'); + var templateNode = workspace.addNode('template'); + var csvNode = workspace.addNode('csv'); + var debugNode = workspace.addNode('debug'); + + templateNode.edit(); + templateNode.setFormat('handlebars'); + templateNode.setSyntax('mustache'); + templateNode.setTemplate('# This is some random data\n' + + 'a,b,c\n' + + '80,18,2\n' + + '52,36,10\n' + + '91,18,61\n' + + '32,47,65'); + templateNode.clickOk(); + + csvNode.edit(); + csvNode.setSkipLines(1); + csvNode.setFirstRow4Names(true); + csvNode.setOutput('mult'); + csvNode.clickOk(); + + injectNode.connect(templateNode); + templateNode.connect(csvNode); + csvNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql([ 'object', 'object', 'object', 'object' ]); + }); + + it('simple GET request', function () { + var injectNode = workspace.addNode('inject'); + var httpRequestNode = workspace.addNode('httpRequest'); + var htmlNode = workspace.addNode('html'); + var debugNode = workspace.addNode('debug'); + + httpRequestNode.edit(); + httpRequestNode.setMethod('GET'); + httpRequestNode.setUrl('https://nodered.org'); + httpRequestNode.clickOk(); + + htmlNode.edit(); + htmlNode.setSelector('.node-red-latest-version'); + htmlNode.clickOk(); + + injectNode.connect(httpRequestNode); + httpRequestNode.connect(htmlNode); + htmlNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.match(/^"v[0-9]+\.[0-9]+\.[0-9]"$/); + }); + + it('split text into one message per line', function () { + var injectNode = workspace.addNode('inject'); + var templateNode = workspace.addNode('template'); + var splitNode = workspace.addNode('split'); + var changeNode = workspace.addNode('change'); + var joinNode = workspace.addNode('join'); + var debugNode = workspace.addNode('debug'); + + templateNode.edit(); + templateNode.setFormat('handlebars'); + templateNode.setSyntax('mustache'); + templateNode.setTemplate('one\ntwo\nthree\nfour\nfive'); + templateNode.clickOk(); + + changeNode.edit(); + changeNode.ruleSet('payload', 'msg', '(parts.index+1) & ": " & payload', 'jsonata'); + changeNode.clickOk(); + + injectNode.connect(templateNode); + templateNode.connect(splitNode); + splitNode.connect(changeNode); + changeNode.connect(joinNode); + joinNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"1: one↵2: two↵3: three↵4: four↵5: five"'); + }); + }); +}); From f88bfa059d7fa80af9af2b5e38f8fad5b88dd97b Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 17 Feb 2020 14:17:13 +0900 Subject: [PATCH 058/346] Make scenario structures same as cookbook --- .../scenario/cookbook_flowcontrol_uispec.js | 81 ++++ ...ec.js => cookbook_httpendpoints_uispec.js} | 18 +- .../scenario/cookbook_httprequests_uispec.js | 300 ++++++++++++ .../scenario/cookbook_messages_uispec.js | 142 ++++++ test/editor/specs/scenario/cookbook_uispec.js | 441 ------------------ 5 files changed, 532 insertions(+), 450 deletions(-) create mode 100644 test/editor/specs/scenario/cookbook_flowcontrol_uispec.js rename test/editor/specs/scenario/{cookbook_endpoint_uispec.js => cookbook_httpendpoints_uispec.js} (98%) create mode 100644 test/editor/specs/scenario/cookbook_httprequests_uispec.js create mode 100644 test/editor/specs/scenario/cookbook_messages_uispec.js delete mode 100644 test/editor/specs/scenario/cookbook_uispec.js diff --git a/test/editor/specs/scenario/cookbook_flowcontrol_uispec.js b/test/editor/specs/scenario/cookbook_flowcontrol_uispec.js new file mode 100644 index 000000000..724d1c56f --- /dev/null +++ b/test/editor/specs/scenario/cookbook_flowcontrol_uispec.js @@ -0,0 +1,81 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('flow control', function () { + it('trigger a flow whenever Node-RED starts', function () { + var injectNode = workspace.addNode('inject'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', 'Started!'); + injectNode.setOnce(true); + injectNode.clickOk(); + injectNode.connect(debugNode); + + debugTab.open(); + workspace.deploy(); + debugTab.getMessage().should.eql('"Started!"'); + }); + + it('trigger a flow at regular intervals', function () { + var injectNode = workspace.addNode('inject'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setRepeat('interval'); + injectNode.setRepeatInterval(1); + injectNode.clickOk(); + injectNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + specUtil.pause(1000); + var t1 = Number(debugTab.getMessage(1)); + t1.should.within(1500000000000, 3000000000000); + specUtil.pause(1000); + debugTab.getMessage(2).should.within(t1 + 900, 3000000000000); + }); + + // skip this case since it needs up to one minite. + it.skip('trigger a flow at a specific time'); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_endpoint_uispec.js b/test/editor/specs/scenario/cookbook_httpendpoints_uispec.js similarity index 98% rename from test/editor/specs/scenario/cookbook_endpoint_uispec.js rename to test/editor/specs/scenario/cookbook_httpendpoints_uispec.js index 9a2ed0710..134498370 100644 --- a/test/editor/specs/scenario/cookbook_endpoint_uispec.js +++ b/test/editor/specs/scenario/cookbook_httpendpoints_uispec.js @@ -23,16 +23,16 @@ var workspace = require('../../pageobjects/editor/workspace_page'); var httpNodeRoot = "/api"; // https://cookbook.nodered.org/ -describe('cookbook', function() { - beforeEach(function() { +describe('cookbook', function () { + beforeEach(function () { workspace.init(); }); - before(function() { + before(function () { helper.startServer(); }); - after(function() { + after(function () { helper.stopServer(); }); @@ -359,7 +359,7 @@ describe('cookbook', function() { debugTab.getMessage().indexOf('Text file').should.not.eql(-1); }); - it('post raw data to a flow', function() { + it('post raw data to a flow', function () { var httpInNode = workspace.addNode("httpIn"); var templateNode = workspace.addNode("template"); var httpResponseNode = workspace.addNode("httpResponse"); @@ -383,7 +383,7 @@ describe('cookbook', function() { var httpRequestNode = workspace.addNode("httpRequest"); var debugNode = workspace.addNode("debug"); - injectNode.edit() + injectNode.edit(); injectNode.setPayload("str", "Nick"); injectNode.clickOk(); @@ -427,7 +427,7 @@ describe('cookbook', function() { var httpRequestNode = workspace.addNode("httpRequest"); var debugNode = workspace.addNode("debug"); - injectNode.edit() + injectNode.edit(); injectNode.setPayload("str", "name=Nick"); injectNode.clickOk(); @@ -451,7 +451,7 @@ describe('cookbook', function() { debugTab.getMessage().indexOf('Hello Nick!').should.not.eql(-1); }); - it('post JSON data to a flow', function() { + it('post JSON data to a flow', function () { var httpInNode = workspace.addNode("httpIn"); var templateNode = workspace.addNode("template"); var httpResponseNode = workspace.addNode("httpResponse"); @@ -476,7 +476,7 @@ describe('cookbook', function() { var httpRequestNode = workspace.addNode("httpRequest"); var debugNode = workspace.addNode("debug"); - injectNode.edit() + injectNode.edit(); injectNode.setPayload("json", '{"name":"Nick"}'); injectNode.clickOk(); diff --git a/test/editor/specs/scenario/cookbook_httprequests_uispec.js b/test/editor/specs/scenario/cookbook_httprequests_uispec.js new file mode 100644 index 000000000..785451429 --- /dev/null +++ b/test/editor/specs/scenario/cookbook_httprequests_uispec.js @@ -0,0 +1,300 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('HTTP requests', function () { + it('simple get request', function () { + var injectNode = workspace.addNode('inject'); + var httpRequestNode = workspace.addNode('httpRequest'); + var htmlNode = workspace.addNode('html'); + var debugNode = workspace.addNode('debug'); + + httpRequestNode.edit(); + httpRequestNode.setMethod('GET'); + httpRequestNode.setUrl(helper.url()); + httpRequestNode.clickOk(); + + htmlNode.edit(); + htmlNode.setSelector('title'); + htmlNode.clickOk(); + + injectNode.connect(httpRequestNode); + httpRequestNode.connect(htmlNode); + htmlNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Node-RED"'); + }); + + it('set the URL of a request', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', helper.url()); + injectNode.clickOk(); + + changeNode.edit(); + changeNode.ruleSet('url', 'msg', 'payload', 'msg'); + changeNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.containEql('Node-RED'); + }); + + it('set the URL of a request using a template', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', 'settings'); + injectNode.clickOk(); + + changeNode.edit(); + changeNode.ruleSet('query', 'msg', 'payload', 'msg'); + changeNode.clickOk(); + + httpRequestNode.edit(); + httpRequestNode.setUrl(helper.url() + '/{{{query}}}'); + httpRequestNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.containEql('httpNodeRoot'); + }); + + it('set the query string parameters', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', 'Nick'); + injectNode.clickOk(); + + changeNode.edit(); + changeNode.ruleSet('query', 'msg', 'payload', 'msg'); + changeNode.clickOk(); + + httpRequestNode.edit(); + httpRequestNode.setUrl(helper.url() + httpNodeRoot + '/set-query?q={{{query}}}'); + httpRequestNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + // The code for confirmation starts from here. + var httpInNode = workspace.addNode('httpIn', 0, 200); + var templateNode = workspace.addNode('template'); + var httpResponseNode = workspace.addNode('httpResponse'); + + httpInNode.edit(); + httpInNode.setMethod('get'); + httpInNode.setUrl('/set-query'); + httpInNode.clickOk(); + + templateNode.edit(); + templateNode.setSyntax('mustache'); + templateNode.setFormat('handlebars'); + templateNode.setTemplate('Hello {{req.query.q}}'); + templateNode.clickOk(); + + httpInNode.connect(templateNode); + templateNode.connect(httpResponseNode); + // The code for confirmation ends here. + + workspace.deploy(); + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Hello Nick"'); + }); + + it('get a parsed JSON response', function () { + var injectNode = workspace.addNode('inject'); + var changeNodeSetPost = workspace.addNode('change'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setPayload('str', 'json-response'); + injectNode.clickOk(); + + changeNodeSetPost.edit(); + changeNodeSetPost.ruleSet('post', 'msg', 'payload', 'msg'); + changeNodeSetPost.clickOk(); + + httpRequestNode.edit(); + httpRequestNode.setMethod('GET'); + var url = helper.url() + httpNodeRoot + '/{{post}}'; + httpRequestNode.setUrl(url); + httpRequestNode.setReturn('obj'); + httpRequestNode.clickOk(); + + debugNode.edit(); + debugNode.setOutput('.title'); + debugNode.clickOk(); + + injectNode.connect(changeNodeSetPost); + changeNodeSetPost.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + // The code for confirmation starts from here. + var httpInNode = workspace.addNode('httpIn', 0, 200); + var templateNode = workspace.addNode('template'); + var changeNodeSetHeader = workspace.addNode('change'); + var httpResponseNode = workspace.addNode('httpResponse'); + + httpInNode.edit(); + httpInNode.setMethod('get'); + httpInNode.setUrl('/json-response'); + httpInNode.clickOk(); + + templateNode.edit(); + templateNode.setSyntax('mustache'); + templateNode.setFormat('handlebars'); + templateNode.setTemplate('{"title": "Hello"}'); + templateNode.clickOk(); + + changeNodeSetHeader.edit(); + changeNodeSetHeader.ruleSet('headers', 'msg', '{"content-type":"application/json"}', 'json'); + changeNodeSetHeader.clickOk(); + + httpInNode.connect(templateNode); + templateNode.connect(changeNodeSetHeader); + changeNodeSetHeader.connect(httpResponseNode); + // The code for confirmation ends here. + + workspace.deploy(); + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Hello"'); + }); + + it('get a binary response', function () { + var injectNode = workspace.addNode('inject'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + httpRequestNode.edit(); + httpRequestNode.setMethod('GET'); + httpRequestNode.setUrl(helper.url() + '/settings'); + httpRequestNode.setReturn('bin'); + httpRequestNode.clickOk(); + + injectNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + + debugTab.getMessage().should.eql(['123', '34', '104', '116', '116', '112', '78', '111', '100', '101']); + }); + + it('set a request header', function () { + var injectNode = workspace.addNode('inject'); + var functionNode = workspace.addNode('function'); + var httpRequestNode = workspace.addNode('httpRequest'); + var debugNode = workspace.addNode('debug'); + + functionNode.edit(); + functionNode.setFunction('msg.payload = "data to post";\nreturn msg;'); + functionNode.clickOk(); + + httpRequestNode.edit(); + httpRequestNode.setMethod('POST'); + var url = helper.url() + httpNodeRoot + '/set-header'; + httpRequestNode.setUrl(url); + httpRequestNode.clickOk(); + + injectNode.connect(functionNode); + functionNode.connect(httpRequestNode); + httpRequestNode.connect(debugNode); + + // The code for confirmation starts from here. + var httpInNode = workspace.addNode('httpIn', 0, 200); + var templateNode = workspace.addNode('template'); + var httpResponseNode = workspace.addNode('httpResponse'); + + httpInNode.edit(); + httpInNode.setMethod('post'); + httpInNode.setUrl('/set-header'); + httpInNode.clickOk(); + + templateNode.edit(); + templateNode.setSyntax('mustache'); + templateNode.setFormat('handlebars'); + templateNode.setTemplate('{{ payload }}'); + templateNode.clickOk(); + + httpInNode.connect(templateNode); + templateNode.connect(httpResponseNode); + // The code for confirmation ends here. + + workspace.deploy(); + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"data to post"'); + }); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_messages_uispec.js b/test/editor/specs/scenario/cookbook_messages_uispec.js new file mode 100644 index 000000000..a9465e5d8 --- /dev/null +++ b/test/editor/specs/scenario/cookbook_messages_uispec.js @@ -0,0 +1,142 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('messages', function () { + it('set a message property to a fixed value', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var debugNode = workspace.addNode('debug'); + + changeNode.edit(); + changeNode.ruleSet('payload', 'msg', 'Hello World!'); + changeNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Hello World!"'); + }); + + it('delete a message property', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var debugNode = workspace.addNode('debug'); + + changeNode.edit(); + changeNode.ruleDelete(); + changeNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('undefined'); + }); + + it('move a message property', function () { + var injectNode = workspace.addNode('inject'); + var changeNode = workspace.addNode('change'); + var debugNode = workspace.addNode('debug'); + + injectNode.edit(); + injectNode.setTopic('Hello'); + injectNode.clickOk(); + + changeNode.edit(); + changeNode.ruleMove('topic', 'payload'); + changeNode.clickOk(); + + injectNode.connect(changeNode); + changeNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql('"Hello"'); + }); + + it('map a property between different numeric ranges', function () { + var injectNode1 = workspace.addNode('inject'); + var injectNode2 = workspace.addNode('inject', 0, 100); + var injectNode3 = workspace.addNode('inject', 0, 200); + var rangeNode = workspace.addNode('range', 200, 100); + var debugNode = workspace.addNode('debug', 400); + + injectNode1.edit(); + injectNode1.setPayload('num', 0); + injectNode1.clickOk(); + injectNode2.edit(); + injectNode2.setPayload('num', 512); + injectNode2.clickOk(); + injectNode3.edit(); + injectNode3.setPayload('num', 1023); + injectNode3.clickOk(); + + rangeNode.edit(); + rangeNode.setAction('clamp'); + rangeNode.setRange(0, 1023, 0, 5); + rangeNode.clickOk(); + + injectNode1.connect(rangeNode); + injectNode2.connect(rangeNode); + injectNode3.connect(rangeNode); + rangeNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode1.clickLeftButton(); + debugTab.getMessage(1).should.eql('0'); + injectNode2.clickLeftButton(); + debugTab.getMessage(2).should.eql('2.5024437927663734'); + injectNode3.clickLeftButton(); + debugTab.getMessage(3).should.eql('5'); + }); + }); +}); diff --git a/test/editor/specs/scenario/cookbook_uispec.js b/test/editor/specs/scenario/cookbook_uispec.js deleted file mode 100644 index 6e16ca8a7..000000000 --- a/test/editor/specs/scenario/cookbook_uispec.js +++ /dev/null @@ -1,441 +0,0 @@ -/** - * Copyright JS Foundation and other contributors, http://js.foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ - -var when = require('when'); -var should = require("should"); -var fs = require('fs-extra'); - -var helper = require("../../editor_helper"); -var debugTab = require('../../pageobjects/editor/debugTab_page'); -var workspace = require('../../pageobjects/editor/workspace_page'); -var specUtil = require('../../pageobjects/util/spec_util_page'); - -var httpNodeRoot = "/api"; - -// https://cookbook.nodered.org/ -describe('cookbook', function() { - beforeEach(function() { - workspace.init(); - }); - - before(function() { - helper.startServer(); - }); - - after(function() { - helper.stopServer(); - }); - - describe('messages', function() { - it('set a message property to a fixed value', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var debugNode = workspace.addNode("debug"); - - changeNode.edit(); - changeNode.ruleSet("payload", "msg", "Hello World!"); - changeNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Hello World!"'); - }); - - it('delete a message property', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var debugNode = workspace.addNode("debug"); - - changeNode.edit(); - changeNode.ruleDelete(); - changeNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql("undefined"); - }); - - it('move a message property', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setTopic("Hello"); - injectNode.clickOk(); - - changeNode.edit(); - changeNode.ruleMove("topic", "payload"); - changeNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Hello"'); - }); - - it('map a property between different numeric ranges', function() { - var injectNode1 = workspace.addNode("inject"); - var injectNode2 = workspace.addNode("inject", 0, 100); - var injectNode3 = workspace.addNode("inject", 0, 200); - var rangeNode = workspace.addNode("range", 200, 100); - var debugNode = workspace.addNode("debug", 400); - - injectNode1.edit(); - injectNode1.setPayload("num", 0); - injectNode1.clickOk(); - injectNode2.edit(); - injectNode2.setPayload("num", 512); - injectNode2.clickOk(); - injectNode3.edit(); - injectNode3.setPayload("num", 1023); - injectNode3.clickOk(); - - rangeNode.edit(); - rangeNode.setAction("clamp"); - rangeNode.setRange(0, 1023, 0, 5); - rangeNode.clickOk(); - - injectNode1.connect(rangeNode); - injectNode2.connect(rangeNode); - injectNode3.connect(rangeNode); - rangeNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode1.clickLeftButton(); - debugTab.getMessage(1).should.eql('0'); - injectNode2.clickLeftButton(); - debugTab.getMessage(2).should.eql('2.5024437927663734'); - injectNode3.clickLeftButton(); - debugTab.getMessage(3).should.eql('5'); - }); - }); - - describe('flow control', function() { - it('trigger a flow whenever Node-RED starts', function() { - var injectNode = workspace.addNode("inject"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", "Started!") - injectNode.setOnce(true); - injectNode.clickOk(); - injectNode.connect(debugNode); - - debugTab.open(); - workspace.deploy(); - debugTab.getMessage().should.eql('"Started!"'); - }); - - it('trigger a flow at regular intervals', function() { - var injectNode = workspace.addNode("inject"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setRepeat("interval"); - injectNode.setRepeatInterval(1); - injectNode.clickOk(); - injectNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - specUtil.pause(1000); - var t1 = Number(debugTab.getMessage(1)); - t1.should.within(1500000000000, 3000000000000); - specUtil.pause(1000); - debugTab.getMessage(2).should.within(t1 + 900, 3000000000000); - }); - - // skip this case since it needs up to one minite. - it.skip('trigger a flow at a specific time'); - }); - - describe('HTTP requests', function() { - it('simple get request', function() { - var injectNode = workspace.addNode("inject"); - var httpRequetNode = workspace.addNode("httpRequest"); - var htmlNode = workspace.addNode("html"); - var debugNode = workspace.addNode("debug"); - - httpRequetNode.edit(); - httpRequetNode.setMethod("GET"); - httpRequetNode.setUrl(helper.url()); - httpRequetNode.clickOk(); - - htmlNode.edit(); - htmlNode.setSelector("title"); - htmlNode.clickOk(); - - injectNode.connect(httpRequetNode); - httpRequetNode.connect(htmlNode); - htmlNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Node-RED"'); - }); - - it('set the URL of a request', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", helper.url()); - injectNode.clickOk(); - - changeNode.edit(); - changeNode.ruleSet("url", "msg", "payload", "msg"); - changeNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.containEql('Node-RED'); - }); - - it('set the URL of a request using a template', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", 'settings'); - injectNode.clickOk(); - - changeNode.edit(); - changeNode.ruleSet("query", "msg", "payload", "msg"); - changeNode.clickOk(); - - httpRequetNode.edit(); - httpRequetNode.setUrl(helper.url() + "/{{{query}}}"); - httpRequetNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.containEql('httpNodeRoot'); - }); - - it('set the query string parameters', function() { - var injectNode = workspace.addNode("inject"); - var changeNode = workspace.addNode("change"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", 'Nick'); - injectNode.clickOk(); - - changeNode.edit(); - changeNode.ruleSet("query", "msg", "payload", "msg"); - changeNode.clickOk(); - - httpRequetNode.edit(); - httpRequetNode.setUrl(helper.url() + httpNodeRoot + '/set-query?q={{{query}}}'); - httpRequetNode.clickOk(); - - injectNode.connect(changeNode); - changeNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - // The code for confirmation starts from here. - var httpInNode = workspace.addNode("httpIn", 0, 200); - var templateNode = workspace.addNode("template"); - var httpResponseNode = workspace.addNode("httpResponse"); - - httpInNode.edit(); - httpInNode.setMethod("get"); - httpInNode.setUrl("/set-query"); - httpInNode.clickOk(); - - templateNode.edit(); - templateNode.setSyntax("mustache"); - templateNode.setFormat("handlebars"); - templateNode.setTemplate("Hello {{req.query.q}}"); - templateNode.clickOk(); - - httpInNode.connect(templateNode); - templateNode.connect(httpResponseNode); - // The code for confirmation ends here. - - workspace.deploy(); - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Hello Nick"'); - }); - - it('get a parsed JSON response', function() { - var injectNode = workspace.addNode("inject"); - var changeNodeSetPost = workspace.addNode("change"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - injectNode.edit(); - injectNode.setPayload("str", "json-response"); - injectNode.clickOk(); - - changeNodeSetPost.edit(); - changeNodeSetPost.ruleSet("post", "msg", "payload", "msg"); - changeNodeSetPost.clickOk(); - - httpRequetNode.edit(); - httpRequetNode.setMethod("GET"); - var url = helper.url() + httpNodeRoot + "/{{post}}"; - httpRequetNode.setUrl(url); - httpRequetNode.setReturn("obj"); - httpRequetNode.clickOk(); - - debugNode.edit(); - debugNode.setOutput(".title"); - debugNode.clickOk(); - - injectNode.connect(changeNodeSetPost); - changeNodeSetPost.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - // The code for confirmation starts from here. - var httpInNode = workspace.addNode("httpIn", 0, 200); - var templateNode = workspace.addNode("template"); - var changeNodeSetHeader = workspace.addNode("change"); - var httpResponseNode = workspace.addNode("httpResponse"); - - httpInNode.edit(); - httpInNode.setMethod("get"); - httpInNode.setUrl("/json-response"); - httpInNode.clickOk(); - - templateNode.edit(); - templateNode.setSyntax("mustache"); - templateNode.setFormat("handlebars"); - templateNode.setTemplate('{"title": "Hello"}'); - templateNode.clickOk(); - - changeNodeSetHeader.edit(); - changeNodeSetHeader.ruleSet("headers", "msg", '{"content-type":"application/json"}', "json"); - changeNodeSetHeader.clickOk(); - - httpInNode.connect(templateNode); - templateNode.connect(changeNodeSetHeader); - changeNodeSetHeader.connect(httpResponseNode); - // The code for confirmation ends here. - - workspace.deploy(); - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"Hello"'); - }); - - it('get a binary response', function() { - var injectNode = workspace.addNode("inject"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - httpRequetNode.edit(); - httpRequetNode.setMethod("GET"); - httpRequetNode.setUrl(helper.url() + "/settings"); - httpRequetNode.setReturn("bin"); - httpRequetNode.clickOk(); - - injectNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - workspace.deploy(); - - debugTab.open(); - injectNode.clickLeftButton(); - - debugTab.getMessage().should.eql(['123', '34', '104', '116', '116', '112', '78', '111', '100', '101']); - }); - - it('set a request header', function() { - var injectNode = workspace.addNode("inject"); - var functionNode = workspace.addNode("function"); - var httpRequetNode = workspace.addNode("httpRequest"); - var debugNode = workspace.addNode("debug"); - - functionNode.edit(); - functionNode.setFunction('msg.payload = "data to post";\nreturn msg;'); - functionNode.clickOk(); - - httpRequetNode.edit(); - httpRequetNode.setMethod("POST"); - var url = helper.url() + httpNodeRoot + "/set-header"; - httpRequetNode.setUrl(url); - httpRequetNode.clickOk(); - - injectNode.connect(functionNode); - functionNode.connect(httpRequetNode); - httpRequetNode.connect(debugNode); - - // The code for confirmation starts from here. - var httpInNode = workspace.addNode("httpIn", 0, 200); - var templateNode = workspace.addNode("template"); - var httpResponseNode = workspace.addNode("httpResponse"); - - httpInNode.edit(); - httpInNode.setMethod("post"); - httpInNode.setUrl("/set-header"); - httpInNode.clickOk(); - - templateNode.edit(); - templateNode.setSyntax("mustache"); - templateNode.setFormat("handlebars"); - templateNode.setTemplate("{{ payload }}"); - templateNode.clickOk(); - - httpInNode.connect(templateNode); - templateNode.connect(httpResponseNode); - // The code for confirmation ends here. - - workspace.deploy(); - debugTab.open(); - injectNode.clickLeftButton(); - debugTab.getMessage().should.eql('"data to post"'); - }); - }); -}); From a53d0c091e7bbcf0fe559c320e6f3c9b024c80a0 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 17 Feb 2020 19:03:45 +0900 Subject: [PATCH 059/346] Merge split and join node objects --- .../nodes/core/sequence/17-join_page.js | 27 ------------------- .../nodes/core/sequence/17-split_page.js | 8 ++++++ .../pageobjects/nodes/nodefactory_page.js | 2 +- 3 files changed, 9 insertions(+), 28 deletions(-) delete mode 100644 test/editor/pageobjects/nodes/core/sequence/17-join_page.js diff --git a/test/editor/pageobjects/nodes/core/sequence/17-join_page.js b/test/editor/pageobjects/nodes/core/sequence/17-join_page.js deleted file mode 100644 index 9426a64f9..000000000 --- a/test/editor/pageobjects/nodes/core/sequence/17-join_page.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright JS Foundation and other contributors, http://js.foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ - -var util = require('util'); - -var nodePage = require('../../node_page'); - -function joinNode(id) { - nodePage.call(this, id); -} - -util.inherits(joinNode, nodePage); - -module.exports = joinNode; diff --git a/test/editor/pageobjects/nodes/core/sequence/17-split_page.js b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js index 9426a64f9..6029e4d90 100644 --- a/test/editor/pageobjects/nodes/core/sequence/17-split_page.js +++ b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js @@ -25,3 +25,11 @@ function joinNode(id) { util.inherits(joinNode, nodePage); module.exports = joinNode; + +function joinNode(id) { + nodePage.call(this, id); +} + +util.inherits(joinNode, nodePage); + +module.exports = joinNode; diff --git a/test/editor/pageobjects/nodes/nodefactory_page.js b/test/editor/pageobjects/nodes/nodefactory_page.js index 05ac8b4d4..33193e112 100644 --- a/test/editor/pageobjects/nodes/nodefactory_page.js +++ b/test/editor/pageobjects/nodes/nodefactory_page.js @@ -26,7 +26,7 @@ var httpInNode = require('./core/network/21-httpin_page'); var httpResponseNode = require('./core/network/21-httpresponse_page'); var httpRequestNode = require('./core/network/21-httprequest_page'); var splitNode = require('./core/sequence/17-split_page'); -var joinNode = require('./core/sequence/17-join_page'); +var joinNode = require('./core/sequence/17-split_page'); var csvNode = require('./core/parsers/70-CSV_page'); var htmlNode = require('./core/parsers/70-HTML_page'); var jsonNode = require('./core/parsers/70-JSON_page'); From 2da1554caa09c3ba6937ed5078403a59c830d503 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 18 Feb 2020 21:38:32 +0900 Subject: [PATCH 060/346] update message catalogue for subflow UI --- .../@node-red/editor-client/locales/en-US/editor.json | 3 ++- .../@node-red/editor-client/locales/ja/editor.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index 7977a5752..6c9196577 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -351,7 +351,8 @@ "bool": "bool", "json": "JSON", "bin": "buffer", - "env": "env variable" + "env": "env variable", + "cred": "credential" }, "menu": { "input": "input", 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 cf04ced1d..656ec764e 100755 --- a/packages/node_modules/@node-red/editor-client/locales/ja/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/ja/editor.json @@ -351,7 +351,8 @@ "bool": "真偽", "json": "JSON", "bin": "バッファ", - "env": "環境変数" + "env": "環境変数", + "cred": "認証情報" }, "menu": { "input": "入力", From 5e892f222b441fbba1e4793fba0bae81cf9ff393 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Wed, 19 Feb 2020 16:40:07 +0000 Subject: [PATCH 061/346] clarify tcp node text re blank parameters. --- .../@node-red/nodes/locales/en-US/network/31-tcpin.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html index e8cb29ffc..173f003f7 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/network/31-tcpin.html @@ -14,14 +14,14 @@ limitations under the License. --> - - - From 9e6bc46540250ffa450d8fb083dbdf09fd6e7474 Mon Sep 17 00:00:00 2001 From: mknj Date: Fri, 21 Feb 2020 07:33:05 +0100 Subject: [PATCH 062/346] bump https-proxy-agent fixes #2469 this is a major version bump of https-proxy-agent, because they set engine to >6 and did some refactoring, which is ok for node-red. all tests pass. --- package.json | 2 +- packages/node_modules/@node-red/nodes/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a6c4c5091..0cc002b45 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "fs-extra": "8.1.0", "fs.notify": "0.0.4", "hash-sum": "2.0.0", - "https-proxy-agent": "2.2.4", + "https-proxy-agent": "5.0.0", "i18next": "15.1.2", "iconv-lite": "0.5.0", "is-utf8": "0.2.1", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index d2b3060ee..ca4e26626 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -27,7 +27,7 @@ "fs-extra": "8.1.0", "fs.notify": "0.0.4", "hash-sum": "2.0.0", - "https-proxy-agent": "2.2.4", + "https-proxy-agent": "5.0.0", "is-utf8": "0.2.1", "js-yaml": "3.13.1", "media-typer": "1.1.0", From 3f86fd7176f3370352f5207f1a21e472fbbb9cd5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 24 Feb 2020 11:22:47 +0000 Subject: [PATCH 063/346] Upgrade to latest marked and dompurify libs --- Gruntfile.js | 3 ++- package.json | 2 ++ .../@node-red/editor-client/src/js/red.js | 2 +- .../src/js/ui/editors/expression.js | 2 +- .../src/js/ui/editors/markdown.js | 4 +-- .../editor-client/src/js/ui/palette.js | 6 ++--- .../src/js/ui/projects/projectSettings.js | 2 +- .../editor-client/src/js/ui/tab-info.js | 17 +++---------- .../editor-client/src/js/ui/utils.js | 25 ++++++++++++++++++- .../src/vendor/marked/marked.min.js | 6 ----- 10 files changed, 39 insertions(+), 30 deletions(-) delete mode 100644 packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js diff --git a/Gruntfile.js b/Gruntfile.js index cbf70f4dc..7f7e80f32 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -193,7 +193,8 @@ module.exports = function(grunt) { "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-migrate-3.0.1.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery-ui.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/jquery/js/jquery.ui.touch-punch.min.js", - "packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js", + "node_modules/marked/marked.min.js", + "node_modules/dompurify/dist/purify.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/d3/d3.v3.min.js", "packages/node_modules/@node-red/editor-client/src/vendor/i18next/i18next.min.js", "node_modules/jsonata/jsonata-es5.min.js", diff --git a/package.json b/package.json index a6c4c5091..3db93d8c6 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,8 @@ "bcrypt": "3.0.6" }, "devDependencies": { + "marked": "0.8.0", + "dompurify": "2.0.8", "grunt": "~1.0.4", "grunt-chmod": "~1.1.1", "grunt-cli": "~1.3.2", 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 a742577d7..f0c11cdd9 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 @@ -431,7 +431,7 @@ var RED = (function() { ''+ '
        '; - RED.sidebar.info.set(aboutHeader+marked(data)); + RED.sidebar.info.set(aboutHeader+RED.utils.renderMarkdown(data)); RED.sidebar.info.show(); }); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js index c8a93964d..9a9765c35 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/editors/expression.js @@ -102,7 +102,7 @@ var f = $(this).val(); var args = RED._('jsonata:'+f+".args",{defaultValue:''}); var title = "
        "+f+"("+args+")
        "; - var body = marked(RED._('jsonata:'+f+'.desc',{defaultValue:''})); + var body = RED.utils.renderMarkdown(RED._('jsonata:'+f+'.desc',{defaultValue:''})); $("#red-ui-editor-type-expression-help").html(title+"

        "+body+"

        "); }) 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 97957eadf..f89c8f3a7 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 @@ -107,7 +107,7 @@ clearTimeout(changeTimer); changeTimer = setTimeout(function() { var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop(); - $(".red-ui-editor-type-markdown-panel-preview").html(marked(expressionEditor.getValue())); + $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue())); $(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop); },200); }) @@ -116,7 +116,7 @@ } if (value) { - $(".red-ui-editor-type-markdown-panel-preview").html(marked(expressionEditor.getValue())); + $(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue())); } panels = RED.panels.create({ id:"red-ui-editor-type-markdown-panels", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 967a61f51..b70854bfa 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -269,7 +269,7 @@ RED.palette = (function() { RED.view.focus(); var helpText; if (nt.indexOf("subflow:") === 0) { - helpText = marked(RED.nodes.subflow(nt.substring(8)).info||"")||(''+RED._("sidebar.info.none")+''); + helpText = RED.utils.renderMarkdown(RED.nodes.subflow(nt.substring(8)).info||"")||(''+RED._("sidebar.info.none")+''); } else { helpText = $("script[data-help-name='"+d.attr("data-palette-type")+"']").html()||(''+RED._("sidebar.info.none")+''); } @@ -370,7 +370,7 @@ RED.palette = (function() { RED.workspaces.show(nt.substring(8)); e.preventDefault(); }); - nodeInfo = marked(def.info||""); + nodeInfo = RED.utils.renderMarkdown(def.info||""); } setLabel(nt,d,label,nodeInfo); @@ -440,7 +440,7 @@ RED.palette = (function() { } else if (portOutput.length !== 0 && sf.out.length === 0) { portOutput.remove(); } - setLabel(sf.type+":"+sf.id,paletteNode,sf.name,marked(sf.info||"")); + setLabel(sf.type+":"+sf.id,paletteNode,sf.name,RED.utils.renderMarkdown(sf.info||"")); setIcon(paletteNode,sf); var currentCategory = paletteNode.data('category'); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js index 2d377ba54..f0944c879 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js @@ -158,7 +158,7 @@ RED.projects.settings = (function() { container.empty(); var desc; if (activeProject.description) { - desc = marked(activeProject.description); + desc = RED.utils.renderMarkdown(activeProject.description); } else { desc = '' + RED._("sidebar.project.noDescriptionAvailable") + ''; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index b8e3b150b..bfa4e4b23 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -15,17 +15,6 @@ **/ RED.sidebar.info = (function() { - marked.setOptions({ - renderer: new marked.Renderer(), - gfm: true, - tables: true, - breaks: false, - pedantic: false, - sanitize: true, - smartLists: true, - smartypants: false - }); - var content; var sections; var propertiesSection; @@ -314,7 +303,7 @@ RED.sidebar.info = (function() { if (subflowNode && node.type !== "subflow") { // Selected a subflow instance node. // - The subflow template info goes into help - helpText = (marked(subflowNode.info||"")||(''+RED._("sidebar.info.none")+'')); + helpText = (RED.utils.renderMarkdown(subflowNode.info||"")||(''+RED._("sidebar.info.none")+'')); } else { helpText = $("script[data-help-name='"+node.type+"']").html()||(''+RED._("sidebar.info.none")+''); } @@ -326,10 +315,10 @@ RED.sidebar.info = (function() { if (node._def && node._def.info) { var info = node._def.info; var textInfo = (typeof info === "function" ? info.call(node) : info); - infoText = infoText + marked(textInfo); + infoText = infoText + RED.utils.renderMarkdown(textInfo); } if (node.info) { - infoText = infoText + marked(node.info || "") + infoText = infoText + RED.utils.renderMarkdown(node.info || "") } setInfoText(infoText, infoSection.content); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js index d35d1f1ba..a9c1c9500 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/utils.js @@ -16,6 +16,28 @@ RED.utils = (function() { + window._marked = window.marked; + window.marked = function(txt) { + console.warn("Use of 'marked()' is deprecated. Use RED.utils.renderMarkdown() instead"); + return renderMarkdown(txt); + } + + _marked.setOptions({ + renderer: new _marked.Renderer(), + gfm: true, + tables: true, + breaks: false, + pedantic: false, + smartLists: true, + smartypants: false + }); + + function renderMarkdown(txt) { + var rendered = _marked(txt); + var cleaned = DOMPurify.sanitize(rendered, {SAFE_FOR_JQUERY: true}) + return cleaned; + } + function formatString(str) { return str.replace(/\r?\n/g,"↵").replace(/\t/g,"→"); } @@ -1053,6 +1075,7 @@ RED.utils = (function() { decodeObject: decodeObject, parseContextKey: parseContextKey, createIconElement: createIconElement, - sanitize: sanitize + sanitize: sanitize, + renderMarkdown: renderMarkdown } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js b/packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js deleted file mode 100644 index 555c1dc1d..000000000 --- a/packages/node_modules/@node-red/editor-client/src/vendor/marked/marked.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * marked - a markdown parser - * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) - * https://github.com/chjj/marked - */ -(function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]||""});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&(cap[1]==="pre"||cap[1]==="script"||cap[1]==="style"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out="",l=text.length,i=0,ch;for(;i.5){ch="x"+ch.toString(16)}out+="&#"+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return"
        "+(escaped?code:escape(code,true))+"\n
        "}return'
        '+(escaped?code:escape(code,true))+"\n
        \n"};Renderer.prototype.blockquote=function(quote){return"
        \n"+quote+"
        \n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"'+text+"\n"};Renderer.prototype.hr=function(){return this.options.xhtml?"
        \n":"
        \n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+"\n"};Renderer.prototype.listitem=function(text){return"
      1. "+text+"
      2. \n"};Renderer.prototype.paragraph=function(text){return"

        "+text+"

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

        "+escape(e.message+"",true)+"
        "}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}()); \ No newline at end of file From 40c3099e4e4f4eafa5e1f5ccedcd8608726d2d23 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 24 Feb 2020 11:41:27 +0000 Subject: [PATCH 064/346] Avoid adding extra newlines when formating jsonata Fixes #2472 --- .../@node-red/editor-client/src/vendor/jsonata/formatter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js index 382537be3..9fbab4fbf 100644 --- a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js +++ b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js @@ -59,8 +59,8 @@ stack.pop(); } } - } + // console.log(stack); var result = str; var indent = 0; @@ -83,7 +83,7 @@ pre = result.substring(0,offset+f.pos+1); post = result.substring(offset+f.pos+1); indented = indentLine(post,indent); - result = pre+"\n"+indented; + result = pre+(/\n$/.test(pre)?"":"\n")+indented; offset += indented.length-post.length+1; } else { longStack.push(false); @@ -94,7 +94,7 @@ pre = result.substring(0,offset+f.pos); post = result.substring(offset+f.pos); indented = indentLine(post,indent); - result = pre+"\n"+indented; + result = pre+(/\n$/.test(pre)?"":"\n")+indented; offset += indented.length-post.length+1; } longStack.pop(); From 04d398192190db914869a52a278386e14de2f551 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 24 Feb 2020 11:42:56 +0000 Subject: [PATCH 065/346] Bump to jsonata 1.8.1 --- package.json | 2 +- packages/node_modules/@node-red/util/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3db93d8c6..a410aa12f 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "is-utf8": "0.2.1", "js-yaml": "3.13.1", "json-stringify-safe": "5.0.1", - "jsonata": "1.8.0", + "jsonata": "1.8.1", "media-typer": "1.1.0", "memorystore": "1.6.1", "mime": "2.4.4", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 411c35a7c..9ad86f6c9 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -18,7 +18,7 @@ "clone": "2.1.2", "i18next": "15.1.2", "json-stringify-safe": "5.0.1", - "jsonata": "1.8.0", + "jsonata": "1.8.1", "when": "3.7.8" } } From e16fe1e6a514572854fcc776892fe06716c1a061 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 24 Feb 2020 13:27:42 +0000 Subject: [PATCH 066/346] Add better regex highlighting in jsonata edit mode Fixes #2465 --- .../src/vendor/jsonata/mode-jsonata.js | 82 ++++++++++++------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js index 2a90e4ce7..48358517e 100644 --- a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js +++ b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/mode-jsonata.js @@ -28,6 +28,11 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m }, "identifier"); this.$rules = { "start" : [ + { + token: "string.regexp", + regex: "\\/", + next: "regex" + }, { token : "string", regex : "'(?=.)", @@ -46,34 +51,35 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m token : "constant.numeric", // float regex : /[+-]?\d[\d_]*(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/ }, - { token: "keyword", - regex: /λ/ - }, - { - token: "keyword", - regex: jsonataFunctions - }, - { - token : keywordMapper, - regex : "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*" - }, - { - token : "punctuation.operator", - regex : /[.](?![.])/ - }, - { - token : "keyword.operator", - regex : /\|\||<=|>=|\.\.|\*\*|!=|:=|[=<>`!$%&*+\-~\/^]/, - next : "start" - }, - { - token : "punctuation.operator", - regex : /[?:,;.]/, - next : "start" - }, - { - token : "paren.lparen", - regex : /[\[({]/, + { + token: "keyword", + regex: /λ/ + }, + { + token: "keyword", + regex: jsonataFunctions + }, + { + token : keywordMapper, + regex : "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*" + }, + { + token : "punctuation.operator", + regex : /[.](?![.])/ + }, + { + token : "keyword.operator", + regex : /\|\||<=|>=|\.\.|\*\*|!=|:=|[=<>`!$%&*+\-~\/^]/, + next : "start" + }, + { + token : "punctuation.operator", + regex : /[?:,;.]/, + next : "start" + }, + { + token : "paren.lparen", + regex : /[\[({]/, next : "start" }, { @@ -86,7 +92,8 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m token : "string", regex : '"|$', next : "start" - }, { + }, + { defaultToken: "string" } ], @@ -95,9 +102,24 @@ ace.define("ace/mode/jsonata",["require","exports","module","ace/lib/oop","ace/m token : "string", regex : "'|$", next : "start" - }, { + }, + { defaultToken: "string" } + ], + "regex" : [ + { + token: "string.regexp", + regex: "\\\\/" + }, + { + token: "string.regexp", + regex: "/[sxngimy]*", + next: "start" + }, + { + defaultToken: "string.regexp" + } ] }; }; From 79feb691bdf0f8ff55d82304153858cfdd6fee40 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 24 Feb 2020 16:08:58 +0000 Subject: [PATCH 067/346] Add regex awareness to jsonata formatter --- .../src/vendor/jsonata/formatter.js | 79 +++++++++++++++---- 1 file changed, 62 insertions(+), 17 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js index 9fbab4fbf..49a02c206 100644 --- a/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js +++ b/packages/node_modules/@node-red/editor-client/src/vendor/jsonata/formatter.js @@ -11,6 +11,7 @@ var length = str.length; var start = 0; var inString = false; + var inRegex = false; var inBox = false; var quoteChar; var list = []; @@ -24,8 +25,13 @@ } for (var i=0;i 70) { + post = result.substring(offset+f.pos+1); + if (!/^\n/.test(post)) { + indented = indentLine(post,indent); + hasNewline = /\n$/.test(pre); + result = pre+(hasNewline?"":"\n")+indented; + offset += indented.length-post.length+(hasNewline?0:1); + } + } + } else if (f.type === "open-block") { - if (f.width > 30) { + if (f.width > 40) { longStack.push(true); indent += 4; pre = result.substring(0,offset+f.pos+1); post = result.substring(offset+f.pos+1); + hasNewline = /\n$/.test(pre); indented = indentLine(post,indent); - result = pre+(/\n$/.test(pre)?"":"\n")+indented; - offset += indented.length-post.length+1; + result = pre+(hasNewline?"":"\n")+indented; + offset += indented.length-post.length+(hasNewline?0:1); } else { longStack.push(false); } } else if (f.type === "close-block") { - if (f.width > 30) { + if (f.width > 40) { indent -= 4; pre = result.substring(0,offset+f.pos); post = result.substring(offset+f.pos); indented = indentLine(post,indent); - result = pre+(/\n$/.test(pre)?"":"\n")+indented; - offset += indented.length-post.length+1; + hasNewline = /\n *$/.test(pre); + if (hasNewline) { + result = pre + post; + } else { + result = pre+"\n"+indented; + offset += indented.length-post.length+1; + } } longStack.pop(); } From 22de8855c19da76049eeab8f38090509ced40dce Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 24 Feb 2020 21:08:29 +0000 Subject: [PATCH 068/346] Handle httpAdminRoot missing ending slash with login strategy Fixes #2473 --- packages/node_modules/@node-red/editor-api/lib/auth/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js index a7ee2e7f3..189f903d8 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js @@ -100,7 +100,10 @@ function login(req,res) { } } else if (mergedAdminAuth.type === "strategy") { - var urlPrefix = (settings.httpAdminRoot==='/')?"":settings.httpAdminRoot; + var urlPrefix = (settings.httpAdminRoot||"").replace(/\/$/,""); + if (urlPrefix.length > 0) { + urlPrefix += "/"; + } response = { "type":"strategy", "prompts":[{type:"button",label:mergedAdminAuth.strategy.label, url: urlPrefix + "auth/strategy"}] From 8405826fab39cf55596ff064221e0f37170f506e Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 24 Feb 2020 21:17:54 +0000 Subject: [PATCH 069/346] Ensure trigger sends complete 2nd msg if set to send latest msg and add test to close #2474 --- .../@node-red/nodes/core/function/89-trigger.js | 13 ++++++++----- test/nodes/core/function/89-trigger_spec.js | 8 +++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index 48a595ccc..d84bf57a8 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -106,7 +106,9 @@ module.exports = function(RED) { }); } + var npay; this.on('input', function(msg) { + if (node.op2type === "payl") { npay = RED.util.cloneMessage(msg); } processMessageQueue(msg); }); @@ -124,7 +126,7 @@ module.exports = function(RED) { else { if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) { promise = Promise.resolve(); - if (node.op2type === "pay" || node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } + if (node.op2type === "pay") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); } else if (node.op2type !== "nul") { promise = new Promise((resolve,reject) => { @@ -188,7 +190,8 @@ module.exports = function(RED) { promise.then(() => { msg2.payload = node.topics[topic].m2; delete node.topics[topic]; - node.send(msg2); + if (node.op2type === "payl") { node.send(npay); } + else { node.send(msg2); } node.status({}); }).catch(err => { node.error(err); @@ -244,9 +247,9 @@ module.exports = function(RED) { }); }, node.duration); } - else { - if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } - } + // else { + // if (node.op2type === "payl") {node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } + // } } return Promise.resolve(); } diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index 5f7bfc2d1..be0094ccf 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -736,10 +736,12 @@ describe('trigger node', function() { try { if (c === 0) { msg.should.have.a.property("payload", "Goodbye"); + msg.should.have.a.property("topic", "test2"); c += 1; } else { msg.should.have.a.property("payload", "World"); + msg.should.have.a.property("topic", "test3"); (Date.now() - ss).should.be.greaterThan(70); done(); } @@ -747,12 +749,12 @@ describe('trigger node', function() { catch(err) { done(err); } }); var ss = Date.now(); - n1.emit("input", {payload:"Hello"}); + n1.emit("input", {payload:"Hello", topic:"test1"}); setTimeout( function() { - n1.emit("input", {payload:"Goodbye"}); + n1.emit("input", {payload:"Goodbye", topic:"test2"}); },20); setTimeout( function() { - n1.emit("input", {payload:"World"}); + n1.emit("input", {payload:"World", topic:"test3"}); },80); }); }); From 01a143cd5af295b39cd93347edafab10221bbae9 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 24 Feb 2020 21:28:40 +0000 Subject: [PATCH 070/346] Emsure trigger complete 2nd msg when set to send latest and add test to close #2474 --- .../@node-red/nodes/core/function/89-trigger.js | 10 +++++----- test/nodes/core/function/89-trigger_spec.js | 8 +++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index 48a595ccc..a068550e2 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -106,7 +106,9 @@ module.exports = function(RED) { }); } + var npay; this.on('input', function(msg) { + if (node.op2type === "payl") { npay = RED.util.cloneMessage(msg); } processMessageQueue(msg); }); @@ -124,7 +126,7 @@ module.exports = function(RED) { else { if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) { promise = Promise.resolve(); - if (node.op2type === "pay" || node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } + if (node.op2type === "pay") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); } else if (node.op2type !== "nul") { promise = new Promise((resolve,reject) => { @@ -188,7 +190,8 @@ module.exports = function(RED) { promise.then(() => { msg2.payload = node.topics[topic].m2; delete node.topics[topic]; - node.send(msg2); + if (node.op2type === "payl") { node.send(npay); } + else { node.send(msg2); } node.status({}); }).catch(err => { node.error(err); @@ -244,9 +247,6 @@ module.exports = function(RED) { }); }, node.duration); } - else { - if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } - } } return Promise.resolve(); } diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index 5f7bfc2d1..be0094ccf 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -736,10 +736,12 @@ describe('trigger node', function() { try { if (c === 0) { msg.should.have.a.property("payload", "Goodbye"); + msg.should.have.a.property("topic", "test2"); c += 1; } else { msg.should.have.a.property("payload", "World"); + msg.should.have.a.property("topic", "test3"); (Date.now() - ss).should.be.greaterThan(70); done(); } @@ -747,12 +749,12 @@ describe('trigger node', function() { catch(err) { done(err); } }); var ss = Date.now(); - n1.emit("input", {payload:"Hello"}); + n1.emit("input", {payload:"Hello", topic:"test1"}); setTimeout( function() { - n1.emit("input", {payload:"Goodbye"}); + n1.emit("input", {payload:"Goodbye", topic:"test2"}); },20); setTimeout( function() { - n1.emit("input", {payload:"World"}); + n1.emit("input", {payload:"World", topic:"test3"}); },80); }); }); From 1fd4b2b9fc817bb4c14c0d6bacc3c5c3a67e5181 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Mon, 24 Feb 2020 21:31:01 +0000 Subject: [PATCH 071/346] join node - check existance before clearing timeout --- packages/node_modules/@node-red/nodes/core/sequence/17-split.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js index 5d93c8faf..0492a0dc2 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js @@ -429,7 +429,7 @@ module.exports = function(RED) { var completeSend = function(partId) { var group = inflight[partId]; - clearTimeout(group.timeout); + if (group.timeout) { clearTimeout(group.timeout); } if ((node.accumulate !== true) || group.msg.hasOwnProperty("complete")) { delete inflight[partId]; } if (group.type === 'array' && group.arrayLen > 1) { var newArray = []; From 608834eafbee4c3b48f60748d99ca1bc5e3e3e06 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 24 Feb 2020 21:49:58 +0000 Subject: [PATCH 072/346] Ensure IPv6 broker names are wrapped in brackets Fixes #2462 --- .../node_modules/@node-red/nodes/core/network/10-mqtt.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js index 6f682b279..3f5b2dff4 100644 --- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js +++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js @@ -153,7 +153,12 @@ module.exports = function(RED) { this.brokerurl="mqtt://"; } if (this.broker !== "") { - this.brokerurl = this.brokerurl+this.broker+":"; + //Check for an IPv6 address + if (/(?:^|(?<=\s))(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(?=\s|$)/.test(this.broker)) { + this.brokerurl = this.brokerurl+"["+this.broker+"]:"; + } else { + this.brokerurl = this.brokerurl+this.broker+":"; + } // port now defaults to 1883 if unset. if (!this.port){ this.brokerurl = this.brokerurl+"1883"; From 5ecf8c83db90f9022ec02c2b64aa26200bdcac4e Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 25 Feb 2020 18:46:02 +0900 Subject: [PATCH 073/346] Support to input JSON path in debug node property --- test/editor/pageobjects/nodes/core/common/21-debug_page.js | 2 ++ test/editor/specs/scenario/cookbook_httprequests_uispec.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/test/editor/pageobjects/nodes/core/common/21-debug_page.js b/test/editor/pageobjects/nodes/core/common/21-debug_page.js index 602d4d542..56854b195 100644 --- a/test/editor/pageobjects/nodes/core/common/21-debug_page.js +++ b/test/editor/pageobjects/nodes/core/common/21-debug_page.js @@ -33,6 +33,8 @@ debugNode.prototype.setOutput = function (complete) { // Select the "msg" type. browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options")][1]/a[1]'); // Input the path in msg. + browser.clickWithWait('//*[contains(@class, "red-ui-typedInput-input")]/input'); + browser.keys(Array('payload'.length).fill('Backspace')); browser.setValue('//*[contains(@class, "red-ui-typedInput-input")]/input', complete); } else { // Select the "complete msg object" type. diff --git a/test/editor/specs/scenario/cookbook_httprequests_uispec.js b/test/editor/specs/scenario/cookbook_httprequests_uispec.js index 785451429..797b06041 100644 --- a/test/editor/specs/scenario/cookbook_httprequests_uispec.js +++ b/test/editor/specs/scenario/cookbook_httprequests_uispec.js @@ -190,7 +190,7 @@ describe('cookbook', function () { httpRequestNode.clickOk(); debugNode.edit(); - debugNode.setOutput('.title'); + debugNode.setOutput('payload.title'); debugNode.clickOk(); injectNode.connect(changeNodeSetPost); From f7d2314d64eb0af5fa0db9f9af942da462531fb5 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 25 Feb 2020 19:01:17 +0900 Subject: [PATCH 074/346] Add page object code for split node and remove duplicated code --- .../editor/pageobjects/nodes/core/sequence/17-split_page.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/editor/pageobjects/nodes/core/sequence/17-split_page.js b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js index 6029e4d90..3c8c70aa5 100644 --- a/test/editor/pageobjects/nodes/core/sequence/17-split_page.js +++ b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js @@ -18,13 +18,13 @@ var util = require('util'); var nodePage = require('../../node_page'); -function joinNode(id) { +function splitNode(id) { nodePage.call(this, id); } -util.inherits(joinNode, nodePage); +util.inherits(splitNode, nodePage); -module.exports = joinNode; +module.exports = splitNode; function joinNode(id) { nodePage.call(this, id); From 21c57f968a0f92e696dbc532a6669995645e24b6 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 25 Feb 2020 19:02:46 +0900 Subject: [PATCH 075/346] Add page object code for nodes --- .../editor/pageobjects/editor/palette_page.js | 8 +++- .../nodes/core/common/24-complete_page.js | 47 ++++++++++++++++++ .../nodes/core/common/25-catch_page.js | 48 +++++++++++++++++++ .../nodes/core/common/25-status_page.js | 48 +++++++++++++++++++ .../nodes/core/common/90-comment_page.js | 27 +++++++++++ .../nodes/core/function/89-delay_page.js | 33 +++++++++++++ .../nodes/core/sequence/19-batch_page.js | 39 +++++++++++++++ .../pageobjects/nodes/nodefactory_page.js | 12 +++++ 8 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 test/editor/pageobjects/nodes/core/common/24-complete_page.js create mode 100644 test/editor/pageobjects/nodes/core/common/25-catch_page.js create mode 100644 test/editor/pageobjects/nodes/core/common/25-status_page.js create mode 100644 test/editor/pageobjects/nodes/core/common/90-comment_page.js create mode 100644 test/editor/pageobjects/nodes/core/function/89-delay_page.js create mode 100644 test/editor/pageobjects/nodes/core/sequence/19-batch_page.js diff --git a/test/editor/pageobjects/editor/palette_page.js b/test/editor/pageobjects/editor/palette_page.js index 97077059b..6af0b5271 100644 --- a/test/editor/pageobjects/editor/palette_page.js +++ b/test/editor/pageobjects/editor/palette_page.js @@ -18,11 +18,16 @@ var idMap = { // common "inject": ".red-ui-palette-node[data-palette-type='inject']", "debug": ".red-ui-palette-node[data-palette-type='debug']", + "complete": ".red-ui-palette-node[data-palette-type='complete']", + "catch": ".red-ui-palette-node[data-palette-type='catch']", + "status": ".red-ui-palette-node[data-palette-type='status']", + "comment": ".red-ui-palette-node[data-palette-type='comment']", // function "function": ".red-ui-palette-node[data-palette-type='function']", "change": ".red-ui-palette-node[data-palette-type='change']", "range": ".red-ui-palette-node[data-palette-type='range']", "template": ".red-ui-palette-node[data-palette-type='template']", + "delay": ".red-ui-palette-node[data-palette-type='delay']", // network "mqttIn": ".red-ui-palette-node[data-palette-type='mqtt in']", "mqttOut": ".red-ui-palette-node[data-palette-type='mqtt out']", @@ -30,8 +35,9 @@ var idMap = { "httpResponse": ".red-ui-palette-node[data-palette-type='http response']", "httpRequest": ".red-ui-palette-node[data-palette-type='http request']", // sequence - "join": ".red-ui-palette-node[data-palette-type='join']", "split": ".red-ui-palette-node[data-palette-type='split']", + "join": ".red-ui-palette-node[data-palette-type='join']", + "batch": ".red-ui-palette-node[data-palette-type='batch']", // parser "csv": ".red-ui-palette-node[data-palette-type='csv']", "html": ".red-ui-palette-node[data-palette-type='html']", diff --git a/test/editor/pageobjects/nodes/core/common/24-complete_page.js b/test/editor/pageobjects/nodes/core/common/24-complete_page.js new file mode 100644 index 000000000..558ee709a --- /dev/null +++ b/test/editor/pageobjects/nodes/core/common/24-complete_page.js @@ -0,0 +1,47 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function completeNode(id) { + nodePage.call(this, id); +} + +util.inherits(completeNode, nodePage); + +completeNode.prototype.setScope = function (scope) { + if (scope) { + browser.clickWithWait('//*[@id="node-input-complete-target-select"]'); + browser.pause(1000); + if (Array.isArray(scope)) { + for (var i = 0; i < scope.length; i++) { + browser.moveToObject(scope[i].id); + browser.buttonDown(); + browser.buttonUp(); + } + } else { + browser.moveToObject(scope.id); + browser.buttonDown(); + browser.buttonUp(); + } + browser.clickWithWait('//*[contains(@class, "red-ui-notification")]/div/button[2]'); + browser.pause(1000); + } +} + +module.exports = completeNode; diff --git a/test/editor/pageobjects/nodes/core/common/25-catch_page.js b/test/editor/pageobjects/nodes/core/common/25-catch_page.js new file mode 100644 index 000000000..89bcb7f1e --- /dev/null +++ b/test/editor/pageobjects/nodes/core/common/25-catch_page.js @@ -0,0 +1,48 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function catchNode(id) { + nodePage.call(this, id); +} + +util.inherits(catchNode, nodePage); + +catchNode.prototype.setScope = function (scope) { + if (scope) { + browser.selectWithWait('#node-input-scope-select', 'target'); + browser.clickWithWait('//*[@id="node-input-catch-target-select"]'); + browser.pause(1000); + if (Array.isArray(scope)) { + for (var i = 0; i < scope.length; i++) { + browser.moveToObject(scope[i].id); + browser.buttonDown(); + browser.buttonUp(); + } + } else { + browser.moveToObject(scope.id); + browser.buttonDown(); + browser.buttonUp(); + } + browser.clickWithWait('//*[contains(@class, "red-ui-notification")]/div/button[2]'); + browser.pause(1000); + } +} + +module.exports = catchNode; diff --git a/test/editor/pageobjects/nodes/core/common/25-status_page.js b/test/editor/pageobjects/nodes/core/common/25-status_page.js new file mode 100644 index 000000000..6de0fcde7 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/common/25-status_page.js @@ -0,0 +1,48 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function statusNode(id) { + nodePage.call(this, id); +} + +util.inherits(statusNode, nodePage); + +statusNode.prototype.setScope = function (scope) { + if (scope) { + browser.selectWithWait('#node-input-scope-select', 'target'); + browser.clickWithWait('//*[@id="node-input-status-target-select"]'); + browser.pause(1000); + if (Array.isArray(scope)) { + for (var i = 0; i < scope.length; i++) { + browser.moveToObject(scope[i].id); + browser.buttonDown(); + browser.buttonUp(); + } + } else { + browser.moveToObject(scope.id); + browser.buttonDown(); + browser.buttonUp(); + } + browser.clickWithWait('//*[contains(@class, "red-ui-notification")]/div/button[2]'); + browser.pause(1000); + } +} + +module.exports = statusNode; diff --git a/test/editor/pageobjects/nodes/core/common/90-comment_page.js b/test/editor/pageobjects/nodes/core/common/90-comment_page.js new file mode 100644 index 000000000..2687dacfe --- /dev/null +++ b/test/editor/pageobjects/nodes/core/common/90-comment_page.js @@ -0,0 +1,27 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function commentNode(id) { + nodePage.call(this, id); +} + +util.inherits(commentNode, nodePage); + +module.exports = commentNode; diff --git a/test/editor/pageobjects/nodes/core/function/89-delay_page.js b/test/editor/pageobjects/nodes/core/function/89-delay_page.js new file mode 100644 index 000000000..a4e2197e4 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/function/89-delay_page.js @@ -0,0 +1,33 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); + +var nodePage = require("../../node_page"); + +var keyPage = require("../../../util/key_page"); + +function delayNode(id) { + nodePage.call(this, id); +} + +util.inherits(delayNode, nodePage); + +delayNode.prototype.setTimeout = function (timeout) { + browser.setValue('//*[@id="node-input-timeout"]', timeout); +} + +module.exports = delayNode; diff --git a/test/editor/pageobjects/nodes/core/sequence/19-batch_page.js b/test/editor/pageobjects/nodes/core/sequence/19-batch_page.js new file mode 100644 index 000000000..0d7b13028 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/sequence/19-batch_page.js @@ -0,0 +1,39 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function batchNode(id) { + nodePage.call(this, id); +} + +batchNode.prototype.setMode = function (mode) { + browser.selectWithWait('#node-input-mode', mode); +} + +batchNode.prototype.setCount = function (count) { + browser.setValue('#node-input-count', count); +} + +batchNode.prototype.setOverlap = function (overlap) { + browser.setValue('#node-input-overlap', overlap); +} + +util.inherits(batchNode, nodePage); + +module.exports = batchNode; diff --git a/test/editor/pageobjects/nodes/nodefactory_page.js b/test/editor/pageobjects/nodes/nodefactory_page.js index 33193e112..17949e313 100644 --- a/test/editor/pageobjects/nodes/nodefactory_page.js +++ b/test/editor/pageobjects/nodes/nodefactory_page.js @@ -16,10 +16,15 @@ var injectNode = require('./core/common/20-inject_page'); var debugNode = require('./core/common/21-debug_page'); +var completeNode = require('./core/common/24-complete_page'); +var catchNode = require('./core/common/25-catch_page'); +var statusNode = require('./core/common/25-status_page'); +var commentNode = require('./core/common/90-comment_page'); var functionNode = require('./core/function/10-function_page'); var changeNode = require('./core/function/15-change_page'); var rangeNode = require('./core/function/16-range_page'); var templateNode = require('./core/function/80-template_page'); +var delayNode = require('./core/function/89-delay_page'); var mqttInNode = require('./core/network/10-mqttin_page'); var mqttOutNode = require('./core/network/10-mqttout_page'); var httpInNode = require('./core/network/21-httpin_page'); @@ -27,6 +32,7 @@ var httpResponseNode = require('./core/network/21-httpresponse_page'); var httpRequestNode = require('./core/network/21-httprequest_page'); var splitNode = require('./core/sequence/17-split_page'); var joinNode = require('./core/sequence/17-split_page'); +var batchNode = require('./core/sequence/19-batch_page'); var csvNode = require('./core/parsers/70-CSV_page'); var htmlNode = require('./core/parsers/70-HTML_page'); var jsonNode = require('./core/parsers/70-JSON_page'); @@ -38,11 +44,16 @@ var nodeCatalog = { // common "inject": injectNode, "debug": debugNode, + "complete": completeNode, + "catch": catchNode, + "status": statusNode, + "comment": commentNode, // function "function": functionNode, "change": changeNode, "range": rangeNode, "template": templateNode, + "delay": delayNode, // network "mqttIn": mqttInNode, "mqttOut": mqttOutNode, @@ -52,6 +63,7 @@ var nodeCatalog = { // sequence "split": splitNode, "join": joinNode, + "batch": batchNode, // parser "csv": csvNode, "html": htmlNode, From 00477fd67a159faf3fb26cefea503fbd483a2b6a Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Tue, 25 Feb 2020 19:56:48 +0900 Subject: [PATCH 076/346] Add UI test case for error handling --- .../scenario/cookbook_errorhandling_uispec.js | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 test/editor/specs/scenario/cookbook_errorhandling_uispec.js diff --git a/test/editor/specs/scenario/cookbook_errorhandling_uispec.js b/test/editor/specs/scenario/cookbook_errorhandling_uispec.js new file mode 100644 index 000000000..5285c5c0f --- /dev/null +++ b/test/editor/specs/scenario/cookbook_errorhandling_uispec.js @@ -0,0 +1,74 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var when = require('when'); +var should = require('should'); +var fs = require('fs-extra'); + +var helper = require('../../editor_helper'); +var debugTab = require('../../pageobjects/editor/debugTab_page'); +var workspace = require('../../pageobjects/editor/workspace_page'); +var specUtil = require('../../pageobjects/util/spec_util_page'); + +var httpNodeRoot = '/api'; + +// https://cookbook.nodered.org/ +describe('cookbook', function () { + beforeEach(function () { + workspace.init(); + }); + + before(function () { + helper.startServer(); + }); + + after(function () { + helper.stopServer(); + }); + + describe('messages', function () { + it('trigger a flow when a node throws an error', function () { + var injectNode = workspace.addNode('inject'); + var functionNode = workspace.addNode('function'); + var catchNode = workspace.addNode('catch', 0 , 80); + var debugNode = workspace.addNode('debug'); + + functionNode.edit(); + functionNode.setFunction('node.error("an example error", msg);'); + functionNode.clickOk(); + + catchNode.edit(); + catchNode.setScope(functionNode); + catchNode.clickOk(); + + debugNode.edit(); + debugNode.setOutput('error'); + debugNode.clickOk(); + + injectNode.connect(functionNode); + catchNode.connect(debugNode); + + workspace.deploy(); + + debugTab.open(); + injectNode.clickLeftButton(); + debugTab.getMessage().should.eql(['"an example error"', 'function']); + }); + + // skip this case since the flow outputs random results. + it.skip('automatically retry an action after an error'); + }); +}); From 37bcd5c6032109f73df3427baea8fa82b360b984 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Tue, 25 Feb 2020 21:28:15 +0000 Subject: [PATCH 077/346] First pass at adding support for GET requests with a body --- .../nodes/core/network/21-httprequest.html | 28 +++++++++++++++++-- .../nodes/core/network/21-httprequest.js | 9 ++++++ .../nodes/locales/en-US/messages.json | 2 +- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html index aecb8eeb9..d2cc61b56 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html @@ -33,8 +33,12 @@
        - - + +
        @@ -106,6 +110,7 @@ method:{value:"GET"}, ret: {value:"txt"}, paytoqs: {value: false}, + paytobody: {value: false}, url:{value:"",validate:function(v) { return (v.trim().length === 0) || (v.indexOf("://") === -1) || (v.trim().indexOf("http") === 0)} }, tls: {type:"tls-config",required: false}, persist: {value:false}, @@ -168,6 +173,13 @@ $(".node-input-paytoqs-row").hide(); } }); + if (this.paytoqs) { + $("#node-input-paytox").val("query"); + } else if (this.paytobody) { + $("#node-input-paytox").val("body"); + } else { + $("#node-input-paytox").val("ignore"); + } if (this.authType) { $('#node-input-useAuth').prop('checked', true); $("#node-input-authType-select").val(this.authType); @@ -226,6 +238,18 @@ if (!$("#node-input-useProxy").is(":checked")) { $("#node-input-proxy").val("_ADD_"); } + + var payto = $("#node-input-paytox").val(); + if(payto == "query") { + this.paytoqs = true; + this.paytobody = false; + } else if (payto == "body") { + this.paytoqs = false; + this.paytobody = true; + } else { + this.paytoqs = false; + this.paytobody = false; + } } }); diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js index 4c39f9f8c..5d911bffe 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js @@ -29,6 +29,7 @@ module.exports = function(RED) { var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1; var nodeMethod = n.method || "GET"; var paytoqs = n.paytoqs; + var paytobody = n.paytobody; var nodeHTTPPersistent = n["persist"]; if (n.tls) { var tlsNode = RED.nodes.getNode(n.tls); @@ -277,6 +278,14 @@ module.exports = function(RED) { node.error(RED._("httpin.errors.invalid-payload"),msg); nodeDone(); return; + } + } else if ( method == "GET" && typeof msg.payload !== "undefined" && paytobody) { + if (typeof msg.payload === "object") { + opts.body = JSON.stringify(msg.payload); + } else if (typeof msg.payload == "number") { + opts.body = msg.payload+""; + } else if (typeof msg.payload === "string" || Buffer.isBuffer(msg.payload)) { + opts.body = msg.payload; } } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index e1d7c6368..94d541444 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -398,7 +398,7 @@ "status": "Status code", "headers": "Headers", "other": "other", - "paytoqs" : "Append msg.payload as query string parameters", + "paytoqs" : "Append msg.payload as", "utf8String": "UTF8 string", "binaryBuffer": "binary buffer", "jsonObject": "parsed JSON object", From 1868289b7127e372be9b2eb72e61240e89be2b53 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 25 Feb 2020 22:15:53 +0000 Subject: [PATCH 078/346] Better fix for trigegr 2nd message in last payload mode Now works correctly in multiple topics mode. And update tests --- .../nodes/core/function/89-trigger.js | 15 +++++--- test/nodes/core/function/89-trigger_spec.js | 35 +++++++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js index a068550e2..4debeacde 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.js @@ -76,6 +76,7 @@ module.exports = function(RED) { var node = this; node.topics = {}; + var npay = {}; var pendingMessages = []; var activeMessagePromise = null; var processMessageQueue = function(msg) { @@ -106,9 +107,7 @@ module.exports = function(RED) { }); } - var npay; this.on('input', function(msg) { - if (node.op2type === "payl") { npay = RED.util.cloneMessage(msg); } processMessageQueue(msg); }); @@ -124,6 +123,7 @@ module.exports = function(RED) { node.status({}); } else { + if (node.op2type === "payl") { npay[topic] = RED.util.cloneMessage(msg); } if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) { promise = Promise.resolve(); if (node.op2type === "pay") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); } @@ -188,10 +188,15 @@ module.exports = function(RED) { }); } promise.then(() => { - msg2.payload = node.topics[topic].m2; + if (node.op2type === "payl") { + node.send(npay[topic]); + delete npay[topic]; + } + else { + msg2.payload = node.topics[topic].m2; + node.send(msg2); + } delete node.topics[topic]; - if (node.op2type === "payl") { node.send(npay); } - else { node.send(msg2); } node.status({}); }).catch(err => { node.error(err); diff --git a/test/nodes/core/function/89-trigger_spec.js b/test/nodes/core/function/89-trigger_spec.js index be0094ccf..7d7450580 100644 --- a/test/nodes/core/function/89-trigger_spec.js +++ b/test/nodes/core/function/89-trigger_spec.js @@ -759,6 +759,41 @@ describe('trigger node', function() { }); }); + it('should be able output the 2nd payload and handle multiple topics', function(done) { + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"false", op1type:"nul", op2type:"payl", op1:"false", op2:"true", duration:"80", bytopic:"topic", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(triggerNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var c = 0; + n2.on("input", function(msg) { + try { + if (c === 0) { + msg.should.have.a.property("payload", "Goodbye1"); + msg.should.have.a.property("topic", "test1"); + c += 1; + } + else { + msg.should.have.a.property("payload", "Goodbye2"); + msg.should.have.a.property("topic", "test2"); + done(); + } + } + catch(err) { done(err); } + }); + n1.emit("input", {payload:"Hello1", topic:"test1"}); + setTimeout( function() { + n1.emit("input", {payload:"Hello2", topic:"test2"}); + },20); + setTimeout( function() { + n1.emit("input", {payload:"Goodbye2", topic:"test2"}); + },20); + setTimeout( function() { + n1.emit("input", {payload:"Goodbye1", topic:"test1"}); + },20); + }); + }); + it('should be able to apply mustache templates to payloads', function(done) { var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1type:"val", op2type:"val", op1:"{{payload}}", op2:"{{topic}}", duration:"50", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; From 43970b404eef80bdf08bcd4a05e14011a5738702 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 25 Feb 2020 23:05:59 +0000 Subject: [PATCH 079/346] Update github templates --- .github/ISSUE_TEMPLATE/--bug_report.md | 2 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/--bug_report.md b/.github/ISSUE_TEMPLATE/--bug_report.md index 63923455e..7cdc9caad 100644 --- a/.github/ISSUE_TEMPLATE/--bug_report.md +++ b/.github/ISSUE_TEMPLATE/--bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: Reproducable software issues in the core of Node-RED +about: Reproducible software issues in the core of Node-RED title: '' labels: '' assignees: '' diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 05fdacd51..92a0abb8a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -29,6 +29,6 @@ the [forum](https://discourse.nodered.org) or - [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md) -- [ ] For non-bugfix PRs, I have discussed this change on the mailing list/slack team. +- [ ] For non-bugfix PRs, I have discussed this change on the forum/slack team. - [ ] I have run `grunt` to verify the unit tests pass - [ ] I have added suitable unit tests to cover the new/changed functionality From bba6855872b915eab70ffc3226418bc83b8b5d0d Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Wed, 26 Feb 2020 12:59:40 +0900 Subject: [PATCH 080/346] Add admin api authentication function --- .../@node-red/editor-api/lib/auth/index.js | 3 +- .../editor-api/lib/auth/strategies.js | 24 ++++++++++- .../@node-red/editor-api/lib/auth/users.js | 14 +++++- .../editor-api/lib/auth/strategies_spec.js | 32 ++++++++++++++ .../editor-api/lib/auth/users_spec.js | 43 +++++++++++++++++++ 5 files changed, 112 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js index a7ee2e7f3..c3948cd27 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js @@ -36,6 +36,7 @@ var log = require("@node-red/util").log; // TODO: separate module passport.use(strategies.bearerStrategy.BearerStrategy); passport.use(strategies.clientPasswordStrategy.ClientPasswordStrategy); passport.use(strategies.anonymousStrategy); +passport.use(strategies.tokensStrategy); var server = oauth2orize.createServer(); @@ -60,7 +61,7 @@ function init(_settings,storage) { function needsPermission(permission) { return function(req,res,next) { if (settings && settings.adminAuth) { - return passport.authenticate(['bearer','anon'],{ session: false })(req,res,function() { + return passport.authenticate(['bearer','anon','tokens'],{ session: false })(req,res,function() { if (!req.user) { return next(); } diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js index b17bf1473..9705ddd28 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js @@ -123,9 +123,31 @@ AnonymousStrategy.prototype.authenticate = function(req) { }); } +function TokensStrategy() { + passport.Strategy.call(this); + this.name = 'tokens'; +} +util.inherits(TokensStrategy, passport.Strategy); +TokensStrategy.prototype.authenticate = function(req) { + var self = this; + var token = req.headers[Users.tokenHeader()]; + if (token) { + Users.tokens(token).then(function(admin) { + if (admin) { + self.success(admin,{scope:admin.permissions}); + } else { + self.fail(401); + } + }); + } else { + self.fail(401); + } +} + module.exports = { bearerStrategy: bearerStrategy, clientPasswordStrategy: clientPasswordStrategy, passwordTokenExchange: passwordTokenExchange, - anonymousStrategy: new AnonymousStrategy() + anonymousStrategy: new AnonymousStrategy(), + tokensStrategy: new TokensStrategy() } diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/users.js b/packages/node_modules/@node-red/editor-api/lib/auth/users.js index 24a762958..f032332db 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/users.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/users.js @@ -59,7 +59,9 @@ function getDefaultUser() { var api = { get: get, authenticate: authenticate, - default: getDefaultUser + default: getDefaultUser, + tokens: getDefaultUser, + tokenHeader: "authorization" } function init(config) { @@ -105,6 +107,12 @@ function init(config) { } else { api.default = getDefaultUser; } + if (config.tokens && typeof config.tokens === "function") { + api.tokens = config.tokens; + if (config.tokenHeader && typeof config.tokenHeader === "string") { + api.tokenHeader = config.tokenHeader.toLowerCase(); + } + } } function cleanUser(user) { if (user && user.hasOwnProperty('password')) { @@ -118,5 +126,7 @@ module.exports = { init: init, get: function(username) { return api.get(username).then(cleanUser)}, authenticate: function() { return api.authenticate.apply(null, arguments) }, - default: function() { return api.default(); } + default: function() { return api.default(); }, + tokens: function(token) { return api.tokens(token); }, + tokenHeader: function() { return api.tokenHeader } }; diff --git a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js index d30f6198a..13573f22a 100644 --- a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js @@ -129,6 +129,38 @@ describe("api/auth/strategies", function() { }) }); + describe("Tokens Strategy", function() { + it('Succeeds if tokens user enabled',function(done) { + var userDefault = sinon.stub(Users,"tokens",function(token) { + return when.resolve("tokens-"+token); + }); + strategies.tokensStrategy._success = strategies.tokensStrategy.success; + strategies.tokensStrategy.success = function(user) { + user.should.equal("tokens-1234"); + strategies.tokensStrategy.success = strategies.tokensStrategy._success; + delete strategies.tokensStrategy._success; + done(); + }; + strategies.tokensStrategy.authenticate({headers:{"authorization":"1234"}}); + }); + it('Fails if tokens user not enabled',function(done) { + var userDefault = sinon.stub(Users,"tokens",function() { + return when.resolve(null); + }); + strategies.tokensStrategy._fail = strategies.tokensStrategy.fail; + strategies.tokensStrategy.fail = function(err) { + err.should.equal(401); + strategies.tokensStrategy.fail = strategies.tokensStrategy._fail; + delete strategies.tokensStrategy._fail; + done(); + }; + strategies.tokensStrategy.authenticate({headers:{"authorization":"1234"}}); + }); + afterEach(function() { + Users.tokens.restore(); + }) + }); + describe("Bearer Strategy", function() { it('Rejects invalid token',function(done) { var getToken = sinon.stub(Tokens,"get",function(token) { diff --git a/test/unit/@node-red/editor-api/lib/auth/users_spec.js b/test/unit/@node-red/editor-api/lib/auth/users_spec.js index 515d23034..228163684 100644 --- a/test/unit/@node-red/editor-api/lib/auth/users_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/users_spec.js @@ -227,4 +227,47 @@ describe("api/auth/users", function() { }); }); }); + + describe('Initialised with tokens set as function',function() { + before(function() { + Users.init({ + type:"strategy", + tokens: function(token) { return("Done-"+token); } + }); + }); + after(function() { + Users.init({}); + }); + describe('#tokens',function() { + it('handles api.tokens being a function',function(done) { + Users.should.have.property('tokens').which.is.a.Function(); + (Users.tokens("1234")).should.equal("Done-1234"); + (Users.tokenHeader()).should.equal("authorization"); + done(); + }); + }); + }); + + describe('Initialised with tokens set as function and tokenHeader set as token header name',function() { + before(function() { + Users.init({ + type:"strategy", + tokens: function(token) { return("Done-"+token); }, + tokenHeader: "X-TEST-TOKEN" + }); + }); + after(function() { + Users.init({}); + }); + describe('#tokens',function() { + it('handles api.tokens being a function and api.tokenHeader being a header name',function(done) { + Users.should.have.property('tokens').which.is.a.Function(); + (Users.tokens("1234")).should.equal("Done-1234"); + Users.should.have.property('tokenHeader').which.is.a.Function(); + (Users.tokenHeader()).should.equal("x-test-token"); + done(); + }); + }); + }); + }); From d09ee6611f5aceeb2e7fbee1f150af7b368a7c42 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 26 Feb 2020 11:37:37 +0000 Subject: [PATCH 081/346] Update dependencies --- package.json | 18 +++++++++--------- .../@node-red/editor-api/package.json | 6 +++--- .../node_modules/@node-red/nodes/package.json | 10 +++++----- .../@node-red/registry/package.json | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index a5c4816f4..335dddbb3 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ } ], "dependencies": { - "ajv": "6.10.2", + "ajv": "6.12.0", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "body-parser": "1.19.0", @@ -34,7 +34,7 @@ "cookie": "0.4.0", "cookie-parser": "1.4.4", "cors": "2.8.5", - "cron": "1.7.2", + "cron": "1.8.2", "denque": "1.4.1", "express": "4.17.1", "express-session": "1.17.0", @@ -43,33 +43,33 @@ "hash-sum": "2.0.0", "https-proxy-agent": "5.0.0", "i18next": "15.1.2", - "iconv-lite": "0.5.0", + "iconv-lite": "0.5.1", "is-utf8": "0.2.1", "js-yaml": "3.13.1", "json-stringify-safe": "5.0.1", "jsonata": "1.8.1", "media-typer": "1.1.0", - "memorystore": "1.6.1", + "memorystore": "1.6.2", "mime": "2.4.4", "mqtt": "2.18.8", "multer": "1.4.2", - "mustache": "3.0.2", + "mustache": "4.0.0", "node-red-node-rbe": "^0.2.6", "node-red-node-sentiment": "^0.1.6", "node-red-node-tail": "^0.1.0", "nopt": "4.0.1", "oauth2orize": "1.11.0", "on-headers": "1.0.2", - "passport": "0.4.0", + "passport": "0.4.1", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", "raw-body": "2.4.1", "request": "2.88.0", "semver": "6.3.0", - "uglify-js": "3.6.9", + "uglify-js": "3.8.0", "when": "3.7.8", "ws": "6.2.1", - "xml2js": "0.4.22" + "xml2js": "0.4.23" }, "optionalDependencies": { "bcrypt": "3.0.6" @@ -104,7 +104,7 @@ "mocha": "^5.2.0", "mosca": "^2.8.3", "node-red-node-test-helper": "^0.2.3", - "node-sass": "^4.13.0", + "node-sass": "^4.13.1", "should": "^8.4.0", "sinon": "1.17.7", "stoppable": "^1.1.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 824f4df5b..81be91b6d 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -24,13 +24,13 @@ "cors": "2.8.5", "express-session": "1.17.0", "express": "4.17.1", - "memorystore": "1.6.1", + "memorystore": "1.6.2", "mime": "2.4.4", - "mustache": "3.0.2", + "mustache": "4.0.0", "oauth2orize": "1.11.0", "passport-http-bearer": "1.0.1", "passport-oauth2-client-password": "0.1.2", - "passport": "0.4.0", + "passport": "0.4.1", "when": "3.7.8", "ws": "6.2.1" }, diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index ca4e26626..1b707b95b 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -15,14 +15,14 @@ } ], "dependencies": { - "ajv": "6.10.2", + "ajv": "6.12.0", "body-parser": "1.19.0", "cheerio": "0.22.0", "content-type": "1.0.4", "cookie-parser": "1.4.4", "cookie": "0.4.0", "cors": "2.8.5", - "cron": "1.7.2", + "cron": "1.8.2", "denque": "1.4.1", "fs-extra": "8.1.0", "fs.notify": "0.0.4", @@ -33,12 +33,12 @@ "media-typer": "1.1.0", "mqtt": "2.18.8", "multer": "1.4.2", - "mustache": "3.0.2", + "mustache": "4.0.0", "on-headers": "1.0.2", "raw-body": "2.4.1", "request": "2.88.0", "ws": "6.2.1", - "xml2js": "0.4.22", - "iconv-lite": "0.5.0" + "xml2js": "0.4.23", + "iconv-lite": "0.5.1" } } diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index fefb24360..c1ee06050 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -18,7 +18,7 @@ "dependencies": { "@node-red/util": "1.0.3", "semver": "6.3.0", - "uglify-js": "3.6.9", + "uglify-js": "3.8.0", "when": "3.7.8" } } From cc5fdd984459f74bb022bfc4f34d7a4f95eb8b7a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 26 Feb 2020 13:17:03 +0000 Subject: [PATCH 082/346] Avoid adding extra divs to edit form to avoid size miscalculation --- .../node_modules/@node-red/editor-client/src/js/ui/editor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 9fb29833a..6641e9b88 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 @@ -584,8 +584,8 @@ RED.editor = (function() { // cases, and also prevent browser auto-fill of password // - the elements cannot be hidden otherwise Chrome will ignore them. // - the elements need to have id's that imply password/username - $('
        ').prependTo(dialogForm); - $('
        ').prependTo(dialogForm); + $('').prependTo(dialogForm); + $('').prependTo(dialogForm); dialogForm.on("submit", function(e) { e.preventDefault();}); dialogForm.find('input').attr("autocomplete","off"); return dialogForm; From 0ca36a89e30e6200cec1f369dd1124b02a4c6616 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Wed, 26 Feb 2020 19:45:01 +0000 Subject: [PATCH 083/346] Updates to match Nick's suggestions --- .../nodes/core/network/21-httprequest.html | 38 ++++++++----------- .../nodes/core/network/21-httprequest.js | 8 +++- .../nodes/locales/en-US/messages.json | 6 ++- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html index d2cc61b56..6fea31766 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html @@ -33,11 +33,10 @@
        - - + + +
        @@ -110,7 +109,6 @@ method:{value:"GET"}, ret: {value:"txt"}, paytoqs: {value: false}, - paytobody: {value: false}, url:{value:"",validate:function(v) { return (v.trim().length === 0) || (v.indexOf("://") === -1) || (v.trim().indexOf("http") === 0)} }, tls: {type:"tls-config",required: false}, persist: {value:false}, @@ -173,12 +171,16 @@ $(".node-input-paytoqs-row").hide(); } }); - if (this.paytoqs) { - $("#node-input-paytox").val("query"); - } else if (this.paytobody) { - $("#node-input-paytox").val("body"); + console.log("paytoqs: " + this.paytoqs); + if (this.paytoqs === true || this.paytoqs == "query") { + $("#node-input-paytoqs").val("query"); + console.log("q"); + } else if (this.paytoqs === "body") { + $("#node-input-paytoqs").val("body"); + console.log("b"); } else { - $("#node-input-paytox").val("ignore"); + $("#node-input-paytoqs").val("ignore"); + console.log("i"); } if (this.authType) { $('#node-input-useAuth').prop('checked', true); @@ -238,18 +240,8 @@ if (!$("#node-input-useProxy").is(":checked")) { $("#node-input-proxy").val("_ADD_"); } - - var payto = $("#node-input-paytox").val(); - if(payto == "query") { - this.paytoqs = true; - this.paytobody = false; - } else if (payto == "body") { - this.paytoqs = false; - this.paytobody = true; - } else { - this.paytoqs = false; - this.paytobody = false; - } + console.log("save - paytoqs " + this.paytoqs); + console.log("save - paytoqs " + $("#node-input-paytoqs").val()); } }); diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js index 5d911bffe..fcb6a29df 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.js @@ -28,8 +28,8 @@ module.exports = function(RED) { var nodeUrl = n.url; var isTemplatedUrl = (nodeUrl||"").indexOf("{{") != -1; var nodeMethod = n.method || "GET"; - var paytoqs = n.paytoqs; - var paytobody = n.paytobody; + var paytoqs = false; + var paytobody = false; var nodeHTTPPersistent = n["persist"]; if (n.tls) { var tlsNode = RED.nodes.getNode(n.tls); @@ -39,6 +39,10 @@ module.exports = function(RED) { if (RED.settings.httpRequestTimeout) { this.reqTimeout = parseInt(RED.settings.httpRequestTimeout) || 120000; } else { this.reqTimeout = 120000; } + if (n.paytoqs === true || n.paytoqs === "query") { paytoqs = true; } + else if (n.paytoqs === "body") { paytobody = true; } + + var prox, noprox; if (process.env.http_proxy) { prox = process.env.http_proxy; } if (process.env.HTTP_PROXY) { prox = process.env.HTTP_PROXY; } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index 94d541444..f4cde30b4 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -398,7 +398,11 @@ "status": "Status code", "headers": "Headers", "other": "other", - "paytoqs" : "Append msg.payload as", + "paytoqs" : { + "ignore": "Ignore msg.payload", + "query": "Append msg.payload to query-string parameters", + "body": "Send msg.payload as request Body" + }, "utf8String": "UTF8 string", "binaryBuffer": "binary buffer", "jsonObject": "parsed JSON object", From 7723ff461bc9261c6b750788ff931488130ccb09 Mon Sep 17 00:00:00 2001 From: Ben Hardill Date: Wed, 26 Feb 2020 19:46:54 +0000 Subject: [PATCH 084/346] Remove console.logs --- .../@node-red/nodes/core/network/21-httprequest.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html index 6fea31766..35fb6ed1c 100644 --- a/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html +++ b/packages/node_modules/@node-red/nodes/core/network/21-httprequest.html @@ -174,13 +174,10 @@ console.log("paytoqs: " + this.paytoqs); if (this.paytoqs === true || this.paytoqs == "query") { $("#node-input-paytoqs").val("query"); - console.log("q"); } else if (this.paytoqs === "body") { $("#node-input-paytoqs").val("body"); - console.log("b"); } else { $("#node-input-paytoqs").val("ignore"); - console.log("i"); } if (this.authType) { $('#node-input-useAuth').prop('checked', true); @@ -240,8 +237,6 @@ if (!$("#node-input-useProxy").is(":checked")) { $("#node-input-proxy").val("_ADD_"); } - console.log("save - paytoqs " + this.paytoqs); - console.log("save - paytoqs " + $("#node-input-paytoqs").val()); } }); From 95982ad46493650a725627142872303cc5e0ea3f Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Thu, 27 Feb 2020 16:01:57 +0900 Subject: [PATCH 085/346] Update adminAuth tokensStrategy test spec --- .../editor-api/lib/auth/strategies_spec.js | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js index 13573f22a..848aaf99d 100644 --- a/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js +++ b/test/unit/@node-red/editor-api/lib/auth/strategies_spec.js @@ -130,10 +130,13 @@ describe("api/auth/strategies", function() { }); describe("Tokens Strategy", function() { - it('Succeeds if tokens user enabled',function(done) { - var userDefault = sinon.stub(Users,"tokens",function(token) { + it('Succeeds if tokens user enabled custom header',function(done) { + var userTokens = sinon.stub(Users,"tokens",function(token) { return when.resolve("tokens-"+token); }); + var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { + return "x-test-token"; + }); strategies.tokensStrategy._success = strategies.tokensStrategy.success; strategies.tokensStrategy.success = function(user) { user.should.equal("tokens-1234"); @@ -141,12 +144,31 @@ describe("api/auth/strategies", function() { delete strategies.tokensStrategy._success; done(); }; - strategies.tokensStrategy.authenticate({headers:{"authorization":"1234"}}); + strategies.tokensStrategy.authenticate({headers:{"x-test-token":"1234"}}); + }); + it('Succeeds if tokens user enabled default header',function(done) { + var userTokens = sinon.stub(Users,"tokens",function(token) { + return when.resolve("tokens-"+token); + }); + var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { + return "authorization"; + }); + strategies.tokensStrategy._success = strategies.tokensStrategy.success; + strategies.tokensStrategy.success = function(user) { + user.should.equal("tokens-1234"); + strategies.tokensStrategy.success = strategies.tokensStrategy._success; + delete strategies.tokensStrategy._success; + done(); + }; + strategies.tokensStrategy.authenticate({headers:{"authorization":"Bearer 1234"}}); }); it('Fails if tokens user not enabled',function(done) { - var userDefault = sinon.stub(Users,"tokens",function() { + var userTokens = sinon.stub(Users,"tokens",function() { return when.resolve(null); }); + var userTokenHeader = sinon.stub(Users,"tokenHeader",function(token) { + return "authorization"; + }); strategies.tokensStrategy._fail = strategies.tokensStrategy.fail; strategies.tokensStrategy.fail = function(err) { err.should.equal(401); @@ -154,10 +176,11 @@ describe("api/auth/strategies", function() { delete strategies.tokensStrategy._fail; done(); }; - strategies.tokensStrategy.authenticate({headers:{"authorization":"1234"}}); + strategies.tokensStrategy.authenticate({headers:{"authorization":"Bearer 1234"}}); }); afterEach(function() { Users.tokens.restore(); + Users.tokenHeader.restore(); }) }); From 458d794f52492a311316add9486a3986c2b64904 Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Thu, 27 Feb 2020 19:41:59 +0900 Subject: [PATCH 086/346] Fix tokensStrategy order --- packages/node_modules/@node-red/editor-api/lib/auth/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/index.js b/packages/node_modules/@node-red/editor-api/lib/auth/index.js index c3948cd27..88e924e53 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/index.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/index.js @@ -61,7 +61,7 @@ function init(_settings,storage) { function needsPermission(permission) { return function(req,res,next) { if (settings && settings.adminAuth) { - return passport.authenticate(['bearer','anon','tokens'],{ session: false })(req,res,function() { + return passport.authenticate(['bearer','tokens','anon'],{ session: false })(req,res,function() { if (!req.user) { return next(); } From 83942c255151aee0e92b2045b93a927576483f54 Mon Sep 17 00:00:00 2001 From: KAZUHIRO ITO Date: Thu, 27 Feb 2020 19:55:21 +0900 Subject: [PATCH 087/346] Fix plugin only receives the actual token --- .../@node-red/editor-api/lib/auth/strategies.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js index 9705ddd28..87023a487 100644 --- a/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js +++ b/packages/node_modules/@node-red/editor-api/lib/auth/strategies.js @@ -130,7 +130,14 @@ function TokensStrategy() { util.inherits(TokensStrategy, passport.Strategy); TokensStrategy.prototype.authenticate = function(req) { var self = this; - var token = req.headers[Users.tokenHeader()]; + var token = null; + if (Users.tokenHeader() === 'authorization') { + if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { + token = req.headers.authorization.split(' ')[1]; + } + } else { + token = req.headers[Users.tokenHeader()]; + } if (token) { Users.tokens(token).then(function(admin) { if (admin) { From 2a6bedbd8d2467da4b1f37607cd486902ba02a6a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 27 Feb 2020 11:38:44 +0000 Subject: [PATCH 088/346] update changelog --- CHANGELOG.md | 490 ++++++++++++++++++++++++++++----------------------- 1 file changed, 272 insertions(+), 218 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7b75d30f..7ea3845c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,57 @@ +#### 1.0.4: Maintenance Release + +Runtime + + - Update all dependencies to latest fix versions + - Update JSONata to 1.8.1 + - #2473 Handle httpAdminRoot missing ending slash with login strategy Fixes + - #2470 Update https-proxy-agent + - #2461 Allow credentials to be provided as part of /flows api + - #2444 Move receive metric position to better reflect async changes Fixes + - #2406 Improve file store error when cache disabled and sync api used Closes + - #2399 cloneMessage should handle undefined without throwing err Fixes + - #2418 Fix the library api routes to prevent too broad matching of regex URLs + - #2417 Remove undefined loadFlowLibrary call + +Editor + + - #2465 Add better regex highlighting in jsonata edit mode Fixes + - Add regex awareness to jsonata formatter + - #2472 Avoid adding extra newlines when formating jsonata Fixes + - #2475 Add UI test case for error handling + - Avoid adding extra divs to edit form to avoid size miscalculation + - Upgrade to latest marked and dompurify libs + - Ensure catalog load errors are logged to the console + - #2460 Track context sidebar element paths to track formatting changes Fixes + - Battling Chrome Autocomplete, part 31: Wrap search input with form + - #2445 Trick chrome into autofilling dummy username/password inputs Fixes + - #2457 Fix garbled characters in library + - #2409 Filter palette using raw label not html formatted label Fixes + - #2400 Wrap long context values when displaying in sidebar Fixes + - Fix duplicating array item in visual json editor + - #2338 Modify history sidebar button positioning to handle long labels Fixes + - #2438 Add some auto-complete snippets to the nrjavascript mode Close + - #2430 Ignore disabled nodes when checking for invalid configs on deploy Closes + - #2442 #2458 #2453 Update zh-CN translations + - #2235 Add initial zh-TW translation + - Re-enable jshint on editor and fixup issues + - #2431 Remove unnecessary namespaces for i18n + - #2440 Support BrowserStack in UI testing + - #2358 Add path property to debug messages Fixes + - Fix false change detection when no config node selected + - Fix IME bug in text editor + - Make node highlighting a bit more obvious for busy flows + - #2392 Add icons and support i18n in typedInput of JSON editor + +Nodes + - #2462 MQTT: Ensure IPv6 broker names are wrapped in brackets Fixes + - Join node - check existance before clearing timeout + - Trigger: Complete 2nd msg when set to send latest + - TCP: clarify text regarding blank parameters. + - #2449 HTTP Request: Add HEAD as Method + - Make min-height for change, switch, batch and mqtt consistent + + #### 1.0.3: Maintenance Release Runtime @@ -44,7 +98,7 @@ Runtime Editor - Ensure node status is refreshed whenever node is edited - - #2315 #2316 Ensure z property included in full message debug payload + - #2316 Ensure z property included in full message debug payload #2315 - #2321 Fixed editor.json (JA nls) - #2313 Fix element to collapse items in visual JSON editor - #2314 Insert divider in menu by calling RED.menu.addItem('id', null); @@ -113,9 +167,9 @@ Editor - Move flow-status button to footer for consistency - Fix node hover effect to prevent jumping position - Filter quick-add properly when splicing a wire - - Mark workspace dirty when deleting link node link Fixes #2274 + - #2274 Mark workspace dirty when deleting link node link Fixes - Add red-ui-button class to strategy login button - - Fix padding of subflow locale select Closes #2276 + - #2276 Fix padding of subflow locale select Closes - Update info text of complete node & add JP text - Add class red-ui-button to cancel button - Add css class to login submit button (#2275) @@ -138,12 +192,12 @@ Runtime - [FEATURE] Add Node Done API - make message passing async - Ensure the subflow stop promise is waiting for before restarting - Limit the regex for the /nodes/ api end points - - Add error event handler to ssh-keygen child_process Fixes #2255 - - Fix default value handling on context array access Fixes #2252 + - #2255 Add error event handler to ssh-keygen child_process Fixes + - #2252 Fix default value handling on context array access Fixes - Remove all ui test dependencies from package.json - Add req back to audit log events and extend to Projects api - - Ensure 2nd arg to node.error is an object Fixes #2228 - - Use a more atomic process for writing context files Fixes #2271 + - #2228 Ensure 2nd arg to node.error is an object Fixes + - #2271 Use a more atomic process for writing context files Fixes Editor @@ -162,25 +216,25 @@ Editor - [FEATURE] add support for specifying subflow template color - [FEATURE] Use ctrl-click on wire to splice node in place - [FEATURE] Allow search results to show more than 25 results - - [FEATURE] Allow a node to change if it has an input port Closes #2268 - - Revealing node position needs to account for zoom level Fixes #2172 - - Fix typedInput option selection Fixes #2174 - - Fix palette node id handling so search works Fixes #2173 + - #2268 [FEATURE] Allow a node to change if it has an input port Closes + - #2172 Revealing node position needs to account for zoom level Fixes + - #2174 Fix typedInput option selection Fixes + - #2173 Fix palette node id handling so search works Fixes - Add popover tooltips to debug sidebar,function and template - Add popovers to context sidebar mini buttons - Ensure node status icon is shown when value set - Revert treeList children function signature change - Restore tray component css for compatibility. Mark as deprecated - fix function name & string compare function - - Handle empty list of example flows Fixes #2171 + - #2171 Handle empty list of example flows Fixes - Ensure library list has an item selected when opened - Ensure tooltip popover doesn't replace normal popover - Fix clipboard export download button - Ensure input box has focus on repeated quick add - Fix width calculation of typedInput - Remove some hardcoded css colors - - Fix display of node help when clicking in palette Fixes #2194 - - Ensure node help is loaded in the right language Fixes #2195 + - #2194 Fix display of node help when clicking in palette Fixes + - #2195 Ensure node help is loaded in the right language Fixes - Do not allow tab focus on clipboard hidden element - Fix undefined error on typedInput due to valueLabel used before being added - Fix undo of flow disable state change @@ -193,7 +247,7 @@ Editor - Update all node icons to SVG - Handle png/svg fallback for def.icon values. Remove old pngs - Ignore empty examples directories (don't add to import menu) - - better handle example file at any depth - #2222 + - #2222 better handle example file at any depth - - Properly escape node types in palette - Ensure session expiry timeout doesn't exceed limit - Use node/tab map to make filterNodes more efficient @@ -201,22 +255,22 @@ Editor - Handle undefined node.\_def in edit stack title. - fix converting selection to subflow - Fix inserting new subflow node to existing wire between nodes - - Support displaying falsey node status values Fixes #2246 + - #2246 Support displaying falsey node status values Fixes - Remove tab menu from node property UI for subflow and config nodes - - Mark workspace dirty when shift-click-drag detaches wires Fixes #2260 + - #2260 Mark workspace dirty when shift-click-drag detaches wires Fixes - Fix subflow category change on palette Nodes - Remove pi gpi, twitter, email and feedparser nodes from core - - Fix error handling in Websocket broadcast function Fixes #2182 + - #2182 Fix error handling in Websocket broadcast function Fixes - Handle websocket item being parseable but not an object better - stop join tripping up if last message of buffer is blank. - Add support for env var propety in switch node - Improve handling of file upload in request node - Add "has key" rule to switch node + tests - Optimise generation of switch node edit dialog - - Add keep-alive option to HTTP Request - #2261 + - #2261 Add keep-alive option to HTTP Request - #### 1.0.0-beta.2: Beta Release @@ -228,7 +282,7 @@ Runtime Runtime - Update runtime apis to support multiple libraries - Add Node 12 to travis (allow_failures) - - Bump all dependencies Fixes #2152 + - #2152 Bump all dependencies Fixes Editor - [BREAKING] complete overhaul of editor DOM/CSS structure @@ -240,17 +294,17 @@ Editor - Allow script tags with src to reference esm modules - Upgrade to jq 3.4.1 / jq-ui 1.12.1 - Allow editor language to be chosen in editor settings - - Only NLS status text that starts with a letter Fixes #2128 - - Fix display of link node list within subflow Fixes #2140 - - Blur the active element when closing edit dialog via action Fixes #2097 - - Trigger change evnt on typedInput when type changes and options present Fixes #2160 + - #2128 Only NLS status text that starts with a letter Fixes + - #2140 Fix display of link node list within subflow Fixes + - #2097 Blur the active element when closing edit dialog via action Fixes + - #2160 Trigger change evnt on typedInput when type changes and options present Fixes - Move library import/export to single dialog - Move type-library dialogs to new style dialog - Fix node drag and drop animation - let status be simple text if wanted - Add workspace statusBar - Complete refresh of German translations - - Fix memory leak in Debug sidebar #2163 + - #2163 Fix memory leak in Debug sidebar - Introduce toggleButton and move flow-disabled to use it - Allow RED.settings.get/set to use full property desc - Add auto-refresh toggle to context sidebar @@ -269,26 +323,26 @@ Nodes #### 0.20.8: Maintenance Release - Sanitize tab name in edit dialog - - Pass httpServer to runtime even when httpAdmin disabled Fixes #2272 + - #2272 Pass httpServer to runtime even when httpAdmin disabled Fixes #### 0.20.7: Maintenance Release - - Update jsonata to 1.6.5 which should fix #2183 + - #2183 Update jsonata to 1.6.5 which should fix - Ensure the subflow stop promise is waiting for before restarting - Properly escape node types in palette #### 0.20.6: Maintenance Release - - Revealing node position needs to account for zoom level Fixes #2172 + - #2172 Revealing node position needs to account for zoom level Fixes - stop join tripping up if last message of buffer is blank. - Improve handling of file upload in request node - - Handle subflow internal node wired to a non-existant node Fixes #2202 + - #2202 Handle subflow internal node wired to a non-existant node Fixes - Do not save subflow env vars with blank names - Don't allow a link node virtual wire to connect to normal port - - Clear HTTP Request node authType when auth disabled Fixes #2215 - - Fix parsing of content-type header Fixes #2216 + - #2215 Clear HTTP Request node authType when auth disabled Fixes + - #2216 Fix parsing of content-type header Fixes - Fix join node reset issue with merging objects - - Copy data-i18n attribute on TypedInput Fixes #2211 + - #2211 Copy data-i18n attribute on TypedInput Fixes #### 0.20.5: Maintenance Release @@ -325,27 +379,27 @@ Nodes #### 0.20.1: Maintenance Release - - Ensure all subflow instances are stopped when flow stopping Fixes #2095 - - modify name of korean locale forders #2091 + - #2095 Ensure all subflow instances are stopped when flow stopping Fixes + - #2091 modify name of korean locale forders - Ensure node names are sanitized before being presented - - Subflow status node must pass status to parent flow Fixes #2087 - - fix problem on displaying option label on Firefox #2090 + - #2087 Subflow status node must pass status to parent flow Fixes + - #2090 fix problem on displaying option label on Firefox #### 0.20.0: Milestone Release Runtime - Pass complete status to Status node and filter to editor - - Ensure flows wait for all nodes to close before restarting Fixes #2067 + - #2067 Ensure flows wait for all nodes to close before restarting Fixes - Fix git clone with password protected key - Allow a project to be located below the root of repo - Detect the cloning of an empty git repo properly - Fix use of custom auth strategy plugins - - Remove remnants of when library in git/index Fixes #2057 + - #2057 Remove remnants of when library in git/index Fixes - Clear subflow status on close - Add exportGlobalContextKeys to prevent exposing functionGlobalContext keys - Add --no-audit and --no-update-notifier flags to npm commands to reduce workload - Add envVarExcludes setting to block named env vars - - Update settings.js docs on userDir to match reality Fixes #2082 + - #2082 Update settings.js docs on userDir to match reality Fixes - Add Korean Language @@ -389,16 +443,16 @@ Editor - Add env type to subflow env var types - Display parent subflow properties in edit dialog - Fix direction value of subflow output - - Add Status Node to Subflow to allow subflow-specific status Closes #597 - - Better handling of multiple flow merges Fixes #2039 + - #597 Add Status Node to Subflow to allow subflow-specific status Closes + - #2039 Better handling of multiple flow merges Fixes Nodes - Various translation updates - - Catch: Add 'catch uncaught only' mode. Closes #1747 + - #1747 Catch: Add 'catch uncaught only' mode. Closes - Link: scroll to current flow in node list - HTTPRequest: add option to urlencode cookies - - HTTPRequest: option to use msg.payload as query params on GET. #1981 + - #1981 HTTPRequest: option to use msg.payload as query params on GET. - Debug: Add local time display option to numerics in debug window - MQTT: Add parsed JSON output option @@ -416,9 +470,9 @@ Editor - German translation - Change default dropdown appearance and sidebar tab menu handling - - Handle multiple-select box when nothing selected Fixes #2021 - - Handle i18n properly when key is a valid sub-identifier Fixes #2028 - - Avoid duplicate links when missing node type installed Fixes #2032 + - #2021 Handle multiple-select box when nothing selected Fixes + - #2028 Handle i18n properly when key is a valid sub-identifier Fixes + - #2032 Avoid duplicate links when missing node type installed Fixes - Add View Tools - Don't collapse version control header when clicking refresh - Add fast entry via keyboard for string of nodes @@ -438,15 +492,15 @@ Editor - Update palette manager view properly when module updated - Add TreeList common widget - - Fix visual jump when opening Comment editor on Safari Part of #2008 - - Fix vertical align of markdown editor in Safari Fixes #2008 - - Avoid marking node as changed if label state is default Fixes #2009 + - #2008 Fix visual jump when opening Comment editor on Safari Part of + - #2008 Fix vertical align of markdown editor in Safari Fixes + - #2009 Avoid marking node as changed if label state is default Fixes - Highlight port on node hover while joining - Support drag-wiring of link nodes - Allow TypeSearch to include a filter option - Improve diff colouring - Allow sections to toggle in 2-element stack - - Add support for ${} env var syntax when skipping validation Closes #1980 + - #1980 Add support for ${} env var syntax when skipping validation Closes - i18 support for markdown editor tooltip - Add RED.editor.registerTypeEditor for custom type editors - Tidy up markdown toolbar handling across all editors @@ -456,15 +510,15 @@ Editor Runtime - - Bump JSONata to 1.6.4 Fixes #2023 + - #2023 Bump JSONata to 1.6.4 Fixes - Add audit logging to admin api - - Fix failure of RED.require #2010 - - Allow oauth strategy callback method to be customised Closes #1998 - - Ensure fs context cache is flushed on close Fixes #2001 + - #2010 Fix failure of RED.require + - #1998 Allow oauth strategy callback method to be customised Closes + - #2001 Ensure fs context cache is flushed on close Fixes - Fix library Buffer( to Buffer.alloc( for node 10 - Catch file-not-found on startup when non-existant flow file specified - Actively expire login sesssions and notify user - - Add quotation marks for basic auth challenge #1976 + - #1976 Add quotation marks for basic auth challenge Nodes @@ -482,7 +536,7 @@ Nodes Editor - Allow the editor to use a custom admin api url root - - Improve performance of Flow Diff dialog - @TothiViseo #1989 + - #1989 Improve performance of Flow Diff dialog - @TothiViseo - Add 'open project' option to Projects Welcome dialog - Add 'type already registered' check in palette editor - Handle missing tab.disabled property @@ -500,11 +554,11 @@ Editor - Allow left-hand node button to act as toggle - Support dbl-click in tab bar to add new flow in position - Fix duplicate subflow detection on import - - Add import notification with info on what has been imported Closes #1862 + - #1862 Add import notification with info on what has been imported Closes - Show error details when trying to import invalid json - Show default icon when non-existent font-awesome icon was specified - Add configurable option for showing node label - - Avoid http redirects as Safari doesn't reuse Auth header Fixes #1903 + - #1903 Avoid http redirects as Safari doesn't reuse Auth header Fixes - Tidy up ace tooltip styling - Add event log to editor - Add tooltips to multiple editor elements @@ -532,30 +586,30 @@ Editor Runtime - Allow a project to be loaded from cmdline - - Handle lookup of undefined property in Global context Fixes #1978 + - #1978 Handle lookup of undefined property in Global context Fixes - Refuse to enable Manage Palette if npm too old - Remove restriction on upgrading non-local modules - - Remove deprecated Buffer constructor usage Fixes #1709 + - #1709 Remove deprecated Buffer constructor usage Fixes - Update httpServerOptions doc in settings.js - Exclude non-testable .js files from the unit tests - Add --safe mode flag to allow starting without flows running - - Add setting-defined accessToken for automated access to the adminAPI - #1789 + - #1789 Add setting-defined accessToken for automated access to the adminAPI - Nodes - - Move all core node EN help to their own locale files - #1990 + - #1990 Move all core node EN help to their own locale files - - CSV: better regex for number detection - Debug: hide button if not configured to send to sidebar - Delay: report queue activity when in by-topic mode - Delay: add msg.flush mode - Exec: Preserve existing properties on msg object - File: remove CR/LF from incoming filename - - Function: create custom ace javascript mode to handle ES6 Fixes #1911 + - #1911 Function: create custom ace javascript mode to handle ES6 Fixes - Function: add env.get - - HTTP Request: Add http-proxy config #1913 + - #1913 HTTP Request: Add http-proxy config - HTTP Request: add msg.redirectList to output - - HTTP Request: add msg.requestTimeout option for per-message setting - @natcl #1959 - - MQTT: add auto-detect and base64 output to mqtt node Fixes #1912 - @DurandA + - #1959 HTTP Request: add msg.requestTimeout option for per-message setting - @natcl + - #1912 - @DurandA MQTT: add auto-detect and base64 output to mqtt node Fixes - MQTT: only unsubscribe node that is being removed - Sentiment: move to node-red-node-sentiment - Switch: add missing edit dialog icon @@ -570,48 +624,48 @@ Nodes #### 0.19.6: Maintenance Release - - Fix encoding of file node from binary to utf8 - #2051 + - #2051 Fix encoding of file node from binary to utf8 - #### 0.19.5: Maintenance Release - Recognize pip installs of RPi.GPIO (#1934) - - Merge pull request #1941 from node-red-hitachi/master-batch - - Merge pull request #1931 from node-red-hitachi/master-typedinput + - #1941 from node-red-hitachi/master-batch Merge pull request + - #1931 from node-red-hitachi/master-typedinput Merge pull request - Set min value of properties and spinners for batch - Fix that unnecessary optionMenu remains - - Merge pull request #1894 from node-red-hitachi/fix-overlapping-file-node-execution - - Merge pull request #1924 from imZack/patch-1 + - #1894 from node-red-hitachi/fix-overlapping-file-node-execution Merge pull request + - #1924 from imZack/patch-1 Merge pull request - Add missing comma - - Do not disable context sidebar during node edit Fixes #1921 - - Don't allow virtual links to be spliced Fixes #1920 + - #1921 Do not disable context sidebar during node edit Fixes + - #1920 Don't allow virtual links to be spliced Fixes - Merge project package changes to avoid overwritten changes - - Handle manually added project deps that are unused Fixes #1908 + - #1908 Handle manually added project deps that are unused Fixes - update close & input handling of File node - make close handler argument only one - - Merge pull request #1907 from amilajack/patch-2 + - #1907 from amilajack/patch-2 Merge pull request - Change repo badge to point to master branch - invoke callbacks if async handler is specified - - Merge pull request #1891 from camlow325/resolve-example-path-for-windows-support - - Merge pull request #1900 from kazuhitoyokoi/master-addtestcases4settings.js + - #1891 from camlow325/resolve-example-path-for-windows-support Merge pull request + - #1900 from kazuhitoyokoi/master-addtestcases4settings.js Merge pull request - wait closing while pending messages exist - Add test cases for red/api/editor/settings.js - - Ensure all palette categories are opened properly Closes #1893 + - #1893 Ensure all palette categories are opened properly Closes - Resolve path when sending example file for Windows support - fix multiple input message processing of file node #### 0.19.4: Maintenance Release - - Fix race condition in non-cache lfs context Fixes #1888 + - #1888 Fix race condition in non-cache lfs context Fixes - LocalFileSystem Context: Remove extra flush code - Prevent race condition in caching mode of lfs context (#1889) - Allow context store name to be provided in the key - Switch node: only use promises when absolutely necessary - Fix dbl-click handling on webkit-based browsers - Ensure context.flow/global cannot be deleted or enumerated - - Handle context.get with multiple levels of unknown key Fixes #1883 + - #1883 Handle context.get with multiple levels of unknown key Fixes - Fix global.get("foo.bar") for functionGlobalContext set values - Fix node color bug (#1877) - - Merge pull request #1857 from cclauss/patch-1 + - #1857 from cclauss/patch-1 Merge pull request - Define raw_input() in Python 3 & fix time.sleep() #### 0.19.3: Maintenance Release @@ -626,14 +680,14 @@ Nodes - TCP-request node - only write payload - JSON schema: perform validation when obj -> obj or str -> str - JSON schema: add draft-06 support (via $schema keyword) - - Mqtt proxy configuration for websocket connection, #1651. + - #1651. Mqtt proxy configuration for websocket connection, - Allows MQTT Shared Subscriptions for MQTT-In core node - Fix use of HTML tag or CSS class specification as icon of typedInput #### 0.19.2: Maintenance Release - Ensure node default colour is used if palette.theme has no match - - fix lost messages / properties in TCPRequest Node; closes #1863 (#1864) + - #1863 (#1864) fix lost messages / properties in TCPRequest Node; closes - Fix typo in template.html - Improve error reporting from context plugin loading - Prevent no-op edit of node marking as changed due to icon @@ -653,8 +707,8 @@ Nodes Editor - Add editorTheme.palette.theme to allow overriding colours - - Index all node properties when searching Fixes #1446 - - Handle NaN and Infinity properly in debug sidebar Fixes #1778 #1779 + - #1446 Index all node properties when searching Fixes + - #1779 Handle NaN and Infinity properly in debug sidebar Fixes #1778 - Prevent horizontal scroll when palette name cannot wrap - Ignore middle-click on node/ports to enable panning - Better wire layout when looping back @@ -668,7 +722,7 @@ Editor - Only edit nodes on dbl click on primary button with no modifiers - Allow subflows to be put in any palette category - Add flow navigator widget - - Cache flow library result to improve response time Fixes #1753 + - #1753 Cache flow library result to improve response time Fixes - Add middle-button-drag to pan the workspace - allow multi-line category name in editor - Redesign sidebar tabs @@ -676,20 +730,20 @@ Editor Nodes - - Change: Ensure runtime errors in Change node can be caught Fixes #1769 + - #1769 Change: Ensure runtime errors in Change node can be caught Fixes - File: Add output to File Out node - Function: add expandable JavaScript editor pane - Function: allow id and name reference in function node code (#1731) - HTTP Request: Move to request module - - HTTP: Ensure apiMaxLength applies to HTTP Nodes Fixes #1278 + - #1278 HTTP: Ensure apiMaxLength applies to HTTP Nodes Fixes - Join: accumulate top level properties - Join: allow environment variable as reduce init value - JSON: add JSON schema validation via msg.schema - Pi: Let nrgpio code work with python 3 - Pi: let Pi nodes be visible/editable on all platforms - Switch: add isEmpty rule - - TCP: queue messages while connecting; closes #1414 - - TLS: Add servername option to TLS config node for SNI Fixes #1805 + - #1414 TCP: queue messages while connecting; closes + - #1805 TLS: Add servername option to TLS config node for SNI Fixes - UDP: Don't accidentally re-use udp port when set to not do so Persistent Context @@ -704,8 +758,8 @@ Persistent Context Runtime - Support flow.disabled and .info in /flow API - - Node errors should be Strings not Errors Fixes #1781 - - Add detection of connection timeout in git communication Fixes #1770 + - #1781 Node errors should be Strings not Errors Fixes + - #1770 Add detection of connection timeout in git communication Fixes - Handle loading empty nodesDir - Add 'private' property to userDir generated package.json - Add RED.require to allow nodes to access other modules @@ -715,7 +769,7 @@ Runtime Editor Fixes - - Do not trim wires if node declares outputs in defaults but misses value Fixes #1737 + - #1737 Do not trim wires if node declares outputs in defaults but misses value Fixes Node Fixes @@ -725,16 +779,16 @@ Node Fixes - typo fix in node help (#1735) Other Fixes - - Tidy up default grunt task and fixup test break due to reorder Fixes #1738 + - #1738 Tidy up default grunt task and fixup test break due to reorder Fixes - Bump jsonata version #### 0.18.6: Maintenance Release Editor Fixes - - Handle a node having wires in the editor on ports it no longer has Fixes #1724 + - #1724 Handle a node having wires in the editor on ports it no longer has Fixes - Add missing ACE snippet files - - Fix wireClippedNodes is not defined Fixes #1726 + - #1726 Fix wireClippedNodes is not defined Fixes - Split node html to isolate bad nodes when loading - Avoid unnecessary use of .html() where .text() will do @@ -761,32 +815,32 @@ New Features Editor Fixes - - Highlight subflow node when log msg comes from inside Fixes #1698 - - Ensure node wires array is not longer than outputs value Fixes #1678 - - Allow importing an unknown config node to be undone Fixes #1681 - - Ensure keyboard shortcuts get saved in runtime settings Fixes #1696 + - #1698 Highlight subflow node when log msg comes from inside Fixes + - #1678 Ensure node wires array is not longer than outputs value Fixes + - #1681 Allow importing an unknown config node to be undone Fixes + - #1696 Ensure keyboard shortcuts get saved in runtime settings Fixes - Don't mark a subflow changed when actually modified nothing (#1665) Node Fixes - bind to correct port when doing udp broadcast/multicast (#1686) - Provide full error stack in Function node log message (#1700) - - Fix http request doc type Fixes #1690 + - #1690 Fix http request doc type Fixes - Make debug slightly larger to pass WCAG AA rating - - Make core nodes labels more consistent, to close #1673 - - Allow template node to be updated more than once Fixes #1671 + - #1673 Make core nodes labels more consistent, to close + - #1671 Allow template node to be updated more than once Fixes - Fix the problem that output labels of switch node sometimes disappear (#1664) - Chinese translations for core nodes (#1607) Runtime Fixes - - Handle and display for invalid flow credentials when project is disabled #1689 (#1694) + - #1689 (#1694) Handle and display for invalid flow credentials when project is disabled - node-red-pi: fix behavior with old bash version (#1713) - Fix ENOENT error on first start when no user dir (#1711) - - Handle null error object in Flow.handleError Fixes #1721 + - #1721 Handle null error object in Flow.handleError Fixes - update settings comments to describe how to setup for ipv6 (#1675) - - Remove credential props after diffing flow to prevent future false positives Fixes #1359 - - Log error if settings unavailable when saving user settings Fixes #1645 + - #1359 Remove credential props after diffing flow to prevent future false positives Fixes + - #1645 Log error if settings unavailable when saving user settings Fixes - Keep backup of .config.json - Add warning if using \_credentialSecret from .config.json - Filter req.user in /settings to prevent potentially leaking info @@ -812,7 +866,7 @@ Editor Fixes - Fix merging a remote diff - Fixed the problems when using a node without defaults - Disable user defined icon for subflow - - getDefaultNodeIcon should handle subflow instance nodes Fixes #1635 + - #1635 getDefaultNodeIcon should handle subflow instance nodes Fixes - Add Japanese info text for core nodes - Fix message lookup for core nodes in case of i18 locales directory exists - Prevent the last tab from being deleted @@ -843,7 +897,7 @@ Editor Fixes - Fix offset calculation when dragging node from palette - Allow a library entry to use non-default node-input- prefixes - - Change remote-diff shortcut and add it to keymap Fixes #1628 + - #1628 Change remote-diff shortcut and add it to keymap Fixes #### 0.18.2: Maintenance Release @@ -882,7 +936,7 @@ Projects - Handle more repo clone error cases - Relax validation of git urls - Revalidate project name on return to project-details view - - Avoid unnecessary project refresh on branch-switch Fixes #1597 + - #1597 Avoid unnecessary project refresh on branch-switch Fixes - Add support for file:// git urls - Handle project first-run without existing flow file - Handle delete of last remote in project settings @@ -893,9 +947,9 @@ Projects Node Fixes - Trigger node migration - ensure bytopic not blank - - Add HEAD to list of methods with no body in http req node #1598 - - Do not include payload in GET requests Fixes #1598 - - Update sort/batch docs Fixes #1601 + - #1598 Add HEAD to list of methods with no body in http req node + - #1598 Do not include payload in GET requests Fixes + - #1601 Update sort/batch docs Fixes - Don't assume node has defaults when exporting icon property @@ -908,11 +962,11 @@ Runtime - Better error reporting when module provides duplicate type - Update jsonata to 1.5.0 - add express-session memorystore without leaks (#1435) - - Allow adminAuth.user to be a Function Fixes #1461 + - #1461 Allow adminAuth.user to be a Function Fixes - Ensure RED.server is set even if admin api disabled - - Ensure strategy login button uses relative URL Fixes #1481 + - #1481 Ensure strategy login button uses relative URL Fixes - ignore `_msgid` when merging full objects - - Move node install to spawn to allow for big stdout Fixes #1488 + - #1488 Move node install to spawn to allow for big stdout Fixes - SIGINT handler should wait for stop to complete before exit Editor @@ -920,12 +974,12 @@ Editor - allow a node's icon to be set dynamically (#1490) - Batch messages sent over comms to increase throughput - Migrate deploy confirmations to notifications - - `oneditdelete` should be available to all node types Closes #1346 + - #1346 `oneditdelete` should be available to all node types Closes - Sort typeSearch results based on position of match - Update ACE to test and add python highlighter (#1373) - - Clear mouse state when typeSearch cancelled Fixes #1517 + - #1517 Clear mouse state when typeSearch cancelled Fixes - Handle scoped modules via palette editor - - TypedInput: handle user defined value/labels options Fixes #1549 + - #1549 TypedInput: handle user defined value/labels options Fixes Nodes @@ -940,7 +994,7 @@ Nodes - Add support for rejectUnauthorized msg property - Add TLS options to WebSocket client - Added parsed YAML support for template node (#1443) - - Allow delay node in rate-limit mode to be reset Fixes #1360 + - #1360 Allow delay node in rate-limit mode to be reset Fixes - Allow setTimeout in Function node to be promisified in node 8 - Debug to status option (#1499) - enable template config via msg.template for stored or generated templates (#1503) @@ -959,13 +1013,13 @@ Nodes - MQTT node - if Server/URL config contains '//' use it as a complete url; enabled ws:// and wss:// - clone messages before delayed send (#1474) - Decrement connected client count rather than show disconnected - - Don't end mqtt client on first error Fixes #1566 - - File out - create dirs synchronously to ensure they exist Fixes #1489 + - #1566 Don't end mqtt client on first error Fixes + - #1489 File out - create dirs synchronously to ensure they exist Fixes - Fix debug message format for Buffer (#1444) - Fix global.keys() bug in function node (#1417) - Handle escape characters in template node which uses Mustache format and JSON output mode (#1377) - - Move all node.send to end of timer functions in trigger node (issue #1527) (#1539) - - Publish null/undefined to mqtt as blank not toString Fixes #1521 + - #1527) (#1539) Move all node.send to end of timer functions in trigger node (issue + - #1521 Publish null/undefined to mqtt as blank not toString Fixes - remove inject node at specific time spinner - restrict inject interval to less that 2^31 millisecs - tag UDP ports in use properly so they get closed correctly (#1508) @@ -974,10 +1028,10 @@ Nodes - Add express-session missing dependency for oauth - Fix improper type tests is core test cases - - File node: recreate write stream when file deleted Fixes #1351 + - #1351 File node: recreate write stream when file deleted Fixes - Add flow stopping trace messages - Fix userDir test case when .config.json exists (#1350) - - Do not try to send msg after http request error handled Fixes #1344 + - #1344 Do not try to send msg after http request error handled Fixes - Fix boundary problem in range node (#1338) - Modify messages in node properties to refer messages.json (#1339) - Fix settings.js replacing webSocketVerifyClient by webSocketNodeVerifyClient (#1343) @@ -988,16 +1042,16 @@ Nodes - Add request node test case for POSTing 0 - Allow false and 0 in payload for httprequest (#1334) - Add file extension into flow name of library automatically (#1331) - - Fix accessing global context from jsonata expressions Fixes #1335 - - Disable editor whilst a deploy is inflight Fixes #1332 + - #1335 Fix accessing global context from jsonata expressions Fixes + - #1332 Disable editor whilst a deploy is inflight Fixes - Replace Unknown nodes with their real versions when node loaded - Retry auto-install of modules that fail - Fix column name in link nodes to refer language file (#1330) - - Use namespaces with link node title attributes i18n name Fixes #1329 - - Tidy up GPIO pin table presentation Fixes #1328 + - #1329 Use namespaces with link node title attributes i18n name Fixes + - #1328 Tidy up GPIO pin table presentation Fixes - Join: count of 0 should not send on every msg - Handle importing only one end of a link node pair - - Make sending to Debug synchronous again Fixes #1323 + - #1323 Make sending to Debug synchronous again Fixes - Make send-error behaviour optional in file node - Restore File In node behaviour of sending msg on error - Expose context.keys within Function node @@ -1011,10 +1065,10 @@ Nodes - Fix missing icons for some nodes (#1321) - Add reformat button to JSONata test data editor - Update delay node status without spawning unnecessary intervals - - Avoid stringify ServerResponse and Socket in Debug node Fixes #1311 + - #1311 Avoid stringify ServerResponse and Socket in Debug node Fixes - Fix creating userDir other than system drive on Windows (#1317) - - Trigger node not handling a duration of 0 as block mode Fixes #1316 - - Unable to config GPIO Pin 13 Fixes #1314 + - #1316 Trigger node not handling a duration of 0 as block mode Fixes + - #1314 Unable to config GPIO Pin 13 Fixes #### 0.17.2: Maintenance Release @@ -1023,7 +1077,7 @@ Nodes #### 0.17.1: Maintenance Release - Fix PI gpio to use BCM - - Prevent event thread contention when sending to Debug node Closes #1311 + - #1311 Prevent event thread contention when sending to Debug node Closes - Fix Bug: Can not display node icon when npm package has scope (#1305) (#1309) - Clear moved flag when nodes are deployed @@ -1031,7 +1085,7 @@ Nodes Runtime - - Return flow rev on reload api when api v2 enabled Closes #1273 + - #1273 Return flow rev on reload api when api v2 enabled Closes - Provide single endpoint to load all node message catalogs - Add .trace and .debug to Node prototype - Rename oauth auth scheme to strategy as it works for openid @@ -1039,16 +1093,16 @@ Runtime - Add support for oauth adminAuth configs - Cache auth details to save needlessly recalculating hashes - Add context.keys function to list top-level keys - - Strip BOM character from JSON files if present Fixes #1239 + - #1239 Strip BOM character from JSON files if present Fixes - Version check no meta (#1243) - - Ensure all nodes have access to global context Fixes #1230 - - Don't process subscription for unauthenticated comms link Fixes #851 - - Clone credentials when passing to node Fixes #1198 + - #1230 Ensure all nodes have access to global context Fixes + - #851 Don't process subscription for unauthenticated comms link Fixes + - #1198 Clone credentials when passing to node Fixes - Resolve dir argument of getLocalNodeFiles function (#1216) - Add wait for writing a library entry into a file. (#1186) - Use correct Buffer.from method rather than constructor - update core nodes to use newer Buffer syntax - - Treat missing msg properties as undefined rather than throw error Fixes #1167 + - #1167 Treat missing msg properties as undefined rather than throw error Fixes - Allows flows to be enabled/disabled in the runtime - add off option to logging settings comment - Log error stack traces if verbose flag is set @@ -1081,8 +1135,8 @@ Nodes - Fix wrong number of double quotes in CSV parsing - let csv node handle ip addresses without trying to parse - Update debug node to register the settings it uses - - Handle IncomingMessage/ServerResponse object types in debug Fixes #1202 - - Toggling debug node enabled/disabled state should set state dirty Fixes #1203 + - #1202 Handle IncomingMessage/ServerResponse object types in debug Fixes + - #1203 Toggling debug node enabled/disabled state should set state dirty Fixes - redo delay node status messages to be interval based - Update delay node ui - Add new msg.delay option to delay node @@ -1118,16 +1172,16 @@ Nodes - First pass of new node-info style - MQTT new style info - Fix empty extra node help content issue - - Handle HTTP In url that is missing its leading / Fixes #1218 + - #1218 Handle HTTP In url that is missing its leading / Fixes - Add file upload support to HTTP In node - HTTP Request node: add info on how to do form encoding - - Prevent unmodified msg.headers from breaking HTTP Request flows Closed #1015 + - #1015 Prevent unmodified msg.headers from breaking HTTP Request flows Closed - Add cookie handling to HTTP Request node - Add guard against the http-request buffer fix being reverted - Multipart streaming - Add http-request node unit tests - http request node add transport validity check and warn. - - Update follow_redirects to fix http_proxy handling Fixes #1172 + - #1172 Update follow_redirects to fix http_proxy handling Fixes - Allow statusCode/headers to be set directly within HTTP Response node - let inject "between time" also fire at start - Plus new info - remove repeat symbol from inject if repeat is 0 @@ -1176,7 +1230,7 @@ Nodes - Move udp sock error listener to only be instantiated once. - Let watch node recurse into subdirectories - Misconfigured WebSocket nodes should not register msg handlers - - Add websocketVerifyClient option to enable custom websocket auth Fixes #1127 + - #1127 Add websocketVerifyClient option to enable custom websocket auth Fixes Editor @@ -1198,11 +1252,11 @@ Editor - Remove unused modified flag on debug messages - Add copy path/value buttons to debug messages - dont match only part of the node type (#1242) - - Add editorTheme.logout.redirect to allow redirect on logout Closes #1213 - - Handle logging out and already logged-out editor Fixes #1288 + - #1213 Add editorTheme.logout.redirect to allow redirect on logout Closes + - #1288 Handle logging out and already logged-out editor Fixes - Fix bug: Export Subflows (#1282) - destroy editor to ensure fully removed on close (function, template, comment) - - Don't try to nls status text starting with '.' Fixes #1258 + - #1258 Don't try to nls status text starting with '.' Fixes - Add note of removed flows in diffConfig (#1253) - Add description to flow same as subflow - Allow tabs to be enabled/disabled in the editor @@ -1274,7 +1328,7 @@ Editor - Allow RED.validators.number to allow blank values as valid - Support dropping json files into the editor - NLS Expression/JSON editor and fix their height calculation - - Update JSONata to 1.2.4 Closes #1275 + - #1275 Update JSONata to 1.2.4 Closes - Remember test expression data on a per-node basis - NLS jsonata test messages - Add JSONata expr tester and improved feedback @@ -1291,13 +1345,13 @@ Other #### 0.16.2: Maintenance Release - - Ensure custom mustache context parent set in Template node fixes #1126 + - #1126 Ensure custom mustache context parent set in Template node fixes - Display debug node name in debug panel if its known - Ensure auth-tokens are removed when no user is specified in settings - Ensure all a tags have blank target in info sidebar - Ensure links do not span tabs in the editor - Avoid creating multiple reconnect timers in websocket node - - Fix inner reference in install fail message catalog entry Fixes #1120 + - #1120 Fix inner reference in install fail message catalog entry Fixes - Display buffer data properly for truncated buffers under Object property #### 0.16.1: Maintenance Release @@ -1305,9 +1359,9 @@ Other - Add colour swatches to debug when hex colour matched - Nodes with hasUsers set to false should not appear unused - Change hard error to verbose warning if using old node.js level - - Don't filter debug properties starting with _ Fixes #1117 - - Node logged errors not displayed properly in debug pane Fixes #1116 - - Do not look for existing nodes when checking for wires on paste Fixes #1114 + - #1117 Don't filter debug properties starting with _ Fixes + - #1116 Node logged errors not displayed properly in debug pane Fixes + - #1114 Do not look for existing nodes when checking for wires on paste Fixes - -v option not enabling verbose mode properly - Add node.js version check on startup @@ -1319,10 +1373,10 @@ Runtime Nodes - - Add option to colourise debug console output Closes #1103 + - #1103 Add option to colourise debug console output Closes - Add property validation to nodes using typedInput - - Add common validator for typedInput fields Closes #1104 - - Update debug node console logging indicator icon Closes #1094 + - #1104 Add common validator for typedInput fields Closes + - #1094 Update debug node console logging indicator icon Closes - Let exec node (spawn) handle commands with spaces in path - Add symbol to debug node to indicate debugging also to console.log - Change file node to use node 4 syntax (drops support for 0.8) @@ -1334,9 +1388,9 @@ Nodes Editor - - Add install/remove dialog to increase friction Closes #1109 - - Report node catalogue load errors Closes #1009 - - Properly report module remove errors in palette editor Fixes #1043 + - #1109 Add install/remove dialog to increase friction Closes + - #1009 Report node catalogue load errors Closes + - #1043 Properly report module remove errors in palette editor Fixes - Update rather than hide install button after success install - Tweak search box styling - Display info tips slightly longer @@ -1357,36 +1411,36 @@ Editor - Focus tray body when edit dialog opened - Hit enter to edit first node in selection - Add node delete button to edit dialog - - Add notification when runtime stopped due to missing types Part of #832 + - #832 Add notification when runtime stopped due to missing types Part of Fixes - - Do not tie debug src loading to needsPermission Fixes #1111 - - Initialise nodeApp regardless of httpAdmin setting Closes #1096 #1095 + - #1111 Do not tie debug src loading to needsPermission Fixes + - #1095 Initialise nodeApp regardless of httpAdmin setting Closes #1096 - Speed up reveal of search dialogs - - Ensure flows exist before delegating status/error events Fixes #1069 + - #1069 Ensure flows exist before delegating status/error events Fixes - Update package dependencies - Update MQTT to latest 2.2.1 - Node status not being refreshed properly in the editor - - Try to prevent auto-fill of password fields in node edit tray Fixes #1081 + - #1081 Try to prevent auto-fill of password fields in node edit tray Fixes - Fix whitespace in localfilesystem - fix bug where savesettings did not honor local settings variables (#1073) - - Tidy up unused/duplicate editor messages Closes #922 + - #922 Tidy up unused/duplicate editor messages Closes - Property expressions must not be blank - Tidy up merge commit of validatePropertyExpression - add port if wires array > number of ports declared. - - Allow quoted property expressions Fixes #1101 + - #1101 Allow quoted property expressions Fixes - Index all node properties for node search - Remove node 0.10 from travis config - update welcome message to use logger so it can be turned off/on if required (#1083) - Fix dynamically loading multiple node-sets from palette editor - - Allow a node to reorder its outputs and maintain links Fixes #1031 + - #1031 Allow a node to reorder its outputs and maintain links Fixes #### 0.15.3: Maintenance Release - Tcpgetfix: Another small check (#1070) - TCPGet: Ensure done() is called only once (#1068) - - Allow $ and _ at start of property identifiers Fixes #1063 + - #1063 Allow $ and _ at start of property identifiers Fixes - TCPGet: Separated the node.connected property for each instance (#1062) - Corrected 'overide' typo in XML node help (#1061) - TCPGet: Last property check (hopefully) (#1059) @@ -1419,11 +1473,11 @@ Fixes #### 0.15.2: Maintenance Release - - Revert bidi changes to nodes and hide menu option until fixed Fixes #1024 + - #1024 Revert bidi changes to nodes and hide menu option until fixed Fixes - Let xml node set options both ways - Bump serialport to use version 4 - gpio node handle multiple bits of data returned in one go - - HTTP In should pass application/octet-stream as buffer not string Fixes #1023 + - #1023 HTTP In should pass application/octet-stream as buffer not string Fixes - Handle missing httpNodeRoot setting properly - Config sidebar not handling node definition error properly - Add minimum show time to deploy spinner to avoid flicker @@ -1431,24 +1485,24 @@ Fixes - Add log.removeHandler function - Add Crtl/Shift/p shortcut for manage palette - Add spinner to deploy button - - Status messages from nodes in subflows not delegated properly Fixes #1016 + - #1016 Status messages from nodes in subflows not delegated properly Fixes - fix spelling in join node info - Speed up tab scrolling - - Update delay burst test to be more tolerant of timing Fixes #1013 + - #1013 Update delay burst test to be more tolerant of timing Fixes #### 0.15.1: Maintenance Release - Update default palette catalogue to use https - Disable palette editor if npm not found - and fix for Windows - - Searching package catalogue should be case-insensitive Fixes #1010 - - contenteditable fields not handled in config nodes Fixes #1011 + - #1010 Searching package catalogue should be case-insensitive Fixes + - #1011 contenteditable fields not handled in config nodes Fixes - Change html link refs from `_new` to `_blank` to be standards compliant #### 0.15.0: Milestone Release Runtime - - Increase default apiMaxLength to 5mb and add to default settings Closes #1001 + - #1001 Increase default apiMaxLength to 5mb and add to default settings Closes - Add v2 /flows api and deploy-overwrite protection - Encrypt credentials by default - Ensure errors thrown by RED.events handlers don't percolate up @@ -1457,7 +1511,7 @@ Editor - Mark nodes as changed when they are moved - Added parent containment option for draggable. (#1006) - - Ignore bidi event handling on non-existent and non-Input elements Closes #999 + - #999 Ignore bidi event handling on non-existent and non-Input elements Closes - Remove list of flows from menu - Allow nodes to be imported with their credentials - Add workspace search option @@ -1469,7 +1523,7 @@ Editor - Add import-to-new-tab option - Add new options to export-nodes dialog - Stop nodes being added beyond the outer bounds of the workspace - - Default config nodes to global scope unless in a subflow Closes #972 + - #972 Default config nodes to global scope unless in a subflow Closes - Bidi support for Text Direction and Structured Text (#961) - Fix jQuery selector, selecting more than one help pane/popover and displaying incorrectly. (#970) - Fixes removeItem not passing row data to callback. (#965) @@ -1483,7 +1537,7 @@ Nodes - Clean up status on close for several core nodes. - Change node: re-parse JSON set value each time to avoid pass-by-ref - Better handle HTTP Request header capitalisation - - Enable ES6 parsing in Function editor by default Fixes #985 + - #985 Enable ES6 parsing in Function editor by default Fixes - Update debug sidebar to use RED.view.reveal to show debug nodes - Add full path tip to file node, And tidy up Pi node tips - Remove WebSocket node maxlistener warning @@ -1502,7 +1556,7 @@ Nodes Other - - Add npm build/test scripts Closes #946 #660 + - #660 Add npm build/test scripts Closes #946 - Move travis to node 6 and 7 - drop 5 and 0.12 @@ -1510,15 +1564,15 @@ Other Fixes - - Tell ace about Function node globals. Closes #927 - - Tidy up mqtt nodes - linting and done handling. Closes #935 + - #927 Tell ace about Function node globals. Closes + - #935 Tidy up mqtt nodes - linting and done handling. Closes - Fix invalid html in TCP and HTML node edit templates - Add proper help text to link nodes - Handle importing old mqtt-broker configs that lack properties - Update ace to 1.2.4 - Allow config nodes to provide a sort function for their select list - Add log warning if node module required version cannot be satisfied - - Handle empty credentials file. Closes #937 + - #937 Handle empty credentials file. Closes - Add RPi.GPIO lib test for ArchLinux #### 0.14.5: Maintenance Release @@ -1528,8 +1582,8 @@ Fixes - Cannot clear cookies with http nodes - let HTML parse node allow msg.select set select - Validate nodes on import after any references have been remapped - - Debug node handles objects without constructor property Fixes #933 - - Ensure 'false' property values are displayed in info panel Fixes #940 + - #933 Debug node handles objects without constructor property Fixes + - #940 Ensure 'false' property values are displayed in info panel Fixes - Fix node enable/disable over restart - load configs after settings init #### 0.14.4: Maintenance Release @@ -1538,20 +1592,20 @@ Nodes - Update trigger node ui to use typedInputs - Better handling of quotes in CSV node - - Clarify the MQTT node sends msg.payload - closes #929 - - Inject node should reuse the message it is triggered with Closes #914 + - #929 Clarify the MQTT node sends msg.payload - closes + - #914 Inject node should reuse the message it is triggered with Closes - Stop trigger node re-using old message - Allow node.status text to be 'falsey' values Fixes - - Handle DOMException when embedded in an iframe of different origin Fixes #932 + - #932 Handle DOMException when embedded in an iframe of different origin Fixes - Fix double firing of menu actions - - Fix select box handling in Safari - fixes #928 - - Clear context in node test helper Fixes #858 - - Allow node properties to be same as existing object functions Fixes #880 + - #928 Fix select box handling in Safari - fixes + - #858 Clear context in node test helper Fixes + - #880 Allow node properties to be same as existing object functions Fixes - Handle comms link closing whilst completing the initial connect - - Protect against node type names that clash with Object property names Fixes #917 + - #917 Protect against node type names that clash with Object property names Fixes - Clone default node properties to avoid reference leakage - Strip tab node definition when exporting - Check for null config properties in editor before over-writing them @@ -1561,16 +1615,16 @@ Editor - Add sql mode to ace editor - Keyboard shortcuts dialog update (#923) - - Ensure importing link nodes to a subflow doesn't add outbound links Fixes #921 + - #921 Ensure importing link nodes to a subflow doesn't add outbound links Fixes - Add updateConfigNodeUsers function to editor - Scroll to bottom when item added to editableList - - Form input widths behave more consistently when resizing Fixes #919 #920 + - #920 Form input widths behave more consistently when resizing Fixes #919 #### 0.14.3: Maintenance Release Fixes - - Create default setting.js in user-specified directory. Fixes #908 + - #908 Create default setting.js in user-specified directory. Fixes - MQTT In subscription qos not defaulting properly - Let exec node handle 0 as well as "0" @@ -1578,7 +1632,7 @@ Fixes Fixes - - Cannot add new twitter credentials. Fixes #913 + - #913 Cannot add new twitter credentials. Fixes - Support array references in Debug property field #### 0.14.1: Maintenance Release @@ -1586,8 +1640,8 @@ Fixes Fixes - Handle undefined property that led to missing wires in the editor - - Remove duplicate 'Delete' entry in keyboard shortcut window. Closes #911 - - Add 'exec' to node-red-pi launch script. Closes #910 + - #911 Remove duplicate 'Delete' entry in keyboard shortcut window. Closes + - #910 Add 'exec' to node-red-pi launch script. Closes #### 0.14.0: Milestone Release @@ -1609,9 +1663,9 @@ Editor Runtime - Always log node warnings on start without requiring -v - - Add support for loading scoped node modules. Closes #885 + - #885 Add support for loading scoped node modules. Closes - Add process.env.PORT to settings.js - - Clear node context on deploy. Closes #870 + - #870 Clear node context on deploy. Closes - Enable finer grained permissions in adminAuth Nodes @@ -1619,9 +1673,9 @@ Nodes - Enable config nodes to reference other config nodes - Add Split/Join nodes - Add Link nodes - - Add support to HTTP In node for PATCH requests. Closes #904 + - #904 Add support to HTTP In node for PATCH requests. Closes - Add cookie handling to HTTP In and HTTP Response nodes - - Add repeat indicator to inject node label. Closes #887 + - #887 Add repeat indicator to inject node label. Closes - Add javascript highlighter to template node - Add optional timeout to exec node - Add TLS node and update MQTT/HTTP nodes to use it @@ -1633,19 +1687,19 @@ Nodes - Update Serial node to support custom baud rates - Add support for array-syntax in typedInput msg properties - Add RED.util to Function node sandbox - - Capture error stack on node.error. Closes #879 + - #879 Capture error stack on node.error. Closes Fixes - Add error handling to all node definition api calls - Handle null return from Function node in array of messages - - Defer loading of token sessions until they are accessed. Fixes #895 + - #895 Defer loading of token sessions until they are accessed. Fixes - set pi gpio pin status correctly if set on start - - Prevent parent window scrolling when view is focused. Fixes #635 + - #635 Prevent parent window scrolling when view is focused. Fixes - Handle missing tab nodes in a loaded flow config - Ensure typedInput dropdown doesn't fall off the page - - Protect against node types with reserved names such as toString. Fixes #880 + - #880 Protect against node types with reserved names such as toString. Fixes - Do not rely on the HTML file to identify where nodes are registered from - Preserve node properties on import - Fix regression in delay node. topic based queue was emptying all the time instead of spreading out messages. @@ -1661,22 +1715,22 @@ Fixes #### 0.13.4: Maintenance Release - Add timed release mode to delay node - - Enable link splicing for when import_dragging nodes. Closes #811 + - #811 Enable link splicing for when import_dragging nodes. Closes - Fix uncaught exception on deploy whilst node sending messages - Deprecate old mqtt client and connection pool modules - - Change node: add bool/num types to change mode Closes #835 - - Validate fields that are `$(env-vars)` Closes #825 + - #835 Change node: add bool/num types to change mode Closes + - #825 Validate fields that are `$(env-vars)` Closes - Handle missing config nodes when validating node properties - Pi node - don't try to send data if closing - Load node message catalog when added dynamically - Split palette labels on spaces and hyphens when laying out - - Warn if editor routes are accessed but runtime not started Closes #816 - - Better handling of zero-length flow files Closes #819 + - #816 Warn if editor routes are accessed but runtime not started Closes + - #819 Better handling of zero-length flow files Closes - Allow runtime calls to RED._ to specify other namespace - Better right alignment of numerics in delay and trigger nodes - Allow node modules to include example flows - Create node_modules in userDir - - Ensure errors in node def functions don't break view rendering Fixes #815 + - #815 Ensure errors in node def functions don't break view rendering Fixes - Updated Inject node info with instructions for flow and global options @@ -1700,13 +1754,13 @@ Fixes - Make jquery spinner element css consistent with other inputs - tcp node add reply (to all) capability - Allow the template node to be treated as plain text - - Validate MQTT In topics Fixes #792 - - httpNodeAuth should not block http options requests Fixes #793 + - #792 Validate MQTT In topics Fixes + - #793 httpNodeAuth should not block http options requests Fixes - Disable perMessageDeflate on WS servers - fixes 'zlib binding closed' error - Clear trigger status icon on re-deploy - Don't default inject payload to blank string - Trigger node, add configurable reset - - Allow function properties in settings Fixes #790 - fixes use of httpNodeMiddleware + - #790 - fixes use of httpNodeMiddleware Allow function properties in settings Fixes - Fix order of config dialog calls to save/creds/validate - Add debounce to Pi GPIO node @@ -1722,7 +1776,7 @@ Fixes - Add 'previous value' option to Switch node - Allow existing nodes to splice into links on drag - - CORS not properly configured on multiple http routes Fixes #783 + - #783 CORS not properly configured on multiple http routes Fixes - Restore shift-drag to snap/unsnap to grid - Moving nodes with keyboard should flag workspace dirty - Notifications flagged as fixed should not be click-closable From 32aa4c41ce7e938878accebe17bdc4f9eb3ab609 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 27 Feb 2020 14:37:25 +0000 Subject: [PATCH 089/346] Bump for 1.0.4 --- package.json | 2 +- .../node_modules/@node-red/editor-api/package.json | 6 +++--- .../node_modules/@node-red/editor-client/package.json | 2 +- packages/node_modules/@node-red/nodes/package.json | 2 +- packages/node_modules/@node-red/registry/package.json | 4 ++-- packages/node_modules/@node-red/runtime/package.json | 6 +++--- packages/node_modules/@node-red/util/package.json | 2 +- packages/node_modules/node-red/package.json | 10 +++++----- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 335dddbb3..ca61f0e49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.0.3", + "version": "1.0.4", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 81be91b6d..9866a81ad 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "1.0.3", + "version": "1.0.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "1.0.3", - "@node-red/editor-client": "1.0.3", + "@node-red/util": "1.0.4", + "@node-red/editor-client": "1.0.4", "bcryptjs": "2.4.3", "body-parser": "1.19.0", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 7f8061c6d..0b3e16e7f 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "1.0.3", + "version": "1.0.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 1b707b95b..cf897dbb0 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "1.0.3", + "version": "1.0.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index c1ee06050..448c46861 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "1.0.3", + "version": "1.0.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,7 +16,7 @@ } ], "dependencies": { - "@node-red/util": "1.0.3", + "@node-red/util": "1.0.4", "semver": "6.3.0", "uglify-js": "3.8.0", "when": "3.7.8" diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index 0d03b355b..50f9fb264 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "1.0.3", + "version": "1.0.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "1.0.3", - "@node-red/util": "1.0.3", + "@node-red/registry": "1.0.4", + "@node-red/util": "1.0.4", "clone": "2.1.2", "express": "4.17.1", "fs-extra": "8.1.0", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 9ad86f6c9..9beb6189b 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "1.0.3", + "version": "1.0.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 618480400..2b7116f64 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "1.0.3", + "version": "1.0.4", "description": "Low-code programming for event-driven applications", "homepage": "http://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "1.0.3", - "@node-red/runtime": "1.0.3", - "@node-red/util": "1.0.3", - "@node-red/nodes": "1.0.3", + "@node-red/editor-api": "1.0.4", + "@node-red/runtime": "1.0.4", + "@node-red/util": "1.0.4", + "@node-red/nodes": "1.0.4", "basic-auth": "2.0.1", "bcryptjs": "2.4.3", "express": "4.17.1", From bd4fc2e5cc4337eded1913df390c1caed9721f35 Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Sat, 29 Feb 2020 09:15:42 -0500 Subject: [PATCH 090/346] Fix workspace CSS properties syntax --- .../@node-red/editor-client/src/sass/workspace.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss index f6255eacf..2162cad8a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss @@ -112,7 +112,7 @@ position: absolute; bottom: 0; right:0; - zIndex: 101; + z-index: 101; border-left: 1px solid $primary-border-color; border-top: 1px solid $primary-border-color; background: $view-navigator-background; @@ -122,7 +122,7 @@ stroke-dasharray: 5,5; pointer-events: none; stroke: $secondary-border-color; - strokeWidth: 1; + stroke-width: 1; fill: $view-background; } From 8a82552bdcdac22329763cf9a099523a3747b49c Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Sat, 29 Feb 2020 15:14:57 -0500 Subject: [PATCH 091/346] Consolidate duplicates --- .../@node-red/editor-client/src/sass/ace.scss | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss index fb6eaf8eb..adcaa3458 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ace.scss @@ -9,19 +9,15 @@ color: transparent !important; } } - - .ace_gutter { + background: $text-editor-gutter-background; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } .ace_scroller { + background: $text-editor-background; border-top-right-radius: 4px; border-bottom-right-radius: 4px; - } - - .ace_scroller { - background: $text-editor-background; color: $text-editor-color; } .ace_marker-layer .ace_active-line { @@ -37,9 +33,6 @@ .ace_gutter-active-line { background: $text-editor-gutter-active-line-background; } - .ace_gutter { - background: $text-editor-gutter-background; - } .ace_tooltip { font-family: $primary-font; line-height: 1.4em; From 491812fac5d524692bcb489b6e20890eb533f1ce Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 2 Mar 2020 05:07:48 +0000 Subject: [PATCH 092/346] Fix XPath in UI tests --- test/editor/pageobjects/nodes/core/function/15-change_page.js | 2 +- test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js | 2 +- test/editor/pageobjects/nodes/core/parsers/70-XML_page.js | 2 +- test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/editor/pageobjects/nodes/core/function/15-change_page.js b/test/editor/pageobjects/nodes/core/function/15-change_page.js index 59ac4f2f1..fab408718 100644 --- a/test/editor/pageobjects/nodes/core/function/15-change_page.js +++ b/test/editor/pageobjects/nodes/core/function/15-change_page.js @@ -85,7 +85,7 @@ changeNode.prototype.ruleMove = function (p, to, index) { } changeNode.prototype.addRule = function () { - browser.clickWithWait('//*[@id="dialog-form"]/div[5]/div/a'); + browser.clickWithWait('//div[contains(@class, "red-ui-editableList")]/a'); } module.exports = changeNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js index 875c3b013..e0b31dd36 100644 --- a/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js +++ b/test/editor/pageobjects/nodes/core/parsers/70-JSON_page.js @@ -29,7 +29,7 @@ jsonNode.prototype.setAction = function (action) { } jsonNode.prototype.setProperty = function (property) { - browser.setValue('//*[@id="dialog-form"]/div[4]/div/div[1]/input', property); + browser.setValue('//*[contains(@class, "red-ui-typedInput-container")]/div[1]/input', property); } module.exports = jsonNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js index 2a8be7d95..696ec59cb 100644 --- a/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js +++ b/test/editor/pageobjects/nodes/core/parsers/70-XML_page.js @@ -29,7 +29,7 @@ xmlNode.prototype.setAction = function (action) { } xmlNode.prototype.setProperty = function (property) { - browser.setValue('//*[@id="dialog-form"]/div[3]/div/div[1]/input', property); + browser.setValue('//*[contains(@class, "red-ui-typedInput-container")]/div[1]/input', property); } module.exports = xmlNode; diff --git a/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js b/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js index 16e4a678d..1002f3eb4 100644 --- a/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js +++ b/test/editor/pageobjects/nodes/core/parsers/70-YAML_page.js @@ -29,7 +29,7 @@ yamlNode.prototype.setAction = function (action) { } yamlNode.prototype.setProperty = function (property) { - browser.setValue('//*[@id="dialog-form"]/div[3]/div/div[1]/input', property); + browser.setValue('//*[contains(@class, "red-ui-typedInput-container")]/div[1]/input', property); } module.exports = yamlNode; From 6675fdf3c2ca8f3f96dd5af38d64655934f3b3c9 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Mon, 2 Mar 2020 05:50:32 +0000 Subject: [PATCH 093/346] Saving the node description property to the library --- .../editor-client/src/js/ui/editor.js | 1 + .../editor-client/src/js/ui/library.js | 21 ++++++++++++------- .../nodes/core/function/10-function.html | 13 ++++++------ 3 files changed, 22 insertions(+), 13 deletions(-) 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 2f5c4fe11..2c1cbec85 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 @@ -1638,6 +1638,7 @@ RED.editor = (function() { }; editorTabs.addTab(descriptionTab); nodeInfoEditor = buildDescriptionForm(descriptionTab.content,node); + node.nodeInfoEditor = nodeInfoEditor; } var appearanceTab = { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js index e25223f94..a653c9d7e 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js @@ -64,12 +64,14 @@ RED.library = (function() { var queryArgs = []; var data = {}; - for (var i=0; i Date: Mon, 2 Mar 2020 19:50:39 +0000 Subject: [PATCH 094/346] Ensure join node handles missing buffer joiner when not in string mode and add tests to close #2491 --- .../nodes/core/sequence/17-split.html | 4 +- .../@node-red/nodes/core/sequence/17-split.js | 9 ++-- test/nodes/core/sequence/17-split_spec.js | 43 +++++++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/sequence/17-split.html b/packages/node_modules/@node-red/nodes/core/sequence/17-split.html index d730bb221..eccd33647 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/17-split.html +++ b/packages/node_modules/@node-red/nodes/core/sequence/17-split.html @@ -14,7 +14,7 @@ limitations under the License. --> - -'; + + var groupDef = { + defaults:{ + name:{value:""}, + style:{value:{}} + }, + category: "config", + oneditprepare: function() { + var style = this.style || {}; + $("#node-input-style-stroke").val(style.stroke || "#eeeeee") + $("#node-input-style-fill").val(style.fill || "none") + }, + oneditresize: function(size) { + }, + oneditsave: function() { + this.style.stroke = $("#node-input-style-stroke").val(); + this.style.fill = $("#node-input-style-fill").val(); + }, + set:{ + module: "node-red" + } + } + + function init() { + + + RED.actions.add("core:group-selection", function() { groupSelection() }) + + $(_groupEditTemplate).appendTo("#red-ui-editor-node-configs"); + + } + + function groupSelection() { + var selection = RED.view.selection(); + // var groupNodes = new Set(); + // + // if (selection.groups) { + // selection.groups.forEach(function(g) { + // g.nodes.forEach() + // }) + // } + // + // if (selection.nodes) { + // + // } + + if (selection.nodes) { + var group = createGroup(selection.nodes); + if (group) { + RED.view.select({groups:[group]}) + } + } + } + function createGroup(nodes) { + if (nodes.length === 0) { + return; + } + // nodes is an array + // each node must be on the same tab (z) + var group = { + id: RED.nodes.id(), + type: 'group', + nodes: [], + style: { + stroke: "#999", + fill: "none" + }, + x: Number.POSITIVE_INFINITY, + y: Number.POSITIVE_INFINITY, + w: 0, + h: 0, + _def: RED.group.def + } + try { + addToGroup(group,nodes); + } catch(err) { + RED.notify(err,"error"); + return; + } + group.z = nodes[0].z; + + RED.nodes.addGroup(group); + return group; + } + + function addToGroup(group,nodes) { + if (!Array.isArray(nodes)) { + nodes = [nodes]; + } + var i,n,z; + var g; + // First pass - validate we can safely add these nodes to the group + for (i=0;i -1) { + g.nodes.splice(ni,1) + } + } + n.g = group.id; + group.nodes.push(n); + group.x = Math.min(group.x,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); + group.y = Math.min(group.y,n.y-n.h/2-25); + group.w = Math.max(group.w,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0) - group.x), + group.h = Math.max(group.h,n.y+n.h/2+25-group.y); + } + } + + function getNodes(group,recursive) { + var nodes = []; + group.nodes.forEach(function(n) { + if (!recursive || n.type !== 'group') { + nodes.push(n); + } else { + nodes = nodes.concat(getNodes(n,recursive)) + } + }) + return nodes; + } + + function groupContains(group,item) { + if (item.g === group.id) { + return true; + } + for (var i=0;i 0) { var gridOffset = [0,0]; node = moving_set[0]; @@ -1327,20 +1326,15 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); } if (!node.n.g && activeGroups) { if (!groupHoverTimer) { groupHoverTimer = setTimeout(function() { - activeHoverGroup = null; + activeHoverGroup = getGroupAt(node.n.x,node.n.y); for (var i=0;i= g.pos.x0 && node.n.x <= g.pos.x1 && - node.n.y >= g.pos.y0 && node.n.y <= g.pos.y1 - ) { - g.dirty = !g.hovered; + if (g === activeHoverGroup) { g.hovered = true; - activeHoverGroup = g; - } else { - // Mark dirty if it is selected - g.dirty = g.hovered; + g.dirty = true; + } else if (g.hovered) { g.hovered = false; + g.dirty = true; } } groupHoverTimer = null; @@ -1972,7 +1966,7 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } RED.notify(RED._("clipboard.nodeCopied",{count:nns.length}),{id:"clipboard"}); } } - + function calculateTextWidth(str, className, offset) { var result=convertLineBreakCharacter(str); var width = 0; @@ -2714,11 +2708,12 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } } + function groupMouseDown(g) { var mouse = d3.touches(this.parentNode)[0]||d3.mouse(this.parentNode); - if (! (mouse[0] < g.pos.x0+10 || mouse[0] > g.pos.x1-10 || mouse[1] < g.pos.y0+10 || mouse[1] > g.pos.y1-10) ) { - return - } + // if (! (mouse[0] < g.x+10 || mouse[0] > g.x+g.w-10 || mouse[1] < g.y+10 || mouse[1] > g.y+g.h-10) ) { + // return + // } focusView(); if (d3.event.button === 1) { @@ -2768,10 +2763,8 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } if (d3.event.button != 2) { var d = g.nodes[0]; prepareDrag(mouse); - mousedown_group.dx0 = mousedown_group.pos.x0 - mouse[0]; - mousedown_group.dy0 = mousedown_group.pos.y0 - mouse[1]; - mousedown_group.dx1 = mousedown_group.pos.x1 - mouse[0]; - mousedown_group.dy1 = mousedown_group.pos.y1 - mouse[1]; + mousedown_group.dx = mousedown_group.x - mouse[0]; + mousedown_group.dy = mousedown_group.y - mouse[1]; } } @@ -2787,7 +2780,8 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } if (includeNodes) { var currentSet = new Set(moving_set.map(function(n) { return n.n })); - g.nodes.forEach(function(n) { + var allNodes = RED.group.getNodes(g,true); + allNodes.forEach(function(n) { if (!currentSet.has(n)) { moving_set.push({n:n}) // n.selected = true; @@ -2820,48 +2814,62 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } } function getGroupAt(x,y) { + var candidateGroups = {}; for (var i=0;i= g.pos.x0 && x <= g.pos.x1 && y >= g.pos.y0 && y <= g.pos.y1) { - return g; + if (x >= g.x && x <= g.x + g.w && y >= g.y && y <= g.y + g.h) { + candidateGroups[g.id] = g; } } - return null; + var ids = Object.keys(candidateGroups); + if (ids.length > 1) { + ids.forEach(function(id) { + if (candidateGroups[id] && candidateGroups[id].g) { + delete candidateGroups[candidateGroups[id].g] + } + }) + ids = Object.keys(candidateGroups); + } + if (ids.length === 0) { + return null; + } else { + return candidateGroups[ids[0]] + } } - function groupHandleMouseDown(group, groupEl, handle,handleIndex) { - d3.event.stopPropagation(); - console.log("GHANDLE MD"); - if (d3.event.button != 2) { - mousedown_group = group; - group.activeHandle = handleIndex; - var mouse = d3.touches(handle.parentNode.parentNode)[0]||d3.mouse(handle.parentNode.parentNode); - switch(handleIndex) { - case 0: group.ox = group.pos.x0; group.oy = group.pos.y0; break; - case 1: group.ox = group.pos.x1; group.oy = group.pos.y0; break; - case 2: group.ox = group.pos.x1; group.oy = group.pos.y1; break; - case 3: group.ox = group.pos.x0; group.oy = group.pos.y1; break; - } - group.dx = group.ox - mouse[0]; - group.dy = group.oy - mouse[1]; - console.log("START",group.ox, group.oy); - mouse_offset = d3.mouse(document.body); - if (isNaN(mouse_offset[0])) { - mouse_offset = d3.touches(document.body)[0]; - } - mouse_mode = RED.state.GROUP_RESIZE; - } - } - function groupHandleMouseUp(group,groupEl,handle,handleIndex) { - console.log("GHANDLE MU"); - d3.event.stopPropagation(); - delete group.ox; - delete group.oy; - delete group.dx; - delete group.dy; - resetMouseVars(); - mouse_mode = RED.state.DEFAULT; - } + // function groupHandleMouseDown(group, groupEl, handle,handleIndex) { + // d3.event.stopPropagation(); + // console.log("GHANDLE MD"); + // if (d3.event.button != 2) { + // mousedown_group = group; + // group.activeHandle = handleIndex; + // var mouse = d3.touches(handle.parentNode.parentNode)[0]||d3.mouse(handle.parentNode.parentNode); + // switch(handleIndex) { + // case 0: group.ox = group.pos.x0; group.oy = group.pos.y0; break; + // case 1: group.ox = group.pos.x1; group.oy = group.pos.y0; break; + // case 2: group.ox = group.pos.x1; group.oy = group.pos.y1; break; + // case 3: group.ox = group.pos.x0; group.oy = group.pos.y1; break; + // } + // group.dx = group.ox - mouse[0]; + // group.dy = group.oy - mouse[1]; + // console.log("START",group.ox, group.oy); + // mouse_offset = d3.mouse(document.body); + // if (isNaN(mouse_offset[0])) { + // mouse_offset = d3.touches(document.body)[0]; + // } + // mouse_mode = RED.state.GROUP_RESIZE; + // } + // } + // function groupHandleMouseUp(group,groupEl,handle,handleIndex) { + // console.log("GHANDLE MU"); + // d3.event.stopPropagation(); + // delete group.ox; + // delete group.oy; + // delete group.dx; + // delete group.dy; + // resetMouseVars(); + // mouse_mode = RED.state.DEFAULT; + // } function isButtonEnabled(d) { var buttonEnabled = true; @@ -3712,18 +3720,19 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } d.dirty = false; - if (d.g) { if (!dirtyGroups[d.g]) { - dirtyGroups[d.g] = RED.nodes.group(d.g); - } - var group = dirtyGroups[d.g]; - group.pos = { - x0: Math.min(group.pos.x0,d.x-d.w/2-25-((d._def.button && d._def.align!=="right")?20:0)), - y0: Math.min(group.pos.y0,d.y-d.h/2-25), - x1: Math.max(group.pos.x1,d.x+d.w/2+25+((d._def.button && d._def.align=="right")?20:0)), - y1: Math.max(group.pos.y1,d.y+d.h/2+25) + var gg = d.g; + while (gg && !dirtyGroups[gg]) { + dirtyGroups[gg] = RED.nodes.group(gg); + gg = dirtyGroups[gg].g; + } } + // var group = dirtyGroups[d.g]; + // group.x = Math.min(group.x,d.x-d.w/2-25-((d._def.button && d._def.align!=="right")?20:0)); + // group.y = Math.min(group.y,d.y-d.h/2-25), + // group.w = Math.max(group.w, d.x+d.w/2+25+((d._def.button && d._def.align=="right")?20:0) - group.x), + // group.h = Math.max(group.h, d.y+d.h/2+25 - group.y) } } }); @@ -3981,20 +3990,21 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } .attr('rx',1).attr('ry',1).style({ "fill":"none", "stroke": "#ff7f0e", + "pointer-events": "stroke", "stroke-opacity": 0, "stroke-width": 15 }) + g.append('rect').classed("red-ui-flow-group-body",true) .attr('rx',1).attr('ry',1).style({ + "pointer-events": "none", "fill":d.fill||"none", "stroke": d.stroke||"none", "stroke-width": 2 }) - - - d.dirty = true; g.on("mousedown",groupMouseDown).on("mouseup",groupMouseUp) + d.dirty = true; }); group.each(function(d,i) { if (d.dirty || dirtyGroups[d.id]) { @@ -4004,26 +4014,33 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } var maxX = 0; var maxY = 0; d.nodes.forEach(function(n) { - minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); - minY = Math.min(minY,n.y-n.h/2-25); - maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0)); - maxY = Math.max(maxY,n.y+n.h/2+25); + if (n.type !== "group") { + minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); + minY = Math.min(minY,n.y-n.h/2-25); + maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0)); + maxY = Math.max(maxY,n.y+n.h/2+25); + } else { + minX = Math.min(minX,n.x-25) + minY = Math.min(minY,n.y-25) + maxX = Math.max(maxX,n.x+n.w+25) + maxY = Math.max(maxY,n.y+n.h+25) + } }); - d.pos = { - x0: minX, y0: minY, - x1: maxX, y1: maxY - } + d.x = minX; + d.y = minY; + d.w = maxX - minX; + d.h = maxY - minY; - g.attr("transform","translate("+d.pos.x0+","+d.pos.y0+")"); + g.attr("transform","translate("+d.x+","+d.y+")"); g.selectAll(".red-ui-flow-group-outline") - .attr("width",d.pos.x1-d.pos.x0) - .attr("height",d.pos.y1-d.pos.y0) + .attr("width",d.w) + .attr("height",d.h) .style("stroke-opacity",function(d) { if (d.selected) { return 0.3 } return 0}); g.selectAll(".red-ui-flow-group-body") - .attr("width",d.pos.x1-d.pos.x0) - .attr("height",d.pos.y1-d.pos.y0) + .attr("width",d.w) + .attr("height",d.h) .style("stroke",function(d) { /*if (d.selected) { return "#ff7f0e" } */return d.style.stroke || "none"}) .style("stroke-dasharray", function(d) { return (d.active||d.hovered)?"10 4":"none"}) .style("fill", function(d) { return d.style.fill || "none"}) @@ -4338,16 +4355,34 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } }, selection: function() { var selection = {}; + + var allNodes = new Set(); + if (moving_set.length > 0) { - selection.nodes = moving_set.map(function(n) { return n.n;}); + moving_set.forEach(function(n) { + allNodes.add(n.n); + }); + } + var selectedGroups = activeGroups.filter(function(g) { return g.selected }); + if (selectedGroups.length > 0) { + if (selectedGroups.length === 1 && selectedGroups[0].active) { + // Let nodes be nodes + } else { + selectedGroups.forEach(function(g) { + var groupNodes = RED.group.getNodes(g,true); + groupNodes.forEach(function(n) { + allNodes.delete(n); + }); + allNodes.add(g); + }); + } + } + if (allNodes.size > 0) { + selection.nodes = Array.from(allNodes); } if (selected_link != null) { selection.link = selected_link; } - selection.groups = activeGroups.filter(function(g) { return g.selected }) - if (selection.groups.length === 0) { - delete selection.groups; - } return selection; }, scale: function() { From 51ea5dc34276cff2f8ae4f6eac091b2336e77aa1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 5 Mar 2020 10:43:28 +0000 Subject: [PATCH 098/346] [groups] Add ungroup-selection action --- .../editor-client/src/js/keymap.json | 3 +- .../@node-red/editor-client/src/js/nodes.js | 7 +- .../editor-client/src/js/ui/group.js | 43 ++++++++---- .../@node-red/editor-client/src/js/ui/view.js | 66 +++++++++++-------- 4 files changed, 75 insertions(+), 44 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/keymap.json b/packages/node_modules/@node-red/editor-client/src/js/keymap.json index c4d666845..5bd938e98 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/keymap.json +++ b/packages/node_modules/@node-red/editor-client/src/js/keymap.json @@ -62,6 +62,7 @@ "shift-left": "core:step-selection-left", "ctrl-shift-j": "core:show-previous-tab", "ctrl-shift-k": "core:show-next-tab", - "ctrl-shift-g": "core:group-selection" + "ctrl-shift-g": "core:group-selection", + "ctrl-shift-u": "core:ungroup-selection" } } 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 2bf8d4a21..fdd6886d1 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 @@ -1452,7 +1452,11 @@ RED.nodes = (function() { groupsByZ[group.z].push(group); groups[group.id] = group; } - + function removeGroup(group) { + var i = groupsByZ[group.z].indexOf(group); + groupsByZ[group.z].splice(i,1); + delete groups[group.id]; + } return { @@ -1551,6 +1555,7 @@ RED.nodes = (function() { subflowContains: subflowContains, addGroup: addGroup, + removeGroup: removeGroup, group: function(id) { return groups[id] }, groups: function(z) { return groupsByZ[z] }, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index 7c9bd67f9..bfc73f098 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -60,6 +60,7 @@ RED.group = (function() { RED.actions.add("core:group-selection", function() { groupSelection() }) + RED.actions.add("core:ungroup-selection", function() { ungroupSelection() }) $(_groupEditTemplate).appendTo("#red-ui-editor-node-configs"); @@ -67,25 +68,39 @@ RED.group = (function() { function groupSelection() { var selection = RED.view.selection(); - // var groupNodes = new Set(); - // - // if (selection.groups) { - // selection.groups.forEach(function(g) { - // g.nodes.forEach() - // }) - // } - // - // if (selection.nodes) { - // - // } - if (selection.nodes) { var group = createGroup(selection.nodes); if (group) { - RED.view.select({groups:[group]}) + RED.view.select({nodes:[group]}) } } } + function ungroupSelection() { + var selection = RED.view.selection(); + if (selection.nodes) { + var newSelection = []; + groups = selection.nodes.filter(function(n) { return n.type === "group" }); + groups.forEach(function(g) { + var parentGroup = RED.nodes.group(g.g); + if (parentGroup) { + var index = parentGroup.nodes.indexOf(g); + parentGroup.nodes.splice(index,1); + } + g.nodes.forEach(function(n) { + newSelection.push(n); + if (parentGroup) { + // Move nodes to parent group + n.g = parentGroup.id; + parentGroup.nodes.push(n); + } else { + delete n.g; + } + }) + RED.nodes.removeGroup(g); + }) + RED.view.select({nodes:newSelection}) + } + } function createGroup(nodes) { if (nodes.length === 0) { return; @@ -168,7 +183,7 @@ RED.group = (function() { group.nodes.push(n); group.x = Math.min(group.x,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); group.y = Math.min(group.y,n.y-n.h/2-25); - group.w = Math.max(group.w,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0) - group.x), + group.w = Math.max(group.w,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0) - group.x); group.h = Math.max(group.h,n.y+n.h/2+25-group.y); } } 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 81ba16b81..94937aa65 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -29,7 +29,6 @@ RED.view = (function() { var DEBUG_EVENTS = false; - 2 var space_width = 5000, space_height = 5000, lineCurveScale = 0.75, @@ -63,30 +62,30 @@ RED.view = (function() { var activeGroups = []; var dirtyGroups = {}; - var selected_link = null, - mousedown_link = null, - mousedown_node = null, - mousedown_group = null, - mousedown_port_type = null, - mousedown_port_index = 0, - mouseup_node = null, - mouse_offset = [0,0], - mouse_position = null, - mouse_mode = 0, - mousedown_group_handle = null; - moving_set = [], - lasso = null, - ghostNode = null, - showStatus = false, - lastClickNode = null, - dblClickPrimed = null, - clickTime = 0, - clickElapsed = 0, - scroll_position = [], - quickAddActive = false, - quickAddLink = null, - showAllLinkPorts = -1, - groupNodeSelectPrimed = false; + var selected_link = null; + var mousedown_link = null; + var mousedown_node = null; + var mousedown_group = null; + var mousedown_port_type = null; + var mousedown_port_index = 0; + var mouseup_node = null; + var mouse_offset = [0,0]; + var mouse_position = null; + var mouse_mode = 0; + var mousedown_group_handle = null; + var moving_set = []; + var lasso = null; + var ghostNode = null; + var showStatus = false; + var lastClickNode = null; + var dblClickPrimed = null; + var clickTime = 0; + var clickElapsed = 0; + var scroll_position = []; + var quickAddActive = false; + var quickAddLink = null; + var showAllLinkPorts = -1; + var groupNodeSelectPrimed = false; var selectNodesOptions; @@ -4342,10 +4341,21 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } moving_set = [{n:selectedNode}]; } } else if (selection) { - if (selection.groups) { + if (selection.nodes) { updateActiveNodes(); - selection.groups.forEach(function(g) { - selectGroup(g,true); + moving_set = []; + // TODO: this selection group span groups + // - if all in one group -> activate the group + // - if in multiple groups (or group/no-group) + // -> select the first 'set' of things in the same group/no-group + selection.nodes.forEach(function(n) { + if (n.type !== "group") { + n.selected = true; + n.dirty = true; + moving_set.push({n:n}); + } else { + selectGroup(n,true); + } }) } } From 4d96d953705757632329081f0a6e7ff840e6ebd3 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 5 Mar 2020 15:52:26 +0000 Subject: [PATCH 099/346] [groups] Add merge-selection-to-group and remove-selection-from-group --- .../editor-client/src/js/ui/group.js | 118 +++++++++++++++--- .../@node-red/editor-client/src/js/ui/view.js | 2 +- 2 files changed, 105 insertions(+), 15 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index bfc73f098..22deb3f32 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -61,6 +61,8 @@ RED.group = (function() { RED.actions.add("core:group-selection", function() { groupSelection() }) RED.actions.add("core:ungroup-selection", function() { ungroupSelection() }) + RED.actions.add("core:merge-selection-to-group", function() { mergeSelection() }) + RED.actions.add("core:remove-selection-from-group", function() { removeSelection() }) $(_groupEditTemplate).appendTo("#red-ui-editor-node-configs"); @@ -81,24 +83,110 @@ RED.group = (function() { var newSelection = []; groups = selection.nodes.filter(function(n) { return n.type === "group" }); groups.forEach(function(g) { - var parentGroup = RED.nodes.group(g.g); - if (parentGroup) { - var index = parentGroup.nodes.indexOf(g); - parentGroup.nodes.splice(index,1); + newSelection = newSelection.concat(ungroup(g)) + }) + RED.view.select({nodes:newSelection}) + } + } + + function ungroup(g) { + var nodes = []; + var parentGroup = RED.nodes.group(g.g); + if (parentGroup) { + var index = parentGroup.nodes.indexOf(g); + parentGroup.nodes.splice(index,1); + } + g.nodes.forEach(function(n) { + nodes.push(n); + if (parentGroup) { + // Move nodes to parent group + n.g = parentGroup.id; + parentGroup.nodes.push(n); + parentGroup.dirty = true; + n.dirty = true; + } else { + delete n.g; + } + }) + RED.nodes.removeGroup(g); + return nodes; + } + + function mergeSelection() { + // TODO: this currently creates an entirely new group. Need to merge properties + // of any existing group + var selection = RED.view.selection(); + if (selection.nodes) { + var nodes = []; + var n; + var parentGroup; + // First pass, check they are all in the same parent + // TODO: DRY mergeSelection,removeSelection,... + for (var i=0; i 0) { if (selectedGroups.length === 1 && selectedGroups[0].active) { // Let nodes be nodes From 9a0c843f29393e7bd871bd88aecbdc71f9114712 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 5 Mar 2020 22:49:31 +0000 Subject: [PATCH 100/346] [groups] Support deleting groups as part of selection --- .../node_modules/@node-red/editor-client/src/js/nodes.js | 9 +++++++++ .../@node-red/editor-client/src/js/ui/group.js | 4 ---- .../@node-red/editor-client/src/js/ui/view.js | 4 ++++ 3 files changed, 13 insertions(+), 4 deletions(-) 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 fdd6886d1..5c0004d8c 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 @@ -1455,6 +1455,15 @@ RED.nodes = (function() { function removeGroup(group) { var i = groupsByZ[group.z].indexOf(group); groupsByZ[group.z].splice(i,1); + + if (group.g) { + if (groups[group.g]) { + var index = groups[group.g].nodes.indexOf(group); + groups[group.g].nodes.splice(index,1); + groups[group.g].dirty = true; + } + } + delete groups[group.id]; } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index 22deb3f32..e4aaf699e 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -92,10 +92,6 @@ RED.group = (function() { function ungroup(g) { var nodes = []; var parentGroup = RED.nodes.group(g.g); - if (parentGroup) { - var index = parentGroup.nodes.indexOf(g); - parentGroup.nodes.splice(index,1); - } g.nodes.forEach(function(n) { nodes.push(n); if (parentGroup) { 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 aaa92fa70..4d99f7b87 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -1823,6 +1823,10 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } node.dirty = true; } } + var selectedGroups = activeGroups.filter(function(g) { return !g.active && g.selected }); + selectedGroups.forEach(function(g) { + RED.nodes.removeGroup(g); + }); if (removedSubflowOutputs.length > 0) { result = RED.subflow.removeOutput(removedSubflowOutputs); if (result) { From 84d2b8ad6db06c34b678c65b12b856f10548c02f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Sat, 7 Mar 2020 01:55:45 +0900 Subject: [PATCH 101/346] add support of initialization & finalization to function node --- .../editor-client/src/js/ui/library.js | 28 ++- .../nodes/core/function/10-function.html | 224 ++++++++++++++++-- .../nodes/core/function/10-function.js | 6 + .../locales/en-US/function/10-function.html | 3 +- .../nodes/locales/en-US/messages.json | 2 + .../locales/ja/function/10-function.html | 3 +- .../@node-red/nodes/locales/ja/messages.json | 2 + test/nodes/core/function/10-function_spec.js | 27 ++- 8 files changed, 269 insertions(+), 26 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js index e25223f94..2520c16a9 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js @@ -50,6 +50,18 @@ RED.library = (function() { ''+ '
        ' + function toSingleLine(text) { + var result = text.replace(/\\/g, "\\\\").replace(/\n/g, "\\n"); + return result; + } + + function fromSingleLine(text) { + var result = text.replace(/\\[\\n]/g, function(s) { + return ((s === "\\\\") ? "\\" : "\n"); + }); + return result; + } + function saveToLibrary() { var elementPrefix = activeLibrary.elementPrefix || "node-input-"; var name = $("#"+elementPrefix+"name").val().trim(); @@ -68,6 +80,10 @@ RED.library = (function() { var field = activeLibrary.fields[i]; if (field == "name") { data.name = name; + } else if(field == "initialize") { + data.initialize = toSingleLine(activeLibrary.initEditor.getValue()); + } else if(field == "finalize") { + data.finalize = toSingleLine(activeLibrary.finalizeEditor.getValue()); } else { data[field] = $("#"+elementPrefix+field).val(); } @@ -523,7 +539,17 @@ RED.library = (function() { var elementPrefix = activeLibrary.elementPrefix || "node-input-"; for (var i=0; i
        -
        - - - + +
        +
          -
          -
          -
          -
          -
          - - + +
          + + + + + + +
          + diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.js b/packages/node_modules/@node-red/nodes/core/function/10-function.js index 65a1b4a61..7bdeada65 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.js +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.js @@ -62,6 +62,8 @@ module.exports = function(RED) { var node = this; this.name = n.name; this.func = n.func; + this.ini = n.initialize; + this.fin = n.finalize; var handleNodeDoneCall = true; // Check to see if the Function appears to call `node.done()`. If so, @@ -89,6 +91,8 @@ module.exports = function(RED) { "};\n"+ this.func+"\n"+ "})(msg,send,done);"; + var iniText = "(function () {\n"+this.ini +"\n})();"; + var finText = "(function () {\n"+this.fin +"\n})();"; this.topic = n.topic; this.outstandingTimers = []; this.outstandingIntervals = []; @@ -229,6 +233,7 @@ module.exports = function(RED) { } var context = vm.createContext(sandbox); try { + vm.runInContext(iniText, context); this.script = vm.createScript(functionText, { filename: 'Function node:'+this.id+(this.name?' ['+this.name+']':''), // filename for stack traces displayErrors: true @@ -297,6 +302,7 @@ module.exports = function(RED) { } }); this.on("close", function() { + vm.runInContext(finText, context); while (node.outstandingTimers.length > 0) { clearTimeout(node.outstandingTimers.pop()); } diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html index 28bc76f3a..c1c768f84 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/function/10-function.html @@ -15,12 +15,13 @@ --> diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index e1d7c6368..bf476ab44 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -455,7 +455,7 @@ "message": "entire message", "tip": { "path1": "By default, payload will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.", - "path2": "This path will be relative to ", + "path2": "This path will be relative to __path__.", "url1": "URL should use ws:// or wss:// scheme and point to an existing websocket listener.", "url2": "By default, payload will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string." }, diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index adc033390..4e728690b 100755 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -455,7 +455,7 @@ "message": "メッセージ全体を送信/受信", "tip": { "path1": "標準では payload がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。", - "path2": "This path will be relative to ", + "path2": "このパスは __path__ の相対パスになります。", "url1": "URLには ws:// または wss:// スキーマを使用して、存在するwebsocketリスナを設定してください。", "url2": "標準では payload がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。" }, From fd2213232c936f93cdec17fabde6c33dd527f5c0 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 13 Mar 2020 16:29:16 +0900 Subject: [PATCH 108/346] Update message catalogs for other languages --- packages/node_modules/@node-red/nodes/locales/de/messages.json | 2 +- packages/node_modules/@node-red/nodes/locales/ko/messages.json | 2 +- .../node_modules/@node-red/nodes/locales/zh-CN/messages.json | 2 +- .../node_modules/@node-red/nodes/locales/zh-TW/messages.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/de/messages.json b/packages/node_modules/@node-red/nodes/locales/de/messages.json index c36c309f3..58fce3a92 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/de/messages.json @@ -397,7 +397,7 @@ "message" : "gesamte Nachricht", "tip" : { "path1" : "Standardmäßig enthält Nutzdaten die Daten, die über einen Websocket gesendet oder von einem Websocket empfangen werden. Der Listener kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge sendet oder empfängt.", - "path2" : "Dieser Pfad ist relativ zu ", + "path2" : "Dieser Pfad ist relativ zu __path__.", "url1" : "URL sollte ws: / & #47; oder wss: / & #47; Schema verwenden und auf einen vorhandenen Websocket-Listener verweisen.", "url2" : "Standardmäßig enthält Nutzdaten die Daten, die über einen Websocket gesendet oder von einem Websocket empfangen werden. Der Client kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge sendet oder empfängt." }, diff --git a/packages/node_modules/@node-red/nodes/locales/ko/messages.json b/packages/node_modules/@node-red/nodes/locales/ko/messages.json index d87b2c0f5..8e3a4f325 100755 --- a/packages/node_modules/@node-red/nodes/locales/ko/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ko/messages.json @@ -446,7 +446,7 @@ "message": "메세지 전체를 송신/수신", "tip": { "path1": "표준으로는 payload 가 websocket에서 송신, 수신된 데이터를 기다립니다. 클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.", - "path2": "This path will be relative to ", + "path2": "This path will be relative to __path__.", "url1": "URL에는 ws:// 또는 wss:// 스키마를 사용하여, 존재하는 websocket리스너를 설정해 주세요.", "url2": "표준으로는 payload 가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다." }, diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json index 4fc5dc4d4..934cc734d 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/messages.json @@ -455,7 +455,7 @@ "message": "完整信息", "tip": { "path1": "默认情况下,payload将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.", - "path2": "这条路径将相对于 ", + "path2": "这条路径将相对于 __path__.", "url1": "URL 应该使用ws://或者wss://方案并指向现有的websocket侦听器.", "url2": "默认情况下,payload 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象." }, diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json index 7ece3333d..5368bb993 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/messages.json @@ -455,7 +455,7 @@ "message": "完整資訊", "tip": { "path1": "預設情況下,payload將包含要發送或從Websocket接收的資料。偵聽器可以配置為以JSON格式的字串發送或接收整個消息物件.", - "path2": "這條路徑將相對於 ", + "path2": "這條路徑將相對於 __path__.", "url1": "URL 應該使用ws://或者wss://方案並指向現有的websocket監聽器.", "url2": "預設情況下,payload 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件." }, From 72126730efb4439d474f61d6077903f9989bfc0e Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 13 Mar 2020 16:32:00 +0900 Subject: [PATCH 109/346] Remove unnecessary code for node property of websocket node in the German language --- .../locales/de/network/22-websocket.html | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html b/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html index 5aa5cf884..26cc078ae 100755 --- a/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html +++ b/packages/node_modules/@node-red/nodes/locales/de/network/22-websocket.html @@ -37,30 +37,6 @@

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

          - - - From 9ba9998bd6fd87443d849a17fddb6df8cff66275 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 13 Mar 2020 11:26:49 +0000 Subject: [PATCH 110/346] make exec node logging consistent with itself. (only be verbose when in verbose mode) --- packages/node_modules/@node-red/nodes/core/function/90-exec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/90-exec.js b/packages/node_modules/@node-red/nodes/core/function/90-exec.js index a5132230e..f5e7edd87 100644 --- a/packages/node_modules/@node-red/nodes/core/function/90-exec.js +++ b/packages/node_modules/@node-red/nodes/core/function/90-exec.js @@ -145,7 +145,7 @@ module.exports = function(RED) { if (error.signal) { msg3.payload.signal = error.signal; } if (error.code === null) { node.status({fill:"red",shape:"dot",text:"killed"}); } else { node.status({fill:"red",shape:"dot",text:"error:"+error.code}); } - node.log('error:' + error); + if (RED.settings.verbose) { node.log('error:' + error); } } else if (node.oldrc === "false") { msg3 = RED.util.cloneMessage(msg); From 0ef3471f8f53ad0d700212a294bf03d63b684721 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 13 Mar 2020 11:27:13 +0000 Subject: [PATCH 111/346] [groups] Add undo of group import --- .../@node-red/editor-client/src/js/history.js | 20 +++++++++++++++++++ .../@node-red/editor-client/src/js/nodes.js | 5 ++++- .../@node-red/editor-client/src/js/ui/view.js | 16 ++++++++------- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index 51d0e00a0..a86e9b9c7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -21,6 +21,7 @@ RED.history = (function() { var i; var len; var node; + var group; var subflow; var modifiedTabs = {}; var inverseEv; @@ -74,6 +75,15 @@ RED.history = (function() { RED.nodes.removeLink(ev.links[i]); } } + if (ev.groups) { + inverseEv.groups = []; + for (i=0;i 0) { result = RED.subflow.removeOutput(removedSubflowOutputs); if (result) { @@ -4057,12 +4057,12 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } g.append('rect').classed("red-ui-flow-group-outline",true) - .attr('rx',1).attr('ry',1).style({ + .attr('rx',0.5).attr('ry',0.5).style({ "fill":"none", "stroke": "#ff7f0e", "pointer-events": "stroke", "stroke-opacity": 0, - "stroke-width": 15 + "stroke-width": 12 }) g.append('rect').classed("red-ui-flow-group-body",true) @@ -4110,7 +4110,7 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } g.selectAll(".red-ui-flow-group-outline") .attr("width",d.w) .attr("height",d.h) - .style("stroke-opacity",function(d) { if (d.selected) { return 0.3 } return 0}); + .style("stroke-opacity",function(d) { if (d.selected) { return 0.8 } return 0}); g.selectAll(".red-ui-flow-group-body") .attr("width",d.w) @@ -4190,9 +4190,10 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } if (result) { var new_nodes = result[0]; var new_links = result[1]; - var new_workspaces = result[2]; - var new_subflows = result[3]; - var new_default_workspace = result[4]; + var new_groups = result[2]; + var new_workspaces = result[3]; + var new_subflows = result[4]; + var new_default_workspace = result[5]; if (addNewFlow && new_default_workspace) { RED.workspaces.show(new_default_workspace.id); } @@ -4265,6 +4266,7 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } t:"add", nodes:new_node_ids, links:new_links, + groups:new_groups, workspaces:new_workspaces, subflows:new_subflows, dirty:RED.nodes.dirty() From b165129388f37f2f8b873016c1cc346b9aaf0185 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 13 Mar 2020 11:28:19 +0000 Subject: [PATCH 112/346] Remove old leagcy wording from file node info to stop confusing users. --- .../@node-red/nodes/core/storage/10-file.html | 4 ++-- .../nodes/locales/en-US/storage/10-file.html | 15 ++------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/storage/10-file.html b/packages/node_modules/@node-red/nodes/core/storage/10-file.html index d20ad75c5..5487920b8 100755 --- a/packages/node_modules/@node-red/nodes/core/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/core/storage/10-file.html @@ -1,5 +1,5 @@ - - - From 09d55a0cbdff545f59138c232163283dfe0fde65 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 13 Mar 2020 11:33:37 +0000 Subject: [PATCH 113/346] remove unneeded title line from file info text --- .../@node-red/nodes/locales/en-US/storage/10-file.html | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html index f25ce0292..fe2bbc324 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/storage/10-file.html @@ -49,7 +49,6 @@
          The contents of the file as either a string or binary buffer.
          filename string
          If not configured in the node, this optional property sets the name of the file to be read.
          -
          error object

          Details

          The filename should be an absolute path, otherwise it will be relative to From a9508a2c0402e055011d48d28e5df3bb353269eb Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 13 Mar 2020 21:31:16 +0900 Subject: [PATCH 114/346] Remove old leagcy wording from file node info (Japanese) --- .../@node-red/nodes/locales/ja/storage/10-file.html | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html index 2df174104..21d4af9be 100644 --- a/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/ja/storage/10-file.html @@ -14,7 +14,7 @@ limitations under the License. --> - - From c700d5c922af369680c27248c8da2863c2c75e63 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 13 Mar 2020 21:38:23 +0900 Subject: [PATCH 115/346] Remove old leagcy wording from file node info (Chinese) --- .../@node-red/nodes/locales/zh-CN/storage/10-file.html | 8 ++------ .../@node-red/nodes/locales/zh-TW/storage/10-file.html | 8 ++------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html index 4ec78cdb4..eb38a5235 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/zh-CN/storage/10-file.html @@ -14,7 +14,7 @@ limitations under the License. --> - - diff --git a/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html index 03705ea5c..ce2531137 100644 --- a/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html +++ b/packages/node_modules/@node-red/nodes/locales/zh-TW/storage/10-file.html @@ -14,7 +14,7 @@ limitations under the License. --> - - From 4c78f06c2b639f8f7eb9edcfe3f31a7d296cce2b Mon Sep 17 00:00:00 2001 From: Mauricio Bonani Date: Fri, 13 Mar 2020 08:44:56 -0400 Subject: [PATCH 116/346] Fix paletteCategories order --- packages/node_modules/node-red/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/node-red/settings.js b/packages/node_modules/node-red/settings.js index 960cb6d17..c5e4355e1 100644 --- a/packages/node_modules/node-red/settings.js +++ b/packages/node_modules/node-red/settings.js @@ -243,7 +243,7 @@ module.exports = { // palette. If a node's category is not in the list, the category will get // added to the end of the palette. // If not set, the following default order is used: - //paletteCategories: ['subflows','flow','input','output','function','parser','social','mobile','storage','analysis','advanced'], + //paletteCategories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], // Configure the logging output logging: { From 20f97d0d136310a994b185b0de13ea6da88b9904 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 13 Mar 2020 13:09:47 +0000 Subject: [PATCH 117/346] Add better handling of host-key-verify error with projects --- .../editor-client/locales/en-US/editor.json | 3 +- .../src/js/ui/projects/projects.js | 171 ++++++++++-------- .../localfilesystem/projects/git/index.js | 6 +- 3 files changed, 101 insertions(+), 79 deletions(-) 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 a5f232aa6..ed4d4f9a6 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -977,7 +977,8 @@ "passphrase": "Passphrase", "retry": "Retry", "update-failed": "Failed to update auth", - "unhandled": "Unhandled error response" + "unhandled": "Unhandled error response", + "host-key-verify-failed": "

          Host key verification failed.

          The repository host key could not be verified. Please update your known_hosts file and try again." }, "create-branch-list": { "invalid": "Invalid branch", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js index 0c297c82a..da54a81c8 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projects.js @@ -1939,100 +1939,121 @@ RED.projects = (function() { } }).fail(function(xhr,textStatus,err) { var responses; + if (options.responses && options.responses[xhr.status]) { responses = options.responses[xhr.status]; if (typeof responses === 'function') { resultCallback = responses; resultCallbackArgs = {error:responses.statusText}; return; - } else if (options.handleAuthFail !== false && xhr.responseJSON.code === 'git_auth_failed') { - var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch; + } else if (options.handleAuthFail !== false && (xhr.responseJSON.code === 'git_auth_failed' || xhr.responseJSON.code === 'git_host_key_verification_failed')) { + if (xhr.responseJSON.code === 'git_auth_failed') { + var url = activeProject.git.remotes[xhr.responseJSON.remote||options.remote||'origin'].fetch; - var message = $('

          '+ + var message = $('
          '+ '
          '+RED._("projects.send-req.auth-req")+':
          '+ '
          '+url+'
          '+ '
          '); - var isSSH = false; - if (/^https?:\/\//.test(url)) { - $('
          '+ - '
          ').appendTo(message); - } else if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(url)) { - isSSH = true; - var row = $('
          ').appendTo(message); - $('').appendTo(row); - var projectRepoSSHKeySelect = $('
          '+ + '
          ').appendTo(message); + } else if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(url)) { + isSSH = true; + var row = $('
          ').appendTo(message); + $('').appendTo(row); + var projectRepoSSHKeySelect = $('').appendTo(row); - } + row = $('
          ').appendTo(message); + $('').appendTo(row); + $('').appendTo(row); + } - var notification = RED.notify(message,{ - type:"error", - fixed: true, - modal: true, - buttons: [ - { - //id: "node-dialog-delete", - //class: 'leftButton', - text: RED._("common.label.cancel"), - click: function() { - notification.close(); - } - },{ - text: ' ' +RED._("projects.send-req.retry") +'', - click: function() { - body = body || {}; - var authBody = {}; - if (isSSH) { - authBody.keyFile = $('#projects-user-auth-key').val(); - authBody.passphrase = $('#projects-user-auth-passphrase').val(); - } else { - authBody.username = $('#projects-user-auth-username').val(); - authBody.password = $('#projects-user-auth-password').val(); + var notification = RED.notify(message,{ + type:"error", + fixed: true, + modal: true, + buttons: [ + { + //id: "node-dialog-delete", + //class: 'leftButton', + text: RED._("common.label.cancel"), + click: function() { + notification.close(); } - var done = function(err) { - if (err) { - console.log(RED._("projects.send-req.update-failed")); - console.log(err); + },{ + text: ' ' +RED._("projects.send-req.retry") +'', + click: function() { + body = body || {}; + var authBody = {}; + if (isSSH) { + authBody.keyFile = $('#projects-user-auth-key').val(); + authBody.passphrase = $('#projects-user-auth-passphrase').val(); } else { - sendRequest(options,body); - notification.close(); + authBody.username = $('#projects-user-auth-username').val(); + authBody.password = $('#projects-user-auth-password').val(); } + var done = function(err) { + if (err) { + console.log(RED._("projects.send-req.update-failed")); + console.log(err); + } else { + sendRequest(options,body); + notification.close(); + } - } - sendRequest({ - url: "projects/"+activeProject.name+"/remotes/"+(xhr.responseJSON.remote||options.remote||'origin'), - type: "PUT", - responses: { - 0: function(error) { - done(error,null); - }, - 200: function(data) { - done(null,data); - }, - 400: { - 'unexpected_error': function(error) { - done(error,null); - } - }, } - },{auth:authBody}); + sendRequest({ + url: "projects/"+activeProject.name+"/remotes/"+(xhr.responseJSON.remote||options.remote||'origin'), + type: "PUT", + responses: { + 0: function(error) { + done(error,null); + }, + 200: function(data) { + done(null,data); + }, + 400: { + 'unexpected_error': function(error) { + done(error,null); + } + }, + } + },{auth:authBody}); + } } - } - ] - }); - return; + ] + }); + return; + } else if (xhr.responseJSON.code === 'git_host_key_verification_failed') { + var message = $('
          '+ + '
          '+RED._("projects.send-req.host-key-verify-failed")+'
          '+ + '
          '); + var notification = RED.notify(message,{ + type:"error", + fixed: true, + modal: true, + buttons: [ + { + text: RED._("common.label.close"), + click: function() { + notification.close(); + } + } + ] + }); + return; + } } else if (responses[xhr.responseJSON.code]) { resultCallback = responses[xhr.responseJSON.code]; resultCallbackArgs = xhr.responseJSON; diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js index b9f114698..d812c05f2 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/git/index.js @@ -41,6 +41,9 @@ function runGitCommand(args,cwd,env,emit) { err.code = "git_connection_failed"; } else if (/Connection timed out/i.test(stderr)) { err.code = "git_connection_failed"; + } else if(/Host key verification failed/i.test(stderr)) { + // TODO: handle host key verification errors separately + err.code = "git_host_key_verification_failed"; } else if (/fatal: could not read/i.test(stderr)) { // Username/Password err.code = "git_auth_failed"; @@ -48,9 +51,6 @@ function runGitCommand(args,cwd,env,emit) { err.code = "git_auth_failed"; } else if(/Permission denied \(publickey\)/i.test(stderr)) { err.code = "git_auth_failed"; - } else if(/Host key verification failed/i.test(stderr)) { - // TODO: handle host key verification errors separately - err.code = "git_auth_failed"; } else if (/commit your changes or stash/i.test(stderr)) { err.code = "git_local_overwrite"; } else if (/CONFLICT/.test(err.stdout)) { From 421b5846f2f93b24699e86d001d93b4520e89f17 Mon Sep 17 00:00:00 2001 From: Kazuhito Yokoi Date: Fri, 13 Mar 2020 22:20:16 +0900 Subject: [PATCH 118/346] Add page objects for UI testing (#2501) * Update page object of change node * Support multiple node outputs in UI testing * Add page object of switch node * Add page objects of trigger and exec nodes * Remove unnecessary code * Update page object of trigger node to select time unit * Add page objects of websocket nodes * Support boolean as value in selectWithWait() * Update page object of split node * Merge page objects of mqtt nodes to make them same as original mqtt node file path --- .../editor/pageobjects/editor/palette_page.js | 5 + .../nodes/core/common/21-debug_page.js | 2 - .../nodes/core/function/10-switch_page.js | 234 ++++++++++++++++++ .../nodes/core/function/15-change_page.js | 67 ++++- .../nodes/core/function/89-delay_page.js | 2 - .../nodes/core/function/89-trigger_page.js | 83 +++++++ .../90-exec_page.js} | 16 +- ...{10-mqttconfig_page.js => 10-mqtt_page.js} | 52 +++- .../nodes/core/network/10-mqttout_page.js | 35 --- .../nodes/core/network/22-websocket_page.js | 93 +++++++ .../nodes/core/sequence/17-split_page.js | 16 +- test/editor/pageobjects/nodes/node_page.js | 7 +- .../pageobjects/nodes/nodefactory_page.js | 18 +- test/editor/pageobjects/util/util_page.js | 2 +- .../scenario/cookbook_messages_uispec.js | 2 +- .../specs/scenario/cookbook_mqtt_uispec.js | 7 +- 16 files changed, 558 insertions(+), 83 deletions(-) create mode 100644 test/editor/pageobjects/nodes/core/function/10-switch_page.js create mode 100644 test/editor/pageobjects/nodes/core/function/89-trigger_page.js rename test/editor/pageobjects/nodes/core/{network/10-mqttin_page.js => function/90-exec_page.js} (66%) rename test/editor/pageobjects/nodes/core/network/{10-mqttconfig_page.js => 10-mqtt_page.js} (52%) delete mode 100644 test/editor/pageobjects/nodes/core/network/10-mqttout_page.js create mode 100644 test/editor/pageobjects/nodes/core/network/22-websocket_page.js diff --git a/test/editor/pageobjects/editor/palette_page.js b/test/editor/pageobjects/editor/palette_page.js index 6af0b5271..3b484a58c 100644 --- a/test/editor/pageobjects/editor/palette_page.js +++ b/test/editor/pageobjects/editor/palette_page.js @@ -24,16 +24,21 @@ var idMap = { "comment": ".red-ui-palette-node[data-palette-type='comment']", // function "function": ".red-ui-palette-node[data-palette-type='function']", + "switch": ".red-ui-palette-node[data-palette-type='switch']", "change": ".red-ui-palette-node[data-palette-type='change']", "range": ".red-ui-palette-node[data-palette-type='range']", "template": ".red-ui-palette-node[data-palette-type='template']", "delay": ".red-ui-palette-node[data-palette-type='delay']", + "trigger": ".red-ui-palette-node[data-palette-type='trigger']", + "exec": ".red-ui-palette-node[data-palette-type='exec']", // network "mqttIn": ".red-ui-palette-node[data-palette-type='mqtt in']", "mqttOut": ".red-ui-palette-node[data-palette-type='mqtt out']", "httpIn": ".red-ui-palette-node[data-palette-type='http in']", "httpResponse": ".red-ui-palette-node[data-palette-type='http response']", "httpRequest": ".red-ui-palette-node[data-palette-type='http request']", + "websocketIn": ".red-ui-palette-node[data-palette-type='websocket in']", + "websocketOut": ".red-ui-palette-node[data-palette-type='websocket out']", // sequence "split": ".red-ui-palette-node[data-palette-type='split']", "join": ".red-ui-palette-node[data-palette-type='join']", diff --git a/test/editor/pageobjects/nodes/core/common/21-debug_page.js b/test/editor/pageobjects/nodes/core/common/21-debug_page.js index 56854b195..990ed6cd9 100644 --- a/test/editor/pageobjects/nodes/core/common/21-debug_page.js +++ b/test/editor/pageobjects/nodes/core/common/21-debug_page.js @@ -18,8 +18,6 @@ var util = require("util"); var nodePage = require("../../node_page"); -var keyPage = require("../../../util/key_page"); - function debugNode(id) { nodePage.call(this, id); } diff --git a/test/editor/pageobjects/nodes/core/function/10-switch_page.js b/test/editor/pageobjects/nodes/core/function/10-switch_page.js new file mode 100644 index 000000000..a04014063 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/function/10-switch_page.js @@ -0,0 +1,234 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require('util'); + +var nodePage = require('../../node_page'); + +function switchNode(id) { + nodePage.call(this, id); +} + +util.inherits(switchNode, nodePage); + +var vtType = { + "msg": 1, + "flow": 2, + "global": 3, + "str": 4, + "num": 5, + "jsonata": 6, + "env": 7, + "prev": 8 +}; + +function setT(t, index) { + browser.selectWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/select', t); +} + +function setV(v, vt, index) { + if (vt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + vtType[vt] + ']'); + } + if (v) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', v); + } +} + +function setBetweenV(v, vt, v2, v2t, index) { + if (vt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + vtType[vt] + ']'); + } + if (v) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/div[1]/input', v); + } + if (v2t) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + vtType[v2t] + ']'); + } + if (v2) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div/div[1]/input', v2); + } +} + +function setSequenceV(v, vt, index) { + var sType = { + "flow": 1, + "global": 2, + "num": 3, + "jsonata": 4, + "env": 5, + }; + + if (vt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + sType[vt] + ']'); + } + if (v) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/div[1]/input', v); + } +} + +switchNode.prototype.ruleEqual = function (v, vt, index) { + index = index || 1; + setT('eq', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleNotEqual = function (v, vt, index) { + index = index || 1; + setT('neq', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleLessThan = function (v, vt, index) { + index = index || 1; + setT('lt', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleLessThanOrEqual = function (v, vt, index) { + index = index || 1; + setT('lte', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleGreaterThan = function (v, vt, index) { + index = index || 1; + setT('gt', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleGreaterThanOrEqual = function (v, vt, index) { + index = index || 1; + setT('gte', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleHasKey = function (v, vt, index) { + index = index || 1; + setT('hask', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleIsBetween = function (v, vt, v2, v2t, index) { + index = index || 1; + setT('btwn', index); + setBetweenV(v, vt, v2, v2t, index); +} + +switchNode.prototype.ruleContains = function (v, vt, index) { + index = index || 1; + setT('cont', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleMatchesRegex = function (v, vt, index) { + index = index || 1; + setT('regex', index); + setV(v, vt, index); +} + +switchNode.prototype.ruleIsTrue = function (index) { + index = index || 1; + setT('true', index); +} + +switchNode.prototype.ruleIsFalse = function (index) { + index = index || 1; + setT('false', index); +} + +switchNode.prototype.ruleIsNull = function (index) { + index = index || 1; + setT('null', index); +} + +switchNode.prototype.ruleIsNotNull = function (index) { + index = index || 1; + setT('nnull', index); +} + +switchNode.prototype.ruleIsOfType = function (t, index) { + index = index || 1; + setT('istype', index); + + var tType = { + "str": 1, + "num": 2, + "boolean": 3, + "array": 4, + "buffer": 5, + "object": 6, + "json": 7, + "undefined": 8, + "null": 9 + }; + + if (t) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + tType[t] + ']'); + } +} + +switchNode.prototype.ruleIsEmpty = function (index) { + index = index || 1; + setT('empty', index); +} + +switchNode.prototype.ruleIsNotEmpty = function (index) { + index = index || 1; + setT('nempty', index); +} + +switchNode.prototype.ruleHead = function (v, vt, index) { + index = index || 1; + setT('head', index); + setSequenceV(v, vt, index); +} + +switchNode.prototype.ruleIndexBetween = function (v, vt, v2, v2t, index) { + index = index || 1; + setT('index', index); + setBetweenV(v, vt, v2, v2t, index); +} + +switchNode.prototype.ruleTail = function (v, vt, index) { + index = index || 1; + setT('tail', index); + setSequenceV(v, vt, index); +} + +switchNode.prototype.ruleJSONataExp = function (v, index) { + index = index || 1; + setT('jsonata_exp', index); + if (v) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div[2]/div[1]/input', v); + } +} + +switchNode.prototype.ruleOtherwise = function (index) { + index = index || 1; + setT('else', index); +} + +switchNode.prototype.addRule = function () { + browser.clickWithWait('//div[contains(@class, "red-ui-editableList")]/a'); +} + +module.exports = switchNode; diff --git a/test/editor/pageobjects/nodes/core/function/15-change_page.js b/test/editor/pageobjects/nodes/core/function/15-change_page.js index fab408718..eb26f48aa 100644 --- a/test/editor/pageobjects/nodes/core/function/15-change_page.js +++ b/test/editor/pageobjects/nodes/core/function/15-change_page.js @@ -51,37 +51,78 @@ function setT(t, index) { // It is better to create a function whose input value is the object type in the future, changeNode.prototype.ruleSet = function (p, pt, to, tot, index) { index = index || 1; - setT("set", index); + setT('set', index); if (pt) { browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); - var num = 5 * (index - 1) + 1; - var ptXPath = '//div[contains(@class, "red-ui-typedInput-options")][' + num + ']/a[' + ptType[pt] + ']'; - browser.clickWithWait(ptXPath); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']'); } if (p) { browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); } if (tot) { browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/button[1]'); - var num = 5 * (index - 1) + 2; - var totXPath = '//div[contains(@class, "red-ui-typedInput-options")][' + num + ']/a[' + totType[tot] + ']'; - browser.clickWithWait(totXPath); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[tot] + ']'); } if (to) { browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[2]/div[2]/div/input', to); } } -changeNode.prototype.ruleDelete = function (index) { +changeNode.prototype.ruleChange = function (p, pt, from, fromt, to, tot, index) { index = index || 1; - setT("delete", index); + setT('change', index); + if (pt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']'); + } + if (p) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); + } + if (fromt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[1]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[pt] + ']'); + } + if (from) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[1]/div[2]/div[1]/input', from); + } + if (tot) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[2]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[pt] + ']'); + } + if (to) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[3]/div[2]/div[2]/div[1]/input', to); + } } -changeNode.prototype.ruleMove = function (p, to, index) { +changeNode.prototype.ruleDelete = function (p, pt, index) { index = index || 1; - setT("move", index); - browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); - browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/div/input', to); + setT('delete', index); + if (pt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']'); + } + if (p) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); + } +} + +changeNode.prototype.ruleMove = function (p, pt, to, tot, index) { + index = index || 1; + setT('move', index); + if (pt) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + ptType[pt] + ']'); + } + if (p) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[1]/div/div/input', p); + } + if (tot) { + browser.clickWithWait('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + totType[pt] + ']'); + } + if (to) { + browser.setValue('//*[@id="node-input-rule-container"]/li[' + index + ']/div/div[4]/div[2]/div/input', to); + } } changeNode.prototype.addRule = function () { diff --git a/test/editor/pageobjects/nodes/core/function/89-delay_page.js b/test/editor/pageobjects/nodes/core/function/89-delay_page.js index a4e2197e4..3604beb67 100644 --- a/test/editor/pageobjects/nodes/core/function/89-delay_page.js +++ b/test/editor/pageobjects/nodes/core/function/89-delay_page.js @@ -18,8 +18,6 @@ var util = require("util"); var nodePage = require("../../node_page"); -var keyPage = require("../../../util/key_page"); - function delayNode(id) { nodePage.call(this, id); } diff --git a/test/editor/pageobjects/nodes/core/function/89-trigger_page.js b/test/editor/pageobjects/nodes/core/function/89-trigger_page.js new file mode 100644 index 000000000..5d24de380 --- /dev/null +++ b/test/editor/pageobjects/nodes/core/function/89-trigger_page.js @@ -0,0 +1,83 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); + +var nodePage = require("../../node_page"); + +function triggerNode(id) { + nodePage.call(this, id); +} + +util.inherits(triggerNode, nodePage); + +triggerNode.prototype.setSend = function (send, sendt) { + var sendType = { + "flow": 1, + "global": 2, + "str": 3, + "num": 4, + "bool": 5, + "json": 6, + "bin": 7, + "date": 8, + "env": 9, + "pay": 10, + "nul": 11 + }; + + if (sendt) { + browser.clickWithWait('//*[@id="dialog-form"]/div[1]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + sendType[sendt] + ']'); + } + if (send) { + browser.setValue('//*[@id="dialog-form"]/div[1]/div/div[1]/input', send); + } +} + +triggerNode.prototype.setDuration = function (duration, units) { + browser.setValue('//*[@id="node-input-duration"]', duration); + if (units) { + browser.selectWithWait('//*[@id="node-input-units"]', units); + } +} + +triggerNode.prototype.setThenSend = function (thenSend, thenSendt) { + var thenSendType = { + "flow": 1, + "global": 2, + "str": 3, + "num": 4, + "bool": 5, + "json": 6, + "bin": 7, + "date": 8, + "env": 9, + "pay": 10, + "payl": 11, + "nul": 12 + }; + + if (thenSendt) { + browser.clickWithWait('//*[@id="dialog-form"]/div[5]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + thenSendType[thenSendt] + ']'); + } + if (thenSend) { + browser.setValue('//*[@id="dialog-form"]/div[5]/div/div[1]/input', thenSend); + } +} + +module.exports = triggerNode; diff --git a/test/editor/pageobjects/nodes/core/network/10-mqttin_page.js b/test/editor/pageobjects/nodes/core/function/90-exec_page.js similarity index 66% rename from test/editor/pageobjects/nodes/core/network/10-mqttin_page.js rename to test/editor/pageobjects/nodes/core/function/90-exec_page.js index 31b909116..69b8b6c9a 100644 --- a/test/editor/pageobjects/nodes/core/network/10-mqttin_page.js +++ b/test/editor/pageobjects/nodes/core/function/90-exec_page.js @@ -18,18 +18,20 @@ var util = require("util"); var nodePage = require("../../node_page"); -function mqttInNode(id) { +function execNode(id) { nodePage.call(this, id); } -util.inherits(mqttInNode, nodePage); +util.inherits(execNode, nodePage); -mqttInNode.prototype.setTopic = function (topic) { - browser.setValue('#node-input-topic', topic); +execNode.prototype.setCommand = function (command) { + browser.setValue('//*[@id="node-input-command"]', command); } -mqttInNode.prototype.setQoS = function (qos) { - browser.selectWithWait('#node-input-qos', qos); +execNode.prototype.setAppend = function (checkbox) { + if (browser.isSelected('#node-input-addpay') !== checkbox) { + browser.click('#node-input-addpay'); + } } -module.exports = mqttInNode; +module.exports = execNode; diff --git a/test/editor/pageobjects/nodes/core/network/10-mqttconfig_page.js b/test/editor/pageobjects/nodes/core/network/10-mqtt_page.js similarity index 52% rename from test/editor/pageobjects/nodes/core/network/10-mqttconfig_page.js rename to test/editor/pageobjects/nodes/core/network/10-mqtt_page.js index c7cdc90c5..4bdd92336 100644 --- a/test/editor/pageobjects/nodes/core/network/10-mqttconfig_page.js +++ b/test/editor/pageobjects/nodes/core/network/10-mqtt_page.js @@ -14,27 +14,61 @@ * limitations under the License. **/ -function setServer(broker, port) { +var util = require("util"); + +var nodePage = require("../../node_page"); + +var mqttBrokerNode = {}; + +mqttBrokerNode.setServer = function (broker, port) { browser.setValue('#node-config-input-broker', broker); port = port ? port : 1883; browser.setValue('#node-config-input-port', port); -} +}; -function clickOk() { +mqttBrokerNode.clickOk = function () { browser.clickWithWait('#node-config-dialog-ok'); // Wait until an config dialog closes. browser.waitForVisible('#node-config-dialog-ok', 10000, true); -} +}; -function edit() { +mqttBrokerNode.edit = function () { browser.waitForVisible('#node-input-lookup-broker'); browser.click('#node-input-lookup-broker'); // Wait until a config dialog opens. browser.waitForVisible('#node-config-dialog-ok', 10000); +}; + +function mqttInNode(id) { + nodePage.call(this, id); } -module.exports = { - setServer: setServer, - clickOk: clickOk, - edit: edit +util.inherits(mqttInNode, nodePage); + +mqttInNode.prototype.setTopic = function (topic) { + browser.setValue('#node-input-topic', topic); }; + +mqttInNode.prototype.setQoS = function (qos) { + browser.selectWithWait('#node-input-qos', qos); +}; + +mqttInNode.prototype.mqttBrokerNode = mqttBrokerNode; +module.exports.mqttInNode = mqttInNode; + +function mqttOutNode(id) { + nodePage.call(this, id); +} + +util.inherits(mqttOutNode, nodePage); + +mqttOutNode.prototype.setTopic = function (topic) { + browser.setValue('#node-input-topic', topic); +}; + +mqttOutNode.prototype.setRetain = function (retain) { + browser.selectWithWait('#node-input-retain', retain); +}; + +mqttOutNode.prototype.mqttBrokerNode = mqttBrokerNode; +module.exports.mqttOutNode = mqttOutNode; diff --git a/test/editor/pageobjects/nodes/core/network/10-mqttout_page.js b/test/editor/pageobjects/nodes/core/network/10-mqttout_page.js deleted file mode 100644 index 783d87b55..000000000 --- a/test/editor/pageobjects/nodes/core/network/10-mqttout_page.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright JS Foundation and other contributors, http://js.foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ - -var util = require("util"); - -var nodePage = require("../../node_page"); - -function mqttOutNode(id) { - nodePage.call(this, id); -} - -util.inherits(mqttOutNode, nodePage); - -mqttOutNode.prototype.setTopic = function(topic) { - browser.setValue('#node-input-topic', topic); -} - -mqttOutNode.prototype.setRetain = function (retain) { - browser.selectWithWait('#node-input-retain', retain); -} - -module.exports = mqttOutNode; \ No newline at end of file diff --git a/test/editor/pageobjects/nodes/core/network/22-websocket_page.js b/test/editor/pageobjects/nodes/core/network/22-websocket_page.js new file mode 100644 index 000000000..8f7dc261e --- /dev/null +++ b/test/editor/pageobjects/nodes/core/network/22-websocket_page.js @@ -0,0 +1,93 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +var util = require("util"); + +var nodePage = require("../../node_page"); + +var websocketListenerNode = {}; + +websocketListenerNode.setPath = function (path) { + browser.setValue('#node-config-input-path', path); +}; + +websocketListenerNode.setSendReceive = function (wholemsg) { + browser.selectWithWait('#node-config-input-wholemsg', wholemsg); +}; + +websocketListenerNode.clickOk = function () { + browser.clickWithWait('#node-config-dialog-ok'); + // Wait until an config dialog closes. + browser.waitForVisible('#node-config-dialog-ok', 10000, true); +}; + +websocketListenerNode.edit = function () { + browser.waitForVisible('#node-input-lookup-server'); + browser.click('#node-input-lookup-server'); + // Wait until a config dialog opens. + browser.waitForVisible('#node-config-dialog-ok', 10000); +}; + +var websocketClientNode = {}; + +websocketClientNode.setPath = function (path) { + browser.setValue('#node-config-input-path', path); +}; + +websocketClientNode.setSendReceive = function (wholemsg) { + browser.selectWithWait('#node-config-input-wholemsg', wholemsg); +}; + +websocketClientNode.clickOk = function () { + browser.clickWithWait('#node-config-dialog-ok'); + // Wait until an config dialog closes. + browser.waitForVisible('#node-config-dialog-ok', 10000, true); +}; + +websocketClientNode.edit = function () { + browser.waitForVisible('#node-input-lookup-client'); + browser.click('#node-input-lookup-client'); + // Wait until a config dialog opens. + browser.waitForVisible('#node-config-dialog-ok', 10000); +}; + +function websocketInNode(id) { + nodePage.call(this, id); +} + +util.inherits(websocketInNode, nodePage); + +websocketInNode.prototype.setType = function (type) { + browser.selectWithWait('#node-input-mode', type); +}; + +websocketInNode.prototype.websocketListenerNode = websocketListenerNode; +websocketInNode.prototype.websocketClientNode = websocketClientNode; +module.exports.websocketInNode = websocketInNode; + +function websocketOutNode(id) { + nodePage.call(this, id); +} + +util.inherits(websocketOutNode, nodePage); + +websocketOutNode.prototype.setType = function (type) { + browser.selectWithWait('#node-input-mode', type); +}; + +websocketOutNode.prototype.websocketListenerNode = websocketListenerNode; +websocketOutNode.prototype.websocketClientNode = websocketClientNode; +module.exports.websocketOutNode = websocketOutNode; diff --git a/test/editor/pageobjects/nodes/core/sequence/17-split_page.js b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js index 3c8c70aa5..8fc32ab1e 100644 --- a/test/editor/pageobjects/nodes/core/sequence/17-split_page.js +++ b/test/editor/pageobjects/nodes/core/sequence/17-split_page.js @@ -24,7 +24,19 @@ function splitNode(id) { util.inherits(splitNode, nodePage); -module.exports = splitNode; +splitNode.prototype.setSplitUsing = function (splt, spltt) { + var spltType = { + "str": 1, + "bin": 2, + "len": 3 + }; + + browser.clickWithWait('//*[@id="dialog-form"]/div[3]/div/button[1]'); + browser.clickWithWait('//div[contains(@class, "red-ui-typedInput-options") and not(contains(@style, "display: none"))]/a[' + spltType[spltt] + ']'); + browser.setValue('//*[@id="dialog-form"]/div[3]/div/div[1]/input', splt); +}; + +module.exports.splitNode = splitNode; function joinNode(id) { nodePage.call(this, id); @@ -32,4 +44,4 @@ function joinNode(id) { util.inherits(joinNode, nodePage); -module.exports = joinNode; +module.exports.joinNode = joinNode; diff --git a/test/editor/pageobjects/nodes/node_page.js b/test/editor/pageobjects/nodes/node_page.js index 5250250e7..03e734cab 100644 --- a/test/editor/pageobjects/nodes/node_page.js +++ b/test/editor/pageobjects/nodes/node_page.js @@ -35,10 +35,11 @@ Node.prototype.clickOk = function () { browser.pause(50); } -Node.prototype.connect = function (targetNode) { - var outputPort = this.id + '/*[@class="red-ui-flow-port-output"]'; +Node.prototype.connect = function (targetNode, port) { + port = port || 1; + var outputPort = this.id + '/*[@class="red-ui-flow-port-output"][' + port + ']'; var inputPort = targetNode.id + '/*[@class="red-ui-flow-port-input"]'; - browser.dragAndDrop(outputPort, inputPort) + browser.dragAndDrop(outputPort, inputPort); } Node.prototype.clickLeftButton = function () { diff --git a/test/editor/pageobjects/nodes/nodefactory_page.js b/test/editor/pageobjects/nodes/nodefactory_page.js index 17949e313..008ecc625 100644 --- a/test/editor/pageobjects/nodes/nodefactory_page.js +++ b/test/editor/pageobjects/nodes/nodefactory_page.js @@ -21,17 +21,22 @@ var catchNode = require('./core/common/25-catch_page'); var statusNode = require('./core/common/25-status_page'); var commentNode = require('./core/common/90-comment_page'); var functionNode = require('./core/function/10-function_page'); +var switchNode = require('./core/function/10-switch_page'); var changeNode = require('./core/function/15-change_page'); var rangeNode = require('./core/function/16-range_page'); var templateNode = require('./core/function/80-template_page'); var delayNode = require('./core/function/89-delay_page'); -var mqttInNode = require('./core/network/10-mqttin_page'); -var mqttOutNode = require('./core/network/10-mqttout_page'); +var triggerNode = require('./core/function/89-trigger_page'); +var execNode = require('./core/function/90-exec_page'); +var mqttInNode = require('./core/network/10-mqtt_page').mqttInNode; +var mqttOutNode = require('./core/network/10-mqtt_page').mqttOutNode; var httpInNode = require('./core/network/21-httpin_page'); var httpResponseNode = require('./core/network/21-httpresponse_page'); var httpRequestNode = require('./core/network/21-httprequest_page'); -var splitNode = require('./core/sequence/17-split_page'); -var joinNode = require('./core/sequence/17-split_page'); +var websocketInNode = require('./core/network/22-websocket_page').websocketInNode; +var websocketOutNode = require('./core/network/22-websocket_page').websocketOutNode; +var splitNode = require('./core/sequence/17-split_page').splitNode; +var joinNode = require('./core/sequence/17-split_page').joinNode; var batchNode = require('./core/sequence/19-batch_page'); var csvNode = require('./core/parsers/70-CSV_page'); var htmlNode = require('./core/parsers/70-HTML_page'); @@ -50,16 +55,21 @@ var nodeCatalog = { "comment": commentNode, // function "function": functionNode, + "switch": switchNode, "change": changeNode, "range": rangeNode, "template": templateNode, "delay": delayNode, + "trigger": triggerNode, + "exec": execNode, // network "mqttIn": mqttInNode, "mqttOut": mqttOutNode, "httpIn": httpInNode, "httpResponse": httpResponseNode, "httpRequest": httpRequestNode, + "websocketIn": websocketInNode, + "websocketOut": websocketOutNode, // sequence "split": splitNode, "join": joinNode, diff --git a/test/editor/pageobjects/util/util_page.js b/test/editor/pageobjects/util/util_page.js index 02508c831..3a764eb93 100644 --- a/test/editor/pageobjects/util/util_page.js +++ b/test/editor/pageobjects/util/util_page.js @@ -70,7 +70,7 @@ function init() { var ret = repeatUntilSuccess(function(args) { return browser.selectByValue(args[0], args[1]); - }, [selector, value]); + }, [selector, value.toString()]); return ret; } catch (e) { console.trace(); diff --git a/test/editor/specs/scenario/cookbook_messages_uispec.js b/test/editor/specs/scenario/cookbook_messages_uispec.js index a9465e5d8..78facbcda 100644 --- a/test/editor/specs/scenario/cookbook_messages_uispec.js +++ b/test/editor/specs/scenario/cookbook_messages_uispec.js @@ -88,7 +88,7 @@ describe('cookbook', function () { injectNode.clickOk(); changeNode.edit(); - changeNode.ruleMove('topic', 'payload'); + changeNode.ruleMove('topic', 'msg', 'payload', 'msg'); changeNode.clickOk(); injectNode.connect(changeNode); diff --git a/test/editor/specs/scenario/cookbook_mqtt_uispec.js b/test/editor/specs/scenario/cookbook_mqtt_uispec.js index 655074750..b68170eb5 100644 --- a/test/editor/specs/scenario/cookbook_mqtt_uispec.js +++ b/test/editor/specs/scenario/cookbook_mqtt_uispec.js @@ -22,7 +22,6 @@ var helper = require("../../editor_helper"); var debugTab = require('../../pageobjects/editor/debugTab_page'); var workspace = require('../../pageobjects/editor/workspace_page'); var specUtil = require('../../pageobjects/util/spec_util_page'); -var mqttConfig = require('../../pageobjects/nodes/core/network/10-mqttconfig_page.js'); var httpNodeRoot = "/api"; @@ -73,9 +72,9 @@ describe('cookbook', function () { var mqttOutNode = workspace.addNode("mqttOut"); mqttOutNode.edit(); - mqttConfig.edit(); - mqttConfig.setServer("localhost", moscaSettings.port); - mqttConfig.clickOk(); + mqttOutNode.mqttBrokerNode.edit(); + mqttOutNode.mqttBrokerNode.setServer("localhost", moscaSettings.port); + mqttOutNode.mqttBrokerNode.clickOk(); mqttOutNode.clickOk(); workspace.deploy(); From 7886e5d57c631e8a9def3b797c0755f096b8dd03 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 13 Mar 2020 23:01:01 +0000 Subject: [PATCH 119/346] [groups] Add undo support for group actions --- .../@node-red/editor-client/src/js/history.js | 54 +++++++- .../@node-red/editor-client/src/js/nodes.js | 6 +- .../editor-client/src/js/ui/group.js | 121 +++++++++++++----- .../@node-red/editor-client/src/js/ui/view.js | 29 ++++- 4 files changed, 174 insertions(+), 36 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index a86e9b9c7..ce7f0e293 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -278,6 +278,13 @@ RED.history = (function() { RED.nodes.addLink(ev.removedLinks[i]); } } + if (ev.addToGroup) { + RED.group.removeFromGroup(ev.addToGroup,ev.nodes.map(function(n) { return n.n })); + inverseEv.removeFromGroup = ev.addToGroup; + } else if (ev.removeFromGroup) { + RED.group.addToGroup(ev.removeFromGroup,ev.nodes.map(function(n) { return n.n })); + inverseEv.addToGroup = ev.removeFromGroup; + } } else if (ev.t == "edit") { inverseEv = { t: "edit", @@ -469,7 +476,52 @@ RED.history = (function() { RED.workspaces.order(ev.order); } } else if (ev.t == "createGroup") { - + inverseEv = { + t: "ungroup", + dirty: RED.nodes.dirty(), + groups: [] + } + if (ev.groups) { + for (i=0;i 0) { + historyEvent.events.push(ungroupHistoryEvent); + } // Finally, create the new group var group = createGroup(nodes); if (group) { RED.view.select({nodes:[group]}) } - + historyEvent.events.push({ + t:"createGroup", + groups: [ group ], + dirty: RED.nodes.dirty() + }); + RED.history.push(historyEvent); + RED.nodes.dirty(true); } } @@ -153,37 +193,24 @@ RED.group = (function() { if (selection.nodes) { var nodes = []; var n; - var parentGroup; - // First pass, check they are all in the same parent - // TODO: DRY mergeSelection,removeSelection,... - for (var i=0; i 0) { + var addedToGroup = null; if (activeHoverGroup) { for (var j=0;j 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus) { + if (removedNodes.length > 0 || removedSubflowOutputs.length > 0 || removedSubflowInputs.length > 0 || removedSubflowStatus || removedGroups.length > 0) { RED.nodes.dirty(true); } } @@ -1964,6 +1983,7 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } t:"delete", nodes:removedNodes, links:removedLinks, + groups: removedGroups, subflowOutputs:removedSubflowOutputs, subflowInputs:removedSubflowInputs, subflow: { @@ -2005,7 +2025,9 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } } var groups = activeGroups.filter(function(g) { return g.selected && !g.active }); while (groups.length > 0) { + console.log("GROUPS",groups.slice()) var group = groups.shift(); + console.log("GROUP",group); nodes.push(group); groups = groups.concat(group.nodes.filter(function(n) { return n.type === "group" })) } @@ -2033,7 +2055,7 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } } } clipboard = JSON.stringify(nns); - console.log(nns); +console.warn(nns); RED.notify(RED._("clipboard.nodeCopied",{count:nns.length}),{id:"clipboard"}); } } @@ -4486,6 +4508,7 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } return result; }, + getGroupAtPoint: getGroupAt, reveal: function(id) { if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) { RED.workspaces.show(id); From 27c462fee913d6be9ecf4185759c544672dcf5a5 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 13 Mar 2020 23:01:19 +0000 Subject: [PATCH 120/346] [groups] Support dragging node from palette into group --- .../editor-client/src/js/ui/palette.js | 29 +++++++++++++++++-- .../@node-red/editor-client/src/js/ui/view.js | 21 ++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 967a61f51..d0842a45f 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -284,6 +284,8 @@ RED.palette = (function() { var mouseX; var mouseY; var spliceTimer; + var groupTimer; + var activeGroup; var paletteWidth; var paletteTop; $(d).draggable({ @@ -295,16 +297,37 @@ RED.palette = (function() { start: function() { paletteWidth = $("#red-ui-palette").width(); paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top; + activeGroup = null; RED.view.focus(); }, - stop: function() { d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null;}}, + stop: function() { + d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); + if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; } + if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; } + }, drag: function(e,ui) { var paletteNode = getPaletteNode(nt); ui.originalPosition.left = paletteNode.offset().left; + mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); + mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop(); + if (!groupTimer) { + groupTimer = setTimeout(function() { + var group = RED.view.getGroupAtPoint(mouseX,mouseY); + if (group !== activeGroup) { + // TODO: needs to be a CSS style on the group + d3.selectAll(".red-ui-flow-group-body").style("stroke-dasharray", function(d) { return (d === group)?"10 4":"none"}) + activeGroup = group; + if (activeGroup) { + $(ui.helper).data('group',activeGroup); + } else { + $(ui.helper).removeData('group'); + } + } + groupTimer = null; + },200) + } if (def.inputs > 0 && def.outputs > 0) { - mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); - mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop(); if (!spliceTimer) { spliceTimer = setTimeout(function() { var nodes = []; 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 ecc009315..6b2603a21 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -389,14 +389,35 @@ RED.view = (function() { historyEvent.removedLinks = [spliceLink]; } + var group = $(ui.helper).data("group"); + if (group) { + RED.group.addToGroup(group, nn); + historyEvent = { + t: 'multi', + events: [historyEvent], + + } + historyEvent.events.push({ + t: "addToGroup", + group: group, + nodes: nn + }) + } + RED.history.push(historyEvent); RED.nodes.add(nn); RED.editor.validateNode(nn); RED.nodes.dirty(true); // auto select dropped node - so info shows (if visible) + exitActiveGroup(); clearSelection(); nn.selected = true; moving_set.push({n:nn}); + if (group) { + selectGroup(group,false); + group.active = true; + activeGroup = group; + } updateActiveNodes(); updateSelection(); redraw(); From c9194c363572971565c8106e8db3bd6404e57ffc Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 13 Mar 2020 23:09:18 +0000 Subject: [PATCH 121/346] [groups] Fix undo/redo handling of addTo/removeFrom group --- .../node_modules/@node-red/editor-client/src/js/history.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index ce7f0e293..c653ac239 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -503,7 +503,7 @@ RED.history = (function() { } } } else if (ev.t == "addToGroup") { - inverse = { + inverseEv = { t: "removeFromGroup", dirty: RED.nodes.dirty(), group: ev.group, @@ -513,7 +513,7 @@ RED.history = (function() { RED.group.removeFromGroup(ev.group,ev.nodes); } } else if (ev.t == "removeFromGroup") { - inverse = { + inverseEv = { t: "addToGroup", dirty: RED.nodes.dirty(), group: ev.group, From 1bf3b3077ed7970e1a008981ecc10548da572071 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sat, 14 Mar 2020 00:17:16 +0000 Subject: [PATCH 122/346] [groups] Include groups when converting selection to subflow --- .../@node-red/editor-client/src/js/history.js | 10 ++++++- .../@node-red/editor-client/src/js/nodes.js | 11 +++++++ .../editor-client/src/js/ui/subflow.js | 29 ++++++++++++++----- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index c653ac239..dc7107c30 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -395,7 +395,9 @@ RED.history = (function() { if (ev.nodes) { inverseEv.movedNodes = []; var z = ev.activeWorkspace; - RED.nodes.filterNodes({z:ev.subflow.subflow.id}).forEach(function(n) { + var fullNodeList = RED.nodes.filterNodes({z:ev.subflow.subflow.id}); + fullNodeList = fullNodeList.concat(RED.nodes.groups(ev.subflow.subflow.id)) + fullNodeList.forEach(function(n) { n.x += ev.subflow.offsetX; n.y += ev.subflow.offsetY; n.dirty = true; @@ -447,6 +449,9 @@ RED.history = (function() { if (ev.movedNodes) { ev.movedNodes.forEach(function(nid) { nn = RED.nodes.node(nid); + if (!nn) { + nn = RED.nodes.group(nid); + } nn.x -= ev.subflow.offsetX; nn.y -= ev.subflow.offsetY; nn.dirty = true; @@ -554,6 +559,9 @@ RED.history = (function() { list: function() { return undoHistory; }, + listRedo: function() { + return redoHistory; + }, depth: function() { return undoHistory.length; }, 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 af79a3a3b..8e56fd5be 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 @@ -305,6 +305,10 @@ RED.nodes = (function() { } function moveNodeToTab(node, z) { + if (node.type === "group") { + moveGroupToTab(node,z); + return; + } if (nodeTabMap[node.z]) { delete nodeTabMap[node.z][node.id]; } @@ -314,6 +318,13 @@ RED.nodes = (function() { nodeTabMap[z][node.id] = node; node.z = z; } + function moveGroupToTab(group, z) { + var index = groupsByZ[group.z].indexOf(group); + groupsByZ[group.z].splice(index,1); + groupsByZ[z] = groupsByZ[z] || []; + groupsByZ[z].push(group); + group.z = z; + } function removeLink(l) { var index = links.indexOf(l); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 01356c3f6..f2fae549d 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -567,6 +567,19 @@ RED.subflow = (function() { return; } var i,n; + var nodeList = new Set(); + var tmplist = selection.nodes.slice(); + while(tmplist.length > 0) { + n = tmplist.shift(); + if (n.type === "group") { + tmplist = tmplist.concat(n.nodes); + } + nodeList.add(n); + } + + nodeList = Array.from(nodeList); + + var nodes = {}; var new_links = []; var removedLinks = []; @@ -575,13 +588,13 @@ RED.subflow = (function() { var candidateOutputs = []; var candidateInputNodes = {}; - var boundingBox = [selection.nodes[0].x, - selection.nodes[0].y, - selection.nodes[0].x, - selection.nodes[0].y]; + var boundingBox = [nodeList[0].x, + nodeList[0].y, + nodeList[0].x, + nodeList[0].y]; - for (i=0;i Date: Sun, 15 Mar 2020 08:02:26 +0900 Subject: [PATCH 123/346] fix tab apperance of subflow template panel --- .../node_modules/@node-red/editor-client/src/js/ui/editor.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 2f5c4fe11..5d4a491b0 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 @@ -2423,15 +2423,16 @@ RED.editor = (function() { buildAppearanceForm(appearanceTab.content,editing_node); editorTabs.addTab(appearanceTab); + buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node); + trayBody.i18n(); + $.getJSON(getCredentialsURL("subflow", subflow.id), function (data) { subflow.credentials = data; subflow.credentials._ = $.extend(true,{},data); - buildEditForm(nodePropertiesTab.content,"dialog-form","subflow-template", undefined, editing_node); $("#subflow-input-name").val(subflow.name); RED.text.bidi.prepareInput($("#subflow-input-name")); - trayBody.i18n(); finishedBuilding = true; done(); }); From 43258ee816f1cf354eb2db75c215bb9e05245f49 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Sun, 15 Mar 2020 15:11:19 +0000 Subject: [PATCH 124/346] Trigger node - reset default timeout value when switcing away from wait for reset --- .../@node-red/nodes/core/function/89-trigger.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html index 28bf0cdeb..d20391b59 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-trigger.html +++ b/packages/node_modules/@node-red/nodes/core/function/89-trigger.html @@ -14,11 +14,11 @@ limitations under the License. --> - From 85a1f59a93664e317934b3beac7f9de60f00b1c9 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Sun, 15 Mar 2020 16:43:32 +0000 Subject: [PATCH 125/346] Fix join to not crash on appending invalid tyoes to buffer. Add extra info to clarify use of complete to Close #2505 --- .../@node-red/nodes/core/sequence/17-split.js | 8 +++++++- .../@node-red/nodes/locales/en-US/messages.json | 3 ++- .../@node-red/nodes/locales/en-US/sequence/17-split.html | 7 ++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js index fc6042625..ced637555 100644 --- a/packages/node_modules/@node-red/nodes/core/sequence/17-split.js +++ b/packages/node_modules/@node-red/nodes/core/sequence/17-split.js @@ -630,7 +630,13 @@ module.exports = function(RED) { var group = inflight[partId]; if (payloadType === 'buffer') { if (property !== undefined) { - inflight[partId].bufferLen += property.length; + if (Buffer.isBuffer(property) || (typeof property === "string") || Array.isArray(property)) { + inflight[partId].bufferLen += property.length; + } + else { + node.error(RED._("join.errors.invalid-type",{error:(typeof property)}),msg); + return; + } } } if (payloadType === 'object') { diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index e1d7c6368..24c80003b 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -894,7 +894,8 @@ "fixup": "Fix-up exp" }, "errors": { - "invalid-expr": "Invalid JSONata expression: __error__" + "invalid-expr": "Invalid JSONata expression: __error__", + "invalid-type": "Cannot join __error__ to buffer" } }, "sort" : { diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html index 5d460f8ee..c9c3e3070 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/sequence/17-split.html @@ -14,7 +14,7 @@ limitations under the License. --> - - - -'; + var colorPalette = [ + "#ff0000", + "#ffC000", + "#ffff00", + "#92d04f", + "#0070c0", + "#001f60", + "#6f2fa0", + "#000000", + "#777777" + ] + var colorSteps = 3; + var colorCount = colorPalette.length; + for (var i=0,len=colorPalette.length*colorSteps;i Date: Fri, 20 Mar 2020 20:00:03 +0000 Subject: [PATCH 136/346] [groups] Add style options for group label --- .../editor-client/locales/en-US/editor.json | 2 +- .../src/js/ui/common/colorPicker.js | 223 ++++++++++++++++++ .../editor-client/src/js/ui/group.js | 139 ++++++++++- .../@node-red/editor-client/src/js/ui/view.js | 62 ++++- .../editor-client/src/sass/editor.scss | 52 +++- 5 files changed, 461 insertions(+), 17 deletions(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js 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 a5254661b..2b3cb691b 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -15,7 +15,7 @@ "next": "Next", "clone": "Clone project", "cont": "Continue", - "stroke": "Stroke", + "line": "Outline", "fill": "Fill" }, "type": { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js new file mode 100644 index 000000000..47f4cfed2 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js @@ -0,0 +1,223 @@ +RED.colorPicker = (function() { + + function getDarkerColor(c) { + var r,g,b; + if (/^#[a-f0-9]{6}$/i.test(c)) { + r = parseInt(c.substring(1, 3), 16); + g = parseInt(c.substring(3, 5), 16); + b = parseInt(c.substring(5, 7), 16); + } else if (/^#[a-f0-9]{3}$/i.test(c)) { + r = parseInt(c.substring(1, 2)+c.substring(1, 2), 16); + g = parseInt(c.substring(2, 3)+c.substring(2, 3), 16); + b = parseInt(c.substring(3, 4)+c.substring(3, 4), 16); + } else { + return c; + } + var l = 0.3 * r/255 + 0.59 * g/255 + 0.11 * b/255 ; + r = Math.max(0,r-50); + g = Math.max(0,g-50); + b = Math.max(0,b-50); + return '#'+((r<<16) + (g<<8) + b).toString(16).padStart(6,'0') + } + + function create(options) { + var color = options.value; + var id = options.id; + var colorPalette = options.palette || []; + var width = options.cellWidth || 30; + var height = options.cellHeight || 30; + var margin = options.cellMargin || 2; + var perRow = options.cellPerRow || 6; + + var container = $("
          ",{style:"display:inline-block"}); + var colorHiddenInput = $("", { id: id, type: "hidden", value: color }).appendTo(container); + var opacityHiddenInput = $("", { id: id+"-opacity", type: "hidden", value: options.hasOwnProperty('opacity')?options.opacity:"1" }).appendTo(container); + + var colorButton = $('
          '+ '
          '+ ''+ @@ -535,9 +536,8 @@ RED.group = (function() { function getNodes(group,recursive) { var nodes = []; group.nodes.forEach(function(n) { - if (!recursive || n.type !== 'group') { - nodes.push(n); - } else { + nodes.push(n); + if (recursive && n.type === 'group') { nodes = nodes.concat(getNodes(n,recursive)) } }) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js index 539fb763d..61ab71c72 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-tools.js @@ -67,9 +67,16 @@ RED.view.tools = (function() { function moveSelection(dx,dy) { if (moving_set === null) { + moving_set = []; var selection = RED.view.selection(); if (selection.nodes) { - moving_set = selection.nodes.map(function(n) { return {n:n}}); + while (selection.nodes.length > 0) { + var n = selection.nodes.shift(); + moving_set.push({n:n}); + if (n.type === "group") { + selection.nodes = selection.nodes.concat(n.nodes); + } + } } } if (moving_set && moving_set.length > 0) { @@ -93,6 +100,9 @@ RED.view.tools = (function() { node.n.x += dx; node.n.y += dy; node.n.dirty = true; + if (node.n.type === "group") { + RED.group.markDirty(node.n); + } minX = Math.min(node.n.x-node.n.w/2-5,minX); minY = Math.min(node.n.y-node.n.h/2-5,minY); } 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 22db1a99b..094e2e0db 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -415,7 +415,7 @@ RED.view = (function() { moving_set.push({n:nn}); if (group) { selectGroup(group,false); - group.active = true; + enterActiveGroup(group); activeGroup = group; } updateActiveNodes(); @@ -790,10 +790,8 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); } function showQuickAddDialog(point, spliceLink, targetGroup) { if (targetGroup && !targetGroup.active) { - targetGroup.active = true; - targetGroup.dirty = true; selectGroup(targetGroup,false); - activeGroup = targetGroup; + enterActiveGroup(targetGroup); RED.view.redraw(); } @@ -1068,9 +1066,7 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); } nn.selected = true; if (targetGroup) { selectGroup(targetGroup,false); - targetGroup.active = true - targetGroup.dirty = true; - activeGroup = targetGroup; + enterActiveGroup(targetGroup); } moving_set.push({n:nn}); updateActiveNodes(); @@ -1294,10 +1290,18 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseDown", mouse_mode); } node.n.x = mousePos[0]+node.dx; node.n.y = mousePos[1]+node.dy; node.n.dirty = true; - minX = Math.min(node.n.x-node.n.w/2-5,minX); - minY = Math.min(node.n.y-node.n.h/2-5,minY); - maxX = Math.max(node.n.x+node.n.w/2+5,maxX); - maxY = Math.max(node.n.y+node.n.h/2+5,maxY); + if (node.n.type === "group") { + RED.group.markDirty(node.n); + minX = Math.min(node.n.x-5,minX); + minY = Math.min(node.n.y-5,minY); + maxX = Math.max(node.n.x+node.n.w+5,maxX); + maxY = Math.max(node.n.y+node.n.h+5,maxY); + } else { + minX = Math.min(node.n.x-node.n.w/2-5,minX); + minY = Math.min(node.n.y-node.n.h/2-5,minY); + maxX = Math.max(node.n.x+node.n.w/2+5,maxX); + maxY = Math.max(node.n.y+node.n.h/2+5,maxY); + } } if (minX !== 0 || minY !== 0) { for (i = 0; i 0) { - var gridOffset = [0,0]; - node = moving_set[0]; - gridOffset[0] = node.n.x-(gridSize*Math.floor((node.n.x-node.n.w/2)/gridSize)+node.n.w/2); - gridOffset[1] = node.n.y-(gridSize*Math.floor(node.n.y/gridSize)); + var i = 0; + + // Prefer to snap nodes to the grid if there is one in the selection + do { + node = moving_set[i++]; + } while(i x && g.x+g.w < x2 && g.y > y && g.y+g.h < y2) { + while (g.g) { + g = RED.nodes.group(g.g); + } + if (!g.selected) { + selectGroup(g,true); + } + } + } + }) + activeNodes.forEach(function(n) { if (!n.selected) { if (n.x > x && n.x < x2 && n.y > y && n.y < y2) { @@ -1469,6 +1500,9 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); } } } }); + + + // var selectionChanged = false; // do { // selectionChanged = false; @@ -1521,11 +1555,9 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); } addedToGroup = activeHoverGroup; activeHoverGroup.hovered = false; - activeHoverGroup.dirty = true; - activeHoverGroup.active = true; - activeGroup = activeHoverGroup; + enterActiveGroup(activeHoverGroup) + // TODO: check back whether this should add to moving_set activeGroup.selected = true; - activeHoverGroup = null; } @@ -1538,6 +1570,7 @@ if (DEBUG_EVENTS) { console.warn("canvasMouseUp", mouse_mode); } n.n.moved = true; } } + if (ns.length > 0) { historyEvent = {t:"move",nodes:ns,dirty:RED.nodes.dirty()}; if (activeSpliceLink) { @@ -1817,6 +1850,8 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } var node = moving_set[0].n; if (node.type === "subflow") { RED.editor.editSubflow(activeSubflow); + } else if (node.type === "group") { + RED.editor.editGroup(node); } else { RED.editor.edit(node); } @@ -1882,11 +1917,14 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } var startDirty = RED.nodes.dirty(); var startChanged = false; + var selectedGroups = []; if (moving_set.length > 0) { for (var i=0;i 0) { var g = selectedGroups.shift(); if (removedGroups.indexOf(g) === -1) { @@ -2040,24 +2077,31 @@ if (DEBUG_EVENTS) { console.warn("clearSelection", mouse_mode); } } }); } else { - if (moving_set.length > 0) { - nodes = moving_set.map(function(n) { return n.n }); - } - var groups = activeGroups.filter(function(g) { return g.selected && !g.active }); - while (groups.length > 0) { - var group = groups.shift(); - nodes.push(group); - groups = groups.concat(group.nodes.filter(function(n) { return n.type === "group" })) + selection = RED.view.selection(); + if (selection.nodes) { + selection.nodes.forEach(function(n) { + nodes.push(n); + if (n.type === 'group') { + nodes = nodes.concat(RED.group.getNodes(n,true)); + } + }) } } if (nodes.length > 0) { var nns = []; + var nodeCount = 0; + var groupCount = 0; for (var n=0;n= 0; i -= 1) { + if (moving_set[i].n === group) { + moving_set.splice(i,1); + break; + } + } + } function exitActiveGroup() { if (activeGroup) { activeGroup.active = false; @@ -2927,8 +2986,9 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } g.dirty = true; } var nodeSet = new Set(g.nodes); + nodeSet.add(g); for (var i = moving_set.length-1; i >= 0; i -= 1) { - if (nodeSet.has(moving_set[i].n)) { + if (nodeSet.has(moving_set[i].n) || moving_set[i].n === g) { moving_set[i].n.selected = false; moving_set[i].n.dirty = true; moving_set.splice(i,1); @@ -4072,30 +4132,48 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } group[0].reverse(); group.each(function(d,i) { + if (d.resize) { + d.minWidth = 0; + delete d.resize; + } if (d.dirty || dirtyGroups[d.id]) { var g = d3.select(this); - var minX = Number.POSITIVE_INFINITY; - var minY = Number.POSITIVE_INFINITY; - var maxX = 0; - var maxY = 0; - d.nodes.forEach(function(n) { - if (n.type !== "group") { - minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); - minY = Math.min(minY,n.y-n.h/2-25); - maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0)); - maxY = Math.max(maxY,n.y+n.h/2+25); - } else { - minX = Math.min(minX,n.x-25) - minY = Math.min(minY,n.y-25) - maxX = Math.max(maxX,n.x+n.w+25) - maxY = Math.max(maxY,n.y+n.h+25) - } - }); + if (d.nodes.length > 0) { + var minX = Number.POSITIVE_INFINITY; + var minY = Number.POSITIVE_INFINITY; + var maxX = 0; + var maxY = 0; + d.nodes.forEach(function(n) { + if (n.type !== "group") { + minX = Math.min(minX,n.x-n.w/2-25-((n._def.button && n._def.align!=="right")?20:0)); + minY = Math.min(minY,n.y-n.h/2-25); + maxX = Math.max(maxX,n.x+n.w/2+25+((n._def.button && n._def.align=="right")?20:0)); + maxY = Math.max(maxY,n.y+n.h/2+25); + } else { + minX = Math.min(minX,n.x-25) + minY = Math.min(minY,n.y-25) + maxX = Math.max(maxX,n.x+n.w+25) + maxY = Math.max(maxY,n.y+n.h+25) + } + }); + + d.x = minX; + d.y = minY; + d.w = maxX - minX; + d.h = maxY - minY; + } else { + d.w = 40; + d.h = 40; + } + if (!d.minWidth) { + if (d.style.label && d.name) { + d.minWidth = calculateTextWidth(d.name||"","red-ui-flow-group-label",8); + } else { + d.minWidth = 40; + } + } + d.w = Math.max(d.minWidth,d.w); - d.x = minX; - d.y = minY; - d.w = maxX - minX; - d.h = maxY - minY; g.attr("transform","translate("+d.x+","+d.y+")") .classed("red-ui-flow-group-hovered",d.hovered); @@ -4113,10 +4191,10 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } g.selectAll(".red-ui-flow-group-body") .attr("width",d.w) .attr("height",d.h) - .style("stroke", d.style.stroke || "none") - .style("stroke-opacity", d.style.hasOwnProperty('stroke-opacity') ? d.style['stroke-opacity'] : 1) - .style("fill", d.style.fill || "none") - .style("fill-opacity", d.style.hasOwnProperty('fill-opacity') ? d.style['fill-opacity'] : 1) + .style("stroke", d.style.stroke || "") + .style("stroke-opacity", d.style.hasOwnProperty('stroke-opacity') ? d.style['stroke-opacity'] : "") + .style("fill", d.style.fill || "") + .style("fill-opacity", d.style.hasOwnProperty('fill-opacity') ? d.style['fill-opacity'] : "") var label = g.selectAll(".red-ui-flow-group-label"); label.classed("hide",!!!d.style.label) @@ -4210,22 +4288,29 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } RED.workspaces.show(new_default_workspace.id); } var new_ms = new_nodes.filter(function(n) { return n.hasOwnProperty("x") && n.hasOwnProperty("y") && n.z == RED.workspaces.active() }).map(function(n) { return {n:n};}); + new_ms = new_ms.concat(new_groups.map(function(g) { return {n:g}})) var new_node_ids = new_nodes.map(function(n){ n.changed = true; return n.id; }); - + // var new_gs = new_groups.filter(function(g) { console.log(g.id,g.x,g.y); return g.nodes.length === 0}).map(function(g) { return {n:g}}) // TODO: pick a more sensible root node - if (new_ms.length > 0) { - var root_node = new_ms[0].n; - var dx = root_node.x; - var dy = root_node.y; + if (new_ms.length > 0 /* || new_gs.length > 0*/) { + if (mouse_position == null) { mouse_position = [0,0]; } + var dx = mouse_position[0]; + var dy = mouse_position[1]; + if (new_ms.length > 0) { + var root_node = new_ms[0].n; + dx = root_node.x; + dy = root_node.y; + } + var minX = 0; var minY = 0; var i; - var node; + var node,group; for (i=0;i 0) { @@ -4402,8 +4506,6 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } RED.view.redraw(); } - - function getSelection() { var selection = {}; @@ -4411,10 +4513,12 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } if (moving_set.length > 0) { moving_set.forEach(function(n) { - allNodes.add(n.n); + if (n.n.type !== 'group') { + allNodes.add(n.n); + } }); } - var selectedGroups = activeGroups.filter(function(g) { return g.selected && !g.active }); + var selectedGroups = activeGroups.filter(function(g) { return g.selected && !g.active }); if (selectedGroups.length > 0) { if (selectedGroups.length === 1 && selectedGroups[0].active) { // Let nodes be nodes From 03e9522d98a72cd6e41a8c9208ffd3ad0913e8d1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 26 Mar 2020 18:01:57 +0000 Subject: [PATCH 149/346] [groups] Include groups when exporting --- .../@node-red/editor-client/src/js/nodes.js | 19 ++++++++++++++++--- .../editor-client/src/js/ui/clipboard.js | 4 +++- 2 files changed, 19 insertions(+), 4 deletions(-) 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 8d3aac89f..14f673d37 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 @@ -697,8 +697,18 @@ RED.nodes = (function() { /** * Converts the current node selection to an exportable JSON Object **/ - function createExportableNodeSet(set, exportedSubflows, exportedConfigNodes) { + function createExportableNodeSet(set, exportedIds, exportedSubflows, exportedConfigNodes) { var nns = []; + + exportedIds = exportedIds || {}; + set = set.filter(function(n) { + if (exportedIds[n.id]) { + return false; + } + exportedIds[n.id] = true; + return true; + }) + exportedConfigNodes = exportedConfigNodes || {}; exportedSubflows = exportedSubflows || {}; for (var n=0;n Date: Thu, 26 Mar 2020 20:26:58 +0000 Subject: [PATCH 150/346] [groups] Include groups when copying whole tabs --- .../@node-red/editor-client/src/js/nodes.js | 3 +++ .../@node-red/editor-client/src/js/ui/view.js | 19 +++---------------- 2 files changed, 6 insertions(+), 16 deletions(-) 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 14f673d37..3403fee6c 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 @@ -372,6 +372,9 @@ RED.nodes = (function() { } } removedGroups = groupsByZ[id] || []; + removedGroups.forEach(function(g) { + delete groups[g.id] + }) delete groupsByZ[id]; for (n=0;n 0 /* || new_gs.length > 0*/) { + if (new_ms.length > 0) { if (mouse_position == null) { @@ -4344,18 +4343,6 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } } - // for (i=0;i Date: Thu, 26 Mar 2020 21:00:22 +0000 Subject: [PATCH 151/346] [groups] Tidy up Info sidebar summary of group selection --- .../editor-client/locales/en-US/editor.json | 1 + .../editor-client/src/js/ui/group.js | 11 +----- .../editor-client/src/js/ui/tab-info.js | 38 ++++++++++++++++++- 3 files changed, 39 insertions(+), 11 deletions(-) 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 e69d0b48d..296b4c6f8 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -551,6 +551,7 @@ "label": "info", "node": "Node", "type": "Type", + "group": "Group", "module": "Module", "id": "ID", "status": "Status", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index 0f2723897..256bf62c8 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -22,16 +22,7 @@ RED.group = (function() { ''+ '
          '+ - // '
          '+ - // ''+ - // ''+ - // ''+ - // ''+ - // ''+ - // ''+ - // ''+ - // '
          '+ - '
          '+ + // '
          '+ '
          '+ ''+ 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 c6e6d0023..3eada5e3f 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 @@ -163,7 +163,8 @@ RED.sidebar.info = (function() { var types = { nodes:0, flows:0, - subflows:0 + subflows:0, + groups: 0 } node.forEach(function(n) { if (n.type === 'tab') { @@ -171,6 +172,8 @@ RED.sidebar.info = (function() { types.nodes += RED.nodes.filterNodes({z:n.id}).length; } else if (n.type === 'subflow') { types.subflows++; + } else if (n.type === 'group') { + types.groups++; } else { types.nodes++; } @@ -190,6 +193,9 @@ RED.sidebar.info = (function() { if (types.nodes > 0) { $('
          ').text(RED._("clipboard.node",{count:types.nodes})).appendTo(counts); } + if (types.groups > 0) { + $('
          ').text(RED._("clipboard.group",{count:types.groups})).appendTo(counts); + } } else { // A single 'thing' selected. @@ -220,6 +226,36 @@ RED.sidebar.info = (function() { propRow = $(''+RED._("sidebar.info.status")+'').appendTo(tableBody); $(propRow.children()[1]).text((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled")) } + } else if (node.type === "group") { + // An actual node is selected in the editor - build up its properties table + propRow = $(''+RED._("sidebar.info.group")+"").appendTo(tableBody); + RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]); + if (node.name) { + propRow = $(''+RED._("common.label.name")+'').appendTo(tableBody); + $('').text(node.name).appendTo(propRow.children()[1]); + } + propRow = $(' ').appendTo(tableBody); + var typeCounts = { + nodes:0, + groups: 0 + } + var allNodes = RED.group.getNodes(node,true); + allNodes.forEach(function(n) { + if (n.type === "group") { + typeCounts.groups++; + } else { + typeCounts.nodes++ + } + }); + var counts = $('
          ').appendTo($(propRow.children()[1])); + if (typeCounts.nodes > 0) { + $('
          ').text(RED._("clipboard.node",{count:typeCounts.nodes})).appendTo(counts); + } + if (typeCounts.groups > 0) { + $('
          ').text(RED._("clipboard.group",{count:typeCounts.groups})).appendTo(counts); + } + + } else { // An actual node is selected in the editor - build up its properties table propRow = $(''+RED._("sidebar.info.node")+"").appendTo(tableBody); From 94ef25bbb9604727a20baa0e37c4e4b84a972643 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 26 Mar 2020 22:50:46 +0000 Subject: [PATCH 152/346] [groups] i18n group messages --- .../editor-client/locales/en-US/editor.json | 11 ++++++++++- .../@node-red/editor-client/src/js/ui/editor.js | 2 +- .../@node-red/editor-client/src/js/ui/group.js | 16 +++++++--------- 3 files changed, 18 insertions(+), 11 deletions(-) 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 296b4c6f8..9899baf83 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -16,7 +16,9 @@ "clone": "Clone project", "cont": "Continue", "line": "Outline", - "fill": "Fill" + "fill": "Fill", + "color": "Color", + "position": "Position" }, "type": { "string": "string", @@ -320,6 +322,13 @@ "multipleInputsToSelection": "Cannot create subflow: multiple inputs to selection" } }, + "group": { + "editGroup": "Edit group: __name__", + "errors": { + "cannotCreateDiffGroups": "Cannot create group using nodes from different groups", + "cannotAddSubflowPorts": "Cannot add subflow ports to a group" + } + }, "editor": { "configEdit": "Edit", "configAdd": "Add", 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 a98fb9cd5..d3a54cbe7 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 @@ -515,7 +515,7 @@ RED.editor = (function() { var node = editStack[i]; label = node.type; if (node.type === 'group') { - label = RED._("subflow.editGroup",{name:RED.utils.sanitize(node.name||node.id)}); + label = RED._("group.editGroup",{name:RED.utils.sanitize(node.name||node.id)}); } else if (node.type === '_expression') { label = RED._("expressionEditor.title"); } else if (node.type === '_js') { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index 256bf62c8..2eb13453b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -39,12 +39,12 @@ RED.group = (function() { '
          '+ '
          '+ ''+ - ''+ + ''+ ''+ '
          '+ '
          '+ ''+ - ''+ + ''+ ''+ '
          '+ '
          '+ @@ -340,7 +340,7 @@ RED.group = (function() { if (i === 0) { parentGroup = n.g; } else if (n.g !== parentGroup) { - RED.notify("Cannot merge nodes from different groups","error"); + RED.notify(RED._("group.errors.cannotCreateDiffGroups"),"error"); return; } } @@ -404,7 +404,7 @@ RED.group = (function() { return; } if (nodes.filter(function(n) { return n.type === "subflow" }).length > 0) { - RED.notify("Cannot add subflow ports to a group","error"); + RED.notify(RED._("group.errors.cannotAddSubflowPorts"),"error"); return; } // nodes is an array @@ -446,7 +446,7 @@ RED.group = (function() { if (!z) { z = n.z; } else if (z !== n.z) { - throw new Error("Cannot create group using nodes with different z properties") + throw new Error("Cannot add nooes with different z properties") } if (n.g) { // This is already in a group. @@ -454,13 +454,13 @@ RED.group = (function() { if (!g) { if (i!==0) { // TODO: this might be ok when merging groups - throw new Error("Cannot create group using nodes from different groups") + throw new Error(RED._("group.errors.cannotCreateDiffGroups")) } g = n.g } } if (g !== n.g) { - throw new Error("Cannot create group using nodes from different groups") + throw new Error(RED._("group.errors.cannotCreateDiffGroups")) } } // The nodes are already in a group. The assumption is they should be @@ -495,7 +495,6 @@ RED.group = (function() { markDirty(group); } function removeFromGroup(group, nodes, reparent) { - console.log('rfg',group,nodes); if (!Array.isArray(nodes)) { nodes = [nodes]; } @@ -504,7 +503,6 @@ RED.group = (function() { // TODO: DRY mergeSelection,removeSelection,... for (var i=0; i Date: Thu, 26 Mar 2020 22:51:06 +0000 Subject: [PATCH 153/346] [groups] Better ordering of group elements on the DOM --- .../@node-red/editor-client/src/js/ui/view.js | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) 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 44948caaa..55055d036 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -554,8 +554,10 @@ RED.view = (function() { activeGroups = RED.nodes.groups(activeWorkspace)||[]; activeGroups.forEach(function(g) { if (g.g) { + g._root = g.g; g._depth = 1; } else { + g._root = g.id; g._depth = 0; } }); @@ -571,15 +573,30 @@ RED.view = (function() { g._depth = parentDepth + 1; changed = true; } - } else { - console.log("Missing group",g.g); + if (g._root !== parentGroup._root) { + g._root = parentGroup._root; + changed = true; + } } } }); } while (changed) activeGroups.sort(function(a,b) { - return a._depth - b._depth; + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._root.localeCompare(b._root); + } }); + + var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); + group.sort(function(a,b) { + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._root.localeCompare(b._root); + } + }) } function generateLinkPath(origX,origY, destX, destY, sc) { @@ -4126,7 +4143,11 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } }); if (addedGroups) { group.sort(function(a,b) { - return a._depth - b._depth; + if (a._root === b._root) { + return a._depth - b._depth; + } else { + return a._root.localeCompare(b._root); + } }) } group[0].reverse(); From 1018c0e8a51815184a46a6fb6e8f3d8d381cde67 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 27 Mar 2020 09:05:58 +0000 Subject: [PATCH 154/346] Handle false values in $env() properly Fixes 2517 --- packages/node_modules/@node-red/util/lib/util.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/util/lib/util.js b/packages/node_modules/@node-red/util/lib/util.js index cd273dfae..3e35f7fde 100644 --- a/packages/node_modules/@node-red/util/lib/util.js +++ b/packages/node_modules/@node-red/util/lib/util.js @@ -566,7 +566,11 @@ function prepareJSONataExpression(value,node) { }); expr.assign('env', function(name) { var val = getSetting(node, name); - return (val ? val : ""); + if (typeof val !== 'undefined') { + return val; + } else { + return "" + } }) expr.registerFunction('clone', cloneMessage, '<(oa)-:o>'); expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value); From 4304d44851add2dc3ddbdcd1a2727451a3ac2b89 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 27 Mar 2020 09:44:15 +0000 Subject: [PATCH 155/346] Ensure complete node scope is remapped in subflows Fixes #2514 --- .../@node-red/runtime/lib/nodes/flows/Subflow.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js index a7bec1234..1d57b0942 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js @@ -154,7 +154,7 @@ class Subflow extends Flow { var ui = old ? old.ui : null; env[e.name] = e; if (ui) { - env[e.name].ui = ui; + env[e.name].ui = ui; } }); } @@ -466,7 +466,7 @@ function createNodeInSubflow(subflowInstanceId, def) { * properties in the nodes object to reference the new node ids. * This handles: * - node.wires, - * - node.scope of Catch and Status nodes, + * - node.scope of Complete, Catch and Status nodes, * - node.XYZ for any property where XYZ is recognised as an old property * @param {[type]} nodes [description] * @param {[type]} nodeMap [description] @@ -489,7 +489,7 @@ function remapSubflowNodes(nodes,nodeMap) { } } } - if ((node.type === 'catch' || node.type === 'status') && node.scope) { + if ((node.type === 'complete' || node.type === 'catch' || node.type === 'status') && node.scope) { node.scope = node.scope.map(function(id) { return nodeMap[id]?nodeMap[id].id:"" }) From 84771f5864c355664bb48755d7452d29d513a7ba Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 27 Mar 2020 23:47:12 +0000 Subject: [PATCH 156/346] Flows/subflows must preinitialise their context objects Fixes #2513 If a node inside a subflow accessed its context object in its constructor, the subflow-instance flow context would not yet have been created. This would cause a place holder context to get created on its behalf, but that place holder doesn't have its parent set properly. This then breaks the usage of $parent inside such a subflow. This fix has changed it so flows (and subflows) create their flow context as part of their initial creation. That ensures it exists when individual nodes from the subflow are created, allowing them to safely access their context. This has also fixed a related issue where any attempt to use $parent to access beyond the root parent would seemingly hang as the callback was never being called. This would cause messages to get stuck in flows. The fix ensures the callback is used in the root context objects and undefined is returned. --- .../runtime/lib/nodes/context/index.js | 105 ++++++++++++++++-- .../@node-red/runtime/lib/nodes/flows/Flow.js | 3 + .../runtime/lib/nodes/flows/Subflow.js | 6 +- .../runtime/lib/nodes/context/index_spec.js | 104 ++++++++++++++--- 4 files changed, 191 insertions(+), 27 deletions(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js index 322fa2868..c395c0615 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js @@ -420,15 +420,39 @@ function createRootContext() { Object.defineProperties(obj, { get: { value: function(key, storage, callback) { + if (!callback && typeof storage === 'function') { + callback = storage; + storage = undefined; + } + if (callback) { + callback() + return; + } return undefined; } }, set: { value: function(key, value, storage, callback) { + if (!callback && typeof storage === 'function') { + callback = storage; + storage = undefined; + } + if (callback) { + callback() + return + } } }, keys: { value: function(storage, callback) { + if (!callback && typeof storage === 'function') { + callback = storage; + storage = undefined; + } + if (callback) { + callback(); + return; + } return undefined; } } @@ -436,25 +460,48 @@ function createRootContext() { return obj; } -function getContext(localId,flowId,parent) { - var contextId = localId; +/** + * Get a flow-level context object. + * @param {string} flowId [description] + * @param {string} parentFlowId the id of the parent flow. undefined + * @return {object}} the context object + */ +function getFlowContext(flowId,parentFlowId) { + if (contexts.hasOwnProperty(flowId)) { + return contexts[flowId]; + } + var parentContext = contexts[parentFlowId]; + if (!parentContext) { + parentContext = createRootContext(); + contexts[parentFlowId] = parentContext; + // throw new Error("Flow "+flowId+" is missing parent context "+parentFlowId); + } + var newContext = createContext(flowId,undefined,parentContext); + contexts[flowId] = newContext; + return newContext; + +} + +function getContext(nodeId, flowId) { + var contextId = nodeId; if (flowId) { - contextId = localId+":"+flowId; + contextId = nodeId+":"+flowId; } if (contexts.hasOwnProperty(contextId)) { return contexts[contextId]; } - var newContext = createContext(contextId,undefined,parent); + var newContext = createContext(contextId); + if (flowId) { - var node = flows.get(flowId); - var parent = undefined; - if (node && node.type.startsWith("subflow:")) { - parent = node.context().flow; + var flowContext = contexts[flowId]; + if (!flowContext) { + // This is most likely due to a unit test for a node which doesn't + // initialise the flow properly. + // To keep things working, initialise the missing context. + // This *does not happen* in normal node-red operation + flowContext = createContext(flowId,undefined,createRootContext()); + contexts[flowId] = flowContext; } - else { - parent = createRootContext(); - } - var flowContext = getContext(flowId,undefined,parent); Object.defineProperty(newContext, 'flow', { value: flowContext }); @@ -466,6 +513,39 @@ function getContext(localId,flowId,parent) { return newContext; } +// +// function getContext(localId,flowId,parent) { +// var contextId = localId; +// if (flowId) { +// contextId = localId+":"+flowId; +// } +// console.log("getContext",localId,flowId,"known?",contexts.hasOwnProperty(contextId)); +// if (contexts.hasOwnProperty(contextId)) { +// return contexts[contextId]; +// } +// var newContext = createContext(contextId,undefined,parent); +// if (flowId) { +// var node = flows.get(flowId); +// console.log("flows,get",flowId,node&&node.type) +// var parent = undefined; +// if (node && node.type.startsWith("subflow:")) { +// parent = node.context().flow; +// } +// else { +// parent = createRootContext(); +// } +// var flowContext = getContext(flowId,undefined,parent); +// Object.defineProperty(newContext, 'flow', { +// value: flowContext +// }); +// } +// Object.defineProperty(newContext, 'global', { +// value: contexts['global'] +// }) +// contexts[contextId] = newContext; +// return newContext; +// } + function deleteContext(id,flowId) { if(!hasConfiguredStore){ // only delete context if there's no configured storage. @@ -517,6 +597,7 @@ module.exports = { load: load, listStores: listStores, get: getContext, + getFlowContext:getFlowContext, delete: deleteContext, clean: clean, close: close diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js index db15e8eb5..5fa77d505 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js @@ -18,6 +18,7 @@ var clone = require("clone"); var redUtil = require("@node-red/util").util; var flowUtil = require("./util"); var events = require("../../events"); +const context = require('../context'); var Subflow; var Log; @@ -54,6 +55,8 @@ class Flow { this.catchNodes = []; this.statusNodes = []; this.path = this.id; + // Ensure a context exists for this flow + this.context = context.getFlowContext(this.id,this.parent.id); } /** diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js index 1d57b0942..d333e3264 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js @@ -16,7 +16,7 @@ const clone = require("clone"); const Flow = require('./Flow').Flow; - +const context = require('../context'); const util = require("util"); const redUtil = require("@node-red/util").util; @@ -227,6 +227,10 @@ class Subflow extends Flow { this.node.on("input", function(msg) { this.send(msg);}); this.node.on("close", function() { this.status({}); }) this.node.status = status => this.parent.handleStatus(this.node,status); + // Create a context instance + console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z) + this._context = context.get(this._alias||this.id,this.z); + this.node._updateWires = this.node.updateWires; diff --git a/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js b/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js index 3c9030b03..6fd76a421 100644 --- a/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js @@ -32,17 +32,20 @@ describe('context', function() { return Context.close(); }); it('stores local property',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); should.not.exist(context1.get("foo")); context1.set("foo","test"); context1.get("foo").should.equal("test"); }); it('stores local property - creates parent properties',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); context1.set("foo.bar","test"); context1.get("foo").should.eql({bar:"test"}); }); it('deletes local property',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); context1.set("foo.abc.bar1","test1"); context1.set("foo.abc.bar2","test2"); @@ -55,12 +58,14 @@ describe('context', function() { should.not.exist(context1.get("foo")); }); it('stores flow property',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); should.not.exist(context1.flow.get("foo")); context1.flow.set("foo","test"); context1.flow.get("foo").should.equal("test"); }); it('stores global property',function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); should.not.exist(context1.global.get("foo")); context1.global.set("foo","test"); @@ -68,6 +73,7 @@ describe('context', function() { }); it('keeps local context local', function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); var context2 = Context.get("2","flowA"); @@ -79,6 +85,7 @@ describe('context', function() { should.not.exist(context2.get("foo")); }); it('flow context accessible to all flow nodes', function() { + var flowContext = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); var context2 = Context.get("2","flowA"); @@ -91,6 +98,8 @@ describe('context', function() { }); it('flow context not shared to nodes on other flows', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB") var context1 = Context.get("1","flowA"); var context2 = Context.get("2","flowB"); @@ -103,6 +112,9 @@ describe('context', function() { }); it('global context shared to all nodes', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB") + var context1 = Context.get("1","flowA"); var context2 = Context.get("2","flowB"); @@ -115,6 +127,7 @@ describe('context', function() { }); it('context.flow/global are not enumerable', function() { + var flowContextA = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); Object.keys(context1).length.should.equal(0); Object.keys(context1.flow).length.should.equal(0); @@ -122,6 +135,7 @@ describe('context', function() { }) it('context.flow/global cannot be deleted', function() { + var flowContextA = Context.getFlowContext("flowA") var context1 = Context.get("1","flowA"); delete context1.flow; should.exist(context1.flow); @@ -130,6 +144,7 @@ describe('context', function() { }) it('deletes context',function() { + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); should.not.exist(context.get("foo")); context.set("foo","abc"); @@ -142,6 +157,7 @@ describe('context', function() { }); it('enumerates context keys - sync', function() { + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); var keys = context.keys(); @@ -160,6 +176,7 @@ describe('context', function() { }); it('enumerates context keys - async', function(done) { + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); var keys = context.keys(function(err,keys) { @@ -183,6 +200,7 @@ describe('context', function() { it('should enumerate only context keys when GlobalContext was given - sync', function() { Context.init({functionGlobalContext: {foo:"bar"}}); Context.load().then(function(){ + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); context.global.set("foo2","bar2"); var keys = context.global.keys(); @@ -195,6 +213,7 @@ describe('context', function() { it('should enumerate only context keys when GlobalContext was given - async', function(done) { Context.init({functionGlobalContext: {foo:"bar"}}); Context.load().then(function(){ + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); context.global.set("foo2","bar2"); context.global.keys(function(err,keys) { @@ -210,6 +229,7 @@ describe('context', function() { it('returns functionGlobalContext value if store value undefined', function() { Context.init({functionGlobalContext: {foo:"bar"}}); return Context.load().then(function(){ + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); var v = context.global.get('foo'); v.should.equal('bar'); @@ -219,6 +239,7 @@ describe('context', function() { it('returns functionGlobalContext sub-value if store value undefined', function() { Context.init({functionGlobalContext: {foo:{bar:123}}}); return Context.load().then(function(){ + var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); var v = context.global.get('foo.bar'); should.equal(v,123); @@ -227,40 +248,67 @@ describe('context', function() { describe("$parent", function() { it('should get undefined for $parent without key', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); + var context1 = Context.get("1","flowB"); var parent = context1.get("$parent"); should.equal(parent, undefined); }); it('should get undefined for $parent of root', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); - var parent = context1.get("$parent.$parent.K"); + var context1 = Context.get("1","flowB"); + var parent = context1.flow.get("$parent.$parent.K"); should.equal(parent, undefined); }); - it('should get value in $parent', function() { + it('should get undefined for $parent of root - callback', function(done) { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); - context0.set("K", "v"); - var v = context1.get("$parent.K"); + var context1 = Context.get("1","flowB"); + context1.flow.get("$parent.$parent.K", function(err, result) { + try { + should.equal(err, undefined); + should.equal(result, undefined); + done(); + } catch(err) { + done(err); + } + }); + + }); + + it('should get value in $parent', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") + var context0 = Context.get("0","flowA"); + var context1 = Context.get("1","flowB"); + flowContextA.set("K", "v"); + var v = context1.flow.get("$parent.K"); should.equal(v, "v"); }); it('should set value in $parent', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); - context1.set("$parent.K", "v"); - var v = context0.get("K"); + var context1 = Context.get("1","flowB"); + context1.flow.set("$parent.K", "v"); + var v = flowContextA.get("K"); should.equal(v, "v"); }); it('should not contain $parent in keys', function() { + var flowContextA = Context.getFlowContext("flowA") + var flowContextB = Context.getFlowContext("flowB","flowA") var context0 = Context.get("0","flowA"); - var context1 = Context.get("1","flowB", context0); + var context1 = Context.get("1","flowB"); var parent = context1.get("$parent"); - context0.set("K0", "v0"); + flowContextA.set("K0", "v0"); context1.set("K1", "v1"); var keys = context1.keys(); keys.should.have.length(1); @@ -366,6 +414,7 @@ describe('context', function() { it('should ignore reserved storage name `_`', function(done) { Context.init({contextStorage:{_:{module:testPlugin}}}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow") var context = Context.get("1","flow"); var cb = function(){} context.set("foo","bar","_",cb); @@ -452,6 +501,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); var cb = function(){done("An error occurred")} Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set("foo","bar","test",cb); context.get("foo","test",cb); @@ -465,6 +515,7 @@ describe('context', function() { it('should store flow property to external context storage',function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.flow.set("foo","bar","test",cb); @@ -479,6 +530,7 @@ describe('context', function() { it('should store global property to external context storage',function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.global.set("foo","bar","test",cb); @@ -493,6 +545,7 @@ describe('context', function() { it('should store data to the default context when non-existent context storage was specified', function(done) { Context.init({contextStorage:contextDefaultStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("foo","bar","nonexist",cb); @@ -510,6 +563,7 @@ describe('context', function() { it('should use the default context', function(done) { Context.init({contextStorage:contextDefaultStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("foo","bar","default",cb); @@ -527,6 +581,7 @@ describe('context', function() { it('should use the alias of default context', function(done) { Context.init({contextStorage:contextDefaultStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("foo","alias",cb); @@ -541,10 +596,11 @@ describe('context', function() { done(); }).catch(done); }); - + it('should allow the store name to be provide in the key', function(done) { Context.init({contextStorage:contextDefaultStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("#:(test)::foo","bar"); @@ -561,6 +617,7 @@ describe('context', function() { it('should use default as the alias of other context', function(done) { Context.init({contextStorage:contextAlias}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.set("foo","alias",cb); @@ -575,6 +632,7 @@ describe('context', function() { it('should not throw an error using undefined storage for local context', function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.get("local","nonexist",cb); @@ -584,6 +642,7 @@ describe('context', function() { it('should throw an error using undefined storage for flow context', function(done) { Context.init({contextStorage:contextStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); var cb = function(){done("An error occurred")} context.flow.get("flow","nonexist",cb); @@ -595,6 +654,7 @@ describe('context', function() { var fGC = { "foo": 456 }; Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC }); Context.load().then(function() { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); // Get foo - should be value from fGC var v = context.global.get("foo"); @@ -615,6 +675,7 @@ describe('context', function() { var fGC = { "foo": 456 }; Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC }); Context.load().then(function() { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); // Get foo - should be value from fGC context.global.get("foo", function(err, v) { @@ -647,6 +708,7 @@ describe('context', function() { it('should return multiple values if key is an array', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set("foo1","bar1","memory"); context.set("foo2","bar2","memory"); @@ -667,6 +729,7 @@ describe('context', function() { var fGC = { "foo1": 456, "foo2": {"bar":789} }; Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC }); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.global.get(["foo1","foo2.bar","foo3"], "memory", function(err,foo1,foo2,foo3){ if (err) { @@ -685,6 +748,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); stubGet.onFirstCall().callsArgWith(2, "error2", "bar1"); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow") var context = Context.get("1","flow"); context.global.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){ if (err === "error2") { @@ -702,6 +766,7 @@ describe('context', function() { stubGet.onSecondCall().callsArgWith(2, null, "bar2"); stubGet.onThirdCall().callsArgWith(2, "error3"); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){ if (err === "error1") { @@ -716,6 +781,7 @@ describe('context', function() { it('should store multiple properties if key and value are arrays', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){ if (err) { @@ -739,6 +805,7 @@ describe('context', function() { it('should deletes multiple properties', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){ if (err) { @@ -777,6 +844,7 @@ describe('context', function() { it('should use null for missing values if the value array is shorter than the key array', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2"], "memory", function(err){ if (err) { @@ -804,6 +872,7 @@ describe('context', function() { it('should use null for missing values if the value is not array', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], "bar1", "memory", function(err){ if (err) { @@ -831,6 +900,7 @@ describe('context', function() { it('should ignore the extra values if the value array is longer than the key array', function(done) { Context.init({contextStorage:memoryStorage}); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3","ignored"], "memory", function(err){ if (err) { @@ -859,6 +929,7 @@ describe('context', function() { Context.init({contextStorage:contextStorage}); stubSet.onFirstCall().callsArgWith(3, "error2"); Context.load().then(function(){ + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1","flow"); context.set(["foo1","foo2","foo3"], ["bar1","bar2","bar3"], "memory", function(err){ if (err === "error2") { @@ -873,6 +944,7 @@ describe('context', function() { it('should throw an error if callback of context.get is not a function', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.get("foo", "memory", "callback"); done("should throw an error."); @@ -884,6 +956,7 @@ describe('context', function() { it('should not throw an error if callback of context.get is not specified', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.get("foo", "memory"); done(); @@ -893,6 +966,7 @@ describe('context', function() { it('should throw an error if callback of context.set is not a function', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.set("foo", "bar", "memory", "callback"); done("should throw an error."); @@ -904,6 +978,7 @@ describe('context', function() { it('should not throw an error if callback of context.set is not specified', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.set("foo", "bar", "memory"); done(); @@ -913,6 +988,7 @@ describe('context', function() { it('should throw an error if callback of context.keys is not a function', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.keys("memory", "callback"); done("should throw an error."); @@ -924,6 +1000,7 @@ describe('context', function() { it('should not throw an error if callback of context.keys is not specified', function (done) { Context.init({ contextStorage: memoryStorage }); Context.load().then(function () { + var flowContext = Context.getFlowContext("flow"); var context = Context.get("1", "flow"); context.keys("memory"); done(); @@ -953,7 +1030,6 @@ describe('context', function() { }).catch(done); }); }); - describe('delete context',function(){ it('should not call delete() when external context storage is used', function(done) { Context.init({contextStorage:contextDefaultStorage}); From 6ae7c51dc53278d690d783c242856d392133fa21 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sun, 29 Mar 2020 20:33:15 +0100 Subject: [PATCH 157/346] Check node props when deciding if pasted node can splice links Fixes #2494 --- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 7a561d9cd..80a9432f7 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -3531,8 +3531,10 @@ RED.view = (function() { if (new_ms.length === 1) { node = new_ms[0]; spliceActive = node.n.hasOwnProperty("_def") && - node.n._def.inputs > 0 && - node.n._def.outputs > 0; + ((node.n.hasOwnProperty("inputs") && node.n.inputs > 0) || (!node.n.hasOwnProperty("inputs") && node.n._def.inputs > 0)) && + ((node.n.hasOwnProperty("outputs") && node.n.outputs > 0) || (!node.n.hasOwnProperty("outputs") && node.n._def.outputs > 0)) + + } } RED.keyboard.add("*","escape",function(){ From a8db3d8dd37020c1b23e50748f91cd741381d18f Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sun, 29 Mar 2020 20:38:05 +0100 Subject: [PATCH 158/346] Don't double-sanitize node name in debug sidebar Fixes #2521 --- .../@node-red/nodes/core/common/lib/debug/debug-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ddab5bbe2..64068ea86 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 @@ -457,7 +457,7 @@ RED.debug = (function() { var metaRow = $('
          ').appendTo(msg); $(''+ getTimestamp()+'').appendTo(metaRow); if (sourceNode) { - $('',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+sanitize(o.name||sourceNode.name||sourceNode.id)) + $('',{href:"#",class:"red-ui-debug-msg-name"}).text('node: '+(o.name||sourceNode.name||sourceNode.id)) .appendTo(metaRow) .on("click", function(evt) { evt.preventDefault(); From 5c06761b1a4f7e5952577b863e4c7135dc55c4d7 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 30 Mar 2020 23:41:58 +0100 Subject: [PATCH 159/346] Remove console.log from subflow --- .../node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js index d333e3264..08d8b21d7 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Subflow.js @@ -228,7 +228,7 @@ class Subflow extends Flow { this.node.on("close", function() { this.status({}); }) this.node.status = status => this.parent.handleStatus(this.node,status); // Create a context instance - console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z) + // console.log("Node.context",this.type,"id:",this._alias||this.id,"z:",this.z) this._context = context.get(this._alias||this.id,this.z); From e5150ea012d442a73dafb6bc9b28669b86774876 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 31 Mar 2020 16:48:20 +0900 Subject: [PATCH 160/346] force redraw after node installation --- .../node_modules/@node-red/editor-client/src/js/nodes.js | 2 +- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) 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 cdf2c8796..ac5347961 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 @@ -1499,7 +1499,7 @@ RED.nodes = (function() { l.target = newNodeMap[l.target.id]; } }); - RED.view.redraw(true); + RED.view.redraw(true, true); } }); }, 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 44f75b481..5a7e11132 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -3765,7 +3765,12 @@ RED.view = (function() { } }, - redraw: function(updateActive) { + redraw: function(updateActive, force) { + if (force) { + activeNodes = []; + activeLinks = []; + redraw(); + } if (updateActive) { updateActiveNodes(); updateSelection(); From fa8236ee2cc34167945399dbe6bfbe6a9d5fb64f Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 31 Mar 2020 20:32:07 +0900 Subject: [PATCH 161/346] update for recent change of dev branch --- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 7229680a5..da3f9b286 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -4565,7 +4565,8 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } if (force) { activeNodes = []; activeLinks = []; - redraw(); + activeGroups = []; + _redraw(); } if (updateActive) { updateActiveNodes(); From e7f942eda753ad9da0c53162d209baee2fd3b826 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 31 Mar 2020 15:34:48 +0100 Subject: [PATCH 162/346] Update nodeTabMap when replacing unknown nodes --- packages/node_modules/@node-red/editor-client/src/js/nodes.js | 3 +++ 1 file changed, 3 insertions(+) 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 5c0778fba..61a5ae806 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 @@ -1434,6 +1434,9 @@ RED.nodes = (function() { delete configNodes[n.id]; } else { nodes.splice(nodes.indexOf(n),1); + if (nodeTabMap[n.z]) { + delete nodeTabMap[n.z][n.id]; + } } reimportList.push(convertNode(n)); }); From 7fa4df082edd3411fbd27f1a2d17bd96a2e147ea Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 31 Mar 2020 15:58:51 +0100 Subject: [PATCH 163/346] Force sync redraw of view when replacing unknown nodes --- .../@node-red/editor-client/src/js/nodes.js | 7 ++++--- .../@node-red/editor-client/src/js/ui/view.js | 14 ++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) 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 7150823c6..af60f6a30 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 @@ -1596,8 +1596,9 @@ RED.nodes = (function() { }); removeLinks.forEach(removeLink); - - RED.view.redraw(true); + // Force the redraw to be synchronous so the view updates + // *now* and removes the unknown node + RED.view.redraw(true, true); var result = importNodes(reimportList,false); var newNodeMap = {}; result[0].forEach(function(n) { @@ -1611,7 +1612,7 @@ RED.nodes = (function() { l.target = newNodeMap[l.target.id]; } }); - RED.view.redraw(true, true); + RED.view.redraw(true); } }); }, 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 da3f9b286..aa2d14e44 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -4561,18 +4561,16 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } }, updateActive: updateActiveNodes, - redraw: function(updateActive, force) { - if (force) { - activeNodes = []; - activeLinks = []; - activeGroups = []; - _redraw(); - } + redraw: function(updateActive, syncRedraw) { if (updateActive) { updateActiveNodes(); updateSelection(); } - redraw(); + if (syncRedraw) { + _redraw(); + } else { + redraw(); + } }, focus: focusView, importNodes: importNodes, From a6ecb54cc4a215002c22893c6f1ff8da9b0acceb Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 31 Mar 2020 19:25:20 +0100 Subject: [PATCH 164/346] Clear node.close timeout to avoid unnecessary work on restart --- .../node_modules/@node-red/runtime/lib/nodes/flows/Flow.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js index 5fa77d505..c132b507b 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js @@ -571,15 +571,18 @@ function stopNode(node,removed) { Log.trace("Stopping node "+node.type+":"+node.id+(removed?" removed":"")); const start = Date.now(); const closePromise = node.close(removed); + let closeTimer = null; const closeTimeout = new Promise((resolve,reject) => { - setTimeout(() => { + closePromise = setTimeout(() => { reject("Close timed out"); }, nodeCloseTimeout); }); return Promise.race([closePromise,closeTimeout]).then(() => { + clearTimeout(closeTimer); var delta = Date.now() - start; Log.trace("Stopped node "+node.type+":"+node.id+" ("+delta+"ms)" ); }).catch(err => { + clearTimeout(closeTimer); node.error(Log._("nodes.flows.stopping-error",{message:err})); Log.debug(err.stack); }) From 5da89892b487c1029232468f311937a68fbcd543 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 1 Apr 2020 14:10:35 +0100 Subject: [PATCH 165/346] [groups] Draw group selection above all other groups --- .../editor-client/src/js/ui/palette.js | 12 ++-- .../@node-red/editor-client/src/js/ui/view.js | 61 +++++++++++++------ 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 706b10570..19e257e79 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -301,17 +301,17 @@ RED.palette = (function() { hoverGroup = null; activeGroup = RED.view.getActiveGroup(); if (activeGroup) { - document.getElementById(activeGroup.id).classList.add("red-ui-flow-group-active-hovered"); + document.getElementById("group_select_"+activeGroup.id).classList.add("red-ui-flow-group-active-hovered"); } RED.view.focus(); }, stop: function() { d3.select('.red-ui-flow-link-splice').classed('red-ui-flow-link-splice',false); if (hoverGroup) { - document.getElementById(hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); + document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); } if (activeGroup) { - document.getElementById(activeGroup.id).classList.remove("red-ui-flow-group-active-hovered"); + document.getElementById("group_select_"+activeGroup.id).classList.remove("red-ui-flow-group-active-hovered"); } if (spliceTimer) { clearTimeout(spliceTimer); spliceTimer = null; } if (groupTimer) { clearTimeout(groupTimer); groupTimer = null; } @@ -323,13 +323,15 @@ RED.palette = (function() { mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop(); if (!groupTimer) { groupTimer = setTimeout(function() { + mouseX /= RED.view.scale(); + mouseY /= RED.view.scale(); var group = RED.view.getGroupAtPoint(mouseX,mouseY); if (group !== hoverGroup) { if (hoverGroup) { - document.getElementById(hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); + document.getElementById("group_select_"+hoverGroup.id).classList.remove("red-ui-flow-group-hovered"); } if (group) { - document.getElementById(group.id).classList.add("red-ui-flow-group-hovered"); + document.getElementById("group_select_"+group.id).classList.add("red-ui-flow-group-hovered"); } hoverGroup = group; if (hoverGroup) { 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 aa2d14e44..0f012e5c3 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -22,9 +22,10 @@ * |- .red-ui-workspace-chart-background * |- .red-ui-workspace-chart-grid "gridLayer" * |- "groupLayer" + * |- "groupSelectLayer" * |- "linkLayer" * |- "dragGroupLayer" - * \- "nodeLayer" + * |- "nodeLayer" */ RED.view = (function() { @@ -110,6 +111,7 @@ RED.view = (function() { var gridLayer; var linkLayer; var dragGroupLayer; + var groupSelectLayer; var nodeLayer; var groupLayer; var drag_lines; @@ -265,9 +267,11 @@ RED.view = (function() { updateGrid(); groupLayer = eventLayer.append("g"); + groupSelectLayer = eventLayer.append("g"); linkLayer = eventLayer.append("g"); dragGroupLayer = eventLayer.append("g"); nodeLayer = eventLayer.append("g"); + drag_lines = []; RED.events.on("workspace:change",function(event) { @@ -3891,11 +3895,6 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } gg = dirtyGroups[gg].g; } } - // var group = dirtyGroups[d.g]; - // group.x = Math.min(group.x,d.x-d.w/2-25-((d._def.button && d._def.align!=="right")?20:0)); - // group.y = Math.min(group.y,d.y-d.h/2-25), - // group.w = Math.max(group.w, d.x+d.w/2+25+((d._def.button && d._def.align=="right")?20:0) - group.x), - // group.h = Math.max(group.h, d.y+d.h/2+25 - group.y) } } }); @@ -4116,21 +4115,34 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } var group = groupLayer.selectAll(".red-ui-flow-group").data(activeGroups,function(d) { return d.id }); - group.exit().remove(); - var groupEnter = group.enter().insert("svg:g") - .attr("class", "red-ui-flow-group") + group.exit().each(function(d,i) { + document.getElementById("group_select_"+d.id).remove() + }).remove(); + var groupEnter = group.enter().insert("svg:g").attr("class", "red-ui-flow-group") + var addedGroups = false; groupEnter.each(function(d,i) { addedGroups = true; var g = d3.select(this); g.attr("id",d.id); - g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5); - g.append('rect').classed("red-ui-flow-group-outline-select",true) + var selectGroup = groupSelectLayer.append('g').attr("class", "red-ui-flow-group").attr("id","group_select_"+d.id); + selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) .attr('rx',1).attr('ry',1) .attr("x",-4) - .attr("y",-4); + .attr("y",-4) + .style("stroke","rgba(255,255,255,0.8)") + .style("stroke-width",6) + + selectGroup.append('rect').classed("red-ui-flow-group-outline-select",true) + .attr('rx',1).attr('ry',1) + .attr("x",-4) + .attr("y",-4) + selectGroup.on("mousedown", function() {console.log("omd");groupMouseDown.call(g[0][0],d)}); + selectGroup.on("mouseup", function() {console.log("omu");groupMouseUp.call(g[0][0],d)}); + + g.append('rect').classed("red-ui-flow-group-outline",true).attr('rx',0.5).attr('ry',0.5); g.append('rect').classed("red-ui-flow-group-body",true) .attr('rx',1).attr('ry',1).style({ @@ -4197,16 +4209,28 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } g.attr("transform","translate("+d.x+","+d.y+")") - .classed("red-ui-flow-group-hovered",d.hovered); g.selectAll(".red-ui-flow-group-outline") .attr("width",d.w) .attr("height",d.h) - g.selectAll(".red-ui-flow-group-outline-select") - .attr("width",d.w+8) - .attr("height",d.h+8) - .style("stroke-opacity",(d.selected)?0.8:0) - .style("stroke-dasharray", (d.active)?"10 4":"none") + + var selectGroup = document.getElementById("group_select_"+d.id); + selectGroup.setAttribute("transform","translate("+d.x+","+d.y+")"); + if (d.hovered) { + selectGroup.classList.add("red-ui-flow-group-hovered") + } else { + selectGroup.classList.remove("red-ui-flow-group-hovered") + } + var selectGroupRect = selectGroup.children[0]; + selectGroupRect.setAttribute("width",d.w+8) + selectGroupRect.setAttribute("height",d.h+8) + selectGroupRect.style.strokeOpacity = (d.selected)?0.8:0; + selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; + selectGroupRect = selectGroup.children[1]; + selectGroupRect.setAttribute("width",d.w+8) + selectGroupRect.setAttribute("height",d.h+8) + selectGroupRect.style.strokeOpacity = (d.selected)?0.8:0; + selectGroupRect.style.strokeDasharray = (d.active)?"10 4":""; g.selectAll(".red-ui-flow-group-body") @@ -4250,7 +4274,6 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } } }) - } else { // JOINING - unselect any selected links linkLayer.selectAll(".red-ui-flow-link-selected").data( From fbfc74e5ca0acea4e5be010b3353708707c5c8af Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 2 Apr 2020 11:57:46 +0100 Subject: [PATCH 166/346] [groups] Ensure newly imported nodes have width/height --- .../node_modules/@node-red/editor-client/src/js/ui/view.js | 3 +++ 1 file changed, 3 insertions(+) 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 0f012e5c3..db4fed72e 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -4362,6 +4362,9 @@ if (DEBUG_EVENTS) { console.warn("nodeMouseDown", mouse_mode,d); } node.n.moved = true; node.n.x -= dx - mouse_position[0]; node.n.y -= dy - mouse_position[1]; + node.n.w = node_width; + node.n.h = node_height; + node.n.resize = true; node.dx = node.n.x - mouse_position[0]; node.dy = node.n.y - mouse_position[1]; if (node.n.type === "group") { From f058de8bcd7d74bd9c5d5972ebaf951c833df3a6 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 2 Apr 2020 16:49:58 +0100 Subject: [PATCH 167/346] Update TypedInput to use flexbox and remove resizing code --- .../src/js/ui/common/typedInput.js | 68 +++---------------- .../src/sass/ui/common/typedInput.scss | 21 ++---- 2 files changed, 16 insertions(+), 73 deletions(-) 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 cb4de12d2..88c842c2f 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 @@ -488,56 +488,6 @@ done(labelWidth); } }, - _resize: function() { - var that = this; - if (this.uiWidth !== null) { - this.uiSelect.width(this.uiWidth); - } - var type = this.typeMap[this.propertyType]; - if (type && type.hasValue === false) { - this.selectTrigger.addClass("red-ui-typedInput-full-width"); - } else { - this.selectTrigger.removeClass("red-ui-typedInput-full-width"); - this._getLabelWidth(this.selectTrigger, function(labelWidth) { - that.elementDiv.css('left',labelWidth+"px"); - that.valueLabelContainer.css('left',labelWidth+"px"); - if (that.optionExpandButton.shown) { - that.elementDiv.css('right',"22px"); - that.valueLabelContainer.css('right',"22px"); - } else { - that.elementDiv.css('right','0'); - that.valueLabelContainer.css('right','0'); - that.input.css({ - 'border-top-right-radius': '4px', - 'border-bottom-right-radius': '4px' - }); - } - if (that.optionSelectTrigger) { - if (type && type.options && type.hasValue === true) { - that.optionSelectLabel.css({'left':'auto'}) - that._getLabelWidth(that.optionSelectLabel, function(lw) { - that.optionSelectTrigger.css({'width':(23+lw)+"px"}); - that.elementDiv.css('right',(23+lw)+"px"); - that.input.css({ - 'border-top-right-radius': 0, - 'border-bottom-right-radius': 0 - }); - }); - } else { - that.optionSelectLabel.css({'left':'0'}) - that.optionSelectTrigger.css({'width':'calc( 100% - '+labelWidth+'px )'}); - if (!that.optionExpandButton.shown) { - that.elementDiv.css({'right':0}); - that.input.css({ - 'border-top-right-radius': '4px', - 'border-bottom-right-radius': '4px' - }); - } - } - } - }); - } - }, _updateOptionSelectLabel: function(o) { var opt = this.typeMap[this.propertyType]; this.optionSelectLabel.empty(); @@ -565,7 +515,6 @@ } if (opt.hasValue) { this.optionValue = o.value; - this._resize(); this.input.trigger('change',this.propertyType,this.value()); } } else { @@ -605,11 +554,12 @@ this.propertyType = null; this.type(currentType); } - setTimeout(function() {that._resize();},0); }, width: function(desiredWidth) { this.uiWidth = desiredWidth; - this._resize(); + if (this.uiWidth !== null) { + this.uiSelect.width(this.uiWidth); + } }, value: function(value) { var that = this; @@ -680,8 +630,6 @@ } else if (opt.icon.indexOf("/") !== -1) { image = new Image(); - image.onload = function() { that._resize(); } - image.onerror = function() { that._resize(); } image.name = opt.icon; image.src = mapDeprecatedIcon(opt.icon); $('',{src:mapDeprecatedIcon(opt.icon),style:"margin-right: 4px;height: 18px;"}).prependTo(this.selectLabel); @@ -693,6 +641,12 @@ if (opt.hasValue === false || (opt.showLabel !== false && !opt.icon)) { this.selectLabel.text(opt.label); } + if (opt.hasValue === false) { + this.selectTrigger.addClass("red-ui-typedInput-full-width"); + } else { + this.selectTrigger.removeClass("red-ui-typedInput-full-width"); + } + if (this.optionMenu) { this.optionMenu.remove(); this.optionMenu = null; @@ -881,9 +835,6 @@ this._trigger("typechange",null,this.propertyType); this.input.trigger('change',this.propertyType,this.value()); } - if (!image) { - this._resize(); - } } } }, @@ -910,7 +861,6 @@ }, show: function() { this.uiSelect.show(); - this._resize(); }, hide: function() { this.uiSelect.hide(); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss index be2d50674..243c42e17 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/typedInput.scss @@ -18,7 +18,7 @@ border: 1px solid $form-input-border-color; border-radius: 4px; height: 34px; - display: inline-block; + display: inline-flex; padding: 0; margin: 0; vertical-align: middle; @@ -26,12 +26,7 @@ overflow:visible; position: relative; .red-ui-typedInput-input-wrap { - position: absolute; - left:0; - right:0; - top:0; - bottom:0; - outline: red; + flex-grow: 1; } input.red-ui-typedInput-input { width: 100%; @@ -49,7 +44,7 @@ border-color: $form-input-focus-color !important; } .red-ui-typedInput-value-label { - position: absolute; + flex-grow: 1; display: inline-block; height: 32px; box-sizing: border-box; @@ -104,12 +99,11 @@ button.red-ui-typedInput-option-trigger { text-align: left; border: none; - position: absolute; + flex-basis: auto; box-sizing: border-box; border-top-left-radius: 4px; border-bottom-left-radius: 4px; padding: 0 1px 0 5px; - display:inline-block; background: $form-button-background; height: 32px; line-height: 30px; @@ -151,7 +145,7 @@ button.red-ui-typedInput-option-trigger text-decoration: none; } &.red-ui-typedInput-full-width { - width: 100%; + flex-grow: 1; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } @@ -167,7 +161,6 @@ button.red-ui-typedInput-option-expand { border-bottom-right-radius: 4px; border-top-left-radius: 0; border-bottom-left-radius: 0; - right: 0; } button.red-ui-typedInput-option-trigger { @@ -176,8 +169,8 @@ button.red-ui-typedInput-option-trigger { border-top-right-radius: 4px; border-bottom-right-radius: 4px; padding: 0 0 0 0; - position:absolute; - right: 0; + position:relative; + flex-grow: 1; .red-ui-typedInput-option-label { background:$form-button-background; color: $form-text-color; From 24f70009188bdc9e531ba6f050791dcc56f218ce Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 2 Apr 2020 23:23:41 +0100 Subject: [PATCH 168/346] [groups] Remove padStart because IE11 --- .../@node-red/editor-client/src/js/ui/common/colorPicker.js | 3 ++- .../node_modules/@node-red/editor-client/src/js/ui/group.js | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js index 47f4cfed2..0c7944c84 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/colorPicker.js @@ -17,7 +17,8 @@ RED.colorPicker = (function() { r = Math.max(0,r-50); g = Math.max(0,g-50); b = Math.max(0,b-50); - return '#'+((r<<16) + (g<<8) + b).toString(16).padStart(6,'0') + var s = ((r<<16) + (g<<8) + b).toString(16); + return '#'+'000000'.slice(0, 6-s.length)+s; } function create(options) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js index 2eb13453b..22c168e22 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/group.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/group.js @@ -78,7 +78,8 @@ RED.group = (function() { r = Math.min(255,Math.floor(r+j*dr)); g = Math.min(255,Math.floor(g+j*dg)); b = Math.min(255,Math.floor(b+j*db)); - colorPalette.push('#'+((r<<16) + (g<<8) + b).toString(16).padStart(6,'0')); + var s = ((r<<16) + (g<<8) + b).toString(16); + colorPalette.push('#'+'000000'.slice(0, 6-s.length)+s); } var defaultGroupStyle = {}; @@ -207,7 +208,8 @@ RED.group = (function() { function convertColorToHex(c) { var m = /^rgb\((\d+), (\d+), (\d+)\)$/.exec(c); if (m) { - return "#"+(((parseInt(m[1])<<16) + (parseInt(m[2])<<8) + parseInt(m[3])).toString(16).padStart(6,'0')) + var s = ((parseInt(m[1])<<16) + (parseInt(m[2])<<8) + parseInt(m[3])).toString(16) + return '#'+'000000'.slice(0, 6-s.length)+s; } return c; } From 9d4400349b23416fdd2f254082483770dc2ce005 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 2 Apr 2020 23:24:18 +0100 Subject: [PATCH 169/346] Fix timer reference in node close handling --- packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js index c132b507b..df4e810fa 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/flows/Flow.js @@ -573,7 +573,7 @@ function stopNode(node,removed) { const closePromise = node.close(removed); let closeTimer = null; const closeTimeout = new Promise((resolve,reject) => { - closePromise = setTimeout(() => { + closeTimer = setTimeout(() => { reject("Close timed out"); }, nodeCloseTimeout); }); From 1d417c07cdb757eac9896f527418fd4e9a3b7765 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 3 Apr 2020 11:14:23 +0100 Subject: [PATCH 170/346] TCP out - tidy up select of which rows to display to help address #2525 --- .../@node-red/nodes/core/network/31-tcpin.html | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.html b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.html index 6029d61ce..61b840268 100644 --- a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.html +++ b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.html @@ -170,15 +170,14 @@ $("#node-input-port-row").hide(); $("#node-input-host-row").hide(); $("#node-input-end-row").hide(); + } else if (sockettype == "client"){ + $("#node-input-port-row").show(); + $("#node-input-host-row").show(); + $("#node-input-end-row").show(); } else { $("#node-input-port-row").show(); - $("#node-input-end-row").show(); - } - - if (sockettype == "client") { - $("#node-input-host-row").show(); - } else { $("#node-input-host-row").hide(); + $("#node-input-end-row").show(); } }; updateOptions(); From 4f3163286318010fb3fd0915ab8f990c92f79cbe Mon Sep 17 00:00:00 2001 From: tmdoit Date: Fri, 3 Apr 2020 16:10:33 +0200 Subject: [PATCH 171/346] Fix: Allow CR and LF control chars to be a part of the value (#2526) To properly parse CSV data. --- packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index 0424e1e9b..fe39f4c59 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -187,7 +187,7 @@ module.exports = function(RED) { // if separator is last char in processing string line (without end of line), add null value at the end - example: '1,2,3\n3,"3",' k[j] = line.length - 1 === i ? null : ""; } - else if ((line[i] === "\n") || (line[i] === "\r")) { // handle multiple lines + else if (((line[i] === "\n") || (line[i] === "\r")) && f) { // handle multiple lines //console.log(j,k,o,k[j]); if (!node.goodtmpl) { node.template[j] = "col"+(j+1); } if ( node.template[j] && (node.template[j] !== "") ) { From e969a1c97cf02a0740aca4294d96278b74ed22d1 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 3 Apr 2020 15:54:19 +0100 Subject: [PATCH 172/346] Let CSV node only send headers once (and then reset that on msg.reset) and also accept msg.columns csv string to set column headers if not specified in node. And Add tests --- .../@node-red/nodes/core/parsers/70-CSV.html | 15 +++- .../@node-red/nodes/core/parsers/70-CSV.js | 32 +++++-- .../nodes/locales/en-US/messages.json | 5 ++ .../nodes/locales/en-US/parsers/70-CSV.html | 4 +- test/nodes/core/parsers/70-CSV_spec.js | 86 +++++++++++++------ 5 files changed, 106 insertions(+), 36 deletions(-) diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html index c7e8fdc58..80778ac1b 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.html @@ -28,7 +28,7 @@
          -   
          +   

          @@ -49,8 +49,13 @@
          - -
          @@ -73,7 +78,7 @@ sep: {value:',',required:true,validate:RED.validators.regex(/^.{1,2}$/)}, //quo: {value:'"',required:true}, hdrin: {value:""}, - hdrout: {value:""}, + hdrout: {value:"none"}, multi: {value:"one",required:true}, ret: {value:'\\n'}, temp: {value:""}, @@ -92,6 +97,8 @@ return this.name?"node_label_italic":""; }, oneditprepare: function() { + if (this.hdrout === false) { this.hdrout = "none"; $("#node-input-hdrout").val("none"); } + if (this.hdrout === true) { this.hdrout = "all"; $("#node-input-hdrout").val("all");} if (this.strings === undefined) { this.strings = true; $("#node-input-strings").prop('checked', true); } if (this.skip === undefined) { this.skip = 0; $("#node-input-skip").val("0");} $("#node-input-skip").spinner({ min:0 }); diff --git a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js index 0424e1e9b..55db3d964 100644 --- a/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js +++ b/packages/node_modules/@node-red/nodes/core/parsers/70-CSV.js @@ -26,7 +26,7 @@ module.exports = function(RED) { this.lineend = "\n"; this.multi = n.multi || "one"; this.hdrin = n.hdrin || false; - this.hdrout = n.hdrout || false; + this.hdrout = n.hdrout || "none"; this.goodtmpl = true; this.skip = parseInt(n.skip || 0); this.store = []; @@ -34,6 +34,8 @@ module.exports = function(RED) { this.include_empty_strings = n.include_empty_strings || false; this.include_null_values = n.include_null_values || false; if (this.parsestrings === undefined) { this.parsestrings = true; } + if (this.hdrout === false) { this.hdrout = "none"; } + if (this.hdrout === true) { this.hdrout = "all"; } var tmpwarn = true; var node = this; @@ -51,14 +53,22 @@ module.exports = function(RED) { return col; } node.template = clean(node.template); + node.hdrSent = false; this.on("input", function(msg) { + if (msg.hasOwnProperty("reset")) { + node.hdrSent = false; + } if (msg.hasOwnProperty("payload")) { if (typeof msg.payload == "object") { // convert object to CSV string try { var ou = ""; - if (node.hdrout) { + if (node.hdrout !== "none" && node.hdrSent === false) { + if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) { + node.template = clean((msg.columns || "").split(",")); + } ou += node.template.join(node.sep) + node.ret; + if (node.hdrout === "once") { node.hdrSent = true; } } if (!Array.isArray(msg.payload)) { msg.payload = [ msg.payload ]; } for (var s = 0; s < msg.payload.length; s++) { @@ -77,13 +87,15 @@ module.exports = function(RED) { ou += msg.payload[s].join(node.sep) + node.ret; } else { + if ((node.template.length === 1) && (node.template[0] === '') && (msg.hasOwnProperty("columns"))) { + node.template = clean((msg.columns || "").split(",")); + } if ((node.template.length === 1) && (node.template[0] === '')) { /* istanbul ignore else */ if (tmpwarn === true) { // just warn about missing template once node.warn(RED._("csv.errors.obj_csv")); tmpwarn = false; } - ou = ""; for (var p in msg.payload[0]) { /* istanbul ignore else */ if (msg.payload[0].hasOwnProperty(p)) { @@ -127,6 +139,7 @@ module.exports = function(RED) { } } msg.payload = ou; + msg.columns = node.template.join(','); if (msg.payload !== '') { node.send(msg); } } catch(e) { node.error(e,msg); } @@ -187,7 +200,7 @@ module.exports = function(RED) { // if separator is last char in processing string line (without end of line), add null value at the end - example: '1,2,3\n3,"3",' k[j] = line.length - 1 === i ? null : ""; } - else if ((line[i] === "\n") || (line[i] === "\r")) { // handle multiple lines + else if (((line[i] === "\n") || (line[i] === "\r")) && f) { // handle multiple lines //console.log(j,k,o,k[j]); if (!node.goodtmpl) { node.template[j] = "col"+(j+1); } if ( node.template[j] && (node.template[j] !== "") ) { @@ -227,6 +240,7 @@ module.exports = function(RED) { a.push(o); // add to the array } var has_parts = msg.hasOwnProperty("parts"); + if (node.multi !== "one") { msg.payload = a; if (has_parts) { @@ -235,12 +249,15 @@ module.exports = function(RED) { } if (msg.parts.index + 1 === msg.parts.count) { msg.payload = node.store; + console.log(node.template) + msg.columns = node.template.filter(val => val).join(','); delete msg.parts; node.send(msg); node.store = []; } } else { + msg.columns = node.template.filter(val => val).join(','); node.send(msg); // finally send the array } } @@ -248,6 +265,7 @@ module.exports = function(RED) { var len = a.length; for (var i = 0; i < len; i++) { var newMessage = RED.util.cloneMessage(msg); + newMessage.columns = node.template.filter(val => val).join(','); newMessage.payload = a[i]; if (!has_parts) { newMessage.parts = { @@ -273,7 +291,11 @@ module.exports = function(RED) { } else { node.warn(RED._("csv.errors.csv_js")); } } - else { node.send(msg); } // If no payload - just pass it on. + else { + if (!msg.hasOwnProperty("reset")) { + node.send(msg); // If no payload and not reset - just pass it on. + } + } }); } RED.nodes.registerType("csv",CSVNode); diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index b5f4474d3..6806f5f21 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -723,6 +723,11 @@ "mac": "Mac (\\r)", "windows": "Windows (\\r\\n)" }, + "hdrout": { + "none": "never send column headers", + "all": "always send column headers", + "once": "send headers once, until msg.reset" + }, "errors": { "csv_js": "This node only handles CSV strings or js objects.", "obj_csv": "No columns template specified for object -> CSV." diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html index 19ece5ac0..6f16f5b72 100644 --- a/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html +++ b/packages/node_modules/@node-red/nodes/locales/en-US/parsers/70-CSV.html @@ -38,11 +38,13 @@

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

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

          +

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

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

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

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

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

          -

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

          +

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

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

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

          diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js index 3917d2d7c..9e7b45d8f 100644 --- a/test/nodes/core/parsers/70-CSV_spec.js +++ b/test/nodes/core/parsers/70-CSV_spec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ /** * Copyright JS Foundation and other contributors, http://js.foundation * @@ -70,12 +71,13 @@ describe('CSV node', function() { it('should convert a simple csv string to a javascript object', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload', { a: 1, b: 2, c: 3, d: 4 }); + msg.should.have.property('columns', "a,b,c,d"); check_parts(msg, 0, 1); done(); }); @@ -86,7 +88,7 @@ describe('CSV node', function() { it('should remove quotes and whitespace from template', function(done) { var flow = [ { id:"n1", type:"csv", temp:'"a", "b" , " c "," d " ', wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -102,12 +104,13 @@ describe('CSV node', function() { it('should create column names if no template provided', function(done) { var flow = [ { id:"n1", type:"csv", temp:'', wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload', { col1: 1, col2: 2, col3: 3, col4: 4 }); + msg.should.have.property('columns', "col1,col2,col3,col4"); check_parts(msg, 0, 1); done(); }); @@ -118,12 +121,13 @@ describe('CSV node', function() { it('should allow dropping of fields from the template', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,,,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload', { a: 1, d: 4 }); + msg.should.have.property('columns', 'a,d'); check_parts(msg, 0, 1); done(); }); @@ -134,7 +138,7 @@ describe('CSV node', function() { it('should leave numbers starting with 0, e and + as strings (except 0.)', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -150,7 +154,7 @@ describe('CSV node', function() { it('should not parse numbers when told not to do so', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", strings:false, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -166,7 +170,7 @@ describe('CSV node', function() { it('should leave handle strings with scientific notation as numbers', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -183,7 +187,7 @@ describe('CSV node', function() { it('should allow quotes in the input (but drop blank strings)', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g,h", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -234,7 +238,7 @@ describe('CSV node', function() { it('should recover from an odd number of quotes in the input', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e,f,g", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -277,12 +281,13 @@ describe('CSV node', function() { it('should be able to output multiple lines as one array', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", multi:"yes", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload', [ { a: 1, b: 2, c: 3, d: 4 },{ a: 5, b: -6, c: '07', d: '+8' },{ a: 9, b: 0, c: 'a', d: 'b' },{ a: 'c', b: 'd', c: 'e', d: 'f' } ]); + msg.should.have.property('columns','a,b,c,d'); msg.should.not.have.property('parts'); done(); }); @@ -293,7 +298,7 @@ describe('CSV node', function() { it('should handle numbers in strings but not IP addresses', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d,e", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -309,7 +314,7 @@ describe('CSV node', function() { it('should preserve parts property', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -353,7 +358,7 @@ describe('CSV node', function() { it('should skip several lines from start if requested', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -367,9 +372,9 @@ describe('CSV node', function() { }); }); - it('should skip several lines from start then use next line as a tempate', function(done) { + it('should skip several lines from start then use next line as a template', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrin:true, skip: 2, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -385,7 +390,7 @@ describe('CSV node', function() { it('should skip several lines from start and correct parts', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", skip: 2, wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -417,11 +422,13 @@ describe('CSV node', function() { n2.on("input", function(msg) { if (c === 0) { msg.should.have.property('payload', { w: 1, x: 2, y: 3, z: 4 }); + msg.should.have.property('columns', 'w,x,y,z'); check_parts(msg, 0, 2); c += 1; } else { msg.should.have.property('payload', { w: 5, x: 6, y: 7, z: 8 }); + msg.should.have.property('columns', 'w,x,y,z'); check_parts(msg, 1, 2); done(); } @@ -445,7 +452,7 @@ describe('CSV node', function() { it('should convert a simple object back to a csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,,e", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -463,7 +470,7 @@ describe('CSV node', function() { it('should convert a simple object back to a csv with no template', function(done) { var flow = [ { id:"n1", type:"csv", temp:" ", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -481,7 +488,7 @@ describe('CSV node', function() { it('should handle a template with spaces in the property names', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b o,c p,,e", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -499,7 +506,7 @@ describe('CSV node', function() { it('should convert an array of objects to a multi-line csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -517,7 +524,7 @@ describe('CSV node', function() { it('should convert a simple array back to a csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -535,7 +542,7 @@ describe('CSV node', function() { it('should convert an array of arrays back to a multi-line csv', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -553,7 +560,7 @@ describe('CSV node', function() { it('should be able to include column names as first row', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", hdrout:true, ret:"\r\n", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -569,9 +576,36 @@ describe('CSV node', function() { }); }); + it('should be able to pass in column names', function(done) { + var flow = [ { id:"n1", type:"csv", temp:"", hdrout:"once", ret:"\r\n", wires:[["n2"]] }, + {id:"n2", type:"helper"} ]; + helper.load(csvNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var count = 0; + n2.on("input", function(msg) { + count += 1; + try { + if (count === 1) { + msg.should.have.property('payload', 'a,,b,a\r\n4,,3,4\r\n'); + } + if (count === 3) { + msg.should.have.property('payload', '4,,3,4\r\n'); + done() + } + } + catch(e) { done(e); } + }); + var testJson = [{ d: 1, b: 3, c: 2, a: 4 }]; + n1.emit("input", {payload:testJson, columns:"a,,b,a"}); + n1.emit("input", {payload:testJson}); + n1.emit("input", {payload:testJson}); + }); + }); + it('should handle quotes and sub-properties', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -591,7 +625,7 @@ describe('CSV node', function() { it('should just pass through if no payload provided', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -611,7 +645,7 @@ describe('CSV node', function() { it('should warn if provided a number or boolean', function(done) { var flow = [ { id:"n1", type:"csv", temp:"a,b,c,d", wires:[["n2"]] }, - {id:"n2", type:"helper"} ]; + {id:"n2", type:"helper"} ]; helper.load(csvNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); From 24eb78d1371bf1ada68ebaa3556376fb92250943 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Fri, 3 Apr 2020 16:55:43 +0100 Subject: [PATCH 173/346] add ja translations --- .../node_modules/@node-red/nodes/locales/ja/messages.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/node_modules/@node-red/nodes/locales/ja/messages.json b/packages/node_modules/@node-red/nodes/locales/ja/messages.json index e15947fa6..92f0b1677 100755 --- a/packages/node_modules/@node-red/nodes/locales/ja/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/ja/messages.json @@ -719,6 +719,11 @@ "mac": "Mac (\\r)", "windows": "Windows (\\r\\n)" }, + "hdrout": { + "none": "カラムヘッダを送信しない", + "all": "カラムヘッダを常に送信する", + "once": "ヘッダを一度だけ送信する(msg.resetの受け付けると再送)" + }, "errors": { "csv_js": "本ノードが処理できる形式は、CSV文字列またはJSONのみです", "obj_csv": "オブジェクトをCSVへ変換する際の列名が設定されていません" From efad7270b7eebf97522200eab38457495ea7245a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 3 Apr 2020 16:56:46 +0100 Subject: [PATCH 174/346] Add polyfills for IE11 --- Gruntfile.js | 1 + .../editor-client/src/js/polyfills.js | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 packages/node_modules/@node-red/editor-client/src/js/polyfills.js diff --git a/Gruntfile.js b/Gruntfile.js index b2da4a514..b6474182a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -125,6 +125,7 @@ module.exports = function(grunt) { src: [ // Ensure editor source files are concatenated in // the right order + "packages/node_modules/@node-red/editor-client/src/js/polyfills.js", "packages/node_modules/@node-red/editor-client/src/js/jquery-addons.js", "packages/node_modules/@node-red/editor-client/src/js/red.js", "packages/node_modules/@node-red/editor-client/src/js/events.js", diff --git a/packages/node_modules/@node-red/editor-client/src/js/polyfills.js b/packages/node_modules/@node-red/editor-client/src/js/polyfills.js new file mode 100644 index 000000000..1a7caf121 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/js/polyfills.js @@ -0,0 +1,34 @@ +(function() { + var isIE11 = !!window.MSInputMethodContext && !!document.documentMode; + + if (isIE11) { + // IE11 does not provide classList on SVGElements + if (! ("classList" in SVGElement.prototype)) { + Object.defineProperty(SVGElement.prototype, 'classList', Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'classList')); + } + + // IE11 does not provide children on SVGElements + if (! ("children" in SVGElement.prototype)) { + Object.defineProperty(SVGElement.prototype, 'children', Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'children')); + } + + if (!Array.from) { + // JSONata provides an Array.from polyfill that doesn't handle iterables. + // So in IE11 we expect Array.from to exist already, it just needs some + // changes to support iterables. + throw new Error("Missing Array.from base polyfill"); + } + Array._from = Array.from; + Array.from = function() { + var arrayLike = arguments[0] + if (arrayLike.forEach) { + var result = []; + arrayLike.forEach(function(i) { + result.push(i); + }) + return result; + } + return Array._from.apply(null,arguments); + } + } +})(); From 161f6090c1c5cd0f19cb1e2f3c5073212a5c8d85 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Mon, 6 Apr 2020 16:34:41 +0900 Subject: [PATCH 175/346] update initialize & finalize processing of function node --- .../editor-client/src/js/ui/library.js | 28 +- .../nodes/core/function/10-function.html | 90 +++--- .../nodes/core/function/10-function.js | 272 ++++++++++++------ .../lib/storage/localfilesystem/library.js | 15 +- 4 files changed, 248 insertions(+), 157 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js index 2520c16a9..06e320f32 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/library.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/library.js @@ -50,18 +50,6 @@ RED.library = (function() { ''+ '
          ' - function toSingleLine(text) { - var result = text.replace(/\\/g, "\\\\").replace(/\n/g, "\\n"); - return result; - } - - function fromSingleLine(text) { - var result = text.replace(/\\[\\n]/g, function(s) { - return ((s === "\\\\") ? "\\" : "\n"); - }); - return result; - } - function saveToLibrary() { var elementPrefix = activeLibrary.elementPrefix || "node-input-"; var name = $("#"+elementPrefix+"name").val().trim(); @@ -80,10 +68,8 @@ RED.library = (function() { var field = activeLibrary.fields[i]; if (field == "name") { data.name = name; - } else if(field == "initialize") { - data.initialize = toSingleLine(activeLibrary.initEditor.getValue()); - } else if(field == "finalize") { - data.finalize = toSingleLine(activeLibrary.finalizeEditor.getValue()); + } else if (typeof(field) === 'object') { + data[field.name] = field.get(); } else { data[field] = $("#"+elementPrefix+field).val(); } @@ -539,13 +525,9 @@ RED.library = (function() { var elementPrefix = activeLibrary.elementPrefix || "node-input-"; for (var i=0; i
          -