mirror of
https://github.com/TriliumNext/Notes.git
synced 2026-01-04 20:10:14 -06:00
attachment WIP
This commit is contained in:
@@ -254,6 +254,13 @@ class AbstractBeccaEntity {
|
||||
? ""
|
||||
: content.toString("utf-8");
|
||||
} else {
|
||||
// see https://github.com/zadam/trilium/issues/3523
|
||||
// IIRC a zero-sized buffer can be returned as null from the database
|
||||
if (content === null) {
|
||||
// this will force de/encryption
|
||||
content = Buffer.alloc(0);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ const dateUtils = require('../../services/date_utils');
|
||||
const AbstractBeccaEntity = require("./abstract_becca_entity");
|
||||
const sql = require("../../services/sql");
|
||||
const protectedSessionService = require("../../services/protected_session.js");
|
||||
const log = require("../../services/log.js");
|
||||
|
||||
const attachmentRoleToNoteTypeMapping = {
|
||||
'image': 'image'
|
||||
@@ -57,6 +58,8 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
this.utcDateModified = row.utcDateModified;
|
||||
/** @type {string} */
|
||||
this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince;
|
||||
|
||||
this.decrypt();
|
||||
}
|
||||
|
||||
/** @returns {BAttachment} */
|
||||
@@ -86,6 +89,22 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
|| protectedSessionService.isProtectedSessionAvailable()
|
||||
}
|
||||
|
||||
getTitleOrProtected() {
|
||||
return this.isContentAvailable() ? this.title : '[protected]';
|
||||
}
|
||||
|
||||
decrypt() {
|
||||
if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
|
||||
try {
|
||||
this.title = protectedSessionService.decryptString(this.title);
|
||||
this.isDecrypted = true;
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Could not decrypt attachment ${this.attachmentId}: ${e.message} ${e.stack}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {string|Buffer} */
|
||||
getContent() {
|
||||
return this._getContent();
|
||||
@@ -192,7 +211,19 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
}
|
||||
|
||||
getPojoToSave() {
|
||||
return this.getPojo();
|
||||
const pojo = this.getPojo();
|
||||
|
||||
if (pojo.isProtected) {
|
||||
if (this.isDecrypted) {
|
||||
pojo.title = protectedSessionService.encrypt(pojo.title);
|
||||
}
|
||||
else {
|
||||
// updating protected note outside of protected session means we will keep original ciphertexts
|
||||
delete pojo.title;
|
||||
}
|
||||
}
|
||||
|
||||
return pojo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
292
src/public/app/services/content_renderer.js
Normal file
292
src/public/app/services/content_renderer.js
Normal file
@@ -0,0 +1,292 @@
|
||||
import renderService from "./render.js";
|
||||
import protectedSessionService from "./protected_session.js";
|
||||
import protectedSessionHolder from "./protected_session_holder.js";
|
||||
import libraryLoader from "./library_loader.js";
|
||||
import openService from "./open.js";
|
||||
import froca from "./froca.js";
|
||||
import utils from "./utils.js";
|
||||
import linkService from "./link.js";
|
||||
import treeService from "./tree.js";
|
||||
import FNote from "../entities/fnote.js";
|
||||
import FAttachment from "../entities/fattachment.js";
|
||||
|
||||
let idCounter = 1;
|
||||
|
||||
/**
|
||||
* @param {FNote|FAttachment} entity
|
||||
* @param {object} options
|
||||
* @return {Promise<{type: string, $renderedContent: jQuery}>}
|
||||
*/
|
||||
async function getRenderedContent(entity, options = {}) {
|
||||
options = Object.assign({
|
||||
trim: false,
|
||||
tooltip: false
|
||||
}, options);
|
||||
|
||||
const type = getRenderingType(entity);
|
||||
// attachment supports only image and file/pdf/audio/video
|
||||
|
||||
const $renderedContent = $('<div class="rendered-note-content">');
|
||||
|
||||
if (type === 'text') {
|
||||
await renderText(entity, options, $renderedContent);
|
||||
}
|
||||
else if (type === 'code') {
|
||||
await renderCode(entity, options, $renderedContent);
|
||||
}
|
||||
else if (type === 'image') {
|
||||
renderImage(entity, $renderedContent);
|
||||
}
|
||||
else if (!options.tooltip && ['file', 'pdf', 'audio', 'video'].includes(type)) {
|
||||
renderFile(entity, type, $renderedContent);
|
||||
}
|
||||
else if (type === 'mermaid') {
|
||||
await renderMermaid(entity, $renderedContent);
|
||||
}
|
||||
else if (type === 'render') {
|
||||
const $content = $('<div>');
|
||||
|
||||
await renderService.render(entity, $content, this.ctx);
|
||||
|
||||
$renderedContent.append($content);
|
||||
}
|
||||
else if (type === 'canvas') {
|
||||
await renderCanvas(entity, $renderedContent);
|
||||
}
|
||||
else if (type === 'book') {
|
||||
// nothing, a book doesn't have its own content
|
||||
}
|
||||
else if (!options.tooltip && type === 'protectedSession') {
|
||||
const $button = $(`<button class="btn btn-sm"><span class="bx bx-log-in"></span> Enter protected session</button>`)
|
||||
.on('click', protectedSessionService.enterProtectedSession);
|
||||
|
||||
$renderedContent.append(
|
||||
$("<div>")
|
||||
.append("<div>This note is protected and to access it you need to enter password.</div>")
|
||||
.append("<br/>")
|
||||
.append($button)
|
||||
);
|
||||
}
|
||||
else {
|
||||
$renderedContent.append($("<p><em>Content of this note cannot be displayed</em></p>"));
|
||||
}
|
||||
|
||||
if (entity instanceof FNote) {
|
||||
$renderedContent.addClass(entity.getCssClass());
|
||||
}
|
||||
|
||||
return {
|
||||
$renderedContent,
|
||||
type
|
||||
};
|
||||
}
|
||||
|
||||
async function renderText(note, options, $renderedContent) {
|
||||
// entity must be FNote
|
||||
const blob = await note.getBlob({preview: options.trim});
|
||||
|
||||
if (!utils.isHtmlEmpty(blob.content)) {
|
||||
$renderedContent.append($('<div class="ck-content">').html(trim(blob.content, options.trim)));
|
||||
|
||||
if ($renderedContent.find('span.math-tex').length > 0) {
|
||||
await libraryLoader.requireLibrary(libraryLoader.KATEX);
|
||||
|
||||
renderMathInElement($renderedContent[0], {trust: true});
|
||||
}
|
||||
|
||||
const getNoteIdFromLink = el => treeService.getNoteIdFromNotePath($(el).attr('href'));
|
||||
const referenceLinks = $renderedContent.find("a.reference-link");
|
||||
const noteIdsToPrefetch = referenceLinks.map(el => getNoteIdFromLink(el));
|
||||
await froca.getNotes(noteIdsToPrefetch);
|
||||
|
||||
for (const el of referenceLinks) {
|
||||
const noteId = getNoteIdFromLink(el);
|
||||
|
||||
await linkService.loadReferenceLinkTitle(noteId, $(el));
|
||||
}
|
||||
} else {
|
||||
await renderChildrenList($renderedContent, note);
|
||||
}
|
||||
}
|
||||
|
||||
async function renderCode(note, options, $renderedContent) {
|
||||
const blob = await note.getBlob({preview: options.trim});
|
||||
|
||||
$renderedContent.append($("<pre>").text(trim(blob.content, options.trim)));
|
||||
}
|
||||
|
||||
function renderImage(entity, $renderedContent) {
|
||||
const sanitizedTitle = entity.title.replace(/[^a-z0-9-.]/gi, "");
|
||||
|
||||
$renderedContent.append(
|
||||
$("<img>")
|
||||
.attr("src", `api/images/${entity.noteId}/${sanitizedTitle}`)
|
||||
.css("max-width", "100%")
|
||||
);
|
||||
}
|
||||
|
||||
function renderFile(entity, type, $renderedContent) {
|
||||
let entityType, entityId;
|
||||
|
||||
if (entity instanceof FNote) {
|
||||
entityType = 'notes';
|
||||
entityId = entity.noteId;
|
||||
} else if (entity instanceof FAttachment) {
|
||||
entityType = 'attachments';
|
||||
entityId = entity.attachmentId;
|
||||
} else {
|
||||
throw new Error(`Can't recognize entity type of '${entity}'`);
|
||||
}
|
||||
|
||||
const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>');
|
||||
const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>');
|
||||
|
||||
$downloadButton.on('click', () => openService.downloadFileNote(entity.noteId));
|
||||
$openButton.on('click', () => openService.openNoteExternally(entity.noteId, entity.mime));
|
||||
|
||||
// open doesn't work for protected notes since it works through a browser which isn't in protected session
|
||||
$openButton.toggle(!entity.isProtected);
|
||||
|
||||
const $content = $('<div style="display: flex; flex-direction: column; height: 100%;">');
|
||||
|
||||
if (type === 'pdf') {
|
||||
const $pdfPreview = $('<iframe class="pdf-preview" style="width: 100%; flex-grow: 100;"></iframe>');
|
||||
$pdfPreview.attr("src", openService.getUrlForDownload(`api/${entityType}/${entityId}/open`));
|
||||
|
||||
$content.append($pdfPreview);
|
||||
} else if (type === 'audio') {
|
||||
const $audioPreview = $('<audio controls></audio>')
|
||||
.attr("src", openService.getUrlForStreaming(`api/${entityType}/${entityId}/open-partial`))
|
||||
.attr("type", entity.mime)
|
||||
.css("width", "100%");
|
||||
|
||||
$content.append($audioPreview);
|
||||
} else if (type === 'video') {
|
||||
const $videoPreview = $('<video controls></video>')
|
||||
.attr("src", openService.getUrlForDownload(`api/${entityType}/${entityId}/open-partial`))
|
||||
.attr("type", entity.mime)
|
||||
.css("width", "100%");
|
||||
|
||||
$content.append($videoPreview);
|
||||
}
|
||||
|
||||
$content.append(
|
||||
$('<div style="display: flex; justify-content: space-evenly; margin-top: 5px;">')
|
||||
.append($downloadButton)
|
||||
.append($openButton)
|
||||
);
|
||||
|
||||
$renderedContent.append($content);
|
||||
}
|
||||
|
||||
async function renderMermaid(note, $renderedContent) {
|
||||
await libraryLoader.requireLibrary(libraryLoader.MERMAID);
|
||||
|
||||
const blob = await note.getBlob();
|
||||
const content = blob.content || "";
|
||||
|
||||
$renderedContent
|
||||
.css("display", "flex")
|
||||
.css("justify-content", "space-around");
|
||||
|
||||
const documentStyle = window.getComputedStyle(document.documentElement);
|
||||
const mermaidTheme = documentStyle.getPropertyValue('--mermaid-theme');
|
||||
|
||||
mermaid.mermaidAPI.initialize({startOnLoad: false, theme: mermaidTheme.trim(), securityLevel: 'antiscript'});
|
||||
|
||||
try {
|
||||
mermaid.mermaidAPI.render("in-mermaid-graph-" + idCounter++, content,
|
||||
content => $renderedContent.append($(content)));
|
||||
} catch (e) {
|
||||
const $error = $("<p>The diagram could not displayed.</p>");
|
||||
|
||||
$renderedContent.append($error);
|
||||
}
|
||||
}
|
||||
|
||||
async function renderCanvas(note, $renderedContent) {
|
||||
// make sure surrounding container has size of what is visible. Then image is shrinked to its boundaries
|
||||
$renderedContent.css({height: "100%", width: "100%"});
|
||||
|
||||
const blob = await note.getBlob();
|
||||
const content = blob.content || "";
|
||||
|
||||
try {
|
||||
const placeHolderSVG = "<svg />";
|
||||
const data = JSON.parse(content)
|
||||
const svg = data.svg || placeHolderSVG;
|
||||
/**
|
||||
* maxWidth: size down to 100% (full) width of container but do not enlarge!
|
||||
* height:auto to ensure that height scales with width
|
||||
*/
|
||||
$renderedContent.append($(svg).css({maxWidth: "100%", maxHeight: "100%", height: "auto", width: "auto"}));
|
||||
} catch (err) {
|
||||
console.error("error parsing content as JSON", content, err);
|
||||
$renderedContent.append($("<div>").text("Error parsing content. Please check console.error() for more details."));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {jQuery} $renderedContent
|
||||
* @param {FNote} note
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function renderChildrenList($renderedContent, note) {
|
||||
$renderedContent.css("padding", "10px");
|
||||
$renderedContent.addClass("text-with-ellipsis");
|
||||
|
||||
let childNoteIds = note.getChildNoteIds();
|
||||
|
||||
if (childNoteIds.length > 10) {
|
||||
childNoteIds = childNoteIds.slice(0, 10);
|
||||
}
|
||||
|
||||
// just load the first 10 child notes
|
||||
const childNotes = await froca.getNotes(childNoteIds);
|
||||
|
||||
for (const childNote of childNotes) {
|
||||
$renderedContent.append(await linkService.createNoteLink(`${note.noteId}/${childNote.noteId}`, {
|
||||
showTooltip: false,
|
||||
showNoteIcon: true
|
||||
}));
|
||||
|
||||
$renderedContent.append("<br>");
|
||||
}
|
||||
}
|
||||
|
||||
function trim(text, doTrim) {
|
||||
if (!doTrim) {
|
||||
return text;
|
||||
}
|
||||
else {
|
||||
return text.substr(0, Math.min(text.length, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
function getRenderingType(entity) {
|
||||
let type = entity.type || entity.role;
|
||||
const mime = entity.mime;
|
||||
|
||||
if (type === 'file' && mime === 'application/pdf') {
|
||||
type = 'pdf';
|
||||
} else if (type === 'file' && mime.startsWith('audio/')) {
|
||||
type = 'audio';
|
||||
} else if (type === 'file' && mime.startsWith('video/')) {
|
||||
type = 'video';
|
||||
}
|
||||
|
||||
if (entity.isProtected) {
|
||||
if (protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
protectedSessionHolder.touchProtectedSession();
|
||||
}
|
||||
else {
|
||||
type = 'protectedSession';
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
export default {
|
||||
getRenderedContent
|
||||
};
|
||||
@@ -1,248 +0,0 @@
|
||||
import server from "./server.js";
|
||||
import renderService from "./render.js";
|
||||
import protectedSessionService from "./protected_session.js";
|
||||
import protectedSessionHolder from "./protected_session_holder.js";
|
||||
import libraryLoader from "./library_loader.js";
|
||||
import openService from "./open.js";
|
||||
import froca from "./froca.js";
|
||||
import utils from "./utils.js";
|
||||
import linkService from "./link.js";
|
||||
import treeService from "./tree.js";
|
||||
|
||||
let idCounter = 1;
|
||||
|
||||
/**
|
||||
* @param {FNote} note
|
||||
* @param {object} options
|
||||
* @return {Promise<{type: string, $renderedContent: jQuery}>}
|
||||
*/
|
||||
async function getRenderedContent(note, options = {}) {
|
||||
options = Object.assign({
|
||||
trim: false,
|
||||
tooltip: false
|
||||
}, options);
|
||||
|
||||
const type = getRenderingType(note);
|
||||
|
||||
const $renderedContent = $('<div class="rendered-note-content">');
|
||||
|
||||
if (type === 'text') {
|
||||
const blob = await note.getBlob({ preview: options.trim });
|
||||
|
||||
if (!utils.isHtmlEmpty(blob.content)) {
|
||||
$renderedContent.append($('<div class="ck-content">').html(trim(blob.content, options.trim)));
|
||||
|
||||
if ($renderedContent.find('span.math-tex').length > 0) {
|
||||
await libraryLoader.requireLibrary(libraryLoader.KATEX);
|
||||
|
||||
renderMathInElement($renderedContent[0], {trust: true});
|
||||
}
|
||||
|
||||
const getNoteIdFromLink = el => treeService.getNoteIdFromNotePath($(el).attr('href'));
|
||||
const referenceLinks = $renderedContent.find("a.reference-link");
|
||||
const noteIdsToPrefetch = referenceLinks.map(el => getNoteIdFromLink(el));
|
||||
await froca.getNotes(noteIdsToPrefetch);
|
||||
|
||||
for (const el of referenceLinks) {
|
||||
const noteId = getNoteIdFromLink(el);
|
||||
|
||||
await linkService.loadReferenceLinkTitle(noteId, $(el));
|
||||
}
|
||||
}
|
||||
else {
|
||||
await renderChildrenList($renderedContent, note);
|
||||
}
|
||||
}
|
||||
else if (type === 'code') {
|
||||
const blob = await note.getBlob({ preview: options.trim });
|
||||
|
||||
$renderedContent.append($("<pre>").text(trim(blob.content, options.trim)));
|
||||
}
|
||||
else if (type === 'image') {
|
||||
const sanitizedTitle = note.title.replace(/[^a-z0-9-.]/gi, "");
|
||||
|
||||
$renderedContent.append(
|
||||
$("<img>")
|
||||
.attr("src", `api/images/${note.noteId}/${sanitizedTitle}`)
|
||||
.css("max-width", "100%")
|
||||
);
|
||||
}
|
||||
else if (!options.tooltip && ['file', 'pdf', 'audio', 'video'].includes(type)) {
|
||||
const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>');
|
||||
const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>');
|
||||
|
||||
$downloadButton.on('click', () => openService.downloadFileNote(note.noteId));
|
||||
$openButton.on('click', () => openService.openNoteExternally(note.noteId, note.mime));
|
||||
|
||||
// open doesn't work for protected notes since it works through a browser which isn't in protected session
|
||||
$openButton.toggle(!note.isProtected);
|
||||
|
||||
const $content = $('<div style="display: flex; flex-direction: column; height: 100%;">');
|
||||
|
||||
if (type === 'pdf') {
|
||||
const $pdfPreview = $('<iframe class="pdf-preview" style="width: 100%; flex-grow: 100;"></iframe>');
|
||||
$pdfPreview.attr("src", openService.getUrlForDownload(`api/notes/${note.noteId}/open`));
|
||||
|
||||
$content.append($pdfPreview);
|
||||
}
|
||||
else if (type === 'audio') {
|
||||
const $audioPreview = $('<audio controls></audio>')
|
||||
.attr("src", openService.getUrlForStreaming(`api/notes/${note.noteId}/open-partial`))
|
||||
.attr("type", note.mime)
|
||||
.css("width", "100%");
|
||||
|
||||
$content.append($audioPreview);
|
||||
}
|
||||
else if (type === 'video') {
|
||||
const $videoPreview = $('<video controls></video>')
|
||||
.attr("src", openService.getUrlForDownload(`api/notes/${note.noteId}/open-partial`))
|
||||
.attr("type", note.mime)
|
||||
.css("width", "100%");
|
||||
|
||||
$content.append($videoPreview);
|
||||
}
|
||||
|
||||
$content.append(
|
||||
$('<div style="display: flex; justify-content: space-evenly; margin-top: 5px;">')
|
||||
.append($downloadButton)
|
||||
.append($openButton)
|
||||
);
|
||||
|
||||
$renderedContent.append($content);
|
||||
}
|
||||
else if (type === 'mermaid') {
|
||||
await libraryLoader.requireLibrary(libraryLoader.MERMAID);
|
||||
|
||||
const blob = await note.getBlob();
|
||||
const content = blob.content || "";
|
||||
|
||||
$renderedContent
|
||||
.css("display", "flex")
|
||||
.css("justify-content", "space-around");
|
||||
|
||||
const documentStyle = window.getComputedStyle(document.documentElement);
|
||||
const mermaidTheme = documentStyle.getPropertyValue('--mermaid-theme');
|
||||
|
||||
mermaid.mermaidAPI.initialize({ startOnLoad: false, theme: mermaidTheme.trim(), securityLevel: 'antiscript' });
|
||||
|
||||
try {
|
||||
mermaid.mermaidAPI.render("in-mermaid-graph-" + idCounter++, content,
|
||||
content => $renderedContent.append($(content)));
|
||||
} catch (e) {
|
||||
const $error = $("<p>The diagram could not displayed.</p>");
|
||||
|
||||
$renderedContent.append($error);
|
||||
}
|
||||
}
|
||||
else if (type === 'render') {
|
||||
const $content = $('<div>');
|
||||
|
||||
await renderService.render(note, $content, this.ctx);
|
||||
|
||||
$renderedContent.append($content);
|
||||
}
|
||||
else if (type === 'canvas') {
|
||||
// make sure surrounding container has size of what is visible. Then image is shrinked to its boundaries
|
||||
$renderedContent.css({height: "100%", width:"100%"});
|
||||
|
||||
const blob = await note.getBlob();
|
||||
const content = blob.content || "";
|
||||
|
||||
try {
|
||||
const placeHolderSVG = "<svg />";
|
||||
const data = JSON.parse(content)
|
||||
const svg = data.svg || placeHolderSVG;
|
||||
/**
|
||||
* maxWidth: size down to 100% (full) width of container but do not enlarge!
|
||||
* height:auto to ensure that height scales with width
|
||||
*/
|
||||
$renderedContent.append($(svg).css({maxWidth: "100%", maxHeight: "100%", height: "auto", width: "auto"}));
|
||||
} catch(err) {
|
||||
console.error("error parsing content as JSON", content, err);
|
||||
$renderedContent.append($("<div>").text("Error parsing content. Please check console.error() for more details."));
|
||||
}
|
||||
}
|
||||
else if (type === 'book') {
|
||||
// nothing, a book doesn't have its own content
|
||||
}
|
||||
else if (!options.tooltip && type === 'protectedSession') {
|
||||
const $button = $(`<button class="btn btn-sm"><span class="bx bx-log-in"></span> Enter protected session</button>`)
|
||||
.on('click', protectedSessionService.enterProtectedSession);
|
||||
|
||||
$renderedContent.append(
|
||||
$("<div>")
|
||||
.append("<div>This note is protected and to access it you need to enter password.</div>")
|
||||
.append("<br/>")
|
||||
.append($button)
|
||||
);
|
||||
}
|
||||
else {
|
||||
$renderedContent.append($("<p><em>Content of this note cannot be displayed in the book format</em></p>"));
|
||||
}
|
||||
|
||||
$renderedContent.addClass(note.getCssClass());
|
||||
|
||||
return {
|
||||
$renderedContent,
|
||||
type
|
||||
};
|
||||
}
|
||||
|
||||
async function renderChildrenList($renderedContent, note) {
|
||||
$renderedContent.css("padding", "10px");
|
||||
$renderedContent.addClass("text-with-ellipsis");
|
||||
|
||||
let childNoteIds = note.getChildNoteIds();
|
||||
|
||||
if (childNoteIds.length > 10) {
|
||||
childNoteIds = childNoteIds.slice(0, 10);
|
||||
}
|
||||
|
||||
// just load the first 10 child notes
|
||||
const childNotes = await froca.getNotes(childNoteIds);
|
||||
|
||||
for (const childNote of childNotes) {
|
||||
$renderedContent.append(await linkService.createNoteLink(`${note.noteId}/${childNote.noteId}`, {
|
||||
showTooltip: false,
|
||||
showNoteIcon: true
|
||||
}));
|
||||
|
||||
$renderedContent.append("<br>");
|
||||
}
|
||||
}
|
||||
|
||||
function trim(text, doTrim) {
|
||||
if (!doTrim) {
|
||||
return text;
|
||||
}
|
||||
else {
|
||||
return text.substr(0, Math.min(text.length, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
function getRenderingType(note) {
|
||||
let type = note.type;
|
||||
|
||||
if (type === 'file' && note.mime === 'application/pdf') {
|
||||
type = 'pdf';
|
||||
} else if (type === 'file' && note.mime.startsWith('audio/')) {
|
||||
type = 'audio';
|
||||
} else if (type === 'file' && note.mime.startsWith('video/')) {
|
||||
type = 'video';
|
||||
}
|
||||
|
||||
if (note.isProtected) {
|
||||
if (protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
protectedSessionHolder.touchProtectedSession();
|
||||
}
|
||||
else {
|
||||
type = 'protectedSession';
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
export default {
|
||||
getRenderedContent
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import linkService from "./link.js";
|
||||
import noteContentRenderer from "./note_content_renderer.js";
|
||||
import contentRenderer from "./content_renderer.js";
|
||||
import froca from "./froca.js";
|
||||
import attributeRenderer from "./attribute_renderer.js";
|
||||
import libraryLoader from "./library_loader.js";
|
||||
@@ -333,7 +333,7 @@ class NoteListRenderer {
|
||||
const $content = $('<div class="note-book-content">');
|
||||
|
||||
try {
|
||||
const {$renderedContent, type} = await noteContentRenderer.getRenderedContent(note, {
|
||||
const {$renderedContent, type} = await contentRenderer.getRenderedContent(note, {
|
||||
trim: this.viewType === 'grid' // for grid only short content is needed
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import linkService from "./link.js";
|
||||
import froca from "./froca.js";
|
||||
import utils from "./utils.js";
|
||||
import attributeRenderer from "./attribute_renderer.js";
|
||||
import noteContentRenderer from "./note_content_renderer.js";
|
||||
import contentRenderer from "./content_renderer.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
|
||||
function setupGlobalTooltip() {
|
||||
@@ -90,7 +90,7 @@ async function renderTooltip(note) {
|
||||
|
||||
const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note);
|
||||
|
||||
const {$renderedContent} = await noteContentRenderer.getRenderedContent(note, {
|
||||
const {$renderedContent} = await contentRenderer.getRenderedContent(note, {
|
||||
tooltip: true,
|
||||
trim: true
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import server from "../services/server.js";
|
||||
import options from "../services/options.js";
|
||||
import imageService from "../services/image.js";
|
||||
import linkService from "../services/link.js";
|
||||
import contentRenderer from "../services/content_renderer.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="attachment-detail">
|
||||
@@ -140,17 +141,7 @@ 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(this.renderContent());
|
||||
}
|
||||
|
||||
renderContent() {
|
||||
if (this.attachment.content) {
|
||||
return $("<pre>").text(this.attachment.content);
|
||||
} else if (this.attachment.role === 'image') {
|
||||
return `<img src="api/attachments/${this.attachment.attachmentId}/image/${encodeURIComponent(this.attachment.title)}?${this.attachment.utcDateModified}">`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
this.$wrapper.find('.attachment-content').append(contentRenderer.getRenderedContent(this.attachment));
|
||||
}
|
||||
|
||||
copyAttachmentReferenceToClipboard() {
|
||||
@@ -164,7 +155,7 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
||||
if (attachmentChange.isDeleted) {
|
||||
this.toggleInt(false);
|
||||
} else {
|
||||
this.attachment = await server.get(`attachments/${this.attachment.attachmentId}?includeContent=true`);
|
||||
this.attachment = await server.get(`attachments/${this.attachment.attachmentId}`);
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import TypeWidget from "./type_widget.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import linkService from "../../services/link.js";
|
||||
import noteContentRenderer from "../../services/note_content_renderer.js";
|
||||
import contentRenderer from "../../services/content_renderer.js";
|
||||
import utils from "../../services/utils.js";
|
||||
|
||||
export default class AbstractTextTypeWidget extends TypeWidget {
|
||||
@@ -86,7 +86,7 @@ export default class AbstractTextTypeWidget extends TypeWidget {
|
||||
.append($link)
|
||||
);
|
||||
|
||||
const {$renderedContent, type} = await noteContentRenderer.getRenderedContent(note);
|
||||
const {$renderedContent, type} = await contentRenderer.getRenderedContent(note);
|
||||
|
||||
$wrapper.append(
|
||||
$(`<div class="include-note-content type-${type}">`)
|
||||
|
||||
@@ -50,7 +50,7 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
|
||||
})
|
||||
);
|
||||
|
||||
const attachment = await server.get(`attachments/${this.attachmentId}/?includeContent=true`);
|
||||
const attachment = await server.get(`attachments/${this.attachmentId}`);
|
||||
|
||||
if (!attachment) {
|
||||
this.$wrapper.html("<strong>This attachment has been deleted.</strong>");
|
||||
@@ -67,8 +67,6 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
const attachmentChange = loadResults.getAttachments().find(att => att.attachmentId === this.attachmentId);
|
||||
|
||||
console.log(attachmentChange);
|
||||
|
||||
if (attachmentChange?.isDeleted) {
|
||||
this.refresh(); // all other updates are handled within AttachmentDetailWidget
|
||||
}
|
||||
|
||||
@@ -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?includeContent=true`);
|
||||
const attachments = await server.get(`notes/${this.noteId}/attachments`);
|
||||
|
||||
if (attachments.length === 0) {
|
||||
this.$list.html('<div class="alert alert-info">This note has no attachments.</div>');
|
||||
@@ -73,7 +73,7 @@ export default class AttachmentListTypeWidget extends TypeWidget {
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
// updates and deletions are handled by the detail, for new attachments the whole list has to be refreshed
|
||||
const attachmentsAdded = loadResults.getAttachments()
|
||||
.find(att => this.renderedAttachmentIds.has(att.attachmentId));
|
||||
.some(att => !this.renderedAttachmentIds.has(att.attachmentId));
|
||||
|
||||
if (attachmentsAdded) {
|
||||
this.refresh();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const becca = require("../../becca/becca");
|
||||
const utils = require("../../services/utils");
|
||||
const blobService = require("../../services/blob.js");
|
||||
|
||||
function getAttachmentBlob(req) {
|
||||
@@ -9,42 +8,15 @@ function getAttachmentBlob(req) {
|
||||
}
|
||||
|
||||
function getAttachments(req) {
|
||||
const includeContent = req.query.includeContent === 'true';
|
||||
const note = becca.getNoteOrThrow(req.params.noteId);
|
||||
|
||||
return note.getAttachments()
|
||||
.map(attachment => processAttachment(attachment, includeContent));
|
||||
return note.getAttachments();
|
||||
}
|
||||
|
||||
function getAttachment(req) {
|
||||
const includeContent = req.query.includeContent === 'true';
|
||||
const {attachmentId} = req.params;
|
||||
|
||||
const attachment = becca.getAttachmentOrThrow(attachmentId);
|
||||
|
||||
return processAttachment(attachment, includeContent);
|
||||
}
|
||||
|
||||
function processAttachment(attachment, includeContent) {
|
||||
const pojo = attachment.getPojo();
|
||||
|
||||
if (includeContent) {
|
||||
if (utils.isStringNote(null, attachment.mime)) {
|
||||
pojo.content = attachment.getContent()?.toString();
|
||||
pojo.contentLength = pojo.content.length;
|
||||
|
||||
const MAX_ATTACHMENT_LENGTH = 1_000_000;
|
||||
|
||||
if (pojo.content.length > MAX_ATTACHMENT_LENGTH) {
|
||||
pojo.content = pojo.content.substring(0, MAX_ATTACHMENT_LENGTH);
|
||||
}
|
||||
} else {
|
||||
const content = attachment.getContent();
|
||||
pojo.contentLength = content?.length;
|
||||
}
|
||||
}
|
||||
|
||||
return pojo;
|
||||
return becca.getAttachmentOrThrow(attachmentId);
|
||||
}
|
||||
|
||||
function saveAttachment(req) {
|
||||
|
||||
@@ -311,18 +311,19 @@ function protectNote(note, protect) {
|
||||
const content = note.getContent();
|
||||
|
||||
note.isProtected = protect;
|
||||
|
||||
// see https://github.com/zadam/trilium/issues/3523
|
||||
// IIRC a zero-sized buffer can be returned as null from the database
|
||||
if (content !== null) {
|
||||
// this will force de/encryption
|
||||
note.setContent(content);
|
||||
}
|
||||
|
||||
note.save();
|
||||
note.setContent(content, { forceSave: true });
|
||||
}
|
||||
|
||||
noteRevisionService.protectNoteRevisions(note);
|
||||
|
||||
for (const attachment of note.getAttachments()) {
|
||||
if (protect !== attachment.isProtected) {
|
||||
const content = attachment.getContent();
|
||||
|
||||
attachment.isProtected = protect;
|
||||
attachment.setContent(content, { forceSave: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Could not un/protect note '${note.noteId}'`);
|
||||
|
||||
Reference in New Issue
Block a user