diff --git a/README.md b/README.md
index 4d7cb6c..4a4f45b 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,7 @@ If you have Vuetify `1.x` (not `2.x`), then you can find docs and demo [here](ht
- the project is ready to actively develop if there is support (stars)!
- the ability to create and use your own extensions
- choose where the extension buttons should be displayed: in the toolbar or in the bubble menu
+- support for custom image upload. You can use any method of upload through your Vue component.
- Vuetify `2.x` and `1.x` support
## Installation
@@ -163,7 +164,7 @@ export default {
```
-### CDN (
+
+
diff --git a/demo/pages/Index.vue b/demo/pages/Index.vue
index f476787..623b50d 100644
--- a/demo/pages/Index.vue
+++ b/demo/pages/Index.vue
@@ -27,6 +27,7 @@
+
+
diff --git a/src/extensions/nativeExtensions/image/ImageSource.ts b/src/extensions/nativeExtensions/image/ImageSource.ts
new file mode 100644
index 0000000..4744e73
--- /dev/null
+++ b/src/extensions/nativeExtensions/image/ImageSource.ts
@@ -0,0 +1,4 @@
+export default interface ImageSource {
+ src: null | string
+ alt: null | string
+}
diff --git a/src/extensions/nativeExtensions/image/ImageUploadArea.vue b/src/extensions/nativeExtensions/image/ImageUploadArea.vue
index 4ad8adb..df47023 100644
--- a/src/extensions/nativeExtensions/image/ImageUploadArea.vue
+++ b/src/extensions/nativeExtensions/image/ImageUploadArea.vue
@@ -22,7 +22,7 @@
-
Choose a file(s) or drag it here.
+ {{ $i18n.getMsg('extensions.Image.window.imageUpload.instruction') }}
@@ -32,10 +32,8 @@
import { mixins } from 'vue-class-component'
import { Component } from 'vue-property-decorator'
import I18nMixin from '~/mixins/I18nMixin'
+import EVENTS from '~/extensions/nativeExtensions/image/events'
-export const EVENTS = {
- SELECT_FILES: 'select-files' as const
-}
const HOLDER_CLASS = 'tiptap-vuetify-image-upload-area-holder'
@Component
@@ -46,7 +44,7 @@ export default class ImageUploadArea extends mixins(I18nMixin) {
input.addEventListener('change', e => {
if (e.target instanceof HTMLInputElement) {
- this.$emit(EVENTS.SELECT_FILES, e.target.files)
+ this.filesSelected(e.target.files)
holder.classList.remove(HOLDER_CLASS + '--dragover')
e.target.value = ''
@@ -66,7 +64,20 @@ export default class ImageUploadArea extends mixins(I18nMixin) {
holder.addEventListener('dragend', dragleaveOrEndHandler)
holder.addEventListener('drop', e => {
e.preventDefault()
- this.$emit(EVENTS.SELECT_FILES, e.dataTransfer!.files)
+ this.filesSelected(e.dataTransfer!.files)
+ })
+ }
+ filesSelected (files: HTMLInputElement['files']) {
+ [...files].forEach(file => {
+ const reader = new FileReader()
+ reader.addEventListener('load', readerEvent => {
+ // TODO URL.createObjectURL(file) and upload
+ this.$emit(EVENTS.SELECT_FILE, {
+ src: readerEvent.target!.result!.toString(),
+ alt: file.name
+ })
+ })
+ reader.readAsDataURL(file)
})
}
}
diff --git a/src/extensions/nativeExtensions/image/ImageWindow.vue b/src/extensions/nativeExtensions/image/ImageWindow.vue
index 910a997..f47d8ff 100644
--- a/src/extensions/nativeExtensions/image/ImageWindow.vue
+++ b/src/extensions/nativeExtensions/image/ImageWindow.vue
@@ -32,7 +32,8 @@
cols="4"
>
+
-
-
-
-
-
-
- {{ $i18n.getMsg('extensions.Image.window.or') }}
-
-
-
-
-
+
+
+
+ {{ imageTab.name }}
+
+
+
+
+
+
import { mixins } from 'vue-class-component'
import { Component, Prop } from 'vue-property-decorator'
-import { VRow, VCol, VImg, VDialog, VCard, VCardTitle, VCardText, VCardActions, VBtn, VSpacer, VIcon, VTextField } from 'vuetify/lib'
+import { VRow, VCol, VImg, VDialog, VCard, VCardTitle, VCardText, VCardActions, VBtn, VSpacer, VIcon, VTextField, VTabs, VTab, VTabsSlider, VTabItem, VTabsItems } from 'vuetify/lib'
import I18nMixin from '~/mixins/I18nMixin'
import ImageUploadArea from '~/extensions/nativeExtensions/image/ImageUploadArea.vue'
+import ImageForm from '~/extensions/nativeExtensions/image/ImageForm.vue'
+import ImageSource from '~/extensions/nativeExtensions/image/ImageSource'
import { VExpandTransition } from 'vuetify/lib/components/transitions'
import { COMMON_ICONS } from '~/configs/theme'
@@ -99,11 +111,13 @@ export const PROPS = {
VALUE: 'value' as const,
CONTEXT: 'context' as const,
EDITOR: 'editor' as const,
+ IMAGE_SOURCES: 'imageSources' as const,
+ IMAGE_SOURCES_OVERRIDE: 'imageSourcesOverride' as const,
NATIVE_EXTENSION_NAME: 'nativeExtensionName' as const
}
@Component({
- components: { VRow, VCol, VExpandTransition, ImageUploadArea, VImg, VDialog, VCard, VCardTitle, VCardText, VCardActions, VBtn, VSpacer, VIcon, VTextField }
+ components: { VRow, VCol, VExpandTransition, ImageForm, ImageUploadArea, VImg, VDialog, VCard, VCardTitle, VCardText, VCardActions, VBtn, VSpacer, VIcon, VTextField, VTabs, VTab, VTabsSlider, VTabItem, VTabsItems }
})
export default class ImageWindow extends mixins(I18nMixin) {
@Prop({
@@ -130,50 +144,81 @@ export default class ImageWindow extends mixins(I18nMixin) {
})
readonly [PROPS.EDITOR]: any
+ @Prop({
+ type: Array,
+ required: false
+ })
+ readonly [PROPS.IMAGE_SOURCES]: any
+
+ @Prop({
+ type: Boolean,
+ required: false
+ })
+ readonly [PROPS.IMAGE_SOURCES_OVERRIDE]: any
+
readonly COMMON_ICONS = COMMON_ICONS
- form: {
- src: null | string
- } = {
- src: null // 'https://www.nationalgeographic.com/content/dam/news/2018/05/17/you-can-train-your-cat/02-cat-training-NationalGeographic_1484324.jpg'
+ readonly defaultImageTabs = [
+ {
+ name: 'URL',
+ component: ImageForm
+ },
+ {
+ name: 'Upload',
+ component: ImageUploadArea
+ }
+ ]
+
+ inputPreviewSources: ImageSource[] = []
+
+ get imageTabs () {
+ if (this[PROPS.IMAGE_SOURCES]) {
+ if (this[PROPS.IMAGE_SOURCES_OVERRIDE]) {
+ return this[PROPS.IMAGE_SOURCES]
+ }
+ return this.defaultImageTabs.concat(this[PROPS.IMAGE_SOURCES])
+ }
+ return this.defaultImageTabs
}
- inputPreviewSources: string[] = []
get previewSources () {
- return [this.form.src, ...this.inputPreviewSources].filter(Boolean)
+ return this.inputPreviewSources.filter(Boolean)
}
get isDisabled () {
return !this.previewSources.length
}
- removeSource (source) {
+ removeSource (source: ImageSource) {
if (this.inputPreviewSources.includes(source)) {
this.inputPreviewSources = this.inputPreviewSources.filter(i => i !== source)
- } else if (this.form.src === source) {
- this.form.src = null
}
}
- onFilesSelect (files: HTMLInputElement['files']) {
- [...files].forEach(file => {
- const reader = new FileReader()
+ onFileSelect (file: ImageSource) {
+ if (file.src !== null && file.src !== '') {
+ const existingFile = this.findFile(file)
+ if (existingFile !== null) {
+ existingFile.alt = file.alt
+ } else {
+ this.inputPreviewSources.push(file)
+ }
+ }
+ }
- reader.addEventListener('load', readerEvent => {
- // TODO URL.createObjectURL(file) and upload
- this.inputPreviewSources.push(readerEvent.target!.result!.toString())
- })
- reader.readAsDataURL(file)
+ findFile (file: ImageSource) {
+ const matches: ImageSource[] = this.inputPreviewSources.filter((source: ImageSource) => {
+ return (source.src === file.src)
})
+ if (matches.length > 0) {
+ return matches[0]
+ }
+ return null
}
apply () {
this.previewSources.forEach(src => {
- this[PROPS.CONTEXT].commands[this[PROPS.NATIVE_EXTENSION_NAME]]({
- // TODO alt, title
- src,
- alt: 'Image'
- })
+ this[PROPS.CONTEXT].commands[this[PROPS.NATIVE_EXTENSION_NAME]](src)
})
this.close()
diff --git a/src/extensions/nativeExtensions/image/events.ts b/src/extensions/nativeExtensions/image/events.ts
new file mode 100644
index 0000000..8907653
--- /dev/null
+++ b/src/extensions/nativeExtensions/image/events.ts
@@ -0,0 +1,3 @@
+export default {
+ SELECT_FILE: 'select-file' as const
+}
diff --git a/src/i18n/de/index.ts b/src/i18n/de/index.ts
index 967b560..2dad4fd 100644
--- a/src/i18n/de/index.ts
+++ b/src/i18n/de/index.ts
@@ -121,7 +121,6 @@ export default {
},
window: {
title: 'Bild hinzufügen',
- or: 'ODER',
form: {
sourceLink: 'Bild URL'
},
diff --git a/src/i18n/en/index.ts b/src/i18n/en/index.ts
index 160ed66..595fcd6 100644
--- a/src/i18n/en/index.ts
+++ b/src/i18n/en/index.ts
@@ -121,9 +121,13 @@ export default {
},
window: {
title: 'Add Image',
- or: 'OR',
form: {
- sourceLink: 'Image URL'
+ sourceLink: 'Image URL',
+ altText: 'Alternative Text',
+ addImage: 'Add Image'
+ },
+ imageUpload: {
+ instruction: 'Choose a file(s) or drag it here.'
},
buttons: {
close: 'Close',
diff --git a/src/i18n/es/index.ts b/src/i18n/es/index.ts
index a6b375b..b40f3e7 100644
--- a/src/i18n/es/index.ts
+++ b/src/i18n/es/index.ts
@@ -121,7 +121,6 @@ export default {
},
window: {
title: 'Agregar Imagen',
- or: 'O',
form: {
sourceLink: 'URL de la Imagen'
},
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
index f12834d..fd1db5b 100644
--- a/src/i18n/index.ts
+++ b/src/i18n/index.ts
@@ -56,6 +56,10 @@ export function getMsg (path: string, args?, lang: null | string = null): string
target = path.split('.').reduce((prev: string, curr: string) => {
return prev[curr]
}, dictionaryByLang)
+ // No error thrown by above reduce function if last stage is undefined - no fallback used and returned value is empty
+ if (target === undefined) {
+ throw new Error(`${path} is undefined.`)
+ }
} catch (e) {
ConsoleLogger.warn(`Cannot get translation "${path}" for language "${currentLang}". Fallback "${defaultLanguage}" is used instead. Contribution to github is welcome.`)
diff --git a/src/i18n/ja/index.ts b/src/i18n/ja/index.ts
index 9507468..dfc935f 100644
--- a/src/i18n/ja/index.ts
+++ b/src/i18n/ja/index.ts
@@ -121,7 +121,6 @@ export default {
},
window: {
title: '画像を追加する',
- or: 'または',
form: {
sourceLink: '画像URL'
},
diff --git a/src/i18n/ko/index.ts b/src/i18n/ko/index.ts
index c78946d..252f245 100644
--- a/src/i18n/ko/index.ts
+++ b/src/i18n/ko/index.ts
@@ -121,7 +121,6 @@ export default {
},
window: {
title: '이미지 추가',
- or: '또는',
form: {
sourceLink: '이미지 URL'
},
diff --git a/src/i18n/nl/index.ts b/src/i18n/nl/index.ts
index e69547c..78b5de6 100644
--- a/src/i18n/nl/index.ts
+++ b/src/i18n/nl/index.ts
@@ -121,9 +121,12 @@ export default {
},
window: {
title: 'Afbeelding toevoegen',
- or: 'OF',
form: {
- sourceLink: 'Afbeelding URL'
+ sourceLink: 'Afbeelding URL',
+ altText: 'Alternatieve Tekst'
+ },
+ imageUpload: {
+ instruction: 'Kies een of meerdere bestanden of sleep ze hiernaartoe.'
},
buttons: {
close: 'Sluiten',
diff --git a/src/i18n/pl/index.ts b/src/i18n/pl/index.ts
index a6c2d1d..1ef8e1c 100644
--- a/src/i18n/pl/index.ts
+++ b/src/i18n/pl/index.ts
@@ -121,7 +121,6 @@ export default {
},
window: {
title: 'Dodaj obrazek',
- or: 'LUB',
form: {
sourceLink: 'Adres URL obrazka'
},
diff --git a/src/i18n/ru/index.ts b/src/i18n/ru/index.ts
index 142d42e..9ff0501 100644
--- a/src/i18n/ru/index.ts
+++ b/src/i18n/ru/index.ts
@@ -121,7 +121,6 @@ export default {
},
window: {
title: 'Добавить картинку',
- or: 'ИЛИ',
form: {
sourceLink: 'Ссылка на картинку'
},
diff --git a/src/i18n/zh/index.ts b/src/i18n/zh/index.ts
index 1663c64..bbd898b 100644
--- a/src/i18n/zh/index.ts
+++ b/src/i18n/zh/index.ts
@@ -121,7 +121,6 @@ export default {
},
window: {
title: '添加图片',
- or: '或者',
form: {
sourceLink: '图片链接'
},