mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	added in-editor help for editing attributes
This commit is contained in:
		| @@ -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.`); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
| @@ -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`); | ||||
|         } | ||||
|   | ||||
| @@ -34,12 +34,12 @@ function setupGlobs() { | ||||
|     <p> | ||||
|     <ul> | ||||
|         <li>Just enter any text for full text search</li> | ||||
|         <li><code>@abc</code> - returns notes with label abc</li> | ||||
|         <li><code>@year=2019</code> - matches notes with label <code>year</code> having value <code>2019</code></li> | ||||
|         <li><code>@rock @pop</code> - matches notes which have both <code>rock</code> and <code>pop</code> labels</li> | ||||
|         <li><code>@rock or @pop</code> - only one of the labels must be present</li> | ||||
|         <li><code>@year<=2000</code> - numerical comparison (also >, >=, <).</li> | ||||
|         <li><code>@dateCreated>=MONTH-1</code> - notes created in the last month</li> | ||||
|         <li><code>#abc</code> - returns notes with label abc</li> | ||||
|         <li><code>#year = 2019</code> - matches notes with label <code>year</code> having value <code>2019</code></li> | ||||
|         <li><code>#rock #pop</code> - matches notes which have both <code>rock</code> and <code>pop</code> labels</li> | ||||
|         <li><code>#rock or #pop</code> - only one of the labels must be present</li> | ||||
|         <li><code>#year <= 2000</code> - numerical comparison (also >, >=, <).</li> | ||||
|         <li><code>note.dateCreated >= MONTH-1</code> - notes created in the last month</li> | ||||
|         <li><code>=handler</code> - will execute script defined in <code>handler</code> relation to get results</li> | ||||
|     </ul> | ||||
|     </p>`; | ||||
|   | ||||
| @@ -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 = ` | ||||
| <p>To add label, just type e.g. <code>#rock</code> or if you want to add also value then e.g. <code>#year = 2020</code></p>  | ||||
|  | ||||
| <p>For relation, type <code>~author = @</code> which should bring up an autocomplete where you can look up the desired note.</p> | ||||
|  | ||||
| <p>Alternatively you can add label and relation using the <code>+</code> button on the right side.</p>`; | ||||
|  | ||||
| const TPL = ` | ||||
| <div style="position: relative"> | ||||
|     <style> | ||||
| @@ -170,7 +177,7 @@ const editorConfig = { | ||||
|     toolbar: { | ||||
|         items: [] | ||||
|     }, | ||||
|     placeholder: "Type the labels and relations here, e.g. #year=2020", | ||||
|     placeholder: "Type the labels and relations here", | ||||
|     mention: mentionSetup | ||||
| }; | ||||
|  | ||||
| @@ -339,10 +346,10 @@ export default class AttributeEditorWidget extends TabAwareWidget { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async handleEditorClick(e) {console.log("click") | ||||
|     async handleEditorClick(e) { | ||||
|         const pos = this.textEditor.model.document.selection.getFirstPosition(); | ||||
|  | ||||
|         if (pos && pos.textNode && pos.textNode.data) {console.log(pos); | ||||
|         if (pos && pos.textNode && pos.textNode.data) { | ||||
|             const clickIndex = this.getClickIndex(pos); | ||||
|  | ||||
|             let parsedAttrs; | ||||
| @@ -350,7 +357,7 @@ export default class AttributeEditorWidget extends TabAwareWidget { | ||||
|             try { | ||||
|                 parsedAttrs = attributesParser.lexAndParse(this.getPreprocessedData(), true); | ||||
|             } | ||||
|             catch (e) {console.log(e); | ||||
|             catch (e) { | ||||
|                 // the input is incorrect because user messed up with it and now needs to fix it manually | ||||
|                 return null; | ||||
|             } | ||||
| @@ -365,6 +372,9 @@ export default class AttributeEditorWidget extends TabAwareWidget { | ||||
|             } | ||||
|  | ||||
|             setTimeout(() => { | ||||
|                 if (matchedAttr) { | ||||
|                     this.$editor.tooltip('hide'); | ||||
|  | ||||
|                     this.attributeDetailWidget.showAttributeDetail({ | ||||
|                         allAttributes: parsedAttrs, | ||||
|                         attribute: matchedAttr, | ||||
| @@ -372,8 +382,27 @@ export default class AttributeEditorWidget extends TabAwareWidget { | ||||
|                         x: e.pageX, | ||||
|                         y: e.pageY | ||||
|                     }); | ||||
|                 } | ||||
|                 else { | ||||
|                     this.showHelpTooltip(); | ||||
|                 } | ||||
|             }, 100); | ||||
|         } | ||||
|         else { | ||||
|             this.showHelpTooltip(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     showHelpTooltip() {console.log("showHelpTooltip"); | ||||
|         this.attributeDetailWidget.hide(); | ||||
|  | ||||
|         this.$editor.tooltip({ | ||||
|             trigger: 'focus', | ||||
|             html: true, | ||||
|             title: HELP_TEXT, | ||||
|             placement: 'bottom', | ||||
|             offset: "0,20" | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     getClickIndex(pos) { | ||||
| @@ -436,8 +465,13 @@ export default class AttributeEditorWidget extends TabAwareWidget { | ||||
|  | ||||
|     async focusOnAttributesEvent({tabId}) { | ||||
|         if (this.tabContext.tabId === tabId) { | ||||
|             if (this.$editor.is(":visible")) { | ||||
|                 this.$editor.trigger('focus'); | ||||
|             } | ||||
|             else { | ||||
|                 this.triggerCommand('focusOnDetail', {tabId: this.tabContext.tabId}); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     updateAttributeList(attributes) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user