7 changed files with 221 additions and 648 deletions
@ -1,116 +1,99 @@
|
||||
<script lang="ts" setup> |
||||
import { nextTick, onMounted, onUnmounted, ref, unref, watch, watchEffect } from 'vue' |
||||
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue' |
||||
import { useDebounceFn } from '@vueuse/core' |
||||
import CodeMirror from 'codemirror' |
||||
import { basicSetup } from 'codemirror' |
||||
import type { ViewUpdate } from '@codemirror/view' |
||||
import { EditorView, keymap } from '@codemirror/view' |
||||
import { EditorState } from '@codemirror/state' |
||||
import { javascript } from '@codemirror/lang-javascript' |
||||
import { json } from '@codemirror/lang-json' |
||||
import { indentWithTab } from '@codemirror/commands' |
||||
import { MODE } from './../typing' |
||||
import { useAppStore } from '@/store/modules/app' |
||||
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn' |
||||
|
||||
// css |
||||
import './codemirror.css' |
||||
import 'codemirror/theme/idea.css' |
||||
import 'codemirror/theme/material-palenight.css' |
||||
|
||||
// modes |
||||
import 'codemirror/mode/javascript/javascript' |
||||
import 'codemirror/mode/css/css' |
||||
import 'codemirror/mode/htmlmixed/htmlmixed' |
||||
|
||||
const props = defineProps({ |
||||
mode: { |
||||
type: String as PropType<MODE>, |
||||
default: MODE.JSON, |
||||
validator(value: any) { |
||||
// 这个值必须匹配下列字符串中的一个 |
||||
return Object.values(MODE).includes(value) |
||||
}, |
||||
}, |
||||
value: { type: String, default: '' }, |
||||
readonly: { type: Boolean, default: false }, |
||||
bordered: { type: Boolean, default: false }, |
||||
readonly: { |
||||
type: Boolean, |
||||
}, |
||||
}) |
||||
|
||||
const emit = defineEmits(['change']) |
||||
|
||||
const el = ref() |
||||
let editor: Nullable<CodeMirror.Editor> |
||||
|
||||
const debounceRefresh = useDebounceFn(refresh, 100) |
||||
const appStore = useAppStore() |
||||
const container = ref() |
||||
let editorView: EditorView |
||||
|
||||
watch( |
||||
() => props.value, |
||||
async (value) => { |
||||
await nextTick() |
||||
const oldValue = editor?.getValue() |
||||
if (value !== oldValue) |
||||
editor?.setValue(value || '') |
||||
if (value !== editorView.state.doc.toString()) { |
||||
editorView.dispatch({ |
||||
changes: { from: 0, insert: value }, |
||||
}) |
||||
} |
||||
}, |
||||
{ flush: 'post' }, |
||||
) |
||||
|
||||
watchEffect(() => { |
||||
editor?.setOption('mode', props.mode) |
||||
}) |
||||
|
||||
watch( |
||||
() => appStore.getDarkMode, |
||||
async () => { |
||||
setTheme() |
||||
}, |
||||
{ |
||||
immediate: true, |
||||
}, |
||||
) |
||||
|
||||
function setTheme() { |
||||
unref(editor)?.setOption('theme', appStore.getDarkMode === 'light' ? 'idea' : 'material-palenight') |
||||
} |
||||
|
||||
function refresh() { |
||||
editor?.refresh() |
||||
} |
||||
|
||||
async function init() { |
||||
const addonOptions = { |
||||
autoCloseBrackets: true, |
||||
autoCloseTags: true, |
||||
foldGutter: true, |
||||
gutters: ['CodeMirror-linenumbers'], |
||||
const extensions = [basicSetup, keymap.of([indentWithTab])] |
||||
switch (props.mode) { |
||||
case MODE.JS: |
||||
extensions.push(javascript()) |
||||
break |
||||
case MODE.JSON: |
||||
extensions.push(json()) |
||||
break |
||||
default: { |
||||
const _exhaustiveCheck: never = props.mode |
||||
console.error(`[CodeMirror]: Unknown language ${props.mode}`) |
||||
return _exhaustiveCheck |
||||
} |
||||
} |
||||
|
||||
editor = CodeMirror(el.value!, { |
||||
value: '', |
||||
mode: props.mode, |
||||
readOnly: props.readonly, |
||||
tabSize: 2, |
||||
theme: 'material-palenight', |
||||
lineWrapping: true, |
||||
lineNumbers: true, |
||||
...addonOptions, |
||||
const startState = EditorState.create({ |
||||
doc: props.value, |
||||
extensions: [ |
||||
...extensions, |
||||
EditorView.updateListener.of(useDebounceFn((e: ViewUpdate) => { |
||||
const context = e.state.doc.toString() |
||||
emit('change', context) |
||||
})), |
||||
EditorState.readOnly.of(props.readonly), |
||||
], |
||||
}) |
||||
editor?.setValue(props.value) |
||||
setTheme() |
||||
editor?.on('change', () => { |
||||
emit('change', editor?.getValue()) |
||||
|
||||
editorView = new EditorView({ |
||||
state: startState, |
||||
parent: container.value, |
||||
}) |
||||
} |
||||
|
||||
onMounted(async () => { |
||||
await nextTick() |
||||
init() |
||||
useWindowSizeFn(debounceRefresh) |
||||
}) |
||||
|
||||
onUnmounted(() => { |
||||
editor = null |
||||
onBeforeUnmount(() => { |
||||
editorView?.destroy() |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div |
||||
ref="el" |
||||
class="relative w-full overflow-hidden !h-full" |
||||
:class="{ 'ant-input': props.bordered, 'css-dev-only-do-not-override-kqecok': props.bordered }" |
||||
ref="container" |
||||
class="code-mirror relative w-full overflow-hidden !h-full" |
||||
/> |
||||
</template> |
||||
|
||||
<style> |
||||
.code-mirror > .cm-editor { |
||||
height: 100%; |
||||
} |
||||
</style> |
||||
|
@ -1,23 +0,0 @@
|
||||
import CodeMirror from 'codemirror' |
||||
import './codemirror.css' |
||||
import 'codemirror/theme/idea.css' |
||||
import 'codemirror/theme/material-palenight.css' |
||||
|
||||
// import 'codemirror/addon/lint/lint.css';
|
||||
|
||||
// modes
|
||||
import 'codemirror/mode/javascript/javascript' |
||||
import 'codemirror/mode/css/css' |
||||
import 'codemirror/mode/htmlmixed/htmlmixed' |
||||
|
||||
// addons
|
||||
// import 'codemirror/addon/edit/closebrackets';
|
||||
// import 'codemirror/addon/edit/closetag';
|
||||
// import 'codemirror/addon/comment/comment';
|
||||
// import 'codemirror/addon/fold/foldcode';
|
||||
// import 'codemirror/addon/fold/foldgutter';
|
||||
// import 'codemirror/addon/fold/brace-fold';
|
||||
// import 'codemirror/addon/fold/indent-fold';
|
||||
// import 'codemirror/addon/lint/json-lint';
|
||||
// import 'codemirror/addon/fold/comment-fold';
|
||||
export { CodeMirror } |
@ -1,528 +0,0 @@
|
||||
/* BASICS */ |
||||
|
||||
.CodeMirror { |
||||
--base: #545281; |
||||
--comment: hsl(210deg 25% 60%); |
||||
--keyword: #af4ab1; |
||||
--variable: #0055d1; |
||||
--function: #c25205; |
||||
--string: #2ba46d; |
||||
--number: #c25205; |
||||
--tags: #d00; |
||||
--qualifier: #ff6032; |
||||
--important: var(--string); |
||||
|
||||
position: relative; |
||||
height: auto; |
||||
height: 100%; |
||||
overflow: hidden; |
||||
font-family: var(--font-code); |
||||
background: white; |
||||
direction: ltr; |
||||
} |
||||
|
||||
/* PADDING */ |
||||
|
||||
.CodeMirror-lines { |
||||
min-height: 1px; /* prevents collapsing before first draw */ |
||||
padding: 4px 0; /* Vertical padding around content */ |
||||
cursor: text; |
||||
} |
||||
|
||||
.CodeMirror-scrollbar-filler, |
||||
.CodeMirror-gutter-filler { |
||||
background-color: white; /* The little square between H and V scrollbars */ |
||||
} |
||||
|
||||
/* GUTTER */ |
||||
|
||||
.CodeMirror-gutters { |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
z-index: 3; |
||||
min-height: 100%; |
||||
white-space: nowrap; |
||||
background-color: transparent; |
||||
border-right: 1px solid #ddd; |
||||
} |
||||
|
||||
.CodeMirror-linenumber { |
||||
min-width: 20px; |
||||
padding: 0 3px 0 5px; |
||||
color: var(--comment); |
||||
text-align: right; |
||||
white-space: nowrap; |
||||
opacity: 0.6; |
||||
} |
||||
|
||||
.CodeMirror-guttermarker { |
||||
color: black; |
||||
} |
||||
|
||||
.CodeMirror-guttermarker-subtle { |
||||
color: #999; |
||||
} |
||||
|
||||
/* FOLD GUTTER */ |
||||
|
||||
.CodeMirror-foldmarker { |
||||
font-family: arial; |
||||
line-height: 0.3; |
||||
color: #414141; |
||||
text-shadow: |
||||
#f96 1px 1px 2px, |
||||
#f96 -1px -1px 2px, |
||||
#f96 1px -1px 2px, |
||||
#f96 -1px 1px 2px; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.CodeMirror-foldgutter { |
||||
width: 0.7em; |
||||
} |
||||
|
||||
.CodeMirror-foldgutter-open, |
||||
.CodeMirror-foldgutter-folded { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.CodeMirror-foldgutter-open::after, |
||||
.CodeMirror-foldgutter-folded::after { |
||||
position: relative; |
||||
top: -0.1em; |
||||
display: inline-block; |
||||
font-size: 0.8em; |
||||
content: '>'; |
||||
opacity: 0.8; |
||||
transform: rotate(90deg); |
||||
transition: transform 0.2s; |
||||
} |
||||
|
||||
.CodeMirror-foldgutter-folded::after { |
||||
transform: none; |
||||
} |
||||
|
||||
/* CURSOR */ |
||||
|
||||
.CodeMirror-cursor { |
||||
position: absolute; |
||||
width: 0; |
||||
pointer-events: none; |
||||
border-right: none; |
||||
border-left: 1px solid black; |
||||
} |
||||
|
||||
/* Shown when moving in bi-directional text */ |
||||
.CodeMirror div.CodeMirror-secondarycursor { |
||||
border-left: 1px solid silver; |
||||
} |
||||
|
||||
.cm-fat-cursor .CodeMirror-cursor { |
||||
width: auto; |
||||
background: #7e7; |
||||
border: 0 !important; |
||||
} |
||||
|
||||
.cm-fat-cursor div.CodeMirror-cursors { |
||||
z-index: 1; |
||||
} |
||||
|
||||
.cm-fat-cursor-mark { |
||||
background-color: rgb(20 255 20 / 50%); |
||||
animation: blink 1.06s steps(1) infinite; |
||||
} |
||||
|
||||
.cm-animate-fat-cursor { |
||||
width: auto; |
||||
background-color: #7e7; |
||||
border: 0; |
||||
animation: blink 1.06s steps(1) infinite; |
||||
} |
||||
@keyframes blink { |
||||
50% { |
||||
background-color: transparent; |
||||
} |
||||
} |
||||
@keyframes blink { |
||||
50% { |
||||
background-color: transparent; |
||||
} |
||||
} |
||||
@keyframes blink { |
||||
50% { |
||||
background-color: transparent; |
||||
} |
||||
} |
||||
|
||||
.cm-tab { |
||||
display: inline-block; |
||||
text-decoration: inherit; |
||||
} |
||||
|
||||
.CodeMirror-rulers { |
||||
position: absolute; |
||||
top: -50px; |
||||
right: 0; |
||||
bottom: -20px; |
||||
left: 0; |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.CodeMirror-ruler { |
||||
position: absolute; |
||||
top: 0; |
||||
bottom: 0; |
||||
border-left: 1px solid #ccc; |
||||
} |
||||
|
||||
/* DEFAULT THEME */ |
||||
.cm-s-default.CodeMirror { |
||||
background-color: transparent; |
||||
} |
||||
|
||||
.cm-s-default .cm-header { |
||||
color: blue; |
||||
} |
||||
|
||||
.cm-s-default .cm-quote { |
||||
color: #090; |
||||
} |
||||
|
||||
.cm-negative { |
||||
color: #d44; |
||||
} |
||||
|
||||
.cm-positive { |
||||
color: #292; |
||||
} |
||||
|
||||
.cm-header, |
||||
.cm-strong { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.cm-em { |
||||
font-style: italic; |
||||
} |
||||
|
||||
.cm-link { |
||||
text-decoration: underline; |
||||
} |
||||
|
||||
.cm-strikethrough { |
||||
text-decoration: line-through; |
||||
} |
||||
|
||||
.cm-s-default .cm-atom, |
||||
.cm-s-default .cm-def, |
||||
.cm-s-default .cm-property, |
||||
.cm-s-default .cm-variable-2, |
||||
.cm-s-default .cm-variable-3, |
||||
.cm-s-default .cm-punctuation { |
||||
color: var(--base); |
||||
} |
||||
|
||||
.cm-s-default .cm-hr, |
||||
.cm-s-default .cm-comment { |
||||
color: var(--comment); |
||||
} |
||||
|
||||
.cm-s-default .cm-attribute, |
||||
.cm-s-default .cm-keyword { |
||||
color: var(--keyword); |
||||
} |
||||
|
||||
.cm-s-default .cm-variable { |
||||
color: var(--variable); |
||||
} |
||||
|
||||
.cm-s-default .cm-bracket, |
||||
.cm-s-default .cm-tag { |
||||
color: var(--tags); |
||||
} |
||||
|
||||
.cm-s-default .cm-number { |
||||
color: var(--number); |
||||
} |
||||
|
||||
.cm-s-default .cm-string, |
||||
.cm-s-default .cm-string-2 { |
||||
color: var(--string); |
||||
} |
||||
|
||||
.cm-s-default .cm-type { |
||||
color: #085; |
||||
} |
||||
|
||||
.cm-s-default .cm-meta { |
||||
color: #555; |
||||
} |
||||
|
||||
.cm-s-default .cm-qualifier { |
||||
color: var(--qualifier); |
||||
} |
||||
|
||||
.cm-s-default .cm-builtin { |
||||
color: #7539ff; |
||||
} |
||||
|
||||
.cm-s-default .cm-link { |
||||
color: var(--flash); |
||||
} |
||||
|
||||
.cm-s-default .cm-error { |
||||
color: #ff008c; |
||||
} |
||||
|
||||
.cm-invalidchar { |
||||
color: #ff008c; |
||||
} |
||||
|
||||
.CodeMirror-composing { |
||||
border-bottom: 2px solid; |
||||
} |
||||
|
||||
/* Default styles for common addons */ |
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket { |
||||
color: #0b0; |
||||
} |
||||
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket { |
||||
color: #a22; |
||||
} |
||||
|
||||
.CodeMirror-matchingtag { |
||||
background: rgb(255 150 0 / 30%); |
||||
} |
||||
|
||||
.CodeMirror-activeline-background { |
||||
background: #e8f2ff; |
||||
} |
||||
|
||||
/* STOP */ |
||||
|
||||
/* The rest of this file contains styles related to the mechanics of |
||||
the editor. You probably shouldn't touch them. */ |
||||
|
||||
.CodeMirror-scroll { |
||||
position: relative; |
||||
height: 100%; |
||||
margin-right: -30px; |
||||
|
||||
/* 30px is the magic margin used to hide the element's real scrollbars */ |
||||
|
||||
/* See overflow: hidden in .CodeMirror */ |
||||
margin-bottom: -30px; |
||||
overflow: scroll !important; /* Things will break if this is overridden */ |
||||
outline: none; /* Prevent dragging from highlighting the element */ |
||||
} |
||||
|
||||
.CodeMirror-sizer { |
||||
position: relative; |
||||
margin-bottom: 20px !important; |
||||
border-right: 30px solid transparent; |
||||
} |
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling |
||||
before actual scrolling happens, thus preventing shaking and |
||||
flickering artifacts. */ |
||||
.CodeMirror-vscrollbar, |
||||
.CodeMirror-hscrollbar, |
||||
.CodeMirror-scrollbar-filler, |
||||
.CodeMirror-gutter-filler { |
||||
position: absolute; |
||||
z-index: 6; |
||||
display: none; |
||||
} |
||||
|
||||
.CodeMirror-vscrollbar { |
||||
top: 0; |
||||
right: 0; |
||||
overflow-x: hidden; |
||||
overflow-y: scroll; |
||||
} |
||||
|
||||
.CodeMirror-hscrollbar { |
||||
bottom: 0; |
||||
left: 0; |
||||
overflow-x: scroll; |
||||
overflow-y: hidden; |
||||
} |
||||
|
||||
.CodeMirror-scrollbar-filler { |
||||
right: 0; |
||||
bottom: 0; |
||||
} |
||||
|
||||
.CodeMirror-gutter-filler { |
||||
bottom: 0; |
||||
left: 0; |
||||
} |
||||
|
||||
.CodeMirror-gutter { |
||||
display: inline-block; |
||||
height: 100%; |
||||
margin-bottom: -30px; |
||||
white-space: normal; |
||||
vertical-align: top; |
||||
} |
||||
|
||||
.CodeMirror-gutter-wrapper { |
||||
position: absolute; |
||||
z-index: 4; |
||||
background: none !important; |
||||
border: none !important; |
||||
} |
||||
|
||||
.CodeMirror-gutter-background { |
||||
position: absolute; |
||||
top: 0; |
||||
bottom: 0; |
||||
z-index: 4; |
||||
} |
||||
|
||||
.CodeMirror-gutter-elt { |
||||
position: absolute; |
||||
z-index: 4; |
||||
cursor: default; |
||||
} |
||||
|
||||
.CodeMirror-gutter-wrapper ::selection { |
||||
background-color: transparent; |
||||
} |
||||
|
||||
.CodeMirrorwrapper ::selection { |
||||
background-color: transparent; |
||||
} |
||||
|
||||
.CodeMirror pre { |
||||
position: relative; |
||||
z-index: 2; |
||||
padding: 0 4px; /* Horizontal padding of content */ |
||||
margin: 0; |
||||
overflow: visible; |
||||
font-family: inherit; |
||||
font-size: inherit; |
||||
line-height: inherit; |
||||
color: inherit; |
||||
word-wrap: normal; |
||||
white-space: pre; |
||||
background: transparent; |
||||
border-width: 0; |
||||
|
||||
/* Reset some styles that the rest of the page might have set */ |
||||
border-radius: 0; |
||||
-webkit-tap-highlight-color: transparent; |
||||
font-variant-ligatures: contextual; |
||||
} |
||||
|
||||
.CodeMirror-wrap pre { |
||||
word-break: normal; |
||||
word-wrap: break-word; |
||||
white-space: pre-wrap; |
||||
} |
||||
|
||||
.CodeMirror-linebackground { |
||||
position: absolute; |
||||
top: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
left: 0; |
||||
z-index: 0; |
||||
} |
||||
|
||||
.CodeMirror-linewidget { |
||||
position: relative; |
||||
z-index: 2; |
||||
padding: 0.1px; /* Force widget margins to stay inside of the container */ |
||||
} |
||||
|
||||
.CodeMirror-rtl pre { |
||||
direction: rtl; |
||||
} |
||||
|
||||
.CodeMirror-code { |
||||
outline: none; |
||||
} |
||||
|
||||
/* Force content-box sizing for the elements where we expect it */ |
||||
.CodeMirror-scroll, |
||||
.CodeMirror-sizer, |
||||
.CodeMirror-gutter, |
||||
.CodeMirror-gutters, |
||||
.CodeMirror-linenumber { |
||||
box-sizing: content-box; |
||||
} |
||||
|
||||
.CodeMirror-measure { |
||||
position: absolute; |
||||
width: 100%; |
||||
height: 0; |
||||
overflow: hidden; |
||||
visibility: hidden; |
||||
} |
||||
|
||||
.CodeMirror-measure pre { |
||||
position: static; |
||||
} |
||||
|
||||
div.CodeMirror-cursors { |
||||
position: relative; |
||||
z-index: 3; |
||||
visibility: hidden; |
||||
} |
||||
|
||||
div.CodeMirror-dragcursors { |
||||
visibility: visible; |
||||
} |
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors { |
||||
visibility: visible; |
||||
} |
||||
|
||||
.CodeMirror-selected { |
||||
background: #d9d9d9; |
||||
} |
||||
|
||||
.CodeMirror-focused .CodeMirror-selected { |
||||
background: #d7d4f0; |
||||
} |
||||
|
||||
.CodeMirror-crosshair { |
||||
cursor: crosshair; |
||||
} |
||||
|
||||
.CodeMirror-line::selection, |
||||
.CodeMirror-line > span::selection, |
||||
.CodeMirror-line > span > span::selection { |
||||
background: #d7d4f0; |
||||
} |
||||
|
||||
.cm-searching { |
||||
background-color: #ffa; |
||||
background-color: rgb(255 255 0 / 40%); |
||||
} |
||||
|
||||
/* Used to force a border model for a node */ |
||||
.cm-force-border { |
||||
padding-right: 0.1px; |
||||
} |
||||
|
||||
@media print { |
||||
/* Hide the cursor when printing */ |
||||
.CodeMirror div.CodeMirror-cursors { |
||||
visibility: hidden; |
||||
} |
||||
} |
||||
|
||||
/* See issue #2901 */ |
||||
.cm-tab-wrap-hack::after { |
||||
content: ''; |
||||
} |
||||
|
||||
/* Help users use markselection to safely style text background */ |
||||
span.CodeMirror-selectedtext { |
||||
background: none; |
||||
} |
Loading…
Reference in new issue