feat: image pasting
This commit is contained in:
@@ -14,10 +14,10 @@
|
|||||||
"@editorjs/editorjs": "^2.29.0",
|
"@editorjs/editorjs": "^2.29.0",
|
||||||
"@editorjs/embed": "^2.7.0",
|
"@editorjs/embed": "^2.7.0",
|
||||||
"@editorjs/header": "^2.8.1",
|
"@editorjs/header": "^2.8.1",
|
||||||
"@editorjs/image": "^2.9.2",
|
|
||||||
"@editorjs/inline-code": "^1.5.0",
|
"@editorjs/inline-code": "^1.5.0",
|
||||||
"@editorjs/nested-list": "^1.4.2",
|
"@editorjs/nested-list": "^1.4.2",
|
||||||
"@editorjs/paragraph": "^2.11.3",
|
"@editorjs/paragraph": "^2.11.3",
|
||||||
|
"@editorjs/simple-image": "^1.6.0",
|
||||||
"chart.js": "^4.4.1",
|
"chart.js": "^4.4.1",
|
||||||
"dayjs": "^1.11.6",
|
"dayjs": "^1.11.6",
|
||||||
"feather-icons": "^4.28.0",
|
"feather-icons": "^4.28.0",
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
class="flex flex-col shadow hover:bg-gray-100 rounded-md p-4 h-full"
|
class="flex flex-col shadow hover:bg-gray-100 rounded-md p-4 h-full"
|
||||||
style="min-height: 150px"
|
style="min-height: 150px"
|
||||||
>
|
>
|
||||||
|
<div class="text-xl font-semibold mb-2">
|
||||||
|
{{ batch.title }}
|
||||||
|
</div>
|
||||||
<Badge
|
<Badge
|
||||||
v-if="batch.seat_count && batch.seats_left > 0"
|
v-if="batch.seat_count && batch.seats_left > 0"
|
||||||
theme="green"
|
theme="green"
|
||||||
@@ -19,9 +22,6 @@
|
|||||||
>
|
>
|
||||||
{{ __('Sold Out') }}
|
{{ __('Sold Out') }}
|
||||||
</Badge>
|
</Badge>
|
||||||
<div class="text-xl font-semibold mb-1">
|
|
||||||
{{ batch.title }}
|
|
||||||
</div>
|
|
||||||
<div class="short-introduction">
|
<div class="short-introduction">
|
||||||
{{ batch.description }}
|
{{ batch.description }}
|
||||||
</div>
|
</div>
|
||||||
@@ -29,23 +29,26 @@
|
|||||||
<div v-if="batch.amount" class="font-semibold text-lg">
|
<div v-if="batch.amount" class="font-semibold text-lg">
|
||||||
{{ batch.price }}
|
{{ batch.price }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<!-- <div class="flex items-center text-sm text-gray-700">
|
||||||
<BookOpen class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />
|
<BookOpen class="h-3 w-3 stroke-1.5 mr-2 text-gray-700" />
|
||||||
<span> {{ batch.courses.length }} {{ __('Courses') }} </span>
|
<span> {{ batch.courses.length }} {{ __('Courses') }} </span>
|
||||||
</div>
|
</div> -->
|
||||||
<DateRange
|
<DateRange
|
||||||
:startDate="batch.start_date"
|
:startDate="batch.start_date"
|
||||||
:endDate="batch.end_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" />
|
<Clock class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />
|
||||||
<span>
|
<span>
|
||||||
{{ formatTime(batch.start_time) }} - {{ formatTime(batch.end_time) }}
|
{{ formatTime(batch.start_time) }} - {{ formatTime(batch.end_time) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="batch.timezone" class="flex items-center">
|
<div
|
||||||
<Globe class="h-4 w-4 stroke-1.5 mr-2 text-gray-700" />
|
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>
|
<span>
|
||||||
{{ batch.timezone }}
|
{{ batch.timezone }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
<div
|
<div
|
||||||
v-show="openInstructorEditor"
|
v-show="openInstructorEditor"
|
||||||
id="instructor-notes"
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
id="content"
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -439,7 +439,8 @@ const pageMeta = computed(() => {
|
|||||||
updateDocumentTitle(pageMeta)
|
updateDocumentTitle(pageMeta)
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.embed-tool__caption {
|
.embed-tool__caption,
|
||||||
|
.cdx-simple-image__caption {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
64
frontend/src/utils/image.js
Normal file
64
frontend/src/utils/image.js
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,13 +4,13 @@ import { Quiz } from '@/utils/quiz'
|
|||||||
import { Upload } from '@/utils/upload'
|
import { Upload } from '@/utils/upload'
|
||||||
import Header from '@editorjs/header'
|
import Header from '@editorjs/header'
|
||||||
import Paragraph from '@editorjs/paragraph'
|
import Paragraph from '@editorjs/paragraph'
|
||||||
import Image from '@editorjs/image'
|
|
||||||
import { CodeBox } from '@/utils/code'
|
import { CodeBox } from '@/utils/code'
|
||||||
import NestedList from '@editorjs/nested-list'
|
import NestedList from '@editorjs/nested-list'
|
||||||
import InlineCode from '@editorjs/inline-code'
|
import InlineCode from '@editorjs/inline-code'
|
||||||
import { watch } from 'vue'
|
import { watch } from 'vue'
|
||||||
import dayjs from '@/utils/dayjs'
|
import dayjs from '@/utils/dayjs'
|
||||||
import Embed from '@editorjs/embed'
|
import Embed from '@editorjs/embed'
|
||||||
|
import SimpleImage from '@editorjs/simple-image'
|
||||||
|
|
||||||
export function createToast(options) {
|
export function createToast(options) {
|
||||||
toast({
|
toast({
|
||||||
@@ -137,7 +137,7 @@ export function getEditorTools() {
|
|||||||
header: Header,
|
header: Header,
|
||||||
quiz: Quiz,
|
quiz: Quiz,
|
||||||
upload: Upload,
|
upload: Upload,
|
||||||
image: Image,
|
image: SimpleImage,
|
||||||
paragraph: {
|
paragraph: {
|
||||||
class: Paragraph,
|
class: Paragraph,
|
||||||
inlineToolbar: true,
|
inlineToolbar: true,
|
||||||
@@ -173,7 +173,7 @@ export function getEditorTools() {
|
|||||||
regex: /(?:https?:\/\/)?(?:www\.)?(?:(?:youtu\.be\/)|(?:youtube\.com)\/(?:v\/|u\/\w\/|embed\/|watch))(?:(?:\?v=)?([^#&?=]*))?((?:[?&]\w*=\w*)*)/,
|
regex: /(?:https?:\/\/)?(?:www\.)?(?:(?:youtu\.be\/)|(?:youtube\.com)\/(?:v\/|u\/\w\/|embed\/|watch))(?:(?:\?v=)?([^#&?=]*))?((?:[?&]\w*=\w*)*)/,
|
||||||
embedUrl:
|
embedUrl:
|
||||||
'https://www.youtube.com/embed/<%= remote_id %>',
|
'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,
|
height: 320,
|
||||||
width: 580,
|
width: 580,
|
||||||
id: ([id, params]) => {
|
id: ([id, params]) => {
|
||||||
@@ -181,7 +181,7 @@ export function getEditorTools() {
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
const paramsMap: Record<string, string> = {
|
const paramsMap = {
|
||||||
start: 'start',
|
start: 'start',
|
||||||
end: 'end',
|
end: 'end',
|
||||||
t: 'start',
|
t: 'start',
|
||||||
@@ -227,7 +227,7 @@ export function getEditorTools() {
|
|||||||
regex: /(?:http[s]?:\/\/)?(?:www.)?aparat\.com\/v\/([^\/\?\&]+)\/?/,
|
regex: /(?:http[s]?:\/\/)?(?:www.)?aparat\.com\/v\/([^\/\?\&]+)\/?/,
|
||||||
embedUrl:
|
embedUrl:
|
||||||
'https://www.aparat.com/video/video/embed/videohash/<%= remote_id %>/vt/frame',
|
'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,
|
height: 300,
|
||||||
width: 600,
|
width: 600,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,6 +27,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.0.5.tgz#d17f39b6a0497c6439f57dd42711817a3dd3679c"
|
resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.0.5.tgz#d17f39b6a0497c6439f57dd42711817a3dd3679c"
|
||||||
integrity sha512-s6H2KXhLz2rgbMZSkRm8dsMJvyUNZsEjxobBEg9ztdrb1B2H3pEzY6iTwI4XUPJWJ3c3qRKwV4TrO3J5jUdoQA==
|
integrity sha512-s6H2KXhLz2rgbMZSkRm8dsMJvyUNZsEjxobBEg9ztdrb1B2H3pEzY6iTwI4XUPJWJ3c3qRKwV4TrO3J5jUdoQA==
|
||||||
|
|
||||||
|
"@codexteam/icons@^0.0.6":
|
||||||
|
version "0.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.0.6.tgz#5553ada48dddf5940851ccc142cfe17835c36ad3"
|
||||||
|
integrity sha512-L7Q5PET8PjKcBT5wp7VR+FCjwCi5PUp7rd/XjsgQ0CI5FJz0DphyHGRILMuDUdCW2MQT9NHbVr4QP31vwAkS/A==
|
||||||
|
|
||||||
"@codexteam/icons@^0.3.0":
|
"@codexteam/icons@^0.3.0":
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.3.0.tgz#62380b4053d487a257de443864b5c72dafab95e6"
|
resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.3.0.tgz#62380b4053d487a257de443864b5c72dafab95e6"
|
||||||
@@ -91,6 +96,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@codexteam/icons" "^0.0.4"
|
"@codexteam/icons" "^0.0.4"
|
||||||
|
|
||||||
|
"@editorjs/simple-image@^1.6.0":
|
||||||
|
version "1.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@editorjs/simple-image/-/simple-image-1.6.0.tgz#711c3900e17845331d6667cf0fe91793a5557f84"
|
||||||
|
integrity sha512-WvdGfQPlozwZd3PXQrJnRXk6gEYbv1U2vRupYJ6lTd3/UsLInXYUX5jSFcnGB5ZMH3bd0JDZfcb4d4Sv1/1big==
|
||||||
|
dependencies:
|
||||||
|
"@codexteam/icons" "^0.0.6"
|
||||||
|
|
||||||
"@esbuild/aix-ppc64@0.20.2":
|
"@esbuild/aix-ppc64@0.20.2":
|
||||||
version "0.20.2"
|
version "0.20.2"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
|
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537"
|
||||||
|
|||||||
@@ -103,6 +103,7 @@
|
|||||||
"fieldname": "start_date",
|
"fieldname": "start_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
|
"in_standard_filter": 1,
|
||||||
"label": "Start Date",
|
"label": "Start Date",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
@@ -127,6 +128,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "start_time",
|
"fieldname": "start_time",
|
||||||
"fieldtype": "Time",
|
"fieldtype": "Time",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Start Time",
|
"label": "Start Time",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
@@ -165,6 +167,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "category",
|
"fieldname": "category",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
"in_standard_filter": 1,
|
||||||
"label": "Category",
|
"label": "Category",
|
||||||
"options": "LMS Category"
|
"options": "LMS Category"
|
||||||
},
|
},
|
||||||
@@ -325,7 +328,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2024-06-24 16:24:45.536453",
|
"modified": "2024-07-18 18:06:37.229885",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Batch",
|
"name": "LMS Batch",
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ class LMSBatch(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
args = {
|
args = {
|
||||||
|
"title": self.title,
|
||||||
"student_name": student.student_name,
|
"student_name": student.student_name,
|
||||||
"start_time": self.start_time,
|
"start_time": self.start_time,
|
||||||
"start_date": self.start_date,
|
"start_date": self.start_date,
|
||||||
|
|||||||
@@ -8,10 +8,11 @@
|
|||||||
<br>
|
<br>
|
||||||
<p>
|
<p>
|
||||||
<b>
|
<b>
|
||||||
{{ _("Important Details:") }}
|
{{ title }}
|
||||||
</b>
|
</b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<b>{{ _("Batch Start Date:") }}</b> {{ frappe.utils.format_date(start_date, "medium") }}
|
<b>{{ _("Batch Start Date:") }}</b> {{ frappe.utils.format_date(start_date, "medium") }}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user