mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Replace soft delete with archive, add unarchive
This commit is contained in:
@@ -13,12 +13,22 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const deleting = ref(false);
|
||||
const softDeleting = ref(false);
|
||||
const archiving = ref(false);
|
||||
const edits = ref({});
|
||||
const isNew = computed(() => primaryKey.value === '+');
|
||||
const isBatch = computed(() => typeof primaryKey.value === 'string' && primaryKey.value.includes(','));
|
||||
const isSingle = computed(() => !!collectionInfo.value?.meta?.singleton);
|
||||
|
||||
const isArchived = computed(() => {
|
||||
if (!collectionInfo.value?.meta?.archive_field) return null;
|
||||
|
||||
if (collectionInfo.value.meta.archive_value === 'true') {
|
||||
return item.value?.[collectionInfo.value.meta.archive_field] === true;
|
||||
}
|
||||
|
||||
return item.value?.[collectionInfo.value.meta.archive_field] === collectionInfo.value.meta.archive_value;
|
||||
});
|
||||
|
||||
const endpoint = computed(() => {
|
||||
return collection.value.startsWith('directus_')
|
||||
? `/${collection.value.substring(9)}`
|
||||
@@ -45,9 +55,10 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
save,
|
||||
isNew,
|
||||
remove,
|
||||
softDelete,
|
||||
deleting,
|
||||
softDeleting,
|
||||
archive,
|
||||
isArchived,
|
||||
archiving,
|
||||
saveAsCopy,
|
||||
isBatch,
|
||||
getItem,
|
||||
@@ -177,21 +188,36 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
}
|
||||
}
|
||||
|
||||
async function softDelete() {
|
||||
if (!collectionInfo.value?.meta?.soft_delete_field) return;
|
||||
async function archive() {
|
||||
if (!collectionInfo.value?.meta?.archive_field) return;
|
||||
|
||||
softDeleting.value = true;
|
||||
archiving.value = true;
|
||||
|
||||
const field = collectionInfo.value.meta.soft_delete_field;
|
||||
const value = collectionInfo.value.meta.soft_delete_value;
|
||||
const field = collectionInfo.value.meta.archive_field;
|
||||
|
||||
let archiveValue: any = collectionInfo.value.meta.archive_value;
|
||||
if (archiveValue === 'true') archiveValue = true;
|
||||
if (archiveValue === 'false') archiveValue = false;
|
||||
|
||||
let unarchiveValue: any = collectionInfo.value.meta.unarchive_value;
|
||||
if (unarchiveValue === 'true') unarchiveValue = true;
|
||||
if (unarchiveValue === 'false') unarchiveValue = false;
|
||||
|
||||
try {
|
||||
let value: any = item.value[field] === archiveValue ? unarchiveValue : archiveValue;
|
||||
|
||||
if (value === 'true') value = true;
|
||||
if (value === 'false') value = false;
|
||||
|
||||
item.value = {
|
||||
...item.value,
|
||||
[field]: value,
|
||||
};
|
||||
|
||||
await api.patch(itemEndpoint.value, {
|
||||
[field]: value,
|
||||
});
|
||||
|
||||
item.value = null;
|
||||
|
||||
notify({
|
||||
title: i18n.tc('item_delete_success', isBatch.value ? 2 : 1),
|
||||
text: i18n.tc('item_in', isBatch.value ? 2 : 1, {
|
||||
@@ -212,7 +238,7 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
|
||||
throw err;
|
||||
} finally {
|
||||
softDeleting.value = false;
|
||||
archiving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ function unbind(element: HTMLElement) {
|
||||
delete handlers[element.dataset.tooltip as string];
|
||||
}
|
||||
|
||||
function update(element: HTMLElement, binding: DirectiveBinding) {
|
||||
console.log(element);
|
||||
}
|
||||
|
||||
const Tooltip: DirectiveOptions = {
|
||||
bind,
|
||||
unbind,
|
||||
@@ -32,6 +36,9 @@ const Tooltip: DirectiveOptions = {
|
||||
bind(element, binding);
|
||||
} else if (!binding.value && binding.oldValue) {
|
||||
unbind(element);
|
||||
} else {
|
||||
unbind(element);
|
||||
bind(element, binding);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
"archive_confirm": "Are you sure you want to archive this item?",
|
||||
"archive_confirm_count": "No Items Selected | Are you sure you want to archive this item? | Are you sure you want to archive these {count} items?",
|
||||
|
||||
"unarchive": "Unarchive",
|
||||
"unarchive_confirm": "Are you sure you want to unarchive this item?",
|
||||
"unarchive_confirm_count": "No Items Selected | Are you sure you want to unarchive this item? | Are you sure you want to unarchive these {count} items?",
|
||||
|
||||
"nested_files_folders_will_be_moved": "Nested files and folders will be moved one level up.",
|
||||
|
||||
"markdown": "Markdown",
|
||||
|
||||
@@ -75,12 +75,12 @@
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog
|
||||
v-model="confirmSoftDelete"
|
||||
v-if="selection.length > 0 && currentCollection.meta && currentCollection.meta.soft_delete_field"
|
||||
v-model="confirmArchive"
|
||||
v-if="selection.length > 0 && currentCollection.meta && currentCollection.meta.archive_field"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-button
|
||||
:disabled="batchSoftDeleteAllowed !== true"
|
||||
:disabled="batchArchiveAllowed !== true"
|
||||
rounded
|
||||
icon
|
||||
class="action-soft-delete"
|
||||
@@ -95,10 +95,10 @@
|
||||
<v-card-title>{{ $tc('archive_confirm_count', selection.length) }}</v-card-title>
|
||||
|
||||
<v-card-actions>
|
||||
<v-button @click="confirmSoftDelete = false" secondary>
|
||||
<v-button @click="confirmArchive = false" secondary>
|
||||
{{ $t('cancel') }}
|
||||
</v-button>
|
||||
<v-button @click="batchDelete" class="action-soft-delete" :loading="softDeleting">
|
||||
<v-button @click="batchDelete" class="action-soft-delete" :loading="archiving">
|
||||
{{ $t('archive') }}
|
||||
</v-button>
|
||||
</v-card-actions>
|
||||
@@ -279,9 +279,9 @@ export default defineComponent({
|
||||
confirmDelete,
|
||||
deleting,
|
||||
batchDelete,
|
||||
confirmSoftDelete,
|
||||
softDelete,
|
||||
softDeleting,
|
||||
confirmArchive,
|
||||
archive,
|
||||
archiving,
|
||||
error: deleteError,
|
||||
} = useBatchDelete();
|
||||
|
||||
@@ -303,7 +303,7 @@ export default defineComponent({
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const { batchEditAllowed, batchSoftDeleteAllowed, batchDeleteAllowed, createAllowed } = usePermissions();
|
||||
const { batchEditAllowed, batchArchiveAllowed, batchDeleteAllowed, createAllowed } = usePermissions();
|
||||
|
||||
return {
|
||||
addNewLink,
|
||||
@@ -332,11 +332,11 @@ export default defineComponent({
|
||||
breadcrumb,
|
||||
marked,
|
||||
clearFilters,
|
||||
confirmSoftDelete,
|
||||
softDelete,
|
||||
softDeleting,
|
||||
confirmArchive,
|
||||
archive,
|
||||
archiving,
|
||||
batchEditAllowed,
|
||||
batchSoftDeleteAllowed,
|
||||
batchArchiveAllowed,
|
||||
batchDeleteAllowed,
|
||||
deleteError,
|
||||
createAllowed,
|
||||
@@ -369,12 +369,12 @@ export default defineComponent({
|
||||
const confirmDelete = ref(false);
|
||||
const deleting = ref(false);
|
||||
|
||||
const confirmSoftDelete = ref(false);
|
||||
const softDeleting = ref(false);
|
||||
const confirmArchive = ref(false);
|
||||
const archiving = ref(false);
|
||||
|
||||
const error = ref<any>();
|
||||
|
||||
return { confirmDelete, deleting, batchDelete, confirmSoftDelete, softDeleting, softDelete, error };
|
||||
return { confirmDelete, deleting, batchDelete, confirmArchive, archiving, archive, error };
|
||||
|
||||
async function batchDelete() {
|
||||
deleting.value = true;
|
||||
@@ -394,10 +394,10 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
async function softDelete() {
|
||||
if (!currentCollection.value?.meta?.soft_delete_field) return;
|
||||
async function archive() {
|
||||
if (!currentCollection.value?.meta?.archive_field) return;
|
||||
|
||||
softDeleting.value = true;
|
||||
archiving.value = true;
|
||||
|
||||
const batchPrimaryKeys = selection.value;
|
||||
|
||||
@@ -406,11 +406,11 @@ export default defineComponent({
|
||||
await layout.value?.refresh?.();
|
||||
|
||||
selection.value = [];
|
||||
confirmSoftDelete.value = false;
|
||||
confirmArchive.value = false;
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
} finally {
|
||||
softDeleting.value = false;
|
||||
archiving.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -486,8 +486,8 @@ export default defineComponent({
|
||||
return !!updatePermissions;
|
||||
});
|
||||
|
||||
const batchSoftDeleteAllowed = computed(() => {
|
||||
if (!currentCollection.value?.meta?.soft_delete_field) return false;
|
||||
const batchArchiveAllowed = computed(() => {
|
||||
if (!currentCollection.value?.meta?.archive_field) return false;
|
||||
const admin = userStore.state?.currentUser?.role.admin === true;
|
||||
if (admin) return true;
|
||||
|
||||
@@ -497,7 +497,7 @@ export default defineComponent({
|
||||
if (!updatePermissions) return false;
|
||||
if (!updatePermissions.fields) return false;
|
||||
if (updatePermissions.fields === '*') return true;
|
||||
return updatePermissions.fields.split(',').includes(currentCollection.value.meta.soft_delete_field);
|
||||
return updatePermissions.fields.split(',').includes(currentCollection.value.meta.archive_field);
|
||||
});
|
||||
|
||||
const batchDeleteAllowed = computed(() => {
|
||||
@@ -520,7 +520,7 @@ export default defineComponent({
|
||||
return !!createPermissions;
|
||||
});
|
||||
|
||||
return { batchEditAllowed, batchSoftDeleteAllowed, batchDeleteAllowed, createAllowed };
|
||||
return { batchEditAllowed, batchArchiveAllowed, batchDeleteAllowed, createAllowed };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -74,33 +74,33 @@
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog
|
||||
v-if="collectionInfo.meta && collectionInfo.meta.soft_delete_field && !isNew"
|
||||
v-model="confirmSoftDelete"
|
||||
:disabled="softDeleteAllowed === false"
|
||||
v-if="collectionInfo.meta && collectionInfo.meta.archive_field && !isNew"
|
||||
v-model="confirmArchive"
|
||||
:disabled="archiveAllowed === false"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-button
|
||||
rounded
|
||||
icon
|
||||
class="action-soft-delete"
|
||||
v-tooltip.bottom="softDeleteAllowed ? $t('archive') : $t('not_allowed')"
|
||||
class="action-archive"
|
||||
v-tooltip.bottom="archiveTooltip"
|
||||
@click="on"
|
||||
:disabled="item === null || softDeleteAllowed !== true"
|
||||
:disabled="item === null || archiveAllowed !== true"
|
||||
v-if="collectionInfo.meta.singleton === false"
|
||||
>
|
||||
<v-icon name="archive" outline />
|
||||
<v-icon :name="isArchived ? 'unarchive' : 'archive'" outline />
|
||||
</v-button>
|
||||
</template>
|
||||
|
||||
<v-card>
|
||||
<v-card-title>{{ $t('archive_confirm') }}</v-card-title>
|
||||
<v-card-title>{{ isArchived ? $t('unarchive_confirm') : $t('archive_confirm') }}</v-card-title>
|
||||
|
||||
<v-card-actions>
|
||||
<v-button @click="confirmSoftDelete = false" secondary>
|
||||
<v-button @click="confirmArchive = false" secondary>
|
||||
{{ $t('cancel') }}
|
||||
</v-button>
|
||||
<v-button @click="deleteAndQuit(true)" class="action-soft-delete" :loading="softDeleting">
|
||||
{{ $t('archive') }}
|
||||
<v-button @click="toggleArchive" class="action-archive" :loading="archiving">
|
||||
{{ isArchived ? $t('unarchive') : $t('archive') }}
|
||||
</v-button>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
@@ -237,8 +237,9 @@ export default defineComponent({
|
||||
save,
|
||||
remove,
|
||||
deleting,
|
||||
softDelete,
|
||||
softDeleting,
|
||||
archive,
|
||||
archiving,
|
||||
isArchived,
|
||||
saveAsCopy,
|
||||
isBatch,
|
||||
refresh,
|
||||
@@ -247,7 +248,7 @@ export default defineComponent({
|
||||
const hasEdits = computed<boolean>(() => Object.keys(edits.value).length > 0);
|
||||
|
||||
const confirmDelete = ref(false);
|
||||
const confirmSoftDelete = ref(false);
|
||||
const confirmArchive = ref(false);
|
||||
|
||||
const confirmLeave = ref(false);
|
||||
const leaveTo = ref<string | null>(null);
|
||||
@@ -272,6 +273,12 @@ export default defineComponent({
|
||||
: i18n.t('editing_in', { collection: collectionInfo.value?.name });
|
||||
});
|
||||
|
||||
const archiveTooltip = computed(() => {
|
||||
if (archiveAllowed.value === false) return i18n.t('not_allowed');
|
||||
if (isArchived.value === true) return i18n.t('unarchive');
|
||||
return i18n.t('archive');
|
||||
});
|
||||
|
||||
useShortcut('mod+s', saveAndStay);
|
||||
useShortcut('mod+shift+s', saveAndAddNew);
|
||||
|
||||
@@ -287,7 +294,7 @@ export default defineComponent({
|
||||
return next();
|
||||
};
|
||||
|
||||
const { deleteAllowed, softDeleteAllowed, saveAllowed, updateAllowed } = usePermissions();
|
||||
const { deleteAllowed, archiveAllowed, saveAllowed, updateAllowed } = usePermissions();
|
||||
|
||||
return {
|
||||
item,
|
||||
@@ -302,14 +309,15 @@ export default defineComponent({
|
||||
saveAndQuit,
|
||||
deleteAndQuit,
|
||||
confirmDelete,
|
||||
confirmSoftDelete,
|
||||
confirmArchive,
|
||||
deleting,
|
||||
softDeleting,
|
||||
archiving,
|
||||
saveAndStay,
|
||||
saveAndAddNew,
|
||||
saveAsCopyAndNavigate,
|
||||
isBatch,
|
||||
templateValues,
|
||||
archiveTooltip,
|
||||
breadcrumb,
|
||||
title,
|
||||
revisionsDrawerDetail,
|
||||
@@ -321,8 +329,10 @@ export default defineComponent({
|
||||
navigationGuard,
|
||||
deleteAllowed,
|
||||
saveAllowed,
|
||||
softDeleteAllowed,
|
||||
archiveAllowed,
|
||||
isArchived,
|
||||
updateAllowed,
|
||||
toggleArchive,
|
||||
};
|
||||
|
||||
function useBreadcrumb() {
|
||||
@@ -369,16 +379,22 @@ export default defineComponent({
|
||||
router.push(`/collections/${props.collection}/${newPrimaryKey}`);
|
||||
}
|
||||
|
||||
async function deleteAndQuit(soft = false) {
|
||||
if (soft) {
|
||||
await softDelete();
|
||||
} else {
|
||||
await remove();
|
||||
}
|
||||
async function deleteAndQuit() {
|
||||
await remove();
|
||||
|
||||
router.push(`/collections/${props.collection}`);
|
||||
}
|
||||
|
||||
async function toggleArchive() {
|
||||
await archive();
|
||||
|
||||
if (isArchived.value === true) {
|
||||
router.push(`/collections/${props.collection}`);
|
||||
} else {
|
||||
confirmArchive.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function discardAndLeave() {
|
||||
if (!leaveTo.value) return;
|
||||
edits.value = {};
|
||||
@@ -396,15 +412,15 @@ export default defineComponent({
|
||||
});
|
||||
const updateAllowed = computed(() => isAllowed(collection.value, 'update', item.value));
|
||||
|
||||
const softDeleteAllowed = computed(() => {
|
||||
if (!collectionInfo.value?.meta?.soft_delete_field) return false;
|
||||
const archiveAllowed = computed(() => {
|
||||
if (!collectionInfo.value?.meta?.archive_field) return false;
|
||||
|
||||
return isAllowed(collection.value, 'update', {
|
||||
[collectionInfo.value.meta.soft_delete_field]: collectionInfo.value.meta.soft_delete_value,
|
||||
[collectionInfo.value.meta.archive_field]: collectionInfo.value.meta.archive_value,
|
||||
});
|
||||
});
|
||||
|
||||
return { deleteAllowed, saveAllowed, softDeleteAllowed, updateAllowed };
|
||||
return { deleteAllowed, saveAllowed, archiveAllowed, updateAllowed };
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -420,7 +436,7 @@ export default defineComponent({
|
||||
--v-button-color-hover: var(--danger);
|
||||
}
|
||||
|
||||
.action-soft-delete {
|
||||
.action-archive {
|
||||
--v-button-background-color: var(--warning-25);
|
||||
--v-button-color: var(--warning);
|
||||
--v-button-background-color-hover: var(--warning-50);
|
||||
|
||||
@@ -15,8 +15,9 @@ export interface CollectionRaw {
|
||||
translation: Translation[] | null;
|
||||
display_template: string | null;
|
||||
sort_field: string | null;
|
||||
soft_delete_field: string | null;
|
||||
soft_delete_value: string | null;
|
||||
archive_field: string | null;
|
||||
archive_value: string | null;
|
||||
unarchive_value: string | null;
|
||||
} | null;
|
||||
schema: Record<string, any>;
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
const showArchiveToggle = computed(() => !!collectionInfo.value?.meta?.soft_delete_field);
|
||||
const showArchiveToggle = computed(() => !!collectionInfo.value?.meta?.archive_field);
|
||||
|
||||
const archived = computed({
|
||||
get() {
|
||||
@@ -156,16 +156,16 @@ export default defineComponent({
|
||||
);
|
||||
},
|
||||
set(showArchived: boolean) {
|
||||
if (!collectionInfo.value?.meta?.soft_delete_field) return;
|
||||
if (!collectionInfo.value?.meta?.archive_field) return;
|
||||
|
||||
if (showArchived === false) {
|
||||
emit('input', [
|
||||
...filters.value,
|
||||
{
|
||||
key: 'hide-archived',
|
||||
field: collectionInfo.value.meta.soft_delete_field,
|
||||
field: collectionInfo.value.meta.archive_field,
|
||||
operator: 'neq',
|
||||
value: collectionInfo.value.meta.soft_delete_value!,
|
||||
value: collectionInfo.value.meta.archive_value!,
|
||||
locked: true,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -42,7 +42,7 @@ export default defineComponent({
|
||||
default: null,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Object, Array],
|
||||
type: [String, Number, Object, Array, Boolean],
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
|
||||
Reference in New Issue
Block a user