mirror of
https://github.com/zadam/trilium.git
synced 2024-09-20 23:55:59 +08:00
frontend validation of attribute name + other changes and fixes
This commit is contained in:
parent
f24e27dadd
commit
3670fbff49
|
@ -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 = <a class="reference-link" href="#root/RclIpMauTOKS/NFi2gL4xtPxM" some-attr="abc" data-note-path="root/RclIpMauTOKS/NFi2gL4xtPxM">note</a> </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();
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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(/ /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
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue