Merge remote-tracking branch 'origin/beta' into beta

This commit is contained in:
zadam 2023-10-02 22:02:31 +02:00
commit 055bb39e4d
6 changed files with 93 additions and 33 deletions

View File

@ -1613,16 +1613,12 @@ class BNote extends AbstractBeccaEntity {
revision.save(); // to generate revisionId, which is then used to save attachments
if (this.type === 'text') {
for (const noteAttachment of this.getAttachments()) {
if (noteAttachment.utcDateScheduledForErasureSince) {
continue;
}
const revisionAttachment = noteAttachment.copy();
revisionAttachment.ownerId = revision.revisionId;
revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
for (const noteAttachment of this.getAttachments()) {
const revisionAttachment = noteAttachment.copy();
revisionAttachment.ownerId = revision.revisionId;
revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
if (this.type === 'text') {
// content is rewritten to point to the revision attachments
noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`,
`attachments/${revisionAttachment.attachmentId}`);

View File

@ -86,6 +86,29 @@ class BRevision extends AbstractBeccaEntity {
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 {object} [opts]
@ -105,6 +128,45 @@ class BRevision extends AbstractBeccaEntity {
.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() {
super.beforeSaving();

View File

@ -45,6 +45,7 @@ const TPL = `
<a class="dropdown-item export-note-button">Export 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="forceSaveRevision" class="dropdown-item save-revision-button"><kbd data-command="forceSaveRevision"></kbd> Save revision</a>
</div>
</div>`;

View File

@ -274,26 +274,11 @@ export default class RevisionsDialog extends BasicWidget {
this.$content.html($table);
} else if (revisionItem.type === 'canvas') {
/**
* 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;
const sanitizedTitle = revisionItem.title.replace(/[^a-z0-9-.]/gi, "");
try {
const data = JSON.parse(content)
const svg = data.svg || "no svg present."
/**
* 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."));
}
this.$content.html($("<img>")
.attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`)
.css("max-width", "100%"));
} else {
this.$content.text("Preview isn't available for this note type.");
}

View File

@ -5,14 +5,27 @@ const becca = require('../../becca/becca');
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
const fs = require('fs');
function returnImage(req, res) {
function returnImageFromNote(req, res) {
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) {
res.set('Content-Type', 'image/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);
}
@ -84,7 +97,8 @@ function updateImage(req) {
}
module.exports = {
returnImage,
returnImageFromNote,
returnImageFromRevision,
returnAttachedImage,
updateImage
};

View File

@ -181,6 +181,8 @@ function register(app) {
apiRoute(GET, '/api/revisions/:revisionId/blob', revisionsApiRoute.getRevisionBlob);
apiRoute(DEL, '/api/revisions/:revisionId', revisionsApiRoute.eraseRevision);
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);
@ -200,7 +202,7 @@ function register(app) {
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
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);
apiRoute(GET, '/api/options', optionsApiRoute.getOptions);