frontend validation of attribute name + other changes and fixes

This commit is contained in:
zadam 2020-08-17 23:54:18 +02:00
parent f24e27dadd
commit 3670fbff49
7 changed files with 71 additions and 50 deletions

View file

@ -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('<p>~relation&nbsp;= <a class="reference-link" href="#root/RclIpMauTOKS/NFi2gL4xtPxM" some-attr="abc" data-note-path="root/RclIpMauTOKS/NFi2gL4xtPxM">note</a>&nbsp;</p>'))
.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();

View file

@ -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())

View file

@ -1,17 +1,3 @@
function preprocess(str) {
if (str.startsWith('<p>')) {
str = str.substr(3);
}
if (str.endsWith('</p>')) {
str = str.substr(0, str.length - 4);
}
str = str.replace(/&nbsp;/g, " ");
return str.replace(/<a[^>]+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

View file

@ -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') {

View file

@ -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(/<a[^>]+href="(#[A-Za-z0-9/]*)"[^>]*>[^<]*<\/a>/g, "$1");
return $("<div>").html(str).text();
}
async initEditor() {
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
@ -332,18 +339,18 @@ export default class AttributeEditorWidget extends TabAwareWidget {
}
}
async handleEditorClick(e) {
async handleEditorClick(e) {console.log("click")
const pos = this.textEditor.model.document.selection.getFirstPosition();
if (pos && pos.textNode && pos.textNode.data) {
if (pos && pos.textNode && pos.textNode.data) {console.log(pos);
const clickIndex = this.getClickIndex(pos);
let parsedAttrs;
try {
parsedAttrs = attributesParser.lexAndParse(this.textEditor.getData(), true);
parsedAttrs = attributesParser.lexAndParse(this.getPreprocessedData(), true);
}
catch (e) {
catch (e) {console.log(e);
// the input is incorrect because user messed up with it and now needs to fix it manually
return null;
}
@ -357,13 +364,15 @@ export default class AttributeEditorWidget extends TabAwareWidget {
}
}
this.attributeDetailWidget.showAttributeDetail({
allAttributes: parsedAttrs,
attribute: matchedAttr,
isOwned: true,
x: e.pageX,
y: e.pageY
});
setTimeout(() => {
this.attributeDetailWidget.showAttributeDetail({
allAttributes: parsedAttrs,
attribute: matchedAttr,
isOwned: true,
x: e.pageX,
y: e.pageY
});
}, 100);
}
}

View file

@ -636,7 +636,7 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
}
.component {
contain: strict;
contain: layout size;
}
.toast {

View file

@ -4,6 +4,7 @@ const noteCache = require('./note_cache');
const hoistedNoteService = require('../hoisted_note');
const protectedSessionService = require('../protected_session');
const stringSimilarity = require('string-similarity');
const log = require('../log');
function isNotePathArchived(notePath) {
const noteId = notePath[notePath.length - 1];
@ -62,6 +63,11 @@ function getNoteTitle(childNoteId, parentNoteId) {
const childNote = noteCache.notes[childNoteId];
const parentNote = noteCache.notes[parentNoteId];
if (!childNote) {
log.info(`Cannot find note in cache for noteId ${childNoteId}`);
return "[error fetching title]";
}
let title;
if (childNote.isProtected) {