mirror of
https://github.com/zadam/trilium.git
synced 2025-11-01 10:55:55 +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
|
exit 1
|
||||||
fi
|
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
|
# expecting the directory at a specific path
|
||||||
cd ~/trilium-flathub
|
cd ~/trilium-flathub
|
||||||
|
|
||||||
@@ -21,7 +24,7 @@ if ! git diff-index --quiet HEAD --; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $VERSION == *"beta"* ]]; then
|
if [[ "$VERSION" == *"beta"* ]]; then
|
||||||
git switch beta
|
git switch beta
|
||||||
else
|
else
|
||||||
git switch master
|
git switch master
|
||||||
@@ -29,9 +32,6 @@ fi
|
|||||||
|
|
||||||
git pull
|
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}"
|
echo "Updating files with version ${VERSION}, date ${VERSION_DATE} and commit ${VERSION_COMMIT}"
|
||||||
|
|
||||||
flatpak-node-generator npm ../trilium/package-lock.json
|
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 });
|
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', () => {
|
app.on('window-all-closed', () => {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform !== 'darwin') {
|
||||||
app.exit(0); // attempt to fix the issue when app.quit() won't terminate processes on windows
|
app.quit()
|
||||||
} else {
|
|
||||||
app.quit();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"productName": "Trilium Notes",
|
"productName": "Trilium Notes",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.55.0-beta",
|
"version": "0.55.1",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ class Becca {
|
|||||||
/** @type {Object.<String, EtapiToken>} */
|
/** @type {Object.<String, EtapiToken>} */
|
||||||
this.etapiTokens = {};
|
this.etapiTokens = {};
|
||||||
|
|
||||||
|
this.dirtyNoteSetCache();
|
||||||
|
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ class Note extends AbstractEntity {
|
|||||||
templateAttributes.push(
|
templateAttributes.push(
|
||||||
...templateNote.__getAttributes(newPath)
|
...templateNote.__getAttributes(newPath)
|
||||||
// template attr is used as a marker for templates, but it's not meant to be inherited
|
// 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:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$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:
|
/notes/{noteId}/export:
|
||||||
parameters:
|
parameters:
|
||||||
- name: noteId
|
- name: noteId
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ class NoteShort {
|
|||||||
attrArrs.push(
|
attrArrs.push(
|
||||||
templateNote.__getCachedAttributes(newPath)
|
templateNote.__getCachedAttributes(newPath)
|
||||||
// template attr is used as a marker for templates, but it's not meant to be inherited
|
// 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 RelationMapButtons from "../widgets/floating_buttons/relation_map_buttons.js";
|
||||||
import MermaidExportButton from "../widgets/floating_buttons/mermaid_export_button.js";
|
import MermaidExportButton from "../widgets/floating_buttons/mermaid_export_button.js";
|
||||||
import EditableCodeButtonsWidget from "../widgets/type_widgets/editable_code_buttons.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 {
|
export default class DesktopLayout {
|
||||||
constructor(customWidgets) {
|
constructor(customWidgets) {
|
||||||
@@ -185,6 +187,7 @@ export default class DesktopLayout {
|
|||||||
.child(new RelationMapButtons())
|
.child(new RelationMapButtons())
|
||||||
.child(new MermaidExportButton())
|
.child(new MermaidExportButton())
|
||||||
.child(new BacklinksWidget())
|
.child(new BacklinksWidget())
|
||||||
|
.child(new HideFloatingButtonsButton())
|
||||||
)
|
)
|
||||||
.child(new MermaidWidget())
|
.child(new MermaidWidget())
|
||||||
.child(
|
.child(
|
||||||
@@ -197,6 +200,7 @@ export default class DesktopLayout {
|
|||||||
.child(new SqlResultWidget())
|
.child(new SqlResultWidget())
|
||||||
)
|
)
|
||||||
.child(new EditableCodeButtonsWidget())
|
.child(new EditableCodeButtonsWidget())
|
||||||
|
.child(new ApiLogWidget())
|
||||||
.child(new FindWidget())
|
.child(new FindWidget())
|
||||||
.child(
|
.child(
|
||||||
...this.customWidgets.get('node-detail-pane'), // typo, let's keep it for a while as BC
|
...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 ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
||||||
import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session_password.js";
|
import ProtectedSessionPasswordDialog from "../widgets/dialogs/protected_session_password.js";
|
||||||
import ConfirmDialog from "../widgets/dialogs/confirm.js";
|
import ConfirmDialog from "../widgets/dialogs/confirm.js";
|
||||||
|
import FilePropertiesWidget from "../widgets/ribbon_widgets/file_properties.js";
|
||||||
|
|
||||||
const MOBILE_CSS = `
|
const MOBILE_CSS = `
|
||||||
<style>
|
<style>
|
||||||
@@ -128,7 +129,7 @@ export default class MobileLayout {
|
|||||||
.child(
|
.child(
|
||||||
new NoteDetailWidget()
|
new NoteDetailWidget()
|
||||||
.css('padding', '5px 20px 10px 0')
|
.css('padding', '5px 20px 10px 0')
|
||||||
)
|
).child(new FilePropertiesWidget().css('font-size','smaller'))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.child(new ProtectedSessionPasswordDialog())
|
.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);
|
branchIdsToDelete = filterRootNote(branchIdsToDelete);
|
||||||
|
|
||||||
if (branchIdsToDelete.length === 0) {
|
if (branchIdsToDelete.length === 0) {
|
||||||
@@ -83,7 +83,7 @@ async function deleteNotes(branchIdsToDelete) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
({proceed, deleteAllClones, eraseNotes} = await new Promise(res =>
|
({proceed, deleteAllClones, eraseNotes} = await new Promise(res =>
|
||||||
appContext.triggerCommand('showDeleteNotesDialog', {branchIdsToDelete, callback: res})));
|
appContext.triggerCommand('showDeleteNotesDialog', {branchIdsToDelete, callback: res, forceDeleteAllClones})));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!proceed) {
|
if (!proceed) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import appContext from "./app_context.js";
|
|||||||
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
|
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
|
||||||
import NoteContextCachingWidget from "../widgets/note_context_caching_widget.js";
|
import NoteContextCachingWidget from "../widgets/note_context_caching_widget.js";
|
||||||
import BasicWidget from "../widgets/basic_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.
|
* 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
|
* @returns {string} random string
|
||||||
*/
|
*/
|
||||||
this.randomString = utils.randomString;
|
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;
|
export default FrontendScriptApi;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import toastService from "./toast.js";
|
|||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
import options from "./options.js";
|
import options from "./options.js";
|
||||||
import frocaUpdater from "./froca_updater.js";
|
import frocaUpdater from "./froca_updater.js";
|
||||||
|
import appContext from "./app_context.js";
|
||||||
|
|
||||||
const messageHandlers = [];
|
const messageHandlers = [];
|
||||||
|
|
||||||
@@ -118,6 +119,9 @@ async function handleMessage(event) {
|
|||||||
else if (message.type === 'consistency-checks-failed') {
|
else if (message.type === 'consistency-checks-failed') {
|
||||||
toastService.showError("Consistency checks failed! See logs for details.", 50 * 60000);
|
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 = [];
|
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",
|
"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",
|
"workspaceTabBackgroundColor": "CSS color used in the note tab when hoisted to this note",
|
||||||
"workspaceCalendarRoot": "Defines per-workspace calendar root",
|
"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",
|
"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",
|
"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",
|
"inbox": "default inbox location for new notes",
|
||||||
|
|||||||
@@ -57,8 +57,10 @@ const TPL = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.add-new-attribute-button:hover, .save-attributes-button:hover {
|
.add-new-attribute-button:hover, .save-attributes-button:hover {
|
||||||
border: 1px solid var(--main-border-color);
|
border: 1px solid var(--button-border-color);
|
||||||
border-radius: 2px;
|
border-radius: var(--button-border-radius);
|
||||||
|
background: var(--button-background-color);
|
||||||
|
color: var(--button-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.attribute-errors {
|
.attribute-errors {
|
||||||
|
|||||||
@@ -8,6 +8,28 @@ class BasicWidget extends Component {
|
|||||||
style: ''
|
style: ''
|
||||||
};
|
};
|
||||||
this.classes = [];
|
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) {
|
id(id) {
|
||||||
|
|||||||
@@ -35,13 +35,31 @@ const TPL = `
|
|||||||
bottom: -30px;
|
bottom: -30px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.update-to-latest-version-button {
|
.update-to-latest-version-button {
|
||||||
display: none;
|
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>
|
</style>
|
||||||
|
|
||||||
<button type="button" data-toggle="dropdown" data-placement="right"
|
<button type="button" data-toggle="dropdown" data-placement="right"
|
||||||
@@ -91,11 +109,30 @@ const TPL = `
|
|||||||
Reload frontend
|
Reload frontend
|
||||||
<kbd data-command="reloadFrontendApp"></kbd>
|
<kbd data-command="reloadFrontendApp"></kbd>
|
||||||
</a>
|
</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">
|
<a class="dropdown-item" data-trigger-command="toggleFullscreen">
|
||||||
<span class="bx bx-empty"></span>
|
<span class="bx bx-empty"></span>
|
||||||
Toggle fullscreen
|
Toggle fullscreen
|
||||||
<kbd data-command="toggleFullscreen"></kbd>
|
<kbd ></kbd>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class="dropdown-item" data-trigger-command="showHelp">
|
<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(".open-dev-tools-button").toggle(isElectron);
|
||||||
this.$widget.find(".switch-to-mobile-version-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.$widget.find(".global-menu-button-update-available").append(
|
||||||
this.updateAvailableWidget.render()
|
this.updateAvailableWidget.render()
|
||||||
@@ -155,11 +196,34 @@ export default class GlobalMenuWidget extends BasicWidget {
|
|||||||
|
|
||||||
this.$updateToLatestVersionButton = this.$widget.find(".update-to-latest-version-button");
|
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();
|
this.updateVersionStatus();
|
||||||
|
|
||||||
setInterval(() => this.updateVersionStatus(), 8 * 60 * 60 * 1000);
|
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() {
|
async updateVersionStatus() {
|
||||||
if (options.get("checkForUpdates") !== 'true') {
|
if (options.get("checkForUpdates") !== 'true') {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||||
import utils from "../../services/utils.js";
|
import utils from "../../services/utils.js";
|
||||||
|
import branchService from "../../services/branches.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="dropdown note-actions">
|
<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 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 import-files-button">Import files</a>
|
||||||
<a class="dropdown-item export-note-button">Export note</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>
|
<a data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
|
||||||
</div>
|
</div>
|
||||||
</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.$widget.on('click', '.dropdown-item', () => this.$widget.find("[data-toggle='dropdown']").dropdown('toggle'));
|
||||||
|
|
||||||
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
|
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) {
|
refreshWithNote(note) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const TPL = `
|
|||||||
height: 21px !important;
|
height: 21px !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
|
||||||
border-radius: 8px;
|
border-radius: var(--button-border-radius);
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
border: none;
|
border: none;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
|||||||
@@ -1,33 +1,6 @@
|
|||||||
import BasicWidget from "../basic_widget.js";
|
import BasicWidget from "../basic_widget.js";
|
||||||
|
|
||||||
export default class Container extends BasicWidget {
|
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() {
|
doRender() {
|
||||||
this.$widget = $(`<div>`);
|
this.$widget = $(`<div>`);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export default class ScrollingContainer extends Container {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.class("scrolling-container");
|
||||||
this.css('overflow', 'auto');
|
this.css('overflow', 'auto');
|
||||||
this.css('position', 'relative');
|
this.css('position', 'relative');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import BasicWidget from "../basic_widget.js";
|
|||||||
const DELETE_NOTE_BUTTON_CLASS = "confirm-dialog-delete-note";
|
const DELETE_NOTE_BUTTON_CLASS = "confirm-dialog-delete-note";
|
||||||
|
|
||||||
const TPL = `
|
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-dialog modal-dialog-scrollable" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export default class DeleteNotesDialog extends BasicWidget {
|
|||||||
|
|
||||||
this.resolve({
|
this.resolve({
|
||||||
proceed: true,
|
proceed: true,
|
||||||
deleteAllClones: this.isDeleteAllClonesChecked(),
|
deleteAllClones: this.forceDeleteAllClones || this.isDeleteAllClonesChecked(),
|
||||||
eraseNotes: this.isEraseNotesChecked()
|
eraseNotes: this.isEraseNotesChecked()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -108,7 +108,7 @@ export default class DeleteNotesDialog extends BasicWidget {
|
|||||||
async renderDeletePreview() {
|
async renderDeletePreview() {
|
||||||
const response = await server.post('delete-notes-preview', {
|
const response = await server.post('delete-notes-preview', {
|
||||||
branchIdsToDelete: this.branchIds,
|
branchIdsToDelete: this.branchIds,
|
||||||
deleteAllClones: this.isDeleteAllClonesChecked()
|
deleteAllClones: this.forceDeleteAllClones || this.isDeleteAllClonesChecked()
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$deleteNotesList.empty();
|
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.branchIds = branchIdsToDelete;
|
||||||
|
this.forceDeleteAllClones = forceDeleteAllClones;
|
||||||
|
|
||||||
await this.renderDeletePreview();
|
await this.renderDeletePreview();
|
||||||
|
|
||||||
utils.openDialog(this.$widget);
|
utils.openDialog(this.$widget);
|
||||||
|
|
||||||
this.$deleteAllClones.prop("checked", false);
|
this.$deleteAllClones
|
||||||
|
.prop("checked", !!forceDeleteAllClones)
|
||||||
|
.prop("disabled", !!forceDeleteAllClones);
|
||||||
|
|
||||||
this.$eraseNotes.prop("checked", false);
|
this.$eraseNotes.prop("checked", false);
|
||||||
|
|
||||||
this.resolve = callback;
|
this.resolve = callback;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import utils from "../../services/utils.js";
|
|||||||
import BasicWidget from "../basic_widget.js";
|
import BasicWidget from "../basic_widget.js";
|
||||||
|
|
||||||
const TPL = `
|
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-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ const TPL = `
|
|||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
font-size: large;
|
font-size: large;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 5px;
|
border-radius: var(--button-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.token-table-button:hover {
|
.token-table-button:hover {
|
||||||
border: 1px solid var(--main-border-color);
|
border: 1px solid var(--button-border-color);
|
||||||
}
|
}
|
||||||
</style>`;
|
</style>`;
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import utils from "../../services/utils.js";
|
|||||||
import BasicWidget from "../basic_widget.js";
|
import BasicWidget from "../basic_widget.js";
|
||||||
|
|
||||||
const TPL = `
|
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-dialog modal-lg" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<form id="prompt-dialog-form">
|
<form class="prompt-dialog-form">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="prompt-title modal-title mr-auto">Prompt</h5>
|
<h5 class="prompt-title modal-title mr-auto">Prompt</h5>
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ export default class PromptDialog extends BasicWidget {
|
|||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
this.$dialogBody = this.$widget.find(".modal-body");
|
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.$question = null;
|
||||||
this.$answer = null;
|
this.$answer = null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import Container from "../containers/container.js";
|
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="floating-buttons">
|
<div class="floating-buttons">
|
||||||
@@ -16,7 +16,7 @@ const TPL = `
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.floating-buttons-children > * {
|
.floating-buttons-children > *:not(.hidden-int):not(.no-content-hidden) {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,12 +24,16 @@ const TPL = `
|
|||||||
font-size: 130%;
|
font-size: 130%;
|
||||||
padding: 5px 10px 4px 10px;
|
padding: 5px 10px 4px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.floating-buttons.temporarily-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="floating-buttons-children"></div>
|
<div class="floating-buttons-children"></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
export default class FloatingButtons extends Container {
|
export default class FloatingButtons extends NoteContextAwareWidget {
|
||||||
doRender() {
|
doRender() {
|
||||||
this.$widget = $(TPL);
|
this.$widget = $(TPL);
|
||||||
this.$children = this.$widget.find(".floating-buttons-children");
|
this.$children = this.$widget.find(".floating-buttons-children");
|
||||||
@@ -38,4 +42,16 @@ export default class FloatingButtons extends Container {
|
|||||||
this.$children.append(widget.render());
|
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 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 = `
|
const TPL = `
|
||||||
<button type="button"
|
<button type="button"
|
||||||
|
|||||||
@@ -89,17 +89,21 @@ export default class BacklinksWidget extends NoteContextAwareWidget {
|
|||||||
const resp = await server.get(`notes/${this.noteId}/backlink-count`);
|
const resp = await server.get(`notes/${this.noteId}/backlink-count`);
|
||||||
|
|
||||||
if (!resp || !resp.count) {
|
if (!resp || !resp.count) {
|
||||||
this.$ticker.hide();
|
this.toggle(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$ticker.show();
|
this.$ticker.toggle(true);
|
||||||
this.$count.text(
|
this.$count.text(
|
||||||
`${resp.count} backlink`
|
`${resp.count} backlink`
|
||||||
+ (resp.count === 1 ? '' : 's')
|
+ (resp.count === 1 ? '' : 's')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggle(show) {
|
||||||
|
this.$widget.toggleClass("hidden-no-content", !show);
|
||||||
|
}
|
||||||
|
|
||||||
clearItems() {
|
clearItems() {
|
||||||
this.$items.empty().hide();
|
this.$items.empty().hide();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import appContext from "../services/app_context.js";
|
|||||||
import keyboardActionsService from "../services/keyboard_actions.js";
|
import keyboardActionsService from "../services/keyboard_actions.js";
|
||||||
import clipboard from "../services/clipboard.js";
|
import clipboard from "../services/clipboard.js";
|
||||||
import protectedSessionService from "../services/protected_session.js";
|
import protectedSessionService from "../services/protected_session.js";
|
||||||
|
import linkService from "../services/link.js";
|
||||||
import syncService from "../services/sync.js";
|
import syncService from "../services/sync.js";
|
||||||
import options from "../services/options.js";
|
import options from "../services/options.js";
|
||||||
import protectedSessionHolder from "../services/protected_session_holder.js";
|
import protectedSessionHolder from "../services/protected_session_holder.js";
|
||||||
@@ -393,6 +394,18 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
title: node.title
|
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));
|
data.dataTransfer.setData("text", JSON.stringify(notes));
|
||||||
return true; // allow dragging to start
|
return true; // allow dragging to start
|
||||||
},
|
},
|
||||||
@@ -933,7 +946,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
if (this.noteContext
|
if (this.noteContext
|
||||||
&& this.noteContext.notePath
|
&& this.noteContext.notePath
|
||||||
&& !this.noteContext.note.isDeleted
|
&& !this.noteContext.note?.isDeleted
|
||||||
&& !this.noteContext.notePath.includes("root/hidden")
|
&& !this.noteContext.notePath.includes("root/hidden")
|
||||||
) {
|
) {
|
||||||
const newActiveNode = await this.getNodeFromPath(this.noteContext.notePath);
|
const newActiveNode = await this.getNodeFromPath(this.noteContext.notePath);
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ const TPL = `
|
|||||||
padding: 0.25rem 0.4rem;
|
padding: 0.25rem 0.4rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 0.5;
|
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 {
|
.sql-console-result-container {
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const TAB_ROW_TPL = `
|
|||||||
|
|
||||||
.note-new-tab:hover {
|
.note-new-tab:hover {
|
||||||
background-color: var(--accented-background-color);
|
background-color: var(--accented-background-color);
|
||||||
border-radius: 5px;
|
border-radius: var(--button-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-row-filler {
|
.tab-row-filler {
|
||||||
|
|||||||
@@ -87,4 +87,10 @@ export default class EditableCodeButtonsWidget extends NoteContextAwareWidget {
|
|||||||
|
|
||||||
this.$openTriliumApiDocsButton.toggle(note.mime.startsWith('application/javascript;env='));
|
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;
|
padding: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 5px;
|
border-radius: var(--button-border-radius);
|
||||||
|
color: var(--button-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-text-note-button:hover {
|
.edit-text-note-button:hover {
|
||||||
border-color: var(--main-border-color);
|
border-color: var(--button-border-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ button.close:hover {
|
|||||||
|
|
||||||
.icon-action {
|
.icon-action {
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 3px;
|
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
width: 35px;
|
width: 35px;
|
||||||
height: 35px;
|
height: 35px;
|
||||||
@@ -117,6 +116,7 @@ button.close:hover {
|
|||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
color: var(--button-text-color);
|
color: var(--button-text-color);
|
||||||
background: var(--button-background-color);
|
background: var(--button-background-color);
|
||||||
|
border-radius: var(--button-border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-action:hover:not(.disabled) {
|
.icon-action:hover:not(.disabled) {
|
||||||
@@ -747,7 +747,7 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-submenu {
|
.dropdown-submenu {
|
||||||
position:relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-submenu > .dropdown-menu {
|
.dropdown-submenu > .dropdown-menu {
|
||||||
@@ -755,6 +755,9 @@ li.dropdown-submenu:hover > ul.dropdown-menu {
|
|||||||
left: 100%;
|
left: 100%;
|
||||||
margin-top: -6px;
|
margin-top: -6px;
|
||||||
min-width: 15rem;
|
min-width: 15rem;
|
||||||
|
/* to make submenu scrollable https://github.com/zadam/trilium/issues/3136 */
|
||||||
|
max-height: 600px;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* rotate caret on hover */
|
/* rotate caret on hover */
|
||||||
@@ -954,3 +957,7 @@ button.close:hover {
|
|||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
color: currentColor;
|
color: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hidden-no-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const log = require('../../services/log');
|
|||||||
const scriptService = require('../../services/script');
|
const scriptService = require('../../services/script');
|
||||||
const searchService = require('../../services/search/services/search');
|
const searchService = require('../../services/search/services/search');
|
||||||
const bulkActionService = require("../../services/bulk_actions");
|
const bulkActionService = require("../../services/bulk_actions");
|
||||||
|
const cls = require("../../services/cls");
|
||||||
const {formatAttrForSearch} = require("../../services/attribute_formatter");
|
const {formatAttrForSearch} = require("../../services/attribute_formatter");
|
||||||
|
|
||||||
function searchFromNoteInt(note) {
|
function searchFromNoteInt(note) {
|
||||||
@@ -189,7 +190,9 @@ function getRelatedNotes(req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function searchTemplates() {
|
function searchTemplates() {
|
||||||
const query = formatAttrForSearch({type: 'label', name: "template"}, false);
|
const query = cls.getHoistedNoteId() === 'root'
|
||||||
|
? '#template'
|
||||||
|
: '#template OR #workspaceTemplate';
|
||||||
|
|
||||||
return searchService.searchNotes(query, {
|
return searchService.searchNotes(query, {
|
||||||
includeArchivedNotes: true,
|
includeArchivedNotes: true,
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const appInfo = require('./app_info');
|
|||||||
const searchService = require('./search/services/search');
|
const searchService = require('./search/services/search');
|
||||||
const SearchContext = require("./search/search_context");
|
const SearchContext = require("./search/search_context");
|
||||||
const becca = require("../becca/becca");
|
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.
|
* 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
|
* @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.
|
* 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: 'workspaceIconClass' },
|
||||||
{ type: 'label', name: 'workspaceTabBackgroundColor' },
|
{ type: 'label', name: 'workspaceTabBackgroundColor' },
|
||||||
{ type: 'label', name: 'workspaceCalendarRoot' },
|
{ type: 'label', name: 'workspaceCalendarRoot' },
|
||||||
|
{ type: 'label', name: 'workspaceTemplate' },
|
||||||
{ type: 'label', name: 'searchHome' },
|
{ type: 'label', name: 'searchHome' },
|
||||||
{ type: 'label', name: 'hoistedInbox' },
|
{ type: 'label', name: 'hoistedInbox' },
|
||||||
{ type: 'label', name: 'hoistedSearchHome' },
|
{ type: 'label', name: 'hoistedSearchHome' },
|
||||||
|
|||||||
@@ -292,7 +292,12 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
content = content.replace(/<\/body>.*<\/html>/gis, "");
|
content = content.replace(/<\/body>.*<\/html>/gis, "");
|
||||||
|
|
||||||
content = content.replace(/src="([^"]*)"/g, (match, url) => {
|
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("/")) {
|
if (isUrlAbsolute(url) || url.startsWith("/")) {
|
||||||
return match;
|
return match;
|
||||||
@@ -304,7 +309,12 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
content = content.replace(/href="([^"]*)"/g, (match, url) => {
|
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)) {
|
if (isUrlAbsolute(url)) {
|
||||||
return match;
|
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)]);
|
const attributeIdsToErase = sql.getColumn("SELECT attributeId FROM attributes WHERE isDeleted = 1 AND utcDateModified <= ?", [dateUtils.utcDateTimeStr(cutoffDate)]);
|
||||||
|
|
||||||
eraseAttributes(attributeIdsToErase);
|
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]);
|
const attributeIdsToErase = sql.getColumn("SELECT attributeId FROM attributes WHERE deleteId = ?", [deleteId]);
|
||||||
|
|
||||||
eraseAttributes(attributeIdsToErase);
|
eraseAttributes(attributeIdsToErase);
|
||||||
|
|
||||||
|
if (noteIdsToErase.length > 0 || branchIdsToErase.length > 0 || attributeIdsToErase.length > 0) {
|
||||||
|
require('../becca/becca_loader').reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function eraseDeletedNotesNow() {
|
function eraseDeletedNotesNow() {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class AttributeExistsExp extends Expression {
|
|||||||
}
|
}
|
||||||
else if (note.isTemplate() &&
|
else if (note.isTemplate() &&
|
||||||
// template attr is used as a marker for templates, but it's not meant to be inherited
|
// 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());
|
resultNoteSet.addAll(note.getTemplatedNotes());
|
||||||
}
|
}
|
||||||
else {
|
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);
|
const jsonStr = JSON.stringify(message);
|
||||||
|
|
||||||
if (webSocketServer) {
|
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);
|
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