diff --git a/.dev/sass/layouts/_hero.scss b/.dev/sass/layouts/_hero.scss index c27244bc..ec0444f4 100644 --- a/.dev/sass/layouts/_hero.scss +++ b/.dev/sass/layouts/_hero.scss @@ -36,6 +36,10 @@ list-style: none; } } + + b, strong { + font-weight: 600; + } } label, input, select, textarea { diff --git a/assets/js/admin/hero-text-widget.js b/assets/js/admin/hero-text-widget.js new file mode 100755 index 00000000..42963709 --- /dev/null +++ b/assets/js/admin/hero-text-widget.js @@ -0,0 +1,169 @@ +/* global jQuery, primer_hero_text_widget */ + +( function( $ ) { + + var link = { + + init: function( input ) { + + if ( ! ( $ && $.ui && $.ui.autocomplete ) ) { + + return; + + } + + var $input = $( input ), + cache, last; + + $input.on( 'keydown', function() { + + $input.removeAttr( 'aria-activedescendant' ); + + } ) + .autocomplete( { + + source: function( request, response ) { + + if ( last === request.term ) { + + response( cache ); + + return; + + } + + if ( /^https?:/.test( request.term ) || request.term.indexOf( '.' ) !== -1 ) { + + return response(); + + } + + $.post( + window.ajaxurl, + { + action: 'wp-link-ajax', + page: 1, + search: request.term, + _ajax_linking_nonce: primer_hero_text_widget._ajax_linking_nonce + }, + function( data ) { + + cache = data; + response( data ); + + }, + 'json' + ); + + last = request.term; + + }, + focus: function( event, ui ) { + + $input.attr( 'aria-activedescendant', 'mce-wp-autocomplete-' + ui.item.ID ); + + /* + * Don't empty the URL input field, when using the arrow keys to + * highlight items. See api.jqueryui.com/autocomplete/#event-focus + */ + event.preventDefault(); + + }, + select: function( event, ui ) { + + $input.val( ui.item.permalink ); + + // This is for the customizer. + $input.trigger( 'change' ); + + return false; + + }, + open: function() { + + $input.attr( 'aria-expanded', 'true' ); + + }, + close: function() { + + $input.attr( 'aria-expanded', 'false' ); + + }, + minLength: 2, + position: { + + my: 'left top+2' + + } + + } ).autocomplete( 'instance' )._renderItem = function( ul, item ) { + + return $( '
  • ' ) + .append( '' + item.title + ' ' + item.info + '' ) + .appendTo( ul ); + + }; + + $input.attr( { + 'role': 'combobox', + 'aria-autocomplete': 'list', + 'aria-expanded': 'false', + 'aria-owns': $input.autocomplete( 'widget' ).attr( 'id' ) + } ) + .on( 'focus', function() { + + var inputValue = $input.val(); + + /* + * Don't trigger a search if the URL field already has a link or is empty. + * Also, avoids screen readers announce `No search results`. + */ + if ( inputValue && ! /^https?:/.test( inputValue ) ) { + + $input.autocomplete( 'search' ); + + } + + } ) + // Returns a jQuery object containing the menu element. + .autocomplete( 'widget' ) + .attr( 'role', 'listbox' ) + .removeAttr( 'tabindex' ) // Remove the `tabindex=0` attribute added by jQuery UI. + /* + * Looks like Safari and VoiceOver need an `aria-selected` attribute. See ticket #33301. + * The `menufocus` and `menublur` events are the same events used to add and remove + * the `ui-state-focus` CSS class on the menu items. See jQuery UI Menu Widget. + */ + .on( 'menufocus', function( event, ui ) { + + ui.item.attr( 'aria-selected', 'true' ); + + } ) + .on( 'menublur', function() { + + /* + * The `menublur` event returns an object where the item is `null` + * so we need to find the active item with other means. + */ + $( this ).find( '[aria-selected="true"]' ).removeAttr( 'aria-selected' ); + + } ); + + } // end init. + + }; + + function addAutocomplete() { + + $( '.primer-hero-text-widget input.link' ).each( function() { + + link.init( this ); + + } ); + + } + + $( document ).ready( addAutocomplete ); + $( document ).on( 'primer.widgets.change', addAutocomplete ); + +} )( jQuery ); diff --git a/assets/js/admin/hero-text-widget.min.js b/assets/js/admin/hero-text-widget.min.js new file mode 100644 index 00000000..781b345b --- /dev/null +++ b/assets/js/admin/hero-text-widget.min.js @@ -0,0 +1 @@ +!function(a){function b(){a(".primer-hero-text-widget input.link").each(function(){c.init(this)})}var c={init:function(b){if(a&&a.ui&&a.ui.autocomplete){var c,d,e=a(b);e.on("keydown",function(){e.removeAttr("aria-activedescendant")}).autocomplete({source:function(b,e){return d===b.term?void e(c):/^https?:/.test(b.term)||b.term.indexOf(".")!==-1?e():(a.post(window.ajaxurl,{action:"wp-link-ajax",page:1,search:b.term,_ajax_linking_nonce:primer_hero_text_widget._ajax_linking_nonce},function(a){c=a,e(a)},"json"),void(d=b.term))},focus:function(a,b){e.attr("aria-activedescendant","mce-wp-autocomplete-"+b.item.ID),a.preventDefault()},select:function(a,b){return e.val(b.item.permalink),e.trigger("change"),!1},open:function(){e.attr("aria-expanded","true")},close:function(){e.attr("aria-expanded","false")},minLength:2,position:{my:"left top+2"}}).autocomplete("instance")._renderItem=function(b,c){return a('
  • ').append(""+c.title+' '+c.info+"").appendTo(b)},e.attr({role:"combobox","aria-autocomplete":"list","aria-expanded":"false","aria-owns":e.autocomplete("widget").attr("id")}).on("focus",function(){var a=e.val();a&&!/^https?:/.test(a)&&e.autocomplete("search")}).autocomplete("widget").attr("role","listbox").removeAttr("tabindex").on("menufocus",function(a,b){b.item.attr("aria-selected","true")}).on("menublur",function(){a(this).find('[aria-selected="true"]').removeAttr("aria-selected")})}}};a(document).ready(b),a(document).on("primer.widgets.change",b)}(jQuery); \ No newline at end of file diff --git a/functions.php b/functions.php index 3cde142b..e4131a16 100755 --- a/functions.php +++ b/functions.php @@ -436,6 +436,21 @@ function primer_register_sidebars() { } add_action( 'widgets_init', 'primer_register_sidebars' ); +/** + * Register Primer widgets. + * + * @link http://codex.wordpress.org/Function_Reference/register_widget + * @since NEXT + */ +function primer_register_widgets() { + + require_once get_template_directory() . '/inc/hero-text-widget.php'; + + register_widget( 'Primer_Hero_Text_Widget' ); + +} +add_action( 'widgets_init', 'primer_register_widgets' ); + /** * Enqueue theme scripts and styles. * diff --git a/inc/hero-text-widget.php b/inc/hero-text-widget.php new file mode 100755 index 00000000..c36fb658 --- /dev/null +++ b/inc/hero-text-widget.php @@ -0,0 +1,263 @@ + true, + 'classname' => 'widget_text primer-widgets primer-hero-text-widget', + 'description' => sprintf( + esc_html_x( "A %s theme widget designed for the Hero area on your site's front page.", 'theme name', 'primer' ), + esc_html( $this->get_current_theme_name() ) + ), + ); + + parent::__construct( 'primer-hero-text', esc_html_x( 'Hero Text', 'the widget title', 'primer' ), $widget_options ); + + add_action( 'admin_init', array( $this, 'register_scripts' ) ); + + } + + /** + * Display the widget on the front-end. + * + * @since NEXT + * + * @param array $args Display arguments including `before_title`, `after_title`, `before_widget`, and `after_widget`. + * @param array $instance The settings for the particular instance of the widget. + */ + public function widget( $args, $instance ) { + + /** + * Filter the widget title. + * + * @link https://developer.wordpress.org/reference/hooks/widget_title/ + * @since NEXT + * + * @param array $instance An array of the widget's settings. + * @param string $id_base The widget ID. + * + * @var string + */ + $title = ! empty( $instance['title'] ) ? (string) apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ) : null; + + /** + * Filter the widget text. + * + * @link https://developer.wordpress.org/reference/hooks/widget_text/ + * @since NEXT + * + * @param array $instance Array of settings for the current widget. + * @param Primer_Hero_Text_Widget $this Current Hero Text widget instance. + * + * @var string + */ + $text = ! empty( $instance['text'] ) ? (string) apply_filters( 'widget_text', $instance['text'], $instance, $this ) : null; + + $button_text = ! empty( $instance['button_text'] ) ? $instance['button_text'] : null; + $button_link = ! empty( $instance['button_link'] ) ? $instance['button_link'] : null; + + // The `button_link` can be empty. + if ( ! $title && ! $text && ! $button_text ) { + + return; + + } + + echo $args['before_widget']; // xss ok. + + ?> +
    + + + + + + + + + + + + + + + +

    + + + +
    + + + + + +
    + +

    + + +

    + +

    + + +

    + +

    + + +

    + +

    + + +

    + +
    + wp_create_nonce( 'internal-linking' ), + ) + ); + + } + + /** + * Print widget admin scripts. + * + * @action admin_print_footer_scripts + * @action customize_controls_print_footer_scripts + * @since NEXT + */ + public function print_scripts() { + + wp_print_scripts( 'primer-admin-hero-text-widget' ); + + } + + /** + * Return the current theme name. + * + * Looks for the `current_theme` option first, and if not + * present will fetch it using `wp_get_theme()`. + * + * @since NEXT + * + * @return string + */ + protected function get_current_theme_name() { + + if ( $current_theme = get_option( 'current_theme' ) ) { + + return $current_theme; + + } + + $theme = wp_get_theme(); + + return $theme->get( 'Name' ); + + } + +} diff --git a/style-rtl.css b/style-rtl.css index d9047b9a..ed4d8aa6 100644 --- a/style-rtl.css +++ b/style-rtl.css @@ -1681,6 +1681,8 @@ body.no-max-width .page-title-container .page-header { padding-right: 0; } .hero .widget ul li, .hero .widget ol li { list-style: none; } + .hero .widget b, .hero .widget strong { + font-weight: 600; } .hero label, .hero input, .hero select, .hero textarea { display: inline; width: auto; } diff --git a/style.css b/style.css index 63e363aa..8989bd0c 100644 --- a/style.css +++ b/style.css @@ -1681,6 +1681,8 @@ body.no-max-width .page-title-container .page-header { padding-left: 0; } .hero .widget ul li, .hero .widget ol li { list-style: none; } + .hero .widget b, .hero .widget strong { + font-weight: 600; } .hero label, .hero input, .hero select, .hero textarea { display: inline; width: auto; }