Last eslint tweak (#18198)

* Should be there now

* Format
This commit is contained in:
Rijk van Zanten
2023-04-14 17:40:50 -04:00
committed by GitHub
parent 37658802b7
commit c48309ab68
293 changed files with 1627 additions and 1 deletions

View File

@@ -5,5 +5,6 @@ export function generateRouter(routes: RouteRecordRaw[]) {
history: createWebHistory(),
routes,
});
return router;
}

View File

@@ -69,6 +69,7 @@ export default defineComponent({
if (theme) {
document.body.classList.add(theme);
document
.querySelector('head meta[name="theme-color"]')
?.setAttribute('content', theme === 'light' ? '#ffffff' : '#263238');

View File

@@ -35,6 +35,7 @@ export default function (
emit('beforeEnter');
el._parent = el.parentNode as (Node & ParentNode & HTMLElement) | null;
el._initialStyle = {
transition: el.style.transition,
visibility: el.style.visibility,
@@ -84,6 +85,7 @@ export default function (
emit('beforeLeave');
el._parent = el.parentNode as (Node & ParentNode & HTMLElement) | null;
el._initialStyle = {
transition: el.style.transition,
visibility: el.style.visibility,

View File

@@ -23,6 +23,7 @@ beforeEach(async () => {
component: h('div', 'empty'),
},
]);
router.push('/');
await router.isReady();

View File

@@ -27,6 +27,7 @@ beforeEach(async () => {
component: h('div', 'empty'),
},
]);
router.push('/');
await router.isReady();

View File

@@ -14,6 +14,7 @@ const emojiPicker = new EmojiButton({
position: 'bottom',
emojisPerRow: 8,
});
const emit = defineEmits(['emoji-selected']);
emojiPicker.on('emoji', (event) => {

View File

@@ -44,6 +44,7 @@ test('Should show fallback component when there is an error', async () => {
},
render: () => h('div', 'test'),
});
const fallbackComponent = defineComponent({ render: () => h('div', 'fallback') });
const wrapper = mount(VErrorBoundary, {

View File

@@ -47,9 +47,11 @@ const { isCopySupported, copyToClipboard } = useClipboard();
async function copyError() {
const error = props.error?.response?.data || props.error;
const isCopied = await copyToClipboard(
JSON.stringify(error, isPlainObject(error) ? null : Object.getOwnPropertyNames(error), 2)
);
if (!isCopied) return;
copied.value = true;
}

View File

@@ -118,6 +118,7 @@ function filter(field: Field, parent?: FieldNode): boolean {
const children = isNil(field.schema?.foreign_key_table)
? fieldsStore.getFieldGroupChildren(field.collection, field.field)
: fieldsStore.getFieldsForCollection(field.schema!.foreign_key_table);
return children?.some((field) => matchesSearch(field)) || matchesSearch(field);
function matchesSearch(field: Field) {

View File

@@ -288,6 +288,7 @@ function setContent() {
}</button>`;
})
.join('');
contentEl.value.innerHTML = newInnerHTML;
}
}

View File

@@ -23,12 +23,14 @@ test('should render', () => {
},
global,
});
expect(wrapper.html()).toMatchSnapshot();
});
// test if there is a value
test('submitting', async () => {
expect(formFieldRawEditor).toBeTruthy();
const wrapper = mount(formFieldRawEditor, {
props: {
showModal: true,
@@ -38,6 +40,7 @@ test('submitting', async () => {
},
global,
});
const button = wrapper.findAll('v-button').at(1);
await button!.trigger('click');
await wrapper.vm.$nextTick();
@@ -54,6 +57,7 @@ it('should cancel with keydown', async () => {
},
global,
});
await wrapper.trigger('esc');
await wrapper.vm.$nextTick();
expect(wrapper.emitted().cancel.length).toBe(1);
@@ -69,6 +73,7 @@ it('should cancel with the cancel button', async () => {
},
global,
});
const button = wrapper.findAll('v-button').at(0);
await button!.trigger('click');
await wrapper.vm.$nextTick();

View File

@@ -179,6 +179,7 @@ function useRaw() {
async function pasteRaw() {
const pastedValue = await pasteFromClipboard();
if (!pastedValue) return;
try {
internalValue.value = parseJSON(pastedValue);
} catch (e) {
@@ -194,6 +195,7 @@ function useRaw() {
function useComputedValues() {
const defaultValue = computed<any>(() => props.field?.schema?.default_value);
const internalValue = ref<any>(getInternalValue());
const isEdited = computed(
() => props.modelValue !== undefined && isEqual(props.modelValue, props.initialValue) === false
);

View File

@@ -251,6 +251,7 @@ function useForm() {
(field: Field) => field.meta?.group === props.group || (props.group === null && isNil(field.meta?.group))
)
);
const fieldNames = computed(() => {
return fieldsInGroup.value.map((f) => f.field);
});
@@ -342,6 +343,7 @@ function apply(updates: { [field: string]: any }) {
const groupFields = getFieldsForGroup(props.group)
.filter((field) => !field.schema?.is_primary_key && !isDisabled(field))
.map((field) => field.field);
emit('update:modelValue', assign({}, omit(props.modelValue, groupFields), pick(updates, updatableKeys)));
} else {
emit('update:modelValue', pick(assign({}, props.modelValue, updates), updatableKeys));
@@ -366,6 +368,7 @@ function useBatch() {
function toggleBatchField(field: Field | undefined) {
if (!field) return;
if (batchActiveFields.value.includes(field.field)) {
batchActiveFields.value = batchActiveFields.value.filter((fieldKey) => fieldKey !== field.field);
@@ -390,6 +393,7 @@ function useRawEditor() {
function toggleRawField(field: Field | undefined) {
if (!field) return;
if (rawActiveFields.value.has(field.field)) {
rawActiveFields.value.delete(field.field);
} else {

View File

@@ -19,6 +19,7 @@ const imageElement = ref<HTMLImageElement>();
const emptyPixel =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
const srcData = ref<string>(emptyPixel);
const observer = new IntersectionObserver((entries, observer) => {
@@ -31,6 +32,7 @@ const observer = new IntersectionObserver((entries, observer) => {
loadImage();
}
});
watch(
() => props.src,
() => {

View File

@@ -151,6 +151,7 @@ const listeners = computed(() => ({
},
focus: (e: PointerEvent) => emit('focus', e),
}));
const attributes = computed(() => omit(attrs, ['class']));
const classes = computed(() => [

View File

@@ -32,6 +32,7 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits(['update:modelValue']);
const { modelValue: selection, multiple, max, mandatory } = toRefs(props);
useGroupableParent(
{
selection: selection,

View File

@@ -27,6 +27,7 @@ const props = withDefaults(defineProps<Props>(), {
});
const { active } = toRefs(props);
const { active: isActive, toggle } = useGroupable({
value: props.value,
group: props.scope,

View File

@@ -242,6 +242,7 @@ function onClickOutsideMiddleware(e: Event) {
function onContentClick(e: Event) {
e.stopPropagation();
if (e.target !== e.currentTarget) {
deactivate();
}
@@ -329,7 +330,9 @@ function usePopper(
modifiers: getModifiers(resolve),
strategy: 'fixed',
});
popperInstance.value.forceUpdate();
observer.observe(popper.value!, {
attributes: false,
childList: true,

View File

@@ -7,6 +7,7 @@ import { createI18n } from 'vue-i18n';
import { Focus } from '@/__utils__/focus';
const i18n = createI18n({ legacy: false });
const global: GlobalMountOptions = {
stubs: [
'v-list',

View File

@@ -218,9 +218,11 @@ const { t } = useI18n();
const { internalItems, internalItemsCount, internalSearch } = useItems();
const { displayValue } = useDisplayValue();
const { modelValue } = toRefs(props);
const { otherValue, usesOtherValue } = useCustomSelection(modelValue as Ref<string>, internalItems, (value) =>
emit('update:modelValue', value)
);
const { otherValues, addOtherValue, setOtherValue } = useCustomSelectionMultiple(
modelValue as Ref<string[]>,
internalItems,
@@ -228,6 +230,7 @@ const { otherValues, addOtherValue, setOtherValue } = useCustomSelectionMultiple
);
const search = ref<string | null>(null);
watch(
search,
debounce((val: string | null) => {

View File

@@ -233,6 +233,7 @@ function onMouseMove(event: PointerEvent) {
if (resizing.value === true) {
const newWidth = resizeStartWidth.value + (event.pageX - resizeStartX.value);
const currentHeaders = clone(props.headers);
const newHeaders = currentHeaders.map((existing: Header) => {
if (existing.value === resizeHeader.value?.value) {
return {
@@ -243,6 +244,7 @@ function onMouseMove(event: PointerEvent) {
return existing;
});
emit('update:headers', newHeaders);
}
}

View File

@@ -298,6 +298,7 @@ function getSelectedState(item: Item) {
const selectedKeys = props.selectionUseKeys
? props.modelValue
: props.modelValue.map((item: any) => item[props.itemKey]);
return selectedKeys.includes(item[props.itemKey]);
}

View File

@@ -86,6 +86,7 @@ function checkKeyDown(event: any) {
input.value!.innerText.substring(caretPos),
true
);
position(input.value!, caretPos + 1);
}
} else if (event.code === 'ArrowUp' && !event.shiftKey) {
@@ -123,6 +124,7 @@ function checkKeyDown(event: any) {
event.preventDefault();
const newCaretPos = matchedPositions[checkCaretPos - 1];
parseHTML(
(input.value!.innerText.substring(0, newCaretPos) + input.value!.innerText.substring(caretPos)).replaceAll(
String.fromCharCode(160),
@@ -130,6 +132,7 @@ function checkKeyDown(event: any) {
),
true
);
position(input.value!, newCaretPos);
emit('update:modelValue', input.value!.innerText);
}
@@ -146,6 +149,7 @@ function checkKeyDown(event: any) {
).replaceAll(String.fromCharCode(160), ' '),
true
);
position(input.value!, caretPos);
emit('update:modelValue', input.value!.innerText);
}
@@ -257,6 +261,7 @@ function parseHTML(innerText?: string, isDirectInput = false) {
}
let searchString = replaceSpaceBefore + match + replaceSpaceAfter;
let replacementString = `${addSpaceBefore}<mark class="preview" data-preview="${
props.items[match.substring(props.triggerCharacter.length)]
}" contenteditable="false">${match}</mark>${addSpaceAfter}`;
@@ -280,6 +285,7 @@ function parseHTML(innerText?: string, isDirectInput = false) {
}
lastMatchIndex = 0;
for (const match of matches ?? []) {
let matchIndex = input.value.innerText.indexOf(match, lastMatchIndex);
matchedPositions.push(matchIndex, matchIndex + match.length);

View File

@@ -131,6 +131,7 @@ const filterByFolder = computed(() => {
function validFiles(files: FileList) {
if (files.length === 0) return false;
for (const file of files) {
if (file.size === 0) return false;
}
@@ -162,6 +163,7 @@ function useUpload() {
}
numberOfFiles.value = files.length;
if (props.multiple === true) {
const uploadedFiles = await uploadFiles(Array.from(files), {
onProgressChange: (percentage) => {

View File

@@ -306,6 +306,7 @@ function useDragDrop() {
function onPointerUp() {
dragging.value = false;
if (
props.editMode === false ||
props.draggable === false ||

View File

@@ -24,6 +24,7 @@ export function useAliasFields(fields: Ref<string[]>, collection: Ref<string | n
for (const field of fields.value) {
const alias = getSimpleHash(field);
const fullFields = adjustFieldsForDisplays([field], collection.value).map((field) => {
if (field.includes('.')) {
return `${alias}.${field.split('.').slice(1).join('.')}`;

View File

@@ -85,6 +85,7 @@ describe('useClipboard', () => {
value: { writeText: vi.fn().mockImplementation(() => Promise.reject()) },
configurable: true,
});
const copyValue = 'test';
const wrapper = mount(testComponent, { global });

View File

@@ -24,15 +24,18 @@ export function useClipboard() {
try {
const valueString = typeof value === 'string' ? value : JSON.stringify(value);
await navigator?.clipboard?.writeText(valueString);
notify({
title: message?.success ?? t('copy_raw_value_success'),
});
return true;
} catch (err: any) {
notify({
type: 'error',
title: message?.fail ?? t('copy_raw_value_fail'),
});
return false;
}
}
@@ -40,15 +43,18 @@ export function useClipboard() {
async function pasteFromClipboard(message?: Message): Promise<string | null> {
try {
const pasteValue = await navigator?.clipboard?.readText();
notify({
title: message?.success ?? t('paste_raw_value_success'),
});
return pasteValue;
} catch (err: any) {
notify({
type: 'error',
title: message?.fail ?? t('paste_raw_value_fail'),
});
return null;
}
}

View File

@@ -7,6 +7,7 @@ export function useDialogRoute(): Ref<boolean> {
const isOpen = ref(false);
let resolveGuard: () => void;
const leaveGuard: Promise<void> = new Promise((resolve) => {
resolveGuard = resolve;
});

View File

@@ -23,6 +23,7 @@ import { Field, Relation } from '@directus/types';
test('Returns tree list of same length', () => {
const fieldsStore = useFieldsStore();
fieldsStore.fields = [
{
collection: 'a',
@@ -82,6 +83,7 @@ test('Returns tree list of same length', () => {
test('Returns tree list with injected field', () => {
const fieldsStore = useFieldsStore();
fieldsStore.fields = [
{
collection: 'a',
@@ -173,6 +175,7 @@ test('Returns tree list with injected field', () => {
test('Returns tree list with filter', () => {
const fieldsStore = useFieldsStore();
fieldsStore.fields = [
{
collection: 'a',
@@ -221,6 +224,7 @@ test('Returns tree list with filter', () => {
const { treeList } = useFieldTree(ref('a'), undefined, filterIntegerFields);
expect(unref(treeList)).toHaveLength(1);
expect(unref(treeList)).toEqual([
{ name: 'ID', field: 'id', collection: 'a', relatedCollection: undefined, key: 'id', path: 'id', type: 'integer' },
]);
@@ -228,6 +232,7 @@ test('Returns tree list with filter', () => {
test('Returns tree list with group', () => {
const fieldsStore = useFieldsStore();
fieldsStore.fields = [
{
collection: 'a',
@@ -313,6 +318,7 @@ test('Returns tree list with group', () => {
test('Returns tree list for O2M', () => {
const fieldsStore = useFieldsStore();
fieldsStore.fields = [
{
collection: 'a',
@@ -402,6 +408,7 @@ test('Returns tree list for O2M', () => {
] as Field[];
const relationsStore = useRelationsStore();
relationsStore.relations = [
{
collection: 'b',
@@ -468,6 +475,7 @@ test('Returns tree list for O2M', () => {
test('Returns tree list for M2O', () => {
const fieldsStore = useFieldsStore();
fieldsStore.fields = [
{
collection: 'a',
@@ -523,6 +531,7 @@ test('Returns tree list for M2O', () => {
] as Field[];
const relationsStore = useRelationsStore();
relationsStore.relations = [
{
collection: 'a',
@@ -572,6 +581,7 @@ test('Returns tree list for M2O', () => {
test('Returns tree list for M2A with single related collection', () => {
const fieldsStore = useFieldsStore();
fieldsStore.fields = [
{
collection: 'a',
@@ -695,6 +705,7 @@ test('Returns tree list for M2A with single related collection', () => {
] as Field[];
const relationsStore = useRelationsStore();
relationsStore.relations = [
{
collection: 'a_m2a',
@@ -788,6 +799,7 @@ test('Returns tree list for M2A with single related collection', () => {
test('Returns tree list for M2A with multiple related collections', () => {
const fieldsStore = useFieldsStore();
fieldsStore.fields = [
{
collection: 'a',
@@ -939,6 +951,7 @@ test('Returns tree list for M2A with multiple related collections', () => {
] as Field[];
const relationsStore = useRelationsStore();
relationsStore.relations = [
{
collection: 'a_m2a',

View File

@@ -194,6 +194,7 @@ export function useItem(
for (const relation of relations) {
const relatedPrimaryKeyField = fieldsStore.getPrimaryKeyFieldForCollection(relation.collection);
const existsJunctionRelated = relationsStore.relations.find((r) => {
return r.collection === relation.collection && r.meta?.many_field === relation.meta?.junction_field;
});
@@ -296,6 +297,7 @@ export function useItem(
[`filter[${relatedPrimaryKeyField!.field}][_in]`]: existingIds.join(','),
},
});
existingItems = response.data.data;
}

View File

@@ -27,6 +27,7 @@ const mockUser = {
id: '00000000-0000-0000-0000-000000000000',
},
};
const mockReadPermissions = {
role: '00000000-0000-0000-0000-000000000000',
permissions: {
@@ -49,6 +50,7 @@ const mockReadPermissions = {
collection: 'test',
action: 'read',
};
const mockFields: Field[] = [
{
collection: 'test',
@@ -116,6 +118,7 @@ describe('usePermissions', () => {
const { fields } = usePermissions(ref('test'), ref(null), ref(false));
expect(fields.value.length).toBeGreaterThan(0);
for (const field of fields.value) {
expect(mockReadPermissions.fields.includes(field.field)).toBe(true);
}

View File

@@ -56,11 +56,13 @@ export function usePreset(
busy.value = true;
const updatedValues = await presetsStore.savePreset(preset ? preset : localPreset.value);
localPreset.value = {
...localPreset.value,
id: updatedValues.id,
user: updatedValues.user,
};
bookmarkSaved.value = true;
busy.value = false;
return updatedValues;

View File

@@ -129,6 +129,7 @@ describe('test o2m relation', () => {
...workerData,
{ name: 'test5', facility: 1, $type: 'created', $index: 0 },
]);
expect(wrapper.emitted()['update:value'][0]).toEqual([
{
create: [
@@ -166,6 +167,7 @@ describe('test o2m relation', () => {
...workerData,
{ name: 'test5 edited', facility: 2, $type: 'created', $index: 0 },
]);
expect(wrapper.emitted()['update:value'][1]).toEqual([
{
create: [
@@ -209,6 +211,7 @@ describe('test o2m relation', () => {
changes.splice(1, 1, { id: 2, name: 'test2-edited', facility: 1, $edits: 0, $type: 'updated', $index: 0 });
expect(wrapper.vm.displayItems).toEqual(changes);
expect(wrapper.emitted()['update:value'][0]).toEqual([
{
create: [],
@@ -255,6 +258,7 @@ describe('test o2m relation', () => {
changes.splice(0, 1, { id: 1, name: 'test', facility: 1, $type: 'deleted', $index: 0 });
expect(wrapper.vm.displayItems).toEqual(changes);
expect(wrapper.emitted()['update:value'][2]).toEqual([
{
create: [],

View File

@@ -116,6 +116,7 @@ export function useRelationMultiple(
const editsIndex = _value.value.update.findIndex(
(edit) => typeof edit === 'object' && edit[targetPKField] === item[targetPKField]
);
const deleteIndex = _value.value.delete.findIndex((id) => id === item[targetPKField]);
let updatedItem: Record<string, any> = cloneDeep(item);
@@ -157,6 +158,7 @@ export function useRelationMultiple(
edit[relation.value.junctionField.field][relation.value.relatedPrimaryKeyField.field] ===
item[relation.value.junctionField.field][relation.value.relatedPrimaryKeyField.field]
);
case 'm2a': {
const itemCollection = item[relation.value.collectionField.field];
const editCollection = edit[relation.value.collectionField.field];
@@ -171,6 +173,7 @@ export function useRelationMultiple(
}
}
});
if (!fetchedItem) return edit;
return merge({}, fetchedItem, edit);
});
@@ -275,6 +278,7 @@ export function useRelationMultiple(
[info.relatedPrimaryKeyField.field]: item,
},
};
case 'm2a': {
if (!collection) throw new Error('You need to provide a collection on an m2a');
@@ -315,6 +319,7 @@ export function useRelationMultiple(
targetCollection = relation.value.junctionCollection.collection;
fields.add(relation.value.junctionPrimaryKeyField.field);
fields.add(relation.value.collectionField.field);
for (const collection of relation.value.allowedCollections) {
const pkField = relation.value.relationPrimaryKeyFields[collection.collection];
fields.add(`${relation.value.junctionField.field}:${collection.collection}.${pkField.field}`);
@@ -466,6 +471,7 @@ export function useRelationMultiple(
return item[relation.value.relatedPrimaryKeyField.field];
case 'm2m':
return item[relation.value.junctionField.field][relation.value.relatedPrimaryKeyField.field];
case 'm2a': {
const collection = item[relation.value.collectionField.field];
return item[relation.value.junctionPrimaryKeyField.field][
@@ -533,6 +539,7 @@ export function useRelationMultiple(
return acc;
}, [])
);
fields.add(relation.relatedPrimaryKeyField.field);
const relatedPKField = relation.relatedPrimaryKeyField.field;
@@ -606,6 +613,7 @@ export function useRelationMultiple(
[relation.junctionField.field]: item,
}))
);
return acc;
}, [] as Record<string, any>[]);
}

View File

@@ -110,6 +110,7 @@ export function useTranslationStrings(): UsableTranslationStrings {
try {
const settingsStore = useSettingsStore();
await settingsStore.updateSettings({ translation_strings: payload }, false);
if (settingsStore.settings?.translation_strings) {
translationStrings.value = settingsStore.settings.translation_strings.map((p: TranslationStringRaw) => ({
key: p.key,
@@ -133,12 +134,14 @@ export function useTranslationStrings(): UsableTranslationStrings {
function mergeTranslationStringsForLanguage(lang: Language) {
if (!translationStrings?.value) return;
const localeMessages: Record<string, any> = translationStrings.value.reduce((acc, cur) => {
if (!cur.key || !cur.translations) return acc;
const translationForCurrentLang = cur.translations.find((t) => t.language === lang);
if (!translationForCurrentLang || !translationForCurrentLang.translation) return acc;
return { ...acc, [cur.key]: getLiteralInterpolatedTranslation(translationForCurrentLang.translation, true) };
}, {});
i18n.global.mergeLocaleMessage(lang, localeMessages);
}

View File

@@ -81,6 +81,7 @@ export function processValue(bindingValue: BindingValue['value']): DirectiveConf
if (isFunction) {
const binding = bindingValue as Handler;
value = {
handler: binding,
middleware: () => true,

View File

@@ -51,6 +51,7 @@ export function createEnterHandler(element: HTMLElement, binding: DirectiveBindi
updateTooltip(element, binding, tooltip);
} else {
clearTimeout(tooltipTimer);
tooltipTimer = window.setTimeout(() => {
animateIn(tooltip);
updateTooltip(element, binding, tooltip);

View File

@@ -51,6 +51,7 @@ export default defineDisplay({
},
options: ({ field }) => {
const options = field.meta?.display_options || {};
const fields = [
{
field: 'relative',

View File

@@ -41,6 +41,7 @@ export default defineComponent({
},
setup(props) {
const { t } = useI18n();
const displayValue = computed(() => {
if (!props.value) return null;

View File

@@ -57,6 +57,7 @@ export default defineComponent({
const collectionExists = !!collectionsStore.collections.find(
(collection) => collection.collection === collectionName
);
if (collectionExists === false) return null;
return collectionName;
});

View File

@@ -95,6 +95,7 @@ const displayTemplate = computed(() => {
return displayTemplate || `{{ ${primaryKey.value || ''} }}`;
});
const requiredFields = computed(() => {
if (!displayTemplate.value || !unref(selectedCollection)) return [];
return adjustFieldsForDisplays(getFieldsFromTemplate(displayTemplate.value), unref(selectedCollection));

View File

@@ -171,6 +171,7 @@ const editImageEditor = ref(false);
async function imageErrorHandler() {
if (!src.value) return;
try {
await api.get(src.value);
} catch (err: any) {

View File

@@ -121,9 +121,11 @@ export default defineComponent({
(newVal, oldVal) => {
if (!props.validationErrors) return;
if (isEqual(newVal, oldVal)) return;
const includedFieldsWithErrors = props.validationErrors.filter((validationError) =>
groupFields.value.find((rootField) => rootField.field === validationError.field)
);
if (includedFieldsWithErrors.length > 0) selection.value = [includedFieldsWithErrors[0].field];
}
);
@@ -154,6 +156,7 @@ export default defineComponent({
}
}
);
watch(
() => props.values,
(newVal) => {

View File

@@ -166,6 +166,7 @@ export default defineComponent({
parser.parseError = (str: string, hash: any) => {
const loc = hash.loc;
found.push({
from: CodeMirror.Pos(loc.first_line - 1, loc.first_column),
to: CodeMirror.Pos(loc.last_line - 1, loc.last_column),
@@ -296,6 +297,7 @@ export default defineComponent({
async (altOptions) => {
if (!altOptions || altOptions.size === 0) return;
await getImports(altOptions);
for (const key in altOptions) {
codemirror?.setOption(key as any, altOptions[key]);
}

View File

@@ -83,6 +83,7 @@ export default defineComponent({
if (props.softLength) return 100 - (props.value.length / +props.softLength) * 100;
return 100;
});
return {
charsRemaining,
percentageRemaining,

View File

@@ -492,9 +492,11 @@ export default defineComponent({
editor.on('init', function () {
editor.shortcuts.remove('meta+k');
editor.addShortcut('meta+k', 'Insert Link', () => {
editor.ui.registry.getAll().buttons.customlink.onAction();
});
setCount();
});

View File

@@ -26,12 +26,14 @@ type UsableLink = {
export default function useLink(editor: Ref<any>): UsableLink {
const linkDrawerOpen = ref(false);
const defaultLinkSelection = {
url: null,
displayText: null,
title: null,
newTab: true,
};
const linkSelection = ref<LinkSelection>(defaultLinkSelection);
const linkNode = ref<HTMLLinkElement | null>(null);
const currentSelectionNode = ref<HTMLElement | null>(null);

View File

@@ -80,6 +80,7 @@ export default function useMedia(editor: Ref<any>, imageToken: Ref<string | unde
...mediaSelection.value,
sourceUrl: newSource,
};
mediaSelection.value.previewUrl = replaceUrlAccessToken(newSource, imageToken.value || getToken());
},
});

View File

@@ -288,6 +288,7 @@ function sortItems(items: DisplayItem[]) {
return changes;
});
update(...sortedItems);
}
@@ -303,10 +304,12 @@ function createItem(collection: string) {
currentlyEditing.value = null;
relatedPrimaryKey.value = null;
editsAtStart.value = {
[relationInfo.value.collectionField.field]: collection,
[relationInfo.value.junctionField.field]: {},
};
newItem = true;
editModalActive.value = true;
}
@@ -316,10 +319,12 @@ function editItem(item: DisplayItem) {
const relationPkField =
relationInfo.value.relationPrimaryKeyFields[item[relationInfo.value.collectionField.field]].field;
const junctionField = relationInfo.value.junctionField.field;
const junctionPkField = relationInfo.value.junctionPrimaryKeyField.field;
newItem = false;
editsAtStart.value = {
...getItemEdits(item),
[relationInfo.value.collectionField.field]: item[relationInfo.value.collectionField.field],

View File

@@ -387,6 +387,7 @@ watch(
const junctionCollection = relationInfo.value.junctionCollection.collection;
const contentWidth: Record<string, number> = {};
(displayItems.value ?? []).forEach((item: Record<string, any>) => {
props.fields.forEach((key) => {
if (!contentWidth[key]) {
@@ -543,6 +544,7 @@ function deleteItem(item: DisplayItem) {
}
const values = inject('values', ref<Record<string, any>>({}));
const customFilter = computed(() => {
const filter: Filter = {
_and: [],
@@ -593,6 +595,7 @@ function getLinkForItem(item: DisplayItem) {
relationInfo.value.junctionField.field,
relationInfo.value.relatedPrimaryKeyField.field,
]);
return `/content/${relationInfo.value.relatedCollection.collection}/${encodeURIComponent(primaryKey)}`;
}

View File

@@ -228,14 +228,17 @@ function change(event: ChangeEvent) {
case 'created':
create(cleanItem(event.added.element));
break;
case 'updated': {
const pkField = relationInfo.value.relatedPrimaryKeyField.field;
const exists = displayItems.value.find((item) => item[pkField] === event.added.element[pkField]);
// We have to make sure we remove the reverseJunctionField when we move it back to its initial position as otherwise it will be selected.
update({
...cleanItem(event.added.element),
[relationInfo.value.reverseJunctionField.field]: exists ? undefined : primaryKey.value,
});
break;
}

View File

@@ -362,6 +362,7 @@ watch(
const relatedCollection = relationInfo.value.relatedCollection.collection;
const contentWidth: Record<string, number> = {};
(displayItems.value ?? []).forEach((item: Record<string, any>) => {
props.fields.forEach((key) => {
if (!contentWidth[key]) {
@@ -505,6 +506,7 @@ function deleteItem(item: DisplayItem) {
}
const values = inject('values', ref<Record<string, any>>({}));
const customFilter = computed(() => {
const filter: Filter = {
_and: [],

View File

@@ -153,6 +153,7 @@ export default defineComponent({
const basemaps = getBasemapSources();
const appStore = useAppStore();
const { basemap } = toRefs(appStore);
const style = computed(() => {
const source = basemaps.find((source) => source.name == basemap.value) ?? basemaps[0];
return basemap.value, getStyleFromBasemapSource(source);
@@ -258,11 +259,14 @@ export default defineComponent({
...props.defaultView,
...(mapboxKey ? { accessToken: mapboxKey } : {}),
});
if (controls.geocoder) {
map.addControl(controls.geocoder as any, 'top-right');
controls.geocoder.on('result', (event: any) => {
location.value = event.result.center;
});
controls.geocoder.on('clear', () => {
location.value = null;
});
@@ -272,6 +276,7 @@ export default defineComponent({
const { longitude, latitude } = event.coords;
location.value = [longitude, latitude];
});
map.addControl(controls.attribution, 'bottom-left');
map.addControl(controls.navigation, 'top-left');
map.addControl(controls.geolocate, 'top-left');
@@ -287,6 +292,7 @@ export default defineComponent({
map.on('draw.modechange', handleDrawModeChange);
map.on('draw.selectionchange', handleSelectionChange);
map.on('move', updateProjection);
for (const layer of activeLayers) {
map.on('mousedown', layer, hideTooltip);
map.on('mousemove', layer, updateTooltip);
@@ -310,6 +316,7 @@ export default defineComponent({
if (!value) {
controls.draw.deleteAll();
currentGeometry = null;
if (geometryType) {
const snaked = snakeCase(geometryType.replace('Multi', ''));
const mode = `draw_${snaked}` as any;
@@ -343,6 +350,7 @@ export default defineComponent({
function fitDataBounds(options: CameraOptions & AnimationOptions) {
if (map && currentGeometry) {
const bbox = getBBox(currentGeometry);
map.fitBounds(bbox as LngLatBoundsLike, {
padding: 80,
maxZoom: 8,
@@ -445,6 +453,7 @@ export default defineComponent({
const coordinates = geometries
.filter(({ type }) => `Multi${type}` == geometryType)
.map(({ coordinates }) => coordinates);
result = { type: geometryType, coordinates } as Geometry;
} else {
result = geometries[geometries.length - 1];
@@ -467,6 +476,7 @@ export default defineComponent({
function handleDrawUpdate() {
currentGeometry = getCurrentGeometry();
if (!currentGeometry) {
controls.draw.deleteAll();
emit('input', null);

View File

@@ -78,6 +78,7 @@ export default defineComponent({
const basemaps = getBasemapSources();
const appStore = useAppStore();
const { basemap } = toRefs(appStore);
const style = computed(() => {
const source = basemaps.find((source) => source.name == basemap.value) ?? basemaps[0];
return getStyleFromBasemapSource(source);
@@ -90,6 +91,7 @@ export default defineComponent({
...(defaultView.value || {}),
...(mapboxKey ? { accessToken: mapboxKey } : {}),
});
map.on('moveend', () => {
defaultView.value = {
center: map.getCenter(),
@@ -99,6 +101,7 @@ export default defineComponent({
};
});
});
onUnmounted(() => {
map.remove();
});

View File

@@ -286,6 +286,7 @@ function useColor() {
let alpha = Math.round(255 * color.value.alpha())
.toString(16)
.toUpperCase();
alpha = alpha.padStart(2, '0');
return color.value.rgb().array().length === 4 ? `${color.value.hex()}${alpha}` : color.value.hex();
}

View File

@@ -135,6 +135,7 @@ const customFilter = computed(() => {
const { t } = useI18n();
const { collection, field } = toRefs(props);
const { relationInfo } = useRelationM2O(collection, field);
const value = computed({
get: () => props.value ?? null,
set: (value) => {

View File

@@ -161,12 +161,14 @@ const firstItem = computed(() => {
return getItemEdits(item);
});
const secondItem = computed(() => {
const item = getItemWithLang(displayItems.value, secondLang.value);
if (item === undefined) return undefined;
return getItemEdits(item);
});
const firstItemInitial = computed(() => getItemWithLang(fetchedItems.value, firstLang.value));
const secondItemInitial = computed(() => getItemWithLang(fetchedItems.value, secondLang.value));

View File

@@ -85,14 +85,17 @@ export default defineLayout<LayoutOptions>({
const viewInfo = syncRefProperty(layoutOptions, 'viewInfo', undefined);
const startDateField = syncRefProperty(layoutOptions, 'startDateField', undefined);
const startDateFieldInfo = computed(() => {
return fieldsInCollection.value.find((field: Field) => field.field === startDateField.value);
});
const endDateField = syncRefProperty(layoutOptions, 'endDateField', undefined);
const endDateFieldInfo = computed(() => {
return fieldsInCollection.value.find((field: Field) => field.field === endDateField.value);
});
const firstDay = syncRefProperty(layoutOptions, 'firstDay', undefined);
const queryFields = computed(() => {

View File

@@ -73,6 +73,7 @@ export default defineComponent({
const firstDayWritable = useSync(props, 'firstDay', emit);
const firstDayOfWeekForDate = startOfWeek(new Date());
const firstDayOptions: { text: string; value: number }[] = [...Array(7).keys()].map((_, i) => ({
text: localizedFormat(add(firstDayOfWeekForDate, { days: i }), 'EEEE'),
value: i,

View File

@@ -116,9 +116,11 @@ export default defineComponent({
return { source, fileType };
});
const showThumbnail = computed(() => {
return imageInfo.value && imageInfo.value.fileType;
});
const selectionIcon = computed(() => {
if (!props.item) return 'radio_button_unchecked';

View File

@@ -82,29 +82,36 @@ export default defineComponent({
const { sidebarOpen, basemap } = toRefs(appStore);
const mapboxKey = settingsStore.settings?.mapbox_key;
const basemaps = getBasemapSources();
const style = computed(() => {
const source = basemaps.find((source) => source.name === basemap.value) ?? basemaps[0];
return getStyleFromBasemapSource(source);
});
const attributionControl = new AttributionControl();
const navigationControl = new NavigationControl({
showCompass: false,
});
const geolocateControl = new GeolocateControl();
const fitDataControl = new ButtonControl('mapboxgl-ctrl-fitdata', () => {
emit('fitdata');
});
const boxSelectControl = new BoxSelectControl({
boxElementClass: 'map-selection-box',
selectButtonClass: 'mapboxgl-ctrl-select',
layers: ['__directus_polygons', '__directus_points', '__directus_lines'],
});
let geocoderControl: MapboxGeocoder | undefined;
if (mapboxKey) {
const marker = document.createElement('div');
marker.className = 'mapboxgl-user-location-dot mapboxgl-search-location-dot';
geocoderControl = new MapboxGeocoder({
accessToken: mapboxKey,
collapsed: true,
@@ -118,6 +125,7 @@ export default defineComponent({
onMounted(() => {
setupMap();
});
onUnmounted(() => {
map.remove();
});
@@ -161,10 +169,12 @@ export default defineComponent({
map.on('mouseleave', '__directus_clusters', hoverCluster);
map.on('select.enable', () => (selectMode.value = true));
map.on('select.disable', () => (selectMode.value = false));
map.on('select.end', (event: MapLayerMouseEvent) => {
const ids = event.features?.map((f) => f.id);
emit('featureselect', { ids, replace: !event.alt });
});
map.on('moveend', () => {
emit('moveend', {
center: map.getCenter(),
@@ -174,6 +184,7 @@ export default defineComponent({
bbox: map.getBounds().toArray().flat(),
});
});
startWatchers();
});
@@ -183,6 +194,7 @@ export default defineComponent({
if (!opened) setTimeout(() => map.resize(), 300);
}
);
setTimeout(() => map.resize(), 300);
}
@@ -240,6 +252,7 @@ export default defineComponent({
}
map.addSource('__directus', { ...newSource, data: props.data });
map.once('sourcedata', () => {
setTimeout(() => props.layers.forEach((layer) => map.addLayer(layer)));
});
@@ -247,9 +260,11 @@ export default defineComponent({
function updateLayers(newLayers?: AnyLayer[], previousLayers?: AnyLayer[]) {
const currentMapLayersId = new Set(map.getStyle().layers?.map(({ id }) => id));
previousLayers?.forEach((layer) => {
if (currentMapLayersId.has(layer.id)) map.removeLayer(layer.id);
});
newLayers?.forEach((layer) => {
map.addLayer(layer);
});
@@ -260,6 +275,7 @@ export default defineComponent({
map.setFeatureState({ id, source: '__directus' }, { selected: false });
map.removeFeatureState({ id, source: '__directus' });
});
newSelection?.forEach((id) => {
map.setFeatureState({ id, source: '__directus' }, { selected: true });
});
@@ -324,10 +340,13 @@ export default defineComponent({
const features = map.queryRenderedFeatures(event.point, {
layers: ['__directus_clusters'],
});
const clusterId = features[0]?.properties?.cluster_id;
const source = map.getSource('__directus') as GeoJSONSource;
source.getClusterExpansionZoom(clusterId, (err: any, zoom: number) => {
if (err) return;
map.flyTo({
center: (features[0].geometry as GeoJSON.Point).coordinates as LngLatLike,
zoom: zoom,

View File

@@ -134,6 +134,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
function fitDataBounds() {
shouldUpdateCamera.value = true;
if (isGeometryFieldNative.value) {
return;
}
@@ -180,6 +181,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
geojsonError.value = null;
geojson.value = toGeoJSON(items.value, geometryOptions.value);
geojsonLoading.value = false;
if (!cameraOptions.value || shouldUpdateCamera.value) {
geojsonBounds.value = geojson.value.bbox;
}

View File

@@ -157,6 +157,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
const limit = syncRefProperty(layoutQuery, 'limit', 25);
const defaultSort = computed(() => (primaryKeyField.value ? [primaryKeyField.value?.field] : []));
const sort = syncRefProperty(layoutQuery, 'sort', defaultSort);
const fieldsDefaultValue = computed(() => {
return fieldsInCollection.value
.filter((field: Field) => !field.meta?.hidden)

View File

@@ -259,6 +259,7 @@ useShortcut(
},
table
);
const permissionsStore = usePermissionsStore();
const userStore = useUserStore();

View File

@@ -18,6 +18,7 @@ init();
async function init() {
console.log(DIRECTUS_LOGO);
console.info(
`Hey! Interested in helping build this open-source data management platform?\nIf so, join our growing team of contributors at: https://directus.chat`
);

View File

@@ -121,6 +121,7 @@ export default defineComponent({
});
item.value = response.data.data;
if (item.value) {
if (te(`field_options.directus_activity.${item.value.action}`))
item.value.action_translated = t(`field_options.directus_activity.${item.value.action}`);

View File

@@ -126,11 +126,13 @@ const name = computed(() => translate(props.bookmark.bookmark));
function useEditBookmark() {
const editActive = ref(false);
const editValue = reactive({
name: props.bookmark.bookmark,
icon: props.bookmark?.icon ?? 'bookmark',
color: props.bookmark?.color ?? null,
});
const editSaving = ref(false);
return { editActive, editValue, editSave, editSaving, editCancel };

View File

@@ -73,6 +73,7 @@ export default defineComponent({
const dense = computed(() => collectionsStore.visibleCollections.length > 5);
const showSearch = computed(() => collectionsStore.visibleCollections.length > 20);
const hasHiddenCollections = computed(
() => collectionsStore.allCollections.length > collectionsStore.visibleCollections.length
);

View File

@@ -604,6 +604,7 @@ export default defineComponent({
icon: bookmark.icon,
color: bookmark.color,
});
router.push(`/content/${newBookmark.collection}?bookmark=${newBookmark.id}`);
bookmarkDialogActive.value = false;
@@ -628,6 +629,7 @@ export default defineComponent({
const updatePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'update' && permission.collection === collection.value
);
return !!updatePermissions;
});
@@ -639,6 +641,7 @@ export default defineComponent({
const updatePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'update' && permission.collection === collection.value
);
if (!updatePermissions) return false;
if (!updatePermissions.fields) return false;
if (updatePermissions.fields.includes('*')) return true;
@@ -652,6 +655,7 @@ export default defineComponent({
const deletePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'delete' && permission.collection === collection.value
);
return !!deletePermissions;
});
@@ -662,6 +666,7 @@ export default defineComponent({
const createPermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'create' && permission.collection === collection.value
);
return !!createPermissions;
});

View File

@@ -286,6 +286,7 @@ export default defineComponent({
if (!props.file) return null;
if (!props.file.folder) return;
loading.value = true;
try {
const response = await api.get(`/folders/${props.file.folder}`, {
params: {

View File

@@ -76,6 +76,7 @@ export default defineModule({
const permission = permissions.find(
(permission) => permission.collection === 'directus_files' && permission.action === 'read'
);
return !!permission;
},
});

View File

@@ -317,6 +317,7 @@ export default defineComponent({
onBeforeRouteLeave(() => {
selection.value = [];
});
onBeforeRouteUpdate(() => {
selection.value = [];
});
@@ -494,6 +495,7 @@ export default defineComponent({
const updatePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'update' && permission.collection === 'directus_files'
);
return !!updatePermissions;
});
@@ -504,6 +506,7 @@ export default defineComponent({
const deletePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'delete' && permission.collection === 'directus_files'
);
return !!deletePermissions;
});
@@ -514,6 +517,7 @@ export default defineComponent({
const createPermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'create' && permission.collection === 'directus_files'
);
return !!createPermissions;
});
@@ -524,6 +528,7 @@ export default defineComponent({
const createPermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'create' && permission.collection === 'directus_folders'
);
return !!createPermissions;
});

View File

@@ -244,6 +244,7 @@ const { confirmLeave, leaveTo } = useEditsGuard(hasEdits);
const confirmDelete = ref(false);
const editActive = ref(false);
const fileSrc = computed(() => {
if (item.value && item.value.modified_on) {
return `assets/${props.primaryKey}?cache-buster=${item.value.modified_on}&key=system-large-contain`;

View File

@@ -165,8 +165,10 @@ function generateFields(updates: StateUpdates, state: State, { getCurrent }: Hel
const junctionCurrent = getCurrent('relations.o2m.field');
const junctionRelated = getCurrent('relations.m2o.field');
const relatedCollection = getCurrent('relations.m2o.related_collection');
const relatedPrimaryKeyField =
fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection) ?? getCurrent('collections.related.fields[0]');
const sort = getCurrent('relations.o2m.meta.sort_field');
if (junctionCollection && junctionCurrent && fieldExists(junctionCollection, junctionCurrent) === false) {
@@ -218,6 +220,7 @@ export function setDefaults(updates: StateUpdates, state: State, { getCurrent }:
const fieldsStore = useFieldsStore();
const currentCollection = state.collection!;
const currentCollectionPrimaryKeyField =
fieldsStore.getPrimaryKeyFieldForCollection(currentCollection)?.field ?? 'id';

View File

@@ -96,6 +96,7 @@ export function setDefaults(updates: StateUpdates, state: State, { getCurrent }:
const fieldsStore = useFieldsStore();
const currentCollection = state.collection!;
const currentCollectionPrimaryKeyField =
fieldsStore.getPrimaryKeyFieldForCollection(currentCollection)?.field ?? 'id';
@@ -105,11 +106,13 @@ export function setDefaults(updates: StateUpdates, state: State, { getCurrent }:
set(updates, 'relations.o2m.field', `${currentCollection}_${currentCollectionPrimaryKeyField}`);
set(updates, 'relations.m2o.collection', junctionName);
set(updates, 'relations.m2o.field', 'item');
set(
updates,
'relations.m2o.meta.one_allowed_collections',
getCurrent('relations.m2o.meta.one_allowed_collections') ?? []
);
set(updates, 'relations.m2o.meta.one_collection_field', 'collection');
}

View File

@@ -125,6 +125,7 @@ export function autoGenerateJunctionFields(updates: StateUpdates, state: State,
const currentCollection = state.collection!;
const currentPrimaryKeyField = fieldsStore.getPrimaryKeyFieldForCollection(currentCollection)?.field ?? 'id';
const relatedCollection =
updates.relations?.m2o?.related_collection ?? getCurrent('relations.m2o.related_collection');
@@ -251,8 +252,10 @@ function generateFields(updates: StateUpdates, state: State, { getCurrent }: Hel
const junctionRelated = getCurrent('relations.m2o.field');
const sort = getCurrent('relations.o2m.meta.sort_field');
const relatedCollection = getCurrent('relations.m2o.related_collection');
const relatedPrimaryKeyField =
fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection) ?? getCurrent('collections.related.fields[0]');
const existsJunctionRelated = relationsStore.relations.find(
(relation) => relation.collection === junctionCollection && relation.field === junctionRelated
);

View File

@@ -118,6 +118,7 @@ export function updateJunctionRelated(updates: StateUpdates, _state: State, { ge
const fieldsStore = useFieldsStore();
const relatedCollection = getCurrent('relations.m2o.related_collection');
const relatedCollectionPrimaryKeyField =
fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection)?.field ?? 'id';
@@ -289,8 +290,10 @@ function generateFields(updates: StateUpdates, state: State, { getCurrent }: Hel
const junctionCurrent = getCurrent('relations.o2m.field');
const junctionRelated = getCurrent('relations.m2o.field');
const relatedCollection = getCurrent('relations.m2o.related_collection');
const relatedPrimaryKeyField =
fieldsStore.getPrimaryKeyFieldForCollection(relatedCollection) ?? getCurrent('collections.related.fields[0]');
const sort = getCurrent('relations.o2m.sort_field');
if (junctionCollection && junctionCurrent && fieldExists(junctionCollection, junctionCurrent) === false) {
@@ -342,6 +345,7 @@ export function setDefaults(updates: StateUpdates, state: State, { getCurrent }:
const fieldsStore = useFieldsStore();
const currentCollection = state.collection!;
const currentCollectionPrimaryKeyField =
fieldsStore.getPrimaryKeyFieldForCollection(currentCollection)?.field ?? 'id';
@@ -353,6 +357,7 @@ export function setDefaults(updates: StateUpdates, state: State, { getCurrent }:
set(updates, 'relations.m2o.related_collection', getCurrent('relations.m2o.related_collection') ?? 'languages');
const languagesCollection = getCurrent('relations.m2o.related_collection');
const languagesCollectionPrimaryKeyField =
fieldsStore.getPrimaryKeyFieldForCollection(languagesCollection)?.field ?? 'id';

View File

@@ -36,17 +36,20 @@ describe('Actions', () => {
describe('startEditing', () => {
it('New Field', () => {
const fieldDetailStore = useFieldDetailStore();
const testValue: { collection: string; field: string; localType: 'presentation' } = {
collection: 'collection_a',
field: '+',
localType: 'presentation',
};
fieldDetailStore.startEditing(testValue.collection, testValue.field, testValue.localType);
expect(fieldDetailStore.collection).toEqual(testValue.collection);
expect(fieldDetailStore.field.collection).toEqual(testValue.collection);
expect(fieldDetailStore.editing).toEqual(testValue.field);
expect(fieldDetailStore.localType).toEqual(testValue.localType);
});
it('Existing Field — M2O', () => {
const mockedField = {
collection: 'collection_a',
@@ -58,6 +61,7 @@ describe('Actions', () => {
},
name: 'Collection A Field',
};
const fieldsStore = useFieldsStore();
(fieldsStore.getField as Mock).mockReturnValue(mockedField);
@@ -79,6 +83,7 @@ describe('Actions', () => {
},
},
];
const relationsStore = useRelationsStore();
(relationsStore.getRelationsForField as Mock).mockReturnValue(mockedRelations);
@@ -90,6 +95,7 @@ describe('Actions', () => {
expect(fieldDetailStore.field.name).toEqual(mockedField.name);
expect(fieldDetailStore.localType).toEqual('m2o');
expect(fieldDetailStore.relations.o2m).toEqual(undefined);
expect(fieldDetailStore.relations.m2o).toEqual(
mockedRelations.find(
(relation) => relation.collection === mockedField.collection && relation.field === mockedField.field
@@ -97,6 +103,7 @@ describe('Actions', () => {
);
});
});
it.todo('Existing Field — M2M');
});
@@ -104,18 +111,22 @@ describe('Alterations', () => {
describe('files', () => {
it('autoGenerateJunctionRelation has changed', () => {
const fieldDetailStore = useFieldDetailStore();
const testValue: { collection: string; field: string; localType: 'files' } = {
collection: 'collection_a',
field: '+',
localType: 'files',
};
fieldDetailStore.startEditing(testValue.collection, testValue.field, testValue.localType);
const fieldsStore = useFieldsStore();
(fieldsStore.getPrimaryKeyFieldForCollection as Mock).mockReturnValue({
collection: 'collection_a',
field: 'id',
});
expect(fieldDetailStore.collection).toEqual(testValue.collection);
expect(fieldDetailStore.field.collection).toEqual(testValue.collection);
expect(fieldDetailStore.editing).toEqual(testValue.field);
@@ -125,6 +136,7 @@ describe('Alterations', () => {
expect(fieldDetailStore.relations.m2o?.field).toEqual('directus_files_id');
fieldDetailStore.update({ autoGenerateJunctionRelation: false });
fieldDetailStore.update({
relations: {
o2m: {
@@ -132,6 +144,7 @@ describe('Alterations', () => {
},
},
});
fieldDetailStore.update({ autoGenerateJunctionRelation: true });
expect(fieldDetailStore.relations.o2m?.collection).toEqual('collection_a_files');

View File

@@ -115,6 +115,7 @@ export const useFieldDetailStore = defineStore({
this.loading = true;
const response = await api.get(`/fields/${collection}/${field}`);
const fetchedFieldMeta = response.data?.data?.meta;
this.$patch({
field: {
meta: {
@@ -169,6 +170,7 @@ export const useFieldDetailStore = defineStore({
for (const relation of Object.values(this.relations)) {
if (!relation || !relation.collection || !relation.field) continue;
if (
// Duplicate checks for O2M & M2O
(relation.collection === relation.related_collection && relation.field === relation.meta?.one_field) ||

View File

@@ -196,6 +196,7 @@ export default defineComponent({
'interface',
computed(() => props.field.meta?.interface ?? null)
);
const interfaceName = computed(() => inter.value?.name ?? null);
const hidden = computed(() => props.field.meta?.hidden === true);
@@ -259,11 +260,13 @@ export default defineComponent({
const duplicateActive = ref(false);
const duplicateName = ref(props.field.field + '_copy');
const duplicating = ref(false);
const collections = computed(() =>
collectionsStore.collections
.map(({ collection }) => collection)
.filter((collection) => collection.startsWith('directus_') === false)
);
const duplicateTo = ref(props.field.collection);
return {

View File

@@ -69,6 +69,7 @@ const arrows = computed(() => {
if (props.arrowInfo?.id === panel.id && props.arrowInfo?.type === 'resolve') {
const { x, y } = getPoints(panel, RESOLVE_OFFSET);
arrows.push({
id: panel.id + '_resolve',
d: createLine(x, y, props.arrowInfo.pos.x, props.arrowInfo.pos.y),
@@ -77,6 +78,7 @@ const arrows = computed(() => {
});
} else if (resolveChild) {
const { x, y, toX, toY } = getPoints(panel, RESOLVE_OFFSET, resolveChild);
arrows.push({
id: panel.id + '_resolve',
d: createLine(x, y, toX as number, toY as number),
@@ -85,6 +87,7 @@ const arrows = computed(() => {
});
} else if (props.editMode && !props.arrowInfo && (panel.id === '$trigger' || props.hoveredPanel === panel.id)) {
const { x: resolveX, y: resolveY } = getPoints(panel, RESOLVE_OFFSET);
arrows.push({
id: panel.id + '_resolve',
d: createLine(resolveX, resolveY, resolveX + 3 * 20, resolveY),
@@ -96,6 +99,7 @@ const arrows = computed(() => {
if (props.arrowInfo?.id === panel.id && props.arrowInfo?.type === 'reject') {
const { x, y } = getPoints(panel, REJECT_OFFSET);
arrows.push({
id: panel.id + '_reject',
d: createLine(x, y, props.arrowInfo.pos.x, props.arrowInfo.pos.y),
@@ -104,6 +108,7 @@ const arrows = computed(() => {
});
} else if (rejectChild) {
const { x, y, toX, toY } = getPoints(panel, REJECT_OFFSET, rejectChild);
arrows.push({
id: panel.id + '_reject',
d: createLine(x, y, toX as number, toY as number),
@@ -112,6 +117,7 @@ const arrows = computed(() => {
});
} else if (props.editMode && !props.arrowInfo && panel.id !== '$trigger' && props.hoveredPanel === panel.id) {
const { x: rejectX, y: rejectY } = getPoints(panel, REJECT_OFFSET);
arrows.push({
id: panel.id + '_reject',
d: createLine(rejectX, rejectY, rejectX + 3 * 20, rejectY),
@@ -182,6 +188,7 @@ const arrows = computed(() => {
}
const arrowSize = 8;
const arrow = `M ${points.at(-1)} L ${points
.at(-1)
?.clone()

View File

@@ -183,6 +183,7 @@ const operationOptions = computed(() => {
function saveOperation() {
saving.value = true;
emit('save', {
flow: props.primaryKey,
name: operationName.value || generatedName.value,

View File

@@ -236,6 +236,7 @@ const pointermove = (event: PointerEvent) => {
rafId = window.requestAnimationFrame(() => {
moving.value = true;
if (!down) return;
const arrowInfo: ArrowInfo =
down === 'parent'
? {

View File

@@ -61,6 +61,7 @@ function saveTrigger() {
...(props.flow ?? {}),
...flowEdits.value,
});
emit('update:open', false);
}

View File

@@ -230,6 +230,7 @@ const flowsStore = useFlowsStore();
const stagedFlow = ref<Partial<FlowRaw>>({});
const fetchedFlow = ref<FlowRaw>();
const flow = computed<FlowRaw | undefined>({
get() {
if (!fetchedFlow.value) return undefined;
@@ -261,6 +262,7 @@ async function loadCurrentFlow() {
fields: ['*', 'operations.*'],
},
});
fetchedFlow.value = response.data.data;
} catch (err: any) {
unexpectedError(err);
@@ -688,6 +690,7 @@ function getNearAttachment(pos: Vector2) {
(panel.x - 1) * 20 + ATTACHMENT_OFFSET.x,
(panel.y - 1) * 20 + ATTACHMENT_OFFSET.y
);
if (attachmentPos.distanceTo(pos) <= 40) return panel.id as string;
}

View File

@@ -245,6 +245,7 @@ async function toggleFlowStatusById(id: string, value: string) {
await api.patch(`/flows/${id}`, {
status: value === 'active' ? 'inactive' : 'active',
});
await flowsStore.hydrate();
} catch (error) {
unexpectedError(error as Error);

View File

@@ -280,6 +280,7 @@ export default defineComponent({
const updatePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'update' && permission.collection === collection.value
);
return !!updatePermissions;
});
@@ -290,6 +291,7 @@ export default defineComponent({
const deletePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'delete' && permission.collection === collection.value
);
return !!deletePermissions;
});
@@ -300,6 +302,7 @@ export default defineComponent({
const createPermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'create' && permission.collection === collection.value
);
return !!createPermissions;
});

View File

@@ -305,6 +305,7 @@ function useValues() {
layout_options: null,
filter: null,
};
if (isNew.value === true) return defaultValues;
if (preset.value === null) return defaultValues;

View File

@@ -193,6 +193,7 @@ export default defineComponent({
const usersCreatePermission = permissionsStore.permissions.find(
(permission) => permission.collection === 'directus_users' && permission.action === 'create'
);
const rolesReadPermission = permissionsStore.permissions.find(
(permission) => permission.collection === 'directus_roles' && permission.action === 'read'
);

View File

@@ -195,6 +195,7 @@ export default defineComponent({
deep: { users: { _limit: 0 } },
},
});
role.value = response.data.data;
}

View File

@@ -34,6 +34,7 @@ export default function useNavigation(): { roles: Ref<BasicRole[] | null>; loadi
fields: ['id', 'name', 'icon', 'admin_access'],
},
});
roles.value = rolesResponse.data.data;
} catch (error: any) {
unexpectedError(error);

View File

@@ -244,6 +244,7 @@ const canInviteUsers = computed(() => {
const usersCreatePermission = permissionsStore.permissions.find(
(permission) => permission.collection === 'directus_users' && permission.action === 'create'
);
const rolesReadPermission = permissionsStore.permissions.find(
(permission) => permission.collection === 'directus_roles' && permission.action === 'read'
);
@@ -258,6 +259,7 @@ const { batchEditAllowed, batchDeleteAllowed, createAllowed } = usePermissions()
onBeforeRouteLeave(() => {
selection.value = [];
});
onBeforeRouteUpdate(() => {
selection.value = [];
});
@@ -340,6 +342,7 @@ function usePermissions() {
const updatePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'update' && permission.collection === 'directus_users'
);
return !!updatePermissions;
});
@@ -350,6 +353,7 @@ function usePermissions() {
const deletePermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'delete' && permission.collection === 'directus_users'
);
return !!deletePermissions;
});
@@ -360,6 +364,7 @@ function usePermissions() {
const createPermissions = permissionsStore.permissions.find(
(permission) => permission.action === 'create' && permission.collection === 'directus_users'
);
return !!createPermissions;
});

View File

@@ -14,11 +14,13 @@ export default defineOperationApp({
],
options: (panel) => {
const flowStore = useFlowsStore();
const flowChoices = flowStore.flows
.filter((flow) => flow.trigger === 'operation')
.map((flow) => {
return { text: flow.name, value: flow.id };
});
return [
{
field: 'flow',

View File

@@ -60,6 +60,7 @@ const props = withDefaults(defineProps<Props>(), {});
/*const emit = */ defineEmits(['input']);
const insightsStore = useInsightsStore();
const value = computed({
get() {
const val = insightsStore.getVariable(props.field);
@@ -76,6 +77,7 @@ const selectModalOpen = ref(false);
function onSelection(data: (number | string)[]) {
selectModalOpen.value = false;
if (!Array.isArray(data) || data.length === 0) {
value.value = [];
return;

View File

@@ -15,6 +15,7 @@ export default function useDisplayItems(collection: Ref<string>, template: Ref<s
const displayItems = ref([]);
const primaryKey = computed(() => fieldStore.getPrimaryKeyFieldForCollection(collection.value)?.field ?? '');
const displayTemplate = computed(() => {
if (template.value) return template.value;
@@ -22,6 +23,7 @@ export default function useDisplayItems(collection: Ref<string>, template: Ref<s
return displayTemplate || `{{ ${primaryKey.value || 'id'} }}`;
});
const requiredFields = computed(() => {
if (!displayTemplate.value || !collection.value) return [];
return adjustFieldsForDisplays(getFieldsFromTemplate(displayTemplate.value), collection.value);

View File

@@ -53,6 +53,7 @@ export default definePanel({
function getParsedOptionsFilter(filter: string | undefined) {
if (!filter) return {};
try {
return JSON.parse(filter);
} catch {

View File

@@ -92,6 +92,7 @@ export const onBeforeEach: NavigationGuard = async (to) => {
// First load
if (firstLoad) {
firstLoad = false;
// Try retrieving a fresh access token on first load
try {
await refresh({ navigate: false });
@@ -111,8 +112,10 @@ export const onBeforeEach: NavigationGuard = async (to) => {
if (to.meta?.public !== true) {
if (appStore.hydrated === false) {
appStore.hydrating = false;
if (appStore.authenticated === true) {
await hydrate();
if (
userStore.currentUser &&
to.fullPath === '/tfa-setup' &&

View File

@@ -94,6 +94,7 @@ export default defineComponent({
async function enable() {
await enableTFA();
if (error.value === null) {
const redirectQuery = router.currentRoute.value.query.redirect as string;
router.push(redirectQuery || (userStore.currentUser as User)?.last_page || '/login');

View File

@@ -35,6 +35,7 @@ test('parseField action should translate field name when translations are added
],
},
});
collectionsStore.collections = [mockCollectionWithTranslations].map(collectionsStore.prepareCollectionForApp);
expect(collectionsStore.collections[0].name).toEqual('Collection A en-US');
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(true);
@@ -49,6 +50,7 @@ test('parseField action should translate field name when translations are added
],
},
});
collectionsStore.collections = [mockCollectionWithMissingTranslations].map(collectionsStore.prepareCollectionForApp);
expect(collectionsStore.collections[0].name).toEqual('A');
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(false);
@@ -67,6 +69,7 @@ test('parseField action should translate field name when all translations are re
],
},
});
collectionsStore.collections = [mockCollectionWithTranslations].map(collectionsStore.prepareCollectionForApp);
expect(collectionsStore.collections[0].name).toEqual('Collection A en-US');
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(true);
@@ -76,6 +79,7 @@ test('parseField action should translate field name when all translations are re
translations: null,
},
});
collectionsStore.collections = [mockCollectionWithoutTranslations].map(collectionsStore.prepareCollectionForApp);
expect(collectionsStore.collections[0].name).toEqual('A');
expect(i18n.global.te(`collection_names.${mockCollection.collection}`)).toBe(false);

Some files were not shown because too many files have changed in this diff Show More