Tweaks for Knossos Integration (#122)
* placeholder * max length & placeholder * remove default placeholder * remove scoped css * allow for throwing in the upload process * explicit import of info * fix aggressive card input selection
This commit is contained in:
parent
79bdea0441
commit
544111846c
@ -3,6 +3,7 @@
|
|||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
const description = ref(null);
|
const description = ref(null);
|
||||||
|
const description1 = ref(null);
|
||||||
const description2 = ref(null);
|
const description2 = ref(null);
|
||||||
const description3 = ref(null);
|
const description3 = ref(null);
|
||||||
|
|
||||||
@ -27,6 +28,20 @@ const description = ref(null)
|
|||||||
<MarkdownEditor v-model="description" />
|
<MarkdownEditor v-model="description" />
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## With options
|
||||||
|
<DemoContainer>
|
||||||
|
<MarkdownEditor v-model="description1" placeholder="Enter a description" max-length="30" />
|
||||||
|
</DemoContainer>
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
const description = ref(null)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<MarkdownEditor v-model="description" placeholder="Enter a description" max-length="30" />
|
||||||
|
```
|
||||||
|
|
||||||
## With image upload
|
## With image upload
|
||||||
<DemoContainer>
|
<DemoContainer>
|
||||||
<MarkdownEditor v-model="description2" :on-image-upload="onImageUpload" />
|
<MarkdownEditor v-model="description2" :on-image-upload="onImageUpload" />
|
||||||
@ -41,6 +56,9 @@ const description = ref(null)
|
|||||||
const onImageUpload = (file: File): string => {
|
const onImageUpload = (file: File): string => {
|
||||||
// Upload the file to your server and return a URL
|
// Upload the file to your server and return a URL
|
||||||
// This example url will not work bc of proxy
|
// This example url will not work bc of proxy
|
||||||
|
|
||||||
|
// If the upload fails, throw an error and it will show as
|
||||||
|
// a Validation Error to the user
|
||||||
return URL.createObjectURL(file).replace("blob:", "");
|
return URL.createObjectURL(file).replace("blob:", "");
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -70,8 +70,6 @@
|
|||||||
:where(input) {
|
:where(input) {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
max-height: 40px;
|
max-height: 40px;
|
||||||
width: 24rem;
|
|
||||||
flex-basis: 24rem;
|
|
||||||
|
|
||||||
&:not(.stylized-toggle) {
|
&:not(.stylized-toggle) {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|||||||
@ -223,11 +223,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div ref="editorRef" :class="{ hide: previewMode }" />
|
<div ref="editorRef" :class="{ hide: previewMode }" />
|
||||||
<div v-if="!previewMode" class="info-blurb">
|
<div v-if="!previewMode" class="info-blurb">
|
||||||
<InfoIcon />
|
<div class="info-blurb">
|
||||||
<span>
|
<InfoIcon />
|
||||||
This editor supports
|
<span
|
||||||
<a href="https://docs.modrinth.com/docs/markdown" target="_blank">Markdown formatting</a>.
|
>This editor supports
|
||||||
</span>
|
<a class="link" href="https://docs.modrinth.com/docs/markdown" target="_blank"
|
||||||
|
>Markdown formatting</a
|
||||||
|
>.</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div :class="{ hide: !props.maxLength }" class="max-length-label">
|
||||||
|
<span>Max length: </span>
|
||||||
|
<span>{{ props.maxLength ?? 'Unlimited' }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="previewMode"
|
v-if="previewMode"
|
||||||
@ -242,7 +250,7 @@
|
|||||||
import { type Component, computed, ref, onMounted, onBeforeUnmount } from 'vue'
|
import { type Component, computed, ref, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
import { EditorState } from '@codemirror/state'
|
import { EditorState } from '@codemirror/state'
|
||||||
import { EditorView, keymap } 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'
|
||||||
|
|
||||||
@ -268,6 +276,7 @@ import {
|
|||||||
Toggle,
|
Toggle,
|
||||||
FileInput,
|
FileInput,
|
||||||
UploadIcon,
|
UploadIcon,
|
||||||
|
InfoIcon,
|
||||||
Chips,
|
Chips,
|
||||||
} from '@/components'
|
} from '@/components'
|
||||||
import { markdownCommands, modrinthMarkdownEditorKeymap } from '@/helpers/codemirror'
|
import { markdownCommands, modrinthMarkdownEditorKeymap } from '@/helpers/codemirror'
|
||||||
@ -278,13 +287,21 @@ const props = withDefaults(
|
|||||||
modelValue: string
|
modelValue: string
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
headingButtons: boolean
|
headingButtons: boolean
|
||||||
|
/**
|
||||||
|
* @param file The file to upload
|
||||||
|
* @throws If the file is invalid or the upload fails
|
||||||
|
*/
|
||||||
onImageUpload?: (file: File) => Promise<string>
|
onImageUpload?: (file: File) => Promise<string>
|
||||||
|
placeholder?: string
|
||||||
|
maxLength?: number
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
modelValue: '',
|
modelValue: '',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
headingButtons: true,
|
headingButtons: true,
|
||||||
onImageUpload: undefined,
|
onImageUpload: undefined,
|
||||||
|
placeholder: undefined,
|
||||||
|
maxLength: undefined,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -321,7 +338,6 @@ onMounted(() => {
|
|||||||
paste: (ev, view) => {
|
paste: (ev, view) => {
|
||||||
// If the user's pasting a url, automatically convert it to a link with the selection as the text or the url itself if no selection content.
|
// If the user's pasting a url, automatically convert it to a link with the selection as the text or the url itself if no selection content.
|
||||||
const url = ev.clipboardData?.getData('text/plain')
|
const url = ev.clipboardData?.getData('text/plain')
|
||||||
|
|
||||||
if (url) {
|
if (url) {
|
||||||
try {
|
try {
|
||||||
cleanUrl(url)
|
cleanUrl(url)
|
||||||
@ -337,6 +353,35 @@ onMounted(() => {
|
|||||||
const linkMarkdown = `[${linkText}](${url})`
|
const linkMarkdown = `[${linkText}](${url})`
|
||||||
return markdownCommands.replaceSelection(view, linkMarkdown)
|
return markdownCommands.replaceSelection(view, linkMarkdown)
|
||||||
}
|
}
|
||||||
|
// Check if the length of the document is greater than the max length. If it is, prevent the paste.
|
||||||
|
if (props.maxLength && view.state.doc.length > props.maxLength) {
|
||||||
|
ev.preventDefault()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeinput: (ev, view) => {
|
||||||
|
if (props.maxLength && view.state.doc.length > props.maxLength) {
|
||||||
|
ev.preventDefault()
|
||||||
|
// Calculate how many characters to remove from the end
|
||||||
|
const excessLength = view.state.doc.length - props.maxLength
|
||||||
|
// Dispatch transaction to remove excess characters
|
||||||
|
view.dispatch({
|
||||||
|
changes: { from: view.state.doc.length - excessLength, to: view.state.doc.length },
|
||||||
|
selection: { anchor: props.maxLength, head: props.maxLength }, // Place cursor at the end
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
blur: (_, view) => {
|
||||||
|
if (props.maxLength && view.state.doc.length > props.maxLength) {
|
||||||
|
// Calculate how many characters to remove from the end
|
||||||
|
const excessLength = view.state.doc.length - props.maxLength
|
||||||
|
// Dispatch transaction to remove excess characters
|
||||||
|
view.dispatch({
|
||||||
|
changes: { from: view.state.doc.length - excessLength, to: view.state.doc.length },
|
||||||
|
selection: { anchor: props.maxLength, head: props.maxLength }, // Place cursor at the end
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -353,6 +398,7 @@ onMounted(() => {
|
|||||||
addKeymap: false,
|
addKeymap: false,
|
||||||
}),
|
}),
|
||||||
keymap.of(historyKeymap),
|
keymap.of(historyKeymap),
|
||||||
|
cm_placeholder(props.placeholder || ''),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -532,6 +578,9 @@ const handleImageUpload = async (files: FileList) => {
|
|||||||
linkUrl.value = url
|
linkUrl.value = url
|
||||||
validateURL()
|
validateURL()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
linkValidationErrorMessage.value = error.message
|
||||||
|
}
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -577,7 +626,7 @@ function openVideoModal() {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="scss">
|
||||||
.display-options {
|
.display-options {
|
||||||
margin-bottom: var(--gap-sm);
|
margin-bottom: var(--gap-sm);
|
||||||
}
|
}
|
||||||
@ -586,7 +635,6 @@ function openVideoModal() {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
overflow: hidden;
|
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: var(--gap-sm);
|
margin-bottom: var(--gap-sm);
|
||||||
gap: var(--gap-xs);
|
gap: var(--gap-xs);
|
||||||
@ -635,6 +683,7 @@ function openVideoModal() {
|
|||||||
.info-blurb {
|
.info-blurb {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
gap: var(--gap-xs);
|
gap: var(--gap-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user