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