Compare commits

...

9 Commits

Author SHA1 Message Date
zadam
e22f77eae7 release 0.60.3 2023-06-15 23:23:37 +02:00
zadam
3223e76787 etapi ZIP import 2023-06-15 23:21:40 +02:00
zadam
74400dad97 fix race condition between script execution and saving, closes #4028 2023-06-15 21:51:41 +02:00
zadam
1b68adf3e4 compatibility with online excalidraw tool 2023-06-15 21:34:42 +02:00
zadam
bea39f37ee stop click propagation in tree item actions 2023-06-13 00:12:55 +02:00
mechanarchy
6548149107 Update etapi.js
(cherry picked from commit b97ebe9f03)
2023-06-12 23:21:38 +02:00
zadam
6015a067ec sql console outputs results of CTEs, fixes #2800 2023-06-12 23:18:57 +02:00
mechanarchy
4a1ecd906b Update entrypoints.js
(cherry picked from commit 08ec866dd2)
2023-06-12 23:13:40 +02:00
zadam
004cfe1965 allow creating backups via ETAPI, #4014 2023-06-12 23:09:29 +02:00
16 changed files with 133 additions and 21 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "trilium",
"version": "0.60.1-beta",
"version": "0.60.2-beta",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "trilium",
"version": "0.60.1-beta",
"version": "0.60.2-beta",
"hasInstallScript": true,
"license": "AGPL-3.0-only",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "trilium",
"productName": "Trilium Notes",
"description": "Trilium Notes",
"version": "0.60.2-beta",
"version": "0.60.3",
"license": "AGPL-3.0-only",
"main": "electron.js",
"bin": {

14
src/etapi/backup.js Normal file
View File

@@ -0,0 +1,14 @@
const eu = require("./etapi_utils");
const backupService = require("../services/backup");
function register(router) {
eu.route(router, 'put', '/etapi/backup/:backupName', async (req, res, next) => {
await backupService.backupNow(req.params.backupName);
res.sendStatus(204);
});
}
module.exports = {
register
};

View File

@@ -33,13 +33,7 @@ paths:
content:
application/json; charset=utf-8:
schema:
properties:
note:
$ref: '#/components/schemas/Note'
description: Created note
branch:
$ref: '#/components/schemas/Branch'
description: Created branch
$ref: '#/components/schemas/NoteWithBranch'
default:
description: unexpected error
content:
@@ -291,6 +285,29 @@ paths:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/notes/{noteId}/import:
parameters:
- name: noteId
in: path
required: true
schema:
$ref: '#/components/schemas/EntityId'
post:
description: Imports ZIP file into a given note.
operationId: importZip
responses:
'201':
description: note created
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/NoteWithBranch'
default:
description: unexpected error
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/notes/{noteId}/note-revision:
parameters:
- name: noteId
@@ -700,7 +717,26 @@ paths:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
/backup/{backupName}:
parameters:
- name: backupName
in: path
required: true
description: If the backupName is e.g. "now", then the backup will be written to "backup-now.db" file
schema:
$ref: '#/components/schemas/StringId'
put:
description: Create a database backup under a given name
operationId: createBackup
responses:
'204':
description: backup has been created
default:
description: unexpected error
content:
application/json; charset=utf-8:
schema:
$ref: '#/components/schemas/Error'
components:
securitySchemes:
EtapiTokenAuth:
@@ -833,6 +869,13 @@ components:
utcDateModified:
$ref: '#/components/schemas/UtcDateTime'
readOnly: true
NoteWithBranch:
type: object
properties:
note:
$ref: '#/components/schemas/Note'
branch:
$ref: '#/components/schemas/Branch'
Attribute:
type: object
description: Attribute (Label, Relation) is a key-value record attached to a note.
@@ -880,6 +923,10 @@ components:
type: string
pattern: '[a-zA-Z0-9_]{4,32}'
example: evnnmvHTCgIn
StringId:
type: string
pattern: '[a-zA-Z0-9_]{1,32}'
example: my_ID
EntityIdList:
type: array
items:

View File

@@ -8,6 +8,7 @@ const v = require("./validators");
const searchService = require("../services/search/services/search");
const SearchContext = require("../services/search/search_context");
const zipExportService = require("../services/export/zip");
const zipImportService = require("../services/import/zip");
function register(router) {
eu.route(router, 'get', '/etapi/notes', (req, res, next) => {
@@ -141,11 +142,21 @@ function register(router) {
// (e.g. branchIds are not seen in UI), that we export "note export" instead.
const branch = note.getParentBranches()[0];
console.log(note.getParentBranches());
zipExportService.exportToZip(taskContext, branch, format, res);
});
eu.route(router, 'post' ,'/etapi/notes/:noteId/import', (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);
const taskContext = new TaskContext('no-progress-reporting');
zipImportService.importZip(taskContext, req.body, note).then(importedNote => {
res.status(201).json({
note: mappers.mapNoteToPojo(importedNote),
branch: mappers.mapBranchToPojo(importedNote.getBranches()[0]),
});
}); // we need better error handling here, async errors won't be properly processed.
});
eu.route(router, 'post' ,'/etapi/notes/:noteId/note-revision', (req, res, next) => {
const note = eu.getAndCheckNote(req.params.noteId);

View File

@@ -173,7 +173,7 @@ export default class Entrypoints extends Component {
const resp = await server.post(`sql/execute/${note.noteId}`);
if (!resp.success) {
toastService.showError(`Error occurred while executing SQL query: ${resp.message}`);
toastService.showError(`Error occurred while executing SQL query: ${resp.error}`);
}
await appContext.triggerEvent('sqlQueryResults', {ntxId: ntxId, results: resp.results});

View File

@@ -230,6 +230,15 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
}
}
async runActiveNoteCommand(params) {
if (this.isNoteContext(params.ntxId)) {
// make sure that script is saved before running it #4028
await this.spacedUpdate.updateNowIfNecessary();
}
return await this.parent.triggerCommand('runActiveNote', params);
}
async printActiveNoteEvent() {
if (!this.noteContext.isActive()) {
return;

View File

@@ -148,6 +148,9 @@ const TPL = `
const MAX_SEARCH_RESULTS_IN_TREE = 100;
// this has to be hanged on the actual elements to effectively intercept and stop click event
const cancelClickPropagation = e => e.stopPropagation();
export default class NoteTreeWidget extends NoteContextAwareWidget {
constructor() {
super();
@@ -559,7 +562,8 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const isHoistedNote = activeNoteContext && activeNoteContext.hoistedNoteId === note.noteId && note.noteId !== 'root';
if (isHoistedNote) {
const $unhoistButton = $('<span class="tree-item-button unhoist-button bx bx-door-open" title="Unhoist"></span>');
const $unhoistButton = $('<span class="tree-item-button unhoist-button bx bx-door-open" title="Unhoist"></span>')
.on("click", cancelClickPropagation);
// unhoist button is prepended since compared to other buttons this is not just convenience
// on the mobile interface - it's the only way to unhoist
@@ -567,19 +571,22 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
}
if (note.hasLabel('workspace') && !isHoistedNote) {
const $enterWorkspaceButton = $('<span class="tree-item-button enter-workspace-button bx bx-door-open" title="Hoist this note (workspace)"></span>');
const $enterWorkspaceButton = $('<span class="tree-item-button enter-workspace-button bx bx-door-open" title="Hoist this note (workspace)"></span>')
.on("click", cancelClickPropagation);
$span.append($enterWorkspaceButton);
}
if (note.type === 'search') {
const $refreshSearchButton = $('<span class="tree-item-button refresh-search-button bx bx-refresh" title="Refresh saved search results"></span>');
const $refreshSearchButton = $('<span class="tree-item-button refresh-search-button bx bx-refresh" title="Refresh saved search results"></span>')
.on("click", cancelClickPropagation);
$span.append($refreshSearchButton);
}
if (!['search', 'launcher'].includes(note.type) && !note.isOptions() && !note.isLaunchBarConfig()) {
const $createChildNoteButton = $('<span class="tree-item-button add-note-button bx bx-plus" title="Create child note"></span>');
const $createChildNoteButton = $('<span class="tree-item-button add-note-button bx bx-plus" title="Create child note"></span>')
.on("click", cancelClickPropagation);
$span.append($createChildNoteButton);
}

View File

@@ -285,6 +285,8 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
})
const content = {
type: "excalidraw",
version: 2,
_meta: "This note has type `canvas`. It uses excalidraw and stores an exported svg alongside.",
elements, // excalidraw
appState, // excalidraw

View File

@@ -109,6 +109,10 @@ export default class EtapiOptions extends OptionsWidget {
message: "Please enter new token's name",
defaultValue: oldName
});
if(tokenName === null) {
return;
}
await server.patch(`etapi-tokens/${etapiTokenId}`, {name: tokenName});

View File

@@ -37,7 +37,7 @@ function execute(req) {
continue;
}
if (query.toLowerCase().startsWith('select')) {
if (query.toLowerCase().startsWith('select') || query.toLowerCase().startsWith('with')) {
results.push(sql.getRows(query));
}
else {

View File

@@ -65,6 +65,7 @@ const etapiBranchRoutes = require('../etapi/branches');
const etapiNoteRoutes = require('../etapi/notes');
const etapiSpecialNoteRoutes = require('../etapi/special_notes');
const etapiSpecRoute = require('../etapi/spec');
const etapiBackupRoute = require('../etapi/backup');
const csrfMiddleware = csurf({
cookie: true,
@@ -315,6 +316,7 @@ function register(app) {
etapiNoteRoutes.register(router);
etapiSpecialNoteRoutes.register(router);
etapiSpecRoute.register(router);
etapiBackupRoute.register(router);
app.use('', router);
}

View File

@@ -1 +1 @@
module.exports = { buildDate:"2023-06-08T22:46:52+02:00", buildRevision: "6e69cafe5419e8efcc6f652647f9227dbcfa1e18" };
module.exports = { buildDate:"2023-06-15T23:23:37+02:00", buildRevision: "3223e767875e5379c99ff58a562cb9c1a2641bdf" };

View File

@@ -6,7 +6,7 @@ const ws = require('./ws');
const taskContexts = {};
class TaskContext {
constructor(taskId, taskType = null, data = null) {
constructor(taskId, taskType = null, data = {}) {
this.taskId = taskId;
this.taskType = taskType;
this.data = data;

View File

@@ -0,0 +1,4 @@
PUT {{triliumHost}}/etapi/backup/etapi_test
Authorization: {{authToken}}
> {% client.assert(response.status === 201); %}

View File

@@ -0,0 +1,12 @@
POST {{triliumHost}}/etapi/notes/root/import
Authorization: {{authToken}}
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
< ../db/demo.zip
> {%
client.assert(response.status === 201);
client.assert(response.body.note.title == "Trilium Demo");
client.assert(response.body.branch.parentNoteId == "root");
%}