mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
wip attachment widget
This commit is contained in:
parent
53aebf1448
commit
2bc78ccafb
@ -35,13 +35,12 @@ CREATE TABLE IF NOT EXISTS "notes" (
|
|||||||
`isProtected` INT NOT NULL DEFAULT 0,
|
`isProtected` INT NOT NULL DEFAULT 0,
|
||||||
`type` TEXT NOT NULL DEFAULT 'text',
|
`type` TEXT NOT NULL DEFAULT 'text',
|
||||||
`mime` TEXT NOT NULL DEFAULT 'text/html',
|
`mime` TEXT NOT NULL DEFAULT 'text/html',
|
||||||
`blobId` TEXT DEFAULT NULL,
|
|
||||||
`isDeleted` INT NOT NULL DEFAULT 0,
|
`isDeleted` INT NOT NULL DEFAULT 0,
|
||||||
`deleteId` TEXT DEFAULT NULL,
|
`deleteId` TEXT DEFAULT NULL,
|
||||||
`dateCreated` TEXT NOT NULL,
|
`dateCreated` TEXT NOT NULL,
|
||||||
`dateModified` TEXT NOT NULL,
|
`dateModified` TEXT NOT NULL,
|
||||||
`utcDateCreated` TEXT NOT NULL,
|
`utcDateCreated` TEXT NOT NULL,
|
||||||
`utcDateModified` TEXT NOT NULL
|
`utcDateModified` TEXT NOT NULL, blobId TEXT DEFAULT NULL,
|
||||||
PRIMARY KEY(`noteId`));
|
PRIMARY KEY(`noteId`));
|
||||||
CREATE TABLE IF NOT EXISTS "note_revisions" (`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
|
CREATE TABLE IF NOT EXISTS "note_revisions" (`noteRevisionId` TEXT NOT NULL PRIMARY KEY,
|
||||||
`noteId` TEXT NOT NULL,
|
`noteId` TEXT NOT NULL,
|
||||||
@ -49,12 +48,11 @@ CREATE TABLE IF NOT EXISTS "note_revisions" (`noteRevisionId` TEXT NOT NULL PRIM
|
|||||||
mime TEXT DEFAULT '' NOT NULL,
|
mime TEXT DEFAULT '' NOT NULL,
|
||||||
`title` TEXT NOT NULL,
|
`title` TEXT NOT NULL,
|
||||||
`isProtected` INT NOT NULL DEFAULT 0,
|
`isProtected` INT NOT NULL DEFAULT 0,
|
||||||
`blobId` TEXT DEFAULT NULL,
|
|
||||||
`utcDateLastEdited` TEXT NOT NULL,
|
`utcDateLastEdited` TEXT NOT NULL,
|
||||||
`utcDateCreated` TEXT NOT NULL,
|
`utcDateCreated` TEXT NOT NULL,
|
||||||
`utcDateModified` TEXT NOT NULL,
|
`utcDateModified` TEXT NOT NULL,
|
||||||
`dateLastEdited` TEXT NOT NULL,
|
`dateLastEdited` TEXT NOT NULL,
|
||||||
`dateCreated` TEXT NOT NULL);
|
`dateCreated` TEXT NOT NULL, blobId TEXT DEFAULT NULL);
|
||||||
CREATE TABLE IF NOT EXISTS "options"
|
CREATE TABLE IF NOT EXISTS "options"
|
||||||
(
|
(
|
||||||
name TEXT not null PRIMARY KEY,
|
name TEXT not null PRIMARY KEY,
|
||||||
@ -104,6 +102,13 @@ CREATE TABLE IF NOT EXISTS "recent_notes"
|
|||||||
notePath TEXT not null,
|
notePath TEXT not null,
|
||||||
utcDateCreated TEXT not null
|
utcDateCreated TEXT not null
|
||||||
);
|
);
|
||||||
|
CREATE TABLE IF NOT EXISTS "blobs" (
|
||||||
|
`blobId` TEXT NOT NULL,
|
||||||
|
`content` TEXT NULL DEFAULT NULL,
|
||||||
|
`dateModified` TEXT NOT NULL,
|
||||||
|
`utcDateModified` TEXT NOT NULL,
|
||||||
|
PRIMARY KEY(`blobId`)
|
||||||
|
);
|
||||||
CREATE TABLE IF NOT EXISTS "attachments"
|
CREATE TABLE IF NOT EXISTS "attachments"
|
||||||
(
|
(
|
||||||
attachmentId TEXT not null primary key,
|
attachmentId TEXT not null primary key,
|
||||||
|
@ -123,7 +123,7 @@ class Becca {
|
|||||||
|
|
||||||
/** @returns {BAttachment|null} */
|
/** @returns {BAttachment|null} */
|
||||||
getAttachment(attachmentId) {
|
getAttachment(attachmentId) {
|
||||||
const row = sql.getRow("SELECT * FROM attachments WHERE attachmentId = ?", [attachmentId]);
|
const row = sql.getRow("SELECT * FROM attachments WHERE attachmentId = ? AND isDeleted = 0", [attachmentId]);
|
||||||
|
|
||||||
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 row ? new BAttachment(row) : null;
|
||||||
|
@ -1352,7 +1352,6 @@ class BNote extends AbstractBeccaEntity {
|
|||||||
*
|
*
|
||||||
* @returns {BAttachment|null} - null if note is not eligible for conversion
|
* @returns {BAttachment|null} - null if note is not eligible for conversion
|
||||||
*/
|
*/
|
||||||
|
|
||||||
convertToParentAttachment(opts = {force: false}) {
|
convertToParentAttachment(opts = {force: false}) {
|
||||||
if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) {
|
if (this.type !== 'image' || !this.isContentAvailable() || this.hasChildren() || this.getParentBranches().length !== 1) {
|
||||||
return null;
|
return null;
|
||||||
@ -1394,6 +1393,61 @@ class BNote extends AbstractBeccaEntity {
|
|||||||
return attachment;
|
return attachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param attachmentId
|
||||||
|
* @returns {{note: BNote, branch: BBranch}}
|
||||||
|
*/
|
||||||
|
convertAttachmentToChildNote(attachmentId) {
|
||||||
|
if (this.type === 'search') {
|
||||||
|
throw new Error(`Note of type search cannot have child notes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachment = this.getAttachmentById(attachmentId);
|
||||||
|
|
||||||
|
if (!attachment) {
|
||||||
|
throw new NotFoundError(`Attachment '${attachmentId} of note '${this.noteId}' doesn't exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachmentRoleToNoteTypeMapping = {
|
||||||
|
'image': 'image'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!(attachment.role in attachmentRoleToNoteTypeMapping)) {
|
||||||
|
throw new Error(`Mapping from attachment role '${attachment.role}' to note's type is not defined`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isContentAvailable()) { // isProtected is same for attachment
|
||||||
|
throw new Error(`Cannot convert protected attachment outside of protected session`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const noteService = require('../../services/notes');
|
||||||
|
|
||||||
|
const {note, branch} = noteService.createNewNote({
|
||||||
|
parentNoteId: this.noteId,
|
||||||
|
title: attachment.title,
|
||||||
|
type: attachmentRoleToNoteTypeMapping[attachment.role],
|
||||||
|
mime: attachment.mime,
|
||||||
|
content: attachment.getContent(),
|
||||||
|
isProtected: this.isProtected
|
||||||
|
});
|
||||||
|
|
||||||
|
attachment.markAsDeleted();
|
||||||
|
|
||||||
|
if (attachment.role === 'image' && this.type === 'text') {
|
||||||
|
const origContent = this.getContent();
|
||||||
|
const oldAttachmentUrl = `api/notes/${this.noteId}/images/${attachment.attachmentId}/`;
|
||||||
|
const newNoteUrl = `api/images/${note.noteId}/`;
|
||||||
|
|
||||||
|
const fixedContent = utils.replaceAll(origContent, oldAttachmentUrl, newNoteUrl);
|
||||||
|
|
||||||
|
if (origContent !== fixedContent) {
|
||||||
|
this.setContent(fixedContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { note, branch };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (Soft) delete a note and all its descendants.
|
* (Soft) delete a note and all its descendants.
|
||||||
*
|
*
|
||||||
|
@ -33,8 +33,9 @@ async function processEntityChanges(entityChanges) {
|
|||||||
options.set(ec.entity.name, ec.entity.value);
|
options.set(ec.entity.name, ec.entity.value);
|
||||||
|
|
||||||
loadResults.addOption(ec.entity.name);
|
loadResults.addOption(ec.entity.name);
|
||||||
}
|
} else if (ec.entityName === 'attachments') {
|
||||||
else if (['etapi_tokens', 'attachments'].includes(ec.entityName)) {
|
loadResults.addAttachment(ec.entity);
|
||||||
|
} else if (ec.entityName === 'etapi_tokens') {
|
||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -23,6 +23,8 @@ export default class LoadResults {
|
|||||||
this.contentNoteIdToComponentId = [];
|
this.contentNoteIdToComponentId = [];
|
||||||
|
|
||||||
this.options = [];
|
this.options = [];
|
||||||
|
|
||||||
|
this.attachments = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
getEntity(entityName, entityId) {
|
getEntity(entityName, entityId) {
|
||||||
@ -116,6 +118,14 @@ export default class LoadResults {
|
|||||||
return this.options.includes(name);
|
return this.options.includes(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addAttachment(attachment) {
|
||||||
|
this.attachments.push(attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAttachments() {
|
||||||
|
return this.attachments;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean} true if there are changes which could affect the attributes (including inherited ones)
|
* @returns {boolean} true if there are changes which could affect the attributes (including inherited ones)
|
||||||
* notably changes in note itself should not have any effect on attributes
|
* notably changes in note itself should not have any effect on attributes
|
||||||
@ -132,7 +142,8 @@ export default class LoadResults {
|
|||||||
&& this.noteReorderings.length === 0
|
&& this.noteReorderings.length === 0
|
||||||
&& this.noteRevisions.length === 0
|
&& this.noteRevisions.length === 0
|
||||||
&& this.contentNoteIdToComponentId.length === 0
|
&& this.contentNoteIdToComponentId.length === 0
|
||||||
&& this.options.length === 0;
|
&& this.options.length === 0
|
||||||
|
&& this.attachments.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
isEmptyForTree() {
|
isEmptyForTree() {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import utils from "../../services/utils.js";
|
import utils from "../services/utils.js";
|
||||||
import AttachmentActionsWidget from "../buttons/attachments_actions.js";
|
import AttachmentActionsWidget from "./buttons/attachments_actions.js";
|
||||||
import BasicWidget from "./basic_widget.js";
|
import BasicWidget from "./basic_widget.js";
|
||||||
|
import server from "../services/server.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="attachment-detail">
|
<div class="attachment-detail">
|
||||||
@ -38,7 +39,7 @@ const TPL = `
|
|||||||
<div class="attachment-title-line">
|
<div class="attachment-title-line">
|
||||||
<h4 class="attachment-title"></h4>
|
<h4 class="attachment-title"></h4>
|
||||||
<div class="attachment-details"></div>
|
<div class="attachment-details"></div>
|
||||||
<div style="flex: 1 1;">
|
<div style="flex: 1 1;"></div>
|
||||||
<div class="attachment-actions-container"></div>
|
<div class="attachment-actions-container"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -50,6 +51,7 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
|||||||
constructor(attachment) {
|
constructor(attachment) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.contentSized();
|
||||||
this.attachment = attachment;
|
this.attachment = attachment;
|
||||||
this.attachmentActionsWidget = new AttachmentActionsWidget(attachment);
|
this.attachmentActionsWidget = new AttachmentActionsWidget(attachment);
|
||||||
this.child(this.attachmentActionsWidget);
|
this.child(this.attachmentActionsWidget);
|
||||||
@ -57,14 +59,25 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
|||||||
|
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
|
this.refresh();
|
||||||
|
|
||||||
|
super.doRender();
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.$widget.find('.attachment-detail-wrapper')
|
||||||
|
.empty()
|
||||||
|
.append(
|
||||||
|
$(TPL)
|
||||||
|
.find('.attachment-detail-wrapper')
|
||||||
|
.html()
|
||||||
|
);
|
||||||
this.$wrapper = this.$widget.find('.attachment-detail-wrapper');
|
this.$wrapper = this.$widget.find('.attachment-detail-wrapper');
|
||||||
this.$wrapper.find('.attachment-title').text(this.attachment.title);
|
this.$wrapper.find('.attachment-title').text(this.attachment.title);
|
||||||
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(this.renderContent());
|
this.$wrapper.find('.attachment-content').append(this.renderContent());
|
||||||
|
|
||||||
super.doRender();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
@ -76,4 +89,20 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async entitiesReloadedEvent({loadResults}) {
|
||||||
|
console.log("AttachmentDetailWidget: entitiesReloadedEvent");
|
||||||
|
|
||||||
|
const attachmentChange = loadResults.getAttachments().find(att => att.attachmentId === this.attachment.attachmentId);
|
||||||
|
|
||||||
|
if (attachmentChange) {
|
||||||
|
if (attachmentChange.isDeleted) {
|
||||||
|
this.toggleInt(false);
|
||||||
|
} else {
|
||||||
|
this.attachment = await server.get(`notes/${this.attachment.parentId}/attachments/${this.attachment.attachmentId}?includeContent=true`);
|
||||||
|
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import BasicWidget from "../basic_widget.js";
|
import BasicWidget from "../basic_widget.js";
|
||||||
import server from "../../services/server.js";
|
import server from "../../services/server.js";
|
||||||
import dialogService from "../../services/dialog.js";
|
import dialogService from "../../services/dialog.js";
|
||||||
|
import toastService from "../../services/toast.js";
|
||||||
|
import ws from "../../services/ws.js";
|
||||||
|
import appContext from "../../components/app_context.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="dropdown attachment-actions">
|
<div class="dropdown attachment-actions">
|
||||||
@ -11,7 +14,7 @@ const TPL = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.attachment-actions .dropdown-menu {
|
.attachment-actions .dropdown-menu {
|
||||||
width: 15em;
|
width: 20em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachment-actions .dropdown-item[disabled], .attachment-actions .dropdown-item[disabled]:hover {
|
.attachment-actions .dropdown-item[disabled], .attachment-actions .dropdown-item[disabled]:hover {
|
||||||
@ -25,9 +28,9 @@ const TPL = `
|
|||||||
aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"></button>
|
aria-expanded="false" class="icon-action icon-action-always-border bx bx-dots-vertical-rounded"></button>
|
||||||
|
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<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="deleteAttachment" class="dropdown-item">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="convertAttachmentIntoNote" class="dropdown-item">Convert attachment into note</a>
|
||||||
<a data-trigger-command="pullAttachmentIntoNote" class="dropdown-item pull-attachment-into-note-button">Copy into clipboard</a>
|
<a data-trigger-command="convertAttachmentIntoNote" class="dropdown-item pull-attachment-into-note-button">Copy into clipboard</a>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
@ -46,6 +49,20 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
|||||||
async deleteAttachmentCommand() {
|
async deleteAttachmentCommand() {
|
||||||
if (await dialogService.confirm(`Are you sure you want to delete attachment '${this.attachment.title}'?`)) {
|
if (await dialogService.confirm(`Are you sure you want to delete attachment '${this.attachment.title}'?`)) {
|
||||||
await server.remove(`notes/${this.attachment.parentId}/attachments/${this.attachment.attachmentId}`);
|
await server.remove(`notes/${this.attachment.parentId}/attachments/${this.attachment.attachmentId}`);
|
||||||
|
|
||||||
|
toastService.showMessage(`Attachment '${this.attachment.title}' has been deleted.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async convertAttachmentIntoNoteCommand() {
|
||||||
|
if (await dialogService.confirm(`Are you sure you want to convert attachment '${this.attachment.title}' into a separate note?`)) {
|
||||||
|
const {note: newNote} = await server.post(`notes/${this.attachment.parentId}/attachments/${this.attachment.attachmentId}/convert-to-note`)
|
||||||
|
|
||||||
|
toastService.showMessage(`Attachment '${this.attachment.title}' has been converted to note.`);
|
||||||
|
|
||||||
|
await ws.waitForMaxKnownEntityChangeId();
|
||||||
|
|
||||||
|
await appContext.tabManager.getActiveContext().setNote(newNote.noteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
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";
|
|
||||||
import AttachmentDetailWidget from "../attachment_detail.js";
|
import AttachmentDetailWidget from "../attachment_detail.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
@ -16,7 +14,9 @@ const TPL = `
|
|||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
export default class AttachmentsTypeWidget extends TypeWidget {
|
export default class AttachmentsTypeWidget extends TypeWidget {
|
||||||
static getType() { return "attachments"; }
|
static getType() {
|
||||||
|
return "attachments";
|
||||||
|
}
|
||||||
|
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
@ -28,6 +28,7 @@ export default class AttachmentsTypeWidget extends TypeWidget {
|
|||||||
async doRefresh(note) {
|
async doRefresh(note) {
|
||||||
this.$list.empty();
|
this.$list.empty();
|
||||||
this.children = [];
|
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?includeContent=true`);
|
||||||
|
|
||||||
@ -41,7 +42,19 @@ export default class AttachmentsTypeWidget extends TypeWidget {
|
|||||||
const attachmentDetailWidget = new AttachmentDetailWidget(attachment);
|
const attachmentDetailWidget = new AttachmentDetailWidget(attachment);
|
||||||
this.child(attachmentDetailWidget);
|
this.child(attachmentDetailWidget);
|
||||||
|
|
||||||
|
this.renderedAttachmentIds.add(attachment.attachmentId);
|
||||||
|
|
||||||
this.$list.append(attachmentDetailWidget.render());
|
this.$list.append(attachmentDetailWidget.render());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
if (attachmentsAdded) {
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
108
src/routes/api/attachments.js
Normal file
108
src/routes/api/attachments.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
const becca = require("../../becca/becca");
|
||||||
|
const NotFoundError = require("../../errors/not_found_error");
|
||||||
|
const utils = require("../../services/utils");
|
||||||
|
const noteService = require("../../services/notes");
|
||||||
|
|
||||||
|
function getAttachments(req) {
|
||||||
|
const includeContent = req.query.includeContent === 'true';
|
||||||
|
const {noteId} = req.params;
|
||||||
|
|
||||||
|
const note = becca.getNote(noteId);
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return note.getAttachments()
|
||||||
|
.map(attachment => processAttachment(attachment, includeContent));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAttachment(req) {
|
||||||
|
const includeContent = req.query.includeContent === 'true';
|
||||||
|
const {noteId, attachmentId} = req.params;
|
||||||
|
|
||||||
|
const note = becca.getNote(noteId);
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachment = note.getAttachmentById(attachmentId);
|
||||||
|
|
||||||
|
if (!attachment) {
|
||||||
|
throw new NotFoundError(`Attachment '${attachmentId} of note '${noteId}' doesn't exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAttachment(req) {
|
||||||
|
const {noteId} = req.params;
|
||||||
|
const {attachmentId, role, mime, title, content} = req.body;
|
||||||
|
|
||||||
|
const note = becca.getNote(noteId);
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
note.saveAttachment({attachmentId, role, mime, title, content});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteAttachment(req) {
|
||||||
|
const {noteId, attachmentId} = req.params;
|
||||||
|
|
||||||
|
const note = becca.getNote(noteId);
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachment = note.getAttachmentById(attachmentId);
|
||||||
|
|
||||||
|
if (attachment) {
|
||||||
|
attachment.markAsDeleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertAttachmentToNote(req) {
|
||||||
|
const {noteId, attachmentId} = req.params;
|
||||||
|
|
||||||
|
const note = becca.getNote(noteId);
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return note.convertAttachmentToChildNote(attachmentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getAttachments,
|
||||||
|
getAttachment,
|
||||||
|
saveAttachment,
|
||||||
|
deleteAttachment,
|
||||||
|
convertAttachmentToNote
|
||||||
|
};
|
@ -127,70 +127,6 @@ function setNoteTypeMime(req) {
|
|||||||
note.save();
|
note.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAttachments(req) {
|
|
||||||
const includeContent = req.query.includeContent === 'true';
|
|
||||||
const {noteId} = req.params;
|
|
||||||
|
|
||||||
const note = becca.getNote(noteId);
|
|
||||||
|
|
||||||
if (!note) {
|
|
||||||
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachments = note.getAttachments();
|
|
||||||
|
|
||||||
return attachments.map(attachment => {
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveAttachment(req) {
|
|
||||||
const {noteId} = req.params;
|
|
||||||
const {attachmentId, role, mime, title, content} = req.body;
|
|
||||||
|
|
||||||
const note = becca.getNote(noteId);
|
|
||||||
|
|
||||||
if (!note) {
|
|
||||||
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
note.saveAttachment({attachmentId, role, mime, title, content});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteAttachment(req) {
|
|
||||||
const {noteId, attachmentId} = req.params;
|
|
||||||
|
|
||||||
const note = becca.getNote(noteId);
|
|
||||||
|
|
||||||
if (!note) {
|
|
||||||
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachment = note.getAttachmentById(attachmentId);
|
|
||||||
|
|
||||||
if (attachment) {
|
|
||||||
attachment.markAsDeleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRelationMap(req) {
|
function getRelationMap(req) {
|
||||||
const {relationMapNoteId, noteIds} = req.body;
|
const {relationMapNoteId, noteIds} = req.body;
|
||||||
|
|
||||||
@ -404,8 +340,5 @@ module.exports = {
|
|||||||
eraseDeletedNotesNow,
|
eraseDeletedNotesNow,
|
||||||
getDeleteNotesPreview,
|
getDeleteNotesPreview,
|
||||||
uploadModifiedFile,
|
uploadModifiedFile,
|
||||||
forceSaveNoteRevision,
|
forceSaveNoteRevision
|
||||||
getAttachments,
|
|
||||||
saveAttachment,
|
|
||||||
deleteAttachment
|
|
||||||
};
|
};
|
||||||
|
@ -25,6 +25,7 @@ const indexRoute = require('./index');
|
|||||||
const treeApiRoute = require('./api/tree');
|
const treeApiRoute = require('./api/tree');
|
||||||
const notesApiRoute = require('./api/notes');
|
const notesApiRoute = require('./api/notes');
|
||||||
const branchesApiRoute = require('./api/branches');
|
const branchesApiRoute = require('./api/branches');
|
||||||
|
const attachmentsApiRoute = require('./api/attachments');
|
||||||
const autocompleteApiRoute = require('./api/autocomplete');
|
const autocompleteApiRoute = require('./api/autocomplete');
|
||||||
const cloningApiRoute = require('./api/cloning');
|
const cloningApiRoute = require('./api/cloning');
|
||||||
const noteRevisionsApiRoute = require('./api/note_revisions');
|
const noteRevisionsApiRoute = require('./api/note_revisions');
|
||||||
@ -126,9 +127,11 @@ function register(app) {
|
|||||||
apiRoute(PUT, '/api/notes/:noteId/sort-children', notesApiRoute.sortChildNotes);
|
apiRoute(PUT, '/api/notes/:noteId/sort-children', notesApiRoute.sortChildNotes);
|
||||||
apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectNote);
|
apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectNote);
|
||||||
apiRoute(PUT, '/api/notes/:noteId/type', notesApiRoute.setNoteTypeMime);
|
apiRoute(PUT, '/api/notes/:noteId/type', notesApiRoute.setNoteTypeMime);
|
||||||
apiRoute(GET, '/api/notes/:noteId/attachments', notesApiRoute.getAttachments);
|
apiRoute(GET, '/api/notes/:noteId/attachments', attachmentsApiRoute.getAttachments);
|
||||||
apiRoute(POST, '/api/notes/:noteId/attachments', notesApiRoute.saveAttachment);
|
apiRoute(GET, '/api/notes/:noteId/attachments/:attachmentId', attachmentsApiRoute.getAttachment);
|
||||||
apiRoute(DELETE, '/api/notes/:noteId/attachments/:attachmentId', notesApiRoute.deleteAttachment);
|
apiRoute(POST, '/api/notes/:noteId/attachments', attachmentsApiRoute.saveAttachment);
|
||||||
|
apiRoute(POST, '/api/notes/:noteId/attachments/:attachmentId/convert-to-note', attachmentsApiRoute.convertAttachmentToNote);
|
||||||
|
apiRoute(DELETE, '/api/notes/:noteId/attachments/:attachmentId', attachmentsApiRoute.deleteAttachment);
|
||||||
apiRoute(GET, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.getNoteRevisions);
|
apiRoute(GET, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.getNoteRevisions);
|
||||||
apiRoute(DELETE, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.eraseAllNoteRevisions);
|
apiRoute(DELETE, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.eraseAllNoteRevisions);
|
||||||
apiRoute(GET, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.getNoteRevision);
|
apiRoute(GET, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.getNoteRevision);
|
||||||
|
@ -137,6 +137,8 @@ function fillInAdditionalProperties(entityChange) {
|
|||||||
}
|
}
|
||||||
} else if (entityChange.entityName === 'blobs') {
|
} else if (entityChange.entityName === 'blobs') {
|
||||||
entityChange.noteIds = sql.getColumn("SELECT noteId FROM notes WHERE blobId = ? AND isDeleted = 0", [entityChange.entityId]);
|
entityChange.noteIds = sql.getColumn("SELECT noteId FROM notes WHERE blobId = ? AND isDeleted = 0", [entityChange.entityId]);
|
||||||
|
} else if (entityChange.entityName === 'attachments') {
|
||||||
|
entityChange.entity = sql.getRow(`SELECT * FROM attachments WHERE attachmentId = ?`, [entityChange.entityId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entityChange.entity instanceof AbstractBeccaEntity) {
|
if (entityChange.entity instanceof AbstractBeccaEntity) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user