Last active
April 30, 2024 15:39
-
-
Save fazlurr/5b942b608160197df12a8cd7a99f8198 to your computer and use it in GitHub Desktop.
tiptap alignment
And custom image handler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Mark } from 'tiptap'; | |
import { updateMark, markInputRule } from 'tiptap-commands'; | |
export default class Align extends Mark { | |
// eslint-disable-next-line class-methods-use-this | |
get name() { | |
return 'align'; | |
} | |
// eslint-disable-next-line class-methods-use-this | |
get schema() { | |
return { | |
attrs: { | |
textAlign: { | |
default: 'left', | |
}, | |
}, | |
parseDOM: [ | |
{ | |
style: 'text-align', | |
getAttrs: value => ({ textAlign: value }), | |
}, | |
], | |
toDOM: mark => ['span', { style: `text-align: ${mark.attrs.textAlign};display: block` }, 0], | |
}; | |
} | |
// eslint-disable-next-line class-methods-use-this | |
commands({ type }) { | |
return attrs => updateMark(type, attrs); | |
} | |
// eslint-disable-next-line class-methods-use-this | |
inputRules({ type }) { | |
return [ | |
markInputRule(/(?:\*\*|__)([^*_]+)(?:\*\*|__)$/, type), | |
]; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<!-- WYSIWYG Editor --> | |
<div class="editor mb-4"> | |
<editor-menu-bar class="editor-bar" :editor="editor"> | |
<div slot-scope="{ commands, isActive, focused, getMarkAttrs }"> | |
<!-- Image --> | |
<label | |
class="btn btn-plain mb-0" | |
:class="{ 'is-loading': isUploading }" | |
v-tooltip="'Add Image'"> | |
<i class="material-icons m-0">image</i> | |
<input type="file" class="hidden" @change="onImageChange(commands.image, ...arguments)"> | |
</label> | |
<!-- Bold --> | |
<button | |
type="button" | |
class="btn btn-plain" | |
:class="{ 'is-active': isActive.bold() }" | |
@click="commands.bold" | |
v-tooltip="'Bold'"> | |
<i class="material-icons">format_bold</i> | |
</button> | |
<!-- Italic --> | |
<button | |
type="button" | |
class="btn btn-plain" | |
:class="{ 'is-active': isActive.italic() }" | |
@click="commands.italic" | |
v-tooltip="'Italic'"> | |
<i class="material-icons">format_italic</i> | |
</button> | |
<!-- Underline --> | |
<button | |
type="button" | |
class="btn btn-plain" | |
:class="{ 'is-active': isActive.underline() }" | |
@click="commands.underline" | |
v-tooltip="'Underline'"> | |
<i class="material-icons">format_underline</i> | |
</button> | |
<!-- Align - Left --> | |
<button | |
type="button" | |
class="btn btn-plain" | |
:class="{ 'is-active': getMarkAttrs('align') && getMarkAttrs('align').textAlign === 'left' }" | |
@click="commands.align({ textAlign: 'left' })" | |
v-tooltip="'Align Left'"> | |
<i class="material-icons">format_align_left</i> | |
</button> | |
<!-- Align - Center --> | |
<button | |
type="button" | |
class="btn btn-plain" | |
:class="{ 'is-active': getMarkAttrs('align') && getMarkAttrs('align').textAlign === 'center' }" | |
@click="commands.align({ textAlign: 'center' })" | |
v-tooltip="'Align Center'"> | |
<i class="material-icons">format_align_center</i> | |
</button> | |
<!-- Algin - Right --> | |
<button | |
type="button" | |
class="btn btn-plain" | |
:class="{ 'is-active': getMarkAttrs('align') && getMarkAttrs('align').textAlign === 'right' }" | |
@click="commands.align({ textAlign: 'right' })" | |
v-tooltip="'Align Right'"> | |
<i class="material-icons">format_align_right</i> | |
</button> | |
<!-- P --> | |
<button | |
type="button" | |
class="btn btn-plain hidden" | |
:class="{ 'is-active': focused && isActive.paragraph() }" | |
@click="commands.paragraph()" | |
v-tooltip="'Paragraph'"> | |
<span class="text">P</span> | |
</button> | |
<!-- H1 --> | |
<button | |
type="button" | |
class="btn btn-plain" | |
:class="{ 'is-active': isActive.heading({ level: 1 }) }" | |
@click="commands.heading({ level: 1 })" | |
v-tooltip="'Headline 1'"> | |
<span class="text">H1</span> | |
</button> | |
<!-- H2 --> | |
<button | |
type="button" | |
class="btn btn-plain" | |
:class="{ 'is-active': isActive.heading({ level: 2 }) }" | |
@click="commands.heading({ level: 2 })" | |
v-tooltip="'Headline 2'"> | |
<span class="text">H2</span> | |
</button> | |
<!-- Bullet List --> | |
<button | |
type="button" | |
class="btn btn-plain" | |
:class="{ 'is-active': isActive.bullet_list() }" | |
@click="commands.bullet_list()" | |
v-tooltip="'Bullet List'"> | |
<i class="material-icons">format_list_bulleted</i> | |
</button> | |
<!-- Bullet List --> | |
<button | |
type="button" | |
class="btn btn-plain" | |
:class="{ 'is-active': isActive.ordered_list() }" | |
@click="commands.ordered_list()" | |
v-tooltip="'Number List'"> | |
<i class="material-icons">format_list_numbered</i> | |
</button> | |
</div> | |
</editor-menu-bar> | |
<editor-content class="editor-content" :editor="editor" /> | |
</div> | |
</template> | |
<script> | |
import { | |
Editor, | |
EditorContent, | |
EditorMenuBar, | |
} from 'tiptap'; | |
import { | |
// Blockquote, | |
// CodeBlock, | |
// HardBreak, | |
Heading, | |
OrderedList, | |
BulletList, | |
ListItem, | |
TodoItem, | |
TodoList, | |
Bold, | |
Code, | |
Italic, | |
Link, | |
Strike, | |
Underline, | |
History, | |
Image, | |
} from 'tiptap-extensions'; | |
import mediaApi from '@/api/media'; | |
import Align from '@/lib/tiptap/align'; | |
export default { | |
name: 'ContentEditor', | |
props: { | |
value: { | |
type: String, | |
default: '', | |
}, | |
}, | |
components: { | |
EditorContent, | |
EditorMenuBar, | |
}, | |
data() { | |
return { | |
editor: null, | |
isUploading: false, | |
}; | |
}, | |
methods: { | |
initEditor() { | |
const content = this.value; | |
const extensions = [ | |
// new Blockquote(), | |
// new CodeBlock(), | |
// new HardBreak(), | |
new Heading({ levels: [1, 2, 3] }), | |
new BulletList(), | |
new OrderedList(), | |
new ListItem(), | |
new TodoItem(), | |
new TodoList(), | |
new Bold(), | |
new Code(), | |
new Italic(), | |
new Link(), | |
new Strike(), | |
new Underline(), | |
new History(), | |
new Image(), | |
new Align(), | |
]; | |
this.editor = new Editor({ | |
content, | |
extensions, | |
onUpdate: this.onUpdate, | |
onFocus: this.onFocus, | |
}); | |
}, | |
onUpdate(editor) { | |
const content = editor.getHTML(); | |
this.$emit('input', content); | |
}, | |
onImageChange(command, event) { | |
const files = event.target.files; | |
if (files.length > 0) { | |
const file = files[0]; | |
const uploadParams = new FormData(); | |
uploadParams.append('image', file); | |
uploadParams.append('resize', false); | |
this.isUploading = true; | |
const callback = (response) => { | |
const path = response.data; | |
const originalPath = path.replace('.', '-large.'); | |
const imageUrl = `${this.mediaUrlPrefix}/${originalPath}`; | |
command({ src: imageUrl }); | |
this.isUploading = false; | |
}; | |
const errorCallback = () => { | |
this.isUploading = false; | |
}; | |
// Upload Image | |
mediaApi.uploadImage(uploadParams, callback, errorCallback); | |
} | |
}, | |
insert(text) { | |
const { selection, state } = this.editor; | |
const { from, to } = selection; | |
const transaction = state.tr.insertText(text, from, to); | |
this.editor.view.dispatch(transaction); | |
// state.doc.textBetween(from, to, null, text); | |
// const oldContent = this.editor.getHTML(); | |
// const newContent = oldContent.substring(0, from) + text + oldContent.substring(to, oldContent.length); | |
// this.$emit('input', newContent); | |
// this.editor.setContent(newContent, false); | |
}, | |
onFocus() { | |
this.$emit('focus'); | |
}, | |
}, | |
mounted() { | |
this.initEditor(); | |
}, | |
beforeDestroy() { | |
this.editor.destroy(); | |
}, | |
watch: { | |
// value(value) { | |
// this.content = value; | |
// if (this.editor) { | |
// this.editor.setContent(value, false); | |
// } | |
// }, | |
}, | |
}; | |
</script> | |
<style lang="scss"> | |
$color__accent: #fa962b; | |
.editor-bar { | |
margin-bottom: .5em; | |
.btn { | |
margin-right: .5em; | |
padding: .175rem .35rem; | |
min-width: 30px; | |
color: $color__accent; | |
&:hover { | |
color: #fff; | |
// background-color: #f5f5f5; | |
background-color: $color__accent; | |
} | |
&.is-active { | |
color: #fff; | |
// background-color: #656565; | |
background-color: $color__accent; | |
} | |
.material-icons { | |
font-size: 1.25rem; | |
} | |
} | |
} | |
.ProseMirror { | |
padding: 0.375rem 0.75rem; | |
max-height: 300px; | |
border-radius: 3px; | |
background-color: #fcfcfc; | |
border: 1px solid #d9dee2; | |
overflow: auto; | |
p { | |
margin-bottom: .5em; | |
} | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment