mirror of
https://github.com/zadam/trilium.git
synced 2025-11-01 19:05:59 +01:00
Compare commits
57 Commits
v0.27.0-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 | ||
|
|
6ce401f260 | ||
|
|
5d74dcd256 | ||
|
|
5a9fc1697b | ||
|
|
927415838c | ||
|
|
d72fcefdc7 | ||
|
|
0be173a8f7 | ||
|
|
c3913a8735 | ||
|
|
e2dfe1b6de | ||
|
|
fec3e47eb8 | ||
|
|
d72efd2450 | ||
|
|
ef1c840aa7 | ||
|
|
1581464d8c | ||
|
|
9de29584a4 | ||
|
|
9e2e6fb50c | ||
|
|
c85979b66b | ||
|
|
ecdc5865a6 | ||
|
|
1771ddb787 | ||
|
|
3ab657fe46 | ||
|
|
8785dae753 | ||
|
|
2f1c5b29d4 | ||
|
|
7135349a10 | ||
|
|
66c639d5e3 | ||
|
|
6704b755d8 |
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 \
|
libtool \
|
||||||
make \
|
make \
|
||||||
nasm \
|
nasm \
|
||||||
|
libpng-dev \
|
||||||
&& npm install --production \
|
&& npm install --production \
|
||||||
&& apk del .build-dependencies
|
&& 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
|
* [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)
|
* [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
|
* 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)
|
* [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)
|
* [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/)
|
[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.
|
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
|
||||||
|
```
|
||||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"version": "0.26.1",
|
"version": "0.27.2-beta",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -399,9 +399,9 @@
|
|||||||
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow=="
|
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow=="
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "8.10.39",
|
"version": "10.12.18",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.39.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
|
||||||
"integrity": "sha512-rE7fktr02J8ybFf6eysife+WF+L4sAHWzw09DgdCebEu+qDwMvv4zl6Bc+825ttGZP73kCKxa3dhJOoGJ8+5mA==",
|
"integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"abab": {
|
"abab": {
|
||||||
@@ -2375,12 +2375,12 @@
|
|||||||
"integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
|
"integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
|
||||||
},
|
},
|
||||||
"electron": {
|
"electron": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-4.0.1.tgz",
|
||||||
"integrity": "sha512-3XPG/3IXlvnT1oe1K6zEushoD0SKbP8xwdrL10EWGe6k2iOV4hSHqJ8vWnR8yZ7VbSXmBRfomEFDNAo/q/cwKw==",
|
"integrity": "sha512-kBWDLn1Vq8Tm6+/HpQc8gkjX7wJyQI8v/lf2kAirfi0Q4cXh6vBjozFvV1U/9gGCbyKnIDM+m8/wpyJIjg4w7g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "^8.0.24",
|
"@types/node": "^10.12.18",
|
||||||
"electron-download": "^4.1.0",
|
"electron-download": "^4.1.0",
|
||||||
"extract-zip": "^1.0.3"
|
"extract-zip": "^1.0.3"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"productName": "Trilium Notes",
|
"productName": "Trilium Notes",
|
||||||
"description": "Trilium Notes",
|
"description": "Trilium Notes",
|
||||||
"version": "0.27.0-beta",
|
"version": "0.27.4",
|
||||||
"license": "AGPL-3.0-only",
|
"license": "AGPL-3.0-only",
|
||||||
"main": "electron.js",
|
"main": "electron.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
"electron-in-page-search": "1.3.2",
|
"electron-in-page-search": "1.3.2",
|
||||||
"express": "4.16.4",
|
"express": "4.16.4",
|
||||||
"express-session": "1.15.6",
|
"express-session": "1.15.6",
|
||||||
|
"file-type": "10.7.0",
|
||||||
"fs-extra": "7.0.1",
|
"fs-extra": "7.0.1",
|
||||||
"get-port": "4.1.0",
|
"get-port": "4.1.0",
|
||||||
"helmet": "3.15.0",
|
"helmet": "3.15.0",
|
||||||
@@ -66,7 +67,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron": "4.0.0",
|
"electron": "4.0.1",
|
||||||
"electron-compile": "6.4.3",
|
"electron-compile": "6.4.3",
|
||||||
"electron-packager": "13.0.1",
|
"electron-packager": "13.0.1",
|
||||||
"electron-rebuild": "1.8.2",
|
"electron-rebuild": "1.8.2",
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ const ENTITY_NAME_TO_ENTITY = {
|
|||||||
"note_revisions": NoteRevision,
|
"note_revisions": NoteRevision,
|
||||||
"recent_notes": RecentNote,
|
"recent_notes": RecentNote,
|
||||||
"options": Option,
|
"options": Option,
|
||||||
"api_tokens": ApiToken
|
"api_tokens": ApiToken,
|
||||||
|
"links": Link
|
||||||
};
|
};
|
||||||
|
|
||||||
function getEntityFromEntityName(entityName) {
|
function getEntityFromEntityName(entityName) {
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ class Note extends Entity {
|
|||||||
setContent(content) {
|
setContent(content) {
|
||||||
this.content = 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 {
|
try {
|
||||||
this.jsonContent = JSON.parse(this.content);
|
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 noteTypeService from './services/note_type.js';
|
||||||
import linkService from './services/link.js';
|
import linkService from './services/link.js';
|
||||||
import noteAutocompleteService from './services/note_autocomplete.js';
|
import noteAutocompleteService from './services/note_autocomplete.js';
|
||||||
|
import macInit from './services/mac_init.js';
|
||||||
|
|
||||||
// required for CKEditor image upload plugin
|
// required for CKEditor image upload plugin
|
||||||
window.glob.getCurrentNode = treeService.getCurrentNode;
|
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 () {
|
$("#export-note-button").click(function () {
|
||||||
if ($(this).hasClass("disabled")) {
|
if ($(this).hasClass("disabled")) {
|
||||||
return;
|
return;
|
||||||
@@ -140,6 +119,8 @@ $("#export-note-button").click(function () {
|
|||||||
exportDialog.showDialog('single');
|
exportDialog.showDialog('single');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
macInit.init();
|
||||||
|
|
||||||
treeService.showTree();
|
treeService.showTree();
|
||||||
|
|
||||||
entrypoints.registerEntrypoints();
|
entrypoints.registerEntrypoints();
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ const $dialog = $("#jump-to-note-dialog");
|
|||||||
const $autoComplete = $("#jump-to-note-autocomplete");
|
const $autoComplete = $("#jump-to-note-autocomplete");
|
||||||
const $showInFullTextButton = $("#show-in-full-text-button");
|
const $showInFullTextButton = $("#show-in-full-text-button");
|
||||||
|
|
||||||
$dialog.on("shown.bs.modal", e => $autoComplete.focus());
|
|
||||||
|
|
||||||
async function showDialog() {
|
async function showDialog() {
|
||||||
glob.activeDialog = $dialog;
|
glob.activeDialog = $dialog;
|
||||||
|
|
||||||
|
|||||||
@@ -198,15 +198,17 @@ addTabHandler((async function () {
|
|||||||
const $syncVersion = $("#sync-version");
|
const $syncVersion = $("#sync-version");
|
||||||
const $buildDate = $("#build-date");
|
const $buildDate = $("#build-date");
|
||||||
const $buildRevision = $("#build-revision");
|
const $buildRevision = $("#build-revision");
|
||||||
|
const $dataDirectory = $("#data-directory");
|
||||||
|
|
||||||
const appInfo = await server.get('app-info');
|
const appInfo = await server.get('app-info');
|
||||||
|
|
||||||
$appVersion.html(appInfo.appVersion);
|
$appVersion.text(appInfo.appVersion);
|
||||||
$dbVersion.html(appInfo.dbVersion);
|
$dbVersion.text(appInfo.dbVersion);
|
||||||
$syncVersion.html(appInfo.syncVersion);
|
$syncVersion.text(appInfo.syncVersion);
|
||||||
$buildDate.html(appInfo.buildDate);
|
$buildDate.text(appInfo.buildDate);
|
||||||
$buildRevision.html(appInfo.buildRevision);
|
$buildRevision.text(appInfo.buildRevision);
|
||||||
$buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision);
|
$buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision);
|
||||||
|
$dataDirectory.text(appInfo.dataDirectory);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
})());
|
})());
|
||||||
|
|||||||
@@ -85,8 +85,11 @@ async function showAttributes() {
|
|||||||
$promotedAttributesContainer.empty().append($tbody);
|
$promotedAttributesContainer.empty().append($tbody);
|
||||||
}
|
}
|
||||||
else if (note.type !== 'relation-map') {
|
else if (note.type !== 'relation-map') {
|
||||||
if (attributes.length > 0) {
|
// display only "own" notes
|
||||||
for (const attribute of attributes) {
|
const ownedAttributes = attributes.filter(attr => attr.noteId === note.noteId);
|
||||||
|
|
||||||
|
if (ownedAttributes.length > 0) {
|
||||||
|
for (const attribute of ownedAttributes) {
|
||||||
if (attribute.type === 'label') {
|
if (attribute.type === 'label') {
|
||||||
$attributeListInner.append(utils.formatLabel(attribute) + " ");
|
$attributeListInner.append(utils.formatLabel(attribute) + " ");
|
||||||
}
|
}
|
||||||
@@ -132,7 +135,9 @@ async function createPromotedAttributeRow(definitionAttr, valueAttr) {
|
|||||||
const $inputCell = $("<td>").append($("<div>").addClass("input-group").append($input));
|
const $inputCell = $("<td>").append($("<div>").addClass("input-group").append($input));
|
||||||
|
|
||||||
const $actionCell = $("<td>");
|
const $actionCell = $("<td>");
|
||||||
const $multiplicityCell = $("<td>").addClass("multiplicity");
|
const $multiplicityCell = $("<td>")
|
||||||
|
.addClass("multiplicity")
|
||||||
|
.attr("nowrap", true);
|
||||||
|
|
||||||
$tr
|
$tr
|
||||||
.append($labelCell)
|
.append($labelCell)
|
||||||
|
|||||||
@@ -117,10 +117,6 @@ function registerEntrypoints() {
|
|||||||
|
|
||||||
utils.bindShortcut('ctrl+f', openInPageSearch);
|
utils.bindShortcut('ctrl+f', openInPageSearch);
|
||||||
|
|
||||||
if (utils.isMac()) {
|
|
||||||
utils.bindShortcut('meta+f', openInPageSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: do we really need these at this point?
|
// FIXME: do we really need these at this point?
|
||||||
utils.bindShortcut("ctrl+shift+up", () => {
|
utils.bindShortcut("ctrl+shift+up", () => {
|
||||||
const node = treeService.getCurrentNode();
|
const node = treeService.getCurrentNode();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import treeCache from './tree_cache.js';
|
|||||||
import noteDetailService from './note_detail.js';
|
import noteDetailService from './note_detail.js';
|
||||||
import noteTypeService from './note_type.js';
|
import noteTypeService from './note_type.js';
|
||||||
import noteTooltipService from './note_tooltip.js';
|
import noteTooltipService from './note_tooltip.js';
|
||||||
|
import protectedSessionService from'./protected_session.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the main frontend API interface for scripts. It's published in the local "api" object.
|
* This is the main frontend API interface for scripts. It's published in the local "api" object.
|
||||||
@@ -42,7 +43,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) {
|
|||||||
this.activateNewNote = async notePath => {
|
this.activateNewNote = async notePath => {
|
||||||
await treeService.reload();
|
await treeService.reload();
|
||||||
|
|
||||||
await treeService.activateNote(notePath, true);
|
await treeService.activateNote(notePath, noteDetailService.focusOnTitle);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -244,7 +245,12 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) {
|
|||||||
* @method
|
* @method
|
||||||
* @param {object} $el - jquery object on which to setup the tooltip
|
* @param {object} $el - jquery object on which to setup the tooltip
|
||||||
*/
|
*/
|
||||||
this.setupElementTooltip = noteTooltipService.setupElementTooltip
|
this.setupElementTooltip = noteTooltipService.setupElementTooltip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method
|
||||||
|
*/
|
||||||
|
this.protectCurrentNote = protectedSessionService.protectNoteAndSendToServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FrontendScriptApi;
|
export default FrontendScriptApi;
|
||||||
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) {
|
function showRecentNotes($el) {
|
||||||
$el.setSelectedPath("");
|
$el.setSelectedPath("");
|
||||||
$el.autocomplete("val", "");
|
$el.autocomplete("val", "");
|
||||||
$el.autocomplete("open");
|
$el.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function initNoteAutocomplete($el, options) {
|
function initNoteAutocomplete($el, options) {
|
||||||
@@ -61,7 +61,13 @@ function initNoteAutocomplete($el, options) {
|
|||||||
|
|
||||||
$clearTextButton.click(() => clearText($el));
|
$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(() => {
|
$goToSelectedNoteButton.click(() => {
|
||||||
if ($el.hasClass("disabled")) {
|
if ($el.hasClass("disabled")) {
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ let noteChangeDisabled = false;
|
|||||||
|
|
||||||
let isNoteChanged = false;
|
let isNoteChanged = false;
|
||||||
|
|
||||||
|
let detailLoadedListeners = [];
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
'code': noteDetailCode,
|
'code': noteDetailCode,
|
||||||
'text': noteDetailText,
|
'text': noteDetailText,
|
||||||
@@ -147,12 +149,6 @@ function setNoteBackgroundIfProtected(note) {
|
|||||||
$unprotectButton.prop("disabled", !protectedSessionHolder.isProtectedSessionAvailable());
|
$unprotectButton.prop("disabled", !protectedSessionHolder.isProtectedSessionAvailable());
|
||||||
}
|
}
|
||||||
|
|
||||||
let isNewNoteCreated = false;
|
|
||||||
|
|
||||||
function newNoteCreated() {
|
|
||||||
isNewNoteCreated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleProtectedSession() {
|
async function handleProtectedSession() {
|
||||||
const newSessionCreated = await protectedSessionService.ensureProtectedSession(currentNote.isProtected, false);
|
const newSessionCreated = await protectedSessionService.ensureProtectedSession(currentNote.isProtected, false);
|
||||||
|
|
||||||
@@ -191,12 +187,6 @@ async function loadNoteDetail(noteId) {
|
|||||||
attributeService.invalidateAttributes();
|
attributeService.invalidateAttributes();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNewNoteCreated) {
|
|
||||||
isNewNoteCreated = false;
|
|
||||||
|
|
||||||
$noteTitle.focus().select();
|
|
||||||
}
|
|
||||||
|
|
||||||
$noteIdDisplay.html(noteId);
|
$noteIdDisplay.html(noteId);
|
||||||
|
|
||||||
setNoteBackgroundIfProtected(currentNote);
|
setNoteBackgroundIfProtected(currentNote);
|
||||||
@@ -240,11 +230,13 @@ async function loadNoteDetail(noteId) {
|
|||||||
// after loading new note make sure editor is scrolled to the top
|
// after loading new note make sure editor is scrolled to the top
|
||||||
getComponent(currentNote.type).scrollToTop();
|
getComponent(currentNote.type).scrollToTop();
|
||||||
|
|
||||||
if (utils.isDesktop()) {
|
fireDetailLoaded();
|
||||||
|
|
||||||
$scriptArea.empty();
|
$scriptArea.empty();
|
||||||
|
|
||||||
await bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteView');
|
await bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteView');
|
||||||
|
|
||||||
|
if (utils.isDesktop()) {
|
||||||
await attributeService.showAttributes();
|
await attributeService.showAttributes();
|
||||||
|
|
||||||
await showChildrenOverview();
|
await showChildrenOverview();
|
||||||
@@ -291,6 +283,30 @@ function focusOnTitle() {
|
|||||||
$noteTitle.focus();
|
$noteTitle.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since detail loading may take some time and user might just browse through the notes using UP-DOWN keys,
|
||||||
|
* we intentionally decouple activation of the note in the tree and full load of the note so just avaiting on
|
||||||
|
* fancytree's activate() won't wait for the full load.
|
||||||
|
*
|
||||||
|
* This causes an issue where in some cases you want to do some action after detail is loaded. For this reason
|
||||||
|
* we provide the listeners here which will be triggered after the detail is loaded and if the loaded note
|
||||||
|
* is the one registered in the listener.
|
||||||
|
*/
|
||||||
|
function addDetailLoadedListener(noteId, callback) {
|
||||||
|
detailLoadedListeners.push({ noteId, callback });
|
||||||
|
}
|
||||||
|
|
||||||
|
function fireDetailLoaded() {
|
||||||
|
for (const {noteId, callback} of detailLoadedListeners) {
|
||||||
|
if (noteId === currentNote.noteId) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all the listeners are one time only
|
||||||
|
detailLoadedListeners = [];
|
||||||
|
}
|
||||||
|
|
||||||
messagingService.subscribeToSyncMessages(syncData => {
|
messagingService.subscribeToSyncMessages(syncData => {
|
||||||
if (syncData.some(sync => sync.entityName === 'notes' && sync.entityId === getCurrentNoteId())) {
|
if (syncData.some(sync => sync.entityName === 'notes' && sync.entityId === getCurrentNoteId())) {
|
||||||
infoService.showMessage('Reloading note because of background changes');
|
infoService.showMessage('Reloading note because of background changes');
|
||||||
@@ -325,11 +341,11 @@ export default {
|
|||||||
getCurrentNote,
|
getCurrentNote,
|
||||||
getCurrentNoteType,
|
getCurrentNoteType,
|
||||||
getCurrentNoteId,
|
getCurrentNoteId,
|
||||||
newNoteCreated,
|
|
||||||
focusOnTitle,
|
focusOnTitle,
|
||||||
saveNote,
|
saveNote,
|
||||||
saveNoteIfChanged,
|
saveNoteIfChanged,
|
||||||
noteChanged,
|
noteChanged,
|
||||||
getCurrentNoteContent,
|
getCurrentNoteContent,
|
||||||
onNoteChange
|
onNoteChange,
|
||||||
|
addDetailLoadedListener
|
||||||
};
|
};
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
import noteDetailService from "./note_detail.js";
|
import noteDetailService from "./note_detail.js";
|
||||||
|
import treeService from "./tree.js";
|
||||||
|
import infoService from './info.js';
|
||||||
|
|
||||||
const $searchString = $("#search-string");
|
const $searchString = $("#search-string");
|
||||||
const $component = $('#note-detail-search');
|
const $component = $('#note-detail-search');
|
||||||
|
const $refreshButton = $('#note-detail-search-refresh-results-button');
|
||||||
|
|
||||||
function getContent() {
|
function getContent() {
|
||||||
JSON.stringify({
|
return JSON.stringify({
|
||||||
searchString: $searchString.val()
|
searchString: $searchString.val()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -25,6 +28,14 @@ function show() {
|
|||||||
$searchString.on('input', noteDetailService.noteChanged);
|
$searchString.on('input', noteDetailService.noteChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$refreshButton.click(async () => {
|
||||||
|
await noteDetailService.saveNoteIfChanged();
|
||||||
|
|
||||||
|
treeService.reload();
|
||||||
|
|
||||||
|
infoService.showMessage('Tree has been refreshed.');
|
||||||
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getContent,
|
getContent,
|
||||||
show,
|
show,
|
||||||
|
|||||||
@@ -184,5 +184,6 @@ export default {
|
|||||||
protectSubtree,
|
protectSubtree,
|
||||||
ensureDialogIsClosed,
|
ensureDialogIsClosed,
|
||||||
enterProtectedSession,
|
enterProtectedSession,
|
||||||
leaveProtectedSession
|
leaveProtectedSession,
|
||||||
|
protectNoteAndSendToServer
|
||||||
};
|
};
|
||||||
@@ -87,7 +87,7 @@ $searchInput.keyup(e => {
|
|||||||
if (e && e.which === $.ui.keyCode.ENTER) {
|
if (e && e.which === $.ui.keyCode.ENTER) {
|
||||||
doSearch();
|
doSearch();
|
||||||
}
|
}
|
||||||
}).focus();
|
});
|
||||||
|
|
||||||
$doSearchButton.click(() => doSearch()); // keep long form because of argument
|
$doSearchButton.click(() => doSearch()); // keep long form because of argument
|
||||||
$resetSearchButton.click(resetSearch);
|
$resetSearchButton.click(resetSearch);
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ async function setNodeTitleWithPrefix(node) {
|
|||||||
node.setTitle(utils.escapeHtml(title));
|
node.setTitle(utils.escapeHtml(title));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getNode(childNoteId, parentNoteId) {
|
||||||
|
return getNodesByNoteId(childNoteId).find(node => !parentNoteId || node.data.parentNoteId === parentNoteId);
|
||||||
|
}
|
||||||
|
|
||||||
async function expandToNote(notePath, expandOpts) {
|
async function expandToNote(notePath, expandOpts) {
|
||||||
utils.assertArguments(notePath);
|
utils.assertArguments(notePath);
|
||||||
|
|
||||||
@@ -90,11 +94,31 @@ async function expandToNote(notePath, expandOpts) {
|
|||||||
|
|
||||||
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
const noteId = treeUtils.getNoteIdFromNotePath(notePath);
|
||||||
|
|
||||||
|
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||||
|
let hoistedNoteFound = false;
|
||||||
|
|
||||||
let parentNoteId = null;
|
let parentNoteId = null;
|
||||||
|
|
||||||
for (const childNoteId of runPath) {
|
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
|
// for first node (!parentNoteId) it doesn't matter which node is found
|
||||||
const node = getNodesByNoteId(childNoteId).find(node => !parentNoteId || node.data.parentNoteId === parentNoteId);
|
let node = getNode(childNoteId, parentNoteId);
|
||||||
|
|
||||||
|
if (!node && parentNoteId) {
|
||||||
|
const parents = getNodesByNoteId(parentNoteId);
|
||||||
|
|
||||||
|
for (const parent of parents) {
|
||||||
|
// force load parents. This is useful when fancytree doesn't contain recently created notes yet.
|
||||||
|
await parent.load(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
node = getNode(childNoteId, parentNoteId);
|
||||||
|
}
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
console.error(`Can't find node for noteId=${childNoteId} with parentNoteId=${parentNoteId}`);
|
console.error(`Can't find node for noteId=${childNoteId} with parentNoteId=${parentNoteId}`);
|
||||||
@@ -102,21 +126,24 @@ async function expandToNote(notePath, expandOpts) {
|
|||||||
|
|
||||||
if (childNoteId === noteId) {
|
if (childNoteId === noteId) {
|
||||||
return node;
|
return node;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
await node.setExpanded(true, expandOpts);
|
await node.setExpanded(true, expandOpts);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
parentNoteId = childNoteId;
|
parentNoteId = childNoteId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function activateNote(notePath, newNote) {
|
async function activateNote(notePath, noteLoadedListener) {
|
||||||
utils.assertArguments(notePath);
|
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();
|
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?")) {
|
if (!await confirmDialog.confirm("Requested note is outside of hoisted note subtree. Do you want to unhoist?")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -131,8 +158,8 @@ async function activateNote(notePath, newNote) {
|
|||||||
|
|
||||||
const node = await expandToNote(notePath);
|
const node = await expandToNote(notePath);
|
||||||
|
|
||||||
if (newNote) {
|
if (noteLoadedListener) {
|
||||||
noteDetailService.newNoteCreated();
|
noteDetailService.addDetailLoadedListener(node.data.noteId, noteLoadedListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we use noFocus because when we reload the tree because of background changes
|
// we use noFocus because when we reload the tree because of background changes
|
||||||
@@ -337,6 +364,7 @@ function clearSelectedNodes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function treeInitialized() {
|
async function treeInitialized() {
|
||||||
|
// - is used in mobile to indicate that we don't want to activate any note after load
|
||||||
if (startNotePath === '-') {
|
if (startNotePath === '-') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -348,7 +376,6 @@ async function treeInitialized() {
|
|||||||
startNotePath = null;
|
startNotePath = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// - is used in mobile to indicate that we don't want to activate any note after load
|
|
||||||
if (startNotePath) {
|
if (startNotePath) {
|
||||||
const node = await activateNote(startNotePath);
|
const node = await activateNote(startNotePath);
|
||||||
|
|
||||||
@@ -411,14 +438,28 @@ function initFancyTree(tree) {
|
|||||||
clones: {
|
clones: {
|
||||||
highlightActiveClones: true
|
highlightActiveClones: true
|
||||||
},
|
},
|
||||||
renderNode: async function (event, data) {
|
enhanceTitle: async function (event, data) {
|
||||||
const node = data.node;
|
const node = data.node;
|
||||||
|
const $span = $(node.span);
|
||||||
|
|
||||||
|
if (node.data.noteId !== 'root'
|
||||||
|
&& node.data.noteId === await hoistedNoteService.getHoistedNoteId()
|
||||||
|
&& $span.find('.unhoist-button').length === 0) {
|
||||||
|
|
||||||
if (node.data.noteId !== 'root' && node.data.noteId === await hoistedNoteService.getHoistedNoteId()) {
|
|
||||||
const unhoistButton = $('<span> (<a class="unhoist-button">unhoist</a>)</span>');
|
const unhoistButton = $('<span> (<a class="unhoist-button">unhoist</a>)</span>');
|
||||||
|
|
||||||
$(node.span).append(unhoistButton);
|
$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();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -547,7 +588,7 @@ async function createNote(node, parentNoteId, target, isProtected, saveSelection
|
|||||||
|
|
||||||
await noteDetailService.saveNoteIfChanged();
|
await noteDetailService.saveNoteIfChanged();
|
||||||
|
|
||||||
noteDetailService.newNoteCreated();
|
noteDetailService.addDetailLoadedListener(note.noteId, noteDetailService.focusOnTitle);
|
||||||
|
|
||||||
const noteEntity = new NoteShort(treeCache, note);
|
const noteEntity = new NoteShort(treeCache, note);
|
||||||
const branchEntity = new Branch(treeCache, branch);
|
const branchEntity = new Branch(treeCache, branch);
|
||||||
@@ -634,11 +675,15 @@ messagingService.subscribeToSyncMessages(syncData => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
utils.bindShortcut('ctrl+o', () => {
|
utils.bindShortcut('ctrl+o', async () => {
|
||||||
const node = getCurrentNode();
|
const node = getCurrentNode();
|
||||||
const parentNoteId = node.data.parentNoteId;
|
const parentNoteId = node.data.parentNoteId;
|
||||||
const isProtected = treeUtils.getParentProtectedStatus(node);
|
const isProtected = treeUtils.getParentProtectedStatus(node);
|
||||||
|
|
||||||
|
if (node.data.noteId === 'root' || node.data.noteId === await hoistedNoteService.getHoistedNoteId()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
createNote(node, parentNoteId, 'after', isProtected, true);
|
createNote(node, parentNoteId, 'after', isProtected, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ async function prepareNode(branch) {
|
|||||||
extraClasses: await getExtraClasses(note),
|
extraClasses: await getExtraClasses(note),
|
||||||
icon: await getIcon(note),
|
icon: await getIcon(note),
|
||||||
refKey: note.noteId,
|
refKey: note.noteId,
|
||||||
expanded: (note.type !== 'search' && branch.isExpanded) || hoistedNoteId === note.noteId
|
expanded: branch.isExpanded || hoistedNoteId === note.noteId
|
||||||
};
|
};
|
||||||
|
|
||||||
if (note.hasChildren() || note.type === 'search') {
|
if (note.hasChildren() || note.type === 'search') {
|
||||||
|
|||||||
@@ -158,7 +158,11 @@ class TreeCache {
|
|||||||
return;
|
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
|
delete treeCache.childParentToBranch[childNoteId + '-' + oldParentNoteId]; // this is correct because we know that oldParentId isn't same as newParentId
|
||||||
|
|
||||||
// remove old associations
|
// remove old associations
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import syncService from "./sync.js";
|
|||||||
import hoistedNoteService from './hoisted_note.js';
|
import hoistedNoteService from './hoisted_note.js';
|
||||||
import ContextMenuItemsContainer from './context_menu_items_container.js';
|
import ContextMenuItemsContainer from './context_menu_items_container.js';
|
||||||
|
|
||||||
const $tree = $("#tree");
|
|
||||||
|
|
||||||
let clipboardIds = [];
|
let clipboardIds = [];
|
||||||
let clipboardMode = null;
|
let clipboardMode = null;
|
||||||
|
|
||||||
@@ -110,11 +108,12 @@ async function getContextMenuItems(event) {
|
|||||||
const note = await treeCache.getNote(node.data.noteId);
|
const note = await treeCache.getNote(node.data.noteId);
|
||||||
const parentNote = await treeCache.getNote(branch.parentNoteId);
|
const parentNote = await treeCache.getNote(branch.parentNoteId);
|
||||||
const isNotRoot = note.noteId !== 'root';
|
const isNotRoot = note.noteId !== 'root';
|
||||||
|
const isHoisted = note.noteId === await hoistedNoteService.getHoistedNoteId();
|
||||||
|
|
||||||
const itemsContainer = new ContextMenuItemsContainer(contextMenuItems);
|
const itemsContainer = new ContextMenuItemsContainer(contextMenuItems);
|
||||||
|
|
||||||
// Modify menu entries depending on node status
|
// Modify menu entries depending on node status
|
||||||
itemsContainer.enableItem("insertNoteAfter", isNotRoot && parentNote.type !== 'search');
|
itemsContainer.enableItem("insertNoteAfter", isNotRoot && !isHoisted && parentNote.type !== 'search');
|
||||||
itemsContainer.enableItem("insertChildNote", note.type !== 'search');
|
itemsContainer.enableItem("insertChildNote", note.type !== 'search');
|
||||||
itemsContainer.enableItem("delete", isNotRoot && parentNote.type !== 'search');
|
itemsContainer.enableItem("delete", isNotRoot && parentNote.type !== 'search');
|
||||||
itemsContainer.enableItem("copy", isNotRoot);
|
itemsContainer.enableItem("copy", isNotRoot);
|
||||||
@@ -125,10 +124,8 @@ async function getContextMenuItems(event) {
|
|||||||
itemsContainer.enableItem("export", note.type !== 'search');
|
itemsContainer.enableItem("export", note.type !== 'search');
|
||||||
itemsContainer.enableItem("editBranchPrefix", isNotRoot && parentNote.type !== 'search');
|
itemsContainer.enableItem("editBranchPrefix", isNotRoot && parentNote.type !== 'search');
|
||||||
|
|
||||||
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
itemsContainer.hideItem("hoist", isHoisted);
|
||||||
|
itemsContainer.hideItem("unhoist", !isHoisted || !isNotRoot);
|
||||||
itemsContainer.hideItem("hoist", note.noteId === hoistedNoteId);
|
|
||||||
itemsContainer.hideItem("unhoist", note.noteId !== hoistedNoteId || !isNotRoot);
|
|
||||||
|
|
||||||
// Activate node on right-click
|
// Activate node on right-click
|
||||||
node.setActive();
|
node.setActive();
|
||||||
|
|||||||
@@ -137,6 +137,11 @@ function randomString(len) {
|
|||||||
|
|
||||||
function bindShortcut(keyboardShortcut, handler) {
|
function bindShortcut(keyboardShortcut, handler) {
|
||||||
if (isDesktop()) {
|
if (isDesktop()) {
|
||||||
|
if (isMac()) {
|
||||||
|
// use CMD (meta) instead of CTRL for all shortcuts
|
||||||
|
keyboardShortcut = keyboardShortcut.replace("ctrl", "meta");
|
||||||
|
}
|
||||||
|
|
||||||
$(document).bind('keydown', keyboardShortcut, e => {
|
$(document).bind('keydown', keyboardShortcut, e => {
|
||||||
handler();
|
handler();
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import utils from "./services/utils.js";
|
import utils from "./services/utils.js";
|
||||||
|
import macInit from './services/mac_init.js';
|
||||||
|
|
||||||
|
macInit.init();
|
||||||
|
|
||||||
function SetupModel() {
|
function SetupModel() {
|
||||||
if (syncInProgress) {
|
if (syncInProgress) {
|
||||||
@@ -25,26 +28,20 @@ function SetupModel() {
|
|||||||
|
|
||||||
this.instanceType = utils.isElectron() ? "desktop" : "server";
|
this.instanceType = utils.isElectron() ? "desktop" : "server";
|
||||||
|
|
||||||
this.setupTypeSelected = this.getSetupType = () =>
|
this.setupTypeSelected = () => !!this.setupType();
|
||||||
this.setupNewDocument()
|
|
||||||
|| this.setupSyncFromDesktop()
|
|
||||||
|| this.setupSyncFromServer();
|
|
||||||
|
|
||||||
this.selectSetupType = () => {
|
this.selectSetupType = () => {
|
||||||
this.step(this.getSetupType());
|
this.step(this.setupType());
|
||||||
this.setupType(this.getSetupType());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.back = () => {
|
this.back = () => {
|
||||||
this.step("setup-type");
|
this.step("setup-type");
|
||||||
|
|
||||||
this.setupNewDocument(false);
|
this.setupType("");
|
||||||
this.setupSyncFromServer(false);
|
|
||||||
this.setupSyncFromDesktop(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.finish = async () => {
|
this.finish = async () => {
|
||||||
if (this.setupNewDocument()) {
|
if (this.setupType() === 'new-document') {
|
||||||
const username = this.username();
|
const username = this.username();
|
||||||
const password1 = this.password1();
|
const password1 = this.password1();
|
||||||
const password2 = this.password2();
|
const password2 = this.password2();
|
||||||
@@ -72,7 +69,7 @@ function SetupModel() {
|
|||||||
window.location.replace("/");
|
window.location.replace("/");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (this.setupSyncFromServer()) {
|
else if (this.setupType() === 'sync-from-server') {
|
||||||
const syncServerHost = this.syncServerHost();
|
const syncServerHost = this.syncServerHost();
|
||||||
const syncProxy = this.syncProxy();
|
const syncProxy = this.syncProxy();
|
||||||
const username = this.username();
|
const username = this.username();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -45,14 +45,24 @@
|
|||||||
#header button {
|
#header button {
|
||||||
padding: 1px 5px 1px 5px;
|
padding: 1px 5px 1px 5px;
|
||||||
font-size: small;
|
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 {
|
#global-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
padding: 10px 0 10px 0;
|
padding: 10px 0 10px 0;
|
||||||
margin: 0 10px 0 16px;
|
margin: 0 20px 0 10px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#context-menu-container {
|
#context-menu-container {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ body {
|
|||||||
/* Fix for CKEditor block gutter icon "stretching" body and causing scrollbar to appear after pressing enter
|
/* Fix for CKEditor block gutter icon "stretching" body and causing scrollbar to appear after pressing enter
|
||||||
on the last line of the editor. */
|
on the last line of the editor. */
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#title-container {
|
#title-container {
|
||||||
@@ -16,6 +17,16 @@ body {
|
|||||||
flex-grow: 100;
|
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 {
|
.fancytree-node:not(.fancytree-loading) .fancytree-expander {
|
||||||
background: none;
|
background: none;
|
||||||
width: auto;
|
width: auto;
|
||||||
@@ -70,6 +81,7 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-detail-component {
|
.note-detail-component {
|
||||||
@@ -130,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 */
|
/* By default not focused active tree item is not easily visible, this makes it more visible */
|
||||||
span.fancytree-active:not(.fancytree-focused) .fancytree-title {
|
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;
|
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 {
|
.ui-autocomplete {
|
||||||
@@ -154,17 +179,6 @@ span.fancytree-active:not(.fancytree-focused) .fancytree-title {
|
|||||||
color: #337ab7 !important;
|
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 {
|
div.ui-tooltip {
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
max-height: 600px;
|
max-height: 600px;
|
||||||
@@ -311,6 +325,12 @@ div.ui-tooltip {
|
|||||||
|
|
||||||
.cm-matchhighlight {background-color: #eeeeee}
|
.cm-matchhighlight {background-color: #eeeeee}
|
||||||
|
|
||||||
|
#attribute-list {
|
||||||
|
overflow: auto;
|
||||||
|
/* limiting the size since actual note content is more important */
|
||||||
|
max-height: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
#label-list, #relation-list, #attribute-list {
|
#label-list, #relation-list, #attribute-list {
|
||||||
color: #777777;
|
color: #777777;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
@@ -370,13 +390,8 @@ div.ui-tooltip {
|
|||||||
height: 150px;
|
height: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#history-navigation {
|
.btn:not(.btn-primary):not(.btn-secondary):not(.btn-danger) {
|
||||||
margin: 0 20px 0 5px;
|
border-color: #ddd;
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:not(.btn-primary):not(.btn-danger) {
|
|
||||||
border-color: #bbb;
|
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,8 +449,13 @@ html.theme-dark body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#note-detail-promoted-attributes {
|
#note-detail-promoted-attributes {
|
||||||
max-width: 70%;
|
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
/* setting the display to block since "table" doesn't support scrolling */
|
||||||
|
display: block;
|
||||||
|
flex-basis: content;
|
||||||
|
flex-shrink: 1;
|
||||||
|
flex-grow: 0;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#note-detail-promoted-attributes td, #note-detail-promoted-attributes th {
|
#note-detail-promoted-attributes td, #note-detail-promoted-attributes th {
|
||||||
@@ -532,7 +552,8 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
|
|||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: black;
|
color: black;
|
||||||
border: 1px solid #aaa;
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const tarImportService = require('../../services/import/tar');
|
|||||||
const singleImportService = require('../../services/import/single');
|
const singleImportService = require('../../services/import/single');
|
||||||
const cls = require('../../services/cls');
|
const cls = require('../../services/cls');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
const noteCacheService = require('../../services/note_cache');
|
||||||
|
|
||||||
async function importToBranch(req) {
|
async function importToBranch(req) {
|
||||||
const parentNoteId = req.params.parentNoteId;
|
const parentNoteId = req.params.parentNoteId;
|
||||||
@@ -28,24 +29,32 @@ async function importToBranch(req) {
|
|||||||
// and may produce unintended consequences
|
// and may produce unintended consequences
|
||||||
cls.disableEntityEvents();
|
cls.disableEntityEvents();
|
||||||
|
|
||||||
|
let note; // typically root of the import - client can show it after finishing the import
|
||||||
|
|
||||||
if (extension === '.tar') {
|
if (extension === '.tar') {
|
||||||
return await tarImportService.importTar(file.buffer, parentNote);
|
note = await tarImportService.importTar(file.buffer, parentNote);
|
||||||
}
|
}
|
||||||
else if (extension === '.opml') {
|
else if (extension === '.opml') {
|
||||||
return await opmlImportService.importOpml(file.buffer, parentNote);
|
note = await opmlImportService.importOpml(file.buffer, parentNote);
|
||||||
}
|
}
|
||||||
else if (extension === '.md') {
|
else if (extension === '.md') {
|
||||||
return await singleImportService.importMarkdown(file, parentNote);
|
note = await singleImportService.importMarkdown(file, parentNote);
|
||||||
}
|
}
|
||||||
else if (extension === '.html' || extension === '.htm') {
|
else if (extension === '.html' || extension === '.htm') {
|
||||||
return await singleImportService.importHtml(file, parentNote);
|
note = await singleImportService.importHtml(file, parentNote);
|
||||||
}
|
}
|
||||||
else if (extension === '.enex') {
|
else if (extension === '.enex') {
|
||||||
return await enexImportService.importEnex(file, parentNote);
|
note = await enexImportService.importEnex(file, parentNote);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return [400, `Unrecognized extension ${extension}, must be .tar or .opml`];
|
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 = {
|
module.exports = {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const appInfo = require('../../services/app_info');
|
|||||||
const eventService = require('../../services/events');
|
const eventService = require('../../services/events');
|
||||||
const cls = require('../../services/cls');
|
const cls = require('../../services/cls');
|
||||||
const sqlInit = require('../../services/sql_init');
|
const sqlInit = require('../../services/sql_init');
|
||||||
|
const sql = require('../../services/sql');
|
||||||
|
|
||||||
async function loginSync(req) {
|
async function loginSync(req) {
|
||||||
if (!await sqlInit.schemaExists()) {
|
if (!await sqlInit.schemaExists()) {
|
||||||
@@ -22,7 +23,8 @@ async function loginSync(req) {
|
|||||||
|
|
||||||
const now = new Date();
|
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' }];
|
return [400, { message: 'Auth request time is out of sync' }];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +46,8 @@ async function loginSync(req) {
|
|||||||
req.session.loggedIn = true;
|
req.session.loggedIn = true;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sourceId: sourceIdService.getCurrentSourceId()
|
sourceId: sourceIdService.getCurrentSourceId(),
|
||||||
|
maxSyncId: await sql.getValue("SELECT MAX(id) FROM sync")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ async function anonymize() {
|
|||||||
await db.run("UPDATE notes SET title = 'title', content = 'text'");
|
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 note_revisions SET title = 'title', content = 'text'");
|
||||||
await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
|
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
|
await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN
|
||||||
('documentSecret', 'encryptedDataKey', 'passwordVerificationHash',
|
('documentSecret', 'encryptedDataKey', 'passwordVerificationHash',
|
||||||
'passwordVerificationSalt', 'passwordDerivedKeySalt')`);
|
'passwordVerificationSalt', 'passwordDerivedKeySalt')`);
|
||||||
|
|||||||
@@ -2,14 +2,16 @@
|
|||||||
|
|
||||||
const build = require('./build');
|
const build = require('./build');
|
||||||
const packageJson = require('../../package');
|
const packageJson = require('../../package');
|
||||||
|
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
||||||
|
|
||||||
const APP_DB_VERSION = 121;
|
const APP_DB_VERSION = 121;
|
||||||
const SYNC_VERSION = 2;
|
const SYNC_VERSION = 3;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
appVersion: packageJson.version,
|
appVersion: packageJson.version,
|
||||||
dbVersion: APP_DB_VERSION,
|
dbVersion: APP_DB_VERSION,
|
||||||
syncVersion: SYNC_VERSION,
|
syncVersion: SYNC_VERSION,
|
||||||
buildDate: build.buildDate,
|
buildDate: build.buildDate,
|
||||||
buildRevision: build.buildRevision
|
buildRevision: build.buildRevision,
|
||||||
|
dataDirectory: TRILIUM_DATA_DIR
|
||||||
};
|
};
|
||||||
@@ -1 +1 @@
|
|||||||
module.exports = { buildDate:"2018-12-30T22:38:11+01:00", buildRevision: "32220476aa6795bab036b7dd9057ea3357d7dd51" };
|
module.exports = { buildDate:"2019-01-10T21:31:30+01:00", buildRevision: "0b251530fa0ee61edc8dcc9235033abb73afc614" };
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const Attribute = require('../entities/attribute');
|
|||||||
const NoteRevision = require('../entities/note_revision');
|
const NoteRevision = require('../entities/note_revision');
|
||||||
const RecentNote = require('../entities/recent_note');
|
const RecentNote = require('../entities/recent_note');
|
||||||
const Option = require('../entities/option');
|
const Option = require('../entities/option');
|
||||||
|
const Link = require('../entities/link');
|
||||||
|
|
||||||
async function getHash(entityConstructor, whereBranch) {
|
async function getHash(entityConstructor, whereBranch) {
|
||||||
// subselect is necessary to have correct ordering in GROUP_CONCAT
|
// subselect is necessary to have correct ordering in GROUP_CONCAT
|
||||||
@@ -37,7 +38,8 @@ async function getHashes() {
|
|||||||
recent_notes: await getHash(RecentNote),
|
recent_notes: await getHash(RecentNote),
|
||||||
options: await getHash(Option, "isSynced = 1"),
|
options: await getHash(Option, "isSynced = 1"),
|
||||||
attributes: await getHash(Attribute),
|
attributes: await getHash(Attribute),
|
||||||
api_tokens: await getHash(ApiToken)
|
api_tokens: await getHash(ApiToken),
|
||||||
|
links: await getHash(Link)
|
||||||
};
|
};
|
||||||
|
|
||||||
const elapseTimeMs = new Date().getTime() - startTime.getTime();
|
const elapseTimeMs = new Date().getTime() - startTime.getTime();
|
||||||
|
|||||||
@@ -55,10 +55,6 @@ function getTriliumDataDir() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TRILIUM_DATA_DIR = getTriliumDataDir();
|
const TRILIUM_DATA_DIR = getTriliumDataDir();
|
||||||
|
|
||||||
// not necessary to log this since if we have logs we already know where data dir is.
|
|
||||||
console.log("Using data dir:", TRILIUM_DATA_DIR);
|
|
||||||
|
|
||||||
const DOCUMENT_PATH = TRILIUM_DATA_DIR + "/document.db";
|
const DOCUMENT_PATH = TRILIUM_DATA_DIR + "/document.db";
|
||||||
const BACKUP_DIR = TRILIUM_DATA_DIR + "/backup";
|
const BACKUP_DIR = TRILIUM_DATA_DIR + "/backup";
|
||||||
const LOG_DIR = TRILIUM_DATA_DIR + "/log";
|
const LOG_DIR = TRILIUM_DATA_DIR + "/log";
|
||||||
|
|||||||
@@ -5,14 +5,25 @@ const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION";
|
|||||||
const ENTITY_CREATED = "ENTITY_CREATED";
|
const ENTITY_CREATED = "ENTITY_CREATED";
|
||||||
const ENTITY_CHANGED = "ENTITY_CHANGED";
|
const ENTITY_CHANGED = "ENTITY_CHANGED";
|
||||||
const ENTITY_DELETED = "ENTITY_DELETED";
|
const ENTITY_DELETED = "ENTITY_DELETED";
|
||||||
|
const ENTITY_SYNCED = "ENTITY_SYNCED";
|
||||||
const CHILD_NOTE_CREATED = "CHILD_NOTE_CREATED";
|
const CHILD_NOTE_CREATED = "CHILD_NOTE_CREATED";
|
||||||
|
|
||||||
const eventListeners = {};
|
const eventListeners = {};
|
||||||
|
|
||||||
function subscribe(eventType, listener) {
|
/**
|
||||||
|
* @param eventTypes - can be either single event or an array of events
|
||||||
|
* @param listener
|
||||||
|
*/
|
||||||
|
function subscribe(eventTypes, listener) {
|
||||||
|
if (!Array.isArray(eventTypes)) {
|
||||||
|
eventTypes = [ eventTypes ];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const eventType of eventTypes) {
|
||||||
eventListeners[eventType] = eventListeners[eventType] || [];
|
eventListeners[eventType] = eventListeners[eventType] || [];
|
||||||
eventListeners[eventType].push(listener);
|
eventListeners[eventType].push(listener);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function emit(eventType, data) {
|
async function emit(eventType, data) {
|
||||||
const listeners = eventListeners[eventType];
|
const listeners = eventListeners[eventType];
|
||||||
@@ -39,5 +50,6 @@ module.exports = {
|
|||||||
ENTITY_CREATED,
|
ENTITY_CREATED,
|
||||||
ENTITY_CHANGED,
|
ENTITY_CHANGED,
|
||||||
ENTITY_DELETED,
|
ENTITY_DELETED,
|
||||||
|
ENTITY_SYNCED,
|
||||||
CHILD_NOTE_CREATED
|
CHILD_NOTE_CREATED
|
||||||
};
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const repository = require('./repository');
|
const repository = require('./repository');
|
||||||
|
const log = require('./log');
|
||||||
const protectedSessionService = require('./protected_session');
|
const protectedSessionService = require('./protected_session');
|
||||||
const noteService = require('./notes');
|
const noteService = require('./notes');
|
||||||
const imagemin = require('imagemin');
|
const imagemin = require('imagemin');
|
||||||
@@ -13,7 +14,13 @@ const sanitizeFilename = require('sanitize-filename');
|
|||||||
|
|
||||||
async function saveImage(buffer, originalName, parentNoteId) {
|
async function saveImage(buffer, originalName, parentNoteId) {
|
||||||
const resizedImage = await resize(buffer);
|
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);
|
const imageFormat = imageType(optimizedImage);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const sax = require("sax");
|
const sax = require("sax");
|
||||||
|
const fileType = require('file-type');
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
const xml2js = require('xml2js');
|
const xml2js = require('xml2js');
|
||||||
const log = require("../log");
|
const log = require("../log");
|
||||||
@@ -144,7 +145,7 @@ async function importEnex(file, parentNote) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (currentTag === 'mime') {
|
else if (currentTag === 'mime') {
|
||||||
resource.mime = text;
|
resource.mime = text.toLowerCase();
|
||||||
|
|
||||||
if (text.startsWith("image/")) {
|
if (text.startsWith("image/")) {
|
||||||
resource.title = "image";
|
resource.title = "image";
|
||||||
@@ -222,7 +223,26 @@ async function importEnex(file, parentNote) {
|
|||||||
|
|
||||||
const mediaRegex = new RegExp(`<en-media hash="${hash}"[^>]*>`, 'g');
|
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 originalName = "image." + resource.mime.substr(6);
|
||||||
|
|
||||||
const { url } = await imageService.saveImage(resource.content, originalName, noteEntity.noteId);
|
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
|
// otherwise image would be removed since no note would include it
|
||||||
note.content += imageLink;
|
note.content += imageLink;
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error("error when saving image from ENEX file: " + e);
|
||||||
|
await createResourceNote();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const resourceNote = (await noteService.createNote(noteEntity.noteId, resource.title, resource.content, {
|
await createResourceNote();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,6 @@ function request(req) {
|
|||||||
logger.info(req.method + " " + req.url);
|
logger.info(req.method + " " + req.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
info("Using data dir: " + dataDir.TRILIUM_DATA_DIR);
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
info,
|
info,
|
||||||
error,
|
error,
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ async function sendMessage(client, message) {
|
|||||||
async function sendMessageToAllClients(message) {
|
async function sendMessageToAllClients(message) {
|
||||||
const jsonStr = JSON.stringify(message);
|
const jsonStr = JSON.stringify(message);
|
||||||
|
|
||||||
|
if (webSocketServer) {
|
||||||
log.info("Sending message to all clients: " + jsonStr);
|
log.info("Sending message to all clients: " + jsonStr);
|
||||||
|
|
||||||
webSocketServer.clients.forEach(function each(client) {
|
webSocketServer.clients.forEach(function each(client) {
|
||||||
@@ -60,6 +61,7 @@ async function sendMessageToAllClients(message) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function sendPing(client, lastSentSyncId) {
|
async function sendPing(client, lastSentSyncId) {
|
||||||
const syncData = await sql.getRows("SELECT * FROM sync WHERE id > ?", [lastSentSyncId]);
|
const syncData = await sql.getRows("SELECT * FROM sync WHERE id > ?", [lastSentSyncId]);
|
||||||
|
|||||||
@@ -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'`);
|
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;
|
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) {
|
function highlightResults(results, allTokens) {
|
||||||
// we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks
|
// we remove < signs because they can cause trouble in matching and overwriting existing highlighted chunks
|
||||||
// which would make the resulting HTML string invalid.
|
// which would make the resulting HTML string invalid.
|
||||||
@@ -299,7 +311,9 @@ function getNotePath(noteId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entity}) => {
|
eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED, eventService.ENTITY_SYNCED], async ({entityName, entity}) => {
|
||||||
|
// note that entity can also be just POJO without methods if coming from sync
|
||||||
|
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -311,8 +325,17 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entity})
|
|||||||
delete noteTitles[note.noteId];
|
delete noteTitles[note.noteId];
|
||||||
delete childToParent[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 {
|
else {
|
||||||
noteTitles[note.noteId] = note.title;
|
noteTitles[note.noteId] = note.title;
|
||||||
|
delete protectedNoteTitles[note.noteId];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (entityName === 'branches') {
|
else if (entityName === 'branches') {
|
||||||
@@ -353,15 +376,9 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entity})
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, async () => {
|
eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => {
|
||||||
if (!loaded) {
|
if (loaded) {
|
||||||
return;
|
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]);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -370,5 +387,6 @@ sqlInit.dbReady.then(() => utils.stopWatch("Autocomplete load", load));
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
findNotes,
|
findNotes,
|
||||||
getNotePath,
|
getNotePath,
|
||||||
getNoteTitleForPath
|
getNoteTitleForPath,
|
||||||
|
load
|
||||||
};
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
|
const sqlInit = require('./sql_init');
|
||||||
const optionService = require('./options');
|
const optionService = require('./options');
|
||||||
const dateUtils = require('./date_utils');
|
const dateUtils = require('./date_utils');
|
||||||
const syncTableService = require('./sync_table');
|
const syncTableService = require('./sync_table');
|
||||||
@@ -153,7 +154,8 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {})
|
|||||||
noteId: note.noteId,
|
noteId: note.noteId,
|
||||||
type: attr.type,
|
type: attr.type,
|
||||||
name: attr.name,
|
name: attr.name,
|
||||||
value: attr.value
|
value: attr.value,
|
||||||
|
isInheritable: !!attr.isInheritable
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,10 +359,14 @@ async function deleteNote(branch) {
|
|||||||
const notDeletedBranches = await note.getBranches();
|
const notDeletedBranches = await note.getBranches();
|
||||||
|
|
||||||
if (notDeletedBranches.length === 0) {
|
if (notDeletedBranches.length === 0) {
|
||||||
note.isDeleted = true;
|
// maybe a bit counter-intuitively, protected notes can be deleted also outside of protected session
|
||||||
// we don't reset content here, that's postponed and done later to give the user
|
// this is because protected notes offer only confidentiality which makes some things simpler - e.g. deletion UI
|
||||||
// a chance to correct a mistake
|
// to allow this, we just set the isDeleted flag, otherwise saving would fail because of attempt to encrypt
|
||||||
await note.save();
|
// content with non-existent protected session key
|
||||||
|
// we don't reset content here, that's postponed and done later to give the user a chance to correct a mistake
|
||||||
|
await sql.execute("UPDATE notes SET isDeleted = 1 WHERE noteId = ?", [note.noteId]);
|
||||||
|
// need to manually trigger sync since it's not taken care of by note save
|
||||||
|
await syncTableService.addNoteSync(note.noteId);
|
||||||
|
|
||||||
for (const noteRevision of await note.getRevisions()) {
|
for (const noteRevision of await note.getRevisions()) {
|
||||||
await noteRevision.save();
|
await noteRevision.save();
|
||||||
@@ -403,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)]);
|
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)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqlInit.dbReady.then(() => {
|
||||||
// first cleanup kickoff 5 minutes after startup
|
// first cleanup kickoff 5 minutes after startup
|
||||||
setTimeout(cls.wrap(cleanupDeletedNotes), 5 * 60 * 1000);
|
setTimeout(cls.wrap(cleanupDeletedNotes), 5 * 60 * 1000);
|
||||||
|
|
||||||
setInterval(cls.wrap(cleanupDeletedNotes), 4 * 3600 * 1000);
|
setInterval(cls.wrap(cleanupDeletedNotes), 4 * 3600 * 1000);
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createNewNote,
|
createNewNote,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const sourceIdService = require('./source_id');
|
|||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
|
|
||||||
async function executeNote(note, originEntity) {
|
async function executeNote(note, originEntity) {
|
||||||
if (!note.isJavaScript()) {
|
if (!note.isJavaScript() || !note.isContentAvailable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +80,10 @@ function getParams(params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = []) {
|
async function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = []) {
|
||||||
|
if (!note.isContentAvailable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!note.isJavaScript() && !note.isHtml()) {
|
if (!note.isJavaScript() && !note.isHtml()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const url = require('url');
|
|
||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
const sql = require('./sql');
|
const sql = require('./sql');
|
||||||
const sqlInit = require('./sql_init');
|
const sqlInit = require('./sql_init');
|
||||||
@@ -99,6 +98,16 @@ async function doLogin() {
|
|||||||
|
|
||||||
syncContext.sourceId = resp.sourceId;
|
syncContext.sourceId = resp.sourceId;
|
||||||
|
|
||||||
|
const lastSyncedPull = await getLastSyncedPull();
|
||||||
|
|
||||||
|
// this is important in a scenario where we setup the sync by manually copying the document
|
||||||
|
// lastSyncedPull then could be pretty off for the newly cloned client
|
||||||
|
if (lastSyncedPull > resp.maxSyncId) {
|
||||||
|
log.info(`Lowering last synced pull from ${lastSyncedPull} to ${resp.maxSyncId}`);
|
||||||
|
|
||||||
|
await setLastSyncedPull(resp.maxSyncId);
|
||||||
|
}
|
||||||
|
|
||||||
return syncContext;
|
return syncContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +265,7 @@ async function getEntityRow(entityName, entityId) {
|
|||||||
&& entity.content !== null
|
&& entity.content !== null
|
||||||
&& (entity.type === 'file' || entity.type === 'image')) {
|
&& (entity.type === 'file' || entity.type === 'image')) {
|
||||||
|
|
||||||
entity.content = entity.content.toString("binary");
|
entity.content = entity.content.toString("base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
return entity;
|
return entity;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const sql = require('./sql');
|
|||||||
const log = require('./log');
|
const log = require('./log');
|
||||||
const eventLogService = require('./event_log');
|
const eventLogService = require('./event_log');
|
||||||
const syncTableService = require('./sync_table');
|
const syncTableService = require('./sync_table');
|
||||||
|
const eventService = require('./events');
|
||||||
|
|
||||||
async function updateEntity(sync, entity, sourceId) {
|
async function updateEntity(sync, entity, sourceId) {
|
||||||
const {entityName} = sync;
|
const {entityName} = sync;
|
||||||
@@ -36,11 +37,20 @@ async function updateEntity(sync, entity, sourceId) {
|
|||||||
else {
|
else {
|
||||||
throw new Error(`Unrecognized entity type ${entityName}`);
|
throw new Error(`Unrecognized entity type ${entityName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// currently making exception for protected notes and note revisions because here
|
||||||
|
// the title and content are not available decrypted as listeners would expect
|
||||||
|
if ((entityName !== 'notes' && entityName !== 'note_revisions') || !entity.isProtected) {
|
||||||
|
await eventService.emit(eventService.ENTITY_SYNCED, {
|
||||||
|
entityName,
|
||||||
|
entity
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deserializeNoteContentBuffer(note) {
|
function deserializeNoteContentBuffer(note) {
|
||||||
if (note.content !== null && (note.type === 'file' || note.type === 'image')) {
|
if (note.content !== null && (note.type === 'file' || note.type === 'image')) {
|
||||||
note.content = new Buffer(note.content, 'binary');
|
note.content = Buffer.from(note.content, 'base64');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<div id="history-navigation" style="display: none;">
|
<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-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>
|
<a id="history-forward-button" title="Go to next note." class="icon-action jam jam-arrow-square-right"></a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
<div id="note-detail-search" class="note-detail-component">
|
<div id="note-detail-search" class="note-detail-component">
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
<strong>Search string: </strong>
|
<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>
|
</div>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<div id="confirm-dialog-custom"></div>
|
<div id="confirm-dialog-custom"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<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;">
|
<div style="display: flex; justify-content: space-between;">
|
||||||
<button class="btn btn-primary">Save</button>
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
<div style="display: flex; justify-content: space-between;">
|
<div style="display: flex; justify-content: space-between;">
|
||||||
<button class="btn btn-primary">Save</button>
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
<div style="display: flex; justify-content: space-between;">
|
<div style="display: flex; justify-content: space-between;">
|
||||||
<button class="btn btn-primary">Save</button>
|
<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>
|
</div>
|
||||||
</form>
|
</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>
|
<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>
|
||||||
|
|
||||||
<div id="advanced" class="tab-pane">
|
<div id="advanced" class="tab-pane">
|
||||||
<h4 style="margin-top: 0px;">Sync</h4>
|
<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/>
|
||||||
<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/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<h4>Debugging</h4>
|
<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)
|
<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>
|
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>
|
<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>
|
||||||
|
|
||||||
<div id="about" class="tab-pane">
|
<div id="about" class="tab-pane">
|
||||||
@@ -216,6 +216,11 @@
|
|||||||
<th>Build revision:</th>
|
<th>Build revision:</th>
|
||||||
<td><a href="" target="_blank" id="build-revision"></a></td>
|
<td><a href="" target="_blank" id="build-revision"></a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th>Data directory:</th>
|
||||||
|
<td id="data-directory"></td>
|
||||||
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<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>
|
<h1>Trilium login</h1>
|
||||||
|
|
||||||
<% if (failedAuth) { %>
|
<% if (failedAuth) { %>
|
||||||
@@ -60,6 +60,8 @@
|
|||||||
device = /Mobi/.test(navigator.userAgent) ? "mobile" : "desktop";
|
device = /Mobi/.test(navigator.userAgent) ? "mobile" : "desktop";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Setting device cookie to:", device);
|
||||||
|
|
||||||
setCookie("trilium-device", device);
|
setCookie("trilium-device", device);
|
||||||
|
|
||||||
function setCookie(name, value) {
|
function setCookie(name, value) {
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
<title>Setup</title>
|
<title>Setup</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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>
|
<h1>Trilium Notes setup</h1>
|
||||||
|
|
||||||
<div class="alert alert-warning" id="alert" style="display: none;">
|
<div class="alert alert-warning" id="alert" style="display: none;">
|
||||||
@@ -13,15 +15,15 @@
|
|||||||
|
|
||||||
<div id="setup-type" data-bind="visible: step() == 'setup-type'" style="margin-top: 20px;">
|
<div id="setup-type" data-bind="visible: step() == 'setup-type'" style="margin-top: 20px;">
|
||||||
<div class="radio" style="margin-bottom: 15px;">
|
<div class="radio" style="margin-bottom: 15px;">
|
||||||
<label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupNewDocument">
|
<label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupType">
|
||||||
I'm a new user and I want to create new Trilium document for my notes</label>
|
I'm a new user and I want to create new Trilium document for my notes</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="radio" data-bind="if: instanceType == 'server'" style="margin-bottom: 15px;">
|
<div class="radio" data-bind="if: instanceType == 'server'" style="margin-bottom: 15px;">
|
||||||
<label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupSyncFromDesktop">
|
<label><input type="radio" name="setup-type" value="sync-from-desktop" data-bind="checked: setupType">
|
||||||
I have desktop instance already and I want to setup sync with it</label>
|
I have desktop instance already and I want to setup sync with it</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="radio" data-bind="if: instanceType == 'desktop'" style="margin-bottom: 15px;">
|
<div class="radio" data-bind="if: instanceType == 'desktop'" style="margin-bottom: 15px;">
|
||||||
<label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupSyncFromServer">
|
<label><input type="radio" name="setup-type" value="sync-from-server" data-bind="checked: setupType">
|
||||||
I have server instance up and I want to setup sync with it</label>
|
I have server instance up and I want to setup sync with it</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -47,7 +49,7 @@
|
|||||||
<input type="password" class="form-control" data-bind="value: password2" placeholder="Password">
|
<input type="password" class="form-control" data-bind="value: password2" placeholder="Password">
|
||||||
</div>
|
</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>
|
<li>once you've done all this, click <a href="/">here</a></li>
|
||||||
</ol>
|
</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>
|
||||||
|
|
||||||
<div data-bind="visible: step() == 'sync-from-server'">
|
<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">
|
<input type="password" id="password1" class="form-control" data-bind="value: password1" placeholder="Password">
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -112,6 +114,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
const baseApiUrl = 'api/';
|
const baseApiUrl = 'api/';
|
||||||
|
|||||||
Reference in New Issue
Block a user