You've already forked AstralRinth
forked from didirus/AstralRinth
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:
@@ -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 />
|
||||||
|
```
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user