mirror of
https://github.com/zadam/trilium.git
synced 2025-11-03 03:46:37 +01:00
Compare commits
34 Commits
v0.27.2-be
...
v0.27.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b32addade | ||
|
|
0b251530fa | ||
|
|
f5b933149a | ||
|
|
48bbfb8bdb | ||
|
|
889971c4d6 | ||
|
|
0722494d41 | ||
|
|
4b977a3306 | ||
|
|
3ff3021acd | ||
|
|
99e56a9c42 | ||
|
|
77279dfe16 | ||
|
|
93f8050454 | ||
|
|
31cfede7a7 | ||
|
|
c8ec86e537 | ||
|
|
05aee884b6 | ||
|
|
012ba9e060 | ||
|
|
8e8fd88857 | ||
|
|
523ccdad6b | ||
|
|
ded3f605be | ||
|
|
030d12a465 | ||
|
|
4d15628840 | ||
|
|
81b849898c | ||
|
|
3824486b85 | ||
|
|
081ab00a0a | ||
|
|
04f6af5c9a | ||
|
|
4dc1f1f6eb | ||
|
|
3930a02123 | ||
|
|
3112de105e | ||
|
|
3b8d7b8fba | ||
|
|
9fca7f09a5 | ||
|
|
fd39d6b3a9 | ||
|
|
a103886ea5 | ||
|
|
373408e401 | ||
|
|
db44c1d8e6 | ||
|
|
95a34c9e2d |
7
.gitpod.yml
Normal file
7
.gitpod.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
tasks:
|
||||
- before: nvm install 10 && nvm use 10
|
||||
init: npm install
|
||||
command: npm run start
|
||||
ports:
|
||||
- port: 8080
|
||||
onOpen: open-preview
|
||||
@@ -17,6 +17,7 @@ RUN set -x \
|
||||
libtool \
|
||||
make \
|
||||
nasm \
|
||||
libpng-dev \
|
||||
&& npm install --production \
|
||||
&& apk del .build-dependencies
|
||||
|
||||
|
||||
13
README.md
13
README.md
@@ -18,6 +18,7 @@ Trilium Notes is a hierarchical note taking application with focus on building l
|
||||
* [Relation maps](https://github.com/zadam/trilium/wiki/Relation-map) for visualizing notes and their relations
|
||||
* [Scripting](https://github.com/zadam/trilium/wiki/Scripts) - see [Advanced showcases](https://github.com/zadam/trilium/wiki/Advanced-showcases)
|
||||
* Scales well in both usability and performance upwards of 100 000 notes
|
||||
* Touch optimized [mobile frontend](https://github.com/zadam/trilium/wiki/Mobile-frontend) for smartphones and tablets
|
||||
* [Night theme](https://github.com/zadam/trilium/wiki/Themes)
|
||||
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) and [Markdown import & export](https://github.com/zadam/trilium/wiki/Markdown)
|
||||
|
||||
@@ -34,3 +35,15 @@ Trilium is provided as either desktop application (Linux, Windows, Mac) or web a
|
||||
[See wiki for complete list of documentation pages.](https://github.com/zadam/trilium/wiki/)
|
||||
|
||||
You can also read [Patterns of personal knowledge base](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base) to get some inspiration on how you might use Trilium.
|
||||
|
||||
## Contribute
|
||||
|
||||
Use a browser based dev environment
|
||||
|
||||
[](https://gitpod.io/#https://github.com/zadam/trilium)
|
||||
|
||||
Or clone locally and run
|
||||
```
|
||||
npm install
|
||||
npm run start
|
||||
```
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.27.1-beta",
|
||||
"version": "0.27.2-beta",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.27.2-beta",
|
||||
"version": "0.27.4",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@@ -33,6 +33,7 @@
|
||||
"electron-in-page-search": "1.3.2",
|
||||
"express": "4.16.4",
|
||||
"express-session": "1.15.6",
|
||||
"file-type": "10.7.0",
|
||||
"fs-extra": "7.0.1",
|
||||
"get-port": "4.1.0",
|
||||
"helmet": "3.15.0",
|
||||
|
||||
@@ -56,6 +56,9 @@ class Note extends Entity {
|
||||
setContent(content) {
|
||||
this.content = content;
|
||||
|
||||
// if parsing below is not successful then there's no jsonContent as opposed to still having the old unupdated ones
|
||||
delete this.jsonContent;
|
||||
|
||||
try {
|
||||
this.jsonContent = JSON.parse(this.content);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import hoistedNoteService from './services/hoisted_note.js';
|
||||
import noteTypeService from './services/note_type.js';
|
||||
import linkService from './services/link.js';
|
||||
import noteAutocompleteService from './services/note_autocomplete.js';
|
||||
import macInit from './services/mac_init.js';
|
||||
|
||||
// required for CKEditor image upload plugin
|
||||
window.glob.getCurrentNode = treeService.getCurrentNode;
|
||||
@@ -110,28 +111,6 @@ if (utils.isElectron()) {
|
||||
});
|
||||
}
|
||||
|
||||
function exec(cmd) {
|
||||
document.execCommand(cmd);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (utils.isElectron() && utils.isMac()) {
|
||||
utils.bindShortcut('ctrl+c', () => exec("copy"));
|
||||
utils.bindShortcut('ctrl+v', () => exec('paste'));
|
||||
utils.bindShortcut('ctrl+x', () => exec('cut'));
|
||||
utils.bindShortcut('ctrl+a', () => exec('selectAll'));
|
||||
utils.bindShortcut('ctrl+z', () => exec('undo'));
|
||||
utils.bindShortcut('ctrl+y', () => exec('redo'));
|
||||
|
||||
utils.bindShortcut('meta+c', () => exec("copy"));
|
||||
utils.bindShortcut('meta+v', () => exec('paste'));
|
||||
utils.bindShortcut('meta+x', () => exec('cut'));
|
||||
utils.bindShortcut('meta+a', () => exec('selectAll'));
|
||||
utils.bindShortcut('meta+z', () => exec('undo'));
|
||||
utils.bindShortcut('meta+y', () => exec('redo'));
|
||||
}
|
||||
|
||||
$("#export-note-button").click(function () {
|
||||
if ($(this).hasClass("disabled")) {
|
||||
return;
|
||||
@@ -140,6 +119,8 @@ $("#export-note-button").click(function () {
|
||||
exportDialog.showDialog('single');
|
||||
});
|
||||
|
||||
macInit.init();
|
||||
|
||||
treeService.showTree();
|
||||
|
||||
entrypoints.registerEntrypoints();
|
||||
|
||||
@@ -6,8 +6,6 @@ const $dialog = $("#jump-to-note-dialog");
|
||||
const $autoComplete = $("#jump-to-note-autocomplete");
|
||||
const $showInFullTextButton = $("#show-in-full-text-button");
|
||||
|
||||
$dialog.on("shown.bs.modal", e => $autoComplete.focus());
|
||||
|
||||
async function showDialog() {
|
||||
glob.activeDialog = $dialog;
|
||||
|
||||
|
||||
@@ -117,10 +117,6 @@ function registerEntrypoints() {
|
||||
|
||||
utils.bindShortcut('ctrl+f', openInPageSearch);
|
||||
|
||||
if (utils.isMac()) {
|
||||
utils.bindShortcut('meta+f', openInPageSearch);
|
||||
}
|
||||
|
||||
// FIXME: do we really need these at this point?
|
||||
utils.bindShortcut("ctrl+shift+up", () => {
|
||||
const node = treeService.getCurrentNode();
|
||||
|
||||
25
src/public/javascripts/services/mac_init.js
Normal file
25
src/public/javascripts/services/mac_init.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Mac specific initialization
|
||||
*/
|
||||
import utils from "./utils.js";
|
||||
|
||||
function init() {
|
||||
if (utils.isElectron() && utils.isMac()) {
|
||||
utils.bindShortcut('meta+c', () => exec("copy"));
|
||||
utils.bindShortcut('meta+v', () => exec('paste'));
|
||||
utils.bindShortcut('meta+x', () => exec('cut'));
|
||||
utils.bindShortcut('meta+a', () => exec('selectAll'));
|
||||
utils.bindShortcut('meta+z', () => exec('undo'));
|
||||
utils.bindShortcut('meta+y', () => exec('redo'));
|
||||
}
|
||||
}
|
||||
|
||||
function exec(cmd) {
|
||||
document.execCommand(cmd);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default {
|
||||
init
|
||||
}
|
||||
@@ -28,7 +28,7 @@ function clearText($el) {
|
||||
function showRecentNotes($el) {
|
||||
$el.setSelectedPath("");
|
||||
$el.autocomplete("val", "");
|
||||
$el.autocomplete("open");
|
||||
$el.focus();
|
||||
}
|
||||
|
||||
function initNoteAutocomplete($el, options) {
|
||||
@@ -61,7 +61,13 @@ function initNoteAutocomplete($el, options) {
|
||||
|
||||
$clearTextButton.click(() => clearText($el));
|
||||
|
||||
$showRecentNotesButton.click(() => showRecentNotes($el));
|
||||
$showRecentNotesButton.click(e => {
|
||||
showRecentNotes($el);
|
||||
|
||||
// this will cause the click not give focus to the "show recent notes" button
|
||||
// this is important because otherwise input will lose focus immediatelly and not show the results
|
||||
return false;
|
||||
});
|
||||
|
||||
$goToSelectedNoteButton.click(() => {
|
||||
if ($el.hasClass("disabled")) {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import noteDetailService from "./note_detail.js";
|
||||
import treeService from "./tree.js";
|
||||
import infoService from './info.js';
|
||||
|
||||
const $searchString = $("#search-string");
|
||||
const $component = $('#note-detail-search');
|
||||
const $refreshButton = $('#note-detail-search-refresh-results-button');
|
||||
|
||||
function getContent() {
|
||||
JSON.stringify({
|
||||
return JSON.stringify({
|
||||
searchString: $searchString.val()
|
||||
});
|
||||
}
|
||||
@@ -25,6 +28,14 @@ function show() {
|
||||
$searchString.on('input', noteDetailService.noteChanged);
|
||||
}
|
||||
|
||||
$refreshButton.click(async () => {
|
||||
await noteDetailService.saveNoteIfChanged();
|
||||
|
||||
treeService.reload();
|
||||
|
||||
infoService.showMessage('Tree has been refreshed.');
|
||||
});
|
||||
|
||||
export default {
|
||||
getContent,
|
||||
show,
|
||||
|
||||
@@ -87,7 +87,7 @@ $searchInput.keyup(e => {
|
||||
if (e && e.which === $.ui.keyCode.ENTER) {
|
||||
doSearch();
|
||||
}
|
||||
}).focus();
|
||||
});
|
||||
|
||||
$doSearchButton.click(() => doSearch()); // keep long form because of argument
|
||||
$resetSearchButton.click(resetSearch);
|
||||
|
||||
@@ -94,9 +94,18 @@ async function expandToNote(notePath, expandOpts) {
|
||||
|
||||
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
||||
|
||||
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||
let hoistedNoteFound = false;
|
||||
|
||||
let parentNoteId = null;
|
||||
|
||||
for (const childNoteId of runPath) {
|
||||
if (childNoteId === hoistedNoteId) {
|
||||
hoistedNoteFound = true;
|
||||
}
|
||||
|
||||
// we expand only after hoisted note since before then nodes are not actually present in the tree
|
||||
if (hoistedNoteFound) {
|
||||
// for first node (!parentNoteId) it doesn't matter which node is found
|
||||
let node = getNode(childNoteId, parentNoteId);
|
||||
|
||||
@@ -117,10 +126,10 @@ async function expandToNote(notePath, expandOpts) {
|
||||
|
||||
if (childNoteId === noteId) {
|
||||
return node;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
await node.setExpanded(true, expandOpts);
|
||||
}
|
||||
}
|
||||
|
||||
parentNoteId = childNoteId;
|
||||
}
|
||||
@@ -129,9 +138,12 @@ async function expandToNote(notePath, expandOpts) {
|
||||
async function activateNote(notePath, noteLoadedListener) {
|
||||
utils.assertArguments(notePath);
|
||||
|
||||
// notePath argument can contain only noteId which is not good when hoisted since
|
||||
// then we need to check the whole note path
|
||||
const runNotePath = await getRunPath(notePath);
|
||||
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||
|
||||
if (hoistedNoteId !== 'root' && !notePath.includes(hoistedNoteId)) {
|
||||
if (hoistedNoteId !== 'root' && !runNotePath.includes(hoistedNoteId)) {
|
||||
if (!await confirmDialog.confirm("Requested note is outside of hoisted note subtree. Do you want to unhoist?")) {
|
||||
return;
|
||||
}
|
||||
@@ -352,6 +364,7 @@ function clearSelectedNodes() {
|
||||
}
|
||||
|
||||
async function treeInitialized() {
|
||||
// - is used in mobile to indicate that we don't want to activate any note after load
|
||||
if (startNotePath === '-') {
|
||||
return;
|
||||
}
|
||||
@@ -363,7 +376,6 @@ async function treeInitialized() {
|
||||
startNotePath = null;
|
||||
}
|
||||
|
||||
// - is used in mobile to indicate that we don't want to activate any note after load
|
||||
if (startNotePath) {
|
||||
const node = await activateNote(startNotePath);
|
||||
|
||||
@@ -438,6 +450,16 @@ function initFancyTree(tree) {
|
||||
|
||||
$span.append(unhoistButton);
|
||||
}
|
||||
},
|
||||
// this is done to automatically lazy load all expanded search notes after tree load
|
||||
loadChildren: function(event, data) {
|
||||
data.node.visit(function(subNode){
|
||||
// Load all lazy/unloaded child nodes
|
||||
// (which will trigger `loadChildren` recursively)
|
||||
if( subNode.isUndefined() && subNode.isExpanded() ) {
|
||||
subNode.load();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ async function prepareNode(branch) {
|
||||
extraClasses: await getExtraClasses(note),
|
||||
icon: await getIcon(note),
|
||||
refKey: note.noteId,
|
||||
expanded: (note.type !== 'search' && branch.isExpanded) || hoistedNoteId === note.noteId
|
||||
expanded: branch.isExpanded || hoistedNoteId === note.noteId
|
||||
};
|
||||
|
||||
if (note.hasChildren() || note.type === 'search') {
|
||||
|
||||
@@ -158,7 +158,11 @@ class TreeCache {
|
||||
return;
|
||||
}
|
||||
|
||||
treeCache.childParentToBranch[childNoteId + '-' + newParentNoteId] = treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId];
|
||||
const branchId = treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId];
|
||||
const branch = await this.getBranch(branchId);
|
||||
branch.parentNoteId = newParentNoteId;
|
||||
|
||||
treeCache.childParentToBranch[childNoteId + '-' + newParentNoteId] = branchId;
|
||||
delete treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId]; // this is correct because we know that oldParentId isn't same as newParentId
|
||||
|
||||
// remove old associations
|
||||
|
||||
@@ -137,6 +137,11 @@ function randomString(len) {
|
||||
|
||||
function bindShortcut(keyboardShortcut, handler) {
|
||||
if (isDesktop()) {
|
||||
if (isMac()) {
|
||||
// use CMD (meta) instead of CTRL for all shortcuts
|
||||
keyboardShortcut = keyboardShortcut.replace("ctrl", "meta");
|
||||
}
|
||||
|
||||
$(document).bind('keydown', keyboardShortcut, e => {
|
||||
handler();
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import utils from "./services/utils.js";
|
||||
import macInit from './services/mac_init.js';
|
||||
|
||||
macInit.init();
|
||||
|
||||
function SetupModel() {
|
||||
if (syncInProgress) {
|
||||
|
||||
@@ -45,15 +45,24 @@
|
||||
#header button {
|
||||
padding: 1px 5px 1px 5px;
|
||||
font-size: small;
|
||||
margin-bottom: 2px;
|
||||
margin-top: 2px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#history-navigation {
|
||||
margin: 0 15px 0 5px;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
#global-buttons {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 10px 0 10px 0;
|
||||
margin: 0 10px 0 16px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin: 0 20px 0 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
#context-menu-container {
|
||||
|
||||
@@ -2,6 +2,7 @@ body {
|
||||
/* Fix for CKEditor block gutter icon "stretching" body and causing scrollbar to appear after pressing enter
|
||||
on the last line of the editor. */
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#title-container {
|
||||
@@ -16,6 +17,16 @@ body {
|
||||
flex-grow: 100;
|
||||
}
|
||||
|
||||
ul.fancytree-container {
|
||||
/* override specific size from fancytree.css */
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
}
|
||||
|
||||
.fancytree-title {
|
||||
margin-left: 7px !important;
|
||||
}
|
||||
|
||||
.fancytree-node:not(.fancytree-loading) .fancytree-expander {
|
||||
background: none;
|
||||
width: auto;
|
||||
@@ -131,8 +142,21 @@ span.fancytree-node.fancytree-active-clone:not(.fancytree-active) .fancytree-tit
|
||||
|
||||
/* By default not focused active tree item is not easily visible, this makes it more visible */
|
||||
span.fancytree-active:not(.fancytree-focused) .fancytree-title {
|
||||
background-color: #eee !important;
|
||||
border-color: #ddd !important;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
span.fancytree-active.fancytree-focused .fancytree-title {
|
||||
background-color: #ddd !important;
|
||||
border-color: #555 !important;
|
||||
border-color: #bbb !important;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.fancytree-plain span.fancytree-node:hover span.fancytree-title {
|
||||
background-color: #eee !important;
|
||||
border-color: #bbb !important;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.ui-autocomplete {
|
||||
@@ -155,17 +179,6 @@ span.fancytree-active:not(.fancytree-focused) .fancytree-title {
|
||||
color: #337ab7 !important;
|
||||
}
|
||||
|
||||
#header-title {
|
||||
padding: 5px 20px 5px 10px;
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#header .btn-sm {
|
||||
margin-bottom: 2px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
div.ui-tooltip {
|
||||
max-width: 600px;
|
||||
max-height: 600px;
|
||||
@@ -377,13 +390,8 @@ div.ui-tooltip {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
#history-navigation {
|
||||
margin: 0 20px 0 5px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.btn:not(.btn-primary):not(.btn-danger) {
|
||||
border-color: #bbb;
|
||||
.btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger) {
|
||||
border-color: #ddd;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
@@ -544,7 +552,8 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
|
||||
max-height: 300px;
|
||||
overflow: hidden;
|
||||
color: black;
|
||||
border: 1px solid #aaa;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ const tarImportService = require('../../services/import/tar');
|
||||
const singleImportService = require('../../services/import/single');
|
||||
const cls = require('../../services/cls');
|
||||
const path = require('path');
|
||||
const noteCacheService = require('../../services/note_cache');
|
||||
|
||||
async function importToBranch(req) {
|
||||
const parentNoteId = req.params.parentNoteId;
|
||||
@@ -28,24 +29,32 @@ async function importToBranch(req) {
|
||||
// and may produce unintended consequences
|
||||
cls.disableEntityEvents();
|
||||
|
||||
let note; // typically root of the import - client can show it after finishing the import
|
||||
|
||||
if (extension === '.tar') {
|
||||
return await tarImportService.importTar(file.buffer, parentNote);
|
||||
note = await tarImportService.importTar(file.buffer, parentNote);
|
||||
}
|
||||
else if (extension === '.opml') {
|
||||
return await opmlImportService.importOpml(file.buffer, parentNote);
|
||||
note = await opmlImportService.importOpml(file.buffer, parentNote);
|
||||
}
|
||||
else if (extension === '.md') {
|
||||
return await singleImportService.importMarkdown(file, parentNote);
|
||||
note = await singleImportService.importMarkdown(file, parentNote);
|
||||
}
|
||||
else if (extension === '.html' || extension === '.htm') {
|
||||
return await singleImportService.importHtml(file, parentNote);
|
||||
note = await singleImportService.importHtml(file, parentNote);
|
||||
}
|
||||
else if (extension === '.enex') {
|
||||
return await enexImportService.importEnex(file, parentNote);
|
||||
note = await enexImportService.importEnex(file, parentNote);
|
||||
}
|
||||
else {
|
||||
return [400, `Unrecognized extension ${extension}, must be .tar or .opml`];
|
||||
}
|
||||
|
||||
// import has deactivated note events so note cache is not updated
|
||||
// instead we force it to reload (can be async)
|
||||
noteCacheService.load();
|
||||
|
||||
return note;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -23,7 +23,8 @@ async function loginSync(req) {
|
||||
|
||||
const now = new Date();
|
||||
|
||||
if (Math.abs(timestamp.getTime() - now.getTime()) > 5000) {
|
||||
// login token is valid for 5 minutes
|
||||
if (Math.abs(timestamp.getTime() - now.getTime()) > 5 * 60 * 1000) {
|
||||
return [400, { message: 'Auth request time is out of sync' }];
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ async function anonymize() {
|
||||
await db.run("UPDATE notes SET title = 'title', content = 'text'");
|
||||
await db.run("UPDATE note_revisions SET title = 'title', content = 'text'");
|
||||
await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
|
||||
await db.run("UPDATE images SET data = NULL");
|
||||
await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN
|
||||
('documentSecret', 'encryptedDataKey', 'passwordVerificationHash',
|
||||
'passwordVerificationSalt', 'passwordDerivedKeySalt')`);
|
||||
|
||||
@@ -1 +1 @@
|
||||
module.exports = { buildDate:"2019-01-04T23:33:32+01:00", buildRevision: "5d74dcd2564ff1341550ade1250aa9d790abc056" };
|
||||
module.exports = { buildDate:"2019-01-10T21:31:30+01:00", buildRevision: "0b251530fa0ee61edc8dcc9235033abb73afc614" };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const repository = require('./repository');
|
||||
const log = require('./log');
|
||||
const protectedSessionService = require('./protected_session');
|
||||
const noteService = require('./notes');
|
||||
const imagemin = require('imagemin');
|
||||
@@ -13,7 +14,13 @@ const sanitizeFilename = require('sanitize-filename');
|
||||
|
||||
async function saveImage(buffer, originalName, parentNoteId) {
|
||||
const resizedImage = await resize(buffer);
|
||||
const optimizedImage = await optimize(resizedImage);
|
||||
let optimizedImage;
|
||||
try {
|
||||
optimizedImage = await optimize(resizedImage);
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
optimizedImage = resizedImage;
|
||||
}
|
||||
|
||||
const imageFormat = imageType(optimizedImage);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const sax = require("sax");
|
||||
const fileType = require('file-type');
|
||||
const stream = require('stream');
|
||||
const xml2js = require('xml2js');
|
||||
const log = require("../log");
|
||||
@@ -144,7 +145,7 @@ async function importEnex(file, parentNote) {
|
||||
});
|
||||
}
|
||||
else if (currentTag === 'mime') {
|
||||
resource.mime = text;
|
||||
resource.mime = text.toLowerCase();
|
||||
|
||||
if (text.startsWith("image/")) {
|
||||
resource.title = "image";
|
||||
@@ -222,7 +223,26 @@ async function importEnex(file, parentNote) {
|
||||
|
||||
const mediaRegex = new RegExp(`<en-media hash="${hash}"[^>]*>`, 'g');
|
||||
|
||||
if (resource.mime.startsWith("image/")) {
|
||||
const fileTypeFromBuffer = fileType(resource.content);
|
||||
if (fileTypeFromBuffer) {
|
||||
// If fileType returns something for buffer, then set the mime given
|
||||
resource.mime = fileTypeFromBuffer.mime;
|
||||
}
|
||||
|
||||
const createResourceNote = async () => {
|
||||
const resourceNote = (await noteService.createNote(noteEntity.noteId, resource.title, resource.content, {
|
||||
attributes: resource.attributes,
|
||||
type: 'file',
|
||||
mime: resource.mime
|
||||
})).note;
|
||||
|
||||
const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
|
||||
|
||||
noteEntity.content = noteEntity.content.replace(mediaRegex, resourceLink);
|
||||
}
|
||||
|
||||
if (["image/jpeg", "image/png", "image/gif"].includes(resource.mime)) {
|
||||
try {
|
||||
const originalName = "image." + resource.mime.substr(6);
|
||||
|
||||
const { url } = await imageService.saveImage(resource.content, originalName, noteEntity.noteId);
|
||||
@@ -236,17 +256,13 @@ async function importEnex(file, parentNote) {
|
||||
// otherwise image would be removed since no note would include it
|
||||
note.content += imageLink;
|
||||
}
|
||||
} catch (e) {
|
||||
log.error("error when saving image from ENEX file: " + e);
|
||||
await createResourceNote();
|
||||
}
|
||||
}
|
||||
else {
|
||||
const resourceNote = (await noteService.createNote(noteEntity.noteId, resource.title, resource.content, {
|
||||
attributes: resource.attributes,
|
||||
type: 'file',
|
||||
mime: resource.mime
|
||||
})).note;
|
||||
|
||||
const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
|
||||
|
||||
noteEntity.content = noteEntity.content.replace(mediaRegex, resourceLink);
|
||||
await createResourceNote();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,9 +33,21 @@ async function load() {
|
||||
|
||||
archived = await sql.getMap(`SELECT noteId, isInheritable FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name = 'archived'`);
|
||||
|
||||
if (protectedSessionService.isProtectedSessionAvailable()) {
|
||||
await loadProtectedNotes();
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
async function loadProtectedNotes() {
|
||||
protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`);
|
||||
|
||||
for (const noteId in protectedNoteTitles) {
|
||||
protectedNoteTitles[noteId] = protectedSessionService.decryptNoteTitle(noteId, protectedNoteTitles[noteId]);
|
||||
}
|
||||
}
|
||||
|
||||
function highlightResults(results, allTokens) {
|
||||
// we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks
|
||||
// which would make the resulting HTML string invalid.
|
||||
@@ -313,8 +325,17 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
|
||||
delete noteTitles[note.noteId];
|
||||
delete childToParent[note.noteId];
|
||||
}
|
||||
else {
|
||||
if (note.isProtected) {
|
||||
// we can assume we have protected session since we managed to update
|
||||
// removing from the maps is important when switching between protected & unprotected
|
||||
protectedNoteTitles[note.noteId] = note.title;
|
||||
delete noteTitles[note.noteId];
|
||||
}
|
||||
else {
|
||||
noteTitles[note.noteId] = note.title;
|
||||
delete protectedNoteTitles[note.noteId];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (entityName === 'branches') {
|
||||
@@ -355,15 +376,9 @@ eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED
|
||||
}
|
||||
});
|
||||
|
||||
eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, async () => {
|
||||
if (!loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
protectedNoteTitles = await sql.getMap(`SELECT noteId, title FROM notes WHERE isDeleted = 0 AND isProtected = 1`);
|
||||
|
||||
for (const noteId in protectedNoteTitles) {
|
||||
protectedNoteTitles[noteId] = protectedSessionService.decryptNoteTitle(noteId, protectedNoteTitles[noteId]);
|
||||
eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => {
|
||||
if (loaded) {
|
||||
loadProtectedNotes();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -372,5 +387,6 @@ sqlInit.dbReady.then(() => utils.stopWatch("Autocomplete load", load));
|
||||
module.exports = {
|
||||
findNotes,
|
||||
getNotePath,
|
||||
getNoteTitleForPath
|
||||
getNoteTitleForPath,
|
||||
load
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
const sql = require('./sql');
|
||||
const sqlInit = require('./sql_init');
|
||||
const optionService = require('./options');
|
||||
const dateUtils = require('./date_utils');
|
||||
const syncTableService = require('./sync_table');
|
||||
@@ -408,10 +409,12 @@ async function cleanupDeletedNotes() {
|
||||
await sql.execute("UPDATE note_revisions SET content = NULL WHERE note_revisions.content IS NOT NULL AND noteId IN (SELECT noteId FROM notes WHERE isDeleted = 1 AND notes.dateModified <= ?)", [dateUtils.dateStr(cutoffDate)]);
|
||||
}
|
||||
|
||||
// first cleanup kickoff 5 minutes after startup
|
||||
setTimeout(cls.wrap(cleanupDeletedNotes), 5 * 60 * 1000);
|
||||
sqlInit.dbReady.then(() => {
|
||||
// first cleanup kickoff 5 minutes after startup
|
||||
setTimeout(cls.wrap(cleanupDeletedNotes), 5 * 60 * 1000);
|
||||
|
||||
setInterval(cls.wrap(cleanupDeletedNotes), 4 * 3600 * 1000);
|
||||
setInterval(cls.wrap(cleanupDeletedNotes), 4 * 3600 * 1000);
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
createNewNote,
|
||||
|
||||
@@ -6,7 +6,7 @@ const sourceIdService = require('./source_id');
|
||||
const log = require('./log');
|
||||
|
||||
async function executeNote(note, originEntity) {
|
||||
if (!note.isJavaScript()) {
|
||||
if (!note.isJavaScript() || !note.isContentAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -80,6 +80,10 @@ function getParams(params) {
|
||||
}
|
||||
|
||||
async function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = []) {
|
||||
if (!note.isContentAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!note.isJavaScript() && !note.isHtml()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div id="history-navigation" style="display: none;">
|
||||
<a id="history-back-button" title="Go to previous note." class="icon-action jam jam-arrow-square-left"></a>
|
||||
|
||||
|
||||
|
||||
|
||||
<a id="history-forward-button" title="Go to next note." class="icon-action jam jam-arrow-square-right"></a>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<div id="note-detail-search" class="note-detail-component">
|
||||
<div style="display: flex; align-items: center;">
|
||||
<strong>Search string: </strong>
|
||||
<textarea rows="4" cols="50" id="search-string"></textarea>
|
||||
<textarea rows="4" cols="40" id="search-string"></textarea>
|
||||
|
||||
<span>
|
||||
|
||||
<button type="button" class="btn btn-primary" id="note-detail-search-refresh-results-button">Refresh tree</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div id="confirm-dialog-custom"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default btn-sm" id="confirm-dialog-cancel-button">Cancel</button>
|
||||
<button class="btn btn-secondary btn-sm" id="confirm-dialog-cancel-button">Cancel</button>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
|
||||
<button class="btn btn-default" type="button" data-help-page="Protected-notes">Help</button>
|
||||
<button class="btn btn-secondary" type="button" data-help-page="Protected-notes">Help</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -125,7 +125,7 @@
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
|
||||
<button class="btn btn-default" type="button" data-help-page="Note-revisions">Help</button>
|
||||
<button class="btn btn-secondary" type="button" data-help-page="Note-revisions">Help</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -154,7 +154,7 @@
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
|
||||
<button class="btn btn-default" type="button" data-help-page="Synchronization">Help</button>
|
||||
<button class="btn btn-secondary" type="button" data-help-page="Synchronization">Help</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -164,24 +164,24 @@
|
||||
|
||||
<p>This will test connection and handshake to the sync server. If sync server isn't initialized, this will set it up to sync with local document.</p>
|
||||
|
||||
<button id="test-sync-button" class="btn btn-default">Test sync</button>
|
||||
<button id="test-sync-button" class="btn btn-secondary">Test sync</button>
|
||||
</div>
|
||||
|
||||
<div id="advanced" class="tab-pane">
|
||||
<h4 style="margin-top: 0px;">Sync</h4>
|
||||
<button id="force-full-sync-button" class="btn btn-default">Force full sync</button>
|
||||
<button id="force-full-sync-button" class="btn btn-secondary">Force full sync</button>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="fill-sync-rows-button" class="btn btn-default">Fill sync rows</button>
|
||||
<button id="fill-sync-rows-button" class="btn btn-secondary">Fill sync rows</button>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h4>Debugging</h4>
|
||||
|
||||
<button id="anonymize-button" class="btn btn-default">Save anonymized database</button><br/><br/>
|
||||
<button id="anonymize-button" class="btn btn-secondary">Save anonymized database</button><br/><br/>
|
||||
|
||||
<p>This action will create a new copy of the database and anonymise it (remove all note content and leave only structure and metadata)
|
||||
for sharing online for debugging purposes without fear of leaking your personal data.</p>
|
||||
@@ -190,7 +190,7 @@
|
||||
|
||||
<p>This will rebuild database which will typically result in smaller database file. No data will be actually changed.</p>
|
||||
|
||||
<button id="vacuum-database-button" class="btn btn-default">Vacuum database</button>
|
||||
<button id="vacuum-database-button" class="btn btn-secondary">Vacuum database</button>
|
||||
</div>
|
||||
|
||||
<div id="about" class="tab-pane">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="col-md-5 offset-md-3" style="padding-top: 25px;">
|
||||
<div class="col-xs-12 col-sm-10 col-md-6 col-lg-4 col-xl-4 mx-auto" style="padding-top: 25px;">
|
||||
<h1>Trilium login</h1>
|
||||
|
||||
<% if (failedAuth) { %>
|
||||
@@ -60,6 +60,8 @@
|
||||
device = /Mobi/.test(navigator.userAgent) ? "mobile" : "desktop";
|
||||
}
|
||||
|
||||
console.log("Setting device cookie to:", device);
|
||||
|
||||
setCookie("trilium-device", device);
|
||||
|
||||
function setCookie(name, value) {
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>Setup</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="setup-dialog" style="width: 700px; margin: auto; padding-top: 50px; display:none; font-size: larger;">
|
||||
<div class="container">
|
||||
<div id="setup-dialog" class="col-md-12 col-lg-8 col-xl-6 mx-auto" style="padding-top: 25px;">
|
||||
<h1>Trilium Notes setup</h1>
|
||||
|
||||
<div class="alert alert-warning" id="alert" style="display: none;">
|
||||
@@ -47,7 +49,7 @@
|
||||
<input type="password" class="form-control" data-bind="value: password2" placeholder="Password">
|
||||
</div>
|
||||
|
||||
<button type="button" data-bind="click: back" class="btn btn-default">Back</button>
|
||||
<button type="button" data-bind="click: back" class="btn btn-secondary">Back</button>
|
||||
|
||||
|
||||
|
||||
@@ -68,7 +70,7 @@
|
||||
<li>once you've done all this, click <a href="/">here</a></li>
|
||||
</ol>
|
||||
|
||||
<button type="button" data-bind="click: back" class="btn btn-default">Back</button>
|
||||
<button type="button" data-bind="click: back" class="btn btn-secondary">Back</button>
|
||||
</div>
|
||||
|
||||
<div data-bind="visible: step() == 'sync-from-server'">
|
||||
@@ -95,7 +97,7 @@
|
||||
<input type="password" id="password1" class="form-control" data-bind="value: password1" placeholder="Password">
|
||||
</div>
|
||||
|
||||
<button type="button" data-bind="click: back" class="btn btn-default">Back</button>
|
||||
<button type="button" data-bind="click: back" class="btn btn-secondary">Back</button>
|
||||
|
||||
|
||||
|
||||
@@ -111,6 +113,7 @@
|
||||
Outstanding sync items: <strong id="outstanding-syncs">N/A</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
Reference in New Issue
Block a user