Add new translations interface (#7727)

* added v-select and button to start sidebyside view

* v-chip next to field name on translations

* v-chip color changed

* add baisc logic

* finish inner workings of translation interface

* finish design

* clean up code

* remove unused prop

* small tweaks

* finish translation interface

* fix lang icon

* tweak styling

* Use v-model over separate bind+event

* Tweak margin definition

* Add class to field-name to prevent span confusion

* Rename classes to match var names

* Add limit -1, remove commented code

* Tweak toggle tooltip wording

* Add hover state to v-icons

* Use self-closing elements

* Remove unused imports

* Rename newVal->sideBySideEnabled

* Use filter + length instead of reducer

* Fix param typo

* Move dividers into main translations component

* Base initial language on fetched languages array

* Move styling to language-select, simplify component

* Don't rely on deep styling

* Tweak interactive state of chip

* Use existing form-grid for side-by-side layoutin

* Only fetch preview values when we dont have them yet

* Improve stability of edited status

* Fix hover state of v-icon

Co-authored-by: Nitwel <nitwel@arcor.de>
Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
This commit is contained in:
Jay Cammarano
2021-09-15 13:03:08 -04:00
committed by GitHub
parent b536905406
commit e243a33cd9
14 changed files with 732 additions and 438 deletions

View File

@@ -104,7 +104,7 @@ body {
border: var(--border-width) solid var(--v-chip-background-color);
border-radius: 16px;
&:hover {
&.clickable:hover {
color: var(--v-chip-color-hover);
background-color: var(--v-chip-background-color-hover);
border-color: var(--v-chip-background-color-hover);
@@ -120,7 +120,7 @@ body {
background-color: var(--v-chip-background-color);
border-color: var(--v-chip-background-color);
&:hover {
&.clickable:hover {
color: var(--v-chip-color);
background-color: var(--v-chip-background-color);
border-color: var(--v-chip-background-color);

View File

@@ -13,7 +13,7 @@
class="v-field-select"
>
<template #item="{ element }">
<v-chip v-tooltip="element.field" class="field draggable" @click="removeField(element.field)">
<v-chip v-tooltip="element.field" clickable class="field draggable" @click="removeField(element.field)">
{{ element.name }}
</v-chip>
</template>

View File

@@ -6,11 +6,12 @@
:value="field.field"
@update:model-value="$emit('toggle-batch', field)"
/>
<span v-tooltip="edited ? t('edited') : null" @click="toggle">
<span v-tooltip="edited ? t('edited') : null" class="field-name" @click="toggle">
{{ field.name }}
<v-icon v-if="field.meta?.required === true" class="required" sup name="star" />
<v-icon v-if="!disabled" class="ctx-arrow" :class="{ active }" name="arrow_drop_down" />
</span>
<v-chip v-if="badge" x-small>{{ badge }}</v-chip>
</div>
</template>
@@ -53,6 +54,10 @@ export default defineComponent({
type: Boolean,
default: false,
},
badge: {
type: String,
default: null,
},
},
emits: ['toggle-batch'],
setup() {
@@ -79,6 +84,11 @@ export default defineComponent({
margin-right: 4px;
}
.v-chip {
margin: 0;
margin-left: 8px;
}
.required {
--v-icon-color: var(--primary);
@@ -88,7 +98,7 @@ export default defineComponent({
.ctx-arrow {
position: absolute;
top: -3px;
right: -20px;
right: -24px;
color: var(--foreground-subdued);
opacity: 0;
transition: opacity var(--fast) var(--transition);
@@ -118,7 +128,7 @@ export default defineComponent({
pointer-events: none;
}
> span {
.field-name {
margin-left: -16px;
padding-left: 16px;
}

View File

@@ -11,6 +11,7 @@
:batch-active="batchActive"
:edited="isEdited"
:has-error="!!validationError"
:badge="badge"
@toggle-batch="$emit('toggle-batch', $event)"
/>
</template>
@@ -111,6 +112,10 @@ export default defineComponent({
type: Boolean,
default: false,
},
badge: {
type: String,
default: null,
},
},
emits: ['toggle-batch', 'unset', 'update:modelValue'],
setup(props, { emit }) {

View File

@@ -52,6 +52,7 @@
:primary-key="primaryKey"
:loading="loading"
:validation-error="validationErrors.find((err) => err.field === field.field)"
:badge="badge"
@update:model-value="setValue(field, $event)"
@unset="unsetValue(field)"
@toggle-batch="toggleBatchField(field)"
@@ -124,6 +125,10 @@ export default defineComponent({
type: Number,
default: null,
},
badge: {
type: String,
default: null,
},
},
emits: ['update:modelValue'],
setup(props, { emit }) {

View File

@@ -1,14 +1,18 @@
<template>
<div
class="v-progress-linear"
:class="{
absolute,
bottom,
fixed,
indeterminate,
rounded,
top,
}"
:class="[
{
absolute,
bottom,
fixed,
indeterminate,
rounded,
top,
colorful,
},
color,
]"
@animationiteration="$emit('animationiteration')"
>
<div
@@ -22,7 +26,7 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { computed, defineComponent } from 'vue';
export default defineComponent({
props: {
@@ -54,8 +58,21 @@ export default defineComponent({
type: Number,
default: 0,
},
colorful: {
type: Boolean,
default: false,
},
},
emits: ['animationiteration'],
setup(props) {
const color = computed(() => {
if (props.value <= 33) return 'danger';
if (props.value <= 66) return 'warning';
return 'success';
});
return { color };
},
});
</script>
@@ -116,6 +133,20 @@ body {
&.top {
top: 0;
}
&.colorful {
&.danger .inner {
background-color: var(--danger);
}
&.warning .inner {
background-color: var(--warning);
}
&.success .inner {
background-color: var(--success);
}
}
}
@keyframes indeterminate {

View File

@@ -23,7 +23,10 @@
@click="toggle"
>
<template v-if="$slots.prepend" #prepend><slot name="prepend" /></template>
<template #append><v-icon name="expand_more" :class="{ active }" /></template>
<template #append>
<v-icon name="expand_more" :class="{ active }" />
<slot name="append" />
</template>
</v-input>
</template>

View File

@@ -18,6 +18,7 @@
:disabled="disabled"
small
label
clickable
@click="toggleTag(preset)"
>
{{ preset }}
@@ -32,6 +33,7 @@
class="tag"
small
label
clickable
@click="removeTag(val)"
>
{{ val }}

View File

@@ -0,0 +1,153 @@
<template>
<v-menu attached class="language-select" :class="{ secondary }">
<template #activator="{ toggle, active }">
<button class="toggle" @click="toggle">
<v-icon class="translate" name="translate" />
<span class="display-value">{{ displayValue }}</span>
<v-icon name="expand_more" :class="{ active }" />
<span class="append-slot"><slot name="append" /></span>
</button>
</template>
<v-list>
<v-list-item v-for="(item, index) in items" :key="index" @click="$emit('update:modelValue', item.value)">
<div class="start">
<div class="dot" :class="{ show: item.edited }"></div>
{{ item.text }}
</div>
<div class="end">
<v-progress-linear
v-tooltip="`${Math.round((item.current / item.max) * 100)}%`"
:value="item.progress"
colorful
/>
</div>
</v-list-item>
</v-list>
</v-menu>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue';
export default defineComponent({
components: {},
props: {
modelValue: {
type: String,
default: null,
},
items: {
type: Array as PropType<Record<string, any>[]>,
default: () => [],
},
secondary: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue'],
setup(props) {
const displayValue = computed(() => {
const item = props.items.find((item) => item.value === props.modelValue);
return item?.text ?? props.modelValue;
});
return { displayValue };
},
});
</script>
<style lang="scss" scoped>
.toggle {
--v-icon-color: var(--primary);
--v-icon-color-hover: var(--primary-150);
display: flex;
align-items: center;
width: 100%;
height: var(--input-height);
padding: var(--input-padding);
color: var(--primary);
text-align: left;
background-color: var(--primary-alt);
border-radius: var(--border-radius);
.display-value {
flex-grow: 1;
margin-left: 8px;
}
.append-slot:not(:empty) {
margin-left: 8px;
}
}
.v-input .input {
color: var(--primary);
background-color: var(--primary-alt);
border: 0px;
}
.v-icon {
margin-left: 6px;
}
.secondary {
.toggle {
--v-icon-color: var(--blue);
--v-icon-color-hover: var(--blue-150);
color: var(--blue);
background-color: var(--blue-alt);
}
}
.v-list {
.v-list-item {
display: flex;
gap: 10px;
align-items: center;
justify-content: space-between;
white-space: nowrap;
cursor: pointer;
.start {
display: flex;
flex: 1;
align-items: center;
}
.end {
display: flex;
flex-grow: 1;
gap: 10px;
align-items: center;
justify-content: flex-end;
color: var(--foreground-subdued);
}
&:hover {
background-color: var(--background-normal);
}
.dot {
width: 8px;
height: 100%;
&.show::before {
display: block;
width: 4px;
height: 4px;
background-color: var(--foreground-subdued);
border-radius: 2px;
content: '';
}
}
.v-progress-linear {
max-width: 100px;
}
}
}
</style>

View File

@@ -4,29 +4,8 @@
</v-notice>
<div v-else class="form-grid">
<div class="field half">
<p class="type-label">{{ t('language_display_template') }}</p>
<v-field-template
v-model="languageTemplate"
:collection="languageCollection"
:depth="2"
:placeholder="
languageCollectionInfo && languageCollectionInfo.meta && languageCollectionInfo.meta.display_template
"
/>
</div>
<div class="field half">
<p class="type-label">{{ t('translations_display_template') }}</p>
<v-field-template
v-model="translationsTemplate"
:collection="translationsCollection"
:depth="2"
:placeholder="
translationsCollectionInfo &&
translationsCollectionInfo.meta &&
translationsCollectionInfo.meta.display_template
"
/>
<p class="type-label">{{ t('interfaces.translations.language_field') }}</p>
<v-select v-model="languageField" :items="languageCollectionFields" item-text="name" item-value="field" />
</div>
</div>
</template>
@@ -36,7 +15,7 @@ import { useI18n } from 'vue-i18n';
import { Relation } from '@/types';
import { Field } from '@directus/shared/types';
import { defineComponent, PropType, computed } from 'vue';
import { useCollectionsStore } from '@/stores/';
import { useFieldsStore } from '@/stores/';
export default defineComponent({
props: {
@@ -61,28 +40,16 @@ export default defineComponent({
setup(props, { emit }) {
const { t } = useI18n();
const collectionsStore = useCollectionsStore();
const fieldsStore = useFieldsStore();
const translationsTemplate = computed({
const languageField = computed({
get() {
return props.value?.translationsTemplate;
return props.value?.languageField;
},
set(newTemplate: string) {
emit('input', {
...(props.value || {}),
translationsTemplate: newTemplate,
});
},
});
const languageTemplate = computed({
get() {
return props.value?.languageTemplate;
},
set(newTemplate: string) {
emit('input', {
...(props.value || {}),
languageTemplate: newTemplate,
languageField: newTemplate,
});
},
});
@@ -109,27 +76,18 @@ export default defineComponent({
);
});
const translationsCollection = computed(() => translationsRelation.value?.collection ?? null);
const languageCollection = computed(() => languageRelation.value?.related_collection ?? null);
const translationsCollectionInfo = computed(() => {
if (!translationsCollection.value) return null;
return collectionsStore.getCollection(translationsCollection.value);
});
const languageCollectionInfo = computed(() => {
if (!languageCollection.value) return null;
return collectionsStore.getCollection(languageCollection.value);
const languageCollectionFields = computed(() => {
if (!languageCollection.value) return [];
return fieldsStore.getFieldsForCollection(languageCollection.value);
});
return {
t,
languageTemplate,
translationsTemplate,
translationsCollection,
translationsCollectionInfo,
languageField,
languageCollection,
languageCollectionInfo,
languageCollectionFields,
};
},
});

View File

@@ -1,54 +1,66 @@
<template>
<div v-if="languagesLoading || previewLoading">
<v-skeleton-loader v-for="n in 5" :key="n" />
</div>
<v-list v-else class="translations">
<v-list-item
v-for="(languageItem, i) in languages"
:key="languageItem[languagesPrimaryKeyField]"
clickable
class="language-row"
block
@click="startEditing(languageItem[languagesPrimaryKeyField])"
>
<v-icon class="translate" name="translate" left />
<render-template :template="internalLanguageTemplate" :collection="languagesCollection" :item="languageItem" />
<render-template
class="preview"
:template="internalTranslationsTemplate"
:collection="translationsCollection"
:item="previewItems[i]"
<div class="translations" :class="{ split: splitViewEnabled }">
<div class="primary" :class="splitViewEnabled ? 'half' : 'full'">
<language-select v-model="firstLang" :items="languageOptions">
<template #append>
<v-icon
v-if="splitViewAvailable && !splitViewEnabled"
v-tooltip="t('interfaces.translations.toggle_split_view')"
name="flip"
clickable
@click.stop="splitView = true"
/>
</template>
</language-select>
<v-form
:loading="valuesLoading"
:fields="fields"
:model-value="firstItem"
:initial-values="firstItemInitial"
:badge="languageOptions.find((lang) => lang.value === firstLang)?.text"
@update:modelValue="updateValue($event, firstLang)"
/>
<div class="spacer" />
</v-list-item>
<drawer-item
v-if="editing"
active
:collection="translationsCollection"
:primary-key="editing"
:edits="edits"
:circular-field="translationsRelation.field"
@input="stageEdits"
@update:active="cancelEdit"
/>
</v-list>
<v-divider />
</div>
<div v-if="splitViewEnabled" class="secondary" :class="splitViewEnabled ? 'half' : 'full'">
<language-select v-model="secondLang" :items="languageOptions" secondary>
<template #append>
<v-icon
v-tooltip="t('interfaces.translations.toggle_split_view')"
name="close"
clickable
@click.stop="splitView = !splitView"
/>
</template>
</language-select>
<v-form
:loading="valuesLoading"
:initial-values="secondItemInitial"
:fields="fields"
:badge="languageOptions.find((lang) => lang.value === secondLang)?.text"
:model-value="secondItem"
@update:modelValue="updateValue($event, secondLang)"
/>
<v-divider />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed, ref, watch } from 'vue';
import { useRelationsStore, useFieldsStore } from '@/stores/';
import LanguageSelect from './language-select.vue';
import { computed, defineComponent, PropType, Ref, ref, toRefs, watch, unref } from 'vue';
import useCollection from '@/composables/use-collection';
import { useFieldsStore, useRelationsStore } from '@/stores/';
import { useI18n } from 'vue-i18n';
import api from '@/api';
import { Relation } from '@/types';
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';
import { isPlainObject } from 'lodash';
import { cloneDeep, isEqual, assign } from 'lodash';
import { notEmpty } from '@/utils/is-empty';
import { useWindowSize } from '@/composables/use-window-size';
export default defineComponent({
components: { DrawerItem },
components: { LanguageSelect },
props: {
collection: {
type: String,
@@ -62,23 +74,37 @@ export default defineComponent({
type: String,
required: true,
},
languageTemplate: {
type: String,
default: null,
},
translationsTemplate: {
languageField: {
type: String,
default: null,
},
value: {
type: Array as PropType<(string | number | Record<string, any>)[]>,
default: () => [],
type: Array as PropType<(string | number | Record<string, any>)[] | null>,
default: null,
},
},
emits: ['input'],
setup(props, { emit }) {
const { collection } = toRefs(props);
const fieldsStore = useFieldsStore();
const relationsStore = useRelationsStore();
const { t } = useI18n();
const { width } = useWindowSize();
const splitView = ref(false);
const firstLang = ref<string | number>();
const secondLang = ref<string | number>();
const { info: collectionInfo } = useCollection(collection);
watch(splitView, (splitViewEnabled) => {
const lang = languageOptions.value;
if (splitViewEnabled && secondLang.value === firstLang.value) {
secondLang.value = lang[0].value === firstLang.value ? lang[1].value : lang[0].value;
}
});
const {
relationsForField,
@@ -91,29 +117,57 @@ export default defineComponent({
translationsLanguageField,
} = useRelations();
const { languages, loading: languagesLoading, template: internalLanguageTemplate } = useLanguages();
const { startEditing, editing, edits, stageEdits, cancelEdit } = useEdits();
const { previewItems, template: internalTranslationsTemplate, loading: previewLoading } = usePreview();
const { languageOptions, loading: languagesLoading } = useLanguages();
const {
items,
firstItem,
loading: valuesLoading,
updateValue,
secondItem,
firstItemInitial,
secondItemInitial,
} = useEdits();
const fields = computed(() => {
if (translationsCollection.value === null) return [];
return fieldsStore.getFieldsForCollection(translationsCollection.value);
});
const splitViewAvailable = computed(() => {
return width.value > 960;
});
const splitViewEnabled = computed(() => {
return splitViewAvailable.value && splitView.value;
});
return {
collectionInfo,
splitView,
firstLang,
secondLang,
t,
languageOptions,
fields,
relationsForField,
translationsRelation,
translationsCollection,
translationsPrimaryKeyField,
languagesRelation,
languages,
internalLanguageTemplate,
internalTranslationsTemplate,
languagesCollection,
languagesPrimaryKeyField,
languagesLoading,
startEditing,
translationsLanguageField,
editing,
stageEdits,
cancelEdit,
edits,
previewItems,
previewLoading,
items,
firstItem,
secondItem,
updateValue,
relationsStore,
firstItemInitial,
secondItemInitial,
splitViewAvailable,
splitViewEnabled,
languagesLoading,
valuesLoading,
};
function useRelations() {
@@ -174,40 +228,84 @@ export default defineComponent({
}
function useLanguages() {
const languages = ref<Record<string, any>[]>();
const languages = ref<Record<string, any>[]>([]);
const loading = ref(false);
const error = ref<any>(null);
const { info: languagesCollectionInfo } = useCollection(languagesCollection);
const template = computed(() => {
if (!languagesPrimaryKeyField.value) return '';
return (
props.languageTemplate ||
languagesCollectionInfo.value?.meta?.display_template ||
`{{ ${languagesPrimaryKeyField.value} }}`
);
});
watch(languagesCollection, fetchLanguages, { immediate: true });
return { languages, loading, error, template };
const languageOptions = computed(() => {
const langField = translationsLanguageField.value;
if (langField === null) return [];
const writableFields = fields.value.filter(
(field) => field.type !== 'alias' && field.meta?.hidden === false && field.meta.readonly === false
);
const totalFields = writableFields.length;
return languages.value.map((language) => {
if (languagesPrimaryKeyField.value === null) return language;
const langCode = language[languagesPrimaryKeyField.value];
const initialValue = items.value.find((item) => item[langField] === langCode) ?? {};
const edits = props.value?.find((val) => typeof val === 'object' && val[langField] === langCode) as
| Record<string, any>
| undefined;
const item = { ...initialValue, ...(edits ?? {}) };
const filledFields = writableFields.filter((field) => {
return field.field in item && notEmpty(item[field.field]);
}).length;
return {
text: language[props.languageField ?? languagesPrimaryKeyField.value],
value: langCode,
edited: edits !== undefined,
progress: (filledFields / totalFields) * 100,
max: totalFields,
current: filledFields,
};
});
});
return { languageOptions, loading, error };
async function fetchLanguages() {
if (!languagesCollection.value || !languagesPrimaryKeyField.value) return;
const fields = getFieldsFromTemplate(template.value);
const fields = new Set<string>();
if (fields.includes(languagesPrimaryKeyField.value) === false) {
fields.push(languagesPrimaryKeyField.value);
if (props.languageField !== null) {
fields.add(props.languageField);
}
fields.add(languagesPrimaryKeyField.value);
loading.value = true;
try {
const response = await api.get(`/items/${languagesCollection.value}`, { params: { fields, limit: -1 } });
const response = await api.get(`/items/${languagesCollection.value}`, {
params: {
fields: Array.from(fields),
limit: -1,
sort: props.languageField ?? languagesPrimaryKeyField.value,
},
});
languages.value = response.data.data;
if (!firstLang.value) {
firstLang.value = response.data.data?.[0]?.[languagesPrimaryKeyField.value];
}
if (!secondLang.value) {
secondLang.value = response.data.data?.[1]?.[languagesPrimaryKeyField.value];
}
} catch (err: any) {
unexpectedError(err);
} finally {
@@ -217,234 +315,174 @@ export default defineComponent({
}
function useEdits() {
const keyMap = ref<Record<string, string | number>[]>();
const loading = ref(false);
const error = ref<any>(null);
const editing = ref<boolean | string | number>(false);
const edits = ref<Record<string, any>>();
const existingPrimaryKeys = computed(() => {
const pkField = translationsPrimaryKeyField.value;
if (!pkField) return [];
return (props.value || [])
.map((value) => {
if (typeof value === 'string' || typeof value === 'number') return value;
return value[pkField];
})
.filter((key) => key);
});
watch(() => props.value, fetchKeyMap, { immediate: true });
return { startEditing, editing, edits, stageEdits, cancelEdit };
function startEditing(language: string | number) {
if (!translationsLanguageField.value || !translationsPrimaryKeyField.value) return;
edits.value = {
[translationsLanguageField.value]: language,
};
const existingEdits = (props.value || []).find((val) => {
if (typeof val === 'string' || typeof val === 'number') return false;
return val[translationsLanguageField.value!] === language;
});
if (existingEdits) {
edits.value = {
...edits.value,
...(existingEdits as Record<string, any>),
};
}
const primaryKey =
keyMap.value?.find((record) => record[translationsLanguageField.value!] === language)?.[
translationsPrimaryKeyField.value
] || '+';
if (primaryKey !== '+') {
edits.value = {
...edits.value,
[translationsPrimaryKeyField.value]: primaryKey,
};
}
editing.value = primaryKey;
}
async function fetchKeyMap() {
if (!props.value) return;
if (keyMap.value) return;
if (!existingPrimaryKeys.value?.length) return;
const pkField = translationsPrimaryKeyField.value;
if (!pkField) return;
const collection = translationsRelation.value?.collection;
if (!collection) return;
const fields = [pkField, translationsLanguageField.value];
loading.value = true;
try {
const response = await api.get(`/items/${collection}`, {
params: {
fields,
filter: {
[pkField]: {
_in: existingPrimaryKeys.value,
},
},
limit: -1,
},
});
keyMap.value = response.data.data;
} catch (err: any) {
error.value = err;
} finally {
loading.value = false;
}
}
function stageEdits(edits: any) {
if (!translationsLanguageField.value) return;
const pkField = translationsPrimaryKeyField.value;
if (!pkField) return;
const editedLanguage = edits[translationsLanguageField.value];
const languageAlreadyEdited = !!(props.value || []).find((val) => {
if (typeof val === 'string' || typeof val === 'number') return false;
return val[translationsLanguageField.value!] === editedLanguage;
});
if (languageAlreadyEdited === true) {
emit(
'input',
props.value.map((val) => {
if (typeof val === 'string' || typeof val === 'number') return val;
if (val[translationsLanguageField.value!] === editedLanguage) {
return edits;
}
return val;
})
);
} else {
if (editing.value === '+') {
emit('input', [...(props.value || []), edits]);
} else {
emit(
'input',
props.value.map((val) => {
if (typeof val === 'string' || typeof val === 'number') {
if (val === editing.value) return edits;
} else {
if (val[pkField] === editing.value) return edits;
}
return val;
})
);
}
}
editing.value = false;
}
function cancelEdit() {
edits.value = {};
editing.value = false;
}
}
function usePreview() {
const items = ref<Record<string, any>[]>([]);
const loading = ref(false);
const error = ref(null);
const previewItems = ref<Record<string, any>[]>([]);
const { info: translationsCollectionInfo } = useCollection(translationsCollection);
const firstItem = computed(() => getEditedValue(firstLang));
const secondItem = computed(() => getEditedValue(secondLang));
const template = computed(() => {
if (!translationsPrimaryKeyField.value) return '';
const firstItemInitial = computed<Record<string, any>>(() => getExistingValue(firstLang));
const secondItemInitial = computed<Record<string, any>>(() => getExistingValue(secondLang));
watch(
() => props.value,
(newVal, oldVal) => {
if (
newVal &&
newVal !== oldVal &&
newVal?.every((item) => typeof item === 'string' || typeof item === 'number')
) {
loadItems();
}
if (newVal === null || newVal.length === 0) {
items.value = [];
}
},
{ immediate: true }
);
return { items, firstItem, updateValue, secondItem, firstItemInitial, secondItemInitial, loading, error };
function getExistingValue(langRef: string | number | undefined | Ref<string | number | undefined>) {
const lang = unref(langRef);
const langField = translationsLanguageField.value;
if (langField === null) return {};
return (items.value.find((item) => item[langField] === lang) as Record<string, any>) ?? {};
}
function getEditedValue(langRef: string | number | undefined | Ref<string | number | undefined>) {
const lang = unref(langRef);
const langField = translationsLanguageField.value;
if (langField === null) return {};
return (
props.translationsTemplate ||
translationsCollectionInfo.value?.meta?.display_template ||
`{{ ${translationsPrimaryKeyField.value} }}`
(props.value?.find((item) => typeof item === 'object' && item[langField] === lang) as Record<string, any>) ??
{}
);
});
}
watch(() => props.value, fetchPreviews, { immediate: true });
watch(languages, fetchPreviews, { immediate: true });
async function loadItems() {
const pkField = translationsPrimaryKeyField.value;
return { loading, error, previewItems, fetchPreviews, template };
async function fetchPreviews() {
if (!translationsRelation.value || !languagesRelation.value || !languages.value) return;
if (props.primaryKey === '+') return;
if (pkField === null || !props.value || props.value.length === 0) return;
loading.value = true;
try {
const fields = getFieldsFromTemplate(template.value);
if (fields.includes(languagesRelation.value.field) === false) {
fields.push(languagesRelation.value.field);
}
const existing = await api.get(`/items/${translationsCollection.value}`, {
const response = await api.get(`/items/${translationsCollection.value}`, {
params: {
fields,
fields: '*',
limit: -1,
filter: {
[translationsRelation.value.field]: {
_eq: props.primaryKey,
[pkField]: {
_in: props.value,
},
},
limit: -1,
},
});
previewItems.value = languages.value.map((language) => {
const pkField = languagesPrimaryKeyField.value;
if (!pkField) return;
const existingEdit =
props.value && Array.isArray(props.value)
? (props.value.find(
(edit) =>
isPlainObject(edit) &&
(edit as Record<string, any>)[languagesRelation.value!.field] === language[pkField]
) as Record<string, any>)
: {};
return {
...(existing.data.data?.find(
(item: Record<string, any>) => item[languagesRelation.value!.field] === language[pkField]
) ?? {}),
...existingEdit,
};
});
} catch (err: any) {
items.value = response.data.data;
} catch (err) {
error.value = err;
previewItems.value = [];
unexpectedError(err);
} finally {
loading.value = false;
}
}
function updateValue(edits: Record<string, any>, lang: string) {
const pkField = translationsPrimaryKeyField.value;
const langField = translationsLanguageField.value;
const existing = getExistingValue(lang);
const values = assign({}, existing, edits);
if (pkField === null || langField === null) return;
let copyValue = cloneDeep(props.value ?? []);
if (pkField in values === false) {
const newIndex = copyValue.findIndex((item) => typeof item === 'object' && item[langField] === lang);
if (newIndex !== -1) {
if (Object.keys(values).length === 1 && langField in values) {
copyValue.splice(newIndex, 1);
} else {
copyValue[newIndex] = values;
}
} else {
copyValue.push({
...values,
[langField]: lang,
});
}
} else {
const initialValues = items.value.find((item) => item[langField] === lang);
copyValue = copyValue.map((item) => {
if (typeof item === 'number' || typeof item === 'string') {
if (values[pkField] === item) {
return values;
} else {
return item;
}
} else {
if (values[pkField] === item[pkField]) {
if (isEqual(initialValues, { ...initialValues, ...values })) {
return values[pkField];
} else {
return values;
}
} else {
return item;
}
}
});
}
emit('input', copyValue);
}
}
},
});
</script>
<style scoped>
.preview {
color: var(--foreground-subdued);
<style lang="scss" scoped>
@import '@/styles/mixins/form-grid';
.translations {
@include form-grid;
.v-form {
--form-vertical-gap: 32px;
--v-chip-color: var(--primary);
--v-chip-background-color: var(--primary-alt);
margin-top: 32px;
}
.v-divider {
margin-top: var(--form-vertical-gap);
}
.primary {
--v-divider-color: var(--primary-50);
}
.secondary {
--v-divider-color: var(--blue-50);
.v-form {
--primary: var(--blue);
--v-chip-color: var(--blue);
--v-chip-background-color: var(--blue-alt);
}
}
}
</style>

View File

@@ -1288,6 +1288,8 @@ interfaces:
translations:
display_template: Display Template
no_collection: No Collection
toggle_split_view: Toggle Split View
language_field: Language Field
list-o2m-tree-view:
description: Tree view for nested recursive one-to-many items
recursive_only: The tree view interface only works for recursive relationships.

View File

@@ -6,118 +6,205 @@ _Changes marked with a :warning: contain potential breaking changes depending on
### ⚠️ Potential Breaking Changes
- Custom API endpoints no longer use a `/custom` prefix. Please update your usage of custom endpoints to drop the `/custom` prefix See [#7695](https://github.com/directus/directus/pull/7695)
- Layouts use a revised setup that relies on props instead of `useState`. See [#7489](https://github.com/directus/directus/pull/7489)
- Custom API endpoints no longer use a `/custom` prefix. Please update your usage of custom endpoints to drop the
`/custom` prefix See [#7695](https://github.com/directus/directus/pull/7695)
- Layouts use a revised setup that relies on props instead of `useState`. See
[#7489](https://github.com/directus/directus/pull/7489)
### :sparkles: New Features
- **API**
- [#7789](https://github.com/directus/directus/pull/7789) Add environment variable to force-exclude tables from Directus ([@rijkvanzanten](https://github.com/rijkvanzanten))
- [#7777](https://github.com/directus/directus/pull/7777) Expose logger through ExtensionContext ([@Moeriki](https://github.com/Moeriki))
- [#7759](https://github.com/directus/directus/pull/7759) Show a warning if PostGIS is missing ([@rijkvanzanten](https://github.com/rijkvanzanten))
- [#7789](https://github.com/directus/directus/pull/7789) Add environment variable to force-exclude tables from
Directus ([@rijkvanzanten](https://github.com/rijkvanzanten))
- [#7777](https://github.com/directus/directus/pull/7777) Expose logger through ExtensionContext
([@Moeriki](https://github.com/Moeriki))
- [#7759](https://github.com/directus/directus/pull/7759) Show a warning if PostGIS is missing
([@rijkvanzanten](https://github.com/rijkvanzanten))
- **App**
- [#7605](https://github.com/directus/directus/pull/7605) Add search result highlighting to tree-view interface ([@rijkvanzanten](https://github.com/rijkvanzanten))
- [#7605](https://github.com/directus/directus/pull/7605) Add search result highlighting to tree-view interface
([@rijkvanzanten](https://github.com/rijkvanzanten))
### :rocket: Improvements
- **App**
- [#7749](https://github.com/directus/directus/pull/7749) Disable attribute inheritance for all layout components ([@nickrum](https://github.com/nickrum))
- [#7738](https://github.com/directus/directus/pull/7738) Warn the user when a collapsed group field had an error ([@Nitwel](https://github.com/Nitwel))
- [#7687](https://github.com/directus/directus/pull/7687) Resolve editor/type warnings ([@Nitwel](https://github.com/Nitwel))
- [#7668](https://github.com/directus/directus/pull/7668) Replace system provide with composables ([@nickrum](https://github.com/nickrum))
- [#7650](https://github.com/directus/directus/pull/7650) Allow to select system collections in m2a ([@Nitwel](https://github.com/Nitwel))
- [#7583](https://github.com/directus/directus/pull/7583) Display private images in WYSIWYG editor ([@jaycammarano](https://github.com/jaycammarano))
- [#7578](https://github.com/directus/directus/pull/7578) Add `search this area` button to map layout. ([@Oreilles](https://github.com/Oreilles))
- [#7563](https://github.com/directus/directus/pull/7563) Move basemap input higher in sidebar options. Keep map interactive under v-info ([@Oreilles](https://github.com/Oreilles))
- [#7535](https://github.com/directus/directus/pull/7535) Allow using regular input interface on TEXT type fields ([@alexkharech](https://github.com/alexkharech))
- [#7749](https://github.com/directus/directus/pull/7749) Disable attribute inheritance for all layout components
([@nickrum](https://github.com/nickrum))
- [#7738](https://github.com/directus/directus/pull/7738) Warn the user when a collapsed group field had an error
([@Nitwel](https://github.com/Nitwel))
- [#7687](https://github.com/directus/directus/pull/7687) Resolve editor/type warnings
([@Nitwel](https://github.com/Nitwel))
- [#7668](https://github.com/directus/directus/pull/7668) Replace system provide with composables
([@nickrum](https://github.com/nickrum))
- [#7650](https://github.com/directus/directus/pull/7650) Allow to select system collections in m2a
([@Nitwel](https://github.com/Nitwel))
- [#7583](https://github.com/directus/directus/pull/7583) Display private images in WYSIWYG editor
([@jaycammarano](https://github.com/jaycammarano))
- [#7578](https://github.com/directus/directus/pull/7578) Add `search this area` button to map layout.
([@Oreilles](https://github.com/Oreilles))
- [#7563](https://github.com/directus/directus/pull/7563) Move basemap input higher in sidebar options. Keep map
interactive under v-info ([@Oreilles](https://github.com/Oreilles))
- [#7535](https://github.com/directus/directus/pull/7535) Allow using regular input interface on TEXT type fields
([@alexkharech](https://github.com/alexkharech))
- **Extensions**
- [#7714](https://github.com/directus/directus/pull/7714) Improve API extension context types ([@nickrum](https://github.com/nickrum))
- :warning: [#7695](https://github.com/directus/directus/pull/7695) Remove /custom subpath for endpoints and add a way to customize the endpoint subpath ([@nickrum](https://github.com/nickrum))
- [#7668](https://github.com/directus/directus/pull/7668) Replace system provide with composables ([@nickrum](https://github.com/nickrum))
- [#7629](https://github.com/directus/directus/pull/7629) Share vue-router between App and extensions ([@nickrum](https://github.com/nickrum))
- [#7627](https://github.com/directus/directus/pull/7627) Allow json imports and replace NODE_ENV env var when building extensions ([@nickrum](https://github.com/nickrum))
- [#7714](https://github.com/directus/directus/pull/7714) Improve API extension context types
([@nickrum](https://github.com/nickrum))
- :warning: [#7695](https://github.com/directus/directus/pull/7695) Remove /custom subpath for endpoints and add a way
to customize the endpoint subpath ([@nickrum](https://github.com/nickrum))
- [#7668](https://github.com/directus/directus/pull/7668) Replace system provide with composables
([@nickrum](https://github.com/nickrum))
- [#7629](https://github.com/directus/directus/pull/7629) Share vue-router between App and extensions
([@nickrum](https://github.com/nickrum))
- [#7627](https://github.com/directus/directus/pull/7627) Allow json imports and replace NODE_ENV env var when
building extensions ([@nickrum](https://github.com/nickrum))
- **API**
- [#7711](https://github.com/directus/directus/pull/7711) Remove permission.limit ([@Nitwel](https://github.com/Nitwel))
- :warning: [#7695](https://github.com/directus/directus/pull/7695) Remove /custom subpath for endpoints and add a way to customize the endpoint subpath ([@nickrum](https://github.com/nickrum))
- [#7604](https://github.com/directus/directus/pull/7604) Log localhost url on startup so it's clickable in terminals ([@zebapy](https://github.com/zebapy))
- [#7711](https://github.com/directus/directus/pull/7711) Remove permission.limit
([@Nitwel](https://github.com/Nitwel))
- :warning: [#7695](https://github.com/directus/directus/pull/7695) Remove /custom subpath for endpoints and add a way
to customize the endpoint subpath ([@nickrum](https://github.com/nickrum))
- [#7604](https://github.com/directus/directus/pull/7604) Log localhost url on startup so it's clickable in terminals
([@zebapy](https://github.com/zebapy))
### :bug: Bug Fixes
- **App**
- [#7780](https://github.com/directus/directus/pull/7780) Use OpenMapTiles font instead of ArcGIS ([@Oreilles](https://github.com/Oreilles))
- [#7778](https://github.com/directus/directus/pull/7778) Fixes bug when trying to edit geometry in code interface. ([@Oreilles](https://github.com/Oreilles))
- [#7768](https://github.com/directus/directus/pull/7768) Fix hash link in docs module ([@rijkvanzanten](https://github.com/rijkvanzanten))
- [#7763](https://github.com/directus/directus/pull/7763) Fix branch emitter logic from grand-to-child ([@rijkvanzanten](https://github.com/rijkvanzanten))
- [#7760](https://github.com/directus/directus/pull/7760) Fix 'Inactive' to 'Invited' translations on user status ([@joselcvarela](https://github.com/joselcvarela))
- [#7756](https://github.com/directus/directus/pull/7756) fix WYSIWYG field focus event ([@azrikahar](https://github.com/azrikahar))
- [#7716](https://github.com/directus/directus/pull/7716) Fix input-code component lint style ([@azrikahar](https://github.com/azrikahar))
- [#7712](https://github.com/directus/directus/pull/7712) Prevent generated columns edition ([@Oreilles](https://github.com/Oreilles))
- [#7703](https://github.com/directus/directus/pull/7703) Fix alignment of collection nav grouping ([@Nitwel](https://github.com/Nitwel))
- [#7698](https://github.com/directus/directus/pull/7698) Add permission prop check ([@Nitwel](https://github.com/Nitwel))
- [#7697](https://github.com/directus/directus/pull/7697) Add upload event for file imports ([@azrikahar](https://github.com/azrikahar))
- [#7684](https://github.com/directus/directus/pull/7684) Add missing translations ([@Nitwel](https://github.com/Nitwel))
- [#7683](https://github.com/directus/directus/pull/7683) Move related values link to icon ([@Nitwel](https://github.com/Nitwel))
- [#7682](https://github.com/directus/directus/pull/7682) Fix firefox being buggy with numbers as value inputs ([@Nitwel](https://github.com/Nitwel))
- [#7669](https://github.com/directus/directus/pull/7669) Add missing translations ([@Nitwel](https://github.com/Nitwel))
- [#7666](https://github.com/directus/directus/pull/7666) Fix items not getting matched properly ([@Nitwel](https://github.com/Nitwel))
- [#7635](https://github.com/directus/directus/pull/7635) Prevent collection from crashing on unknown layout ([@rijkvanzanten](https://github.com/rijkvanzanten))
- [#7632](https://github.com/directus/directus/pull/7632) Assign edits instead of merge ([@Nitwel](https://github.com/Nitwel))
- [#7631](https://github.com/directus/directus/pull/7631) Fix o2m flashing / reloading when typing ([@Nitwel](https://github.com/Nitwel))
- [#7780](https://github.com/directus/directus/pull/7780) Use OpenMapTiles font instead of ArcGIS
([@Oreilles](https://github.com/Oreilles))
- [#7778](https://github.com/directus/directus/pull/7778) Fixes bug when trying to edit geometry in code interface.
([@Oreilles](https://github.com/Oreilles))
- [#7768](https://github.com/directus/directus/pull/7768) Fix hash link in docs module
([@rijkvanzanten](https://github.com/rijkvanzanten))
- [#7763](https://github.com/directus/directus/pull/7763) Fix branch emitter logic from grand-to-child
([@rijkvanzanten](https://github.com/rijkvanzanten))
- [#7760](https://github.com/directus/directus/pull/7760) Fix 'Inactive' to 'Invited' translations on user status
([@joselcvarela](https://github.com/joselcvarela))
- [#7756](https://github.com/directus/directus/pull/7756) fix WYSIWYG field focus event
([@azrikahar](https://github.com/azrikahar))
- [#7716](https://github.com/directus/directus/pull/7716) Fix input-code component lint style
([@azrikahar](https://github.com/azrikahar))
- [#7712](https://github.com/directus/directus/pull/7712) Prevent generated columns edition
([@Oreilles](https://github.com/Oreilles))
- [#7703](https://github.com/directus/directus/pull/7703) Fix alignment of collection nav grouping
([@Nitwel](https://github.com/Nitwel))
- [#7698](https://github.com/directus/directus/pull/7698) Add permission prop check
([@Nitwel](https://github.com/Nitwel))
- [#7697](https://github.com/directus/directus/pull/7697) Add upload event for file imports
([@azrikahar](https://github.com/azrikahar))
- [#7684](https://github.com/directus/directus/pull/7684) Add missing translations
([@Nitwel](https://github.com/Nitwel))
- [#7683](https://github.com/directus/directus/pull/7683) Move related values link to icon
([@Nitwel](https://github.com/Nitwel))
- [#7682](https://github.com/directus/directus/pull/7682) Fix firefox being buggy with numbers as value inputs
([@Nitwel](https://github.com/Nitwel))
- [#7669](https://github.com/directus/directus/pull/7669) Add missing translations
([@Nitwel](https://github.com/Nitwel))
- [#7666](https://github.com/directus/directus/pull/7666) Fix items not getting matched properly
([@Nitwel](https://github.com/Nitwel))
- [#7635](https://github.com/directus/directus/pull/7635) Prevent collection from crashing on unknown layout
([@rijkvanzanten](https://github.com/rijkvanzanten))
- [#7632](https://github.com/directus/directus/pull/7632) Assign edits instead of merge
([@Nitwel](https://github.com/Nitwel))
- [#7631](https://github.com/directus/directus/pull/7631) Fix o2m flashing / reloading when typing
([@Nitwel](https://github.com/Nitwel))
- [#7628](https://github.com/directus/directus/pull/7628) Truely unref item ([@Nitwel](https://github.com/Nitwel))
- [#7602](https://github.com/directus/directus/pull/7602) Add mapbox-key to map interface initialization ([@Oreilles](https://github.com/Oreilles))
- [#7599](https://github.com/directus/directus/pull/7599) Check if perms have edits ([@Nitwel](https://github.com/Nitwel))
- [#7562](https://github.com/directus/directus/pull/7562) Fix calendar layout not opening detail pages for system collections ([@azrikahar](https://github.com/azrikahar))
- :warning: [#7489](https://github.com/directus/directus/pull/7489) Rework layout extension component management ([@nickrum](https://github.com/nickrum))
- [#7602](https://github.com/directus/directus/pull/7602) Add mapbox-key to map interface initialization
([@Oreilles](https://github.com/Oreilles))
- [#7599](https://github.com/directus/directus/pull/7599) Check if perms have edits
([@Nitwel](https://github.com/Nitwel))
- [#7562](https://github.com/directus/directus/pull/7562) Fix calendar layout not opening detail pages for system
collections ([@azrikahar](https://github.com/azrikahar))
- :warning: [#7489](https://github.com/directus/directus/pull/7489) Rework layout extension component management
([@nickrum](https://github.com/nickrum))
- Update WYSIWYG styling ([@benhaynes](https://github.com/benhaynes))
- **Extensions**
- [#7624](https://github.com/directus/directus/pull/7624) Enable browser module resolution when building app extensions ([@nickrum](https://github.com/nickrum))
- [#7624](https://github.com/directus/directus/pull/7624) Enable browser module resolution when building app
extensions ([@nickrum](https://github.com/nickrum))
- **API**
- [#7581](https://github.com/directus/directus/pull/7581) Fix uploaded_by not always setting user ([@rijkvanzanten](https://github.com/rijkvanzanten))
- [#7568](https://github.com/directus/directus/pull/7568) fix(api): merge original user object into payload from auth hook ([@azrikahar](https://github.com/azrikahar))
- [#7561](https://github.com/directus/directus/pull/7561) Handle difference between `pg` and `postgres` as db client in geometry helper ([@Oreilles](https://github.com/Oreilles))
- [#7553](https://github.com/directus/directus/pull/7553) Fix asset transformation `withEnlargement` type ([@azrikahar](https://github.com/azrikahar))
- [#7581](https://github.com/directus/directus/pull/7581) Fix uploaded_by not always setting user
([@rijkvanzanten](https://github.com/rijkvanzanten))
- [#7568](https://github.com/directus/directus/pull/7568) fix(api): merge original user object into payload from auth
hook ([@azrikahar](https://github.com/azrikahar))
- [#7561](https://github.com/directus/directus/pull/7561) Handle difference between `pg` and `postgres` as db client
in geometry helper ([@Oreilles](https://github.com/Oreilles))
- [#7553](https://github.com/directus/directus/pull/7553) Fix asset transformation `withEnlargement` type
([@azrikahar](https://github.com/azrikahar))
### :memo: Documentation
- [#7771](https://github.com/directus/directus/pull/7771) tiny rewrite of operator descriptions in docs/reference/filter-rules ([@definiteIymaybe](https://github.com/definiteIymaybe))
- [#7757](https://github.com/directus/directus/pull/7757) Document usage of custom reset URL in request password in the SDK ([@joselcvarela](https://github.com/joselcvarela))
- [#7750](https://github.com/directus/directus/pull/7750) Update layout docs to new layouts system ([@nickrum](https://github.com/nickrum))
- [#7648](https://github.com/directus/directus/pull/7648) Update mentions of Vue 2 to Vue 3 in codebase-overview.md ([@azrikahar](https://github.com/azrikahar))
- [#7586](https://github.com/directus/directus/pull/7586) Add installation guide for plesk/shared hosting ([@Tummerhore](https://github.com/Tummerhore))
- [#7771](https://github.com/directus/directus/pull/7771) tiny rewrite of operator descriptions in
docs/reference/filter-rules ([@definiteIymaybe](https://github.com/definiteIymaybe))
- [#7757](https://github.com/directus/directus/pull/7757) Document usage of custom reset URL in request password in the
SDK ([@joselcvarela](https://github.com/joselcvarela))
- [#7750](https://github.com/directus/directus/pull/7750) Update layout docs to new layouts system
([@nickrum](https://github.com/nickrum))
- [#7648](https://github.com/directus/directus/pull/7648) Update mentions of Vue 2 to Vue 3 in codebase-overview.md
([@azrikahar](https://github.com/azrikahar))
- [#7586](https://github.com/directus/directus/pull/7586) Add installation guide for plesk/shared hosting
([@Tummerhore](https://github.com/Tummerhore))
### :package: Dependency Updates
- [#7786](https://github.com/directus/directus/pull/7786) Update dependency npm to v7.22.0 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7785](https://github.com/directus/directus/pull/7785) Update vue monorepo to v3.2.8 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7770](https://github.com/directus/directus/pull/7770) Update dependency sass to v1.39.0 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7769](https://github.com/directus/directus/pull/7769) Update dependency knex-schema-inspector to v1.6.0 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7766](https://github.com/directus/directus/pull/7766) Update vue monorepo to v3.2.7 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7752](https://github.com/directus/directus/pull/7752) Update dependency vite to v2.5.3 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7742](https://github.com/directus/directus/pull/7742) Update dependency @types/sharp to v0.28.6 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7728](https://github.com/directus/directus/pull/7728) Update gatsby monorepo to v3.13.0 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7718](https://github.com/directus/directus/pull/7718) Update dependency knex-schema-inspector to v1.5.15 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7715](https://github.com/directus/directus/pull/7715) Update dependency vite to v2.5.2 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7708](https://github.com/directus/directus/pull/7708) Update dependency knex-schema-inspector to v1.5.14 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7705](https://github.com/directus/directus/pull/7705) Update dependency eslint-plugin-prettier to v4 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7704](https://github.com/directus/directus/pull/7704) Update typescript-eslint monorepo to v4.30.0 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7690](https://github.com/directus/directus/pull/7690) Update dependency micromark to v3 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7672](https://github.com/directus/directus/pull/7672) Update dependency sass to v1.38.2 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7656](https://github.com/directus/directus/pull/7656) update jest monorepo to v27.1.0 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7655](https://github.com/directus/directus/pull/7655) update dependency @types/markdown-it to v12.2.1 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7646](https://github.com/directus/directus/pull/7646) update dependency tinymce to v5.9.1 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7643](https://github.com/directus/directus/pull/7643) update dependency eslint-plugin-vue to v7.17.0 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7638](https://github.com/directus/directus/pull/7638) update dependency typescript to v4.4.2 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7614](https://github.com/directus/directus/pull/7614) update dependency tinymce to v5.9.0 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7606](https://github.com/directus/directus/pull/7606) pin dependencies ([@renovate[bot]](https://github.com/apps/renovate))
- [#7595](https://github.com/directus/directus/pull/7595) update dependency nock to v13.1.3 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7582](https://github.com/directus/directus/pull/7582) pin dependency @types/supertest to 2.0.11 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7580](https://github.com/directus/directus/pull/7580) update dependency @vitejs/plugin-vue to v1.6.0 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7579](https://github.com/directus/directus/pull/7579) update vue monorepo to v3.2.6 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7576](https://github.com/directus/directus/pull/7576) update vue monorepo to v3.2.5 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7571](https://github.com/directus/directus/pull/7571) update dependency @vitejs/plugin-vue to v1.5.0 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7570](https://github.com/directus/directus/pull/7570) update dependency vite to v2.5.1 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7558](https://github.com/directus/directus/pull/7558) update dependency sass to v1.38.1 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7556](https://github.com/directus/directus/pull/7556) update dependency @types/marked to v2.0.5 ([@renovate[bot]](https://github.com/apps/renovate))
- [#7786](https://github.com/directus/directus/pull/7786) Update dependency npm to v7.22.0
([@renovate[bot]](https://github.com/apps/renovate))
- [#7785](https://github.com/directus/directus/pull/7785) Update vue monorepo to v3.2.8
([@renovate[bot]](https://github.com/apps/renovate))
- [#7770](https://github.com/directus/directus/pull/7770) Update dependency sass to v1.39.0
([@renovate[bot]](https://github.com/apps/renovate))
- [#7769](https://github.com/directus/directus/pull/7769) Update dependency knex-schema-inspector to v1.6.0
([@renovate[bot]](https://github.com/apps/renovate))
- [#7766](https://github.com/directus/directus/pull/7766) Update vue monorepo to v3.2.7
([@renovate[bot]](https://github.com/apps/renovate))
- [#7752](https://github.com/directus/directus/pull/7752) Update dependency vite to v2.5.3
([@renovate[bot]](https://github.com/apps/renovate))
- [#7742](https://github.com/directus/directus/pull/7742) Update dependency @types/sharp to v0.28.6
([@renovate[bot]](https://github.com/apps/renovate))
- [#7728](https://github.com/directus/directus/pull/7728) Update gatsby monorepo to v3.13.0
([@renovate[bot]](https://github.com/apps/renovate))
- [#7718](https://github.com/directus/directus/pull/7718) Update dependency knex-schema-inspector to v1.5.15
([@renovate[bot]](https://github.com/apps/renovate))
- [#7715](https://github.com/directus/directus/pull/7715) Update dependency vite to v2.5.2
([@renovate[bot]](https://github.com/apps/renovate))
- [#7708](https://github.com/directus/directus/pull/7708) Update dependency knex-schema-inspector to v1.5.14
([@renovate[bot]](https://github.com/apps/renovate))
- [#7705](https://github.com/directus/directus/pull/7705) Update dependency eslint-plugin-prettier to v4
([@renovate[bot]](https://github.com/apps/renovate))
- [#7704](https://github.com/directus/directus/pull/7704) Update typescript-eslint monorepo to v4.30.0
([@renovate[bot]](https://github.com/apps/renovate))
- [#7690](https://github.com/directus/directus/pull/7690) Update dependency micromark to v3
([@renovate[bot]](https://github.com/apps/renovate))
- [#7672](https://github.com/directus/directus/pull/7672) Update dependency sass to v1.38.2
([@renovate[bot]](https://github.com/apps/renovate))
- [#7656](https://github.com/directus/directus/pull/7656) update jest monorepo to v27.1.0
([@renovate[bot]](https://github.com/apps/renovate))
- [#7655](https://github.com/directus/directus/pull/7655) update dependency @types/markdown-it to v12.2.1
([@renovate[bot]](https://github.com/apps/renovate))
- [#7646](https://github.com/directus/directus/pull/7646) update dependency tinymce to v5.9.1
([@renovate[bot]](https://github.com/apps/renovate))
- [#7643](https://github.com/directus/directus/pull/7643) update dependency eslint-plugin-vue to v7.17.0
([@renovate[bot]](https://github.com/apps/renovate))
- [#7638](https://github.com/directus/directus/pull/7638) update dependency typescript to v4.4.2
([@renovate[bot]](https://github.com/apps/renovate))
- [#7614](https://github.com/directus/directus/pull/7614) update dependency tinymce to v5.9.0
([@renovate[bot]](https://github.com/apps/renovate))
- [#7606](https://github.com/directus/directus/pull/7606) pin dependencies
([@renovate[bot]](https://github.com/apps/renovate))
- [#7595](https://github.com/directus/directus/pull/7595) update dependency nock to v13.1.3
([@renovate[bot]](https://github.com/apps/renovate))
- [#7582](https://github.com/directus/directus/pull/7582) pin dependency @types/supertest to 2.0.11
([@renovate[bot]](https://github.com/apps/renovate))
- [#7580](https://github.com/directus/directus/pull/7580) update dependency @vitejs/plugin-vue to v1.6.0
([@renovate[bot]](https://github.com/apps/renovate))
- [#7579](https://github.com/directus/directus/pull/7579) update vue monorepo to v3.2.6
([@renovate[bot]](https://github.com/apps/renovate))
- [#7576](https://github.com/directus/directus/pull/7576) update vue monorepo to v3.2.5
([@renovate[bot]](https://github.com/apps/renovate))
- [#7571](https://github.com/directus/directus/pull/7571) update dependency @vitejs/plugin-vue to v1.5.0
([@renovate[bot]](https://github.com/apps/renovate))
- [#7570](https://github.com/directus/directus/pull/7570) update dependency vite to v2.5.1
([@renovate[bot]](https://github.com/apps/renovate))
- [#7558](https://github.com/directus/directus/pull/7558) update dependency sass to v1.38.1
([@renovate[bot]](https://github.com/apps/renovate))
- [#7556](https://github.com/directus/directus/pull/7556) update dependency @types/marked to v2.0.5
([@renovate[bot]](https://github.com/apps/renovate))
## v9.0.0-rc.91 (August 23, 2021)

View File

@@ -61,8 +61,8 @@ export default {};
#### Accessing the API from within your extension
The Directus App's Vue app instance provides a field called `api`, which can be injected into Vue components using
[Vue's inject framework](https://v3.vuejs.org/guide/component-provide-inject.html). This `api` field contains a
property called `api`, which is an authenticated Axios instance. Here's an example of how to use it:
[Vue's inject framework](https://v3.vuejs.org/guide/component-provide-inject.html). This `api` field contains a property
called `api`, which is an authenticated Axios instance. Here's an example of how to use it:
```vue
<template>