mirror of
https://github.com/directus/directus.git
synced 2026-04-03 03:00:39 -04:00
Merge branch 'main' into file-replace
This commit is contained in:
@@ -119,6 +119,10 @@ router.get(
|
||||
res.attachment(file.filename_download);
|
||||
res.setHeader('Content-Type', file.type);
|
||||
|
||||
if (req.query.hasOwnProperty('download') === false) {
|
||||
res.removeHeader('Content-Disposition');
|
||||
}
|
||||
|
||||
stream.pipe(res);
|
||||
})
|
||||
);
|
||||
|
||||
@@ -49,7 +49,7 @@ router.get(
|
||||
router.get(
|
||||
'/me',
|
||||
asyncHandler(async (req, res, next) => {
|
||||
if (!req.accountability?.user || !req.accountability?.role) {
|
||||
if (!req.accountability?.user) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
|
||||
@@ -58,11 +58,21 @@ router.get(
|
||||
if (!req.accountability?.user) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
const service = new UsersService({ accountability: req.accountability });
|
||||
|
||||
const item = await service.readByKey(req.accountability.user, req.sanitizedQuery);
|
||||
try {
|
||||
const item = await service.readByKey(req.accountability.user, req.sanitizedQuery);
|
||||
res.locals.payload = { data: item || null };
|
||||
} catch (error) {
|
||||
if (error instanceof ForbiddenException) {
|
||||
res.locals.payload = { data: { id: req.accountability.user } };
|
||||
return next();
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
res.locals.payload = { data: item || null };
|
||||
return next();
|
||||
}),
|
||||
respond
|
||||
|
||||
@@ -31,7 +31,7 @@ data:
|
||||
cards:
|
||||
icon: account_circle
|
||||
title: '{{ first_name }} {{ last_name }}'
|
||||
subtitle: '{{ title }}'
|
||||
subtitle: '{{ email }}'
|
||||
size: 4
|
||||
|
||||
- collection: directus_activity
|
||||
|
||||
@@ -101,7 +101,7 @@ fields:
|
||||
display: user
|
||||
- collection: directus_files
|
||||
field: modified_on
|
||||
interface: dateTime
|
||||
interface: datetime
|
||||
locked: true
|
||||
special: date-updated
|
||||
width: half
|
||||
@@ -111,4 +111,4 @@ fields:
|
||||
display: datetime
|
||||
- collection: directus_files
|
||||
field: created_by
|
||||
display: user
|
||||
display: user
|
||||
|
||||
@@ -7,7 +7,10 @@ export function parseFilter(filter: Filter, accountability: Accountability | nul
|
||||
if (val === 'true') return true;
|
||||
if (val === 'false') return false;
|
||||
|
||||
if (key === '_in' || key === '_nin') return toArray(val);
|
||||
if (key === '_in' || key === '_nin') {
|
||||
if (typeof val === 'string' && val.includes(',')) return val.split(',');
|
||||
else return toArray(val);
|
||||
}
|
||||
|
||||
if (val === '$NOW') return new Date();
|
||||
if (val === '$CURRENT_USER') return accountability?.user || null;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Accountability, Query, Sort, Filter, Meta } from '../types';
|
||||
import logger from '../logger';
|
||||
import { parseFilter } from '../utils/parse-filter';
|
||||
import { flatten } from 'lodash';
|
||||
|
||||
export function sanitizeQuery(
|
||||
rawQuery: Record<string, any>,
|
||||
@@ -75,6 +76,9 @@ function sanitizeFields(rawFields: any) {
|
||||
if (typeof rawFields === 'string') fields = rawFields.split(',');
|
||||
else if (Array.isArray(rawFields)) fields = rawFields as string[];
|
||||
|
||||
// Case where array item includes CSV (fe fields[]=id,name):
|
||||
fields = flatten(fields.map((field) => (field.includes(',') ? field.split(',') : field)));
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
|
||||
162
app/package-lock.json
generated
162
app/package-lock.json
generated
@@ -6601,51 +6601,6 @@
|
||||
"tslint": "^5.20.1",
|
||||
"webpack": "^4.0.0",
|
||||
"yorkie": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"fork-ts-checker-webpack-plugin-v5": {
|
||||
"version": "npm:fork-ts-checker-webpack-plugin@5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.0.tgz",
|
||||
"integrity": "sha512-NEKcI0+osT5bBFZ1SFGzJMQETjQWZrSvMO1g0nAR/w0t328Z41eN8BJEIZyFCl2HsuiJpa9AN474Nh2qLVwGLQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.8.3",
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"chalk": "^4.1.0",
|
||||
"cosmiconfig": "^6.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"fs-extra": "^9.0.0",
|
||||
"memfs": "^3.1.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"schema-utils": "2.7.0",
|
||||
"semver": "^7.3.2",
|
||||
"tapable": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
|
||||
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.4",
|
||||
"ajv": "^6.12.2",
|
||||
"ajv-keywords": "^3.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@vue/cli-plugin-unit-jest": {
|
||||
@@ -6785,17 +6740,6 @@
|
||||
"unique-filename": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
@@ -6879,18 +6823,6 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
@@ -7004,18 +6936,6 @@
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-oouKUQWWHbSihqSD7mhymGPX1OQ4hedzAHyvm8RdyHh6m3oIvoRF+NM45i/bhNOlo8jCnuJhaSUf/6oDjv978g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
@@ -11744,6 +11664,51 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"fork-ts-checker-webpack-plugin-v5": {
|
||||
"version": "npm:fork-ts-checker-webpack-plugin@5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.0.tgz",
|
||||
"integrity": "sha512-NEKcI0+osT5bBFZ1SFGzJMQETjQWZrSvMO1g0nAR/w0t328Z41eN8BJEIZyFCl2HsuiJpa9AN474Nh2qLVwGLQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.8.3",
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"chalk": "^4.1.0",
|
||||
"cosmiconfig": "^6.0.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"fs-extra": "^9.0.0",
|
||||
"memfs": "^3.1.2",
|
||||
"minimatch": "^3.0.4",
|
||||
"schema-utils": "2.7.0",
|
||||
"semver": "^7.3.2",
|
||||
"tapable": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
|
||||
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.4",
|
||||
"ajv": "^6.12.2",
|
||||
"ajv-keywords": "^3.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"form-data": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
|
||||
@@ -20377,6 +20342,43 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-loader-v16": {
|
||||
"version": "npm:vue-loader@16.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-oouKUQWWHbSihqSD7mhymGPX1OQ4hedzAHyvm8RdyHh6m3oIvoRF+NM45i/bhNOlo8jCnuJhaSUf/6oDjv978g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"chalk": "^4.1.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"vue-router": {
|
||||
"version": "3.4.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.6.tgz",
|
||||
|
||||
@@ -14,15 +14,7 @@
|
||||
</template>
|
||||
</v-info>
|
||||
|
||||
<router-view v-else-if="!hydrating && appAccess" />
|
||||
|
||||
<v-info v-else-if="appAccess === false" center :title="$t('no_app_access')" type="danger" icon="block">
|
||||
{{ $t('no_app_access_copy') }}
|
||||
|
||||
<template #append>
|
||||
<v-button to="/logout">Switch User</v-button>
|
||||
</template>
|
||||
</v-info>
|
||||
<router-view v-else-if="!hydrating" />
|
||||
|
||||
<portal-target name="dialog-outlet" transition="transition-dialog" multiple />
|
||||
<portal-target name="menu-outlet" transition="transition-bounce" multiple />
|
||||
@@ -88,7 +80,7 @@ export default defineComponent({
|
||||
document.body.classList.remove('light');
|
||||
document.body.classList.remove('auto');
|
||||
|
||||
if (newUser !== undefined && newUser !== null) {
|
||||
if (newUser !== undefined && newUser !== null && newUser.theme) {
|
||||
document.body.classList.add(newUser.theme);
|
||||
} else {
|
||||
// Default to light mode
|
||||
@@ -108,11 +100,6 @@ export default defineComponent({
|
||||
return settingsStore.state?.settings?.custom_css || '';
|
||||
});
|
||||
|
||||
const appAccess = computed(() => {
|
||||
if (!userStore.state.currentUser) return true;
|
||||
return userStore.state.currentUser?.role?.app_access || false;
|
||||
});
|
||||
|
||||
const error = computed(() => appStore.state.error);
|
||||
|
||||
/**
|
||||
@@ -124,7 +111,7 @@ export default defineComponent({
|
||||
axios,
|
||||
});
|
||||
|
||||
return { hydrating, brandStyle, appAccess, error, customCSS };
|
||||
return { hydrating, brandStyle, error, customCSS };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
<template>
|
||||
<v-list>
|
||||
<v-list-item :disabled="value === null" @click="$emit('input', null)">
|
||||
<v-list-item
|
||||
v-if="defaultValue === null || !isRequired"
|
||||
:disabled="value === null"
|
||||
@click="$emit('input', null)"
|
||||
>
|
||||
<v-list-item-icon><v-icon name="delete_outline" /></v-list-item-icon>
|
||||
<v-list-item-content>{{ $t('clear_value') }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item @click="$emit('input', defaultValue)">
|
||||
<v-list-item
|
||||
v-if="defaultValue !== null"
|
||||
:disabled="value === defaultValue"
|
||||
@click="$emit('input', defaultValue)"
|
||||
>
|
||||
<v-list-item-icon>
|
||||
<v-icon name="settings_backup_restore" />
|
||||
</v-list-item-icon>
|
||||
@@ -52,7 +60,11 @@ export default defineComponent({
|
||||
return savedValue !== undefined ? savedValue : null;
|
||||
});
|
||||
|
||||
return { defaultValue };
|
||||
const isRequired = computed(() => {
|
||||
return props.field?.schema?.is_nullable === false;
|
||||
});
|
||||
|
||||
return { defaultValue, isRequired };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -53,5 +53,5 @@ export default defineDisplay(({ i18n }) => ({
|
||||
},
|
||||
},
|
||||
],
|
||||
fields: ['id', 'avatar.id', 'first_name', 'last_name'],
|
||||
fields: ['id', 'avatar.id', 'email', 'first_name', 'last_name'],
|
||||
}));
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
v-if="(display === 'avatar' || display === 'both') && src"
|
||||
:src="src"
|
||||
role="presentation"
|
||||
:alt="value && `${value.first_name} ${value.last_name}`"
|
||||
:alt="value && userName(value)"
|
||||
:class="{ circle }"
|
||||
/>
|
||||
<img
|
||||
v-else-if="(display === 'avatar' || display === 'both') && src === null"
|
||||
src="../../assets/avatar-placeholder.svg"
|
||||
role="presentation"
|
||||
:alt="value && `${value.first_name} ${value.last_name}`"
|
||||
:alt="value && userName(value)"
|
||||
:class="{ circle }"
|
||||
/>
|
||||
<span v-if="display === 'name' || display === 'both'">{{ value.first_name }} {{ value.last_name }}</span>
|
||||
<span v-if="display === 'name' || display === 'both'">{{ userName(value) }}</span>
|
||||
</div>
|
||||
</user-popover>
|
||||
</template>
|
||||
@@ -23,12 +23,14 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed } from '@vue/composition-api';
|
||||
import { getRootPath } from '@/utils/get-root-path';
|
||||
import { userName } from '@/utils/user-name';
|
||||
|
||||
type User = {
|
||||
id: number;
|
||||
avatar: {
|
||||
id: string;
|
||||
};
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
};
|
||||
@@ -58,7 +60,7 @@ export default defineComponent({
|
||||
return null;
|
||||
});
|
||||
|
||||
return { src };
|
||||
return { src, userName };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -57,11 +57,11 @@ export async function hydrate(stores = useStores()) {
|
||||
*/
|
||||
await userStore.hydrate();
|
||||
|
||||
await setLanguage((userStore.state.currentUser?.language as Language) || 'en-US');
|
||||
|
||||
await Promise.all(stores.filter(({ id }) => id !== 'userStore').map((store) => store.hydrate?.()));
|
||||
|
||||
await registerModules();
|
||||
if (userStore.state.currentUser?.role) {
|
||||
await setLanguage((userStore.state.currentUser?.language as Language) || 'en-US');
|
||||
await Promise.all(stores.filter(({ id }) => id !== 'userStore').map((store) => store.hydrate?.()));
|
||||
await registerModules();
|
||||
}
|
||||
} catch (error) {
|
||||
appStore.state.error = error;
|
||||
} finally {
|
||||
|
||||
@@ -12,11 +12,7 @@
|
||||
>
|
||||
<template #input v-if="currentUser">
|
||||
<div class="preview">
|
||||
<render-template
|
||||
collection="directus_users"
|
||||
:item="currentUser"
|
||||
:template="displayTemplate"
|
||||
/>
|
||||
{{ userName(currentUser) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -65,7 +61,7 @@
|
||||
@click="setCurrent(item)"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<render-template collection="directus_users" :template="displayTemplate" :item="item" />
|
||||
{{ userName(currentUser) }}
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</template>
|
||||
@@ -105,10 +101,6 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
template: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
selectMode: {
|
||||
type: String as PropType<'auto' | 'dropdown' | 'modal'>,
|
||||
default: 'auto',
|
||||
@@ -122,7 +114,7 @@ export default defineComponent({
|
||||
const { usesMenu, menuActive } = useMenu();
|
||||
const { info: collectionInfo } = useCollection(ref('directus_users'));
|
||||
const { selection, stageSelection, selectModalActive } = useSelection();
|
||||
const { displayTemplate, onPreviewClick, requiredFields } = usePreview();
|
||||
const { onPreviewClick } = usePreview();
|
||||
const { totalCount, loading: usersLoading, fetchUsers, users } = useUsers();
|
||||
|
||||
const { setCurrent, currentUser, loading: loadingCurrent, currentPrimaryKey } = useCurrent();
|
||||
@@ -134,7 +126,6 @@ export default defineComponent({
|
||||
return {
|
||||
collectionInfo,
|
||||
currentUser,
|
||||
displayTemplate,
|
||||
users,
|
||||
usersLoading,
|
||||
loadingCurrent,
|
||||
@@ -192,16 +183,10 @@ export default defineComponent({
|
||||
async function fetchCurrent() {
|
||||
loading.value = true;
|
||||
|
||||
const fields = requiredFields;
|
||||
|
||||
if (fields.includes('id') === false) {
|
||||
fields.push('id');
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.get(`/users/${props.value}`, {
|
||||
params: {
|
||||
fields: fields,
|
||||
fields: ['id', 'email', 'first_name', 'last_name'],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -231,11 +216,7 @@ export default defineComponent({
|
||||
|
||||
loading.value = true;
|
||||
|
||||
const fields = requiredFields;
|
||||
|
||||
if (fields.includes('id') === false) {
|
||||
fields.push('id');
|
||||
}
|
||||
const fields = ['id', 'email', 'first_name', 'last_name'];
|
||||
|
||||
try {
|
||||
const response = await api.get(`/users`, {
|
||||
@@ -280,10 +261,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function usePreview() {
|
||||
const displayTemplate = '{{ first_name }} {{ last_name }}';
|
||||
const requiredFields = ['first_name', 'last_name'];
|
||||
|
||||
return { onPreviewClick, displayTemplate, requiredFields };
|
||||
return { onPreviewClick };
|
||||
|
||||
function onPreviewClick() {
|
||||
if (props.disabled) return;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
/* stylelint-disable font-family-no-missing-generic-family-keyword */
|
||||
|
||||
.tox {
|
||||
font-family: var(--family-sans-serif);
|
||||
}
|
||||
|
||||
.tox .tox-tbtn {
|
||||
margin: 2px 2px 4px 0;
|
||||
color: var(--foreground-normal);
|
||||
@@ -138,8 +142,9 @@ body.dark .tox .tox-toolbar__overflow {
|
||||
}
|
||||
|
||||
.tox .tox-dialog__header {
|
||||
padding: 16px 24px 0 24px;
|
||||
padding: 20px;
|
||||
color: var(--foreground-normal);
|
||||
font-size: 16px;
|
||||
background-color: var(--background-page);
|
||||
}
|
||||
|
||||
@@ -156,18 +161,61 @@ body.dark .tox .tox-toolbar__overflow {
|
||||
}
|
||||
|
||||
.tox .tox-textfield,
|
||||
.tox .tox-listboxfield .tox-listbox,
|
||||
.tox .tox-toolbar-textfield,
|
||||
.tox .tox-selectfield select,
|
||||
.tox .tox-textarea {
|
||||
padding: 12px;
|
||||
color: var(--foreground-normal);
|
||||
font-family: monospace;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
font-family: var(--family-sans-serif);
|
||||
background-color: var(--background-page);
|
||||
border: 2px solid var(--border-normal);
|
||||
border-radius: var(--border-radius);
|
||||
transition: var(--fast) var(--transition);
|
||||
}
|
||||
|
||||
.tox .tox-textarea {
|
||||
font-family: var(--family-monospace);
|
||||
}
|
||||
|
||||
.tox .tox-textfield:focus,
|
||||
.tox .tox-listboxfield .tox-listbox:focus,
|
||||
.tox .tox-toolbar-textfield:focus,
|
||||
.tox .tox-selectfield select:focus,
|
||||
.tox .tox-textarea:focus {
|
||||
background-color: var(--background-page);
|
||||
}
|
||||
|
||||
.tox .tox-menu {
|
||||
box-sizing: border-box;
|
||||
padding: 4px !important;
|
||||
color: var(--foreground-normal);
|
||||
font-family: var(--family-sans-serif);
|
||||
background-color: var(--background-subdued);
|
||||
border: 2px solid var(--border-normal);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.tox .tox-collection__item {
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.tox .tox-collection--list .tox-collection__item {
|
||||
color: var(--foreground-normal);
|
||||
}
|
||||
|
||||
.tox .tox-collection--list .tox-collection__item--active {
|
||||
color: var(--foreground-normal) !important;
|
||||
background-color: var(--background-page) !important;
|
||||
}
|
||||
|
||||
.tox .tox-collection--list .tox-collection__item--enabled {
|
||||
color: var(--foreground-normal);
|
||||
background-color: var(--background-page);
|
||||
}
|
||||
|
||||
.tox .tox-textfield:focus,
|
||||
.tox .tox-selectfield select:focus,
|
||||
.tox .tox-textarea:focus {
|
||||
@@ -184,6 +232,7 @@ body.dark .tox .tox-toolbar__overflow {
|
||||
color: var(--white);
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
font-family: var(--family-sans-serif);
|
||||
line-height: 19px;
|
||||
background-color: var(--primary);
|
||||
border: 2px solid var(--primary);
|
||||
@@ -228,12 +277,45 @@ body.dark .tox .tox-toolbar__overflow {
|
||||
}
|
||||
|
||||
.tox .tox-form__group {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.tox .tox-label,
|
||||
.tox .tox-toolbar-label {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 4px;
|
||||
color: var(--foreground-normal);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tox .tox-dialog__body-nav-item {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
margin-bottom: 4px;
|
||||
padding: 12px 16px;
|
||||
color: var(--foreground-normal);
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
border-bottom: none;
|
||||
border-radius: var(--border-radius);
|
||||
transition: var(--fast) var(--transition);
|
||||
transition-property: background-color, color;
|
||||
}
|
||||
|
||||
.tox .tox-dialog__body-nav-item:hover {
|
||||
background-color: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
.tox .tox-dialog__body-nav-item--active {
|
||||
background-color: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
.tox .tox-dialog__body-nav-item--active:focus {
|
||||
background-color: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
.tox .tox-dialog__body-nav-item {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@@ -902,6 +902,8 @@
|
||||
"statuses_not_configured": "Status mapping configured incorrectly",
|
||||
"status_mapping": "Status Mapping",
|
||||
|
||||
"unknown_user": "Unknown User",
|
||||
|
||||
"creating_in": "Creating Item in {collection}",
|
||||
"editing_in": "Editing Item in {collection}",
|
||||
"editing_in_batch": "Batch Editing {count} Items",
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
<v-list-item to="/activity?action=comment" exact>
|
||||
<v-list-item-icon>
|
||||
<v-icon name="notes" />
|
||||
<v-icon name="chat_bubble_outline" />
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
{{ $t('comment') }}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<!-- @TODO add final design -->
|
||||
<p class="type-label">User:</p>
|
||||
<user-popover v-if="item.user" :user="item.user.id">
|
||||
{{ item.user.first_name }} {{ item.user.last_name }}
|
||||
{{ userName(item.user) }}
|
||||
</user-popover>
|
||||
|
||||
<p class="type-label">Action:</p>
|
||||
@@ -51,6 +51,7 @@ import { defineComponent, computed, toRefs, ref, watch } from '@vue/composition-
|
||||
import { i18n } from '@/lang';
|
||||
import router from '@/router';
|
||||
import api from '@/api';
|
||||
import { userName } from '@/utils/user-name';
|
||||
|
||||
type Values = {
|
||||
[field: string]: any;
|
||||
@@ -58,6 +59,7 @@ type Values = {
|
||||
|
||||
type ActivityRecord = {
|
||||
user: {
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
} | null;
|
||||
@@ -97,6 +99,7 @@ export default defineComponent({
|
||||
error,
|
||||
close,
|
||||
openItemLink,
|
||||
userName,
|
||||
};
|
||||
|
||||
async function loadActivity() {
|
||||
@@ -107,6 +110,7 @@ export default defineComponent({
|
||||
params: {
|
||||
fields: [
|
||||
'user.id',
|
||||
'user.email',
|
||||
'user.first_name',
|
||||
'user.last_name',
|
||||
'action',
|
||||
|
||||
@@ -123,6 +123,7 @@ import marked from 'marked';
|
||||
import localizedFormat from '@/utils/localized-format';
|
||||
import api from '@/api';
|
||||
import getRootPath from '@/utils/get-root-path';
|
||||
import { userName } from '@/utils/user-name';
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
@@ -213,31 +214,31 @@ export default defineComponent({
|
||||
try {
|
||||
const response = await api.get(`/users/${props.file.uploaded_by}`, {
|
||||
params: {
|
||||
fields: ['id', 'first_name', 'last_name', 'role'],
|
||||
fields: ['id', 'email', 'first_name', 'last_name', 'role'],
|
||||
},
|
||||
});
|
||||
|
||||
const { id, first_name, last_name, role } = response.data.data;
|
||||
const user = response.data.data;
|
||||
|
||||
userCreated.value = {
|
||||
id: props.file.uploaded_by,
|
||||
name: first_name + ' ' + last_name,
|
||||
link: `/users/${id}`,
|
||||
name: userName(user),
|
||||
link: `/users/${user.id}`,
|
||||
};
|
||||
|
||||
if (props.file.modified_by) {
|
||||
const response = await api.get(`/users/${props.file.modified_by}`, {
|
||||
params: {
|
||||
fields: ['id', 'first_name', 'last_name', 'role'],
|
||||
fields: ['id', 'email', 'first_name', 'last_name', 'role'],
|
||||
},
|
||||
});
|
||||
|
||||
const { id, first_name, last_name, role } = response.data.data;
|
||||
const user = response.data.data;
|
||||
|
||||
userModified.value = {
|
||||
id: props.file.modified_by,
|
||||
name: first_name + ' ' + last_name,
|
||||
link: `/users/${id}`,
|
||||
name: userName(user),
|
||||
link: `/users/${user.id}`,
|
||||
};
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item to="/files/mine" exact>
|
||||
<v-list-item-icon><v-icon name="face" /></v-list-item-icon>
|
||||
<v-list-item-icon><v-icon name="folder_shared" /></v-list-item-icon>
|
||||
<v-list-item-content>{{ $t('my_files') }}</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
|
||||
@@ -393,7 +393,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function downloadFile() {
|
||||
const filePath = getRootPath() + `assets/${props.primaryKey}?download=true`;
|
||||
const filePath = getRootPath() + `assets/${props.primaryKey}?download`;
|
||||
window.open(filePath, '_blank');
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,15 @@
|
||||
|
||||
<template #input>
|
||||
<div class="label">
|
||||
<span class="name" v-tooltip="field.name">{{ field.field }}</span>
|
||||
<span class="name" v-tooltip="field.name">
|
||||
{{ field.field }}
|
||||
<v-icon
|
||||
name="star"
|
||||
class="required"
|
||||
sup
|
||||
v-if="field.schema && field.schema.is_nullable === false"
|
||||
/>
|
||||
</span>
|
||||
<span v-if="field.meta" class="interface">{{ interfaceName }}</span>
|
||||
<span v-else class="interface">{{ $t('db_only_click_to_configure') }}</span>
|
||||
</div>
|
||||
@@ -522,4 +530,10 @@ export default defineComponent({
|
||||
--v-button-background-color: var(--danger);
|
||||
--v-button-background-color-hover: var(--danger-125);
|
||||
}
|
||||
|
||||
.required {
|
||||
position: relative;
|
||||
left: -8px;
|
||||
color: var(--primary);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -373,7 +373,6 @@ export default defineComponent({
|
||||
special: ['user-created'],
|
||||
interface: 'user',
|
||||
options: {
|
||||
template: '{{first_name}} {{last_name}}',
|
||||
display: 'both',
|
||||
},
|
||||
readonly: true,
|
||||
@@ -407,7 +406,6 @@ export default defineComponent({
|
||||
special: ['user-updated'],
|
||||
interface: 'user',
|
||||
options: {
|
||||
template: '{{first_name}} {{last_name}}',
|
||||
display: 'both',
|
||||
},
|
||||
readonly: true,
|
||||
|
||||
@@ -103,11 +103,12 @@ import { TranslateResult } from 'vue-i18n';
|
||||
import router from '@/router';
|
||||
import ValueNull from '@/views/private/components/value-null';
|
||||
import PresetsInfoSidebarDetail from './components/presets-info-sidebar-detail.vue';
|
||||
import { userName } from '@/utils/user-name';
|
||||
|
||||
type PresetRaw = {
|
||||
id: number;
|
||||
bookmark: null | string;
|
||||
user: null | { first_name: string; last_name: string };
|
||||
user: null | { email: string; first_name: string; last_name: string };
|
||||
role: null | { name: string };
|
||||
collection: string;
|
||||
layout: string;
|
||||
@@ -173,7 +174,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
if (preset.user) {
|
||||
scope = `${preset.user.first_name} ${preset.user.last_name}`;
|
||||
scope = userName(preset.user);
|
||||
}
|
||||
|
||||
const collection = collectionsStore.getCollection(preset.collection)?.name;
|
||||
@@ -200,6 +201,7 @@ export default defineComponent({
|
||||
fields: [
|
||||
'id',
|
||||
'bookmark',
|
||||
'user.email',
|
||||
'user.first_name',
|
||||
'user.last_name',
|
||||
'role.name',
|
||||
|
||||
@@ -102,6 +102,7 @@ import { useCollectionsStore, usePresetsStore } from '@/stores';
|
||||
import { getLayouts } from '@/layouts';
|
||||
import router from '@/router';
|
||||
import marked from 'marked';
|
||||
import { userName } from '@/utils/user-name';
|
||||
|
||||
type User = {
|
||||
id: number;
|
||||
@@ -365,12 +366,12 @@ export default defineComponent({
|
||||
try {
|
||||
const response = await api.get(`/users`, {
|
||||
params: {
|
||||
fields: ['first_name', 'last_name', 'id'],
|
||||
fields: ['email', 'first_name', 'last_name', 'id'],
|
||||
},
|
||||
});
|
||||
|
||||
users.value = response.data.data.map((user: any) => ({
|
||||
name: user.first_name + ' ' + user.last_name,
|
||||
name: userName(user),
|
||||
id: user.id,
|
||||
}));
|
||||
} catch (err) {
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
<div class="user-box" v-if="isNew === false">
|
||||
<div class="avatar">
|
||||
<v-skeleton-loader v-if="loading || previewLoading" />
|
||||
<img v-else-if="avatarSrc" :src="avatarSrc" :alt="item.first_name" />
|
||||
<img v-else-if="avatarSrc" :src="avatarSrc" :alt="item.email" />
|
||||
<v-icon v-else name="account_circle" outline x-large />
|
||||
</div>
|
||||
<div class="user-box-content">
|
||||
@@ -112,7 +112,7 @@
|
||||
<v-skeleton-loader type="text" />
|
||||
</template>
|
||||
<template v-else-if="isNew === false">
|
||||
<div class="name type-title">{{ item.first_name }} {{ item.last_name }}</div>
|
||||
<div class="name type-title">{{ userName(item) }}</div>
|
||||
<div class="email">{{ item.email }}</div>
|
||||
<v-chip :class="item.status" small>{{ roleName }}</v-chip>
|
||||
</template>
|
||||
@@ -179,6 +179,7 @@ import { getRootPath } from '@/utils/get-root-path';
|
||||
import useShortcut from '@/composables/use-shortcut';
|
||||
import { isAllowed } from '@/utils/is-allowed';
|
||||
import useCollection from '@/composables/use-collection';
|
||||
import { userName } from '@/utils/user-name';
|
||||
|
||||
type Values = {
|
||||
[field: string]: any;
|
||||
@@ -255,7 +256,7 @@ export default defineComponent({
|
||||
|
||||
if (isNew.value === false && item.value !== null) {
|
||||
const user = item.value as any;
|
||||
return `${user.first_name} ${user.last_name}`;
|
||||
return userName(user);
|
||||
}
|
||||
|
||||
return i18n.t('adding_user');
|
||||
@@ -334,6 +335,7 @@ export default defineComponent({
|
||||
archiving,
|
||||
archiveTooltip,
|
||||
form,
|
||||
userName,
|
||||
};
|
||||
|
||||
function useBreadcrumb() {
|
||||
|
||||
@@ -17,6 +17,7 @@ import { defineComponent, computed, watch, ref } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import { hydrate } from '@/hydrate';
|
||||
import router from '@/router';
|
||||
import { userName } from '@/utils/user-name';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
@@ -36,11 +37,11 @@ export default defineComponent({
|
||||
try {
|
||||
const response = await api.get(`/users/me`, {
|
||||
params: {
|
||||
fields: ['first_name', 'last_name', 'last_page'],
|
||||
fields: ['email', 'first_name', 'last_name', 'last_page'],
|
||||
},
|
||||
});
|
||||
|
||||
name.value = response.data.data.first_name + ' ' + response.data.data.last_name;
|
||||
name.value = userName(response.data.data);
|
||||
lastPage.value = response.data.data.last_page;
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
|
||||
@@ -3,6 +3,7 @@ import api from '@/api';
|
||||
import { useLatencyStore } from '@/stores';
|
||||
|
||||
import { User } from '@/types';
|
||||
import { userName } from '@/utils/user-name';
|
||||
|
||||
export const useUserStore = createStore({
|
||||
id: 'userStore',
|
||||
@@ -14,7 +15,7 @@ export const useUserStore = createStore({
|
||||
getters: {
|
||||
fullName(state) {
|
||||
if (state.currentUser === null) return null;
|
||||
return state.currentUser.first_name + ' ' + state.currentUser.last_name;
|
||||
return userName(state.currentUser);
|
||||
},
|
||||
isAdmin(state) {
|
||||
return state.currentUser?.role.admin_access === true || false;
|
||||
|
||||
18
app/src/utils/user-name.ts
Normal file
18
app/src/utils/user-name.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { User } from '@/types';
|
||||
import { i18n } from '@/lang';
|
||||
|
||||
export function userName(user: Partial<User>): string {
|
||||
if (user.first_name && user.last_name) {
|
||||
return `${user.first_name} ${user.last_name}`;
|
||||
}
|
||||
|
||||
if (user.first_name) {
|
||||
return user.first_name;
|
||||
}
|
||||
|
||||
if (user.email) {
|
||||
return user.email;
|
||||
}
|
||||
|
||||
return i18n.t('unknown_user') as string;
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
<template>
|
||||
<div class="comment-header">
|
||||
<v-avatar x-small>
|
||||
<img
|
||||
v-if="avatarSource"
|
||||
:src="avatarSource"
|
||||
:alt="activity.user.first_name + ' ' + activity.user.last_name"
|
||||
/>
|
||||
<img v-if="avatarSource" :src="avatarSource" :alt="userName(activity.user)" />
|
||||
<v-icon v-else name="person_outline" />
|
||||
</v-avatar>
|
||||
|
||||
@@ -13,7 +9,7 @@
|
||||
<user-popover v-if="activity.user && activity.user.id" :user="activity.user.id">
|
||||
<span>
|
||||
<template v-if="activity.user && activity.user">
|
||||
{{ activity.user.first_name }} {{ activity.user.last_name }}
|
||||
{{ userName(activity.user) }}
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
@@ -28,7 +24,7 @@
|
||||
<template #activator="{ toggle, active }">
|
||||
<v-icon class="more" :class="{ active }" name="more_horiz" @click="toggle" />
|
||||
<div class="time">
|
||||
<span class="dot" v-if="activity.revisions.length > 0" v-tooltip="editedOnFormatted" />
|
||||
<span class="dot" v-tooltip="editedOnFormatted" />
|
||||
{{ formattedTime }}
|
||||
</div>
|
||||
</template>
|
||||
@@ -70,6 +66,7 @@ import { Activity } from './types';
|
||||
import format from 'date-fns/format';
|
||||
import i18n from '@/lang';
|
||||
import getRootPath from '@/utils/get-root-path';
|
||||
import { userName } from '@/utils/user-name';
|
||||
|
||||
import api from '@/api';
|
||||
import localizedFormat from '@/utils/localized-format';
|
||||
@@ -117,7 +114,7 @@ export default defineComponent({
|
||||
|
||||
const { confirmDelete, deleting, remove } = useDelete();
|
||||
|
||||
return { formattedTime, avatarSource, confirmDelete, deleting, remove, editedOnFormatted };
|
||||
return { formattedTime, avatarSource, confirmDelete, deleting, remove, editedOnFormatted, userName };
|
||||
|
||||
function useDelete() {
|
||||
const confirmDelete = ref(false);
|
||||
|
||||
@@ -81,6 +81,7 @@ export default defineComponent({
|
||||
'action',
|
||||
'timestamp',
|
||||
'user.id',
|
||||
'user.email',
|
||||
'user.first_name',
|
||||
'user.last_name',
|
||||
'user.avatar.id',
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
import { Avatar } from '@/types';
|
||||
import { User } from '@/types';
|
||||
|
||||
export type Activity = {
|
||||
id: number;
|
||||
action: 'comment';
|
||||
user: null | {
|
||||
id: number;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
avatar: null | Avatar;
|
||||
};
|
||||
user: null | Partial<User>;
|
||||
timestamp: string;
|
||||
edited_on: null | string;
|
||||
comment: null | string;
|
||||
|
||||
@@ -52,6 +52,7 @@ import { debounce } from 'lodash';
|
||||
import { FieldTree } from './types';
|
||||
import FieldListItem from './field-list-item.vue';
|
||||
import { useCollection } from '@/composables/use-collection';
|
||||
import getAvailableOperatorsForType from './get-available-operators-for-type';
|
||||
|
||||
export default defineComponent({
|
||||
components: { FieldFilter, FieldListItem },
|
||||
@@ -189,12 +190,15 @@ export default defineComponent({
|
||||
return { fieldTree, addFilterForField, filters, removeFilter, updateFilter, showArchiveToggle, archived };
|
||||
|
||||
function addFilterForField(fieldKey: string) {
|
||||
const field = fieldsStore.getField(props.collection, fieldKey);
|
||||
const defaultOperator = getAvailableOperatorsForType(field.type).operators[0];
|
||||
|
||||
emit('input', [
|
||||
...props.value,
|
||||
{
|
||||
key: nanoid(),
|
||||
field: fieldKey,
|
||||
operator: 'contains',
|
||||
operator: defaultOperator || 'contains',
|
||||
value: '',
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function getAvailableOperatorsForType(type: string) {
|
||||
case 'string':
|
||||
return {
|
||||
type: 'text',
|
||||
operators: ['eq', 'neq', 'contains', 'ncontains', 'empty', 'nempty', 'in', 'nin'],
|
||||
operators: ['contains', 'ncontains', 'eq', 'neq', 'empty', 'nempty', 'in', 'nin'],
|
||||
};
|
||||
// Boolean
|
||||
case 'boolean':
|
||||
|
||||
@@ -21,6 +21,7 @@ import { defineComponent, PropType, computed } from '@vue/composition-api';
|
||||
import { Revision } from './types';
|
||||
import i18n from '@/lang';
|
||||
import { format } from 'date-fns';
|
||||
import { userName } from '@/utils/user-name';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -61,11 +62,7 @@ export default defineComponent({
|
||||
|
||||
const user = computed(() => {
|
||||
if (props.revision?.activity?.user && typeof props.revision.activity.user === 'object') {
|
||||
const { first_name, last_name } = props.revision.activity.user as {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
};
|
||||
return `${first_name} ${last_name}`;
|
||||
return userName(props.revision.activity.user);
|
||||
}
|
||||
|
||||
return i18n.t('private_user');
|
||||
|
||||
@@ -131,6 +131,7 @@ export default defineComponent({
|
||||
'activity.action',
|
||||
'activity.timestamp',
|
||||
'activity.user.id',
|
||||
'activity.user.email',
|
||||
'activity.user.first_name',
|
||||
'activity.user.last_name',
|
||||
'activity.ip',
|
||||
@@ -191,11 +192,6 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sidebar-detail {
|
||||
--v-badge-color: var(--background-normal);
|
||||
--v-badge-background-color: var(--foreground-normal);
|
||||
}
|
||||
|
||||
.v-progress-linear {
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import { Revision } from './types';
|
||||
import useSync from '@/composables/use-sync';
|
||||
import localizedFormat from '@/utils/localized-format';
|
||||
import i18n from '@/lang';
|
||||
import { userName } from '@/utils/user-name';
|
||||
|
||||
type Option = {
|
||||
text: string;
|
||||
@@ -59,8 +60,8 @@ export default defineComponent({
|
||||
let user = i18n.t('private_user');
|
||||
|
||||
if (typeof revision.activity.user === 'object') {
|
||||
const { first_name, last_name } = revision.activity.user;
|
||||
user = `${first_name} ${last_name}`;
|
||||
const userInfo = revision.activity.user;
|
||||
user = userName(userInfo);
|
||||
}
|
||||
|
||||
const text = String(i18n.t('revision_delta_by', { date, user }));
|
||||
|
||||
@@ -10,9 +10,10 @@ export type Revision = {
|
||||
user_agent: string;
|
||||
timestamp: string;
|
||||
user:
|
||||
| number
|
||||
| string
|
||||
| {
|
||||
id: number;
|
||||
id: string;
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
};
|
||||
|
||||
@@ -72,6 +72,8 @@ body {
|
||||
--v-badge-offset-x: 2px;
|
||||
--v-badge-offset-y: 4px;
|
||||
--v-badge-border-color: var(--background-normal-alt);
|
||||
--v-badge-background-color: var(--foreground-normal);
|
||||
--v-badge-color: var(--background-normal);
|
||||
|
||||
display: contents;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<v-icon name="person" outline v-else />
|
||||
</v-avatar>
|
||||
<div class="data">
|
||||
<div class="name type-title">{{ data.first_name }} {{ data.last_name }}</div>
|
||||
<div class="name type-title">{{ userName(data) }}</div>
|
||||
<div class="status-role" :class="data.status">{{ $t(data.status) }} {{ data.role.name }}</div>
|
||||
<div class="email">{{ data.email }}</div>
|
||||
</div>
|
||||
@@ -33,6 +33,7 @@
|
||||
import { defineComponent, ref, watch, onUnmounted, computed } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import { getRootPath } from '@/utils/get-root-path';
|
||||
import { userName } from '@/utils/user-name';
|
||||
|
||||
type User = {
|
||||
first_name: string;
|
||||
@@ -77,7 +78,7 @@ export default defineComponent({
|
||||
data.value = null;
|
||||
});
|
||||
|
||||
return { loading, error, data, active, avatarSrc };
|
||||
return { loading, error, data, active, avatarSrc, userName };
|
||||
|
||||
async function fetchUser() {
|
||||
loading.value = true;
|
||||
@@ -86,7 +87,7 @@ export default defineComponent({
|
||||
try {
|
||||
const response = await api.get(`/users/${props.user}`, {
|
||||
params: {
|
||||
fields: ['first_name', 'last_name', 'avatar.id', 'role.name', 'status', 'email'],
|
||||
fields: ['email', 'first_name', 'last_name', 'avatar.id', 'role.name', 'status', 'email'],
|
||||
},
|
||||
});
|
||||
data.value = response.data.data;
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
<template>
|
||||
<div class="private-view" :class="{ theme }">
|
||||
<v-info v-if="appAccess === false" center :title="$t('no_app_access')" type="danger" icon="block">
|
||||
{{ $t('no_app_access_copy') }}
|
||||
|
||||
<template #append>
|
||||
<v-button to="/logout">Switch User</v-button>
|
||||
</template>
|
||||
</v-info>
|
||||
|
||||
<div v-else class="private-view" :class="{ theme }">
|
||||
<aside role="navigation" aria-label="Module Navigation" class="navigation" :class="{ 'is-open': navOpen }">
|
||||
<module-bar />
|
||||
<div class="module-nav alt-colors">
|
||||
@@ -81,6 +89,11 @@ export default defineComponent({
|
||||
const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const appAccess = computed(() => {
|
||||
if (!userStore.state.currentUser) return true;
|
||||
return userStore.state.currentUser?.role?.app_access || false;
|
||||
});
|
||||
|
||||
const notificationsPreviewActive = ref(false);
|
||||
|
||||
const { sidebarOpen } = toRefs(appStore.state);
|
||||
@@ -98,6 +111,7 @@ export default defineComponent({
|
||||
sidebarOpen,
|
||||
openSidebar,
|
||||
notificationsPreviewActive,
|
||||
appAccess,
|
||||
};
|
||||
|
||||
function openSidebar(event: PointerEvent) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- name: Getting Started
|
||||
icon: play_arrow
|
||||
icon: play_circle_outline
|
||||
to: "/getting-started"
|
||||
children:
|
||||
- name: Introduction
|
||||
|
||||
@@ -17,29 +17,34 @@ get:
|
||||
description: The key of the asset size configured in settings.
|
||||
schema:
|
||||
type: string
|
||||
- name: w
|
||||
- name: width
|
||||
in: query
|
||||
description: Width of the file in pixels.
|
||||
schema:
|
||||
type: integer
|
||||
- name: h
|
||||
- name: height
|
||||
in: query
|
||||
description: Height of the file in pixels.
|
||||
schema:
|
||||
type: integer
|
||||
- name: f
|
||||
- name: fit
|
||||
in: query
|
||||
description: Fit of the file
|
||||
schema:
|
||||
type: string
|
||||
enum: [crop, contain]
|
||||
- name: q
|
||||
- name: quality
|
||||
in: query
|
||||
description: Quality of compression.
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 1
|
||||
maximum: 100
|
||||
- name: download
|
||||
in: query
|
||||
description: Download the asset to your computer
|
||||
schema:
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
description: Successful request
|
||||
|
||||
Reference in New Issue
Block a user