mirror of
https://github.com/zadam/trilium.git
synced 2025-10-27 16:26:31 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f195c7d1b6 | ||
|
|
10f3df3ed4 | ||
|
|
18e2e6779b | ||
|
|
ed129c307b | ||
|
|
8742e4bfe9 |
@@ -1,109 +0,0 @@
|
|||||||
const crypto = require('crypto');
|
|
||||||
const {
|
|
||||||
deleteEtapi,
|
|
||||||
getEtapiResponse,
|
|
||||||
describeEtapi, postEtapi,
|
|
||||||
getEtapi,
|
|
||||||
getEtapiContent,
|
|
||||||
patchEtapi, putEtapi,
|
|
||||||
putEtapiContent
|
|
||||||
} = require("../support/etapi");
|
|
||||||
|
|
||||||
describeEtapi("notes", () => {
|
|
||||||
it("create", async () => {
|
|
||||||
const {note, branch} = await postEtapi('create-note', {
|
|
||||||
parentNoteId: 'root',
|
|
||||||
type: 'text',
|
|
||||||
title: 'Hello World!',
|
|
||||||
content: 'Content',
|
|
||||||
prefix: 'Custom prefix'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(note.title).toEqual("Hello World!");
|
|
||||||
expect(branch.parentNoteId).toEqual("root");
|
|
||||||
expect(branch.prefix).toEqual("Custom prefix");
|
|
||||||
|
|
||||||
const rNote = await getEtapi(`notes/${note.noteId}`);
|
|
||||||
expect(rNote.title).toEqual("Hello World!");
|
|
||||||
|
|
||||||
const rContent = await (await getEtapiContent(`notes/${note.noteId}/content`)).text();
|
|
||||||
expect(rContent).toEqual("Content");
|
|
||||||
|
|
||||||
const rBranch = await getEtapi(`branches/${branch.branchId}`);
|
|
||||||
expect(rBranch.parentNoteId).toEqual("root");
|
|
||||||
expect(rBranch.prefix).toEqual("Custom prefix");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("patch", async () => {
|
|
||||||
const {note} = await postEtapi('create-note', {
|
|
||||||
parentNoteId: 'root',
|
|
||||||
type: 'text',
|
|
||||||
title: 'Hello World!',
|
|
||||||
content: 'Content'
|
|
||||||
});
|
|
||||||
|
|
||||||
await patchEtapi(`notes/${note.noteId}`, {
|
|
||||||
title: 'new title',
|
|
||||||
type: 'code',
|
|
||||||
mime: 'text/apl',
|
|
||||||
dateCreated: '2000-01-01 12:34:56.999+0200',
|
|
||||||
utcDateCreated: '2000-01-01 10:34:56.999Z',
|
|
||||||
});
|
|
||||||
|
|
||||||
const rNote = await getEtapi(`notes/${note.noteId}`);
|
|
||||||
expect(rNote.title).toEqual("new title");
|
|
||||||
expect(rNote.type).toEqual("code");
|
|
||||||
expect(rNote.mime).toEqual("text/apl");
|
|
||||||
expect(rNote.dateCreated).toEqual("2000-01-01 12:34:56.999+0200");
|
|
||||||
expect(rNote.utcDateCreated).toEqual("2000-01-01 10:34:56.999Z");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("update content", async () => {
|
|
||||||
const {note} = await postEtapi('create-note', {
|
|
||||||
parentNoteId: 'root',
|
|
||||||
type: 'text',
|
|
||||||
title: 'Hello World!',
|
|
||||||
content: 'Content'
|
|
||||||
});
|
|
||||||
|
|
||||||
await putEtapiContent(`notes/${note.noteId}/content`, "new content");
|
|
||||||
|
|
||||||
const rContent = await (await getEtapiContent(`notes/${note.noteId}/content`)).text();
|
|
||||||
expect(rContent).toEqual("new content");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("create / update binary content", async () => {
|
|
||||||
const {note} = await postEtapi('create-note', {
|
|
||||||
parentNoteId: 'root',
|
|
||||||
type: 'file',
|
|
||||||
title: 'Hello World!',
|
|
||||||
content: 'ZZZ'
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedContent = crypto.randomBytes(16);
|
|
||||||
|
|
||||||
await putEtapiContent(`notes/${note.noteId}/content`, updatedContent);
|
|
||||||
|
|
||||||
const rContent = await (await getEtapiContent(`notes/${note.noteId}/content`)).arrayBuffer();
|
|
||||||
expect(Buffer.from(new Uint8Array(rContent))).toEqual(updatedContent);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("delete note", async () => {
|
|
||||||
const {note} = await postEtapi('create-note', {
|
|
||||||
parentNoteId: 'root',
|
|
||||||
type: 'text',
|
|
||||||
title: 'Hello World!',
|
|
||||||
content: 'Content'
|
|
||||||
});
|
|
||||||
|
|
||||||
await deleteEtapi(`notes/${note.noteId}`);
|
|
||||||
|
|
||||||
const resp = await getEtapiResponse(`notes/${note.noteId}`);
|
|
||||||
expect(resp.status).toEqual(404);
|
|
||||||
|
|
||||||
const error = await resp.json();
|
|
||||||
expect(error.status).toEqual(404);
|
|
||||||
expect(error.code).toEqual("NOTE_NOT_FOUND");
|
|
||||||
expect(error.message).toEqual(`Note '${note.noteId}' not found.`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,5 +1,108 @@
|
|||||||
describe("Notes", () => {
|
const crypto = require('crypto');
|
||||||
it("zzz", () => {
|
const {
|
||||||
|
deleteEtapi,
|
||||||
|
getEtapiResponse,
|
||||||
|
describeEtapi, postEtapi,
|
||||||
|
getEtapi,
|
||||||
|
getEtapiContent,
|
||||||
|
patchEtapi, putEtapi,
|
||||||
|
putEtapiContent
|
||||||
|
} = require("../support/etapi");
|
||||||
|
|
||||||
|
describeEtapi("notes", () => {
|
||||||
|
it("create", async () => {
|
||||||
|
const {note, branch} = await postEtapi('create-note', {
|
||||||
|
parentNoteId: 'root',
|
||||||
|
type: 'text',
|
||||||
|
title: 'Hello World!',
|
||||||
|
content: 'Content',
|
||||||
|
prefix: 'Custom prefix'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(note.title).toEqual("Hello World!");
|
||||||
|
expect(branch.parentNoteId).toEqual("root");
|
||||||
|
|
||||||
|
const rNote = await getEtapi(`notes/${note.noteId}`);
|
||||||
|
expect(rNote.title).toEqual("Hello World!");
|
||||||
|
|
||||||
|
const rContent = await (await getEtapiContent(`notes/${note.noteId}/content`)).text();
|
||||||
|
expect(rContent).toEqual("Content");
|
||||||
|
|
||||||
|
const rBranch = await getEtapi(`branches/${branch.branchId}`);
|
||||||
|
expect(rBranch.parentNoteId).toEqual("root");
|
||||||
|
expect(rBranch.prefix).toEqual("Custom prefix");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("patch", async () => {
|
||||||
|
const {note} = await postEtapi('create-note', {
|
||||||
|
parentNoteId: 'root',
|
||||||
|
type: 'text',
|
||||||
|
title: 'Hello World!',
|
||||||
|
content: 'Content'
|
||||||
|
});
|
||||||
|
|
||||||
|
await patchEtapi(`notes/${note.noteId}`, {
|
||||||
|
title: 'new title',
|
||||||
|
type: 'code',
|
||||||
|
mime: 'text/apl',
|
||||||
|
dateCreated: '2000-01-01 12:34:56.999+0200',
|
||||||
|
utcDateCreated: '2000-01-01 10:34:56.999Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
const rNote = await getEtapi(`notes/${note.noteId}`);
|
||||||
|
expect(rNote.title).toEqual("new title");
|
||||||
|
expect(rNote.type).toEqual("code");
|
||||||
|
expect(rNote.mime).toEqual("text/apl");
|
||||||
|
expect(rNote.dateCreated).toEqual("2000-01-01 12:34:56.999+0200");
|
||||||
|
expect(rNote.utcDateCreated).toEqual("2000-01-01 10:34:56.999Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("update content", async () => {
|
||||||
|
const {note} = await postEtapi('create-note', {
|
||||||
|
parentNoteId: 'root',
|
||||||
|
type: 'text',
|
||||||
|
title: 'Hello World!',
|
||||||
|
content: 'Content'
|
||||||
|
});
|
||||||
|
|
||||||
|
await putEtapiContent(`notes/${note.noteId}/content`, "new content");
|
||||||
|
|
||||||
|
const rContent = await (await getEtapiContent(`notes/${note.noteId}/content`)).text();
|
||||||
|
expect(rContent).toEqual("new content");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("create / update binary content", async () => {
|
||||||
|
const {note} = await postEtapi('create-note', {
|
||||||
|
parentNoteId: 'root',
|
||||||
|
type: 'file',
|
||||||
|
title: 'Hello World!',
|
||||||
|
content: 'ZZZ'
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedContent = crypto.randomBytes(16);
|
||||||
|
|
||||||
|
await putEtapiContent(`notes/${note.noteId}/content`, updatedContent);
|
||||||
|
|
||||||
|
const rContent = await (await getEtapiContent(`notes/${note.noteId}/content`)).arrayBuffer();
|
||||||
|
expect(Buffer.from(new Uint8Array(rContent))).toEqual(updatedContent);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("delete note", async () => {
|
||||||
|
const {note} = await postEtapi('create-note', {
|
||||||
|
parentNoteId: 'root',
|
||||||
|
type: 'text',
|
||||||
|
title: 'Hello World!',
|
||||||
|
content: 'Content'
|
||||||
|
});
|
||||||
|
|
||||||
|
await deleteEtapi(`notes/${note.noteId}`);
|
||||||
|
|
||||||
|
const resp = await getEtapiResponse(`notes/${note.noteId}`);
|
||||||
|
expect(resp.status).toEqual(404);
|
||||||
|
|
||||||
|
const error = await resp.json();
|
||||||
|
expect(error.status).toEqual(404);
|
||||||
|
expect(error.code).toEqual("NOTE_NOT_FOUND");
|
||||||
|
expect(error.message).toEqual(`Note '${note.noteId}' not found.`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
43
spec/etapi/search.spec.js
Normal file
43
spec/etapi/search.spec.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
const crypto = require('crypto');
|
||||||
|
const {
|
||||||
|
deleteEtapi,
|
||||||
|
getEtapiResponse,
|
||||||
|
describeEtapi, postEtapi,
|
||||||
|
getEtapi,
|
||||||
|
getEtapiContent,
|
||||||
|
patchEtapi, putEtapi,
|
||||||
|
putEtapiContent
|
||||||
|
} = require("../support/etapi");
|
||||||
|
const {createTextNote} = require("../support/etapi.js");
|
||||||
|
|
||||||
|
describeEtapi("search", () => {
|
||||||
|
describe('search', () => {
|
||||||
|
let europe, america;
|
||||||
|
let austria, czechia;
|
||||||
|
let usa, canada;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
europe = await createTextNote(null, 'Europe');
|
||||||
|
austria = await createTextNote(europe.noteId, 'Austria');
|
||||||
|
czechia = await createTextNote(europe.noteId, 'Czechia');
|
||||||
|
|
||||||
|
america = await createTextNote(null, 'America');
|
||||||
|
usa = await createTextNote(null, 'USA');
|
||||||
|
canada = await createTextNote(null, 'Canada');
|
||||||
|
});
|
||||||
|
|
||||||
|
async function search(searchString, params) {
|
||||||
|
const keyToValues = Object.keys(params).map(key => `${key}=${params[key]}`);
|
||||||
|
|
||||||
|
const {results} = await getEtapi(`notes?search=${searchString}&${keyToValues.join('&')}`);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
it("search", async () => {
|
||||||
|
const results = await search('Austria');
|
||||||
|
|
||||||
|
expect(results.length).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -8,6 +8,8 @@ const getEtapiAuthorizationHeader = () => "Basic " + Buffer.from(`etapi:${etapiA
|
|||||||
const PORT = '9999';
|
const PORT = '9999';
|
||||||
const HOST = 'http://localhost:' + PORT;
|
const HOST = 'http://localhost:' + PORT;
|
||||||
|
|
||||||
|
let currentTestRootNote = null;
|
||||||
|
|
||||||
function describeEtapi(description, specDefinitions) {
|
function describeEtapi(description, specDefinitions) {
|
||||||
describe(description, () => {
|
describe(description, () => {
|
||||||
let appProcess;
|
let appProcess;
|
||||||
@@ -42,6 +44,10 @@ function describeEtapi(description, specDefinitions) {
|
|||||||
})).json()).authToken;
|
})).json()).authToken;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
currentTestRootNote = await createTextNote('root', "test root");
|
||||||
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
console.log("Attempting to kill the Trilium process as part of the cleanup...");
|
console.log("Attempting to kill the Trilium process as part of the cleanup...");
|
||||||
kill(appProcess.pid, 'SIGKILL', () => { console.log("Trilium process killed.") });
|
kill(appProcess.pid, 'SIGKILL', () => { console.log("Trilium process killed.") });
|
||||||
@@ -51,6 +57,30 @@ function describeEtapi(description, specDefinitions) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createTextNote(parentNoteId = null, title = 'new note', content = '') {
|
||||||
|
if (!parentNoteId) {
|
||||||
|
parentNoteId = currentTestRootNote.noteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {note} = await postEtapi('create-note', {
|
||||||
|
parentNoteId,
|
||||||
|
type: 'text',
|
||||||
|
title,
|
||||||
|
content
|
||||||
|
});
|
||||||
|
|
||||||
|
return note;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createLabel(noteId, name, value = '', isInheritable = false) {
|
||||||
|
return await postEtapi('attributes', {
|
||||||
|
type: 'label',
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
isInheritable
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function getEtapiResponse(url) {
|
async function getEtapiResponse(url) {
|
||||||
return await fetch(`${HOST}/etapi/${url}`, {
|
return await fetch(`${HOST}/etapi/${url}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -172,6 +202,9 @@ function checkStatus(response) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
describeEtapi,
|
describeEtapi,
|
||||||
|
createTextNote,
|
||||||
|
createLabel,
|
||||||
|
getCurrentTestRootNote: () => currentTestRootNote,
|
||||||
getEtapi,
|
getEtapi,
|
||||||
getEtapiResponse,
|
getEtapiResponse,
|
||||||
getEtapiContent,
|
getEtapiContent,
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ export default class HighlightsListWidget extends RightPanelWidget {
|
|||||||
.class("icon-action"),
|
.class("icon-action"),
|
||||||
new OnClickButtonWidget()
|
new OnClickButtonWidget()
|
||||||
.icon("bx-x")
|
.icon("bx-x")
|
||||||
.title("Close Highlights List")
|
|
||||||
.titlePlacement("left")
|
.titlePlacement("left")
|
||||||
.onClick(widget => widget.triggerCommand("closeHlt"))
|
.onClick(widget => widget.triggerCommand("closeHlt"))
|
||||||
.class("icon-action")
|
.class("icon-action")
|
||||||
|
|||||||
@@ -258,10 +258,11 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
|||||||
.append($("<h2>").text(this.note.title))
|
.append($("<h2>").text(this.note.title))
|
||||||
.append($promotedAttributes)
|
.append($promotedAttributes)
|
||||||
.prop('outerHTML'),
|
.prop('outerHTML'),
|
||||||
|
|
||||||
footer: `
|
footer: `
|
||||||
<script src="${assetPath}/libraries/katex/katex.min.js"></script>
|
<script src="${assetPath}/node_modules/katex/dist/katex.min.js"></script>
|
||||||
<script src="${assetPath}/libraries/katex/mhchem.min.js"></script>
|
<script src="${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script>
|
||||||
<script src="${assetPath}/libraries/katex/auto-render.min.js"></script>
|
<script src="${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.body.className += ' ck-content printed-content';
|
document.body.className += ' ck-content printed-content';
|
||||||
|
|
||||||
@@ -273,7 +274,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
|||||||
`${assetPath}/libraries/codemirror/codemirror.css`,
|
`${assetPath}/libraries/codemirror/codemirror.css`,
|
||||||
`${assetPath}/libraries/ckeditor/ckeditor-content.css`,
|
`${assetPath}/libraries/ckeditor/ckeditor-content.css`,
|
||||||
`${assetPath}/libraries/bootstrap/css/bootstrap.min.css`,
|
`${assetPath}/libraries/bootstrap/css/bootstrap.min.css`,
|
||||||
`${assetPath}/libraries/katex/katex.min.css`,
|
`${assetPath}/node_modules/katex/dist/katex.min.css`,
|
||||||
`${assetPath}/stylesheets/print.css`,
|
`${assetPath}/stylesheets/print.css`,
|
||||||
`${assetPath}/stylesheets/relation_map.css`,
|
`${assetPath}/stylesheets/relation_map.css`,
|
||||||
`${assetPath}/stylesheets/ckeditor-theme.css`
|
`${assetPath}/stylesheets/ckeditor-theme.css`
|
||||||
|
|||||||
@@ -8,8 +8,14 @@ import options from "../../services/options.js";
|
|||||||
import utils from "../../services/utils.js";
|
import utils from "../../services/utils.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div>
|
<div class="promoted-attributes-widget">
|
||||||
<style>
|
<style>
|
||||||
|
body.mobile .promoted-attributes-widget {
|
||||||
|
/* https://github.com/zadam/trilium/issues/4468 */
|
||||||
|
flex-shrink: 0.4;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.promoted-attributes-container {
|
.promoted-attributes-container {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ export default class TocWidget extends RightPanelWidget {
|
|||||||
.class("icon-action"),
|
.class("icon-action"),
|
||||||
new OnClickButtonWidget()
|
new OnClickButtonWidget()
|
||||||
.icon("bx-x")
|
.icon("bx-x")
|
||||||
.title("Close Table of Contents")
|
|
||||||
.titlePlacement("left")
|
.titlePlacement("left")
|
||||||
.onClick(widget => widget.triggerCommand("closeToc"))
|
.onClick(widget => widget.triggerCommand("closeToc"))
|
||||||
.class("icon-action")
|
.class("icon-action")
|
||||||
|
|||||||
@@ -889,6 +889,10 @@ function scanForLinks(note, content) {
|
|||||||
* Things which have to be executed after updating content, but asynchronously (separate transaction)
|
* Things which have to be executed after updating content, but asynchronously (separate transaction)
|
||||||
*/
|
*/
|
||||||
async function asyncPostProcessContent(note, content) {
|
async function asyncPostProcessContent(note, content) {
|
||||||
|
if (note.hasStringContent() && !utils.isString(content)) {
|
||||||
|
content = content.toString();
|
||||||
|
}
|
||||||
|
|
||||||
scanForLinks(note, content);
|
scanForLinks(note, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -303,6 +303,10 @@ function toMap(list, key) {
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isString(x) {
|
||||||
|
return Object.prototype.toString.call(x) === "[object String]";
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
randomSecureToken,
|
randomSecureToken,
|
||||||
randomString,
|
randomString,
|
||||||
@@ -335,4 +339,5 @@ module.exports = {
|
|||||||
normalize,
|
normalize,
|
||||||
hashedBlobId,
|
hashedBlobId,
|
||||||
toMap,
|
toMap,
|
||||||
|
isString
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user