mirror of
				https://github.com/zulip/zulip-desktop.git
				synced 2025-11-03 21:43:18 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			702 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			702 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import type {OpenDialogOptions} from "electron/renderer";
 | 
						|
import fs from "node:fs";
 | 
						|
import path from "node:path";
 | 
						|
import process from "node:process";
 | 
						|
 | 
						|
import * as remote from "@electron/remote";
 | 
						|
import {app, dialog, session} from "@electron/remote";
 | 
						|
import Tagify from "@yaireo/tagify";
 | 
						|
import {z} from "zod";
 | 
						|
 | 
						|
import supportedLocales from "../../../../../public/translations/supported-locales.json";
 | 
						|
import * as ConfigUtil from "../../../../common/config-util.ts";
 | 
						|
import * as EnterpriseUtil from "../../../../common/enterprise-util.ts";
 | 
						|
import {html} from "../../../../common/html.ts";
 | 
						|
import * as t from "../../../../common/translation-util.ts";
 | 
						|
import {ipcRenderer} from "../../typed-ipc-renderer.ts";
 | 
						|
 | 
						|
import {generateSelectHtml, generateSettingOption} from "./base-section.ts";
 | 
						|
 | 
						|
const currentBrowserWindow = remote.getCurrentWindow();
 | 
						|
 | 
						|
type GeneralSectionProperties = {
 | 
						|
  $root: Element;
 | 
						|
};
 | 
						|
 | 
						|
