Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b0a3f828fb | |||
| 76f5736255 | |||
| a82066d899 | |||
| 45c5287d53 | |||
| dce54c7af3 | |||
| ee15db0ae1 | |||
| c48dbb0913 | |||
| 6f32d6fabe | |||
| 1e123f2390 | |||
| b29155775e | |||
| b821ed28fc | |||
| fdb8959aa1 | |||
| 7554cb057b | |||
| fab959539a | |||
| afe44a6fe8 | |||
| 7ed526beb7 | |||
| af695802e3 | |||
| 156f040880 | |||
| cf6f04defb |
BIN
Binary file not shown.
@@ -959,6 +959,7 @@ class Note extends Entity {
|
||||
|
||||
delete pojo.isContentAvailable;
|
||||
delete pojo.__attributeCache;
|
||||
delete pojo.__ownedAttributeCache;
|
||||
delete pojo.content;
|
||||
/** zero references to contentHash, probably can be removed */
|
||||
delete pojo.contentHash;
|
||||
|
||||
@@ -4975,6 +4975,90 @@ Internally this serializes the anonymous function into string and sends it to ba
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h4 class="name" id="waitUntilSynced"><span class="type-signature"></span>waitUntilSynced<span class="signature">()</span><span class="type-signature"></span></h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dl class="details">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<dt class="tag-source">Source:</dt>
|
||||
<dd class="tag-source"><ul class="dummy"><li>
|
||||
<a href="services_frontend_script_api.js.html">services/frontend_script_api.js</a>, <a href="services_frontend_script_api.js.html#line389">line 389</a>
|
||||
</li></ul></dd>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class Branch {
|
||||
|
||||
/** @returns {NoteShort} */
|
||||
async getNote() {
|
||||
return await this.treeCache.getNote(this.noteId);
|
||||
return this.treeCache.getNote(this.noteId);
|
||||
}
|
||||
|
||||
/** @returns {boolean} true if it's top level, meaning its parent is root note */
|
||||
|
||||
@@ -410,6 +410,11 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
|
||||
* @param {function} handler
|
||||
*/
|
||||
this.bindGlobalShortcut = utils.bindGlobalShortcut;
|
||||
|
||||
/**
|
||||
* @method
|
||||
*/
|
||||
this.waitUntilSynced = ws.waitForMaxKnownSyncId;
|
||||
}
|
||||
|
||||
export default FrontendScriptApi;</code></pre>
|
||||
|
||||
Generated
+7
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.37.8",
|
||||
"version": "0.38.0-beta",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -6996,9 +6996,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
|
||||
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
@@ -9307,9 +9307,9 @@
|
||||
}
|
||||
},
|
||||
"sqlite3": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.1.0.tgz",
|
||||
"integrity": "sha512-RvqoKxq+8pDHsJo7aXxsFR18i+dU2Wp5o12qAJOV5LNcDt+fgJsc2QKKg3sIRfXrN9ZjzY1T7SNe/DFVqAXjaw==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.1.1.tgz",
|
||||
"integrity": "sha512-CvT5XY+MWnn0HkbwVKJAyWEMfzpAPwnTiB3TobA5Mri44SrTovmmh499NPQP+gatkeOipqPlBLel7rn4E/PCQg==",
|
||||
"requires": {
|
||||
"nan": "^2.12.1",
|
||||
"node-pre-gyp": "^0.11.0",
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.38.0-beta",
|
||||
"version": "0.38.1-beta",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@@ -68,7 +68,7 @@
|
||||
"session-file-store": "1.3.1",
|
||||
"simple-node-logger": "18.12.23",
|
||||
"sqlite": "3.0.3",
|
||||
"sqlite3": "4.1.0",
|
||||
"sqlite3": "4.1.1",
|
||||
"string-similarity": "3.0.0",
|
||||
"tar-stream": "2.1.0",
|
||||
"turndown": "5.0.3",
|
||||
|
||||
@@ -931,6 +931,7 @@ class Note extends Entity {
|
||||
|
||||
delete pojo.isContentAvailable;
|
||||
delete pojo.__attributeCache;
|
||||
delete pojo.__ownedAttributeCache;
|
||||
delete pojo.content;
|
||||
/** zero references to contentHash, probably can be removed */
|
||||
delete pojo.contentHash;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import server from "../services/server.js";
|
||||
import utils from "../services/utils.js";
|
||||
|
||||
const $dialog = $("#backend-log-dialog");
|
||||
const $backendLogTextArea = $("#backend-log-textarea");
|
||||
const $refreshBackendLog = $("#refresh-backend-log-button");
|
||||
|
||||
export async function showDialog() {
|
||||
utils.closeActiveDialog();
|
||||
|
||||
glob.activeDialog = $dialog;
|
||||
|
||||
$dialog.modal();
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
$backendLogTextArea.scrollTop($backendLogTextArea[0].scrollHeight);
|
||||
}
|
||||
|
||||
async function load() {
|
||||
const backendLog = await server.get('backend-log');
|
||||
|
||||
$backendLogTextArea.text(backendLog);
|
||||
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
$refreshBackendLog.on('click', load);
|
||||
|
||||
$dialog.on('shown.bs.modal', scrollToBottom);
|
||||
@@ -26,6 +26,10 @@ async function convertMarkdownToHtml(text) {
|
||||
}
|
||||
|
||||
export async function importMarkdownInline() {
|
||||
if (noteDetailService.getActiveTabNoteType() !== 'text') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const {clipboard} = require('electron');
|
||||
const text = clipboard.readText();
|
||||
|
||||
@@ -13,6 +13,10 @@ const TPL = `
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h4>Consistency checks</h4>
|
||||
|
||||
<button id="find-and-fix-consistency-issues-button" class="btn">Find and fix consistency issues</button><br/><br/>
|
||||
|
||||
<h4>Debugging</h4>
|
||||
|
||||
<button id="anonymize-button" class="btn">Save anonymized database</button><br/><br/>
|
||||
@@ -33,9 +37,8 @@ export default class AdvancedOptions {
|
||||
this.$forceFullSyncButton = $("#force-full-sync-button");
|
||||
this.$fillSyncRowsButton = $("#fill-sync-rows-button");
|
||||
this.$anonymizeButton = $("#anonymize-button");
|
||||
this.$cleanupSoftDeletedButton = $("#cleanup-soft-deleted-items-button");
|
||||
this.$cleanupUnusedImagesButton = $("#cleanup-unused-images-button");
|
||||
this.$vacuumDatabaseButton = $("#vacuum-database-button");
|
||||
this.$findAndFixConsistencyIssuesButton = $("#find-and-fix-consistency-issues-button");
|
||||
|
||||
this.$forceFullSyncButton.on('click', async () => {
|
||||
await server.post('sync/force-full-sync');
|
||||
@@ -55,26 +58,16 @@ export default class AdvancedOptions {
|
||||
toastService.showMessage("Created anonymized database");
|
||||
});
|
||||
|
||||
this.$cleanupSoftDeletedButton.on('click', async () => {
|
||||
if (confirm("Do you really want to clean up soft-deleted items?")) {
|
||||
await server.post('cleanup/cleanup-soft-deleted-items');
|
||||
|
||||
toastService.showMessage("Soft deleted items have been cleaned up");
|
||||
}
|
||||
});
|
||||
|
||||
this.$cleanupUnusedImagesButton.on('click', async () => {
|
||||
if (confirm("Do you really want to clean up unused images?")) {
|
||||
await server.post('cleanup/cleanup-unused-images');
|
||||
|
||||
toastService.showMessage("Unused images have been cleaned up");
|
||||
}
|
||||
});
|
||||
|
||||
this.$vacuumDatabaseButton.on('click', async () => {
|
||||
await server.post('cleanup/vacuum-database');
|
||||
|
||||
toastService.showMessage("Database has been vacuumed");
|
||||
});
|
||||
|
||||
this.$findAndFixConsistencyIssuesButton.on('click', async () => {
|
||||
await server.post('cleanup/find-and-fix-consistency-issues');
|
||||
|
||||
toastService.showMessage("Consistency issues should be fixed.");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,8 @@ import utils from "../services/utils.js";
|
||||
const $dialog = $("#sql-console-dialog");
|
||||
const $query = $('#sql-console-query');
|
||||
const $executeButton = $('#sql-console-execute');
|
||||
const $resultHead = $('#sql-console-results thead');
|
||||
const $resultBody = $('#sql-console-results tbody');
|
||||
const $tables = $("#sql-console-tables");
|
||||
const $tableSchemas = $("#sql-console-table-schemas");
|
||||
const $resultContainer = $("#result-container");
|
||||
|
||||
let codeEditor;
|
||||
|
||||
@@ -19,7 +18,7 @@ export async function showDialog() {
|
||||
|
||||
glob.activeDialog = $dialog;
|
||||
|
||||
await showTables();
|
||||
await showTableSchemas();
|
||||
|
||||
$dialog.modal();
|
||||
}
|
||||
@@ -45,6 +44,10 @@ async function initEditor() {
|
||||
|
||||
codeEditor.setOption("mode", "text/x-sqlite");
|
||||
CodeMirror.autoLoadMode(codeEditor, "sql");
|
||||
|
||||
codeEditor.setValue(`SELECT title, isProtected, type, mime FROM notes WHERE noteId = 'root';
|
||||
---
|
||||
SELECT noteId, parentNoteId, notePosition, prefix FROM branches WHERE branchId = 'root';`);
|
||||
}
|
||||
|
||||
codeEditor.focus();
|
||||
@@ -70,55 +73,66 @@ async function execute() {
|
||||
toastService.showMessage("Query was executed successfully.");
|
||||
}
|
||||
|
||||
const rows = result.rows;
|
||||
const results = result.results;
|
||||
|
||||
$resultHead.empty();
|
||||
$resultBody.empty();
|
||||
$resultContainer.empty();
|
||||
|
||||
for (const rows of results) {
|
||||
if (rows.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const $table = $('<table class="table table-striped">');
|
||||
$resultContainer.append($table);
|
||||
|
||||
if (rows.length > 0) {
|
||||
const result = rows[0];
|
||||
const rowEl = $("<tr>");
|
||||
const $row = $("<tr>");
|
||||
|
||||
for (const key in result) {
|
||||
rowEl.append($("<th>").html(key));
|
||||
$row.append($("<th>").html(key));
|
||||
}
|
||||
|
||||
$resultHead.append(rowEl);
|
||||
}
|
||||
$table.append($row);
|
||||
|
||||
for (const result of rows) {
|
||||
const rowEl = $("<tr>");
|
||||
for (const result of rows) {
|
||||
const $row = $("<tr>");
|
||||
|
||||
for (const key in result) {
|
||||
rowEl.append($("<td>").html(result[key]));
|
||||
for (const key in result) {
|
||||
$row.append($("<td>").html(result[key]));
|
||||
}
|
||||
|
||||
$table.append($row);
|
||||
}
|
||||
|
||||
$resultBody.append(rowEl);
|
||||
}
|
||||
}
|
||||
|
||||
async function showTables() {
|
||||
async function showTableSchemas() {
|
||||
const tables = await server.get('sql/schema');
|
||||
|
||||
$tables.empty();
|
||||
$tableSchemas.empty();
|
||||
|
||||
for (const table of tables) {
|
||||
const $tableLink = $('<button class="btn">').text(table.name);
|
||||
|
||||
const $columns = $("<table>");
|
||||
const $columns = $("<ul>");
|
||||
|
||||
for (const column of table.columns) {
|
||||
$columns.append(
|
||||
$("<tr>")
|
||||
.append($("<td>").text(column.name))
|
||||
.append($("<td>").text(column.type))
|
||||
$("<li>")
|
||||
.append($("<span>").text(column.name))
|
||||
.append($("<span>").text(column.type))
|
||||
);
|
||||
}
|
||||
|
||||
$tables.append($tableLink).append(" ");
|
||||
$tableSchemas.append($tableLink).append(" ");
|
||||
|
||||
$tableLink
|
||||
.tooltip({html: true, title: $columns.html()})
|
||||
.tooltip({
|
||||
html: true,
|
||||
placement: 'bottom',
|
||||
boundary: 'window',
|
||||
title: $columns[0].outerHTML
|
||||
})
|
||||
.on('click', () => codeEditor.setValue("SELECT * FROM " + table.name + " LIMIT 100"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class Branch {
|
||||
|
||||
/** @returns {NoteShort} */
|
||||
async getNote() {
|
||||
return await this.treeCache.getNote(this.noteId);
|
||||
return this.treeCache.getNote(this.noteId);
|
||||
}
|
||||
|
||||
/** @returns {boolean} true if it's top level, meaning its parent is root note */
|
||||
|
||||
@@ -17,6 +17,7 @@ const JUMP_TO_NOTE = "../dialogs/jump_to_note.js";
|
||||
const NOTE_SOURCE = "../dialogs/note_source.js";
|
||||
const RECENT_CHANGES = "../dialogs/recent_changes.js";
|
||||
const SQL_CONSOLE = "../dialogs/sql_console.js";
|
||||
const BACKEND_LOG = "../dialogs/backend_log.js";
|
||||
const ATTRIBUTES = "../dialogs/attributes.js";
|
||||
const HELP = "../dialogs/help.js";
|
||||
const NOTE_INFO = "../dialogs/note_info.js";
|
||||
@@ -95,6 +96,10 @@ function registerEntrypoints() {
|
||||
$("#open-sql-console-button").on('click', showSqlConsoleDialog);
|
||||
keyboardActionService.setGlobalActionHandler("ShowSQLConsole", showSqlConsoleDialog);
|
||||
|
||||
const showBackendLogDialog = () => import(BACKEND_LOG).then(d => d.showDialog());
|
||||
$("#show-backend-log-button").on('click', showBackendLogDialog);
|
||||
keyboardActionService.setGlobalActionHandler("ShowBackendLog", showBackendLogDialog);
|
||||
|
||||
$("#show-about-dialog-button").on('click', () => import(ABOUT).then(d => d.showDialog()));
|
||||
|
||||
if (utils.isElectron()) {
|
||||
@@ -127,6 +132,12 @@ function registerEntrypoints() {
|
||||
|
||||
$("#open-dev-tools-button").toggle(utils.isElectron());
|
||||
|
||||
keyboardActionService.setGlobalActionHandler("PasteMarkdownIntoText", async () => {
|
||||
const dialog = await import("../dialogs/markdown_import.js");
|
||||
|
||||
dialog.importMarkdownInline();
|
||||
});
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const openDevTools = () => {
|
||||
require('electron').remote.getCurrentWindow().toggleDevTools();
|
||||
|
||||
@@ -382,6 +382,17 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, tabConte
|
||||
* @param {function} handler
|
||||
*/
|
||||
this.bindGlobalShortcut = utils.bindGlobalShortcut;
|
||||
|
||||
/**
|
||||
* Trilium runs in backend and frontend process, when something is changed on the backend from script,
|
||||
* frontend will get asynchronously synchronized.
|
||||
*
|
||||
* This method returns a promise which resolves once all the backend -> frontend synchronization is finished.
|
||||
* Typical use case is when new note has been created, we should wait until it is synced into frontend and only then activate it.
|
||||
*
|
||||
* @method
|
||||
*/
|
||||
this.waitUntilSynced = ws.waitForMaxKnownSyncId;
|
||||
}
|
||||
|
||||
export default FrontendScriptApi;
|
||||
@@ -55,18 +55,21 @@ class NoteDetailCode {
|
||||
this.onNoteChange(() => this.ctx.noteChanged());
|
||||
}
|
||||
|
||||
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
|
||||
// we provide fallback
|
||||
this.codeEditor.setValue(this.ctx.note.content || "");
|
||||
// lazy loading above can take time and tab might have been already switched to another note
|
||||
if (this.ctx.note && this.ctx.note.type === 'code') {
|
||||
// CodeMirror breaks pretty badly on null so even though it shouldn't happen (guarded by consistency check)
|
||||
// we provide fallback
|
||||
this.codeEditor.setValue(this.ctx.note.content || "");
|
||||
|
||||
const info = CodeMirror.findModeByMIME(this.ctx.note.mime);
|
||||
const info = CodeMirror.findModeByMIME(this.ctx.note.mime);
|
||||
|
||||
if (info) {
|
||||
this.codeEditor.setOption("mode", info.mime);
|
||||
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
|
||||
if (info) {
|
||||
this.codeEditor.setOption("mode", info.mime);
|
||||
CodeMirror.autoLoadMode(this.codeEditor, info.mode);
|
||||
}
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
show() {
|
||||
|
||||
@@ -238,14 +238,17 @@ class NoteDetailRelationMap {
|
||||
|
||||
await libraryLoader.requireLibrary(libraryLoader.RELATION_MAP);
|
||||
|
||||
this.loadMapData();
|
||||
|
||||
jsPlumb.ready(() => {
|
||||
this.initJsPlumbInstance();
|
||||
// lazy loading above can take time and tab might have been already switched to another note
|
||||
if (this.ctx.note && this.ctx.note.type === 'relation-map') {
|
||||
this.loadMapData();
|
||||
|
||||
this.initPanZoom();
|
||||
this.initJsPlumbInstance();
|
||||
|
||||
this.loadNotesAndRelations();
|
||||
this.initPanZoom();
|
||||
|
||||
this.loadNotesAndRelations();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,40 @@ import treeService from './tree.js';
|
||||
import noteAutocompleteService from './note_autocomplete.js';
|
||||
import mimeTypesService from './mime_types.js';
|
||||
|
||||
const mentionSetup = {
|
||||
feeds: [
|
||||
{
|
||||
marker: '@',
|
||||
feed: queryText => {
|
||||
return new Promise((res, rej) => {
|
||||
noteAutocompleteService.autocompleteSource(queryText, rows => {
|
||||
if (rows.length === 1 && rows[0].title === 'No results') {
|
||||
rows = [];
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
row.text = row.name = row.noteTitle;
|
||||
row.id = '@' + row.text;
|
||||
row.link = '#' + row.path;
|
||||
}
|
||||
|
||||
res(rows);
|
||||
});
|
||||
});
|
||||
},
|
||||
itemRenderer: item => {
|
||||
const itemElement = document.createElement('span');
|
||||
|
||||
itemElement.classList.add('mentions-item');
|
||||
itemElement.innerHTML = `${item.highlightedTitle} `;
|
||||
|
||||
return itemElement;
|
||||
},
|
||||
minimumCharacters: 0
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
class NoteDetailText {
|
||||
/**
|
||||
* @param {TabContext} ctx
|
||||
@@ -34,6 +68,16 @@ class NoteDetailText {
|
||||
if (!this.textEditor) {
|
||||
await libraryLoader.requireLibrary(libraryLoader.CKEDITOR);
|
||||
|
||||
const codeBlockLanguages =
|
||||
(await mimeTypesService.getMimeTypes())
|
||||
.filter(mt => mt.enabled)
|
||||
.map(mt => {
|
||||
return {
|
||||
language: mt.mime.toLowerCase().replace(/[\W_]+/g,"-"),
|
||||
label: mt.title
|
||||
}
|
||||
});
|
||||
|
||||
// CKEditor since version 12 needs the element to be visible before initialization. At the same time
|
||||
// we want to avoid flicker - i.e. show editor only once everything is ready. That's why we have separate
|
||||
// display of $component in both branches.
|
||||
@@ -42,53 +86,9 @@ class NoteDetailText {
|
||||
// textEditor might have been initialized during previous await so checking again
|
||||
// looks like double initialization can freeze CKEditor pretty badly
|
||||
if (!this.textEditor) {
|
||||
const codeBlockLanguages =
|
||||
(await mimeTypesService.getMimeTypes())
|
||||
.filter(mt => mt.enabled)
|
||||
.map(mt => {
|
||||
return {
|
||||
language: mt.mime.toLowerCase().replace(/[\W_]+/g,"-"),
|
||||
label: mt.title
|
||||
}
|
||||
});
|
||||
|
||||
this.textEditor = await BalloonEditor.create(this.$editorEl[0], {
|
||||
placeholder: "Type the content of your note here ...",
|
||||
mention: {
|
||||
feeds: [
|
||||
{
|
||||
marker: '@',
|
||||
feed: queryText => {
|
||||
return new Promise((res, rej) => {
|
||||
noteAutocompleteService.autocompleteSource(queryText, rows => {
|
||||
if (rows.length === 1 && rows[0].title === 'No results') {
|
||||
rows = [];
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
row.text = row.name = row.noteTitle;
|
||||
row.id = '@' + row.text;
|
||||
row.link = '#' + row.path;
|
||||
}
|
||||
|
||||
console.log(rows.slice(0, Math.min(5, rows.length)));
|
||||
|
||||
res(rows);
|
||||
});
|
||||
});
|
||||
},
|
||||
itemRenderer: item => {
|
||||
const itemElement = document.createElement('span');
|
||||
|
||||
itemElement.classList.add('mentions-item');
|
||||
itemElement.innerHTML = `${item.highlightedTitle} `;
|
||||
|
||||
return itemElement;
|
||||
},
|
||||
minimumCharacters: 0
|
||||
}
|
||||
]
|
||||
},
|
||||
mention: mentionSetup,
|
||||
codeBlock: {
|
||||
languages: codeBlockLanguages
|
||||
}
|
||||
@@ -98,11 +98,14 @@ class NoteDetailText {
|
||||
}
|
||||
}
|
||||
|
||||
this.textEditor.isReadOnly = await this.isReadOnly();
|
||||
// lazy loading above can take time and tab might have been already switched to another note
|
||||
if (this.ctx.note && this.ctx.note.type === 'text') {
|
||||
this.textEditor.isReadOnly = await this.isReadOnly();
|
||||
|
||||
this.$component.show();
|
||||
this.$component.show();
|
||||
|
||||
this.textEditor.setData(this.ctx.note.content);
|
||||
this.textEditor.setData(this.ctx.note.content);
|
||||
}
|
||||
}
|
||||
|
||||
getContent() {
|
||||
|
||||
@@ -42,12 +42,16 @@ async function remove(url, headers = {}) {
|
||||
let i = 1;
|
||||
const reqResolves = {};
|
||||
|
||||
let maxKnownSyncId = 0;
|
||||
|
||||
async function call(method, url, data, headers = {}) {
|
||||
let resp;
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const ipc = require('electron').ipcRenderer;
|
||||
const requestId = i++;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
resp = await new Promise((resolve, reject) => {
|
||||
reqResolves[requestId] = resolve;
|
||||
|
||||
if (REQUEST_LOGGING_ENABLED) {
|
||||
@@ -64,32 +68,58 @@ async function call(method, url, data, headers = {}) {
|
||||
});
|
||||
}
|
||||
else {
|
||||
return await ajax(url, method, data, headers);
|
||||
resp = await ajax(url, method, data, headers);
|
||||
}
|
||||
|
||||
const maxSyncIdStr = resp.headers['trilium-max-sync-id'];
|
||||
|
||||
if (maxSyncIdStr && maxSyncIdStr.trim()) {
|
||||
maxKnownSyncId = Math.max(maxKnownSyncId, parseInt(maxSyncIdStr));
|
||||
}
|
||||
|
||||
return resp.body;
|
||||
}
|
||||
|
||||
async function ajax(url, method, data, headers) {
|
||||
const options = {
|
||||
url: baseApiUrl + url,
|
||||
type: method,
|
||||
headers: getHeaders(headers),
|
||||
timeout: 60000
|
||||
};
|
||||
function ajax(url, method, data, headers) {
|
||||
return new Promise((res, rej) => {
|
||||
const options = {
|
||||
url: baseApiUrl + url,
|
||||
type: method,
|
||||
headers: getHeaders(headers),
|
||||
timeout: 60000,
|
||||
success: (body, textStatus, jqXhr) => {
|
||||
const respHeaders = {};
|
||||
|
||||
if (data) {
|
||||
try {
|
||||
options.data = JSON.stringify(data);
|
||||
}
|
||||
catch (e) {
|
||||
console.log("Can't stringify data: ", data, " because of error: ", e)
|
||||
}
|
||||
options.contentType = "application/json";
|
||||
}
|
||||
jqXhr.getAllResponseHeaders().trim().split(/[\r\n]+/).forEach(line => {
|
||||
const parts = line.split(': ');
|
||||
const header = parts.shift();
|
||||
respHeaders[header] = parts.join(': ');
|
||||
});
|
||||
|
||||
return await $.ajax(options).catch(e => {
|
||||
const message = "Error when calling " + method + " " + url + ": " + e.status + " - " + e.statusText;
|
||||
toastService.showError(message);
|
||||
toastService.throwError(message);
|
||||
res({
|
||||
body,
|
||||
headers: respHeaders
|
||||
});
|
||||
},
|
||||
error: (jqXhr, textStatus, error) => {
|
||||
const message = "Error when calling " + method + " " + url + ": " + textStatus + " - " + error;
|
||||
toastService.showError(message);
|
||||
toastService.throwError(message);
|
||||
|
||||
rej(error);
|
||||
}
|
||||
};
|
||||
|
||||
if (data) {
|
||||
try {
|
||||
options.data = JSON.stringify(data);
|
||||
} catch (e) {
|
||||
console.log("Can't stringify data: ", data, " because of error: ", e)
|
||||
}
|
||||
options.contentType = "application/json";
|
||||
}
|
||||
|
||||
$.ajax(options);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -101,7 +131,10 @@ if (utils.isElectron()) {
|
||||
console.log(utils.now(), "Response #" + arg.requestId + ": " + arg.statusCode);
|
||||
}
|
||||
|
||||
reqResolves[arg.requestId](arg.body);
|
||||
reqResolves[arg.requestId]({
|
||||
body: arg.body,
|
||||
headers: arg.headers
|
||||
});
|
||||
|
||||
delete reqResolves[arg.requestId];
|
||||
});
|
||||
@@ -114,5 +147,6 @@ export default {
|
||||
remove,
|
||||
ajax,
|
||||
// don't remove, used from CKEditor image upload!
|
||||
getHeaders
|
||||
getHeaders,
|
||||
getMaxKnownSyncId: () => maxKnownSyncId
|
||||
};
|
||||
@@ -65,6 +65,11 @@ async function getIcon(note) {
|
||||
|
||||
async function prepareNode(branch) {
|
||||
const note = await branch.getNote();
|
||||
|
||||
if (!note) {
|
||||
console.log(`Branch has no note: ${branch}`);
|
||||
}
|
||||
|
||||
const title = (branch.prefix ? (branch.prefix + " - ") : "") + note.title;
|
||||
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||
|
||||
|
||||
@@ -161,6 +161,11 @@ class TreeCache {
|
||||
/** @return {Promise<NoteShort>} */
|
||||
async getNote(noteId, silentNotFoundError = false) {
|
||||
if (noteId === 'none') {
|
||||
console.log(`No 'none' note.`);
|
||||
return null;
|
||||
}
|
||||
else if (!noteId) {
|
||||
console.log(`Falsy noteId ${noteId}, returning null.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import utils from './utils.js';
|
||||
import toastService from "./toast.js";
|
||||
import server from "./server.js";
|
||||
|
||||
const $outstandingSyncsCount = $("#outstanding-syncs-count");
|
||||
|
||||
@@ -71,8 +72,6 @@ async function handleMessage(event) {
|
||||
// finish and set to null to signal somebody else can pick it up
|
||||
consumeQueuePromise = null;
|
||||
}
|
||||
|
||||
checkSyncIdListeners();
|
||||
}
|
||||
else if (message.type === 'sync-hash-check-failed') {
|
||||
toastService.showError("Sync check failed!", 60000);
|
||||
@@ -98,6 +97,10 @@ function waitForSyncId(desiredSyncId) {
|
||||
});
|
||||
}
|
||||
|
||||
function waitForMaxKnownSyncId() {
|
||||
return waitForSyncId(server.getMaxKnownSyncId());
|
||||
}
|
||||
|
||||
function checkSyncIdListeners() {
|
||||
syncIdReachedListeners
|
||||
.filter(l => l.desiredSyncId <= lastProcessedSyncId)
|
||||
@@ -129,6 +132,8 @@ async function consumeSyncData() {
|
||||
|
||||
lastProcessedSyncId = Math.max(lastProcessedSyncId, allSyncData[allSyncData.length - 1].id);
|
||||
}
|
||||
|
||||
checkSyncIdListeners();
|
||||
}
|
||||
|
||||
function connectWebSocket() {
|
||||
@@ -193,5 +198,6 @@ export default {
|
||||
subscribeToMessages,
|
||||
subscribeToAllSyncMessages,
|
||||
subscribeToOutsideSyncMessages,
|
||||
waitForSyncId
|
||||
waitForSyncId,
|
||||
waitForMaxKnownSyncId
|
||||
};
|
||||
@@ -794,7 +794,7 @@ div[data-notify="container"] {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#sql-console-tables button {
|
||||
#sql-console-table-schemas button {
|
||||
padding: 0.25rem 0.4rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 0.5;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require('fs');
|
||||
const dateUtils = require('../../services/date_utils');
|
||||
const {LOG_DIR} = require('../../services/data_dir.js');
|
||||
|
||||
async function getBackendLog() {
|
||||
const file = `${LOG_DIR}/trilium-${dateUtils.localNowDate()}.log`;
|
||||
|
||||
return fs.readFileSync(file, 'utf8');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getBackendLog
|
||||
};
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
const sql = require('../../services/sql');
|
||||
const log = require('../../services/log');
|
||||
const consistencyChecksService = require('../../services/consistency_checks');
|
||||
|
||||
async function vacuumDatabase() {
|
||||
await sql.execute("VACUUM");
|
||||
@@ -9,6 +10,11 @@ async function vacuumDatabase() {
|
||||
log.info("Database has been vacuumed.");
|
||||
}
|
||||
|
||||
async function findAndFixConsistencyIssues() {
|
||||
await consistencyChecksService.runOnDemandChecks(true);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
vacuumDatabase
|
||||
vacuumDatabase,
|
||||
findAndFixConsistencyIssues
|
||||
};
|
||||
@@ -34,7 +34,8 @@ async function addClipping(req) {
|
||||
clippingNote = (await noteService.createNewNote({
|
||||
parentNoteId: todayNote.noteId,
|
||||
title: title,
|
||||
content: ''
|
||||
content: '',
|
||||
type: 'text'
|
||||
})).note;
|
||||
|
||||
await clippingNote.setLabel('clipType', 'clippings');
|
||||
@@ -58,7 +59,8 @@ async function createNote(req) {
|
||||
const {note} = await noteService.createNewNote({
|
||||
parentNoteId: todayNote.noteId,
|
||||
title,
|
||||
content
|
||||
content,
|
||||
type: 'text'
|
||||
});
|
||||
|
||||
await note.setLabel('clipType', clipType);
|
||||
|
||||
+12
-2
@@ -17,12 +17,22 @@ async function getSchema() {
|
||||
}
|
||||
|
||||
async function execute(req) {
|
||||
const query = req.body.query;
|
||||
const queries = req.body.query.split("\n---");
|
||||
|
||||
try {
|
||||
const results = [];
|
||||
|
||||
for (const query of queries) {
|
||||
if (!query.trim()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
results.push(await sql.getRows(query));
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
rows: await sql.getRows(query)
|
||||
results
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
@@ -12,10 +12,14 @@ function init(app) {
|
||||
}
|
||||
};
|
||||
|
||||
const respHeaders = {};
|
||||
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
getHeader: () => {},
|
||||
setHeader: () => {},
|
||||
getHeader: name => respHeaders[name],
|
||||
setHeader: (name, value) => {
|
||||
respHeaders[name] = value.toString();
|
||||
},
|
||||
status: statusCode => {
|
||||
res.statusCode = statusCode;
|
||||
return res;
|
||||
@@ -24,6 +28,7 @@ function init(app) {
|
||||
event.sender.send('server-response', {
|
||||
requestId: arg.requestId,
|
||||
statusCode: res.statusCode,
|
||||
headers: respHeaders,
|
||||
body: obj
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ const linkMapRoute = require('./api/link_map');
|
||||
const clipperRoute = require('./api/clipper');
|
||||
const similarNotesRoute = require('./api/similar_notes');
|
||||
const keysRoute = require('./api/keys');
|
||||
const backendLogRoute = require('./api/backend_log');
|
||||
|
||||
const log = require('../services/log');
|
||||
const express = require('express');
|
||||
@@ -43,6 +44,7 @@ const auth = require('../services/auth');
|
||||
const cls = require('../services/cls');
|
||||
const sql = require('../services/sql');
|
||||
const protectedSessionService = require('../services/protected_session');
|
||||
const syncTableService = require('../services/sync_table');
|
||||
const csurf = require('csurf');
|
||||
|
||||
const csrfMiddleware = csurf({
|
||||
@@ -51,6 +53,8 @@ const csrfMiddleware = csurf({
|
||||
});
|
||||
|
||||
function apiResultHandler(req, res, result) {
|
||||
res.setHeader('trilium-max-sync-id', syncTableService.getMaxSyncId());
|
||||
|
||||
// if it's an array and first element is integer then we consider this to be [statusCode, response] format
|
||||
if (Array.isArray(result) && result.length > 0 && Number.isInteger(result[0])) {
|
||||
const [statusCode, response] = result;
|
||||
@@ -211,10 +215,11 @@ function register(app) {
|
||||
apiRoute(POST, '/api/sql/execute', sqlRoute.execute);
|
||||
apiRoute(POST, '/api/anonymization/anonymize', anonymizationRoute.anonymize);
|
||||
|
||||
apiRoute(POST, '/api/cleanup/cleanup-unused-images', cleanupRoute.cleanupUnusedImages);
|
||||
// VACUUM requires execution outside of transaction
|
||||
route(POST, '/api/cleanup/vacuum-database', [auth.checkApiAuthOrElectron, csrfMiddleware], cleanupRoute.vacuumDatabase, apiResultHandler, false);
|
||||
|
||||
route(POST, '/api/cleanup/find-and-fix-consistency-issues', [auth.checkApiAuthOrElectron, csrfMiddleware], cleanupRoute.findAndFixConsistencyIssues, apiResultHandler, false);
|
||||
|
||||
apiRoute(POST, '/api/script/exec', scriptRoute.exec);
|
||||
apiRoute(POST, '/api/script/run/:noteId', scriptRoute.run);
|
||||
apiRoute(GET, '/api/script/startup', scriptRoute.getStartupBundles);
|
||||
@@ -247,6 +252,8 @@ function register(app) {
|
||||
apiRoute(GET, '/api/keyboard-actions', keysRoute.getKeyboardActions);
|
||||
apiRoute(GET, '/api/keyboard-shortcuts-for-notes', keysRoute.getShortcutsForNotes);
|
||||
|
||||
apiRoute(GET, '/api/backend-log', backendLogRoute.getBackendLog);
|
||||
|
||||
app.use('', router);
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2019-12-04T22:40:31+01:00", buildRevision: "e0aabe4f9cb37b68dd70bbba6eae5dcc5893f075" };
|
||||
module.exports = { buildDate:"2019-12-10T23:09:02+01:00", buildRevision: "76f5736255251afe0e94b07236dc5c183384242f" };
|
||||
|
||||
@@ -113,6 +113,11 @@ module.exports = function(filters, selectedColumns = 'notes.*') {
|
||||
condition = "NOT(" + condition + ")";
|
||||
}
|
||||
|
||||
if (['text', 'title', 'content'].includes(filter.name)) {
|
||||
// for title/content search does not make sense to search for protected notes
|
||||
condition = `(${condition} AND notes.isProtected = 0)`;
|
||||
}
|
||||
|
||||
where += condition;
|
||||
}
|
||||
else if ([">", ">=", "<", "<="].includes(filter.operator)) {
|
||||
|
||||
+593
-607
File diff suppressed because it is too large
Load Diff
@@ -225,6 +225,11 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
||||
defaultShortcuts: ["Alt+O"],
|
||||
description: "Shows SQL Console dialog"
|
||||
},
|
||||
{
|
||||
actionName: "ShowBackendLog",
|
||||
defaultShortcuts: [],
|
||||
description: "Shows Backend Log dialog"
|
||||
},
|
||||
{
|
||||
actionName: "ShowHelp",
|
||||
defaultShortcuts: ["F1"],
|
||||
@@ -245,6 +250,11 @@ const DEFAULT_KEYBOARD_ACTIONS = [
|
||||
actionName: "InsertDateTimeToText",
|
||||
defaultShortcuts: ["Alt+T"]
|
||||
},
|
||||
{
|
||||
actionName: "PasteMarkdownIntoText",
|
||||
defaultShortcuts: [],
|
||||
description: "Pastes Markdown from clipboard into text note"
|
||||
},
|
||||
|
||||
{
|
||||
separator: "Other"
|
||||
|
||||
@@ -82,7 +82,8 @@ const defaultOptions = [
|
||||
{ name: 'hideTabRowForOneTab', value: 'false', isSynced: false },
|
||||
{ name: 'imageMaxWidthHeight', value: '1200', isSynced: true },
|
||||
{ name: 'imageJpegQuality', value: '75', isSynced: true },
|
||||
{ name: 'autoFixConsistencyIssues', value: 'true', isSynced: false }
|
||||
{ name: 'autoFixConsistencyIssues', value: 'true', isSynced: false },
|
||||
{ name: 'codeNotesMimeTypes', value: '["text/x-csrc","text/x-c++src","text/x-csharp","text/css","text/x-go","text/x-groovy","text/x-haskell","text/html","message/http","text/x-java","application/javascript;env=frontend","application/javascript;env=backend","application/json","text/x-kotlin","text/x-markdown","text/x-perl","text/x-php","text/x-python","text/x-ruby",null,"text/x-sql","text/x-swift","text/xml","text/x-yaml"]', isSynced: true }
|
||||
];
|
||||
|
||||
async function initStartupOptions() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const dayjs = require("dayjs");
|
||||
|
||||
const filterRegex = /(\b(AND|OR)\s+)?@(!?)([\w_]+|"[^"]+")\s*((=|!=|<|<=|>|>=|!?\*=|!?=\*|!?\*=\*)\s*([\w_/-]+|"[^"]+"))?/ig;
|
||||
const filterRegex = /(\b(AND|OR)\s+)?@(!?)([\p{L}_]+|"[^"]+")\s*((=|!=|<|<=|>|>=|!?\*=|!?=\*|!?\*=\*)\s*([\p{L}_/-]+|"[^"]+"))?/igu;
|
||||
const smartValueRegex = /^(NOW|TODAY|WEEK|MONTH|YEAR) *([+\-] *\d+)?$/i;
|
||||
|
||||
function calculateSmartValue(v) {
|
||||
|
||||
@@ -21,6 +21,10 @@ async function addEntitySync(entityName, entityId, sourceId) {
|
||||
setTimeout(() => require('./ws').sendPingToAllClients(), 50);
|
||||
}
|
||||
|
||||
function getMaxSyncId() {
|
||||
return syncs.length === 0 ? 0 : syncs[syncs.length - 1].id;
|
||||
}
|
||||
|
||||
function getEntitySyncsNewerThan(syncId) {
|
||||
return syncs.filter(s => s.id > syncId);
|
||||
}
|
||||
@@ -96,5 +100,6 @@ module.exports = {
|
||||
addApiTokenSync: async (apiTokenId, sourceId) => await addEntitySync("api_tokens", apiTokenId, sourceId),
|
||||
addEntitySync,
|
||||
fillAllSyncRows,
|
||||
getEntitySyncsNewerThan
|
||||
getEntitySyncsNewerThan,
|
||||
getMaxSyncId
|
||||
};
|
||||
@@ -81,6 +81,12 @@
|
||||
<kbd data-kb-action="ShowSQLConsole"></kbd>
|
||||
</a>
|
||||
|
||||
<a class="dropdown-item" id="show-backend-log-button">
|
||||
<span class="bx bx-empty"></span>
|
||||
Show backend log
|
||||
<kbd data-kb-action="ShowBackendLog"></kbd>
|
||||
</a>
|
||||
|
||||
<a class="dropdown-item" id="reload-frontend-button" title="Reload can help with some visual glitches without restarting the whole app.">
|
||||
<span class="bx bx-empty"></span>
|
||||
Reload frontend
|
||||
@@ -187,6 +193,7 @@
|
||||
<% include dialogs/link_map.ejs %>
|
||||
<% include dialogs/clone_to.ejs %>
|
||||
<% include dialogs/move_to.ejs %>
|
||||
<% include dialogs/backend_log.ejs %>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<div id="backend-log-dialog" class="modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Backend log</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<textarea id="backend-log-textarea" readonly="readonly" style="min-height: 600px; width: 100%;"></textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="refresh-backend-log-button" class="btn btn-primary">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,5 +1,5 @@
|
||||
<div id="sql-console-dialog" class="modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" style="max-width: 1000px;" role="document">
|
||||
<div class="modal-dialog modal-dialog-scrollable modal-xl" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">SQL console</h5>
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="modal-body">
|
||||
<div>
|
||||
Tables:
|
||||
<span id="sql-console-tables"></span>
|
||||
<span id="sql-console-table-schemas"></span>
|
||||
</div>
|
||||
|
||||
<div id="sql-console-query"></div>
|
||||
@@ -19,11 +19,7 @@
|
||||
<button class="btn btn-danger" id="sql-console-execute">Execute <kbd>CTRL+ENTER</kbd></button>
|
||||
</div>
|
||||
|
||||
<div style="width: 100%; overflow: auto;"> <!-- This is necessary for the table to be scrollable -->
|
||||
<table id="sql-console-results" class="table table-striped">
|
||||
<thead></thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<div id="result-container" style="width: 100%; font-size: smaller; margin-top: 10px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>Trilium Notes</title>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="images/app-icons/ios/apple-touch-icon.png">
|
||||
|
||||
@@ -74,7 +74,28 @@ async function startTrilium() {
|
||||
|
||||
httpServer.keepAliveTimeout = 120000 * 5;
|
||||
httpServer.listen(usedPort, usedHost);
|
||||
httpServer.on('error', onError);
|
||||
httpServer.on('error', error => {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(`Port ${usedPort} requires elevated privileges`);
|
||||
process.exit(1);
|
||||
break;
|
||||
|
||||
case 'EADDRINUSE':
|
||||
console.error(`Port ${usedPort} is already in use`);
|
||||
process.exit(1);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
httpServer.on('listening', () => debug('Listening on port' + httpServer.address().port));
|
||||
|
||||
sqlInit.dbReady.then(() => ws.init(httpServer, sessionParser));
|
||||
@@ -86,29 +107,3 @@ async function startTrilium() {
|
||||
}
|
||||
|
||||
startTrilium();
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error('Port requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
|
||||
case 'EADDRINUSE':
|
||||
console.error('Port is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user