Reflect Content Version in URL / history (#21624)

* Reflect Content Version in URL / history

* Add changeset

* Update lockfile

* Update .changeset/stupid-oranges-listen.md
This commit is contained in:
Pascal Jufer
2024-02-29 03:08:49 +01:00
committed by GitHub
parent 6c3efce4e5
commit 9dbc1f4957
6 changed files with 99 additions and 20 deletions

View File

@@ -0,0 +1,5 @@
---
'@directus/app': minor
---
Integrated the Content Version in the browser URL / history to enable browser navigation and page refresh

View File

@@ -98,6 +98,7 @@
"@vitejs/plugin-vue": "5.0.4",
"@vue/test-utils": "2.4.4",
"@vueuse/core": "10.7.2",
"@vueuse/router": "10.9.0",
"apexcharts": "3.46.0",
"axios": "1.6.7",
"base-64": "1.0.0",

View File

@@ -1,9 +1,11 @@
import { ref, unref, type Ref } from 'vue';
import { useRoute } from 'vue-router';
import { isEqual } from 'lodash';
import { MaybeRef, Ref, ref, unref } from 'vue';
import { LocationQuery, useRoute } from 'vue-router';
import { useNavigationGuard } from './use-navigation-guard';
type EditsGuardOptions = {
ignorePrefix?: string | Ref<string>;
ignorePrefix?: MaybeRef<string>;
compareQuery?: MaybeRef<string[]>;
};
export function useEditsGuard(hasEdits: Ref<boolean>, opts?: EditsGuardOptions) {
@@ -11,9 +13,12 @@ export function useEditsGuard(hasEdits: Ref<boolean>, opts?: EditsGuardOptions)
const leaveTo = ref<string | null>(null);
useNavigationGuard(hasEdits, (to) => {
const { path } = useRoute();
const { path, query } = useRoute();
if (hasEdits.value && !isSubpath(path, to.path) && !isIgnoredPath(unref(opts?.ignorePrefix), to.path)) {
if (
hasEdits.value &&
((!isSubpath(path, to.path) && !isIgnoredPath(to.path)) || hasChangedQuery(query, to.query))
) {
confirmLeave.value = true;
leaveTo.value = to.fullPath;
return false;
@@ -23,16 +28,31 @@ export function useEditsGuard(hasEdits: Ref<boolean>, opts?: EditsGuardOptions)
});
return { confirmLeave, leaveTo };
}
function isSubpath(currentPath: string, newPath: string) {
return (
currentPath === newPath || (newPath.startsWith(currentPath) && newPath.substring(currentPath.length).includes('/'))
);
}
function isSubpath(currentPath: string, newPath: string) {
return (
currentPath === newPath ||
(newPath.startsWith(currentPath) && newPath.substring(currentPath.length).includes('/'))
);
}
function isIgnoredPath(ignorePrefix: string | undefined, newPath: string) {
if (!ignorePrefix) return false;
function isIgnoredPath(newPath: string) {
const ignorePrefix = unref(opts?.ignorePrefix);
return newPath.startsWith(ignorePrefix);
if (!ignorePrefix) return false;
return newPath.startsWith(ignorePrefix);
}
function hasChangedQuery(currentQuery: LocationQuery, newQuery: LocationQuery) {
const compareQuery = unref(opts?.compareQuery);
if (!compareQuery) return false;
for (const query of compareQuery) {
if (!isEqual(currentQuery[query], newQuery[query])) return true;
}
return false;
}
}

View File

@@ -1,17 +1,45 @@
import api from '@/api';
import { unexpectedError } from '@/utils/unexpected-error';
import { ContentVersion, Filter, Query } from '@directus/types';
import { useRouteQuery } from '@vueuse/router';
import { Ref, computed, ref, unref, watch } from 'vue';
import { useCollectionPermissions } from './use-permissions';
export function useVersions(collection: Ref<string>, isSingleton: Ref<boolean>, primaryKey: Ref<string | null>) {
const { readAllowed: readVersionsAllowed } = useCollectionPermissions('directus_versions');
const currentVersion = ref<ContentVersion | null>(null);
const versions = ref<ContentVersion[] | null>(null);
const loading = ref(false);
const saveVersionLoading = ref(false);
const { readAllowed: readVersionsAllowed } = useCollectionPermissions('directus_versions');
const queryVersion = useRouteQuery<string | null>('version', null, {
transform: (value) => (Array.isArray(value) ? value[0] : value),
mode: 'push',
});
watch(
[queryVersion, versions],
([newQueryVersion, newVersions]) => {
if (!newVersions) return;
let version;
if (queryVersion) {
version = newVersions.find((version) => version.key === newQueryVersion);
}
if (version?.key === currentVersion.value?.key) return;
currentVersion.value = version ?? null;
},
{ immediate: true },
);
watch(currentVersion, (newCurrentVersion) => {
queryVersion.value = newCurrentVersion?.key ?? null;
});
const query = computed<Query>(() => {
if (!currentVersion.value) return {};
@@ -22,7 +50,7 @@ export function useVersions(collection: Ref<string>, isSingleton: Ref<boolean>,
watch(
[collection, isSingleton, primaryKey],
([newCollection, _newIsSingleton, _newPrimaryKey], [oldCollection, _oldIsSingleton, _oldPrimaryKey]) => {
([newCollection], [oldCollection]) => {
if (newCollection !== oldCollection) currentVersion.value = null;
getVersions();
},

View File

@@ -89,7 +89,7 @@ const {
const { templateData } = useTemplateData(collectionInfo, primaryKey);
const { confirmLeave, leaveTo } = useEditsGuard(hasEdits);
const { confirmLeave, leaveTo } = useEditsGuard(hasEdits, { compareQuery: ['version'] });
const confirmDelete = ref(false);
const confirmArchive = ref(false);

29
pnpm-lock.yaml generated
View File

@@ -721,6 +721,9 @@ importers:
'@vueuse/core':
specifier: 10.7.2
version: 10.7.2(vue@3.4.19)
'@vueuse/router':
specifier: 10.9.0
version: 10.9.0(vue-router@4.3.0)(vue@3.4.19)
apexcharts:
specifier: 3.46.0
version: 3.46.0
@@ -961,7 +964,7 @@ importers:
version: 5.3.3
vite:
specifier: 5.1.1
version: 5.1.1(sass@1.71.1)
version: 5.1.1(@types/node@18.19.17)
vite-plugin-dts:
specifier: 3.7.3
version: 3.7.3(rollup@4.10.0)(typescript@5.3.3)(vite@5.1.1)
@@ -1890,7 +1893,7 @@ importers:
version: 5.3.3
vite:
specifier: 5.1.1
version: 5.1.1(sass@1.71.1)
version: 5.1.1(@types/node@18.19.17)
vite-plugin-dts:
specifier: 3.7.3
version: 3.7.3(rollup@4.10.0)(typescript@5.3.3)(vite@5.1.1)
@@ -8761,6 +8764,19 @@ packages:
/@vueuse/metadata@10.7.2:
resolution: {integrity: sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==}
/@vueuse/router@10.9.0(vue-router@4.3.0)(vue@3.4.19):
resolution: {integrity: sha512-MOmrCMQlRuPS4PExE1hy8T0XbZUXaNbEuh7CAG5mC8kdvdgANQMkdvJ7vIEOP27n5mXK/4YjvXJOZSsur4E0QQ==}
peerDependencies:
vue-router: '>=4.0.0-rc.1'
dependencies:
'@vueuse/shared': 10.9.0(vue@3.4.19)
vue-demi: 0.14.7(vue@3.4.19)
vue-router: 4.3.0(vue@3.4.19)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: true
/@vueuse/shared@10.7.0(vue@3.3.8):
resolution: {integrity: sha512-kc00uV6CiaTdc3i1CDC4a3lBxzaBE9AgYNtFN87B5OOscqeWElj/uza8qVDmk7/U8JbqoONLbtqiLJ5LGRuqlw==}
dependencies:
@@ -8787,6 +8803,15 @@ packages:
- '@vue/composition-api'
- vue
/@vueuse/shared@10.9.0(vue@3.4.19):
resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==}
dependencies:
vue-demi: 0.14.7(vue@3.4.19)
transitivePeerDependencies:
- '@vue/composition-api'
- vue
dev: true
/@xmldom/xmldom@0.8.10:
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
engines: {node: '>=10.0.0'}