diff --git a/src/public/app/layouts/desktop_layout.js b/src/public/app/layouts/desktop_layout.js index 2b3463cb1..c04aa42d7 100644 --- a/src/public/app/layouts/desktop_layout.js +++ b/src/public/app/layouts/desktop_layout.js @@ -218,20 +218,20 @@ export default class DesktopLayout { .child(new DeleteNotesDialog()) .child(new InfoDialog()) .child(new ConfirmDialog()) - .child(new PromptDialog()) - .child(new OptionsDialog() - .child(new AppearanceOptions()) - .child(new KeyboardShortcutsOptions()) - .child(new TextNotesOptions()) - .child(new CodeNotesOptions()) - .child(new ImageOptions()) - .child(new SpellcheckOptions()) - .child(new PasswordOptions()) - .child(new EtapiOptions()) - .child(new BackupOptions()) - .child(new SyncOptions()) - .child(new OtherOptions()) - .child(new AdvancedOptions()) - ); + .child(new PromptDialog()); + // .child(new OptionsDialog() + // .child(new AppearanceOptions()) + // .child(new KeyboardShortcutsOptions()) + // .child(new TextNotesOptions()) + // .child(new CodeNotesOptions()) + // .child(new ImageOptions()) + // .child(new SpellcheckOptions()) + // .child(new PasswordOptions()) + // .child(new EtapiOptions()) + // .child(new BackupOptions()) + // .child(new SyncOptions()) + // .child(new OtherOptions()) + // .child(new AdvancedOptions()) + // ); } } diff --git a/src/public/app/widgets/dialogs/options/options_tab.js b/src/public/app/widgets/dialogs/options/options_tab.js index 3e74dcbc7..e5541acb6 100644 --- a/src/public/app/widgets/dialogs/options/options_tab.js +++ b/src/public/app/widgets/dialogs/options/options_tab.js @@ -1,8 +1,8 @@ -import BasicWidget from "../../basic_widget.js"; import server from "../../../services/server.js"; import toastService from "../../../services/toast.js"; +import NoteContextAwareWidget from "../../note_context_aware_widget.js"; -export default class OptionsTab extends BasicWidget { +export default class OptionsTab extends NoteContextAwareWidget { async updateOption(name, value) { const opts = { [name]: value }; @@ -34,4 +34,10 @@ export default class OptionsTab extends BasicWidget { setCheckboxState($checkbox, optionValue) { $checkbox.prop('checked', optionValue === 'true'); } + + async refreshWithNote(note) { + const options = await server.get('options'); + + this.optionsLoaded(options); + } } diff --git a/src/public/app/widgets/type_widgets/content_widget.js b/src/public/app/widgets/type_widgets/content_widget.js index 9cac12438..66e62eb45 100644 --- a/src/public/app/widgets/type_widgets/content_widget.js +++ b/src/public/app/widgets/type_widgets/content_widget.js @@ -1,12 +1,22 @@ import TypeWidget from "./type_widget.js"; +import AppearanceOptions from "./options/appearance.js"; -const TPL = `
`; +const TPL = `
+ + +
+
`; export default class ContentWidgetTypeWidget extends TypeWidget { static getType() { return "content-widget"; } doRender() { this.$widget = $(TPL); + this.$content = this.$widget.find(".note-detail-content-widget-content"); super.doRender(); } @@ -14,10 +24,19 @@ export default class ContentWidgetTypeWidget extends TypeWidget { async doRefresh(note) { const contentWidget = note.getLabelValue('contentWidget'); + this.$content.empty(); + this.children = []; + if (contentWidget === 'optionsAppearance') { - this.$widget.empty().append("HI!"); + const widget = new AppearanceOptions(); + + await widget.handleEvent('setNoteContext', { noteContext: this.noteContext }); + this.child(widget); + + this.$content.append(widget.render()); + await widget.refresh(); } else { - this.$widget.empty().append(`Unknown widget of type "${contentWidget}"`); + this.$content.append(`Unknown widget of type "${contentWidget}"`); } } } diff --git a/src/public/app/widgets/type_widgets/options/appearance.js b/src/public/app/widgets/type_widgets/options/appearance.js new file mode 100644 index 000000000..798eca0f7 --- /dev/null +++ b/src/public/app/widgets/type_widgets/options/appearance.js @@ -0,0 +1,326 @@ +import server from "../../../services/server.js"; +import utils from "../../../services/utils.js"; +import appContext from "../../../components/app_context.js"; +import OptionsTab from "../../dialogs/options/options_tab.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 = ` +
+

Settings on this options tab are saved automatically after each change.

+ + + +
+
+
+ + + +
+ +
+ + + +
+
+ +

Zooming can be controlled with CTRL+- and CTRL+= shortcuts as well.

+
+ +
+

Theme

+ +
+
+ + +
+ +
+ + +
+
+
+ +
+

Fonts

+ +
Main font
+ +
+
+ + +
+ +
+ + +
+ +
+ % +
+
+
+
+ +
Note tree font
+ +
+
+ + +
+ +
+ + +
+ +
+ % +
+
+
+
+ +
Note detail font
+ +
+
+ + +
+ +
+ + +
+ +
+ % +
+
+
+
+ +
Monospace (code) font
+ +
+
+ + +
+ +
+ + +
+ +
+ % +
+
+
+
+ +

Note that tree and detail font sizing is relative to the main font size setting.

+ +

Not all listed fonts may be available on your system.

+
+ +

+ To apply font changes, click on + +

+ +
+

Content width

+ +

Trilium by default limits max content width to improve readability for maximized screens on wide screens.

+ +
+
+ + +
+
+ +

+ To content width changes, click on + +

+
+
`; + +export default class AppearanceOptions extends OptionsTab { + get tabTitle() { return "Appearance" } + + doRender() { + this.$widget = $(TPL); + + this.$zoomFactorSelect = this.$widget.find(".zoom-factor-select"); + this.$nativeTitleBarSelect = this.$widget.find(".native-title-bar-select"); + + this.$themeSelect = this.$widget.find(".theme-select"); + this.$overrideThemeFonts = this.$widget.find(".override-theme-fonts"); + + this.$overridenFontSettings = this.$widget.find(".overriden-font-settings"); + + this.$mainFontSize = this.$widget.find(".main-font-size"); + this.$mainFontFamily = this.$widget.find(".main-font-family"); + + this.$treeFontSize = this.$widget.find(".tree-font-size"); + this.$treeFontFamily = this.$widget.find(".tree-font-family"); + + this.$detailFontSize = this.$widget.find(".detail-font-size"); + this.$detailFontFamily = this.$widget.find(".detail-font-family"); + + this.$monospaceFontSize = this.$widget.find(".monospace-font-size"); + this.$monospaceFontFamily = this.$widget.find(".monospace-font-family"); + + this.$widget.find(".reload-frontend-button").on("click", () => utils.reloadFrontendApp("changes from appearance options")); + + this.$body = this.$widget.find("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 () => { + this.updateCheckboxOption('overrideThemeFonts', this.$overrideThemeFonts); + + this.$overridenFontSettings.toggle(this.$overrideThemeFonts.is(":checked")); + }); + + this.$zoomFactorSelect.on('change', () => { appContext.triggerCommand('setZoomFactorAndSave', {zoomFactor: this.$zoomFactorSelect.val()}); }); + + this.$nativeTitleBarSelect.on('change', () => { + const nativeTitleBarVisible = this.$nativeTitleBarSelect.val() === 'show' ? 'true' : 'false'; + + this.updateOption('nativeTitleBarVisible', nativeTitleBarVisible); + }); + + const optionsToSave = [ + 'mainFontFamily', 'mainFontSize', + 'treeFontFamily', 'treeFontSize', + 'detailFontFamily', 'detailFontSize', + 'monospaceFontFamily', 'monospaceFontSize' + ]; + + for (const optionName of optionsToSave) { + this['$' + optionName].on('change', () => + this.updateOption(optionName, this['$' + optionName].val())); + } + + this.$maxContentWidth = this.$widget.find(".max-content-width"); + + this.$maxContentWidth.on('change', async () => + this.updateOption('maxContentWidth', this.$maxContentWidth.val())) + } + + 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'); + + 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($("