mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
attachment improvements
This commit is contained in:
parent
fd8a2d4d92
commit
9be524ef89
@ -114,6 +114,17 @@ function formatLabel(label) {
|
||||
return str;
|
||||
}
|
||||
|
||||
function formatSize(size) {
|
||||
size = Math.max(Math.round(size / 1024), 1);
|
||||
|
||||
if (size < 1024) {
|
||||
return `${size} KiB`;
|
||||
}
|
||||
else {
|
||||
return `${Math.round(size / 102.4) / 10} MiB`;
|
||||
}
|
||||
}
|
||||
|
||||
function toObject(array, fn) {
|
||||
const obj = {};
|
||||
|
||||
@ -363,6 +374,7 @@ export default {
|
||||
formatDate,
|
||||
formatDateISO,
|
||||
formatDateTime,
|
||||
formatSize,
|
||||
localNowDateTime,
|
||||
now,
|
||||
isElectron,
|
||||
|
95
src/public/app/widgets/buttons/attachments_actions.js
Normal file
95
src/public/app/widgets/buttons/attachments_actions.js
Normal file
@ -0,0 +1,95 @@
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="dropdown attachment-actions">
|
||||
<style>
|
||||
.attachment-actions {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.attachment-actions .dropdown-menu {
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
.attachment-actions .dropdown-item[disabled], .attachment-actions .dropdown-item[disabled]:hover {
|
||||
color: var(--muted-text-color) !important;
|
||||
background-color: transparent !important;
|
||||
pointer-events: none; /* makes it unclickable */
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"></button>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a data-trigger-command="deleteAttachment" class="dropdown-item delete-attachment-button">Delete attachment</a>
|
||||
<a data-trigger-command="pullAttachmentIntoNote" class="dropdown-item pull-attachment-into-note-button">Pull attachment into note</a>
|
||||
<a data-trigger-command="pullAttachmentIntoNote" class="dropdown-item pull-attachment-into-note-button">Copy into clipboard</a>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class AttachmentActionsWidget extends BasicWidget {
|
||||
constructor(attachment) {
|
||||
super();
|
||||
|
||||
this.attachment = attachment;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
// this.$findInTextButton = this.$widget.find('.find-in-text-button');
|
||||
// this.$printActiveNoteButton = this.$widget.find('.print-active-note-button');
|
||||
// this.$showSourceButton = this.$widget.find('.show-source-button');
|
||||
// this.$renderNoteButton = this.$widget.find('.render-note-button');
|
||||
//
|
||||
// this.$exportNoteButton = this.$widget.find('.export-note-button');
|
||||
// this.$exportNoteButton.on("click", () => {
|
||||
// if (this.$exportNoteButton.hasClass("disabled")) {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// this.triggerCommand("showExportDialog", {
|
||||
// notePath: this.noteContext.notePath,
|
||||
// defaultType: "single"
|
||||
// });
|
||||
// });
|
||||
//
|
||||
// this.$importNoteButton = this.$widget.find('.import-files-button');
|
||||
// this.$importNoteButton.on("click", () => this.triggerCommand("showImportDialog", {noteId: this.noteId}));
|
||||
//
|
||||
// this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
|
||||
//
|
||||
// this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
|
||||
//
|
||||
// this.$deleteNoteButton = this.$widget.find(".delete-note-button");
|
||||
// this.$deleteNoteButton.on("click", () => {
|
||||
// if (this.note.noteId === 'root') {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// branchService.deleteNotes([this.note.getParentBranches()[0].branchId], true);
|
||||
// });
|
||||
}
|
||||
|
||||
refreshWithNote(note) {
|
||||
// this.toggleDisabled(this.$findInTextButton, ['text', 'code', 'book', 'search'].includes(note.type));
|
||||
//
|
||||
// this.toggleDisabled(this.$showSourceButton, ['text', 'relationMap', 'mermaid'].includes(note.type));
|
||||
//
|
||||
// this.toggleDisabled(this.$printActiveNoteButton, ['text', 'code'].includes(note.type));
|
||||
//
|
||||
// this.$renderNoteButton.toggle(note.type === 'render');
|
||||
//
|
||||
// this.$openNoteExternallyButton.toggle(utils.isElectron());
|
||||
}
|
||||
|
||||
toggleDisabled($el, enable) {
|
||||
if (enable) {
|
||||
$el.removeAttr('disabled');
|
||||
} else {
|
||||
$el.attr('disabled', 'disabled');
|
||||
}
|
||||
}
|
||||
}
|
@ -85,6 +85,11 @@ export default class BacklinksWidget extends NoteContextAwareWidget {
|
||||
async refreshWithNote(note) {
|
||||
this.clearItems();
|
||||
|
||||
if (this.noteContext?.viewScope?.viewMode !== 'default') {
|
||||
this.toggle(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// can't use froca since that would count only relations from loaded notes
|
||||
const resp = await server.get(`note-map/${this.noteId}/backlink-count`);
|
||||
|
||||
|
@ -70,10 +70,16 @@ export default class NoteTitleWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.$noteTitle.val(note.title);
|
||||
const viewMode = this.noteContext.viewScope.viewMode;
|
||||
this.$noteTitle.val(viewMode === 'default'
|
||||
? note.title
|
||||
: `${viewMode}: ${note.title}`);
|
||||
|
||||
this.$noteTitle.prop("readonly", (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable())
|
||||
|| ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId));
|
||||
this.$noteTitle.prop("readonly",
|
||||
(note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable())
|
||||
|| ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId)
|
||||
|| viewMode !== 'default'
|
||||
);
|
||||
|
||||
this.setProtectedStatus(note);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import server from "../../services/server.js";
|
||||
import utils from "../../services/utils.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="note-info-widget">
|
||||
@ -105,12 +106,12 @@ export default class NoteInfoWidget extends NoteContextAwareWidget {
|
||||
this.$subTreeSize.empty().append($('<span class="bx bx-loader bx-spin"></span>'));
|
||||
|
||||
const noteSizeResp = await server.get(`stats/note-size/${this.noteId}`);
|
||||
this.$noteSize.text(this.formatSize(noteSizeResp.noteSize));
|
||||
this.$noteSize.text(utils.formatSize(noteSizeResp.noteSize));
|
||||
|
||||
const subTreeResp = await server.get(`stats/subtree-size/${this.noteId}`);
|
||||
|
||||
if (subTreeResp.subTreeNoteCount > 1) {
|
||||
this.$subTreeSize.text(`(subtree size: ${this.formatSize(subTreeResp.subTreeSize)} in ${subTreeResp.subTreeNoteCount} notes)`);
|
||||
this.$subTreeSize.text(`(subtree size: ${utils.formatSize(subTreeResp.subTreeSize)} in ${subTreeResp.subTreeNoteCount} notes)`);
|
||||
}
|
||||
else {
|
||||
this.$subTreeSize.text("");
|
||||
@ -143,17 +144,6 @@ export default class NoteInfoWidget extends NoteContextAwareWidget {
|
||||
this.$noteSizesWrapper.hide();
|
||||
}
|
||||
|
||||
formatSize(size) {
|
||||
size = Math.max(Math.round(size / 1024), 1);
|
||||
|
||||
if (size < 1024) {
|
||||
return `${size} KiB`;
|
||||
}
|
||||
else {
|
||||
return `${Math.round(size / 102.4) / 10} MiB`;
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({loadResults}) {
|
||||
if (loadResults.isNoteReloaded(this.noteId) || loadResults.isNoteContentReloaded(this.noteId)) {
|
||||
this.refresh();
|
||||
|
@ -1,14 +1,29 @@
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import server from "../../services/server.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import AttachmentActionsWidget from "../buttons/attachments_actions.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="note-attachments note-detail-printable">
|
||||
<div class="attachments note-detail-printable">
|
||||
<style>
|
||||
.note-attachments {
|
||||
.attachments {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.attachment-content {
|
||||
.attachment-wrapper {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.attachment-title-line {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.attachment-details {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.attachment-content pre {
|
||||
max-height: 400px;
|
||||
background: var(--accented-background-color);
|
||||
padding: 10px;
|
||||
@ -16,18 +31,15 @@ const TPL = `
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.attachment-details th {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
.attachment-content img {
|
||||
margin: 10px;
|
||||
max-height: 300px;
|
||||
max-width: 90%;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="alert alert-info" style="margin: 10px 0 10px 0; padding: 20px;">
|
||||
Note attachments are pieces of data attached to a given note, providing attachment support.
|
||||
This view is useful for diagnostics.
|
||||
</div>
|
||||
|
||||
<div class="note-attachment-list"></div>
|
||||
<div class="attachment-list"></div>
|
||||
</div>`;
|
||||
|
||||
export default class AttachmentsTypeWidget extends TypeWidget {
|
||||
@ -35,13 +47,14 @@ export default class AttachmentsTypeWidget extends TypeWidget {
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$list = this.$widget.find('.note-attachment-list');
|
||||
this.$list = this.$widget.find('.attachment-list');
|
||||
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
async doRefresh(note) {
|
||||
this.$list.empty();
|
||||
this.children = [];
|
||||
|
||||
const attachments = await server.get(`notes/${this.noteId}/attachments?includeContent=true`);
|
||||
|
||||
@ -52,28 +65,36 @@ export default class AttachmentsTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
for (const attachment of attachments) {
|
||||
const attachmentActionsWidget = new AttachmentActionsWidget();
|
||||
this.child(attachmentActionsWidget);
|
||||
|
||||
this.$list.append(
|
||||
$('<div class="note-attachment-wrapper">')
|
||||
$('<div class="attachment-wrapper">')
|
||||
.append(
|
||||
$('<h4>').append($('<span class="attachment-name">').text(attachment.name))
|
||||
)
|
||||
.append(
|
||||
$('<table class="attachment-details">')
|
||||
$('<div class="attachment-title-line">')
|
||||
.append($('<h4>').append($('<span class="attachment-title">').text(attachment.title)))
|
||||
.append(
|
||||
$('<tr>')
|
||||
.append($('<th>').text('Length:'))
|
||||
.append($('<td>').text(attachment.contentLength))
|
||||
.append($('<th>').text('MIME:'))
|
||||
.append($('<td>').text(attachment.mime))
|
||||
.append($('<th>').text('Date modified:'))
|
||||
.append($('<td>').text(attachment.utcDateModified))
|
||||
$('<div class="attachment-details">')
|
||||
.text(`Role: ${attachment.role}, Size: ${utils.formatSize(attachment.contentLength)}`)
|
||||
)
|
||||
.append($('<div style="flex: 1 1;">')) // spacer
|
||||
.append(attachmentActionsWidget.render())
|
||||
)
|
||||
.append(
|
||||
$('<pre class="attachment-content">')
|
||||
.text(attachment.content)
|
||||
$('<div class="attachment-content">')
|
||||
.append(this.renderContent(attachment))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
renderContent(attachment) {
|
||||
if (attachment.content) {
|
||||
return $("<pre>").text(attachment.content);
|
||||
} else if (attachment.role === 'image') {
|
||||
return `<img src="api/notes/${attachment.parentId}/images/${attachment.attachmentId}/${encodeURIComponent(attachment.title)}?${attachment.utcDateModified}">`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
@ -119,6 +119,10 @@ button.close:hover {
|
||||
border-radius: var(--button-border-radius);
|
||||
}
|
||||
|
||||
.icon-action-always-border {
|
||||
border-color: var(--button-border-color);
|
||||
}
|
||||
|
||||
.icon-action:hover:not(.disabled) {
|
||||
text-decoration: none;
|
||||
border-color: var(--button-border-color);
|
||||
|
@ -142,14 +142,19 @@ function getAttachments(req) {
|
||||
return attachments.map(attachment => {
|
||||
const pojo = attachment.getPojo();
|
||||
|
||||
if (includeContent && utils.isStringNote(null, attachment.mime)) {
|
||||
pojo.content = attachment.getContent()?.toString();
|
||||
pojo.contentLength = pojo.content.length;
|
||||
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;
|
||||
const MAX_ATTACHMENT_LENGTH = 1_000_000;
|
||||
|
||||
if (pojo.content.length > MAX_ATTACHMENT_LENGTH) {
|
||||
pojo.content = pojo.content.substring(0, MAX_ATTACHMENT_LENGTH);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user