| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- <template>
- <section>
- <h3 v-text="i18n('labelEditor')"></h3>
- <div class="mr-1c flex center-items">
- <span v-text="i18n('labelTheme')"/>
- <select v-model="theme" :disabled="busy" :title="css">
- <option :value="DEFAULT" v-text="i18n('labelRunAtDefault')"/>
- <option value="" v-text="i18n('labelBadgeNone')"/>
- <option v-for="name in themes" :key="name" v-text="name"/>
- </select>
- <a :href="ghURL" target="_blank">↗</a>
- <p v-text="error"/>
- </div>
- <p v-html="i18n('descEditorOptions')" class="my-1"/>
- <setting-text name="editor" ref="editor" :json="true" :has-reset="true" @save="onSave">
- <button v-text="i18n('buttonShowEditorState')" @click="toggleStateHint"/>
- </setting-text>
- <pre v-text="hint" class="monospace-font dim-hint" />
- </section>
- </template>
- <script>
- import options from '@/common/options';
- import hookSetting from '@/common/hook-setting';
- import { showMessage } from '@/common/ui';
- import SettingText from '@/common/ui/setting-text';
- const keyThemeCSS = 'editorTheme';
- const keyThemeNAME = 'editorThemeName';
- const THEMES = process.env.CODEMIRROR_THEMES;
- const gh = 'github.com';
- const ghREPO = 'codemirror/CodeMirror';
- const ghBRANCH = 'master';
- const ghPATH = 'theme';
- const ghURL = `https://${gh}/${ghREPO}/tree/${ghBRANCH}/${ghPATH}`;
- const DEFAULT = 'default';
- const previewLINES = 20;
- const previewLENGTH = 100;
- const makeTextPreview = css => (
- css
- ? css.split('\n', previewLINES + 1).map((s, i) => (
- i === previewLINES && (
- '...'
- ) || s.length > previewLENGTH && (
- `${s.slice(0, previewLENGTH)}...`
- ) || s
- )).join('\n')
- : null
- );
- export default {
- data() {
- return {
- hint: null,
- busy: false,
- error: null,
- css: null,
- theme: null,
- themes: THEMES,
- DEFAULT,
- ghURL,
- };
- },
- components: {
- SettingText,
- },
- beforeUnmount() {
- this.revokers.forEach(revoke => revoke());
- this.revokers = null;
- },
- async mounted() {
- this.$refs.editor.$el.addEventListener('dblclick', this.toggleBoolean);
- if (!this.revokers) {
- this.css = makeTextPreview(options.get(keyThemeCSS));
- this.revokers = [
- hookSetting(keyThemeNAME, val => { this.theme = val ?? DEFAULT; }),
- ];
- await options.ready; // Waiting for hookSetting to set the value before watching for changes
- this.$watch('theme', async val => {
- const url = val && val !== DEFAULT
- && `https://raw.githubusercontent.com/${ghREPO}/${ghBRANCH}/${ghPATH}/${val}.css`;
- const css = url && await this.fetch(url);
- options.set(keyThemeNAME, !url || css ? val : DEFAULT);
- options.set(keyThemeCSS, css || '');
- this.css = makeTextPreview(css);
- });
- }
- },
- methods: {
- async fetch(url, method = 'text') {
- const el = document.activeElement;
- this.busy = true;
- try {
- const res = await (await fetch(url))[method]();
- this.error = null;
- return res;
- } catch (e) {
- this.error = e.message || e.code || `${e}`;
- } finally {
- this.busy = false;
- this.$nextTick(() => el?.focus());
- }
- },
- onSave() {
- showMessage({ text: this.$refs.editor.error || this.i18n('msgSavedEditorOptions') });
- },
- toggleBoolean(event) {
- const el = /** @type {HTMLTextAreaElement} */ event.target;
- const { selectionStart: start, selectionEnd: end, value } = el;
- const toggled = { false: 'true', true: 'false' }[value.slice(start, end)];
- // FF can't run execCommand on textarea, https://bugzil.la/1220696#c24
- if (toggled && !document.execCommand('insertText', false, toggled)) {
- el.value = value.slice(0, start) + toggled + value.slice(end);
- el.setSelectionRange(start + toggled.length, start + toggled.length);
- el.dispatchEvent(new Event('input'));
- el.onblur = () => el.dispatchEvent(new Event('change'));
- }
- },
- async toggleStateHint() {
- if (this.hint) {
- this.hint = null;
- return;
- }
- const HIDE_OPTS = [
- // we activate only one mode: js
- 'mode',
- // duh
- 'value',
- // these accept only a function
- 'configureMouse',
- 'lineNumberFormatter',
- 'specialCharPlaceholder',
- ];
- const opts = {};
- Object.entries({
- ...(await import('codemirror')).default.defaults,
- ...(await import('@/common/ui/code')).default.data().cmDefaults,
- ...options.get('editor'),
- })
- // sort by keys alphabetically to make it more readable
- .sort(([a], [b]) => (a < b ? -1 : a > b))
- .filter(([key, val]) => !HIDE_OPTS.includes(key) && typeof val !== 'function')
- .forEach(([key, val]) => { opts[key] = val; });
- this.hint = JSON.stringify(opts, null, ' ');
- setTimeout(() => {
- if (this.$el.getBoundingClientRect().bottom > window.innerHeight) {
- this.$el.scrollIntoView({ behavior: 'smooth' });
- }
- });
- },
- },
- };
- </script>
- <style>
- .dim-hint {
- font-size: .85rem;
- color: var(--fill-8);
- }
- </style>
|