Compare commits

...

22 Commits

Author SHA1 Message Date
zadam
e76093e75c release 0.62.6 2024-01-21 23:49:23 +01:00
zadam
4f8073daa7 Revert "don't tag beta images with latest #4590"
This reverts commit 47fb96faa8.
2024-01-21 23:48:56 +01:00
zadam
47fb96faa8 don't tag beta images with latest #4590 2024-01-21 23:42:57 +01:00
zadam
6e33553146 fix migration 2024-01-21 23:11:27 +01:00
zadam
807941e6a5 disable scanning for links while migration is running #4535 2024-01-21 20:50:38 +01:00
zadam
1e30c0702e add indexes sooner in the migration process to speed it up #4535 2024-01-21 11:13:45 +01:00
zadam
390ad6d813 fix rendering image title in share renderer, closes #4578 2024-01-09 23:38:44 +01:00
zadam
77800d073f fix URL unescaping in improper place, #4566 2024-01-09 23:22:45 +01:00
zadam
1953c7896f support SVG image upload, fixes #4573 2024-01-09 23:13:33 +01:00
zadam
cd43752f61 remove conflicting shortcut, fixes #4570 2024-01-09 22:52:13 +01:00
zadam
d6046efa1b release 0.62.5 2024-01-08 00:05:13 +01:00
zadam
ee608fcf46 unescape HTML before downloading images, #4566 2024-01-08 00:03:11 +01:00
zadam
894b08a1b8 correctly save attachment URL, #4566 2024-01-07 23:51:38 +01:00
zadam
4e549baedc fix auto-download of images, closes #4566 2024-01-07 23:45:40 +01:00
zadam
6b6e42e9ba document attachment ETAPI APIs in OpenAPI spec, fixes #4559 2024-01-07 23:11:55 +01:00
zadam
0404b78fb8 fix loading katex in share #4558 2024-01-07 22:52:16 +01:00
zadam
439743d2b0 convert absolute image attachment URLs to relative without domain, fixes #4509 2023-12-27 23:22:40 +01:00
zadam
0ac397e7ff fix setNoteToParent API breakage, closes #4505 2023-12-11 23:05:05 +01:00
zadam
d243880099 release 0.62.4 2023-12-07 00:03:59 +01:00
zadam
2e23c521c3 electron upgrade 2023-12-06 23:54:17 +01:00
zadam
eb761b286f electron upgrade 2023-12-04 00:17:00 +01:00
zadam
d0f6ff5f98 fix erasing revisions 2023-12-04 00:11:24 +01:00
21 changed files with 253 additions and 52 deletions

View File

