diff --git a/assets/css/markdown.css b/assets/css/markdown.css index c362ce288..4a1847baa 100644 --- a/assets/css/markdown.css +++ b/assets/css/markdown.css @@ -122,10 +122,8 @@ } .markdown pre > code { - @apply p-4 rounded-lg text-sm align-middle font-mono flex-1 overflow-auto; /* Match the editor colors */ - background-color: #282c34; - color: #c4cad6; + @apply p-4 rounded-lg align-middle flex-1 overflow-auto bg-editor text-editor font-editor; } .markdown kbd { diff --git a/assets/css/utilities.css b/assets/css/utilities.css index 3595d6d02..f49c4f3ff 100644 --- a/assets/css/utilities.css +++ b/assets/css/utilities.css @@ -5,8 +5,12 @@ background-color: #282c34; } + body[data-editor-theme="highContrast"] .bg-editor { + background-color: #060708; + } + .text-editor { - color: #abb2bf; + color: #c4cad6; } .font-editor { diff --git a/assets/js/app.js b/assets/js/app.js index 7d703b564..96c19c17e 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -29,6 +29,7 @@ import KeyboardControl from "./keyboard_control"; import morphdomCallbacks from "./morphdom_callbacks"; import JSOutput from "./js_output"; import { loadUserData } from "./lib/user"; +import { settingsStore } from "./lib/settings"; const hooks = { Headline, @@ -109,3 +110,9 @@ window.addEventListener("contextmenu", (event) => { target.dispatchEvent(new Event("click", { bubbles: true })); } }); + +// Global configuration + +settingsStore.getAndSubscribe((settings) => { + document.body.setAttribute("data-editor-theme", settings.editor_theme); +}); diff --git a/assets/js/cell/index.js b/assets/js/cell/index.js index fcc8e663c..cf448f2fd 100644 --- a/assets/js/cell/index.js +++ b/assets/js/cell/index.js @@ -5,7 +5,6 @@ import { globalPubSub } from "../lib/pub_sub"; import { md5Base64, smoothlyScrollToElement } from "../lib/utils"; import scrollIntoView from "scroll-into-view-if-needed"; import { loadLocalSettings } from "../lib/settings"; -import { THEME_BACKGROUND_COLOR } from "./live_editor/theme"; /** * A hook managing a single cell. @@ -43,10 +42,6 @@ const Cell = { // Create an empty container for the editor to be mounted in. const editorElement = document.createElement("div"); editorContainer.appendChild(editorElement); - // Adjust the background color based on local settings - const settings = loadLocalSettings(); - editorContainer.style.backgroundColor = - THEME_BACKGROUND_COLOR[settings.editor_theme]; // Setup the editor instance. this.state.liveEditor = new LiveEditor( this, diff --git a/assets/js/cell/live_editor.js b/assets/js/cell/live_editor.js index 31a38afe2..4b4fb702a 100644 --- a/assets/js/cell/live_editor.js +++ b/assets/js/cell/live_editor.js @@ -4,7 +4,7 @@ import MonacoEditorAdapter from "./live_editor/monaco_editor_adapter"; import HookServerAdapter from "./live_editor/hook_server_adapter"; import RemoteUser from "./live_editor/remote_user"; import { replacedSuffixLength } from "../lib/text_utils"; -import { loadLocalSettings } from "../lib/settings"; +import { settingsStore } from "../lib/settings"; /** * Mounts cell source editor with real-time collaboration mechanism. @@ -141,7 +141,7 @@ class LiveEditor { } __mountEditor() { - const settings = loadLocalSettings(); + const settings = settingsStore.get(); this.editor = monaco.editor.create(this.container, { language: this.type, @@ -219,7 +219,7 @@ class LiveEditor { * Defines cell-specific providers for various editor features. */ __setupIntellisense() { - const settings = loadLocalSettings(); + const settings = settingsStore.get(); this.handlerByRef = {}; diff --git a/assets/js/cell/live_editor/theme.js b/assets/js/cell/live_editor/theme.js index 4d772fd0a..6a10b39fe 100644 --- a/assets/js/cell/live_editor/theme.js +++ b/assets/js/cell/live_editor/theme.js @@ -13,7 +13,7 @@ const colors = { peach: "#d19a66", }; -const THEME_BACKGROUND_COLOR = { default: "#282c34", highContrast: "#060708" }; +const background = { default: "#282c34", highContrast: "#060708" }; const theme = { base: "vs-dark", @@ -57,7 +57,7 @@ const theme = { ], colors: { - "editor.background": THEME_BACKGROUND_COLOR.default, + "editor.background": background.default, "editor.foreground": colors.default, "editorLineNumber.foreground": "#636d83", "editorCursor.foreground": "#636d83", @@ -77,8 +77,8 @@ const highContrast = { ...theme, colors: { ...theme.colors, - "editor.background": THEME_BACKGROUND_COLOR.highContrast, + "editor.background": background.highContrast, }, }; -export { theme, highContrast, THEME_BACKGROUND_COLOR }; +export { theme, highContrast }; diff --git a/assets/js/editor_settings/index.js b/assets/js/editor_settings/index.js index beec4e298..404ab7add 100644 --- a/assets/js/editor_settings/index.js +++ b/assets/js/editor_settings/index.js @@ -1,9 +1,4 @@ -import { - loadLocalSettings, - storeLocalSettings, - EDITOR_FONT_SIZE, - EDITOR_THEME, -} from "../lib/settings"; +import { settingsStore, EDITOR_FONT_SIZE, EDITOR_THEME } from "../lib/settings"; /** * A hook for the editor settings. @@ -14,7 +9,7 @@ import { */ const EditorSettings = { mounted() { - const settings = loadLocalSettings(); + const settings = settingsStore.get(); const editorAutoCompletionCheckbox = this.el.querySelector( `[name="editor_auto_completion"][value="true"]` @@ -37,15 +32,15 @@ const EditorSettings = { settings.editor_theme === EDITOR_THEME.highContrast ? true : false; editorAutoCompletionCheckbox.addEventListener("change", (event) => { - storeLocalSettings({ editor_auto_completion: event.target.checked }); + settingsStore.update({ editor_auto_completion: event.target.checked }); }); editorAutoSignatureCheckbox.addEventListener("change", (event) => { - storeLocalSettings({ editor_auto_signature: event.target.checked }); + settingsStore.update({ editor_auto_signature: event.target.checked }); }); editorFontSizeCheckbox.addEventListener("change", (event) => { - storeLocalSettings({ + settingsStore.update({ editor_font_size: event.target.checked ? EDITOR_FONT_SIZE.large : EDITOR_FONT_SIZE.normal, @@ -53,7 +48,7 @@ const EditorSettings = { }); editorHighContrastCheckbox.addEventListener("change", (event) => { - storeLocalSettings({ + settingsStore.update({ editor_theme: event.target.checked ? EDITOR_THEME.highContrast : EDITOR_THEME.default, diff --git a/assets/js/lib/settings.js b/assets/js/lib/settings.js index e0b2658a1..e6af49461 100644 --- a/assets/js/lib/settings.js +++ b/assets/js/lib/settings.js @@ -18,32 +18,69 @@ const DEFAULT_SETTINGS = { }; /** - * Stores the given settings in local storage. - * - * The given attributes are merged into the current settings. + * Stores local configuration and persists it across browser sessions. */ -export function storeLocalSettings(settings) { - const prevSettings = loadLocalSettings(); - const newSettings = { ...prevSettings, ...settings }; +class SettingsStore { + constructor() { + this._subscribers = []; + this._settings = DEFAULT_SETTINGS; - try { - const json = JSON.stringify(newSettings); - localStorage.setItem(SETTINGS_KEY, json); - } catch (error) { - console.error(`Failed to store local settings, reason: ${error.message}`); + this._loadSettings(); + } + + /** + * Returns the current settings. + */ + get() { + return this._settings; + } + + /** + * Stores new settings. + * + * The given attributes are merged into the current settings. + */ + update(newSettings) { + const prevSettings = this._settings; + this._settings = { ...this._settings, ...newSettings }; + this._subscribers.forEach((callback) => + callback(this._settings, prevSettings) + ); + this._storeSettings(); + } + + /** + * Registers to settings changes. + * + * The given function is called immediately with the current + * settings and then on every change. + */ + getAndSubscribe(callback) { + this._subscribers.push(callback); + callback(this._settings); + } + + _loadSettings() { + try { + const json = localStorage.getItem(SETTINGS_KEY); + + if (json) { + const settings = JSON.parse(json); + this._settings = { ...this._settings, ...settings }; + } + } catch (error) { + console.error(`Failed to load local settings, reason: ${error.message}`); + } + } + + _storeSettings() { + try { + const json = JSON.stringify(this._settings); + localStorage.setItem(SETTINGS_KEY, json); + } catch (error) { + console.error(`Failed to store local settings, reason: ${error.message}`); + } } } -/** - * Loads settings from local storage. - */ -export function loadLocalSettings() { - try { - const json = localStorage.getItem(SETTINGS_KEY); - const settings = json ? JSON.parse(json) : {}; - return { ...DEFAULT_SETTINGS, ...settings }; - } catch (error) { - console.error(`Failed to load local settings, reason: ${error.message}`); - return DEFAULT_SETTINGS; - } -} +export const settingsStore = new SettingsStore();