|
|
@@ -13,7 +13,8 @@
|
|
|
<span class="subtle" v-if="script.config.removed" v-text="i18n('headerRecycleBin') + ' / '"/>
|
|
|
{{scriptName}}
|
|
|
</div>
|
|
|
- <p v-if="frozen" class="text-upper text-right text-red" v-text="i18n('readonly')"/>
|
|
|
+ <p v-if="frozen && nav === 'code'" v-text="i18n('readonly')"
|
|
|
+ class="text-upper text-right text-red"/>
|
|
|
<div v-else class="edit-hint text-right ellipsis">
|
|
|
<a href="https://violentmonkey.github.io/posts/how-to-edit-scripts-with-your-favorite-editor/"
|
|
|
target="_blank"
|
|
|
@@ -46,7 +47,7 @@
|
|
|
class="flex-auto"
|
|
|
:value="code"
|
|
|
:readOnly="frozen"
|
|
|
- ref="code"
|
|
|
+ ref="$code"
|
|
|
v-show="nav === 'code'"
|
|
|
:active="nav === 'code'"
|
|
|
:commands="commands"
|
|
|
@@ -84,7 +85,8 @@
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
-<script>
|
|
|
+<script setup>
|
|
|
+import { computed, nextTick, onActivated, onDeactivated, onMounted, ref, watch } from 'vue';
|
|
|
import {
|
|
|
browserWindows,
|
|
|
debounce, formatByteLength, getScriptName, getScriptUpdateUrl, i18n, isEmpty,
|
|
|
@@ -103,6 +105,74 @@ import VMSettingsUpdate from './settings-update';
|
|
|
import VmValues from './values';
|
|
|
import VmHelp from './help';
|
|
|
|
|
|
+let CM;
|
|
|
+let $codeComp;
|
|
|
+let disposeList;
|
|
|
+let K_SAVE; // deduced from the current CodeMirror keymap
|
|
|
+let savedCopy;
|
|
|
+let shouldSavePositionOnSave;
|
|
|
+let toggleUnloadSentry;
|
|
|
+
|
|
|
+const emit = defineEmits(['close']);
|
|
|
+const props = defineProps({
|
|
|
+ /** @type {VMScript} */
|
|
|
+ initial: Object,
|
|
|
+ initialCode: String,
|
|
|
+ readOnly: Boolean,
|
|
|
+});
|
|
|
+
|
|
|
+const $code = ref();
|
|
|
+const nav = ref('code');
|
|
|
+const canSave = ref(false);
|
|
|
+const script = ref();
|
|
|
+const code = ref('');
|
|
|
+const codeDirty = ref(false);
|
|
|
+const commands = {
|
|
|
+ save,
|
|
|
+ close,
|
|
|
+ showHelp: () => { nav.value = 'help'; },
|
|
|
+};
|
|
|
+const hotkeys = ref();
|
|
|
+const errors = ref();
|
|
|
+const fatal = ref();
|
|
|
+const frozen = ref(false);
|
|
|
+const frozenNote = ref(false);
|
|
|
+const urlMatching = ref('https://violentmonkey.github.io/api/matching/');
|
|
|
+
|
|
|
+const navItems = computed(() => {
|
|
|
+ const { meta, props: { id } } = script.value;
|
|
|
+ const req = meta.require.length && '@require';
|
|
|
+ const res = !isEmpty(meta.resources) && '@resource';
|
|
|
+ const size = store.storageSize;
|
|
|
+ return {
|
|
|
+ code: i18n('editNavCode'),
|
|
|
+ settings: i18n('editNavSettings'),
|
|
|
+ ...id && {
|
|
|
+ values: i18n('editNavValues') + (size ? ` (${formatByteLength(size)})` : ''),
|
|
|
+ },
|
|
|
+ ...(req || res) && { externals: [req, res]::trueJoin('/') },
|
|
|
+ help: '?',
|
|
|
+ };
|
|
|
+});
|
|
|
+const scriptName = computed(() => (store.title = getScriptName(script.value)));
|
|
|
+
|
|
|
+watch(nav, val => {
|
|
|
+ keyboardService.setContext('tabCode', val === 'code');
|
|
|
+ if (val === 'code') nextTick(() => CM.focus());
|
|
|
+});
|
|
|
+watch(canSave, val => {
|
|
|
+ toggleUnloadSentry(val);
|
|
|
+ keyboardService.setContext('canSave', val);
|
|
|
+});
|
|
|
+// usually errors for resources
|
|
|
+watch(() => props.initial.error, error => {
|
|
|
+ if (error) {
|
|
|
+ showMessage({ text: `${props.initial.message}\n\n${error}` });
|
|
|
+ }
|
|
|
+});
|
|
|
+watch(codeDirty, onDirty);
|
|
|
+watch(script, onScript);
|
|
|
+
|
|
|
const CUSTOM_PROPS = {
|
|
|
name: '',
|
|
|
homepageURL: '',
|
|
|
@@ -130,10 +200,168 @@ const CUSTOM_ENUM = [
|
|
|
RUN_AT,
|
|
|
];
|
|
|
const toEnum = val => val || null; // `null` removes the prop from script object
|
|
|
+const K_PREV_PANEL = 'Alt-PageUp';
|
|
|
+const K_NEXT_PANEL = 'Alt-PageDown';
|
|
|
+const compareString = (a, b) => (a < b ? -1 : a > b);
|
|
|
+/** @param {VMScript.Config} config */
|
|
|
+const collectShouldUpdate = ({ shouldUpdate, _editable }) => (
|
|
|
+ +shouldUpdate && (shouldUpdate + _editable)
|
|
|
+);
|
|
|
|
|
|
-let shouldSavePositionOnSave;
|
|
|
+{
|
|
|
+ // The eslint rule is bugged as this is a block scope, not a global scope.
|
|
|
+ const src = props.initial; // eslint-disable-line vue/no-setup-props-destructure
|
|
|
+ code.value = props.initialCode; // eslint-disable-line vue/no-setup-props-destructure
|
|
|
+ script.value = deepCopy(src);
|
|
|
+ watch(() => script.value.config, onChange, { deep: true });
|
|
|
+ watch(() => script.value.custom, onChange, { deep: true });
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ $codeComp = $code.value;
|
|
|
+ CM = $codeComp.cm;
|
|
|
+ toggleUnloadSentry = getUnloadSentry(null, () => CM.focus());
|
|
|
+ if (options.get('editorWindow') && global.history.length === 1) {
|
|
|
+ browser.windows?.getCurrent({ populate: true }).then(setupSavePosition);
|
|
|
+ }
|
|
|
+ store.storageSize = 0;
|
|
|
+ // hotkeys
|
|
|
+ const navLabels = Object.values(navItems.value);
|
|
|
+ const hk = hotkeys.value = [
|
|
|
+ [K_PREV_PANEL, ` ${navLabels.join(' < ')}`],
|
|
|
+ [K_NEXT_PANEL, ` ${navLabels.join(' > ')}`],
|
|
|
+ ...Object.entries($codeComp.expandKeyMap())
|
|
|
+ .sort((a, b) => compareString(a[1], b[1]) || compareString(a[0], b[0])),
|
|
|
+ ];
|
|
|
+ K_SAVE = hk.find(([, cmd]) => cmd === 'save')?.[0];
|
|
|
+ if (!K_SAVE) {
|
|
|
+ K_SAVE = 'Ctrl-S';
|
|
|
+ hk.unshift([K_SAVE, 'save']);
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+onActivated(() => {
|
|
|
+ document.body.classList.add('edit-open');
|
|
|
+ disposeList = [
|
|
|
+ keyboardService.register('a-pageup', switchPrevPanel),
|
|
|
+ keyboardService.register('a-pagedown', switchNextPanel),
|
|
|
+ keyboardService.register(K_SAVE.replace(/(?:Ctrl|Cmd)-/i, 'ctrlcmd-'), save),
|
|
|
+ keyboardService.register('escape', () => { nav.value = 'code'; }, {
|
|
|
+ condition: '!tabCode',
|
|
|
+ }),
|
|
|
+ ];
|
|
|
+ store.title = scriptName.value;
|
|
|
+});
|
|
|
+
|
|
|
+onDeactivated(() => {
|
|
|
+ document.body.classList.remove('edit-open');
|
|
|
+ store.title = null;
|
|
|
+ toggleUnloadSentry(false);
|
|
|
+ disposeList?.forEach(dispose => dispose());
|
|
|
+});
|
|
|
+
|
|
|
+async function save() {
|
|
|
+ if (!canSave.value) return;
|
|
|
+ if (shouldSavePositionOnSave) savePosition();
|
|
|
+ const scr = script.value;
|
|
|
+ const { config, custom } = scr;
|
|
|
+ const { notifyUpdates } = config;
|
|
|
+ const { noframes } = custom;
|
|
|
+ try {
|
|
|
+ const id = scr.props.id;
|
|
|
+ const res = await sendCmdDirectly('ParseScript', {
|
|
|
+ id,
|
|
|
+ code: $codeComp.getRealContent(),
|
|
|
+ config: {
|
|
|
+ notifyUpdates: notifyUpdates ? +notifyUpdates : null, // 0, 1, null
|
|
|
+ shouldUpdate: collectShouldUpdate(config), // 0, 1, 2
|
|
|
+ },
|
|
|
+ custom: {
|
|
|
+ ...objectPick(custom, Object.keys(CUSTOM_PROPS), toProp),
|
|
|
+ ...objectPick(custom, CUSTOM_LISTS, toList),
|
|
|
+ ...objectPick(custom, CUSTOM_ENUM, toEnum),
|
|
|
+ noframes: noframes ? +noframes : null,
|
|
|
+ },
|
|
|
+ // User created scripts MUST be marked `isNew` so that
|
|
|
+ // the backend is able to check namespace conflicts,
|
|
|
+ // otherwise the script with same namespace will be overridden
|
|
|
+ isNew: !id,
|
|
|
+ message: '',
|
|
|
+ });
|
|
|
+ const newId = res?.where?.id;
|
|
|
+ CM.markClean();
|
|
|
+ codeDirty.value = false; // triggers onDirty which sets canSave
|
|
|
+ canSave.value = false; // ...and set it explicitly in case codeDirty was false
|
|
|
+ frozenNote.value = false;
|
|
|
+ errors.value = res.errors;
|
|
|
+ script.value = res.update; // triggers onScript+onChange to handle the new `meta` and `props`
|
|
|
+ if (newId && !id) history.replaceState(null, scriptName.value, `${ROUTE_SCRIPTS}/${newId}`);
|
|
|
+ fatal.value = null;
|
|
|
+ } catch (err) {
|
|
|
+ fatal.value = err.message.split('\n');
|
|
|
+ }
|
|
|
+}
|
|
|
+function close(cm) {
|
|
|
+ if (cm && nav.value !== 'code') {
|
|
|
+ nav.value = 'code';
|
|
|
+ } else {
|
|
|
+ emit('close');
|
|
|
+ // FF doesn't emit `blur` when CodeMirror's textarea is removed
|
|
|
+ if (IS_FIREFOX) document.activeElement?.blur();
|
|
|
+ }
|
|
|
+}
|
|
|
+function saveClose() {
|
|
|
+ save().then(close);
|
|
|
+}
|
|
|
+function switchPanel(step) {
|
|
|
+ const keys = Object.keys(navItems.value);
|
|
|
+ nav.value = keys[(keys.indexOf(nav.value) + step + keys.length) % keys.length];
|
|
|
+}
|
|
|
+function switchPrevPanel() {
|
|
|
+ switchPanel(-1);
|
|
|
+}
|
|
|
+function switchNextPanel() {
|
|
|
+ switchPanel(1);
|
|
|
+}
|
|
|
+function onChange(evt) {
|
|
|
+ const scr = script.value;
|
|
|
+ const { config } = scr;
|
|
|
+ const { removed } = config;
|
|
|
+ const remote = scr._remote = !!getScriptUpdateUrl(scr);
|
|
|
+ const remoteMode = remote && collectShouldUpdate(config);
|
|
|
+ const fz = !!(removed || remoteMode === 1 || props.readOnly);
|
|
|
+ frozen.value = fz;
|
|
|
+ frozenNote.value = !removed && (fz || remoteMode >= 1);
|
|
|
+ if (!removed && evt) onDirty();
|
|
|
+}
|
|
|
+function onDirty() {
|
|
|
+ canSave.value = codeDirty.value || !deepEqual(script.value, savedCopy);
|
|
|
+}
|
|
|
+function onScript(scr) {
|
|
|
+ const { custom, config } = scr;
|
|
|
+ const { shouldUpdate } = config;
|
|
|
+ // Matching Vue model types, so deepEqual can work properly
|
|
|
+ config._editable = shouldUpdate === 2;
|
|
|
+ config.shouldUpdate = !!shouldUpdate;
|
|
|
+ config.notifyUpdates = nullBool2string(config.notifyUpdates);
|
|
|
+ custom.noframes = nullBool2string(custom.noframes);
|
|
|
+ // 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' : ''}` : '';
|
|
|
+ }
|
|
|
+ onChange();
|
|
|
+ if (!config.removed) savedCopy = deepCopy(scr);
|
|
|
+}
|
|
|
/** @param {chrome.windows.Window} [wnd] */
|
|
|
-const savePosition = async wnd => {
|
|
|
+async function savePosition(wnd) {
|
|
|
if (options.get('editorWindow')) {
|
|
|
if (!wnd) wnd = await browserWindows?.getCurrent() || {};
|
|
|
/* chrome.windows API can't set both the state and coords, so we have to choose:
|
|
|
@@ -145,9 +373,10 @@ const savePosition = async wnd => {
|
|
|
options.set('editorWindowPos', objectPick(wnd, ['left', 'top', 'width', 'height']));
|
|
|
}
|
|
|
}
|
|
|
-};
|
|
|
+}
|
|
|
+
|
|
|
/** @param {chrome.windows.Window} _ */
|
|
|
-const setupSavePosition = ({ id: curWndId, tabs }) => {
|
|
|
+function setupSavePosition({ id: curWndId, tabs }) {
|
|
|
if (tabs.length === 1) {
|
|
|
const { onBoundsChanged } = chrome.windows;
|
|
|
if (onBoundsChanged) {
|
|
|
@@ -161,248 +390,7 @@ const setupSavePosition = ({ id: curWndId, tabs }) => {
|
|
|
shouldSavePositionOnSave = true;
|
|
|
}
|
|
|
}
|
|
|
-};
|
|
|
-
|
|
|
-let K_SAVE; // deduced from the current CodeMirror keymap
|
|
|
-const K_PREV_PANEL = 'Alt-PageUp';
|
|
|
-const K_NEXT_PANEL = 'Alt-PageDown';
|
|
|
-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 {
|
|
|
- props: ['initial', 'initialCode', 'readOnly'],
|
|
|
- components: {
|
|
|
- VmCode,
|
|
|
- VmSettings,
|
|
|
- VMSettingsUpdate,
|
|
|
- VmValues,
|
|
|
- VmExternals,
|
|
|
- VmHelp,
|
|
|
- },
|
|
|
- data() {
|
|
|
- return {
|
|
|
- nav: 'code',
|
|
|
- canSave: false,
|
|
|
- script: null,
|
|
|
- code: '',
|
|
|
- codeDirty: false,
|
|
|
- commands: {
|
|
|
- save: this.save,
|
|
|
- close: this.close,
|
|
|
- showHelp: () => {
|
|
|
- this.nav = 'help';
|
|
|
- },
|
|
|
- },
|
|
|
- hotkeys: null,
|
|
|
- errors: null,
|
|
|
- fatal: null,
|
|
|
- frozen: false,
|
|
|
- frozenNote: false,
|
|
|
- urlMatching: 'https://violentmonkey.github.io/api/matching/',
|
|
|
- };
|
|
|
- },
|
|
|
- computed: {
|
|
|
- navItems() {
|
|
|
- const { meta, props } = this.script;
|
|
|
- const req = meta.require.length && '@require';
|
|
|
- const res = !isEmpty(meta.resources) && '@resource';
|
|
|
- const size = store.storageSize;
|
|
|
- return {
|
|
|
- code: i18n('editNavCode'),
|
|
|
- settings: i18n('editNavSettings'),
|
|
|
- ...props.id && {
|
|
|
- values: i18n('editNavValues') + (size ? ` (${formatByteLength(size)})` : ''),
|
|
|
- },
|
|
|
- ...(req || res) && { externals: [req, res]::trueJoin('/') },
|
|
|
- help: '?',
|
|
|
- };
|
|
|
- },
|
|
|
- scriptName() {
|
|
|
- const { script } = this;
|
|
|
- const scriptName = script?.meta && getScriptName(script);
|
|
|
- store.title = scriptName;
|
|
|
- return scriptName;
|
|
|
- },
|
|
|
- },
|
|
|
- watch: {
|
|
|
- nav(val) {
|
|
|
- keyboardService.setContext('tabCode', val === 'code');
|
|
|
- if (val === 'code') {
|
|
|
- this.$nextTick(() => {
|
|
|
- this.$refs.code.cm.focus();
|
|
|
- });
|
|
|
- }
|
|
|
- },
|
|
|
- canSave(val) {
|
|
|
- this.toggleUnloadSentry(val);
|
|
|
- keyboardService.setContext('canSave', val);
|
|
|
- },
|
|
|
- // usually errors for resources
|
|
|
- 'initial.error'(error) {
|
|
|
- if (error) {
|
|
|
- showMessage({ text: `${this.initial.message}\n\n${error}` });
|
|
|
- }
|
|
|
- },
|
|
|
- codeDirty: 'onDirty',
|
|
|
- script: 'onScript',
|
|
|
- 'script.config': deepWatchScript,
|
|
|
- 'script.custom': deepWatchScript,
|
|
|
- },
|
|
|
- created() {
|
|
|
- this.script = deepCopy(this.initial);
|
|
|
- this.toggleUnloadSentry = getUnloadSentry(null, () => {
|
|
|
- this.$refs.code.cm.focus();
|
|
|
- });
|
|
|
- if (options.get('editorWindow') && global.history.length === 1) {
|
|
|
- browser.windows?.getCurrent({ populate: true }).then(setupSavePosition);
|
|
|
- }
|
|
|
- },
|
|
|
- async mounted() {
|
|
|
- document.body.classList.add('edit-open');
|
|
|
- store.storageSize = 0;
|
|
|
- // hotkeys
|
|
|
- {
|
|
|
- const navLabels = Object.values(this.navItems);
|
|
|
- const hotkeys = [
|
|
|
- [K_PREV_PANEL, ` ${navLabels.join(' < ')}`],
|
|
|
- [K_NEXT_PANEL, ` ${navLabels.join(' > ')}`],
|
|
|
- ...Object.entries(this.$refs.code.expandKeyMap())
|
|
|
- .sort((a, b) => compareString(a[1], b[1]) || compareString(a[0], b[0])),
|
|
|
- ];
|
|
|
- K_SAVE = hotkeys.find(([, cmd]) => cmd === 'save')?.[0];
|
|
|
- if (!K_SAVE) {
|
|
|
- K_SAVE = 'Ctrl-S';
|
|
|
- hotkeys.unshift([K_SAVE, 'save']);
|
|
|
- }
|
|
|
- this.hotkeys = hotkeys;
|
|
|
- }
|
|
|
- this.disposeList = [
|
|
|
- keyboardService.register('a-pageup', this.switchPrevPanel),
|
|
|
- keyboardService.register('a-pagedown', this.switchNextPanel),
|
|
|
- keyboardService.register(K_SAVE.replace(/(?:Ctrl|Cmd)-/i, 'ctrlcmd-'), this.save),
|
|
|
- keyboardService.register('escape', () => { this.nav = 'code'; }, {
|
|
|
- condition: '!tabCode',
|
|
|
- }),
|
|
|
- ];
|
|
|
- this.code = this.initialCode;
|
|
|
- },
|
|
|
- methods: {
|
|
|
- async save() {
|
|
|
- if (!this.canSave) return;
|
|
|
- if (shouldSavePositionOnSave) savePosition();
|
|
|
- const script = this.script;
|
|
|
- const { config, custom } = script;
|
|
|
- const { notifyUpdates } = config;
|
|
|
- const { noframes } = custom;
|
|
|
- let fatal;
|
|
|
- try {
|
|
|
- const codeComponent = this.$refs.code;
|
|
|
- const id = script.props.id;
|
|
|
- const res = await sendCmdDirectly('ParseScript', {
|
|
|
- id,
|
|
|
- code: codeComponent.getRealContent(),
|
|
|
- config: {
|
|
|
- notifyUpdates: notifyUpdates ? +notifyUpdates : null, // 0, 1, null
|
|
|
- shouldUpdate: collectShouldUpdate(config), // 0, 1, 2
|
|
|
- },
|
|
|
- custom: {
|
|
|
- ...objectPick(custom, Object.keys(CUSTOM_PROPS), toProp),
|
|
|
- ...objectPick(custom, CUSTOM_LISTS, toList),
|
|
|
- ...objectPick(custom, CUSTOM_ENUM, toEnum),
|
|
|
- noframes: noframes ? +noframes : null,
|
|
|
- },
|
|
|
- // User created scripts MUST be marked `isNew` so that
|
|
|
- // the backend is able to check namespace conflicts,
|
|
|
- // otherwise the script with same namespace will be overridden
|
|
|
- isNew: !id,
|
|
|
- message: '',
|
|
|
- });
|
|
|
- const newId = res?.where?.id;
|
|
|
- codeComponent.cm.markClean();
|
|
|
- this.codeDirty = false; // triggers onDirty which sets canSave
|
|
|
- this.canSave = false; // ...and set it explicitly in case codeDirty was false
|
|
|
- this.frozenNote = false;
|
|
|
- this.errors = res.errors;
|
|
|
- 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) {
|
|
|
- fatal = err.message.split('\n');
|
|
|
- }
|
|
|
- this.fatal = fatal;
|
|
|
- },
|
|
|
- close(cm) {
|
|
|
- if (cm && this.nav !== 'code') {
|
|
|
- this.nav = 'code';
|
|
|
- } else {
|
|
|
- this.$emit('close');
|
|
|
- // FF doesn't emit `blur` when CodeMirror's textarea is removed
|
|
|
- if (IS_FIREFOX) document.activeElement?.blur();
|
|
|
- }
|
|
|
- },
|
|
|
- saveClose() {
|
|
|
- this.save().then(this.close);
|
|
|
- },
|
|
|
- switchPanel(step) {
|
|
|
- const keys = Object.keys(this.navItems);
|
|
|
- this.nav = keys[(keys.indexOf(this.nav) + step + keys.length) % keys.length];
|
|
|
- },
|
|
|
- switchPrevPanel() {
|
|
|
- this.switchPanel(-1);
|
|
|
- },
|
|
|
- switchNextPanel() {
|
|
|
- this.switchPanel(1);
|
|
|
- },
|
|
|
- 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.frozenNote = !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;
|
|
|
- // Matching Vue model types, so deepEqual can work properly
|
|
|
- config._editable = shouldUpdate === 2;
|
|
|
- config.shouldUpdate = !!shouldUpdate;
|
|
|
- config.notifyUpdates = nullBool2string(config.notifyUpdates);
|
|
|
- custom.noframes = nullBool2string(custom.noframes);
|
|
|
- // 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() {
|
|
|
- document.body.classList.remove('edit-open');
|
|
|
- store.title = null;
|
|
|
- this.toggleUnloadSentry(false);
|
|
|
- this.disposeList?.forEach(dispose => {
|
|
|
- dispose();
|
|
|
- });
|
|
|
- },
|
|
|
-};
|
|
|
+}
|
|
|
</script>
|
|
|
|
|
|
<style>
|