rails_admin_image_manager 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +125 -0
- data/Rakefile +36 -0
- data/app/assets/config/rails_admin_image_manager_manifest.js +2 -0
- data/app/assets/fonts/FontAwesome.otf +0 -0
- data/app/assets/fonts/Simple-Line-Icons.eot +0 -0
- data/app/assets/fonts/Simple-Line-Icons.svg +1369 -0
- data/app/assets/fonts/Simple-Line-Icons.ttf +0 -0
- data/app/assets/fonts/Simple-Line-Icons.woff +0 -0
- data/app/assets/fonts/fontawesome-webfont.eot +0 -0
- data/app/assets/fonts/fontawesome-webfont.svg +2671 -0
- data/app/assets/fonts/fontawesome-webfont.ttf +0 -0
- data/app/assets/fonts/fontawesome-webfont.woff +0 -0
- data/app/assets/fonts/fontawesome-webfont.woff2 +0 -0
- data/app/assets/fonts/glyphicons-halflings-regular.eot +0 -0
- data/app/assets/fonts/glyphicons-halflings-regular.svg +288 -0
- data/app/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/app/assets/fonts/glyphicons-halflings-regular.woff +0 -0
- data/app/assets/fonts/glyphicons-halflings-regular.woff2 +0 -0
- data/app/assets/images/rails_admin_image_manager/image.png +0 -0
- data/app/assets/javascripts/rails_admin_image_manager/app-compiled.js.erb +34896 -0
- data/app/assets/javascripts/rails_admin_image_manager/app-vue.js +51 -0
- data/app/assets/javascripts/rails_admin_image_manager/application.js +17 -0
- data/app/assets/javascripts/rails_admin_image_manager/base.js.erb +62 -0
- data/app/assets/javascripts/rails_admin_image_manager/ckeditor_plugin.js.erb +58 -0
- data/app/assets/javascripts/rails_admin_image_manager/components/confirmationOverlay.vue +69 -0
- data/app/assets/javascripts/rails_admin_image_manager/components/imageInsertButton.vue +41 -0
- data/app/assets/javascripts/rails_admin_image_manager/components/imageInsertOverlay.vue +92 -0
- data/app/assets/javascripts/rails_admin_image_manager/components/imageListing.vue +167 -0
- data/app/assets/javascripts/rails_admin_image_manager/components/imageShow.vue +112 -0
- data/app/assets/javascripts/rails_admin_image_manager/components/imageTagSelector.vue +94 -0
- data/app/assets/javascripts/rails_admin_image_manager/components/imageUpload.vue +94 -0
- data/app/assets/javascripts/rails_admin_image_manager/components/listingFilter.vue +32 -0
- data/app/assets/javascripts/rails_admin_image_manager/components/notificationOverlay.vue +47 -0
- data/app/assets/javascripts/rails_admin_image_manager/components/progressOverlay.vue +37 -0
- data/app/assets/javascripts/rails_admin_image_manager/components/searchAutocomplete.vue +140 -0
- data/app/assets/javascripts/rails_admin_image_manager/editor/image_manager_picker.js.erb +41 -0
- data/app/assets/javascripts/rails_admin_image_manager/filters/index.js +7 -0
- data/app/assets/javascripts/rails_admin_image_manager/libs/helpers.js +13 -0
- data/app/assets/javascripts/rails_admin_image_manager/libs/lazyload.js +63 -0
- data/app/assets/javascripts/rails_admin_image_manager/router/index.js +55 -0
- data/app/assets/javascripts/rails_admin_image_manager/stores/ckeditor.js +25 -0
- data/app/assets/javascripts/rails_admin_image_manager/stores/index.js +16 -0
- data/app/assets/javascripts/rails_admin_image_manager/stores/medias.js +342 -0
- data/app/assets/javascripts/rails_admin_image_manager/stores/overlay.js +57 -0
- data/app/assets/javascripts/rails_admin_image_manager/stores/railsAdmin.js +28 -0
- data/app/assets/javascripts/rails_admin_image_manager/vendors/fuse.min.js +9 -0
- data/app/assets/javascripts/rails_admin_image_manager/vendors/oneui.min.js +8 -0
- data/app/assets/javascripts/rails_admin_image_manager/vendors/underscore.min.js +6 -0
- data/app/assets/stylesheets/rails_admin_image_manager/_font.scss +2845 -0
- data/app/assets/stylesheets/rails_admin_image_manager/application.css +16 -0
- data/app/assets/stylesheets/rails_admin_image_manager/global.css.erb +3723 -0
- data/app/assets/stylesheets/rails_admin_image_manager/global.sass +1 -0
- data/app/assets/stylesheets/rails_admin_image_manager/vendors/bootstrap.min.css +5 -0
- data/app/assets/stylesheets/rails_admin_image_manager/vendors/oneui.css +8865 -0
- data/app/controllers/rails_admin_image_manager/application_controller.rb +41 -0
- data/app/controllers/rails_admin_image_manager/home_controller.rb +8 -0
- data/app/controllers/rails_admin_image_manager/images_controller.rb +117 -0
- data/app/helpers/rails_admin_image_manager/application_helper.rb +4 -0
- data/app/jobs/rails_admin_image_manager/application_job.rb +4 -0
- data/app/models/rails_admin_image_manager/application_record.rb +5 -0
- data/app/models/rails_admin_image_manager/file.rb +78 -0
- data/app/models/rails_admin_image_manager/tag.rb +41 -0
- data/app/views/layouts/rails_admin_image_manager/application.html.erb +26 -0
- data/app/views/rails_admin/main/_form_image_manager_picker.html.erb +45 -0
- data/app/views/rails_admin_image_manager/images/index.html.erb +2 -0
- data/config/initializers/rails_admin/image_manager_file.rb +87 -0
- data/config/initializers/rails_admin/image_manager_tag.rb +22 -0
- data/config/locales/rails_admin_image_manager.en.yml +33 -0
- data/config/locales/rails_admin_image_manager.fr.yml +33 -0
- data/config/routes.rb +12 -0
- data/db/migrate/20170626000000_create_image_manager.rb +37 -0
- data/lib/dynamic_paperclip_patch.rb +92 -0
- data/lib/generators/rails_admin_image_manager/install/install_generator.rb +22 -0
- data/lib/paperclip_patch.rb +22 -0
- data/lib/rails_admin_image_manager.rb +46 -0
- data/lib/rails_admin_image_manager/engine.rb +36 -0
- data/lib/rails_admin_image_manager/has_managed_file.rb +47 -0
- data/lib/rails_admin_image_manager/rails_admin/config/fields/types/image_manager_picker.rb +49 -0
- data/lib/rails_admin_image_manager/version.rb +3 -0
- data/lib/tasks/rails_admin_image_manager_tasks.rake +4 -0
- metadata +253 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="content content-narrow animated fadeIn">
|
3
|
+
<div class="row items-push">
|
4
|
+
<div class="col-sm-12">
|
5
|
+
<a href="#"><i class="fa fa-arrow-left"></i> Retour à la liste</a>
|
6
|
+
</div>
|
7
|
+
</div>
|
8
|
+
<div class="block">
|
9
|
+
<div class="block-header">
|
10
|
+
<h3 class="block-title">Modifier l'image</h3>
|
11
|
+
</div>
|
12
|
+
<div class="block-content">
|
13
|
+
<div class="row">
|
14
|
+
|
15
|
+
<div class="col-sm-6">
|
16
|
+
<div class="form-horizontal">
|
17
|
+
|
18
|
+
<div :class="[{ 'has-error': errors.name },'form-group']">
|
19
|
+
<div class="col-sm-12">
|
20
|
+
<div class="form-material">
|
21
|
+
<input v-model="currentImgTitle" class="form-control" type="text" id="image-title" name="material-text" >
|
22
|
+
<label for="image-title">Titre</label>
|
23
|
+
<span class="help-block" v-if="errors.name">{{ errors.name[0] }}</span>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
|
28
|
+
<div :class="[{ 'has-error': errors.copyright },'form-group']">
|
29
|
+
<div class="col-sm-12">
|
30
|
+
<div class="form-material">
|
31
|
+
<input v-model="currentImgCopyright" class="form-control" type="text" id="image-copyright" name="material-text" >
|
32
|
+
<label for="image-copyright">Copyright</label>
|
33
|
+
<span class="help-block" v-if="errors.copyright">{{ errors.copyright[0] }}</span>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
<div :class="[{ 'has-error': errors.description },'form-group']">
|
38
|
+
<div class="col-sm-12">
|
39
|
+
<div class="form-material">
|
40
|
+
<textarea v-model="currentImgDescription" class="form-control" id="material-textarea-large" name="material-textarea-large" rows="8"></textarea>
|
41
|
+
<label for="material-textarea-large">Description</label>
|
42
|
+
<span class="help-block" v-if="errors.description">{{ errors.description[0] }}</span>
|
43
|
+
</div>
|
44
|
+
</div>
|
45
|
+
</div>
|
46
|
+
|
47
|
+
<div class="form-group">
|
48
|
+
<div class="col-sm-12">
|
49
|
+
<image-tag-selector label="Étiquettes"></image-tag-selector>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
|
53
|
+
<div class="form-group">
|
54
|
+
<div class="col-sm-10">
|
55
|
+
<button class="btn btn-sm btn-primary" type="button" @click="save">Enregistrer</button>
|
56
|
+
<image-insert-button :save-method="save" :id="currentImgId"/>
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
<div class="col-sm-6">
|
62
|
+
<p><image-upload></image-upload></p>
|
63
|
+
</div>
|
64
|
+
</div>
|
65
|
+
</div>
|
66
|
+
</div>
|
67
|
+
</div>
|
68
|
+
</template>
|
69
|
+
|
70
|
+
<script>
|
71
|
+
import {mapState} from 'vuex'
|
72
|
+
import imageInsertButton from './imageInsertButton.vue'
|
73
|
+
import imageUpload from './imageUpload.vue'
|
74
|
+
import imageTagSelector from './imageTagSelector.vue'
|
75
|
+
|
76
|
+
export default {
|
77
|
+
components: {imageInsertButton, imageUpload, imageTagSelector},
|
78
|
+
data () {
|
79
|
+
return {
|
80
|
+
currentImgTitle: '',
|
81
|
+
currentImgCopyright: '',
|
82
|
+
currentImgDescription: '',
|
83
|
+
currentImgId: ''
|
84
|
+
}
|
85
|
+
},
|
86
|
+
methods: {
|
87
|
+
save() {
|
88
|
+
let imgData = {
|
89
|
+
name: this.currentImgTitle,
|
90
|
+
description: this.currentImgDescription,
|
91
|
+
copyright: this.currentImgCopyright,
|
92
|
+
}
|
93
|
+
return new Promise((resolve, reject) => {
|
94
|
+
this.$store.dispatch('mediasStore/setCurrentImg', imgData)
|
95
|
+
this.$store.dispatch('mediasStore/saveCurrentImg').then(resolve).catch(reject)
|
96
|
+
});
|
97
|
+
},
|
98
|
+
},
|
99
|
+
computed: {
|
100
|
+
...mapState('mediasStore', ['errors'])
|
101
|
+
},
|
102
|
+
created () {
|
103
|
+
this.currentImgId = this.$store.state.mediasStore.currentImgId
|
104
|
+
this.currentImgTitle = this.$store.state.mediasStore.currentImgTitle
|
105
|
+
this.currentImgCopyright = this.$store.state.mediasStore.currentImgCopyright
|
106
|
+
this.currentImgDescription = this.$store.state.mediasStore.currentImgDescription
|
107
|
+
}
|
108
|
+
}
|
109
|
+
</script>
|
110
|
+
|
111
|
+
<style media="screen">
|
112
|
+
</style>
|
@@ -0,0 +1,94 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="image-tag-selector form-material">
|
3
|
+
<div class="image-tag-selector__choices">
|
4
|
+
<p><span class="label label-primary" @click="removeSelected(tag)" v-for="tag in currentImgTags">{{tag}}</span></p>
|
5
|
+
</div>
|
6
|
+
<input type="text" placeholder="Saisir une étiquette et faire enter" class="form-control" v-model="searchText" @keyup="addCusttom">
|
7
|
+
<label for="example-select2-multiple">{{label}}</label>
|
8
|
+
<ul class="image-tag-selector__pool-list">
|
9
|
+
<li v-for="tag in filteredTags" @click="selectTag(tag)"><button type="button">{{ tag }}</button></li>
|
10
|
+
</ul>
|
11
|
+
</div>
|
12
|
+
</template>
|
13
|
+
|
14
|
+
<script>
|
15
|
+
import { mapState } from 'vuex'
|
16
|
+
|
17
|
+
export default {
|
18
|
+
props: ['label'],
|
19
|
+
data () {
|
20
|
+
return {
|
21
|
+
searchText: '',
|
22
|
+
tagsPool: []
|
23
|
+
}
|
24
|
+
},
|
25
|
+
computed: {
|
26
|
+
filteredTags() {
|
27
|
+
let filtreTexte = (string) => {
|
28
|
+
if (string == '') return []
|
29
|
+
return this.tagsPool.filter((el) =>
|
30
|
+
el.toLowerCase().indexOf(string.toLowerCase()) > -1
|
31
|
+
);
|
32
|
+
}
|
33
|
+
return filtreTexte(this.searchText)
|
34
|
+
},
|
35
|
+
...mapState('mediasStore', ['currentImgTags', 'tags'])
|
36
|
+
},
|
37
|
+
methods: {
|
38
|
+
selectTag(string) {
|
39
|
+
if (this.currentImgTags.indexOf(string) < 0) {
|
40
|
+
this.$store.dispatch('mediasStore/addTag', string)
|
41
|
+
this.searchText = ''
|
42
|
+
}
|
43
|
+
},
|
44
|
+
removeSelected(string) {
|
45
|
+
this.$store.dispatch('mediasStore/removeTag', string)
|
46
|
+
},
|
47
|
+
addCusttom(e) {
|
48
|
+
if (e.keyCode == 13 && this.tagsPool.indexOf(this.searchText) < 0 && this.currentImgTags.indexOf(this.searchText) < 0) {
|
49
|
+
this.$store.dispatch('mediasStore/addTag', this.searchText)
|
50
|
+
this.searchText = ''
|
51
|
+
}
|
52
|
+
}
|
53
|
+
},
|
54
|
+
mounted () {
|
55
|
+
this.$store.dispatch('mediasStore/fetchTags').then(()=> {
|
56
|
+
for (var i = 0; i < this.tags.length; i++) {
|
57
|
+
let tag = this.tags[i]
|
58
|
+
this.tagsPool.push(tag.name)
|
59
|
+
}
|
60
|
+
})
|
61
|
+
}
|
62
|
+
}
|
63
|
+
</script>
|
64
|
+
|
65
|
+
<style media="screen" lang="sass">
|
66
|
+
|
67
|
+
.image-tag-selector__pool-list
|
68
|
+
list-style: none
|
69
|
+
margin: 0
|
70
|
+
padding: 0
|
71
|
+
position: absolute
|
72
|
+
top: 100%
|
73
|
+
width: 100%
|
74
|
+
z-index: 100
|
75
|
+
|
76
|
+
li
|
77
|
+
background-color: #fcfcfc
|
78
|
+
|
79
|
+
button
|
80
|
+
background-color: transparent
|
81
|
+
border: none
|
82
|
+
display: block
|
83
|
+
width: 100%
|
84
|
+
text-align: left
|
85
|
+
padding-top: 10px
|
86
|
+
padding-bottom: 10px
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
.image-tag-selector__choices
|
91
|
+
.label
|
92
|
+
margin: 0 3px
|
93
|
+
cursor: pointer
|
94
|
+
</style>
|
@@ -0,0 +1,94 @@
|
|
1
|
+
<template>
|
2
|
+
<div :class="[{'has-error': imageError}, 'image-upload']">
|
3
|
+
<div class="image-upload__content">
|
4
|
+
<img :src="currentImgSrc" class="image-upload__img" v-if="currentImgSrc" alt="">
|
5
|
+
<div class="image-upload__placeholder" v-if="currentImgSrc == ''">
|
6
|
+
<i class="fa fa-image"></i>
|
7
|
+
Ajouter une image
|
8
|
+
</div>
|
9
|
+
<input class="image-upload__file-input" type="file" @change="encode($event)">
|
10
|
+
</div>
|
11
|
+
<span class="help-block" :if="imageError">{{imageError}}</span>
|
12
|
+
|
13
|
+
<button type="button" class="btn btn-default push-20-t" v-if="showOriginal" @click="undo" alt=""><i class="fa fa-undo"></i> Annuler</button>
|
14
|
+
</div>
|
15
|
+
</template>
|
16
|
+
|
17
|
+
<script>
|
18
|
+
import { encodeImageFileAsURL } from '../libs/helpers.js'
|
19
|
+
import { mapState } from 'vuex'
|
20
|
+
|
21
|
+
export default {
|
22
|
+
data() {
|
23
|
+
return {
|
24
|
+
originalSrc: '',
|
25
|
+
showOriginal: false
|
26
|
+
}
|
27
|
+
},
|
28
|
+
computed: {
|
29
|
+
...mapState('mediasStore', ['currentImgSrc', 'errors']),
|
30
|
+
imageError() {
|
31
|
+
if(this.errors.hasOwnProperty('image')) {
|
32
|
+
return this.errors.image[0]
|
33
|
+
}
|
34
|
+
}
|
35
|
+
},
|
36
|
+
created () {
|
37
|
+
this.originalSrc = this.currentImgSrc
|
38
|
+
},
|
39
|
+
methods: {
|
40
|
+
encode(e) {
|
41
|
+
encodeImageFileAsURL(e.target)
|
42
|
+
.then((result) => {
|
43
|
+
if (this.originalSrc != '') this.showOriginal = true
|
44
|
+
this.$store.dispatch('mediasStore/updateSrc', result.src)
|
45
|
+
this.$store.dispatch('mediasStore/updateImageName', result.name)
|
46
|
+
})
|
47
|
+
},
|
48
|
+
undo() {
|
49
|
+
this.$store.dispatch('mediasStore/updateSrc', this.originalSrc)
|
50
|
+
this.showOriginal = false
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
</script>
|
55
|
+
|
56
|
+
<style media="screen" lang="sass">
|
57
|
+
.image-upload
|
58
|
+
position: relative
|
59
|
+
|
60
|
+
.image-upload__img
|
61
|
+
width: 100%
|
62
|
+
|
63
|
+
.image-upload__file-input
|
64
|
+
position: absolute
|
65
|
+
top: 0
|
66
|
+
left: 0
|
67
|
+
bottom: 0
|
68
|
+
right: 0
|
69
|
+
width: 100%
|
70
|
+
opacity: 0
|
71
|
+
cursor: pointer
|
72
|
+
|
73
|
+
.image-upload__content
|
74
|
+
position: relative
|
75
|
+
border: 6px dashed #fcfcfc
|
76
|
+
transition: all 200ms linear
|
77
|
+
.has-error &
|
78
|
+
border-color: #d26a5c
|
79
|
+
|
80
|
+
&:hover
|
81
|
+
border-color: #c9c9c9
|
82
|
+
|
83
|
+
.image-upload__placeholder
|
84
|
+
width: 100%
|
85
|
+
padding: 30px 0
|
86
|
+
display: flex
|
87
|
+
align-items: center
|
88
|
+
justify-content: center
|
89
|
+
flex-direction: column
|
90
|
+
cursor: pointer
|
91
|
+
opacity: 0.8
|
92
|
+
i
|
93
|
+
font-size: 70px
|
94
|
+
</style>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<template>
|
2
|
+
<span class="listing-filter" @click.prevent="filter()">{{label}}
|
3
|
+
<i v-if="activeFilters[type] == 'ASC'" class="fa fa-arrow-down"></i>
|
4
|
+
<i v-if="activeFilters[type] == 'DESC'" class="fa fa-arrow-up"></i>
|
5
|
+
</span>
|
6
|
+
</template>
|
7
|
+
|
8
|
+
<script>
|
9
|
+
import {mapState} from 'vuex'
|
10
|
+
|
11
|
+
export default {
|
12
|
+
props: ['label', 'type'],
|
13
|
+
data () {
|
14
|
+
return {}
|
15
|
+
},
|
16
|
+
computed: {
|
17
|
+
...mapState('mediasStore', ['activeFilters'])
|
18
|
+
},
|
19
|
+
methods: {
|
20
|
+
filter () {
|
21
|
+
this.$store.dispatch('mediasStore/setSearchPage', 1)
|
22
|
+
this.$store.dispatch('mediasStore/clearImgListing')
|
23
|
+
this.$store.dispatch('mediasStore/toggleFilter', this.type)
|
24
|
+
this.$store.dispatch('mediasStore/fetchImage')
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
</script>
|
29
|
+
<style media="screen" lang="sass">
|
30
|
+
.listing-filter
|
31
|
+
cursor: pointer
|
32
|
+
</style>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="notif-overlay">
|
3
|
+
<transition-group name="fade-top">
|
4
|
+
<div :class="[{'alert-success': notification.success, 'alert-danger': notification.error}, 'alert', 'notification-item']" :key="notification" v-for="notification in notifications">
|
5
|
+
<p>{{ notification.msg }}</p>
|
6
|
+
</div>
|
7
|
+
</transition-group>
|
8
|
+
</div>
|
9
|
+
</template>
|
10
|
+
|
11
|
+
<script>
|
12
|
+
import {mapState} from 'vuex'
|
13
|
+
|
14
|
+
export default {
|
15
|
+
computed: {
|
16
|
+
...mapState('overlayStore', ['notifications'])
|
17
|
+
}
|
18
|
+
}
|
19
|
+
</script>
|
20
|
+
|
21
|
+
<style media="screen" lang='sass'>
|
22
|
+
.notif-overlay
|
23
|
+
position: fixed
|
24
|
+
bottom: 20px
|
25
|
+
right: 20px
|
26
|
+
|
27
|
+
.notification-item
|
28
|
+
width: 300px
|
29
|
+
height: 100px
|
30
|
+
margin: 5px
|
31
|
+
|
32
|
+
&.error
|
33
|
+
background-color: red
|
34
|
+
color: white
|
35
|
+
&.success
|
36
|
+
background-color: #c3f0cc
|
37
|
+
color: #3daa41
|
38
|
+
|
39
|
+
|
40
|
+
.fade-top-enter-active, .fade-top-leave-active
|
41
|
+
transition: all 400ms
|
42
|
+
|
43
|
+
.fade-top-enter, .fade-top-leave-to
|
44
|
+
opacity: 0
|
45
|
+
|
46
|
+
|
47
|
+
</style>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
<template>
|
2
|
+
<transition name="fade">
|
3
|
+
<div class="progress-overlay progress active" v-if="showProgress" style="height: 6px">
|
4
|
+
<div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div>
|
5
|
+
</div>
|
6
|
+
</transition>
|
7
|
+
</template>
|
8
|
+
|
9
|
+
<script>
|
10
|
+
import {mapState} from 'vuex'
|
11
|
+
|
12
|
+
export default {
|
13
|
+
computed: {
|
14
|
+
...mapState('overlayStore', ['showProgress'])
|
15
|
+
}
|
16
|
+
}
|
17
|
+
</script>
|
18
|
+
|
19
|
+
<style media="screen" lang='sass'>
|
20
|
+
.progress-overlay
|
21
|
+
position: fixed
|
22
|
+
top: 0
|
23
|
+
width: 100%
|
24
|
+
|
25
|
+
.fade-enter-active, .fade-leave-active
|
26
|
+
transition: opacity .150s
|
27
|
+
.fade-enter, .fade-leave-to
|
28
|
+
opacity: 0
|
29
|
+
|
30
|
+
.image-insert-overlay__close
|
31
|
+
position: absolute
|
32
|
+
top: 0
|
33
|
+
right: 0
|
34
|
+
background-color: transparent
|
35
|
+
border: none
|
36
|
+
|
37
|
+
</style>
|
@@ -0,0 +1,140 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="search-autocomplete">
|
3
|
+
<div class="search-autocomplete__input">
|
4
|
+
<input @keyup.prevent="fuzzySearch" class="form-control" v-model="query" type="text" placeholder="Rechercher par titre ou étiquette ...">
|
5
|
+
<div class="search-autocomplete__tags-list">
|
6
|
+
<span @click="selectedTagIndex = index; selectTag()" :class="[{'active': isActive(index)}, 'search-autocomplete__tag']" v-for="(tag,index) in filteredTags">{{tag.name}}</span>
|
7
|
+
</div>
|
8
|
+
</div>
|
9
|
+
<span @click="deselectTag(activeTag)" class="label label-primary search-autocomplete__selected-tag" v-for="activeTag in activeFilters.tags">{{getTagFromId(activeTag)}}</span>
|
10
|
+
</div>
|
11
|
+
|
12
|
+
</template>
|
13
|
+
|
14
|
+
<script>
|
15
|
+
import {mapState, mapGetters} from 'vuex'
|
16
|
+
import Fuse from '../vendors/fuse.min.js'
|
17
|
+
export default {
|
18
|
+
data () {
|
19
|
+
return {
|
20
|
+
query: '',
|
21
|
+
fuseSearch: null,
|
22
|
+
selectedTagIndex: -1
|
23
|
+
}
|
24
|
+
},
|
25
|
+
computed: {
|
26
|
+
...mapState('mediasStore', ['tags', 'activeFilters']),
|
27
|
+
// Autocomplet will search for last string in input (seperated by space)
|
28
|
+
searchFor() {
|
29
|
+
let lastElem = this.query.split(' ').reverse()[0]
|
30
|
+
return lastElem
|
31
|
+
},
|
32
|
+
filteredTags() {
|
33
|
+
if (this.fuseSearch) {
|
34
|
+
return this.fuseSearch.search(this.searchFor);
|
35
|
+
} else {
|
36
|
+
return []
|
37
|
+
}
|
38
|
+
|
39
|
+
}
|
40
|
+
},
|
41
|
+
methods:{
|
42
|
+
fuzzySearch(e) {
|
43
|
+
if(e.key == 'ArrowUp' || e.key == 'ArrowDown') {
|
44
|
+
this.navigatesTags(e.key)
|
45
|
+
} else if (e.key == 'Enter' && this.selectedTagIndex >= 0) {
|
46
|
+
this.selectTag()
|
47
|
+
} else if (e.key == 'Enter') {
|
48
|
+
this.$store.dispatch('mediasStore/setSearchQuery', this.query)
|
49
|
+
this.$store.dispatch('mediasStore/clearImgListing')
|
50
|
+
this.$store.dispatch('mediasStore/setSearchPage', 1)
|
51
|
+
this.$store.dispatch('mediasStore/fetchImage')
|
52
|
+
console.log('search');
|
53
|
+
this.query = ""
|
54
|
+
} else if((e.key == 'Backspace' || e.key == 'Meta') && this.query == '' && this.activeFilters.search != '') {
|
55
|
+
this.clearSearch()
|
56
|
+
}
|
57
|
+
},
|
58
|
+
clearSearch() {
|
59
|
+
this.$store.dispatch('mediasStore/setSearchQuery', '')
|
60
|
+
this.$store.dispatch('mediasStore/clearImgListing')
|
61
|
+
this.$store.dispatch('mediasStore/fetchImage')
|
62
|
+
},
|
63
|
+
navigatesTags(key) {
|
64
|
+
if (key == 'ArrowUp') {
|
65
|
+
(this.selectedTagIndex > 0) ? this.selectedTagIndex -- : 0
|
66
|
+
} else {
|
67
|
+
(this.selectedTagIndex < this.filteredTags.length - 1) ? this.selectedTagIndex ++ : this.filteredTags.length
|
68
|
+
}
|
69
|
+
},
|
70
|
+
selectTag() {
|
71
|
+
let query = this.query
|
72
|
+
let queryArray = query.split(' ').reverse()
|
73
|
+
queryArray[0] = this.filteredTags[this.selectedTagIndex].name
|
74
|
+
let newQuery = queryArray.reverse().join(' ')
|
75
|
+
this.$store.dispatch('mediasStore/setSearchQuery', '')
|
76
|
+
this.$store.dispatch('mediasStore/setSearchPage', 1)
|
77
|
+
this.$store.dispatch('mediasStore/clearImgListing')
|
78
|
+
this.$store.dispatch('mediasStore/addToTagFilter', this.filteredTags[this.selectedTagIndex].id)
|
79
|
+
this.$store.dispatch('mediasStore/fetchImage')
|
80
|
+
this.selectedTagIndex = -1
|
81
|
+
this.query = ""
|
82
|
+
},
|
83
|
+
deselectTag(id){
|
84
|
+
this.$store.dispatch('mediasStore/clearImgListing')
|
85
|
+
this.$store.dispatch('mediasStore/removeFromTagFilter', id)
|
86
|
+
this.$store.dispatch('mediasStore/fetchImage')
|
87
|
+
},
|
88
|
+
isActive(index) {
|
89
|
+
return index == this.selectedTagIndex;
|
90
|
+
},
|
91
|
+
getTagFromId(id) {
|
92
|
+
let myTag = _.find(this.tags,(tag) => {return tag.id == id})
|
93
|
+
return myTag.name
|
94
|
+
}
|
95
|
+
|
96
|
+
},
|
97
|
+
mounted() {
|
98
|
+
this.$store.dispatch('mediasStore/fetchTags').then(() => {
|
99
|
+
this.fuseSearch = new Fuse(this.tags, {keys: ['name']})
|
100
|
+
})
|
101
|
+
}
|
102
|
+
}
|
103
|
+
</script>
|
104
|
+
|
105
|
+
<style media="screen" lang="sass">
|
106
|
+
.search-autocomplete__selected-tag
|
107
|
+
display: inline-flex
|
108
|
+
margin: 3px
|
109
|
+
position: relative
|
110
|
+
cursor: pointer
|
111
|
+
&:first-of-type
|
112
|
+
margin-left: 0
|
113
|
+
|
114
|
+
.search-autocomplete__input
|
115
|
+
position: relative
|
116
|
+
|
117
|
+
.search-autocomplete
|
118
|
+
width: 100%
|
119
|
+
position: relative
|
120
|
+
|
121
|
+
.search-autocomplete__tags-list
|
122
|
+
position: absolute
|
123
|
+
top: 100%
|
124
|
+
width: 100%
|
125
|
+
z-index: 100
|
126
|
+
background-color: white
|
127
|
+
box-shadow: 0 5px 8px rgba(0, 0, 0, 0.1)
|
128
|
+
|
129
|
+
.search-autocomplete__tag
|
130
|
+
display: block
|
131
|
+
padding: 5px
|
132
|
+
transition: all 200ms linear
|
133
|
+
cursor: pointer
|
134
|
+
&:hover
|
135
|
+
background-color: #ededed
|
136
|
+
|
137
|
+
&.active
|
138
|
+
background-color: #5c90d2
|
139
|
+
color: white
|
140
|
+
</style>
|