diff --git a/api/src/controllers/assets.ts b/api/src/controllers/assets.ts index bd36b4b650..5aa5bc0865 100644 --- a/api/src/controllers/assets.ts +++ b/api/src/controllers/assets.ts @@ -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); }) ); diff --git a/api/src/controllers/permissions.ts b/api/src/controllers/permissions.ts index ca8a6b91b3..568bae09b9 100644 --- a/api/src/controllers/permissions.ts +++ b/api/src/controllers/permissions.ts @@ -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(); } diff --git a/api/src/controllers/users.ts b/api/src/controllers/users.ts index 2e0888141e..b3b7ce4eb1 100644 --- a/api/src/controllers/users.ts +++ b/api/src/controllers/users.ts @@ -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 diff --git a/api/src/database/seeds/02-rows/03-presets.yaml b/api/src/database/seeds/02-rows/03-presets.yaml index ccb7b53382..df9e909390 100644 --- a/api/src/database/seeds/02-rows/03-presets.yaml +++ b/api/src/database/seeds/02-rows/03-presets.yaml @@ -31,7 +31,7 @@ data: cards: icon: account_circle title: '{{ first_name }} {{ last_name }}' - subtitle: '{{ title }}' + subtitle: '{{ email }}' size: 4 - collection: directus_activity diff --git a/api/src/database/seeds/03-fields/05-files.yaml b/api/src/database/seeds/03-fields/05-files.yaml index 27554000c0..6d3a4aff64 100644 --- a/api/src/database/seeds/03-fields/05-files.yaml +++ b/api/src/database/seeds/03-fields/05-files.yaml @@ -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 \ No newline at end of file + display: user diff --git a/api/src/utils/parse-filter.ts b/api/src/utils/parse-filter.ts index eef2d5aecd..ea4a1e4f64 100644 --- a/api/src/utils/parse-filter.ts +++ b/api/src/utils/parse-filter.ts @@ -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; diff --git a/api/src/utils/sanitize-query.ts b/api/src/utils/sanitize-query.ts index 9597ce5a2a..adae6792a3 100644 --- a/api/src/utils/sanitize-query.ts +++ b/api/src/utils/sanitize-query.ts @@ -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, @@ -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; } diff --git a/app/package-lock.json b/app/package-lock.json index 7dcd398b6e..cc1b6b1812 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -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", diff --git a/app/src/app.vue b/app/src/app.vue index aac919bf04..3d63279fbb 100644 --- a/app/src/app.vue +++ b/app/src/app.vue @@ -14,15 +14,7 @@ - - - - {{ $t('no_app_access_copy') }} - - - + @@ -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 }; }, }); diff --git a/app/src/components/v-form/form-field-menu.vue b/app/src/components/v-form/form-field-menu.vue index 125393e8ed..3b7d0fa53d 100644 --- a/app/src/components/v-form/form-field-menu.vue +++ b/app/src/components/v-form/form-field-menu.vue @@ -1,10 +1,18 @@ @@ -23,12 +23,14 @@ diff --git a/app/src/hydrate.ts b/app/src/hydrate.ts index e8b1144d64..603ed6a16a 100644 --- a/app/src/hydrate.ts +++ b/app/src/hydrate.ts @@ -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 { diff --git a/app/src/interfaces/user/user.vue b/app/src/interfaces/user/user.vue index a8da9f1a2d..28c7744fd7 100644 --- a/app/src/interfaces/user/user.vue +++ b/app/src/interfaces/user/user.vue @@ -12,11 +12,7 @@ > @@ -65,7 +61,7 @@ @click="setCurrent(item)" > - + {{ userName(currentUser) }} @@ -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; diff --git a/app/src/interfaces/wysiwyg/tinymce-overrides.css b/app/src/interfaces/wysiwyg/tinymce-overrides.css index b5029b72fa..716e9a1276 100644 --- a/app/src/interfaces/wysiwyg/tinymce-overrides.css +++ b/app/src/interfaces/wysiwyg/tinymce-overrides.css @@ -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; + } +} \ No newline at end of file diff --git a/app/src/lang/en-US/index.json b/app/src/lang/en-US/index.json index 61a900c099..2865e066ac 100644 --- a/app/src/lang/en-US/index.json +++ b/app/src/lang/en-US/index.json @@ -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", diff --git a/app/src/modules/activity/components/navigation.vue b/app/src/modules/activity/components/navigation.vue index 3b4eeb62e7..618fe17928 100644 --- a/app/src/modules/activity/components/navigation.vue +++ b/app/src/modules/activity/components/navigation.vue @@ -49,7 +49,7 @@ - + {{ $t('comment') }} diff --git a/app/src/modules/activity/routes/item.vue b/app/src/modules/activity/routes/item.vue index dbe60c6285..7ac8308826 100644 --- a/app/src/modules/activity/routes/item.vue +++ b/app/src/modules/activity/routes/item.vue @@ -12,7 +12,7 @@

