mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 13:39:01 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/beta' into beta
This commit is contained in:
		
						commit
						055bb39e4d
					
				@ -1613,16 +1613,12 @@ class BNote extends AbstractBeccaEntity {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            revision.save(); // to generate revisionId, which is then used to save attachments
 | 
					            revision.save(); // to generate revisionId, which is then used to save attachments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (this.type === 'text') {
 | 
					 | 
				
			||||||
            for (const noteAttachment of this.getAttachments()) {
 | 
					            for (const noteAttachment of this.getAttachments()) {
 | 
				
			||||||
                    if (noteAttachment.utcDateScheduledForErasureSince) {
 | 
					 | 
				
			||||||
                        continue;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                const revisionAttachment = noteAttachment.copy();
 | 
					                const revisionAttachment = noteAttachment.copy();
 | 
				
			||||||
                revisionAttachment.ownerId = revision.revisionId;
 | 
					                revisionAttachment.ownerId = revision.revisionId;
 | 
				
			||||||
                revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
 | 
					                revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (this.type === 'text') {
 | 
				
			||||||
                    // content is rewritten to point to the revision attachments
 | 
					                    // content is rewritten to point to the revision attachments
 | 
				
			||||||
                    noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`,
 | 
					                    noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`,
 | 
				
			||||||
                        `attachments/${revisionAttachment.attachmentId}`);
 | 
					                        `attachments/${revisionAttachment.attachmentId}`);
 | 
				
			||||||
 | 
				
			|||||||
@ -86,6 +86,29 @@ class BRevision extends AbstractBeccaEntity {
 | 
				
			|||||||
        return this._getContent();
 | 
					        return this._getContent();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @returns {*}
 | 
				
			||||||
 | 
					     * @throws Error in case of invalid JSON */
 | 
				
			||||||
 | 
					    getJsonContent() {
 | 
				
			||||||
 | 
					        const content = this.getContent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!content || !content.trim()) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return JSON.parse(content);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {*|null} valid object or null if the content cannot be parsed as JSON */
 | 
				
			||||||
 | 
					    getJsonContentSafely() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return this.getJsonContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (e) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @param content
 | 
					     * @param content
 | 
				
			||||||
     * @param {object} [opts]
 | 
					     * @param {object} [opts]
 | 
				
			||||||
@ -105,6 +128,45 @@ class BRevision extends AbstractBeccaEntity {
 | 
				
			|||||||
            .map(row => new BAttachment(row));
 | 
					            .map(row => new BAttachment(row));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {BAttachment|null} */
 | 
				
			||||||
 | 
					    getAttachmentById(attachmentId, opts = {}) {
 | 
				
			||||||
 | 
					        opts.includeContentLength = !!opts.includeContentLength;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const query = opts.includeContentLength
 | 
				
			||||||
 | 
					            ? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
 | 
				
			||||||
 | 
					               FROM attachments 
 | 
				
			||||||
 | 
					               JOIN blobs USING (blobId) 
 | 
				
			||||||
 | 
					               WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`
 | 
				
			||||||
 | 
					            : `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sql.getRows(query, [this.revisionId, attachmentId])
 | 
				
			||||||
 | 
					            .map(row => new BAttachment(row))[0];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {BAttachment[]} */
 | 
				
			||||||
 | 
					    getAttachmentsByRole(role) {
 | 
				
			||||||
 | 
					        return sql.getRows(`
 | 
				
			||||||
 | 
					                SELECT attachments.*
 | 
				
			||||||
 | 
					                FROM attachments 
 | 
				
			||||||
 | 
					                WHERE ownerId = ? 
 | 
				
			||||||
 | 
					                  AND role = ?
 | 
				
			||||||
 | 
					                  AND isDeleted = 0
 | 
				
			||||||
 | 
					                ORDER BY position`, [this.revisionId, role])
 | 
				
			||||||
 | 
					            .map(row => new BAttachment(row));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {BAttachment} */
 | 
				
			||||||
 | 
					    getAttachmentByTitle(title) {
 | 
				
			||||||
 | 
					        return sql.getRows(`
 | 
				
			||||||
 | 
					                SELECT attachments.*
 | 
				
			||||||
 | 
					                FROM attachments 
 | 
				
			||||||
 | 
					                WHERE ownerId = ? 
 | 
				
			||||||
 | 
					                  AND title = ?
 | 
				
			||||||
 | 
					                  AND isDeleted = 0
 | 
				
			||||||
 | 
					                ORDER BY position`, [this.revisionId, title])
 | 
				
			||||||
 | 
					            .map(row => new BAttachment(row))[0];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    beforeSaving() {
 | 
					    beforeSaving() {
 | 
				
			||||||
        super.beforeSaving();
 | 
					        super.beforeSaving();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -45,6 +45,7 @@ const TPL = `
 | 
				
			|||||||
        <a class="dropdown-item export-note-button">Export note</a>
 | 
					        <a class="dropdown-item export-note-button">Export note</a>
 | 
				
			||||||
        <a class="dropdown-item delete-note-button">Delete note</a>
 | 
					        <a class="dropdown-item delete-note-button">Delete note</a>
 | 
				
			||||||
        <a data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
 | 
					        <a data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
 | 
				
			||||||
 | 
					        <a data-trigger-command="forceSaveRevision" class="dropdown-item save-revision-button"><kbd data-command="forceSaveRevision"></kbd> Save revision</a>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</div>`;
 | 
					</div>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -274,26 +274,11 @@ export default class RevisionsDialog extends BasicWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            this.$content.html($table);
 | 
					            this.$content.html($table);
 | 
				
			||||||
        } else if (revisionItem.type === 'canvas') {
 | 
					        } else if (revisionItem.type === 'canvas') {
 | 
				
			||||||
            /**
 | 
					            const sanitizedTitle = revisionItem.title.replace(/[^a-z0-9-.]/gi, "");
 | 
				
			||||||
             * FIXME: We load a font called Virgil.wof2, which originates from excalidraw.com
 | 
					 | 
				
			||||||
             *        REMOVE external dependency!!!! This is defined in the svg in defs.style
 | 
					 | 
				
			||||||
             */
 | 
					 | 
				
			||||||
            const content = fullRevision.content;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            try {
 | 
					            this.$content.html($("<img>")
 | 
				
			||||||
                const data = JSON.parse(content)
 | 
					                .attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`)
 | 
				
			||||||
                const svg = data.svg || "no svg present."
 | 
					                .css("max-width", "100%"));
 | 
				
			||||||
 | 
					 | 
				
			||||||
                /**
 | 
					 | 
				
			||||||
                 * maxWidth: 100% use full width of container but do not enlarge!
 | 
					 | 
				
			||||||
                 * height:auto to ensure that height scales with width
 | 
					 | 
				
			||||||
                 */
 | 
					 | 
				
			||||||
                const $svgHtml = $(svg).css({maxWidth: "100%", height: "auto"});
 | 
					 | 
				
			||||||
                this.$content.html($('<div>').append($svgHtml));
 | 
					 | 
				
			||||||
            } catch (err) {
 | 
					 | 
				
			||||||
                console.error("error parsing fullRevision.content as JSON", fullRevision.content, err);
 | 
					 | 
				
			||||||
                this.$content.html($("<div>").text("Error parsing content. Please check console.error() for more details."));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            this.$content.text("Preview isn't available for this note type.");
 | 
					            this.$content.text("Preview isn't available for this note type.");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -5,14 +5,27 @@ const becca = require('../../becca/becca');
 | 
				
			|||||||
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
 | 
					const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
 | 
				
			||||||
const fs = require('fs');
 | 
					const fs = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function returnImage(req, res) {
 | 
					function returnImageFromNote(req, res) {
 | 
				
			||||||
    const image = becca.getNote(req.params.noteId);
 | 
					    const image = becca.getNote(req.params.noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return returnImageInt(image, res);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function returnImageFromRevision(req, res) {
 | 
				
			||||||
 | 
					    const image = becca.getRevision(req.params.revisionId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return returnImageInt(image, res);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {BNote|BRevision} image
 | 
				
			||||||
 | 
					 * @param res
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function returnImageInt(image, res) {
 | 
				
			||||||
    if (!image) {
 | 
					    if (!image) {
 | 
				
			||||||
        res.set('Content-Type', 'image/png');
 | 
					        res.set('Content-Type', 'image/png');
 | 
				
			||||||
        return res.send(fs.readFileSync(`${RESOURCE_DIR}/db/image-deleted.png`));
 | 
					        return res.send(fs.readFileSync(`${RESOURCE_DIR}/db/image-deleted.png`));
 | 
				
			||||||
    }
 | 
					    } else if (!["image", "canvas"].includes(image.type)) {
 | 
				
			||||||
    else if (!["image", "canvas"].includes(image.type)){
 | 
					 | 
				
			||||||
        return res.sendStatus(400);
 | 
					        return res.sendStatus(400);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -84,7 +97,8 @@ function updateImage(req) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    returnImage,
 | 
					    returnImageFromNote,
 | 
				
			||||||
 | 
					    returnImageFromRevision,
 | 
				
			||||||
    returnAttachedImage,
 | 
					    returnAttachedImage,
 | 
				
			||||||
    updateImage
 | 
					    updateImage
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -181,6 +181,8 @@ function register(app) {
 | 
				
			|||||||
    apiRoute(GET, '/api/revisions/:revisionId/blob', revisionsApiRoute.getRevisionBlob);
 | 
					    apiRoute(GET, '/api/revisions/:revisionId/blob', revisionsApiRoute.getRevisionBlob);
 | 
				
			||||||
    apiRoute(DEL, '/api/revisions/:revisionId', revisionsApiRoute.eraseRevision);
 | 
					    apiRoute(DEL, '/api/revisions/:revisionId', revisionsApiRoute.eraseRevision);
 | 
				
			||||||
    apiRoute(PST, '/api/revisions/:revisionId/restore', revisionsApiRoute.restoreRevision);
 | 
					    apiRoute(PST, '/api/revisions/:revisionId/restore', revisionsApiRoute.restoreRevision);
 | 
				
			||||||
 | 
					    route(GET, '/api/revisions/:revisionId/image/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImageFromRevision);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    route(GET, '/api/revisions/:revisionId/download', [auth.checkApiAuthOrElectron], revisionsApiRoute.downloadRevision);
 | 
					    route(GET, '/api/revisions/:revisionId/download', [auth.checkApiAuthOrElectron], revisionsApiRoute.downloadRevision);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -200,7 +202,7 @@ function register(app) {
 | 
				
			|||||||
    apiRoute(GET, '/api/attribute-values/:attributeName', attributesRoute.getValuesForAttribute);
 | 
					    apiRoute(GET, '/api/attribute-values/:attributeName', attributesRoute.getValuesForAttribute);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
 | 
					    // :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
 | 
				
			||||||
    route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImage);
 | 
					    route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImageFromNote);
 | 
				
			||||||
    route(PUT, '/api/images/:noteId', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], imageRoute.updateImage, apiResultHandler);
 | 
					    route(PUT, '/api/images/:noteId', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], imageRoute.updateImage, apiResultHandler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apiRoute(GET, '/api/options', optionsApiRoute.getOptions);
 | 
					    apiRoute(GET, '/api/options', optionsApiRoute.getOptions);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user