diff --git a/src/public/app/services/attribute_parser.js b/src/public/app/services/attribute_parser.js index f214fcfc8..d2fd1944e 100644 --- a/src/public/app/services/attribute_parser.js +++ b/src/public/app/services/attribute_parser.js @@ -1,3 +1,5 @@ +import utils from "./utils.js"; + function lex(str) { str = str.trim(); @@ -105,14 +107,12 @@ function lex(str) { return tokens; } -const attrNameMatcher = new RegExp("^[\\p{L}\\p{N}_:]+$", "u"); - function checkAttributeName(attrName) { if (attrName.length === 0) { throw new Error("Attribute name is empty, please fill the name."); } - if (!attrNameMatcher.test(attrName)) { + if (!utils.isValidAttributeName(attrName)) { throw new Error(`Attribute name "${attrName}" contains disallowed characters, only alphanumeric characters, colon and underscore are allowed.`); } } diff --git a/src/public/app/services/utils.js b/src/public/app/services/utils.js index 6f9c97b3e..07ad8c89b 100644 --- a/src/public/app/services/utils.js +++ b/src/public/app/services/utils.js @@ -334,6 +334,16 @@ function initHelpButtons($el) { }); } +function filterAttributeName(name) { + return name.replace(/[^\p{L}\p{N}_:]/ug, ""); +} + +const ATTR_NAME_MATCHER = new RegExp("^[\\p{L}\\p{N}_:]+$", "u"); + +function isValidAttributeName(name) { + return ATTR_NAME_MATCHER.test(name); +} + export default { reloadApp, parseDate, @@ -374,5 +384,7 @@ export default { dynamicRequire, timeLimit, initHelpDropdown, - initHelpButtons + initHelpButtons, + filterAttributeName, + isValidAttributeName }; diff --git a/src/public/app/widgets/attribute_widgets/attribute_detail.js b/src/public/app/widgets/attribute_widgets/attribute_detail.js index 0aad9c8fc..39f3d2414 100644 --- a/src/public/app/widgets/attribute_widgets/attribute_detail.js +++ b/src/public/app/widgets/attribute_widgets/attribute_detail.js @@ -172,8 +172,6 @@ const ATTR_TITLES = { "relation-definition": "Relation definition detail" }; -const ATTR_NAME_MATCHER = new RegExp("^[\\p{L}\\p{N}_:]+$", "u"); - const ATTR_HELP = { "label": { "disableVersioning": "disables auto-versioning. Useful for e.g. large, but unimportant notes - e.g. large JS libraries used for scripting", @@ -573,9 +571,9 @@ export default class AttributeDetailWidget extends TabAwareWidget { updateAttributeInEditor() { let attrName = this.$inputName.val(); - if (!ATTR_NAME_MATCHER.test(attrName)) { + if (!utils.isValidAttributeName(attrName)) { // invalid characters are simply ignored (from user perspective they are not even entered) - attrName = attrName.replace(/[^\p{L}\p{N}_:]/ug, ""); + attrName = utils.filterAttributeName(attrName); this.$inputName.val(attrName); } diff --git a/src/public/app/widgets/type_widgets/relation_map.js b/src/public/app/widgets/type_widgets/relation_map.js index b4ab69372..76bfa7c0c 100644 --- a/src/public/app/widgets/type_widgets/relation_map.js +++ b/src/public/app/widgets/type_widgets/relation_map.js @@ -471,14 +471,22 @@ export default class RelationMapTypeWidget extends TypeWidget { } const promptDialog = await import("../../dialogs/prompt.js"); - const name = await promptDialog.ask({ - message: "Specify new relation name:", - shown: ({ $answer }) => + let name = await promptDialog.ask({ + message: "Specify new relation name (allowed characters: alphanumeric, colon and underscore):", + shown: ({ $answer }) => { + $answer.on('keyup', () => { + // invalid characters are simply ignored (from user perspective they are not even entered) + const attrName = utils.filterAttributeName($answer.val()); + + $answer.val(attrName); + }); + attributeAutocompleteService.initAttributeNameAutocomplete({ $el: $answer, attributeType: "relation", open: true - }) + }); + } }); if (!name || !name.trim()) { @@ -487,6 +495,8 @@ export default class RelationMapTypeWidget extends TypeWidget { return; } + name = utils.filterAttributeName(name); + const targetNoteId = this.idToNoteId(connection.target.id); const sourceNoteId = this.idToNoteId(connection.source.id);