| 
									
										
										
										
											2021-05-22 12:35:41 +02:00
										 |  |  | import NoteContextAwareWidget from "../note_context_aware_widget.js"; | 
					
						
							| 
									
										
										
										
											2020-11-26 23:00:27 +01:00
										 |  |  | import noteAutocompleteService from "../../services/note_autocomplete.js"; | 
					
						
							|  |  |  | import server from "../../services/server.js"; | 
					
						
							| 
									
										
										
										
											2022-08-05 16:44:26 +02:00
										 |  |  | import contextMenuService from "../../menus/context_menu.js"; | 
					
						
							| 
									
										
										
										
											2020-11-26 23:00:27 +01:00
										 |  |  | import attributesParser from "../../services/attribute_parser.js"; | 
					
						
							|  |  |  | import libraryLoader from "../../services/library_loader.js"; | 
					
						
							| 
									
										
										
										
											2021-04-16 23:01:56 +02:00
										 |  |  | import froca from "../../services/froca.js"; | 
					
						
							| 
									
										
										
										
											2020-11-26 23:00:27 +01:00
										 |  |  | import attributeRenderer from "../../services/attribute_renderer.js"; | 
					
						
							|  |  |  | import noteCreateService from "../../services/note_create.js"; | 
					
						
							|  |  |  | import treeService from "../../services/tree.js"; | 
					
						
							| 
									
										
										
										
											2021-08-25 22:49:24 +02:00
										 |  |  | import attributeService from "../../services/attributes.js"; | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-21 23:08:53 +02:00
										 |  |  | 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>`; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | const TPL = `
 | 
					
						
							| 
									
										
										
										
											2020-10-31 22:47:15 +01:00
										 |  |  | <div style="position: relative; padding-top: 10px; padding-bottom: 10px"> | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     <style> | 
					
						
							|  |  |  |     .attribute-list-editor { | 
					
						
							|  |  |  |         border: 0 !important; | 
					
						
							|  |  |  |         outline: 0 !important; | 
					
						
							|  |  |  |         box-shadow: none !important; | 
					
						
							|  |  |  |         padding: 0 0 0 5px !important; | 
					
						
							|  |  |  |         margin: 0 !important; | 
					
						
							| 
									
										
										
										
											2020-07-23 00:19:50 +02:00
										 |  |  |         max-height: 100px; | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         overflow: auto; | 
					
						
							| 
									
										
										
										
											2020-09-14 21:07:00 +02:00
										 |  |  |         transition: opacity .1s linear; | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-03-29 23:29:14 +02:00
										 |  |  |      | 
					
						
							|  |  |  |     .attribute-list-editor.ck-content .mention { | 
					
						
							|  |  |  |         color: var(--muted-text-color) !important; | 
					
						
							|  |  |  |         background: transparent !important; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |          | 
					
						
							|  |  |  |     .save-attributes-button { | 
					
						
							|  |  |  |         color: var(--muted-text-color); | 
					
						
							|  |  |  |         position: absolute;  | 
					
						
							| 
									
										
										
										
											2020-11-19 23:02:25 +01:00
										 |  |  |         bottom: 14px; | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         right: 25px; | 
					
						
							|  |  |  |         cursor: pointer; | 
					
						
							|  |  |  |         border: 1px solid transparent; | 
					
						
							|  |  |  |         font-size: 130%; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     .add-new-attribute-button { | 
					
						
							|  |  |  |         color: var(--muted-text-color); | 
					
						
							|  |  |  |         position: absolute;  | 
					
						
							| 
									
										
										
										
											2020-11-19 23:02:25 +01:00
										 |  |  |         bottom: 13px; | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         right: 0;  | 
					
						
							|  |  |  |         cursor: pointer; | 
					
						
							|  |  |  |         border: 1px solid transparent; | 
					
						
							|  |  |  |         font-size: 130%; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     .add-new-attribute-button:hover, .save-attributes-button:hover { | 
					
						
							| 
									
										
										
										
											2022-09-18 13:52:19 +02:00
										 |  |  |         border: 1px solid var(--button-border-color); | 
					
						
							|  |  |  |         border-radius: var(--button-border-radius); | 
					
						
							|  |  |  |         background: var(--button-background-color); | 
					
						
							|  |  |  |         color: var(--button-text-color); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |      | 
					
						
							|  |  |  |     .attribute-errors { | 
					
						
							|  |  |  |         color: red; | 
					
						
							| 
									
										
										
										
											2020-09-01 22:47:36 +02:00
										 |  |  |         padding: 5px 50px 0px 5px; /* large right padding to avoid buttons */ | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     </style> | 
					
						
							|  |  |  |      | 
					
						
							|  |  |  |     <div class="attribute-list-editor" tabindex="200"></div> | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-18 00:20:24 +02:00
										 |  |  |     <div class="bx bx-save save-attributes-button" title="Save attributes <enter>"></div> | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     <div class="bx bx-plus add-new-attribute-button" title="Add a new attribute"></div> | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |      | 
					
						
							|  |  |  |     <div class="attribute-errors" style="display: none;"></div> | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | </div> | 
					
						
							|  |  |  | `;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const mentionSetup = { | 
					
						
							|  |  |  |     feeds: [ | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             marker: '@', | 
					
						
							| 
									
										
										
										
											2020-09-21 22:08:54 +02:00
										 |  |  |             feed: queryText => noteAutocompleteService.autocompleteSourceForCKEditor(queryText), | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |             itemRenderer: item => { | 
					
						
							| 
									
										
										
										
											2020-08-12 23:39:05 +02:00
										 |  |  |                 const itemElement = document.createElement('button'); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 itemElement.innerHTML = `${item.highlightedNotePathTitle} `; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return itemElement; | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             minimumCharacters: 0 | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             marker: '#', | 
					
						
							|  |  |  |             feed: async queryText => { | 
					
						
							|  |  |  |                 const names = await server.get(`attributes/names/?type=label&query=${encodeURIComponent(queryText)}`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return names.map(name => { | 
					
						
							|  |  |  |                     return { | 
					
						
							| 
									
										
										
										
											2022-12-21 15:19:05 +01:00
										 |  |  |                         id: `#${name}`, | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |                         name: name | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             minimumCharacters: 0, | 
					
						
							|  |  |  |             attributeMention: true | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             marker: '~', | 
					
						
							|  |  |  |             feed: async queryText => { | 
					
						
							|  |  |  |                 const names = await server.get(`attributes/names/?type=relation&query=${encodeURIComponent(queryText)}`); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return names.map(name => { | 
					
						
							|  |  |  |                     return { | 
					
						
							| 
									
										
										
										
											2022-12-21 15:19:05 +01:00
										 |  |  |                         id: `~${name}`, | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |                         name: name | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             minimumCharacters: 0, | 
					
						
							|  |  |  |             attributeMention: true | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const editorConfig = { | 
					
						
							|  |  |  |     removePlugins: [ | 
					
						
							|  |  |  |         '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', | 
					
						
							| 
									
										
										
										
											2020-09-21 22:57:22 +02:00
										 |  |  |         'CutToNote', | 
					
						
							|  |  |  |         'Mathematics', | 
					
						
							| 
									
										
										
										
											2021-07-05 10:13:52 +02:00
										 |  |  |         'AutoformatMath', | 
					
						
							| 
									
										
										
										
											2020-09-21 22:57:22 +02:00
										 |  |  |         'indentBlockShortcutPlugin', | 
					
						
							| 
									
										
										
										
											2021-04-05 11:28:34 +02:00
										 |  |  |         'removeFormatLinksPlugin' | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     ], | 
					
						
							|  |  |  |     toolbar: { | 
					
						
							|  |  |  |         items: [] | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2020-08-21 23:08:53 +02:00
										 |  |  |     placeholder: "Type the labels and relations here", | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     mention: mentionSetup | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-22 12:35:41 +02:00
										 |  |  | export default class AttributeEditorWidget extends NoteContextAwareWidget { | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |     constructor(attributeDetailWidget) { | 
					
						
							|  |  |  |         super(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.attributeDetailWidget = attributeDetailWidget; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     doRender() { | 
					
						
							|  |  |  |         this.$widget = $(TPL); | 
					
						
							|  |  |  |         this.$editor = this.$widget.find('.attribute-list-editor'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.initialized = this.initEditor(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.$editor.on('keydown', async e => { | 
					
						
							| 
									
										
										
										
											2020-09-01 22:47:36 +02:00
										 |  |  |             if (e.which === 13) { | 
					
						
							| 
									
										
										
										
											2021-10-30 15:22:20 +02:00
										 |  |  |                 // allow autocomplete to fill the result textarea
 | 
					
						
							|  |  |  |                 setTimeout(() => this.save(), 100); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             this.attributeDetailWidget.hide(); | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-18 00:20:24 +02:00
										 |  |  |         this.$editor.on('blur', () => this.save()); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         this.$addNewAttributeButton = this.$widget.find('.add-new-attribute-button'); | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |         this.$addNewAttributeButton.on('click', e => this.addNewAttribute(e)); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |         this.$saveAttributesButton = this.$widget.find('.save-attributes-button'); | 
					
						
							|  |  |  |         this.$saveAttributesButton.on('click', () => this.save()); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |         this.$errors = this.$widget.find('.attribute-errors'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |     addNewAttribute(e) { | 
					
						
							|  |  |  |         contextMenuService.show({ | 
					
						
							|  |  |  |             x: e.pageX, | 
					
						
							|  |  |  |             y: e.pageY, | 
					
						
							|  |  |  |             orientation: 'left', | 
					
						
							|  |  |  |             items: [ | 
					
						
							| 
									
										
										
										
											2022-05-31 22:45:57 +02:00
										 |  |  |                 {title: `Add new label <kbd data-command="addNewLabel"></kbd>`, command: "addNewLabel", uiIcon: "bx bx-hash"}, | 
					
						
							|  |  |  |                 {title: `Add new relation <kbd data-command="addNewRelation"></kbd>`, command: "addNewRelation", uiIcon: "bx bx-transfer"}, | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |                 {title: "----"}, | 
					
						
							| 
									
										
										
										
											2022-05-31 22:45:57 +02:00
										 |  |  |                 {title: "Add new label definition", command: "addNewLabelDefinition", uiIcon: "bx bx-empty"}, | 
					
						
							|  |  |  |                 {title: "Add new relation definition", command: "addNewRelationDefinition", uiIcon: "bx bx-empty"}, | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |             ], | 
					
						
							|  |  |  |             selectMenuItemHandler: ({command}) => this.handleAddNewAttributeCommand(command) | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-01 23:18:28 +02:00
										 |  |  |     // triggered from keyboard shortcut
 | 
					
						
							| 
									
										
										
										
											2021-05-22 12:26:45 +02:00
										 |  |  |     addNewLabelEvent({ntxId}) { | 
					
						
							| 
									
										
										
										
											2021-05-22 12:42:34 +02:00
										 |  |  |         if (this.isNoteContext(ntxId)) { | 
					
						
							| 
									
										
										
										
											2020-09-04 23:35:10 +02:00
										 |  |  |             this.handleAddNewAttributeCommand('addNewLabel'); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-09-01 23:18:28 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // triggered from keyboard shortcut
 | 
					
						
							| 
									
										
										
										
											2021-05-22 12:26:45 +02:00
										 |  |  |     addNewRelationEvent({ntxId}) { | 
					
						
							| 
									
										
										
										
											2021-05-22 12:42:34 +02:00
										 |  |  |         if (this.isNoteContext(ntxId)) { | 
					
						
							| 
									
										
										
										
											2020-09-04 23:35:10 +02:00
										 |  |  |             this.handleAddNewAttributeCommand('addNewRelation'); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-09-01 23:18:28 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |     async handleAddNewAttributeCommand(command) { | 
					
						
							|  |  |  |         const attrs = this.parseAttributes(); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |         if (!attrs) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:28 +02:00
										 |  |  |         let type, name, value; | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (command === 'addNewLabel') { | 
					
						
							|  |  |  |             type = 'label'; | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:28 +02:00
										 |  |  |             name = 'myLabel'; | 
					
						
							|  |  |  |             value = ''; | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |         } else if (command === 'addNewRelation') { | 
					
						
							|  |  |  |             type = 'relation'; | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:28 +02:00
										 |  |  |             name = 'myRelation'; | 
					
						
							|  |  |  |             value = ''; | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |         } else if (command === 'addNewLabelDefinition') { | 
					
						
							|  |  |  |             type = 'label'; | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:28 +02:00
										 |  |  |             name = 'label:myLabel'; | 
					
						
							|  |  |  |             value = 'promoted,single,text'; | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |         } else if (command === 'addNewRelationDefinition') { | 
					
						
							|  |  |  |             type = 'label'; | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:28 +02:00
										 |  |  |             name = 'relation:myRelation'; | 
					
						
							|  |  |  |             value = 'promoted,single'; | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |         } else { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |         attrs.push({ | 
					
						
							|  |  |  |             type, | 
					
						
							|  |  |  |             name, | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:28 +02:00
										 |  |  |             value, | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |             isInheritable: false | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-18 00:20:24 +02:00
										 |  |  |         await this.renderOwnedAttributes(attrs, false); | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this.$editor.scrollTop(this.$editor[0].scrollHeight); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const rect = this.$editor[0].getBoundingClientRect(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         setTimeout(() => { | 
					
						
							|  |  |  |             // showing a little bit later because there's a conflict with outside click closing the attr detail
 | 
					
						
							|  |  |  |             this.attributeDetailWidget.showAttributeDetail({ | 
					
						
							|  |  |  |                 allAttributes: attrs, | 
					
						
							|  |  |  |                 attribute: attrs[attrs.length - 1], | 
					
						
							|  |  |  |                 isOwned: true, | 
					
						
							|  |  |  |                 x: (rect.left + rect.right) / 2, | 
					
						
							| 
									
										
										
										
											2020-09-01 22:47:36 +02:00
										 |  |  |                 y: rect.bottom, | 
					
						
							|  |  |  |                 focus: 'name' | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |             }); | 
					
						
							|  |  |  |         }, 100); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async save() { | 
					
						
							| 
									
										
										
										
											2022-08-26 22:02:40 +02:00
										 |  |  |         if (this.lastUpdatedNoteId !== this.noteId) { | 
					
						
							|  |  |  |             // https://github.com/zadam/trilium/issues/3090
 | 
					
						
							|  |  |  |             console.warn("Ignoring blur event because a different note is loaded."); | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         const attributes = this.parseAttributes(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (attributes) { | 
					
						
							|  |  |  |             await server.put(`notes/${this.noteId}/attributes`, attributes, this.componentId); | 
					
						
							| 
									
										
										
										
											2020-07-23 00:19:50 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             this.$saveAttributesButton.fadeOut(); | 
					
						
							| 
									
										
										
										
											2020-09-14 21:07:00 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             // blink the attribute text to give visual hint that save has been executed
 | 
					
						
							|  |  |  |             this.$editor.css('opacity', 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // revert back
 | 
					
						
							|  |  |  |             setTimeout(() => this.$editor.css('opacity', 1), 100); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     parseAttributes() { | 
					
						
							|  |  |  |         try { | 
					
						
							| 
									
										
										
										
											2020-08-17 23:54:18 +02:00
										 |  |  |             const attrs = attributesParser.lexAndParse(this.getPreprocessedData()); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             return attrs; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         catch (e) { | 
					
						
							| 
									
										
										
										
											2020-08-17 23:54:18 +02:00
										 |  |  |             this.$errors.text(e.message).slideDown(); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-17 23:54:18 +02:00
										 |  |  |     getPreprocessedData() { | 
					
						
							|  |  |  |         const str = this.textEditor.getData() | 
					
						
							| 
									
										
										
										
											2022-11-28 23:39:23 +01:00
										 |  |  |             .replace(/<a[^>]+href="(#[A-Za-z0-9_/]*)"[^>]*>[^<]*<\/a>/g, "$1") | 
					
						
							| 
									
										
										
										
											2020-08-28 16:00:30 +02:00
										 |  |  |             .replace(/ /g, " "); // otherwise .text() below outputs non-breaking space in unicode
 | 
					
						
							| 
									
										
										
										
											2020-08-17 23:54:18 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         return $("<div>").html(str).text(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     async initEditor() { | 
					
						
							|  |  |  |         await libraryLoader.requireLibrary(libraryLoader.CKEDITOR); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.$widget.show(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.$editor.on("click", e => this.handleEditorClick(e)); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-15 21:04:17 +01:00
										 |  |  |         /** @property {BalloonEditor} */ | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         this.textEditor = await BalloonEditor.create(this.$editor[0], editorConfig); | 
					
						
							|  |  |  |         this.textEditor.model.document.on('change:data', () => this.dataChanged()); | 
					
						
							| 
									
										
										
										
											2021-04-07 22:35:06 +02:00
										 |  |  |         this.textEditor.editing.view.document.on('enter', (event, data) => { | 
					
						
							|  |  |  |             // disable entering new line - see https://github.com/ckeditor/ckeditor5/issues/9422
 | 
					
						
							|  |  |  |             data.preventDefault(); | 
					
						
							|  |  |  |             event.stop(); | 
					
						
							|  |  |  |         }, {priority: 'high'}); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // disable spellcheck for attribute editor
 | 
					
						
							|  |  |  |         this.textEditor.editing.view.change(writer => writer.setAttribute('spellcheck', 'false', this.textEditor.editing.view.document.getRoot())); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-09 16:13:22 +01:00
										 |  |  |         //await import(/* webpackIgnore: true */'../../libraries/ckeditor/inspector');
 | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         //CKEditorInspector.attach(this.textEditor);
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     dataChanged() { | 
					
						
							| 
									
										
										
										
											2022-08-26 22:02:40 +02:00
										 |  |  |         this.lastUpdatedNoteId = this.noteId; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |         if (this.lastSavedContent === this.textEditor.getData()) { | 
					
						
							|  |  |  |             this.$saveAttributesButton.fadeOut(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							|  |  |  |             this.$saveAttributesButton.fadeIn(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (this.$errors.is(":visible")) { | 
					
						
							| 
									
										
										
										
											2020-09-01 23:43:09 +02:00
										 |  |  |             // using .hide() instead of .slideUp() since this will also hide the error after confirming
 | 
					
						
							|  |  |  |             // mention for relation name which suits up. When using.slideUp() error will appear and the slideUp which is weird
 | 
					
						
							|  |  |  |             this.$errors.hide(); | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-21 23:08:53 +02:00
										 |  |  |     async handleEditorClick(e) { | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         const pos = this.textEditor.model.document.selection.getFirstPosition(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-21 23:08:53 +02:00
										 |  |  |         if (pos && pos.textNode && pos.textNode.data) { | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |             const clickIndex = this.getClickIndex(pos); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |             let parsedAttrs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             try { | 
					
						
							| 
									
										
										
										
											2020-08-17 23:54:18 +02:00
										 |  |  |                 parsedAttrs = attributesParser.lexAndParse(this.getPreprocessedData(), true); | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-08-21 23:08:53 +02:00
										 |  |  |             catch (e) { | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  |                 // the input is incorrect because user messed up with it and now needs to fix it manually
 | 
					
						
							|  |  |  |                 return null; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             let matchedAttr = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for (const attr of parsedAttrs) { | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:28 +02:00
										 |  |  |                 if (clickIndex > attr.startIndex && clickIndex <= attr.endIndex) { | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |                     matchedAttr = attr; | 
					
						
							|  |  |  |                     break; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-17 23:54:18 +02:00
										 |  |  |             setTimeout(() => { | 
					
						
							| 
									
										
										
										
											2020-08-21 23:08:53 +02:00
										 |  |  |                 if (matchedAttr) { | 
					
						
							|  |  |  |                     this.$editor.tooltip('hide'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     this.attributeDetailWidget.showAttributeDetail({ | 
					
						
							|  |  |  |                         allAttributes: parsedAttrs, | 
					
						
							|  |  |  |                         attribute: matchedAttr, | 
					
						
							|  |  |  |                         isOwned: true, | 
					
						
							|  |  |  |                         x: e.pageX, | 
					
						
							|  |  |  |                         y: e.pageY | 
					
						
							|  |  |  |                     }); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 else { | 
					
						
							|  |  |  |                     this.showHelpTooltip(); | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-08-17 23:54:18 +02:00
										 |  |  |             }, 100); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-08-21 23:08:53 +02:00
										 |  |  |         else { | 
					
						
							|  |  |  |             this.showHelpTooltip(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-28 22:52:57 +02:00
										 |  |  |     showHelpTooltip() { | 
					
						
							| 
									
										
										
										
											2020-08-21 23:08:53 +02:00
										 |  |  |         this.attributeDetailWidget.hide(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.$editor.tooltip({ | 
					
						
							|  |  |  |             trigger: 'focus', | 
					
						
							|  |  |  |             html: true, | 
					
						
							|  |  |  |             title: HELP_TEXT, | 
					
						
							|  |  |  |             placement: 'bottom', | 
					
						
							| 
									
										
										
										
											2020-09-01 22:47:36 +02:00
										 |  |  |             offset: "0,30" | 
					
						
							| 
									
										
										
										
											2020-08-21 23:08:53 +02:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2020-09-01 23:47:56 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this.$editor.tooltip('show'); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getClickIndex(pos) { | 
					
						
							|  |  |  |         let clickIndex = pos.offset - pos.textNode.startOffset; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let curNode = pos.textNode; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         while (curNode.previousSibling) { | 
					
						
							|  |  |  |             curNode = curNode.previousSibling; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (curNode.name === 'reference') { | 
					
						
							|  |  |  |                 clickIndex += curNode._attrs.get('notePath').length + 1; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 clickIndex += curNode.data.length; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return clickIndex; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async loadReferenceLinkTitle(noteId, $el) { | 
					
						
							| 
									
										
										
										
											2021-04-16 22:57:37 +02:00
										 |  |  |         const note = await froca.getNote(noteId, true); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         let title; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!note) { | 
					
						
							|  |  |  |             title = '[missing]'; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         else { | 
					
						
							| 
									
										
										
										
											2020-12-18 22:35:40 +01:00
										 |  |  |             title = note.isDeleted ? `${note.title} (deleted)` : note.title; | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $el.text(title); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     async refreshWithNote(note) { | 
					
						
							| 
									
										
										
										
											2020-07-18 00:20:24 +02:00
										 |  |  |         await this.renderOwnedAttributes(note.getOwnedAttributes(), true); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-18 00:20:24 +02:00
										 |  |  |     async renderOwnedAttributes(ownedAttributes, saved) { | 
					
						
							| 
									
										
										
										
											2020-09-04 23:35:10 +02:00
										 |  |  |         ownedAttributes = ownedAttributes.filter(oa => !oa.isDeleted); | 
					
						
							| 
									
										
										
										
											2022-06-19 12:18:13 +02:00
										 |  |  |         // attrs are not resorted if position changes after initial load
 | 
					
						
							|  |  |  |         ownedAttributes.sort((a, b) => a.position < b.position ? -1 : 1); | 
					
						
							| 
									
										
										
										
											2020-09-04 23:35:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-08 21:45:07 +02:00
										 |  |  |         let htmlAttrs = (await attributeRenderer.renderAttributes(ownedAttributes, true)).html(); | 
					
						
							| 
									
										
										
										
											2020-09-03 22:37:51 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (htmlAttrs.length > 0) { | 
					
						
							|  |  |  |             htmlAttrs += " "; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.textEditor.setData(htmlAttrs); | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-18 00:20:24 +02:00
										 |  |  |         if (saved) { | 
					
						
							|  |  |  |             this.lastSavedContent = this.textEditor.getData(); | 
					
						
							| 
									
										
										
										
											2020-07-17 23:55:59 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-18 00:20:24 +02:00
										 |  |  |             this.$saveAttributesButton.fadeOut(0); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-21 22:08:54 +02:00
										 |  |  |     async createNoteForReferenceLink(title) { | 
					
						
							| 
									
										
										
										
											2022-06-02 14:16:49 +02:00
										 |  |  |         const {note} = await noteCreateService.createNoteWithTypePrompt(this.notePath, { | 
					
						
							| 
									
										
										
										
											2020-09-21 22:08:54 +02:00
										 |  |  |             activate: false, | 
					
						
							|  |  |  |             title: title | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return treeService.getSomeNotePath(note); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-21 23:08:39 +02:00
										 |  |  |     async updateAttributeList(attributes) { | 
					
						
							|  |  |  |         await this.renderOwnedAttributes(attributes, false); | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-08-12 00:02:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-01 20:24:58 +01:00
										 |  |  |     focus() { | 
					
						
							|  |  |  |         this.$editor.trigger('focus'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.textEditor.model.change( writer => { | 
					
						
							|  |  |  |             const positionAt = writer.createPositionAt(this.textEditor.model.document.getRoot(), 'end'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             writer.setSelection(positionAt); | 
					
						
							|  |  |  |         } ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-12 00:02:19 +02:00
										 |  |  |     entitiesReloadedEvent({loadResults}) { | 
					
						
							| 
									
										
										
										
											2021-08-25 22:49:24 +02:00
										 |  |  |         if (loadResults.getAttributes(this.componentId).find(attr => attributeService.isAffecting(attr, this.note))) { | 
					
						
							| 
									
										
										
										
											2020-08-12 00:02:19 +02:00
										 |  |  |             this.refresh(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-07-17 00:08:28 +02:00
										 |  |  | } |