mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-11-04 15:11:31 +08:00 
			
		
		
		
	wip attachment support
This commit is contained in:
		
							parent
							
								
									2bc78ccafb
								
							
						
					
					
						commit
						5d6d9ab6d6
					
				@ -7,8 +7,9 @@ CREATE TABLE IF NOT EXISTS "attachments"
 | 
				
			|||||||
    title         TEXT not null,
 | 
					    title         TEXT not null,
 | 
				
			||||||
    isProtected    INT  not null DEFAULT 0,
 | 
					    isProtected    INT  not null DEFAULT 0,
 | 
				
			||||||
    blobId    TEXT DEFAULT null,
 | 
					    blobId    TEXT DEFAULT null,
 | 
				
			||||||
    utcDateScheduledForDeletionSince TEXT DEFAULT NULL,
 | 
					    dateModified TEXT NOT NULL,
 | 
				
			||||||
    utcDateModified TEXT not null,
 | 
					    utcDateModified TEXT not null,
 | 
				
			||||||
 | 
					    utcDateScheduledForDeletionSince TEXT DEFAULT NULL,
 | 
				
			||||||
    isDeleted    INT  not null,
 | 
					    isDeleted    INT  not null,
 | 
				
			||||||
    deleteId    TEXT DEFAULT NULL);
 | 
					    deleteId    TEXT DEFAULT NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -118,8 +118,9 @@ CREATE TABLE IF NOT EXISTS "attachments"
 | 
				
			|||||||
    title         TEXT not null,
 | 
					    title         TEXT not null,
 | 
				
			||||||
    isProtected    INT  not null DEFAULT 0,
 | 
					    isProtected    INT  not null DEFAULT 0,
 | 
				
			||||||
    blobId    TEXT DEFAULT null,
 | 
					    blobId    TEXT DEFAULT null,
 | 
				
			||||||
    utcDateScheduledForDeletionSince TEXT DEFAULT NULL,
 | 
					    dateModified TEXT NOT NULL,
 | 
				
			||||||
    utcDateModified TEXT not null,
 | 
					    utcDateModified TEXT not null,
 | 
				
			||||||
 | 
					    utcDateScheduledForDeletionSince TEXT DEFAULT NULL,
 | 
				
			||||||
    isDeleted    INT  not null,
 | 
					    isDeleted    INT  not null,
 | 
				
			||||||
    deleteId    TEXT DEFAULT NULL);
 | 
					    deleteId    TEXT DEFAULT NULL);
 | 
				
			||||||
CREATE INDEX IDX_attachments_parentId_role
 | 
					CREATE INDEX IDX_attachments_parentId_role
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,8 @@ const becca = require('../becca');
 | 
				
			|||||||
