|
@@ -1,5 +1,5 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <div class="edit frame flex flex-col abs-full">
|
|
|
|
|
|
|
+ <div class="edit frame flex flex-col abs-full" :class="{frozen, readOnly}">
|
|
|
<div class="edit-header flex mr-1c">
|
|
<div class="edit-header flex mr-1c">
|
|
|
<nav>
|
|
<nav>
|
|
|
<div
|
|
<div
|
|
@@ -10,16 +10,17 @@
|
|
|
/>
|
|
/>
|
|
|
</nav>
|
|
</nav>
|
|
|
<div class="edit-name text-center ellipsis flex-1">
|
|
<div class="edit-name text-center ellipsis flex-1">
|
|
|
- <span class="subtle" v-if="script?.config?.removed" v-text="i18n('headerRecycleBin') + ' / '"></span>
|
|
|
|
|
|
|
+ <span class="subtle" v-if="script.config.removed" v-text="i18n('headerRecycleBin') + ' / '"/>
|
|
|
{{scriptName}}
|
|
{{scriptName}}
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="edit-hint text-right ellipsis">
|
|
|
|
|
|
|
+ <p v-if="frozen" class="text-upper text-right text-red" v-text="i18n('readonly')"/>
|
|
|
|
|
+ <div v-else class="edit-hint text-right ellipsis">
|
|
|
<a href="https://violentmonkey.github.io/posts/how-to-edit-scripts-with-your-favorite-editor/"
|
|
<a href="https://violentmonkey.github.io/posts/how-to-edit-scripts-with-your-favorite-editor/"
|
|
|
target="_blank"
|
|
target="_blank"
|
|
|
rel="noopener noreferrer"
|
|
rel="noopener noreferrer"
|
|
|
v-text="i18n('editHowToHint')"/>
|
|
v-text="i18n('editHowToHint')"/>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="edit-buttons">
|
|
|
|
|
|
|
+ <div class="mr-1">
|
|
|
<button v-text="i18n('buttonSave')" @click="save" :disabled="!canSave"
|
|
<button v-text="i18n('buttonSave')" @click="save" :disabled="!canSave"
|
|
|
:class="{'has-error': errors}" :title="errors"/>
|
|
:class="{'has-error': errors}" :title="errors"/>
|
|
|
<button v-text="i18n('buttonSaveClose')" @click="saveClose" :disabled="!canSave"/>
|
|
<button v-text="i18n('buttonSaveClose')" @click="saveClose" :disabled="!canSave"/>
|
|
@@ -27,44 +28,46 @@
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div v-text="i18n('editReadonly')" class="mx-1 my-1 text-red" v-if="!canEdit"/>
|
|
|
|
|
|
|
+ <div class="frozen-note mr-2c flex flex-wrap" v-if="note && nav === 'code'">
|
|
|
|
|
+ <p v-text="i18n('readonlyNote')"/>
|
|
|
|
|
+ <keep-alive>
|
|
|
|
|
+ <VMSettingsUpdate class="flex ml-2c" :script="script"/>
|
|
|
|
|
+ </keep-alive>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
<vm-code
|
|
<vm-code
|
|
|
class="flex-auto"
|
|
class="flex-auto"
|
|
|
:value="code"
|
|
:value="code"
|
|
|
- :readOnly="readOnly || script.$canUpdate === 1"
|
|
|
|
|
- v-on="{ keypress: script.$canUpdate === 1 && (() => (this.canEdit = false)) }"
|
|
|
|
|
|
|
+ :readOnly="frozen"
|
|
|
|
|
+ :title="frozen ? i18n('readonly') : null"
|
|
|
ref="code"
|
|
ref="code"
|
|
|
v-show="nav === 'code'"
|
|
v-show="nav === 'code'"
|
|
|
:active="nav === 'code'"
|
|
:active="nav === 'code'"
|
|
|
:commands="commands"
|
|
:commands="commands"
|
|
|
@code-dirty="codeDirty = $event"
|
|
@code-dirty="codeDirty = $event"
|
|
|
/>
|
|
/>
|
|
|
|
|
+ <keep-alive>
|
|
|
<vm-settings
|
|
<vm-settings
|
|
|
class="edit-body"
|
|
class="edit-body"
|
|
|
- v-show="nav === 'settings'"
|
|
|
|
|
- :readOnly="readOnly"
|
|
|
|
|
- :active="nav === 'settings'"
|
|
|
|
|
- :settings="settings"
|
|
|
|
|
- :value="script"
|
|
|
|
|
|
|
+ v-if="nav === 'settings'"
|
|
|
|
|
+ v-bind="{readOnly, script}"
|
|
|
/>
|
|
/>
|
|
|
<vm-values
|
|
<vm-values
|
|
|
class="edit-body"
|
|
class="edit-body"
|
|
|
- v-show="nav === 'values'"
|
|
|
|
|
- :readOnly="readOnly"
|
|
|
|
|
- :active="nav === 'values'"
|
|
|
|
|
- :script="script"
|
|
|
|
|
|
|
+ v-else-if="nav === 'values'"
|
|
|
|
|
+ v-bind="{readOnly, script}"
|
|
|
/>
|
|
/>
|
|
|
<vm-externals
|
|
<vm-externals
|
|
|
class="flex-auto"
|
|
class="flex-auto"
|
|
|
- v-if="nav === 'externals'"
|
|
|
|
|
|
|
+ v-else-if="nav === 'externals'"
|
|
|
:value="script"
|
|
:value="script"
|
|
|
/>
|
|
/>
|
|
|
<vm-help
|
|
<vm-help
|
|
|
class="edit-body"
|
|
class="edit-body"
|
|
|
- v-show="nav === 'help'"
|
|
|
|
|
|
|
+ v-else-if="nav === 'help'"
|
|
|
:hotkeys="hotkeys"
|
|
:hotkeys="hotkeys"
|
|
|
/>
|
|
/>
|
|
|
|
|
+ </keep-alive>
|
|
|
|
|
|
|
|
<div v-if="errors" class="errors my-1c">
|
|
<div v-if="errors" class="errors my-1c">
|
|
|
<p v-for="e in errors" :key="e" v-text="e" class="text-red"/>
|
|
<p v-for="e in errors" :key="e" v-text="e" class="text-red"/>
|
|
@@ -78,7 +81,7 @@
|
|
|
<script>
|
|
<script>
|
|
|
import {
|
|
import {
|
|
|
browserWindows,
|
|
browserWindows,
|
|
|
- debounce, formatByteLength, getScriptName, i18n, isEmpty,
|
|
|
|
|
|
|
+ debounce, formatByteLength, getScriptName, getScriptUpdateUrl, i18n, isEmpty,
|
|
|
sendCmdDirectly, trueJoin,
|
|
sendCmdDirectly, trueJoin,
|
|
|
} from '@/common';
|
|
} from '@/common';
|
|
|
import { deepCopy, deepEqual, objectPick } from '@/common/object';
|
|
import { deepCopy, deepEqual, objectPick } from '@/common/object';
|
|
@@ -90,6 +93,7 @@ import options from '@/common/options';
|
|
|
import { getUnloadSentry } from '@/common/router';
|
|
import { getUnloadSentry } from '@/common/router';
|
|
|
import { store } from '../../utils';
|
|
import { store } from '../../utils';
|
|
|
import VmSettings from './settings';
|
|
import VmSettings from './settings';
|
|
|
|
|
+import VMSettingsUpdate from './settings-update';
|
|
|
import VmValues from './values';
|
|
import VmValues from './values';
|
|
|
import VmHelp from './help';
|
|
import VmHelp from './help';
|
|
|
|
|
|
|
@@ -103,7 +107,6 @@ const CUSTOM_PROPS = {
|
|
|
origMatch: true,
|
|
origMatch: true,
|
|
|
origExcludeMatch: true,
|
|
origExcludeMatch: true,
|
|
|
};
|
|
};
|
|
|
-const fromProp = (val, key) => val ?? CUSTOM_PROPS[key];
|
|
|
|
|
const toProp = val => val !== '' ? val : null; // `null` removes the prop from script object
|
|
const toProp = val => val !== '' ? val : null; // `null` removes the prop from script object
|
|
|
const CUSTOM_LISTS = [
|
|
const CUSTOM_LISTS = [
|
|
|
'include',
|
|
'include',
|
|
@@ -111,12 +114,6 @@ const CUSTOM_LISTS = [
|
|
|
'exclude',
|
|
'exclude',
|
|
|
'excludeMatch',
|
|
'excludeMatch',
|
|
|
];
|
|
];
|
|
|
-const fromList = list => (
|
|
|
|
|
- list
|
|
|
|
|
- // Adding a new row so the user can click it and type, just like in an empty textarea.
|
|
|
|
|
- ? `${list.join('\n')}${list.length ? '\n' : ''}`
|
|
|
|
|
- : ''
|
|
|
|
|
-);
|
|
|
|
|
const toList = text => (
|
|
const toList = text => (
|
|
|
text.trim()
|
|
text.trim()
|
|
|
? text.split('\n').map(line => line.trim()).filter(Boolean)
|
|
? text.split('\n').map(line => line.trim()).filter(Boolean)
|
|
@@ -126,9 +123,7 @@ const CUSTOM_ENUM = [
|
|
|
INJECT_INTO,
|
|
INJECT_INTO,
|
|
|
RUN_AT,
|
|
RUN_AT,
|
|
|
];
|
|
];
|
|
|
-const fromEnum = val => val || '';
|
|
|
|
|
const toEnum = val => val || null; // `null` removes the prop from script object
|
|
const toEnum = val => val || null; // `null` removes the prop from script object
|
|
|
-let savedSettings;
|
|
|
|
|
|
|
|
|
|
let shouldSavePositionOnSave;
|
|
let shouldSavePositionOnSave;
|
|
|
/** @param {chrome.windows.Window} [wnd] */
|
|
/** @param {chrome.windows.Window} [wnd] */
|
|
@@ -166,12 +161,18 @@ let K_SAVE; // deduced from the current CodeMirror keymap
|
|
|
const K_PREV_PANEL = 'Alt-PageUp';
|
|
const K_PREV_PANEL = 'Alt-PageUp';
|
|
|
const K_NEXT_PANEL = 'Alt-PageDown';
|
|
const K_NEXT_PANEL = 'Alt-PageDown';
|
|
|
const compareString = (a, b) => (a < b ? -1 : a > b);
|
|
const compareString = (a, b) => (a < b ? -1 : a > b);
|
|
|
|
|
+/** @param {VMScript.Config} config */
|
|
|
|
|
+const collectShouldUpdate = ({ shouldUpdate, _editable }) => (
|
|
|
|
|
+ +shouldUpdate && (shouldUpdate + _editable)
|
|
|
|
|
+);
|
|
|
|
|
+const deepWatchScript = { handler: 'onChange', deep: true };
|
|
|
|
|
|
|
|
export default {
|
|
export default {
|
|
|
props: ['initial', 'initialCode', 'readOnly'],
|
|
props: ['initial', 'initialCode', 'readOnly'],
|
|
|
components: {
|
|
components: {
|
|
|
VmCode,
|
|
VmCode,
|
|
|
VmSettings,
|
|
VmSettings,
|
|
|
|
|
+ VMSettingsUpdate,
|
|
|
VmValues,
|
|
VmValues,
|
|
|
VmExternals,
|
|
VmExternals,
|
|
|
VmHelp,
|
|
VmHelp,
|
|
@@ -179,12 +180,10 @@ export default {
|
|
|
data() {
|
|
data() {
|
|
|
return {
|
|
return {
|
|
|
nav: 'code',
|
|
nav: 'code',
|
|
|
- canEdit: true,
|
|
|
|
|
canSave: false,
|
|
canSave: false,
|
|
|
script: null,
|
|
script: null,
|
|
|
code: '',
|
|
code: '',
|
|
|
codeDirty: false,
|
|
codeDirty: false,
|
|
|
- settings: {},
|
|
|
|
|
commands: {
|
|
commands: {
|
|
|
save: this.save,
|
|
save: this.save,
|
|
|
close: this.close,
|
|
close: this.close,
|
|
@@ -194,19 +193,21 @@ export default {
|
|
|
},
|
|
},
|
|
|
hotkeys: null,
|
|
hotkeys: null,
|
|
|
errors: null,
|
|
errors: null,
|
|
|
|
|
+ frozen: false,
|
|
|
|
|
+ note: false,
|
|
|
urlMatching: 'https://violentmonkey.github.io/api/matching/',
|
|
urlMatching: 'https://violentmonkey.github.io/api/matching/',
|
|
|
};
|
|
};
|
|
|
},
|
|
},
|
|
|
computed: {
|
|
computed: {
|
|
|
navItems() {
|
|
navItems() {
|
|
|
- const { meta, props } = this.script || {};
|
|
|
|
|
- const req = meta?.require.length && '@require';
|
|
|
|
|
- const res = !isEmpty(meta?.resources) && '@resource';
|
|
|
|
|
|
|
+ const { meta, props } = this.script;
|
|
|
|
|
+ const req = meta.require.length && '@require';
|
|
|
|
|
+ const res = !isEmpty(meta.resources) && '@resource';
|
|
|
const size = store.storageSize;
|
|
const size = store.storageSize;
|
|
|
return {
|
|
return {
|
|
|
code: i18n('editNavCode'),
|
|
code: i18n('editNavCode'),
|
|
|
settings: i18n('editNavSettings'),
|
|
settings: i18n('editNavSettings'),
|
|
|
- ...props?.id && {
|
|
|
|
|
|
|
+ ...props.id && {
|
|
|
values: i18n('editNavValues') + (size ? ` (${formatByteLength(size)})` : ''),
|
|
values: i18n('editNavValues') + (size ? ` (${formatByteLength(size)})` : ''),
|
|
|
},
|
|
},
|
|
|
...(req || res) && { externals: [req, res]::trueJoin('/') },
|
|
...(req || res) && { externals: [req, res]::trueJoin('/') },
|
|
@@ -239,9 +240,13 @@ export default {
|
|
|
showMessage({ text: `${this.initial.message}\n\n${error}` });
|
|
showMessage({ text: `${this.initial.message}\n\n${error}` });
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
+ codeDirty: 'onDirty',
|
|
|
|
|
+ script: 'onScript',
|
|
|
|
|
+ 'script.config': deepWatchScript,
|
|
|
|
|
+ 'script.custom': deepWatchScript,
|
|
|
},
|
|
},
|
|
|
created() {
|
|
created() {
|
|
|
- this.script = this.initial;
|
|
|
|
|
|
|
+ this.script = deepCopy(this.initial);
|
|
|
this.toggleUnloadSentry = getUnloadSentry(null, () => {
|
|
this.toggleUnloadSentry = getUnloadSentry(null, () => {
|
|
|
this.$refs.code.cm.focus();
|
|
this.$refs.code.cm.focus();
|
|
|
});
|
|
});
|
|
@@ -252,26 +257,6 @@ export default {
|
|
|
async mounted() {
|
|
async mounted() {
|
|
|
document.body.classList.add('edit-open');
|
|
document.body.classList.add('edit-open');
|
|
|
store.storageSize = 0;
|
|
store.storageSize = 0;
|
|
|
- this.nav = 'code';
|
|
|
|
|
- const { custom, config } = this.script;
|
|
|
|
|
- const { noframes } = custom;
|
|
|
|
|
- this.settings = {
|
|
|
|
|
- config: {
|
|
|
|
|
- notifyUpdates: `${config.notifyUpdates ?? ''}`,
|
|
|
|
|
- // Needs to match Vue model type so deepEqual can work properly
|
|
|
|
|
- shouldUpdate: Boolean(config.shouldUpdate),
|
|
|
|
|
- },
|
|
|
|
|
- custom: {
|
|
|
|
|
- // Adding placeholders for any missing values so deepEqual can work properly
|
|
|
|
|
- ...objectPick(custom, Object.keys(CUSTOM_PROPS), fromProp),
|
|
|
|
|
- ...objectPick(custom, CUSTOM_ENUM, fromEnum),
|
|
|
|
|
- ...objectPick(custom, CUSTOM_LISTS, fromList),
|
|
|
|
|
- noframes: noframes == null ? '' : +noframes, // it was boolean in old VM
|
|
|
|
|
- },
|
|
|
|
|
- };
|
|
|
|
|
- savedSettings = deepCopy(this.settings);
|
|
|
|
|
- this.$watch('codeDirty', this.onChange);
|
|
|
|
|
- this.$watch('settings', this.onChange, { deep: true });
|
|
|
|
|
// hotkeys
|
|
// hotkeys
|
|
|
{
|
|
{
|
|
|
const navLabels = Object.values(this.navItems);
|
|
const navLabels = Object.values(this.navItems);
|
|
@@ -302,19 +287,19 @@ export default {
|
|
|
async save() {
|
|
async save() {
|
|
|
if (!this.canSave) return;
|
|
if (!this.canSave) return;
|
|
|
if (shouldSavePositionOnSave) savePosition();
|
|
if (shouldSavePositionOnSave) savePosition();
|
|
|
- const { settings } = this;
|
|
|
|
|
- const { config, custom } = settings;
|
|
|
|
|
|
|
+ const script = this.script;
|
|
|
|
|
+ const { config, custom } = script;
|
|
|
const { notifyUpdates } = config;
|
|
const { notifyUpdates } = config;
|
|
|
const { noframes } = custom;
|
|
const { noframes } = custom;
|
|
|
try {
|
|
try {
|
|
|
const codeComponent = this.$refs.code;
|
|
const codeComponent = this.$refs.code;
|
|
|
- const id = this.script?.props?.id;
|
|
|
|
|
|
|
+ const id = script.props.id;
|
|
|
const res = await sendCmdDirectly('ParseScript', {
|
|
const res = await sendCmdDirectly('ParseScript', {
|
|
|
id,
|
|
id,
|
|
|
code: codeComponent.getRealContent(),
|
|
code: codeComponent.getRealContent(),
|
|
|
config: {
|
|
config: {
|
|
|
- ...config,
|
|
|
|
|
- notifyUpdates: notifyUpdates ? +notifyUpdates : null,
|
|
|
|
|
|
|
+ notifyUpdates: notifyUpdates ? +notifyUpdates : null, // 0, 1, null
|
|
|
|
|
+ shouldUpdate: collectShouldUpdate(config), // 0, 1, 2
|
|
|
},
|
|
},
|
|
|
custom: {
|
|
custom: {
|
|
|
...objectPick(custom, Object.keys(CUSTOM_PROPS), toProp),
|
|
...objectPick(custom, Object.keys(CUSTOM_PROPS), toProp),
|
|
@@ -329,15 +314,13 @@ export default {
|
|
|
message: '',
|
|
message: '',
|
|
|
});
|
|
});
|
|
|
const newId = res?.where?.id;
|
|
const newId = res?.where?.id;
|
|
|
- savedSettings = deepCopy(settings);
|
|
|
|
|
codeComponent.cm.markClean();
|
|
codeComponent.cm.markClean();
|
|
|
- this.codeDirty = false; // triggers onChange which sets canSave
|
|
|
|
|
|
|
+ this.codeDirty = false; // triggers onDirty which sets canSave
|
|
|
this.canSave = false; // ...and set it explicitly in case codeDirty was false
|
|
this.canSave = false; // ...and set it explicitly in case codeDirty was false
|
|
|
|
|
+ this.note = false;
|
|
|
this.errors = res.errors;
|
|
this.errors = res.errors;
|
|
|
- if (newId) {
|
|
|
|
|
- this.script = res.update;
|
|
|
|
|
- if (!id) history.replaceState(null, this.scriptName, `${ROUTE_SCRIPTS}/${newId}`);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ this.script = res.update; // triggers onScript+onChange to handle the new `meta` and `props`
|
|
|
|
|
+ if (newId && !id) history.replaceState(null, this.scriptName, `${ROUTE_SCRIPTS}/${newId}`);
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
|
showConfirmation(`${err.message || err}`, {
|
|
showConfirmation(`${err.message || err}`, {
|
|
|
cancel: false,
|
|
cancel: false,
|
|
@@ -366,9 +349,44 @@ export default {
|
|
|
switchNextPanel() {
|
|
switchNextPanel() {
|
|
|
this.switchPanel(1);
|
|
this.switchPanel(1);
|
|
|
},
|
|
},
|
|
|
- onChange() {
|
|
|
|
|
- this.canSave = this.codeDirty || !deepEqual(this.settings, savedSettings);
|
|
|
|
|
|
|
+ onChange(evt) {
|
|
|
|
|
+ const { script } = this;
|
|
|
|
|
+ const { config } = script;
|
|
|
|
|
+ const { removed } = config;
|
|
|
|
|
+ const remote = script._remote = !!getScriptUpdateUrl(script);
|
|
|
|
|
+ const remoteMode = remote && collectShouldUpdate(config);
|
|
|
|
|
+ const frozen = !!(removed || remoteMode === 1 || this.readOnly);
|
|
|
|
|
+ this.frozen = frozen;
|
|
|
|
|
+ this.note = !removed && (frozen || remoteMode >= 1);
|
|
|
|
|
+ if (!removed && evt) this.onDirty();
|
|
|
},
|
|
},
|
|
|
|
|
+ onDirty() {
|
|
|
|
|
+ this.canSave = this.codeDirty || !deepEqual(this.script, this.saved);
|
|
|
|
|
+ },
|
|
|
|
|
+ onScript(script) {
|
|
|
|
|
+ const { custom, config } = script;
|
|
|
|
|
+ const { shouldUpdate } = config;
|
|
|
|
|
+ const { noframes } = custom;
|
|
|
|
|
+ // Matching Vue model types, so deepEqual can work properly
|
|
|
|
|
+ config._editable = shouldUpdate === 2;
|
|
|
|
|
+ config.notifyUpdates == `${config.notifyUpdates ?? ''}`;
|
|
|
|
|
+ config.shouldUpdate = !!shouldUpdate;
|
|
|
|
|
+ custom.noframes = noframes == null ? '' : +noframes; // it was boolean in old VM
|
|
|
|
|
+ // Adding placeholders for any missing values so deepEqual can work properly
|
|
|
|
|
+ for (const key in CUSTOM_PROPS) {
|
|
|
|
|
+ if (custom[key] == null) custom[key] = CUSTOM_PROPS[key];
|
|
|
|
|
+ }
|
|
|
|
|
+ for (const key of CUSTOM_ENUM) {
|
|
|
|
|
+ if (!custom[key]) custom[key] = '';
|
|
|
|
|
+ }
|
|
|
|
|
+ for (const key of CUSTOM_LISTS) {
|
|
|
|
|
+ const val = custom[key];
|
|
|
|
|
+ // Adding a new row so the user can click it and type, just like in an empty textarea.
|
|
|
|
|
+ custom[key] = val ? `${val.join('\n')}${val.length ? '\n' : ''}` : '';
|
|
|
|
|
+ }
|
|
|
|
|
+ this.onChange();
|
|
|
|
|
+ if (!config.removed) this.saved = deepCopy(script);
|
|
|
|
|
+ }
|
|
|
},
|
|
},
|
|
|
beforeUnmount() {
|
|
beforeUnmount() {
|
|
|
document.body.classList.remove('edit-open');
|
|
document.body.classList.remove('edit-open');
|
|
@@ -383,6 +401,7 @@ export default {
|
|
|
|
|
|
|
|
<style>
|
|
<style>
|
|
|
.edit {
|
|
.edit {
|
|
|
|
|
+ --border: 1px solid var(--fill-3);
|
|
|
z-index: 2000;
|
|
z-index: 2000;
|
|
|
&-header {
|
|
&-header {
|
|
|
position: sticky;
|
|
position: sticky;
|
|
@@ -390,7 +409,7 @@ export default {
|
|
|
z-index: 1;
|
|
z-index: 1;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
|
- border-bottom: 1px solid var(--fill-3);
|
|
|
|
|
|
|
+ border-bottom: var(--border);
|
|
|
background: inherit;
|
|
background: inherit;
|
|
|
}
|
|
}
|
|
|
&-name {
|
|
&-name {
|
|
@@ -449,6 +468,17 @@ export default {
|
|
|
border-top: 2px solid red;
|
|
border-top: 2px solid red;
|
|
|
padding: .5em 1em;
|
|
padding: .5em 1em;
|
|
|
}
|
|
}
|
|
|
|
|
+ .frozen-note {
|
|
|
|
|
+ background: var(--bg);
|
|
|
|
|
+ padding: .5em 1em;
|
|
|
|
|
+ border-bottom: var(--border);
|
|
|
|
|
+ }
|
|
|
|
|
+ &.readOnly &-header button:nth-last-child(n + 2) {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ }
|
|
|
|
|
+ &.frozen .CodeMirror {
|
|
|
|
|
+ background: var(--fill-0-5);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@media (max-width: 767px) {
|
|
@media (max-width: 767px) {
|