mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			250 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			250 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import TabAwareWidget from "./tab_aware_widget.js";
 | |
| import libraryLoader from "../services/library_loader.js";
 | |
| import noteAutocompleteService from "../services/note_autocomplete.js";
 | |
| import treeCache from "../services/tree_cache.js";
 | |
| import server from "../services/server.js";
 | |
| import utils from "../services/utils.js";
 | |
| import ws from "../services/ws.js";
 | |
| import SpacedUpdate from "../services/spaced_update.js";
 | |
| import attributesParser from "../services/attribute_parser.js";
 | |
| 
 | |
| const mentionSetup = {
 | |
|     feeds: [
 | |
|         {
 | |
|             marker: '@',
 | |
|             feed: queryText => {
 | |
|                 return new Promise((res, rej) => {
 | |
|                     noteAutocompleteService.autocompleteSource(queryText, rows => {
 | |
|                         res(rows.map(row => {
 | |
|                             return {
 | |
|                                 id: '@' + row.notePathTitle,
 | |
|                                 name: row.notePathTitle,
 | |
|                                 link: '#' + row.notePath,
 | |
|                                 notePath: row.notePath,
 | |
|                                 highlightedNotePathTitle: row.highlightedNotePathTitle
 | |
|                             }
 | |
|                         }));
 | |
|                     });
 | |
|                 });
 | |
|             },
 | |
|             itemRenderer: item => {
 | |
|                 const itemElement = document.createElement('span');
 | |
| 
 | |
|                 itemElement.classList.add('mentions-item');
 | |
|                 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 {
 | |
|                         id: '#' + name,
 | |
|                         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 {
 | |
|                         id: '~' + name,
 | |
|                         name: name
 | |
|                     }
 | |
|                 });
 | |
|             },
 | |
|             minimumCharacters: 0,
 | |
|             attributeMention: true
 | |
|         }
 | |
|     ]
 | |
| };
 | |
| 
 | |
| const TPL = `
 | |
| <div class="note-attributes">
 | |
| <div class="note-attributes-editor"></div>
 | |
| </div>
 | |
| `;
 | |
| 
 | |
| export default class NoteAttributesWidget extends TabAwareWidget {
 | |
|     constructor() {
 | |
|         super();
 | |
| 
 | |
|         this.spacedUpdate = new SpacedUpdate(async () => {
 | |
|             const content = this.textEditor.getData();
 | |
| 
 | |
|             this.parse(content);
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     doRender() {
 | |
|         this.$widget = $(TPL);
 | |
|         this.$editor = this.$widget.find('.note-attributes-editor');
 | |
|         this.initialized = this.initEditor();
 | |
| 
 | |
|         this.$editor.keypress(async e => {
 | |
|             const keycode = (e.keyCode ? e.keyCode : e.which);
 | |
|             if (keycode === 13) {
 | |
|                 const attributes = attributesParser.lexAndParse(this.textEditor.getData());
 | |
| 
 | |
|                 await server.put(`notes/${this.noteId}/attributes2`, attributes, this.componentId);
 | |
| 
 | |
|                 console.log("Saved!");
 | |
|             }
 | |
|         })
 | |
| 
 | |
|         return this.$widget;
 | |
|     }
 | |
| 
 | |
|     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", () => {
 | |
|             const pos = this.textEditor.model.document.selection.getFirstPosition();
 | |
|             console.log(pos.textNode && pos.textNode.data, pos.parent.textNode && pos.parent.textNode.data, pos.offset);
 | |
|         });
 | |
| 
 | |
|         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.model.document.on('change:data', () => this.spacedUpdate.scheduleUpdate());
 | |
| 
 | |
|         await import(/* webpackIgnore: true */'../../libraries/ckeditor/inspector.js');
 | |
|         CKEditorInspector.attach(this.textEditor);
 | |
|     }
 | |
| 
 | |
|     async loadReferenceLinkTitle(noteId, $el) {
 | |
|         const note = await treeCache.getNote(noteId, true);
 | |
| 
 | |
|         let title;
 | |
| 
 | |
|         if (!note) {
 | |
|             title = '[missing]';
 | |
|         }
 | |
|         else if (!note.isDeleted) {
 | |
|             title = note.title;
 | |
|         }
 | |
|         else {
 | |
|             title = note.isErased ? '[erased]' : `${note.title} (deleted)`;
 | |
|         }
 | |
| 
 | |
|         $el.text(title);
 | |
|     }
 | |
| 
 | |
|     async refreshWithNote(note) {
 | |
|         const ownedAttributes = note.getOwnedAttributes();
 | |
|         const $attributesContainer = $("<div>");
 | |
| 
 | |
|         await this.renderAttributes(ownedAttributes, $attributesContainer);
 | |
| console.log($attributesContainer.html());
 | |
|         this.textEditor.setData($attributesContainer.html());
 | |
|     }
 | |
| 
 | |
|     createNoteLink(noteId) {
 | |
|         return $("<a>", {
 | |
|             href: '#' + noteId,
 | |
|             class: 'reference-link',
 | |
|             'data-note-path': noteId
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     async renderAttributes(attributes, $container) {
 | |
|         for (const attribute of attributes) {
 | |
|             if (attribute.type === 'label') {
 | |
|                 $container.append(utils.formatLabel(attribute) + " ");
 | |
|             } else if (attribute.type === 'relation') {
 | |
|                 if (attribute.isAutoLink) {
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 if (attribute.value) {
 | |
|                     $container.append('~' + attribute.name + "=");
 | |
|                     $container.append(this.createNoteLink(attribute.value));
 | |
|                     $container.append(" ");
 | |
|                 } else {
 | |
|                     ws.logError(`Relation ${attribute.attributeId} has empty target`);
 | |
|                 }
 | |
|             } else {
 | |
|                 ws.logError("Unknown attr type: " + attribute.type);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     parse(content) {
 | |
|         if (content.startsWith('<p>')) {
 | |
|             content = content.substr(3);
 | |
|         }
 | |
| 
 | |
|         if (content.endsWith('</p>')) {
 | |
|             content = content.substr(0, content - 4);
 | |
|         }
 | |
| 
 | |
|         console.log(content);
 | |
|     }
 | |
| }
 |