export function initGeneralSection({$root}: GeneralSectionProperties): void {
 | 
						|
  $root.innerHTML = html`
 | 
						|
    <div class="settings-pane">
 | 
						|
      <div class="title">${t.__("Appearance")}</div>
 | 
						|
      <div id="appearance-option-settings" class="settings-card">
 | 
						|
        <div class="setting-row" id="tray-option">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Show app icon in system tray")}
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
        <div
 | 
						|
          class="setting-row"
 | 
						|
          id="menubar-option"
 | 
						|
          style="display:${process.platform === "darwin" ? "none" : ""}"
 | 
						|
        >
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Auto hide menu bar (Press Alt key to display)")}
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
        <div class="setting-row" id="sidebar-option">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Show sidebar")} (<span class="code"
 | 
						|
              >${process.platform === "darwin"
 | 
						|
                ? "Cmd+Shift+S"
 | 
						|
                : "Ctrl+Shift+S"}</span
 | 
						|
            >)
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
        <div class="setting-row" id="badge-option">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Show unread count badge on app icon")}
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
        <div
 | 
						|
          class="setting-row"
 | 
						|
          id="dock-bounce-option"
 | 
						|
          style="display:${process.platform === "darwin" ? "" : "none"}"
 | 
						|
        >
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Bounce dock on new private message")}
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
        <div
 | 
						|
          class="setting-row"
 | 
						|
          id="flash-taskbar-option"
 | 
						|
          style="display:${process.platform === "win32" ? "" : "none"}"
 | 
						|
        >
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Flash taskbar on new message")}
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
      </div>
 | 
						|
      <div class="title">${t.__("Desktop Notifications")}</div>
 | 
						|
      <div class="settings-card">
 | 
						|
        <div class="setting-row" id="show-notification-option">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Show desktop notifications")}
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
        <div class="setting-row" id="silent-option">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Mute all sounds from Zulip")}
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
      </div>
 | 
						|
      <div class="title">${t.__("App Updates")}</div>
 | 
						|
      <div class="settings-card">
 | 
						|
        <div class="setting-row" id="autoupdate-option">
 | 
						|
          <div class="setting-description">${t.__("Enable auto updates")}</div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
        <div class="setting-row" id="betaupdate-option">
 | 
						|
          <div class="setting-description">${t.__("Get beta updates")}</div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
      </div>
 | 
						|
      <div class="title">${t.__("Functionality")}</div>
 | 
						|
      <div class="settings-card">
 | 
						|
        <div class="setting-row" id="startAtLogin-option">
 | 
						|
          <div class="setting-description">${t.__("Start app at login")}</div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
        <div class="setting-row" id="start-minimize-option">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Always start minimized")}
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
        <div class="setting-row" id="quitOnClose-option">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Quit when the window is closed")}
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
        <div class="setting-row" id="enable-spellchecker-option">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Enable spellchecker (requires restart)")}
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
        <div
 | 
						|
          class="setting-row"
 | 
						|
          id="spellcheck-langs"
 | 
						|
          style="display:${process.platform === "darwin" ? "none" : ""}"
 | 
						|
        ></div>
 | 
						|
        <div class="setting-row" id="note"></div>
 | 
						|
      </div>
 | 
						|
 | 
						|
      <div class="title">${t.__("Advanced")}</div>
 | 
						|
      <div class="settings-card">
 | 
						|
        <div class="setting-row" id="enable-error-reporting">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Enable error reporting (requires restart)")}
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
 | 
						|
        <div class="setting-row" id="app-language">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("App language (requires restart)")}
 | 
						|
          </div>
 | 
						|
          <div id="lang-div" class="lang-div"></div>
 | 
						|
        </div>
 | 
						|
 | 
						|
        <div class="setting-row" id="add-custom-css">
 | 
						|
          <div class="setting-description">${t.__("Add custom CSS")}</div>
 | 
						|
          <button class="custom-css-button green">${t.__("Upload")}</button>
 | 
						|
        </div>
 | 
						|
        <div class="setting-row" id="remove-custom-css">
 | 
						|
          <div class="setting-description">
 | 
						|
            <div class="selected-css-path" id="custom-css-path">
 | 
						|
              ${ConfigUtil.getConfigItem("customCSS", "")}
 | 
						|
            </div>
 | 
						|
          </div>
 | 
						|
          <div class="action red" id="css-delete-action">
 | 
						|
            <i class="material-icons">indeterminate_check_box</i>
 | 
						|
            <span>${t.__("Delete")}</span>
 | 
						|
          </div>
 | 
						|
        </div>
 | 
						|
        <div class="setting-row" id="download-folder">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Default download location")}
 | 
						|
          </div>
 | 
						|
          <button class="download-folder-button green">
 | 
						|
            ${t.__("Change")}
 | 
						|
          </button>
 | 
						|
        </div>
 | 
						|
        <div class="setting-row">
 | 
						|
          <div class="setting-description">
 | 
						|
            <div class="download-folder-path">
 | 
						|
              ${ConfigUtil.getConfigItem(
 | 
						|
                "downloadsPath",
 | 
						|
                app.getPath("downloads"),
 | 
						|
              )}
 | 
						|
            </div>
 | 
						|
          </div>
 | 
						|
        </div>
 | 
						|
        <div class="setting-row" id="prompt-download">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__("Ask where to save files before downloading")}
 | 
						|
          </div>
 | 
						|
          <div class="setting-control"></div>
 | 
						|
        </div>
 | 
						|
      </div>
 | 
						|
      <div class="title">${t.__("Factory Reset Data")}</div>
 | 
						|
      <div class="settings-card">
 | 
						|
        <div class="setting-row" id="factory-reset-option">
 | 
						|
          <div class="setting-description">
 | 
						|
            ${t.__(
 | 
						|
              "Reset the application, thus deleting all the connected organizations and accounts.",
 | 
						|
            )}
 | 
						|
          </div>
 | 
						|
          <button class="factory-reset-button red w-150">
 | 
						|
            ${t.__("Factory Reset")}
 | 
						|
          </button>
 | 
						|
        </div>
 | 
						|
      </div>
 | 
						|
    </div>
 | 
						|
  `.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: $root.querySelector("#tray-option .setting-control")!,
 | 
						|
      value: ConfigUtil.getConfigItem("trayIcon", true),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("trayIcon", true);
 | 
						|
        ConfigUtil.setConfigItem("trayIcon", newValue);
 | 
						|
        ipcRenderer.send("forward-message", "toggletray");
 | 
						|
        updateTrayOption();
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function updateMenubarOption(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector("#menubar-option .setting-control")!,
 | 
						|
      value: ConfigUtil.getConfigItem("autoHideMenubar", false),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("autoHideMenubar", false);
 | 
						|
        ConfigUtil.setConfigItem("autoHideMenubar", newValue);
 | 
						|
        ipcRenderer.send("toggle-menubar", newValue);
 | 
						|
        updateMenubarOption();
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function updateBadgeOption(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector("#badge-option .setting-control")!,
 | 
						|
      value: ConfigUtil.getConfigItem("badgeOption", true),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("badgeOption", true);
 | 
						|
        ConfigUtil.setConfigItem("badgeOption", newValue);
 | 
						|
        ipcRenderer.send("toggle-badge-option", newValue);
 | 
						|
        updateBadgeOption();
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function updateDockBouncing(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector("#dock-bounce-option .setting-control")!,
 | 
						|
      value: ConfigUtil.getConfigItem("dockBouncing", true),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("dockBouncing", true);
 | 
						|
        ConfigUtil.setConfigItem("dockBouncing", newValue);
 | 
						|
        updateDockBouncing();
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function updateFlashTaskbar(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector("#flash-taskbar-option .setting-control")!,
 | 
						|
      value: ConfigUtil.getConfigItem("flashTaskbarOnMessage", true),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem(
 | 
						|
          "flashTaskbarOnMessage",
 | 
						|
          true,
 | 
						|
        );
 | 
						|
        ConfigUtil.setConfigItem("flashTaskbarOnMessage", newValue);
 | 
						|
        updateFlashTaskbar();
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function autoUpdateOption(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector("#autoupdate-option .setting-control")!,
 | 
						|
      disabled: EnterpriseUtil.configItemExists("autoUpdate"),
 | 
						|
      value: ConfigUtil.getConfigItem("autoUpdate", true),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("autoUpdate", true);
 | 
						|
        ConfigUtil.setConfigItem("autoUpdate", newValue);
 | 
						|
        if (!newValue) {
 | 
						|
          ConfigUtil.setConfigItem("betaUpdate", false);
 | 
						|
          betaUpdateOption();
 | 
						|
        }
 | 
						|
 | 
						|
        autoUpdateOption();
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function betaUpdateOption(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector("#betaupdate-option .setting-control")!,
 | 
						|
      value: ConfigUtil.getConfigItem("betaUpdate", false),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("betaUpdate", false);
 | 
						|
        if (ConfigUtil.getConfigItem("autoUpdate", true)) {
 | 
						|
          ConfigUtil.setConfigItem("betaUpdate", newValue);
 | 
						|
          betaUpdateOption();
 | 
						|
        }
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function updateSilentOption(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector("#silent-option .setting-control")!,
 | 
						|
      value: ConfigUtil.getConfigItem("silent", false),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("silent", true);
 | 
						|
        ConfigUtil.setConfigItem("silent", newValue);
 | 
						|
        updateSilentOption();
 | 
						|
        ipcRenderer.send(
 | 
						|
          "forward-to",
 | 
						|
          currentBrowserWindow.webContents.id,
 | 
						|
          "toggle-silent",
 | 
						|
          newValue,
 | 
						|
        );
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function showDesktopNotification(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.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: $root.querySelector("#sidebar-option .setting-control")!,
 | 
						|
      value: ConfigUtil.getConfigItem("showSidebar", true),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("showSidebar", true);
 | 
						|
        ConfigUtil.setConfigItem("showSidebar", newValue);
 | 
						|
        ipcRenderer.send("forward-message", "toggle-sidebar", newValue);
 | 
						|
        updateSidebarOption();
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function updateStartAtLoginOption(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector("#startAtLogin-option .setting-control")!,
 | 
						|
      value: ConfigUtil.getConfigItem("startAtLogin", false),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("startAtLogin", false);
 | 
						|
        ConfigUtil.setConfigItem("startAtLogin", newValue);
 | 
						|
        ipcRenderer.send("toggleAutoLauncher", newValue);
 | 
						|
        updateStartAtLoginOption();
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function updateQuitOnCloseOption(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector("#quitOnClose-option .setting-control")!,
 | 
						|
      value: ConfigUtil.getConfigItem("quitOnClose", false),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("quitOnClose", false);
 | 
						|
        ConfigUtil.setConfigItem("quitOnClose", newValue);
 | 
						|
        updateQuitOnCloseOption();
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function enableSpellchecker(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector(
 | 
						|
        "#enable-spellchecker-option .setting-control",
 | 
						|
      )!,
 | 
						|
      value: ConfigUtil.getConfigItem("enableSpellchecker", true),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("enableSpellchecker", true);
 | 
						|
        ConfigUtil.setConfigItem("enableSpellchecker", newValue);
 | 
						|
        ipcRenderer.send("configure-spell-checker");
 | 
						|
        enableSpellchecker();
 | 
						|
        const spellcheckerLanguageInput: HTMLElement =
 | 
						|
          $root.querySelector("#spellcheck-langs")!;
 | 
						|
        const spellcheckerNote: HTMLElement = $root.querySelector("#note")!;
 | 
						|
        spellcheckerLanguageInput.style.display =
 | 
						|
          spellcheckerLanguageInput.style.display === "none" ? "" : "none";
 | 
						|
        spellcheckerNote.style.display =
 | 
						|
          spellcheckerNote.style.display === "none" ? "" : "none";
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function enableErrorReporting(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector(
 | 
						|
        "#enable-error-reporting .setting-control",
 | 
						|
      )!,
 | 
						|
      value: ConfigUtil.getConfigItem("errorReporting", true),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("errorReporting", true);
 | 
						|
        ConfigUtil.setConfigItem("errorReporting", newValue);
 | 
						|
        enableErrorReporting();
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  async function customCssDialog(): Promise<void> {
 | 
						|
    const showDialogOptions: OpenDialogOptions = {
 | 
						|
      title: t.__("Select file"),
 | 
						|
      properties: ["openFile"],
 | 
						|
      filters: [{name: t.__("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 = $root.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 = $root.querySelector(".lang-menu")!;
 | 
						|
 | 
						|
    // The next three lines set the selected language visible on the dropdown button
 | 
						|
    let language = ConfigUtil.getConfigItem("appLanguage", "en");
 | 
						|
    language =
 | 
						|
      language && langMenu.options.namedItem(language) ? language : "en";
 | 
						|
    langMenu.options.namedItem(language)!.selected = true;
 | 
						|
 | 
						|
    langMenu.addEventListener("change", () => {
 | 
						|
      ConfigUtil.setConfigItem("appLanguage", langMenu.value);
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function minimizeOnStart(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector("#start-minimize-option .setting-control")!,
 | 
						|
      value: ConfigUtil.getConfigItem("startMinimized", false),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("startMinimized", false);
 | 
						|
        ConfigUtil.setConfigItem("startMinimized", newValue);
 | 
						|
        minimizeOnStart();
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function addCustomCss(): void {
 | 
						|
    const customCssButton = $root.querySelector(
 | 
						|
      "#add-custom-css .custom-css-button",
 | 
						|
    )!;
 | 
						|
    customCssButton.addEventListener("click", async () => {
 | 
						|
      await customCssDialog();
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function showCustomCssPath(): void {
 | 
						|
    if (!ConfigUtil.getConfigItem("customCSS", null)) {
 | 
						|
      const cssPath: HTMLElement = $root.querySelector("#remove-custom-css")!;
 | 
						|
      cssPath.style.display = "none";
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function removeCustomCss(): void {
 | 
						|
    const removeCssButton = $root.querySelector("#css-delete-action")!;
 | 
						|
    removeCssButton.addEventListener("click", () => {
 | 
						|
      ConfigUtil.setConfigItem("customCSS", "");
 | 
						|
      ipcRenderer.send("forward-message", "hard-reload");
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  async function downloadFolderDialog(): Promise<void> {
 | 
						|
    const showDialogOptions: OpenDialogOptions = {
 | 
						|
      title: t.__("Select Download Location"),
 | 
						|
      properties: ["openDirectory"],
 | 
						|
    };
 | 
						|
 | 
						|
    const {filePaths, canceled} =
 | 
						|
      await dialog.showOpenDialog(showDialogOptions);
 | 
						|
    if (!canceled) {
 | 
						|
      ConfigUtil.setConfigItem("downloadsPath", filePaths[0]);
 | 
						|
      const downloadFolderPath: HTMLElement = $root.querySelector(
 | 
						|
        ".download-folder-path",
 | 
						|
      )!;
 | 
						|
      downloadFolderPath.textContent = filePaths[0];
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  function downloadFolder(): void {
 | 
						|
    const downloadFolder = $root.querySelector(
 | 
						|
      "#download-folder .download-folder-button",
 | 
						|
    )!;
 | 
						|
    downloadFolder.addEventListener("click", async () => {
 | 
						|
      await downloadFolderDialog();
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function updatePromptDownloadOption(): void {
 | 
						|
    generateSettingOption({
 | 
						|
      $element: $root.querySelector("#prompt-download .setting-control")!,
 | 
						|
      value: ConfigUtil.getConfigItem("promptDownload", false),
 | 
						|
      clickHandler() {
 | 
						|
        const newValue = !ConfigUtil.getConfigItem("promptDownload", false);
 | 
						|
        ConfigUtil.setConfigItem("promptDownload", newValue);
 | 
						|
        updatePromptDownloadOption();
 | 
						|
      },
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  async function factoryResetSettings(): Promise<void> {
 | 
						|
    const clearAppDataMessage = t.__(
 | 
						|
      "When the application restarts, it will be as if you have just downloaded the Zulip app.",
 | 
						|
    );
 | 
						|
    const getAppPath = path.join(app.getPath("appData"), app.name);
 | 
						|
 | 
						|
    const {response} = await dialog.showMessageBox({
 | 
						|
      type: "warning",
 | 
						|
      buttons: [t.__("Yes"), t.__("No")],
 | 
						|
      defaultId: 0,
 | 
						|
      message: t.__("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 = $root.querySelector(
 | 
						|
      "#factory-reset-option .factory-reset-button",
 | 
						|
    )!;
 | 
						|
    factoryResetButton.addEventListener("click", async () => {
 | 
						|
      await factoryResetSettings();
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  function initSpellChecker(): void {
 | 
						|
    // The Electron API is a no-op on macOS and macOS default spellchecker is used.
 | 
						|
    if (process.platform === "darwin") {
 | 
						|
      const note: HTMLElement = $root.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 = $root.querySelector("#note")!;
 | 
						|
      note.append(
 | 
						|
        t.__("You can select a maximum of 3 languages for spellchecking."),
 | 
						|
      );
 | 
						|
      const spellDiv: HTMLElement = $root.querySelector("#spellcheck-langs")!;
 | 
						|
      spellDiv.innerHTML += html`
 | 
						|
        <div class="setting-description">${t.__("Spellchecker Languages")}</div>
 | 
						|
        <div id="spellcheck-langs-value">
 | 
						|
          <input name="spellcheck" placeholder="${t.__("Enter Languages")}" />
 | 
						|
        </div>
 | 
						|
      `.html;
 | 
						|
 | 
						|
      const availableLanguages = session.fromPartition(
 | 
						|
        "persist:webviewsession",
 | 
						|
      ).availableSpellCheckerLanguages;
 | 
						|
      let languagePairs = new Map<string, string>();
 | 
						|
      for (const l of availableLanguages) {
 | 
						|
        const locale = new Intl.Locale(l.replaceAll("_", "-"));
 | 
						|
        let displayName = new Intl.DisplayNames([locale], {
 | 
						|
          type: "language",
 | 
						|
        }).of(locale.language);
 | 
						|
        if (displayName === undefined) continue;
 | 
						|
        displayName = displayName.replace(/^./u, (firstChar) =>
 | 
						|
          firstChar.toLocaleUpperCase(locale),
 | 
						|
        );
 | 
						|
        if (locale.script !== undefined)
 | 
						|
          displayName += ` (${new Intl.DisplayNames([locale], {type: "script"}).of(locale.script)})`;
 | 
						|
        if (locale.region !== undefined)
 | 
						|
          displayName += ` (${new Intl.DisplayNames([locale], {type: "region"}).of(locale.region)})`;
 | 
						|
        languagePairs.set(displayName, l);
 | 
						|
      }
 | 
						|
 | 
						|
      languagePairs = new Map(
 | 
						|
        [...languagePairs].sort((a, b) => a[0].localeCompare(b[1])),
 | 
						|
      );
 | 
						|
 | 
						|
      const tagField: HTMLInputElement = $root.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,
 | 
						|
          position: "manual",
 | 
						|
          classname: "settings-tagify-dropdown",
 | 
						|
        },
 | 
						|
      });
 | 
						|
      tagify.DOM.input.addEventListener("focus", () => {
 | 
						|
        tagify.dropdown.show();
 | 
						|
        $root
 | 
						|
          .querySelector("#spellcheck-langs-value")!
 | 
						|
          .append(tagify.DOM.dropdown);
 | 
						|
      });
 | 
						|
      tagify.DOM.input.addEventListener("blur", () => {
 | 
						|
        tagify.dropdown.hide(true);
 | 
						|
        tagify.DOM.dropdown.remove();
 | 
						|
      });
 | 
						|
 | 
						|
      const configuredLanguages: string[] = (
 | 
						|
        ConfigUtil.getConfigItem("spellcheckerLanguages", null) ?? []
 | 
						|
      ).map(
 | 
						|
        (code: string) =>
 | 
						|
          [...languagePairs].find((pair) => pair[1] === code)![0],
 | 
						|
      );
 | 
						|
      tagify.addTags(configuredLanguages);
 | 
						|
 | 
						|
      tagField.addEventListener("change", () => {
 | 
						|
        if (tagField.value.length === 0) {
 | 
						|
          ConfigUtil.setConfigItem("spellcheckerLanguages", []);
 | 
						|
          ipcRenderer.send("configure-spell-checker");
 | 
						|
        } else {
 | 
						|
          const data: unknown = JSON.parse(tagField.value);
 | 
						|
          const spellLangs: string[] = z
 | 
						|
            .array(z.object({value: z.string()}))
 | 
						|
            .parse(data)
 | 
						|
            .map((elt) => languagePairs.get(elt.value)!);
 | 
						|
          ConfigUtil.setConfigItem("spellcheckerLanguages", spellLangs);
 | 
						|
          ipcRenderer.send("configure-spell-checker");
 | 
						|
        }
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    // Do not display the spellchecker input and note if it is disabled
 | 
						|
    if (!ConfigUtil.getConfigItem("enableSpellchecker", true)) {
 | 
						|
      const spellcheckerLanguageInput: HTMLElement =
 | 
						|
        $root.querySelector("#spellcheck-langs")!;
 | 
						|
      const spellcheckerNote: HTMLElement = $root.querySelector("#note")!;
 | 
						|
      spellcheckerLanguageInput.style.display = "none";
 | 
						|
      spellcheckerNote.style.display = "none";
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 |