diff --git a/packages/bbui/src/helpers.ts b/packages/bbui/src/helpers.ts index 330f381d53..10ccf4683d 100644 --- a/packages/bbui/src/helpers.ts +++ b/packages/bbui/src/helpers.ts @@ -211,9 +211,12 @@ const localeDateFormat = new Intl.DateTimeFormat() // Formats a dayjs date according to schema flags export const getDateDisplayValue = ( - value: dayjs.Dayjs | null, + value: dayjs.Dayjs | string | null, { enableTime = true, timeOnly = false } = {} ): string => { + if (typeof value === "string") { + value = dayjs(value) + } if (!value?.isValid()) { return "" } diff --git a/packages/builder/src/components/design/ScreenDetailsModal.svelte b/packages/builder/src/components/design/ScreenDetailsModal.svelte index 3f8e08d031..410ddee8a9 100644 --- a/packages/builder/src/components/design/ScreenDetailsModal.svelte +++ b/packages/builder/src/components/design/ScreenDetailsModal.svelte @@ -80,5 +80,6 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + margin-top: 4px; } diff --git a/packages/builder/src/components/design/settings/componentSettings.js b/packages/builder/src/components/design/settings/componentSettings.js index 1cf4b0211c..86014f152c 100644 --- a/packages/builder/src/components/design/settings/componentSettings.js +++ b/packages/builder/src/components/design/settings/componentSettings.js @@ -22,6 +22,7 @@ import ValidationEditor from "./controls/ValidationEditor/ValidationEditor.svelt import DrawerBindableInput from "@/components/common/bindings/DrawerBindableInput.svelte" import ColumnEditor from "./controls/ColumnEditor/ColumnEditor.svelte" import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte" +import TopLevelColumnEditor from "./controls/ColumnEditor/TopLevelColumnEditor.svelte" import GridColumnEditor from "./controls/GridColumnConfiguration/GridColumnConfiguration.svelte" import BarButtonList from "./controls/BarButtonList.svelte" import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte" @@ -62,7 +63,10 @@ const componentMap = { stepConfiguration: FormStepConfiguration, formStepControls: FormStepControls, columns: ColumnEditor, + // "Basic" actually includes nested JSON and relationship fields "columns/basic": BasicColumnEditor, + // "Top level" is only the top level schema fields + "columns/toplevel": TopLevelColumnEditor, "columns/grid": GridColumnEditor, tableConditions: TableConditionEditor, "field/sortable": SortableFieldSelect, diff --git a/packages/builder/src/components/design/settings/controls/ColumnEditor/ColumnDrawer.svelte b/packages/builder/src/components/design/settings/controls/ColumnEditor/ColumnDrawer.svelte index 09734c2ca4..df7932c74d 100644 --- a/packages/builder/src/components/design/settings/controls/ColumnEditor/ColumnDrawer.svelte +++ b/packages/builder/src/components/design/settings/controls/ColumnEditor/ColumnDrawer.svelte @@ -145,7 +145,7 @@
- By default, all columns will automatically be shown. + The default column configuration will automatically be shown.
You can manually control which columns are included by adding them below. diff --git a/packages/builder/src/components/design/settings/controls/ColumnEditor/ColumnEditor.svelte b/packages/builder/src/components/design/settings/controls/ColumnEditor/ColumnEditor.svelte index 5e27b591f8..fab905e8b7 100644 --- a/packages/builder/src/components/design/settings/controls/ColumnEditor/ColumnEditor.svelte +++ b/packages/builder/src/components/design/settings/controls/ColumnEditor/ColumnEditor.svelte @@ -10,10 +10,18 @@ } from "@/dataBinding" import { selectedScreen, tables } from "@/stores/builder" - export let componentInstance + const getSearchableFields = (schema, tableList) => { + return search.getFields(tableList, Object.values(schema || {}), { + allowLinks: true, + }) + } + + export let componentInstance = undefined export let value = [] export let allowCellEditing = true export let allowReorder = true + export let getSchemaFields = getSearchableFields + export let placeholder = "All columns" const dispatch = createEventDispatcher() @@ -28,13 +36,7 @@ : enrichedSchemaFields?.map(field => field.name) $: sanitisedValue = getValidColumns(value, options) $: updateBoundValue(sanitisedValue) - $: enrichedSchemaFields = search.getFields( - $tables.list, - Object.values(schema || {}), - { - allowLinks: true, - } - ) + $: enrichedSchemaFields = getSchemaFields(schema, $tables.list) $: { value = (value || []).filter( @@ -44,7 +46,7 @@ const getText = value => { if (!value?.length) { - return "All columns" + return placeholder } let text = `${value.length} column` if (value.length !== 1) { diff --git a/packages/builder/src/components/design/settings/controls/ColumnEditor/TopLevelColumnEditor.svelte b/packages/builder/src/components/design/settings/controls/ColumnEditor/TopLevelColumnEditor.svelte new file mode 100644 index 0000000000..69a80a85da --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/ColumnEditor/TopLevelColumnEditor.svelte @@ -0,0 +1,15 @@ + + + diff --git a/packages/builder/src/constants/index.ts b/packages/builder/src/constants/index.ts index 3c3a6888ad..7068a1bb96 100644 --- a/packages/builder/src/constants/index.ts +++ b/packages/builder/src/constants/index.ts @@ -71,4 +71,5 @@ export const AutoScreenTypes = { BLANK: "blank", TABLE: "table", FORM: "form", + PDF: "pdf", } diff --git a/packages/builder/src/dataBinding.js b/packages/builder/src/dataBinding.js index 1011774ac5..ada1ee274d 100644 --- a/packages/builder/src/dataBinding.js +++ b/packages/builder/src/dataBinding.js @@ -26,7 +26,7 @@ import { getJsHelperList, } from "@budibase/string-templates" import { TableNames } from "./constants" -import { JSONUtils, Constants } from "@budibase/frontend-core" +import { JSONUtils, Constants, SchemaUtils } from "@budibase/frontend-core" import ActionDefinitions from "@/components/design/settings/controls/ButtonActionEditor/manifest.json" import { environment, licensing } from "@/stores/portal" import { convertOldFieldFormat } from "@/components/design/settings/controls/FieldConfiguration/utils" @@ -1026,25 +1026,7 @@ export const getSchemaForDatasource = (asset, datasource, options) => { // Check for any JSON fields so we can add any top level properties if (schema) { - let jsonAdditions = {} - Object.keys(schema).forEach(fieldKey => { - const fieldSchema = schema[fieldKey] - if (fieldSchema?.type === "json") { - const jsonSchema = JSONUtils.convertJSONSchemaToTableSchema( - fieldSchema, - { - squashObjects: true, - } - ) - Object.keys(jsonSchema).forEach(jsonKey => { - jsonAdditions[`${fieldKey}.${jsonKey}`] = { - type: jsonSchema[jsonKey].type, - nestedJSON: true, - } - }) - } - }) - schema = { ...schema, ...jsonAdditions } + schema = SchemaUtils.addNestedJSONSchemaFields(schema) } // Determine if we should add ID and rev to the schema diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte index 31479bc820..1da03377b5 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte @@ -16,6 +16,7 @@ import { getBindableProperties } from "@/dataBinding" import BarButtonList from "@/components/design/settings/controls/BarButtonList.svelte" import URLVariableTestInput from "@/components/design/settings/controls/URLVariableTestInput.svelte" + import { DrawerBindableInput } from "@/components/common/bindings" $: bindings = getBindableProperties($selectedScreen, null) $: screenSettings = getScreenSettings($selectedScreen) @@ -23,7 +24,59 @@ let errors = {} const getScreenSettings = screen => { - let settings = [ + // Determine correct screen settings for the top level component + let screenComponentSettings = [] + switch ($selectedScreen.props._component) { + case "@budibase/standard-components/pdf": + screenComponentSettings = [ + { + key: "props.fileName", + label: "PDF title", + defaultValue: "Report", + control: DrawerBindableInput, + }, + { + key: "props.buttonText", + label: "Button text", + defaultValue: "Download PDF", + control: DrawerBindableInput, + }, + ] + break + default: + screenComponentSettings = [ + { + key: "width", + label: "Width", + control: Select, + props: { + options: ["Extra small", "Small", "Medium", "Large", "Max"], + placeholder: "Default", + disabled: !!screen.layoutId, + }, + }, + { + key: "props.layout", + label: "Layout", + defaultValue: "flex", + control: BarButtonList, + props: { + options: [ + { + barIcon: "ModernGridView", + value: "flex", + }, + { + barIcon: "ViewGrid", + value: "grid", + }, + ], + }, + }, + ] + } + + return [ { key: "routing.homeScreen", control: Checkbox, @@ -66,34 +119,7 @@ label: "On screen load", control: ButtonActionEditor, }, - { - key: "width", - label: "Width", - control: Select, - props: { - options: ["Extra small", "Small", "Medium", "Large", "Max"], - placeholder: "Default", - disabled: !!screen.layoutId, - }, - }, - { - key: "props.layout", - label: "Layout", - defaultValue: "flex", - control: BarButtonList, - props: { - options: [ - { - barIcon: "ModernGridView", - value: "flex", - }, - { - barIcon: "ViewGrid", - value: "grid", - }, - ], - }, - }, + ...screenComponentSettings, { key: "urlTest", control: URLVariableTestInput, @@ -102,8 +128,6 @@ }, }, ] - - return settings } const routeTaken = url => { diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/ThemePanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/ThemePanel.svelte index af693a872f..c1e68010ad 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/ThemePanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/ThemePanel.svelte @@ -26,7 +26,9 @@
- These settings apply to all screens + + These settings apply to all screens. PDFs are always light theme. +
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/NewComponentPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/NewComponentPanel.svelte index 1654ff5e06..3dccd97701 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/NewComponentPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/NewComponentPanel.svelte @@ -58,7 +58,7 @@ // Get initial set of allowed components let allowedComponents = [] const definition = componentStore.getDefinition(component?._component) - if (definition.legalDirectChildren?.length) { + if (definition?.legalDirectChildren?.length) { allowedComponents = definition.legalDirectChildren.map(x => { return `@budibase/standard-components/${x}` }) @@ -67,7 +67,7 @@ } // Build up list of illegal children from ancestors - let illegalChildren = definition.illegalChildren || [] + let illegalChildren = definition?.illegalChildren || [] path.forEach(ancestor => { // Sidepanels and modals can be nested anywhere in the component tree, but really they are always rendered at the top level. // Because of this, it doesn't make sense to carry over any parent illegal children to them, so the array is reset here. @@ -144,11 +144,6 @@ } }) - // Swap blocks and plugins - let tmp = enrichedStructure[1] - enrichedStructure[1] = enrichedStructure[0] - enrichedStructure[0] = tmp - return enrichedStructure } diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json index d809095dc0..c4ee0c9dd4 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json @@ -20,9 +20,11 @@ "name": "Data", "icon": "Data", "children": [ + "singlerowprovider", "dataprovider", "repeater", "gridblock", + "pdftable", "spreadsheet", "dynamicfilter", "daterangepicker" diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte index 9dd7aab640..be409eff09 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPanel.svelte @@ -1,10 +1,13 @@
@@ -14,10 +17,12 @@
- {#if $appStore.clientFeatures.devicePreview} - + {#if !isPDF} + {#if $appStore.clientFeatures.devicePreview} + + {/if} + {/if} -
diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte index edc502bbb4..12186a7055 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte @@ -53,7 +53,7 @@ // Otherwise choose a datasource datasourceModal.show() } - } else if (mode === AutoScreenTypes.BLANK) { + } else if (mode === AutoScreenTypes.BLANK || mode === AutoScreenTypes.PDF) { screenDetailsModal.show() } else { throw new Error("Invalid mode provided") @@ -101,8 +101,11 @@ } } - const createBlankScreen = async ({ route }) => { - const screenTemplates = screenTemplating.blank({ route, screens }) + const createBasicScreen = async ({ route }) => { + const screenTemplates = + mode === AutoScreenTypes.BLANK + ? screenTemplating.blank({ route, screens }) + : screenTemplating.pdf({ route, screens }) const newScreens = await createScreens(screenTemplates) loadNewScreen(newScreens[0]) } @@ -243,7 +246,7 @@ - + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/pdf.svg b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/pdf.svg new file mode 100644 index 0000000000..16ac02e2d7 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/images/pdf.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte index bbd6fda256..d50d284965 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/index.svelte @@ -4,8 +4,10 @@ import blank from "./images/blank.svg" import table from "./images/tableInline.svg" import form from "./images/formUpdate.svg" + import pdf from "./images/pdf.svg" import CreateScreenModal from "./CreateScreenModal.svelte" import { screenStore } from "@/stores/builder" + import { AutoScreenTypes } from "@/constants" export let onClose = null @@ -27,32 +29,54 @@
-
createScreenModal.show("blank")}> +
createScreenModal.show(AutoScreenTypes.BLANK)} + >
A blank screen
- Blank + Blank Add an empty blank screen
-
createScreenModal.show("table")}> +
createScreenModal.show(AutoScreenTypes.TABLE)} + >
A table of data
- Table + Table List rows in a table
-
createScreenModal.show("form")}> +
createScreenModal.show(AutoScreenTypes.PDF)} + > +
+ A form containing data +
+
+ PDF + Create, edit and export your PDF +
+
+ +
createScreenModal.show(AutoScreenTypes.FORM)} + >
A form containing data
- Form + Form Capture data from your users
@@ -111,14 +135,13 @@ .text { border: 1px solid var(--grey-4); border-radius: 0 0 4px 4px; - padding: 8px 16px 13px 16px; - } - - .text :global(p:nth-child(1)) { - margin-bottom: 6px; + padding: 12px 16px 12px 16px; + display: flex; + flex-direction: column; + gap: 2px; } .text :global(p:nth-child(2)) { - color: var(--grey-6); + color: var(--spectrum-global-color-gray-600); } diff --git a/packages/builder/src/stores/builder/screens.ts b/packages/builder/src/stores/builder/screens.ts index 51072adbb8..a67490badb 100644 --- a/packages/builder/src/stores/builder/screens.ts +++ b/packages/builder/src/stores/builder/screens.ts @@ -4,22 +4,24 @@ import { Helpers } from "@budibase/bbui" import { RoleUtils, Utils } from "@budibase/frontend-core" import { findAllMatchingComponents } from "@/helpers/components" import { - layoutStore, appStore, componentStore, + layoutStore, navigationStore, + previewStore, selectedComponent, } from "@/stores/builder" import { createHistoryStore, HistoryStore } from "@/stores/builder/history" import { API } from "@/api" import { BudiStore } from "../BudiStore" import { - FetchAppPackageResponse, - DeleteScreenResponse, - Screen, Component, - SaveScreenResponse, ComponentDefinition, + DeleteScreenResponse, + FetchAppPackageResponse, + SaveScreenResponse, + Screen, + ScreenVariant, } from "@budibase/types" interface ScreenState { @@ -115,6 +117,14 @@ export class ScreenStore extends BudiStore { state.selectedScreenId = screen._id return state }) + + // If this is a PDF screen, ensure we're on desktop + if ( + screen.variant === ScreenVariant.PDF && + get(previewStore).previewDevice !== "desktop" + ) { + previewStore.setDevice("desktop") + } } /** diff --git a/packages/builder/src/templates/screenTemplating/Screen.js b/packages/builder/src/templates/screenTemplating/Screen.js index da3c83820e..4eb0e7a098 100644 --- a/packages/builder/src/templates/screenTemplating/Screen.js +++ b/packages/builder/src/templates/screenTemplating/Screen.js @@ -1,5 +1,6 @@ import { BaseStructure } from "../BaseStructure" import { Helpers } from "@budibase/bbui" +import { ScreenVariant } from "@budibase/types" export class Screen extends BaseStructure { constructor() { @@ -81,3 +82,25 @@ export class Screen extends BaseStructure { return this } } + +export class PDFScreen extends Screen { + constructor() { + super() + this._json.variant = ScreenVariant.PDF + this._json.width = "Max" + this._json.showNavigation = false + this._json.props = { + _id: Helpers.uuid(), + _component: "@budibase/standard-components/pdf", + _styles: { + normal: {}, + hover: {}, + active: {}, + selected: {}, + }, + _children: [], + _instanceName: "PDF", + title: "PDF", + } + } +} diff --git a/packages/builder/src/templates/screenTemplating/index.js b/packages/builder/src/templates/screenTemplating/index.js index ff15687776..855f255a3a 100644 --- a/packages/builder/src/templates/screenTemplating/index.js +++ b/packages/builder/src/templates/screenTemplating/index.js @@ -1,3 +1,4 @@ export { default as blank } from "./blank" export { default as form } from "./form" export { default as table } from "./table" +export { default as pdf } from "./pdf" diff --git a/packages/builder/src/templates/screenTemplating/pdf.js b/packages/builder/src/templates/screenTemplating/pdf.js new file mode 100644 index 0000000000..5c10e5cec2 --- /dev/null +++ b/packages/builder/src/templates/screenTemplating/pdf.js @@ -0,0 +1,20 @@ +import { PDFScreen } from "./Screen" +import { capitalise } from "@/helpers" +import getValidRoute from "./getValidRoute" +import { Roles } from "@/constants/backend" + +const pdf = ({ route, screens }) => { + const validRoute = getValidRoute(screens, route, Roles.BASIC) + + const template = new PDFScreen().role(Roles.BASIC).route(validRoute).json() + + return [ + { + data: template, + navigationLinkLabel: + validRoute === "/" ? null : capitalise(validRoute.split("/")[1]), + }, + ] +} + +export default pdf diff --git a/packages/client/manifest.json b/packages/client/manifest.json index fc0d5a4614..64f3c34a60 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -8017,6 +8017,32 @@ "key": "text", "wide": true }, + { + "type": "select", + "label": "Size", + "key": "size", + "defaultValue": "14px", + "showInBar": true, + "placeholder": "Default", + "options": [ + { + "label": "Small", + "value": "12px" + }, + { + "label": "Medium", + "value": "14px" + }, + { + "label": "Large", + "value": "18px" + }, + { + "label": "Extra large", + "value": "24px" + } + ] + }, { "type": "select", "label": "Alignment", @@ -8058,5 +8084,133 @@ "showInBar": true } ] + }, + "pdf": { + "name": "PDF Generator", + "icon": "Document", + "hasChildren": true, + "showEmptyState": false, + "illegalChildren": ["sidepanel", "modal", "gridblock"], + "grid": { + "hAlign": "center", + "vAlign": "start" + }, + "size": { + "width": 800, + "height": 1200 + }, + "description": "A component to render PDFs from other Budibase components", + "settings": [ + { + "type": "text", + "label": "PDF title", + "key": "fileName", + "defaultValue": "Report" + }, + { + "type": "text", + "label": "Button text", + "key": "buttonText", + "defaultValue": "Download PDF" + } + ] + }, + "pdftable": { + "name": "PDF Table", + "icon": "Table", + "styles": ["size"], + "size": { + "width": 600, + "height": 304 + }, + "grid": { + "hAlign": "stretch", + "vAlign": "stretch" + }, + "settings": [ + { + "type": "dataSource", + "label": "Data", + "key": "datasource", + "required": true + }, + { + "type": "filter", + "label": "Filtering", + "key": "filter", + "resetOn": "datasource", + "dependsOn": { + "setting": "datasource.type", + "value": "custom", + "invert": true + } + }, + { + "type": "field/sortable", + "label": "Sort column", + "key": "sortColumn", + "placeholder": "Default", + "resetOn": "datasource", + "dependsOn": { + "setting": "datasource.type", + "value": "custom", + "invert": true + } + }, + { + "type": "select", + "label": "Sort order", + "key": "sortOrder", + "resetOn": "datasource", + "options": ["Ascending", "Descending"], + "defaultValue": "Ascending", + "dependsOn": "sortColumn" + }, + { + "type": "columns/toplevel", + "label": "Columns", + "key": "columns", + "resetOn": "datasource", + "placeholder": "First 3 columns" + }, + { + "type": "number", + "label": "Limit", + "key": "limit", + "defaultValue": 10 + } + ] + }, + "singlerowprovider": { + "name": "Single Row Provider", + "icon": "SQLQuery", + "hasChildren": true, + "actions": ["RefreshDatasource"], + "size": { + "width": 500, + "height": 200 + }, + "grid": { + "hAlign": "stretch", + "vAlign": "stretch" + }, + "settings": [ + { + "type": "table", + "label": "Datasource", + "key": "datasource", + "required": true + }, + { + "type": "text", + "label": "Row ID", + "key": "rowId", + "required": true, + "resetOn": "datasource" + } + ], + "context": { + "type": "schema" + } } } diff --git a/packages/client/package.json b/packages/client/package.json index daaf0ca433..00e7b8538c 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -29,6 +29,7 @@ "apexcharts": "^3.48.0", "dayjs": "^1.10.8", "downloadjs": "1.4.7", + "html2pdf.js": "^0.9.3", "html5-qrcode": "^2.3.8", "leaflet": "^1.7.1", "sanitize-html": "^2.13.0", diff --git a/packages/client/src/components/BlockComponent.svelte b/packages/client/src/components/BlockComponent.svelte index cacb4f64c1..04a16aa701 100644 --- a/packages/client/src/components/BlockComponent.svelte +++ b/packages/client/src/components/BlockComponent.svelte @@ -1,20 +1,20 @@ -
+
diff --git a/packages/client/src/components/app/SingleRowProvider.svelte b/packages/client/src/components/app/SingleRowProvider.svelte new file mode 100644 index 0000000000..b8a25a6005 --- /dev/null +++ b/packages/client/src/components/app/SingleRowProvider.svelte @@ -0,0 +1,46 @@ + + +
+ + + +
+ + diff --git a/packages/client/src/components/app/Text.svelte b/packages/client/src/components/app/Text.svelte index 17c12656af..c4937fec3d 100644 --- a/packages/client/src/components/app/Text.svelte +++ b/packages/client/src/components/app/Text.svelte @@ -5,12 +5,13 @@ export let text: any = "" export let color: string | undefined = undefined export let align: "left" | "center" | "right" | "justify" = "left" + export let size: string | undefined = "14px" const component = getContext("component") const { styleable } = getContext("sdk") // Add in certain settings to styles - $: styles = enrichStyles($component.styles, color, align) + $: styles = enrichStyles($component.styles, color, align, size) // Ensure we're always passing in a string value to the markdown editor $: safeText = stringify(text) @@ -18,10 +19,12 @@ const enrichStyles = ( styles: any, colorStyle: typeof color, - alignStyle: typeof align + alignStyle: typeof align, + size: string | undefined ) => { let additions: Record = { "text-align": alignStyle, + "font-size": size || "14px", } if (colorStyle) { additions.color = colorStyle diff --git a/packages/client/src/components/app/container/GridContainer.svelte b/packages/client/src/components/app/container/GridContainer.svelte index 03598d53bd..66b7e14bac 100644 --- a/packages/client/src/components/app/container/GridContainer.svelte +++ b/packages/client/src/components/app/container/GridContainer.svelte @@ -135,12 +135,18 @@ use:styleable={$styles} data-cols={GridColumns} data-col-size={colSize} + data-required-rows={requiredRows} on:click={onClick} > {#if inBuilder} -
- {#each { length: GridColumns * rows } as _, idx} -
+
+ {#each { length: rows } as _} +
+ {/each} +
+
+ {#each { length: GridColumns } as _} +
{/each}
{/if} @@ -151,7 +157,8 @@ diff --git a/packages/client/src/components/app/pdf/PDFTable.svelte b/packages/client/src/components/app/pdf/PDFTable.svelte new file mode 100644 index 0000000000..e4d415fa01 --- /dev/null +++ b/packages/client/src/components/app/pdf/PDFTable.svelte @@ -0,0 +1,143 @@ + + +
+
+ {#if schema} + {#each Object.keys(schema) as col} +
{schema[col].displayName}
+ {/each} + {#each stringifiedRows as row} + {#each Object.keys(schema) as col} +
{row[col]}
+ {/each} + {/each} + {/if} +
+
+ + diff --git a/packages/client/src/components/app/pdf/index.ts b/packages/client/src/components/app/pdf/index.ts new file mode 100644 index 0000000000..ae100c894b --- /dev/null +++ b/packages/client/src/components/app/pdf/index.ts @@ -0,0 +1,2 @@ +export { default as pdf } from "./PDF.svelte" +export { default as pdftable } from "./PDFTable.svelte" diff --git a/packages/client/src/components/app/pdf/pdf.ts b/packages/client/src/components/app/pdf/pdf.ts new file mode 100644 index 0000000000..ed47dc9831 --- /dev/null +++ b/packages/client/src/components/app/pdf/pdf.ts @@ -0,0 +1,78 @@ +// @ts-ignore +import html2pdf from "html2pdf.js" + +export const pxToPt = (px: number) => (px / 4) * 3 +export const ptToPx = (pt: number) => (pt / 3) * 4 + +export const A4HeightPx = ptToPx(841.92) + 1 + +export interface PDFOptions { + fileName?: string + marginPt?: number + orientation?: "portrait" | "landscape" + htmlScale?: number + footer?: boolean +} + +export async function htmlToPdf(el: HTMLElement, opts: PDFOptions = {}) { + const userOpts: Required = { + fileName: "file.pdf", + marginPt: 60, + orientation: "portrait", + htmlScale: 1, + footer: true, + ...opts, + } + + return new Promise(resolve => { + // Sanity check title + let fileName = userOpts.fileName + if (!fileName.endsWith(".pdf")) { + fileName += ".pdf" + } + + // Config + const options = { + margin: userOpts.marginPt, + filename: fileName, + image: { type: "jpeg", quality: 0.95 }, + html2canvas: { dpi: 192, scale: 2, useCORS: true }, + jsPDF: { + orientation: userOpts.orientation, + unit: "pt", + format: "a4", + }, + pagebreak: { avoid: ".no-break" }, + + // Custom params + htmlScale: userOpts.htmlScale, + } + + let worker = html2pdf().set(options).from(el).toPdf() + + // Add footer if required + if (opts.footer) { + worker = worker.get("pdf").then((pdf: any) => { + const totalPages = pdf.internal.getNumberOfPages() + for (let i = 1; i <= totalPages; i++) { + pdf.setPage(i) + pdf.setFontSize(10) + pdf.setTextColor(200) + pdf.text( + `Page ${i} of ${totalPages}`, + pdf.internal.pageSize.getWidth() - options.margin, + pdf.internal.pageSize.getHeight() - options.margin / 2, + "right" + ) + pdf.text( + options.filename.replace(".pdf", ""), + options.margin, + pdf.internal.pageSize.getHeight() - options.margin / 2 + ) + } + }) + } + + worker.save().then(resolve) + }) +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 1be6072343..500debf87d 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -31,6 +31,8 @@ import { } from "@budibase/types" import { ActionTypes } from "@/constants" import { APIClient } from "@budibase/frontend-core" +import BlockComponent from "./components/BlockComponent.svelte" +import Block from "./components/Block.svelte" if (typeof window !== "undefined") { window.addEventListener("beforeinstallprompt", e => { @@ -99,6 +101,8 @@ export interface SDK { notificationStore: typeof notificationStore environmentStore: typeof environmentStore appStore: typeof appStore + Block: typeof Block + BlockComponent: typeof BlockComponent } let app: ClientApp diff --git a/packages/client/src/stores/screens.js b/packages/client/src/stores/screens.js index 1cc205b382..7340e6828c 100644 --- a/packages/client/src/stores/screens.js +++ b/packages/client/src/stores/screens.js @@ -8,6 +8,7 @@ import { RoleUtils } from "@budibase/frontend-core" import { findComponentById, findComponentParent } from "../utils/components.js" import { Helpers } from "@budibase/bbui" import { DNDPlaceholderID, ScreenslotID, ScreenslotType } from "@/constants" +import { ScreenVariant } from "@budibase/types" const createScreenStore = () => { const store = derived( @@ -193,5 +194,8 @@ const createScreenStore = () => { export const screenStore = createScreenStore() export const isGridScreen = derived(screenStore, $screenStore => { - return $screenStore.activeScreen?.props?.layout === "grid" + return ( + $screenStore.activeScreen?.props?.layout === "grid" || + $screenStore.activeScreen?.variant === ScreenVariant.PDF + ) }) diff --git a/packages/client/src/utils/grid.ts b/packages/client/src/utils/grid.ts index 3412e734c6..08520ff781 100644 --- a/packages/client/src/utils/grid.ts +++ b/packages/client/src/utils/grid.ts @@ -116,6 +116,9 @@ export const gridLayout = (node: HTMLDivElement, metadata: GridMetadata) => { return } + // Add a unique class to elements we mutate so we can easily find them later + node.classList.add("grid-child") + // Callback to select the component when clicking on the wrapper selectComponent = (e: Event) => { e.stopPropagation() diff --git a/packages/frontend-core/src/components/grid/cells/NumberCell.svelte b/packages/frontend-core/src/components/grid/cells/NumberCell.svelte index c8ae96ef21..5ac6e14b6e 100644 --- a/packages/frontend-core/src/components/grid/cells/NumberCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/NumberCell.svelte @@ -1,8 +1,5 @@ - - { + const type = typeof value + if (type !== "string" && type !== "number") { + return "" + } + if (type === "string" && !value.trim().length) { + return "" + } + const res = NumberFormatter.format(value) + return res === "NaN" ? stringifyValue(value) : res +} + +// Attempts to stringify any type of value +const stringifyValue = (value: any): string => { + if (value == null) { + return "" + } + if (typeof value === "string") { + return value + } + if (typeof value.toString === "function") { + return stringifyValue(value.toString()) + } + try { + return JSON.stringify(value) + } catch (e) { + return "" + } +} + +const stringifyField = (value: any, schema: FieldSchema): string => { + switch (schema.type) { + // Auto should not exist as it should always be typed by its underlying + // real type, like date or user + case FieldType.AUTO: + return "" + + // Just state whether signatures exist or not + case FieldType.SIGNATURE_SINGLE: + return value ? "Yes" : "No" + + // Extract attachment names + case FieldType.ATTACHMENT_SINGLE: + case FieldType.ATTACHMENTS: { + if (!value) { + return "" + } + const arrayValue = Array.isArray(value) ? value : [value] + return arrayValue + .map(x => x.name) + .filter(x => !!x) + .join(", ") + } + + // Extract primary displays from relationships + case FieldType.LINK: { + if (!value) { + return "" + } + const arrayValue = Array.isArray(value) ? value : [value] + return arrayValue + .map(x => x.primaryDisplay) + .filter(x => !!x) + .join(", ") + } + + // Stringify JSON blobs + case FieldType.JSON: + return value ? JSON.stringify(value) : "" + + // User is the only BB reference subtype right now + case FieldType.BB_REFERENCE: + case FieldType.BB_REFERENCE_SINGLE: { + if ( + schema.subtype !== BBReferenceFieldSubType.USERS && + schema.subtype !== BBReferenceFieldSubType.USER + ) { + return "" + } + if (!value) { + return "" + } + const arrayVal = Array.isArray(value) ? value : [value] + return arrayVal?.map((user: any) => user.primaryDisplay).join(", ") || "" + } + + // Join arrays with commas + case FieldType.ARRAY: + return value?.join(", ") || "" + + // Just capitalise booleans + case FieldType.BOOLEAN: + return Helpers.capitalise(value?.toString() || "false") + + // Format dates into something readable + case FieldType.DATETIME: { + return Helpers.getDateDisplayValue(value, { + enableTime: !schema.dateOnly, + timeOnly: schema.timeOnly, + }) + } + + // Format numbers using a locale string + case FieldType.NUMBER: + return formatNumber(value) + + // Simple string types + case FieldType.STRING: + case FieldType.LONGFORM: + case FieldType.BIGINT: + case FieldType.OPTIONS: + case FieldType.AI: + case FieldType.BARCODEQR: + return value || "" + + // Fallback for unknown types or future column types that we forget to add + case FieldType.FORMULA: + default: + return stringifyValue(value) + } +} + +// Stringifies every property of a row, ensuring they are all human-readable +// strings for display +export const stringifyRow = (row: Row, schema: TableSchema): StringifiedRow => { + let stringified: StringifiedRow = {} + Object.entries(schema).forEach(([field, fieldSchema]) => { + stringified[field] = stringifyField( + Helpers.deepGet(row, field), + fieldSchema + ) + }) + return stringified +} diff --git a/packages/frontend-core/src/utils/index.ts b/packages/frontend-core/src/utils/index.ts index e1f66d348d..314353bfc8 100644 --- a/packages/frontend-core/src/utils/index.ts +++ b/packages/frontend-core/src/utils/index.ts @@ -15,3 +15,4 @@ export * from "./relatedColumns" export * from "./table" export * from "./components" export * from "./validation" +export * from "./formatting" diff --git a/packages/frontend-core/src/utils/schema.js b/packages/frontend-core/src/utils/schema.js index cd55d4983d..135dbd3e35 100644 --- a/packages/frontend-core/src/utils/schema.js +++ b/packages/frontend-core/src/utils/schema.js @@ -1,5 +1,6 @@ import { helpers } from "@budibase/shared-core" import { TypeIconMap } from "../constants" +import { convertJSONSchemaToTableSchema } from "./json" export const getColumnIcon = column => { // For some reason we have remix icons saved under this property sometimes, @@ -24,3 +25,25 @@ export const getColumnIcon = column => { return result || "Text" } + +export const addNestedJSONSchemaFields = schema => { + if (!schema) { + return schema + } + let jsonAdditions = {} + Object.keys(schema).forEach(fieldKey => { + const fieldSchema = schema[fieldKey] + if (fieldSchema?.type === "json") { + const jsonSchema = convertJSONSchemaToTableSchema(fieldSchema, { + squashObjects: true, + }) + Object.keys(jsonSchema).forEach(jsonKey => { + jsonAdditions[`${fieldKey}.${jsonKey}`] = { + type: jsonSchema[jsonKey].type, + nestedJSON: true, + } + }) + } + }) + return { ...schema, ...jsonAdditions } +} diff --git a/packages/server/src/api/controllers/row/index.ts b/packages/server/src/api/controllers/row/index.ts index 8f4629a5b0..1d0271a726 100644 --- a/packages/server/src/api/controllers/row/index.ts +++ b/packages/server/src/api/controllers/row/index.ts @@ -144,9 +144,11 @@ export async function find(ctx: UserCtx) { const { tableId, viewId } = utils.getSourceId(ctx) const sourceId = viewId || tableId const rowId = ctx.params.rowId - - const response = await sdk.rows.find(sourceId, rowId) - ctx.body = response + try { + ctx.body = await sdk.rows.find(sourceId, rowId) + } catch (e) { + ctx.throw(404, "That row couldn't be found") + } } function isDeleteRows(input: any): input is DeleteRows { diff --git a/packages/types/src/documents/app/screen.ts b/packages/types/src/documents/app/screen.ts index f1a119083c..29bc5a97e3 100644 --- a/packages/types/src/documents/app/screen.ts +++ b/packages/types/src/documents/app/screen.ts @@ -15,6 +15,10 @@ export interface ScreenRouting { homeScreen?: boolean } +export enum ScreenVariant { + PDF = "pdf", +} + export interface Screen extends Document { layoutId?: string showNavigation?: boolean @@ -24,6 +28,7 @@ export interface Screen extends Document { name?: string pluginAdded?: boolean onLoad?: EventHandler[] + variant?: ScreenVariant } export interface ScreenRoutesViewOutput extends Document { diff --git a/packages/types/src/documents/app/table/schema.ts b/packages/types/src/documents/app/table/schema.ts index 86e15e4974..e3f9d3af3f 100644 --- a/packages/types/src/documents/app/table/schema.ts +++ b/packages/types/src/documents/app/table/schema.ts @@ -207,6 +207,8 @@ export interface BaseFieldSchema extends UIFieldMetadata { autocolumn?: boolean autoReason?: AutoReason.FOREIGN_KEY subtype?: never + // added when enriching nested JSON fields into schema + nestedJSON?: boolean } interface OtherFieldMetadata extends BaseFieldSchema { diff --git a/yarn.lock b/yarn.lock index 21a42ec7b9..7d54ebcb0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7534,6 +7534,11 @@ a-sync-waterfall@^1.0.0: resolved "https://registry.yarnpkg.com/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz#75b6b6aa72598b497a125e7a2770f14f4c8a1fa7" integrity sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA== +abab@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" + integrity sha512-I+Wi+qiE2kUXyrRhNsWv6XsjUTBJjSoVSctKNBfLG5zG/Xe7Rjbxf13+vqYHNTwHaFU+FtSlVxOCTiMEVtPv0A== + abab@^2.0.3, abab@^2.0.5, abab@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" @@ -7605,6 +7610,13 @@ accepts@^1.3.5, accepts@^1.3.7, accepts@~1.3.4, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn-globals@^1.0.4: + version "1.0.9" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-1.0.9.tgz#55bb5e98691507b74579d0513413217c380c54cf" + integrity sha512-j3/4pkfih8W4NK22gxVSXcEonTpAHOHh0hu5BoZrKcOsW/4oBPxTi4Yk3SAj+FhC1f3+bRTkXdm4019gw1vg9g== + dependencies: + acorn "^2.1.0" + acorn-globals@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" @@ -7643,6 +7655,11 @@ acorn-walk@^8.0.2, acorn-walk@^8.1.1, acorn-walk@^8.2.0: dependencies: acorn "^8.11.0" +acorn@^2.1.0, acorn@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7" + integrity sha512-pXK8ez/pVjqFdAgBkF1YPVRacuLQ9EXBKaKWaeh58WNfMkCmZhOZzu+NtKSPD5PHmCCHheQ5cD29qM1K4QTxIg== + acorn@^5.2.1: version "5.7.4" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" @@ -7958,6 +7975,11 @@ array-differ@^3.0.0: resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== +array-equal@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.2.tgz#a8572e64e822358271250b9156d20d96ef5dec04" + integrity sha512-gUHx76KtnhEgB3HOuFYiCm3FIdEs6ocM2asHvNTkfu/Y09qQVrrVVaOKENmS2KkSaGoxgXNqC+ZVtR/n0MOkSA== + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -8378,6 +8400,11 @@ base62@^1.1.0: resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.8.tgz#1264cb0fb848d875792877479dbe8bae6bae3428" integrity sha512-V6YHUbjLxN1ymqNLb1DPHoU1CpfdL7d2YTIp5W3U4hhoG4hhxNmsFDs66M9EXxBiSEke5Bt5dwdfMwwZF70iLA== +base64-arraybuffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" + integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== + base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -8860,6 +8887,16 @@ caniuse-lite@^1.0.30001449: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001460.tgz#31d2e26f0a2309860ed3eff154e03890d9d851a7" integrity sha512-Bud7abqjvEjipUkpLs4D7gR0l8hBYBHoa+tGtKJHvT2AYzLp1z7EmVkUT4ERpVUfca8S2HGIVs883D8pUH1ZzQ== +canvg@^1.0: + version "1.5.3" + resolved "https://registry.yarnpkg.com/canvg/-/canvg-1.5.3.tgz#aad17915f33368bf8eb80b25d129e3ae922ddc5f" + integrity sha512-7Gn2IuQzvUQWPIuZuFHrzsTM0gkPz2RRT9OcbdmA03jeKk8kltrD8gqUzNX15ghY/4PV5bbe5lmD6yDLDY6Ybg== + dependencies: + jsdom "^8.1.0" + rgbcolor "^1.0.1" + stackblur-canvas "^1.4.1" + xmldom "^0.1.22" + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -8870,6 +8907,11 @@ catering@^2.0.0, catering@^2.1.0: resolved "https://registry.yarnpkg.com/catering/-/catering-2.1.1.tgz#66acba06ed5ee28d5286133982a927de9a04b510" integrity sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w== +cf-blob.js@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/cf-blob.js/-/cf-blob.js-0.0.1.tgz#f5ab7e12e798caf08ccf828c69aba0f063d83f99" + integrity sha512-KkUmNT/rgVK+KehG7cSvbLwMb+OS5Qby6ADB4LP12jtx6rfVvHCdyqFUjAeQnDpGpQNNwvpi0R/tluT2J6P99Q== + chai@^4.3.7: version "4.5.0" resolved "https://registry.yarnpkg.com/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8" @@ -9708,6 +9750,13 @@ crypto-randomuuid@^1.0.0: resolved "https://registry.yarnpkg.com/crypto-randomuuid/-/crypto-randomuuid-1.0.0.tgz#acf583e5e085e867ae23e107ff70279024f9e9e7" integrity sha512-/RC5F4l1SCqD/jazwUF6+t34Cd8zTSAGZ7rvvZu1whZUhD2a5MOGKjSGowoGcpj/cbVZk1ZODIooJEQQq3nNAA== +css-line-break@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0" + integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w== + dependencies: + utrie "^1.0.2" + css-tree@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" @@ -9726,15 +9775,22 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0", cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== -cssom@~0.3.6: - version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== +"cssstyle@>= 0.2.34 < 0.3.0": + version "0.2.37" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" + integrity sha512-FUpKc+1FNBsHUr9IsfSGCovr8VuGOiiuzlgCyppKBjJi2jYTOFLN3oiiNRMIvYqbFzF38mqKj4BgcevzU5/kIA== + dependencies: + cssom "0.3.x" cssstyle@^2.3.0: version "2.3.0" @@ -11046,6 +11102,11 @@ es6-error@^4.0.1, es6-error@^4.1.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== +es6-promise@^4.2.5: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + esbuild-node-externals@^1.14.0: version "1.14.0" resolved "https://registry.yarnpkg.com/esbuild-node-externals/-/esbuild-node-externals-1.14.0.tgz#fc2950c67a068dc2b538fd1381ad7d8e20a6f54d" @@ -11112,6 +11173,18 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escodegen@^1.6.1: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + escodegen@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" @@ -11389,7 +11462,7 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: +estraverse@^4.1.1, estraverse@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== @@ -11790,6 +11863,11 @@ file-entry-cache@^8.0.0: dependencies: flat-cache "^4.0.0" +file-saver@1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-1.3.8.tgz#e68a30c7cb044e2fb362b428469feb291c2e09d8" + integrity sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg== + file-type@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/file-type/-/file-type-11.1.0.tgz#93780f3fed98b599755d846b99a1617a2ad063b8" @@ -12997,6 +13075,23 @@ html-tag@^2.0.0: is-self-closing "^1.0.1" kind-of "^6.0.0" +html2canvas@^1.0.0-alpha.12: + version "1.4.1" + resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543" + integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA== + dependencies: + css-line-break "^2.1.0" + text-segmentation "^1.0.3" + +html2pdf.js@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/html2pdf.js/-/html2pdf.js-0.9.3.tgz#e7fc6143f748ce253670eaae403987342b66b15c" + integrity sha512-M254g3Z+ZsjtQFDxJlU6E8Zgb8xOpCBQQM1lFPn4Lq+myAdWoYtMFnwlVo/eOI9R1cG75+YmMSDQofkugwOV/Q== + dependencies: + es6-promise "^4.2.5" + html2canvas "^1.0.0-alpha.12" + jspdf "1.4.1" + html5-qrcode@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/html5-qrcode/-/html5-qrcode-2.3.8.tgz#0b0cdf7a9926cfd4be530e13a51db47592adfa0d" @@ -13147,7 +13242,7 @@ ical-generator@4.1.0: dependencies: uuid-random "^1.3.2" -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.5: +iconv-lite@0.4.24, iconv-lite@^0.4.13, iconv-lite@^0.4.24, iconv-lite@^0.4.5: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -14636,6 +14731,29 @@ jsdom@^24.1.1: ws "^8.18.0" xml-name-validator "^5.0.0" +jsdom@^8.1.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-8.5.0.tgz#d4d8f5dbf2768635b62a62823b947cf7071ebc98" + integrity sha512-rvWfcn2O8SrXPaX5fTYIfPVwvnbU8DnZkjAXK305wfP67csyaJBhgg0F2aU6imqJ+lZmj9EmrBAXy6rWHf2/9Q== + dependencies: + abab "^1.0.0" + acorn "^2.4.0" + acorn-globals "^1.0.4" + array-equal "^1.0.0" + cssom ">= 0.3.0 < 0.4.0" + cssstyle ">= 0.2.34 < 0.3.0" + escodegen "^1.6.1" + iconv-lite "^0.4.13" + nwmatcher ">= 1.3.7 < 2.0.0" + parse5 "^1.5.1" + request "^2.55.0" + sax "^1.1.4" + symbol-tree ">= 3.1.0 < 4.0.0" + tough-cookie "^2.2.0" + webidl-conversions "^3.0.1" + whatwg-url "^2.0.1" + xml-name-validator ">= 2.0.1 < 3.0.0" + jsesc@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" @@ -14755,6 +14873,17 @@ jsonwebtoken@9.0.2, jsonwebtoken@^9.0.0: ms "^2.1.1" semver "^7.5.4" +jspdf@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jspdf/-/jspdf-1.4.1.tgz#8dbd437986346d65efe20ede5361927666b8e4ca" + integrity sha512-2vYVdrvrQUdKKPyWHw81t1jEYYAJ6uFJ/HtTcGbI4qXIQEdl18dLEuL2wTeSv2GzeQLSgUvEvwsXsszuHK+PTw== + dependencies: + canvg "^1.0" + cf-blob.js "0.0.1" + file-saver "1.3.8" + omggif "1.0.7" + stackblur "^1.0.0" + jsprim@^1.2.2: version "1.4.2" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" @@ -16801,6 +16930,11 @@ nunjucks@^3.2.3: asap "^2.0.3" commander "^5.1.0" +"nwmatcher@>= 1.3.7 < 2.0.0": + version "1.4.4" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" + integrity sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ== + nwsapi@^2.2.0, nwsapi@^2.2.4: version "2.2.12" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.12.tgz#fb6af5c0ec35b27b4581eb3bbad34ec9e5c696f8" @@ -16982,6 +17116,11 @@ obliterator@^1.6.1: resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-1.6.1.tgz#dea03e8ab821f6c4d96a299e17aef6a3af994ef3" integrity sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig== +omggif@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.7.tgz#59d2eecb0263de84635b3feb887c0c9973f1e49d" + integrity sha512-KVVUF85EHKUB9kxxT2D8CksGgfayZKxWtH/+i34zbyDdxFHvsqQs+O756usW7uri2YBD8jE/8GgAsA6wVA1tjg== + omggif@^1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19" @@ -17452,6 +17591,11 @@ parse5@6.0.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +parse5@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + integrity sha512-w2jx/0tJzvgKwZa58sj2vAYq/S/K1QJfIB3cWYea/Iu1scFPDQQ3IQiVZTHWtRBwAjv2Yd7S/xeZf3XqLDb3bA== + parse5@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" @@ -19015,7 +19159,7 @@ remixicon@2.5.0: resolved "https://registry.yarnpkg.com/remixicon/-/remixicon-2.5.0.tgz#b5e245894a1550aa23793f95daceadbf96ad1a41" integrity sha512-q54ra2QutYDZpuSnFjmeagmEiN9IMo56/zz5dDNitzKD23oFRw77cWo4TsrAdmdkPiEn8mxlrTqxnkujDbEGww== -request@^2.88.0: +request@^2.55.0, request@^2.88.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -19180,6 +19324,11 @@ rfdc@^1.3.0, rfdc@^1.3.1: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== +rgbcolor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rgbcolor/-/rgbcolor-1.0.1.tgz#d6505ecdb304a6595da26fa4b43307306775945d" + integrity sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw== + rimraf@3.0.2, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -19405,6 +19554,11 @@ sax@>=0.6.0: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +sax@^1.1.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + saxes@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" @@ -20067,6 +20221,16 @@ stackback@0.0.2: resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== +stackblur-canvas@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/stackblur-canvas/-/stackblur-canvas-1.4.1.tgz#849aa6f94b272ff26f6471fa4130ed1f7e47955b" + integrity sha512-TfbTympL5C1K+F/RizDkMBqH18EkUKU8V+4PphIXR+fWhZwwRi3bekP04gy2TOwOT3R6rJQJXAXFrbcZde7wow== + +stackblur@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stackblur/-/stackblur-1.0.0.tgz#b407a7e05c93b08d66883bb808d7cba3a503f12f" + integrity sha512-K92JX8alrs0pTox5U2arVBqB8tJmak9dh9i4Xausy94TnnGMdLfTn7P2Dp/NOzlmxvEs7lDzeryo8YqOy0BHRQ== + standard-as-callback@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" @@ -20626,7 +20790,7 @@ swagger-parser@10.0.2: dependencies: "@apidevtools/swagger-parser" "10.0.2" -symbol-tree@^3.2.4: +"symbol-tree@>= 3.1.0 < 4.0.0", symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== @@ -20811,6 +20975,13 @@ text-hex@1.0.x: resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== +text-segmentation@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943" + integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw== + dependencies: + utrie "^1.0.2" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -21004,7 +21175,7 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" -tough-cookie@4.1.3, "tough-cookie@^2.3.3 || ^3.0.1 || ^4.0.0", tough-cookie@^4.0.0, tough-cookie@^4.1.2, tough-cookie@^4.1.4, tough-cookie@~2.5.0: +tough-cookie@4.1.3, tough-cookie@^2.2.0, "tough-cookie@^2.3.3 || ^3.0.1 || ^4.0.0", tough-cookie@^4.0.0, tough-cookie@^4.1.2, tough-cookie@^4.1.4, tough-cookie@~2.5.0: version "4.1.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf" integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw== @@ -21616,6 +21787,13 @@ utils-merge@1.0.1, utils-merge@1.x.x, utils-merge@^1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== +utrie@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645" + integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw== + dependencies: + base64-arraybuffer "^1.0.2" + uue@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/uue/-/uue-3.1.2.tgz#e99368414e87200012eb37de4dbaebaa1c742ad2" @@ -21867,7 +22045,7 @@ webfinger@^0.4.2: step "0.0.x" xml2js "0.1.x" -webidl-conversions@^3.0.0: +webidl-conversions@^3.0.0, webidl-conversions@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== @@ -21947,6 +22125,14 @@ whatwg-url@^14.0.0: tr46 "^5.0.0" webidl-conversions "^7.0.0" +whatwg-url@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-2.0.1.tgz#5396b2043f020ee6f704d9c45ea8519e724de659" + integrity sha512-sX+FT4N6iR0ZiqGqyDEKklyfMGR99zvxZD+LQ8IGae5uVGswQ7DOeLPB5KgJY8FzkwSzwqOXLQeVQvtOTSQU9Q== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -22236,6 +22422,11 @@ xhr@^2.4.1: parse-headers "^2.0.0" xtend "^4.0.0" +"xml-name-validator@>= 2.0.1 < 3.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" + integrity sha512-jRKe/iQYMyVJpzPH+3HL97Lgu5HrCfii+qSo+TfjKHtOnvbnvdVfMYrn9Q34YV81M2e5sviJlI6Ko9y+nByzvA== + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" @@ -22274,6 +22465,11 @@ xmlchars@^2.2.0: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmldom@^0.1.22: + version "0.1.31" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" + integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ== + xmlhttprequest-ssl@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz#91360c86b914e67f44dce769180027c0da618c67"