@concretecms/bedrock 1.1.5 → 1.1.7

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.
@@ -31,6 +31,7 @@
31
31
  </div>
32
32
  <div>
33
33
  <files v-if="keywords"
34
+ :key-prefix="'external-file-provider' + id"
34
35
  :selectedFiles.sync="selectedFiles"
35
36
  :resultsFormFactor="formFactor"
36
37
  :routePath="routePath + id + '/search/' + keywords"
@@ -1,11 +1,14 @@
1
1
  <template>
2
2
  <div>
3
3
  <chooser-header :resultsFormFactor.sync="formFactor"
4
+ :resultsSearchQuery.sync="searchQuery"
4
5
  :breadcrumb-items="breadcrumbItems"
6
+ :enable-search="searchChooserIsEnabled"
5
7
  @breadcrumbItemClick="activeFolder = $event.folderId"
6
8
  :title="title"/>
7
9
 
8
10
  <files :selectedFiles.sync="selectedFiles"
11
+ key-prefix="file-manager"
9
12
  :resultsFormFactor="formFactor"
10
13
  :routePath="routePath + activeFolder"
11
14
  :enable-pagination="true"
@@ -32,9 +35,15 @@ export default {
32
35
  selectedFiles: [],
33
36
  breadcrumbItems: [],
34
37
  routePath: '/ccm/system/file/chooser/get_folder_files/',
35
- formFactor: 'grid'
38
+ formFactor: 'grid',
39
+ searchQuery: ''
36
40
  }),