User:

- {{ item.user.first_name }} {{ item.user.last_name }} + {{ userName(item.user) }}

Action:

@@ -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', diff --git a/app/src/modules/files/components/file-info-sidebar-detail.vue b/app/src/modules/files/components/file-info-sidebar-detail.vue index 4945b90b2e..b1cc415469 100644 --- a/app/src/modules/files/components/file-info-sidebar-detail.vue +++ b/app/src/modules/files/components/file-info-sidebar-detail.vue @@ -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 { diff --git a/app/src/modules/files/components/navigation.vue b/app/src/modules/files/components/navigation.vue index a83a327841..a2af1e58cc 100644 --- a/app/src/modules/files/components/navigation.vue +++ b/app/src/modules/files/components/navigation.vue @@ -34,7 +34,7 @@
- + {{ $t('my_files') }} diff --git a/app/src/modules/files/routes/item.vue b/app/src/modules/files/routes/item.vue index ca623381eb..67fb6bd885 100644 --- a/app/src/modules/files/routes/item.vue +++ b/app/src/modules/files/routes/item.vue @@ -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'); } diff --git a/app/src/modules/settings/routes/data-model/fields/components/field-select.vue b/app/src/modules/settings/routes/data-model/fields/components/field-select.vue index 618753e0b1..33b8c1a9ed 100644 --- a/app/src/modules/settings/routes/data-model/fields/components/field-select.vue +++ b/app/src/modules/settings/routes/data-model/fields/components/field-select.vue @@ -49,7 +49,15 @@ @@ -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() { diff --git a/app/src/routes/login/components/continue-as/continue-as.vue b/app/src/routes/login/components/continue-as/continue-as.vue index f5f47b5e9e..3cf12fa39a 100644 --- a/app/src/routes/login/components/continue-as/continue-as.vue +++ b/app/src/routes/login/components/continue-as/continue-as.vue @@ -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; diff --git a/app/src/stores/user.ts b/app/src/stores/user.ts index 10197744a6..db10df3789 100644 --- a/app/src/stores/user.ts +++ b/app/src/stores/user.ts @@ -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; diff --git a/app/src/utils/user-name.ts b/app/src/utils/user-name.ts new file mode 100644 index 0000000000..180c89a928 --- /dev/null +++ b/app/src/utils/user-name.ts @@ -0,0 +1,18 @@ +import { User } from '@/types'; +import { i18n } from '@/lang'; + +export function userName(user: Partial): 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; +} diff --git a/app/src/views/private/components/comments-sidebar-detail/comment-item-header.vue b/app/src/views/private/components/comments-sidebar-detail/comment-item-header.vue index 5b2196cbbe..42387b5192 100644 --- a/app/src/views/private/components/comments-sidebar-detail/comment-item-header.vue +++ b/app/src/views/private/components/comments-sidebar-detail/comment-item-header.vue @@ -1,11 +1,7 @@