Fix editor state issues (#173)

* Add MarkdownEditor component with default value and disabled option

* Add editorThemeCompartment for customizing editor theme
This commit is contained in:
Carter
2023-12-28 16:47:05 -08:00
parent f6eff090e7
commit 1940c6e1ba
2 changed files with 127 additions and 16 deletions

View File

@@ -7,6 +7,10 @@ const description1 = ref(null);
const description2 = ref(null); const description2 = ref(null);
const description3 = ref(null); const description3 = ref(null);
const description4 = ref("Hello, world! This is a **bold** statement.");
const isDisabled = ref(false);
const onImageUpload = (file) => { const onImageUpload = (file) => {
return URL.createObjectURL(file).replace("blob:", ""); return URL.createObjectURL(file).replace("blob:", "");
}; };
@@ -79,3 +83,34 @@ const description = ref(null)
<MarkdownEditor v-model="description" :heading-buttons="false" /> <MarkdownEditor v-model="description" :heading-buttons="false" />
``` ```
## With default value
<DemoContainer>
<MarkdownEditor v-model="description4" />
</DemoContainer>
```vue
<script setup>
import { ref } from "vue";
const description = ref("Hello, world! This is a **bold** statement.");
</script>
<MardownEditor v-model="description" />
```
## Disabled
<DemoContainer>
<Toggle v-model="isDisabled" label="Disabled" />
<MarkdownEditor v-model="description" :disabled="isDisabled" />
</DemoContainer>
```vue
<script setup>
import { ref } from "vue";
const description = ref(null);
</script>
<MardownEditor v-model="description" disabled />
```

View File

@@ -250,7 +250,7 @@
</span> </span>
</div> </div>
</div> </div>
<div v-if="previewMode"> <div v-else>
<div class="markdown-body-wrapper"> <div class="markdown-body-wrapper">
<div <div
style="width: 100%" style="width: 100%"
@@ -265,7 +265,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { type Component, computed, ref, onMounted, onBeforeUnmount, toRef, watch } from 'vue' import { type Component, computed, ref, onMounted, onBeforeUnmount, toRef, watch } from 'vue'
import { EditorState } from '@codemirror/state' import { Compartment, EditorState } from '@codemirror/state'
import { EditorView, keymap, placeholder as cm_placeholder } from '@codemirror/view' import { EditorView, keymap, placeholder as cm_placeholder } from '@codemirror/view'
import { markdown } from '@codemirror/lang-markdown' import { markdown } from '@codemirror/lang-markdown'
import { indentWithTab, historyKeymap, history } from '@codemirror/commands' import { indentWithTab, historyKeymap, history } from '@codemirror/commands'
@@ -327,6 +327,8 @@ const props = withDefaults(
const editorRef = ref<HTMLDivElement>() const editorRef = ref<HTMLDivElement>()
let editor: EditorView | null = null let editor: EditorView | null = null
let isDisabledCompartment: Compartment | null = null
let editorThemeCompartment: Compartment | null = null
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
@@ -337,8 +339,10 @@ onMounted(() => {
} }
}) })
editorThemeCompartment = new Compartment()
const theme = EditorView.theme({ const theme = EditorView.theme({
// in defualts.scss there's references to .cm-content and such to inherit global styles // in defaults.scss there's references to .cm-content and such to inherit global styles
'.cm-content': { '.cm-content': {
marginBlockEnd: '0.5rem', marginBlockEnd: '0.5rem',
padding: '0.5rem', padding: '0.5rem',
@@ -355,6 +359,10 @@ onMounted(() => {
}, },
}) })
isDisabledCompartment = new Compartment()
const disabledCompartment = EditorState.readOnly.of(props.disabled)
const eventHandlers = EditorView.domEventHandlers({ const eventHandlers = EditorView.domEventHandlers({
paste: (ev, view) => { paste: (ev, view) => {
const { clipboardData } = ev const { clipboardData } = ev
@@ -425,7 +433,6 @@ onMounted(() => {
const editorState = EditorState.create({ const editorState = EditorState.create({
extensions: [ extensions: [
theme,
eventHandlers, eventHandlers,
updateListener, updateListener,
keymap.of([indentWithTab]), keymap.of([indentWithTab]),
@@ -437,13 +444,24 @@ onMounted(() => {
keymap.of(historyKeymap), keymap.of(historyKeymap),
cm_placeholder(props.placeholder || ''), cm_placeholder(props.placeholder || ''),
inputFilter, inputFilter,
isDisabledCompartment.of(disabledCompartment),
editorThemeCompartment.of(theme),
], ],
}) })
editor = new EditorView({ editor = new EditorView({
state: editorState, state: editorState,
parent: editorRef.value, parent: editorRef.value,
doc: props.modelValue, doc: props.modelValue ?? '', // This doesn't work for some reason
})
// set editor content to props.modelValue
editor?.dispatch({
changes: {
from: 0,
to: editor.state.doc.length,
insert: props.modelValue,
},
}) })
}) })
@@ -541,18 +559,70 @@ const BUTTONS: ButtonGroupMap = {
}, },
} }
const currentValue = toRef(props, 'modelValue') watch(
watch(currentValue, (newValue) => { () => props.disabled,
if (editor) { (newValue) => {
editor.dispatch({ if (editor) {
changes: { if (isDisabledCompartment) {
from: 0, editor.dispatch({
to: editor.state.doc.length, effects: [isDisabledCompartment.reconfigure(EditorState.readOnly.of(newValue))],
insert: newValue, })
}, }
})
if (editorThemeCompartment) {
editor.dispatch({
effects: [
editorThemeCompartment.reconfigure(
EditorView.theme({
// in defaults.scss there's references to .cm-content and such to inherit global styles
'.cm-content': {
marginBlockEnd: '0.5rem',
padding: '0.5rem',
minHeight: '200px',
caretColor: 'var(--color-contrast)',
width: '100%',
overflowX: 'scroll',
maxHeight: props.maxHeight ? `${props.maxHeight}px` : 'unset',
overflowY: 'scroll',
opacity: newValue ? 0.6 : 1,
pointerEvents: newValue ? 'none' : 'all',
cursor: newValue ? 'not-allowed' : 'auto',
},
'.cm-scroller': {
height: '100%',
overflow: 'visible',
},
})
),
],
})
}
}
},
{
immediate: true,
} }
}) )
const currentValue = toRef(props, 'modelValue')
watch(
currentValue,
(newValue) => {
if (editor && newValue !== editor.state.doc.toString()) {
editor.dispatch({
changes: {
from: 0,
to: editor.state.doc.length,
insert: newValue,
},
})
}
},
{
immediate: true,
}
)
const updateCurrentValue = (newValue: string) => { const updateCurrentValue = (newValue: string) => {
emit('update:modelValue', newValue) emit('update:modelValue', newValue)
@@ -873,4 +943,10 @@ function openVideoModal() {
justify-content: start; justify-content: start;
} }
} }
.cm-disabled {
opacity: 0.6;
pointer-events: none;
cursor: not-allowed;
}
</style> </style>