mirror of
https://github.com/zadam/trilium.git
synced 2025-11-17 18:50:41 +01:00
attachments WIP
This commit is contained in:
@@ -77,6 +77,7 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
});
|
||||
}
|
||||
|
||||
/** @returns {BNote} */
|
||||
getNote() {
|
||||
return this.becca.notes[this.parentId];
|
||||
}
|
||||
|
||||
@@ -1116,6 +1116,8 @@ class BNote extends AbstractBeccaEntity {
|
||||
/** @returns {BAttachment[]} */
|
||||
getAttachments(opts = {}) {
|
||||
opts.includeContentLength = !!opts.includeContentLength;
|
||||
// from testing it looks like calculating length does not make a difference in performance even on large-ish DB
|
||||
// given that we're always fetching attachments only for a specific note, we might just do it always
|
||||
|
||||
const query = opts.includeContentLength
|
||||
? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
|
||||
|
||||
@@ -231,8 +231,7 @@ class FNote {
|
||||
/** @returns {Promise<FAttachment[]>} */
|
||||
async getAttachments() {
|
||||
if (!this.attachments) {
|
||||
this.attachments = (await server.get(`notes/${this.noteId}/attachments`))
|
||||
.map(row => new FAttachment(froca, row));
|
||||
this.attachments = await this.froca.getAttachmentsForNote(this.noteId);
|
||||
}
|
||||
|
||||
return this.attachments;
|
||||
|
||||
@@ -20,7 +20,7 @@ async function renderAttribute(attribute, renderIsInheritable) {
|
||||
// when the relation has just been created, then it might not have a value
|
||||
if (attribute.value) {
|
||||
$attr.append(document.createTextNode(`~${attribute.name}${isInheritable}=`));
|
||||
$attr.append(await createNoteLink(attribute.value));
|
||||
$attr.append(await createLink(attribute.value));
|
||||
}
|
||||
} else {
|
||||
ws.logError(`Unknown attr type: ${attribute.type}`);
|
||||
@@ -47,7 +47,7 @@ function formatValue(val) {
|
||||
}
|
||||
}
|
||||
|
||||
async function createNoteLink(noteId) {
|
||||
async function createLink(noteId) {
|
||||
const note = await froca.getNote(noteId);
|
||||
|
||||
if (!note) {
|
||||
|
||||
@@ -71,7 +71,7 @@ async function copy(branchIds) {
|
||||
const links = [];
|
||||
|
||||
for (const branch of froca.getBranches(clipboardBranchIds)) {
|
||||
const $link = await linkService.createNoteLink(`${branch.parentNoteId}/${branch.noteId}`, { referenceLink: true });
|
||||
const $link = await linkService.createLink(`${branch.parentNoteId}/${branch.noteId}`, { referenceLink: true });
|
||||
links.push($link[0].outerHTML);
|
||||
}
|
||||
|
||||
|
||||
@@ -100,9 +100,7 @@ async function renderText(note, options, $renderedContent) {
|
||||
await froca.getNotes(noteIdsToPrefetch);
|
||||
|
||||
for (const el of referenceLinks) {
|
||||
const noteId = getNoteIdFromLink(el);
|
||||
|
||||
await linkService.loadReferenceLinkTitle(noteId, $(el));
|
||||
await linkService.loadReferenceLinkTitle($(el));
|
||||
}
|
||||
} else {
|
||||
await renderChildrenList($renderedContent, note);
|
||||
@@ -146,15 +144,6 @@ function renderFile(entity, type, $renderedContent) {
|
||||
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') {
|
||||
@@ -178,11 +167,23 @@ function renderFile(entity, type, $renderedContent) {
|
||||
$content.append($videoPreview);
|
||||
}
|
||||
|
||||
$content.append(
|
||||
$('<div style="display: flex; justify-content: space-evenly; margin-top: 5px;">')
|
||||
.append($downloadButton)
|
||||
.append($openButton)
|
||||
);
|
||||
if (entityType === 'notes') {
|
||||
// TODO: we should make this available also for attachments, but there's a problem with "Open externally" support
|
||||
// in attachment list
|
||||
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);
|
||||
|
||||
$content.append(
|
||||
$('<div style="display: flex; justify-content: space-evenly; margin-top: 5px;">')
|
||||
.append($downloadButton)
|
||||
.append($openButton)
|
||||
);
|
||||
}
|
||||
|
||||
$renderedContent.append($content);
|
||||
}
|
||||
@@ -253,7 +254,7 @@ async function renderChildrenList($renderedContent, note) {
|
||||
const childNotes = await froca.getNotes(childNoteIds);
|
||||
|
||||
for (const childNote of childNotes) {
|
||||
$renderedContent.append(await linkService.createNoteLink(`${note.noteId}/${childNote.noteId}`, {
|
||||
$renderedContent.append(await linkService.createLink(`${note.noteId}/${childNote.noteId}`, {
|
||||
showTooltip: false,
|
||||
showNoteIcon: true
|
||||
}));
|
||||
|
||||
@@ -314,10 +314,45 @@ class Froca {
|
||||
return child.parentToBranch[parentNoteId];
|
||||
}
|
||||
|
||||
/** @returns {Promise<FAttachment>} */
|
||||
async getAttachment(attachmentId) {
|
||||
const attachmentRow = await server.get(`attachments/${attachmentId}`);
|
||||
const attachment = this.attachments[attachmentId];
|
||||
if (attachment) {
|
||||
return attachment;
|
||||
}
|
||||
|
||||
return attachmentRow ? new FAttachment(this, attachmentRow) : null;
|
||||
// load all attachments for the given note even if one is requested, don't load one by one
|
||||
const attachmentRows = await server.get(`attachments/${attachmentId}/all`);
|
||||
const attachments = this.processAttachmentRows(attachmentRows);
|
||||
|
||||
if (attachments.length) {
|
||||
attachments[0].getNote().attachments = attachments;
|
||||
}
|
||||
|
||||
return this.attachments[attachmentId];
|
||||
}
|
||||
|
||||
/** @returns {Promise<FAttachment[]>} */
|
||||
async getAttachmentsForNote(noteId) {
|
||||
const attachmentRows = await server.get(`notes/${noteId}/attachments`);
|
||||
return this.processAttachmentRows(attachmentRows);
|
||||
}
|
||||
|
||||
/** @returns {FAttachment[]} */
|
||||
processAttachmentRows(attachmentRows) {
|
||||
return attachmentRows.map(attachmentRow => {
|
||||
let attachment;
|
||||
|
||||
if (attachmentRow.attachmentId in this.attachments) {
|
||||
attachment = this.attachments[attachmentRow.attachmentId];
|
||||
attachment.update(attachmentRow);
|
||||
} else {
|
||||
attachment = new FAttachment(this, attachmentRow);
|
||||
this.attachments[attachment.attachmentId] = attachment;
|
||||
}
|
||||
|
||||
return attachment;
|
||||
});
|
||||
}
|
||||
|
||||
/** @returns {Promise<FBlob>} */
|
||||
|
||||
@@ -269,7 +269,7 @@ function processAttachment(loadResults, ec) {
|
||||
} else {
|
||||
const note = froca.notes[ec.entity.parentId];
|
||||
|
||||
if (note && note.attachments) {
|
||||
if (note?.attachments) {
|
||||
note.attachments.push(new FAttachment(froca, ec.entity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
||||
* Returns list of notes. If note is missing from the cache, it's loaded.
|
||||
*
|
||||
* This is often used to bulk-fill the cache with notes which would have to be picked one by one
|
||||
* otherwise (by e.g. createNoteLink())
|
||||
* otherwise (by e.g. createLink())
|
||||
*
|
||||
* @method
|
||||
* @param {string[]} noteIds
|
||||
@@ -302,7 +302,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
||||
* @param {boolean} [params.showNoteIcon=false] - show also note icon before the title
|
||||
* @param {string} [params.title=] - custom link tile with note's title as default
|
||||
*/
|
||||
this.createNoteLink = linkService.createNoteLink;
|
||||
this.createLink = linkService.createLink;
|
||||
|
||||
/**
|
||||
* Adds given text to the editor cursor
|
||||
|
||||
@@ -10,7 +10,7 @@ function getNotePathFromUrl(url) {
|
||||
return notePathMatch === null ? null : notePathMatch[1];
|
||||
}
|
||||
|
||||
async function createNoteLink(notePath, options = {}) {
|
||||
async function createLink(notePath, options = {}) {
|
||||
if (!notePath || !notePath.trim()) {
|
||||
logError("Missing note path");
|
||||
|
||||
@@ -29,26 +29,50 @@ async function createNoteLink(notePath, options = {}) {
|
||||
const referenceLink = options.referenceLink === undefined ? false : options.referenceLink;
|
||||
|
||||
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromNotePath(notePath);
|
||||
const noteTitle = options.title || await treeService.getNoteTitle(noteId, parentNoteId);
|
||||
const viewScope = options.viewScope || {};
|
||||
const viewMode = viewScope.viewMode || 'default';
|
||||
let linkTitle = options.title;
|
||||
|
||||
if (!linkTitle) {
|
||||
if (viewMode === 'attachments' && viewScope.attachmentId) {
|
||||
const attachment = await froca.getAttachment(viewScope.attachmentId);
|
||||
|
||||
linkTitle = attachment ? attachment.title : '[missing attachment]';
|
||||
} else {
|
||||
linkTitle = await treeService.getNoteTitle(noteId, parentNoteId);
|
||||
}
|
||||
}
|
||||
|
||||
const $container = $("<span>");
|
||||
|
||||
if (showNoteIcon) {
|
||||
const note = await froca.getNote(noteId);
|
||||
let icon;
|
||||
|
||||
$container
|
||||
.append($("<span>").addClass(`bx ${note.getIcon()}`))
|
||||
.append(" ");
|
||||
if (viewMode === 'default') {
|
||||
const note = await froca.getNote(noteId);
|
||||
|
||||
icon = note.getIcon();
|
||||
} else if (viewMode === 'source') {
|
||||
icon = 'bx-code-curly';
|
||||
} else if (viewMode === 'attachments') {
|
||||
icon = 'bx-file';
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
$container
|
||||
.append($("<span>").addClass(`bx ${icon}`))
|
||||
.append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
const hash = calculateHash({
|
||||
notePath,
|
||||
viewScope: options.viewScope
|
||||
viewScope: viewScope
|
||||
});
|
||||
|
||||
const $noteLink = $("<a>", {
|
||||
href: hash,
|
||||
text: noteTitle
|
||||
text: linkTitle
|
||||
});
|
||||
|
||||
if (!showTooltip) {
|
||||
@@ -228,7 +252,14 @@ function linkContextMenu(e) {
|
||||
linkContextMenuService.openContextMenu(notePath, e, viewScope, null);
|
||||
}
|
||||
|
||||
async function loadReferenceLinkTitle(noteId, $el) {
|
||||
async function loadReferenceLinkTitle($el) {
|
||||
const url = $el.attr("href");
|
||||
if (!url) {
|
||||
console.warn("Empty URL for parsing");
|
||||
return;
|
||||
}
|
||||
|
||||
const {noteId} = parseNavigationStateFromUrl(url);
|
||||
const note = await froca.getNote(noteId, true);
|
||||
|
||||
let title;
|
||||
@@ -279,7 +310,7 @@ $(document).on('mousedown', 'a', e => {
|
||||
|
||||
export default {
|
||||
getNotePathFromUrl,
|
||||
createNoteLink,
|
||||
createLink,
|
||||
goToLink,
|
||||
loadReferenceLinkTitle,
|
||||
calculateHash,
|
||||
|
||||
@@ -284,7 +284,7 @@ class NoteListRenderer {
|
||||
.append($('<span class="note-icon">').addClass(note.getIcon()))
|
||||
.append(this.viewType === 'grid'
|
||||
? $('<span class="note-book-title">').text(note.title)
|
||||
: (await linkService.createNoteLink(notePath, {showTooltip: false, showNotePath: this.showNotePath}))
|
||||
: (await linkService.createLink(notePath, {showTooltip: false, showNotePath: this.showNotePath}))
|
||||
.addClass("note-book-title")
|
||||
)
|
||||
.append($renderedAttributes)
|
||||
|
||||
@@ -513,6 +513,15 @@ function areObjectsEqual () {
|
||||
return true;
|
||||
}
|
||||
|
||||
function copyHtmlToClipboard(content) {
|
||||
const clipboardItem = new ClipboardItem({
|
||||
'text/html': new Blob([content], {type: 'text/html'}),
|
||||
'text/plain': new Blob([content], {type: 'text/plain'})
|
||||
});
|
||||
|
||||
navigator.clipboard.write([clipboardItem]);
|
||||
}
|
||||
|
||||
export default {
|
||||
reloadFrontendApp,
|
||||
parseDate,
|
||||
@@ -558,5 +567,6 @@ export default {
|
||||
isValidAttributeName,
|
||||
sleep,
|
||||
escapeRegExp,
|
||||
areObjectsEqual
|
||||
areObjectsEqual,
|
||||
copyHtmlToClipboard
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ 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";
|
||||
import toastService from "../services/toast.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="attachment-detail-widget">
|
||||
@@ -126,7 +127,7 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
||||
|
||||
if (!this.isFullDetail) {
|
||||
this.$wrapper.find('.attachment-title').append(
|
||||
await linkService.createNoteLink(this.attachment.parentId, {
|
||||
await linkService.createLink(this.attachment.parentId, {
|
||||
title: this.attachment.title,
|
||||
viewScope: {
|
||||
viewMode: 'attachments',
|
||||
@@ -174,16 +175,20 @@ export default class AttachmentDetailWidget extends BasicWidget {
|
||||
if (this.attachment.role === 'image') {
|
||||
imageService.copyImageReferenceToClipboard(this.$wrapper.find('.attachment-content-wrapper'));
|
||||
} else if (this.attachment.role === 'file') {
|
||||
const $link = await linkService.createNoteLink(this.attachment.parentId, {
|
||||
const $link = await linkService.createLink(this.attachment.parentId, {
|
||||
referenceLink: true,
|
||||
viewScope: {
|
||||
viewMode: 'attachments',
|
||||
attachmentId: this.attachment.attachmentId
|
||||
}
|
||||
});
|
||||
|
||||
utils.copyHtmlToClipboard($link[0].outerHTML);
|
||||
} else {
|
||||
throw new Error(`Unrecognized attachment role '${this.attachment.role}'.`);
|
||||
}
|
||||
|
||||
toastService.showMessage("Attachment link copied to clipboard.");
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({loadResults}) {
|
||||
|
||||
@@ -437,7 +437,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
.empty()
|
||||
.append(attribute.type === 'label' ? 'Label' : 'Relation')
|
||||
.append(' is owned by note ')
|
||||
.append(await linkService.createNoteLink(attribute.noteId))
|
||||
.append(await linkService.createLink(attribute.noteId))
|
||||
}
|
||||
|
||||
this.$inputName
|
||||
@@ -602,7 +602,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
|
||||
for (const note of displayedNotes) {
|
||||
const notePath = note.getBestNotePathString(hoistedNoteId);
|
||||
const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true});
|
||||
const $noteLink = await linkService.createLink(notePath, {showNotePath: true});
|
||||
|
||||
this.$relatedNotesList.append(
|
||||
$("<li>").append($noteLink)
|
||||
@@ -699,7 +699,7 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget {
|
||||
this.toggleInt(false);
|
||||
}
|
||||
|
||||
createNoteLink(noteId) {
|
||||
createLink(noteId) {
|
||||
return $("<a>", {
|
||||
href: `#root/${noteId}`,
|
||||
class: 'reference-link'
|
||||
|
||||
@@ -2,12 +2,13 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import noteAutocompleteService from "../../services/note_autocomplete.js";
|
||||
import server from "../../services/server.js";
|
||||
import contextMenuService from "../../menus/context_menu.js";
|
||||
import attributesParser from "../../services/attribute_parser.js";
|
||||
import attributeParser from "../../services/attribute_parser.js";
|
||||
import libraryLoader from "../../services/library_loader.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import attributeRenderer from "../../services/attribute_renderer.js";
|
||||
import noteCreateService from "../../services/note_create.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import linkService from "../../services/link.js";
|
||||
|
||||
const HELP_TEXT = `
|
||||
<p>To add label, just type e.g. <code>#rock</code> or if you want to add also value then e.g. <code>#year = 2020</code></p>
|
||||
@@ -321,7 +322,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
|
||||
|
||||
parseAttributes() {
|
||||
try {
|
||||
return attributesParser.lexAndParse(this.getPreprocessedData());
|
||||
return attributeParser.lexAndParse(this.getPreprocessedData());
|
||||
}
|
||||
catch (e) {
|
||||
this.$errors.text(e.message).slideDown();
|
||||
@@ -385,7 +386,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
|
||||
let parsedAttrs;
|
||||
|
||||
try {
|
||||
parsedAttrs = attributesParser.lexAndParse(this.getPreprocessedData(), true);
|
||||
parsedAttrs = attributeParser.lexAndParse(this.getPreprocessedData(), true);
|
||||
}
|
||||
catch (e) {
|
||||
// the input is incorrect because user messed up with it and now needs to fix it manually
|
||||
@@ -455,7 +456,8 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget {
|
||||
return clickIndex;
|
||||
}
|
||||
|
||||
async loadReferenceLinkTitle(noteId, $el) {
|
||||
async loadReferenceLinkTitle($el) {
|
||||
const {noteId} = linkService.parseNavigationStateFromUrl($el.attr("href"));
|
||||
const note = await froca.getNote(noteId, true);
|
||||
|
||||
let title;
|
||||
|
||||
@@ -56,7 +56,6 @@ export default class AttachmentActionsWidget extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
|
||||
this.$widget.find("[data-trigger-command='copyAttachmentLinkToClipboard']").toggle(this.attachment.role === 'image');
|
||||
|
||||
this.$uploadNewRevisionInput = this.$widget.find(".attachment-upload-new-revision-input");
|
||||
this.$uploadNewRevisionInput.on('change', async () => {
|
||||
|
||||
@@ -66,7 +66,7 @@ export default class BookmarkFolderWidget extends RightDropdownButtonWidget {
|
||||
};
|
||||
|
||||
this.$parentNote.append(
|
||||
(await linkService.createNoteLink(this.note.noteId, linkOptions))
|
||||
(await linkService.createLink(this.note.noteId, linkOptions))
|
||||
.addClass("note-link")
|
||||
);
|
||||
|
||||
@@ -74,7 +74,7 @@ export default class BookmarkFolderWidget extends RightDropdownButtonWidget {
|
||||
this.$childrenNotes.append(
|
||||
$("<li>")
|
||||
.append(
|
||||
(await linkService.createNoteLink(childNote.noteId, linkOptions))
|
||||
(await linkService.createLink(childNote.noteId, linkOptions))
|
||||
.addClass("note-link")
|
||||
)
|
||||
);
|
||||
|
||||
@@ -120,7 +120,7 @@ export default class DeleteNotesDialog extends BasicWidget {
|
||||
for (const note of await froca.getNotes(response.noteIdsToBeDeleted)) {
|
||||
this.$deleteNotesList.append(
|
||||
$("<li>").append(
|
||||
await linkService.createNoteLink(note.noteId, {showNotePath: true})
|
||||
await linkService.createLink(note.noteId, {showNotePath: true})
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -136,9 +136,9 @@ export default class DeleteNotesDialog extends BasicWidget {
|
||||
this.$brokenRelationsList.append(
|
||||
$("<li>")
|
||||
.append(`Note `)
|
||||
.append(await linkService.createNoteLink(attr.value))
|
||||
.append(await linkService.createLink(attr.value))
|
||||
.append(` (to be deleted) is referenced by relation <code>${attr.name}</code> originating from `)
|
||||
.append(await linkService.createNoteLink(attr.noteId))
|
||||
.append(await linkService.createLink(attr.noteId))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export default class RecentChangesDialog extends BasicWidget {
|
||||
const notePath = note.getBestNotePathString();
|
||||
|
||||
if (notePath) {
|
||||
$noteLink = await linkService.createNoteLink(notePath, {
|
||||
$noteLink = await linkService.createLink(notePath, {
|
||||
title: change.title,
|
||||
showNotePath: true
|
||||
});
|
||||
|
||||
@@ -131,7 +131,7 @@ export default class BacklinksWidget extends NoteContextAwareWidget {
|
||||
for (const backlink of backlinks) {
|
||||
const $item = $("<div>");
|
||||
|
||||
$item.append(await linkService.createNoteLink(backlink.noteId, {
|
||||
$item.append(await linkService.createLink(backlink.noteId, {
|
||||
showNoteIcon: true,
|
||||
showNotePath: true,
|
||||
showTooltip: false
|
||||
|
||||
@@ -396,11 +396,11 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
}));
|
||||
|
||||
if (notes.length === 1) {
|
||||
linkService.createNoteLink(notes[0].noteId, {referenceLink: true})
|
||||
linkService.createLink(notes[0].noteId, {referenceLink: true})
|
||||
.then($link => data.dataTransfer.setData("text/html", $link[0].outerHTML));
|
||||
}
|
||||
else {
|
||||
Promise.all(notes.map(note => linkService.createNoteLink(note.noteId, {referenceLink: true}))).then(links => {
|
||||
Promise.all(notes.map(note => linkService.createLink(note.noteId, {referenceLink: true}))).then(links => {
|
||||
const $list = $("<ul>").append(...links.map($link => $("<li>").append($link)));
|
||||
|
||||
data.dataTransfer.setData("text/html", $list[0].outerHTML);
|
||||
|
||||
@@ -121,7 +121,7 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
for (const note of await froca.getNotes(displayedNoteIds)) {
|
||||
const $link = await linkService.createNoteLink(note.noteId, {showNotePath: true});
|
||||
const $link = await linkService.createLink(note.noteId, {showNotePath: true});
|
||||
$link.addClass('dropdown-item');
|
||||
$link.attr("tabIndex", "0");
|
||||
$link.on('click', e => {
|
||||
|
||||
@@ -78,7 +78,7 @@ export default class EditedNotesWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
else {
|
||||
$item.append(editedNote.notePath
|
||||
? await linkService.createNoteLink(editedNote.notePath.join("/"), {showNotePath: true})
|
||||
? await linkService.createLink(editedNote.notePath.join("/"), {showNotePath: true})
|
||||
: $("<span>").text(editedNote.title));
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
|
||||
async getRenderedPath(notePath, notePathRecord = null) {
|
||||
const title = await treeService.getNotePathTitle(notePath);
|
||||
|
||||
const $noteLink = await linkService.createNoteLink(notePath, {title});
|
||||
const $noteLink = await linkService.createLink(notePath, {title});
|
||||
|
||||
$noteLink
|
||||
.find('a')
|
||||
|
||||
@@ -85,7 +85,7 @@ export default class SimilarNotesWidget extends NoteContextAwareWidget {
|
||||
continue;
|
||||
}
|
||||
|
||||
const $item = (await linkService.createNoteLink(similarNote.notePath.join("/")))
|
||||
const $item = (await linkService.createLink(similarNote.notePath.join("/")))
|
||||
.css("font-size", 24 * (1 - 1 / (1 + similarNote.score)));
|
||||
|
||||
$list.append($item);
|
||||
|
||||
@@ -77,7 +77,7 @@ export default class AbstractTextTypeWidget extends TypeWidget {
|
||||
if (note) {
|
||||
const $wrapper = $('<div class="include-note-wrapper">');
|
||||
|
||||
const $link = await linkService.createNoteLink(note.noteId, {
|
||||
const $link = await linkService.createLink(note.noteId, {
|
||||
showTooltip: false
|
||||
});
|
||||
|
||||
@@ -97,8 +97,48 @@ export default class AbstractTextTypeWidget extends TypeWidget {
|
||||
}
|
||||
}
|
||||
|
||||
async loadReferenceLinkTitle(noteId, $el) {
|
||||
await linkService.loadReferenceLinkTitle(noteId, $el);
|
||||
async loadReferenceLinkTitle($el) {
|
||||
await linkService.loadReferenceLinkTitle($el);
|
||||
}
|
||||
|
||||
async getReferenceLinkTitle(href) {
|
||||
const {noteId, viewScope} = linkService.parseNavigationStateFromUrl(href);
|
||||
if (!noteId) {
|
||||
return "[missing note]";
|
||||
}
|
||||
|
||||
const note = await froca.getNote(noteId);
|
||||
if (!note) {
|
||||
return "[missing note]";
|
||||
}
|
||||
|
||||
if (viewScope?.viewMode === 'attachments' && viewScope?.attachmentId) {
|
||||
const attachment = await note.getAttachmentById(viewScope.attachmentId);
|
||||
|
||||
return attachment ? attachment.title : "[missing attachment]";
|
||||
} else {
|
||||
return note.title;
|
||||
}
|
||||
}
|
||||
|
||||
getReferenceLinkTitleSync(href) {
|
||||
const {noteId, viewScope} = linkService.parseNavigationStateFromUrl(href);
|
||||
if (!noteId) {
|
||||
return "[missing note]";
|
||||
}
|
||||
|
||||
const note = froca.getNoteFromCache(noteId);
|
||||
if (!note) {
|
||||
return "[missing note]";
|
||||
}
|
||||
|
||||
if (viewScope?.viewMode === 'attachments' && viewScope?.attachmentId) {
|
||||
const attachment = note.attachments[viewScope.attachmentId];
|
||||
|
||||
return attachment ? attachment.title : "[missing attachment]";
|
||||
} else {
|
||||
return note.title;
|
||||
}
|
||||
}
|
||||
|
||||
refreshIncludedNote($container, noteId) {
|
||||
|
||||
@@ -47,9 +47,9 @@ export default class AttachmentDetailTypeWidget extends TypeWidget {
|
||||
|
||||
this.$linksWrapper.append(
|
||||
"Owning note: ",
|
||||
await linkService.createNoteLink(this.noteId),
|
||||
await linkService.createLink(this.noteId),
|
||||
", you can also open the ",
|
||||
await linkService.createNoteLink(this.noteId, {
|
||||
await linkService.createLink(this.noteId, {
|
||||
title: 'List of all attachments',
|
||||
viewScope: {
|
||||
viewMode: 'attachments'
|
||||
|
||||
@@ -40,7 +40,7 @@ export default class AttachmentListTypeWidget extends TypeWidget {
|
||||
this.$linksWrapper.append(
|
||||
$('<div>').append(
|
||||
"Owning note: ",
|
||||
await linkService.createNoteLink(this.noteId),
|
||||
await linkService.createLink(this.noteId),
|
||||
),
|
||||
$('<button class="btn btn-xs">')
|
||||
.text("Upload attachments")
|
||||
|
||||
@@ -97,10 +97,7 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget {
|
||||
this.$content.html(blob.content);
|
||||
|
||||
this.$content.find("a.reference-link").each(async (_, el) => {
|
||||
const notePath = $(el).attr('href');
|
||||
const noteId = treeService.getNoteIdFromNotePath(notePath);
|
||||
|
||||
this.loadReferenceLinkTitle(noteId, $(el));
|
||||
this.loadReferenceLinkTitle($(el));
|
||||
});
|
||||
|
||||
this.$content.find("section").each(async (_, el) => {
|
||||
|
||||
@@ -469,7 +469,7 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
async createNoteBox(noteId, title, x, y) {
|
||||
const $link = await linkService.createNoteLink(noteId, {title});
|
||||
const $link = await linkService.createLink(noteId, {title});
|
||||
$link.mousedown(e => linkService.goToLink(e));
|
||||
|
||||
const note = await froca.getNote(noteId);
|
||||
|
||||
@@ -19,6 +19,14 @@ function getAttachment(req) {
|
||||
return becca.getAttachmentOrThrow(attachmentId, {includeContentLength: true});
|
||||
}
|
||||
|
||||
function getAllAttachments(req) {
|
||||
const {attachmentId} = req.params;
|
||||
// one particular attachment is requested, but return all note's attachments
|
||||
|
||||
const attachment = becca.getAttachmentOrThrow(attachmentId);
|
||||
return attachment.getNote()?.getAttachments({includeContentLength: true}) || [];
|
||||
}
|
||||
|
||||
function saveAttachment(req) {
|
||||
const {noteId} = req.params;
|
||||
const {attachmentId, role, mime, title, content} = req.body;
|
||||
@@ -48,6 +56,7 @@ module.exports = {
|
||||
getAttachmentBlob,
|
||||
getAttachments,
|
||||
getAttachment,
|
||||
getAllAttachments,
|
||||
saveAttachment,
|
||||
deleteAttachment,
|
||||
convertAttachmentToNote
|
||||
|
||||
@@ -153,6 +153,7 @@ function register(app) {
|
||||
apiRoute(GET, '/api/notes/:noteId/attachments', attachmentsApiRoute.getAttachments);
|
||||
apiRoute(PST, '/api/notes/:noteId/attachments', attachmentsApiRoute.saveAttachment);
|
||||
apiRoute(GET, '/api/attachments/:attachmentId', attachmentsApiRoute.getAttachment);
|
||||
apiRoute(GET, '/api/attachments/:attachmentId/all', attachmentsApiRoute.getAllAttachments);
|
||||
apiRoute(PST, '/api/attachments/:attachmentId/convert-to-note', attachmentsApiRoute.convertAttachmentToNote);
|
||||
apiRoute(DEL, '/api/attachments/:attachmentId', attachmentsApiRoute.deleteAttachment);
|
||||
apiRoute(GET, '/api/attachments/:attachmentId/blob', attachmentsApiRoute.getAttachmentBlob);
|
||||
|
||||
Reference in New Issue
Block a user