Skip to content

Commit

Permalink
feat: Custom images upload
Browse files Browse the repository at this point in the history
  • Loading branch information
avgeeklucky authored May 8, 2020
1 parent 471449f commit 3af6fc3
Show file tree
Hide file tree
Showing 19 changed files with 272 additions and 60 deletions.
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -163,7 +164,7 @@ export default {
</script>
```

### CDN (<script>)
### CDN

**Attention: it seems that this method does not work due to the fact that this is not done in the `tiptap` package itself. Therefore, it most likely will not work. [More details](https://github.com/iliyaZelenko/tiptap-vuetify/issues/146#issuecomment-601548526).**

Expand Down Expand Up @@ -293,6 +294,43 @@ data () {

[Here](https://github.com/iliyaZelenko/tiptap-vuetify/issues/100#issuecomment-551950075) is example of how to create your extension from scratch.

### custom image upload components
A custom image upload / selection component allows you to upload images to or select images from your application's backend system.
The when properly configured, the component will be displayed as a tab in the Add Image window.

To implement this, first create a component where users can upload and/or select images. The component will not get any props from the image window.
When a user selects an image, the component must emit a `select-file` event with an object containing `src` and `alt` properties.
For example:
```js
selectImage() {
// There may be your asynchronous file upload to the server (backend), when the file is uploaded you can set the path to the "src" property.
this.$emit('select-file', { src: '/path/to/image.jpg', alt: 'Uploaded image' });
}
```

To add your component to the image extension, make the following changes:
Import your component, e.g.
```js
import FileSelector from '~/Components/FileSelector'
```
Update `tiptap-vuetify :extensions` value for Image as follows:
```js
...
[Image, {
options: {
imageSources: [
{ component: FileSelector, name: 'File Selector' }
]
}
}]
...
```
The value of `name` will be the tab name.

By default, your component will be added to tiptap-vuetify's own image sources (URL and data url Upload). If you want to exclude these image sources you can set `imageSourcesOverride: true` in the extension's options.

A basic example implementation can be found in the package's demo code in [FileSelector.vue](demo/Components/FileSelector.vue) and [Index.vue](demo/pages/Index.vue).

### output-format

The format to output from the v-model. This defaults to `html`
Expand Down
50 changes: 50 additions & 0 deletions demo/Components/FileSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<template>
<div class="d-flex flex-row flex-wrap">
<v-img
v-for="(img, i) in images"
:key="'img-' + i"
:alt="img.alt"
:src="img.src"
class="ma-2 selectable"
max-height="100"
max-width="100"
@click="selectImage(img)"
/>
</div>
</template>

<script>
/**
* Example of a custom Image selector
* Key is to emit a select-file event when a file needs to be added
*/
import { VImg } from 'vuetify/lib'
export default {
name: "FileSelector",
components: { VImg },
data() {
// Some public domain images from wikimedia.
return {
images: [
{ src: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Streifenhoernchen.jpg/1024px-Streifenhoernchen.jpg', alt: 'Siberian Chipmunk' },
{ src: 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/d8/NASA_Mars_Rover.jpg/750px-NASA_Mars_Rover.jpg', alt: 'NASA Mars Rover' },
{ src: 'https://upload.wikimedia.org/wikipedia/commons/d/dd/Muybridge_race_horse_animated.gif', alt: 'Muybridge race horse animated' },
{ src: 'https://upload.wikimedia.org/wikipedia/commons/2/2a/Locomotive_TEM2M-063_2006_G2.jpg', alt: 'Locomotive TEM2M-063 2006 G2' },
{ src: 'https://upload.wikimedia.org/wikipedia/commons/8/80/ISS_March_2009.jpg', alt: 'ISS March 2009' },
{ src: 'https://upload.wikimedia.org/wikipedia/commons/4/44/F-18F_after_launch_from_USS_Abraham_Lincoln_%28CVN-72%29.jpg', alt: 'F-18F after launch from USS Abraham Lincoln (CVN-72)' },
]
};
},
methods: {
selectImage(img) {
this.$emit('select-file', img);
}
}
}
</script>

<style scoped>
.selectable {
cursor: pointer;
}
</style>
7 changes: 6 additions & 1 deletion demo/pages/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<script>
import { MAIN_MODULE } from '../config'
import MyCustomExtension from '../MyCustomExtension'
import FileSelector from '../Components/FileSelector'
export default {
components: {
Expand Down Expand Up @@ -90,7 +91,11 @@ export default {
ListItem, // если нужно использовать список (BulletList, OrderedList)
BulletList,
OrderedList,
Image,
[Image, {
options: {
imageSources: [{ component: FileSelector, name: 'File Selector' }]
}
}],
[Heading, {
// Опции которые попадают в расширение tiptap
options: {
Expand Down
5 changes: 4 additions & 1 deletion src/extensions/nativeExtensions/image/Image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default class Image extends AbstractExtension {

get availableActions (): ExtensionActionInterface[] {
const nativeExtensionName = 'image'
const options = this.options

return [
{
Expand All @@ -35,7 +36,9 @@ export default class Image extends AbstractExtension {
value: true,
nativeExtensionName,
context,
editor
editor,
imageSources: options.imageSources,
imageSourcesOverride: options.imageSourcesOverride
}
})

Expand Down
49 changes: 49 additions & 0 deletions src/extensions/nativeExtensions/image/ImageForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<template>
<div>
<v-text-field
v-model="form.src"
:label="$i18n.getMsg('extensions.Image.window.form.sourceLink')"
/>
<v-text-field
v-model="form.alt"
:label="$i18n.getMsg('extensions.Image.window.form.altText')"
/>
<v-btn @click="addImage">
{{ $i18n.getMsg('extensions.Image.window.form.addImage') }}
</v-btn>
</div>
</template>

<script lang="ts">
import { mixins } from 'vue-class-component'
import { Component } from 'vue-property-decorator'
import { VTextField } from 'vuetify/lib'
import I18nMixin from '../../../mixins/I18nMixin'
import EVENTS from '~/extensions/nativeExtensions/image/events'
@Component({
components: { VTextField }
})
export default class ImageForm extends mixins(I18nMixin) {
form: {
src: null | string
alt: 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'
alt: null
}
addImage () {
this.$emit(EVENTS.SELECT_FILE, {
src: this.form.src,
alt: this.form.alt
})
this.form.src = null
this.form.alt = null
}
}
</script>

<style scoped>
</style>
4 changes: 4 additions & 0 deletions src/extensions/nativeExtensions/image/ImageSource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default interface ImageSource {
src: null | string
alt: null | string
}
23 changes: 17 additions & 6 deletions src/extensions/nativeExtensions/image/ImageUploadArea.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<path d="M48.4 26.5c-.9 0-1.7.7-1.7 1.7v11.6h-43.3v-11.6c0-.9-.7-1.7-1.7-1.7s-1.7.7-1.7 1.7v13.2c0 .9.7 1.7 1.7 1.7h46.7c.9 0 1.7-.7 1.7-1.7v-13.2c0-1-.7-1.7-1.7-1.7zm-24.5 6.1c.3.3.8.5 1.2.5.4 0 .9-.2 1.2-.5l10-11.6c.7-.7.7-1.7 0-2.4s-1.7-.7-2.4 0l-7.1 8.3v-25.3c0-.9-.7-1.7-1.7-1.7s-1.7.7-1.7 1.7v25.3l-7.1-8.3c-.7-.7-1.7-.7-2.4 0s-.7 1.7 0 2.4l10 11.6z" />
</svg>
<br><br>
<h3>Choose a file(s) or drag it here.</h3>
<h3>{{ $i18n.getMsg('extensions.Image.window.imageUpload.instruction') }}</h3>
</div>
</label>
</div>
Expand All @@ -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
Expand All @@ -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 = ''
Expand All @@ -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)
})
}
}
Expand Down
Loading

0 comments on commit 3af6fc3

Please sign in to comment.