chore(client/ts): port options/appearance

This commit is contained in:
Elian Doran 2025-01-11 11:02:22 +02:00
parent 5bfcf88acd
commit 552cc2753f
No known key found for this signature in database
7 changed files with 83 additions and 24 deletions

View File

@ -188,6 +188,9 @@ export type CommandMappings = {
copyTabToNewWindow: CommandData; copyTabToNewWindow: CommandData;
closeActiveTab: CommandData & { closeActiveTab: CommandData & {
$el: JQuery<HTMLElement> $el: JQuery<HTMLElement>
},
setZoomFactorAndSave: {
zoomFactor: string;
} }
}; };

View File

@ -1,3 +1,4 @@
import type { OptionMap } from "../../../../../../services/options_interface.js";
import { t } from "../../../../services/i18n.js"; import { t } from "../../../../services/i18n.js";
import library_loader from "../../../../services/library_loader.js"; import library_loader from "../../../../services/library_loader.js";
import server from "../../../../services/server.js"; import server from "../../../../services/server.js";
@ -54,15 +55,27 @@ const TPL = `
</div> </div>
`; `;
interface Theme {
title: string;
val: string;
}
type Response = Record<string, Theme[]>
/** /**
* Contains appearance settings for code blocks within text notes, such as the theme for the syntax highlighter. * Contains appearance settings for code blocks within text notes, such as the theme for the syntax highlighter.
*/ */
export default class CodeBlockOptions extends OptionsWidget { export default class CodeBlockOptions extends OptionsWidget {
private $themeSelect!: JQuery<HTMLElement>;
private $wordWrap!: JQuery<HTMLElement>;
private $sampleEl!: JQuery<HTMLElement>;
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$themeSelect = this.$widget.find(".theme-select"); this.$themeSelect = this.$widget.find(".theme-select");
this.$themeSelect.on("change", async () => { this.$themeSelect.on("change", async () => {
const newTheme = this.$themeSelect.val(); const newTheme = String(this.$themeSelect.val());
library_loader.loadHighlightingTheme(newTheme); library_loader.loadHighlightingTheme(newTheme);
await server.put(`options/codeBlockTheme/${newTheme}`); await server.put(`options/codeBlockTheme/${newTheme}`);
}); });
@ -74,7 +87,7 @@ export default class CodeBlockOptions extends OptionsWidget {
this.$sampleEl = this.$widget.find(".code-sample"); this.$sampleEl = this.$widget.find(".code-sample");
} }
#setupPreview(shouldEnableSyntaxHighlight) { #setupPreview(shouldEnableSyntaxHighlight: boolean) {
const text = SAMPLE_CODE; const text = SAMPLE_CODE;
if (shouldEnableSyntaxHighlight) { if (shouldEnableSyntaxHighlight) {
library_loader.requireLibrary(library_loader.HIGHLIGHT_JS).then(() => { library_loader.requireLibrary(library_loader.HIGHLIGHT_JS).then(() => {
@ -88,8 +101,8 @@ export default class CodeBlockOptions extends OptionsWidget {
} }
} }
async optionsLoaded(options) { async optionsLoaded(options: OptionMap) {
const themeGroups = await server.get("options/codeblock-themes"); const themeGroups = await server.get<Response>("options/codeblock-themes");
this.$themeSelect.empty(); this.$themeSelect.empty();
for (const [key, themes] of Object.entries(themeGroups)) { for (const [key, themes] of Object.entries(themeGroups)) {
@ -104,7 +117,9 @@ export default class CodeBlockOptions extends OptionsWidget {
this.$themeSelect.append(option); this.$themeSelect.append(option);
} }
} }
this.$themeSelect.append($group); if ($group) {
this.$themeSelect.append($group);
}
} }
this.$themeSelect.val(options.codeBlockTheme); this.$themeSelect.val(options.codeBlockTheme);
this.setCheckboxState(this.$wordWrap, options.codeBlockWordWrap); this.setCheckboxState(this.$wordWrap, options.codeBlockWordWrap);

View File

@ -1,15 +1,16 @@
import OptionsWidget from "../options_widget.js"; import OptionsWidget from "../options_widget.js";
import { t } from "../../../../services/i18n.js"; import { t } from "../../../../services/i18n.js";
import utils from "../../../../services/utils.js"; import utils from "../../../../services/utils.js";
import type { OptionMap } from "../../../../../../services/options_interface.js";
const TPL = ` const TPL = `
<div class="options-section"> <div class="options-section">
<h4>${t("electron_integration.desktop-application")}</h4> <h4>${t("electron_integration.desktop-application")}</h4>
<div class="form-group row"> <div class="form-group row">
<div class="col-12"> <div class="col-12">
<label for="zoom-factor-select">${t("electron_integration.zoom-factor")}</label> <label for="zoom-factor-select">${t("electron_integration.zoom-factor")}</label>
<input id="zoom-factor-select" type="number" class="zoom-factor-select form-control options-number-input" min="0.3" max="2.0" step="0.1"/> <input id="zoom-factor-select" type="number" class="zoom-factor-select form-control options-number-input" min="0.3" max="2.0" step="0.1"/>
<p>${t("zoom_factor.description")}</p> <p>${t("zoom_factor.description")}</p>
</div> </div>
</div> </div>
@ -20,7 +21,7 @@ const TPL = `
<input type="checkbox" class="native-title-bar form-check-input" /> <input type="checkbox" class="native-title-bar form-check-input" />
<strong>${t("electron_integration.native-title-bar")}</strong> <strong>${t("electron_integration.native-title-bar")}</strong>
<p>${t("electron_integration.native-title-bar-description")}</p> <p>${t("electron_integration.native-title-bar-description")}</p>
</label> </label>
</div> </div>
<div class="side-checkbox"> <div class="side-checkbox">
@ -28,7 +29,7 @@ const TPL = `
<input type="checkbox" class="background-effects form-check-input" /> <input type="checkbox" class="background-effects form-check-input" />
<strong>${t("electron_integration.background-effects")}</strong> <strong>${t("electron_integration.background-effects")}</strong>
<p>${t("electron_integration.background-effects-description")}</p> <p>${t("electron_integration.background-effects-description")}</p>
</label> </label>
</div> </div>
<button class="btn btn-micro restart-app-button">${t("electron_integration.restart-app-button")}</button> <button class="btn btn-micro restart-app-button">${t("electron_integration.restart-app-button")}</button>
@ -36,12 +37,17 @@ const TPL = `
`; `;
export default class ElectronIntegrationOptions extends OptionsWidget { export default class ElectronIntegrationOptions extends OptionsWidget {
private $zoomFactorSelect!: JQuery<HTMLElement>;
private $nativeTitleBar!: JQuery<HTMLElement>;
private $backgroundEffects!: JQuery<HTMLElement>;
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$zoomFactorSelect = this.$widget.find(".zoom-factor-select"); this.$zoomFactorSelect = this.$widget.find(".zoom-factor-select");
this.$zoomFactorSelect.on("change", () => { this.$zoomFactorSelect.on("change", () => {
appContext.triggerCommand("setZoomFactorAndSave", { zoomFactor: this.$zoomFactorSelect.val() }); this.triggerCommand("setZoomFactorAndSave", { zoomFactor: String(this.$zoomFactorSelect.val()) });
}); });
this.$nativeTitleBar = this.$widget.find("input.native-title-bar"); this.$nativeTitleBar = this.$widget.find("input.native-title-bar");
@ -62,7 +68,7 @@ export default class ElectronIntegrationOptions extends OptionsWidget {
return utils.isElectron(); return utils.isElectron();
} }
async optionsLoaded(options) { async optionsLoaded(options: OptionMap) {
this.$zoomFactorSelect.val(options.zoomFactor); this.$zoomFactorSelect.val(options.zoomFactor);
this.setCheckboxState(this.$nativeTitleBar, options.nativeTitleBarVisible); this.setCheckboxState(this.$nativeTitleBar, options.nativeTitleBarVisible);
this.setCheckboxState(this.$backgroundEffects, options.backgroundEffects); this.setCheckboxState(this.$backgroundEffects, options.backgroundEffects);

View File

@ -2,6 +2,7 @@ import OptionsWidget from "../options_widget.js";
import server from "../../../../services/server.js"; import server from "../../../../services/server.js";
import utils from "../../../../services/utils.js"; import utils from "../../../../services/utils.js";
import { t } from "../../../../services/i18n.js"; import { t } from "../../../../services/i18n.js";
import type { OptionMap } from "../../../../../../services/options_interface.js";
const TPL = ` const TPL = `
<div class="options-section"> <div class="options-section">
@ -24,7 +25,17 @@ const TPL = `
</div> </div>
`; `;
// TODO: Deduplicate with server.
interface Locale {
id: string;
name: string;
}
export default class LocalizationOptions extends OptionsWidget { export default class LocalizationOptions extends OptionsWidget {
private $localeSelect!: JQuery<HTMLElement>;
private $firstDayOfWeek!: JQuery<HTMLElement>;
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
@ -37,12 +48,12 @@ export default class LocalizationOptions extends OptionsWidget {
this.$firstDayOfWeek = this.$widget.find(".first-day-of-week-select"); this.$firstDayOfWeek = this.$widget.find(".first-day-of-week-select");
this.$firstDayOfWeek.on("change", () => { this.$firstDayOfWeek.on("change", () => {
this.updateOption("firstDayOfWeek", this.$firstDayOfWeek.val()); this.updateOption("firstDayOfWeek", String(this.$firstDayOfWeek.val()));
}); });
} }
async optionsLoaded(options) { async optionsLoaded(options: OptionMap) {
const availableLocales = await server.get("options/locales"); const availableLocales = await server.get<Locale[]>("options/locales");
this.$localeSelect.empty(); this.$localeSelect.empty();
for (const locale of availableLocales) { for (const locale of availableLocales) {

View File

@ -1,6 +1,7 @@
import OptionsWidget from "../options_widget.js"; import OptionsWidget from "../options_widget.js";
import utils from "../../../../services/utils.js"; import utils from "../../../../services/utils.js";
import { t } from "../../../../services/i18n.js"; import { t } from "../../../../services/i18n.js";
import type { OptionMap } from "../../../../../../services/options_interface.js";
const MIN_VALUE = 640; const MIN_VALUE = 640;
@ -24,17 +25,20 @@ const TPL = `
</div>`; </div>`;
export default class MaxContentWidthOptions extends OptionsWidget { export default class MaxContentWidthOptions extends OptionsWidget {
private $maxContentWidth!: JQuery<HTMLElement>;
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$maxContentWidth = this.$widget.find(".max-content-width"); this.$maxContentWidth = this.$widget.find(".max-content-width");
this.$maxContentWidth.on("change", async () => this.updateOption("maxContentWidth", this.$maxContentWidth.val())); this.$maxContentWidth.on("change", async () => this.updateOption("maxContentWidth", String(this.$maxContentWidth.val())));
this.$widget.find(".reload-frontend-button").on("click", () => utils.reloadFrontendApp(t("max_content_width.reload_description"))); this.$widget.find(".reload-frontend-button").on("click", () => utils.reloadFrontendApp(t("max_content_width.reload_description")));
} }
async optionsLoaded(options) { async optionsLoaded(options: OptionMap) {
this.$maxContentWidth.val(Math.max(MIN_VALUE, options.maxContentWidth)); this.$maxContentWidth.val(Math.max(MIN_VALUE, parseInt(options.maxContentWidth, 10)));
} }
} }

View File

@ -1,3 +1,4 @@
import type { OptionMap } from "../../../../../../services/options_interface.js";
import { t } from "../../../../services/i18n.js"; import { t } from "../../../../services/i18n.js";
import OptionsWidget from "../options_widget.js"; import OptionsWidget from "../options_widget.js";
@ -8,7 +9,7 @@ const TPL = `
<input type="checkbox" class="promoted-attributes-open-in-ribbon form-check-input"> <input type="checkbox" class="promoted-attributes-open-in-ribbon form-check-input">
${t("ribbon.promoted_attributes_message")} ${t("ribbon.promoted_attributes_message")}
</label> </label>
<label> <label>
<input type="checkbox" class="edited-notes-open-in-ribbon form-check-input"> <input type="checkbox" class="edited-notes-open-in-ribbon form-check-input">
${t("ribbon.edited_notes_message")} ${t("ribbon.edited_notes_message")}
@ -16,6 +17,10 @@ const TPL = `
</div>`; </div>`;
export default class RibbonOptions extends OptionsWidget { export default class RibbonOptions extends OptionsWidget {
private $promotedAttributesOpenInRibbon!: JQuery<HTMLElement>;
private $editedNotesOpenInRibbon!: JQuery<HTMLElement>;
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
@ -26,7 +31,7 @@ export default class RibbonOptions extends OptionsWidget {
this.$editedNotesOpenInRibbon.on("change", () => this.updateCheckboxOption("editedNotesOpenInRibbon", this.$editedNotesOpenInRibbon)); this.$editedNotesOpenInRibbon.on("change", () => this.updateCheckboxOption("editedNotesOpenInRibbon", this.$editedNotesOpenInRibbon));
} }
async optionsLoaded(options) { async optionsLoaded(options: OptionMap) {
this.setCheckboxState(this.$promotedAttributesOpenInRibbon, options.promotedAttributesOpenInRibbon); this.setCheckboxState(this.$promotedAttributesOpenInRibbon, options.promotedAttributesOpenInRibbon);
this.setCheckboxState(this.$editedNotesOpenInRibbon, options.editedNotesOpenInRibbon); this.setCheckboxState(this.$editedNotesOpenInRibbon, options.editedNotesOpenInRibbon);
} }

View File

@ -2,6 +2,7 @@ import OptionsWidget from "../options_widget.js";
import server from "../../../../services/server.js"; import server from "../../../../services/server.js";
import utils from "../../../../services/utils.js"; import utils from "../../../../services/utils.js";
import { t } from "../../../../services/i18n.js"; import { t } from "../../../../services/i18n.js";
import type { OptionMap } from "../../../../../../services/options_interface.js";
const TPL = ` const TPL = `
<div class="options-section"> <div class="options-section">
@ -44,13 +45,24 @@ const TPL = `
</div> </div>
</div>`; </div>`;
interface Theme {
val: string;
title: string;
noteId?: string;
}
export default class ThemeOptions extends OptionsWidget { export default class ThemeOptions extends OptionsWidget {
private $themeSelect!: JQuery<HTMLElement>;
private $overrideThemeFonts!: JQuery<HTMLElement>;
private $layoutOrientation!: JQuery<HTMLElement>;
doRender() { doRender() {
this.$widget = $(TPL); this.$widget = $(TPL);
this.$themeSelect = this.$widget.find(".theme-select"); this.$themeSelect = this.$widget.find(".theme-select");
this.$overrideThemeFonts = this.$widget.find(".override-theme-fonts"); this.$overrideThemeFonts = this.$widget.find(".override-theme-fonts");
this.$layoutOrientation = this.$widget.find(`input[name="layout-orientation"]`).on("change", async () => { this.$layoutOrientation = this.$widget.find(`input[name="layout-orientation"]`).on("change", async () => {
const newLayoutOrientation = this.$widget.find(`input[name="layout-orientation"]:checked`).val(); const newLayoutOrientation = String(this.$widget.find(`input[name="layout-orientation"]:checked`).val());
await this.updateOption("layoutOrientation", newLayoutOrientation); await this.updateOption("layoutOrientation", newLayoutOrientation);
utils.reloadFrontendApp("layout orientation change"); utils.reloadFrontendApp("layout orientation change");
}); });
@ -66,20 +78,23 @@ export default class ThemeOptions extends OptionsWidget {
this.$overrideThemeFonts.on("change", () => this.updateCheckboxOption("overrideThemeFonts", this.$overrideThemeFonts)); this.$overrideThemeFonts.on("change", () => this.updateCheckboxOption("overrideThemeFonts", this.$overrideThemeFonts));
} }
async optionsLoaded(options) { async optionsLoaded(options: OptionMap) {
const themes = [ const themes: Theme[] = [
{ val: "next", title: t("theme.triliumnext") }, { val: "next", title: t("theme.triliumnext") },
{ val: "next-light", title: t("theme.triliumnext-light") }, { val: "next-light", title: t("theme.triliumnext-light") },
{ val: "next-dark", title: t("theme.triliumnext-dark") }, { val: "next-dark", title: t("theme.triliumnext-dark") },
{ val: "auto", title: t("theme.auto_theme") }, { val: "auto", title: t("theme.auto_theme") },
{ val: "light", title: t("theme.light_theme") }, { val: "light", title: t("theme.light_theme") },
{ val: "dark", title: t("theme.dark_theme") } { val: "dark", title: t("theme.dark_theme") }
].concat(await server.get("options/user-themes")); ].concat(await server.get<Theme[]>("options/user-themes"));
this.$themeSelect.empty(); this.$themeSelect.empty();
for (const theme of themes) { for (const theme of themes) {
this.$themeSelect.append($("<option>").attr("value", theme.val).attr("data-note-id", theme.noteId).text(theme.title)); this.$themeSelect.append($("<option>")
.attr("value", theme.val)
.attr("data-note-id", theme.noteId || "")
.text(theme.title));
} }
this.$themeSelect.val(options.theme); this.$themeSelect.val(options.theme);