diff options
| author | かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> | 2023-10-29 14:12:40 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-10-29 14:12:40 +0900 |
| commit | 1a8243f1cace06c2eb872177d39536f76c9a8f5d (patch) | |
| tree | d75937ed6b116a98e3139d98b34bea4344c86f3e /packages/frontend/src/components/MkCodeEditor.vue | |
| parent | enhance(frontend): tweak about-misskey page (diff) | |
| download | misskey-1a8243f1cace06c2eb872177d39536f76c9a8f5d.tar.gz misskey-1a8243f1cace06c2eb872177d39536f76c9a8f5d.tar.bz2 misskey-1a8243f1cace06c2eb872177d39536f76c9a8f5d.zip | |
MkCodeのパースエンジンをShikiに変更 (#12102)
* (swap) prism -> shiki
* fix styles
* (bump) aiscript-vscode to v0.0.5
* refactor
* replace prism-editor (beta)
* Update scratchpad.vue
* (enhance) MkCodeEditor自動インデント改行
* (fix) lint
* (add) scratchpad: MkStickyContainer
* Update CHANGELOG.md
* clean up
---------
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Diffstat (limited to 'packages/frontend/src/components/MkCodeEditor.vue')
| -rw-r--r-- | packages/frontend/src/components/MkCodeEditor.vue | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/packages/frontend/src/components/MkCodeEditor.vue b/packages/frontend/src/components/MkCodeEditor.vue new file mode 100644 index 0000000000..2d56a61963 --- /dev/null +++ b/packages/frontend/src/components/MkCodeEditor.vue @@ -0,0 +1,166 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="[$style.codeEditorRoot, { [$style.disabled]: disabled, [$style.focused]: focused }]"> + <div :class="$style.codeEditorScroller"> + <textarea + ref="inputEl" + v-model="vModel" + :class="[$style.textarea]" + :disabled="disabled" + :required="required" + :readonly="readonly" + autocomplete="off" + wrap="off" + spellcheck="false" + @focus="focused = true" + @blur="focused = false" + @keydown="onKeydown($event)" + @input="onInput" + ></textarea> + <XCode :class="$style.codeEditorHighlighter" :codeEditor="true" :code="v" :lang="lang"/> + </div> +</div> +</template> + +<script lang="ts" setup> +import { ref, watch, toRefs, shallowRef, nextTick } from 'vue'; +import XCode from '@/components/MkCode.core.vue'; + +const props = withDefaults(defineProps<{ + modelValue: string | null; + lang: string; + required?: boolean; + readonly?: boolean; + disabled?: boolean; +}>(), { + lang: 'js', +}); + +const emit = defineEmits<{ + (ev: 'change', _ev: KeyboardEvent): void; + (ev: 'keydown', _ev: KeyboardEvent): void; + (ev: 'enter'): void; + (ev: 'update:modelValue', value: string): void; +}>(); + +const { modelValue } = toRefs(props); +const vModel = ref<string>(modelValue.value ?? ''); +const v = ref<string>(modelValue.value ?? ''); +const focused = ref(false); +const changed = ref(false); +const inputEl = shallowRef<HTMLTextAreaElement>(); + +const onInput = (ev) => { + v.value = ev.target?.value ?? v.value; + changed.value = true; + emit('change', ev); +}; + +const onKeydown = (ev: KeyboardEvent) => { + if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return; + + emit('keydown', ev); + + if (ev.code === 'Enter') { + const pos = inputEl.value?.selectionStart ?? 0; + const posEnd = inputEl.value?.selectionEnd ?? vModel.value.length; + if (pos === posEnd) { + const lines = vModel.value.slice(0, pos).split('\n'); + const currentLine = lines[lines.length - 1]; + const currentLineSpaces = currentLine.match(/^\s+/); + const posDelta = currentLineSpaces ? currentLineSpaces[0].length : 0; + ev.preventDefault(); + vModel.value = vModel.value.slice(0, pos) + '\n' + (currentLineSpaces ? currentLineSpaces[0] : '') + vModel.value.slice(pos); + v.value = vModel.value; + nextTick(() => { + inputEl.value?.setSelectionRange(pos + 1 + posDelta, pos + 1 + posDelta); + }); + } + emit('enter'); + } + + if (ev.key === 'Tab') { + const pos = inputEl.value?.selectionStart ?? 0; + const posEnd = inputEl.value?.selectionEnd ?? vModel.value.length; + vModel.value = vModel.value.slice(0, pos) + '\t' + vModel.value.slice(posEnd); + v.value = vModel.value; + nextTick(() => { + inputEl.value?.setSelectionRange(pos + 1, pos + 1); + }); + ev.preventDefault(); + } +}; + +const updated = () => { + changed.value = false; + emit('update:modelValue', v.value); +}; + +watch(modelValue, newValue => { + v.value = newValue ?? ''; +}); + +watch(v, () => { + updated(); +}); +</script> + +<style lang="scss" module> +.codeEditorRoot { + min-width: 100%; + max-width: 100%; + overflow-x: auto; + overflow-y: hidden; + box-sizing: border-box; + margin: 0; + padding: 0; + color: var(--fg); + border: solid 1px var(--panel); + transition: border-color 0.1s ease-out; + font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; + &:hover { + border-color: var(--inputBorderHover) !important; + } +} + +.focused.codeEditorRoot { + border-color: var(--accent) !important; + border-radius: 6px; +} + +.codeEditorScroller { + position: relative; + display: inline-block; + min-width: 100%; + height: 100%; +} + +.textarea { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: inline-block; + appearance: none; + resize: none; + text-align: left; + color: transparent; + caret-color: rgb(225, 228, 232); + background-color: transparent; + border: 0; + outline: 0; + padding: 12px; + line-height: 1.5em; + font-size: 1em; + font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; +} + +.textarea::selection { + color: #fff; +} +</style> |