mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	WIP
This commit is contained in:
		
							
								
								
									
										12
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1337,9 +1337,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "async-mutex": { |     "async-mutex": { | ||||||
|       "version": "0.2.3", |       "version": "0.2.4", | ||||||
|       "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.3.tgz", |       "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.4.tgz", | ||||||
|       "integrity": "sha512-766xaN3BZJyNa7rxdsN1XV34/XFKiyuqJ7VBc8wrCS3wetLdCEgvarIiv7U+Mojly8TVxZxAXZcmFBEdmvDVCA==", |       "integrity": "sha512-fcQKOXUKMQc57JlmjBCHtkKNrfGpHyR7vu18RfuLfeTAf4hK9PgOadPR5cDrBQ682zasrLUhJFe7EKAHJOduDg==", | ||||||
|       "requires": { |       "requires": { | ||||||
|         "tslib": "^2.0.0" |         "tslib": "^2.0.0" | ||||||
|       }, |       }, | ||||||
| @@ -2841,9 +2841,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "electron": { |     "electron": { | ||||||
|       "version": "10.0.0-beta.4", |       "version": "10.0.0-beta.9", | ||||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-10.0.0-beta.4.tgz", |       "resolved": "https://registry.npmjs.org/electron/-/electron-10.0.0-beta.9.tgz", | ||||||
|       "integrity": "sha512-/Jp9i0yiuM/WUdiKFjf7+5gZQJITGhijl++Zp31m94MY+QNMLEnFhaKLSqzrmPA2FPrXn2KlUPNpQs+4Wjcvpg==", |       "integrity": "sha512-trK7AsHIr8+f5kfCVLM/IAiHhr0CVIpf6GEokvkUMqoi/6q3q2PAgeaPnQhb102AREwKruCiE/wVEXCZiY7spQ==", | ||||||
|       "dev": true, |       "dev": true, | ||||||
|       "requires": { |       "requires": { | ||||||
|         "@electron/get": "^1.0.1", |         "@electron/get": "^1.0.1", | ||||||
|   | |||||||
| @@ -75,7 +75,7 @@ | |||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "cross-env": "^7.0.2", |     "cross-env": "^7.0.2", | ||||||
|     "electron": "10.0.0-beta.4", |     "electron": "10.0.0-beta.9", | ||||||
|     "electron-builder": "22.7.0", |     "electron-builder": "22.7.0", | ||||||
|     "electron-packager": "15.0.0", |     "electron-packager": "15.0.0", | ||||||
|     "electron-rebuild": "1.11.0", |     "electron-rebuild": "1.11.0", | ||||||
|   | |||||||
| @@ -1,6 +1,13 @@ | |||||||
| import attributeParser from '../src/public/app/services/attribute_parser.js'; | import attributeParser from '../src/public/app/services/attribute_parser.js'; | ||||||
| import {describe, it, expect, execute} from './mini_test.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", () => { | describe("Lexer", () => { | ||||||
|     it("simple label", () => { |     it("simple label", () => { | ||||||
|         expect(attributeParser.lexer("#label").map(t => t.text)) |         expect(attributeParser.lexer("#label").map(t => t.text)) | ||||||
| @@ -19,11 +26,8 @@ describe("Lexer", () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("relation with value", () => { |     it("relation with value", () => { | ||||||
|         expect(attributeParser.lexer('~relation=<a class="reference-link" href="#root/RclIpMauTOKS/NFi2gL4xtPxM" data-note-path="root/RclIpMauTOKS/NFi2gL4xtPxM">note</a>').map(t => t.text)) |         expect(attributeParser.lexer('~relation=#root/RclIpMauTOKS/NFi2gL4xtPxM').map(t => t.text)) | ||||||
|             .toEqual(["~relation", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"]); |             .toEqual(["~relation", "=", "#root/RclIpMauTOKS/NFi2gL4xtPxM"]); | ||||||
|  |  | ||||||
|         expect(attributeParser.lexer('~relation=<a class="reference-link" id="abc" href="#NFi2gL4xtPxM">note</a>').map(t => t.text)) |  | ||||||
|             .toEqual(["~relation", "=", "#NFi2gL4xtPxM"]); |  | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("use quotes to define value", () => { |     it("use quotes to define value", () => { | ||||||
| @@ -74,7 +78,7 @@ describe("Parser", () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("error cases", () => { |     it("error cases", () => { | ||||||
|         expect(() => attributeParser.parser(["~token"].map(t => ({text: t})))) |         expect(() => attributeParser.parser(["~token"].map(t => ({text: t})), "~token")) | ||||||
|             .toThrow('Relation "~token" should point to a note.'); |             .toThrow('Relation "~token" should point to a note.'); | ||||||
|     }); |     }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -44,6 +44,7 @@ export function expect(val) { | |||||||
|                     console.trace("toThrow caught exception, but messages differ"); |                     console.trace("toThrow caught exception, but messages differ"); | ||||||
|                     console.error(`expected: ${errorMessage}`); |                     console.error(`expected: ${errorMessage}`); | ||||||
|                     console.error(`got:      ${e.message}`); |                     console.error(`got:      ${e.message}`); | ||||||
|  |                     console.error(`${e.stack}`); | ||||||
|  |  | ||||||
|                     errorCount++; |                     errorCount++; | ||||||
|                 } |                 } | ||||||
|   | |||||||
| @@ -195,6 +195,7 @@ function lexAndParse(str, allowEmptyRelations = false) { | |||||||
| } | } | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|  |     preprocess, | ||||||
|     lexer, |     lexer, | ||||||
|     parser, |     parser, | ||||||
|     lexAndParse |     lexAndParse | ||||||
|   | |||||||
| @@ -115,13 +115,16 @@ export default class AttributeDetailWidget extends BasicWidget { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async showAttributeDetail({attribute, isOwned, x, y}) { |     async showAttributeDetail({allAttributes, attribute, isOwned, x, y}) { | ||||||
|         if (!attribute) { |         if (!attribute) { | ||||||
|             this.hide(); |             this.hide(); | ||||||
|  |  | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         this.allAttributes = allAttributes; | ||||||
|  |         this.attribute = attribute; | ||||||
|  |  | ||||||
|         this.toggleInt(true); |         this.toggleInt(true); | ||||||
|  |  | ||||||
|         let {results, count} = await server.post('search-related', attribute); |         let {results, count} = await server.post('search-related', attribute); | ||||||
| @@ -173,11 +176,13 @@ export default class AttributeDetailWidget extends BasicWidget { | |||||||
|  |  | ||||||
|         this.$attrEditName |         this.$attrEditName | ||||||
|             .val(attribute.name) |             .val(attribute.name) | ||||||
|             .attr('readonly', () => !isOwned); |             .attr('readonly', () => !isOwned) | ||||||
|  |             .on('keyup', () => this.updateParent()); | ||||||
|  |  | ||||||
|         this.$attrEditValue |         this.$attrEditValue | ||||||
|             .val(attribute.value) |             .val(attribute.value) | ||||||
|             .attr('readonly', () => !isOwned); |             .attr('readonly', () => !isOwned) | ||||||
|  |             .on('keyup', () => this.updateParent()); | ||||||
|  |  | ||||||
|         this.$attrEditButtonRow.toggle(!!isOwned); |         this.$attrEditButtonRow.toggle(!!isOwned); | ||||||
|  |  | ||||||
| @@ -186,6 +191,13 @@ export default class AttributeDetailWidget extends BasicWidget { | |||||||
|         this.$widget.show(); |         this.$widget.show(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     updateParent() { | ||||||
|  |         this.attribute.name = this.$attrEditName.val(); | ||||||
|  |         this.attribute.value = this.$attrEditValue.val(); | ||||||
|  |  | ||||||
|  |         this.triggerCommand('updateAttributeList', { attributes: this.allAttributes }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     hide() { |     hide() { | ||||||
|         this.toggleInt(false); |         this.toggleInt(false); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -71,6 +71,59 @@ const mentionSetup = { | |||||||
|     ] |     ] | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | const editorConfig = { | ||||||
|  |     removePlugins: [ | ||||||
|  |         'Enter', | ||||||
|  |         'ShiftEnter', | ||||||
|  |         'Heading', | ||||||
|  |         'Link', | ||||||
|  |         'Autoformat', | ||||||
|  |         'Bold', | ||||||
|  |         'Italic', | ||||||
|  |         'Underline', | ||||||
|  |         'Strikethrough', | ||||||
|  |         'Code', | ||||||
|  |         'Superscript', | ||||||
|  |         'Subscript', | ||||||
|  |         'BlockQuote', | ||||||
|  |         'Image', | ||||||
|  |         'ImageCaption', | ||||||
|  |         'ImageStyle', | ||||||
|  |         'ImageToolbar', | ||||||
|  |         'ImageUpload', | ||||||
|  |         'ImageResize', | ||||||
|  |         'List', | ||||||
|  |         'TodoList', | ||||||
|  |         'PasteFromOffice', | ||||||
|  |         'Table', | ||||||
|  |         'TableToolbar', | ||||||
|  |         'TableProperties', | ||||||
|  |         'TableCellProperties', | ||||||
|  |         'Indent', | ||||||
|  |         'IndentBlock', | ||||||
|  |         'BlockToolbar', | ||||||
|  |         'ParagraphButtonUI', | ||||||
|  |         'HeadingButtonsUI', | ||||||
|  |         'UploadimagePlugin', | ||||||
|  |         'InternalLinkPlugin', | ||||||
|  |         'MarkdownImportPlugin', | ||||||
|  |         'CuttonotePlugin', | ||||||
|  |         'TextTransformation', | ||||||
|  |         'Font', | ||||||
|  |         'FontColor', | ||||||
|  |         'FontBackgroundColor', | ||||||
|  |         'CodeBlock', | ||||||
|  |         'SelectAll', | ||||||
|  |         'IncludeNote', | ||||||
|  |         'CutToNote' | ||||||
|  |     ], | ||||||
|  |     toolbar: { | ||||||
|  |         items: [] | ||||||
|  |     }, | ||||||
|  |     placeholder: "Type the labels and relations here ...", | ||||||
|  |     mention: mentionSetup | ||||||
|  | }; | ||||||
|  |  | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="note-attributes"> | <div class="note-attributes"> | ||||||
| <style> | <style> | ||||||
| @@ -321,102 +374,48 @@ export default class NoteAttributesWidget extends TabAwareWidget { | |||||||
|     async initEditor() { |     async initEditor() { | ||||||
|         await libraryLoader.requireLibrary(libraryLoader.CKEDITOR); |         await libraryLoader.requireLibrary(libraryLoader.CKEDITOR); | ||||||
|  |  | ||||||
|         // CKEditor since version 12 needs the element to be visible before initialization. At the same time |  | ||||||
|         // we want to avoid flicker - i.e. show editor only once everything is ready. That's why we have separate |  | ||||||
|         // display of $widget in both branches. |  | ||||||
|         this.$widget.show(); |         this.$widget.show(); | ||||||
|  |  | ||||||
|         this.$editor.on("click", async e => { |         this.$editor.on("click", e => this.handleEditorClick(e)); | ||||||
|             const pos = this.textEditor.model.document.selection.getFirstPosition(); |  | ||||||
|  |  | ||||||
|             if (pos && pos.textNode && pos.textNode.data) { |  | ||||||
|                 const attrText = pos.textNode.data; |  | ||||||
|                 const clickIndex = pos.offset - pos.textNode.startOffset; |  | ||||||
|  |  | ||||||
|                 const parsedAttrs = attributesParser.lexAndParse(attrText, true); |  | ||||||
|  |  | ||||||
|                 let matchedAttr = null; |  | ||||||
|  |  | ||||||
|                 for (const attr of parsedAttrs) { |  | ||||||
|                     if (clickIndex >= attr.startIndex && clickIndex <= attr.endIndex) { |  | ||||||
|                         matchedAttr = attr; |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 this.attributeDetailWidget.showAttributeDetail({ |  | ||||||
|                     attribute: matchedAttr, |  | ||||||
|                     isOwned: true, |  | ||||||
|                     x: e.pageX, |  | ||||||
|                     y: e.pageY |  | ||||||
|                 }); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         this.textEditor = await BalloonEditor.create(this.$editor[0], { |  | ||||||
|             removePlugins: [ |  | ||||||
|                 'Enter', |  | ||||||
|                 'ShiftEnter', |  | ||||||
|                 'Heading', |  | ||||||
|                 'Link', |  | ||||||
|                 'Autoformat', |  | ||||||
|                 'Bold', |  | ||||||
|                 'Italic', |  | ||||||
|                 'Underline', |  | ||||||
|                 'Strikethrough', |  | ||||||
|                 'Code', |  | ||||||
|                 'Superscript', |  | ||||||
|                 'Subscript', |  | ||||||
|                 'BlockQuote', |  | ||||||
|                 'Image', |  | ||||||
|                 'ImageCaption', |  | ||||||
|                 'ImageStyle', |  | ||||||
|                 'ImageToolbar', |  | ||||||
|                 'ImageUpload', |  | ||||||
|                 'ImageResize', |  | ||||||
|                 'List', |  | ||||||
|                 'TodoList', |  | ||||||
|                 'PasteFromOffice', |  | ||||||
|                 'Table', |  | ||||||
|                 'TableToolbar', |  | ||||||
|                 'TableProperties', |  | ||||||
|                 'TableCellProperties', |  | ||||||
|                 'Indent', |  | ||||||
|                 'IndentBlock', |  | ||||||
|                 'BlockToolbar', |  | ||||||
|                 'ParagraphButtonUI', |  | ||||||
|                 'HeadingButtonsUI', |  | ||||||
|                 'UploadimagePlugin', |  | ||||||
|                 'InternalLinkPlugin', |  | ||||||
|                 'MarkdownImportPlugin', |  | ||||||
|                 'CuttonotePlugin', |  | ||||||
|                 'TextTransformation', |  | ||||||
|                 'Font', |  | ||||||
|                 'FontColor', |  | ||||||
|                 'FontBackgroundColor', |  | ||||||
|                 'CodeBlock', |  | ||||||
|                 'SelectAll', |  | ||||||
|                 'IncludeNote', |  | ||||||
|                 'CutToNote' |  | ||||||
|             ], |  | ||||||
|             toolbar: { |  | ||||||
|                 items: [] |  | ||||||
|             }, |  | ||||||
|             placeholder: "Type the labels and relations here ...", |  | ||||||
|             mention: mentionSetup |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|  |         this.textEditor = await BalloonEditor.create(this.$editor[0], editorConfig); | ||||||
|         this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate()); |         this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate()); | ||||||
|  |  | ||||||
|         // disable spellcheck for attribute editor |         // disable spellcheck for attribute editor | ||||||
|         this.textEditor.editing.view.change( writer => { |         this.textEditor.editing.view.change(writer => writer.setAttribute('spellcheck', 'false', this.textEditor.editing.view.document.getRoot())); | ||||||
|             writer.setAttribute( 'spellcheck', 'false', this.textEditor.editing.view.document.getRoot() ); |  | ||||||
|         } ); |  | ||||||
|  |  | ||||||
|         //await import(/* webpackIgnore: true */'../../libraries/ckeditor/inspector.js'); |         //await import(/* webpackIgnore: true */'../../libraries/ckeditor/inspector.js'); | ||||||
|         //CKEditorInspector.attach(this.textEditor); |         //CKEditorInspector.attach(this.textEditor); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async handleEditorClick(e) { | ||||||
|  |         const pos = this.textEditor.model.document.selection.getFirstPosition(); | ||||||
|  |  | ||||||
|  |         if (pos && pos.textNode && pos.textNode.data) { | ||||||
|  |             const attrText = pos.textNode.data; | ||||||
|  |             const clickIndex = pos.offset - pos.textNode.startOffset; | ||||||
|  |  | ||||||
|  |             const parsedAttrs = attributesParser.lexAndParse(attrText, true); | ||||||
|  |  | ||||||
|  |             let matchedAttr = null; | ||||||
|  |  | ||||||
|  |             for (const attr of parsedAttrs) { | ||||||
|  |                 if (clickIndex >= attr.startIndex && clickIndex <= attr.endIndex) { | ||||||
|  |                     matchedAttr = attr; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             this.attributeDetailWidget.showAttributeDetail({ | ||||||
|  |                 allAttributes: parsedAttrs, | ||||||
|  |                 attribute: matchedAttr, | ||||||
|  |                 isOwned: true, | ||||||
|  |                 x: e.pageX, | ||||||
|  |                 y: e.pageY | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     async loadReferenceLinkTitle(noteId, $el) { |     async loadReferenceLinkTitle(noteId, $el) { | ||||||
|         const note = await treeCache.getNote(noteId, true); |         const note = await treeCache.getNote(noteId, true); | ||||||
|  |  | ||||||
| @@ -436,14 +435,7 @@ export default class NoteAttributesWidget extends TabAwareWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async refreshWithNote(note) { |     async refreshWithNote(note) { | ||||||
|         const ownedAttributes = note.getOwnedAttributes(); |         await this.renderOwnedAttributes(note.getOwnedAttributes()); | ||||||
|         const $attributesContainer = $("<div>"); |  | ||||||
|  |  | ||||||
|         await this.renderAttributesIntoCKEditor(ownedAttributes, $attributesContainer); |  | ||||||
|  |  | ||||||
|         await this.spacedUpdate.allowUpdateWithoutChange(() => { |  | ||||||
|             this.textEditor.setData($attributesContainer.html()); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         const inheritedAttributes = note.getAttributes().filter(attr => attr.noteId !== this.noteId); |         const inheritedAttributes = note.getAttributes().filter(attr => attr.noteId !== this.noteId); | ||||||
|  |  | ||||||
| @@ -465,6 +457,16 @@ export default class NoteAttributesWidget extends TabAwareWidget { | |||||||
|         this.parseAttributes(); |         this.parseAttributes(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     async renderOwnedAttributes(ownedAttributes) { | ||||||
|  |         const $attributesContainer = $("<div>"); | ||||||
|  |  | ||||||
|  |         await this.renderAttributesIntoCKEditor(ownedAttributes, $attributesContainer); | ||||||
|  |  | ||||||
|  |         await this.spacedUpdate.allowUpdateWithoutChange(() => { | ||||||
|  |             this.textEditor.setData($attributesContainer.html()); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     attrPlural(number) { |     attrPlural(number) { | ||||||
|         return 'attribute' + (number === 1 ? '' : 's'); |         return 'attribute' + (number === 1 ? '' : 's'); | ||||||
|     } |     } | ||||||
| @@ -554,4 +556,8 @@ export default class NoteAttributesWidget extends TabAwareWidget { | |||||||
|             this.$editor.trigger('focus'); |             this.$editor.trigger('focus'); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     updateAttributeListCommand({attributes}) { | ||||||
|  |         this.renderOwnedAttributes(attributes); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -168,7 +168,23 @@ function executeWithoutTransaction(query, params = []) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function executeMany(query, params) { | function executeMany(query, params) { | ||||||
|     getManyRows(query, params); |     while (params.length > 0) { | ||||||
|  |         const curParams = params.slice(0, Math.min(params.length, PARAM_LIMIT)); | ||||||
|  |         params = params.slice(curParams.length); | ||||||
|  |  | ||||||
|  |         const curParamsObj = {}; | ||||||
|  |  | ||||||
|  |         let j = 1; | ||||||
|  |         for (const param of curParams) { | ||||||
|  |             curParamsObj['param' + j++] = param; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         let i = 1; | ||||||
|  |         const questionMarks = curParams.map(() => ":param" + i++).join(","); | ||||||
|  |         const curQuery = query.replace(/\?\?\?/g, questionMarks); | ||||||
|  |  | ||||||
|  |         dbConnection.prepare(curQuery).run(curParamsObj); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function executeScript(query) { | function executeScript(query) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user