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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 130 additions and 47 deletions

View File

@ -21,3 +21,25 @@
<UploadIcon />
</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
<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>
```vue
@ -39,7 +39,7 @@ import { ref } from "vue";
const description = ref(null)
</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

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 {
cursor: pointer;
color: var(--color-blue);
color: var(--color-link);
&:focus-visible,
&:hover {

View File

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

View File

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

View File

@ -37,11 +37,13 @@
<span class="label__title">Preview</span>
<span class="label__description"></span>
</span>
<div
style="width: 100%"
class="markdown-body"
v-html="renderHighlightedString(linkMarkdown)"
/>
<div class="markdown-body-wrapper">
<div
style="width: 100%"
class="markdown-body"
v-html="renderHighlightedString(linkMarkdown)"
/>
</div>
<div class="input-group push-right">
<Button :action="() => linkModal?.hide()"><XIcon /> Cancel</Button>
<Button
@ -86,13 +88,14 @@
</div>
<div
v-if="props.onImageUpload && imageUploadOption === 'upload'"
class="iconified-input btn-input-alternative"
class="btn-input-alternative"
>
<FileInput
accept="image/png,image/jpeg,image/gif,image/webp"
prompt="Upload an image"
class="btn"
prompt="Drag and drop to upload or click to select file"
long-style
should-always-reset
class="file-input"
@change="handleImageUpload"
>
<UploadIcon />
@ -121,11 +124,13 @@
<span class="label__title">Preview</span>
<span class="label__description"></span>
</span>
<div
style="width: 100%"
class="markdown-body"
v-html="renderHighlightedString(imageMarkdown)"
/>
<div class="markdown-body-wrapper">
<div
style="width: 100%"
class="markdown-body"
v-html="renderHighlightedString(imageMarkdown)"
/>
</div>
<div class="input-group push-right">
<Button :action="() => imageModal?.hide()"><XIcon /> Cancel</Button>
<Button
@ -172,11 +177,14 @@
<span class="label__title">Preview</span>
<span class="label__description"></span>
</span>
<div
style="width: 100%"
class="markdown-body"
v-html="renderHighlightedString(videoMarkdown)"
/>
<div class="markdown-body-wrapper">
<div
style="width: 100%"
class="markdown-body"
v-html="renderHighlightedString(videoMarkdown)"
/>
</div>
<div class="input-group push-right">
<Button :action="() => videoModal?.hide()"><XIcon /> Cancel</Button>
<Button
@ -227,22 +235,30 @@
<InfoIcon />
<span
>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
>.</span
>
</div>
<div :class="{ hide: !props.maxLength }" class="max-length-label">
<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
v-if="previewMode"
style="width: 100%"
class="markdown-body"
v-html="renderHighlightedString(currentValue ?? '')"
/>
</div>
</template>
@ -260,6 +276,7 @@ import {
Heading3Icon,
BoldIcon,
ItalicIcon,
ScanEyeIcon,
StrikethroughIcon,
CodeIcon,
ListBulletedIcon,
@ -294,14 +311,16 @@ const props = withDefaults(
onImageUpload?: (file: File) => Promise<string>
placeholder?: string
maxLength?: number
maxHeight?: number
}>(),
{
modelValue: '',
disabled: false,
headingButtons: true,
onImageUpload: undefined,
placeholder: undefined,
placeholder: 'Write something...',
maxLength: undefined,
maxHeight: undefined,
}
)
@ -320,13 +339,15 @@ onMounted(() => {
const theme = EditorView.theme({
// in defualts.scss there's references to .cm-content and such to inherit global styles
'.cm-content, .cm-gutter': {
'.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',
},
'.cm-scroller': {
height: '100%',
@ -359,19 +380,6 @@ onMounted(() => {
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
@ -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({
doc: props.modelValue,
extensions: [
@ -399,6 +414,7 @@ onMounted(() => {
}),
keymap.of(historyKeymap),
cm_placeholder(props.placeholder || ''),
inputFilter,
],
})
@ -468,6 +484,7 @@ const BUTTONS: ButtonGroupMap = {
markdownCommands.toggleStrikethrough
),
composeCommandButton('Code', CodeIcon, markdownCommands.toggleCodeBlock),
composeCommandButton('Spoiler', ScanEyeIcon, markdownCommands.toggleSpoiler),
],
},
lists: {
@ -627,6 +644,38 @@ function openVideoModal() {
</script>
<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 {
margin-bottom: var(--gap-sm);
}
@ -698,9 +747,10 @@ function openVideoModal() {
gap: var(--gap-xs);
}
.markdown-body {
.markdown-body-wrapper {
border: 1px solid var(--color-button-bg);
border-radius: var(--radius-md);
width: 100%;
padding: var(--radius-md);
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 SaveIcon } from '@/assets/icons/save.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 SendIcon } from '@/assets/icons/send.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)
}
const toggleSpoiler: Command = ({ state, dispatch }) => {
return toggleAround(state, dispatch, '||', '||')
}
const toggleHeader: Command = ({ state, dispatch }) => {
return toggleLineStart(state, dispatch, '# ')
}
@ -342,6 +346,7 @@ const commands = {
toggleItalic,
toggleStrikethrough,
toggleCodeBlock,
toggleSpoiler,
toggleHeader,
toggleHeader2,
toggleHeader3,

View File

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