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;
|
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) {
|
function toObject(array, fn) {
|
||||||
const obj = {};
|
const obj = {};
|
||||||
|
|
||||||
@ -363,6 +374,7 @@ export default {
|
|||||||
formatDate,
|
formatDate,
|
||||||
formatDateISO,
|
formatDateISO,
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
|
formatSize,
|
||||||
localNowDateTime,
|
localNowDateTime,
|
||||||
now,
|
now,
|
||||||
isElectron,
|
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) {
|
async refreshWithNote(note) {
|
||||||
this.clearItems();
|
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
|
// can't use froca since that would count only relations from loaded notes
|
||||||
const resp = await server.get(`note-map/${this.noteId}/backlink-count`);
|
const resp = await server.get(`note-map/${this.noteId}/backlink-count`);
|
||||||
|
|
||||||
|
@ -70,10 +70,16 @@ export default class NoteTitleWidget extends NoteContextAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async refreshWithNote(note) {
|
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())
|
this.$noteTitle.prop("readonly",
|
||||||
|| ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId));
|
(note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable())
|
||||||
|
|| ['_lbRoot', '_lbAvailableLaunchers', '_lbVisibleLaunchers'].includes(note.noteId)
|
||||||
|
|| viewMode !== 'default'
|
||||||
|
);
|
||||||
|
|
||||||
this.setProtectedStatus(note);
|
this.setProtectedStatus(note);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||||
import server from "../../services/server.js";
|
import server from "../../services/server.js";
|
||||||
|
import utils from "../../services/utils.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="note-info-widget">
|
<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>'));
|
this.$subTreeSize.empty().append($('<span class="bx bx-loader bx-spin"></span>'));
|
||||||
|
|
||||||
const noteSizeResp = await server.get(`stats/note-size/${this.noteId}`);
|
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}`);
|
const subTreeResp = await server.get(`stats/subtree-size/${this.noteId}`);
|
||||||
|
|
||||||
if (subTreeResp.subTreeNoteCount > 1) {
|
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 {
|
else {
|
||||||
this.$subTreeSize.text("");
|
this.$subTreeSize.text("");
|
||||||
@ -143,17 +144,6 @@ export default class NoteInfoWidget extends NoteContextAwareWidget {
|
|||||||
this.$noteSizesWrapper.hide();
|
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}) {
|
entitiesReloadedEvent({loadResults}) {
|
||||||
if (loadResults.isNoteReloaded(this.noteId) || loadResults.isNoteContentReloaded(this.noteId)) {
|
if (loadResults.isNoteReloaded(this.noteId) || loadResults.isNoteContentReloaded(this.noteId)) {
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
@ -1,14 +1,29 @@
|
|||||||
import TypeWidget from "./type_widget.js";
|
import TypeWidget from "./type_widget.js";
|
||||||
import server from "../../services/server.js";
|
import server from "../../services/server.js";
|
||||||
|
import utils from "../../services/utils.js";
|
||||||
|
import AttachmentActionsWidget from "../buttons/attachments_actions.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="note-attachments note-detail-printable">
|
<div class="attachments note-detail-printable">
|
||||||
<style>
|
<style>
|
||||||
.note-attachments {
|
.attachments {
|
||||||
padding: 15px;
|
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;
|
max-height: 400px;
|
||||||
background: var(--accented-background-color);
|
background: var(--accented-background-color);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@ -16,18 +31,15 @@ const TPL = `
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachment-details th {
|
.attachment-content img {
|
||||||
padding-left: 10px;
|
margin: 10px;
|
||||||
padding-right: 10px;
|
max-height: 300px;
|
||||||
|
max-width: 90%;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="alert alert-info" style="margin: 10px 0 10px 0; padding: 20px;">
|
<div class="attachment-list"></div>
|
||||||
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>`;
|
</div>`;
|
||||||
|
|
||||||
export default class AttachmentsTypeWidget extends TypeWidget {
|
export default class AttachmentsTypeWidget extends TypeWidget {
|
||||||
@ -35,13 +47,14 @@ export default class AttachmentsTypeWidget extends TypeWidget {
|
|||||||
|
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
this.$list = this.$widget.find('.note-attachment-list');
|
this.$list = this.$widget.find('.attachment-list');
|
||||||
|
|
||||||
super.doRender();
|
super.doRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
async doRefresh(note) {
|
async doRefresh(note) {
|
||||||
this.$list.empty();
|
this.$list.empty();
|
||||||
|
this.children = [];
|
||||||
|
|
||||||
const attachments = await server.get(`notes/${this.noteId}/attachments?includeContent=true`);
|
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) {
|
for (const attachment of attachments) {
|
||||||
|
const attachmentActionsWidget = new AttachmentActionsWidget();
|
||||||
|
this.child(attachmentActionsWidget);
|
||||||
|
|
||||||
this.$list.append(
|
this.$list.append(
|
||||||
$('<div class="note-attachment-wrapper">')
|
$('<div class="attachment-wrapper">')
|
||||||
.append(
|
.append(
|
||||||
$('<h4>').append($('<span class="attachment-name">').text(attachment.name))
|
$('<div class="attachment-title-line">')
|
||||||
|
.append($('<h4>').append($('<span class="attachment-title">').text(attachment.title)))
|
||||||
|
.append(
|
||||||
|
$('<div class="attachment-details">')
|
||||||
|
.text(`Role: ${attachment.role}, Size: ${utils.formatSize(attachment.contentLength)}`)
|
||||||
|
)
|
||||||
|
.append($('<div style="flex: 1 1;">')) // spacer
|
||||||
|
.append(attachmentActionsWidget.render())
|
||||||
)
|
)
|
||||||
.append(
|
.append(
|
||||||
$('<table class="attachment-details">')
|
$('<div class="attachment-content">')
|
||||||
.append(
|
.append(this.renderContent(attachment))
|
||||||
$('<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))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.append(
|
|
||||||
$('<pre class="attachment-content">')
|
|
||||||
.text(attachment.content)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
border-radius: var(--button-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-action-always-border {
|
||||||
|
border-color: var(--button-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
.icon-action:hover:not(.disabled) {
|
.icon-action:hover:not(.disabled) {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-color: var(--button-border-color);
|
border-color: var(--button-border-color);
|
||||||
|
@ -142,7 +142,8 @@ function getAttachments(req) {
|
|||||||
return attachments.map(attachment => {
|
return attachments.map(attachment => {
|
||||||
const pojo = attachment.getPojo();
|
const pojo = attachment.getPojo();
|
||||||
|
|
||||||
if (includeContent && utils.isStringNote(null, attachment.mime)) {
|
if (includeContent) {
|
||||||
|
if (utils.isStringNote(null, attachment.mime)) {
|
||||||
pojo.content = attachment.getContent()?.toString();
|
pojo.content = attachment.getContent()?.toString();
|
||||||
pojo.contentLength = pojo.content.length;
|
pojo.contentLength = pojo.content.length;
|
||||||
|
|
||||||
@ -151,6 +152,10 @@ function getAttachments(req) {
|
|||||||
if (pojo.content.length > MAX_ATTACHMENT_LENGTH) {
|
if (pojo.content.length > MAX_ATTACHMENT_LENGTH) {
|
||||||
pojo.content = pojo.content.substring(0, MAX_ATTACHMENT_LENGTH);
|
pojo.content = pojo.content.substring(0, MAX_ATTACHMENT_LENGTH);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const content = attachment.getContent();
|
||||||
|
pojo.contentLength = content?.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pojo;
|
return pojo;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user