Fix relational fields for Download Page as CSV & relevant displays' handler improvements (#14147)

* fix save-as-csv for aliased relational fields

* strip & decode HTML in formatted-value handler

* fix labels handler when value is an array

* fallback to value when text is empty

* Update app/src/displays/labels/index.ts

Co-authored-by: Brainslug <br41nslug@users.noreply.github.com>

* use dompurify to strip html tags

Co-authored-by: Brainslug <br41nslug@users.noreply.github.com>
This commit is contained in:
Azri Kahar
2022-06-29 03:17:05 +08:00
committed by GitHub
parent ad7d39203f
commit 7d01e8e4e2
4 changed files with 58 additions and 16 deletions

View File

@@ -25,6 +25,7 @@ import formatTitle from '@directus/format-title';
import { decode } from 'html-entities';
import { useI18n } from 'vue-i18n';
import { isNil } from 'lodash';
import dompurify from 'dompurify';
export default defineComponent({
props: {
@@ -152,7 +153,7 @@ export default defineComponent({
let value = String(props.value);
// Strip out all HTML tags
value = value.replace(/(<([^>]+)>)/gi, '');
value = dompurify.sanitize(value, { ALLOWED_TAGS: [] });
// Decode any HTML encoded characters (like &copy;)
value = decode(value);

View File

@@ -2,6 +2,8 @@ import { defineDisplay } from '@directus/shared/utils';
import { DisplayConfig } from '@directus/shared/types';
import DisplayFormattedValue from './formatted-value.vue';
import formatTitle from '@directus/format-title';
import { decode } from 'html-entities';
import dompurify from 'dompurify';
export default defineDisplay({
id: 'formatted-value',
@@ -13,7 +15,16 @@ export default defineDisplay({
handler: (value, options) => {
const prefix = options.prefix ?? '';
const suffix = options.suffix ?? '';
const formattedValue = options.format ? formatTitle(value) : value;
let sanitizedValue = String(value);
// Strip out all HTML tags
sanitizedValue = dompurify.sanitize(value, { ALLOWED_TAGS: [] });
// Decode any HTML encoded characters (like &copy;)
sanitizedValue = decode(sanitizedValue);
const formattedValue = options.format ? formatTitle(sanitizedValue) : sanitizedValue;
return `${prefix}${formattedValue}${suffix}`;
},

View File

@@ -10,16 +10,24 @@ export default defineDisplay({
icon: 'flag',
component: DisplayLabels,
handler: (value, options, { interfaceOptions }) => {
const configuredChoice =
options?.choices?.find((choice: { value: string }) => choice.value === value) ??
interfaceOptions?.choices?.find((choice: { value: string }) => choice.value === value);
if (configuredChoice) {
const { text } = translate(configuredChoice);
return text;
if (Array.isArray(value)) {
return value.map((val) => getConfiguredChoice(val)).join(', ');
} else {
return getConfiguredChoice(value);
}
return value;
function getConfiguredChoice(val: string) {
const configuredChoice =
options?.choices?.find((choice: { value: string }) => choice.value === val) ??
interfaceOptions?.choices?.find((choice: { value: string }) => choice.value === val);
if (configuredChoice) {
const { text } = translate(configuredChoice);
return text ? text : val;
}
return val;
}
},
options: [
{

View File

@@ -1,9 +1,11 @@
import { Item, Field, DisplayConfig } from '@directus/shared/types';
import { saveAs } from 'file-saver';
import { parse } from 'json2csv';
import useAliasFields from '@/composables/use-alias-fields';
import { getDisplay } from '@/displays';
import { useFieldsStore } from '@/stores';
import { get } from 'lodash';
import { get } from '@/utils/get-with-arrays';
import { DisplayConfig, Field, Item } from '@directus/shared/types';
import { saveAs } from 'file-saver';
import { parse } from 'json2csv';
import { ref } from 'vue';
/**
* Saves the given collection + items combination as a CSV file
@@ -17,14 +19,34 @@ export async function saveAsCSV(collection: string, fields: string[], items: Ite
fieldsUsed[key] = fieldsStore.getField(collection, key);
}
const { aliasFields } = useAliasFields(ref(fields));
const parsedItems = [];
for (const item of items) {
const parsedItem: Record<string, any> = {};
for (const key of fields) {
const name = fieldsUsed[key]?.name ?? key;
const value = get(item, key);
let name: string;
const keyParts = key.split('.');
if (keyParts.length > 1) {
const names = keyParts.map((fieldKey, index) => {
const pathPrefix = keyParts.slice(0, index);
const field = fieldsStore.getField(collection, [...pathPrefix, fieldKey].join('.'));
return field?.name ?? fieldKey;
});
name = names.join(' -> ');
} else {
name = fieldsUsed[key]?.name ?? key;
}
const value =
!aliasFields.value?.[key] || item[key] !== undefined
? get(item, key)
: get(item, aliasFields.value[key].fullAlias);
let display: DisplayConfig | undefined;