From 017a6d5e70c166a9490d21fca00bc87723d92a84 Mon Sep 17 00:00:00 2001 From: dmh <5150636+dmh@users.noreply.github.com> Date: Thu, 31 Aug 2023 17:24:01 +0300 Subject: [PATCH] [TASK] add video and embed fields, add NESTED_COMMON_FIELDS types, more typings --- index.js | 8 +++- lib/content-tab-fields.js | 73 +++++++++++++++++++++++++++++- lib/fields.js | 12 ++--- lib/group.js | 9 ++-- lib/init.js | 29 ++++++------ lib/style-tab-fields.js | 8 ++-- lib/types/types.js | 47 +++++++++++++++++-- lib/utils/common-properties.js | 2 + package.json | 4 +- partials/button.js | 2 +- test/1partials-js.module/fields.js | 3 +- 11 files changed, 158 insertions(+), 39 deletions(-) diff --git a/index.js b/index.js index af0c1b7..bbf72da 100644 --- a/index.js +++ b/index.js @@ -21,7 +21,9 @@ import { text, link, richtext, - url + url, + video, + embed } from './lib/content-tab-fields.js' /** @@ -46,7 +48,9 @@ const moduleFields = { gradient, backgroundimage, border, - textalignment + textalignment, + video, + embed } /** * #### Theme fields entry point diff --git a/lib/content-tab-fields.js b/lib/content-tab-fields.js index 6f80446..28ca0f5 100644 --- a/lib/content-tab-fields.js +++ b/lib/content-tab-fields.js @@ -175,4 +175,75 @@ function url (label, name, fields) { return { ...contentOptions, ...otherOptions } } -export { text, link, richtext, url } +/** + * #### Video fields provide content editors with a place to add HubSpot Video to their module content without the need of using rich text fields. + * `only_content_tab` + * + * Some parameters have `default` values, `OMIT` such parameters if you don't want to override them. + * @example + * fi.video('label', 'name', {...options if needed}) + * @memberof Fields + * @param {string} label The text the content creator sees describing the field. May contain spaces. + * @param {string} name Field/HubL variable name, which you'll reference when incorporating the field and its values in the module or theme. `Cannot contain spaces or special characters!` + * @param {Object} [fields] + * @param {boolean} [fields.resizable=true] `true` Whether the video is resizable + * @param {boolean} [fields.show_preview=true] `true` Whether the video is previewable + * @param {boolean} [fields.show_advanced_options=true] `true` Whether the video is advanced options + * @param {Object} [fields.default] Default object --> It's generally a good idea to skip setting the `default` object, as it should be configured by the editor, not the developer + * @param {EDITOR_OPTIONS} [fields.editor_options] Editor options group. + * @param {DISPLAY_CONDITIONS} [fields.display_conditions] Display conditions group. + * @param {REPEATER_OPTIONS} [fields.repeater_options] Repeater options. Keep `occurrence` object empty to just enable a feature. `repeater_options: {occurrence: {}}` + * @param {REPEATER_OPTIONS_DEFAULT} [fields.repeater_options_default] Repeater options with predefined repeated fields and `default` values for each field. + * @returns {COMMON_FIELDS} + */ +function video (label, name, fields) { + const contentOptions = {} + contentOptions.resizable = fields?.resizable ?? true + contentOptions.show_preview = fields?.show_preview ?? true + contentOptions.show_advanced_options = fields?.show_advanced_options ?? true + contentOptions.default = fields?.default ?? {} + + const otherOptions = initField(label, name, 'videoplayer', fields) + return { ...contentOptions, ...otherOptions } +} + +/** + * #### Embed fields allow the user to add a URL from an oEmbed-enabled site or paste in an embed code from another site. + * `only_content_tab` + * + * Some parameters have `default` values, `OMIT` such parameters if you don't want to override them. + * @example + * fi.embed('label', 'name', {...options if needed}) + * @memberof Fields + * @param {string} label The text the content creator sees describing the field. May contain spaces. + * @param {string} name Field/HubL variable name, which you'll reference when incorporating the field and its values in the module or theme. `Cannot contain spaces or special characters!` + * @param {Object} [fields] + * @param {Array<'oembed'|'html'>} [fields.supported_source_types=all] `all` Array of source types that are supported by the field. + * @param {Array<'photo'|'video'|'link'|'rich'>} [fields.supported_oembed_types=all] `all` Array of oEmbed types that are supported by the field. + * @param {boolean} [fields.resizable=true] `true` Whether the embed is resizable + * @param {boolean} [fields.show_preview=true] `true` Whether the embed is previewable + * @param {Array} [fields.supported_media_bridge_providers] Array of provider IDs that determine which Media Bridge providers are available to select content from. + * @param {Object} [fields.default] Default object --> It's generally a good idea to skip setting the `default` object, as it should be configured by the editor, not the developer + * @param {EDITOR_OPTIONS} [fields.editor_options] Editor options group. + * @param {DISPLAY_CONDITIONS} [fields.display_conditions] Display conditions group. + * @param {REPEATER_OPTIONS} [fields.repeater_options] Repeater options. Keep `occurrence` object empty to just enable a feature. `repeater_options: {occurrence: {}}` + * @param {REPEATER_OPTIONS_DEFAULT} [fields.repeater_options_default] Repeater options with predefined repeated fields and `default` values for each field. + * @returns {COMMON_FIELDS} + */ +function embed (label, name, fields) { + const contentOptions = {} + contentOptions.supported_source_types = fields?.supported_source_types ?? ['oembed', 'html'] + contentOptions.supported_oembed_types = fields?.supported_oembed_types ?? ['photo', 'video', 'link', 'rich'] + contentOptions.resizable = fields?.resizable ?? true + contentOptions.show_preview = fields?.show_preview ?? true + contentOptions.supported_media_bridge_providers = fields?.supported_media_bridge_providers ?? [] + const defaultObj = { + source_type: 'oembed' + } + contentOptions.default = { ...defaultObj, ...fields?.default } + + const otherOptions = initField(label, name, 'embed', fields) + return { ...contentOptions, ...otherOptions } +} + +export { text, link, richtext, url, video, embed } diff --git a/lib/fields.js b/lib/fields.js index 1fde6c2..a114b8b 100644 --- a/lib/fields.js +++ b/lib/fields.js @@ -200,8 +200,8 @@ function choice (label, name, fields) { * @param {Object} [fields] * @param {Boolean} [fields.show_opacity=true] `true` Sets whether opacity input is shown. true: the opacity input is shown. false: the opacity input is hidden. If left undefined, opacity input will not display in email modules, but will display in other module types. * @param {Object} [fields.default] Sets the default selected color and opacity --> It's generally a good idea to skip setting the `default` object, as it should be configured by the editor, not the developer. - * @param {String} [fields.default.color] The default color - * @param {Number} [fields.default.opacity] The default opacity + * @param {string?} [fields.default.color] The default color + * @param {number?} [fields.default.opacity] The default opacity * @param {EDITOR_OPTIONS} [fields.editor_options] Editor options group. * @param {DISPLAY_CONDITIONS} [fields.display_conditions] Display conditions group. * @param {REPEATER_OPTIONS} [fields.repeater_options] Repeater options. Keep `occurrence` object empty to just enable a feature. `repeater_options: {occurrence: {}}` @@ -239,13 +239,13 @@ function color (label, name, fields) { * @param {Object} [fields] * @param {Boolean} [fields.load_external_fonts=true] `true` HubSpot automatically loads the selected web font to the page if the font is selected and referenced by HubL in a stylesheet or in a module. Set this to false, if you are already loading the font to the page, that way the font won't load twice. * @param {Object} [fields.default] Font default object --> It's generally a good idea to skip setting the `default` object, as it should be configured by the editor, not the developer - * @param {String} fields.default.font Default family name of the font + * @param {string} [fields.default.font] Default family name of the font * @param {'sans-serif'|'serif'|null} [fields.default.fallback] Default fallback font * @param {'100'|'200'|'300'|'regular'|'500'|'600'|'700'|'800'|'900'|'100italic'|'200italic'|'300italic'|'400italic'|'500italic'|'600italic'|'700italic'|'800italic'|'900italic'|null} [fields.default.variant] Font variant - * @param {'GOOGLE'|'DEFAULT'} fields.default.font_set Default font set + * @param {'GOOGLE'|'DEFAULT'} [fields.default.font_set] Default font set * @param {TEXT_STYLES} [fields.default.styles] Default text styles - * @param {Number} [fields.default.size] Default font size - * @param {String} [fields.default.color] Default font color + * @param {number} [fields.default.size] Default font size + * @param {string?} [fields.default.color] Default font color * @param {'rem'|'px'|'em'|'rem'|'%'|'ex'|'ch'} [fields.default.size_unit] Default font size unit * @param {EDITOR_OPTIONS} [fields.editor_options] Editor options group. * @param {DISPLAY_CONDITIONS} [fields.display_conditions] Display conditions group. diff --git a/lib/group.js b/lib/group.js index 94d697d..42acdf8 100644 --- a/lib/group.js +++ b/lib/group.js @@ -8,6 +8,7 @@ import { EditorOptions, DisplayConditions, RepeaterOptions, RepeaterOptionsDefau * @typedef {TYPES.REPEATER_OPTIONS} REPEATER_OPTIONS {@link REPEATER_OPTIONS} * @typedef {TYPES.REPEATER_OPTIONS_DEFAULT} REPEATER_OPTIONS_DEFAULT {@link REPEATER_OPTIONS_DEFAULT} * @typedef {TYPES.COMMON_FIELDS} COMMON_FIELDS {@link COMMON_FIELDS} + * @typedef {TYPES.NESTED_COMMON_FIELDS} NESTED_COMMON_FIELDS {@link NESTED_COMMON_FIELDS} */ // ************* @@ -32,7 +33,7 @@ import { EditorOptions, DisplayConditions, RepeaterOptions, RepeaterOptionsDefau * @param {DISPLAY_CONDITIONS} [fields.display_conditions] Display conditions group. * @param {REPEATER_OPTIONS} [fields.repeater_options] Repeater options. Keep `occurrence` object empty to just enable a feature. `repeater_options: {occurrence: {}}` * @param {REPEATER_OPTIONS_DEFAULT} [fields.repeater_options_default] Repeater options with predefined repeated fields and `default` values for each field. - * @param {Array} childrens + * @param {Array} childrens * @returns {COMMON_FIELDS} */ function group (label, name, fields, ...childrens) { @@ -49,7 +50,7 @@ function group (label, name, fields, ...childrens) { groupOptions.expanded = fields?.expanded ?? false childrens.forEach(element => { if (Array.isArray(element)) { - requiredFields.children?.push(...element) + requiredFields.children?.push(...element.flat(3)) } else { requiredFields.children?.push(element) } @@ -71,7 +72,7 @@ function group (label, name, fields, ...childrens) { * styleGroup( * fi.alignment('alignment', 'Alignment') * ) - * @param {Array} childrens + * @param {Array} childrens * @returns {COMMON_FIELDS} */ function styleGroup (...childrens) { @@ -87,7 +88,7 @@ function styleGroup (...childrens) { } childrens.forEach(element => { if (Array.isArray(element)) { - baseFields.children?.push(...element) + baseFields.children?.push(...element.flat(3)) } else { baseFields.children?.push(element) } diff --git a/lib/init.js b/lib/init.js index 552e279..26b23d1 100644 --- a/lib/init.js +++ b/lib/init.js @@ -5,6 +5,7 @@ import path from 'node:path' /** * @ignore * @typedef {TYPES.COMMON_FIELDS} COMMON_FIELDS {@link COMMON_FIELDS} + * @typedef {TYPES.NESTED_COMMON_FIELDS} NESTED_COMMON_FIELDS {@link NESTED_COMMON_FIELDS} */ /** @@ -14,21 +15,21 @@ import path from 'node:path' * @param {string} [prefix] prefix * @returns {Array} portal name|names */ -function addIdToObjects (array, prefix = '') { - array.forEach(obj => { - const id = prefix ? `${prefix}.${obj.name}` : obj.name - obj.id = id - if ('children' in obj && Array.isArray(obj.children)) { - addIdToObjects(obj.children, id) - } - }) - return array -} +// function addIdToObjects (array, prefix = '') { +// array.forEach(obj => { +// const id = prefix ? `${prefix}.${obj.name}` : obj.name +// obj.id = id +// if ('children' in obj && Array.isArray(obj.children)) { +// addIdToObjects(obj.children, id) +// } +// }) +// return array +// } /** * #### Initialize and return an array of fields object from fields.js * Combine all the fields and groups objects into one array, and return it. - * @param {Array} fields + * @param {Array} fields * @returns {COMMON_FIELDS[]} * @example * \/* eslint-disable no-unused-vars *\/ @@ -51,15 +52,15 @@ function addIdToObjects (array, prefix = '') { */ function init (...fields) { /** @type {COMMON_FIELDS[]} */ - let correctedFields = [] + const correctedFields = [] fields.forEach(element => { if (Array.isArray(element)) { - correctedFields.push(...element) + correctedFields.push(...element.flat(3)) } else { correctedFields.push(element) } }) - correctedFields = addIdToObjects(correctedFields) + // correctedFields = addIdToObjects(correctedFields) return correctedFields } diff --git a/lib/style-tab-fields.js b/lib/style-tab-fields.js index 4abf00e..630929b 100644 --- a/lib/style-tab-fields.js +++ b/lib/style-tab-fields.js @@ -186,28 +186,28 @@ function backgroundimage (label, name, fields) { * @param {Object} [fields.default] Border default object --> It's generally a good idea to skip setting the `default` object, as it should be configured by the editor, not the developer * @param {Object} [fields.default.top] * @param {Object} fields.default.top.width - * @param {number} fields.default.top.width.value + * @param {number?} fields.default.top.width.value * @param {'px'} fields.default.top.width.units * @param {number} fields.default.top.opacity * @param {'solid'|'double'|'dotted'|'dashed'} fields.default.top.style * @param {string} fields.default.top.color * @param {Object} [fields.default.bottom] * @param {Object} fields.default.bottom.width - * @param {number} fields.default.bottom.width.value + * @param {number?} fields.default.bottom.width.value * @param {'px'} fields.default.bottom.width.units * @param {number} fields.default.bottom.opacity * @param {'solid'|'double'|'dotted'|'dashed'} fields.default.bottom.style * @param {string} fields.default.bottom.color * @param {Object} [fields.default.left] * @param {Object} fields.default.left.width - * @param {number} fields.default.left.width.value + * @param {number?} fields.default.left.width.value * @param {'px'} fields.default.left.width.units * @param {number} fields.default.left.opacity * @param {'solid'|'double'|'dotted'|'dashed'} fields.default.left.style * @param {string} fields.default.left.color * @param {Object} [fields.default.right] * @param {Object} fields.default.right.width - * @param {number} fields.default.right.width.value + * @param {number?} fields.default.right.width.value * @param {'px'} fields.default.right.width.units * @param {number} fields.default.right.opacity * @param {'solid'|'double'|'dotted'|'dashed'} fields.default.right.style diff --git a/lib/types/types.js b/lib/types/types.js index baaccc4..17a0eb6 100644 --- a/lib/types/types.js +++ b/lib/types/types.js @@ -1,3 +1,9 @@ +/** + * #### Inherited values + * @typedef {any} INHERITED_VALUES + * @property {string} [color] + */ + /** * #### HubSpot module editor options * @typedef {Object} EDITOR_OPTIONS @@ -6,6 +12,36 @@ * @property {string} [help_text] Text that displays in the editor within a tooltip on hover to assist the content creator (limit 300 characters). Best used for information that is supplementary but not required to use the field. You can include the following HTML tags (other tags will be ignored on render): a, b, br, em, i, p, small, strong, span. * @property {string} [inline_help_text] Text that displays inline below field's label (limit 400 characters). Best used for information required to use the field. You can include the following HTML tags (other tags will be ignored on render): a, b, br, em, i, p, small, strong, span. * @property {null|'half_width'} [display_width=full_width] `full_width` By default, fields are full-width in the editor. + * @property {Object} [inherited_value] + * @property {INHERITED_VALUES} [inherited_value.property_value_paths] + */ + +/** + * #### Hidden subfields properties + * @typedef {Object} HIDDEN_SUBFIELDS + * @property {boolean} [margin] + * @property {boolean} [padding] + * @property {boolean} [bold] + * @property {boolean} [italic] + * @property {boolean} [underline] + * @property {boolean} [color] + * @property {boolean} [size] + */ + +/** + * #### Advanced visibility criteria + * @typedef {Object} ADVANCED_VISIBILITY_CRITERIA + * @property {string} [controlling_field_path] The path of the field that controls the display condition. + * @property {string} [controlling_value_regex] The value in the controlling field that needs to be met to display the field. + * @property {'NOT_EQUAL'|'EQUAL'|'EMPTY'|'NOT_EMPTY'|'MATCHES_REGEX'} [operator] The operator that defines how the `controlling_value_regex` value needs to be met. + * @property {?string} [property] Sets visibility based on a specific property of the target field. For example, you can enable visibility when an image field's src property is equal to a specific value. By default, if no value is provided for this field, visibility is based on the stringified value of `controlling_value_regex`. + */ + +/** + * #### Advanced visibility + * @typedef {Object} ADVANCED_VISIBILITY + * @property {'OR'|'AND'} [advanced_visibility.boolean_operator=OR] `OR` The boolean operator for the conditional criteria. Can be `AND` or `OR` + * @property {Array} [advanced_visibility.criteria] An array of visibility objects that defines the conditional criteria that needs to be met for the field to display. */ /** @@ -13,16 +49,15 @@ * @description Occasionally, you may want to show a field or field group, but only under specfic conditions. Examples might include: `Address fields that correspond to a specific country` or `An image description that is only entered after an image is selected` * @typedef {Object} DISPLAY_CONDITIONS * @property {Object} [visibility] Sets the field's display conditions. For example, you can set a field to only display when another checkbox field has been selected + * @property {HIDDEN_SUBFIELDS} [visibility.hidden_subfields] Hide subfields of a field. For example, you can hide the margin from spacing field and show only the padding. Ony works for Theme settings fields. * @property {?string} [visibility.controlling_field_path] The path of the field that controls the display condition. If the field is not nested inside a field group, use the field's name (i.e. `field_name`). For fields nested in groups, the path should match its grouping structure, separated by a period. For example: `field_group_name.field_name` or `parent_group.child_group.field_name` * @property {?string} [visibility.controlling_value_regex] The regular expression in the controlling field that needs to be present for the field to display. The regex must match the entire string (not a subset) and is run case-sensitively. * @property {'NOT_EQUAL'|'EQUAL'|'EMPTY'|'NOT_EMPTY'|'MATCHES_REGEX'} [visibility.operator=EMPTY] `EMPTY` The operator that defines how the `controlling_value_regex` value needs to be met * @property {?string} [visibility.property] Sets visibility based on a specific property of the target field. For example, you can enable visibility when an image field's src property is equal to a specific value. By default, if no value is provided for this field, visibility is based on the stringified value of `controlling_value_regex`. * @property {Object} [advanced_visibility] To include multiple criteria with multiple operators, as well as order of operations, you can use advanced_visibility. * @property {'OR'|'AND'} [advanced_visibility.boolean_operator=OR] `OR` The boolean operator for the conditional criteria. Can be `AND` or `OR` - * @property {Object[]} [advanced_visibility.criteria] An array of visibility objects that defines the conditional criteria that needs to be met for the field to display. - * @property {string} [advanced_visibility.criteria[].controlling_field_path] The path of the field that controls the display condition. - * @property {string} [advanced_visibility.criteria[].controlling_value_regex] The value in the controlling field that needs to be met to display the field. - * @property {'NOT_EQUAL'|'EQUAL'|'EMPTY'|'NOT_EMPTY'|'MATCHES_REGEX'} [advanced_visibility.criteria[].operator] The operator that defines how the `controlling_value_regex` value needs to be met. + * @property {Array} [advanced_visibility.criteria] An array of visibility objects that defines the conditional criteria that needs to be met for the field to display. + * @property {Array} [advanced_visibility.children] */ /** @@ -55,7 +90,11 @@ * @property {string} [tab] * @property {boolean} [locked] * @property {Array} [children] + */ +/** + * #### nested common fields + * @typedef { COMMON_FIELDS | COMMON_FIELDS[] | Array | Array> } NESTED_COMMON_FIELDS */ export default {} diff --git a/lib/utils/common-properties.js b/lib/utils/common-properties.js index f6a159b..fe7dd8b 100644 --- a/lib/utils/common-properties.js +++ b/lib/utils/common-properties.js @@ -28,6 +28,7 @@ function EditorOptions (fields) { if ('inline_help_text' in fields) { this.inline_help_text = fields.inline_help_text } + this.inherited_value = fields?.inherited_value ?? {} return this } @@ -45,6 +46,7 @@ function DisplayConditions (fields) { this.visibility.controlling_value_regex = fields.visibility?.controlling_value_regex ?? null this.visibility.operator = fields.visibility?.operator ?? 'EMPTY' this.visibility.property = fields.visibility?.property ?? null + this.visibility.hidden_subfields = fields.visibility?.hidden_subfields ?? {} } // advanced visibility if ('advanced_visibility' in fields) { diff --git a/package.json b/package.json index a43648c..e49153a 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,14 @@ }, "license": "MIT", "author": "Resultify", - "main": "index.js", "files": [ "./index.js", "lib/", "partials/" ], + "exports": { + ".": "./index.js" + }, "repository": { "type": "git", "url": "https://github.com/Resultify/hubspot-fields-js.git" diff --git a/partials/button.js b/partials/button.js index 44a775d..bb2398f 100644 --- a/partials/button.js +++ b/partials/button.js @@ -52,5 +52,5 @@ const buttonGroup = [ ) ] -// console.log(buttonGroup) + export { buttonGroup } diff --git a/test/1partials-js.module/fields.js b/test/1partials-js.module/fields.js index d3d8fe4..4b2ad0f 100644 --- a/test/1partials-js.module/fields.js +++ b/test/1partials-js.module/fields.js @@ -11,6 +11,7 @@ import { pa } from '../../partials/all.js' writeJson(init( fi.text('Text', 'text', { allow_new_line: true }), group('Group2', 'group2', {}, + pa.buttonGroup, fi.text('Text', 'text_field') ), pa.buttonGroup, @@ -33,5 +34,3 @@ writeJson(init( pa.textStyle, pa.textStyle2 )) - -// console.log(pa.imageGroup)