Skip to content

Commit 7a7dcca

Browse files
authored
Pro 8003 manage tags (#4992)
* apos context menu expose show method to be called from template ref like we do with hide * updates manage tags button to open image tags manager, refresh images and tags when closing
1 parent 09e96b2 commit 7a7dcca

4 files changed

Lines changed: 81 additions & 45 deletions

File tree

modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
@search="search"
7878
@filter="filter"
7979
@batch="handleBatchAction"
80+
@refresh-data="refreshData"
8081
/>
8182
</template>
8283
<template #bodyMain>
@@ -315,7 +316,7 @@ export default {
315316
this.isFirstLoading = false;
316317
});
317318
318-
this.batchTags = await this.getTags();
319+
await this.getTags();
319320
},
320321
321322
beforeUnmount() {
@@ -373,28 +374,26 @@ export default {
373374
delete qs[prop];
374375
};
375376
}
376-
const apiResponse = (await apos.http.get(
377-
this.moduleOptions.action, {
378-
qs,
379-
draft: true
380-
}
381-
));
377+
const apiResponse = await apos.http.get(this.moduleOptions.action, {
378+
qs,
379+
draft: true
380+
});
382381
383382
if (options.tags) {
384383
if (filtered) {
385384
// We never filter the tag list because they are presented like
386385
// folders, and folders don't disappear when empty. So we need to make
387386
// a separate query for distinct tags if our first query was filtered
388-
const apiResponse = (await apos.http.get(
387+
const tagApiResponse = await apos.http.get(
389388
this.moduleOptions.action, {
390389
busy: true,
391390
qs: {
392391
choices: '_tags'
393392
},
394393
draft: true
395394
}
396-
));
397-
result.tagList = apiResponse.choices._tags;
395+
);
396+
result.tagList = tagApiResponse.choices._tags;
398397
} else {
399398
result.tagList = apiResponse.choices ? apiResponse.choices._tags : [];
400399
}
@@ -629,27 +628,7 @@ export default {
629628
}
630629
631630
if (docIds && action === 'tag') {
632-
const { items: updatedImages, tagList } = await this.getMedia({
633-
_ids: docIds,
634-
tags: true
635-
});
636-
updatedImages.forEach(this.updateStateDoc);
637-
638-
this.batchTags = await this.getTags();
639-
if (Array.isArray(tagList)) {
640-
this.tagList = tagList;
641-
}
642-
643-
await this.updateEditing(null);
644-
645-
// If we were editing one, replacing it.
646-
if (this.editing && updatedImages.length === 1) {
647-
this.modified = false;
648-
// Needed to refresh the AposMediaManagerEditor
649-
await this.$nextTick();
650-
await this.updateEditing(updatedImages.at(0)._id);
651-
}
652-
631+
await this.refreshData(docIds);
653632
return;
654633
}
655634
@@ -662,6 +641,29 @@ export default {
662641
await this.updateEditing(null);
663642
},
664643
644+
async refreshData(docIds) {
645+
const { items: updatedImages, tagList } = await this.getMedia({
646+
...docIds && { _ids: docIds },
647+
tags: true
648+
});
649+
updatedImages.forEach(this.updateStateDoc);
650+
651+
await this.getTags();
652+
if (Array.isArray(tagList)) {
653+
this.tagList = tagList;
654+
}
655+
656+
await this.updateEditing(null);
657+
658+
// If we were editing one, replacing it.
659+
if (this.editing && updatedImages.length === 1) {
660+
this.modified = false;
661+
// Needed to refresh the AposMediaManagerEditor
662+
await this.$nextTick();
663+
await this.updateEditing(updatedImages.at(0)._id);
664+
}
665+
},
666+
665667
updateStateDoc(doc) {
666668
const index = this.items.findIndex(item => item._id === doc._id);
667669
const checkedIndex = this.checkedDocs
@@ -817,7 +819,7 @@ export default {
817819
};
818820
});
819821
820-
return tags;
822+
this.batchTags = tags;
821823
} catch (error) {
822824
// TODO: notify message
823825
apos.notify(error.message);

modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
@added="title => updateTag('create', { title })"
3131
@checked="slug => updateTag('add', { slug })"
3232
@unchecked="slug => updateTag('remove', { slug })"
33+
@refresh-data="$emit('refresh-data')"
3334
/>
3435
<AposButton
3536
v-else-if="!operations"
@@ -159,11 +160,12 @@ export default {
159160
}
160161
},
161162
emits: [
162-
'select-click',
163+
'batch',
163164
'filter',
164-
'search',
165165
'page-change',
166-
'batch'
166+
'refresh-data',
167+
'search',
168+
'select-click'
167169
],
168170
data() {
169171
return {

modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ const menuResizeObserver = new ResizeObserver((entries) => {
205205
206206
defineExpose({
207207
hide,
208+
show,
208209
setDropdownPosition
209210
});
210211

modules/@apostrophecms/ui/ui/apos/components/AposTagApply.vue

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
<template>
22
<AposContextMenu
3+
ref="contextMenu"
34
:menu-placement
45
:button
56
:disabled="isDisabled"
67
class="apos-apply-tag-menu"
8+
:class="{ 'apos-apply-tag-menu--create-ui': createUi }"
79
@open="isOpen = $event"
810
@close="clearSearch"
911
>
@@ -61,7 +63,7 @@
6163
type="quiet"
6264
:disabled="isTagFound"
6365
:disable-focus="!isOpen"
64-
@click.stop="createOrManage"
66+
@click.stop="createOrSearch"
6567
/>
6668
</div>
6769
</div>
@@ -128,9 +130,10 @@ const props = defineProps({
128130
}
129131
});
130132
131-
const emit = defineEmits([ 'added', 'checked', 'unchecked' ]);
133+
const emit = defineEmits([ 'added', 'checked', 'unchecked', 'refresh-data' ]);
132134
133135
const textInputEl = useTemplateRef('textInput');
136+
const contextMenuEl = useTemplateRef('contextMenu');
134137
135138
const isOpen = ref(false);
136139
const searchValue = ref({ data: '' });
@@ -259,7 +262,26 @@ function checkOrCreate() {
259262
create();
260263
};
261264
262-
function createOrManage() {
265+
async function createOrManage() {
266+
if (createUi.value) {
267+
const imageTagMod = apos.modules['@apostrophecms/image-tag'];
268+
await apos.modal.execute(imageTagMod.components.managerModal, {
269+
moduleName: imageTagMod.name
270+
});
271+
272+
emit('refresh-data');
273+
contextMenuEl.value.show();
274+
return;
275+
}
276+
277+
if (searchValue.value.data) {
278+
return create();
279+
}
280+
281+
toggleCreateUi();
282+
}
283+
284+
function createOrSearch() {
263285
if (!createUi.value && searchValue.value.data) {
264286
return create();
265287
}
@@ -348,6 +370,11 @@ function getCheckedState(tag) {
348370
padding: 0;
349371
}
350372
373+
.apos-apply-tag-menu:not(.apos-apply-tag-menu--create-ui)
374+
:deep(.apos-context-menu__pane) {
375+
height: 450px;
376+
}
377+
351378
.apos-apply-tag-menu__inner,
352379
.apos-apply-tag-menu__tags,
353380
.apos-apply-tag-menu__empty {
@@ -383,27 +410,31 @@ function getCheckedState(tag) {
383410
384411
.apos-apply-tag-menu__tags {
385412
@include apos-list-reset();
386-
387-
& {
388-
max-height: 315px;
389-
overflow-y: auto;
390-
}
391413
}
392414
393415
.apos-apply-tag-menu__tag {
394-
padding: $spacing-base $spacing-double;
416+
padding: 11px 20px;
395417
border-top: 1px solid var(--a-base-9);
418+
419+
&:last-child {
420+
border-bottom: 1px solid var(--a-base-9);
421+
}
396422
}
397423
398424
.apos-apply-tag-menu__search-body {
399425
flex: 1;
426+
overflow-y: auto;
400427
}
401428
429+
/* TODO: Fix UI when no tags found or none exist */
402430
.apos-apply-tag-menu__empty {
403431
display: flex;
432+
box-sizing: border-box;
404433
flex-direction: column;
405434
align-items: center;
406-
padding: 50px 20px 60px;
435+
justify-content: center;
436+
height: 100%;
437+
padding: 0 0 10px;
407438
}
408439
409440
.apos-apply-tag-menu__empty-message {

0 commit comments

Comments
 (0)