mirror of
https://github.com/zadam/trilium.git
synced 2025-11-09 14:55:50 +01:00
converted options dialog to new pattern
This commit is contained in:
99
src/public/app/widgets/dialogs/options.js
Normal file
99
src/public/app/widgets/dialogs/options.js
Normal file
@@ -0,0 +1,99 @@
|
||||
"use strict";
|
||||
|
||||
import server from '../../services/server.js';
|
||||
import utils from "../../services/utils.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
|
||||
const TPL = `
|
||||
<div class="options-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable" style="min-width: 1000px;" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Options</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#options-appearance">Appearance</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#options-shortcuts">Shortcuts</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#options-code-notes">Code notes</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#options-password">Password</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#options-etapi">ETAPI</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#options-backup">Backup</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#options-sync-setup">Sync</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#options-other">Other</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#options-advanced">Advanced</a>
|
||||
</li>
|
||||
</ul>
|
||||
<br/>
|
||||
<div class="tab-content">
|
||||
<div id="options-appearance" class="tab-pane active"></div>
|
||||
<div id="options-shortcuts" class="tab-pane"></div>
|
||||
<div id="options-code-notes" class="tab-pane"></div>
|
||||
<div id="options-password" class="tab-pane"></div>
|
||||
<div id="options-etapi" class="tab-pane"></div>
|
||||
<div id="options-backup" class="tab-pane"></div>
|
||||
<div id="options-sync-setup" class="tab-pane"></div>
|
||||
<div id="options-other" class="tab-pane"></div>
|
||||
<div id="options-advanced" class="tab-pane"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
||||
export default class OptionsDialog extends BasicWidget {
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
}
|
||||
|
||||
async showOptionsEvent({openTab}) {
|
||||
const options = await server.get('options');
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
|
||||
(await Promise.all([
|
||||
import('./options/appearance.js'),
|
||||
import('./options/shortcuts.js'),
|
||||
import('./options/code_notes.js'),
|
||||
import('./options/password.js'),
|
||||
import('./options/etapi.js'),
|
||||
import('./options/backup.js'),
|
||||
import('./options/sync.js'),
|
||||
import('./options/other.js'),
|
||||
import('./options/advanced.js')
|
||||
]))
|
||||
.map(m => new m.default)
|
||||
.forEach(tab => {
|
||||
if (tab.optionsLoaded) {
|
||||
tab.optionsLoaded(options)
|
||||
}
|
||||
});
|
||||
|
||||
if (openTab) {
|
||||
$(`.nav-link[href='#options-${openTab}']`).trigger("click");
|
||||
}
|
||||
}
|
||||
}
|
||||
130
src/public/app/widgets/dialogs/options/advanced.js
Normal file
130
src/public/app/widgets/dialogs/options/advanced.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import server from "../../../services/server.js";
|
||||
import toastService from "../../../services/toast.js";
|
||||
|
||||
const TPL = `
|
||||
<h4 style="margin-top: 0;">Sync</h4>
|
||||
<button id="force-full-sync-button" class="btn">Force full sync</button>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<button id="fill-entity-changes-button" class="btn">Fill entity changes records</button>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h4>Database integrity check</h4>
|
||||
|
||||
<p>This will check that the database is not corrupted on the SQLite level. It might take some time, depending on the DB size.</p>
|
||||
|
||||
<button id="check-integrity-button" class="btn">Check database integrity</button><br/><br/>
|
||||
|
||||
<h4>Consistency checks</h4>
|
||||
|
||||
<button id="find-and-fix-consistency-issues-button" class="btn">Find and fix consistency issues</button><br/><br/>
|
||||
|
||||
<h4>Anonymize database</h4>
|
||||
|
||||
<h5>Full anonymization</h5>
|
||||
|
||||
<p>This action will create a new copy of the database and anonymize it (remove all note content and leave only structure and some non-sensitive metadata)
|
||||
for sharing online for debugging purposes without fear of leaking your personal data.</p>
|
||||
|
||||
<button id="anonymize-full-button" class="btn">Save fully anonymized database</button><br/><br/>
|
||||
|
||||
<h5>Light anonymization</h5>
|
||||
|
||||
<p>This action will create a new copy of the database and do a light anonymization on it - specifically only content of all notes will be removed, but titles and attributes will remaing. Additionally, custom JS frontend/backend script notes and custom widgets will remain. This provides more context to debug the issues.</p>
|
||||
|
||||
<p>You can decide yourself if you want to provide fully or lightly anonymized database. Even fully anonymized DB is very useful, however in some cases lightly anonymized database can speed up the process of bug identification and fixing.</p>
|
||||
|
||||
<button id="anonymize-light-button" class="btn">Save lightly anonymized database</button><br/><br/>
|
||||
|
||||
<h4>Vacuum database</h4>
|
||||
|
||||
<p>This will rebuild the database which will typically result in a smaller database file. No data will be actually changed.</p>
|
||||
|
||||
<button id="vacuum-database-button" class="btn">Vacuum database</button>`;
|
||||
|
||||
export default class AdvancedOptions {
|
||||
constructor() {
|
||||
$("#options-advanced").html(TPL);
|
||||
|
||||
this.$forceFullSyncButton = $("#force-full-sync-button");
|
||||
this.$fillEntityChangesButton = $("#fill-entity-changes-button");
|
||||
this.$anonymizeFullButton = $("#anonymize-full-button");
|
||||
this.$anonymizeLightButton = $("#anonymize-light-button");
|
||||
this.$vacuumDatabaseButton = $("#vacuum-database-button");
|
||||
this.$findAndFixConsistencyIssuesButton = $("#find-and-fix-consistency-issues-button");
|
||||
this.$checkIntegrityButton = $("#check-integrity-button");
|
||||
|
||||
this.$forceFullSyncButton.on('click', async () => {
|
||||
await server.post('sync/force-full-sync');
|
||||
|
||||
toastService.showMessage("Full sync triggered");
|
||||
});
|
||||
|
||||
this.$fillEntityChangesButton.on('click', async () => {
|
||||
toastService.showMessage("Filling entity changes rows...");
|
||||
|
||||
await server.post('sync/fill-entity-changes');
|
||||
|
||||
toastService.showMessage("Sync rows filled successfully");
|
||||
});
|
||||
|
||||
this.$anonymizeFullButton.on('click', async () => {
|
||||
toastService.showMessage("Creating fully anonymized database...");
|
||||
|
||||
const resp = await server.post('database/anonymize/full');
|
||||
|
||||
if (!resp.success) {
|
||||
toastService.showError("Could not create anonymized database, check backend logs for details");
|
||||
}
|
||||
else {
|
||||
toastService.showMessage(`Created fully anonymized database in ${resp.anonymizedFilePath}`, 10000);
|
||||
}
|
||||
});
|
||||
|
||||
this.$anonymizeLightButton.on('click', async () => {
|
||||
toastService.showMessage("Creating lightly anonymized database...");
|
||||
|
||||
const resp = await server.post('database/anonymize/light');
|
||||
|
||||
if (!resp.success) {
|
||||
toastService.showError("Could not create anonymized database, check backend logs for details");
|
||||
}
|
||||
else {
|
||||
toastService.showMessage(`Created lightly anonymized database in ${resp.anonymizedFilePath}`, 10000);
|
||||
}
|
||||
});
|
||||
|
||||
this.$vacuumDatabaseButton.on('click', async () => {
|
||||
toastService.showMessage("Vacuuming database...");
|
||||
|
||||
await server.post('database/vacuum-database');
|
||||
|
||||
toastService.showMessage("Database has been vacuumed");
|
||||
});
|
||||
|
||||
this.$findAndFixConsistencyIssuesButton.on('click', async () => {
|
||||
toastService.showMessage("Finding and fixing consistency issues...");
|
||||
|
||||
await server.post('database/find-and-fix-consistency-issues');
|
||||
|
||||
toastService.showMessage("Consistency issues should be fixed.");
|
||||
});
|
||||
|
||||
this.$checkIntegrityButton.on('click', async () => {
|
||||
toastService.showMessage("Checking database integrity...");
|
||||
|
||||
const {results} = await server.get('database/check-integrity');
|
||||
|
||||
if (results.length === 1 && results[0].integrity_check === "ok") {
|
||||
toastService.showMessage("Integrity check succeeded - no problems found.");
|
||||
}
|
||||
else {
|
||||
toastService.showMessage("Integrity check failed: " + JSON.stringify(results, null, 2), 15000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
333
src/public/app/widgets/dialogs/options/appearance.js
Normal file
333
src/public/app/widgets/dialogs/options/appearance.js
Normal file
@@ -0,0 +1,333 @@
|
||||
import server from "../../../services/server.js";
|
||||
import utils from "../../../services/utils.js";
|
||||
import appContext from "../../../services/app_context.js";
|
||||
|
||||
const FONT_FAMILIES = [
|
||||
{ value: "theme", label: "Theme defined" },
|
||||
{ value: "serif", label: "Serif" },
|
||||
{ value: "sans-serif", label: "Sans Serif" },
|
||||
{ value: "monospace", label: "Monospace" },
|
||||
{ value: "Arial", label: "Arial" },
|
||||
{ value: "Verdana", label: "Verdana" },
|
||||
{ value: "Helvetica", label: "Helvetica" },
|
||||
{ value: "Tahoma", label: "Tahoma" },
|
||||
{ value: "Trebuchet MS", label: "Trebuchet MS" },
|
||||
{ value: "Times New Roman", label: "Times New Roman" },
|
||||
{ value: "Georgia", label: "Georgia" },
|
||||
{ value: "Garamond", label: "Garamond" },
|
||||
{ value: "Courier New", label: "Courier New" },
|
||||
{ value: "Brush Script MT", label: "Brush Script MT" },
|
||||
{ value: "Impact", label: "Impact" },
|
||||
{ value: "American Typewriter", label: "American Typewriter" },
|
||||
{ value: "Andalé Mono", label: "Andalé Mono" },
|
||||
{ value: "Lucida Console", label: "Lucida Console" },
|
||||
{ value: "Monaco", label: "Monaco" },
|
||||
{ value: "Bradley Hand", label: "Bradley Hand" },
|
||||
{ value: "Luminari", label: "Luminari" },
|
||||
{ value: "Comic Sans MS", label: "Comic Sans MS" },
|
||||
];
|
||||
|
||||
const TPL = `
|
||||
<p><strong>Settings on this options tab are saved automatically after each change.</strong></p>
|
||||
|
||||
<form>
|
||||
<div class="form-group row">
|
||||
<div class="col-4">
|
||||
<label for="heading-style">Heading style</label>
|
||||
<select class="form-control" id="heading-style">
|
||||
<option value="plain">Plain</option>
|
||||
<option value="underline">Underline</option>
|
||||
<option value="markdown">Markdown-style</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<label for="zoom-factor-select">Zoom factor (desktop build only)</label>
|
||||
|
||||
<input type="number" class="form-control" id="zoom-factor-select" min="0.3" max="2.0" step="0.1"/>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<label for="native-title-bar-select">Native title bar (requires app restart)</label>
|
||||
|
||||
<select class="form-control" id="native-title-bar-select">
|
||||
<option value="show">enabled</option>
|
||||
<option value="hide">disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Zooming can be controlled with CTRL+- and CTRL+= shortcuts as well.</p>
|
||||
|
||||
<h4>Theme</h4>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-4">
|
||||
<label for="theme-select">Theme</label>
|
||||
<select class="form-control" id="theme-select"></select>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<label for="override-theme-fonts">Override theme fonts</label>
|
||||
<input type="checkbox" class="form-control" id="override-theme-fonts">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="overriden-font-settings">
|
||||
<h4>Fonts</h4>
|
||||
|
||||
<h5>Main font</h5>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-4">
|
||||
<label for="main-font-family">Font family</label>
|
||||
<select class="form-control" id="main-font-family"></select>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<label for="main-font-size">Size</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="main-font-size" min="50" max="200" step="10"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5>Note tree font</h5>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-4">
|
||||
<label for="tree-font-family">Font family</label>
|
||||
<select class="form-control" id="tree-font-family"></select>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<label for="tree-font-size">Size</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="tree-font-size" min="50" max="200" step="10"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5>Note detail font</h5>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-4">
|
||||
<label for="detail-font-family">Font family</label>
|
||||
<select class="form-control" id="detail-font-family"></select>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<label for="detail-font-size">Size</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="detail-font-size" min="50" max="200" step="10"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5>Monospace font</h5>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-4">
|
||||
<label for="monospace-font-family">Font family</label>
|
||||
<select class="form-control" id="monospace-font-family"></select>
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
<label for="monospace-font-size">Size</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" id="monospace-font-size" min="50" max="200" step="10"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>Note that tree and detail font sizing is relative to the main font size setting.</p>
|
||||
|
||||
<p>Not all listed fonts may be available on your system.</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
To apply font changes, click on
|
||||
<button class="btn btn-micro reload-frontend-button">reload frontend</button>
|
||||
</p>
|
||||
|
||||
<h4>Content width</h4>
|
||||
|
||||
<p>Trilium by default limits max content width to improve readability for maximized screens on wide screens.</p>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-4">
|
||||
<label for="max-content-width">Max content width in pixels</label>
|
||||
<input type="number" min="200" step="10" class="form-control" id="max-content-width">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
To content width changes, click on
|
||||
<button class="btn btn-micro reload-frontend-button">reload frontend</button>
|
||||
</p>
|
||||
</form>`;
|
||||
|
||||
export default class ApperanceOptions {
|
||||
constructor() {
|
||||
$("#options-appearance").html(TPL);
|
||||
|
||||
this.$zoomFactorSelect = $("#zoom-factor-select");
|
||||
this.$nativeTitleBarSelect = $("#native-title-bar-select");
|
||||
this.$headingStyle = $("#heading-style");
|
||||
|
||||
this.$themeSelect = $("#theme-select");
|
||||
this.$overrideThemeFonts = $("#override-theme-fonts");
|
||||
|
||||
this.$overridenFontSettings = $("#overriden-font-settings");
|
||||
|
||||
this.$mainFontSize = $("#main-font-size");
|
||||
this.$mainFontFamily = $("#main-font-family");
|
||||
|
||||
this.$treeFontSize = $("#tree-font-size");
|
||||
this.$treeFontFamily = $("#tree-font-family");
|
||||
|
||||
this.$detailFontSize = $("#detail-font-size");
|
||||
this.$detailFontFamily = $("#detail-font-family");
|
||||
|
||||
this.$monospaceFontSize = $("#monospace-font-size");
|
||||
this.$monospaceFontFamily = $("#monospace-font-family");
|
||||
|
||||
$(".reload-frontend-button").on("click", () => utils.reloadFrontendApp("changes from appearance options"));
|
||||
|
||||
this.$body = $("body");
|
||||
|
||||
this.$themeSelect.on('change', async () => {
|
||||
const newTheme = this.$themeSelect.val();
|
||||
|
||||
await server.put('options/theme/' + newTheme);
|
||||
|
||||
utils.reloadFrontendApp("theme change");
|
||||
});
|
||||
|
||||
this.$overrideThemeFonts.on('change', async () => {
|
||||
const isOverriden = this.$overrideThemeFonts.is(":checked");
|
||||
|
||||
await server.put('options/overrideThemeFonts/' + isOverriden.toString());
|
||||
|
||||
this.$overridenFontSettings.toggle(isOverriden);
|
||||
});
|
||||
|
||||
this.$zoomFactorSelect.on('change', () => { appContext.triggerCommand('setZoomFactorAndSave', {zoomFactor: this.$zoomFactorSelect.val()}); });
|
||||
|
||||
this.$nativeTitleBarSelect.on('change', () => {
|
||||
const nativeTitleBarVisible = this.$nativeTitleBarSelect.val() === 'show' ? 'true' : 'false';
|
||||
|
||||
server.put('options/nativeTitleBarVisible/' + nativeTitleBarVisible);
|
||||
});
|
||||
|
||||
this.$headingStyle.on('change', () => {
|
||||
const newHeadingStyle = this.$headingStyle.val();
|
||||
|
||||
this.toggleBodyClass("heading-style-", newHeadingStyle);
|
||||
|
||||
server.put('options/headingStyle/' + newHeadingStyle);
|
||||
});
|
||||
|
||||
const optionsToSave = [
|
||||
'mainFontFamily', 'mainFontSize',
|
||||
'treeFontFamily', 'treeFontSize',
|
||||
'detailFontFamily', 'detailFontSize',
|
||||
'monospaceFontFamily', 'monospaceFontSize'
|
||||
];
|
||||
|
||||
for (const optionName of optionsToSave) {
|
||||
this['$' + optionName].on('change', () => server.put(`options/${optionName}/${this['$' + optionName].val()}`));
|
||||
}
|
||||
|
||||
this.$maxContentWidth = $("#max-content-width");
|
||||
|
||||
this.$maxContentWidth.on('change', async () => {
|
||||
const maxContentWidth = this.$maxContentWidth.val();
|
||||
|
||||
await server.put('options/maxContentWidth/' + maxContentWidth);
|
||||
})
|
||||
}
|
||||
|
||||
toggleBodyClass(prefix, value) {
|
||||
for (const clazz of Array.from(this.$body[0].classList)) { // create copy to safely iterate over while removing classes
|
||||
if (clazz.startsWith(prefix)) {
|
||||
this.$body.removeClass(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
this.$body.addClass(prefix + value);
|
||||
}
|
||||
|
||||
async optionsLoaded(options) {
|
||||
if (utils.isElectron()) {
|
||||
this.$zoomFactorSelect.val(options.zoomFactor);
|
||||
}
|
||||
else {
|
||||
this.$zoomFactorSelect.prop('disabled', true);
|
||||
}
|
||||
|
||||
this.$nativeTitleBarSelect.val(options.nativeTitleBarVisible === 'true' ? 'show' : 'hide');
|
||||
|
||||
this.$headingStyle.val(options.headingStyle);
|
||||
|
||||
const themes = [
|
||||
{ val: 'light', title: 'Light' },
|
||||
{ val: 'dark', title: 'Dark' }
|
||||
].concat(await server.get('options/user-themes'));
|
||||
|
||||
this.$themeSelect.empty();
|
||||
|
||||
for (const theme of themes) {
|
||||
this.$themeSelect.append($("<option>")
|
||||
.attr("value", theme.val)
|
||||
.attr("data-note-id", theme.noteId)
|
||||
.html(theme.title));
|
||||
}
|
||||
|
||||
this.$themeSelect.val(options.theme);
|
||||
|
||||
this.$overrideThemeFonts.prop('checked', options.overrideThemeFonts === 'true');
|
||||
this.$overridenFontSettings.toggle(options.overrideThemeFonts === 'true');
|
||||
|
||||
this.$mainFontSize.val(options.mainFontSize);
|
||||
this.fillFontFamilyOptions(this.$mainFontFamily, options.mainFontFamily);
|
||||
|
||||
this.$treeFontSize.val(options.treeFontSize);
|
||||
this.fillFontFamilyOptions(this.$treeFontFamily, options.treeFontFamily);
|
||||
|
||||
this.$detailFontSize.val(options.detailFontSize);
|
||||
this.fillFontFamilyOptions(this.$detailFontFamily, options.detailFontFamily);
|
||||
|
||||
this.$monospaceFontSize.val(options.monospaceFontSize);
|
||||
this.fillFontFamilyOptions(this.$monospaceFontFamily, options.monospaceFontFamily);
|
||||
|
||||
this.$maxContentWidth.val(options.maxContentWidth);
|
||||
}
|
||||
|
||||
fillFontFamilyOptions($select, currentValue) {
|
||||
$select.empty();
|
||||
|
||||
for (const {value, label} of FONT_FAMILIES) {
|
||||
$select.append($("<option>")
|
||||
.attr("value", value)
|
||||
.prop("selected", value === currentValue)
|
||||
.text(label));
|
||||
}
|
||||
}
|
||||
}
|
||||
78
src/public/app/widgets/dialogs/options/backup.js
Normal file
78
src/public/app/widgets/dialogs/options/backup.js
Normal file
@@ -0,0 +1,78 @@
|
||||
import server from "../../../services/server.js";
|
||||
import toastService from "../../../services/toast.js";
|
||||
|
||||
const TPL = `
|
||||
<h4>Automatic backup</h4>
|
||||
|
||||
<p>Trilium can back up the database automatically:</p>
|
||||
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="daily-backup-enabled">
|
||||
<label class="custom-control-label" for="daily-backup-enabled">Enable daily backup</label>
|
||||
</div>
|
||||
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="weekly-backup-enabled">
|
||||
<label class="custom-control-label" for="weekly-backup-enabled">Enable weekly backup</label>
|
||||
</div>
|
||||
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="monthly-backup-enabled">
|
||||
<label class="custom-control-label" for="monthly-backup-enabled">Enable monthly backup</label>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<p>It's recommended to keep the backup turned on, but this can make application startup slow with large databases and/or slow storage devices.</p>
|
||||
|
||||
<br/>
|
||||
|
||||
<h4>Backup now</h4>
|
||||
|
||||
<button id="backup-database-button" class="btn">Backup database now</button><br/><br/>
|
||||
`;
|
||||
|
||||
export default class BackupOptions {
|
||||
constructor() {
|
||||
$("#options-backup").html(TPL);
|
||||
|
||||
this.$backupDatabaseButton = $("#backup-database-button");
|
||||
|
||||
this.$backupDatabaseButton.on('click', async () => {
|
||||
const {backupFile} = await server.post('database/backup-database');
|
||||
|
||||
toastService.showMessage("Database has been backed up to " + backupFile, 10000);
|
||||
});
|
||||
|
||||
this.$dailyBackupEnabled = $("#daily-backup-enabled");
|
||||
this.$weeklyBackupEnabled = $("#weekly-backup-enabled");
|
||||
this.$monthlyBackupEnabled = $("#monthly-backup-enabled");
|
||||
|
||||
this.$dailyBackupEnabled.on('change', () => {
|
||||
const opts = { 'dailyBackupEnabled': this.$dailyBackupEnabled.is(":checked") ? "true" : "false" };
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$weeklyBackupEnabled.on('change', () => {
|
||||
const opts = { 'weeklyBackupEnabled': this.$weeklyBackupEnabled.is(":checked") ? "true" : "false" };
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$monthlyBackupEnabled.on('change', () => {
|
||||
const opts = { 'monthlyBackupEnabled': this.$monthlyBackupEnabled.is(":checked") ? "true" : "false" };
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
optionsLoaded(options) {
|
||||
this.$dailyBackupEnabled.prop("checked", options['dailyBackupEnabled'] === 'true');
|
||||
this.$weeklyBackupEnabled.prop("checked", options['weeklyBackupEnabled'] === 'true');
|
||||
this.$monthlyBackupEnabled.prop("checked", options['monthlyBackupEnabled'] === 'true');
|
||||
}
|
||||
}
|
||||
62
src/public/app/widgets/dialogs/options/code_notes.js
Normal file
62
src/public/app/widgets/dialogs/options/code_notes.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import mimeTypesService from "../../../services/mime_types.js";
|
||||
import options from "../../../services/options.js";
|
||||
import server from "../../../services/server.js";
|
||||
import toastService from "../../../services/toast.js";
|
||||
import utils from "../../../services/utils.js";
|
||||
|
||||
const TPL = `
|
||||
<h4>Use vim keybindings in CodeNotes (no ex mode)</h4>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="vim-keymap-enabled">
|
||||
<label class="custom-control-label" for="vim-keymap-enabled">Enable Vim Keybindings</label>
|
||||
</div>
|
||||
<h4>Available MIME types in the dropdown</h4>
|
||||
|
||||
<ul id="options-mime-types" style="max-height: 500px; overflow: auto; list-style-type: none;"></ul>`;
|
||||
|
||||
export default class CodeNotesOptions {
|
||||
constructor() {
|
||||
$("#options-code-notes").html(TPL);
|
||||
|
||||
this.$vimKeymapEnabled = $("#vim-keymap-enabled");
|
||||
this.$vimKeymapEnabled.on('change', () => {
|
||||
const opts = { 'vimKeymapEnabled': this.$vimKeymapEnabled.is(":checked") ? "true" : "false" };
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options change have been saved."));
|
||||
return false;
|
||||
});
|
||||
this.$mimeTypes = $("#options-mime-types");
|
||||
}
|
||||
|
||||
async optionsLoaded(options) {
|
||||
this.$mimeTypes.empty();
|
||||
this.$vimKeymapEnabled.prop("checked", options['vimKeymapEnabled'] === 'true');
|
||||
let idCtr = 1;
|
||||
|
||||
for (const mimeType of await mimeTypesService.getMimeTypes()) {
|
||||
const id = "code-mime-type-" + (idCtr++);
|
||||
|
||||
this.$mimeTypes.append($("<li>")
|
||||
.append($('<input type="checkbox">')
|
||||
.attr("id", id)
|
||||
.attr("data-mime-type", mimeType.mime)
|
||||
.prop("checked", mimeType.enabled))
|
||||
.on('change', () => this.save())
|
||||
.append(" ")
|
||||
.append($('<label>')
|
||||
.attr("for", id)
|
||||
.text(mimeType.title))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async save() {
|
||||
const enabledMimeTypes = [];
|
||||
|
||||
this.$mimeTypes.find("input:checked").each(
|
||||
(i, el) => enabledMimeTypes.push($(el).attr("data-mime-type")));
|
||||
|
||||
await options.save('codeNotesMimeTypes', JSON.stringify(enabledMimeTypes));
|
||||
|
||||
mimeTypesService.loadMimeTypes();
|
||||
}
|
||||
}
|
||||
125
src/public/app/widgets/dialogs/options/etapi.js
Normal file
125
src/public/app/widgets/dialogs/options/etapi.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import server from "../../../services/server.js";
|
||||
import dialogService from "../../dialog.js";
|
||||
|
||||
const TPL = `
|
||||
<h4>ETAPI</h4>
|
||||
|
||||
<p>ETAPI is a REST API used to access Trilium instance programmatically, without UI. <br/>
|
||||
See more details on <a href="https://github.com/zadam/trilium/wiki/ETAPI">wiki</a> and <a onclick="window.open('etapi/etapi.openapi.yaml')" href="etapi/etapi.openapi.yaml">ETAPI OpenAPI spec</a>.</p>
|
||||
|
||||
<button type="button" class="btn btn-sm" id="create-etapi-token">Create new ETAPI token</button>
|
||||
|
||||
<br/><br/>
|
||||
|
||||
<h5>Existing tokens</h5>
|
||||
|
||||
<div id="no-tokens-yet">There are no tokens yet. Click on the button above to create one.</div>
|
||||
|
||||
<div style="overflow: auto; height: 500px;">
|
||||
<table id="tokens-table" class="table table-stripped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Token name</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.token-table-button {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: 3px;
|
||||
margin-right: 20px;
|
||||
font-size: large;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.token-table-button:hover {
|
||||
border: 1px solid var(--main-border-color);
|
||||
}
|
||||
</style>`;
|
||||
|
||||
export default class EtapiOptions {
|
||||
constructor() {
|
||||
$("#options-etapi").html(TPL);
|
||||
|
||||
$("#create-etapi-token").on("click", async () => {
|
||||
const tokenName = await dialogService.prompt({
|
||||
title: "New ETAPI token",
|
||||
message: "Please enter new token's name",
|
||||
defaultValue: "new token"
|
||||
});
|
||||
|
||||
if (!tokenName.trim()) {
|
||||
alert("Token name can't be empty");
|
||||
return;
|
||||
}
|
||||
|
||||
const {authToken} = await server.post('etapi-tokens', {tokenName});
|
||||
|
||||
await dialogService.prompt({
|
||||
title: "ETAPI token created",
|
||||
message: 'Copy the created token into clipboard. Trilium stores the token hashed and this is the last time you see it.',
|
||||
defaultValue: authToken
|
||||
});
|
||||
|
||||
this.refreshTokens();
|
||||
});
|
||||
|
||||
this.refreshTokens();
|
||||
}
|
||||
|
||||
async refreshTokens() {
|
||||
const $noTokensYet = $("#no-tokens-yet");
|
||||
const $tokensTable = $("#tokens-table");
|
||||
|
||||
const tokens = await server.get('etapi-tokens');
|
||||
|
||||
$noTokensYet.toggle(tokens.length === 0);
|
||||
$tokensTable.toggle(tokens.length > 0);
|
||||
|
||||
const $tokensTableBody = $tokensTable.find("tbody");
|
||||
$tokensTableBody.empty();
|
||||
|
||||
for (const token of tokens) {
|
||||
$tokensTableBody.append(
|
||||
$("<tr>")
|
||||
.append($("<td>").text(token.name))
|
||||
.append($("<td>").text(token.utcDateCreated))
|
||||
.append($("<td>").append(
|
||||
$('<span class="bx bx-pen token-table-button" title="Rename this token"></span>')
|
||||
.on("click", () => this.renameToken(token.etapiTokenId, token.name)),
|
||||
$('<span class="bx bx-trash token-table-button" title="Delete / deactive this token"></span>')
|
||||
.on("click", () => this.deleteToken(token.etapiTokenId, token.name))
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async renameToken(etapiTokenId, oldName) {
|
||||
const tokenName = await dialogService.prompt({
|
||||
title: "Rename token",
|
||||
message: "Please enter new token's name",
|
||||
defaultValue: oldName
|
||||
});
|
||||
|
||||
await server.patch(`etapi-tokens/${etapiTokenId}`, {name: tokenName});
|
||||
|
||||
this.refreshTokens();
|
||||
}
|
||||
|
||||
async deleteToken(etapiTokenId, name) {
|
||||
if (!confirm(`Are you sure you want to delete ETAPI token "${name}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await server.remove(`etapi-tokens/${etapiTokenId}`);
|
||||
|
||||
this.refreshTokens();
|
||||
}
|
||||
}
|
||||
276
src/public/app/widgets/dialogs/options/other.js
Normal file
276
src/public/app/widgets/dialogs/options/other.js
Normal file
@@ -0,0 +1,276 @@
|
||||
import utils from "../../../services/utils.js";
|
||||
import server from "../../../services/server.js";
|
||||
import toastService from "../../../services/toast.js";
|
||||
|
||||
const TPL = `
|
||||
<style>
|
||||
.disabled-field {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
<h4>Spell check</h4>
|
||||
|
||||
<p>These options apply only for desktop builds, browsers will use their own native spell check. App restart is required after change.</p>
|
||||
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="spell-check-enabled">
|
||||
<label class="custom-control-label" for="spell-check-enabled">Enable spellcheck</label>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="spell-check-language-code">Language code(s)</label>
|
||||
<input type="text" class="form-control" id="spell-check-language-code" placeholder="for example "en-US", "de-AT"">
|
||||
</div>
|
||||
|
||||
<p>Multiple languages can be separated by comma, e.g. <code>en-US, de-DE, cs</code>. Changes to the spell check options will take effect after application restart.</p>
|
||||
|
||||
<p><strong>Available language codes: </strong> <span id="available-language-codes"></span></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Images</h4>
|
||||
|
||||
<div class="form-group">
|
||||
<input id="download-images-automatically" type="checkbox" name="download-images-automatically">
|
||||
<label for="download-images-automatically">Download images automatically for offline use.</label>
|
||||
<p>(pasted HTML can contain references to online images, Trilium will find those references and download the images so that they are available offline)</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<input id="image-compresion-enabled" type="checkbox" name="image-compression-enabled">
|
||||
<label for="image-compresion-enabled">Enable image compression</label>
|
||||
</div>
|
||||
|
||||
<div id="image-compression-enabled-wraper">
|
||||
<div class="form-group">
|
||||
<label for="image-max-width-height">Max width / height of an image in pixels (image will be resized if it exceeds this setting).</label>
|
||||
<input class="form-control" id="image-max-width-height" type="number" min="1">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="image-jpeg-quality">JPEG quality (10 - worst quality, 100 best quality, 50 - 85 is recommended)</label>
|
||||
<input class="form-control" id="image-jpeg-quality" min="10" max="100" type="number">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Note erasure timeout</h4>
|
||||
|
||||
<p>Deleted notes (and attributes, revisions...) are at first only marked as deleted and it is possible to recover them
|
||||
from Recent Notes dialog. After a period of time, deleted notes are "erased" which means
|
||||
their content is not recoverable anymore. This setting allows you to configure the length
|
||||
of the period between deleting and erasing the note.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="erase-entities-after-time-in-seconds">Erase notes after X seconds</label>
|
||||
<input class="form-control" id="erase-entities-after-time-in-seconds" type="number" min="0">
|
||||
</div>
|
||||
|
||||
<p>You can also trigger erasing manually:</p>
|
||||
|
||||
<button id="erase-deleted-notes-now-button" class="btn">Erase deleted notes now</button>
|
||||
|
||||
<br/><br/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Protected session timeout</h4>
|
||||
|
||||
<p>Protected session timeout is a time period after which the protected session is wiped from
|
||||
the browser's memory. This is measured from the last interaction with protected notes. See <a href="https://github.com/zadam/trilium/wiki/Protected-notes" class="external">wiki</a> for more info.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="protected-session-timeout-in-seconds">Protected session timeout (in seconds)</label>
|
||||
<input class="form-control" id="protected-session-timeout-in-seconds" type="number" min="60">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Note revisions snapshot interval</h4>
|
||||
|
||||
<p>Note revision snapshot time interval is time in seconds after which a new note revision will be created for the note. See <a href="https://github.com/zadam/trilium/wiki/Note-revisions" class="external">wiki</a> for more info.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="note-revision-snapshot-time-interval-in-seconds">Note revision snapshot time interval (in seconds)</label>
|
||||
<input class="form-control" id="note-revision-snapshot-time-interval-in-seconds" type="number" min="10">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Automatic readonly size</h4>
|
||||
|
||||
<p>Automatic readonly note size is the size after which notes will be displayed in a readonly mode (for performance reasons).</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="auto-readonly-size-text">Automatic readonly size (text notes)</label>
|
||||
<input class="form-control" id="auto-readonly-size-text" type="number" min="0">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="auto-readonly-size-code">Automatic readonly size (code notes)</label>
|
||||
<input class="form-control" id="auto-readonly-size-code" type="number" min="0">
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class ProtectedSessionOptions {
|
||||
constructor() {
|
||||
$("#options-other").html(TPL);
|
||||
|
||||
this.$spellCheckEnabled = $("#spell-check-enabled");
|
||||
this.$spellCheckLanguageCode = $("#spell-check-language-code");
|
||||
|
||||
this.$spellCheckEnabled.on('change', () => {
|
||||
const opts = { 'spellCheckEnabled': this.$spellCheckEnabled.is(":checked") ? "true" : "false" };
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$spellCheckLanguageCode.on('change', () => {
|
||||
const opts = { 'spellCheckLanguageCode': this.$spellCheckLanguageCode.val() };
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$availableLanguageCodes = $("#available-language-codes");
|
||||
|
||||
if (utils.isElectron()) {
|
||||
const {webContents} = utils.dynamicRequire('@electron/remote').getCurrentWindow();
|
||||
|
||||
this.$availableLanguageCodes.text(webContents.session.availableSpellCheckerLanguages.join(', '));
|
||||
}
|
||||
|
||||
this.$eraseEntitiesAfterTimeInSeconds = $("#erase-entities-after-time-in-seconds");
|
||||
|
||||
this.$eraseEntitiesAfterTimeInSeconds.on('change', () => {
|
||||
const eraseEntitiesAfterTimeInSeconds = this.$eraseEntitiesAfterTimeInSeconds.val();
|
||||
|
||||
server.put('options', { 'eraseEntitiesAfterTimeInSeconds': eraseEntitiesAfterTimeInSeconds }).then(() => {
|
||||
toastService.showMessage("Options changed have been saved.");
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$eraseDeletedNotesButton = $("#erase-deleted-notes-now-button");
|
||||
this.$eraseDeletedNotesButton.on('click', () => {
|
||||
server.post('notes/erase-deleted-notes-now').then(() => {
|
||||
toastService.showMessage("Deleted notes have been erased.");
|
||||
});
|
||||
});
|
||||
|
||||
this.$protectedSessionTimeout = $("#protected-session-timeout-in-seconds");
|
||||
|
||||
this.$protectedSessionTimeout.on('change', () => {
|
||||
const protectedSessionTimeout = this.$protectedSessionTimeout.val();
|
||||
|
||||
server.put('options', { 'protectedSessionTimeout': protectedSessionTimeout }).then(() => {
|
||||
toastService.showMessage("Options changed have been saved.");
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$noteRevisionsTimeInterval = $("#note-revision-snapshot-time-interval-in-seconds");
|
||||
|
||||
this.$noteRevisionsTimeInterval.on('change', () => {
|
||||
const opts = { 'noteRevisionSnapshotTimeInterval': this.$noteRevisionsTimeInterval.val() };
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$imageMaxWidthHeight = $("#image-max-width-height");
|
||||
this.$imageJpegQuality = $("#image-jpeg-quality");
|
||||
|
||||
this.$imageMaxWidthHeight.on('change', () => {
|
||||
const opts = { 'imageMaxWidthHeight': this.$imageMaxWidthHeight.val() };
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$imageJpegQuality.on('change', () => {
|
||||
const opts = { 'imageJpegQuality': this.$imageJpegQuality.val() };
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$autoReadonlySizeText = $("#auto-readonly-size-text");
|
||||
|
||||
this.$autoReadonlySizeText.on('change', () => {
|
||||
const opts = { 'autoReadonlySizeText': this.$autoReadonlySizeText.val() };
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$autoReadonlySizeCode = $("#auto-readonly-size-code");
|
||||
|
||||
this.$autoReadonlySizeCode.on('change', () => {
|
||||
const opts = { 'autoReadonlySizeCode': this.$autoReadonlySizeText.val() };
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$downloadImagesAutomatically = $("#download-images-automatically");
|
||||
|
||||
this.$downloadImagesAutomatically.on("change", () => {
|
||||
const isChecked = this.$downloadImagesAutomatically.prop("checked");
|
||||
const opts = { 'downloadImagesAutomatically': isChecked ? 'true' : 'false' };
|
||||
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
});
|
||||
|
||||
this.$enableImageCompression = $("#image-compresion-enabled");
|
||||
this.$imageCompressionWrapper = $("#image-compression-enabled-wraper");
|
||||
|
||||
this.setImageCompression = (isChecked) => {
|
||||
if (isChecked) {
|
||||
this.$imageCompressionWrapper.removeClass("disabled-field");
|
||||
} else {
|
||||
this.$imageCompressionWrapper.addClass("disabled-field");
|
||||
}
|
||||
};
|
||||
|
||||
this.$enableImageCompression.on("change", () => {
|
||||
const isChecked = this.$enableImageCompression.prop("checked");
|
||||
const opts = { 'compressImages': isChecked ? 'true' : 'false' };
|
||||
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
|
||||
this.setImageCompression(isChecked);
|
||||
});
|
||||
}
|
||||
|
||||
optionsLoaded(options) {
|
||||
this.$spellCheckEnabled.prop("checked", options['spellCheckEnabled'] === 'true');
|
||||
this.$spellCheckLanguageCode.val(options['spellCheckLanguageCode']);
|
||||
|
||||
this.$eraseEntitiesAfterTimeInSeconds.val(options['eraseEntitiesAfterTimeInSeconds']);
|
||||
this.$protectedSessionTimeout.val(options['protectedSessionTimeout']);
|
||||
this.$noteRevisionsTimeInterval.val(options['noteRevisionSnapshotTimeInterval']);
|
||||
|
||||
this.$imageMaxWidthHeight.val(options['imageMaxWidthHeight']);
|
||||
this.$imageJpegQuality.val(options['imageJpegQuality']);
|
||||
|
||||
this.$autoReadonlySizeText.val(options['autoReadonlySizeText']);
|
||||
this.$autoReadonlySizeCode.val(options['autoReadonlySizeCode']);
|
||||
|
||||
const downloadImagesAutomatically = options['downloadImagesAutomatically'] === 'true';
|
||||
this.$downloadImagesAutomatically.prop('checked', downloadImagesAutomatically);
|
||||
|
||||
const compressImages = options['compressImages'] === 'true';
|
||||
this.$enableImageCompression.prop('checked', compressImages);
|
||||
this.setImageCompression(compressImages);
|
||||
}
|
||||
}
|
||||
98
src/public/app/widgets/dialogs/options/password.js
Normal file
98
src/public/app/widgets/dialogs/options/password.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import server from "../../../services/server.js";
|
||||
import protectedSessionHolder from "../../../services/protected_session_holder.js";
|
||||
import toastService from "../../../services/toast.js";
|
||||
|
||||
const TPL = `
|
||||
<h3 id="password-heading"></h3>
|
||||
|
||||
<div class="alert alert-warning" role="alert" style="font-weight: bold; color: red !important;">
|
||||
Please take care to remember your new password. Password is used to encrypt protected notes.
|
||||
If you forget your password, then all your protected notes are forever lost.
|
||||
In case you did forget your password, <a id="reset-password-button" href="javascript:">click here to reset it</a>.
|
||||
</div>
|
||||
|
||||
<form id="change-password-form">
|
||||
<div class="form-group" id="old-password-form-group">
|
||||
<label for="old-password">Old password</label>
|
||||
<input class="form-control" id="old-password" type="password">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="new-password1">New password</label>
|
||||
<input class="form-control" id="new-password1" type="password">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="new-password2">New password Confirmation</label>
|
||||
<input class="form-control" id="new-password2" type="password">
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" id="save-password-button">Change password</button>
|
||||
</form>`;
|
||||
|
||||
export default class ChangePasswordOptions {
|
||||
constructor() {
|
||||
$("#options-password").html(TPL);
|
||||
|
||||
this.$passwordHeading = $("#password-heading");
|
||||
this.$form = $("#change-password-form");
|
||||
this.$oldPassword = $("#old-password");
|
||||
this.$newPassword1 = $("#new-password1");
|
||||
this.$newPassword2 = $("#new-password2");
|
||||
this.$savePasswordButton = $("#save-password-button");
|
||||
this.$resetPasswordButton = $("#reset-password-button");
|
||||
|
||||
this.$resetPasswordButton.on("click", async () => {
|
||||
if (confirm("By resetting the password you will forever lose access to all your existing protected notes. Do you really want to reset the password?")) {
|
||||
await server.post("password/reset?really=yesIReallyWantToResetPasswordAndLoseAccessToMyProtectedNotes");
|
||||
|
||||
const options = await server.get('options');
|
||||
this.optionsLoaded(options);
|
||||
|
||||
alert("Password has been reset. Please set new password");
|
||||
}
|
||||
});
|
||||
|
||||
this.$form.on('submit', () => this.save());
|
||||
}
|
||||
|
||||
optionsLoaded(options) {
|
||||
const isPasswordSet = options.isPasswordSet === 'true';
|
||||
|
||||
$("#old-password-form-group").toggle(isPasswordSet);
|
||||
this.$passwordHeading.text(isPasswordSet ? 'Change password' : 'Set password');
|
||||
this.$savePasswordButton.text(isPasswordSet ? 'Change password' : 'Set password');
|
||||
}
|
||||
|
||||
save() {
|
||||
const oldPassword = this.$oldPassword.val();
|
||||
const newPassword1 = this.$newPassword1.val();
|
||||
const newPassword2 = this.$newPassword2.val();
|
||||
|
||||
this.$oldPassword.val('');
|
||||
this.$newPassword1.val('');
|
||||
this.$newPassword2.val('');
|
||||
|
||||
if (newPassword1 !== newPassword2) {
|
||||
alert("New passwords are not the same.");
|
||||
return false;
|
||||
}
|
||||
|
||||
server.post('password/change', {
|
||||
'current_password': oldPassword,
|
||||
'new_password': newPassword1
|
||||
}).then(result => {
|
||||
if (result.success) {
|
||||
alert("Password has been changed. Trilium will be reloaded after you press OK.");
|
||||
|
||||
// password changed so current protected session is invalid and needs to be cleared
|
||||
protectedSessionHolder.resetProtectedSession();
|
||||
}
|
||||
else {
|
||||
toastService.showError(result.message);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
140
src/public/app/widgets/dialogs/options/shortcuts.js
Normal file
140
src/public/app/widgets/dialogs/options/shortcuts.js
Normal file
@@ -0,0 +1,140 @@
|
||||
import server from "../../../services/server.js";
|
||||
import utils from "../../../services/utils.js";
|
||||
import dialogService from "../../dialog.js";
|
||||
|
||||
const TPL = `
|
||||
<h4>Keyboard shortcuts</h4>
|
||||
|
||||
<p>Multiple shortcuts for the same action can be separated by comma.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" id="keyboard-shortcut-filter" placeholder="Type text to filter shortcuts...">
|
||||
</div>
|
||||
|
||||
<div style="overflow: auto; height: 500px;">
|
||||
<table id="keyboard-shortcut-table" cellpadding="10">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Action name</th>
|
||||
<th>Shortcuts</th>
|
||||
<th>Default shortcuts</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between">
|
||||
<button class="btn btn-primary" id="options-keyboard-shortcuts-reload-app">Reload app to apply changes</button>
|
||||
|
||||
<button class="btn" id="options-keyboard-shortcuts-set-all-to-default">Set all shortcuts to the default</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let globActions;
|
||||
|
||||
export default class KeyboardShortcutsOptions {
|
||||
constructor() {
|
||||
$("#options-shortcuts").html(TPL);
|
||||
|
||||
$("#options-keyboard-shortcuts-reload-app").on("click", () => utils.reloadFrontendApp());
|
||||
|
||||
const $table = $("#keyboard-shortcut-table tbody");
|
||||
|
||||
server.get('keyboard-actions').then(actions => {
|
||||
globActions = actions;
|
||||
|
||||
for (const action of actions) {
|
||||
const $tr = $("<tr>");
|
||||
|
||||
if (action.separator) {
|
||||
$tr.append(
|
||||
$('<td colspan="4">')
|
||||
.attr("style","background-color: var(--accented-background-color); font-weight: bold;")
|
||||
.text(action.separator)
|
||||
)
|
||||
}
|
||||
else {
|
||||
$tr.append($("<td>").text(action.actionName))
|
||||
.append($("<td>").append(
|
||||
$(`<input type="text" class="form-control">`)
|
||||
.val(action.effectiveShortcuts.join(", "))
|
||||
.attr('data-keyboard-action-name', action.actionName)
|
||||
.attr('data-default-keyboard-shortcuts', action.defaultShortcuts.join(", "))
|
||||
)
|
||||
)
|
||||
.append($("<td>").text(action.defaultShortcuts.join(", ")))
|
||||
.append($("<td>").text(action.description));
|
||||
}
|
||||
|
||||
$table.append($tr);
|
||||
}
|
||||
});
|
||||
|
||||
$table.on('change', 'input.form-control', e => {
|
||||
const $input = $(e.target);
|
||||
const actionName = $input.attr('data-keyboard-action-name');
|
||||
const shortcuts = $input.val()
|
||||
.replace('+,', "+Comma")
|
||||
.split(",")
|
||||
.map(shortcut => shortcut.replace("+Comma", "+,"))
|
||||
.filter(shortcut => !!shortcut);
|
||||
|
||||
const opts = {};
|
||||
opts['keyboardShortcuts' + actionName.substr(0, 1).toUpperCase() + actionName.substr(1)] = JSON.stringify(shortcuts);
|
||||
|
||||
server.put('options', opts);
|
||||
});
|
||||
|
||||
$("#options-keyboard-shortcuts-set-all-to-default").on('click', async () => {
|
||||
if (!await dialogService.confirm("Do you really want to reset all keyboard shortcuts to the default?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table.find('input.form-control').each(function() {
|
||||
const defaultShortcuts = $(this).attr('data-default-keyboard-shortcuts');
|
||||
|
||||
if ($(this).val() !== defaultShortcuts) {
|
||||
$(this)
|
||||
.val(defaultShortcuts)
|
||||
.trigger('change');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const $filter = $("#keyboard-shortcut-filter");
|
||||
|
||||
$filter.on('keyup', () => {
|
||||
const filter = $filter.val().trim().toLowerCase();
|
||||
|
||||
$table.find("tr").each((i, el) => {
|
||||
if (!filter) {
|
||||
$(el).show();
|
||||
return;
|
||||
}
|
||||
|
||||
const actionName = $(el).find('input').attr('data-keyboard-action-name');
|
||||
|
||||
if (!actionName) {
|
||||
$(el).hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const action = globActions.find(act => act.actionName === actionName);
|
||||
|
||||
if (!action) {
|
||||
$(el).hide();
|
||||
return;
|
||||
}
|
||||
|
||||
$(el).toggle(!!( // !! to avoid toggle overloads with different behavior
|
||||
action.actionName.toLowerCase().includes(filter)
|
||||
|| action.defaultShortcuts.some(shortcut => shortcut.toLowerCase().includes(filter))
|
||||
|| action.effectiveShortcuts.some(shortcut => shortcut.toLowerCase().includes(filter))
|
||||
|| (action.description && action.description.toLowerCase().includes(filter))
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
81
src/public/app/widgets/dialogs/options/sync.js
Normal file
81
src/public/app/widgets/dialogs/options/sync.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import server from "../../../services/server.js";
|
||||
import toastService from "../../../services/toast.js";
|
||||
|
||||
const TPL = `
|
||||
<h4 style="margin-top: 0px;">Sync configuration</h4>
|
||||
|
||||
<form id="sync-setup-form">
|
||||
<div class="form-group">
|
||||
<label for="sync-server-host">Server instance address</label>
|
||||
<input class="form-control" id="sync-server-host" placeholder="https://<host>:<port>">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sync-server-timeout">Sync timeout (milliseconds)</label>
|
||||
<input class="form-control" id="sync-server-timeout" min="1" max="10000000" type="number" style="text-align: left;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sync-proxy">Sync proxy server (optional)</label>
|
||||
<input class="form-control" id="sync-proxy" placeholder="https://<host>:<port>">
|
||||
|
||||
<p><strong>Note:</strong> If you leave the proxy setting blank, the system proxy will be used (applies to desktop/electron build only)</p>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<button class="btn btn-primary">Save</button>
|
||||
|
||||
<button class="btn" type="button" data-help-page="Synchronization">Help</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<br/>
|
||||
|
||||
<h4>Sync test</h4>
|
||||
|
||||
<p>This will test the connection and handshake to the sync server. If the sync server isn't initialized, this will set it up to sync with the local document.</p>
|
||||
|
||||
<button id="test-sync-button" class="btn">Test sync</button>`;
|
||||
|
||||
export default class SyncOptions {
|
||||
constructor() {
|
||||
$("#options-sync-setup").html(TPL);
|
||||
|
||||
this.$form = $("#sync-setup-form");
|
||||
this.$syncServerHost = $("#sync-server-host");
|
||||
this.$syncServerTimeout = $("#sync-server-timeout");
|
||||
this.$syncProxy = $("#sync-proxy");
|
||||
this.$testSyncButton = $("#test-sync-button");
|
||||
|
||||
this.$form.on('submit', () => this.save());
|
||||
|
||||
this.$testSyncButton.on('click', async () => {
|
||||
const result = await server.post('sync/test');
|
||||
|
||||
if (result.success) {
|
||||
toastService.showMessage(result.message);
|
||||
}
|
||||
else {
|
||||
toastService.showError("Sync server handshake failed, error: " + result.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
optionsLoaded(options) {
|
||||
this.$syncServerHost.val(options['syncServerHost']);
|
||||
this.$syncServerTimeout.val(options['syncServerTimeout']);
|
||||
this.$syncProxy.val(options['syncProxy']);
|
||||
}
|
||||
|
||||
save() {
|
||||
const opts = {
|
||||
'syncServerHost': this.$syncServerHost.val(),
|
||||
'syncServerTimeout': this.$syncServerTimeout.val(),
|
||||
'syncProxy': this.$syncProxy.val()
|
||||
};
|
||||
|
||||
server.put('options', opts).then(() => toastService.showMessage("Options changed have been saved."));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user