feat: image pasting

This commit is contained in:
Jannat Patel
2024-07-19 11:55:36 +05:30
parent 4bc3ac1665
commit 69d266e018
9 changed files with 106 additions and 21 deletions

View File

@@ -3,6 +3,9 @@
class="flex flex-col shadow hover:bg-gray-100 rounded-md p-4 h-full"
style="min-height: 150px"
>
<div class="text-xl font-semibold mb-2">
{{ batch.title }}
</div>
<Badge
v-if="batch.seat_count && batch.seats_left > 0"
theme="green"
@@ -19,9 +22,6 @@
>
{{ __('Sold Out') }}
</Badge>
<div class="text-xl font-semibold mb-1">
{{ batch.title }}
</div>
<div class="short-introduction">
{{ batch.description }}
</div>
@@ -29,23 +29,26 @@
<div v-if="batch.amount" class="font-semibold text-lg">
{{ batch.price }}
</div>
<div class="flex items-center">
<BookOpen class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />
<!-- <div class="flex items-center text-sm text-gray-700">
<BookOpen class="h-3 w-3 stroke-1.5 mr-2 text-gray-700" />
<span> {{ batch.courses.length }} {{ __('Courses') }} </span>
</div>
</div> -->
<DateRange
:startDate="batch.start_date"
:endDate="batch.end_date"
class="mb-3"
class="text-sm text-gray-700 mb-3"
/>
<div class="flex items-center">
<div class="flex items-center text-sm text-gray-700">
<Clock class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />
<span>
{{ formatTime(batch.start_time) }} - {{ formatTime(batch.end_time) }}
</span>
</div>
<div v-if="batch.timezone" class="flex items-center">
<Globe class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />
<div
v-if="batch.timezone"
class="flex items-center text-sm text-gray-700"
>
<Globe class="h-4 w-4 stroke-1.5 mr-2 text-gray-600" />
<span>
{{ batch.timezone }}
</span>

View File

@@ -43,7 +43,7 @@
<div
v-show="openInstructorEditor"
id="instructor-notes"
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal mt-6 py-3"
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal py-3"
></div>
</div>
</div>
@@ -54,7 +54,7 @@
</label>
<div
id="content"
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal mt-6 py-3"
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-gray-300 prose-th:border-gray-300 prose-td:relative prose-th:relative prose-th:bg-gray-100 prose-sm max-w-none !whitespace-normal py-3"
></div>
</div>
</div>
@@ -439,7 +439,8 @@ const pageMeta = computed(() => {
updateDocumentTitle(pageMeta)
</script>
<style>
.embed-tool__caption {
.embed-tool__caption,
.cdx-simple-image__caption {
display: none;
}

View File

@@ -0,0 +1,64 @@
class ImageTool {
constructor({ data, api }) {
this.api = api
this.data = data
this.wrapper = undefined
}
static get toolbox() {
return {
title: 'Image',
icon: '<svg width="18" height="18" viewBox="0 0 24 24"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zm-2 0H5V5h14v14zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/></svg>',
}
}
render() {
this.wrapper = document.createElement('div')
this.wrapper.classList.add('image-tool')
if (this.data && this.data.url) {
this._createImage(this.data.url)
}
this.wrapper.addEventListener('paste', (event) => {
this._handlePaste(event)
})
return this.wrapper
}
_createImage(url) {
const image = document.createElement('img')
image.src = url
image.classList.add('image-tool__image')
this.wrapper.innerHTML = ''
this.wrapper.appendChild(image)
const resizeObserver = new ResizeObserver(() => {
image.style.width = `${this.wrapper.clientWidth}px`
})
resizeObserver.observe(this.wrapper)
}
_handlePaste(event) {
const clipboardData = event.clipboardData || window.clipboardData
const items = clipboardData.items
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
const file = items[i].getAsFile()
const reader = new FileReader()
reader.onload = (e) => {
this._createImage(e.target.result)
}
reader.readAsDataURL(file)
}
}
}
save(blockContent) {
const img = blockContent.querySelector('img')
return {
url: img.src,
}
}
}

View File

@@ -4,13 +4,13 @@ import { Quiz } from '@/utils/quiz'
import { Upload } from '@/utils/upload'
import Header from '@editorjs/header'
import Paragraph from '@editorjs/paragraph'
import Image from '@editorjs/image'
import { CodeBox } from '@/utils/code'
import NestedList from '@editorjs/nested-list'
import InlineCode from '@editorjs/inline-code'
import { watch } from 'vue'
import dayjs from '@/utils/dayjs'
import Embed from '@editorjs/embed'
import SimpleImage from '@editorjs/simple-image'
export function createToast(options) {
toast({
@@ -137,7 +137,7 @@ export function getEditorTools() {
header: Header,
quiz: Quiz,
upload: Upload,
image: Image,
image: SimpleImage,
paragraph: {
class: Paragraph,
inlineToolbar: true,
@@ -173,7 +173,7 @@ export function getEditorTools() {
regex: /(?:https?:\/\/)?(?:www\.)?(?:(?:youtu\.be\/)|(?:youtube\.com)\/(?:v\/|u\/\w\/|embed\/|watch))(?:(?:\?v=)?([^#&?=]*))?((?:[?&]\w*=\w*)*)/,
embedUrl:
'https://www.youtube.com/embed/<%= remote_id %>',
html: '<iframe style="width:100%;" height="320" frameborder="0" allowfullscreen></iframe>',
html: '<iframe style="width:100%; height: 30rem;" frameborder="0" allowfullscreen></iframe>',
height: 320,
width: 580,
id: ([id, params]) => {
@@ -181,7 +181,7 @@ export function getEditorTools() {
return id
}
const paramsMap: Record<string, string> = {
const paramsMap = {
start: 'start',
end: 'end',
t: 'start',
@@ -227,7 +227,7 @@ export function getEditorTools() {
regex: /(?:http[s]?:\/\/)?(?:www.)?aparat\.com\/v\/([^\/\?\&]+)\/?/,
embedUrl:
'https://www.aparat.com/video/video/embed/videohash/<%= remote_id %>/vt/frame',
html: '<iframe width="600" height="300" style="margin: 0 auto;" frameborder="0" scrolling="no" allowtransparency="true"></iframe>',
html: '<iframe style="margin: 0 auto; width: 100%; height: 25rem;" frameborder="0" scrolling="no" allowtransparency="true"></iframe>',
height: 300,
width: 600,
},