diff --git a/spec-es6/attribute_parser.spec.js b/spec-es6/attribute_parser.spec.js index 16c3b496b..62c95c3b0 100644 --- a/spec-es6/attribute_parser.spec.js +++ b/spec-es6/attribute_parser.spec.js @@ -1,13 +1,6 @@ import attributeParser from '../src/public/app/services/attribute_parser.js'; import {describe, it, expect, execute} from './mini_test.js'; -describe("Preprocessor", () => { - it("relation with value", () => { - expect(attributeParser.preprocess('
~relation = note
')) - .toEqual("~relation = #root/RclIpMauTOKS/NFi2gL4xtPxM "); - }); -}); - describe("Lexer", () => { it("simple label", () => { expect(attributeParser.lexer("#label").map(t => t.text)) @@ -95,11 +88,16 @@ describe("Parser", () => { expect(attrs[0].name).toEqual("token"); expect(attrs[0].value).toEqual('NFi2gL4xtPxM'); }); +}); - // it("error cases", () => { - // expect(() => attributeParser.parser(["~token"].map(t => ({text: t})), "~token")) - // .toThrow('Relation "~token" should point to a note.'); - // }); +describe("error cases", () => { + it("error cases", () => { + expect(() => attributeParser.lexAndParse('~token')) + .toThrow('Relation "~token" in "~token" should point to a note.'); + + expect(() => attributeParser.lexAndParse("#a&b/s")) + .toThrow(`Attribute name "a&b/s" contains disallowed characters, only alphanumeric characters, colon and underscore are allowed.`); + }); }); execute(); diff --git a/src/public/app/layouts/desktop_main_window_layout.js b/src/public/app/layouts/desktop_main_window_layout.js index 9f39417f5..c2055de91 100644 --- a/src/public/app/layouts/desktop_main_window_layout.js +++ b/src/public/app/layouts/desktop_main_window_layout.js @@ -131,7 +131,7 @@ export default class DesktopMainWindowLayout { .child(new FlexContainer('column').id('center-pane') .child(new FlexContainer('row').class('title-row') .cssBlock('.title-row > * { margin: 5px; }') - .css('height', '55px') + .overflowing() .child(new NoteTitleWidget()) .child(new RunScriptButtonsWidget().hideInZenMode()) .child(new NoteTypeWidget().hideInZenMode()) diff --git a/src/public/app/services/attribute_parser.js b/src/public/app/services/attribute_parser.js index 0513e11d6..02eb8a63b 100644 --- a/src/public/app/services/attribute_parser.js +++ b/src/public/app/services/attribute_parser.js @@ -1,17 +1,3 @@ -function preprocess(str) { - if (str.startsWith('')) { - str = str.substr(3); - } - - if (str.endsWith('
')) { - str = str.substr(0, str.length - 4); - } - - str = str.replace(/ /g, " "); - - return str.replace(/]+href="(#[A-Za-z0-9/]*)"[^>]*>[^<]*<\/a>/g, "$1"); -} - function lexer(str) { const tokens = []; @@ -117,6 +103,14 @@ function lexer(str) { return tokens; } +const attrNameMatcher = new RegExp("^[\\p{L}\\p{N}_:]+$", "u"); + +function checkAttributeName(attrName) { + if (!attrNameMatcher.test(attrName)) { + throw new Error(`Attribute name "${attrName}" contains disallowed characters, only alphanumeric characters, colon and underscore are allowed.`); + } +} + function parser(tokens, str, allowEmptyRelations = false) { const attrs = []; @@ -149,9 +143,13 @@ function parser(tokens, str, allowEmptyRelations = false) { } if (text.startsWith('#')) { + const labelName = text.substr(1); + + checkAttributeName(labelName); + const attr = { type: 'label', - name: text.substr(1), + name: labelName, isInheritable: isInheritable(), startIndex: startIndex, endIndex: tokens[i].endIndex // i could be moved by isInheritable @@ -171,9 +169,13 @@ function parser(tokens, str, allowEmptyRelations = false) { attrs.push(attr); } else if (text.startsWith('~')) { + const relationName = text.substr(1); + + checkAttributeName(relationName); + const attr = { type: 'relation', - name: text.substr(1), + name: relationName, isInheritable: isInheritable(), startIndex: startIndex, endIndex: tokens[i].endIndex // i could be moved by isInheritable @@ -211,15 +213,12 @@ function parser(tokens, str, allowEmptyRelations = false) { } function lexAndParse(str, allowEmptyRelations = false) { - str = preprocess(str); - const tokens = lexer(str); return parser(tokens, str, allowEmptyRelations); } export default { - preprocess, lexer, parser, lexAndParse diff --git a/src/public/app/widgets/attribute_detail.js b/src/public/app/widgets/attribute_detail.js index 31efef0a8..c494e325e 100644 --- a/src/public/app/widgets/attribute_detail.js +++ b/src/public/app/widgets/attribute_detail.js @@ -158,6 +158,8 @@ const ATTR_TITLES = { "relation-definition": "Relation definition detail" }; +const ATTR_NAME_MATCHER = new RegExp("^[\\p{L}\\p{N}_:]+$", "u"); + export default class AttributeDetailWidget extends TabAwareWidget { async refresh() { // this widget is not activated in a standard way @@ -280,7 +282,7 @@ export default class AttributeDetailWidget extends TabAwareWidget { return; } -console.log("RENDERING"); + this.attrType = this.getAttrType(attribute); const attrName = @@ -365,16 +367,16 @@ console.log("RENDERING"); this.toggleInt(true); - this.$widget.css("left", x - this.$widget.outerWidth() / 2); - this.$widget.css("top", y + 25); + const offset = this.parent.$widget.offset(); + + this.$widget.css("left", x - offset.left - this.$widget.outerWidth() / 2); + this.$widget.css("top", y - offset.top + 70); // so that the detail window always fits this.$widget.css("max-height", this.$widget.outerHeight() + y > $(window).height() - 50 ? $(window).height() - y - 50 : 10000); - - console.log("RENDERING DONE"); } async updateRelatedNotes() { @@ -435,6 +437,13 @@ console.log("RENDERING"); updateAttributeInEditor() { let attrName = this.$inputName.val(); + if (!ATTR_NAME_MATCHER.test(attrName)) { + // invalid characters are simply ignored (from user perspective they are not even entered) + attrName = attrName.replace(/[^\p{L}\p{N}_:]/ug, ""); + + this.$inputName.val(attrName); + } + if (this.attrType === 'label-definition') { attrName = 'label:' + attrName; } else if (this.attrType === 'relation-definition') { diff --git a/src/public/app/widgets/attribute_editor.js b/src/public/app/widgets/attribute_editor.js index 5f076dbea..5fd72b614 100644 --- a/src/public/app/widgets/attribute_editor.js +++ b/src/public/app/widgets/attribute_editor.js @@ -293,15 +293,22 @@ export default class AttributeEditorWidget extends TabAwareWidget { parseAttributes() { try { - const attrs = attributesParser.lexAndParse(this.textEditor.getData()); + const attrs = attributesParser.lexAndParse(this.getPreprocessedData()); return attrs; } catch (e) { - this.$errors.show().text(e.message); + this.$errors.text(e.message).slideDown(); } } + getPreprocessedData() { + const str = this.textEditor.getData() + .replace(/]+href="(#[A-Za-z0-9/]*)"[^>]*>[^<]*<\/a>/g, "$1"); + + return $("