mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Add page titles (#4775)
* restructure template rendering * add useTitle composable * Split up render-string-template from getFieldsFromTemplate Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
This commit is contained in:
4
app/src/composables/use-title/index.ts
Normal file
4
app/src/composables/use-title/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { useTitle } from './use-title';
|
||||
|
||||
export { useTitle };
|
||||
export default useTitle;
|
||||
18
app/src/composables/use-title/use-title.ts
Normal file
18
app/src/composables/use-title/use-title.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ref, Ref, watch } from '@vue/composition-api';
|
||||
import { TranslateResult } from 'vue-i18n';
|
||||
|
||||
export function useTitle(newTitle: string | Ref<string>) {
|
||||
if (newTitle === undefined || newTitle === null) return;
|
||||
|
||||
const titleRef = typeof newTitle === 'string' ? ref(newTitle) : newTitle;
|
||||
|
||||
watch(
|
||||
titleRef,
|
||||
(title, oldTitle) => {
|
||||
if (typeof title === 'string' && oldTitle !== title) document.title = title;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return titleRef;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineDisplay } from '@/displays/define';
|
||||
import DisplayRelatedValues from './related-values.vue';
|
||||
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
|
||||
import { getFieldsFromTemplate } from '@/utils/get-fields-from-template';
|
||||
import adjustFieldsForDisplays from '@/utils/adjust-fields-for-displays';
|
||||
import getRelatedCollection from '@/utils/get-related-collection';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
|
||||
@@ -28,26 +28,11 @@
|
||||
|
||||
<template #append v-if="!disabled">
|
||||
<template v-if="currentItem">
|
||||
<v-icon
|
||||
name="open_in_new"
|
||||
class="edit"
|
||||
v-tooltip="$t('edit')"
|
||||
@click.stop="editModalActive = true"
|
||||
/>
|
||||
<v-icon
|
||||
name="close"
|
||||
class="deselect"
|
||||
@click.stop="$emit('input', null)"
|
||||
v-tooltip="$t('deselect')"
|
||||
/>
|
||||
<v-icon name="open_in_new" class="edit" v-tooltip="$t('edit')" @click.stop="editModalActive = true" />
|
||||
<v-icon name="close" class="deselect" @click.stop="$emit('input', null)" v-tooltip="$t('deselect')" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-icon
|
||||
class="add"
|
||||
name="add"
|
||||
v-tooltip="$t('create_item')"
|
||||
@click.stop="editModalActive = true"
|
||||
/>
|
||||
<v-icon class="add" name="add" v-tooltip="$t('create_item')" @click.stop="editModalActive = true" />
|
||||
<v-icon class="expand" :class="{ active }" name="expand_more" />
|
||||
</template>
|
||||
</template>
|
||||
@@ -71,11 +56,7 @@
|
||||
@click="setCurrent(item)"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<render-template
|
||||
:collection="relatedCollection.collection"
|
||||
:template="displayTemplate"
|
||||
:item="item"
|
||||
/>
|
||||
<render-template :collection="relatedCollection.collection" :template="displayTemplate" :item="item" />
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</template>
|
||||
@@ -105,7 +86,7 @@
|
||||
import { defineComponent, computed, ref, toRefs, watch, PropType } from '@vue/composition-api';
|
||||
import { useCollectionsStore, useRelationsStore } from '@/stores/';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
|
||||
import { getFieldsFromTemplate } from '@/utils/get-fields-from-template';
|
||||
import api from '@/api';
|
||||
import DrawerItem from '@/views/private/components/drawer-item';
|
||||
import DrawerCollection from '@/views/private/components/drawer-collection';
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from '@vue/composition-api';
|
||||
import { render } from 'micromustache';
|
||||
import i18n from '@/lang';
|
||||
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
|
||||
import { getFieldsFromTemplate } from '@/utils/get-fields-from-template';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
||||
@@ -33,7 +33,7 @@ import { defineComponent, PropType, computed, ref, watch } from '@vue/compositio
|
||||
import { useRelationsStore } from '@/stores/';
|
||||
import api from '@/api';
|
||||
import { Relation } from '@/types';
|
||||
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
|
||||
import { getFieldsFromTemplate } from '@/utils/get-fields-from-template';
|
||||
import DrawerItem from '@/views/private/components/drawer-item/drawer-item.vue';
|
||||
import { useCollection } from '@/composables/use-collection';
|
||||
import { unexpectedError } from '@/utils/unexpected-error';
|
||||
|
||||
@@ -50,7 +50,7 @@ import { defineComponent, ref, computed, PropType, onMounted, watch } from '@vue
|
||||
import { useCollection } from '@/composables/use-collection';
|
||||
import { useRelationsStore } from '@/stores';
|
||||
import api from '@/api';
|
||||
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
|
||||
import { getFieldsFromTemplate } from '@/utils/get-fields-from-template';
|
||||
import draggable from 'vuedraggable';
|
||||
import hideDragImage from '@/utils/hide-drag-image';
|
||||
import NestedDraggable from './nested-draggable.vue';
|
||||
|
||||
@@ -141,7 +141,7 @@ import useSync from '@/composables/use-sync/';
|
||||
import useCollection from '@/composables/use-collection/';
|
||||
import useItems from '@/composables/use-items';
|
||||
import Card from './components/card.vue';
|
||||
import getFieldsFromTemplate from '@/utils/get-fields-from-template';
|
||||
import { getFieldsFromTemplate } from '@/utils/get-fields-from-template';
|
||||
import { useRelationsStore } from '@/stores/';
|
||||
|
||||
import CardsHeader from './components/header.vue';
|
||||
|
||||
@@ -207,6 +207,8 @@ import useShortcut from '@/composables/use-shortcut';
|
||||
import { NavigationGuard } from 'vue-router';
|
||||
import { usePermissions } from '@/composables/use-permissions';
|
||||
import unsavedChanges from '@/composables/unsaved-changes';
|
||||
import { useTitle } from '@/composables/use-title';
|
||||
import { renderStringTemplate } from '@/utils/render-string-template';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'collections-item',
|
||||
@@ -300,6 +302,24 @@ export default defineComponent({
|
||||
: i18n.t('editing_in', { collection: collectionInfo.value?.name });
|
||||
});
|
||||
|
||||
const tabTitle = computed(() => {
|
||||
let tabTitle = (collectionInfo.value?.name || '') + ' | ';
|
||||
|
||||
if (collectionInfo.value && collectionInfo.value.meta) {
|
||||
if (collectionInfo.value.meta.singleton === true) {
|
||||
return tabTitle + collectionInfo.value.name;
|
||||
} else if (isNew.value === false && collectionInfo.value.meta.display_template) {
|
||||
const { displayValue } = renderStringTemplate(collectionInfo.value.meta.display_template, templateValues);
|
||||
|
||||
if (displayValue.value !== undefined) return tabTitle + displayValue.value;
|
||||
}
|
||||
}
|
||||
|
||||
return tabTitle + title.value;
|
||||
});
|
||||
|
||||
useTitle(tabTitle);
|
||||
|
||||
const archiveTooltip = computed(() => {
|
||||
if (archiveAllowed.value === false) return i18n.t('not_allowed');
|
||||
if (isArchived.value === true) return i18n.t('unarchive');
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
export default function getFieldsFromTemplate(string: string) {
|
||||
export function getFieldsFromTemplate(template: string | null) {
|
||||
if (template === null) return [];
|
||||
|
||||
const regex = /{{(.*?)}}/g;
|
||||
let fields = string.match(regex);
|
||||
let fields = template.match(regex);
|
||||
|
||||
if (!Array.isArray(fields)) {
|
||||
return [];
|
||||
@@ -9,5 +11,5 @@ export default function getFieldsFromTemplate(string: string) {
|
||||
fields = fields.map((field) => {
|
||||
return field.replace(/{{/g, '').replace(/}}/g, '').trim();
|
||||
});
|
||||
return fields;
|
||||
return fields as string[];
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import getFieldsFromTemplate from './get-fields-from-template';
|
||||
|
||||
export { getFieldsFromTemplate };
|
||||
export default getFieldsFromTemplate;
|
||||
19
app/src/utils/render-string-template.ts
Normal file
19
app/src/utils/render-string-template.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { computed, Ref } from '@vue/composition-api';
|
||||
import { render } from 'micromustache';
|
||||
import { getFieldsFromTemplate } from './get-fields-from-template';
|
||||
|
||||
export function renderStringTemplate(template: Ref<string | null> | string, item: Ref<Record<string, any> | null>) {
|
||||
const templateString = computed(() => (typeof template === 'string' ? template : template.value));
|
||||
|
||||
const fieldsInTemplate = computed(() => getFieldsFromTemplate(templateString.value));
|
||||
|
||||
const displayValue = computed(() => {
|
||||
if (!item.value || !templateString.value || !fieldsInTemplate.value) return;
|
||||
|
||||
try {
|
||||
return render(templateString.value, item.value, { propsExist: true });
|
||||
} catch {}
|
||||
});
|
||||
|
||||
return { fieldsInTemplate, displayValue };
|
||||
}
|
||||
@@ -72,6 +72,7 @@ import NotificationsPreview from './components/notifications-preview/';
|
||||
import NotificationDialogs from './components/notification-dialogs/';
|
||||
import { useUserStore, useAppStore } from '@/stores';
|
||||
import router from '@/router';
|
||||
import useTitle from '@/composables/use-title';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -90,7 +91,8 @@ export default defineComponent({
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
setup(props) {
|
||||
const { title } = toRefs(props);
|
||||
const navOpen = ref(false);
|
||||
const contentEl = ref<Element>();
|
||||
const userStore = useUserStore();
|
||||
@@ -113,20 +115,10 @@ export default defineComponent({
|
||||
|
||||
router.afterEach(async (to, from) => {
|
||||
contentEl.value?.scrollTo({ top: 0 });
|
||||
|
||||
// await nextTick();
|
||||
|
||||
// const hash = to.hash;
|
||||
|
||||
// if (hash) {
|
||||
// const linkedEl = document.querySelector(hash) as HTMLElement;
|
||||
|
||||
// if (linkedEl) {
|
||||
// contentEl.value?.scrollTo({ top: linkedEl.offsetTop - 100, behavior: 'smooth' });
|
||||
// }
|
||||
// }
|
||||
});
|
||||
|
||||
useTitle(title);
|
||||
|
||||
return {
|
||||
navOpen,
|
||||
contentEl,
|
||||
|
||||
Reference in New Issue
Block a user