Requested Changes for Editor Knossos Implementation (#129)

* placeholder

* max length & placeholder

* Accept editor comment conflict

* integrate requested features

* null check for ref

* Change prompt for image upload

* change filter for proper input blocking

* Add spoiler button

* change url of helper link

* shallow resource link style

* resource link inherit site style

* detach preview styling from markdown-body style

* remove sizing dependance on global styles

* Bump 0.6.5
This commit is contained in:
Carter
2023-10-30 16:59:43 -07:00
committed by GitHub
parent 544111846c
commit 39a4297168
10 changed files with 130 additions and 47 deletions

View File

@@ -21,3 +21,25 @@
<UploadIcon /> <UploadIcon />
</FileInput> </FileInput>
``` ```
## Long Style
<DemoContainer>
<FileInput
:max-size="262144"
accept="image/png,image/jpeg,image/gif,image/webp"
long-style
class="btn"
prompt="Upload icon"
/>
</DemoContainer>
```vue
<FileInput
:max-size="262144"
accept="image/png,image/jpeg,image/gif,image/webp"
long-style
class="btn"
prompt="Upload icon"
/>
```

View File

@@ -30,7 +30,7 @@ const description = ref(null)
## With options ## With options
<DemoContainer> <DemoContainer>
<MarkdownEditor v-model="description1" placeholder="Enter a description" max-length="30" /> <MarkdownEditor v-model="description1" placeholder="Enter a description" max-length="800" max-height="400" />
</DemoContainer> </DemoContainer>
```vue ```vue
@@ -39,7 +39,7 @@ import { ref } from "vue";
const description = ref(null) const description = ref(null)
</script> </script>
<MarkdownEditor v-model="description" placeholder="Enter a description" max-length="30" /> <MarkdownEditor v-model="description" placeholder="Enter a description" max-length="800" max-height="400" />
``` ```
## With image upload ## With image upload

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-scan-eye"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="1"/><path d="M5 12s2.5-5 7-5 7 5 7 5-2.5 5-7 5-7-5-7-5"/></svg>

After

Width:  |  Height:  |  Size: 444 B

View File

@@ -883,7 +883,7 @@ a,
a { a {
cursor: pointer; cursor: pointer;
color: var(--color-blue); color: var(--color-link);
&:focus-visible, &:focus-visible,
&:hover { &:hover {

View File

@@ -191,6 +191,7 @@ const isChildOfDropdown = (element) => {
<style lang="scss" scoped> <style lang="scss" scoped>
.animated-dropdown { .animated-dropdown {
width: 20rem; width: 20rem;
min-height: 40px;
position: relative; position: relative;
display: inline-block; display: inline-block;
@@ -199,6 +200,9 @@ const isChildOfDropdown = (element) => {
} }
.selected { .selected {
height: 100%;
width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;

View File

@@ -100,7 +100,7 @@ label {
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
border: dashed 0.3rem var(--color-contrast); border: dashed 0.3rem var(--color-contrast);
cursor: pointer; cursor: pointer;
color: var(--color-accent-contrast); color: var(--color-contrast);
} }
} }
</style> </style>

View File

@@ -37,11 +37,13 @@
<span class="label__title">Preview</span> <span class="label__title">Preview</span>
<span class="label__description"></span> <span class="label__description"></span>
</span> </span>
<div <div class="markdown-body-wrapper">
style="width: 100%" <div
class="markdown-body" style="width: 100%"
v-html="renderHighlightedString(linkMarkdown)" class="markdown-body"
/> v-html="renderHighlightedString(linkMarkdown)"
/>
</div>
<div class="input-group push-right"> <div class="input-group push-right">
<Button :action="() => linkModal?.hide()"><XIcon /> Cancel</Button> <Button :action="() => linkModal?.hide()"><XIcon /> Cancel</Button>
<Button <Button
@@ -86,13 +88,14 @@
</div> </div>
<div <div
v-if="props.onImageUpload && imageUploadOption === 'upload'" v-if="props.onImageUpload && imageUploadOption === 'upload'"
class="iconified-input btn-input-alternative" class="btn-input-alternative"
> >
<FileInput <FileInput
accept="image/png,image/jpeg,image/gif,image/webp" accept="image/png,image/jpeg,image/gif,image/webp"
prompt="Upload an image" prompt="Drag and drop to upload or click to select file"
class="btn" long-style
should-always-reset should-always-reset
class="file-input"
@change="handleImageUpload" @change="handleImageUpload"
> >
<UploadIcon /> <UploadIcon />
@@ -121,11 +124,13 @@
<span class="label__title">Preview</span> <span class="label__title">Preview</span>
<span class="label__description"></span> <span class="label__description"></span>
</span> </span>
<div <div class="markdown-body-wrapper">
style="width: 100%" <div
class="markdown-body" style="width: 100%"
v-html="renderHighlightedString(imageMarkdown)" class="markdown-body"
/> v-html="renderHighlightedString(imageMarkdown)"
/>
</div>
<div class="input-group push-right"> <div class="input-group push-right">
<Button :action="() => imageModal?.hide()"><XIcon /> Cancel</Button> <Button :action="() => imageModal?.hide()"><XIcon /> Cancel</Button>
<Button <Button
@@ -172,11 +177,14 @@
<span class="label__title">Preview</span> <span class="label__title">Preview</span>
<span class="label__description"></span> <span class="label__description"></span>
</span> </span>
<div
style="width: 100%" <div class="markdown-body-wrapper">
class="markdown-body" <div
v-html="renderHighlightedString(videoMarkdown)" style="width: 100%"
/> class="markdown-body"
v-html="renderHighlightedString(videoMarkdown)"
/>
</div>
<div class="input-group push-right"> <div class="input-group push-right">
<Button :action="() => videoModal?.hide()"><XIcon /> Cancel</Button> <Button :action="() => videoModal?.hide()"><XIcon /> Cancel</Button>
<Button <Button
@@ -227,22 +235,30 @@
<InfoIcon /> <InfoIcon />
<span <span
>This editor supports >This editor supports
<a class="link" href="https://docs.modrinth.com/docs/markdown" target="_blank" <a
class="markdown-resource-link"
href="https://docs.modrinth.com/markdown"
target="_blank"
>Markdown formatting</a >Markdown formatting</a
>.</span >.</span
> >
</div> </div>
<div :class="{ hide: !props.maxLength }" class="max-length-label"> <div :class="{ hide: !props.maxLength }" class="max-length-label">
<span>Max length: </span> <span>Max length: </span>
<span>{{ props.maxLength ?? 'Unlimited' }}</span> <span>
{{ props.maxLength ? `${currentValue?.length || 0}/${props.maxLength}` : 'Unlimited' }}
</span>
</div>
</div>
<div v-if="previewMode">
<div class="markdown-body-wrapper">
<div
style="width: 100%"
class="markdown-body"
v-html="renderHighlightedString(currentValue ?? '')"
/>
</div> </div>
</div> </div>
<div
v-if="previewMode"
style="width: 100%"
class="markdown-body"
v-html="renderHighlightedString(currentValue ?? '')"
/>
</div> </div>
</template> </template>
@@ -260,6 +276,7 @@ import {
Heading3Icon, Heading3Icon,
BoldIcon, BoldIcon,
ItalicIcon, ItalicIcon,
ScanEyeIcon,
StrikethroughIcon, StrikethroughIcon,
CodeIcon, CodeIcon,
ListBulletedIcon, ListBulletedIcon,
@@ -294,14 +311,16 @@ const props = withDefaults(
onImageUpload?: (file: File) => Promise<string> onImageUpload?: (file: File) => Promise<string>
placeholder?: string placeholder?: string
maxLength?: number maxLength?: number
maxHeight?: number
}>(), }>(),
{ {
modelValue: '', modelValue: '',
disabled: false, disabled: false,
headingButtons: true, headingButtons: true,
onImageUpload: undefined, onImageUpload: undefined,
placeholder: undefined, placeholder: 'Write something...',
maxLength: undefined, maxLength: undefined,
maxHeight: undefined,
} }
) )
@@ -320,13 +339,15 @@ onMounted(() => {
const theme = EditorView.theme({ const theme = EditorView.theme({
// in defualts.scss there's references to .cm-content and such to inherit global styles // in defualts.scss there's references to .cm-content and such to inherit global styles
'.cm-content, .cm-gutter': { '.cm-content': {
marginBlockEnd: '0.5rem', marginBlockEnd: '0.5rem',
padding: '0.5rem', padding: '0.5rem',
minHeight: '200px', minHeight: '200px',
caretColor: 'var(--color-contrast)', caretColor: 'var(--color-contrast)',
width: '100%', width: '100%',
overflowX: 'scroll', overflowX: 'scroll',
maxHeight: props.maxHeight ? `${props.maxHeight}px` : 'unset',
overflowY: 'scroll',
}, },
'.cm-scroller': { '.cm-scroller': {
height: '100%', height: '100%',
@@ -359,19 +380,6 @@ onMounted(() => {
return false 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) => { blur: (_, view) => {
if (props.maxLength && view.state.doc.length > props.maxLength) { if (props.maxLength && view.state.doc.length > props.maxLength) {
// Calculate how many characters to remove from the end // Calculate how many characters to remove from the end
@@ -385,6 +393,13 @@ onMounted(() => {
}, },
}) })
const inputFilter = EditorState.changeFilter.of((transaction) => {
if (props.maxLength && transaction.newDoc.length > props.maxLength) {
return false
}
return true
})
const editorState = EditorState.create({ const editorState = EditorState.create({
doc: props.modelValue, doc: props.modelValue,
extensions: [ extensions: [
@@ -399,6 +414,7 @@ onMounted(() => {
}), }),
keymap.of(historyKeymap), keymap.of(historyKeymap),
cm_placeholder(props.placeholder || ''), cm_placeholder(props.placeholder || ''),
inputFilter,
], ],
}) })
@@ -468,6 +484,7 @@ const BUTTONS: ButtonGroupMap = {
markdownCommands.toggleStrikethrough markdownCommands.toggleStrikethrough
), ),
composeCommandButton('Code', CodeIcon, markdownCommands.toggleCodeBlock), composeCommandButton('Code', CodeIcon, markdownCommands.toggleCodeBlock),
composeCommandButton('Spoiler', ScanEyeIcon, markdownCommands.toggleSpoiler),
], ],
}, },
lists: { lists: {
@@ -627,6 +644,38 @@ function openVideoModal() {
</script> </script>
<style lang="scss"> <style lang="scss">
.file-input {
width: 100%;
padding: 1.5rem;
padding-left: 2.5rem;
background: var(--color-button-bg);
border: 2px dashed var(--color-gray);
border-radius: var(--radius-md);
cursor: pointer;
transition: opacity 0.5s ease-in-out, filter 0.2s ease-in-out, scale 0.05s ease-in-out,
outline 0.2s ease-in-out;
&:hover {
filter: brightness(0.85);
}
}
.markdown-resource-link {
cursor: pointer;
color: var(--color-link);
&:focus-visible,
&:hover {
filter: brightness(1.2);
text-decoration: none;
}
&:active {
filter: brightness(1.1);
text-decoration: none;
}
}
.display-options { .display-options {
margin-bottom: var(--gap-sm); margin-bottom: var(--gap-sm);
} }
@@ -698,9 +747,10 @@ function openVideoModal() {
gap: var(--gap-xs); gap: var(--gap-xs);
} }
.markdown-body { .markdown-body-wrapper {
border: 1px solid var(--color-button-bg); border: 1px solid var(--color-button-bg);
border-radius: var(--radius-md); border-radius: var(--radius-md);
width: 100%;
padding: var(--radius-md); padding: var(--radius-md);
min-height: 6rem; min-height: 6rem;
} }

View File

@@ -152,6 +152,7 @@ export { default as ReportIcon } from '@/assets/icons/report.svg?component'
export { default as RightArrowIcon } from '@/assets/icons/right-arrow.svg?component' export { default as RightArrowIcon } from '@/assets/icons/right-arrow.svg?component'
export { default as SaveIcon } from '@/assets/icons/save.svg?component' export { default as SaveIcon } from '@/assets/icons/save.svg?component'
export { default as ScaleIcon } from '@/assets/icons/scale.svg?component' export { default as ScaleIcon } from '@/assets/icons/scale.svg?component'
export { default as ScanEyeIcon } from '@/assets/icons/scan-eye.svg?component'
export { default as SearchIcon } from '@/assets/icons/search.svg?component' export { default as SearchIcon } from '@/assets/icons/search.svg?component'
export { default as SendIcon } from '@/assets/icons/send.svg?component' export { default as SendIcon } from '@/assets/icons/send.svg?component'
export { default as ServerIcon } from '@/assets/icons/server.svg?component' export { default as ServerIcon } from '@/assets/icons/server.svg?component'

View File

@@ -22,6 +22,10 @@ const toggleCodeBlock: Command = ({ state, dispatch }) => {
return toggleAround(state, dispatch, codeBlockMark, codeBlockMark) return toggleAround(state, dispatch, codeBlockMark, codeBlockMark)
} }
const toggleSpoiler: Command = ({ state, dispatch }) => {
return toggleAround(state, dispatch, '||', '||')
}
const toggleHeader: Command = ({ state, dispatch }) => { const toggleHeader: Command = ({ state, dispatch }) => {
return toggleLineStart(state, dispatch, '# ') return toggleLineStart(state, dispatch, '# ')
} }
@@ -342,6 +346,7 @@ const commands = {
toggleItalic, toggleItalic,
toggleStrikethrough, toggleStrikethrough,
toggleCodeBlock, toggleCodeBlock,
toggleSpoiler,
toggleHeader, toggleHeader,
toggleHeader2, toggleHeader2,
toggleHeader3, toggleHeader3,

View File

@@ -1,7 +1,7 @@
{ {
"name": "omorphia", "name": "omorphia",
"type": "module", "type": "module",
"version": "0.6.4", "version": "0.6.5",
"files": [ "files": [
"dist" "dist"
], ],