mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
attachment content rendering
This commit is contained in:
parent
e20fac19ba
commit
579ed7e194
@ -31,7 +31,7 @@ describe("Parser", () => {
|
||||
const rootExp = parse({
|
||||
fulltextTokens: tokens(["hello", "hi"]),
|
||||
expressionTokens: [],
|
||||
searchContext: new SearchContext({includeNoteContent: false, excludeArchived: true})
|
||||
searchContext: new SearchContext({excludeArchived: true})
|
||||
});
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
@ -45,7 +45,7 @@ describe("Parser", () => {
|
||||
const rootExp = parse({
|
||||
fulltextTokens: tokens(["hello", "hi"]),
|
||||
expressionTokens: [],
|
||||
searchContext: new SearchContext({includeNoteContent: true})
|
||||
searchContext: new SearchContext()
|
||||
});
|
||||
|
||||
expect(rootExp.constructor.name).toEqual("AndExp");
|
||||
|
@ -153,16 +153,25 @@ class Becca {
|
||||
}
|
||||
|
||||
/** @returns {BAttachment|null} */
|
||||
getAttachment(attachmentId) {
|
||||
const row = sql.getRow("SELECT * FROM attachments WHERE attachmentId = ? AND isDeleted = 0", [attachmentId]);
|
||||
getAttachment(attachmentId, opts = {}) {
|
||||
opts.includeContentLength = !!opts.includeContentLength;
|
||||
|
||||
const query = opts.includeContentLength
|
||||
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
|
||||
FROM attachments
|
||||
JOIN blobs USING (blobId)
|
||||
WHERE attachmentId = ? AND isDeleted = 0`
|
||||
: `SELECT * FROM attachments WHERE attachmentId = ? AND isDeleted = 0`;
|
||||
|
||||
const BAttachment = require("./entities/battachment"); // avoiding circular dependency problems
|
||||
return row ? new BAttachment(row) : null;
|
||||
|
||||
return sql.getRows(query, [attachmentId])
|
||||
.map(row => new BAttachment(row))[0];
|
||||
}
|
||||
|
||||
/** @returns {BAttachment} */
|
||||
getAttachmentOrThrow(attachmentId) {
|
||||
const attachment = this.getAttachment(attachmentId);
|
||||
getAttachmentOrThrow(attachmentId, opts = {}) {
|
||||
const attachment = this.getAttachment(attachmentId, opts);
|
||||
if (!attachment) {
|
||||
throw new NotFoundError(`Attachment '${attachmentId}' has not been found.`);
|
||||
}
|
||||
|
@ -59,6 +59,9 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
/** @type {string} */
|
||||
this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince;
|
||||
|
||||
/** @type {integer} optionally added to the entity */
|
||||
this.contentLength = row.contentLength;
|
||||
|
||||
this.decrypt();
|
||||
}
|
||||
|
||||
@ -206,12 +209,14 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
isDeleted: false,
|
||||
dateModified: this.dateModified,
|
||||
utcDateModified: this.utcDateModified,
|
||||
utcDateScheduledForErasureSince: this.utcDateScheduledForErasureSince
|
||||
utcDateScheduledForErasureSince: this.utcDateScheduledForErasureSince,
|
||||
contentLength: this.contentLength
|
||||
};
|
||||
}
|
||||
|
||||
getPojoToSave() {
|
||||
const pojo = this.getPojo();
|
||||
delete pojo.contentLength;
|
||||
|
||||
if (pojo.isProtected) {
|
||||
if (this.isDecrypted) {
|
||||
|
@ -1114,24 +1114,33 @@ class BNote extends AbstractBeccaEntity {
|
||||
}
|
||||
|
||||
/** @returns {BAttachment[]} */
|
||||
getAttachments() {
|
||||
return sql.getRows(`
|
||||
SELECT attachments.*
|
||||
FROM attachments
|
||||
WHERE parentId = ?
|
||||
AND isDeleted = 0
|
||||
ORDER BY position`, [this.noteId])
|
||||
getAttachments(opts = {}) {
|
||||
opts.includeContentLength = !!opts.includeContentLength;
|
||||
|
||||
const query = opts.includeContentLength
|
||||
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
|
||||
FROM attachments
|
||||
JOIN blobs USING (blobId)
|
||||
WHERE parentId = ? AND isDeleted = 0
|
||||
ORDER BY position`
|
||||
: `SELECT * FROM attachments WHERE parentId = ? AND isDeleted = 0 ORDER BY position`;
|
||||
|
||||
return sql.getRows(query, [this.noteId])
|
||||
.map(row => new BAttachment(row));
|
||||
}
|
||||
|
||||
/** @returns {BAttachment|null} */
|
||||
getAttachmentById(attachmentId) {
|
||||
return sql.getRows(`
|
||||
SELECT attachments.*
|
||||
FROM attachments
|
||||
WHERE parentId = ?
|
||||
AND attachmentId = ?
|
||||
AND isDeleted = 0`, [this.noteId, attachmentId])
|
||||
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 parentId = ? AND attachmentId = ? AND isDeleted = 0`
|
||||
: `SELECT * FROM attachments WHERE parentId = ? AND attachmentId = ? AND isDeleted = 0`;
|
||||
|
||||
return sql.getRows(query, [this.noteId, attachmentId])
|
||||
.map(row => new BAttachment(row))[0];
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,9 @@ class FAttachment {
|
||||
/** @type {string} */
|
||||
this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince;
|
||||
|
||||
/** @type {integer} optionally added to the entity */
|
||||
this.contentLength = row.contentLength;
|
||||
|
||||
this.froca.attachments[this.attachmentId] = this;
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ async function getRenderedContent(entity, options = {}) {
|
||||
const type = getRenderingType(entity);
|
||||
// attachment supports only image and file/pdf/audio/video
|
||||
|
||||
const $renderedContent = $('<div class="rendered-note-content">');
|
||||
const $renderedContent = $('<div class="rendered-content">');
|
||||
|
||||
if (type === 'text') {
|
||||
await renderText(entity, options, $renderedContent);
|
||||
@ -118,9 +118,17 @@ async function renderCode(note, options, $renderedContent) {
|
||||
function renderImage(entity, $renderedContent) {
|
||||
const sanitizedTitle = entity.title.replace(/[^a-z0-9-.]/gi, "");
|
||||
|
||||
let url;
|
||||
|
||||
if (entity instanceof FNote) {
|
||||
url = `api/images/${entity.noteId}/${sanitizedTitle}?${entity.utcDateModified}`;
|
||||
} else if (entity instanceof FAttachment) {
|
||||
url = `api/attachments/${entity.attachmentId}/image/${sanitizedTitle}?${entity.utcDateModified}">`;
|
||||
}
|
||||
|
||||
$renderedContent.append(
|
||||
$("<img>")
|
||||
.attr("src", `api/images/${entity.noteId}/${sanitizedTitle}`)
|
||||
.attr("src", url)
|
||||
.css("max-width", "100%")
|
||||
);
|
||||
}
|
||||
|
@ -8,10 +8,16 @@ import linkService from "../services/link.js";
|
||||
import contentRenderer from "../services/content_renderer.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="attachment-detail">
|
||||
<div class="attachment-detail-widget">
|
||||
<style>
|
||||
.attachment-detail-widget {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.attachment-detail-wrapper {
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.attachment-title-line {
|
||||
@ -24,33 +30,53 @@ const TPL = `
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.attachment-content pre {
|
||||
.attachment-content-wrapper {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.attachment-content-wrapper .rendered-content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.attachment-content-wrapper pre {
|
||||
background: var(--accented-background-color);
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.attachment-detail-wrapper.list-view .attachment-content pre {
|
||||
.attachment-detail-wrapper.list-view .attachment-content-wrapper {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
.attachment-detail-wrapper.full-detail {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.attachment-detail-wrapper.full-detail .attachment-content-wrapper {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.attachment-detail-wrapper.list-view .attachment-content-wrapper pre {
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.attachment-content img {
|
||||
.attachment-content-wrapper img {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.attachment-detail-wrapper.list-view .attachment-content img {
|
||||
.attachment-detail-wrapper.list-view .attachment-content-wrapper img {
|
||||
max-height: 300px;
|
||||
max-width: 90%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.attachment-detail-wrapper.full-detail .attachment-content img {
|
||||
.attachment-detail-wrapper.full-detail .attachment-content-wrapper img {
|
||||
max-width: 90%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.attachment-detail-wrapper.scheduled-for-deletion .attachment-content img {
|
||||
.attachment-detail-wrapper.scheduled-for-deletion .attachment-content-wrapper img {
|
||||
filter: contrast(10%);
|
||||
}
|
||||
</style>
|
||||
@ -65,7 +91,7 @@ const TPL = `
|
||||
|
||||
<div class="attachment-deletion-warning alert alert-info"></div>
|
||||
|
||||
<div class="attachment-content"></div>
|
||||
<div class="attachment-content-wrapper"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
@ -141,11 +167,11 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
||||
this.$wrapper.find('.attachment-details')
|
||||
.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-content').append(contentRenderer.getRenderedContent(this.attachment));
|
||||
this.$wrapper.find('.attachment-content-wrapper').append((await contentRenderer.getRenderedContent(this.attachment)).$renderedContent);
|
||||
}
|
||||
|
||||
copyAttachmentReferenceToClipboard() {
|
||||
imageService.copyImageReferenceToClipboard(this.$wrapper.find('.attachment-content'));
|
||||
imageService.copyImageReferenceToClipboard(this.$wrapper.find('.attachment-content-wrapper'));
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
|
@ -167,9 +167,13 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
checkFullHeight() {
|
||||
// https://github.com/zadam/trilium/issues/2522
|
||||
this.$widget.toggleClass("full-height",
|
||||
!this.noteContext.hasNoteList()
|
||||
&& ['editableText', 'editableCode', 'canvas', 'webView', 'noteMap'].includes(this.type)
|
||||
&& this.mime !== 'text/x-sqlite;schema=trilium');
|
||||
(
|
||||
!this.noteContext.hasNoteList()
|
||||
&& ['editableText', 'editableCode', 'canvas', 'webView', 'noteMap'].includes(this.type)
|
||||
&& this.mime !== 'text/x-sqlite;schema=trilium'
|
||||
)
|
||||
|| this.noteContext.viewScope.viewMode === 'attachments'
|
||||
);
|
||||
}
|
||||
|
||||
getTypeWidget() {
|
||||
|
@ -1,19 +1,26 @@
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import server from "../../services/server.js";
|
||||
import AttachmentDetailWidget from "../attachment_detail.js";
|
||||
import linkService from "../../services/link.js";
|
||||
import froca from "../../services/froca.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="attachment-detail note-detail-printable">
|
||||
<style>
|
||||
.attachment-detail {
|
||||
padding: 15px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.attachment-detail .links-wrapper {
|
||||
padding: 16px;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.attachment-detail .attachment-wrapper {
|
||||
flex-grow: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="links-wrapper"></div>
|
||||
@ -50,7 +57,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
|
||||
})
|
||||
);
|
||||
|
||||
const attachment = await server.get(`attachments/${this.attachmentId}`);
|
||||
const attachment = await froca.getAttachment(this.attachmentId);
|
||||
|
||||
if (!attachment) {
|
||||
this.$wrapper.html("<strong>This attachment has been deleted.</strong>");
|
||||
|
@ -51,7 +51,7 @@ export default class AttachmentListTypeWidget extends TypeWidget {
|
||||
this.children = [];
|
||||
this.renderedAttachmentIds = new Set();
|
||||
|
||||
const attachments = await server.get(`notes/${this.noteId}/attachments`);
|
||||
const attachments = await note.getAttachments();
|
||||
|
||||
if (attachments.length === 0) {
|
||||
this.$list.html('<div class="alert alert-info">This note has no attachments.</div>');
|
||||
|
@ -736,11 +736,11 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.include-note.box-size-medium .include-note-content.type-pdf .rendered-note-content {
|
||||
.include-note.box-size-medium .include-note-content.type-pdf .rendered-content {
|
||||
height: 20em; /* PDF is rendered in iframe and must be sized absolutely */
|
||||
}
|
||||
|
||||
.include-note.box-size-full .include-note-content.type-pdf .rendered-note-content {
|
||||
.include-note.box-size-full .include-note-content.type-pdf .rendered-content {
|
||||
height: 50em; /* PDF is rendered in iframe and it's not possible to put full height so at least a large height */
|
||||
}
|
||||
|
||||
|
@ -10,13 +10,13 @@ function getAttachmentBlob(req) {
|
||||
function getAttachments(req) {
|
||||
const note = becca.getNoteOrThrow(req.params.noteId);
|
||||
|
||||
return note.getAttachments();
|
||||
return note.getAttachments({includeContentLength: true});
|
||||
}
|
||||
|
||||
function getAttachment(req) {
|
||||
const {attachmentId} = req.params;
|
||||
|
||||
return becca.getAttachmentOrThrow(attachmentId);
|
||||
return becca.getAttachmentOrThrow(attachmentId, {includeContentLength: true});
|
||||
}
|
||||
|
||||
function saveAttachment(req) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user