mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 09:58:32 +02:00
Merge remote-tracking branch 'origin/master' into next
# Conflicts: # package-lock.json # src/public/app/services/note_content_renderer.js # src/public/stylesheets/style.css # src/routes/api/files.js # src/routes/routes.js
This commit is contained in:
commit
7494491560
3
.idea/vcs.xml
generated
3
.idea/vcs.xml
generated
@ -2,6 +2,5 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
@ -10,7 +10,7 @@ fi
|
|||||||
|
|
||||||
cd dist
|
cd dist
|
||||||
wget https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz
|
wget https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz
|
||||||
tar xvfJ node-v${NODE_VERSION}-linux-x64.tar.xz
|
tar xfJ node-v${NODE_VERSION}-linux-x64.tar.xz
|
||||||
rm node-v${NODE_VERSION}-linux-x64.tar.xz
|
rm node-v${NODE_VERSION}-linux-x64.tar.xz
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ if [[ $# -eq 0 ]] ; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
npm run webpack
|
n exec 12 npm run webpack
|
||||||
|
|
||||||
DIR=$1
|
DIR=$1
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ cp -r electron.js $DIR/
|
|||||||
cp webpack-* $DIR/
|
cp webpack-* $DIR/
|
||||||
|
|
||||||
# run in subshell (so we return to original dir)
|
# run in subshell (so we return to original dir)
|
||||||
(cd $DIR && npm install --only=prod)
|
(cd $DIR && n exec 12 npm install --only=prod)
|
||||||
|
|
||||||
# cleanup of useless files in dependencies
|
# cleanup of useless files in dependencies
|
||||||
rm -r $DIR/node_modules/image-q/demo
|
rm -r $DIR/node_modules/image-q/demo
|
||||||
|
@ -8,9 +8,9 @@ fi
|
|||||||
VERSION=$1
|
VERSION=$1
|
||||||
SERIES=${VERSION:0:4}-latest
|
SERIES=${VERSION:0:4}-latest
|
||||||
|
|
||||||
sudo docker push zadam/trilium:$VERSION
|
docker push zadam/trilium:$VERSION
|
||||||
sudo docker push zadam/trilium:$SERIES
|
docker push zadam/trilium:$SERIES
|
||||||
|
|
||||||
if [[ $1 != *"beta"* ]]; then
|
if [[ $1 != *"beta"* ]]; then
|
||||||
sudo docker push zadam/trilium:latest
|
docker push zadam/trilium:latest
|
||||||
fi
|
fi
|
||||||
|
@ -55,47 +55,20 @@ echo "Creating release in GitHub"
|
|||||||
EXTRA=
|
EXTRA=
|
||||||
|
|
||||||
if [[ $TAG == *"beta"* ]]; then
|
if [[ $TAG == *"beta"* ]]; then
|
||||||
EXTRA=--pre-release
|
EXTRA=--prerelease
|
||||||
fi
|
fi
|
||||||
|
|
||||||
github-release release \
|
echo "$GITHUB_CLI_AUTH_TOKEN" | gh auth login --with-token
|
||||||
--tag $TAG \
|
|
||||||
--name "$TAG release" $EXTRA
|
|
||||||
|
|
||||||
echo "Uploading debian x64 package"
|
gh release create "$TAG" \
|
||||||
|
--title "$TAG release" \
|
||||||
github-release upload \
|
--notes "" \
|
||||||
--tag $TAG \
|
$EXTRA \
|
||||||
--name "$DEBIAN_X64_BUILD" \
|
"dist/$DEBIAN_X64_BUILD" \
|
||||||
--file "dist/$DEBIAN_X64_BUILD"
|
"dist/$LINUX_X64_BUILD" \
|
||||||
|
"dist/$WINDOWS_X64_BUILD" \
|
||||||
echo "Uploading linux x64 build"
|
"dist/$MAC_X64_BUILD" \
|
||||||
|
"dist/$SERVER_BUILD"
|
||||||
github-release upload \
|
|
||||||
--tag $TAG \
|
|
||||||
--name "$LINUX_X64_BUILD" \
|
|
||||||
--file "dist/$LINUX_X64_BUILD"
|
|
||||||
|
|
||||||
echo "Uploading windows x64 build"
|
|
||||||
|
|
||||||
github-release upload \
|
|
||||||
--tag $TAG \
|
|
||||||
--name "$WINDOWS_X64_BUILD" \
|
|
||||||
--file "dist/$WINDOWS_X64_BUILD"
|
|
||||||
|
|
||||||
echo "Uploading mac x64 build"
|
|
||||||
|
|
||||||
github-release upload \
|
|
||||||
--tag $TAG \
|
|
||||||
--name "$MAC_X64_BUILD" \
|
|
||||||
--file "dist/$MAC_X64_BUILD"
|
|
||||||
|
|
||||||
echo "Uploading linux x64 server build"
|
|
||||||
|
|
||||||
github-release upload \
|
|
||||||
--tag $TAG \
|
|
||||||
--name "$SERVER_BUILD" \
|
|
||||||
--file "dist/$SERVER_BUILD"
|
|
||||||
|
|
||||||
echo "Building docker image"
|
echo "Building docker image"
|
||||||
|
|
||||||
@ -105,4 +78,4 @@ echo "Pushing docker image to dockerhub"
|
|||||||
|
|
||||||
bin/push-docker-image.sh $VERSION
|
bin/push-docker-image.sh $VERSION
|
||||||
|
|
||||||
echo "Release finished!"
|
echo "Release finished!"
|
||||||
|
@ -22,7 +22,7 @@ app.on('window-all-closed', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.on('ready', async () => {
|
app.on('ready', async () => {
|
||||||
app.setAppUserModelId('com.github.zadam.trilium');
|
// app.setAppUserModelId('com.github.zadam.trilium');
|
||||||
|
|
||||||
// if db is not initialized -> setup process
|
// if db is not initialized -> setup process
|
||||||
// if db is initialized, then we need to wait until the migration process is finished
|
// if db is initialized, then we need to wait until the migration process is finished
|
||||||
|
2
libraries/ckeditor/ckeditor.js
vendored
2
libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
17
package.json
17
package.json
@ -2,7 +2,7 @@
|
|||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"productName": "Trilium Notes",
|
"productName": "Trilium Notes",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.47.0-beta",
|
"version": "0.47.1-beta",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -28,6 +28,7 @@
|
|||||||
"axios": "0.21.1",
|
"axios": "0.21.1",
|
||||||
"better-sqlite3": "7.1.4",
|
"better-sqlite3": "7.1.4",
|
||||||
"body-parser": "1.19.0",
|
"body-parser": "1.19.0",
|
||||||
|
"chokidar": "^3.5.1",
|
||||||
"cls-hooked": "4.2.2",
|
"cls-hooked": "4.2.2",
|
||||||
"commonmark": "0.29.3",
|
"commonmark": "0.29.3",
|
||||||
"cookie-parser": "1.4.5",
|
"cookie-parser": "1.4.5",
|
||||||
@ -42,7 +43,7 @@
|
|||||||
"express-partial-content": "^1.0.2",
|
"express-partial-content": "^1.0.2",
|
||||||
"express-session": "1.17.1",
|
"express-session": "1.17.1",
|
||||||
"fs-extra": "9.1.0",
|
"fs-extra": "9.1.0",
|
||||||
"helmet": "4.4.1",
|
"helmet": "4.5.0",
|
||||||
"html": "1.0.0",
|
"html": "1.0.0",
|
||||||
"html2plaintext": "2.1.2",
|
"html2plaintext": "2.1.2",
|
||||||
"http-proxy-agent": "4.0.1",
|
"http-proxy-agent": "4.0.1",
|
||||||
@ -53,11 +54,11 @@
|
|||||||
"is-svg": "4.3.1",
|
"is-svg": "4.3.1",
|
||||||
"jimp": "0.16.1",
|
"jimp": "0.16.1",
|
||||||
"joplin-turndown-plugin-gfm": "1.0.12",
|
"joplin-turndown-plugin-gfm": "1.0.12",
|
||||||
"jsdom": "16.5.2",
|
"jsdom": "16.5.3",
|
||||||
"mime-types": "2.1.30",
|
"mime-types": "2.1.30",
|
||||||
"multer": "1.4.2",
|
"multer": "1.4.2",
|
||||||
"node-abi": "2.21.0",
|
"node-abi": "2.26.0",
|
||||||
"open": "8.0.5",
|
"open": "8.0.6",
|
||||||
"portscanner": "2.2.0",
|
"portscanner": "2.2.0",
|
||||||
"rand-token": "1.0.1",
|
"rand-token": "1.0.1",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
@ -73,13 +74,13 @@
|
|||||||
"tmp": "^0.2.1",
|
"tmp": "^0.2.1",
|
||||||
"turndown": "7.0.0",
|
"turndown": "7.0.0",
|
||||||
"unescape": "1.0.1",
|
"unescape": "1.0.1",
|
||||||
"ws": "7.4.4",
|
"ws": "7.4.5",
|
||||||
"yauzl": "2.10.0",
|
"yauzl": "2.10.0",
|
||||||
"yazl": "2.5.1"
|
"yazl": "2.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"electron": "13.0.0-beta.12",
|
"electron": "13.0.0-beta.17",
|
||||||
"electron-builder": "22.10.5",
|
"electron-builder": "22.10.5",
|
||||||
"electron-packager": "15.2.0",
|
"electron-packager": "15.2.0",
|
||||||
"electron-rebuild": "2.3.5",
|
"electron-rebuild": "2.3.5",
|
||||||
@ -88,7 +89,7 @@
|
|||||||
"jsdoc": "3.6.6",
|
"jsdoc": "3.6.6",
|
||||||
"lorem-ipsum": "2.0.3",
|
"lorem-ipsum": "2.0.3",
|
||||||
"rcedit": "3.0.0",
|
"rcedit": "3.0.0",
|
||||||
"webpack": "5.31.2",
|
"webpack": "5.35.1",
|
||||||
"webpack-cli": "4.6.0"
|
"webpack-cli": "4.6.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
|
@ -121,14 +121,14 @@ ws.subscribeToMessages(async message => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === 'task-error') {
|
if (message.type === 'taskError') {
|
||||||
toastService.closePersistent(message.taskId);
|
toastService.closePersistent(message.taskId);
|
||||||
toastService.showError(message.message);
|
toastService.showError(message.message);
|
||||||
}
|
}
|
||||||
else if (message.type === 'task-progress-count') {
|
else if (message.type === 'taskProgressCount') {
|
||||||
toastService.showPersistent(makeToast(message.taskId, "Export in progress: " + message.progressCount));
|
toastService.showPersistent(makeToast(message.taskId, "Export in progress: " + message.progressCount));
|
||||||
}
|
}
|
||||||
else if (message.type === 'task-succeeded') {
|
else if (message.type === 'taskSucceeded') {
|
||||||
const toast = makeToast(message.taskId, "Export finished successfully.");
|
const toast = makeToast(message.taskId, "Export finished successfully.");
|
||||||
toast.closeAfter = 5000;
|
toast.closeAfter = 5000;
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ import SearchResultWidget from "../widgets/search_result.js";
|
|||||||
import SyncStatusWidget from "../widgets/sync_status.js";
|
import SyncStatusWidget from "../widgets/sync_status.js";
|
||||||
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
import ScrollingContainer from "../widgets/containers/scrolling_container.js";
|
||||||
import RootContainer from "../widgets/containers/root_container.js";
|
import RootContainer from "../widgets/containers/root_container.js";
|
||||||
|
import NoteUpdateStatusWidget from "../widgets/note_update_status.js";
|
||||||
|
|
||||||
const RIGHT_PANE_CSS = `
|
const RIGHT_PANE_CSS = `
|
||||||
<style>
|
<style>
|
||||||
@ -177,6 +178,7 @@ export default class DesktopLayout {
|
|||||||
.child(new InheritedAttributesWidget())
|
.child(new InheritedAttributesWidget())
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.child(new NoteUpdateStatusWidget())
|
||||||
.child(
|
.child(
|
||||||
new TabCachingWidget(() => new ScrollingContainer()
|
new TabCachingWidget(() => new ScrollingContainer()
|
||||||
.child(new SqlTableSchemasWidget())
|
.child(new SqlTableSchemasWidget())
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import froca from "./froca.js";
|
import froca from "./froca.js";
|
||||||
import bundleService from "./bundle.js";
|
import bundleService from "./bundle.js";
|
||||||
import DialogCommandExecutor from "./dialog_command_executor.js";
|
import RootCommandExecutor from "./root_command_executor.js";
|
||||||
import Entrypoints from "./entrypoints.js";
|
import Entrypoints from "./entrypoints.js";
|
||||||
import options from "./options.js";
|
import options from "./options.js";
|
||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
@ -57,7 +57,7 @@ class AppContext extends Component {
|
|||||||
|
|
||||||
this.executors = [
|
this.executors = [
|
||||||
this.tabManager,
|
this.tabManager,
|
||||||
new DialogCommandExecutor(),
|
new RootCommandExecutor(),
|
||||||
new Entrypoints(),
|
new Entrypoints(),
|
||||||
new MainTreeExecutors()
|
new MainTreeExecutors()
|
||||||
];
|
];
|
||||||
|
@ -153,12 +153,12 @@ ws.subscribeToMessages(async message => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === 'task-error') {
|
if (message.type === 'taskError') {
|
||||||
toastService.closePersistent(message.taskId);
|
toastService.closePersistent(message.taskId);
|
||||||
toastService.showError(message.message);
|
toastService.showError(message.message);
|
||||||
} else if (message.type === 'task-progress-count') {
|
} else if (message.type === 'taskProgressCount') {
|
||||||
toastService.showPersistent(makeToast(message.taskId, "Delete notes in progress: " + message.progressCount));
|
toastService.showPersistent(makeToast(message.taskId, "Delete notes in progress: " + message.progressCount));
|
||||||
} else if (message.type === 'task-succeeded') {
|
} else if (message.type === 'taskSucceeded') {
|
||||||
const toast = makeToast(message.taskId, "Delete finished successfully.");
|
const toast = makeToast(message.taskId, "Delete finished successfully.");
|
||||||
toast.closeAfter = 5000;
|
toast.closeAfter = 5000;
|
||||||
|
|
||||||
@ -167,16 +167,16 @@ ws.subscribeToMessages(async message => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ws.subscribeToMessages(async message => {
|
ws.subscribeToMessages(async message => {
|
||||||
if (message.taskType !== 'undelete-notes') {
|
if (message.taskType !== 'undeleteNotes') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === 'task-error') {
|
if (message.type === 'taskError') {
|
||||||
toastService.closePersistent(message.taskId);
|
toastService.closePersistent(message.taskId);
|
||||||
toastService.showError(message.message);
|
toastService.showError(message.message);
|
||||||
} else if (message.type === 'task-progress-count') {
|
} else if (message.type === 'taskProgressCount') {
|
||||||
toastService.showPersistent(makeToast(message.taskId, "Undeleting notes in progress: " + message.progressCount));
|
toastService.showPersistent(makeToast(message.taskId, "Undeleting notes in progress: " + message.progressCount));
|
||||||
} else if (message.type === 'task-succeeded') {
|
} else if (message.type === 'taskSucceeded') {
|
||||||
const toast = makeToast(message.taskId, "Undeleting notes finished successfully.");
|
const toast = makeToast(message.taskId, "Undeleting notes finished successfully.");
|
||||||
toast.closeAfter = 5000;
|
toast.closeAfter = 5000;
|
||||||
|
|
||||||
|
@ -64,13 +64,12 @@ async function getWidgetBundlesByParent() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
widget = await executeBundle(bundle);
|
widget = await executeBundle(bundle);
|
||||||
|
widgetsByParent.add(widget);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
logError("Widget initialization failed: ", e);
|
logError("Widget initialization failed: ", e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
widgetsByParent.add(widget);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return widgetsByParent;
|
return widgetsByParent;
|
||||||
|
36
src/public/app/services/file_watcher.js
Normal file
36
src/public/app/services/file_watcher.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import ws from "./ws.js";
|
||||||
|
import appContext from "./app_context.js";
|
||||||
|
|
||||||
|
const fileModificationStatus = {};
|
||||||
|
|
||||||
|
function getFileModificationStatus(noteId) {
|
||||||
|
return fileModificationStatus[noteId];
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileModificationUploaded(noteId) {
|
||||||
|
delete fileModificationStatus[noteId];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ignoreModification(noteId) {
|
||||||
|
delete fileModificationStatus[noteId];
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.subscribeToMessages(async message => {
|
||||||
|
if (message.type !== 'openedFileUpdated') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileModificationStatus[message.noteId] = message;
|
||||||
|
|
||||||
|
appContext.triggerEvent('openedFileUpdated', {
|
||||||
|
noteId: message.noteId,
|
||||||
|
lastModifiedMs: message.lastModifiedMs,
|
||||||
|
filePath: message.filePath
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getFileModificationStatus,
|
||||||
|
fileModificationUploaded,
|
||||||
|
ignoreModification
|
||||||
|
}
|
@ -52,12 +52,12 @@ ws.subscribeToMessages(async message => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === 'task-error') {
|
if (message.type === 'taskError') {
|
||||||
toastService.closePersistent(message.taskId);
|
toastService.closePersistent(message.taskId);
|
||||||
toastService.showError(message.message);
|
toastService.showError(message.message);
|
||||||
} else if (message.type === 'task-progress-count') {
|
} else if (message.type === 'taskProgressCount') {
|
||||||
toastService.showPersistent(makeToast(message.taskId, "Import in progress: " + message.progressCount));
|
toastService.showPersistent(makeToast(message.taskId, "Import in progress: " + message.progressCount));
|
||||||
} else if (message.type === 'task-succeeded') {
|
} else if (message.type === 'taskSucceeded') {
|
||||||
const toast = makeToast(message.taskId, "Import finished successfully.");
|
const toast = makeToast(message.taskId, "Import finished successfully.");
|
||||||
toast.closeAfter = 5000;
|
toast.closeAfter = 5000;
|
||||||
|
|
||||||
|
@ -89,7 +89,11 @@ function updateDisplayedShortcuts($container) {
|
|||||||
const action = await getAction(actionName, true);
|
const action = await getAction(actionName, true);
|
||||||
|
|
||||||
if (action) {
|
if (action) {
|
||||||
$(el).text(action.effectiveShortcuts.join(', '));
|
const keyboardActions = action.effectiveShortcuts.join(', ');
|
||||||
|
|
||||||
|
if (keyboardActions || $(el).text() !== "not set") {
|
||||||
|
$(el).text(keyboardActions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -39,12 +39,12 @@ async function getRenderedContent(note, options = {}) {
|
|||||||
.css("max-width", "100%")
|
.css("max-width", "100%")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (!options.tooltip && ['file', 'pdf', 'audio', 'video']) {
|
else if (!options.tooltip && ['file', 'pdf', 'audio', 'video'].includes(type)) {
|
||||||
const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>');
|
const $downloadButton = $('<button class="file-download btn btn-primary" type="button">Download</button>');
|
||||||
const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>');
|
const $openButton = $('<button class="file-open btn btn-primary" type="button">Open</button>');
|
||||||
|
|
||||||
$downloadButton.on('click', () => openService.downloadFileNote(note.noteId));
|
$downloadButton.on('click', () => openService.downloadFileNote(note.noteId));
|
||||||
$openButton.on('click', () => openService.openFileNote(note.noteId));
|
$openButton.on('click', () => openService.openNoteExternally(note.noteId));
|
||||||
|
|
||||||
// open doesn't work for protected notes since it works through browser which isn't in protected session
|
// open doesn't work for protected notes since it works through browser which isn't in protected session
|
||||||
$openButton.toggle(!note.isProtected);
|
$openButton.toggle(!note.isProtected);
|
||||||
@ -59,7 +59,7 @@ async function getRenderedContent(note, options = {}) {
|
|||||||
}
|
}
|
||||||
else if (type === 'audio') {
|
else if (type === 'audio') {
|
||||||
const $audioPreview = $('<audio controls></audio>')
|
const $audioPreview = $('<audio controls></audio>')
|
||||||
.attr("src", openService.getUrlForDownload("api/notes/" + note.noteId + "/open"))
|
.attr("src", openService.getUrlForStreaming("api/notes/" + note.noteId + "/open-partial"))
|
||||||
.attr("type", note.mime)
|
.attr("type", note.mime)
|
||||||
.css("width", "100%");
|
.css("width", "100%");
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ async function getRenderedContent(note, options = {}) {
|
|||||||
}
|
}
|
||||||
else if (type === 'video') {
|
else if (type === 'video') {
|
||||||
const $videoPreview = $('<video controls></video>')
|
const $videoPreview = $('<video controls></video>')
|
||||||
.attr("src", openService.getUrlForDownload("api/notes/" + note.noteId + "/open"))
|
.attr("src", openService.getUrlForDownload("api/notes/" + note.noteId + "/open-partial"))
|
||||||
.attr("type", note.mime)
|
.attr("type", note.mime)
|
||||||
.css("width", "100%");
|
.css("width", "100%");
|
||||||
|
|
||||||
|
@ -317,7 +317,9 @@ class NoteListRenderer {
|
|||||||
const $expander = $('<span class="note-expander bx bx-chevron-right"></span>');
|
const $expander = $('<span class="note-expander bx bx-chevron-right"></span>');
|
||||||
|
|
||||||
const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note);
|
const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note);
|
||||||
const notePath = this.parentNote.noteId + '/' + note.noteId;
|
const notePath = this.parentNote.type === 'search'
|
||||||
|
? note.noteId // for search note parent we want to display non-search path
|
||||||
|
: this.parentNote.noteId + '/' + note.noteId;
|
||||||
|
|
||||||
const $card = $('<div class="note-book-card">')
|
const $card = $('<div class="note-book-card">')
|
||||||
.attr('data-note-id', note.noteId)
|
.attr('data-note-id', note.noteId)
|
||||||
|
@ -21,9 +21,9 @@ function downloadFileNote(noteId) {
|
|||||||
download(url);
|
download(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openFileNote(noteId) {
|
async function openNoteExternally(noteId) {
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
const resp = await server.post("notes/" + noteId + "/saveToTmpDir");
|
const resp = await server.post("notes/" + noteId + "/save-to-tmp-dir");
|
||||||
|
|
||||||
const electron = utils.dynamicRequire('electron');
|
const electron = utils.dynamicRequire('electron');
|
||||||
const res = await electron.shell.openPath(resp.tmpFilePath);
|
const res = await electron.shell.openPath(resp.tmpFilePath);
|
||||||
@ -66,7 +66,7 @@ function getHost() {
|
|||||||
export default {
|
export default {
|
||||||
download,
|
download,
|
||||||
downloadFileNote,
|
downloadFileNote,
|
||||||
openFileNote,
|
openNoteExternally,
|
||||||
downloadNoteRevision,
|
downloadNoteRevision,
|
||||||
getUrlForDownload
|
getUrlForDownload
|
||||||
}
|
}
|
||||||
|
@ -89,18 +89,18 @@ function makeToast(message, protectingLabel, text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ws.subscribeToMessages(async message => {
|
ws.subscribeToMessages(async message => {
|
||||||
if (message.taskType !== 'protect-notes') {
|
if (message.taskType !== 'protectNotes') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const protectingLabel = message.data.protect ? "Protecting" : "Unprotecting";
|
const protectingLabel = message.data.protect ? "Protecting" : "Unprotecting";
|
||||||
|
|
||||||
if (message.type === 'task-error') {
|
if (message.type === 'taskError') {
|
||||||
toastService.closePersistent(message.taskId);
|
toastService.closePersistent(message.taskId);
|
||||||
toastService.showError(message.message);
|
toastService.showError(message.message);
|
||||||
} else if (message.type === 'task-progress-count') {
|
} else if (message.type === 'taskProgressCount') {
|
||||||
toastService.showPersistent(makeToast(message, protectingLabel,protectingLabel + " in progress: " + message.progressCount));
|
toastService.showPersistent(makeToast(message, protectingLabel,protectingLabel + " in progress: " + message.progressCount));
|
||||||
} else if (message.type === 'task-succeeded') {
|
} else if (message.type === 'taskSucceeded') {
|
||||||
const toast = makeToast(message, protectingLabel, protectingLabel + " finished successfully.");
|
const toast = makeToast(message, protectingLabel, protectingLabel + " finished successfully.");
|
||||||
toast.closeAfter = 3000;
|
toast.closeAfter = 3000;
|
||||||
|
|
||||||
|
@ -2,8 +2,9 @@ import Component from "../widgets/component.js";
|
|||||||
import appContext from "./app_context.js";
|
import appContext from "./app_context.js";
|
||||||
import dateNoteService from "../services/date_notes.js";
|
import dateNoteService from "../services/date_notes.js";
|
||||||
import treeService from "../services/tree.js";
|
import treeService from "../services/tree.js";
|
||||||
|
import openService from "./open.js";
|
||||||
|
|
||||||
export default class DialogCommandExecutor extends Component {
|
export default class RootCommandExecutor extends Component {
|
||||||
jumpToNoteCommand() {
|
jumpToNoteCommand() {
|
||||||
import("../dialogs/jump_to_note.js").then(d => d.showDialog());
|
import("../dialogs/jump_to_note.js").then(d => d.showDialog());
|
||||||
}
|
}
|
||||||
@ -84,4 +85,12 @@ export default class DialogCommandExecutor extends Component {
|
|||||||
showBackendLogCommand() {
|
showBackendLogCommand() {
|
||||||
import("../dialogs/backend_log.js").then(d => d.showDialog());
|
import("../dialogs/backend_log.js").then(d => d.showDialog());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openNoteExternallyCommand() {
|
||||||
|
const noteId = appContext.tabManager.getActiveTabNoteId();
|
||||||
|
|
||||||
|
if (noteId) {
|
||||||
|
openService.openNoteExternally(noteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -127,7 +127,7 @@ async function sortAlphabetically(noteId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ws.subscribeToMessages(message => {
|
ws.subscribeToMessages(message => {
|
||||||
if (message.type === 'open-note') {
|
if (message.type === 'openNote') {
|
||||||
appContext.tabManager.activateOrOpenNote(message.noteId);
|
appContext.tabManager.activateOrOpenNote(message.noteId);
|
||||||
|
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
|
@ -59,7 +59,7 @@ async function handleMessage(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (message.type === 'frontend-update') {
|
if (message.type === 'frontend-update') {
|
||||||
let {entityChanges, lastSyncedPush} = message.data;
|
let {entityChanges} = message.data;
|
||||||
lastPingTs = Date.now();
|
lastPingTs = Date.now();
|
||||||
|
|
||||||
if (entityChanges.length > 0) {
|
if (entityChanges.length > 0) {
|
||||||
|
@ -22,6 +22,7 @@ const TPL = `
|
|||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import TabAwareWidget from "./tab_aware_widget.js";
|
import TabAwareWidget from "./tab_aware_widget.js";
|
||||||
import protectedSessionService from "../services/protected_session.js";
|
import protectedSessionService from "../services/protected_session.js";
|
||||||
|
import utils from "../services/utils.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="dropdown note-actions">
|
<div class="dropdown note-actions">
|
||||||
<style>
|
<style>
|
||||||
.note-actions .dropdown-menu {
|
.note-actions .dropdown-menu {
|
||||||
width: 15em;
|
width: 20em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover {
|
.note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover {
|
||||||
@ -84,6 +85,7 @@ const TPL = `
|
|||||||
<a data-trigger-command="showNoteRevisions" class="dropdown-item show-note-revisions-button">Revisions</a>
|
<a data-trigger-command="showNoteRevisions" class="dropdown-item show-note-revisions-button">Revisions</a>
|
||||||
<a data-trigger-command="showLinkMap" class="dropdown-item show-link-map-button"><kbd data-command="showLinkMap"></kbd> Link map</a>
|
<a data-trigger-command="showLinkMap" class="dropdown-item show-link-map-button"><kbd data-command="showLinkMap"></kbd> Link map</a>
|
||||||
<a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> Note source</a>
|
<a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> Note source</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 data-trigger-command="printActiveNote" class="dropdown-item print-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
|
<a data-trigger-command="printActiveNote" class="dropdown-item print-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
|
||||||
@ -119,6 +121,8 @@ export default class NoteActionsWidget extends TabAwareWidget {
|
|||||||
|
|
||||||
this.$widget.on('click', '.dropdown-item',
|
this.$widget.on('click', '.dropdown-item',
|
||||||
() => this.$widget.find('.dropdown-toggle').dropdown('toggle'));
|
() => this.$widget.find('.dropdown-toggle').dropdown('toggle'));
|
||||||
|
|
||||||
|
this.$openNoteExternallyButton = this.$widget.find(".open-note-externally-button");
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshWithNote(note) {
|
refreshWithNote(note) {
|
||||||
@ -128,6 +132,8 @@ export default class NoteActionsWidget extends TabAwareWidget {
|
|||||||
|
|
||||||
this.$protectButton.toggle(!note.isProtected);
|
this.$protectButton.toggle(!note.isProtected);
|
||||||
this.$unprotectButton.toggle(!!note.isProtected);
|
this.$unprotectButton.toggle(!!note.isProtected);
|
||||||
|
|
||||||
|
this.$openNoteExternallyButton.toggle(utils.isElectron());
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleDisabled($el, enable) {
|
toggleDisabled($el, enable) {
|
||||||
|
@ -382,8 +382,6 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
node.setActive();
|
node.setActive();
|
||||||
|
|
||||||
this.clearSelectedNodes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -393,6 +391,8 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
// click event won't propagate so let's close context menu manually
|
// click event won't propagate so let's close context menu manually
|
||||||
contextMenu.hide();
|
contextMenu.hide();
|
||||||
|
|
||||||
|
this.clearSelectedNodes();
|
||||||
|
|
||||||
const notePath = treeService.getNotePath(data.node);
|
const notePath = treeService.getNotePath(data.node);
|
||||||
|
|
||||||
const activeTabContext = appContext.tabManager.getActiveTabContext();
|
const activeTabContext = appContext.tabManager.getActiveTabContext();
|
||||||
@ -1144,11 +1144,12 @@ export default class NoteTreeWidget extends TabAwareWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused});
|
|
||||||
|
|
||||||
if (activeNodeFocused) {
|
if (activeNodeFocused) {
|
||||||
node.setFocus(true);
|
// needed by Firefox: https://github.com/zadam/trilium/issues/1865
|
||||||
|
this.tree.$container.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// this is used when original note has been deleted and we want to move the focus to the note above/below
|
// this is used when original note has been deleted and we want to move the focus to the note above/below
|
||||||
|
64
src/public/app/widgets/note_update_status.js
Normal file
64
src/public/app/widgets/note_update_status.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import TabAwareWidget from "./tab_aware_widget.js";
|
||||||
|
import server from "../services/server.js";
|
||||||
|
import fileWatcher from "../services/file_watcher.js";
|
||||||
|
|
||||||
|
const TPL = `
|
||||||
|
<div class="dropdown note-update-status-widget alert alert-warning">
|
||||||
|
<style>
|
||||||
|
.note-update-status-widget {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<p>File <code class="file-path"></code> has been last modified on <span class="file-last-modified"></span>.</p>
|
||||||
|
|
||||||
|
<div style="display: flex; flex-direction: row; justify-content: space-evenly;">
|
||||||
|
<button class="btn btn-sm file-upload-button">Upload modified file</button>
|
||||||
|
|
||||||
|
<button class="btn btn-sm ignore-this-change-button">Ignore this change</button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
export default class NoteUpdateStatusWidget extends TabAwareWidget {
|
||||||
|
isEnabled() {
|
||||||
|
return super.isEnabled()
|
||||||
|
&& !!fileWatcher.getFileModificationStatus(this.noteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
doRender() {
|
||||||
|
this.$widget = $(TPL);
|
||||||
|
this.overflowing();
|
||||||
|
|
||||||
|
this.$filePath = this.$widget.find(".file-path");
|
||||||
|
this.$fileLastModified = this.$widget.find(".file-last-modified");
|
||||||
|
this.$fileUploadButton = this.$widget.find(".file-upload-button");
|
||||||
|
|
||||||
|
this.$fileUploadButton.on("click", async () => {
|
||||||
|
await server.post(`notes/${this.noteId}/upload-modified-file`, {
|
||||||
|
filePath: this.$filePath.text()
|
||||||
|
});
|
||||||
|
|
||||||
|
fileWatcher.fileModificationUploaded(this.noteId);
|
||||||
|
this.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$ignoreThisChangeButton = this.$widget.find(".ignore-this-change-button");
|
||||||
|
this.$ignoreThisChangeButton.on('click', () => {
|
||||||
|
fileWatcher.ignoreModification(this.noteId);
|
||||||
|
this.refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshWithNote(note) {
|
||||||
|
const status = fileWatcher.getFileModificationStatus(note.noteId);
|
||||||
|
|
||||||
|
this.$filePath.text(status.filePath);
|
||||||
|
this.$fileLastModified.text(dayjs.unix(status.lastModifiedMs / 1000).format("HH:mm:ss"));
|
||||||
|
}
|
||||||
|
|
||||||
|
openedFileUpdatedEvent(data) {
|
||||||
|
if (data.noteId === this.noteId) {
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ const TPL = `
|
|||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<span class="bx bx-trash"></span>
|
<span class="bx bx-trash"></span>
|
||||||
|
|
||||||
Delete matched note
|
Delete matched notes
|
||||||
</td>
|
</td>
|
||||||
<td class="button-column">
|
<td class="button-column">
|
||||||
<span class="bx bx-x icon-action action-conf-del"></span>
|
<span class="bx bx-x icon-action action-conf-del"></span>
|
||||||
|
@ -21,6 +21,7 @@ const TPL = `
|
|||||||
<option value="ownedLabelCount">Number of labels</option>
|
<option value="ownedLabelCount">Number of labels</option>
|
||||||
<option value="ownedRelationCount">Number of relations</option>
|
<option value="ownedRelationCount">Number of relations</option>
|
||||||
<option value="targetRelationCount">Number of relations targeting the note</option>
|
<option value="targetRelationCount">Number of relations targeting the note</option>
|
||||||
|
<option value="random">Random order</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select name="orderDirection" class="form-control w-auto d-inline">
|
<select name="orderDirection" class="form-control w-auto d-inline">
|
||||||
|
@ -82,7 +82,7 @@ export default class FilePropertiesWidget extends TabAwareWidget {
|
|||||||
this.$uploadNewRevisionInput = this.$widget.find(".file-upload-new-revision-input");
|
this.$uploadNewRevisionInput = this.$widget.find(".file-upload-new-revision-input");
|
||||||
|
|
||||||
this.$downloadButton.on('click', () => openService.downloadFileNote(this.noteId));
|
this.$downloadButton.on('click', () => openService.downloadFileNote(this.noteId));
|
||||||
this.$openButton.on('click', () => openService.openFileNote(this.noteId));
|
this.$openButton.on('click', () => openService.openNoteExternally(this.noteId));
|
||||||
|
|
||||||
this.$uploadNewRevisionButton.on("click", () => {
|
this.$uploadNewRevisionButton.on("click", () => {
|
||||||
this.$uploadNewRevisionInput.trigger("click");
|
this.$uploadNewRevisionInput.trigger("click");
|
||||||
|
@ -26,6 +26,8 @@ const TPL = `
|
|||||||
<div class="no-print" style="display: flex; justify-content: space-evenly; margin: 10px;">
|
<div class="no-print" style="display: flex; justify-content: space-evenly; margin: 10px;">
|
||||||
<button class="image-download btn btn-sm btn-primary" type="button">Download</button>
|
<button class="image-download btn btn-sm btn-primary" type="button">Download</button>
|
||||||
|
|
||||||
|
<button class="image-open btn btn-sm btn-primary" type="button">Open</button>
|
||||||
|
|
||||||
<button class="image-copy-to-clipboard btn btn-sm btn-primary" type="button">Copy to clipboard</button>
|
<button class="image-copy-to-clipboard btn btn-sm btn-primary" type="button">Copy to clipboard</button>
|
||||||
|
|
||||||
<button class="image-upload-new-revision btn btn-sm btn-primary" type="button">Upload new revision</button>
|
<button class="image-upload-new-revision btn btn-sm btn-primary" type="button">Upload new revision</button>
|
||||||
@ -59,6 +61,9 @@ export default class ImagePropertiesWidget extends TabAwareWidget {
|
|||||||
this.$fileType = this.$widget.find(".image-filetype");
|
this.$fileType = this.$widget.find(".image-filetype");
|
||||||
this.$fileSize = this.$widget.find(".image-filesize");
|
this.$fileSize = this.$widget.find(".image-filesize");
|
||||||
|
|
||||||
|
this.$openButton = this.$widget.find(".image-open");
|
||||||
|
this.$openButton.on('click', () => openService.openNoteExternally(this.noteId));
|
||||||
|
|
||||||
this.$imageDownloadButton = this.$widget.find(".image-download");
|
this.$imageDownloadButton = this.$widget.find(".image-download");
|
||||||
this.$imageDownloadButton.on('click', () => openService.downloadFileNote(this.noteId));
|
this.$imageDownloadButton.on('click', () => openService.downloadFileNote(this.noteId));
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import utils from "../../services/utils.js";
|
|
||||||
import openService from "../../services/open.js";
|
import openService from "../../services/open.js";
|
||||||
import TypeWidget from "./type_widget.js";
|
import TypeWidget from "./type_widget.js";
|
||||||
|
import fileWatcher from "../../services/file_watcher.js";
|
||||||
|
import server from "../../services/server.js";
|
||||||
|
|
||||||
const TPL = `
|
const TPL = `
|
||||||
<div class="note-detail-file note-detail-printable">
|
<div class="note-detail-file note-detail-printable">
|
||||||
@ -50,9 +51,6 @@ export default class FileTypeWidget extends TypeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async doRefresh(note) {
|
async doRefresh(note) {
|
||||||
const attributes = note.getAttributes();
|
|
||||||
const attributeMap = utils.toObject(attributes, l => [l.name, l.value]);
|
|
||||||
|
|
||||||
this.$widget.show();
|
this.$widget.show();
|
||||||
|
|
||||||
const noteComplement = await this.tabContext.getNoteComplement();
|
const noteComplement = await this.tabContext.getNoteComplement();
|
||||||
@ -73,14 +71,14 @@ export default class FileTypeWidget extends TypeWidget {
|
|||||||
else if (note.mime.startsWith('video/')) {
|
else if (note.mime.startsWith('video/')) {
|
||||||
this.$videoPreview
|
this.$videoPreview
|
||||||
.show()
|
.show()
|
||||||
.attr("src", openService.getUrlForDownload("api/notes/" + this.noteId + "/open"))
|
.attr("src", openService.getUrlForDownload("api/notes/" + this.noteId + "/open-partial"))
|
||||||
.attr("type", this.note.mime)
|
.attr("type", this.note.mime)
|
||||||
.css("width", this.$widget.width());
|
.css("width", this.$widget.width());
|
||||||
}
|
}
|
||||||
else if (note.mime.startsWith('audio/')) {
|
else if (note.mime.startsWith('audio/')) {
|
||||||
this.$audioPreview
|
this.$audioPreview
|
||||||
.show()
|
.show()
|
||||||
.attr("src", openService.getUrlForDownload("api/notes/" + this.noteId + "/open"))
|
.attr("src", openService.getUrlForDownload("api/notes/" + this.noteId + "/open-partial"))
|
||||||
.attr("type", this.note.mime)
|
.attr("type", this.note.mime)
|
||||||
.css("width", this.$widget.width());
|
.css("width", this.$widget.width());
|
||||||
}
|
}
|
||||||
|
@ -164,11 +164,6 @@ div.ui-tooltip {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: 5px;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* .search-inactive is added to search window <webview> when the window
|
* .search-inactive is added to search window <webview> when the window
|
||||||
* is inactive.
|
* is inactive.
|
||||||
@ -761,9 +756,14 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 8px 14px;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.alert-warning, .alert-info {
|
.alert-warning, .alert-info {
|
||||||
color: var(--main-text-color) !important;
|
color: var(--main-text-color) !important;
|
||||||
background-color: var(--accented-background-color) !important;
|
background-color: transparent !important;
|
||||||
border-color: var(--main-border-color) !important;
|
border-color: var(--main-border-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +107,11 @@ function processContent(images, note, content) {
|
|||||||
const filename = path.basename(src);
|
const filename = path.basename(src);
|
||||||
|
|
||||||
if (!dataUrl || !dataUrl.startsWith("data:image")) {
|
if (!dataUrl || !dataUrl.startsWith("data:image")) {
|
||||||
log.info("Image could not be recognized as data URL:", dataUrl.substr(0, Math.min(100, dataUrl.length)));
|
const excerpt = dataUrl
|
||||||
|
? dataUrl.substr(0, Math.min(100, dataUrl.length))
|
||||||
|
: "null";
|
||||||
|
|
||||||
|
log.info("Image could not be recognized as data URL: " + excerpt);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,7 +144,7 @@ function processContent(images, note, content) {
|
|||||||
function openNote(req) {
|
function openNote(req) {
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
ws.sendMessageToAllClients({
|
ws.sendMessageToAllClients({
|
||||||
type: 'open-note',
|
type: 'openNote',
|
||||||
noteId: req.params.noteId
|
noteId: req.params.noteId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,10 +3,13 @@
|
|||||||
const protectedSessionService = require('../../services/protected_session');
|
const protectedSessionService = require('../../services/protected_session');
|
||||||
const repository = require('../../services/repository');
|
const repository = require('../../services/repository');
|
||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
|
const log = require('../../services/log');
|
||||||
const noteRevisionService = require('../../services/note_revisions');
|
const noteRevisionService = require('../../services/note_revisions');
|
||||||
const tmp = require('tmp');
|
const tmp = require('tmp');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { Readable } = require('stream');
|
const { Readable } = require('stream');
|
||||||
|
const chokidar = require('chokidar');
|
||||||
|
const ws = require('../../services/ws');
|
||||||
|
|
||||||
function updateFile(req) {
|
function updateFile(req) {
|
||||||
const {noteId} = req.params;
|
const {noteId} = req.params;
|
||||||
@ -120,6 +123,19 @@ function saveToTmpDir(req) {
|
|||||||
fs.writeSync(tmpObj.fd, note.getContent());
|
fs.writeSync(tmpObj.fd, note.getContent());
|
||||||
fs.closeSync(tmpObj.fd);
|
fs.closeSync(tmpObj.fd);
|
||||||
|
|
||||||
|
log.info(`Saved temporary file for note ${noteId} into ${tmpObj.name}`);
|
||||||
|
|
||||||
|
if (utils.isElectron()) {
|
||||||
|
chokidar.watch(tmpObj.name).on('change', (path, stats) => {
|
||||||
|
ws.sendMessageToAllClients({
|
||||||
|
type: 'openedFileUpdated',
|
||||||
|
noteId: noteId,
|
||||||
|
lastModifiedMs: stats.atimeMs,
|
||||||
|
filePath: tmpObj.name
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tmpFilePath: tmpObj.name
|
tmpFilePath: tmpObj.name
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,8 @@ const sql = require('../../services/sql');
|
|||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
const TaskContext = require('../../services/task_context');
|
const TaskContext = require('../../services/task_context');
|
||||||
|
const fs = require('fs');
|
||||||
|
const noteRevisionService = require("../../services/note_revisions.js");
|
||||||
|
|
||||||
function getNote(req) {
|
function getNote(req) {
|
||||||
const noteId = req.params.noteId;
|
const noteId = req.params.noteId;
|
||||||
@ -80,7 +82,7 @@ function deleteNote(req) {
|
|||||||
function undeleteNote(req) {
|
function undeleteNote(req) {
|
||||||
const note = repository.getNote(req.params.noteId);
|
const note = repository.getNote(req.params.noteId);
|
||||||
|
|
||||||
const taskContext = TaskContext.getInstance(utils.randomString(10), 'undelete-notes');
|
const taskContext = TaskContext.getInstance(utils.randomString(10), 'undeleteNotes');
|
||||||
|
|
||||||
noteService.undeleteNote(note, note.deleteId, taskContext);
|
noteService.undeleteNote(note, note.deleteId, taskContext);
|
||||||
|
|
||||||
@ -109,7 +111,7 @@ function protectNote(req) {
|
|||||||
const protect = !!parseInt(req.params.isProtected);
|
const protect = !!parseInt(req.params.isProtected);
|
||||||
const includingSubTree = !!parseInt(req.query.subtree);
|
const includingSubTree = !!parseInt(req.query.subtree);
|
||||||
|
|
||||||
const taskContext = new TaskContext(utils.randomString(10), 'protect-notes', {protect});
|
const taskContext = new TaskContext(utils.randomString(10), 'protectNotes', {protect});
|
||||||
|
|
||||||
noteService.protectNoteRecursively(note, protect, includingSubTree, taskContext);
|
noteService.protectNoteRecursively(note, protect, includingSubTree, taskContext);
|
||||||
|
|
||||||
@ -273,6 +275,29 @@ function getDeleteNotesPreview(req) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uploadModifiedFile(req) {
|
||||||
|
const noteId = req.params.noteId;
|
||||||
|
const {filePath} = req.body;
|
||||||
|
|
||||||
|
const note = repository.getNote(noteId);
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
return [404, `Note ${noteId} has not been found`];
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`Updating note ${noteId} with content from ${filePath}`);
|
||||||
|
|
||||||
|
noteRevisionService.createNoteRevision(note);
|
||||||
|
|
||||||
|
const fileContent = fs.readFileSync(filePath);
|
||||||
|
|
||||||
|
if (!fileContent) {
|
||||||
|
return [400, `File ${fileContent} is empty`];
|
||||||
|
}
|
||||||
|
|
||||||
|
note.setContent(fileContent);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getNote,
|
getNote,
|
||||||
updateNote,
|
updateNote,
|
||||||
@ -286,5 +311,6 @@ module.exports = {
|
|||||||
changeTitle,
|
changeTitle,
|
||||||
duplicateSubtree,
|
duplicateSubtree,
|
||||||
eraseDeletedNotesNow,
|
eraseDeletedNotesNow,
|
||||||
getDeleteNotesPreview
|
getDeleteNotesPreview,
|
||||||
|
uploadModifiedFile
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
const utils = require('../services/utils');
|
const utils = require('../services/utils');
|
||||||
const optionService = require('../services/options');
|
const optionService = require('../services/options');
|
||||||
const myScryptService = require('../services/my_scrypt');
|
const myScryptService = require('../services/my_scrypt');
|
||||||
|
const log = require('../services/log');
|
||||||
|
|
||||||
function loginPage(req, res) {
|
function loginPage(req, res) {
|
||||||
res.render('login', { failedAuth: false });
|
res.render('login', { failedAuth: false });
|
||||||
@ -28,6 +29,9 @@ function login(req, res) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// note that logged IP address is usually meaningless since the traffic should come from a reverse proxy
|
||||||
|
log.info(`WARNING: Wrong username / password from ${req.ip}, rejecting.`);
|
||||||
|
|
||||||
res.render('login', {'failedAuth': true});
|
res.render('login', {'failedAuth': true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,6 +165,7 @@ function register(app) {
|
|||||||
apiRoute(POST, '/api/notes/erase-deleted-notes-now', notesApiRoute.eraseDeletedNotesNow);
|
apiRoute(POST, '/api/notes/erase-deleted-notes-now', notesApiRoute.eraseDeletedNotesNow);
|
||||||
apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle);
|
apiRoute(PUT, '/api/notes/:noteId/change-title', notesApiRoute.changeTitle);
|
||||||
apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateSubtree);
|
apiRoute(POST, '/api/notes/:noteId/duplicate/:parentNoteId', notesApiRoute.duplicateSubtree);
|
||||||
|
apiRoute(POST, '/api/notes/:noteId/upload-modified-file', notesApiRoute.uploadModifiedFile);
|
||||||
|
|
||||||
apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate);
|
apiRoute(GET, '/api/edited-notes/:date', noteRevisionsApiRoute.getEditedNotesOnDate);
|
||||||
|
|
||||||
@ -177,14 +178,15 @@ function register(app) {
|
|||||||
route(PUT, '/api/notes/:noteId/file', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware],
|
route(PUT, '/api/notes/:noteId/file', [auth.checkApiAuthOrElectron, uploadMiddleware, csrfMiddleware],
|
||||||
filesRoute.updateFile, apiResultHandler);
|
filesRoute.updateFile, apiResultHandler);
|
||||||
|
|
||||||
route(GET, '/api/notes/:noteId/open', [auth.checkApiAuthOrElectron],
|
route(GET, '/api/notes/:noteId/open', [auth.checkApiAuthOrElectron], filesRoute.openFile);
|
||||||
|
route(GET, '/api/notes/:noteId/open-partial', [auth.checkApiAuthOrElectron],
|
||||||
createPartialContentHandler(filesRoute.fileContentProvider, {
|
createPartialContentHandler(filesRoute.fileContentProvider, {
|
||||||
debug: (string, extra) => { console.log(string, extra); }
|
debug: (string, extra) => { console.log(string, extra); }
|
||||||
}));
|
}));
|
||||||
route(GET, '/api/notes/:noteId/download', [auth.checkApiAuthOrElectron], filesRoute.downloadFile);
|
route(GET, '/api/notes/:noteId/download', [auth.checkApiAuthOrElectron], filesRoute.downloadFile);
|
||||||
// this "hacky" path is used for easier referencing of CSS resources
|
// this "hacky" path is used for easier referencing of CSS resources
|
||||||
route(GET, '/api/notes/download/:noteId', [auth.checkApiAuthOrElectron], filesRoute.downloadFile);
|
route(GET, '/api/notes/download/:noteId', [auth.checkApiAuthOrElectron], filesRoute.downloadFile);
|
||||||
apiRoute(POST, '/api/notes/:noteId/saveToTmpDir', filesRoute.saveToTmpDir);
|
apiRoute(POST, '/api/notes/:noteId/save-to-tmp-dir', filesRoute.saveToTmpDir);
|
||||||
|
|
||||||
apiRoute(GET, '/api/notes/:noteId/attributes', attributesRoute.getEffectiveNoteAttributes);
|
apiRoute(GET, '/api/notes/:noteId/attributes', attributesRoute.getEffectiveNoteAttributes);
|
||||||
apiRoute(POST, '/api/notes/:noteId/attributes', attributesRoute.addNoteAttribute);
|
apiRoute(POST, '/api/notes/:noteId/attributes', attributesRoute.addNoteAttribute);
|
||||||
|
Binary file not shown.
@ -1 +1 @@
|
|||||||
module.exports = { buildDate:"2021-04-11T22:29:56+02:00", buildRevision: "58e4bd4974275a113c50e4ed7a554987921d55fc" };
|
module.exports = { buildDate:"2021-04-19T22:43:03+02:00", buildRevision: "6136243d6117910b80feafad4fc7121ecc42d794" };
|
||||||
|
@ -36,6 +36,12 @@ async function importOpml(taskContext, fileBuffer, parentNote) {
|
|||||||
if (opmlVersion === 1) {
|
if (opmlVersion === 1) {
|
||||||
title = outline.$.title;
|
title = outline.$.title;
|
||||||
content = toHtml(outline.$.text);
|
content = toHtml(outline.$.text);
|
||||||
|
|
||||||
|
if (!title || !title.trim()) {
|
||||||
|
// https://github.com/zadam/trilium/issues/1862
|
||||||
|
title = outline.$.text;
|
||||||
|
content = '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (opmlVersion === 2) {
|
else if (opmlVersion === 2) {
|
||||||
title = outline.$.text;
|
title = outline.$.text;
|
||||||
|
@ -352,6 +352,12 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
|||||||
defaultShortcuts: [],
|
defaultShortcuts: [],
|
||||||
scope: "window"
|
scope: "window"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
actionName: "openNoteExternally",
|
||||||
|
defaultShortcuts: [],
|
||||||
|
description: "Open note as a file with default application",
|
||||||
|
scope: "window"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
actionName: "renderActiveNote",
|
actionName: "renderActiveNote",
|
||||||
defaultShortcuts: [],
|
defaultShortcuts: [],
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const Expression = require('./expression');
|
const Expression = require('./expression');
|
||||||
|
const TrueExp = require("./true.js");
|
||||||
|
|
||||||
class AndExp extends Expression {
|
class AndExp extends Expression {
|
||||||
static of(subExpressions) {
|
static of(subExpressions) {
|
||||||
@ -10,6 +11,8 @@ class AndExp extends Expression {
|
|||||||
return subExpressions[0];
|
return subExpressions[0];
|
||||||
} else if (subExpressions.length > 0) {
|
} else if (subExpressions.length > 0) {
|
||||||
return new AndExp(subExpressions);
|
return new AndExp(subExpressions);
|
||||||
|
} else {
|
||||||
|
return new TrueExp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const Expression = require('./expression');
|
const Expression = require('./expression');
|
||||||
const NoteSet = require('../note_set');
|
const NoteSet = require('../note_set');
|
||||||
|
const TrueExp = require("./true");
|
||||||
|
|
||||||
class OrExp extends Expression {
|
class OrExp extends Expression {
|
||||||
static of(subExpressions) {
|
static of(subExpressions) {
|
||||||
@ -13,6 +14,9 @@ class OrExp extends Expression {
|
|||||||
else if (subExpressions.length > 0) {
|
else if (subExpressions.length > 0) {
|
||||||
return new OrExp(subExpressions);
|
return new OrExp(subExpressions);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
return new TrueExp();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(subExpressions) {
|
constructor(subExpressions) {
|
||||||
|
@ -28,17 +28,33 @@ class OrderByAndLimitExp extends Expression {
|
|||||||
let valA = valueExtractor.extract(a);
|
let valA = valueExtractor.extract(a);
|
||||||
let valB = valueExtractor.extract(b);
|
let valB = valueExtractor.extract(b);
|
||||||
|
|
||||||
if (!isNaN(valA) && !isNaN(valB)) {
|
if (valA === null && valB === null) {
|
||||||
|
// neither has attribute at all
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (valB === null) {
|
||||||
|
return smaller;
|
||||||
|
}
|
||||||
|
else if (valA === null) {
|
||||||
|
return larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if both are numbers then parse them for numerical comparison
|
||||||
|
// beware that isNaN will return false for empty string and null
|
||||||
|
if (valA.trim() !== "" && valB.trim() !== "" && !isNaN(valA) && !isNaN(valB)) {
|
||||||
valA = parseFloat(valA);
|
valA = parseFloat(valA);
|
||||||
valB = parseFloat(valB);
|
valB = parseFloat(valB);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (valA < valB) {
|
if (!valA && !valB) {
|
||||||
|
// the attribute is not defined in either note so continue to next order definition
|
||||||
|
continue;
|
||||||
|
} else if (!valB || valA < valB) {
|
||||||
return smaller;
|
return smaller;
|
||||||
} else if (valA > valB) {
|
} else if (!valA || valA > valB) {
|
||||||
return larger;
|
return larger;
|
||||||
}
|
}
|
||||||
// else go to next order definition
|
// else the values are equal and continue to next order definition
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
11
src/services/search/expressions/true.js
Normal file
11
src/services/search/expressions/true.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const Expression = require('./expression');
|
||||||
|
|
||||||
|
class TrueExp extends Expression {
|
||||||
|
execute(inputNoteSet, executionContext) {
|
||||||
|
return inputNoteSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TrueExp;
|
@ -329,6 +329,9 @@ function getExpression(tokens, searchContext, level = 0) {
|
|||||||
else if (op === 'or') {
|
else if (op === 'or') {
|
||||||
return OrExp.of(expressions);
|
return OrExp.of(expressions);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
throw new Error(`Unrecognized op=${op}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < tokens.length; i++) {
|
for (i = 0; i < tokens.length; i++) {
|
||||||
@ -358,8 +361,7 @@ function getExpression(tokens, searchContext, level = 0) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
exp.subExpression = getAggregateExpression();
|
exp.subExpression = getAggregateExpression();console.log(exp);
|
||||||
|
|
||||||
return exp;
|
return exp;
|
||||||
}
|
}
|
||||||
else if (token === 'not') {
|
else if (token === 'not') {
|
||||||
|
@ -69,7 +69,7 @@ class ValueExtractor {
|
|||||||
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
else if (pathEl in PROP_MAPPING) {
|
else if (pathEl in PROP_MAPPING || pathEl === 'random') {
|
||||||
if (i !== this.propertyPath.length - 1) {
|
if (i !== this.propertyPath.length - 1) {
|
||||||
return `${pathEl} is a terminal property specifier and must be at the end`;
|
return `${pathEl} is a terminal property specifier and must be at the end`;
|
||||||
}
|
}
|
||||||
@ -113,6 +113,9 @@ class ValueExtractor {
|
|||||||
else if (cur() === 'children') {
|
else if (cur() === 'children') {
|
||||||
cursor = cursor.children[0];
|
cursor = cursor.children[0];
|
||||||
}
|
}
|
||||||
|
else if (cur() === 'random') {
|
||||||
|
return Math.random();
|
||||||
|
}
|
||||||
else if (cur() in PROP_MAPPING) {
|
else if (cur() in PROP_MAPPING) {
|
||||||
return cursor[PROP_MAPPING[cur()]];
|
return cursor[PROP_MAPPING[cur()]];
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@ async function sync() {
|
|||||||
if (e.message &&
|
if (e.message &&
|
||||||
(e.message.includes('ECONNREFUSED') ||
|
(e.message.includes('ECONNREFUSED') ||
|
||||||
e.message.includes('ERR_CONNECTION_REFUSED') ||
|
e.message.includes('ERR_CONNECTION_REFUSED') ||
|
||||||
|
e.message.includes('ERR_ADDRESS_UNREACHABLE') ||
|
||||||
e.message.includes('Bad Gateway'))) {
|
e.message.includes('Bad Gateway'))) {
|
||||||
|
|
||||||
ws.syncFailed();
|
ws.syncFailed();
|
||||||
|
@ -38,7 +38,7 @@ class TaskContext {
|
|||||||
this.lastSentCountTs = Date.now();
|
this.lastSentCountTs = Date.now();
|
||||||
|
|
||||||
ws.sendMessageToAllClients({
|
ws.sendMessageToAllClients({
|
||||||
type: 'task-progress-count',
|
type: 'taskProgressCount',
|
||||||
taskId: this.taskId,
|
taskId: this.taskId,
|
||||||
taskType: this.taskType,
|
taskType: this.taskType,
|
||||||
data: this.data,
|
data: this.data,
|
||||||
@ -49,7 +49,7 @@ class TaskContext {
|
|||||||
|
|
||||||
reportError(message) {
|
reportError(message) {
|
||||||
ws.sendMessageToAllClients({
|
ws.sendMessageToAllClients({
|
||||||
type: 'task-error',
|
type: 'taskError',
|
||||||
taskId: this.taskId,
|
taskId: this.taskId,
|
||||||
taskType: this.taskType,
|
taskType: this.taskType,
|
||||||
data: this.data,
|
data: this.data,
|
||||||
@ -59,7 +59,7 @@ class TaskContext {
|
|||||||
|
|
||||||
taskSucceeded(result) {
|
taskSucceeded(result) {
|
||||||
ws.sendMessageToAllClients({
|
ws.sendMessageToAllClients({
|
||||||
type: 'task-succeeded',
|
type: 'taskSucceeded',
|
||||||
taskId: this.taskId,
|
taskId: this.taskId,
|
||||||
taskType: this.taskType,
|
taskType: this.taskType,
|
||||||
data: this.data,
|
data: this.data,
|
||||||
|
@ -188,6 +188,8 @@ function formatDownloadTitle(filename, type, mime) {
|
|||||||
filename = "untitled";
|
filename = "untitled";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filename = sanitize(filename);
|
||||||
|
|
||||||
if (type === 'text') {
|
if (type === 'text') {
|
||||||
return filename + '.html';
|
return filename + '.html';
|
||||||
} else if (['relation-map', 'search'].includes(type)) {
|
} else if (['relation-map', 'search'].includes(type)) {
|
||||||
|
@ -18,12 +18,12 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><kbd>UP</kbd>, <kbd>DOWN</kbd> - go up/down in the list of notes</li>
|
<li><kbd>UP</kbd>, <kbd>DOWN</kbd> - go up/down in the list of notes</li>
|
||||||
<li><kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - collapse/expand node</li>
|
<li><kbd>LEFT</kbd>, <kbd>RIGHT</kbd> - collapse/expand node</li>
|
||||||
<li><kbd data-command="backInNoteHistory"></kbd>, <kbd data-command="forwardInNoteHistory"></kbd> - go back / forwards in the history</li>
|
<li><kbd data-command="backInNoteHistory">not set</kbd>, <kbd data-command="forwardInNoteHistory">not set</kbd> - go back / forwards in the history</li>
|
||||||
<li><kbd data-command="jumpToNote"></kbd> - show <a class="external" href="https://github.com/zadam/trilium/wiki/Note-navigation#jump-to-note">"Jump to" dialog</a></li>
|
<li><kbd data-command="jumpToNote">not set</kbd> - show <a class="external" href="https://github.com/zadam/trilium/wiki/Note-navigation#jump-to-note">"Jump to" dialog</a></li>
|
||||||
<li><kbd data-command="scrollToActiveNote"></kbd> - scroll to active note</li>
|
<li><kbd data-command="scrollToActiveNote">not set</kbd> - scroll to active note</li>
|
||||||
<li><kbd>Backspace</kbd> - jump to parent note</li>
|
<li><kbd>Backspace</kbd> - jump to parent note</li>
|
||||||
<li><kbd data-command="collapseTree"></kbd> - collapse whole note tree</li>
|
<li><kbd data-command="collapseTree">not set</kbd> - collapse whole note tree</li>
|
||||||
<li><kbd data-command="collapseSubtree"></kbd> - collapse sub-tree</li>
|
<li><kbd data-command="collapseSubtree">not set</kbd> - collapse sub-tree</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -40,10 +40,10 @@
|
|||||||
|
|
||||||
Only in desktop (electron build):
|
Only in desktop (electron build):
|
||||||
<ul>
|
<ul>
|
||||||
<li><kbd data-command="openNewTab"></kbd> open empty tab</li>
|
<li><kbd data-command="openNewTab">not set</kbd> open empty tab</li>
|
||||||
<li><kbd data-command="closeActiveTab"></kbd> close active tab</li>
|
<li><kbd data-command="closeActiveTab">not set</kbd> close active tab</li>
|
||||||
<li><kbd data-command="activateNextTab"></kbd> activate next tab</li>
|
<li><kbd data-command="activateNextTab">not set</kbd> activate next tab</li>
|
||||||
<li><kbd data-command="activatePreviousTab"></kbd> activate previous tab</li>
|
<li><kbd data-command="activatePreviousTab">not set</kbd> activate previous tab</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -55,9 +55,9 @@
|
|||||||
|
|
||||||
<p class="card-text">
|
<p class="card-text">
|
||||||
<ul>
|
<ul>
|
||||||
<li><kbd data-command="createNoteAfter"></kbd> - create new note after the active note</li>
|
<li><kbd data-command="createNoteAfter">not set</kbd> - create new note after the active note</li>
|
||||||
<li><kbd data-command="createNoteInto"></kbd> - create new sub-note into active note</li>
|
<li><kbd data-command="createNoteInto">not set</kbd> - create new sub-note into active note</li>
|
||||||
<li><kbd data-command="editBranchPrefix"></kbd> - edit <a class="external" href="https://github.com/zadam/trilium/wiki/Tree concepts#prefix">prefix</a> of active note clone</li>
|
<li><kbd data-command="editBranchPrefix">not set</kbd> - edit <a class="external" href="https://github.com/zadam/trilium/wiki/Tree concepts#prefix">prefix</a> of active note clone</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -69,15 +69,15 @@
|
|||||||
|
|
||||||
<p class="card-text">
|
<p class="card-text">
|
||||||
<ul>
|
<ul>
|
||||||
<li><kbd data-command="moveNoteUp"></kbd>, <kbd data-command="moveNoteDown"></kbd> - move note up/down in the note list</li>
|
<li><kbd data-command="moveNoteUp">not set</kbd>, <kbd data-command="moveNoteDown">not set</kbd> - move note up/down in the note list</li>
|
||||||
<li><kbd data-command="moveNoteUpInHierarchy"></kbd>, <kbd data-command="moveNoteDownInHierarchy"></kbd> - move note up in the hierarchy</li>
|
<li><kbd data-command="moveNoteUpInHierarchy">not set</kbd>, <kbd data-command="moveNoteDownInHierarchy">not set</kbd> - move note up in the hierarchy</li>
|
||||||
<li><kbd data-command="addNoteAboveToSelection"></kbd>, <kbd data-command="addNoteBelowToSelection"></kbd> - multi-select note above/below</li>
|
<li><kbd data-command="addNoteAboveToSelection">not set</kbd>, <kbd data-command="addNoteBelowToSelection">not set</kbd> - multi-select note above/below</li>
|
||||||
<li><kbd data-command="selectAllNotesInParent"></kbd> - select all notes in the current level</li>
|
<li><kbd data-command="selectAllNotesInParent">not set</kbd> - select all notes in the current level</li>
|
||||||
<li><kbd>Shift+click</kbd> - select note</li>
|
<li><kbd>Shift+click</kbd> - select note</li>
|
||||||
<li><kbd data-command="copyNotesToClipboard"></kbd> - copy active note (or current selection) into clipboard (used for <a class="external" href="https://github.com/zadam/trilium/wiki/Cloning notes">cloning</a>)</li>
|
<li><kbd data-command="copyNotesToClipboard">not set</kbd> - copy active note (or current selection) into clipboard (used for <a class="external" href="https://github.com/zadam/trilium/wiki/Cloning notes">cloning</a>)</li>
|
||||||
<li><kbd data-command="cutNotesToClipboard"></kbd> - cut current (or current selection) note into clipboard (used for moving notes)</li>
|
<li><kbd data-command="cutNotesToClipboard">not set</kbd> - cut current (or current selection) note into clipboard (used for moving notes)</li>
|
||||||
<li><kbd data-command="pasteNotesFromClipboard"></kbd> - paste note(s) as sub-note into active note (which is either move or clone depending on whether it was copied or cut into clipboard)</li>
|
<li><kbd data-command="pasteNotesFromClipboard">not set</kbd> - paste note(s) as sub-note into active note (which is either move or clone depending on whether it was copied or cut into clipboard)</li>
|
||||||
<li><kbd data-command="deleteNotes"></kbd> - delete note / sub-tree</li>
|
<li><kbd data-command="deleteNotes">not set</kbd> - delete note / sub-tree</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -89,12 +89,12 @@
|
|||||||
|
|
||||||
<p class="card-text">
|
<p class="card-text">
|
||||||
<ul>
|
<ul>
|
||||||
<li><kbd data-command="editNoteTitle"></kbd> in tree pane will switch from tree pane into note title. Enter from note title will switch focus to text editor.
|
<li><kbd data-command="editNoteTitle">not set</kbd> in tree pane will switch from tree pane into note title. Enter from note title will switch focus to text editor.
|
||||||
<kbd data-command="scrollToActiveNote"></kbd> will switch back from editor to tree pane.</li>
|
<kbd data-command="scrollToActiveNote">not set</kbd> will switch back from editor to tree pane.</li>
|
||||||
<li><kbd>Ctrl+K</kbd> - create / edit external link</li>
|
<li><kbd>Ctrl+K</kbd> - create / edit external link</li>
|
||||||
<li><kbd data-command="addLinkToText"></kbd> - create internal link</li>
|
<li><kbd data-command="addLinkToText">not set</kbd> - create internal link</li>
|
||||||
<li><kbd data-command="insertDateTimeToText"></kbd> - insert current date and time at caret position</li>
|
<li><kbd data-command="insertDateTimeToText">not set</kbd> - insert current date and time at caret position</li>
|
||||||
<li><kbd data-command="scrollToActiveNote"></kbd> - jump away to the tree pane and scroll to active note</li>
|
<li><kbd data-command="scrollToActiveNote">not set</kbd> - jump away to the tree pane and scroll to active note</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -121,9 +121,9 @@
|
|||||||
|
|
||||||
<p class="card-text">
|
<p class="card-text">
|
||||||
<ul>
|
<ul>
|
||||||
<li><kbd data-command="reloadFrontendApp"></kbd> - reload Trilium frontend</li>
|
<li><kbd data-command="reloadFrontendApp">not set</kbd> - reload Trilium frontend</li>
|
||||||
<li><kbd data-command="openDevTools"></kbd> - show developer tools</li>
|
<li><kbd data-command="openDevTools">not set</kbd> - show developer tools</li>
|
||||||
<li><kbd data-command="showSQLConsole"></kbd> - show SQL console</li>
|
<li><kbd data-command="showSQLConsole">not set</kbd> - show SQL console</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -135,9 +135,9 @@
|
|||||||
|
|
||||||
<p class="card-text">
|
<p class="card-text">
|
||||||
<ul>
|
<ul>
|
||||||
<li><kbd data-command="toggleZenMode"></kbd> - Zen mode - display only note editor, everything else is hidden</li>
|
<li><kbd data-command="toggleZenMode">not set</kbd> - Zen mode - display only note editor, everything else is hidden</li>
|
||||||
<li><kbd data-command="searchNotes"></kbd> - toggle search form in tree pane</li>
|
<li><kbd data-command="quickSearch">not set</kbd> - focus on quick search input</li>
|
||||||
<li><kbd data-command="findInText"></kbd> - in page search</li>
|
<li><kbd data-command="findInText">not set</kbd> - in page search</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
11
src/www
11
src/www
@ -8,10 +8,13 @@ process.on('unhandledRejection', error => {
|
|||||||
require('./services/log').info(error);
|
require('./services/log').info(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('SIGINT', function() {
|
function exit() {
|
||||||
console.log("Caught interrupt signal. Exiting.");
|
console.log("Caught interrupt/termination signal. Exiting.");
|
||||||
process.exit();
|
process.exit(0);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
process.on('SIGINT', exit);
|
||||||
|
process.on('SIGTERM', exit);
|
||||||
|
|
||||||
const { app, sessionParser } = require('./app');
|
const { app, sessionParser } = require('./app');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
@ -10,9 +10,12 @@
|
|||||||
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/src/public/app-dist" />
|
<excludeFolder url="file://$MODULE_DIR$/src/public/app-dist" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/libraries" />
|
<excludeFolder url="file://$MODULE_DIR$/libraries" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/libraries" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/docs" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/bin/better-sqlite3" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
<orderEntry type="library" name="@types/jquery" level="application" />
|
<orderEntry type="library" name="@types/jquery" level="application" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user