@@ -8,3 +8,6 @@ CREATE TABLE IF NOT EXISTS "blobs" (
ALTER TABLE notes ADD blobId TEXT DEFAULT NULL;
ALTER TABLE note_revisions ADD blobId TEXT DEFAULT NULL;
CREATE INDEX IF NOT EXISTS IDX_notes_blobId on notes (blobId);
CREATE INDEX IF NOT EXISTS IDX_note_revisions_blobId on note_revisions (blobId);

View File

@@ -21,5 +21,6 @@ CREATE INDEX `IDX_revisions_utcDateCreated` ON `revisions` (`utcDateCreated`);
CREATE INDEX `IDX_revisions_utcDateLastEdited` ON `revisions` (`utcDateLastEdited`);
CREATE INDEX `IDX_revisions_dateCreated` ON `revisions` (`dateCreated`);
CREATE INDEX `IDX_revisions_dateLastEdited` ON `revisions` (`dateLastEdited`);
CREATE INDEX IF NOT EXISTS IDX_revisions_blobId on revisions (blobId);
UPDATE entity_changes SET entityName = 'revisions' WHERE entityName = 'note_revisions';

View File

@@ -19,3 +19,5 @@ CREATE INDEX IDX_attachments_ownerId_role
CREATE INDEX IDX_attachments_utcDateScheduledForErasureSince
on attachments (utcDateScheduledForErasureSince);
CREATE INDEX IF NOT EXISTS IDX_attachments_blobId on attachments (blobId);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

19
package-lock.json generated
View File

@@ -1,11 +1,12 @@
{
"name": "trilium",
"version": "0.62.2",
"version": "0.62.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.62.2",
"name": "trilium",
"version": "0.62.5",
"hasInstallScript": true,
"license": "AGPL-3.0-only",
"dependencies": {
@@ -80,7 +81,7 @@
},
"devDependencies": {
"cross-env": "7.0.3",
"electron": "25.9.5",
"electron": "25.9.8",
"electron-builder": "24.6.4",
"electron-packager": "17.1.2",
"electron-rebuild": "3.2.9",
@@ -4365,9 +4366,9 @@
}
},
"node_modules/electron": {
"version": "25.9.5",
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.5.tgz",
"integrity": "sha512-gM7GXUSd3JVRcYbBnNOtZeNnE5MCJjtZTT8QyIxJvpQ0Dh9dz3hTuEL62dOwnMFW/l47ACQ6es/8qi01P4QGZA==",
"version": "25.9.8",
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.8.tgz",
"integrity": "sha512-PGgp6PH46QVENHuAHc2NT1Su8Q1qov7qIl2jI5tsDpTibwV2zD8539AeWBQySeBU4dhbj9onIl7+1bXQ0wefBg==",
"hasInstallScript": true,
"dependencies": {
"@electron/get": "^2.0.0",
@@ -16963,9 +16964,9 @@
}
},
"electron": {
"version": "25.9.5",
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.5.tgz",
"integrity": "sha512-gM7GXUSd3JVRcYbBnNOtZeNnE5MCJjtZTT8QyIxJvpQ0Dh9dz3hTuEL62dOwnMFW/l47ACQ6es/8qi01P4QGZA==",
"version": "25.9.8",
"resolved": "https://registry.npmjs.org/electron/-/electron-25.9.8.tgz",
"integrity": "sha512-PGgp6PH46QVENHuAHc2NT1Su8Q1qov7qIl2jI5tsDpTibwV2zD8539AeWBQySeBU4dhbj9onIl7+1bXQ0wefBg==",
"requires": {
"@electron/get": "^2.0.0",
"@types/node": "^18.11.18",

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.62.3",
"version": "0.62.6",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {
@@ -104,7 +104,7 @@
},
"devDependencies": {
"cross-env": "7.0.3",
"electron": "25.9.5",
"electron": "25.9.8",
"electron-builder": "24.6.4",
"electron-packager": "17.1.2",
"electron-rebuild": "3.2.9",

View File

@@ -427,6 +427,116 @@ paths:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/attachments:
post:
description: create an attachment
operationId: postAttachment
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateAttachment'
responses:
'201':
description: attachment created
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Attachment'
default:
description: unexpected error
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/attachments/{attachmentId}:
parameters:
- name: attachmentId
in: path
required: true
schema:
$ref: '#/components/schemas/EntityId'
get:
description: Returns an attachment identified by its ID
operationId: getAttachmentById
responses:
'200':
description: attachment response
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Attachment'
default:
description: unexpected error
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
patch:
description: patch an attachment identified by the attachmentId with changes in the body. Only role, mime, title, and position are patchable.
operationId: patchAttachmentById
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Attachment'
responses:
'200':
description: attribute updated
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Attachment'
default:
description: unexpected error
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
delete:
description: deletes an attachment based on the attachmentId supplied.
operationId: deleteAttachmentById
responses:
'204':
description: attachment deleted
default:
description: unexpected error
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/attachments/{attachmentId}/content:
parameters:
- name: attachmentId
in: path
required: true
schema:
$ref: '#/components/schemas/EntityId'
get:
description: Returns attachment content identified by its ID
operationId: getAttachmentContent
responses:
'200':
description: attachment content response
content:
text/html:
schema:
type: string
put:
description: Updates attachment content identified by its ID
operationId: putAttachmentContentById
requestBody:
description: html content of attachment
required: true
content:
text/plain:
schema:
type: string
responses:
'204':
description: attachment content updated
/attributes:
post:
description: create an attribute for a given note
@@ -474,7 +584,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
patch:
description: patch a attribute identified by the attributeId with changes in the body. For labels, only value and position can be updated. For relations, only position can be updated. If you want to modify other properties, you need to delete the old attribute and create a new one.
description: patch an attribute identified by the attributeId with changes in the body. For labels, only value and position can be updated. For relations, only position can be updated. If you want to modify other properties, you need to delete the old attribute and create a new one.
operationId: patchAttributeById
requestBody:
required: true
@@ -496,7 +606,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
delete:
description: deletes a attribute based on the attributeId supplied.
description: deletes an attribute based on the attributeId supplied.
operationId: deleteAttributeById
responses:
'204':
@@ -884,6 +994,57 @@ components:
$ref: '#/components/schemas/Note'
branch:
$ref: '#/components/schemas/Branch'
Attachment:
type: object
description: Attachment is owned by a note, has title and content
properties:
attachmentId:
$ref: '#/components/schemas/EntityId'
readOnly: true
ownerId:
$ref: '#/components/schemas/EntityId'
description: identifies the owner of the attachment, is either noteId or revisionId
role:
type: string
mime:
type: string
title:
type: string
position:
type: integer
format: int32
blobId:
type: string
description: ID of the blob object which effectively serves as a content hash
dateModified:
$ref: '#/components/schemas/LocalDateTime'
readOnly: true
utcDateModified:
$ref: '#/components/schemas/UtcDateTime'
readOnly: true
utcDateScheduledForErasureSince:
$ref: '#/components/schemas/UtcDateTime'
readOnly: true
contentLength:
type: integer
format: int32
CreateAttachment:
type: object
properties:
ownerId:
$ref: '#/components/schemas/EntityId'
description: identifies the owner of the attachment, is either noteId or revisionId
role:
type: string
mime:
type: string
title:
type: string
content:
type: string
position:
type: integer
format: int32
Attribute:
type: object
description: Attribute (Label, Relation) is a key-value record attached to a note.

View File

@@ -8,6 +8,7 @@ const cls = require('../../services/cls');
const path = require('path');
const becca = require("../../becca/becca");
const blobService = require("../../services/blob");
const eraseService = require("../../services/erase.js");
function getRevisionBlob(req) {
const preview = req.query.preview === 'true';
@@ -88,11 +89,11 @@ function eraseAllRevisions(req) {
const revisionIdsToErase = sql.getColumn('SELECT revisionId FROM revisions WHERE noteId = ?',
[req.params.noteId]);
revisionService.eraseRevisions(revisionIdsToErase);
eraseService.eraseRevisions(revisionIdsToErase);
}
function eraseRevision(req) {
revisionService.eraseRevisions([req.params.revisionId]);
eraseService.eraseRevisions([req.params.revisionId]);
}
function restoreRevision(req) {

View File

@@ -1 +1 @@
module.exports = { buildDate:"2023-11-27T23:37:24+01:00", buildRevision: "a6036859b8bd24be31dfcf6318e57cf696fa25bc" };
module.exports = { buildDate:"2024-01-21T23:49:23+01:00", buildRevision: "4f8073daa7cff1b8b6737ae45792b2e87c2adf33" };

View File

@@ -4,6 +4,7 @@ const becca = require("../becca/becca");
const cloningService = require("./cloning");
const branchService = require("./branches");
const utils = require("./utils");
const eraseService = require("./erase.js");
const ACTION_HANDLERS = {
addLabel: (action, note) => {
@@ -18,7 +19,7 @@ const ACTION_HANDLERS = {
note.deleteNote(deleteId);
},
deleteRevisions: (action, note) => {
revisionService.eraseRevisions(note.getRevisions().map(rev => rev.revisionId));
eraseService.eraseRevisions(note.getRevisions().map(rev => rev.revisionId));
},
deleteLabel: (action, note) => {
for (const label of note.getOwnedLabels(action.labelName)) {

View File

@@ -48,6 +48,14 @@ function isEntityEventsDisabled() {
return !!namespace.get('disableEntityEvents');
}
function setMigrationRunning(running) {
namespace.set('migrationRunning', !!running);
}
function isMigrationRunning() {
return !!namespace.get('migrationRunning');
}
function disableSlowQueryLogging(disable) {
namespace.set('disableSlowQueryLogging', disable);
}
@@ -102,5 +110,7 @@ module.exports = {
putEntityChange,
ignoreEntityChangeIds,
disableSlowQueryLogging,
isSlowQueryLoggingDisabled
isSlowQueryLoggingDisabled,
setMigrationRunning,
isMigrationRunning
};

View File

@@ -467,7 +467,7 @@ class ConsistencyChecks {
WHERE blobs.blobId IS NULL`,
({revisionId, blobId}) => {
if (this.autoFix) {
revisionService.eraseRevisions([revisionId]);
eraseService.eraseRevisions([revisionId]);
this.reloadNeeded = true;

View File

@@ -29,7 +29,7 @@ function eraseNotes(noteIdsToErase) {
const revisionIdsToErase = sql.getManyRows(`SELECT revisionId FROM revisions WHERE noteId IN (???)`, noteIdsToErase)
.map(row => row.revisionId);
revisionService.eraseRevisions(revisionIdsToErase);
eraseRevisions(revisionIdsToErase);
log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
}
@@ -79,6 +79,18 @@ function eraseAttachments(attachmentIdsToErase) {
log.info(`Erased attachments: ${JSON.stringify(attachmentIdsToErase)}`);
}
function eraseRevisions(revisionIdsToErase) {
if (revisionIdsToErase.length === 0) {
return;
}
sql.executeMany(`DELETE FROM revisions WHERE revisionId IN (???)`, revisionIdsToErase);
setEntityChangesAsErased(sql.getManyRows(`SELECT * FROM entity_changes WHERE entityName = 'revisions' AND entityId IN (???)`, revisionIdsToErase));
log.info(`Removed revisions: ${JSON.stringify(revisionIdsToErase)}`);
}
function eraseUnusedBlobs() {
const unusedBlobIds = sql.getColumn(`
SELECT blobs.blobId
@@ -184,5 +196,6 @@ module.exports = {
eraseUnusedAttachmentsNow,
eraseNotesWithDeleteId,
eraseUnusedBlobs,
eraseAttachments
eraseAttachments,
eraseRevisions
};

View File

@@ -302,7 +302,7 @@ const DEFAULT_KEYBOARD_ACTIONS = [
},
{
actionName: "lastTab",
defaultShortcuts: ["CommandOrControl+0"],
defaultShortcuts: [],
description: "Activates the last tab in the list",
scope: "window"
},

View File

@@ -5,12 +5,13 @@ const log = require('./log');
const utils = require('./utils');
const resourceDir = require('./resource_dir');
const appInfo = require('./app_info');
const cls = require('./cls.js');
async function migrate() {
const currentDbVersion = getDbVersion();
if (currentDbVersion < 214) {
log.error("Direct migration from your current version is not supported. Please upgrade to the latest v0.60.X first and only then to this version.");
log.error("Direct migration from your current version is not supported. Please upgrade to the latest v0.60.4 first and only then to this version.");
utils.crash();
return;
@@ -18,7 +19,7 @@ async function migrate() {
// backup before attempting migration
await backupService.backupNow(
// creating a special backup for versions 0.60.X, the changes in 0.61 are major.
// creating a special backup for version 0.60.4, the changes in 0.61 are major.
currentDbVersion === 214
? `before-migration-v060`
: 'before-migration'
@@ -51,6 +52,9 @@ async function migrate() {
// all migrations are executed in one transaction - upgrade either succeeds, or the user can stay at the old version
// otherwise if half of the migrations succeed, user can't use any version - DB is too "new" for the old app,
// and too old for the new app version.
cls.setMigrationRunning(true);
sql.transactional(() => {
for (const mig of migrations) {
try {

View File

@@ -471,6 +471,8 @@ function findRelationMapLinks(content, foundLinks) {
const imageUrlToAttachmentIdMapping = {};
async function downloadImage(noteId, imageUrl) {
const unescapedUrl = utils.unescapeHtml(imageUrl);
try {
let imageBuffer;
@@ -487,14 +489,14 @@ async function downloadImage(noteId, imageUrl) {
});
});
} else {
imageBuffer = await request.getImage(imageUrl);
imageBuffer = await request.getImage(unescapedUrl);
}
const parsedUrl = url.parse(imageUrl);
const parsedUrl = url.parse(unescapedUrl);
const title = path.basename(parsedUrl.pathname);
const imageService = require('../services/image');
const {attachment} = imageService.saveImageToAttachment(noteId, imageBuffer, title, true, true);
const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, title, true, true);
imageUrlToAttachmentIdMapping[imageUrl] = attachment.attachmentId;
@@ -511,7 +513,7 @@ const downloadImagePromises = {};
function replaceUrl(content, url, attachment) {
const quotedUrl = utils.quoteRegex(url);
return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "ig"), ` src="api/attachments/${encodeURIComponent(attachment.title)}/image"`);
return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "ig"), ` src="api/attachments/${attachment.attachmentId}/image/${encodeURIComponent(attachment.title)}"`);
}
function downloadImages(noteId, content) {
@@ -636,6 +638,10 @@ function saveAttachments(note, content) {
content = `${content.substr(0, attachmentMatch.index)}<a class="reference-link" href="#root/${note.noteId}?viewMode=attachments&attachmentId=${attachment.attachmentId}">${title}</a>${content.substr(attachmentMatch.index + attachmentMatch[0].length)}`;
}
// removing absolute references to server to keep it working between instances,
// we also omit / at the beginning to keep the paths relative
content = content.replace(/src="[^"]*\/api\/attachments\//g, 'src="api/attachments/');
return content;
}
@@ -889,6 +895,11 @@ function scanForLinks(note, content) {
* Things which have to be executed after updating content, but asynchronously (separate transaction)
*/
async function asyncPostProcessContent(note, content) {
if (cls.isMigrationRunning()) {
// this is rarely needed for migrations, but can cause trouble by e.g. triggering downloads
return;
}
if (note.hasStringContent() && !utils.isString(content)) {
content = content.toString();
}

View File

@@ -46,18 +46,6 @@ function protectRevisions(note) {
}
}
function eraseRevisions(revisionIdsToErase) {
if (revisionIdsToErase.length === 0) {
return;
}
log.info(`Removing revisions: ${JSON.stringify(revisionIdsToErase)}`);
sql.executeMany(`DELETE FROM revisions WHERE revisionId IN (???)`, revisionIdsToErase);
sql.executeMany(`UPDATE entity_changes SET isErased = 1, utcDateChanged = '${dateUtils.utcNowDateTime()}' WHERE entityName = 'revisions' AND entityId IN (???)`, revisionIdsToErase);
}
module.exports = {
protectRevisions,
eraseRevisions
protectRevisions
};

View File

@@ -209,8 +209,9 @@ function sortNotesIfNeeded(parentNoteId) {
function setNoteToParent(noteId, prefix, parentNoteId) {
const parentNote = becca.getNote(parentNoteId);
if (!parentNote) {
throw new Error(`Cannot move note to deleted parent note '${parentNoteId}'`);
if (parentNoteId && !parentNote) {
// null parentNoteId is a valid value
throw new Error(`Cannot move note to deleted / missing parent note '${parentNoteId}'`);
}
// case where there might be more such branches is ignored. It's expected there should be just one

View File

@@ -105,10 +105,10 @@ function renderText(result, note) {
if (result.content.includes(`<span class="math-tex">`)) {
result.header += `
<script src="../../${assetPath}/libraries/katex/katex.min.js"></script>
<link rel="stylesheet" href="../../${assetPath}/libraries/katex/katex.min.css">
<script src="../../${assetPath}/libraries/katex/auto-render.min.js"></script>
<script src="../../${assetPath}/libraries/katex/mhchem.min.js"></script>
<script src="../../${assetPath}/node_modules/katex/dist/katex.min.js"></script>
<link rel="stylesheet" href="../../${assetPath}/node_modules/katex/dist/katex.min.css">
<script src="../../${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script>
<script src="../../${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
renderMathInElement(document.getElementById('content'));
@@ -137,7 +137,7 @@ function renderCode(result) {
function renderMermaid(result, note) {
result.content = `
<img src="api/images/${note.noteId}/${note.escapedTitle}?${note.utcDateModified}">
<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">
<hr>
<details>
<summary>Chart source</summary>
@@ -146,7 +146,7 @@ function renderMermaid(result, note) {
}
function renderImage(result, note) {
result.content = `<img src="api/images/${note.noteId}/${note.escapedTitle}?${note.utcDateModified}">`;
result.content = `<img src="api/images/${note.noteId}/${note.encodedTitle}?${note.utcDateModified}">`;
}
function renderFile(note, result) {

View File

@@ -490,6 +490,10 @@ class SNote extends AbstractShacaEntity {
return escape(this.title);
}
get encodedTitle() {
return encodeURIComponent(this.title);
}
getPojo() {
return {
noteId: this.noteId,