import type {OpenDialogOptions} from "electron"; import {ipcRenderer, remote} from "electron"; import fs from "fs"; import path from "path"; import Tagify from "@yaireo/tagify"; import ISO6391 from "iso-639-1"; import * as ConfigUtil from "../../../../common/config-util"; import * as EnterpriseUtil from "../../../../common/enterprise-util"; import {html} from "../../../../common/html"; import * as t from "../../../../common/translation-util"; import supportedLocales from "../../../../translations/supported-locales.json"; import {generateSelectHTML, generateSettingOption} from "./base-section"; const {app, dialog, session} = remote; const currentBrowserWindow = remote.getCurrentWindow(); interface GeneralSectionProps { $root: Element; } export function initGeneralSection(props: GeneralSectionProps): void { props.$root.innerHTML = html`
${t.__("Appearance")}
${t.__("Show app icon in system tray")}
${t.__("Show app unread badge")}
${t.__("Bounce dock on new private message")}
${t.__("Flash taskbar on new message")}
${t.__("Desktop Notifications")}
${t.__("Show desktop notifications")}
${t.__("Mute all sounds from Zulip")}
${t.__("App Updates")}
${t.__("Enable auto updates")}
${t.__("Get beta updates")}
${t.__("Functionality")}
${t.__("Start app at login")}
${t.__("Always start minimized")}
${t.__("Quit when the window is closed")}
${t.__("Enable spellchecker (requires restart)")}
${t.__("Advanced")}
${t.__("Enable error reporting (requires restart)")}
${t.__("App language (requires restart)")}
${t.__("Add custom CSS")}
${ConfigUtil.getConfigString("customCSS", "")}
indeterminate_check_box ${t.__("Delete")}
${t.__("Default download location")}
${ConfigUtil.getConfigString( "downloadsPath", app.getPath("downloads"), )}
${t.__("Ask where to save files before downloading")}
${t.__("Factory Reset Data")}
${t.__( "Reset the application, thus deleting all the connected organizations and accounts.", )}
`.html; updateTrayOption(); updateBadgeOption(); updateSilentOption(); autoUpdateOption(); betaUpdateOption(); updateSidebarOption(); updateStartAtLoginOption(); factoryReset(); showDesktopNotification(); enableSpellchecker(); minimizeOnStart(); addCustomCSS(); showCustomCSSPath(); removeCustomCSS(); downloadFolder(); updateQuitOnCloseOption(); updatePromptDownloadOption(); enableErrorReporting(); setLocale(); initSpellChecker(); // Platform specific settings // Flashing taskbar on Windows if (process.platform === "win32") { updateFlashTaskbar(); } // Dock bounce on macOS if (process.platform === "darwin") { updateDockBouncing(); } // Auto hide menubar on Windows and Linux if (process.platform !== "darwin") { updateMenubarOption(); } function updateTrayOption(): void { generateSettingOption({ $element: document.querySelector("#tray-option .setting-control")!, value: ConfigUtil.getConfigItem("trayIcon", true), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("trayIcon"); ConfigUtil.setConfigItem("trayIcon", newValue); ipcRenderer.send("forward-message", "toggletray"); updateTrayOption(); }, }); } function updateMenubarOption(): void { generateSettingOption({ $element: document.querySelector("#menubar-option .setting-control")!, value: ConfigUtil.getConfigItem("autoHideMenubar", false), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("autoHideMenubar"); ConfigUtil.setConfigItem("autoHideMenubar", newValue); ipcRenderer.send("toggle-menubar", newValue); updateMenubarOption(); }, }); } function updateBadgeOption(): void { generateSettingOption({ $element: document.querySelector("#badge-option .setting-control")!, value: ConfigUtil.getConfigItem("badgeOption", true), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("badgeOption"); ConfigUtil.setConfigItem("badgeOption", newValue); ipcRenderer.send("toggle-badge-option", newValue); updateBadgeOption(); }, }); } function updateDockBouncing(): void { generateSettingOption({ $element: document.querySelector("#dock-bounce-option .setting-control")!, value: ConfigUtil.getConfigItem("dockBouncing", true), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("dockBouncing"); ConfigUtil.setConfigItem("dockBouncing", newValue); updateDockBouncing(); }, }); } function updateFlashTaskbar(): void { generateSettingOption({ $element: document.querySelector( "#flash-taskbar-option .setting-control", )!, value: ConfigUtil.getConfigItem("flashTaskbarOnMessage", true), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("flashTaskbarOnMessage"); ConfigUtil.setConfigItem("flashTaskbarOnMessage", newValue); updateFlashTaskbar(); }, }); } function autoUpdateOption(): void { generateSettingOption({ $element: document.querySelector("#autoupdate-option .setting-control")!, disabled: EnterpriseUtil.configItemExists("autoUpdate"), value: ConfigUtil.getConfigItem("autoUpdate", true), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("autoUpdate"); ConfigUtil.setConfigItem("autoUpdate", newValue); if (!newValue) { ConfigUtil.setConfigItem("betaUpdate", false); betaUpdateOption(); } autoUpdateOption(); }, }); } function betaUpdateOption(): void { generateSettingOption({ $element: document.querySelector("#betaupdate-option .setting-control")!, value: ConfigUtil.getConfigItem("betaUpdate", false), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("betaUpdate"); if (ConfigUtil.getConfigItem("autoUpdate")) { ConfigUtil.setConfigItem("betaUpdate", newValue); betaUpdateOption(); } }, }); } function updateSilentOption(): void { generateSettingOption({ $element: document.querySelector("#silent-option .setting-control")!, value: ConfigUtil.getConfigItem("silent", false), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("silent", true); ConfigUtil.setConfigItem("silent", newValue); updateSilentOption(); currentBrowserWindow.webContents.send("toggle-silent", newValue); }, }); } function showDesktopNotification(): void { generateSettingOption({ $element: document.querySelector( "#show-notification-option .setting-control", )!, value: ConfigUtil.getConfigItem("showNotification", true), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("showNotification", true); ConfigUtil.setConfigItem("showNotification", newValue); showDesktopNotification(); }, }); } function updateSidebarOption(): void { generateSettingOption({ $element: document.querySelector("#sidebar-option .setting-control")!, value: ConfigUtil.getConfigItem("showSidebar", true), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("showSidebar"); ConfigUtil.setConfigItem("showSidebar", newValue); ipcRenderer.send("forward-message", "toggle-sidebar", newValue); updateSidebarOption(); }, }); } function updateStartAtLoginOption(): void { generateSettingOption({ $element: document.querySelector( "#startAtLogin-option .setting-control", )!, value: ConfigUtil.getConfigItem("startAtLogin", false), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("startAtLogin"); ConfigUtil.setConfigItem("startAtLogin", newValue); ipcRenderer.send("toggleAutoLauncher", newValue); updateStartAtLoginOption(); }, }); } function updateQuitOnCloseOption(): void { generateSettingOption({ $element: document.querySelector("#quitOnClose-option .setting-control")!, value: ConfigUtil.getConfigItem("quitOnClose", false), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("quitOnClose"); ConfigUtil.setConfigItem("quitOnClose", newValue); updateQuitOnCloseOption(); }, }); } function enableSpellchecker(): void { generateSettingOption({ $element: document.querySelector( "#enable-spellchecker-option .setting-control", )!, value: ConfigUtil.getConfigItem("enableSpellchecker", true), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("enableSpellchecker"); ConfigUtil.setConfigItem("enableSpellchecker", newValue); enableSpellchecker(); const spellcheckerLanguageInput: HTMLElement = document.querySelector( "#spellcheck-langs", )!; const spellcheckerNote: HTMLElement = document.querySelector("#note")!; spellcheckerLanguageInput.style.display = spellcheckerLanguageInput.style.display === "none" ? "" : "none"; spellcheckerNote.style.display = spellcheckerNote.style.display === "none" ? "" : "none"; }, }); } function enableErrorReporting(): void { generateSettingOption({ $element: document.querySelector( "#enable-error-reporting .setting-control", )!, value: ConfigUtil.getConfigItem("errorReporting", true), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("errorReporting"); ConfigUtil.setConfigItem("errorReporting", newValue); enableErrorReporting(); }, }); } async function customCssDialog(): Promise { const showDialogOptions: OpenDialogOptions = { title: "Select file", properties: ["openFile"], filters: [{name: "CSS file", extensions: ["css"]}], }; const {filePaths, canceled} = await dialog.showOpenDialog( showDialogOptions, ); if (!canceled) { ConfigUtil.setConfigItem("customCSS", filePaths[0]); ipcRenderer.send("forward-message", "hard-reload"); } } function setLocale(): void { const langDiv: HTMLSelectElement = document.querySelector(".lang-div")!; const langListHTML = generateSelectHTML(supportedLocales, "lang-menu"); langDiv.innerHTML += langListHTML.html; // `langMenu` is the select-option dropdown menu formed after executing the previous command const langMenu: HTMLSelectElement = document.querySelector(".lang-menu")!; // The next three lines set the selected language visible on the dropdown button let language = ConfigUtil.getConfigItem("appLanguage"); language = language && langMenu.options.namedItem(language) ? language : "en"; langMenu.options.namedItem(language)!.selected = true; langMenu.addEventListener("change", (event: Event) => { ConfigUtil.setConfigItem( "appLanguage", (event.target as HTMLSelectElement).value, ); }); } function minimizeOnStart(): void { generateSettingOption({ $element: document.querySelector( "#start-minimize-option .setting-control", )!, value: ConfigUtil.getConfigItem("startMinimized", false), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("startMinimized"); ConfigUtil.setConfigItem("startMinimized", newValue); minimizeOnStart(); }, }); } function addCustomCSS(): void { const customCSSButton = document.querySelector( "#add-custom-css .custom-css-button", )!; customCSSButton.addEventListener("click", async () => { await customCssDialog(); }); } function showCustomCSSPath(): void { if (!ConfigUtil.getConfigItem("customCSS")) { const cssPATH: HTMLElement = document.querySelector( "#remove-custom-css", )!; cssPATH.style.display = "none"; } } function removeCustomCSS(): void { const removeCSSButton = document.querySelector("#css-delete-action")!; removeCSSButton.addEventListener("click", () => { ConfigUtil.setConfigItem("customCSS", ""); ipcRenderer.send("forward-message", "hard-reload"); }); } async function downloadFolderDialog(): Promise { const showDialogOptions: OpenDialogOptions = { title: "Select Download Location", properties: ["openDirectory"], }; const {filePaths, canceled} = await dialog.showOpenDialog( showDialogOptions, ); if (!canceled) { ConfigUtil.setConfigItem("downloadsPath", filePaths[0]); const downloadFolderPath: HTMLElement = document.querySelector( ".download-folder-path", )!; downloadFolderPath.textContent = filePaths[0]; } } function downloadFolder(): void { const downloadFolder = document.querySelector( "#download-folder .download-folder-button", )!; downloadFolder.addEventListener("click", async () => { await downloadFolderDialog(); }); } function updatePromptDownloadOption(): void { generateSettingOption({ $element: document.querySelector("#prompt-download .setting-control")!, value: ConfigUtil.getConfigItem("promptDownload", false), clickHandler: () => { const newValue = !ConfigUtil.getConfigItem("promptDownload"); ConfigUtil.setConfigItem("promptDownload", newValue); updatePromptDownloadOption(); }, }); } async function factoryResetSettings(): Promise { const clearAppDataMessage = "When the application restarts, it will be as if you have just downloaded Zulip app."; const getAppPath = path.join(app.getPath("appData"), app.name); const {response} = await dialog.showMessageBox({ type: "warning", buttons: ["YES", "NO"], defaultId: 0, message: "Are you sure?", detail: clearAppDataMessage, }); if (response === 0) { await fs.promises.rmdir(getAppPath, {recursive: true}); setTimeout(() => { ipcRenderer.send("clear-app-settings"); }, 1000); } } function factoryReset(): void { const factoryResetButton = document.querySelector( "#factory-reset-option .factory-reset-button", )!; factoryResetButton.addEventListener("click", async () => { await factoryResetSettings(); }); } function initSpellChecker(): void { // The elctron API is a no-op on macOS and macOS default spellchecker is used. if (process.platform === "darwin") { const note: HTMLElement = document.querySelector("#note")!; note.append(t.__("On macOS, the OS spellchecker is used.")); note.append(document.createElement("br")); note.append( t.__( "Change the language from System Preferences → Keyboard → Text → Spelling.", ), ); } else { const note: HTMLElement = document.querySelector("#note")!; note.append( t.__("You can select a maximum of 3 languages for spellchecking."), ); const spellDiv: HTMLElement = document.querySelector( "#spellcheck-langs", )!; spellDiv.innerHTML += html`
${t.__("Spellchecker Languages")}
`.html; const availableLanguages = session.fromPartition("persist:webviewsession") .availableSpellCheckerLanguages; let languagePairs: Map = new Map(); for (const l of availableLanguages) { if (ISO6391.validate(l)) { languagePairs.set(ISO6391.getName(l), l); } } // Manually set names for languages not available in ISO6391 languagePairs.set("English (AU)", "en-AU"); languagePairs.set("English (CA)", "en-CA"); languagePairs.set("English (GB)", "en-GB"); languagePairs.set("English (US)", "en-US"); languagePairs.set("Spanish (Latin America)", "es-419"); languagePairs.set("Spanish (Argentina)", "es-AR"); languagePairs.set("Spanish (Mexico)", "es-MX"); languagePairs.set("Spanish (US)", "es-US"); languagePairs.set("Portuguese (Brazil)", "pt-BR"); languagePairs.set("Portuguese (Portugal)", "pt-PT"); languagePairs.set("Serbo-Croatian", "sh"); languagePairs = new Map( [...languagePairs].sort((a, b) => (a[0] < b[0] ? -1 : 1)), ); const tagField: HTMLInputElement = document.querySelector( "input[name=spellcheck]", )!; const tagify = new Tagify(tagField, { whitelist: [...languagePairs.keys()], enforceWhitelist: true, maxTags: 3, dropdown: { enabled: 0, maxItems: Number.POSITIVE_INFINITY, closeOnSelect: false, highlightFirst: true, }, }); const configuredLanguages: string[] = ConfigUtil.getConfigItem( "spellcheckerLanguages", ).map( (code: string) => [...languagePairs].find((pair) => pair[1] === code)![0], ); tagify.addTags(configuredLanguages); tagField.addEventListener("change", (event) => { if ((event.target as HTMLInputElement).value.length === 0) { ConfigUtil.setConfigItem("spellcheckerLanguages", []); ipcRenderer.send("set-spellcheck-langs"); } else { const spellLangs: string[] = [ ...JSON.parse((event.target as HTMLInputElement).value).values(), ].map((elt) => languagePairs.get(elt.value)!); ConfigUtil.setConfigItem("spellcheckerLanguages", spellLangs); ipcRenderer.send("set-spellcheck-langs"); } }); } // Do not display the spellchecker input and note if it is disabled if (!ConfigUtil.getConfigItem("enableSpellchecker")) { const spellcheckerLanguageInput: HTMLElement = document.querySelector( "#spellcheck-langs", )!; const spellcheckerNote: HTMLElement = document.querySelector("#note")!; spellcheckerLanguageInput.style.display = "none"; spellcheckerNote.style.display = "none"; } } }