mirror of
https://github.com/zadam/trilium.git
synced 2025-10-29 17:26:38 +01:00
Compare commits
30 Commits
v0.27.0-be
...
v0.27.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -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)
|
||||
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trilium",
|
||||
"version": "0.26.1",
|
||||
"version": "0.27.2-beta",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -399,9 +399,9 @@
|
||||
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow=="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "8.10.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.39.tgz",
|
||||
"integrity": "sha512-rE7fktr02J8ybFf6eysife+WF+L4sAHWzw09DgdCebEu+qDwMvv4zl6Bc+825ttGZP73kCKxa3dhJOoGJ8+5mA==",
|
||||
"version": "10.12.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz",
|
||||
"integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"abab": {
|
||||
@@ -2375,12 +2375,12 @@
|
||||
"integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
|
||||
},
|
||||
"electron": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-4.0.0.tgz",
|
||||
"integrity": "sha512-3XPG/3IXlvnT1oe1K6zEushoD0SKbP8xwdrL10EWGe6k2iOV4hSHqJ8vWnR8yZ7VbSXmBRfomEFDNAo/q/cwKw==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-4.0.1.tgz",
|
||||
"integrity": "sha512-kBWDLn1Vq8Tm6+/HpQc8gkjX7wJyQI8v/lf2kAirfi0Q4cXh6vBjozFvV1U/9gGCbyKnIDM+m8/wpyJIjg4w7g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "^8.0.24",
|
||||
"@types/node": "^10.12.18",
|
||||
"electron-download": "^4.1.0",
|
||||
"extract-zip": "^1.0.3"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.27.0-beta",
|
||||
"version": "0.27.3",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@@ -66,7 +66,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"devtron": "1.4.0",
|
||||
"electron": "4.0.0",
|
||||
"electron": "4.0.1",
|
||||
"electron-compile": "6.4.3",
|
||||
"electron-packager": "13.0.1",
|
||||
"electron-rebuild": "1.8.2",
|
||||
|
||||
@@ -15,7 +15,8 @@ const ENTITY_NAME_TO_ENTITY = {
|
||||
"note_revisions": NoteRevision,
|
||||
"recent_notes": RecentNote,
|
||||
"options": Option,
|
||||
"api_tokens": ApiToken
|
||||
"api_tokens": ApiToken,
|
||||
"links": Link
|
||||
};
|
||||
|
||||
function getEntityFromEntityName(entityName) {
|
||||
|
||||
@@ -198,15 +198,17 @@ addTabHandler((async function () {
|
||||
const $syncVersion = $("#sync-version");
|
||||
const $buildDate = $("#build-date");
|
||||
const $buildRevision = $("#build-revision");
|
||||
const $dataDirectory = $("#data-directory");
|
||||
|
||||
const appInfo = await server.get('app-info');
|
||||
|
||||
$appVersion.html(appInfo.appVersion);
|
||||
$dbVersion.html(appInfo.dbVersion);
|
||||
$syncVersion.html(appInfo.syncVersion);
|
||||
$buildDate.html(appInfo.buildDate);
|
||||
$buildRevision.html(appInfo.buildRevision);
|
||||
$appVersion.text(appInfo.appVersion);
|
||||
$dbVersion.text(appInfo.dbVersion);
|
||||
$syncVersion.text(appInfo.syncVersion);
|
||||
$buildDate.text(appInfo.buildDate);
|
||||
$buildRevision.text(appInfo.buildRevision);
|
||||
$buildRevision.attr('href', 'https://github.com/zadam/trilium/commit/' + appInfo.buildRevision);
|
||||
$dataDirectory.text(appInfo.dataDirectory);
|
||||
|
||||
return {};
|
||||
})());
|
||||
|
||||
@@ -85,8 +85,11 @@ async function showAttributes() {
|
||||
$promotedAttributesContainer.empty().append($tbody);
|
||||
}
|
||||
else if (note.type !== 'relation-map') {
|
||||
if (attributes.length > 0) {
|
||||
for (const attribute of attributes) {
|
||||
// display only "own" notes
|
||||
const ownedAttributes = attributes.filter(attr => attr.noteId === note.noteId);
|
||||
|
||||
if (ownedAttributes.length > 0) {
|
||||
for (const attribute of ownedAttributes) {
|
||||
if (attribute.type === 'label') {
|
||||
$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 $actionCell = $("<td>");
|
||||
const $multiplicityCell = $("<td>").addClass("multiplicity");
|
||||
const $multiplicityCell = $("<td>")
|
||||
.addClass("multiplicity")
|
||||
.attr("nowrap", true);
|
||||
|
||||
$tr
|
||||
.append($labelCell)
|
||||
|
||||
@@ -7,6 +7,7 @@ import treeCache from './tree_cache.js';
|
||||
import noteDetailService from './note_detail.js';
|
||||
import noteTypeService from './note_type.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.
|
||||
@@ -42,7 +43,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null) {
|
||||
this.activateNewNote = async notePath => {
|
||||
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
|
||||
* @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;
|
||||
@@ -37,6 +37,8 @@ let noteChangeDisabled = false;
|
||||
|
||||
let isNoteChanged = false;
|
||||
|
||||
let detailLoadedListeners = [];
|
||||
|
||||
const components = {
|
||||
'code': noteDetailCode,
|
||||
'text': noteDetailText,
|
||||
@@ -147,12 +149,6 @@ function setNoteBackgroundIfProtected(note) {
|
||||
$unprotectButton.prop("disabled", !protectedSessionHolder.isProtectedSessionAvailable());
|
||||
}
|
||||
|
||||
let isNewNoteCreated = false;
|
||||
|
||||
function newNoteCreated() {
|
||||
isNewNoteCreated = true;
|
||||
}
|
||||
|
||||
async function handleProtectedSession() {
|
||||
const newSessionCreated = await protectedSessionService.ensureProtectedSession(currentNote.isProtected, false);
|
||||
|
||||
@@ -191,12 +187,6 @@ async function loadNoteDetail(noteId) {
|
||||
attributeService.invalidateAttributes();
|
||||
}
|
||||
|
||||
if (isNewNoteCreated) {
|
||||
isNewNoteCreated = false;
|
||||
|
||||
$noteTitle.focus().select();
|
||||
}
|
||||
|
||||
$noteIdDisplay.html(noteId);
|
||||
|
||||
setNoteBackgroundIfProtected(currentNote);
|
||||
@@ -240,11 +230,13 @@ async function loadNoteDetail(noteId) {
|
||||
// after loading new note make sure editor is scrolled to the top
|
||||
getComponent(currentNote.type).scrollToTop();
|
||||
|
||||
fireDetailLoaded();
|
||||
|
||||
$scriptArea.empty();
|
||||
|
||||
await bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteView');
|
||||
|
||||
if (utils.isDesktop()) {
|
||||
$scriptArea.empty();
|
||||
|
||||
await bundleService.executeRelationBundles(getCurrentNote(), 'runOnNoteView');
|
||||
|
||||
await attributeService.showAttributes();
|
||||
|
||||
await showChildrenOverview();
|
||||
@@ -291,6 +283,30 @@ function focusOnTitle() {
|
||||
$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 => {
|
||||
if (syncData.some(sync => sync.entityName === 'notes' && sync.entityId === getCurrentNoteId())) {
|
||||
infoService.showMessage('Reloading note because of background changes');
|
||||
@@ -325,11 +341,11 @@ export default {
|
||||
getCurrentNote,
|
||||
getCurrentNoteType,
|
||||
getCurrentNoteId,
|
||||
newNoteCreated,
|
||||
focusOnTitle,
|
||||
saveNote,
|
||||
saveNoteIfChanged,
|
||||
noteChanged,
|
||||
getCurrentNoteContent,
|
||||
onNoteChange
|
||||
onNoteChange,
|
||||
addDetailLoadedListener
|
||||
};
|
||||
@@ -184,5 +184,6 @@ export default {
|
||||
protectSubtree,
|
||||
ensureDialogIsClosed,
|
||||
enterProtectedSession,
|
||||
leaveProtectedSession
|
||||
leaveProtectedSession,
|
||||
protectNoteAndSendToServer
|
||||
};
|
||||
@@ -83,6 +83,10 @@ async function setNodeTitleWithPrefix(node) {
|
||||
node.setTitle(utils.escapeHtml(title));
|
||||
}
|
||||
|
||||
function getNode(childNoteId, parentNoteId) {
|
||||
return getNodesByNoteId(childNoteId).find(node => !parentNoteId || node.data.parentNoteId === parentNoteId);
|
||||
}
|
||||
|
||||
async function expandToNote(notePath, expandOpts) {
|
||||
utils.assertArguments(notePath);
|
||||
|
||||
@@ -94,7 +98,18 @@ async function expandToNote(notePath, expandOpts) {
|
||||
|
||||
for (const childNoteId of runPath) {
|
||||
// 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) {
|
||||
console.error(`Can't find node for noteId=${childNoteId} with parentNoteId=${parentNoteId}`);
|
||||
@@ -111,7 +126,7 @@ async function expandToNote(notePath, expandOpts) {
|
||||
}
|
||||
}
|
||||
|
||||
async function activateNote(notePath, newNote) {
|
||||
async function activateNote(notePath, noteLoadedListener) {
|
||||
utils.assertArguments(notePath);
|
||||
|
||||
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||
@@ -131,8 +146,8 @@ async function activateNote(notePath, newNote) {
|
||||
|
||||
const node = await expandToNote(notePath);
|
||||
|
||||
if (newNote) {
|
||||
noteDetailService.newNoteCreated();
|
||||
if (noteLoadedListener) {
|
||||
noteDetailService.addDetailLoadedListener(node.data.noteId, noteLoadedListener);
|
||||
}
|
||||
|
||||
// we use noFocus because when we reload the tree because of background changes
|
||||
@@ -411,13 +426,17 @@ function initFancyTree(tree) {
|
||||
clones: {
|
||||
highlightActiveClones: true
|
||||
},
|
||||
renderNode: async function (event, data) {
|
||||
enhanceTitle: async function (event, data) {
|
||||
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>');
|
||||
|
||||
$(node.span).append(unhoistButton);
|
||||
$span.append(unhoistButton);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -547,7 +566,7 @@ async function createNote(node, parentNoteId, target, isProtected, saveSelection
|
||||
|
||||
await noteDetailService.saveNoteIfChanged();
|
||||
|
||||
noteDetailService.newNoteCreated();
|
||||
noteDetailService.addDetailLoadedListener(note.noteId, noteDetailService.focusOnTitle);
|
||||
|
||||
const noteEntity = new NoteShort(treeCache, note);
|
||||
const branchEntity = new Branch(treeCache, branch);
|
||||
@@ -634,11 +653,15 @@ messagingService.subscribeToSyncMessages(syncData => {
|
||||
}
|
||||
});
|
||||
|
||||
utils.bindShortcut('ctrl+o', () => {
|
||||
utils.bindShortcut('ctrl+o', async () => {
|
||||
const node = getCurrentNode();
|
||||
const parentNoteId = node.data.parentNoteId;
|
||||
const isProtected = treeUtils.getParentProtectedStatus(node);
|
||||
|
||||
if (node.data.noteId === 'root' || node.data.noteId === await hoistedNoteService.getHoistedNoteId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
createNote(node, parentNoteId, 'after', isProtected, true);
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,8 +13,6 @@ import syncService from "./sync.js";
|
||||
import hoistedNoteService from './hoisted_note.js';
|
||||
import ContextMenuItemsContainer from './context_menu_items_container.js';
|
||||
|
||||
const $tree = $("#tree");
|
||||
|
||||
let clipboardIds = [];
|
||||
let clipboardMode = null;
|
||||
|
||||
@@ -110,11 +108,12 @@ async function getContextMenuItems(event) {
|
||||
const note = await treeCache.getNote(node.data.noteId);
|
||||
const parentNote = await treeCache.getNote(branch.parentNoteId);
|
||||
const isNotRoot = note.noteId !== 'root';
|
||||
const isHoisted = note.noteId === await hoistedNoteService.getHoistedNoteId();
|
||||
|
||||
const itemsContainer = new ContextMenuItemsContainer(contextMenuItems);
|
||||
|
||||
// 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("delete", isNotRoot && parentNote.type !== 'search');
|
||||
itemsContainer.enableItem("copy", isNotRoot);
|
||||
@@ -125,10 +124,8 @@ async function getContextMenuItems(event) {
|
||||
itemsContainer.enableItem("export", note.type !== 'search');
|
||||
itemsContainer.enableItem("editBranchPrefix", isNotRoot && parentNote.type !== 'search');
|
||||
|
||||
const hoistedNoteId = await hoistedNoteService.getHoistedNoteId();
|
||||
|
||||
itemsContainer.hideItem("hoist", note.noteId === hoistedNoteId);
|
||||
itemsContainer.hideItem("unhoist", note.noteId !== hoistedNoteId || !isNotRoot);
|
||||
itemsContainer.hideItem("hoist", isHoisted);
|
||||
itemsContainer.hideItem("unhoist", !isHoisted || !isNotRoot);
|
||||
|
||||
// Activate node on right-click
|
||||
node.setActive();
|
||||
|
||||
@@ -25,26 +25,20 @@ function SetupModel() {
|
||||
|
||||
this.instanceType = utils.isElectron() ? "desktop" : "server";
|
||||
|
||||
this.setupTypeSelected = this.getSetupType = () =>
|
||||
this.setupNewDocument()
|
||||
|| this.setupSyncFromDesktop()
|
||||
|| this.setupSyncFromServer();
|
||||
this.setupTypeSelected = () => !!this.setupType();
|
||||
|
||||
this.selectSetupType = () => {
|
||||
this.step(this.getSetupType());
|
||||
this.setupType(this.getSetupType());
|
||||
this.step(this.setupType());
|
||||
};
|
||||
|
||||
this.back = () => {
|
||||
this.step("setup-type");
|
||||
|
||||
this.setupNewDocument(false);
|
||||
this.setupSyncFromServer(false);
|
||||
this.setupSyncFromDesktop(false);
|
||||
this.setupType("");
|
||||
};
|
||||
|
||||
this.finish = async () => {
|
||||
if (this.setupNewDocument()) {
|
||||
if (this.setupType() === 'new-document') {
|
||||
const username = this.username();
|
||||
const password1 = this.password1();
|
||||
const password2 = this.password2();
|
||||
@@ -72,7 +66,7 @@ function SetupModel() {
|
||||
window.location.replace("/");
|
||||
});
|
||||
}
|
||||
else if (this.setupSyncFromServer()) {
|
||||
else if (this.setupType() === 'sync-from-server') {
|
||||
const syncServerHost = this.syncServerHost();
|
||||
const syncProxy = this.syncProxy();
|
||||
const username = this.username();
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -45,14 +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;
|
||||
margin: 0 20px 0 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#context-menu-container {
|
||||
|
||||
@@ -70,6 +70,7 @@ body {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.note-detail-component {
|
||||
@@ -154,17 +155,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;
|
||||
@@ -311,6 +301,12 @@ div.ui-tooltip {
|
||||
|
||||
.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 {
|
||||
color: #777777;
|
||||
padding: 5px;
|
||||
@@ -370,13 +366,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;
|
||||
border-color: #ddd;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
@@ -434,8 +425,13 @@ html.theme-dark body {
|
||||
}
|
||||
|
||||
#note-detail-promoted-attributes {
|
||||
max-width: 70%;
|
||||
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 {
|
||||
@@ -532,7 +528,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ const appInfo = require('../../services/app_info');
|
||||
const eventService = require('../../services/events');
|
||||
const cls = require('../../services/cls');
|
||||
const sqlInit = require('../../services/sql_init');
|
||||
const sql = require('../../services/sql');
|
||||
|
||||
async function loginSync(req) {
|
||||
if (!await sqlInit.schemaExists()) {
|
||||
@@ -44,7 +45,8 @@ async function loginSync(req) {
|
||||
req.session.loggedIn = true;
|
||||
|
||||
return {
|
||||
sourceId: sourceIdService.getCurrentSourceId()
|
||||
sourceId: sourceIdService.getCurrentSourceId(),
|
||||
maxSyncId: await sql.getValue("SELECT MAX(id) FROM sync")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
|
||||
const build = require('./build');
|
||||
const packageJson = require('../../package');
|
||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
|
||||
|
||||
const APP_DB_VERSION = 121;
|
||||
const SYNC_VERSION = 2;
|
||||
const SYNC_VERSION = 3;
|
||||
|
||||
module.exports = {
|
||||
appVersion: packageJson.version,
|
||||
dbVersion: APP_DB_VERSION,
|
||||
syncVersion: SYNC_VERSION,
|
||||
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-05T22:48:11+01:00", buildRevision: "9fca7f09a564c719c834d38d76c3b595c8579b2a" };
|
||||
|
||||
@@ -12,6 +12,7 @@ const Attribute = require('../entities/attribute');
|
||||
const NoteRevision = require('../entities/note_revision');
|
||||
const RecentNote = require('../entities/recent_note');
|
||||
const Option = require('../entities/option');
|
||||
const Link = require('../entities/link');
|
||||
|
||||
async function getHash(entityConstructor, whereBranch) {
|
||||
// subselect is necessary to have correct ordering in GROUP_CONCAT
|
||||
@@ -37,7 +38,8 @@ async function getHashes() {
|
||||
recent_notes: await getHash(RecentNote),
|
||||
options: await getHash(Option, "isSynced = 1"),
|
||||
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();
|
||||
|
||||
@@ -55,10 +55,6 @@ function 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 BACKUP_DIR = TRILIUM_DATA_DIR + "/backup";
|
||||
const LOG_DIR = TRILIUM_DATA_DIR + "/log";
|
||||
|
||||
@@ -5,13 +5,24 @@ const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION";
|
||||
const ENTITY_CREATED = "ENTITY_CREATED";
|
||||
const ENTITY_CHANGED = "ENTITY_CHANGED";
|
||||
const ENTITY_DELETED = "ENTITY_DELETED";
|
||||
const ENTITY_SYNCED = "ENTITY_SYNCED";
|
||||
const CHILD_NOTE_CREATED = "CHILD_NOTE_CREATED";
|
||||
|
||||
const eventListeners = {};
|
||||
|
||||
function subscribe(eventType, listener) {
|
||||
eventListeners[eventType] = eventListeners[eventType] || [];
|
||||
eventListeners[eventType].push(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].push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
async function emit(eventType, data) {
|
||||
@@ -39,5 +50,6 @@ module.exports = {
|
||||
ENTITY_CREATED,
|
||||
ENTITY_CHANGED,
|
||||
ENTITY_DELETED,
|
||||
ENTITY_SYNCED,
|
||||
CHILD_NOTE_CREATED
|
||||
};
|
||||
@@ -45,8 +45,6 @@ function request(req) {
|
||||
logger.info(req.method + " " + req.url);
|
||||
}
|
||||
|
||||
info("Using data dir: " + dataDir.TRILIUM_DATA_DIR);
|
||||
|
||||
module.exports = {
|
||||
info,
|
||||
error,
|
||||
|
||||
@@ -52,13 +52,15 @@ async function sendMessage(client, message) {
|
||||
async function sendMessageToAllClients(message) {
|
||||
const jsonStr = JSON.stringify(message);
|
||||
|
||||
log.info("Sending message to all clients: " + jsonStr);
|
||||
if (webSocketServer) {
|
||||
log.info("Sending message to all clients: " + jsonStr);
|
||||
|
||||
webSocketServer.clients.forEach(function each(client) {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(jsonStr);
|
||||
}
|
||||
});
|
||||
webSocketServer.clients.forEach(function each(client) {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(jsonStr);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function sendPing(client, lastSentSyncId) {
|
||||
|
||||
@@ -299,7 +299,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) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
@@ -153,7 +154,8 @@ async function createNote(parentNoteId, title, content = "", extraOptions = {})
|
||||
noteId: note.noteId,
|
||||
type: attr.type,
|
||||
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();
|
||||
|
||||
if (notDeletedBranches.length === 0) {
|
||||
note.isDeleted = true;
|
||||
// we don't reset content here, that's postponed and done later to give the user
|
||||
// a chance to correct a mistake
|
||||
await note.save();
|
||||
// maybe a bit counter-intuitively, protected notes can be deleted also outside of protected session
|
||||
// this is because protected notes offer only confidentiality which makes some things simpler - e.g. deletion UI
|
||||
// to allow this, we just set the isDeleted flag, otherwise saving would fail because of attempt to encrypt
|
||||
// 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()) {
|
||||
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)]);
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
const url = require('url');
|
||||
const log = require('./log');
|
||||
const sql = require('./sql');
|
||||
const sqlInit = require('./sql_init');
|
||||
@@ -99,6 +98,16 @@ async function doLogin() {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -256,7 +265,7 @@ async function getEntityRow(entityName, entityId) {
|
||||
&& entity.content !== null
|
||||
&& (entity.type === 'file' || entity.type === 'image')) {
|
||||
|
||||
entity.content = entity.content.toString("binary");
|
||||
entity.content = entity.content.toString("base64");
|
||||
}
|
||||
|
||||
return entity;
|
||||
|
||||
@@ -2,6 +2,7 @@ const sql = require('./sql');
|
||||
const log = require('./log');
|
||||
const eventLogService = require('./event_log');
|
||||
const syncTableService = require('./sync_table');
|
||||
const eventService = require('./events');
|
||||
|
||||
async function updateEntity(sync, entity, sourceId) {
|
||||
const {entityName} = sync;
|
||||
@@ -36,11 +37,20 @@ async function updateEntity(sync, entity, sourceId) {
|
||||
else {
|
||||
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) {
|
||||
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;">
|
||||
<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>
|
||||
|
||||
@@ -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">
|
||||
@@ -216,6 +216,11 @@
|
||||
<th>Build revision:</th>
|
||||
<td><a href="" target="_blank" id="build-revision"></a></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Data directory:</th>
|
||||
<td id="data-directory"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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,113 +2,116 @@
|
||||
<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;">
|
||||
<h1>Trilium Notes setup</h1>
|
||||
<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;">
|
||||
</div>
|
||||
|
||||
<div id="setup-type" data-bind="visible: step() == 'setup-type'" style="margin-top: 20px;">
|
||||
<div class="radio" style="margin-bottom: 15px;">
|
||||
<label><input type="radio" name="setup-type" value="new-document" data-bind="checked: setupNewDocument">
|
||||
I'm a new user and I want to create new Trilium document for my notes</label>
|
||||
</div>
|
||||
<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">
|
||||
I have desktop instance already and I want to setup sync with it</label>
|
||||
</div>
|
||||
<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">
|
||||
I have server instance up and I want to setup sync with it</label>
|
||||
<div class="alert alert-warning" id="alert" style="display: none;">
|
||||
</div>
|
||||
|
||||
<button type="button" data-bind="disable: !setupTypeSelected(), click: selectSetupType" class="btn btn-primary">Next</button>
|
||||
</div>
|
||||
<div id="setup-type" data-bind="visible: step() == 'setup-type'" style="margin-top: 20px;">
|
||||
<div class="radio" style="margin-bottom: 15px;">
|
||||
<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>
|
||||
</div>
|
||||
<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: setupType">
|
||||
I have desktop instance already and I want to setup sync with it</label>
|
||||
</div>
|
||||
<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: setupType">
|
||||
I have server instance up and I want to setup sync with it</label>
|
||||
</div>
|
||||
|
||||
<div data-bind="visible: step() == 'new-document'">
|
||||
<h2>New document</h2>
|
||||
|
||||
<p>You're almost done with the setup. The last thing is to choose username and password using which you'll login to the application.
|
||||
This password is also used for generating encryption key which encrypts protected notes.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" data-bind="value: username" placeholder="Choose alphanumeric username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password1">Password</label>
|
||||
<input type="password" class="form-control" data-bind="value: password1" placeholder="Password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password2">Repeat password</label>
|
||||
<input type="password" class="form-control" data-bind="value: password2" placeholder="Password">
|
||||
<button type="button" data-bind="disable: !setupTypeSelected(), click: selectSetupType" class="btn btn-primary">Next</button>
|
||||
</div>
|
||||
|
||||
<button type="button" data-bind="click: back" class="btn btn-default">Back</button>
|
||||
<div data-bind="visible: step() == 'new-document'">
|
||||
<h2>New document</h2>
|
||||
|
||||
|
||||
<p>You're almost done with the setup. The last thing is to choose username and password using which you'll login to the application.
|
||||
This password is also used for generating encryption key which encrypts protected notes.</p>
|
||||
|
||||
<button type="button" data-bind="click: finish" class="btn btn-primary">Finish setup</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" data-bind="value: username" placeholder="Choose alphanumeric username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password1">Password</label>
|
||||
<input type="password" class="form-control" data-bind="value: password1" placeholder="Password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password2">Repeat password</label>
|
||||
<input type="password" class="form-control" data-bind="value: password2" placeholder="Password">
|
||||
</div>
|
||||
|
||||
<div data-bind="visible: step() == 'sync-from-desktop'">
|
||||
<h2>Sync from Desktop</h2>
|
||||
<button type="button" data-bind="click: back" class="btn btn-secondary">Back</button>
|
||||
|
||||
<p>This setup needs to be initiated from the desktop instance:</p>
|
||||
|
||||
|
||||
<ol>
|
||||
<li>please open your desktop instance of Trilium Notes</li>
|
||||
<li>click on Options button in the top right</li>
|
||||
<li>click on Sync tab</li>
|
||||
<li>configure server instance address to the: <span id="current-host"></span> and click save.</li>
|
||||
<li>click on "Test sync" button</li>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div data-bind="visible: step() == 'sync-from-server'">
|
||||
<h2>Sync from Server</h2>
|
||||
|
||||
<p>Please enter Trilium server address and credentials below. This will download the whole Trilium document from server and setup sync to it. Depending on the document size and your connection speed, this may take a while.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sync-server-host">Trilium server address</label>
|
||||
<input type="text" id="syncServerHost" class="form-control" data-bind="value: syncServerHost" placeholder="https://<hostname>:<port>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sync-proxy">Proxy server (optional)</label>
|
||||
<input type="text" id="sync-proxy" class="form-control" data-bind="value: syncProxy" placeholder="https://<hostname>:<port>">
|
||||
|
||||
<p><strong>Note:</strong> If you leave proxy setting blank, system proxy will be used (applies to desktop/electron build only)</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" id="username" class="form-control" data-bind="value: username" placeholder="Username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password1">Password</label>
|
||||
<input type="password" id="password1" class="form-control" data-bind="value: password1" placeholder="Password">
|
||||
<button type="button" data-bind="click: finish" class="btn btn-primary">Finish setup</button>
|
||||
</div>
|
||||
|
||||
<button type="button" data-bind="click: back" class="btn btn-default">Back</button>
|
||||
<div data-bind="visible: step() == 'sync-from-desktop'">
|
||||
<h2>Sync from Desktop</h2>
|
||||
|
||||
|
||||
<p>This setup needs to be initiated from the desktop instance:</p>
|
||||
|
||||
<button type="button" data-bind="click: finish" class="btn btn-primary">Finish setup</button>
|
||||
</div>
|
||||
<ol>
|
||||
<li>please open your desktop instance of Trilium Notes</li>
|
||||
<li>click on Options button in the top right</li>
|
||||
<li>click on Sync tab</li>
|
||||
<li>configure server instance address to the: <span id="current-host"></span> and click save.</li>
|
||||
<li>click on "Test sync" button</li>
|
||||
<li>once you've done all this, click <a href="/">here</a></li>
|
||||
</ol>
|
||||
|
||||
<div data-bind="visible: step() == 'sync-in-progress'">
|
||||
<h2>Sync in progress</h2>
|
||||
<button type="button" data-bind="click: back" class="btn btn-secondary">Back</button>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-success">Sync has been correctly set up. It will take some time for the initial sync to finish. Once it's done, you'll be redirected to the login page.</div>
|
||||
<div data-bind="visible: step() == 'sync-from-server'">
|
||||
<h2>Sync from Server</h2>
|
||||
|
||||
<div data-bind="if: instanceType == 'desktop'">
|
||||
Outstanding sync items: <strong id="outstanding-syncs">N/A</strong>
|
||||
<p>Please enter Trilium server address and credentials below. This will download the whole Trilium document from server and setup sync to it. Depending on the document size and your connection speed, this may take a while.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sync-server-host">Trilium server address</label>
|
||||
<input type="text" id="syncServerHost" class="form-control" data-bind="value: syncServerHost" placeholder="https://<hostname>:<port>">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sync-proxy">Proxy server (optional)</label>
|
||||
<input type="text" id="sync-proxy" class="form-control" data-bind="value: syncProxy" placeholder="https://<hostname>:<port>">
|
||||
|
||||
<p><strong>Note:</strong> If you leave proxy setting blank, system proxy will be used (applies to desktop/electron build only)</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" id="username" class="form-control" data-bind="value: username" placeholder="Username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password1">Password</label>
|
||||
<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-secondary">Back</button>
|
||||
|
||||
|
||||
|
||||
<button type="button" data-bind="click: finish" class="btn btn-primary">Finish setup</button>
|
||||
</div>
|
||||
|
||||
<div data-bind="visible: step() == 'sync-in-progress'">
|
||||
<h2>Sync in progress</h2>
|
||||
|
||||
<div class="alert alert-success">Sync has been correctly set up. It will take some time for the initial sync to finish. Once it's done, you'll be redirected to the login page.</div>
|
||||
|
||||
<div data-bind="if: instanceType == 'desktop'">
|
||||
Outstanding sync items: <strong id="outstanding-syncs">N/A</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user