mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	WIP
This commit is contained in:
		
							
								
								
									
										12
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1337,9 +1337,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "async-mutex": { | ||||
|       "version": "0.2.3", | ||||
|       "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.3.tgz", | ||||
|       "integrity": "sha512-766xaN3BZJyNa7rxdsN1XV34/XFKiyuqJ7VBc8wrCS3wetLdCEgvarIiv7U+Mojly8TVxZxAXZcmFBEdmvDVCA==", | ||||
|       "version": "0.2.4", | ||||
|       "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.4.tgz", | ||||
|       "integrity": "sha512-fcQKOXUKMQc57JlmjBCHtkKNrfGpHyR7vu18RfuLfeTAf4hK9PgOadPR5cDrBQ682zasrLUhJFe7EKAHJOduDg==", | ||||
|       "requires": { | ||||
|         "tslib": "^2.0.0" | ||||
|       }, | ||||
| @@ -2841,9 +2841,9 @@ | ||||
|       } | ||||
|     }, | ||||
|     "electron": { | ||||
|       "version": "10.0.0-beta.4", | ||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-10.0.0-beta.4.tgz", | ||||
|       "integrity": "sha512-/Jp9i0yiuM/WUdiKFjf7+5gZQJITGhijl++Zp31m94MY+QNMLEnFhaKLSqzrmPA2FPrXn2KlUPNpQs+4Wjcvpg==", | ||||
|       "version": "10.0.0-beta.9", | ||||
|       "resolved": "https://registry.npmjs.org/electron/-/electron-10.0.0-beta.9.tgz", | ||||
|       "integrity": "sha512-trK7AsHIr8+f5kfCVLM/IAiHhr0CVIpf6GEokvkUMqoi/6q3q2PAgeaPnQhb102AREwKruCiE/wVEXCZiY7spQ==", | ||||
|       "dev": true, | ||||
|       "requires": { | ||||
|         "@electron/get": "^1.0.1", | ||||
|   | ||||
| @@ -75,7 +75,7 @@ | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "cross-env": "^7.0.2", | ||||
|     "electron": "10.0.0-beta.4", | ||||
|     "electron": "10.0.0-beta.9", | ||||
|     "electron-builder": "22.7.0", | ||||
|     "electron-packager": "15.0.0", | ||||
|     "electron-rebuild": "1.11.0", | ||||
|   | ||||
| @@ -1,6 +1,13 @@ | ||||
| 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)) | ||||
| @@ -19,11 +26,8 @@ describe("Lexer", () => { | ||||
|     }); | ||||
|  | ||||
|     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"]); | ||||
|  | ||||
|         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", () => { | ||||
| @@ -74,7 +78,7 @@ describe("Parser", () => { | ||||
|     }); | ||||
|  | ||||
|     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.'); | ||||
|     }); | ||||
| }); | ||||
|   | ||||
| @@ -44,6 +44,7 @@ export function expect(val) { | ||||
|                     console.trace("toThrow caught exception, but messages differ"); | ||||
|                     console.error(`expected: ${errorMessage}`); | ||||
|                     console.error(`got:      ${e.message}`); | ||||
|                     console.error(`${e.stack}`); | ||||
|  | ||||
|                     errorCount++; | ||||
|                 } | ||||
|   | ||||
| @@ -195,6 +195,7 @@ function lexAndParse(str, allowEmptyRelations = false) { | ||||
| } | ||||
|  | ||||
| export default { | ||||
|     preprocess, | ||||
|     lexer, | ||||
|     parser, | ||||
|     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) { | ||||
|             this.hide(); | ||||
|  | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         this.allAttributes = allAttributes; | ||||
|         this.attribute = attribute; | ||||
|  | ||||
|         this.toggleInt(true); | ||||
|  | ||||
|         let {results, count} = await server.post('search-related', attribute); | ||||
| @@ -173,11 +176,13 @@ export default class AttributeDetailWidget extends BasicWidget { | ||||
|  | ||||
|         this.$attrEditName | ||||
|             .val(attribute.name) | ||||
|             .attr('readonly', () => !isOwned); | ||||
|             .attr('readonly', () => !isOwned) | ||||
|             .on('keyup', () => this.updateParent()); | ||||
|  | ||||
|         this.$attrEditValue | ||||
|             .val(attribute.value) | ||||
|             .attr('readonly', () => !isOwned); | ||||
|             .attr('readonly', () => !isOwned) | ||||
|             .on('keyup', () => this.updateParent()); | ||||
|  | ||||
|         this.$attrEditButtonRow.toggle(!!isOwned); | ||||
|  | ||||
| @@ -186,6 +191,13 @@ export default class AttributeDetailWidget extends BasicWidget { | ||||
|         this.$widget.show(); | ||||
|     } | ||||
|  | ||||
|     updateParent() { | ||||
|         this.attribute.name = this.$attrEditName.val(); | ||||
|         this.attribute.value = this.$attrEditValue.val(); | ||||
|  | ||||
|         this.triggerCommand('updateAttributeList', { attributes: this.allAttributes }); | ||||
|     } | ||||
|  | ||||
|     hide() { | ||||
|         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 = ` | ||||
| <div class="note-attributes"> | ||||
| <style> | ||||
| @@ -321,102 +374,48 @@ export default class NoteAttributesWidget extends TabAwareWidget { | ||||
|     async initEditor() { | ||||
|         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.$editor.on("click", async 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.$editor.on("click", e => this.handleEditorClick(e)); | ||||
|  | ||||
|         this.textEditor = await BalloonEditor.create(this.$editor[0], editorConfig); | ||||
|         this.textEditor.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate()); | ||||
|  | ||||
|         // disable spellcheck for attribute editor | ||||
|         this.textEditor.editing.view.change( writer => { | ||||
|             writer.setAttribute( 'spellcheck', 'false', this.textEditor.editing.view.document.getRoot() ); | ||||
|         } ); | ||||
|         this.textEditor.editing.view.change(writer => writer.setAttribute('spellcheck', 'false', this.textEditor.editing.view.document.getRoot())); | ||||
|  | ||||
|         //await import(/* webpackIgnore: true */'../../libraries/ckeditor/inspector.js'); | ||||
|         //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) { | ||||
|         const note = await treeCache.getNote(noteId, true); | ||||
|  | ||||
| @@ -436,14 +435,7 @@ export default class NoteAttributesWidget extends TabAwareWidget { | ||||
|     } | ||||
|  | ||||
|     async refreshWithNote(note) { | ||||
|         const ownedAttributes = note.getOwnedAttributes(); | ||||
|         const $attributesContainer = $("<div>"); | ||||
|  | ||||
|         await this.renderAttributesIntoCKEditor(ownedAttributes, $attributesContainer); | ||||
|  | ||||
|         await this.spacedUpdate.allowUpdateWithoutChange(() => { | ||||
|             this.textEditor.setData($attributesContainer.html()); | ||||
|         }); | ||||
|         await this.renderOwnedAttributes(note.getOwnedAttributes()); | ||||
|  | ||||
|         const inheritedAttributes = note.getAttributes().filter(attr => attr.noteId !== this.noteId); | ||||
|  | ||||
| @@ -465,6 +457,16 @@ export default class NoteAttributesWidget extends TabAwareWidget { | ||||
|         this.parseAttributes(); | ||||
|     } | ||||
|  | ||||
|     async renderOwnedAttributes(ownedAttributes) { | ||||
|         const $attributesContainer = $("<div>"); | ||||
|  | ||||
|         await this.renderAttributesIntoCKEditor(ownedAttributes, $attributesContainer); | ||||
|  | ||||
|         await this.spacedUpdate.allowUpdateWithoutChange(() => { | ||||
|             this.textEditor.setData($attributesContainer.html()); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     attrPlural(number) { | ||||
|         return 'attribute' + (number === 1 ? '' : 's'); | ||||
|     } | ||||
| @@ -554,4 +556,8 @@ export default class NoteAttributesWidget extends TabAwareWidget { | ||||
|             this.$editor.trigger('focus'); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     updateAttributeListCommand({attributes}) { | ||||
|         this.renderOwnedAttributes(attributes); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -168,7 +168,23 @@ function executeWithoutTransaction(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) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user