Skip to content

Commit

Permalink
Merge branch 'beta'
Browse files Browse the repository at this point in the history
# Conflicts:
#	package-lock.json
  • Loading branch information
zadam committed Sep 13, 2023
2 parents 925bba1 + 4a67f63 commit 035113d
Show file tree
Hide file tree
Showing 21 changed files with 246 additions and 250 deletions.
1 change: 0 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ module.exports = {
glob: true,
log: true,
EditorWatchdog: true,
// \src\share\canvas_share.js
React: true,
appState: true,
ExcalidrawLib: true,
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"jimp": "0.22.10",
"joplin-turndown-plugin-gfm": "1.0.12",
"jsdom": "22.1.0",
"marked": "8.0.1",
"marked": "9.0.0",
"mime-types": "2.1.35",
"multer": "1.4.5-lts.1",
"node-abi": "3.47.0",
Expand All @@ -91,13 +91,13 @@
"tmp": "0.2.1",
"turndown": "7.1.2",
"unescape": "1.0.1",
"ws": "8.14.0",
"ws": "8.14.1",
"xml2js": "0.6.2",
"yauzl": "2.10.0"
},
"devDependencies": {
"cross-env": "7.0.3",
"electron": "25.8.0",
"electron": "25.8.1",
"electron-builder": "24.6.4",
"electron-packager": "17.1.2",
"electron-rebuild": "3.2.9",
Expand Down
28 changes: 26 additions & 2 deletions src/becca/entities/bnote.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,9 @@ class BNote extends AbstractBeccaEntity {
return this._getContent();
}

/** @returns {*} */
/**
* @returns {*}
* @throws Error in case of invalid JSON */
getJsonContent() {
const content = this.getContent();

Expand All @@ -240,6 +242,16 @@ class BNote extends AbstractBeccaEntity {
return JSON.parse(content);
}

/** @returns {*|null} valid object or null if the content cannot be parsed as JSON */
getJsonContentSafely() {
try {
return this.getJsonContent();
}
catch (e) {
return null;
}
}

/**
* @param content
* @param {object} [opts]
Expand Down Expand Up @@ -1143,7 +1155,7 @@ class BNote extends AbstractBeccaEntity {
}

/** @returns {BAttachment[]} */
getAttachmentByRole(role) {
getAttachmentsByRole(role) {
return sql.getRows(`
SELECT attachments.*
FROM attachments
Expand All @@ -1154,6 +1166,18 @@ class BNote extends AbstractBeccaEntity {
.map(row => new BAttachment(row));
}

/** @returns {BAttachment} */
getAttachmentByTitle(title) {
return sql.getRows(`
SELECT attachments.*
FROM attachments
WHERE ownerId = ?
AND title = ?
AND isDeleted = 0
ORDER BY position`, [this.noteId, title])
.map(row => new BAttachment(row))[0];
}

/**
* Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles)
*
Expand Down
21 changes: 21 additions & 0 deletions src/public/app/entities/fblob.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,25 @@ export default class FBlob {
/** @type {string} */
this.utcDateModified = row.utcDateModified;
}

/**
* @returns {*}
* @throws Error in case of invalid JSON */
getJsonContent() {
if (!this.content || !this.content.trim()) {
return null;
}

return JSON.parse(this.content);
}

/** @returns {*|null} valid object or null if the content cannot be parsed as JSON */
getJsonContentSafely() {
try {
return this.getJsonContent();
}
catch (e) {
return null;
}
}
}
6 changes: 6 additions & 0 deletions src/public/app/entities/fnote.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,12 @@ class FNote {
return this.attachments;
}

/** @returns {Promise<FAttachment[]>} */
async getAttachmentsByRole(role) {
return (await this.getAttachments())
.filter(attachment => attachment.role === role);
}

/** @returns {Promise<FAttachment>} */
async getAttachmentById(attachmentId) {
const attachments = await this.getAttachments();
Expand Down
6 changes: 5 additions & 1 deletion src/public/app/menus/tree_context_menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ export default class TreeContextMenu {
{ title: 'Collapse subtree <kbd data-command="collapseSubtree"></kbd>', command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes },
{ title: 'Sort by ... <kbd data-command="sortChildNotes"></kbd>', command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch },
{ title: 'Recent changes in subtree', command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes },
{ title: 'Convert to attachment', command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted }
{ title: 'Convert to attachment', command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted },
{ title: 'Copy note path to clipboard', command: "copyNotePathToClipboard", uiIcon: "bx bx-empty", enabled: true }
] },
{ title: "----" },
{ title: "Protect subtree", command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes },
Expand Down Expand Up @@ -153,6 +154,9 @@ export default class TreeContextMenu {

toastService.showMessage(`${converted} notes have been converted to attachments.`);
}
else if (command === 'copyNotePathToClipboard') {
navigator.clipboard.writeText('#' + notePath);
}
else {
this.treeWidget.triggerCommand(command, {
node: this.node,
Expand Down
29 changes: 2 additions & 27 deletions src/public/app/services/content_renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async function getRenderedContent(entity, options = {}) {
else if (type === 'code') {
await renderCode(entity, $renderedContent);
}
else if (type === 'image') {
else if (type === 'image' || type === 'canvas') {
renderImage(entity, $renderedContent, options);
}
else if (!options.tooltip && ['file', 'pdf', 'audio', 'video'].includes(type)) {
Expand All @@ -49,9 +49,6 @@ async function getRenderedContent(entity, options = {}) {

$renderedContent.append($content);
}
else if (type === 'canvas') {
await renderCanvas(entity, $renderedContent);
}
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);
Expand Down Expand Up @@ -125,7 +122,7 @@ function renderImage(entity, $renderedContent, options = {}) {
let url;

if (entity instanceof FNote) {
url = `api/images/${entity.noteId}/${sanitizedTitle}?${entity.utcDateModified}`;
url = `api/images/${entity.noteId}/${sanitizedTitle}?${Math.random()}`;
} else if (entity instanceof FAttachment) {
url = `api/attachments/${entity.attachmentId}/image/${sanitizedTitle}?${entity.utcDateModified}">`;
}
Expand Down Expand Up @@ -236,28 +233,6 @@ async function renderMermaid(note, $renderedContent) {
}
}

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
Expand Down
24 changes: 13 additions & 11 deletions src/public/app/services/link.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,14 +194,18 @@ function goToLink(evt) {
const $link = $(evt.target).closest("a,.block-link");
const hrefLink = $link.attr('href') || $link.attr('data-href');

return goToLinkExt(evt, hrefLink, $link);
}

function goToLinkExt(evt, hrefLink, $link) {
if (hrefLink?.startsWith("data:")) {
return true;
}

evt.preventDefault();
evt.stopPropagation();

const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink);
const {notePath, viewScope} = parseNavigationStateFromUrl(hrefLink);

const ctrlKey = utils.isCtrlKey(evt);
const isLeftClick = evt.which === 1;
Expand All @@ -213,34 +217,31 @@ function goToLink(evt) {

if (notePath) {
if (openInNewTab) {
appContext.tabManager.openTabWithNoteWithHoisting(notePath, { viewScope });
}
else if (isLeftClick) {
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {viewScope});
} else if (isLeftClick) {
const ntxId = $(evt.target).closest("[data-ntx-id]").attr("data-ntx-id");

const noteContext = ntxId
? appContext.tabManager.getNoteContextById(ntxId)
: appContext.tabManager.getActiveContext();

noteContext.setNote(notePath, { viewScope }).then(() => {
noteContext.setNote(notePath, {viewScope}).then(() => {
if (noteContext !== appContext.tabManager.getActiveContext()) {
appContext.tabManager.activateNoteContext(noteContext.ntxId);
}
});
}
}
else if (hrefLink) {
const withinEditLink = $link.hasClass("ck-link-actions__preview");
const outsideOfCKEditor = $link.closest("[contenteditable]").length === 0;
} else if (hrefLink) {
const withinEditLink = $link?.hasClass("ck-link-actions__preview");
const outsideOfCKEditor = !$link || $link.closest("[contenteditable]").length === 0;

if (openInNewTab
|| (withinEditLink && (leftClick || middleClick))
|| (outsideOfCKEditor && (leftClick || middleClick))
) {
if (hrefLink.toLowerCase().startsWith('http') || hrefLink.startsWith("api/")) {
window.open(hrefLink, '_blank');
}
else if (hrefLink.toLowerCase().startsWith('file:') && utils.isElectron()) {
} else if (hrefLink.toLowerCase().startsWith('file:') && utils.isElectron()) {
const electron = utils.dynamicRequire('electron');

electron.shell.openPath(hrefLink);
Expand Down Expand Up @@ -364,6 +365,7 @@ export default {
getNotePathFromUrl,
createLink,
goToLink,
goToLinkExt,
loadReferenceLinkTitle,
getReferenceLinkTitle,
getReferenceLinkTitleSync,
Expand Down
2 changes: 1 addition & 1 deletion src/public/app/widgets/buttons/note_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {

this.toggleDisabled(this.$findInTextButton, ['text', 'code', 'book'].includes(note.type));

this.toggleDisabled(this.$showSourceButton, ['text', 'relationMap', 'mermaid'].includes(note.type));
this.toggleDisabled(this.$showSourceButton, ['text', 'code', 'relationMap', 'mermaid', 'canvas'].includes(note.type));

this.toggleDisabled(this.$printActiveNoteButton, ['text', 'code'].includes(note.type));

Expand Down
4 changes: 3 additions & 1 deletion src/public/app/widgets/note_detail.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
protectedSessionHolder.touchProtectedSessionIfNecessary(note);

await server.put(`notes/${noteId}/data`, data, this.componentId);

this.getTypeWidget().dataSaved?.();
});

appContext.addBeforeUnloadListener(this);
Expand Down Expand Up @@ -167,7 +169,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
let type = note.type;
const viewScope = this.noteContext.viewScope;

if (type === 'text' && viewScope.viewMode === 'source') {
if (viewScope.viewMode === 'source') {
type = 'readOnlyCode';
} else if (viewScope.viewMode === 'attachments') {
type = viewScope.attachmentId ? 'attachmentDetail' : 'attachmentList';
Expand Down
17 changes: 15 additions & 2 deletions src/public/app/widgets/note_tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import keyboardActionsService from "../services/keyboard_actions.js";
import clipboard from "../services/clipboard.js";
import protectedSessionService from "../services/protected_session.js";
import linkService from "../services/link.js";
import syncService from "../services/sync.js";
import options from "../services/options.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import dialogService from "../services/dialog.js";
Expand Down Expand Up @@ -586,6 +585,17 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
});
},
select: (event, {node}) => {
if (hoistedNoteService.getHoistedNoteId() === 'root'
&& node.data.noteId === '_hidden'
&& node.isSelected()) {

// hidden is hackily hidden from the tree via CSS when root is hoisted
// make sure it's not selected by mistake, it could be e.g. deleted by mistake otherwise
node.setSelected(false);

return;
}

$(node.span).find(".fancytree-custom-icon").attr("title",
node.isSelected() ? "Apply bulk actions on selected notes" : "");
}
Expand Down Expand Up @@ -799,7 +809,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
nodes.push(this.getActiveNode());
}

return nodes;
// hidden subtree is hackily hidden via CSS when hoisted to root
// make sure it's never selected for e.g. deletion in such a case
return nodes.filter(node => hoistedNoteService.getHoistedNoteId() !== 'root'
|| node.data.noteId !== '_hidden');
}

async setExpandedStatusForSubtree(node, isExpanded) {
Expand Down
4 changes: 3 additions & 1 deletion src/public/app/widgets/type_widgets/attachment_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ export default class AttachmentListTypeWidget extends TypeWidget {
const $helpButton = $('<button class="attachment-help-button" type="button" data-help-page="attachments" title="Open help page on attachments"><span class="bx bx-help-circle"></span></button>');
utils.initHelpButtons($helpButton);

const noteLink = await linkService.createLink(this.noteId); // do separately to avoid race condition between empty() and .append()

this.$linksWrapper.empty().append(
$('<div>').append(
"Owning note: ",
await linkService.createLink(this.noteId),
noteLink,
),
$('<div>').append(
$('<button class="btn btn-sm">')
Expand Down
Loading

0 comments on commit 035113d

Please sign in to comment.