mirror of
https://github.com/zadam/trilium.git
synced 2025-10-31 18:36:30 +01:00
Compare commits
29 Commits
v0.55.0-be
...
v0.55.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15ed381f85 | ||
|
|
180051d252 | ||
|
|
a19c58703f | ||
|
|
fc43d9222a | ||
|
|
af6bf08243 | ||
|
|
fb6a0bc2a6 | ||
|
|
1f61c1b3b6 | ||
|
|
fc69f3b8f3 | ||
|
|
d4658b9c2a | ||
|
|
84f72edf1d | ||
|
|
552d872047 | ||
|
|
47235965d5 | ||
|
|
24e4455e91 | ||
|
|
ea35b0c800 | ||
|
|
1a30087426 | ||
|
|
5e9d004ca2 | ||
|
|
513d1c020c | ||
|
|
05231bd1c2 | ||
|
|
b816773d02 | ||
|
|
3c49ea6cb1 | ||
|
|
539eac4be7 | ||
|
|
8a6ead6d86 | ||
|
|
01a7ed8311 | ||
|
|
cf6330dee6 | ||
|
|
6c39b6f548 | ||
|
|
e7ef1b86cc | ||
|
|
3663d56917 | ||
|
|
135064a18f | ||
|
|
7233f58767 |
@@ -13,6 +13,9 @@ then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION_DATE=$(git log -1 --format=%aI "v${VERSION}" | cut -c -10)
|
||||
VERSION_COMMIT=$(git rev-list -n 1 "v${VERSION}")
|
||||
|
||||
# expecting the directory at a specific path
|
||||
cd ~/trilium-flathub
|
||||
|
||||
@@ -21,7 +24,7 @@ if ! git diff-index --quiet HEAD --; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $VERSION == *"beta"* ]]; then
|
||||
if [[ "$VERSION" == *"beta"* ]]; then
|
||||
git switch beta
|
||||
else
|
||||
git switch master
|
||||
@@ -29,9 +32,6 @@ fi
|
||||
|
||||
git pull
|
||||
|
||||
VERSION_DATE=$(git log -1 --format=%aI v${VERSION} | cut -c -10)
|
||||
VERSION_COMMIT=$(git rev-list -n 1 v${VERSION})
|
||||
|
||||
echo "Updating files with version ${VERSION}, date ${VERSION_DATE} and commit ${VERSION_COMMIT}"
|
||||
|
||||
flatpak-node-generator npm ../trilium/package-lock.json
|
||||
|
||||
BIN
db/demo.zip
BIN
db/demo.zip
Binary file not shown.
@@ -13,11 +13,12 @@ appIconService.installLocalAppIcon();
|
||||
|
||||
require('electron-dl')({ saveAs: true });
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform === 'win32') {
|
||||
app.exit(0); // attempt to fix the issue when app.quit() won't terminate processes on windows
|
||||
} else {
|
||||
app.quit();
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.55.0-beta",
|
||||
"version": "0.55.1",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
|
||||
@@ -27,6 +27,8 @@ class Becca {
|
||||
/** @type {Object.<String, EtapiToken>} */
|
||||
this.etapiTokens = {};
|
||||
|
||||
this.dirtyNoteSetCache();
|
||||
|
||||
this.loaded = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -403,7 +403,7 @@ class Note extends AbstractEntity {
|
||||
templateAttributes.push(
|
||||
...templateNote.__getAttributes(newPath)
|
||||
// template attr is used as a marker for templates, but it's not meant to be inherited
|
||||
.filter(attr => !(attr.type === 'label' && attr.name === 'template'))
|
||||
.filter(attr => !(attr.type === 'label' && (attr.name === 'template' || attr.name === 'workspacetemplate')))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +228,36 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
/notes/{noteId}/content:
|
||||
parameters:
|
||||
- name: noteId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/components/schemas/EntityId'
|
||||
get:
|
||||
description: Returns note content idenfied by its ID
|
||||
operationId: getNoteContent
|
||||
responses:
|
||||
'200':
|
||||
description: note content response
|
||||
content:
|
||||
text/html:
|
||||
schema:
|
||||
type: string
|
||||
put:
|
||||
description: Updates note content idenfied by its ID
|
||||
operationId: putNoteContentById
|
||||
requestBody:
|
||||
description: html content of note
|
||||
required: true
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: note content updated
|
||||
/notes/{noteId}/export:
|
||||
parameters:
|
||||
- name: noteId
|
||||
|
||||
@@ -267,7 +267,7 @@ class NoteShort {
|
||||
attrArrs.push(
|
||||
templateNote.__getCachedAttributes(newPath)
|
||||
// template attr is used as a marker for templates, but it's not meant to be inherited
|
||||
.filter(attr => !(attr.type === 'label' && attr.name === 'template'))
|
||||
.filter(attr => !(attr.type === 'label' && (attr.name === 'template' || attr.name === 'workspacetemplate')))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,8 @@ import FloatingButtons from "../widgets/floating_buttons/floating_buttons.js";
|
||||
import RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
|
||||
import MermaidExportButton from "../widgets/floating_buttons/mermaid_export_button.js";
|
||||
import EditableCodeButtonsWidget from "../widgets/type_widgets/editable_code_buttons.js";
|
||||
import ApiLogWidget from "../widgets/api_log.js";
|
||||
import HideFloatingButtonsButton from "../widgets/floating_buttons/hide_floating_buttons_button.js";
|
||||
|
||||
export default class DesktopLayout {
|
||||
constructor(customWidgets) {
|
||||
@@ -185,6 +187,7 @@ export default class DesktopLayout {
|
||||
.child(new RelationMapButtons())
|
||||
.child(new MermaidExportButton())
|
||||
.child(new BacklinksWidget())
|
||||
.child(new HideFloatingButtonsButton())
|
||||
)
|
||||
.child(new MermaidWidget())
|
||||
.child(
|
||||
@@ -197,6 +200,7 @@ export default class DesktopLayout {
|
||||
.child(new SqlResultWidget())
|
||||
)
|
||||
.child(new EditableCodeButtonsWidget())
|
||||
.child(new ApiLogWidget())
|
||||
.child(new FindWidget())
|
||||
.child(
|
||||
...this.customWidgets.get('node-detail-pane'), // typo, let's keep it for a while as BC
|
||||
|
||||
@@ -10,6 +10,7 @@ import ScreenContainer from "../widgets/mobile_widgets/screen_container.js";
|
||||
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
||||
import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session_password.js";
|
||||
import ConfirmDialog from "../widgets/dialogs/confirm.js";
|
||||
import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js";
|
||||
|
||||
const MOBILE_CSS = `
|
||||
<style>
|
||||
@@ -128,7 +129,7 @@ export default class MobileLayout {
|
||||
.child(
|
||||
new NoteDetailWidget()
|
||||
.css('padding', '5px 20px 10px 0')
|
||||
)
|
||||
).child(new FilePropertiesWidget().css('font-size','smaller'))
|
||||
)
|
||||
)
|
||||
.child(new ProtectedSessionPasswordDialog())
|
||||
|
||||
@@ -68,7 +68,7 @@ async function moveToParentNote(branchIdsToMove, newParentBranchId) {
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteNotes(branchIdsToDelete) {
|
||||
async function deleteNotes(branchIdsToDelete, forceDeleteAllClones = false) {
|
||||
branchIdsToDelete = filterRootNote(branchIdsToDelete);
|
||||
|
||||
if (branchIdsToDelete.length === 0) {
|
||||
@@ -83,7 +83,7 @@ async function deleteNotes(branchIdsToDelete) {
|
||||
}
|
||||
else {
|
||||
({proceed, deleteAllClones, eraseNotes} = await new Promise(res =>
|
||||
appContext.triggerCommand('showDeleteNotesDialog', {branchIdsToDelete, callback: res})));
|
||||
appContext.triggerCommand('showDeleteNotesDialog', {branchIdsToDelete, callback: res, forceDeleteAllClones})));
|
||||
}
|
||||
|
||||
if (!proceed) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import appContext from "./app_context.js";
|
||||
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
|
||||
import NoteContextCachingWidget from "../widgets/note_context_caching_widget.js";
|
||||
import BasicWidget from "../widgets/basic_widget.js";
|
||||
import SpacedUpdate from "./spaced_update.js";
|
||||
|
||||
/**
|
||||
* This is the main frontend API interface for scripts. It's published in the local "api" object.
|
||||
@@ -594,6 +595,33 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain
|
||||
* @returns {string} random string
|
||||
*/
|
||||
this.randomString = utils.randomString;
|
||||
|
||||
this.logMessages = {};
|
||||
this.logSpacedUpdates = {};
|
||||
|
||||
/**
|
||||
* Log given message to the log pane in UI
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
this.log = message => {
|
||||
const {noteId} = this.startNote;
|
||||
|
||||
message = utils.now() + ": " + message;
|
||||
|
||||
console.log(`Script ${noteId}: ${message}`);
|
||||
|
||||
this.logMessages[noteId] = this.logMessages[noteId] || [];
|
||||
this.logSpacedUpdates[noteId] = this.logSpacedUpdates[noteId] || new SpacedUpdate(() => {
|
||||
const messages = this.logMessages[noteId];
|
||||
this.logMessages[noteId] = [];
|
||||
|
||||
appContext.triggerEvent("apiLogMessages", {noteId, messages});
|
||||
}, 100);
|
||||
|
||||
this.logMessages[noteId].push(message);
|
||||
this.logSpacedUpdates[noteId].scheduleUpdate();
|
||||
};
|
||||
}
|
||||
|
||||
export default FrontendScriptApi;
|
||||
|
||||
@@ -3,6 +3,7 @@ import toastService from "./toast.js";
|
||||
import server from "./server.js";
|
||||
import options from "./options.js";
|
||||
import frocaUpdater from "./froca_updater.js";
|
||||
import appContext from "./app_context.js";
|
||||
|
||||
const messageHandlers = [];
|
||||
|
||||
@@ -118,6 +119,9 @@ async function handleMessage(event) {
|
||||
else if (message.type === 'consistency-checks-failed') {
|
||||
toastService.showError("Consistency checks failed! See logs for details.", 50 * 60000);
|
||||
}
|
||||
else if (message.type === 'api-log-messages') {
|
||||
appContext.triggerEvent("apiLogMessages", {noteId: message.noteId, messages: message.messages});
|
||||
}
|
||||
}
|
||||
|
||||
let entityChangeIdReachedListeners = [];
|
||||
|
||||
75
src/public/app/widgets/api_log.js
Normal file
75
src/public/app/widgets/api_log.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="api-log-widget">
|
||||
<style>
|
||||
.api-log-widget {
|
||||
padding: 15px;
|
||||
flex-grow: 1;
|
||||
max-height: 40%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hidden-api-log {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.api-log-container {
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.close-api-log-button {
|
||||
padding: 5px;
|
||||
border: 1px solid var(--button-border-color);
|
||||
background-color: var(--button-background-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
color: var(--button-text-color);
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="bx bx-x close-api-log-button" title="Close"></div>
|
||||
|
||||
<div class="api-log-container"></div>
|
||||
</div>`;
|
||||
|
||||
export default class ApiLogWidget extends NoteContextAwareWidget {
|
||||
isEnabled() {
|
||||
return this.note
|
||||
&& this.note.mime.startsWith('application/javascript;env=')
|
||||
&& super.isEnabled();
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.toggle(false);
|
||||
|
||||
this.$logContainer = this.$widget.find('.api-log-container');
|
||||
this.$closeButton = this.$widget.find(".close-api-log-button");
|
||||
this.$closeButton.on("click", () => this.toggle(false));
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.$logContainer.empty();
|
||||
}
|
||||
|
||||
apiLogMessagesEvent({messages, noteId}) {
|
||||
if (!this.isNote(noteId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggle(true);
|
||||
|
||||
for (const message of messages) {
|
||||
this.$logContainer.append(message).append($("<br>"));
|
||||
}
|
||||
}
|
||||
|
||||
toggle(show) {
|
||||
this.$widget.toggleClass("hidden-api-log", !show);
|
||||
}
|
||||
}
|
||||
@@ -207,6 +207,7 @@ const ATTR_HELP = {
|
||||
"workspaceIconClass": "defines box icon CSS class which will be used in tab when hoisted to this note",
|
||||
"workspaceTabBackgroundColor": "CSS color used in the note tab when hoisted to this note",
|
||||
"workspaceCalendarRoot": "Defines per-workspace calendar root",
|
||||
"workspaceTemplate": "This note will appear in the selection of available template when creating new note, but only when hoisted into a workspace containing this template",
|
||||
"searchHome": "new search notes will be created as children of this note",
|
||||
"hoistedSearchHome": "new search notes will be created as children of this note when hoisted to some ancestor of this note",
|
||||
"inbox": "default inbox location for new notes",
|
||||
|
||||
@@ -57,8 +57,10 @@ const TPL = `
|
||||
}
|
||||
|
||||
.add-new-attribute-button:hover, .save-attributes-button:hover {
|
||||
border: 1px solid var(--main-border-color);
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
background: var(--button-background-color);
|
||||
color: var(--button-text-color);
|
||||
}
|
||||
|
||||
.attribute-errors {
|
||||
|
||||
@@ -8,6 +8,28 @@ class BasicWidget extends Component {
|
||||
style: ''
|
||||
};
|
||||
this.classes = [];
|
||||
|
||||
this.children = [];
|
||||
this.childPositionCounter = 10;
|
||||
}
|
||||
|
||||
child(...components) {
|
||||
if (!components) {
|
||||
return this;
|
||||
}
|
||||
|
||||
super.child(...components);
|
||||
|
||||
for (const component of components) {
|
||||
if (component.position === undefined) {
|
||||
component.position = this.childPositionCounter;
|
||||
this.childPositionCounter += 10;
|
||||
}
|
||||
}
|
||||
|
||||
this.children.sort((a, b) => a.position - b.position < 0 ? -1 : 1);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
id(id) {
|
||||
|
||||
@@ -35,13 +35,31 @@ const TPL = `
|
||||
bottom: -30px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.update-to-latest-version-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.global-menu .zoom-buttons a {
|
||||
display: inline-block;
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
color: var(--button-text-color);
|
||||
background-color: var(--button-background-color);
|
||||
padding: 3px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.global-menu .zoom-buttons a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.global-menu .zoom-state {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" data-toggle="dropdown" data-placement="right"
|
||||
@@ -91,11 +109,30 @@ const TPL = `
|
||||
Reload frontend
|
||||
<kbd data-command="reloadFrontendApp"></kbd>
|
||||
</a>
|
||||
|
||||
<span class="zoom-container dropdown-item" style="display: flex; flex-direction: row; justify-content: space-between;">
|
||||
<div>
|
||||
<span class="bx bx-empty"></span>
|
||||
Zoom
|
||||
</div>
|
||||
|
||||
<div class="zoom-buttons">
|
||||
<a data-trigger-command="toggleFullscreen" title="Toggle fullscreen" class="bx bx-expand-alt"></a>
|
||||
|
||||
|
||||
|
||||
<a data-trigger-command="zoomOut" title="Zoom out" class="bx bx-minus"></a>
|
||||
|
||||
<span class="zoom-state"></span>
|
||||
|
||||
<a data-trigger-command="zoomIn" title="Zoom in" class="bx bx-plus"></a>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<a class="dropdown-item" data-trigger-command="toggleFullscreen">
|
||||
<span class="bx bx-empty"></span>
|
||||
Toggle fullscreen
|
||||
<kbd data-command="toggleFullscreen"></kbd>
|
||||
<kbd ></kbd>
|
||||
</a>
|
||||
|
||||
<a class="dropdown-item" data-trigger-command="showHelp">
|
||||
@@ -145,9 +182,13 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
this.$widget.find(".open-dev-tools-button").toggle(isElectron);
|
||||
this.$widget.find(".switch-to-mobile-version-button").toggle(!isElectron);
|
||||
|
||||
this.$widget.on('click', '.dropdown-item', e => {
|
||||
if ($(e.target).parent(".zoom-buttons")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$widget.on('click', '.dropdown-item',
|
||||
() => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
|
||||
this.$widget.find("[data-toggle='dropdown']").dropdown('toggle');
|
||||
});
|
||||
|
||||
this.$widget.find(".global-menu-button-update-available").append(
|
||||
this.updateAvailableWidget.render()
|
||||
@@ -155,11 +196,34 @@ export default class GlobalMenuWidget extends BasicWidget {
|
||||
|
||||
this.$updateToLatestVersionButton = this.$widget.find(".update-to-latest-version-button");
|
||||
|
||||
if (!utils.isElectron()) {
|
||||
this.$widget.find(".zoom-container").hide();
|
||||
}
|
||||
|
||||
this.$zoomState = this.$widget.find(".zoom-state");
|
||||
this.$widget.on('show.bs.dropdown', () => this.updateZoomState());
|
||||
|
||||
this.$widget.find(".zoom-buttons").on("click",
|
||||
// delay to wait for the actual zoom change
|
||||
() => setTimeout(() => this.updateZoomState(), 300)
|
||||
);
|
||||
|
||||
this.updateVersionStatus();
|
||||
|
||||
setInterval(() => this.updateVersionStatus(), 8 * 60 * 60 * 1000);
|
||||
}
|
||||
|
||||
updateZoomState() {
|
||||
if (!utils.isElectron()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const zoomFactor = utils.dynamicRequire('electron').webFrame.getZoomFactor();
|
||||
const zoomPercent = Math.round(zoomFactor * 100);
|
||||
|
||||
this.$zoomState.text(zoomPercent + "%");
|
||||
}
|
||||
|
||||
async updateVersionStatus() {
|
||||
if (options.get("checkForUpdates") !== 'true') {
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import branchService from "../../services/branches.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="dropdown note-actions">
|
||||
@@ -30,6 +31,7 @@ const TPL = `
|
||||
<a data-trigger-command="openNoteExternally" class="dropdown-item open-note-externally-button"><kbd data-command="openNoteExternally"></kbd> Open note externally</a>
|
||||
<a class="dropdown-item import-files-button">Import files</a>
|
||||
<a class="dropdown-item export-note-button">Export note</a>
|
||||
<a class="dropdown-item delete-note-button">Delete note</a>
|
||||
<a data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
|
||||
</div>
|
||||
</div>`;
|
||||
@@ -65,6 +67,15 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
|
||||
this.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
|
||||
|
||||
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
|
||||
|
||||
this.$deleteNoteButton = this.$widget.find(".delete-note-button");
|
||||
this.$deleteNoteButton.on("click", () => {
|
||||
if (this.note.noteId === 'root') {
|
||||
return;
|
||||
}
|
||||
|
||||
branchService.deleteNotes([this.note.getParentBranches()[0].branchId], true);
|
||||
});
|
||||
}
|
||||
|
||||
refreshWithNote(note) {
|
||||
|
||||
@@ -8,7 +8,7 @@ const TPL = `
|
||||
height: 21px !important;
|
||||
padding: 0 !important;
|
||||
|
||||
border-radius: 8px;
|
||||
border-radius: var(--button-border-radius);
|
||||
transform: scale(0.9);
|
||||
border: none;
|
||||
opacity: 0.8;
|
||||
|
||||
@@ -1,33 +1,6 @@
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
|
||||
export default class Container extends BasicWidget {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.children = [];
|
||||
|
||||
this.positionCounter = 10;
|
||||
}
|
||||
|
||||
child(...components) {
|
||||
if (!components) {
|
||||
return this;
|
||||
}
|
||||
|
||||
super.child(...components);
|
||||
|
||||
for (const component of components) {
|
||||
if (component.position === undefined) {
|
||||
component.position = this.positionCounter;
|
||||
this.positionCounter += 10;
|
||||
}
|
||||
}
|
||||
|
||||
this.children.sort((a, b) => a.position - b.position < 0 ? -1 : 1);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(`<div>`);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ export default class ScrollingContainer extends Container {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.class("scrolling-container");
|
||||
this.css('overflow', 'auto');
|
||||
this.css('position', 'relative');
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import BasicWidget from "../basic_widget.js";
|
||||
const DELETE_NOTE_BUTTON_CLASS = "confirm-dialog-delete-note";
|
||||
|
||||
const TPL = `
|
||||
<div class="confirm-dialog modal mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="confirm-dialog modal mx-auto" tabindex="-1" role="dialog" style="z-index: 2000;">
|
||||
<div class="modal-dialog modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
||||
@@ -97,7 +97,7 @@ export default class DeleteNotesDialog extends BasicWidget {
|
||||
|
||||
this.resolve({
|
||||
proceed: true,
|
||||
deleteAllClones: this.isDeleteAllClonesChecked(),
|
||||
deleteAllClones: this.forceDeleteAllClones || this.isDeleteAllClonesChecked(),
|
||||
eraseNotes: this.isEraseNotesChecked()
|
||||
});
|
||||
});
|
||||
@@ -108,7 +108,7 @@ export default class DeleteNotesDialog extends BasicWidget {
|
||||
async renderDeletePreview() {
|
||||
const response = await server.post('delete-notes-preview', {
|
||||
branchIdsToDelete: this.branchIds,
|
||||
deleteAllClones: this.isDeleteAllClonesChecked()
|
||||
deleteAllClones: this.forceDeleteAllClones || this.isDeleteAllClonesChecked()
|
||||
});
|
||||
|
||||
this.$deleteNotesList.empty();
|
||||
@@ -143,14 +143,18 @@ export default class DeleteNotesDialog extends BasicWidget {
|
||||
}
|
||||
}
|
||||
|
||||
async showDeleteNotesDialogEvent({branchIdsToDelete, callback}) {
|
||||
async showDeleteNotesDialogEvent({branchIdsToDelete, callback, forceDeleteAllClones}) {
|
||||
this.branchIds = branchIdsToDelete;
|
||||
this.forceDeleteAllClones = forceDeleteAllClones;
|
||||
|
||||
await this.renderDeletePreview();
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
|
||||
this.$deleteAllClones.prop("checked", false);
|
||||
this.$deleteAllClones
|
||||
.prop("checked", !!forceDeleteAllClones)
|
||||
.prop("disabled", !!forceDeleteAllClones);
|
||||
|
||||
this.$eraseNotes.prop("checked", false);
|
||||
|
||||
this.resolve = callback;
|
||||
|
||||
@@ -2,7 +2,7 @@ import utils from "../../services/utils.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="info-dialog modal mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="info-dialog modal mx-auto" tabindex="-1" role="dialog" style="z-index: 2000;">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
||||
@@ -37,11 +37,11 @@ const TPL = `
|
||||
margin-right: 20px;
|
||||
font-size: large;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 5px;
|
||||
border-radius: var(--button-border-radius);
|
||||
}
|
||||
|
||||
.token-table-button:hover {
|
||||
border: 1px solid var(--main-border-color);
|
||||
border: 1px solid var(--button-border-color);
|
||||
}
|
||||
</style>`;
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ import utils from "../../services/utils.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="prompt-dialog modal mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="prompt-dialog modal mx-auto" tabindex="-1" role="dialog" style="z-index: 2000;">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<form id="prompt-dialog-form">
|
||||
<form class="prompt-dialog-form">
|
||||
<div class="modal-header">
|
||||
<h5 class="prompt-title modal-title mr-auto">Prompt</h5>
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class PromptDialog extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$dialogBody = this.$widget.find(".modal-body");
|
||||
this.$form = this.$widget.find("#prompt-dialog-form");
|
||||
this.$form = this.$widget.find(".prompt-dialog-form");
|
||||
this.$question = null;
|
||||
this.$answer = null;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Container from "../containers/container.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="floating-buttons">
|
||||
@@ -16,7 +16,7 @@ const TPL = `
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.floating-buttons-children > * {
|
||||
.floating-buttons-children > *:not(.hidden-int):not(.no-content-hidden) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@@ -24,12 +24,16 @@ const TPL = `
|
||||
font-size: 130%;
|
||||
padding: 5px 10px 4px 10px;
|
||||
}
|
||||
|
||||
.floating-buttons.temporarily-hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="floating-buttons-children"></div>
|
||||
</div>`;
|
||||
|
||||
export default class FloatingButtons extends Container {
|
||||
export default class FloatingButtons extends NoteContextAwareWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$children = this.$widget.find(".floating-buttons-children");
|
||||
@@ -38,4 +42,16 @@ export default class FloatingButtons extends Container {
|
||||
this.$children.append(widget.render());
|
||||
}
|
||||
}
|
||||
|
||||
async refreshWithNote(note) {
|
||||
this.toggle(true);
|
||||
}
|
||||
|
||||
toggle(show) {
|
||||
this.$widget.toggleClass("temporarily-hidden", !show);
|
||||
}
|
||||
|
||||
hideFloatingButtonsCommand() {
|
||||
this.toggle(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="close-floating-buttons">
|
||||
<style>
|
||||
.close-floating-buttons {
|
||||
display: none;
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
|
||||
/* conditionally display close button if there's some other button visible */
|
||||
.floating-buttons *:not(.hidden-int):not(.hidden-no-content) ~ .close-floating-buttons {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.close-floating-buttons-button {
|
||||
border: 1px solid transparent;
|
||||
color: var(--button-text-color);
|
||||
padding: 6px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.close-floating-buttons-button:hover {
|
||||
border: 1px solid var(--button-border-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button"
|
||||
class="close-floating-buttons-button btn bx bx-x no-print"
|
||||
title="Hide buttons"></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default class HideFloatingButtonsButton extends NoteContextAwareWidget {
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.on('click', () => this.triggerCommand('hideFloatingButtons'));
|
||||
this.contentSized();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import dialogService from "../dialog.js";
|
||||
import server from "../../services/server.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
|
||||
const TPL = `
|
||||
<button type="button"
|
||||
|
||||
@@ -89,17 +89,21 @@ export default class BacklinksWidget extends NoteContextAwareWidget {
|
||||
const resp = await server.get(`notes/${this.noteId}/backlink-count`);
|
||||
|
||||
if (!resp || !resp.count) {
|
||||
this.$ticker.hide();
|
||||
this.toggle(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$ticker.show();
|
||||
this.$ticker.toggle(true);
|
||||
this.$count.text(
|
||||
`${resp.count} backlink`
|
||||
+ (resp.count === 1 ? '' : 's')
|
||||
);
|
||||
}
|
||||
|
||||
toggle(show) {
|
||||
this.$widget.toggleClass("hidden-no-content", !show);
|
||||
}
|
||||
|
||||
clearItems() {
|
||||
this.$items.empty().hide();
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import appContext from "../services/app_context.js";
|
||||
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";
|
||||
@@ -393,6 +394,18 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
title: node.title
|
||||
}));
|
||||
|
||||
if (notes.length === 1) {
|
||||
linkService.createNoteLink(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 => {
|
||||
const $list = $("<ul>").append(...links.map($link => $("<li>").append($link)));
|
||||
|
||||
data.dataTransfer.setData("text/html", $list[0].outerHTML);
|
||||
});
|
||||
}
|
||||
|
||||
data.dataTransfer.setData("text", JSON.stringify(notes));
|
||||
return true; // allow dragging to start
|
||||
},
|
||||
@@ -933,7 +946,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
|
||||
if (this.noteContext
|
||||
&& this.noteContext.notePath
|
||||
&& !this.noteContext.note.isDeleted
|
||||
&& !this.noteContext.note?.isDeleted
|
||||
&& !this.noteContext.notePath.includes("root/hidden")
|
||||
) {
|
||||
const newActiveNode = await this.getNodeFromPath(this.noteContext.notePath);
|
||||
|
||||
@@ -16,7 +16,10 @@ const TPL = `
|
||||
padding: 0.25rem 0.4rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 0.5;
|
||||
border-radius: 0.2rem;
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
background: var(--button-background-color);
|
||||
color: var(--button-text-color);
|
||||
}
|
||||
|
||||
.sql-console-result-container {
|
||||
|
||||
@@ -93,7 +93,7 @@ const TAB_ROW_TPL = `
|
||||
|
||||
.note-new-tab:hover {
|
||||
background-color: var(--accented-background-color);
|
||||
border-radius: 5px;
|
||||
border-radius: var(--button-border-radius);
|
||||
}
|
||||
|
||||
.tab-row-filler {
|
||||
|
||||
@@ -87,4 +87,10 @@ export default class EditableCodeButtonsWidget extends NoteContextAwareWidget {
|
||||
|
||||
this.$openTriliumApiDocsButton.toggle(note.mime.startsWith('application/javascript;env='));
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent({noteId}) {
|
||||
if (this.isNote(noteId)) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,11 +53,12 @@ const TPL = `
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 5px;
|
||||
border-radius: var(--button-border-radius);
|
||||
color: var(--button-text-color);
|
||||
}
|
||||
|
||||
.edit-text-note-button:hover {
|
||||
border-color: var(--main-border-color);
|
||||
border-color: var(--button-border-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -109,7 +109,6 @@ button.close:hover {
|
||||
|
||||
.icon-action {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 3px;
|
||||
padding: 5px;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
@@ -117,6 +116,7 @@ button.close:hover {
|
||||
font-size: 1.5em;
|
||||
color: var(--button-text-color);
|
||||
background: var(--button-background-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
}
|
||||
|
||||
.icon-action:hover:not(.disabled) {
|
||||
@@ -747,7 +747,7 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
|
||||
}
|
||||
|
||||
.dropdown-submenu {
|
||||
position:relative;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdown-submenu > .dropdown-menu {
|
||||
@@ -755,6 +755,9 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
|
||||
left: 100%;
|
||||
margin-top: -6px;
|
||||
min-width: 15rem;
|
||||
/* to make submenu scrollable https://github.com/zadam/trilium/issues/3136 */
|
||||
max-height: 600px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* rotate caret on hover */
|
||||
@@ -954,3 +957,7 @@ button.close:hover {
|
||||
text-shadow: none;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.hidden-no-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ const log = require('../../services/log');
|
||||
const scriptService = require('../../services/script');
|
||||
const searchService = require('../../services/search/services/search');
|
||||
const bulkActionService = require("../../services/bulk_actions");
|
||||
const cls = require("../../services/cls");
|
||||
const {formatAttrForSearch} = require("../../services/attribute_formatter");
|
||||
|
||||
function searchFromNoteInt(note) {
|
||||
@@ -189,7 +190,9 @@ function getRelatedNotes(req) {
|
||||
}
|
||||
|
||||
function searchTemplates() {
|
||||
const query = formatAttrForSearch({type: 'label', name: "template"}, false);
|
||||
const query = cls.getHoistedNoteId() === 'root'
|
||||
? '#template'
|
||||
: '#template OR #workspaceTemplate';
|
||||
|
||||
return searchService.searchNotes(query, {
|
||||
includeArchivedNotes: true,
|
||||
|
||||
@@ -14,6 +14,8 @@ const appInfo = require('./app_info');
|
||||
const searchService = require('./search/services/search');
|
||||
const SearchContext = require("./search/search_context");
|
||||
const becca = require("../becca/becca");
|
||||
const ws = require("./ws");
|
||||
const SpacedUpdate = require("./spaced_update");
|
||||
|
||||
/**
|
||||
* This is the main backend API interface for scripts. It's published in the local "api" object.
|
||||
@@ -288,12 +290,34 @@ function BackendScriptApi(currentNote, apiParams) {
|
||||
});
|
||||
};
|
||||
|
||||
this.logMessages = {};
|
||||
this.logSpacedUpdates = {};
|
||||
|
||||
/**
|
||||
* Log given message to trilium logs.
|
||||
* Log given message to trilium logs and log pane in UI
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
this.log = message => log.info(message);
|
||||
this.log = message => {
|
||||
log.info(message);
|
||||
|
||||
const {noteId} = this.startNote;
|
||||
|
||||
this.logMessages[noteId] = this.logMessages[noteId] || [];
|
||||
this.logSpacedUpdates[noteId] = this.logSpacedUpdates[noteId] || new SpacedUpdate(() => {
|
||||
const messages = this.logMessages[noteId];
|
||||
this.logMessages[noteId] = [];
|
||||
|
||||
ws.sendMessageToAllClients({
|
||||
type: 'api-log-messages',
|
||||
noteId,
|
||||
messages
|
||||
});
|
||||
}, 100);
|
||||
|
||||
this.logMessages[noteId].push(message);
|
||||
this.logSpacedUpdates[noteId].scheduleUpdate();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns root note of the calendar.
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2022-09-12T22:37:41+02:00", buildRevision: "ca03c412057da043f240689e37bae049d8311891" };
|
||||
module.exports = { buildDate:"2022-09-21T22:43:34+02:00", buildRevision: "180051d252d254a2543f8192a28e87e134594a38" };
|
||||
|
||||
@@ -30,6 +30,7 @@ module.exports = [
|
||||
{ type: 'label', name: 'workspaceIconClass' },
|
||||
{ type: 'label', name: 'workspaceTabBackgroundColor' },
|
||||
{ type: 'label', name: 'workspaceCalendarRoot' },
|
||||
{ type: 'label', name: 'workspaceTemplate' },
|
||||
{ type: 'label', name: 'searchHome' },
|
||||
{ type: 'label', name: 'hoistedInbox' },
|
||||
{ type: 'label', name: 'hoistedSearchHome' },
|
||||
|
||||
@@ -292,7 +292,12 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
content = content.replace(/<\/body>.*<\/html>/gis, "");
|
||||
|
||||
content = content.replace(/src="([^"]*)"/g, (match, url) => {
|
||||
url = decodeURIComponent(url);
|
||||
try {
|
||||
url = decodeURIComponent(url);
|
||||
} catch (e) {
|
||||
log.error(`Cannot parse image URL '${url}', keeping original (${e}).`);
|
||||
return `src="${url}"`;
|
||||
}
|
||||
|
||||
if (isUrlAbsolute(url) || url.startsWith("/")) {
|
||||
return match;
|
||||
@@ -304,7 +309,12 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
||||
});
|
||||
|
||||
content = content.replace(/href="([^"]*)"/g, (match, url) => {
|
||||
url = decodeURIComponent(url);
|
||||
try {
|
||||
url = decodeURIComponent(url);
|
||||
} catch (e) {
|
||||
log.error(`Cannot parse link URL '${url}', keeping original (${e}).`);
|
||||
return `href="${url}"`;
|
||||
}
|
||||
|
||||
if (isUrlAbsolute(url)) {
|
||||
return match;
|
||||
|
||||
@@ -735,6 +735,10 @@ function eraseDeletedEntities(eraseEntitiesAfterTimeInSeconds = null) {
|
||||
const attributeIdsToErase = sql.getColumn("SELECT attributeId FROM attributes WHERE isDeleted = 1 AND utcDateModified <= ?", [dateUtils.utcDateTimeStr(cutoffDate)]);
|
||||
|
||||
eraseAttributes(attributeIdsToErase);
|
||||
|
||||
if (noteIdsToErase.length > 0 || branchIdsToErase.length > 0 || attributeIdsToErase.length > 0) {
|
||||
require('../becca/becca_loader').reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -750,6 +754,10 @@ function eraseNotesWithDeleteId(deleteId) {
|
||||
const attributeIdsToErase = sql.getColumn("SELECT attributeId FROM attributes WHERE deleteId = ?", [deleteId]);
|
||||
|
||||
eraseAttributes(attributeIdsToErase);
|
||||
|
||||
if (noteIdsToErase.length > 0 || branchIdsToErase.length > 0 || attributeIdsToErase.length > 0) {
|
||||
require('../becca/becca_loader').reload();
|
||||
}
|
||||
}
|
||||
|
||||
function eraseDeletedNotesNow() {
|
||||
|
||||
@@ -28,7 +28,7 @@ class AttributeExistsExp extends Expression {
|
||||
}
|
||||
else if (note.isTemplate() &&
|
||||
// template attr is used as a marker for templates, but it's not meant to be inherited
|
||||
!(this.attributeType === 'label' && this.attributeName === 'template')) {
|
||||
!(this.attributeType === 'label' && (this.attributeName === 'template' || this.attributeName === 'workspacetemplate'))) {
|
||||
resultNoteSet.addAll(note.getTemplatedNotes());
|
||||
}
|
||||
else {
|
||||
|
||||
67
src/services/spaced_update.js
Normal file
67
src/services/spaced_update.js
Normal file
@@ -0,0 +1,67 @@
|
||||
class SpacedUpdate {
|
||||
constructor(updater, updateInterval = 1000) {
|
||||
this.updater = updater;
|
||||
this.lastUpdated = Date.now();
|
||||
this.changed = false;
|
||||
this.updateInterval = updateInterval;
|
||||
}
|
||||
|
||||
scheduleUpdate() {
|
||||
if (!this.changeForbidden) {
|
||||
this.changed = true;
|
||||
setTimeout(() => this.triggerUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
async updateNowIfNecessary() {
|
||||
if (this.changed) {
|
||||
this.changed = false; // optimistic...
|
||||
|
||||
try {
|
||||
await this.updater();
|
||||
}
|
||||
catch (e) {
|
||||
this.changed = true;
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isAllSavedAndTriggerUpdate() {
|
||||
const allSaved = !this.changed;
|
||||
|
||||
this.updateNowIfNecessary();
|
||||
|
||||
return allSaved;
|
||||
}
|
||||
|
||||
triggerUpdate() {
|
||||
if (!this.changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Date.now() - this.lastUpdated > this.updateInterval) {
|
||||
this.updater();
|
||||
this.lastUpdated = Date.now();
|
||||
this.changed = false;
|
||||
}
|
||||
else {
|
||||
// update not triggered but changes are still pending so we need to schedule another check
|
||||
this.scheduleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
async allowUpdateWithoutChange(callback) {
|
||||
this.changeForbidden = true;
|
||||
|
||||
try {
|
||||
await callback();
|
||||
}
|
||||
finally {
|
||||
this.changeForbidden = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SpacedUpdate;
|
||||
@@ -67,7 +67,7 @@ function sendMessageToAllClients(message) {
|
||||
const jsonStr = JSON.stringify(message);
|
||||
|
||||
if (webSocketServer) {
|
||||
if (message.type !== 'sync-failed') {
|
||||
if (message.type !== 'sync-failed' && message.type !== 'api-log-messages') {
|
||||
log.info("Sending message to all clients: " + jsonStr);
|
||||
}
|
||||
|
||||
|
||||
25
test-etapi/get-note-content.http
Normal file
25
test-etapi/get-note-content.http
Normal file
@@ -0,0 +1,25 @@
|
||||
POST {{triliumHost}}/etapi/create-note
|
||||
Authorization: {{authToken}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"parentNoteId": "root",
|
||||
"title": "Hello",
|
||||
"type": "text",
|
||||
"content": "Hi there!"
|
||||
}
|
||||
|
||||
> {%
|
||||
client.global.set("createdNoteId", response.body.note.noteId);
|
||||
client.global.set("createdBranchId", response.body.branch.branchId);
|
||||
%}
|
||||
|
||||
###
|
||||
|
||||
GET {{triliumHost}}/etapi/notes/{{createdNoteId}}/content
|
||||
Authorization: {{authToken}}
|
||||
|
||||
> {%
|
||||
client.assert(response.status === 200);
|
||||
client.assert(response.body === "<p>Hi there!</p>");
|
||||
%}
|
||||
Reference in New Issue
Block a user