diff --git a/src/block/heading/index.js b/src/block/heading/index.js index 02e0fd2869..17470555cc 100644 --- a/src/block/heading/index.js +++ b/src/block/heading/index.js @@ -17,6 +17,7 @@ import metadata from './block.json' import example from './example' import deprecated from './deprecated' import substitute from './substitute' +import variations from './variations' export const settings = { ...metadata, @@ -33,6 +34,7 @@ export const settings = { save, example, transforms, + variations, merge( attributes, attributesToMerge ) { // Make sure that the selection is always at the end of the text. // @see https://github.com/WordPress/gutenberg/blob/3da717b8d0ac7d7821fc6d0475695ccf3ae2829f/packages/block-library/src/paragraph/index.js diff --git a/src/block/heading/transforms.js b/src/block/heading/transforms.js index 5ee5797f21..6c86d35035 100644 --- a/src/block/heading/transforms.js +++ b/src/block/heading/transforms.js @@ -3,10 +3,16 @@ */ import { createBlock, createBlocksFromInnerBlocksTemplate } from '@wordpress/blocks' +/** + * External dependencies + */ +import { settings } from 'stackable' + /** * Internal dependencies */ import { TEMPLATE as ICON_LABEL_TEMPLATE } from '../icon-label/edit' +import { getHeadingDefaultAttributes } from './util' const transforms = { from: [ @@ -15,7 +21,10 @@ const transforms = { isMultiBlock: true, blocks: [ 'stackable/text' ], transform: attributes => { - return attributes.map( ( { ...attrs } ) => createBlock( 'stackable/heading', { ...attrs } ) ) + return attributes.map( ( { ...attrs } ) => createBlock( 'stackable/heading', { + ...getHeadingDefaultAttributes( settings ), + ...attrs, + } ) ) }, }, { @@ -23,7 +32,10 @@ const transforms = { isMultiBlock: true, blocks: [ 'stackable/subtitle' ], transform: attributes => { - return attributes.map( ( { ...attrs } ) => createBlock( 'stackable/heading', { ...attrs } ) ) + return attributes.map( ( { ...attrs } ) => createBlock( 'stackable/heading', { + ...getHeadingDefaultAttributes( settings ), + ...attrs, + } ) ) }, }, { @@ -32,6 +44,7 @@ const transforms = { blocks: [ 'core/paragraph' ], transform: attributes => { return attributes.map( ( { content } ) => createBlock( 'stackable/heading', { + ...getHeadingDefaultAttributes( settings ), text: content, } ) ) }, @@ -42,6 +55,7 @@ const transforms = { blocks: [ 'core/heading' ], transform: attributes => { return attributes.map( ( { content } ) => createBlock( 'stackable/heading', { + ...getHeadingDefaultAttributes( settings ), text: content, } ) ) }, diff --git a/src/block/heading/util.js b/src/block/heading/util.js new file mode 100644 index 0000000000..540d4afd07 --- /dev/null +++ b/src/block/heading/util.js @@ -0,0 +1,12 @@ +// This helper is called only by the inserter variation and explicit block +// transforms. Parsed existing blocks and Design Library blocks do not call it. +export const getHeadingDefaultAttributes = ( editorSettings = {} ) => { + const isPost = editorSettings.stackable_current_post_type === 'post' + + return { + // Posts and non-post editor screens have separate admin defaults. + useThemeTextMargins: isPost + ? !! editorSettings.stackable_enable_heading_default_theme_margins_posts + : !! editorSettings.stackable_enable_heading_default_theme_margins_non_posts, + } +} diff --git a/src/block/heading/variations.js b/src/block/heading/variations.js new file mode 100644 index 0000000000..4797f47d4c --- /dev/null +++ b/src/block/heading/variations.js @@ -0,0 +1,21 @@ +/** + * External dependencies + */ +import { settings } from 'stackable' + +/** + * Internal dependencies + */ +import { getHeadingDefaultAttributes } from './util' + +// Scope this variation to direct inserter creation. +export const getHeadingVariations = ( editorSettings = {} ) => [ + { + name: 'default', + isDefault: true, + scope: [ 'inserter' ], + attributes: getHeadingDefaultAttributes( editorSettings ), + }, +] + +export default getHeadingVariations( settings ) diff --git a/src/block/icon-list/index.js b/src/block/icon-list/index.js index 33545dd867..b49995b586 100644 --- a/src/block/icon-list/index.js +++ b/src/block/icon-list/index.js @@ -13,6 +13,7 @@ import schema from './schema' import example from './example' import deprecated from './deprecated' import transforms from './transforms' +import variations from './variations' /** * WordPress dependencies @@ -35,4 +36,5 @@ export const settings = { edit, save, transforms, + variations, } diff --git a/src/block/icon-list/transforms.js b/src/block/icon-list/transforms.js index de436fd791..3e1a62439b 100644 --- a/src/block/icon-list/transforms.js +++ b/src/block/icon-list/transforms.js @@ -6,9 +6,15 @@ import { create, split, toHTMLString, } from '@wordpress/rich-text' +/** + * External dependencies + */ +import { settings } from 'stackable' + /** * Internal dependencies */ +import { getIconListDefaultAttributes } from './util' // import { createListBlockFromDOMElement } from './util' @@ -69,6 +75,7 @@ const transforms = { return createBlock( 'stackable/icon-list', { + ...getIconListDefaultAttributes( settings ), anchor: blockAttributes.anchor, }, childBlocks @@ -85,6 +92,7 @@ const transforms = { return createBlock( 'stackable/icon-list', { + ...getIconListDefaultAttributes( settings ), anchor: blockAttributes.anchor, }, childBlocks diff --git a/src/block/icon-list/util.js b/src/block/icon-list/util.js index dec825207e..c76d40adfa 100644 --- a/src/block/icon-list/util.js +++ b/src/block/icon-list/util.js @@ -12,6 +12,13 @@ import { i18n } from 'stackable' // The default icon list SVG. export const DEFAULT_SVG = '' +// This helper is called only by the inserter variation and explicit block +// transforms. Parsed existing blocks and Design Library blocks do not call it. +export const getIconListDefaultAttributes = ( editorSettings = {} ) => ( { + // Preserve the original schema icon when no custom admin default is set. + icon: editorSettings.stackable_icon_list_block_default_icon || DEFAULT_SVG, +} ) + export const IconSvgDef = props => { const { icon, uniqueId } = props @@ -122,4 +129,3 @@ export function migrateTypeToInlineStyle( attributes ) { return attributes } - diff --git a/src/block/icon-list/variations.js b/src/block/icon-list/variations.js new file mode 100644 index 0000000000..4cf4f58421 --- /dev/null +++ b/src/block/icon-list/variations.js @@ -0,0 +1,21 @@ +/** + * External dependencies + */ +import { settings } from 'stackable' + +/** + * Internal dependencies + */ +import { getIconListDefaultAttributes } from './util' + +// Scope this variation to direct inserter creation. +export const getIconListVariations = ( editorSettings = {} ) => [ + { + name: 'default', + isDefault: true, + scope: [ 'inserter' ], + attributes: getIconListDefaultAttributes( editorSettings ), + }, +] + +export default getIconListVariations( settings ) diff --git a/src/components/admin-icon-setting/editor.scss b/src/components/admin-icon-setting/editor.scss new file mode 100644 index 0000000000..c784c04d47 --- /dev/null +++ b/src/components/admin-icon-setting/editor.scss @@ -0,0 +1,3 @@ +.ugb-admin-icon-setting { + --wp-components-color-accent: var(--stk-skin-dark); +} diff --git a/src/components/admin-icon-setting/index.js b/src/components/admin-icon-setting/index.js new file mode 100644 index 0000000000..d27554e708 --- /dev/null +++ b/src/components/admin-icon-setting/index.js @@ -0,0 +1,32 @@ +import AdminBaseSetting from '../admin-base-setting' +import IconControl from '../icon-control' +import classnames from 'classnames' + +const defaultIcon = '' + +const AdminIconSetting = props => { + return ( + + { + props.onChange( icon ) + } } + allowReset={ false } + hasPanelModifiedIndicator={ true } + /> + { props.children } + + ) +} + +AdminIconSetting.defaultProps = { + value: '', + onChange: () => {}, +} + +export default AdminIconSetting diff --git a/src/components/icon-control/index.js b/src/components/icon-control/index.js index 617f5ee98f..94b6cffe2b 100644 --- a/src/components/icon-control/index.js +++ b/src/components/icon-control/index.js @@ -29,7 +29,7 @@ const IconControl = props => { {}, defaultValue: '', + allowReset: true, hasPanelModifiedIndicator: true, } diff --git a/src/editor-settings.php b/src/editor-settings.php index d37c8d3807..c07d859db4 100644 --- a/src/editor-settings.php +++ b/src/editor-settings.php @@ -241,12 +241,62 @@ public function register_settings() { 'default' => false, ) ); + + register_setting( + 'stackable_editor_settings', + 'stackable_enable_heading_default_theme_margins_posts', + array( + 'type' => 'boolean', + 'description' => __( "When enabled, newly added Stackable Heading blocks in Posts will use the theme's default margins automatically.", STACKABLE_I18N ), + 'sanitize_callback' => 'sanitize_text_field', + 'show_in_rest' => true, + 'default' => false, + ) + ); + + register_setting( + 'stackable_editor_settings', + 'stackable_enable_heading_default_theme_margins_non_posts', + array( + 'type' => 'boolean', + 'description' => __( "When enabled, newly added Stackable Heading blocks in non-Post content (Pages and custom post types) will use the theme's default margins automatically.", STACKABLE_I18N ), + 'sanitize_callback' => 'sanitize_text_field', + 'show_in_rest' => true, + 'default' => false, + ) + ); + + register_setting( + 'stackable_editor_settings', + 'stackable_icon_list_block_default_icon', + array( + 'type' => 'string', + 'description' => __( 'Choose the default icon that will be used when adding a new Icon List block.', STACKABLE_I18N ), + 'sanitize_callback' => array( $this, 'sanitize_svg_setting' ), + 'show_in_rest' => true, + 'default' => '', + ) + ); } public function sanitize_array_setting( $input ) { return ! is_array( $input ) ? array( array() ) : $input; } + public function sanitize_svg_setting( $input ) { + if ( empty( $input ) ) { + return ''; + } + + // Remove scripts, event handlers, foreignObject, iframe, embeds + $input = preg_replace( '/<\s*(script|iframe|embed|object|foreignObject)[^>]*>.*?<\s*\/\s*\1\s*>/is', '', $input ); + $input = preg_replace( '/on\w+\s*=\s*"[^"]*"/i', '', $input ); + $input = preg_replace( "/on\w+\s*=\s*'[^']*'/i", '', $input ); + $input = preg_replace( '/javascript:/i', '', $input ); + + return $input; + } + /** * Make our settings available in the editor. * @@ -267,6 +317,14 @@ public function add_settings( $settings ) { $settings['stackable_enable_reset_layout'] = get_option( 'stackable_enable_reset_layout' ); $settings['stackable_enable_save_as_default_block'] = get_option( 'stackable_enable_save_as_default_block' ); $settings['stackable_enable_text_default_block'] = get_option( 'stackable_enable_text_default_block' ); + $settings['stackable_enable_heading_default_theme_margins_posts'] = get_option( 'stackable_enable_heading_default_theme_margins_posts' ); + $settings['stackable_enable_heading_default_theme_margins_non_posts'] = get_option( 'stackable_enable_heading_default_theme_margins_non_posts' ); + $settings['stackable_icon_list_block_default_icon'] = get_option( 'stackable_icon_list_block_default_icon' ); + + // Inserter variations are registered before the block Edit component renders, + // so provide the post type here. + $current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; + $settings['stackable_current_post_type'] = $current_screen && ! empty( $current_screen->post_type ) ? $current_screen->post_type : ''; return $settings; } diff --git a/src/welcome/admin.js b/src/welcome/admin.js index 2091a76a84..bfcb034c59 100644 --- a/src/welcome/admin.js +++ b/src/welcome/admin.js @@ -38,6 +38,7 @@ import AdminSelectSetting from '~stackable/components/admin-select-setting' import AdminToggleSetting from '~stackable/components/admin-toggle-setting' import AdminTextSetting from '~stackable/components/admin-text-setting' import AdminToolbarSetting from '~stackable/components/admin-toolbar-setting' +import AdminIconSetting from '~stackable/components/admin-icon-setting' import { GettingStarted } from './getting-started' import { BLOCK_STATE } from '~stackable/util/blocks' import { BlockToggler, OptimizationSettings } from '~stackable/deprecated/v2/welcome/admin' @@ -81,6 +82,14 @@ const SEARCH_TREE = [ __( 'Nested Wide Block Width', i18n ), ], }, + { + id: 'block-defaults', + children: [ + __( 'Default to Theme Margins for Headings (Posts)', i18n ), + __( 'Default to Theme Margins for Headings (Non-Posts)', i18n ), + __( 'Default Icon for Icon List Block', i18n ), + ], + }, { id: 'editor', children: [ @@ -631,6 +640,7 @@ const EditorSettings = props => { const groups = filteredSearchTree.find( tab => tab.id === 'editor-settings' ).groups const blocks = groups.find( group => group.id === 'blocks' ) + const blockDefaults = groups.find( group => group.id === 'block-defaults' ) const editor = groups.find( group => group.id === 'editor' ) const toolbar = groups.find( group => group.id === 'toolbar' ) const inspector = groups.find( group => group.id === 'inspector' ) @@ -668,6 +678,39 @@ const EditorSettings = props => { /> } + { ( blockDefaults.children === null || blockDefaults.children.length > 0 ) && +
+

{ __( 'Block Defaults', i18n ) }

+

{ __( 'Adjust the default behavior of some Stackable blocks.', i18n ) }

+ { + handleSettingsChange( { stackable_enable_heading_default_theme_margins_posts: value } ) // eslint-disable-line camelcase + } } + help={ __( "When enabled, newly added Stackable Heading blocks in Posts will use the theme's default margins automatically. Existing blocks are not affected.", i18n ) } + /> + { + handleSettingsChange( { stackable_enable_heading_default_theme_margins_non_posts: value } ) // eslint-disable-line camelcase + } } + help={ __( "When enabled, newly added Stackable Heading blocks in non-Post content (Pages and custom post types) will use the theme's default margins automatically. Existing blocks are not affected.", i18n ) } + /> + { + handleSettingsChange( { stackable_icon_list_block_default_icon: value } ) // eslint-disable-line camelcase + } } + help={ __( 'Choose the default icon that will be used when adding a new Icon List block.', i18n ) } + /> +
+ } { ( editor.children === null || editor.children.length > 0 ) &&

{ __( 'Editor', i18n ) }