diff --git a/manifest.json b/manifest.json index 67e4494..401ccb7 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-style-settings", "name": "Style Settings", - "version": "0.2.7", + "version": "0.3.0", "minAppVersion": "0.11.5", "description": "Offers controls for adjusting theme, plugin, and snippet CSS variables.", "author": "mgmeyers", diff --git a/src/SettingsManager.ts b/src/SettingsManager.ts index 303db43..8f538a1 100644 --- a/src/SettingsManager.ts +++ b/src/SettingsManager.ts @@ -12,6 +12,13 @@ import { AltFormatList, } from "./settingHandlers"; import chroma from "chroma-js"; +import { + App, + ButtonComponent, + Modal, + Setting, + TextAreaComponent, +} from "obsidian"; type VariableKV = Array<{ key: string; value: string }>; @@ -252,35 +259,35 @@ export class CSSSettingsManager { } initClasses() { - Object.keys(this.config).forEach(section => { + Object.keys(this.config).forEach((section) => { const config = this.config[section]; - Object.keys(config).forEach(settingId => { + Object.keys(config).forEach((settingId) => { const setting = config[settingId]; - if (setting.type === 'class-toggle') { + if (setting.type === "class-toggle") { if (this.getSetting(section, settingId)) { document.body.classList.add(setting.id); } } - }) - }) + }); + }); } removeClasses() { - Object.keys(this.config).forEach(section => { + Object.keys(this.config).forEach((section) => { const config = this.config[section]; - Object.keys(config).forEach(settingId => { + Object.keys(config).forEach((settingId) => { const setting = config[settingId]; - if (setting.type === 'class-toggle') { + if (setting.type === "class-toggle") { if (this.getSetting(section, settingId)) { document.body.classList.remove(setting.id); } } - }) - }) + }); + }); } setCSSVariables() { @@ -344,15 +351,32 @@ export class CSSSettingsManager { return this.settings[`${sectionId}@@${settingId}`]; } + getSettings(sectionId: string, ids: string[]) { + return ids.reduce>((settings, id) => { + const fullId = `${sectionId}@@${id}`; + + if (this.settings[fullId]) { + settings[fullId] = this.settings[fullId]; + } + + return settings; + }, {}); + } + setSetting(sectionId: string, settingId: string, value: SettingValue) { this.settings[`${sectionId}@@${settingId}`] = value; this.save(); } - clearSetting( - sectionId: string, - settingId: string - ) { + setSettings(settings: Record) { + Object.keys(settings).forEach((id) => { + this.settings[id] = settings[id]; + }); + + return this.save(); + } + + clearSetting(sectionId: string, settingId: string) { delete this.settings[`${sectionId}@@${settingId}`]; this.save(); } @@ -361,9 +385,199 @@ export class CSSSettingsManager { Object.keys(this.settings).forEach((key) => { const [section] = key.split("@@"); if (section === sectionId) { - delete this.settings[key] + delete this.settings[key]; } }); this.save(); } + + export(section: string, config: Record) { + new ExportModal(this.plugin.app, this.plugin, section, config).open(); + } + + import() { + new ImportModal(this.plugin.app, this.plugin).open(); + } +} + +export class ExportModal extends Modal { + plugin: CSSSettingsPlugin; + section: string; + config: Record; + + constructor( + app: App, + plugin: CSSSettingsPlugin, + section: string, + config: Record + ) { + super(app); + this.plugin = plugin; + this.config = config; + this.section = section; + } + + onOpen() { + let { contentEl, modalEl } = this; + + modalEl.addClass("modal-style-settings"); + + new Setting(contentEl) + .setName(`Export settings for: ${this.section}`) + .then((setting) => { + const output = JSON.stringify(this.config, null, 2); + + // Build a copy to clipboard link + setting.controlEl.createEl( + "a", + { + cls: "style-settings-copy", + text: "Copy to clipboard", + href: "#", + }, + (copyButton) => { + new TextAreaComponent(contentEl) + .setValue(output) + .then((textarea) => { + copyButton.addEventListener("click", (e) => { + e.preventDefault(); + + // Select the textarea contents and copy them to the clipboard + textarea.inputEl.select(); + textarea.inputEl.setSelectionRange(0, 99999); + document.execCommand("copy"); + + copyButton.addClass("success"); + + setTimeout(() => { + // If the button is still in the dom, remove the success class + if (copyButton.parentNode) { + copyButton.removeClass("success"); + } + }, 2000); + }); + }); + } + ); + + // Build a download link + setting.controlEl.createEl("a", { + cls: "style-settings-download", + text: "Download", + attr: { + download: "style-settings.json", + href: `data:application/json;charset=utf-8,${encodeURIComponent( + output + )}`, + }, + }); + }); + } + + onClose() { + let { contentEl } = this; + contentEl.empty(); + } +} + +export class ImportModal extends Modal { + plugin: CSSSettingsPlugin; + + constructor(app: App, plugin: CSSSettingsPlugin) { + super(app); + this.plugin = plugin; + } + + onOpen() { + let { contentEl, modalEl } = this; + + modalEl.addClass("modal-style-settings"); + + new Setting(contentEl) + .setName("Import style setting") + .setDesc("Import an entire or partial configuration. Warning: this may override existing settings"); + + new Setting(contentEl).then((setting) => { + // Build an error message container + const errorSpan = createSpan({ + cls: "style-settings-import-error", + text: "Error importing config", + }); + + setting.nameEl.appendChild(errorSpan); + + // Attempt to parse the imported data and close if successful + const importAndClose = async (str: string) => { + if (str) { + try { + const importedSettings = JSON.parse(str) as Record< + string, + SettingValue + >; + + await this.plugin.settingsManager.setSettings(importedSettings); + + this.plugin.settingsTab.display(); + this.close(); + } catch (e) { + errorSpan.addClass("active"); + errorSpan.setText(`Error importing style settings: ${e}`); + } + } else { + errorSpan.addClass("active"); + errorSpan.setText(`Error importing style settings: config is empty`); + } + }; + + // Build a file input + setting.controlEl.createEl( + "input", + { + cls: "style-settings-import-input", + attr: { + id: "style-settings-import-input", + name: "style-settings-import-input", + type: "file", + accept: ".json", + }, + }, + (importInput) => { + // Set up a FileReader so we can parse the file contents + importInput.addEventListener("change", (e) => { + const reader = new FileReader(); + + reader.onload = async (e: ProgressEvent) => { + await importAndClose(e.target.result.toString().trim()); + }; + + reader.readAsText((e.target as HTMLInputElement).files[0]); + }); + } + ); + + // Build a label we will style as a link + setting.controlEl.createEl("label", { + cls: "style-settings-import-label", + text: "Import from file", + attr: { + for: "style-settings-import-input", + }, + }); + + new TextAreaComponent(contentEl) + .setPlaceholder("Paste config here...") + .then((ta) => { + new ButtonComponent(contentEl) + .setButtonText("Save") + .onClick(async () => { + await importAndClose(ta.getValue().trim()); + }); + }); + }); + } + + onClose() { + let { contentEl } = this; + contentEl.empty(); + } } diff --git a/src/main.ts b/src/main.ts index edd033a..4585932 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,4 +1,4 @@ -import { App, Plugin, PluginSettingTab } from "obsidian"; +import { App, Plugin, PluginSettingTab, Setting } from "obsidian"; import { CSSSettingsManager } from "./SettingsManager"; import { CleanupFunction, @@ -159,8 +159,7 @@ class CSSSettingsTab extends PluginSettingTab { ); frag.createEl("a", { text: "Click here for details and examples.", - href: - "https://github.com/mgmeyers/obsidian-style-settings#obsidian-style-settings-plugin", + href: "https://github.com/mgmeyers/obsidian-style-settings#obsidian-style-settings-plugin", }); }) ); @@ -179,6 +178,41 @@ class CSSSettingsTab extends PluginSettingTab { return this.displayEmpty(); } + new Setting(containerEl) + .then((setting) => { + // Build and import link to open the import modal + setting.controlEl.createEl( + "a", + { + cls: "icon-swapper-import", + text: "Import", + href: "#", + }, + (el) => { + el.addEventListener("click", (e) => { + e.preventDefault(); + this.plugin.settingsManager.import(); + }); + } + ); + + // Build and export link to open the export modal + setting.controlEl.createEl( + "a", + { + cls: "icon-swapper-export", + text: "Export", + href: "#", + }, + (el) => { + el.addEventListener("click", (e) => { + e.preventDefault(); + this.plugin.settingsManager.export("All settings", this.plugin.settingsManager.settings); + }); + } + ); + }) + const cleanupFns: CleanupFunction[] = []; settings.forEach((s) => { @@ -200,6 +234,7 @@ class CSSSettingsTab extends PluginSettingTab { const cleanup = createSettings({ containerEl, sectionId: s.id, + sectionName: s.name, settings: options, settingsManager: plugin.settingsManager, }); diff --git a/src/settingHandlers.ts b/src/settingHandlers.ts index 2a1b4bb..5e9ed68 100644 --- a/src/settingHandlers.ts +++ b/src/settingHandlers.ts @@ -60,6 +60,10 @@ export interface Heading extends Meta { export function createHeading(opts: { config: Heading; containerEl: HTMLElement; + children: string[]; + sectionId: string; + sectionName: string; + settingsManager: CSSSettingsManager; }) { new Setting(opts.containerEl) .setHeading() @@ -93,6 +97,21 @@ export function createHeading(opts: { .onClick(opts.config.resetFn); }); } + + setting.addExtraButton((b) => { + b.setIcon("install") + .setTooltip("Export settings") + .then((b) => { + b.extraSettingsEl.onClickEvent((e) => { + e.stopPropagation(); + const title = opts.sectionName === opts.config.title ? opts.config.title : `${opts.sectionName} > ${opts.config.title}`; + opts.settingsManager.export( + title, + opts.settingsManager.getSettings(opts.sectionId, opts.children) + ); + }); + }); + }); }); } @@ -643,14 +662,21 @@ export interface ParsedCSSSettings { export function createSettings(opts: { containerEl: HTMLElement; sectionId: string; + sectionName: string; settings: CSSSetting[]; settingsManager: CSSSettingsManager; }): CleanupFunction[] { - const { containerEl, sectionId, settings, settingsManager } = opts; + const { containerEl, sectionId, settings, settingsManager, sectionName } = + opts; const containerStack: HTMLElement[] = [containerEl]; + const idStack: string[] = [sectionId]; const cleanup: CleanupFunction[] = []; + const settingGroups: Record> = { + [sectionId]: [], + }; + let containerLevel = 0; function getTargetContainer(stack: HTMLElement[]) { @@ -658,11 +684,23 @@ export function createSettings(opts: { return stack[stack.length - 1]; } + function pushId(id: string) { + idStack.forEach((containerId) => { + if (settingGroups[containerId]) { + settingGroups[containerId].push(id); + } else { + settingGroups[containerId] = [id]; + } + }); + } + settings.forEach((setting) => { switch (setting.type) { case "heading": { const config = setting as Heading; + settingGroups[config.id] = []; + let targetContainer = getTargetContainer(containerStack); if (config.level > containerLevel) { @@ -670,15 +708,24 @@ export function createSettings(opts: { createHeading({ config, containerEl: targetContainer, + children: settingGroups[config.id], + settingsManager, + sectionName, + sectionId, }); } else if (config.level === containerLevel) { // Same level containerStack.pop(); + idStack.pop(); targetContainer = getTargetContainer(containerStack); createHeading({ config, containerEl: targetContainer, + children: settingGroups[config.id], + settingsManager, + sectionName, + sectionId, }); } else { // Step up to the appropriate level @@ -688,6 +735,7 @@ export function createSettings(opts: { config.level ) { containerStack.pop(); + idStack.pop(); } targetContainer = getTargetContainer(containerStack); @@ -695,6 +743,10 @@ export function createSettings(opts: { createHeading({ config, containerEl: targetContainer, + children: settingGroups[config.id], + settingsManager, + sectionName, + sectionId, }); } @@ -703,6 +755,7 @@ export function createSettings(opts: { (container) => { container.dataset.level = config.level.toString(); containerStack.push(container); + idStack.push(config.id); } ); containerLevel = config.level; @@ -710,6 +763,7 @@ export function createSettings(opts: { break; } case "class-toggle": { + pushId(setting.id); createClassToggle({ sectionId, config: setting as ClassToggle, @@ -719,6 +773,7 @@ export function createSettings(opts: { break; } case "variable-text": { + pushId(setting.id); createVariableText({ sectionId, config: setting as VariableText, @@ -728,6 +783,7 @@ export function createSettings(opts: { break; } case "variable-number": { + pushId(setting.id); createVariableNumber({ sectionId, config: setting as VariableNumber, @@ -737,6 +793,7 @@ export function createSettings(opts: { break; } case "variable-number-slider": { + pushId(setting.id); createVariableNumberSlider({ sectionId, config: setting as VariableNumberSlider, @@ -746,6 +803,7 @@ export function createSettings(opts: { break; } case "variable-select": { + pushId(setting.id); createVariableSelect({ sectionId, config: setting as VariableSelect, @@ -755,6 +813,7 @@ export function createSettings(opts: { break; } case "variable-color": { + pushId(setting.id); cleanup.push( createVariableColor({ sectionId, @@ -766,6 +825,8 @@ export function createSettings(opts: { break; } case "variable-themed-color": { + // TODO: multiple ids? + pushId(setting.id); cleanup.push( createVariableThemedColor({ sectionId, diff --git a/src/settings.css b/src/settings.css index 1181e8e..eb23003 100644 --- a/src/settings.css +++ b/src/settings.css @@ -1,76 +1,166 @@ .style-settings-heading { - cursor: pointer; - margin-bottom: 18px; - padding-bottom: 6px; - border-bottom: 1px solid var(--background-modifier-border); + cursor: pointer; + margin-bottom: 18px; + padding-bottom: 6px; + border-bottom: 1px solid var(--background-modifier-border); } .style-settings-heading[data-level="0"] { - margin-bottom: 26px; + margin-bottom: 26px; } .style-settings-container { - padding-bottom: 16px; + padding-bottom: 16px; } .style-settings-heading[data-level="0"] + .style-settings-container { - padding-left: 34px; + padding-left: 34px; } .style-settings-heading.is-collapsed { - margin-bottom: 0; + margin-bottom: 0; } .style-settings-heading.is-collapsed + .style-settings-container { - display: none; + display: none; } .style-settings-collapse-indicator { - color: var(--text-faint); - display: inline-block; - margin-right: 8px; - position: relative; - top: -1px; + color: var(--text-faint); + display: inline-block; + margin-right: 8px; + position: relative; + top: -1px; } -.style-settings-heading[data-level="0"] + .style-settings-container .style-settings-collapse-indicator { - margin-left: -17px; +.style-settings-heading[data-level="0"] + + .style-settings-container + .style-settings-collapse-indicator { + margin-left: -17px; } .style-settings-collapse-indicator > svg { - height: 9px; - width: 9px; + height: 9px; + width: 9px; } .style-settings-heading.is-collapsed .style-settings-collapse-indicator > svg { - transform: rotate(-90deg); + transform: rotate(-90deg); } .style-settings-error { - font-size: 14px; - border-radius: 6px; - background: rgba(var(--background-modifier-error-rgb), 0.2); - color: var(--text-error); - padding: 10px; - margin-bottom: 1rem; + font-size: 14px; + border-radius: 6px; + background: rgba(var(--background-modifier-error-rgb), 0.2); + color: var(--text-error); + padding: 10px; + margin-bottom: 1rem; } .style-settings-error-name { - font-weight: bold; - margin-bottom: 5px; + font-weight: bold; + margin-bottom: 5px; } .style-settings-error-desc { - white-space: pre; + white-space: pre; } .style-settings-empty { - font-size: 14px; - background: var(--background-secondary); - padding: 10px; + font-size: 14px; + background: var(--background-secondary); + padding: 10px; } .style-settings-empty-name { - font-weight: bold; - margin-bottom: 5px; -} \ No newline at end of file + font-weight: bold; + margin-bottom: 5px; +} + +.style-settings-import-input { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; +} + +.style-settings-import-label { + cursor: pointer; + color: var(--text-accent); + text-decoration: underline; +} + +.style-settings-import-label:hover { + color: var(--text-accent-hover); +} + +.style-settings-export, +.style-settings-import { + display: inline-block; + margin-right: 10px; +} + +.style-settings-copy, +.style-settings-download { + position: relative; + display: inline-block; + margin-left: 10px; +} + +.style-settings-copy:before { + color: var(--interactive-success); + content: "✓"; + position: absolute; + left: -18px; + font-weight: bold; + opacity: 0; + transition: 150ms opacity ease-in-out; +} + +.style-settings-copy.success:before { + opacity: 1; +} + +.modal-style-settings { + height: 70vh; + display: flex; + flex-direction: column; +} + +.modal-style-settings .modal-content { + flex-grow: 1; + margin: 0; + display: flex; + flex-direction: column; +} + +.modal-style-settings textarea { + display: block; + width: 100%; + height: 100%; + font-family: var(--font-monospace) !important; + font-size: 12px; + white-space: pre; + overflow-wrap: normal; + overflow-x: scroll; + margin-bottom: 5px; +} + +.modal-style-settings .setting-item { + align-items: flex-start; +} + +.modal-style-settings button { + margin: 10px 0 0; +} + +.style-settings-import-error { + display: none; + color: var(--text-error); +} + +.style-settings-import-error.active { + display: block; +} diff --git a/styles.css b/styles.css index 20d9acb..434279c 100644 --- a/styles.css +++ b/styles.css @@ -32,78 +32,169 @@ display: block; } .style-settings-heading { - cursor: pointer; - margin-bottom: 18px; - padding-bottom: 6px; - border-bottom: 1px solid var(--background-modifier-border); + cursor: pointer; + margin-bottom: 18px; + padding-bottom: 6px; + border-bottom: 1px solid var(--background-modifier-border); } .style-settings-heading[data-level="0"] { - margin-bottom: 26px; + margin-bottom: 26px; } .style-settings-container { - padding-bottom: 16px; + padding-bottom: 16px; } .style-settings-heading[data-level="0"] + .style-settings-container { - padding-left: 34px; + padding-left: 34px; } .style-settings-heading.is-collapsed { - margin-bottom: 0; + margin-bottom: 0; } .style-settings-heading.is-collapsed + .style-settings-container { - display: none; + display: none; } .style-settings-collapse-indicator { - color: var(--text-faint); - display: inline-block; - margin-right: 8px; - position: relative; - top: -1px; + color: var(--text-faint); + display: inline-block; + margin-right: 8px; + position: relative; + top: -1px; } -.style-settings-heading[data-level="0"] + .style-settings-container .style-settings-collapse-indicator { - margin-left: -17px; +.style-settings-heading[data-level="0"] + + .style-settings-container + .style-settings-collapse-indicator { + margin-left: -17px; } .style-settings-collapse-indicator > svg { - height: 9px; - width: 9px; + height: 9px; + width: 9px; } .style-settings-heading.is-collapsed .style-settings-collapse-indicator > svg { - transform: rotate(-90deg); + transform: rotate(-90deg); } .style-settings-error { - font-size: 14px; - border-radius: 6px; - background: rgba(var(--background-modifier-error-rgb), 0.2); - color: var(--text-error); - padding: 10px; - margin-bottom: 1rem; + font-size: 14px; + border-radius: 6px; + background: rgba(var(--background-modifier-error-rgb), 0.2); + color: var(--text-error); + padding: 10px; + margin-bottom: 1rem; } .style-settings-error-name { - font-weight: bold; - margin-bottom: 5px; + font-weight: bold; + margin-bottom: 5px; } .style-settings-error-desc { - white-space: pre; + white-space: pre; } .style-settings-empty { - font-size: 14px; - background: var(--background-secondary); - padding: 10px; + font-size: 14px; + background: var(--background-secondary); + padding: 10px; } .style-settings-empty-name { - font-weight: bold; - margin-bottom: 5px; -}/*! Pickr 1.8.0 MIT | https://github.com/Simonwep/pickr */.pickr{position:relative;overflow:visible;transform:translateY(0)}.pickr *{box-sizing:border-box;outline:none;border:none;-webkit-appearance:none}.pickr .pcr-button{position:relative;height:2em;width:2em;padding:.5em;cursor:pointer;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;border-radius:.15em;background:url('data:image/svg+xml;utf8, ') no-repeat 50%;background-size:0;transition:all .3s}.pickr .pcr-button:before{background:url('data:image/svg+xml;utf8, ');background-size:.5em;z-index:-1;z-index:auto}.pickr .pcr-button:after,.pickr .pcr-button:before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;border-radius:.15em}.pickr .pcr-button:after{transition:background .3s;background:currentColor}.pickr .pcr-button.clear{background-size:70%}.pickr .pcr-button.clear:before{opacity:0}.pickr .pcr-button.clear:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px currentColor}.pickr .pcr-button.disabled{cursor:not-allowed}.pcr-app *,.pickr *{box-sizing:border-box;outline:none;border:none;-webkit-appearance:none}.pcr-app button.pcr-active,.pcr-app button:focus,.pcr-app input.pcr-active,.pcr-app input:focus,.pickr button.pcr-active,.pickr button:focus,.pickr input.pcr-active,.pickr input:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px currentColor}.pcr-app .pcr-palette,.pcr-app .pcr-slider,.pickr .pcr-palette,.pickr .pcr-slider{transition:box-shadow .3s}.pcr-app .pcr-palette:focus,.pcr-app .pcr-slider:focus,.pickr .pcr-palette:focus,.pickr .pcr-slider:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px rgba(0,0,0,.25)}.pcr-app{position:fixed;display:flex;flex-direction:column;z-index:10000;border-radius:.1em;background:#fff;opacity:0;visibility:hidden;transition:opacity .3s,visibility 0s .3s;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;box-shadow:0 .15em 1.5em 0 rgba(0,0,0,.1),0 0 1em 0 rgba(0,0,0,.03);left:0;top:0}.pcr-app.visible{transition:opacity .3s;visibility:visible;opacity:1}.pcr-app .pcr-swatches{display:flex;flex-wrap:wrap;margin-top:.75em}.pcr-app .pcr-swatches.pcr-last{margin:0}@supports (display:grid){.pcr-app .pcr-swatches{display:grid;align-items:center;grid-template-columns:repeat(auto-fit,1.75em)}}.pcr-app .pcr-swatches>button{font-size:1em;position:relative;width:calc(1.75em - 5px);height:calc(1.75em - 5px);border-radius:.15em;cursor:pointer;margin:2.5px;flex-shrink:0;justify-self:center;transition:all .15s;overflow:hidden;background:transparent;z-index:1}.pcr-app .pcr-swatches>button:before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, ');background-size:6px;border-radius:.15em;z-index:-1}.pcr-app .pcr-swatches>button:after{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background:currentColor;border:1px solid rgba(0,0,0,.05);border-radius:.15em;box-sizing:border-box}.pcr-app .pcr-swatches>button:hover{filter:brightness(1.05)}.pcr-app .pcr-swatches>button:not(.pcr-active){box-shadow:none}.pcr-app .pcr-interaction{display:flex;flex-wrap:wrap;align-items:center;margin:0 -.2em}.pcr-app .pcr-interaction>*{margin:0 .2em}.pcr-app .pcr-interaction input{letter-spacing:.07em;font-size:.75em;text-align:center;cursor:pointer;color:#75797e;background:#f1f3f4;border-radius:.15em;transition:all .15s;padding:.45em .5em;margin-top:.75em}.pcr-app .pcr-interaction input:hover{filter:brightness(.975)}.pcr-app .pcr-interaction input:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px rgba(66,133,244,.75)}.pcr-app .pcr-interaction .pcr-result{color:#75797e;text-align:left;flex:1 1 8em;min-width:8em;transition:all .2s;border-radius:.15em;background:#f1f3f4;cursor:text}.pcr-app .pcr-interaction .pcr-result::-moz-selection{background:#4285f4;color:#fff}.pcr-app .pcr-interaction .pcr-result::selection{background:#4285f4;color:#fff}.pcr-app .pcr-interaction .pcr-type.active{color:#fff;background:#4285f4}.pcr-app .pcr-interaction .pcr-cancel,.pcr-app .pcr-interaction .pcr-clear,.pcr-app .pcr-interaction .pcr-save{width:auto;color:#fff}.pcr-app .pcr-interaction .pcr-cancel:hover,.pcr-app .pcr-interaction .pcr-clear:hover,.pcr-app .pcr-interaction .pcr-save:hover{filter:brightness(.925)}.pcr-app .pcr-interaction .pcr-save{background:#4285f4}.pcr-app .pcr-interaction .pcr-cancel,.pcr-app .pcr-interaction .pcr-clear{background:#f44250}.pcr-app .pcr-interaction .pcr-cancel:focus,.pcr-app .pcr-interaction .pcr-clear:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px rgba(244,66,80,.75)}.pcr-app .pcr-selection .pcr-picker{position:absolute;height:18px;width:18px;border:2px solid #fff;border-radius:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pcr-app .pcr-selection .pcr-color-chooser,.pcr-app .pcr-selection .pcr-color-opacity,.pcr-app .pcr-selection .pcr-color-palette{position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:flex;flex-direction:column;cursor:grab;cursor:-webkit-grab}.pcr-app .pcr-selection .pcr-color-chooser:active,.pcr-app .pcr-selection .pcr-color-opacity:active,.pcr-app .pcr-selection .pcr-color-palette:active{cursor:grabbing;cursor:-webkit-grabbing}.pcr-app[data-theme=nano]{width:14.25em;max-width:95vw}.pcr-app[data-theme=nano] .pcr-swatches{margin-top:.6em;padding:0 .6em}.pcr-app[data-theme=nano] .pcr-interaction{padding:0 .6em .6em}.pcr-app[data-theme=nano] .pcr-selection{display:grid;grid-gap:.6em;grid-template-columns:1fr 4fr;grid-template-rows:5fr auto auto;align-items:center;height:10.5em;width:100%;align-self:flex-start}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview{grid-area:2/1/4/1;height:100%;width:100%;display:flex;flex-direction:row;justify-content:center;margin-left:.6em}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview .pcr-last-color{display:none}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview .pcr-current-color{position:relative;background:currentColor;width:2em;height:2em;border-radius:50em;overflow:hidden}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview .pcr-current-color:before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, ');background-size:.5em;border-radius:.15em;z-index:-1}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-palette{grid-area:1/1/2/3;width:100%;height:100%;z-index:1}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-palette .pcr-palette{border-radius:.15em;width:100%;height:100%}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-palette .pcr-palette:before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, ');background-size:.5em;border-radius:.15em;z-index:-1}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser{grid-area:2/2/2/2}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity{grid-area:3/2/3/2}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser,.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity{height:.5em;margin:0 .6em}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser .pcr-picker,.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity .pcr-picker{top:50%;transform:translateY(-50%)}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser .pcr-slider,.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity .pcr-slider{flex-grow:1;border-radius:50em}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser .pcr-slider{background:linear-gradient(90deg,red,#ff0,#0f0,#0ff,#00f,#f0f,red)}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity .pcr-slider{background:linear-gradient(90deg,transparent,#000),url('data:image/svg+xml;utf8, ');background-size:100%,.25em} \ No newline at end of file + font-weight: bold; + margin-bottom: 5px; +} + +.style-settings-import-input { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; +} + +.style-settings-import-label { + cursor: pointer; + color: var(--text-accent); + text-decoration: underline; +} + +.style-settings-import-label:hover { + color: var(--text-accent-hover); +} + +.style-settings-export, +.style-settings-import { + display: inline-block; + margin-right: 10px; +} + +.style-settings-copy, +.style-settings-download { + position: relative; + display: inline-block; + margin-left: 10px; +} + +.style-settings-copy:before { + color: var(--interactive-success); + content: "✓"; + position: absolute; + left: -18px; + font-weight: bold; + opacity: 0; + transition: 150ms opacity ease-in-out; +} + +.style-settings-copy.success:before { + opacity: 1; +} + +.modal-style-settings { + height: 70vh; + display: flex; + flex-direction: column; +} + +.modal-style-settings .modal-content { + flex-grow: 1; + margin: 0; + display: flex; + flex-direction: column; +} + +.modal-style-settings textarea { + display: block; + width: 100%; + height: 100%; + font-family: var(--font-monospace) !important; + font-size: 12px; + white-space: pre; + overflow-wrap: normal; + overflow-x: scroll; + margin-bottom: 5px; +} + +.modal-style-settings .setting-item { + align-items: flex-start; +} + +.modal-style-settings button { + margin: 10px 0 0; +} + +.style-settings-import-error { + display: none; + color: var(--text-error); +} + +.style-settings-import-error.active { + display: block; +} +/*! Pickr 1.8.0 MIT | https://github.com/Simonwep/pickr */.pickr{position:relative;overflow:visible;transform:translateY(0)}.pickr *{box-sizing:border-box;outline:none;border:none;-webkit-appearance:none}.pickr .pcr-button{position:relative;height:2em;width:2em;padding:.5em;cursor:pointer;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;border-radius:.15em;background:url('data:image/svg+xml;utf8, ') no-repeat 50%;background-size:0;transition:all .3s}.pickr .pcr-button:before{background:url('data:image/svg+xml;utf8, ');background-size:.5em;z-index:-1;z-index:auto}.pickr .pcr-button:after,.pickr .pcr-button:before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;border-radius:.15em}.pickr .pcr-button:after{transition:background .3s;background:currentColor}.pickr .pcr-button.clear{background-size:70%}.pickr .pcr-button.clear:before{opacity:0}.pickr .pcr-button.clear:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px currentColor}.pickr .pcr-button.disabled{cursor:not-allowed}.pcr-app *,.pickr *{box-sizing:border-box;outline:none;border:none;-webkit-appearance:none}.pcr-app button.pcr-active,.pcr-app button:focus,.pcr-app input.pcr-active,.pcr-app input:focus,.pickr button.pcr-active,.pickr button:focus,.pickr input.pcr-active,.pickr input:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px currentColor}.pcr-app .pcr-palette,.pcr-app .pcr-slider,.pickr .pcr-palette,.pickr .pcr-slider{transition:box-shadow .3s}.pcr-app .pcr-palette:focus,.pcr-app .pcr-slider:focus,.pickr .pcr-palette:focus,.pickr .pcr-slider:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px rgba(0,0,0,.25)}.pcr-app{position:fixed;display:flex;flex-direction:column;z-index:10000;border-radius:.1em;background:#fff;opacity:0;visibility:hidden;transition:opacity .3s,visibility 0s .3s;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;box-shadow:0 .15em 1.5em 0 rgba(0,0,0,.1),0 0 1em 0 rgba(0,0,0,.03);left:0;top:0}.pcr-app.visible{transition:opacity .3s;visibility:visible;opacity:1}.pcr-app .pcr-swatches{display:flex;flex-wrap:wrap;margin-top:.75em}.pcr-app .pcr-swatches.pcr-last{margin:0}@supports (display:grid){.pcr-app .pcr-swatches{display:grid;align-items:center;grid-template-columns:repeat(auto-fit,1.75em)}}.pcr-app .pcr-swatches>button{font-size:1em;position:relative;width:calc(1.75em - 5px);height:calc(1.75em - 5px);border-radius:.15em;cursor:pointer;margin:2.5px;flex-shrink:0;justify-self:center;transition:all .15s;overflow:hidden;background:transparent;z-index:1}.pcr-app .pcr-swatches>button:before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, ');background-size:6px;border-radius:.15em;z-index:-1}.pcr-app .pcr-swatches>button:after{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background:currentColor;border:1px solid rgba(0,0,0,.05);border-radius:.15em;box-sizing:border-box}.pcr-app .pcr-swatches>button:hover{filter:brightness(1.05)}.pcr-app .pcr-swatches>button:not(.pcr-active){box-shadow:none}.pcr-app .pcr-interaction{display:flex;flex-wrap:wrap;align-items:center;margin:0 -.2em}.pcr-app .pcr-interaction>*{margin:0 .2em}.pcr-app .pcr-interaction input{letter-spacing:.07em;font-size:.75em;text-align:center;cursor:pointer;color:#75797e;background:#f1f3f4;border-radius:.15em;transition:all .15s;padding:.45em .5em;margin-top:.75em}.pcr-app .pcr-interaction input:hover{filter:brightness(.975)}.pcr-app .pcr-interaction input:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px rgba(66,133,244,.75)}.pcr-app .pcr-interaction .pcr-result{color:#75797e;text-align:left;flex:1 1 8em;min-width:8em;transition:all .2s;border-radius:.15em;background:#f1f3f4;cursor:text}.pcr-app .pcr-interaction .pcr-result::-moz-selection{background:#4285f4;color:#fff}.pcr-app .pcr-interaction .pcr-result::selection{background:#4285f4;color:#fff}.pcr-app .pcr-interaction .pcr-type.active{color:#fff;background:#4285f4}.pcr-app .pcr-interaction .pcr-cancel,.pcr-app .pcr-interaction .pcr-clear,.pcr-app .pcr-interaction .pcr-save{width:auto;color:#fff}.pcr-app .pcr-interaction .pcr-cancel:hover,.pcr-app .pcr-interaction .pcr-clear:hover,.pcr-app .pcr-interaction .pcr-save:hover{filter:brightness(.925)}.pcr-app .pcr-interaction .pcr-save{background:#4285f4}.pcr-app .pcr-interaction .pcr-cancel,.pcr-app .pcr-interaction .pcr-clear{background:#f44250}.pcr-app .pcr-interaction .pcr-cancel:focus,.pcr-app .pcr-interaction .pcr-clear:focus{box-shadow:0 0 0 1px hsla(0,0%,100%,.85),0 0 0 3px rgba(244,66,80,.75)}.pcr-app .pcr-selection .pcr-picker{position:absolute;height:18px;width:18px;border:2px solid #fff;border-radius:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pcr-app .pcr-selection .pcr-color-chooser,.pcr-app .pcr-selection .pcr-color-opacity,.pcr-app .pcr-selection .pcr-color-palette{position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:flex;flex-direction:column;cursor:grab;cursor:-webkit-grab}.pcr-app .pcr-selection .pcr-color-chooser:active,.pcr-app .pcr-selection .pcr-color-opacity:active,.pcr-app .pcr-selection .pcr-color-palette:active{cursor:grabbing;cursor:-webkit-grabbing}.pcr-app[data-theme=nano]{width:14.25em;max-width:95vw}.pcr-app[data-theme=nano] .pcr-swatches{margin-top:.6em;padding:0 .6em}.pcr-app[data-theme=nano] .pcr-interaction{padding:0 .6em .6em}.pcr-app[data-theme=nano] .pcr-selection{display:grid;grid-gap:.6em;grid-template-columns:1fr 4fr;grid-template-rows:5fr auto auto;align-items:center;height:10.5em;width:100%;align-self:flex-start}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview{grid-area:2/1/4/1;height:100%;width:100%;display:flex;flex-direction:row;justify-content:center;margin-left:.6em}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview .pcr-last-color{display:none}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview .pcr-current-color{position:relative;background:currentColor;width:2em;height:2em;border-radius:50em;overflow:hidden}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-preview .pcr-current-color:before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, ');background-size:.5em;border-radius:.15em;z-index:-1}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-palette{grid-area:1/1/2/3;width:100%;height:100%;z-index:1}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-palette .pcr-palette{border-radius:.15em;width:100%;height:100%}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-palette .pcr-palette:before{position:absolute;content:"";top:0;left:0;width:100%;height:100%;background:url('data:image/svg+xml;utf8, ');background-size:.5em;border-radius:.15em;z-index:-1}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser{grid-area:2/2/2/2}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity{grid-area:3/2/3/2}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser,.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity{height:.5em;margin:0 .6em}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser .pcr-picker,.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity .pcr-picker{top:50%;transform:translateY(-50%)}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser .pcr-slider,.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity .pcr-slider{flex-grow:1;border-radius:50em}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-chooser .pcr-slider{background:linear-gradient(90deg,red,#ff0,#0f0,#0ff,#00f,#f0f,red)}.pcr-app[data-theme=nano] .pcr-selection .pcr-color-opacity .pcr-slider{background:linear-gradient(90deg,transparent,#000),url('data:image/svg+xml;utf8, ');background-size:100%,.25em} \ No newline at end of file