mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge branch 'master' into dev
This commit is contained in:
@@ -1215,11 +1215,9 @@
|
||||
"validator": {
|
||||
"errors": {
|
||||
"invalid-json": "Invalid JSON data: __error__",
|
||||
"invalid-json-prop": "__prop__: invalid JSON data: __error__",
|
||||
"invalid-expr": "Invalid JSONata expression: __error__",
|
||||
"invalid-prop": "Invalid property expression",
|
||||
"invalid-prop-prop": "__prop__: invalid property expression",
|
||||
"invalid-num": "Invalid number",
|
||||
"invalid-num-prop": "__prop__: invalid number",
|
||||
"invalid-regexp": "Invalid input pattern",
|
||||
"invalid-regex-prop": "__prop__: invalid input pattern",
|
||||
"missing-required-prop": "__prop__: property value missing",
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
"searchInput": "Rechercher vos flux",
|
||||
"subflows": "Sous-flux",
|
||||
"createSubflow": "Créer un sous-flux",
|
||||
"selectionToSubflow": "Selection d'un sous-flux",
|
||||
"selectionToSubflow": "Convertir en sous-flux",
|
||||
"flows": "Flux",
|
||||
"add": "Ajouter",
|
||||
"rename": "Renommer",
|
||||
@@ -274,23 +274,23 @@
|
||||
"recoveredNodesInfo": "Les noeuds importés sur ce flux contiennent un mauvais identifiant de flux. Ces noeuds ont été ajoutés à ce flux afin que vous puissiez les restaurer ou les supprimer.",
|
||||
"recoveredNodesNotification": "<p>Noeuds importés sans identifiant de flux valide</p><p>Ils ont été ajoutés à un nouveau flux appelé '__flowName__'.</p>",
|
||||
"export": {
|
||||
"selected": "noeuds sélectionnés",
|
||||
"current": "flux actuel",
|
||||
"selected": "les noeuds sélectionnés",
|
||||
"current": "le flux actuel",
|
||||
"all": "tous les flux",
|
||||
"compact": "condensé",
|
||||
"formatted": "formaté",
|
||||
"compact": "Condensé",
|
||||
"formatted": "Formaté",
|
||||
"copy": "Copier dans le presse-papier",
|
||||
"export": "Exporter vers la bibliothèque",
|
||||
"exportAs": "Exporter en tant que",
|
||||
"exportAs": "Exporter comme",
|
||||
"overwrite": "Remplacer",
|
||||
"exists": "<p><b>\"__file__\"</b> existe déjà.</p><p>Voulez-vous le remplacer ?</p>"
|
||||
},
|
||||
"import": {
|
||||
"import": "Importer vers",
|
||||
"importSelected": "Importation sélectionnée",
|
||||
"importSelected": "Importer la sélection",
|
||||
"importCopy": "Importer une copie",
|
||||
"viewNodes": "Afficher les noeuds...",
|
||||
"newFlow": "Nouveau flux",
|
||||
"viewNodes": "Vérifier ces noeuds",
|
||||
"newFlow": "un nouveau flux",
|
||||
"replace": "Remplacer",
|
||||
"errors": {
|
||||
"notArray": "L'entrée n'est pas un tableau JSON",
|
||||
@@ -299,7 +299,7 @@
|
||||
"missingType": "L'entrée n'est pas un flux valide - l'élément '__index__' n'a pas de propriété 'type'"
|
||||
},
|
||||
"conflictNotification1": "Certains des noeuds que vous avez importés existent déjà dans votre espace de travail.",
|
||||
"conflictNotification2": "Sélectionner les noeuds à importer et choisir s'il faut remplacer les noeuds existants ou en importer une copie."
|
||||
"conflictNotification2": "Sélectionnez les noeuds à importer et choisissez s'il faut remplacer les noeuds existants ou en importer une copie."
|
||||
},
|
||||
"copyMessagePath": "Chemin copié",
|
||||
"copyMessageValue": "Valeur copiée",
|
||||
@@ -391,10 +391,10 @@
|
||||
"subflowInstances": "Il existe __count__ instance de ce modèle de sous-flux",
|
||||
"subflowInstances_plural": "Il existe __count__ instances de ce modèle de sous-flux",
|
||||
"editSubflowProperties": "modifier les propriétés",
|
||||
"input": "entrées:",
|
||||
"output": "sorties:",
|
||||
"status": "statut du noeud",
|
||||
"deleteSubflow": "supprimer le sous-flux",
|
||||
"input": "Entrées:",
|
||||
"output": "Sorties:",
|
||||
"status": "Statut du noeud",
|
||||
"deleteSubflow": "Supprimer le sous-flux",
|
||||
"confirmDelete": "Voulez-vous vraiment supprimer ce sous-flux ?",
|
||||
"info": "Description",
|
||||
"category": "Catégorie",
|
||||
@@ -416,6 +416,7 @@
|
||||
},
|
||||
"errors": {
|
||||
"noNodesSelected": "<strong>Impossible de créer un sous-flux</strong> : aucun noeud sélectionné",
|
||||
"acrossMultipleGroups": "Impossible de créer un sous-flux sur plusieurs groupes",
|
||||
"multipleInputsToSelection": "<strong>Impossible de créer un sous-flux</strong> : plusieurs entrées pour la sélection"
|
||||
}
|
||||
},
|
||||
@@ -447,8 +448,8 @@
|
||||
"default": "Par défaut",
|
||||
"noDefaultLabel": "Aucune",
|
||||
"defaultLabel": "Utiliser l'étiquette par défaut",
|
||||
"searchIcons": "Icônes de recherche",
|
||||
"useDefault": "Utilisation par défaut",
|
||||
"searchIcons": "Rechercher une icône",
|
||||
"useDefault": "Icône par défaut",
|
||||
"description": "Description",
|
||||
"show": "Afficher",
|
||||
"hide": "Masquer",
|
||||
@@ -498,13 +499,13 @@
|
||||
"keyboard": {
|
||||
"title": "Raccourcis clavier",
|
||||
"keyboard": "Clavier",
|
||||
"filterActions": "Actions de filtrage",
|
||||
"shortcut": "raccourci",
|
||||
"scope": "portée",
|
||||
"filterActions": "Rechercher l'action",
|
||||
"shortcut": "Raccourci",
|
||||
"scope": "Portée",
|
||||
"unassigned": "Non attribué",
|
||||
"global": "global",
|
||||
"workspace": "espace de travail",
|
||||
"editor": "boîte de dialogue d'édition",
|
||||
"global": "Global",
|
||||
"workspace": "Espace de travail",
|
||||
"editor": "Boîte de dialogue d'édition",
|
||||
"selectAll": "Tout sélectionner",
|
||||
"selectNone": "Ne rien sélectionner",
|
||||
"selectAllConnected": "Sélectionner tous les éléments connectés",
|
||||
@@ -550,22 +551,22 @@
|
||||
},
|
||||
"palette": {
|
||||
"noInfo": "Pas d'information disponible",
|
||||
"filter": "Filtrer les noeuds",
|
||||
"filter": "Rechercher le noeud",
|
||||
"search": "Rechercher les modules",
|
||||
"addCategory": "Ajouter un nouveau...",
|
||||
"label": {
|
||||
"subflows": "sous-flux",
|
||||
"network": "réseau",
|
||||
"common": "commun",
|
||||
"input": "entrée",
|
||||
"output": "sortie",
|
||||
"function": "fonction",
|
||||
"sequence": "séquence",
|
||||
"parser": "analyseur",
|
||||
"social": "social",
|
||||
"storage": "stockage",
|
||||
"analysis": "analyse",
|
||||
"advanced": "avancé"
|
||||
"subflows": "Sous-flux",
|
||||
"network": "Réseau",
|
||||
"common": "Commun",
|
||||
"input": "Entrée",
|
||||
"output": "Sortie",
|
||||
"function": "Fonction",
|
||||
"sequence": "Séquence",
|
||||
"parser": "Analyseur",
|
||||
"social": "Social",
|
||||
"storage": "Stockage",
|
||||
"analysis": "Analyse",
|
||||
"advanced": "Avancé"
|
||||
},
|
||||
"actions": {
|
||||
"collapse-all": "Réduire toutes les catégories",
|
||||
@@ -586,6 +587,7 @@
|
||||
"editor": {
|
||||
"title": "Gérer la palette",
|
||||
"palette": "Palette",
|
||||
"allCatalogs": "Tous les catalogues",
|
||||
"times": {
|
||||
"seconds": "il y a quelques secondes",
|
||||
"minutes": "il y a quelques minutes",
|
||||
@@ -609,24 +611,25 @@
|
||||
"nodeCount_plural": "__label__ noeuds",
|
||||
"moduleCount": "__count__ module disponible",
|
||||
"moduleCount_plural": "__count__ modules disponibles",
|
||||
"inuse": "en cours d'utilisation",
|
||||
"enableall": "activer tout",
|
||||
"disableall": "désactiver tout",
|
||||
"enable": "activer",
|
||||
"disable": "désactiver",
|
||||
"remove": "supprimer",
|
||||
"update": "mettre à jour vers __version__",
|
||||
"updated": "mis à jour",
|
||||
"install": "installer",
|
||||
"installed": "installé",
|
||||
"conflict": "conflit",
|
||||
"inuse": "En cours d'utilisation",
|
||||
"enableall": "Activer tout",
|
||||
"disableall": "Désactiver tout",
|
||||
"enable": "Activer",
|
||||
"disable": "Désactiver",
|
||||
"remove": "Supprimer",
|
||||
"update": "Mettre à jour vers __version__",
|
||||
"updated": "Mis à jour",
|
||||
"install": "Installer",
|
||||
"installed": "Installé",
|
||||
"conflict": "Conflit",
|
||||
"conflictTip": "<p>Ce module ne peut pas être installé car il inclut un<br/>type de noeud qui a déjà été installé</p><p>Conflits avec <code>__module__</code></p>",
|
||||
"loading": "Chargement des catalogues...",
|
||||
"tab-nodes": "Noeuds",
|
||||
"tab-install": "Installer",
|
||||
"sort": "trier:",
|
||||
"sortAZ": "a-z",
|
||||
"sortRecent": "récent",
|
||||
"sort": "Trier:",
|
||||
"sortRelevance": "Pertinence",
|
||||
"sortAZ": "A-Z",
|
||||
"sortRecent": "Récent",
|
||||
"more": "+ __count__ en plus",
|
||||
"upload": "Charger le fichier tgz du module",
|
||||
"refresh": "Actualiser la liste des modules",
|
||||
@@ -667,7 +670,7 @@
|
||||
"info": {
|
||||
"name": "Information",
|
||||
"tabName": "Nom",
|
||||
"label": "info",
|
||||
"label": "Info",
|
||||
"node": "Noeud",
|
||||
"type": "Type",
|
||||
"group": "Groupe",
|
||||
@@ -681,10 +684,10 @@
|
||||
"properties": "Propriétés",
|
||||
"info": "Information",
|
||||
"desc": "Description",
|
||||
"blank": "vide",
|
||||
"null": "nul",
|
||||
"showMore": "afficher en plus",
|
||||
"showLess": "afficher en moins",
|
||||
"blank": "Vide",
|
||||
"null": "Nul",
|
||||
"showMore": "Afficher en plus",
|
||||
"showLess": "Afficher en moins",
|
||||
"flow": "Flux",
|
||||
"selection": "Sélection",
|
||||
"nodes": "__count__ noeuds",
|
||||
@@ -695,7 +698,7 @@
|
||||
"arrayItems": "__count__ éléments",
|
||||
"showTips": "Vous pouvez ouvrir les astuces à partir du panneau des paramètres",
|
||||
"outline": "Plan",
|
||||
"empty": "vide",
|
||||
"empty": "Vide",
|
||||
"globalConfig": "Noeuds de configuration globale",
|
||||
"triggerAction": "Déclencher une action",
|
||||
"find": "Rechercher dans l'espace de travail",
|
||||
@@ -706,7 +709,7 @@
|
||||
},
|
||||
"help": {
|
||||
"name": "Aide",
|
||||
"label": "aide",
|
||||
"label": "Aide",
|
||||
"search": "Aide à la recherche",
|
||||
"nodeHelp": "Aide sur les noeuds",
|
||||
"showHelp": "Afficher l'aide",
|
||||
@@ -717,23 +720,23 @@
|
||||
},
|
||||
"config": {
|
||||
"name": "Noeuds de configuration",
|
||||
"label": "configuration",
|
||||
"label": "Configuration",
|
||||
"global": "Tous les flux",
|
||||
"none": "aucun",
|
||||
"subflows": "sous-flux",
|
||||
"flows": "flux",
|
||||
"filterAll": "tout",
|
||||
"none": "Aucun",
|
||||
"subflows": "Sous-flux",
|
||||
"flows": "Flux",
|
||||
"filterAll": "Tout",
|
||||
"showAllConfigNodes": "Afficher tous les noeuds de configuration",
|
||||
"filterUnused": "inutilisé",
|
||||
"filterUnused": "Inutilisé",
|
||||
"showAllUnusedConfigNodes": "Afficher tous les noeuds de configuration inutilisés",
|
||||
"filtered": "__count__ caché(s)"
|
||||
},
|
||||
"context": {
|
||||
"name": "Données contextuelles",
|
||||
"label": "contexte",
|
||||
"none": "aucune sélection",
|
||||
"refresh": "actualiser pour charger",
|
||||
"empty": "vide",
|
||||
"label": "Contexte",
|
||||
"none": "Aucune sélection",
|
||||
"refresh": "Actualiser pour charger",
|
||||
"empty": "Vide",
|
||||
"node": "Noeud",
|
||||
"flow": "Flux",
|
||||
"global": "Global",
|
||||
@@ -744,10 +747,10 @@
|
||||
},
|
||||
"palette": {
|
||||
"name": "Gestion des palettes",
|
||||
"label": "palette"
|
||||
"label": "Palette"
|
||||
},
|
||||
"project": {
|
||||
"label": "projet",
|
||||
"label": "Projet",
|
||||
"name": "Projet",
|
||||
"description": "Description",
|
||||
"dependencies": "Dépendances",
|
||||
@@ -760,11 +763,11 @@
|
||||
"showProjectSettings": "Afficher les paramètres du projet",
|
||||
"projectSettings": {
|
||||
"title": "Paramètres du projet",
|
||||
"edit": "modifier",
|
||||
"edit": "Modifier",
|
||||
"none": "Vide",
|
||||
"install": "installer",
|
||||
"removeFromProject": "supprimer du projet",
|
||||
"addToProject": "ajouter au projet",
|
||||
"install": "Installer",
|
||||
"removeFromProject": "Supprimer du projet",
|
||||
"addToProject": "Ajouter au projet",
|
||||
"files": "Fichiers",
|
||||
"flow": "Flux",
|
||||
"credentials": "Identifiants",
|
||||
@@ -812,7 +815,7 @@
|
||||
"workflowAutoTip": "Les modifications sont validées automatiquement à chaque déploiement",
|
||||
"sshKeys": "Clés SSH",
|
||||
"sshKeysTip": "Vous permet de créer des connexions sécurisées aux référentiels Git distants.",
|
||||
"add": "ajouter une clé",
|
||||
"add": "Ajouter une clé",
|
||||
"addSshKey": "Ajouter une clé SSH",
|
||||
"addSshKeyTip": "Générer une nouvelle paire de clés publique/privée",
|
||||
"name": "Nom",
|
||||
@@ -848,7 +851,7 @@
|
||||
"none": "Vide",
|
||||
"conflictResolve": "Tous les conflits ont été résolus. Valider les modifications pour terminer la fusion.",
|
||||
"localFiles": "Fichiers locaux",
|
||||
"all": "tout",
|
||||
"all": "Tout",
|
||||
"unmergedChanges": "Modifications non fusionnées",
|
||||
"abortMerge": "Abandonner la fusion",
|
||||
"commit": "Valider",
|
||||
@@ -1097,9 +1100,9 @@
|
||||
"desc8": "Le fichier contenant les identifiants ne sera pas crypté et son contenu sera facilement lisible",
|
||||
"create-project-files": "Créer des fichiers de projet",
|
||||
"create-project": "Créer un projet",
|
||||
"already-exists": "existe déjà",
|
||||
"already-exists": "Existe déjà",
|
||||
"git-error": "Erreur Git",
|
||||
"git-auth-error": "erreur d'authentification Git"
|
||||
"git-auth-error": "Erreur d'authentification Git"
|
||||
},
|
||||
"create-success": {
|
||||
"success": "Vous avez créé avec succès votre premier projet !",
|
||||
@@ -1135,8 +1138,8 @@
|
||||
"desc2": "Avant de pouvoir cloner un référentiel sur ssh, vous devez ajouter une clé SSH pour y accéder.",
|
||||
"add-ssh-key": "Ajouter une clé ssh",
|
||||
"credentials-encryption-key": "Clé de chiffrement des identifiants",
|
||||
"already-exists-2": "existe déjà",
|
||||
"git-error": "erreur git",
|
||||
"already-exists-2": "Existe déjà",
|
||||
"git-error": "Erreur git",
|
||||
"con-failed": "La connexion a échoué",
|
||||
"not-git": "Ce n'est pas un dépôt git",
|
||||
"no-resource": "Référentiel introuvable",
|
||||
@@ -1148,8 +1151,8 @@
|
||||
"confirm": "Voulez-vous vraiment supprimer ce projet ?"
|
||||
},
|
||||
"create-project-list": {
|
||||
"search": "rechercher vos projets",
|
||||
"current": "actuel"
|
||||
"search": "Rechercher vos projets",
|
||||
"current": "Actuel"
|
||||
},
|
||||
"require-clean": {
|
||||
"confirm": "<p>Vous avez des modifications non déployées qui seront perdues.</p><p>Voulez-vous continuer ?</p>"
|
||||
@@ -1212,11 +1215,8 @@
|
||||
"validator": {
|
||||
"errors": {
|
||||
"invalid-json": "Données JSON invalides : __error__",
|
||||
"invalid-json-prop": "__prop__: données JSON invalides : __error__",
|
||||
"invalid-prop": "Expression de propriété non valide",
|
||||
"invalid-prop-prop": "__prop__: expression de propriété invalide",
|
||||
"invalid-num": "Numéro invalide",
|
||||
"invalid-num-prop": "__prop__: numéro invalide",
|
||||
"invalid-regexp": "Modèle d'entrée non valide",
|
||||
"invalid-regex-prop": "__prop__: modèle d'entrée non valide",
|
||||
"missing-required-prop": "__prop__: valeur de la propriété manquante",
|
||||
|
||||
@@ -270,5 +270,9 @@
|
||||
"$moment": {
|
||||
"args": "[str]",
|
||||
"desc": "Obtient un objet de date à l'aide de la bibliothèque Moment."
|
||||
},
|
||||
"$clone": {
|
||||
"args": "valeur",
|
||||
"desc": "Cloner un objet en toute sécurité."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1215,11 +1215,8 @@
|
||||
"validator": {
|
||||
"errors": {
|
||||
"invalid-json": "JSONデータが不正: __error__",
|
||||
"invalid-json-prop": "__prop__: JSONデータが不正: __error__",
|
||||
"invalid-prop": "プロパティ式が不正",
|
||||
"invalid-prop-prop": "__prop__: プロパティ式が不正",
|
||||
"invalid-num": "数値が不正",
|
||||
"invalid-num-prop": "__prop__: 数値が不正",
|
||||
"invalid-regexp": "入力パターンが不正",
|
||||
"invalid-regex-prop": "__prop__: 入力パターンが不正",
|
||||
"missing-required-prop": "__prop__: プロパティが未設定",
|
||||
|
||||
@@ -1186,11 +1186,8 @@
|
||||
"validator": {
|
||||
"errors": {
|
||||
"invalid-json": "Dados JSON inválidos: __error__",
|
||||
"invalid-json-prop": "__prop__: dados JSON inválidos: __error__",
|
||||
"invalid-prop": "Expressão de propriedade inválida",
|
||||
"invalid-prop-prop": "__prop__: expressão de propriedade inválida",
|
||||
"invalid-num": "Número inválido",
|
||||
"invalid-num-prop": "__prop__: número inválido",
|
||||
"invalid-regexp": "Padrão de entrada inválido",
|
||||
"invalid-regex-prop": "__prop__: Padrão de entrada inválido",
|
||||
"missing-required-prop": "__prop__: valor de propriedade ausente",
|
||||
|
||||
@@ -1199,11 +1199,8 @@
|
||||
"validator": {
|
||||
"errors": {
|
||||
"invalid-json": "无效的 JSON 数据: __error__",
|
||||
"invalid-json-prop": "__prop__: 无效的 JSON 数据: __error__",
|
||||
"invalid-prop": "无效的属性表达式",
|
||||
"invalid-prop-prop": "__prop__: 无效的属性表达式",
|
||||
"invalid-num": "无效的数字",
|
||||
"invalid-num-prop": "__prop__: 无效的数字",
|
||||
"invalid-regexp": "输入格式无效",
|
||||
"invalid-regex-prop": "__prop__: 输入格式无效",
|
||||
"missing-required-prop": "__prop__: 缺少属性值",
|
||||
|
||||
@@ -797,8 +797,8 @@ RED.nodes = (function() {
|
||||
|
||||
if (node && node._def.onremove) {
|
||||
// Deprecated: never documented but used by some early nodes
|
||||
console.log("Deprecated API warning: node type ",node.type," has an onremove function - should be oneditremove - please report");
|
||||
node._def.onremove.call(n);
|
||||
console.log("Deprecated API warning: node type ",node.type," has an onremove function - should be oneditdelete - please report");
|
||||
node._def.onremove.call(node);
|
||||
}
|
||||
return {links:removedLinks,nodes:removedNodes};
|
||||
}
|
||||
@@ -2198,6 +2198,12 @@ RED.nodes = (function() {
|
||||
}
|
||||
node._config.x = node.x;
|
||||
node._config.y = node.y;
|
||||
if (n.hasOwnProperty('w')) {
|
||||
node.w = n.w
|
||||
}
|
||||
if (n.hasOwnProperty('h')) {
|
||||
node.h = n.h
|
||||
}
|
||||
} else if (n.type.substring(0,7) === "subflow") {
|
||||
var parentId = n.type.split(":")[1];
|
||||
var subflow = subflow_denylist[parentId]||subflow_map[parentId]||getSubflow(parentId);
|
||||
|
||||
@@ -498,6 +498,15 @@ var RED = (function() {
|
||||
]
|
||||
}
|
||||
}
|
||||
} else if (notificationId === 'restart-required') {
|
||||
options.buttons = [
|
||||
{
|
||||
text: RED._("common.label.close"),
|
||||
click: function() {
|
||||
persistentNotifications[notificationId].hideNotification();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
if (!persistentNotifications.hasOwnProperty(notificationId)) {
|
||||
persistentNotifications[notificationId] = RED.notify(text,options);
|
||||
@@ -525,6 +534,10 @@ var RED = (function() {
|
||||
RED.view.redrawStatus(node);
|
||||
}
|
||||
});
|
||||
|
||||
let pendingNodeRemovedNotifications = []
|
||||
let pendingNodeRemovedTimeout
|
||||
|
||||
RED.comms.subscribe("notification/node/#",function(topic,msg) {
|
||||
var i,m;
|
||||
var typeList;
|
||||
@@ -562,8 +575,15 @@ var RED = (function() {
|
||||
m = msg[i];
|
||||
info = RED.nodes.removeNodeSet(m.id);
|
||||
if (info.added) {
|
||||
typeList = "<ul><li>"+m.types.map(RED.utils.sanitize).join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
|
||||
pendingNodeRemovedNotifications = pendingNodeRemovedNotifications.concat(m.types.map(RED.utils.sanitize))
|
||||
if (pendingNodeRemovedTimeout) {
|
||||
clearTimeout(pendingNodeRemovedTimeout)
|
||||
}
|
||||
pendingNodeRemovedTimeout = setTimeout(function () {
|
||||
typeList = "<ul><li>"+pendingNodeRemovedNotifications.join("</li><li>")+"</li></ul>";
|
||||
RED.notify(RED._("palette.event.nodeRemoved", {count:pendingNodeRemovedNotifications.length})+typeList,"success");
|
||||
pendingNodeRemovedNotifications = []
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
loadIconList();
|
||||
|
||||
@@ -182,7 +182,9 @@
|
||||
valueLabel: contextLabel
|
||||
},
|
||||
str: {value:"str",label:"string",icon:"red/images/typedInput/az.svg"},
|
||||
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/},
|
||||
num: {value:"num",label:"number",icon:"red/images/typedInput/09.svg",validate: function(v) {
|
||||
return (true === RED.utils.validateTypedProperty(v, "num"));
|
||||
} },
|
||||
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.svg",options:["true","false"]},
|
||||
json: {
|
||||
value:"json",
|
||||
|
||||
@@ -168,8 +168,8 @@ RED.contextMenu = (function () {
|
||||
|
||||
menuItems.push(
|
||||
null,
|
||||
{ onselect: 'core:undo', disabled: RED.history.list().length === 0 },
|
||||
{ onselect: 'core:redo', disabled: RED.history.listRedo().length === 0 },
|
||||
{ onselect: 'core:undo', label: RED._("keyboard.undoChange"), disabled: RED.history.list().length === 0 },
|
||||
{ onselect: 'core:redo', label: RED._("keyboard.redoChange"), disabled: RED.history.listRedo().length === 0 },
|
||||
null,
|
||||
{ onselect: 'core:cut-selection-to-internal-clipboard', label: RED._("keyboard.cutNode"), disabled: !canEdit || !hasSelection },
|
||||
{ onselect: 'core:copy-selection-to-internal-clipboard', label: RED._("keyboard.copyNode"), disabled: !hasSelection },
|
||||
@@ -177,7 +177,7 @@ RED.contextMenu = (function () {
|
||||
{ onselect: 'core:delete-selection', disabled: !canEdit || !canDelete },
|
||||
{ onselect: 'core:delete-selection-and-reconnect', label: RED._('keyboard.deleteReconnect'), disabled: !canEdit || !canDelete },
|
||||
{ onselect: 'core:show-export-dialog', label: RED._("menu.label.export") },
|
||||
{ onselect: 'core:select-all-nodes' },
|
||||
{ onselect: 'core:select-all-nodes', label: RED._("keyboard.selectAll") },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -989,9 +989,10 @@ RED.diff = (function() {
|
||||
}
|
||||
if (localNode && remoteNode && typeof localNode[d] === "string") {
|
||||
if (/\n/.test(localNode[d]) || /\n/.test(remoteNode[d])) {
|
||||
$('<button class="red-ui-button red-ui-button-small red-ui-diff-text-diff-button"><i class="fa fa-file-o"> <i class="fa fa-caret-left"></i> <i class="fa fa-caret-right"></i> <i class="fa fa-file-o"></i></button>').on("click", function() {
|
||||
var textDiff = $('<button class="red-ui-button red-ui-button-small red-ui-diff-text-diff-button"><i class="fa fa-file-o"> <i class="fa fa-caret-left"></i> <i class="fa fa-caret-right"></i> <i class="fa fa-file-o"></i></button>').on("click", function() {
|
||||
showTextDiff(localNode[d],remoteNode[d]);
|
||||
}).appendTo(propertyNameCell);
|
||||
RED.popover.tooltip(textDiff, RED._("diff.compareChanges"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,8 +115,9 @@ RED.editor = (function() {
|
||||
var valid = validateNodeProperty(node, definition, prop, properties[prop]);
|
||||
if ((typeof valid) === "string") {
|
||||
result.push(valid);
|
||||
}
|
||||
else if(!valid) {
|
||||
} else if (Array.isArray(valid)) {
|
||||
result = result.concat(valid)
|
||||
} else if(!valid) {
|
||||
result.push(prop);
|
||||
}
|
||||
}
|
||||
@@ -165,7 +166,7 @@ RED.editor = (function() {
|
||||
// If the validator takes two arguments, it is a 3.x validator that
|
||||
// can return a String to mean 'invalid' and provide a reason
|
||||
if ((definition[property].validate.length === 2) &&
|
||||
((typeof valid) === "string")) {
|
||||
((typeof valid) === "string") || Array.isArray(valid)) {
|
||||
return valid;
|
||||
} else {
|
||||
// Otherwise, a 2.x returns a truth-like/false-like value that
|
||||
@@ -181,6 +182,17 @@ RED.editor = (function() {
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
} else if (valid) {
|
||||
// If the validator is not provided in node property => Check if the input has a validator
|
||||
if ("category" in node._def) {
|
||||
const isConfig = node._def.category === "config";
|
||||
const prefix = isConfig ? "node-config-input" : "node-input";
|
||||
const input = $("#"+prefix+"-"+property);
|
||||
const isTypedInput = input.length > 0 && input.next(".red-ui-typedInput-container").length > 0;
|
||||
if (isTypedInput) {
|
||||
valid = input.typedInput("validate");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (valid && definition[property].type && RED.nodes.getType(definition[property].type) && !("validate" in definition[property])) {
|
||||
if (!value || value == "_ADD_") {
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
var i=0,l=bufferBinValue.length;
|
||||
var c = 0;
|
||||
for(i=0;i<l;i++) {
|
||||
var d = parseInt(bufferBinValue[i]);
|
||||
var d = parseInt(Number(bufferBinValue[i]));
|
||||
if (!isString && (isNaN(d) || d < 0 || d > 255)) {
|
||||
valid = false;
|
||||
break;
|
||||
|
||||
@@ -966,12 +966,10 @@ RED.editor.codeEditor.monaco = (function() {
|
||||
|
||||
//Unbind ctrl-Enter (default action is to insert a newline in editor) This permits the shortcut to close the tray.
|
||||
try {
|
||||
ed._standaloneKeybindingService.addDynamicKeybinding(
|
||||
'-editor.action.insertLineAfter', // command ID prefixed by '-'
|
||||
null, // keybinding
|
||||
() => {} // need to pass an empty handler
|
||||
);
|
||||
} catch (error) { }
|
||||
monaco.editor.addKeybindingRule({keybinding: 0, command: "-editor.action.insertLineAfter"});
|
||||
} catch (error) {
|
||||
console.warn(error)
|
||||
}
|
||||
|
||||
ed.nodered = {
|
||||
refreshModuleLibs: refreshModuleLibs //expose this for function node externalModules refresh
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
var currentScrollTop = $(".red-ui-editor-type-markdown-panel-preview").scrollTop();
|
||||
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
|
||||
$(".red-ui-editor-type-markdown-panel-preview").scrollTop(currentScrollTop);
|
||||
mermaid.init();
|
||||
RED.editor.mermaid.render()
|
||||
},200);
|
||||
})
|
||||
if (options.header) {
|
||||
@@ -178,7 +178,7 @@
|
||||
|
||||
if (value) {
|
||||
$(".red-ui-editor-type-markdown-panel-preview").html(RED.utils.renderMarkdown(expressionEditor.getValue()));
|
||||
mermaid.init();
|
||||
RED.editor.mermaid.render()
|
||||
}
|
||||
panels = RED.panels.create({
|
||||
id:"red-ui-editor-type-markdown-panels",
|
||||
|
||||
54
packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js
vendored
Normal file
54
packages/node_modules/@node-red/editor-client/src/js/ui/editors/mermaid.js
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
RED.editor.mermaid = (function () {
|
||||
let initializing = false
|
||||
let loaded = false
|
||||
let pendingEvals = []
|
||||
let diagramIds = 0
|
||||
|
||||
function render(selector = '.mermaid') {
|
||||
// $(selector).hide()
|
||||
if (!loaded) {
|
||||
pendingEvals.push(selector)
|
||||
|
||||
if (!initializing) {
|
||||
initializing = true
|
||||
$.getScript(
|
||||
'vendor/mermaid/mermaid.min.js',
|
||||
function (data, stat, jqxhr) {
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: RED.settings.get('mermaid', {}).theme
|
||||
})
|
||||
loaded = true
|
||||
while(pendingEvals.length > 0) {
|
||||
const pending = pendingEvals.shift()
|
||||
render(pending)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
const nodes = document.querySelectorAll(selector)
|
||||
|
||||
nodes.forEach(async node => {
|
||||
if (!node.getAttribute('mermaid-processed')) {
|
||||
const mermaidContent = node.innerText
|
||||
node.setAttribute('mermaid-processed', true)
|
||||
try {
|
||||
const { svg } = await mermaid.render('mermaid-render-'+Date.now()+'-'+(diagramIds++), mermaidContent);
|
||||
node.innerHTML = svg
|
||||
} catch (err) {
|
||||
$('<div>').css({
|
||||
fontSize: '0.8em',
|
||||
border: '1px solid var(--red-ui-border-color-error)',
|
||||
padding: '5px',
|
||||
marginBottom: '10px',
|
||||
}).text(err.toString()).prependTo(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return {
|
||||
render: render,
|
||||
};
|
||||
})();
|
||||
@@ -196,7 +196,7 @@
|
||||
}
|
||||
|
||||
$('<div class="form-row">'+
|
||||
'<label for="node-input-show-label-btn" data-i18n="editor.label"></label>'+
|
||||
'<label for="node-input-show-label" data-i18n="editor.label"></label>'+
|
||||
'<span style="margin-right: 2px;"/>'+
|
||||
'<input type="checkbox" id="node-input-show-label"/>'+
|
||||
'</div>').appendTo(dialogForm);
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
// Mermaid diagram stub library for on-demand dynamic loading
|
||||
// Will be overwritten after script loading by $.getScript
|
||||
var mermaid = (function () {
|
||||
var enabled /* = undefined */;
|
||||
|
||||
var initializing = false;
|
||||
var initCalled = false;
|
||||
|
||||
function initialize(opt) {
|
||||
if (enabled === undefined) {
|
||||
if (RED.settings.markdownEditor &&
|
||||
RED.settings.markdownEditor.mermaid) {
|
||||
enabled = RED.settings.markdownEditor.mermaid.enabled;
|
||||
}
|
||||
else {
|
||||
enabled = true;
|
||||
}
|
||||
}
|
||||
if (enabled) {
|
||||
initializing = true;
|
||||
$.getScript("vendor/mermaid/mermaid.min.js",
|
||||
function (data, stat, jqxhr) {
|
||||
$(".mermaid").show();
|
||||
// invoke loaded mermaid API
|
||||
initializing = false;
|
||||
mermaid.initialize(opt);
|
||||
if (initCalled) {
|
||||
mermaid.init();
|
||||
initCalled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (initializing) {
|
||||
$(".mermaid").hide();
|
||||
initCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
initialize: initialize,
|
||||
init: init,
|
||||
};
|
||||
})();
|
||||
@@ -166,7 +166,7 @@ RED.projects.settings = (function() {
|
||||
var description = addTargetToExternalLinks($('<span class="red-ui-text-bidi-aware" dir=\"'+RED.text.bidi.resolveBaseTextDir(desc)+'">'+desc+'</span>')).appendTo(container);
|
||||
description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
|
||||
setTimeout(function () {
|
||||
mermaid.init();
|
||||
RED.editor.mermaid.render()
|
||||
}, 200);
|
||||
}
|
||||
|
||||
|
||||
@@ -647,9 +647,9 @@ RED.sidebar.versionControl = (function() {
|
||||
$.getJSON("projects/"+activeProject.name+"/commits/"+entry.sha,function(result) {
|
||||
result.project = activeProject;
|
||||
result.parents = entry.parents;
|
||||
result.oldRev = entry.sha+"~1";
|
||||
result.oldRev = entry.parents[0].length !== 0 ? entry.sha+"~1" : entry.sha;
|
||||
result.newRev = entry.sha;
|
||||
result.oldRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7)+"~1";
|
||||
result.oldRevTitle = entry.parents[0].length !== 0 ? RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7)+"~1" : " ";
|
||||
result.newRevTitle = RED._("sidebar.project.versionControl.commitCapital")+" "+entry.sha.substring(0,7);
|
||||
result.date = humanizeSinceDate(parseInt(entry.date));
|
||||
RED.diff.showCommitDiff(result);
|
||||
|
||||
@@ -383,6 +383,7 @@ RED.sidebar.help = (function() {
|
||||
$(this).toggleClass('expanded',!isExpanded);
|
||||
})
|
||||
helpSection.parent().scrollTop(0);
|
||||
RED.editor.mermaid.render()
|
||||
}
|
||||
|
||||
function set(html,title) {
|
||||
|
||||
@@ -464,7 +464,7 @@ RED.sidebar.info = (function() {
|
||||
}
|
||||
$(this).toggleClass('expanded',!isExpanded);
|
||||
});
|
||||
mermaid.init();
|
||||
RED.editor.mermaid.render()
|
||||
}
|
||||
|
||||
var tips = (function() {
|
||||
|
||||
@@ -323,7 +323,7 @@ RED.typeSearch = (function() {
|
||||
}
|
||||
}
|
||||
function applyFilter(filter,type,def) {
|
||||
return !filter ||
|
||||
return !def || !filter ||
|
||||
(
|
||||
(!filter.spliceMultiple) &&
|
||||
(!filter.type || type === filter.type) &&
|
||||
|
||||
@@ -101,28 +101,8 @@ RED.utils = (function() {
|
||||
|
||||
renderer.code = function (code, lang) {
|
||||
if(lang === "mermaid") {
|
||||
// mermaid diagram rendering
|
||||
if (mermaidIsEnabled === undefined) {
|
||||
if (RED.settings.markdownEditor &&
|
||||
RED.settings.markdownEditor.mermaid) {
|
||||
mermaidIsEnabled = RED.settings.markdownEditor.mermaid.enabled;
|
||||
}
|
||||
else {
|
||||
mermaidIsEnabled = true;
|
||||
}
|
||||
}
|
||||
if (mermaidIsEnabled) {
|
||||
if (!mermaidIsInitialized) {
|
||||
mermaidIsInitialized = true;
|
||||
mermaid.initialize({startOnLoad:false});
|
||||
}
|
||||
return `<pre class='mermaid'>${code}</pre>`;
|
||||
}
|
||||
else {
|
||||
return `<details><summary>${RED._("markdownEditor.mermaid.summary")}</summary><pre><code>${code}</code></pre></details>`;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return `<pre class='mermaid'>${code}</pre>`;
|
||||
} else {
|
||||
return "<pre><code>" +code +"</code></pre>";
|
||||
}
|
||||
};
|
||||
@@ -917,6 +897,51 @@ RED.utils = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a typed property is valid according to the type.
|
||||
* Returns true if valid.
|
||||
* Return String error message if invalid
|
||||
* @param {*} propertyType
|
||||
* @param {*} propertyValue
|
||||
* @returns true if valid, String if invalid
|
||||
*/
|
||||
function validateTypedProperty(propertyValue, propertyType, opt) {
|
||||
|
||||
let error
|
||||
if (propertyType === 'json') {
|
||||
try {
|
||||
JSON.parse(propertyValue);
|
||||
} catch(err) {
|
||||
error = RED._("validator.errors.invalid-json", {
|
||||
error: err.message
|
||||
})
|
||||
}
|
||||
} else if (propertyType === 'msg' || propertyType === 'flow' || propertyType === 'global' ) {
|
||||
if (!RED.utils.validatePropertyExpression(propertyValue)) {
|
||||
error = RED._("validator.errors.invalid-prop")
|
||||
}
|
||||
} else if (propertyType === 'num') {
|
||||
if (!/^NaN$|^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$|^[+-]?(0b|0B)[01]+$|^[+-]?(0o|0O)[0-7]+$|^[+-]?(0x|0X)[0-9a-fA-F]+$/.test(propertyValue)) {
|
||||
error = RED._("validator.errors.invalid-num")
|
||||
}
|
||||
} else if (propertyType === 'jsonata') {
|
||||
try {
|
||||
jsonata(propertyValue)
|
||||
} catch(err) {
|
||||
error = RED._("validator.errors.invalid-expr", {
|
||||
error: err.message
|
||||
})
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
if (opt && opt.label) {
|
||||
return opt.label+': '+error
|
||||
}
|
||||
return error
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function getMessageProperty(msg,expr) {
|
||||
var result = null;
|
||||
var msgPropParts;
|
||||
@@ -1451,6 +1476,7 @@ RED.utils = (function() {
|
||||
getDarkerColor: getDarkerColor,
|
||||
parseModuleList: parseModuleList,
|
||||
checkModuleAllowed: checkModuleAllowed,
|
||||
getBrowserInfo: getBrowserInfo
|
||||
getBrowserInfo: getBrowserInfo,
|
||||
validateTypedProperty: validateTypedProperty
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -4187,7 +4187,7 @@ RED.view = (function() {
|
||||
nodeEl.__statusGroup__.style.display = "none";
|
||||
} else {
|
||||
nodeEl.__statusGroup__.style.display = "inline";
|
||||
let backgroundWidth = 12
|
||||
let backgroundWidth = 15
|
||||
var fill = status_colours[d.status.fill]; // Only allow our colours for now
|
||||
if (d.status.shape == null && fill == null) {
|
||||
backgroundWidth = 0
|
||||
@@ -4207,7 +4207,11 @@ RED.view = (function() {
|
||||
nodeEl.__statusLabel__.textContent = "";
|
||||
}
|
||||
const textSize = nodeEl.__statusLabel__.getBBox()
|
||||
nodeEl.__statusBackground__.setAttribute('width', backgroundWidth + textSize.width + 6)
|
||||
backgroundWidth += textSize.width
|
||||
if (backgroundWidth > 0 && textSize.width > 0) {
|
||||
backgroundWidth += 6
|
||||
}
|
||||
nodeEl.__statusBackground__.setAttribute('width', backgroundWidth)
|
||||
}
|
||||
delete d.dirtyStatus;
|
||||
}
|
||||
@@ -4619,8 +4623,8 @@ RED.view = (function() {
|
||||
statusBackground.setAttribute("y",-1);
|
||||
statusBackground.setAttribute("width",200);
|
||||
statusBackground.setAttribute("height",13);
|
||||
statusBackground.setAttribute("rx",1);
|
||||
statusBackground.setAttribute("ry",1);
|
||||
statusBackground.setAttribute("rx",2);
|
||||
statusBackground.setAttribute("ry",2);
|
||||
|
||||
statusEl.appendChild(statusBackground);
|
||||
node[0][0].__statusBackground__ = statusBackground;
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
RED.workspaces = (function() {
|
||||
|
||||
const documentTitle = document.title;
|
||||
|
||||
var activeWorkspace = 0;
|
||||
var workspaceIndex = 0;
|
||||
|
||||
@@ -339,12 +341,18 @@ RED.workspaces = (function() {
|
||||
$("#red-ui-workspace-chart").show();
|
||||
activeWorkspace = tab.id;
|
||||
window.location.hash = 'flow/'+tab.id;
|
||||
if (tab.label) {
|
||||
document.title = `${documentTitle} : ${tab.label}`
|
||||
} else {
|
||||
document.title = documentTitle
|
||||
}
|
||||
$("#red-ui-workspace").toggleClass("red-ui-workspace-disabled", !!tab.disabled);
|
||||
$("#red-ui-workspace").toggleClass("red-ui-workspace-locked", !!tab.locked);
|
||||
} else {
|
||||
$("#red-ui-workspace-chart").hide();
|
||||
activeWorkspace = 0;
|
||||
window.location.hash = '';
|
||||
document.title = documentTitle
|
||||
}
|
||||
event.workspace = activeWorkspace;
|
||||
RED.events.emit("workspace:change",event);
|
||||
|
||||
@@ -40,46 +40,29 @@ RED.validators = {
|
||||
return opt ? RED._("validator.errors.invalid-regexp") : false;
|
||||
};
|
||||
},
|
||||
typedInput: function(ptypeName,isConfig,mopt) {
|
||||
typedInput: function(ptypeName, isConfig, mopt) {
|
||||
let options = ptypeName
|
||||
if (typeof ptypeName === 'string' ) {
|
||||
options = {}
|
||||
options.typeField = ptypeName
|
||||
options.isConfig = isConfig
|
||||
options.allowBlank = false
|
||||
}
|
||||
return function(v, opt) {
|
||||
var ptype = $("#node-"+(isConfig?"config-":"")+"input-"+ptypeName).val() || this[ptypeName];
|
||||
if (ptype === 'json') {
|
||||
try {
|
||||
JSON.parse(v);
|
||||
return true;
|
||||
} catch(err) {
|
||||
if (opt && opt.label) {
|
||||
return RED._("validator.errors.invalid-json-prop", {
|
||||
error: err.message,
|
||||
prop: opt.label,
|
||||
});
|
||||
}
|
||||
return opt ? RED._("validator.errors.invalid-json", {
|
||||
error: err.message
|
||||
}) : false;
|
||||
}
|
||||
} else if (ptype === 'msg' || ptype === 'flow' || ptype === 'global' ) {
|
||||
if (RED.utils.validatePropertyExpression(v)) {
|
||||
return true;
|
||||
}
|
||||
if (opt && opt.label) {
|
||||
return RED._("validator.errors.invalid-prop-prop", {
|
||||
prop: opt.label
|
||||
});
|
||||
}
|
||||
return opt ? RED._("validator.errors.invalid-prop") : false;
|
||||
} else if (ptype === 'num') {
|
||||
if (/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/.test(v)) {
|
||||
return true;
|
||||
}
|
||||
if (opt && opt.label) {
|
||||
return RED._("validator.errors.invalid-num-prop", {
|
||||
prop: opt.label
|
||||
});
|
||||
}
|
||||
return opt ? RED._("validator.errors.invalid-num") : false;
|
||||
let ptype = options.type
|
||||
if (!ptype && options.typeField) {
|
||||
ptype = $("#node-"+(options.isConfig?"config-":"")+"input-"+options.typeField).val() || this[options.typeField];
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (options.allowBlank && v === '') {
|
||||
return true
|
||||
}
|
||||
const result = RED.utils.validateTypedProperty(v, ptype, opt)
|
||||
if (result === true || opt) {
|
||||
// Valid, or opt provided - return result as-is
|
||||
return result
|
||||
}
|
||||
// No opt - need to return false for backwards compatibilty
|
||||
return false
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -114,6 +114,7 @@
|
||||
pointer-events: stroke;
|
||||
}
|
||||
.red-ui-flow-group-outline-select {
|
||||
cursor: move;
|
||||
fill: none;
|
||||
stroke: var(--red-ui-node-selected-color);
|
||||
pointer-events: none;
|
||||
|
||||
@@ -825,6 +825,7 @@ div.red-ui-projects-dialog-ssh-public-key {
|
||||
margin-top: 0 !important;
|
||||
padding: 5px 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 3px 3px 0px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user