From ed6181a85e639625656bfa086c23f44f8840024a Mon Sep 17 00:00:00 2001
From: zadam
Date: Fri, 21 Aug 2020 23:08:53 +0200
Subject: [PATCH] added in-editor help for editing attributes
---
spec-es6/attribute_parser.spec.js | 38 +++++++-----
src/public/app/services/attribute_parser.js | 18 ++++--
src/public/app/services/attribute_renderer.js | 4 +-
src/public/app/services/glob.js | 12 ++--
src/public/app/widgets/attribute_editor.js | 60 +++++++++++++++----
5 files changed, 90 insertions(+), 42 deletions(-)
diff --git a/spec-es6/attribute_parser.spec.js b/spec-es6/attribute_parser.spec.js
index 62c95c3b0..d1ef19096 100644
--- a/spec-es6/attribute_parser.spec.js
+++ b/spec-es6/attribute_parser.spec.js
@@ -1,51 +1,56 @@
import attributeParser from '../src/public/app/services/attribute_parser.js';
import {describe, it, expect, execute} from './mini_test.js';
-describe("Lexer", () => {
+describe("Lexing", () => {
it("simple label", () => {
- expect(attributeParser.lexer("#label").map(t => t.text))
+ expect(attributeParser.lex("#label").map(t => t.text))
+ .toEqual(["#label"]);
+ });
+
+ it("simple label with trailing spaces", () => {
+ expect(attributeParser.lex(" #label ").map(t => t.text))
.toEqual(["#label"]);
});
it("inherited label", () => {
- expect(attributeParser.lexer("#label(inheritable)").map(t => t.text))
+ expect(attributeParser.lex("#label(inheritable)").map(t => t.text))
.toEqual(["#label", "(", "inheritable", ")"]);
- expect(attributeParser.lexer("#label ( inheritable ) ").map(t => t.text))
+ expect(attributeParser.lex("#label ( inheritable ) ").map(t => t.text))
.toEqual(["#label", "(", "inheritable", ")"]);
});
it("label with value", () => {
- expect(attributeParser.lexer("#label=Hallo").map(t => t.text))
+ expect(attributeParser.lex("#label=Hallo").map(t => t.text))
.toEqual(["#label", "=", "Hallo"]);
});
it("label with value", () => {
- const tokens = attributeParser.lexer("#label=Hallo");
+ const tokens = attributeParser.lex("#label=Hallo");
expect(tokens[0].startIndex).toEqual(0);
expect(tokens[0].endIndex).toEqual(5);
});
it("relation with value", () => {
- expect(attributeParser.lexer('~relation=#root/RclIpMauTOKS/NFi2gL4xtPxM').map(t => t.text))
+ expect(attributeParser.lex('~relation=#root/RclIpMauTOKS/NFi2gL4xtPxM').map(t => t.text))
.toEqual(["~relation", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"]);
});
it("use quotes to define value", () => {
- expect(attributeParser.lexer("#'label a'='hello\"` world'").map(t => t.text))
+ expect(attributeParser.lex("#'label a'='hello\"` world'").map(t => t.text))
.toEqual(["#label a", "=", 'hello"` world']);
- expect(attributeParser.lexer('#"label a" = "hello\'` world"').map(t => t.text))
+ expect(attributeParser.lex('#"label a" = "hello\'` world"').map(t => t.text))
.toEqual(["#label a", "=", "hello'` world"]);
- expect(attributeParser.lexer('#`label a` = `hello\'" world`').map(t => t.text))
+ expect(attributeParser.lex('#`label a` = `hello\'" world`').map(t => t.text))
.toEqual(["#label a", "=", "hello'\" world"]);
});
});
describe("Parser", () => {
it("simple label", () => {
- const attrs = attributeParser.parser(["#token"].map(t => ({text: t})));
+ const attrs = attributeParser.parse(["#token"].map(t => ({text: t})));
expect(attrs.length).toEqual(1);
expect(attrs[0].type).toEqual('label');
@@ -55,7 +60,7 @@ describe("Parser", () => {
});
it("inherited label", () => {
- const attrs = attributeParser.parser(["#token", "(", "inheritable", ")"].map(t => ({text: t})));
+ const attrs = attributeParser.parse(["#token", "(", "inheritable", ")"].map(t => ({text: t})));
expect(attrs.length).toEqual(1);
expect(attrs[0].type).toEqual('label');
@@ -65,7 +70,7 @@ describe("Parser", () => {
});
it("label with value", () => {
- const attrs = attributeParser.parser(["#token", "=", "val"].map(t => ({text: t})));
+ const attrs = attributeParser.parse(["#token", "=", "val"].map(t => ({text: t})));
expect(attrs.length).toEqual(1);
expect(attrs[0].type).toEqual('label');
@@ -74,14 +79,14 @@ describe("Parser", () => {
});
it("relation", () => {
- let attrs = attributeParser.parser(["~token", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"].map(t => ({text: t})));
+ let attrs = attributeParser.parse(["~token", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"].map(t => ({text: t})));
expect(attrs.length).toEqual(1);
expect(attrs[0].type).toEqual('relation');
expect(attrs[0].name).toEqual("token");
expect(attrs[0].value).toEqual('NFi2gL4xtPxM');
- attrs = attributeParser.parser(["~token", "=", "#NFi2gL4xtPxM"].map(t => ({text: t})));
+ attrs = attributeParser.parse(["~token", "=", "#NFi2gL4xtPxM"].map(t => ({text: t})));
expect(attrs.length).toEqual(1);
expect(attrs[0].type).toEqual('relation');
@@ -97,6 +102,9 @@ describe("error cases", () => {
expect(() => attributeParser.lexAndParse("#a&b/s"))
.toThrow(`Attribute name "a&b/s" contains disallowed characters, only alphanumeric characters, colon and underscore are allowed.`);
+
+ expect(() => attributeParser.lexAndParse("#"))
+ .toThrow(`Attribute name is empty, please fill the name.`);
});
});
diff --git a/src/public/app/services/attribute_parser.js b/src/public/app/services/attribute_parser.js
index 02eb8a63b..f214fcfc8 100644
--- a/src/public/app/services/attribute_parser.js
+++ b/src/public/app/services/attribute_parser.js
@@ -1,4 +1,6 @@
-function lexer(str) {
+function lex(str) {
+ str = str.trim();
+
const tokens = [];
let quotes = false;
@@ -106,12 +108,16 @@ function lexer(str) {
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)) {
throw new Error(`Attribute name "${attrName}" contains disallowed characters, only alphanumeric characters, colon and underscore are allowed.`);
}
}
-function parser(tokens, str, allowEmptyRelations = false) {
+function parse(tokens, str, allowEmptyRelations = false) {
const attrs = [];
function context(i) {
@@ -213,13 +219,13 @@ function parser(tokens, str, allowEmptyRelations = false) {
}
function lexAndParse(str, allowEmptyRelations = false) {
- const tokens = lexer(str);
+ const tokens = lex(str);
- return parser(tokens, str, allowEmptyRelations);
+ return parse(tokens, str, allowEmptyRelations);
}
export default {
- lexer,
- parser,
+ lex,
+ parse,
lexAndParse
}
diff --git a/src/public/app/services/attribute_renderer.js b/src/public/app/services/attribute_renderer.js
index d52777d6d..462eaa07d 100644
--- a/src/public/app/services/attribute_renderer.js
+++ b/src/public/app/services/attribute_renderer.js
@@ -11,7 +11,7 @@ function renderAttribute(attribute, $container, renderIsInheritable) {
$container.append(document.createTextNode(formatValue(attribute.value)));
}
- $container.append(' ');
+ $container.append(" ");
} else if (attribute.type === 'relation') {
if (attribute.isAutoLink) {
return;
@@ -20,7 +20,7 @@ function renderAttribute(attribute, $container, renderIsInheritable) {
if (attribute.value) {
$container.append(document.createTextNode('~' + attribute.name + isInheritable + "="));
$container.append(createNoteLink(attribute.value));
- $container.append(" ");
+ $container.append(" ");
} else {
ws.logError(`Relation ${attribute.attributeId} has empty target`);
}
diff --git a/src/public/app/services/glob.js b/src/public/app/services/glob.js
index 9e4ee89a2..6692bf6ff 100644
--- a/src/public/app/services/glob.js
+++ b/src/public/app/services/glob.js
@@ -34,12 +34,12 @@ function setupGlobs() {
- Just enter any text for full text search
- @abc
- returns notes with label abc
- @year=2019
- matches notes with label year
having value 2019
- @rock @pop
- matches notes which have both rock
and pop
labels
- @rock or @pop
- only one of the labels must be present
- @year<=2000
- numerical comparison (also >, >=, <).
- @dateCreated>=MONTH-1
- notes created in the last month
+ #abc
- returns notes with label abc
+ #year = 2019
- matches notes with label year
having value 2019
+ #rock #pop
- matches notes which have both rock
and pop
labels
+ #rock or #pop
- only one of the labels must be present
+ #year <= 2000
- numerical comparison (also >, >=, <).
+ note.dateCreated >= MONTH-1
- notes created in the last month
=handler
- will execute script defined in handler
relation to get results
`;
diff --git a/src/public/app/widgets/attribute_editor.js b/src/public/app/widgets/attribute_editor.js
index ebf1e6489..e0d46352c 100644
--- a/src/public/app/widgets/attribute_editor.js
+++ b/src/public/app/widgets/attribute_editor.js
@@ -7,6 +7,13 @@ import libraryLoader from "../services/library_loader.js";
import treeCache from "../services/tree_cache.js";
import attributeRenderer from "../services/attribute_renderer.js";
+const HELP_TEXT = `
+To add label, just type e.g. #rock
or if you want to add also value then e.g. #year = 2020
+
+For relation, type ~author = @
which should bring up an autocomplete where you can look up the desired note.
+
+Alternatively you can add label and relation using the +
button on the right side.
`;
+
const TPL = `