37
41
  props: {
42
+ searchChooserIsEnabled: {
43
+ type: Boolean,
44
+ required: false,
45
+ default: true
46
+ },
38
47
  resultsFormFactor: {
39
48
  type: String,
40
49
  required: false,
@@ -59,7 +68,7 @@ export default {
59
68
  },
60
69
  created() {
61
70
  this.activeFolder = this.$props.startFolder
62
- this.fetchBreadcrumb(this.activeFolder)
71
+ // this.fetchBreadcrumb(this.activeFolder) // Not sure why this is here, it leads to duplicate requests and I don't think it needs to be
63
72
  },
64
73
  methods: {
65
74
  fetchBreadcrumb(folderId = '') {
@@ -81,15 +90,19 @@ export default {
81
90
  formFactor(value) {
82
91
  this.$emit('update:resultsFormFactor', value)
83
92
  },
93
+ searchQuery(value) {
94
+ this.$emit('update:resultsSearchQuery', value)
95
+ },
84
96
  startFolder(value) {
85
97
  this.activeFolder = value
86
- this.fetchBreadcrumb(value)
98
+ // this.fetchBreadcrumb(value) // Not sure why this is here, it leads to duplicate requests and I don't think it needs to be
87
99
  }
88
100
  },
89
101
  mounted() {
90
102
  this.formFactor = this.resultsFormFactor
103
+ this.searchQuery = this.resultsSearchQuery
91
104
  this.activeFolder = this.$props.startFolder
92
- this.fetchBreadcrumb(this.activeFolder)
105
+ // this.fetchBreadcrumb(this.activeFolder) // Not sure why this is here, it leads to duplicate requests and I don't think it needs to be
93
106
  }
94
107
  }
95
108
  </script>
@@ -17,6 +17,7 @@
17
17
  </div>
18
18
  <div class="mt-3" v-show="activeSet">
19
19
  <files v-if="activeSet"
20
+ key-prefix="file-sets"
20
21
  :selectedFiles.sync="selectedFiles"
21
22
  :resultsFormFactor="formFactor"
22
23
  :routePath="routePath + activeSet"
@@ -3,10 +3,10 @@
3
3
  <svg v-if="isLoading" class="ccm-loader-dots"><use xlink:href="#icon-loader-circles" /></svg>
4
4
  <div v-if="!isLoading">
5
5
  <div class="ccm-image-cell-grid container-fluid ps-0" v-if="resultsFormFactor === 'grid'">
6
- <div v-for="row in rows" class="row text-center" :key="row.index">
7
- <div class="col-md-3" v-for="file in row" :key="(file.fID || file.treeNodeID) + 'grid'">
6
+ <div v-for="(row, index) in rows" class="row text-center" :key="keyPrefix + '-' + index">
7
+ <div class="col-md-3" v-for="file in row" :key="keyPrefix + '-' + (file.fID || file.treeNodeID) + '-grid'">
8
8
  <div class="ccm-image-cell" @click="onItemClick(file)">
9
- <label class="form-label" :for="'file-' + (file.fID || file.treeNodeID)"><span v-html="file.resultsThumbnailImg"></span></label>
9
+ <label class="form-label" :data-bs-content="getGridHoverContent(file)" :for="'file-' + (file.fID || file.treeNodeID)"><span v-html="file.resultsThumbnailImg"></span></label>
10
10
  <div class="ccm-image-cell-title pt-1">
11
11
  <div class="form-check form-check-inline">
12
12
  <input :disabled="!canChooseFile(file)" class="form-check-input" type="checkbox" v-if="multipleSelection && !file.isFolder" v-model="selectedFiles" :id="'file-' + file.fID" :value="file.fID">
@@ -19,7 +19,7 @@
19
19
  </div>
20
20
  </div>
21
21
  <div v-if="resultsFormFactor === 'list'">
22
- <table class="table ccm-image-chooser-list-view ccm-search-results-table">
22
+ <table class="table align-middle ccm-image-chooser-list-view ccm-search-results-table">
23
23
  <thead>
24
24
  <tr>
25
25
  <th></th>
@@ -33,18 +33,24 @@
33
33
  <a v-if="enableSort" href="#" @click.prevent="sortBy(dateSortColumn)">{{ i18n.uploaded }}</a>
34
34
  <span v-else>{{ i18n.uploaded }}</span>
35
35
  </th>
36
+ <th>{{ i18n.size }}</th>
37
+ <th>{{ i18n.width }}</th>
38
+ <th>{{ i18n.height }}</th>
36
39
  </tr>
37
40
  </thead>
38
41
  <tbody>
39
- <tr v-for="file in fileList" :key="(file.fID || file.treeNodeID) + 'list'" @click="onItemClick(file)">
42
+ <tr v-for="file in fileList" :key="keyPrefix + '-' + (file.fID || file.treeNodeID) + '-list'" @click="onItemClick(file)">
40
43
  <td>
41
44
  <input type="checkbox" :disabled="!canChooseFile(file)" v-if="multipleSelection && !file.isFolder" v-model="selectedFiles" :id="'file-' + file.fID" :value="file.fID">
42
45
  <input type="radio" :disabled="!canChooseFile(file)" v-if="!multipleSelection && !file.isFolder" v-model="selectedFiles" :id="'file-' + file.fID" :value="file.fID">
43
46
  </td>
44
- <td class="ccm-image-chooser-icon"><span v-html="file.resultsThumbnailImg" width="32" height="32"></span></td>
47
+ <td class="ccm-image-chooser-icon"><div :data-bs-content="getListHoverContent(file)"><span v-html="file.resultsThumbnailImg" width="32" height="32"></span></div></td>
45
48
  <td>{{file.fID}}</td>
46
49
  <td>{{file.title}}</td>
47
50
  <td>{{file.fvDateAdded}}</td>
51
+ <td>{{file.size}}</td>
52
+ <td>{{file.attributes ? file.attributes.width : ''}}</td>
53
+ <td>{{file.attributes ? file.attributes.height : ''}}</td>
48
54
  </tr>
49
55
  </tbody>
50
56
  </table>
@@ -71,7 +77,7 @@
71
77
  }
72
78
  </style>
73
79
  <script>
74
- /* global CCM_DISPATCHER_FILENAME, ConcreteAjaxRequest */
80
+ /* global CCM_DISPATCHER_FILENAME, bootstrap, ConcreteAjaxRequest */
75
81
  /* eslint-disable no-new */
76
82
  import Pagination from '../../Pagination'
77
83
 
@@ -83,7 +89,10 @@ export default {
83
89
  i18n: {
84
90
  id: 'ID',
85
91
  name: 'Name',
86
- uploaded: 'Uploaded'
92
+ uploaded: 'Uploaded',
93
+ size: 'Size',
94
+ width: 'Width',
95
+ height: 'Height'
87
96
  },
88
97
  currentPage: 1,
89
98
  rows: false,
@@ -103,6 +112,11 @@ export default {
103
112
  filters: {
104
113
  type: Array
105
114
  },
115
+ keyPrefix: {
116
+ type: String,
117
+ required: false,
118
+ default: ''
119
+ },
106
120
  enableSort: {
107
121
  type: Boolean,
108
122
  required: false,
@@ -174,6 +188,40 @@ export default {
174
188
  }
175
189
  },
176
190
  methods: {
191
+ getGridHoverContent(file) {
192
+ if (!file.isFolder) {
193
+ var title = ''
194
+ if (file.resultsThumbnailDetailImg) {
195
+ title += '<div>' + file.resultsThumbnailDetailImg + '</div>'
196
+ }
197
+ title += '<div class="text-center"><b>' + file.size + '</b></div>'
198
+ if (file.attributes && file.attributes.width && file.attributes.height) {
199
+ title += '<div class="text-center text-muted"><small>' + file.attributes.width + 'x' + file.attributes.height + '</small></div>'
200
+ }
201
+ return title
202
+ }
203
+ },
204
+ getListHoverContent(file) {
205
+ if (!file.isFolder) {
206
+ if (file.resultsThumbnailDetailImg) {
207
+ return '<div>' + file.resultsThumbnailDetailImg + '</div>'
208
+ }
209
+ }
210
+ },
211
+ setupHoverPreview() {
212
+ var $cells = $(this.$el).find('[data-bs-content]')
213
+ $cells.each(function(i) {
214
+ return new bootstrap.Popover($cells.get(i), {
215
+ container: '#ccm-tooltip-holder',
216
+ customClass: 'ccm-image-chooser-popover',
217
+ placement: 'bottom',
218
+ delay: 500,
219
+ trigger: 'hover',
220
+ fallbackPlacements: ['top'],
221
+ html: true
222
+ })
223
+ })
224
+ },
177
225
  canChooseFile(file) {
178
226
  var canChooseFile = -1
179
227
  if (this.filters) {
@@ -284,6 +332,17 @@ export default {
284
332
  }
285
333
  },
286
334
  watch: {
335
+ resultsFormFactor(value) {
336
+ var my = this
337
+ setTimeout(function() {
338
+ my.setupHoverPreview()
339
+ }, 5)
340
+ },
341
+ viewIsLoading(value) {
342
+ if (!value) {
343
+ this.setupHoverPreview()
344
+ }
345
+ },
287
346
  selectedFiles(value) {
288
347
  this.$emit('update:selectedFiles', Array.isArray(value) ? value : [value])
289
348
  },
@@ -5,7 +5,8 @@
5
5
  @breadcrumbItemClick="activeFolder = $event.folderId"
6
6
  :title="title"/>
7
7
 
8
- <files :selectedFiles.sync="selectedFiles"
8
+ <files :key-prefix="'folder-bookmark' + activeFolder"
9
+ :selectedFiles.sync="selectedFiles"
9
10
  :resultsFormFactor="formFactor"
10
11
  :routePath="routePath + activeFolder"
11
12
  :enable-pagination="true"
@@ -1,14 +1,27 @@
1
1
  <template>
2
2
  <div class="row">
3
- <div class="mb-3 col-sm-12">
4
- <header>
3
+ <div class="mb-3">
4
+ <header class="hstack align-items-start gap-3">
5
+ <div class="me-auto">
6
+ <h5>{{title}}</h5>
7
+ <breadcrumb v-if="breadcrumbItems" :breadcrumb-items="breadcrumbItems" @itemClick="onBreadcrumbItemClick" />
8
+ </div>
9
+ <form @submit.prevent="search" v-if="enableSearch">
10
+ <div class="ccm-header-search-form-input input-group input-group-sm">
11
+ <input type="text" class="form-control border-end-0" :placeholder="i18n.search"
12
+ autocomplete="false" v-model="searchText">
13
+ <button type="submit" class="input-group-icon">
14
+ <svg width="16" height="16">
15
+ <use xlink:href="#icon-search"/>
16
+ </svg>
17
+ </button>
18
+ </div>
19
+ </form>
5
20
  <button type="button" @click="toggleFormFactor" v-if="showFormFactorSelector"
6
- class="btn btn-sm float-end btn-secondary">
21
+ class="btn btn-sm btn-secondary">
7
22
  <i v-if="resultsFormFactor === 'grid'" class="fas fa-th"></i>
8
23
  <i v-if="resultsFormFactor === 'list'" class="fas fa-list"></i>
9
24
  </button>
10
- <h5>{{title}}</h5>
11
- <breadcrumb v-if="breadcrumbItems" :breadcrumb-items="breadcrumbItems" @itemClick="onBreadcrumbItemClick" />
12
25
  </header>
13
26
  </div>
14
27
  </div>
@@ -31,6 +44,14 @@ export default {
31
44
  required: false,
32
45
  default: true
33
46
  },
47
+ enableSearch: {
48
+ type: Boolean,
49
+ required: false,
50
+ default: false
51
+ },
52
+ resultsSearchQuery: {
53
+ type: String
54
+ },
34
55
  resultsFormFactor: {
35
56
  type: String,
36
57
  required: false,
@@ -42,7 +63,24 @@ export default {
42
63
  required: false
43
64
  }
44
65
  },
66
+ data() {
67
+ return {
68
+ i18n: {
69
+ search: 'Search'
70
+ },
71
+ searchText: ''
72
+ }
73
+ },
74
+ watch: {
75
+ resultsSearchQuery(value) {
76
+ this.searchText = value
77
+ }
78
+ },
45
79
  methods: {
80
+ search() {
81
+ const my = this
82
+ my.$emit('update:resultsSearchQuery', my.searchText)
83
+ },
46
84
  toggleFormFactor() {
47
85
  const my = this
48
86
  if (this.resultsFormFactor === 'grid') {
@@ -54,6 +92,15 @@ export default {
54
92
  onBreadcrumbItemClick(item) {
55
93
  this.$emit('breadcrumbItemClick', item)
56
94
  }
95
+ },
96
+ mounted() {
97
+ if (window.ccmi18n_filemanager) {
98
+ for (const key in this.i18n) {
99
+ if (window.ccmi18n_filemanager[key]) {
100
+ this.i18n[key] = window.ccmi18n_filemanager[key]
101
+ }
102
+ }
103
+ }
57
104
  }
58
105
  }
59
106
  </script>
@@ -1,8 +1,11 @@
1
1
  <template>
2
2
  <div>
3
- <chooser-header :resultsFormFactor.sync="formFactor" :title="title"/>
3
+ <chooser-header :resultsFormFactor.sync="formFactor"
4
+ :resultsSearchQuery.sync="searchQuery"
5
+ :title="title"/>
4
6
 
5
7
  <files :selectedFiles.sync="selectedFiles"
8
+ key-prefix="recent-uploads"
6
9
  :resultsFormFactor="formFactor"
7
10
  :filters="filters"
8
11
  routePath="/ccm/system/file/chooser/recent"
@@ -22,7 +25,8 @@ export default {
22
25
  },
23
26
  data: () => ({
24
27
  selectedFiles: [],
25
- formFactor: 'grid'
28
+ formFactor: 'grid',
29
+ searchQuery: ''
26
30
  }),
27
31
  props: {
28
32
  resultsFormFactor: {
@@ -49,10 +53,14 @@ export default {
49
53
  },
50
54
  formFactor(value) {
51
55
  this.$emit('update:resultsFormFactor', value)
56
+ },
57
+ searchQuery(value) {
58
+ this.$emit('update:resultsSearchQuery', value)
52
59
  }
53
60
  },
54
61
  mounted() {
55
62
  this.formFactor = this.resultsFormFactor
63
+ this.searchQuery = this.resultsSearchQuery
56
64
  }
57
65
  }
58
66
  </script>
@@ -20,6 +20,7 @@
20
20
  </div>
21
21
  <div class="mt-3" v-show="activeSearchPreset">
22
22
  <files v-if="activeSearchPreset"
23
+ key-prefix="saved-search"
23
24
  :selectedFiles.sync="selectedFiles"
24
25
  :resultsFormFactor="formFactor"
25
26
  :filters="filters"
@@ -1,22 +1,10 @@
1
1
  <template>
2
2
  <div>
3
- <chooser-header :resultsFormFactor.sync="formFactor" :title="title"/>
3
+ <chooser-header :resultsFormFactor.sync="formFactor"
4
+ :resultsSearchQuery.sync="searchQuery"
5
+ :enable-search="true"
6
+ :title="title"/>
4
7
 
5
- <div class="row mb-3">
6
- <div class="col-md-4 ms-auto">
7
- <form @submit.prevent="search">
8
- <div class="ccm-header-search-form-input input-group">
9
- <input type="text" class="form-control border-end-0" :placeholder="i18n.search"
10
- autocomplete="false" v-model="searchText">
11
- <button type="submit" class="input-group-icon">
12
- <svg width="16" height="16">
13
- <use xlink:href="#icon-search"/>
14
- </svg>
15
- </button>
16
- </div>
17
- </form>
18
- </div>
19
- </div>
20
8
  <div v-show="!keywords" class="text-center mt-5">
21
9
  <span class="search-icon my-4">
22
10
  <Icon icon="search" type="fas" color="#f4f4f4"/>
@@ -25,6 +13,7 @@
25
13
  </div>
26
14
  <div>
27
15
  <files v-if="keywords"
16
+ key-prefix="search"
28
17
  :selectedFiles.sync="selectedFiles"
29
18
  :resultsFormFactor="formFactor"
30
19
  :routePath="routePath + keywords"
@@ -50,14 +39,13 @@ export default {
50
39
  },
51
40
  data: () => ({
52
41
  i18n: {
53
- search: 'Search',
54
42
  initialSearchChooserTip: "Let's get some info on what you're looking for."
55
43
  },
56
- searchText: '',
57
44
  keywords: '',
58
45
  selectedFiles: [],
59
46
  routePath: '/ccm/system/file/chooser/search/',
60
- formFactor: 'grid'
47
+ formFactor: 'grid',
48
+ searchQuery: ''
61
49
  }),
62
50
  props: {
63
51
  resultsFormFactor: {
@@ -66,6 +54,9 @@ export default {
66
54
  default: 'grid', // grid | list
67
55
  validator: value => ['grid', 'list'].indexOf(value) !== -1
68
56
  },
57
+ resultsSearchQuery: {
58
+ type: String
59
+ },
69
60
  title: {
70
61
  type: String,
71
62
  required: true
@@ -89,6 +80,9 @@ export default {
89
80
  },
90
81
  formFactor(value) {
91
82
  this.$emit('update:resultsFormFactor', value)
83
+ },
84
+ searchQuery(value) {
85
+ this.keywords = value
92
86
  }
93
87
  },
94
88
  mounted() {
@@ -100,6 +94,7 @@ export default {
100
94
  }
101
95
  }
102
96
  this.formFactor = this.resultsFormFactor
97
+ this.searchQuery = this.resultsSearchQuery
103
98
  }
104
99
  }
105
100
  </script>
@@ -4,8 +4,8 @@
4
4
  <div class="row h-100">
5
5
  <div class="col-4 border-end p-3">
6
6
  <ul class="nav flex-column">
7
- <li class="nav-item" v-for="item in choosers" :key="item.id">
8
- <a :class="{'nav-link': true, 'active': activeNavItem.id === item.id}"
7
+ <li class="nav-item" v-for="item in choosers" :key="item.componentKey + '-' + item.id">
8
+ <a :class="{'nav-link': true, 'active': activeNavItem.id === item.id && activeNavItem.componentKey === item.componentKey}"
9
9
  @click.prevent="activateTab(item)"
10
10
  href="javascript:void(0)">
11
11
  {{item.title}}
@@ -14,8 +14,8 @@
14
14
  </ul>
15
15
  <hr>
16
16
  <ul class="nav flex-column">
17
- <li class="nav-item" v-for="item in uploaders" :key="item.id">
18
- <a :class="{'nav-link': true, 'active': activeNavItem.id === item.id}"
17
+ <li class="nav-item" v-for="item in uploaders" :key="item.componentKey + '-' + item.id">
18
+ <a :class="{'nav-link': true, 'active': activeNavItem.id === item.id && activeNavItem.componentKey === item.componentKey}"
19
19
  @click.prevent="activateTab(item)"
20
20
  href="javascript:void(0)">
21
21
  {{item.title}}
@@ -32,6 +32,8 @@
32
32
  :extraData="activeNavItem.data || {}"
33
33
  :multipleSelection="multipleSelection"
34
34
  :selectedFiles.sync="selectedFiles"
35
+ :searchChooserIsEnabled="searchChooserIsEnabled()"
36
+ :resultsSearchQuery.sync="resultsSearchQuery"
35
37
  :resultsFormFactor.sync="resultsFormFactor"
36
38
  :filesReadyToUpload.sync="filesReadyToUpload"
37
39
  :filters="filters"
@@ -86,6 +88,7 @@ export default {
86
88
  search: 'Search',
87
89
  uploadFiles: 'Upload Files'
88
90
  },
91
+ resultsSearchQuery: '',
89
92
  filesReadyToUpload: 0,
90
93
  activeNavItem: null,
91
94
  resultsFormFactor: 'grid',
@@ -155,9 +158,23 @@ export default {
155
158
  },
156
159
  uploaders() {
157
160
  this.applyLocalization()
161
+ },
162
+ resultsSearchQuery(value) {
163
+ if (value) {
164
+ this.activateTabByKey('search')
165
+ }
158
166
  }
159
167
  },
160
168
  methods: {
169
+ searchChooserIsEnabled() {
170
+ var enabled = false
171
+ this.choosers.forEach(function(chooser) {
172
+ if (chooser.id === 'search') {
173
+ enabled = true
174
+ }
175
+ })
176
+ return enabled
177
+ },
161
178
  applyLocalization() {
162
179
  if (this.choosers) {
163
180
  for (const chooser of this.choosers) {
@@ -72,6 +72,12 @@ export default {
72
72
  this.$emit('change', null)
73
73
  }
74
74
  }
75
+ if (!this.isFirstRun) {
76
+ // Fire the jQuery change event.
77
+ // @deprecated - do not use this unless you have to. Use this component directly instead and listen
78
+ // to its change event. This will be removed when the jQuery dependency is removed.
79
+ $('input[name=' + this.inputName + ']').trigger('change')
80
+ }
75
81
  this.isFirstRun = false
76
82
  }
77
83
  }
@@ -1,9 +1,18 @@
1
1
  .ccm-ui {
2
+ .ccm-image-chooser-popover {
3
+ max-width: 400px;
4
+
5
+ img {
6
+ height: auto;
7
+ margin-bottom: 1rem;
8
+ max-width: 100%;
9
+ }
10
+ }
11
+
2
12
  table.ccm-image-chooser-list-view {
3
13
  img {
4
14
  height: 32px;
5
15
  width: 32px;
6
16
  }
7
-
8
17
  }
9
18
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@concretecms/bedrock",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "The asset framework and dependencies for Concrete CMS.",
5
5
  "scripts": {
6
6
  "lint": "standardx \"**/*.{js,vue}\" && stylelint assets/**/*.{scss,vue}",