const AbstractBeccaEntity = require("./abstract_becca_entity");
 | 
					const AbstractBeccaEntity = require("./abstract_becca_entity");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 | 
					 * FIXME: how to order attachments?
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 * Attachment represent data related/attached to the note. Conceptually similar to attributes, but intended for
 | 
					 * Attachment represent data related/attached to the note. Conceptually similar to attributes, but intended for
 | 
				
			||||||
 * larger amounts of data and generally not accessible to the user.
 | 
					 * larger amounts of data and generally not accessible to the user.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@ -45,9 +47,11 @@ class BAttachment extends AbstractBeccaEntity {
 | 
				
			|||||||
        /** @type {boolean} */
 | 
					        /** @type {boolean} */
 | 
				
			||||||
        this.isProtected = !!row.isProtected;
 | 
					        this.isProtected = !!row.isProtected;
 | 
				
			||||||
        /** @type {string} */
 | 
					        /** @type {string} */
 | 
				
			||||||
        this.utcDateScheduledForDeletionSince = row.utcDateScheduledForDeletionSince;
 | 
					        this.dateModified = row.dateModified;
 | 
				
			||||||
        /** @type {string} */
 | 
					        /** @type {string} */
 | 
				
			||||||
        this.utcDateModified = row.utcDateModified;
 | 
					        this.utcDateModified = row.utcDateModified;
 | 
				
			||||||
 | 
					        /** @type {string} */
 | 
				
			||||||
 | 
					        this.utcDateScheduledForDeletionSince = row.utcDateScheduledForDeletionSince;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getNote() {
 | 
					    getNote() {
 | 
				
			||||||
@ -76,6 +80,7 @@ class BAttachment extends AbstractBeccaEntity {
 | 
				
			|||||||
    beforeSaving() {
 | 
					    beforeSaving() {
 | 
				
			||||||
        super.beforeSaving();
 | 
					        super.beforeSaving();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.dateModified = dateUtils.localNowDateTime();
 | 
				
			||||||
        this.utcDateModified = dateUtils.utcNowDateTime();
 | 
					        this.utcDateModified = dateUtils.utcNowDateTime();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -89,8 +94,9 @@ class BAttachment extends AbstractBeccaEntity {
 | 
				
			|||||||
            blobId: this.blobId,
 | 
					            blobId: this.blobId,
 | 
				
			||||||
            isProtected: !!this.isProtected,
 | 
					            isProtected: !!this.isProtected,
 | 
				
			||||||
            isDeleted: false,
 | 
					            isDeleted: false,
 | 
				
			||||||
            utcDateScheduledForDeletionSince: this.utcDateScheduledForDeletionSince,
 | 
					            dateModified: this.dateModified,
 | 
				
			||||||
            utcDateModified: this.utcDateModified
 | 
					            utcDateModified: this.utcDateModified,
 | 
				
			||||||
 | 
					            utcDateScheduledForDeletionSince: this.utcDateScheduledForDeletionSince
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -38,7 +38,7 @@ export default class Entrypoints extends Component {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        await ws.waitForMaxKnownEntityChangeId();
 | 
					        await ws.waitForMaxKnownEntityChangeId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await appContext.tabManager.openTabWithNoteWithHoisting(note.noteId, true);
 | 
					        await appContext.tabManager.openTabWithNoteWithHoisting(note.noteId, {activate: true});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        appContext.triggerEvent('focusAndSelectTitle', {isNewNote: true});
 | 
					        appContext.triggerEvent('focusAndSelectTitle', {isNewNote: true});
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -135,7 +135,7 @@ export default class Entrypoints extends Component {
 | 
				
			|||||||
        utils.reloadFrontendApp("Switching to mobile version");
 | 
					        utils.reloadFrontendApp("Switching to mobile version");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async openInWindowCommand({notePath, hoistedNoteId}) {
 | 
					    async openInWindowCommand({notePath, hoistedNoteId, viewScope}) {
 | 
				
			||||||
        if (!hoistedNoteId) {
 | 
					        if (!hoistedNoteId) {
 | 
				
			||||||
            hoistedNoteId = 'root';
 | 
					            hoistedNoteId = 'root';
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -143,10 +143,10 @@ export default class Entrypoints extends Component {
 | 
				
			|||||||
        if (utils.isElectron()) {
 | 
					        if (utils.isElectron()) {
 | 
				
			||||||
            const {ipcRenderer} = utils.dynamicRequire('electron');
 | 
					            const {ipcRenderer} = utils.dynamicRequire('electron');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ipcRenderer.send('create-extra-window', {notePath, hoistedNoteId});
 | 
					            ipcRenderer.send('create-extra-window', {notePath, hoistedNoteId, viewScope});
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
            const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extra=1#${notePath}`;
 | 
					            const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=1&extraHoistedNoteId=${hoistedNoteId}&extraViewScope=${JSON.stringify(viewScope)}#${notePath}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            window.open(url, '', 'width=1000,height=800');
 | 
					            window.open(url, '', 'width=1000,height=800');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -53,8 +53,8 @@ class NoteContext extends Component {
 | 
				
			|||||||
        this.notePath = resolvedNotePath;
 | 
					        this.notePath = resolvedNotePath;
 | 
				
			||||||
        ({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath));
 | 
					        ({noteId: this.noteId, parentNoteId: this.parentNoteId} = treeService.getNoteIdAndParentIdFromNotePath(resolvedNotePath));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.resetViewScope();
 | 
					        this.viewScope = opts.viewScope || {};
 | 
				
			||||||
        this.viewScope.viewMode = opts.viewMode || "default";
 | 
					        this.viewScope.viewMode = this.viewScope.viewMode || "default";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.saveToRecentNotes(resolvedNotePath);
 | 
					        this.saveToRecentNotes(resolvedNotePath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -187,7 +187,7 @@ class NoteContext extends Component {
 | 
				
			|||||||
            notePath: this.notePath,
 | 
					            notePath: this.notePath,
 | 
				
			||||||
            hoistedNoteId: this.hoistedNoteId,
 | 
					            hoistedNoteId: this.hoistedNoteId,
 | 
				
			||||||
            active: this.isActive(),
 | 
					            active: this.isActive(),
 | 
				
			||||||
            viewMode: this.viewScope.viewMode
 | 
					            viewScope: this.viewScope
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -117,7 +117,12 @@ export default class RootCommandExecutor extends Component {
 | 
				
			|||||||
        const notePath = appContext.tabManager.getActiveContextNotePath();
 | 
					        const notePath = appContext.tabManager.getActiveContextNotePath();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (notePath) {
 | 
					        if (notePath) {
 | 
				
			||||||
            await appContext.tabManager.openContextWithNote(notePath, { activate: true, viewMode: 'source' });
 | 
					            await appContext.tabManager.openContextWithNote(notePath, {
 | 
				
			||||||
 | 
					                activate: true,
 | 
				
			||||||
 | 
					                viewScope: {
 | 
				
			||||||
 | 
					                    viewMode: 'source'
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -125,7 +130,25 @@ export default class RootCommandExecutor extends Component {
 | 
				
			|||||||
        const notePath = appContext.tabManager.getActiveContextNotePath();
 | 
					        const notePath = appContext.tabManager.getActiveContextNotePath();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (notePath) {
 | 
					        if (notePath) {
 | 
				
			||||||
            await appContext.tabManager.openContextWithNote(notePath, { activate: true, viewMode: 'attachments' });
 | 
					            await appContext.tabManager.openContextWithNote(notePath, {
 | 
				
			||||||
 | 
					                activate: true,
 | 
				
			||||||
 | 
					                viewScope: {
 | 
				
			||||||
 | 
					                    viewMode: 'attachments'
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async showAttachmentDetailCommand() {
 | 
				
			||||||
 | 
					        const notePath = appContext.tabManager.getActiveContextNotePath();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (notePath) {
 | 
				
			||||||
 | 
					            await appContext.tabManager.openContextWithNote(notePath, {
 | 
				
			||||||
 | 
					                activate: true,
 | 
				
			||||||
 | 
					                viewScope: {
 | 
				
			||||||
 | 
					                    viewMode: 'attachments'
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -86,7 +86,8 @@ export default class TabManager extends Component {
 | 
				
			|||||||
                filteredTabs.push({
 | 
					                filteredTabs.push({
 | 
				
			||||||
                    notePath: notePathInUrl || 'root',
 | 
					                    notePath: notePathInUrl || 'root',
 | 
				
			||||||
                    active: true,
 | 
					                    active: true,
 | 
				
			||||||
                    hoistedNoteId: glob.extraHoistedNoteId || 'root'
 | 
					                    hoistedNoteId: glob.extraHoistedNoteId || 'root',
 | 
				
			||||||
 | 
					                    viewScope: glob.extraViewScope || {}
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -101,7 +102,7 @@ export default class TabManager extends Component {
 | 
				
			|||||||
                        ntxId: tab.ntxId,
 | 
					                        ntxId: tab.ntxId,
 | 
				
			||||||
                        mainNtxId: tab.mainNtxId,
 | 
					                        mainNtxId: tab.mainNtxId,
 | 
				
			||||||
                        hoistedNoteId: tab.hoistedNoteId,
 | 
					                        hoistedNoteId: tab.hoistedNoteId,
 | 
				
			||||||
                        viewMode: tab.viewMode
 | 
					                        viewScope: tab.viewScope
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
@ -271,7 +272,7 @@ export default class TabManager extends Component {
 | 
				
			|||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * If the requested notePath is within current note hoisting scope then keep the note hoisting also for the new tab.
 | 
					     * If the requested notePath is within current note hoisting scope then keep the note hoisting also for the new tab.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async openTabWithNoteWithHoisting(notePath, activate = false) {
 | 
					    async openTabWithNoteWithHoisting(notePath, opts = {}) {
 | 
				
			||||||
        const noteContext = this.getActiveContext();
 | 
					        const noteContext = this.getActiveContext();
 | 
				
			||||||
        let hoistedNoteId = 'root';
 | 
					        let hoistedNoteId = 'root';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -283,7 +284,9 @@ export default class TabManager extends Component {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return this.openContextWithNote(notePath, { activate, hoistedNoteId });
 | 
					        opts.hoistedNoteId = hoistedNoteId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.openContextWithNote(notePath, opts);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async openContextWithNote(notePath, opts = {}) {
 | 
					    async openContextWithNote(notePath, opts = {}) {
 | 
				
			||||||
@ -291,7 +294,7 @@ export default class TabManager extends Component {
 | 
				
			|||||||
        const ntxId = opts.ntxId || null;
 | 
					        const ntxId = opts.ntxId || null;
 | 
				
			||||||
        const mainNtxId = opts.mainNtxId || null;
 | 
					        const mainNtxId = opts.mainNtxId || null;
 | 
				
			||||||
        const hoistedNoteId = opts.hoistedNoteId || 'root';
 | 
					        const hoistedNoteId = opts.hoistedNoteId || 'root';
 | 
				
			||||||
        const viewMode = opts.viewMode || "default";
 | 
					        const viewScope = opts.viewScope || { viewMode: "default" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const noteContext = await this.openEmptyTab(ntxId, hoistedNoteId, mainNtxId);
 | 
					        const noteContext = await this.openEmptyTab(ntxId, hoistedNoteId, mainNtxId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -299,7 +302,7 @@ export default class TabManager extends Component {
 | 
				
			|||||||
            await noteContext.setNote(notePath, {
 | 
					            await noteContext.setNote(notePath, {
 | 
				
			||||||
                // if activate is false then send normal noteSwitched event
 | 
					                // if activate is false then send normal noteSwitched event
 | 
				
			||||||
                triggerSwitchEvent: !activate,
 | 
					                triggerSwitchEvent: !activate,
 | 
				
			||||||
                viewMode: viewMode
 | 
					                viewScope: viewScope
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										33
									
								
								src/public/app/entities/fattachment.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/public/app/entities/fattachment.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					class FAttachment {
 | 
				
			||||||
 | 
					    constructor(froca, row) {
 | 
				
			||||||
 | 
					        this.froca = froca;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.update(row);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    update(row) {
 | 
				
			||||||
 | 
					        /** @type {string} */
 | 
				
			||||||
 | 
					        this.attachmentId = row.attachmentId;
 | 
				
			||||||
 | 
					        /** @type {string} */
 | 
				
			||||||
 | 
					        this.parentId = row.parentId;
 | 
				
			||||||
 | 
					        /** @type {string} */
 | 
				
			||||||
 | 
					        this.role = row.role;
 | 
				
			||||||
 | 
					        /** @type {string} */
 | 
				
			||||||
 | 
					        this.mime = row.mime;
 | 
				
			||||||
 | 
					        /** @type {string} */
 | 
				
			||||||
 | 
					        this.title = row.title;
 | 
				
			||||||
 | 
					        /** @type {string} */
 | 
				
			||||||
 | 
					        this.dateModified = row.dateModified;
 | 
				
			||||||
 | 
					        /** @type {string} */
 | 
				
			||||||
 | 
					        this.utcDateModified = row.utcDateModified;
 | 
				
			||||||
 | 
					        /** @type {string} */
 | 
				
			||||||
 | 
					        this.utcDateScheduledForDeletionSince = row.utcDateScheduledForDeletionSince;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.froca.attachments[this.attachmentId] = this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {FNote} */
 | 
				
			||||||
 | 
					    getNote() {
 | 
				
			||||||
 | 
					        return this.froca.notes[this.parentId];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -51,6 +51,9 @@ class FNote {
 | 
				
			|||||||
        /** @type {Object.<string, string>} */
 | 
					        /** @type {Object.<string, string>} */
 | 
				
			||||||
        this.childToBranch = {};
 | 
					        this.childToBranch = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** @type {FAttachment[]|null} */
 | 
				
			||||||
 | 
					        this.attachments = null; // lazy loaded
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.update(row);
 | 
					        this.update(row);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -225,6 +228,23 @@ class FNote {
 | 
				
			|||||||
        return await this.froca.getNotes(this.children);
 | 
					        return await this.froca.getNotes(this.children);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {Promise<FAttachment[]>} */
 | 
				
			||||||
 | 
					    async getAttachments() {
 | 
				
			||||||
 | 
					        if (!this.attachments) {
 | 
				
			||||||
 | 
					            this.attachments = (await server.get(`notes/${this.noteId}/attachments`))
 | 
				
			||||||
 | 
					                .map(row => new FAttachment(froca, row));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.attachments;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {Promise<FAttachment>} */
 | 
				
			||||||
 | 
					    async getAttachmentById(attachmentId) {
 | 
				
			||||||
 | 
					        const attachments = await this.getAttachments();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return attachments.find(att => att.attachmentId === attachmentId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @param {string} [type] - (optional) attribute type to filter
 | 
					     * @param {string} [type] - (optional) attribute type to filter
 | 
				
			||||||
     * @param {string} [name] - (optional) attribute name to filter
 | 
					     * @param {string} [name] - (optional) attribute name to filter
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,5 @@
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 | 
					 * FIXME: probably make it a FBlob
 | 
				
			||||||
 * Complements the FNote with the main note content and other extra attributes
 | 
					 * Complements the FNote with the main note content and other extra attributes
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class FNoteComplement {
 | 
					class FNoteComplement {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import contextMenu from "./context_menu.js";
 | 
					import contextMenu from "./context_menu.js";
 | 
				
			||||||
import appContext from "../components/app_context.js";
 | 
					import appContext from "../components/app_context.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function openContextMenu(notePath, hoistedNoteId, e) {
 | 
					function openContextMenu(notePath, e, viewScope = {}, hoistedNoteId = null) {
 | 
				
			||||||
    contextMenu.show({
 | 
					    contextMenu.show({
 | 
				
			||||||
        x: e.pageX,
 | 
					        x: e.pageX,
 | 
				
			||||||
        y: e.pageY,
 | 
					        y: e.pageY,
 | 
				
			||||||
@ -16,16 +16,16 @@ function openContextMenu(notePath, hoistedNoteId, e) {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (command === 'openNoteInNewTab') {
 | 
					            if (command === 'openNoteInNewTab') {
 | 
				
			||||||
                appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId });
 | 
					                appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId, viewScope });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (command === 'openNoteInNewSplit') {
 | 
					            else if (command === 'openNoteInNewSplit') {
 | 
				
			||||||
                const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
 | 
					                const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
 | 
				
			||||||
                const {ntxId} = subContexts[subContexts.length - 1];
 | 
					                const {ntxId} = subContexts[subContexts.length - 1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath, hoistedNoteId});
 | 
					                appContext.triggerCommand("openNewNoteSplit", {ntxId, notePath, hoistedNoteId, viewScope});
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (command === 'openNoteInNewWindow') {
 | 
					            else if (command === 'openNoteInNewWindow') {
 | 
				
			||||||
                appContext.triggerCommand('openInWindow', {notePath, hoistedNoteId});
 | 
					                appContext.triggerCommand('openInWindow', {notePath, hoistedNoteId, viewScope});
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
				
			|||||||
@ -34,6 +34,10 @@ class Froca {
 | 
				
			|||||||
        /** @type {Object.<string, FAttribute>} */
 | 
					        /** @type {Object.<string, FAttribute>} */
 | 
				
			||||||
        this.attributes = {};
 | 
					        this.attributes = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /** @type {Object.<string, FAttachment>} */
 | 
				
			||||||
 | 
					        this.attachments = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // FIXME
 | 
				
			||||||
        /** @type {Object.<string, Promise<FNoteComplement>>} */
 | 
					        /** @type {Object.<string, Promise<FNoteComplement>>} */
 | 
				
			||||||
        this.blobPromises = {};
 | 
					        this.blobPromises = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -311,6 +315,7 @@ class Froca {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
					     * // FIXME
 | 
				
			||||||
     * @returns {Promise<FNoteComplement>}
 | 
					     * @returns {Promise<FNoteComplement>}
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    async getNoteComplement(noteId) {
 | 
					    async getNoteComplement(noteId) {
 | 
				
			||||||
 | 
				
			|||||||
@ -34,7 +34,7 @@ async function processEntityChanges(entityChanges) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                loadResults.addOption(ec.entity.name);
 | 
					                loadResults.addOption(ec.entity.name);
 | 
				
			||||||
            } else if (ec.entityName === 'attachments') {
 | 
					            } else if (ec.entityName === 'attachments') {
 | 
				
			||||||
                loadResults.addAttachment(ec.entity);
 | 
					                processAttachment(loadResults, ec);
 | 
				
			||||||
            } else if (ec.entityName === 'etapi_tokens') {
 | 
					            } else if (ec.entityName === 'etapi_tokens') {
 | 
				
			||||||
                // NOOP
 | 
					                // NOOP
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -231,6 +231,43 @@ function processAttributeChange(loadResults, ec) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function processAttachment(loadResults, ec) {
 | 
				
			||||||
 | 
					    if (ec.isErased && ec.entityId in froca.attachments) {
 | 
				
			||||||
 | 
					        utils.reloadFrontendApp(`${ec.entityName} ${ec.entityId} is erased, need to do complete reload.`);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const attachment = froca.attachments[ec.entityId];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ec.isErased || ec.entity?.isDeleted) {
 | 
				
			||||||
 | 
					        if (attachment) {
 | 
				
			||||||
 | 
					            const note = attachment.getNote();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (note && note.attachments) {
 | 
				
			||||||
 | 
					                note.attachments = note.attachments.filter(att => att.attachmentId !== attachment.attachmentId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            loadResults.addAttachment(ec.entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            delete froca.attachments[ec.entityId];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (attachment) {
 | 
				
			||||||
 | 
					        attachment.update(ec.entity);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const note = froca.notes[ec.entity.parentId];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (note && note.attachments) {
 | 
				
			||||||
 | 
					            note.attachments.push(new FAttachment(froca, ec.entity));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    loadResults.addAttachment(ec.entity);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    processEntityChanges
 | 
					    processEntityChanges
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -87,7 +87,16 @@ function getNotePathFromLink($link) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const url = $link.attr('href');
 | 
					    const url = $link.attr('href');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return url ? getNotePathFromUrl(url) : null;
 | 
					    const notePath = url ? getNotePathFromUrl(url) : null;
 | 
				
			||||||
 | 
					    const viewScope = {
 | 
				
			||||||
 | 
					        viewMode: $link.attr('data-view-mode'),
 | 
				
			||||||
 | 
					        attachmentId: $link.attr('data-attachment-id'),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        notePath,
 | 
				
			||||||
 | 
					        viewScope
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function goToLink(evt) {
 | 
					function goToLink(evt) {
 | 
				
			||||||
@ -101,22 +110,25 @@ function goToLink(evt) {
 | 
				
			|||||||
    evt.preventDefault();
 | 
					    evt.preventDefault();
 | 
				
			||||||
    evt.stopPropagation();
 | 
					    evt.stopPropagation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const notePath = getNotePathFromLink($link);
 | 
					    const {notePath, viewScope} = getNotePathFromLink($link);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const ctrlKey = utils.isCtrlKey(evt);
 | 
					    const ctrlKey = utils.isCtrlKey(evt);
 | 
				
			||||||
 | 
					    const isLeftClick = evt.which === 1;
 | 
				
			||||||
 | 
					    const isMiddleClick = evt.which === 2;
 | 
				
			||||||
 | 
					    const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (notePath) {
 | 
					    if (notePath) {
 | 
				
			||||||
        if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
 | 
					        if (openInNewTab) {
 | 
				
			||||||
            appContext.tabManager.openTabWithNoteWithHoisting(notePath);
 | 
					            appContext.tabManager.openTabWithNoteWithHoisting(notePath, { viewScope });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (evt.which === 1) {
 | 
					        else if (isLeftClick) {
 | 
				
			||||||
            const ntxId = $(evt.target).closest("[data-ntx-id]").attr("data-ntx-id");
 | 
					            const ntxId = $(evt.target).closest("[data-ntx-id]").attr("data-ntx-id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const noteContext = ntxId
 | 
					            const noteContext = ntxId
 | 
				
			||||||
                ? appContext.tabManager.getNoteContextById(ntxId)
 | 
					                ? appContext.tabManager.getNoteContextById(ntxId)
 | 
				
			||||||
                : appContext.tabManager.getActiveContext();
 | 
					                : appContext.tabManager.getActiveContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            noteContext.setNote(notePath).then(() => {
 | 
					            noteContext.setNote(notePath, { viewScope }).then(() => {
 | 
				
			||||||
                if (noteContext !== appContext.tabManager.getActiveContext()) {
 | 
					                if (noteContext !== appContext.tabManager.getActiveContext()) {
 | 
				
			||||||
                    appContext.tabManager.activateNoteContext(noteContext.ntxId);
 | 
					                    appContext.tabManager.activateNoteContext(noteContext.ntxId);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -124,7 +136,7 @@ function goToLink(evt) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {
 | 
					    else {
 | 
				
			||||||
        if ((evt.which === 1 && ctrlKey) || evt.which === 2
 | 
					        if (openInNewTab
 | 
				
			||||||
            || $link.hasClass("ck-link-actions__preview") // within edit link dialog single click suffices
 | 
					            || $link.hasClass("ck-link-actions__preview") // within edit link dialog single click suffices
 | 
				
			||||||
            || $link.closest("[contenteditable]").length === 0 // outside of CKEditor single click suffices
 | 
					            || $link.closest("[contenteditable]").length === 0 // outside of CKEditor single click suffices
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
@ -147,7 +159,7 @@ function goToLink(evt) {
 | 
				
			|||||||
function linkContextMenu(e) {
 | 
					function linkContextMenu(e) {
 | 
				
			||||||
    const $link = $(e.target).closest("a");
 | 
					    const $link = $(e.target).closest("a");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const notePath = getNotePathFromLink($link);
 | 
					    const {notePath, viewScope} = getNotePathFromLink($link);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!notePath) {
 | 
					    if (!notePath) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
@ -155,7 +167,7 @@ function linkContextMenu(e) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    e.preventDefault();
 | 
					    e.preventDefault();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    linkContextMenuService.openContextMenu(notePath, null, e);
 | 
					    linkContextMenuService.openContextMenu(notePath, e, viewScope, null);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function loadReferenceLinkTitle(noteId, $el) {
 | 
					async function loadReferenceLinkTitle(noteId, $el) {
 | 
				
			||||||
 | 
				
			|||||||
@ -37,7 +37,7 @@ const TPL = `
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <div class="attachment-detail-wrapper">
 | 
					    <div class="attachment-detail-wrapper">
 | 
				
			||||||
        <div class="attachment-title-line">
 | 
					        <div class="attachment-title-line">
 | 
				
			||||||
            <h4 class="attachment-title"></h4>                
 | 
					            <h4 class="attachment-title"><a href="javascript:" data-trigger-command="openAttachmentDetail"></a></h4>                
 | 
				
			||||||
            <div class="attachment-details"></div>
 | 
					            <div class="attachment-details"></div>
 | 
				
			||||||
            <div style="flex: 1 1;"></div>
 | 
					            <div style="flex: 1 1;"></div>
 | 
				
			||||||
            <div class="attachment-actions-container"></div>
 | 
					            <div class="attachment-actions-container"></div>
 | 
				
			||||||
@ -73,7 +73,7 @@ export default class AttachmentDetailWidget extends BasicWidget {
 | 
				
			|||||||
                    .html()
 | 
					                    .html()
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        this.$wrapper = this.$widget.find('.attachment-detail-wrapper');
 | 
					        this.$wrapper = this.$widget.find('.attachment-detail-wrapper');
 | 
				
			||||||
        this.$wrapper.find('.attachment-title').text(this.attachment.title);
 | 
					        this.$wrapper.find('.attachment-title a').text(this.attachment.title);
 | 
				
			||||||
        this.$wrapper.find('.attachment-details')
 | 
					        this.$wrapper.find('.attachment-details')
 | 
				
			||||||
            .text(`Role: ${this.attachment.role}, Size: ${utils.formatSize(this.attachment.contentLength)}`);
 | 
					            .text(`Role: ${this.attachment.role}, Size: ${utils.formatSize(this.attachment.contentLength)}`);
 | 
				
			||||||
        this.$wrapper.find('.attachment-actions-container').append(this.attachmentActionsWidget.render());
 | 
					        this.$wrapper.find('.attachment-actions-container').append(this.attachmentActionsWidget.render());
 | 
				
			||||||
@ -90,9 +90,11 @@ export default class AttachmentDetailWidget extends BasicWidget {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async entitiesReloadedEvent({loadResults}) {
 | 
					    openAttachmentDetailCommand() {
 | 
				
			||||||
        console.log("AttachmentDetailWidget: entitiesReloadedEvent");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async entitiesReloadedEvent({loadResults}) {
 | 
				
			||||||
        const attachmentChange = loadResults.getAttachments().find(att => att.attachmentId === this.attachment.attachmentId);
 | 
					        const attachmentChange = loadResults.getAttachments().find(att => att.attachmentId === this.attachment.attachmentId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (attachmentChange) {
 | 
					        if (attachmentChange) {
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,7 @@ export default class NoteLauncher extends AbstractLauncher {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                const hoistedNoteId = this.getHoistedNoteId();
 | 
					                const hoistedNoteId = this.getHoistedNoteId();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                linkContextMenuService.openContextMenu(targetNoteId, hoistedNoteId, evt);
 | 
					                linkContextMenuService.openContextMenu(targetNoteId, evt, {}, hoistedNoteId);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ export default class OpenNoteButtonWidget extends OnClickButtonWidget {
 | 
				
			|||||||
            .icon(() => this.noteToOpen.getIcon())
 | 
					            .icon(() => this.noteToOpen.getIcon())
 | 
				
			||||||
            .onClick((widget, evt) => this.launch(evt))
 | 
					            .onClick((widget, evt) => this.launch(evt))
 | 
				
			||||||
            .onAuxClick((widget, evt) => this.launch(evt))
 | 
					            .onAuxClick((widget, evt) => this.launch(evt))
 | 
				
			||||||
            .onContextMenu(evt => linkContextMenuService.openContextMenu(this.noteToOpen.noteId, null, evt));
 | 
					            .onContextMenu(evt => linkContextMenuService.openContextMenu(this.noteToOpen.noteId, evt));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async launch(evt) {
 | 
					    async launch(evt) {
 | 
				
			||||||
 | 
				
			|||||||
@ -34,7 +34,7 @@ export default class SplitNoteContainer extends FlexContainer {
 | 
				
			|||||||
        this.child(widget);
 | 
					        this.child(widget);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async openNewNoteSplitEvent({ntxId, notePath, hoistedNoteId}) {
 | 
					    async openNewNoteSplitEvent({ntxId, notePath, hoistedNoteId, viewScope}) {
 | 
				
			||||||
        const mainNtxId = appContext.tabManager.getActiveMainContext().ntxId;
 | 
					        const mainNtxId = appContext.tabManager.getActiveMainContext().ntxId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!ntxId) {
 | 
					        if (!ntxId) {
 | 
				
			||||||
@ -63,7 +63,7 @@ export default class SplitNoteContainer extends FlexContainer {
 | 
				
			|||||||
        await appContext.tabManager.activateNoteContext(noteContext.ntxId);
 | 
					        await appContext.tabManager.activateNoteContext(noteContext.ntxId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (notePath) {
 | 
					        if (notePath) {
 | 
				
			||||||
            await noteContext.setNote(notePath);
 | 
					            await noteContext.setNote(notePath, viewScope);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
            await noteContext.setEmpty();
 | 
					            await noteContext.setEmpty();
 | 
				
			||||||
 | 
				
			|||||||
@ -61,6 +61,10 @@ export default class NoteContextAwareWidget extends BasicWidget {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @param {FNote} note
 | 
				
			||||||
 | 
					     * @returns {Promise<void>}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
    async refreshWithNote(note) {}
 | 
					    async refreshWithNote(note) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async noteSwitchedEvent({noteContext, notePath}) {
 | 
					    async noteSwitchedEvent({noteContext, notePath}) {
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,8 @@ import NoteMapTypeWidget from "./type_widgets/note_map.js";
 | 
				
			|||||||
import WebViewTypeWidget from "./type_widgets/web_view.js";
 | 
					import WebViewTypeWidget from "./type_widgets/web_view.js";
 | 
				
			||||||
import DocTypeWidget from "./type_widgets/doc.js";
 | 
					import DocTypeWidget from "./type_widgets/doc.js";
 | 
				
			||||||
import ContentWidgetTypeWidget from "./type_widgets/content_widget.js";
 | 
					import ContentWidgetTypeWidget from "./type_widgets/content_widget.js";
 | 
				
			||||||
import AttachmentsTypeWidget from "./type_widgets/attachments.js";
 | 
					import AttachmentListTypeWidget from "./type_widgets/attachment_list.js";
 | 
				
			||||||
 | 
					import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TPL = `
 | 
					const TPL = `
 | 
				
			||||||
<div class="note-detail">
 | 
					<div class="note-detail">
 | 
				
			||||||
@ -63,7 +64,8 @@ const typeWidgetClasses = {
 | 
				
			|||||||
    'webView': WebViewTypeWidget,
 | 
					    'webView': WebViewTypeWidget,
 | 
				
			||||||
    'doc': DocTypeWidget,
 | 
					    'doc': DocTypeWidget,
 | 
				
			||||||
    'contentWidget': ContentWidgetTypeWidget,
 | 
					    'contentWidget': ContentWidgetTypeWidget,
 | 
				
			||||||
    'attachments': AttachmentsTypeWidget
 | 
					    'attachmentDetail': AttachmentDetailTypeWidget,
 | 
				
			||||||
 | 
					    'attachmentList': AttachmentListTypeWidget
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class NoteDetailWidget extends NoteContextAwareWidget {
 | 
					export default class NoteDetailWidget extends NoteContextAwareWidget {
 | 
				
			||||||
@ -188,11 +190,12 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let type = note.type;
 | 
					        let type = note.type;
 | 
				
			||||||
 | 
					        const viewScope = this.noteContext.viewScope;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (type === 'text' && this.noteContext.viewScope.viewMode === 'source') {
 | 
					        if (type === 'text' && viewScope.viewMode === 'source') {
 | 
				
			||||||
            type = 'readOnlyCode';
 | 
					            type = 'readOnlyCode';
 | 
				
			||||||
        } else if (this.noteContext.viewScope.viewMode === 'attachments') {
 | 
					        } else if (viewScope.viewMode === 'attachments') {
 | 
				
			||||||
            type = 'attachments';
 | 
					            type = viewScope.attachmentId ? 'attachmentDetail' : 'attachmentList';
 | 
				
			||||||
        } else if (type === 'text' && await this.noteContext.isReadOnly()) {
 | 
					        } else if (type === 'text' && await this.noteContext.isReadOnly()) {
 | 
				
			||||||
            type = 'readOnlyText';
 | 
					            type = 'readOnlyText';
 | 
				
			||||||
        } else if ((type === 'code' || type === 'mermaid') && await this.noteContext.isReadOnly()) {
 | 
					        } else if ((type === 'code' || type === 'mermaid') && await this.noteContext.isReadOnly()) {
 | 
				
			||||||
 | 
				
			|||||||
@ -113,7 +113,7 @@ export default class NoteMapWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
            .linkWidth(1)
 | 
					            .linkWidth(1)
 | 
				
			||||||
            .linkColor(() => this.css.mutedTextColor)
 | 
					            .linkColor(() => this.css.mutedTextColor)
 | 
				
			||||||
            .onNodeClick(node => appContext.tabManager.getActiveContext().setNote(node.id))
 | 
					            .onNodeClick(node => appContext.tabManager.getActiveContext().setNote(node.id))
 | 
				
			||||||
            .onNodeRightClick((node, e) => linkContextMenuService.openContextMenu(node.id, null, e));
 | 
					            .onNodeRightClick((node, e) => linkContextMenuService.openContextMenu(node.id, e));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.mapType === 'link') {
 | 
					        if (this.mapType === 'link') {
 | 
				
			||||||
            this.graph
 | 
					            this.graph
 | 
				
			||||||
 | 
				
			|||||||
@ -70,20 +70,38 @@ export default class NoteTitleWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async refreshWithNote(note) {
 | 
					    async refreshWithNote(note) {
 | 
				
			||||||
        const viewMode = this.noteContext.viewScope.viewMode;
 | 
					        this.$noteTitle.val(await this.getTitleText(note));
 | 
				
			||||||
        this.$noteTitle.val(viewMode === 'default'
 | 
					 | 
				
			||||||
            ? note.title
 | 
					 | 
				
			||||||
            : `${viewMode}: ${note.title}`);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.$noteTitle.prop("readonly",
 | 
					        this.$noteTitle.prop("readonly",
 | 
				
			||||||
            (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable())
 | 
					            (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable())
 | 
				
			||||||
            || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId)
 | 
					            || ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId)
 | 
				
			||||||
            || viewMode !== 'default'
 | 
					            || this.noteContext.viewScope.viewMode !== 'default'
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.setProtectedStatus(note);
 | 
					        this.setProtectedStatus(note);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @param {FNote} note */
 | 
				
			||||||
 | 
					    async getTitleText(note) {
 | 
				
			||||||
 | 
					        const viewScope = this.noteContext.viewScope;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let title = viewScope.viewMode === 'default'
 | 
				
			||||||
 | 
					            ? note.title
 | 
				
			||||||
 | 
					            : `${note.title}: ${viewScope.viewMode}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (viewScope.attachmentId) {
 | 
				
			||||||
 | 
					            // assuming the attachment has been already loaded
 | 
				
			||||||
 | 
					            const attachment = await note.getAttachmentById(viewScope.attachmentId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (attachment) {
 | 
				
			||||||
 | 
					                title += `: ${attachment.title}`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return title;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @param {FNote} note */
 | 
				
			||||||
    setProtectedStatus(note) {
 | 
					    setProtectedStatus(note) {
 | 
				
			||||||
        this.$noteTitle.toggleClass("protected", !!note.isProtected);
 | 
					        this.$noteTitle.toggleClass("protected", !!note.isProtected);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										54
									
								
								src/public/app/widgets/type_widgets/attachment_detail.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/public/app/widgets/type_widgets/attachment_detail.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					import TypeWidget from "./type_widget.js";
 | 
				
			||||||
 | 
					import server from "../../services/server.js";
 | 
				
			||||||
 | 
					import AttachmentDetailWidget from "../attachment_detail.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const TPL = `
 | 
				
			||||||
 | 
					<div class="attachment-detail note-detail-printable">
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        .attachment-detail {
 | 
				
			||||||
 | 
					            padding: 15px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="attachment-wrapper"></div>
 | 
				
			||||||
 | 
					</div>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class AttachmentDetailTypeWidget extends TypeWidget {
 | 
				
			||||||
 | 
					    static getType() {
 | 
				
			||||||
 | 
					        return "attachmentDetail";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    doRender() {
 | 
				
			||||||
 | 
					        this.$widget = $(TPL);
 | 
				
			||||||
 | 
					        this.$wrapper = this.$widget.find('.attachment-wrapper');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super.doRender();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async doRefresh(note) {
 | 
				
			||||||
 | 
					        this.$wrapper.empty();
 | 
				
			||||||
 | 
					        this.children = [];
 | 
				
			||||||
 | 
					        this.renderedAttachmentIds = new Set();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const attachment = await server.get(`notes/${this.noteId}/attachments/${this.noteContext.viewScope.attachment.attachmentId}/?includeContent=true`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!attachment) {
 | 
				
			||||||
 | 
					            this.$list.html("<strong>This attachment has been deleted.</strong>");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const attachmentDetailWidget = new AttachmentDetailWidget(attachment);
 | 
				
			||||||
 | 
					        this.child(attachmentDetailWidget);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.$list.append(attachmentDetailWidget.render());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async entitiesReloadedEvent({loadResults}) {
 | 
				
			||||||
 | 
					        const attachmentChange = loadResults.getAttachments().find(att => att.attachmentId === this.attachment.attachmentId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (attachmentChange.isDeleted) {
 | 
				
			||||||
 | 
					            this.refresh(); // all other updates are handled within AttachmentDetailWidget
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -3,24 +3,24 @@ import server from "../../services/server.js";
 | 
				
			|||||||
import AttachmentDetailWidget from "../attachment_detail.js";
 | 
					import AttachmentDetailWidget from "../attachment_detail.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TPL = `
 | 
					const TPL = `
 | 
				
			||||||
<div class="attachments note-detail-printable">
 | 
					<div class="attachment-list note-detail-printable">
 | 
				
			||||||
    <style>
 | 
					    <style>
 | 
				
			||||||
        .attachments {
 | 
					        .attachment-list {
 | 
				
			||||||
            padding: 15px;
 | 
					            padding: 15px;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    </style>
 | 
					    </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="attachment-list"></div>
 | 
					    <div class="attachment-list-wrapper"></div>
 | 
				
			||||||
</div>`;
 | 
					</div>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class AttachmentsTypeWidget extends TypeWidget {
 | 
					export default class AttachmentListTypeWidget extends TypeWidget {
 | 
				
			||||||
    static getType() {
 | 
					    static getType() {
 | 
				
			||||||
        return "attachments";
 | 
					        return "attachmentList";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    doRender() {
 | 
					    doRender() {
 | 
				
			||||||
        this.$widget = $(TPL);
 | 
					        this.$widget = $(TPL);
 | 
				
			||||||
        this.$list = this.$widget.find('.attachment-list');
 | 
					        this.$list = this.$widget.find('.attachment-list-wrapper');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        super.doRender();
 | 
					        super.doRender();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -34,8 +34,10 @@ function index(req, res) {
 | 
				
			|||||||
        instanceName: config.General ? config.General.instanceName : null,
 | 
					        instanceName: config.General ? config.General.instanceName : null,
 | 
				
			||||||
        appCssNoteIds: getAppCssNoteIds(),
 | 
					        appCssNoteIds: getAppCssNoteIds(),
 | 
				
			||||||
        isDev: env.isDev(),
 | 
					        isDev: env.isDev(),
 | 
				
			||||||
        isMainWindow: !req.query.extra,
 | 
					        isMainWindow: !req.query.extraWindow,
 | 
				
			||||||
        extraHoistedNoteId: req.query.extraHoistedNoteId,
 | 
					        extraHoistedNoteId: req.query.extraHoistedNoteId,
 | 
				
			||||||
 | 
					        // make sure only valid JSON gets rendered
 | 
				
			||||||
 | 
					        extraViewScope: JSON.stringify(req.query.extraViewScope ? JSON.parse(req.query.extraViewScope) : {}),
 | 
				
			||||||
        isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(),
 | 
					        isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(),
 | 
				
			||||||
        maxContentWidth: parseInt(options.maxContentWidth),
 | 
					        maxContentWidth: parseInt(options.maxContentWidth),
 | 
				
			||||||
        triliumVersion: packageJson.version,
 | 
					        triliumVersion: packageJson.version,
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ let mainWindow;
 | 
				
			|||||||
/** @type {Electron.BrowserWindow} */
 | 
					/** @type {Electron.BrowserWindow} */
 | 
				
			||||||
let setupWindow;
 | 
					let setupWindow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function createExtraWindow(notePath, hoistedNoteId = 'root') {
 | 
					async function createExtraWindow(notePath, hoistedNoteId = 'root', viewScope = {}) {
 | 
				
			||||||
    const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled');
 | 
					    const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const {BrowserWindow} = require('electron');
 | 
					    const {BrowserWindow} = require('electron');
 | 
				
			||||||
@ -35,13 +35,13 @@ async function createExtraWindow(notePath, hoistedNoteId = 'root') {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    win.setMenuBarVisibility(false);
 | 
					    win.setMenuBarVisibility(false);
 | 
				
			||||||
    win.loadURL(`http://127.0.0.1:${port}/?extra=1&extraHoistedNoteId=${hoistedNoteId}#${notePath}`);
 | 
					    win.loadURL(`http://127.0.0.1:${port}/?extraWindow=1&extraHoistedNoteId=${hoistedNoteId}&extraViewScope=${JSON.stringify(viewScope)}#${notePath}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    configureWebContents(win.webContents, spellcheckEnabled);
 | 
					    configureWebContents(win.webContents, spellcheckEnabled);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ipcMain.on('create-extra-window', (event, arg) => {
 | 
					ipcMain.on('create-extra-window', (event, arg) => {
 | 
				
			||||||
    createExtraWindow(arg.notePath, arg.hoistedNoteId);
 | 
					    createExtraWindow(arg.notePath, arg.hoistedNoteId, arg.viewScope);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function createMainWindow(app) {
 | 
					async function createMainWindow(app) {
 | 
				
			||||||
 | 
				
			|||||||
@ -33,6 +33,7 @@
 | 
				
			|||||||
        appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>,
 | 
					        appCssNoteIds: <%- JSON.stringify(appCssNoteIds) %>,
 | 
				
			||||||
        isMainWindow: <%= isMainWindow %>,
 | 
					        isMainWindow: <%= isMainWindow %>,
 | 
				
			||||||
        extraHoistedNoteId: '<%= extraHoistedNoteId %>',
 | 
					        extraHoistedNoteId: '<%= extraHoistedNoteId %>',
 | 
				
			||||||
 | 
					        extraViewScope: <%- extraViewScope %>,
 | 
				
			||||||
        isProtectedSessionAvailable: <%= isProtectedSessionAvailable %>,
 | 
					        isProtectedSessionAvailable: <%= isProtectedSessionAvailable %>,
 | 
				
			||||||
        triliumVersion: "<%= triliumVersion %>",
 | 
					        triliumVersion: "<%= triliumVersion %>",
 | 
				
			||||||
        assetPath: "<%= assetPath %>",
 | 
					        assetPath: "<%= assetPath %>",
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user