Plugin Directory

Changeset 1430660


Ignore:
Timestamp:
06/05/2016 03:10:19 AM (10 years ago)
Author:
valendesigns
Message:

Committing 0.6.0 to trunk

Location:
customize-posts/trunk
Files:
4 added
1 deleted
29 edited

Legend:

Unmodified
Added
Removed
  • customize-posts/trunk/css/customize-posts.css

    r1406933 r1430660  
    1 /* In case Customize Setting Validation is not active */
    2 div.customize-setting-validation-message.error {
    3     display: none;
    4 }
    51
    62.customize-posts-panel-notice {
     
    117}
    128
     9.customize-posts-trashed {
     10    font-weight: normal;
     11}
     12
     13#customize-controls .accordion-section.is-trashed .accordion-section-title {
     14    opacity: .5;
     15}
     16
    1317.customize-section-title > div.customize-setting-validation-message {
    1418    border-top: 1px solid #ddd;
     
    1620    margin-bottom: 0;
    1721}
    18 .customize-setting-validation-message .override-post-conflict {
     22
     23.customize-control-notifications-container .override-post-conflict {
    1924    margin-left: 1ex;
    2025    float: right;
     26}
     27
     28#customize-controls .control-panel-posts .customize-info {
     29    margin-bottom: 0;
     30}
     31
     32#customize-controls .customize-posts-navigation {
     33    position: absolute;
     34    top: 4px;
     35    right: 1px;
     36    padding: 20px;
     37    width: 20px;
     38    height: 20px;
     39    cursor: pointer;
     40    -webkit-appearance: none;
     41    background: transparent;
     42    color: #555;
     43    border: none;
     44}
     45
     46#customize-controls .customize-posts-navigation:before {
     47    padding: 4px;
     48    position: absolute;
     49    top: 6px;
     50    left: 6px;
     51    -webkit-border-radius: 100%;
     52    border-radius: 100%;
     53}
     54
     55#customize-controls .customize-posts-navigation:focus:before {
     56    -webkit-box-shadow:
     57        0 0 0 1px #5b9dd9,
     58        0 0 2px 1px rgba(30, 140, 190, .8);
     59    box-shadow:
     60        0 0 0 1px #5b9dd9,
     61        0 0 2px 1px rgba(30, 140, 190, .8);
     62}
     63
     64#customize-controls .customize-posts-navigation:focus,
     65#customize-controls .customize-posts-navigation:hover {
     66    color: #0073aa;
     67}
     68
     69#customize-controls .customize-posts-navigation:focus {
     70    outline: none;
     71}
     72
     73.customize-posts-add-new {
     74    padding: 15px 12px;
     75    margin: 0;
     76    overflow: hidden;
     77}
     78
     79.customize-posts-add-new .add-new-post-stub {
     80    float: right;
     81}
     82
     83.customize-posts-add-new .add-new-post-stub:before {
     84    content: "\f132";
     85    display: inline-block;
     86    position: relative;
     87    left: -2px;
     88    top: -1px;
     89    font: normal 20px/1 dashicons;
     90    vertical-align: middle;
     91    -webkit-transition: all 0.2s;
     92    transition: all 0.2s;
     93    -webkit-font-smoothing: antialiased;
     94    -moz-osx-font-smoothing: grayscale;
    2195}
    2296
     
    31105    background: #f1f1f1;
    32106    display: block;
    33     -webkit-transition: bottom 0.2s;
    34     transition: bottom 0.2s;
    35 
    36     /* @todo This should have visibility:hidden to ensure accessible when closed. */
     107    -webkit-transition: all 0.2s;
     108    transition: all 0.2s;
     109    visibility: hidden;
     110}
     111 
     112body.customize-posts-content-editor-pane-resize #customize-preview,
     113body.customize-posts-content-editor-pane-resize #customize-posts-content-editor-pane {
     114    -webkit-transition: none;
     115    transition: none;
    37116}
    38117
     
    43122body.customize-posts-content-editor-pane-open #customize-posts-content-editor-pane {
    44123    bottom: 0;
     124    visibility: inherit;
    45125}
    46126
     
    75155}
    76156
     157/* vertical resize bar */
     158#customize-posts-content-editor-dragbar {
     159    top: 0;
     160    cursor: row-resize;
     161    display: block;
     162    height: 4px;
     163    position: absolute;
     164    width: 100%;
     165    z-index: 21;
     166}
     167
     168body.customize-posts-content-editor-pane-resize #customize-preview:before {
     169    top: 0;
     170    right: 0;
     171    bottom: 0;
     172    left: 0;
     173    position: absolute;
     174    height: 100%;
     175    width: 100%;
     176    z-index: 999999;
     177}
     178
    77179/* @todo Mobile support for rich text editor */
    78180
     
    96198}
    97199
    98 @media screen and ( max-width: 782px ) {
    99     body.customize-posts-content-editor-pane-open #customize-theme-controls [id^=accordion-panel-posts] ul.accordion-section-content {
    100         padding-bottom: 300px;
    101     }
    102 }
     200.customize-posts-content-editor-pane-open .wp-full-overlay.collapsed .wp-full-overlay-sidebar {
     201    z-index: 30;
     202}
     203.customize-posts-content-editor-pane-open .wp-full-overlay.collapsed .collapse-sidebar {
     204    bottom: 308px;
     205}
     206.customize-posts-content-editor-pane-open .wp-full-overlay.expanded .collapse-sidebar {
     207    bottom: 0 !important;
     208}
     209.customize-posts-content-editor-pane-resize .wp-full-overlay.collapsed .collapse-sidebar {
     210    -webkit-transition: none;
     211    transition: none;
     212}
     213.customize-posts-content-editor-pane-open.mce-fullscreen .wp-full-overlay.collapsed .collapse-sidebar {
     214    bottom: 8px !important;
     215}
  • customize-posts/trunk/css/customize-posts.min.css

    r1406933 r1430660  
    1 div.customize-setting-validation-message.error{display:none}.customize-posts-panel-notice{color:#555;background:#fff;padding:12px 15px;border-top:1px solid #ddd}.customize-section-title>div.customize-setting-validation-message{border-top:1px solid #ddd;margin-top:0;margin-bottom:0}.customize-setting-validation-message .override-post-conflict{margin-left:1ex;float:right}#customize-posts-content-editor-pane{border-top:solid 1px #ddd;position:absolute;height:300px;bottom:-301px;right:0;left:0;z-index:20;background:#f1f1f1;display:block;-webkit-transition:bottom .2s;transition:bottom .2s}body.mce-fullscreen.customize-posts-content-editor-pane-open #customize-posts-content-editor-pane{top:0}body.customize-posts-content-editor-pane-open #customize-posts-content-editor-pane{bottom:0}#customize-posts-content-editor-pane .wp-editor-tools{padding-top:5px;padding-right:10px}#customize-posts-content-editor-pane .wp-media-buttons{padding-left:5px}#customize-preview{height:auto}body.customize-posts-content-editor-pane-open #customize-preview{bottom:300px}body.mce-fullscreen #customize-preview{bottom:0}body.mce-fullscreen.customize-posts-content-editor-pane-open div.mce-fullscreen{position:relative;left:0}#wp-customize-posts-content-editor-container{border-left:0}.wp-customizer .ui-autocomplete.wplink-autocomplete{z-index:500110}.wp-customizer #wp-link-wrap{z-index:500105}.wp-customizer #wp_editbtns,.wp-customizer #wp_gallerybtns{z-index:500020}.wp-customizer #TB_overlay,.wp-customizer #TB_window{z-index:500050}.wp-customizer .mce-panel,.wp-customizer .mce-tooltip{z-index:500100!important}@media screen and (max-width:782px){body.customize-posts-content-editor-pane-open #customize-theme-controls [id^=accordion-panel-posts] ul.accordion-section-content{padding-bottom:300px}}
     1.customize-posts-panel-notice{color:#555;background:#fff;padding:12px 15px;border-top:1px solid #ddd}.customize-posts-trashed{font-weight:400}#customize-controls .accordion-section.is-trashed .accordion-section-title{opacity:.5}.customize-section-title>div.customize-setting-validation-message{border-top:1px solid #ddd;margin-top:0;margin-bottom:0}.customize-control-notifications-container .override-post-conflict{margin-left:1ex;float:right}#customize-controls .control-panel-posts .customize-info{margin-bottom:0}#customize-controls .customize-posts-navigation{position:absolute;top:4px;right:1px;padding:20px;width:20px;height:20px;cursor:pointer;-webkit-appearance:none;background:0 0;color:#555;border:none}#customize-controls .customize-posts-navigation:before{padding:4px;position:absolute;top:6px;left:6px;-webkit-border-radius:100%;border-radius:100%}#customize-controls .customize-posts-navigation:focus:before{-webkit-box-shadow:0 0 0 1px #5b9dd9,0 0 2px 1px rgba(30,140,190,.8);box-shadow:0 0 0 1px #5b9dd9,0 0 2px 1px rgba(30,140,190,.8)}#customize-controls .customize-posts-navigation:focus,#customize-controls .customize-posts-navigation:hover{color:#0073aa}#customize-controls .customize-posts-navigation:focus{outline:0}.customize-posts-add-new{padding:15px 12px;margin:0;overflow:hidden}.customize-posts-add-new .add-new-post-stub{float:right}.customize-posts-add-new .add-new-post-stub:before{content:"\f132";display:inline-block;position:relative;left:-2px;top:-1px;font:400 20px/1 dashicons;vertical-align:middle;-webkit-transition:all .2s;transition:all .2s;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#customize-posts-content-editor-pane{border-top:solid 1px #ddd;position:absolute;height:300px;bottom:-301px;right:0;left:0;z-index:20;background:#f1f1f1;display:block;-webkit-transition:all .2s;transition:all .2s;visibility:hidden}body.customize-posts-content-editor-pane-resize #customize-posts-content-editor-pane,body.customize-posts-content-editor-pane-resize #customize-preview{-webkit-transition:none;transition:none}body.mce-fullscreen.customize-posts-content-editor-pane-open #customize-posts-content-editor-pane{top:0}body.customize-posts-content-editor-pane-open #customize-posts-content-editor-pane{bottom:0;visibility:inherit}#customize-posts-content-editor-pane .wp-editor-tools{padding-top:5px;padding-right:10px}#customize-posts-content-editor-pane .wp-media-buttons{padding-left:5px}#customize-preview{height:auto}body.customize-posts-content-editor-pane-open #customize-preview{bottom:300px}body.mce-fullscreen #customize-preview{bottom:0}body.mce-fullscreen.customize-posts-content-editor-pane-open div.mce-fullscreen{position:relative;left:0}#wp-customize-posts-content-editor-container{border-left:0}#customize-posts-content-editor-dragbar{top:0;cursor:row-resize;display:block;height:4px;position:absolute;width:100%;z-index:21}body.customize-posts-content-editor-pane-resize #customize-preview:before{top:0;right:0;bottom:0;left:0;position:absolute;height:100%;width:100%;z-index:999999}.wp-customizer .ui-autocomplete.wplink-autocomplete{z-index:500110}.wp-customizer #wp-link-wrap{z-index:500105}.wp-customizer #wp_editbtns,.wp-customizer #wp_gallerybtns{z-index:500020}.wp-customizer #TB_overlay,.wp-customizer #TB_window{z-index:500050}.wp-customizer .mce-panel,.wp-customizer .mce-tooltip{z-index:500100!important}.customize-posts-content-editor-pane-open .wp-full-overlay.collapsed .wp-full-overlay-sidebar{z-index:30}.customize-posts-content-editor-pane-open .wp-full-overlay.collapsed .collapse-sidebar{bottom:308px}.customize-posts-content-editor-pane-open .wp-full-overlay.expanded .collapse-sidebar{bottom:0!important}.customize-posts-content-editor-pane-resize .wp-full-overlay.collapsed .collapse-sidebar{-webkit-transition:none;transition:none}.customize-posts-content-editor-pane-open.mce-fullscreen .wp-full-overlay.collapsed .collapse-sidebar{bottom:8px!important}
  • customize-posts/trunk/customize-posts.php

    r1406933 r1430660  
    44 * Description: Manage posts and postmeta via the Customizer. Works best in conjunction with the <a href="https://wordpress.org/plugins/customize-setting-validation/">Customize Setting Validation</a> plugin.
    55 * Plugin URI: https://github.com/xwp/wp-customize-posts/
    6  * Version: 0.5.0
     6 * Version: 0.6.0
    77 * Author: XWP
    88 * Author URI: https://make.xwp.co/
  • customize-posts/trunk/js/customize-dynamic-control.js

    r1406933 r1430660  
    2828            }
    2929
     30            control.propertyElements = [];
    3031            api.Control.prototype.initialize.call( control, id, args );
    31             control.propertyElements = [];
    3232        },
    3333
  • customize-posts/trunk/js/customize-dynamic-control.min.js

    r1406933 r1430660  
    1 !function(a,b){"use strict";a.DynamicControl=a.Control.extend({initialize:function(c,d){var e,f=this;e=d||{},e.params=e.params||{},e.params.type||(e.params.type="dynamic"),e.params.content||(e.params.content=b("<li></li>"),e.params.content.attr("id","customize-control-"+c.replace(/]/g,"").replace(/\[/g,"-")),e.params.content.attr("class","customize-control customize-control-"+e.params.type)),a.Control.prototype.initialize.call(f,c,e),f.propertyElements=[]},_setUpSettingRootLinks:function(){var c,d,e;c=this,d=c.container.find("[data-customize-setting-link]"),e={},d.each(function(){var f,g=b(this);if(g.is(":radio")){if(f=g.prop("name"),e[f])return;e[f]=!0,g=d.filter('[name="'+f+'"]')}a(g.data("customizeSettingLink"),function(b){var d=new a.Element(g);c.elements.push(d),d.sync(b),d.set(b())})})},_setUpSettingPropertyLinks:function(){var c,d,e=this;e.setting&&(c=e.container.find("[data-customize-setting-property-link]"),d={},c.each(function(){var f,g,h=b(this),i=h.data("customizeSettingPropertyLink");if(h.is(":radio")){if(f=h.prop("name"),d[f])return;d[f]=!0,h=c.filter('[name="'+f+'"]')}g=new a.Element(h),e.propertyElements.push(g),g.set(e.setting()[i]),g.bind(function(a){var b=e.setting();a!==b[i]&&(b=_.clone(b),b[i]=a,e.setting.set(b))}),e.setting.bind(function(a){a[i]!==g.get()&&g.set(a[i])})}))},ready:function(){var b=this;b._setUpSettingRootLinks(),b._setUpSettingPropertyLinks(),a.Control.prototype.ready.call(b),b.deferred.embedded.done(function(){})},embed:function(){var b=this,c=b.section();c&&a.section(c,function(c){c.expanded()||a.settings.autofocus.control===b.id?b.actuallyEmbed():c.expanded.bind(function(a){a&&b.actuallyEmbed()})})},actuallyEmbed:function(){var a=this;"resolved"!==a.deferred.embedded.state()&&(a.renderContent(),a.deferred.embedded.resolve())},focus:function(b){var c=this;c.actuallyEmbed(),a.Control.prototype.focus.call(c,b)}}),a.controlConstructor.dynamic=a.DynamicControl}(wp.customize,jQuery);
     1!function(a,b){"use strict";a.DynamicControl=a.Control.extend({initialize:function(c,d){var e,f=this;e=d||{},e.params=e.params||{},e.params.type||(e.params.type="dynamic"),e.params.content||(e.params.content=b("<li></li>"),e.params.content.attr("id","customize-control-"+c.replace(/]/g,"").replace(/\[/g,"-")),e.params.content.attr("class","customize-control customize-control-"+e.params.type)),f.propertyElements=[],a.Control.prototype.initialize.call(f,c,e)},_setUpSettingRootLinks:function(){var c,d,e;c=this,d=c.container.find("[data-customize-setting-link]"),e={},d.each(function(){var f,g=b(this);if(g.is(":radio")){if(f=g.prop("name"),e[f])return;e[f]=!0,g=d.filter('[name="'+f+'"]')}a(g.data("customizeSettingLink"),function(b){var d=new a.Element(g);c.elements.push(d),d.sync(b),d.set(b())})})},_setUpSettingPropertyLinks:function(){var c,d,e=this;e.setting&&(c=e.container.find("[data-customize-setting-property-link]"),d={},c.each(function(){var f,g,h=b(this),i=h.data("customizeSettingPropertyLink");if(h.is(":radio")){if(f=h.prop("name"),d[f])return;d[f]=!0,h=c.filter('[name="'+f+'"]')}g=new a.Element(h),e.propertyElements.push(g),g.set(e.setting()[i]),g.bind(function(a){var b=e.setting();a!==b[i]&&(b=_.clone(b),b[i]=a,e.setting.set(b))}),e.setting.bind(function(a){a[i]!==g.get()&&g.set(a[i])})}))},ready:function(){var b=this;b._setUpSettingRootLinks(),b._setUpSettingPropertyLinks(),a.Control.prototype.ready.call(b),b.deferred.embedded.done(function(){})},embed:function(){var b=this,c=b.section();c&&a.section(c,function(c){c.expanded()||a.settings.autofocus.control===b.id?b.actuallyEmbed():c.expanded.bind(function(a){a&&b.actuallyEmbed()})})},actuallyEmbed:function(){var a=this;"resolved"!==a.deferred.embedded.state()&&(a.renderContent(),a.deferred.embedded.resolve())},focus:function(b){var c=this;c.actuallyEmbed(),a.Control.prototype.focus.call(c,b)}}),a.controlConstructor.dynamic=a.DynamicControl}(wp.customize,jQuery);
  • customize-posts/trunk/js/customize-post-field-partial.js

    r1406933 r1430660  
    2222         */
    2323        initialize: function( id, options ) {
    24             var partial = this, args, matches, baseSelector, singularSelector, idPattern = /^post\[(.+?)]\[(-?\d+)]\[(.+?)](?:\[(.+?)])?$/;
     24            var partial = this, args, matches, idPattern = /^post\[(.+?)]\[(-?\d+)]\[(.+?)](?:\[(.+?)])?$/;
    2525
    2626            args = options || {};
     
    3535            args.params.placement = matches[4] || '';
    3636
    37             if ( ! args.params.selector ) {
    38                 baseSelector = '.hentry.post-' + String( args.params.post_id ) + '.type-' + args.params.post_type;
    39                 singularSelector = '.postid-' + String( args.params.post_id ) + '.single-' + args.params.post_type;
    40                 if ( 'post_title' === args.params.field_id ) {
    41                     args.params.selector = baseSelector + ' .entry-title';
    42                 } else if ( 'post_content' === args.params.field_id ) {
    43                     args.params.selector = baseSelector + ' .entry-content';
    44                 } else if ( 'post_excerpt' === args.params.field_id ) {
    45                     args.params.selector = baseSelector + ' .entry-summary';
    46                 } else if ( 'comment_status' === args.params.field_id ) {
    47                     if ( 'comments-area' === args.params.placement ) {
    48                         args.params.selector = singularSelector + ' .comments-area';
    49                     } else if ( 'comments-link' === args.params.placement ) {
    50                         args.params.selector = baseSelector + ' .comments-link';
    51                     }
    52                 } else if ( 'ping_status' === args.params.field_id ) {
    53                     args.params.selector = singularSelector + ' .comments-area';
    54                 } else if ( 'post_author' === args.params.field_id ) {
    55                     if ( 'author-bio' === args.params.placement ) {
    56                         args.params.selector = baseSelector + ' .author-info';
    57                     } else if ( 'byline' === args.params.placement ) {
    58                         args.params.selector = baseSelector + ' .vcard a.fn';
    59                     } else if ( 'avatar' === args.params.placement ) {
    60                         args.params.selector = baseSelector + ' .vcard img.avatar';
    61                     }
    62                 }
    63             }
    6437            api.selectiveRefresh.Partial.prototype.initialize.call( partial, id, args );
    6538        },
  • customize-posts/trunk/js/customize-post-field-partial.min.js

    r1406933 r1430660  
    1 !function(a){"use strict";a.previewPosts||(a.previewPosts={}),a.previewPosts.PostFieldPartial=a.selectiveRefresh.Partial.extend({initialize:function(b,c){var d,e,f,g,h=this,i=/^post\[(.+?)]\[(-?\d+)]\[(.+?)](?:\[(.+?)])?$/;if(d=c||{},d.params=d.params||{},e=b.match(i),!e)throw new Error("Bad PostFieldPartial id. Expected post[:post_type][:post_id][:field_id]");d.params.post_type=e[1],d.params.post_id=parseInt(e[2],10),d.params.field_id=e[3],d.params.placement=e[4]||"",d.params.selector||(f=".hentry.post-"+String(d.params.post_id)+".type-"+d.params.post_type,g=".postid-"+String(d.params.post_id)+".single-"+d.params.post_type,"post_title"===d.params.field_id?d.params.selector=f+" .entry-title":"post_content"===d.params.field_id?d.params.selector=f+" .entry-content":"post_excerpt"===d.params.field_id?d.params.selector=f+" .entry-summary":"comment_status"===d.params.field_id?"comments-area"===d.params.placement?d.params.selector=g+" .comments-area":"comments-link"===d.params.placement&&(d.params.selector=f+" .comments-link"):"ping_status"===d.params.field_id?d.params.selector=g+" .comments-area":"post_author"===d.params.field_id&&("author-bio"===d.params.placement?d.params.selector=f+" .author-info":"byline"===d.params.placement?d.params.selector=f+" .vcard a.fn":"avatar"===d.params.placement&&(d.params.selector=f+" .vcard img.avatar"))),a.selectiveRefresh.Partial.prototype.initialize.call(h,b,d)},showControl:function(){var b=this,c=b.params.primarySetting;c||(c=_.first(b.settings())),a.preview.send("focus-control",c+"["+b.params.field_id+"]")},isRelatedSetting:function(b,c,d){var e=this;return _.isObject(c)&&_.isObject(d)&&e.params.field_id&&c[e.params.field_id]===d[e.params.field_id]?!1:a.selectiveRefresh.Partial.prototype.isRelatedSetting.call(e,b)}}),a.selectiveRefresh.partialConstructor.post_field=a.previewPosts.PostFieldPartial}(wp.customize);
     1!function(a){"use strict";a.previewPosts||(a.previewPosts={}),a.previewPosts.PostFieldPartial=a.selectiveRefresh.Partial.extend({initialize:function(b,c){var d,e,f=this,g=/^post\[(.+?)]\[(-?\d+)]\[(.+?)](?:\[(.+?)])?$/;if(d=c||{},d.params=d.params||{},e=b.match(g),!e)throw new Error("Bad PostFieldPartial id. Expected post[:post_type][:post_id][:field_id]");d.params.post_type=e[1],d.params.post_id=parseInt(e[2],10),d.params.field_id=e[3],d.params.placement=e[4]||"",a.selectiveRefresh.Partial.prototype.initialize.call(f,b,d)},showControl:function(){var b=this,c=b.params.primarySetting;c||(c=_.first(b.settings())),a.preview.send("focus-control",c+"["+b.params.field_id+"]")},isRelatedSetting:function(b,c,d){var e=this;return _.isObject(c)&&_.isObject(d)&&e.params.field_id&&c[e.params.field_id]===d[e.params.field_id]?!1:a.selectiveRefresh.Partial.prototype.isRelatedSetting.call(e,b)}}),a.selectiveRefresh.partialConstructor.post_field=a.previewPosts.PostFieldPartial}(wp.customize);
  • customize-posts/trunk/js/customize-post-section.js

    r1406933 r1430660  
    11/* global wp, tinyMCE */
    2 /* eslint consistent-this: [ "error", "section" ] */
     2/* eslint consistent-this: [ "error", "section" ], no-magic-numbers: [ "error", { "ignore": [-1,0,1] } ] */
    33
    44(function( api, $ ) {
     
    7575
    7676            api.Section.prototype.initialize.call( section, id, args );
    77         },
    78 
    79         /**
     77
     78            section.active.validate = function( active ) {
     79                var setting = api( section.id );
     80                if ( setting ) {
     81                    return setting._dirty || active;
     82                } else {
     83                    return true;
     84                }
     85            };
     86        },
     87
     88        /**
     89         * Ready.
     90         *
    8091         * @todo Defer embedding section until panel is expanded?
     92         *
     93         * @returns {void}
    8194         */
    8295        ready: function() {
     
    8598            section.setupTitleUpdating();
    8699            section.setupSettingValidation();
     100            section.setupPostNavigation();
    87101            section.setupControls();
    88102
     
    95109        /**
    96110         * Keep the title updated in the UI when the title updates in the setting.
     111         *
     112         * @returns {void}
    97113         */
    98114        setupTitleUpdating: function() {
     
    106122            setting.bind( function( newPostData, oldPostData ) {
    107123                var title;
    108                 if ( newPostData.post_title !== oldPostData.post_title ) {
     124                if ( newPostData.post_title !== oldPostData.post_title && 'trash' !== newPostData.post_status ) {
    109125                    title = newPostData.post_title || api.Posts.data.l10n.noTitle;
    110126                    sectionOuterTitleElement.text( title );
     
    116132
    117133        /**
     134         * Reload the pane based on the current posts preview url.
     135         *
     136         * @returns {void}
     137         */
     138        setupPostNavigation: function() {
     139            var section = this,
     140                sectionNavigationButton,
     141                sectionContainer = section.container.closest( '.accordion-section' ),
     142                sectionTitle = sectionContainer.find( '.customize-section-title:first' ),
     143                sectionNavigationButtonTemplate = wp.template( 'customize-posts-navigation' ),
     144                postTypeObj = api.Posts.data.postTypes[ section.params.post_type ];
     145
     146            // Short-circuit showing a link if the post type is not publicly queryable anyway.
     147            if ( ! postTypeObj['public'] ) {
     148                return;
     149            }
     150
     151            sectionNavigationButton = $( sectionNavigationButtonTemplate( {
     152                label: postTypeObj.labels.singular_name
     153            } ) );
     154            sectionTitle.append( sectionNavigationButton );
     155
     156            // Hide the link when the post is currently in the preview.
     157            api.previewer.bind( 'customized-posts', function( data ) {
     158                sectionNavigationButton.toggle( section.params.post_id !== data.queriedPostId );
     159            } );
     160
     161            sectionNavigationButton.on( 'click', function( event ) {
     162                event.preventDefault();
     163                api.previewer.previewUrl( api.Posts.getPreviewUrl( section.params ) );
     164            } );
     165        },
     166
     167        /**
    118168         * Set up the post field controls.
     169         *
     170         * @returns {void}
    119171         */
    120172        setupControls: function() {
     
    125177                section.addTitleControl();
    126178            }
     179            if ( postTypeObj.supports.title || postTypeObj.supports.slug ) {
     180                section.addSlugControl();
     181            }
     182            if ( 'undefined' === typeof EditPostPreviewCustomize ) {
     183                section.addPostStatusControl();
     184            }
    127185            if ( postTypeObj.supports.editor ) {
    128186                section.addContentControl();
     
    140198
    141199        /**
     200         * Prevent notifications for settings from being added to post field control notifications.
     201         *
     202         * @param {string} code                            Notification code.
     203         * @param {wp.customize.Notification} notification Notification object.
     204         * @returns {wp.customize.Notification|null} Notification if not bypassed.
     205         */
     206        addPostFieldControlNotification: function( code, notification ) {
     207            if ( -1 !== code.indexOf( ':' ) ) {
     208                return null;
     209            } else {
     210                return api.Values.prototype.add.call( this, code, notification );
     211            }
     212        },
     213
     214        /**
    142215         * Add post title control.
    143216         *
    144          * @returns {wp.customize.Control}
     217         * @returns {wp.customize.Control} Added control.
    145218         */
    146219        addTitleControl: function() {
     
    169242            api.control.add( control.id, control );
    170243
    171             // Remove the setting from the settingValidationMessages since it is not specific to this field.
    172             if ( control.settingValidationMessages ) {
    173                 control.settingValidationMessages.remove( setting.id );
    174                 control.settingValidationMessages.add( control.id, new api.Value( '' ) );
     244            if ( control.notifications ) {
     245                control.notifications.add = section.addPostFieldControlNotification;
    175246            }
    176247            return control;
     
    178249
    179250        /**
     251         * Add post slug control.
     252         *
     253         * @returns {wp.customize.Control} Added control.
     254         */
     255        addSlugControl: function() {
     256            var section = this, control, setting = api( section.id );
     257            control = new api.controlConstructor.dynamic( section.id + '[post_name]', {
     258                params: {
     259                    section: section.id,
     260                    priority: 15,
     261                    label: api.Posts.data.l10n.fieldSlugLabel,
     262                    active: true,
     263                    settings: {
     264                        'default': setting.id
     265                    },
     266                    field_type: 'text',
     267                    setting_property: 'post_name'
     268                }
     269            } );
     270
     271            // Supply a placeholder for the input field to approximate how an empty slug will be derived from the title.
     272            control.deferred.embedded.done( function() {
     273                var input = control.container.find( 'input' );
     274                function setPlaceholder() {
     275                    var slug = api.Posts.sanitizeTitleWithDashes( setting.get().post_title );
     276                    input.prop( 'placeholder', slug );
     277                }
     278                setPlaceholder();
     279                setting.bind( setPlaceholder );
     280            } );
     281
     282            // Override preview trying to de-activate control not present in preview context.
     283            control.active.validate = function() {
     284                return true;
     285            };
     286
     287            // Register.
     288            section.postFieldControls.post_name = control;
     289            api.control.add( control.id, control );
     290
     291            if ( control.notifications ) {
     292                control.notifications.add = section.addPostFieldControlNotification;
     293            }
     294            return control;
     295        },
     296
     297        /**
     298         * Add post status control.
     299         *
     300         * @returns {wp.customize.Control} Added control.
     301         */
     302        addPostStatusControl: function() {
     303            var section = this, control, setting = api( section.id ), sectionContainer, sectionTitle;
     304
     305            sectionContainer = section.container.closest( '.accordion-section' );
     306            sectionTitle = sectionContainer.find( '.accordion-section-title:first' );
     307
     308            control = new api.controlConstructor.dynamic( section.id + '[post_status]', {
     309                params: {
     310                    section: section.id,
     311                    priority: 20,
     312                    label: api.Posts.data.l10n.fieldPostStatusLabel,
     313                    active: true,
     314                    settings: {
     315                        'default': setting.id
     316                    },
     317                    field_type: 'select',
     318                    setting_property: 'post_status',
     319                    choices: api.Posts.data.postStatusChoices
     320                }
     321            } );
     322
     323            /**
     324             * Update the UI when a post is transitioned from/to trash.
     325             *
     326             * @param {boolean} trashed - Whether or not the post_status is 'trash'.
     327             * @returns {void}
     328             */
     329            control.toggleTrash = function( trashed ) {
     330                sectionContainer.toggleClass( 'is-trashed', trashed );
     331                if ( true === trashed ) {
     332                    if ( 0 === sectionTitle.find( '.customize-posts-trashed' ).length ) {
     333                        sectionTitle.append( wp.template( 'customize-posts-trashed' )() );
     334                    }
     335                } else {
     336                    sectionContainer.find( '.customize-posts-trashed' ).remove();
     337                }
     338            };
     339
     340            /**
     341             * Update the status UI when the setting changes its state.
     342             */
     343            setting.bind( function( newPostData, oldPostData ) {
     344                if ( newPostData.post_status !== oldPostData.post_status ) {
     345                    control.toggleTrash( 'trash' === newPostData.post_status );
     346                }
     347            } );
     348
     349            // Override preview trying to de-activate control not present in preview context.
     350            control.active.validate = function() {
     351                return true;
     352            };
     353
     354            // Register.
     355            section.postFieldControls.post_status = control;
     356            api.control.add( control.id, control );
     357
     358            // Initialize the trashed UI.
     359            api.panel( 'posts[' + section.params.post_type + ']' ).expanded.bind( function() {
     360                control.toggleTrash( 'trash' === setting.get().post_status );
     361            } );
     362
     363            control.deferred.embedded.done( function() {
     364                var embeddedDelay = 50;
     365
     366                _.delay( function() {
     367                    control.toggleTrash( 'trash' === setting.get().post_status );
     368                }, embeddedDelay );
     369            } );
     370
     371            if ( control.notifications ) {
     372                control.notifications.add = section.addPostFieldControlNotification;
     373            }
     374            return control;
     375        },
     376
     377        /**
    180378         * Add post content control.
    181379         *
    182380         * @todo It is hacky how the dynamic control is overloaded to connect to the shared TinyMCE editor.
    183381         *
    184          * @returns {wp.customize.Control}
     382         * @returns {wp.customize.Control} Added control.
    185383         */
    186384        addContentControl: function() {
    187             var section = this, control, setting = api( section.id );
     385            var section = this,
     386                control,
     387                setting = api( section.id ),
     388                preview = $( '#customize-preview' ),
     389                editorPane = $( '#customize-posts-content-editor-pane' ),
     390                editorFrame = $( '#customize-posts-content_ifr' ),
     391                mceTools = $( '#wp-customize-posts-content-editor-tools' ),
     392                mceToolbar = $( '.mce-toolbar-grp' ),
     393                mceStatusbar = $( '.mce-statusbar' ),
     394                dragbar = $( '#customize-posts-content-editor-dragbar' ),
     395                collapse = $( '.collapse-sidebar' ),
     396                resizeHeight;
    188397
    189398            control = new api.controlConstructor.dynamic( section.id + '[post_content]', {
    190399                params: {
    191400                    section: section.id,
    192                     priority: 20,
     401                    priority: 25,
    193402                    label: api.Posts.data.l10n.fieldContentLabel,
    194403                    active: true,
     
    209418            /**
    210419             * Update the setting value when the editor changes its state.
     420             *
     421             * @returns {void}
    211422             */
    212423            control.onVisualEditorChange = function() {
     
    224435            /**
    225436             * Update the setting value when the editor changes its state.
     437             *
     438             * @returns {void}
    226439             */
    227440            control.onTextEditorChange = function() {
     
    262475                    editor.on( 'input change keyup', control.onVisualEditorChange );
    263476                    textarea.on( 'input', control.onTextEditorChange );
     477                    control.resizeEditor( window.innerHeight - editorPane.height() );
    264478                } else {
    265479                    editor.off( 'input change keyup', control.onVisualEditorChange );
     
    269483                    editor.execCommand( 'wp_link_cancel' );
    270484                    $( '.mce-active' ).click();
     485                    preview.css( 'bottom', '' );
     486                    collapse.css( 'bottom', '' );
    271487                }
    272488            } );
     
    298514             * Expand the editor and focus on it when the post content control is focused.
    299515             *
    300              * @param args
     516             * @param {object} args Focus args.
     517             * @returns {void}
    301518             */
    302519            control.focus = function( args ) {
     
    306523                editor.focus();
    307524            };
     525
     526            /**
     527             * Vertically Resize Expanded Post Editor.
     528             *
     529             * @param {int} position - The position of the post editor from the top of the browser window.
     530             * @returns {void}
     531             */
     532            control.resizeEditor = function( position ) {
     533                var windowHeight = window.innerHeight,
     534                    windowWidth = window.innerWidth,
     535                    sectionContent = $( '[id^=accordion-panel-posts] ul.accordion-section-content' ),
     536                    minScroll = 40,
     537                    maxScroll = 1,
     538                    mobileWidth = 782,
     539                    collapseMinSpacing = 56,
     540                    collapseBottomOutsideEditor = 8,
     541                    collapseBottomInsideEditor = 4,
     542                    args = {};
     543
     544                if ( ! $( document.body ).hasClass( 'customize-posts-content-editor-pane-open' ) ) {
     545                    return;
     546                }
     547
     548                if ( ! _.isNaN( position ) ) {
     549                    resizeHeight = windowHeight - position;
     550                }
     551
     552                args.height = resizeHeight;
     553                args.components = mceTools.outerHeight() + mceToolbar.outerHeight() + mceStatusbar.outerHeight();
     554
     555                if ( resizeHeight < minScroll ) {
     556                    args.height = minScroll;
     557                }
     558
     559                if ( resizeHeight > windowHeight - maxScroll ) {
     560                    args.height = windowHeight - maxScroll;
     561                }
     562
     563                if ( windowHeight < editorPane.outerHeight() ) {
     564                    args.height = windowHeight;
     565                }
     566
     567                preview.css( 'bottom', args.height );
     568                editorPane.css( 'height', args.height );
     569                editorFrame.css( 'height', args.height - args.components );
     570                collapse.css( 'bottom', args.height + collapseBottomOutsideEditor );
     571
     572                if ( collapseMinSpacing > windowHeight - args.height ) {
     573                    collapse.css( 'bottom', mceStatusbar.outerHeight() + collapseBottomInsideEditor );
     574                }
     575
     576                if ( windowWidth <= mobileWidth ) {
     577                    sectionContent.css( 'padding-bottom', args.height );
     578                } else {
     579                    sectionContent.css( 'padding-bottom', '' );
     580                }
     581            };
     582
     583            // Resize the editor.
     584            dragbar.on( 'mousedown', function() {
     585                if ( ! section.expanded() ) {
     586                    return;
     587                }
     588                $( document ).on( 'mousemove.customize-posts-editor', function( event ) {
     589                    event.preventDefault();
     590                    $( document.body ).addClass( 'customize-posts-content-editor-pane-resize' );
     591                    editorFrame.css( 'pointer-events', 'none' );
     592                    control.resizeEditor( event.pageY );
     593                } );
     594            } );
     595
     596            // Remove editor resize.
     597            dragbar.on( 'mouseup', function() {
     598                if ( ! section.expanded() ) {
     599                    return;
     600                }
     601                $( document ).off( 'mousemove.customize-posts-editor' );
     602                $( document.body ).removeClass( 'customize-posts-content-editor-pane-resize' );
     603                editorFrame.css( 'pointer-events', '' );
     604            } );
     605
     606            // Resize the editor when the viewport changes.
     607            $( window ).on( 'resize', function() {
     608                var resizeDelay = 50;
     609                if ( ! section.expanded() ) {
     610                    return;
     611                }
     612                _.delay( function() {
     613                    control.resizeEditor( window.innerHeight - editorPane.height() );
     614                }, resizeDelay );
     615            } );
    308616
    309617            // Override preview trying to de-activate control not present in preview context.
     
    325633            } );
    326634
    327             // Remove the setting from the settingValidationMessages since it is not specific to this field.
    328             if ( control.settingValidationMessages ) {
    329                 control.settingValidationMessages.remove( setting.id );
    330                 control.settingValidationMessages.add( control.id, new api.Value( '' ) );
     635            if ( control.notifications ) {
     636                control.notifications.add = section.addPostFieldControlNotification;
    331637            }
    332638            return control;
     
    336642         * Add post excerpt control.
    337643         *
    338          * @returns {wp.customize.Control}
     644         * @returns {wp.customize.Control} Added control.
    339645         */
    340646        addExcerptControl: function() {
     
    363669            api.control.add( control.id, control );
    364670
    365             // Remove the setting from the settingValidationMessages since it is not specific to this field.
    366             if ( control.settingValidationMessages ) {
    367                 control.settingValidationMessages.remove( setting.id );
    368                 control.settingValidationMessages.add( control.id, new api.Value( '' ) );
     671            if ( control.notifications ) {
     672                control.notifications.add = section.addPostFieldControlNotification;
    369673            }
    370674            return control;
     
    374678         * Add discussion fields (comments and ping status fields) control.
    375679         *
    376          * @returns {wp.customize.Control}
     680         * @returns {wp.customize.Control} Added control.
    377681         */
    378682        addDiscussionFieldsControl: function() {
     
    401705            api.control.add( control.id, control );
    402706
    403             // Remove the setting from the settingValidationMessages since it is not specific to this field.
    404             if ( control.settingValidationMessages ) {
    405                 control.settingValidationMessages.remove( setting.id );
    406                 control.settingValidationMessages.add( control.id, new api.Value( '' ) );
     707            if ( control.notifications ) {
     708                control.notifications.add = section.addPostFieldControlNotification;
    407709            }
    408710            return control;
     
    412714         * Add post author control.
    413715         *
    414          * @returns {wp.customize.Control}
     716         * @returns {wp.customize.Control} Added control.
    415717         */
    416718        addAuthorControl: function() {
     
    440742            api.control.add( control.id, control );
    441743
    442             // Remove the setting from the settingValidationMessages since it is not specific to this field.
    443             if ( control.settingValidationMessages ) {
    444                 control.settingValidationMessages.remove( setting.id );
    445                 control.settingValidationMessages.add( control.id, new api.Value( '' ) );
     744            if ( control.notifications ) {
     745                control.notifications.add = section.addPostFieldControlNotification;
    446746            }
    447747            return control;
     
    450750        /**
    451751         * Set up setting validation.
     752         *
     753         * @returns {void}
    452754         */
    453755        setupSettingValidation: function() {
    454             var section = this, setting = api( section.id );
    455             if ( ! setting.validationMessage ) {
     756            var section = this, setting = api( section.id ), debouncedRenderNotifications;
     757            if ( ! setting.notifications ) {
    456758                return;
    457759            }
    458760
    459             section.validationMessageElement = $( '<div class="customize-setting-validation-message error" aria-live="assertive"></div>' );
    460             section.container.find( '.customize-section-title' ).append( section.validationMessageElement );
    461             setting.validationMessage.bind( function( message ) {
    462                 var template = wp.template( 'customize-setting-validation-message' );
    463                 section.validationMessageElement.empty().append( $.trim(
    464                     template( { messages: [ message ] } )
    465                 ) );
    466                 if ( message ) {
    467                     section.validationMessageElement.slideDown( 'fast' );
    468                 } else {
    469                     section.validationMessageElement.slideUp( 'fast' );
    470                 }
    471                 section.container.toggleClass( 'customize-setting-invalid', '' !== message );
    472             } );
     761            // Add the notifications API.
     762            section.notifications = new api.Values({ defaultConstructor: api.Notification });
     763            section.notificationsContainer = $( '<div class="customize-control-notifications-container"></div>' );
     764            section.notificationsTemplate = wp.template( 'customize-post-section-notifications' );
     765            section.container.find( '.customize-section-title' ).after( section.notificationsContainer );
     766            section.getNotificationsContainerElement = function() {
     767                return section.notificationsContainer;
     768            };
     769            section.renderNotifications = api.Control.prototype.renderNotifications;
     770
     771            // Sync setting notifications into the section notifications
     772            setting.notifications.bind( 'add', function( settingNotification ) {
     773                var notification = new api.Notification( setting.id + ':' + settingNotification.code, settingNotification );
     774                section.notifications.add( notification.code, notification );
     775            } );
     776            setting.notifications.bind( 'remove', function( settingNotification ) {
     777                section.notifications.remove( setting.id + ':' + settingNotification.code );
     778            } );
     779
     780            /*
     781             * Render notifications when the collection is updated.
     782             * Note that this debounced/deferred rendering is needed for two reasons:
     783             * 1) The 'remove' event is triggered just _before_ the notification is actually removed.
     784             * 2) Improve performance when adding/removing multiple notifications at a time.
     785             */
     786            debouncedRenderNotifications = _.debounce( function renderNotifications() {
     787                section.renderNotifications();
     788            } );
     789            section.notifications.bind( 'add', function( notification ) {
     790                wp.a11y.speak( notification.message, 'assertive' );
     791                debouncedRenderNotifications();
     792            } );
     793            section.notifications.bind( 'remove', debouncedRenderNotifications );
     794            section.renderNotifications();
    473795
    474796            // Dismiss conflict block when clicking on button.
    475             section.validationMessageElement.on( 'click', '.override-post-conflict', function( e ) {
     797            section.notificationsContainer.on( 'click', '.override-post-conflict', function( e ) {
    476798                var ourValue;
    477799                e.preventDefault();
     
    479801                ourValue.post_modified_gmt = '';
    480802                setting.set( ourValue );
    481                 section.resetPostFieldControlSettingValidationMessages();
     803
     804                _.each( section.postFieldControls, function( control ) {
     805                    if ( control.notifications ) {
     806                        control.notifications.remove( 'post_update_conflict' );
     807                    }
     808                } );
     809                setting.notifications.remove( 'post_update_conflict' );
    482810            } );
    483811
    484812            // Detect conflict errors.
    485813            api.bind( 'error', function( response ) {
    486                 var theirValue, ourValue, overrideButton, wasOverrideButtonAdded = false;
     814                var theirValue, ourValue;
    487815                if ( ! response.update_conflicted_setting_values ) {
    488816                    return;
     
    494822                ourValue = setting.get();
    495823                _.each( theirValue, function( theirFieldValue, fieldId ) {
    496                     var control, validationMessage;
     824                    var control, notification;
    497825                    if ( 'post_modified' === fieldId || 'post_modified_gmt' === fieldId || theirFieldValue === ourValue[ fieldId ] ) {
    498826                        return;
    499827                    }
    500828                    control = api.control( setting.id + '[' + fieldId + ']' );
    501                     if ( control && control.settingValidationMessages && control.settingValidationMessages.has( control.id ) ) {
    502                         validationMessage = api.Posts.data.l10n.theirChange.replace( '%s', String( theirFieldValue ) );
    503                         control.settingValidationMessages( control.id ).set( validationMessage );
    504 
    505                         if ( ! wasOverrideButtonAdded ) {
    506                             overrideButton = $( '<button class="button override-post-conflict" type="button"></button>' );
    507                             overrideButton.text( api.Posts.data.l10n.overrideButtonText );
    508                             section.validationMessageElement.find( 'li:first' ).prepend( overrideButton );
    509                             wasOverrideButtonAdded = true;
    510                         }
     829                    if ( control && control.notifications ) {
     830                        notification = new api.Notification( 'post_update_conflict', {
     831                            message: api.Posts.data.l10n.theirChange.replace( '%s', String( theirFieldValue ) )
     832                        } );
     833                        control.notifications.remove( notification.code );
     834                        control.notifications.add( notification.code, notification );
    511835                    }
    512836                } );
     
    514838
    515839            api.bind( 'save', function() {
    516                 section.resetPostFieldControlSettingValidationMessages();
     840                section.resetPostFieldControlErrorNotifications();
    517841            } );
    518842        },
     
    520844        /**
    521845         * Reset all of the validation messages for all of the post fields in the section.
    522          */
    523         resetPostFieldControlSettingValidationMessages: function() {
     846         *
     847         * @returns {void}
     848         */
     849        resetPostFieldControlErrorNotifications: function() {
    524850            var section = this;
    525851            _.each( section.postFieldControls, function( postFieldControl ) {
    526                 if ( postFieldControl.settingValidationMessages ) {
    527                     postFieldControl.settingValidationMessages.each( function( validationMessage ) {
    528                         validationMessage.set( '' );
     852                if ( postFieldControl.notifications ) {
     853                    postFieldControl.notifications.each( function( notification ) {
     854                        if ( 'error' === notification.type ) {
     855                            postFieldControl.notifications.remove( notification.code );
     856                        }
    529857                    } );
    530858                }
     
    535863         * Allow an active section to be contextually active even when it has no active controls.
    536864         *
    537          * @returns {boolean}
     865         * @returns {boolean} Active.
    538866         */
    539867        isContextuallyActive: function() {
  • customize-posts/trunk/js/customize-post-section.min.js

    r1406933 r1430660  
    1 !function(a,b){"use strict";var c,d,e={};a.Posts||(a.Posts={}),c=a.Element.synchronizer.checkbox.update,d=a.Element.synchronizer.checkbox.refresh,_.extend(a.Element.synchronizer.checkbox,{update:function(a){var b;b=_.isUndefined(this.element.data("on-value"))||_.isUndefined(this.element.data("off-value"))?a:a===this.element.data("on-value"),c.call(this,b)},refresh:function(){return _.isUndefined(this.element.data("on-value"))||_.isUndefined(this.element.data("off-value"))?d.call(this):this.element.prop("checked")?this.element.data("on-value"):this.element.data("off-value")}}),a.Posts.PostSection=a.Section.extend({initialize:function(b,c){var d,f=this;if(d=c||{},d.params=d.params||{},!d.params.post_type||!a.Posts.data.postTypes[d.params.post_type])throw new Error("Missing post_type");if(_.isNaN(d.params.post_id))throw new Error("Missing post_id");if(!a.has(b))throw new Error("No setting id");d.params.title||(d.params.title=a(b).get().post_title),d.params.title||(d.params.title=a.Posts.data.l10n.noTitle),f.postFieldControls={},d.params.priority||(e[d.params.post_type]||(e[d.params.post_type]=a.Section.prototype.defaults.priority),e[d.params.post_type]+=1,d.params.priority=e[d.params.post_type]),a.Section.prototype.initialize.call(f,b,d)},ready:function(){var b=this;b.setupTitleUpdating(),b.setupSettingValidation(),b.setupControls(),a.Section.prototype.ready.call(b)},setupTitleUpdating:function(){var b,c,d,e,f=this,g=a(f.id);b=f.container.closest(".accordion-section"),c=b.find(".accordion-section-title:first"),d=b.find(".customize-section-title h3").first(),e=d.find(".customize-action").first(),g.bind(function(b,f){var g;b.post_title!==f.post_title&&(g=b.post_title||a.Posts.data.l10n.noTitle,c.text(g),d.text(g),d.prepend(e))})},setupControls:function(){var b,c=this;b=a.Posts.data.postTypes[c.params.post_type],b.supports.title&&c.addTitleControl(),b.supports.editor&&c.addContentControl(),b.supports.excerpt&&c.addExcerptControl(),(b.supports.comments||b.supports.trackbacks)&&c.addDiscussionFieldsControl(),b.supports.author&&c.addAuthorControl()},addTitleControl:function(){var b,c=this,d=a(c.id);return b=new a.controlConstructor.dynamic(c.id+"[post_title]",{params:{section:c.id,priority:10,label:a.Posts.data.l10n.fieldTitleLabel,active:!0,settings:{"default":d.id},field_type:"text",setting_property:"post_title"}}),b.active.validate=function(){return!0},c.postFieldControls.post_title=b,a.control.add(b.id,b),b.settingValidationMessages&&(b.settingValidationMessages.remove(d.id),b.settingValidationMessages.add(b.id,new a.Value(""))),b},addContentControl:function(){var c,d=this,e=a(d.id);return c=new a.controlConstructor.dynamic(d.id+"[post_content]",{params:{section:d.id,priority:20,label:a.Posts.data.l10n.fieldContentLabel,active:!0,settings:{"default":e.id},field_type:"textarea",setting_property:"post_content"}}),c.editorExpanded=new a.Value(!1),c.editorToggleExpandButton=b('<button type="button" class="button"></button>'),c.updateEditorToggleExpandButtonLabel=function(b){c.editorToggleExpandButton.text(b?a.Posts.data.l10n.closeEditor:a.Posts.data.l10n.openEditor)},c.updateEditorToggleExpandButtonLabel(c.editorExpanded.get()),c.onVisualEditorChange=function(){var a,b;c.editorSyncSuspended||(b=tinyMCE.get("customize-posts-content"),a=wp.editor.removep(b.getContent()),c.editorSyncSuspended=!0,c.propertyElements[0].set(a),c.editorSyncSuspended=!1)},c.onTextEditorChange=function(){c.editorSyncSuspended||(c.editorSyncSuspended=!0,c.propertyElements[0].set(b(this).val()),c.editorSyncSuspended=!1)},e.bind(function(a,b){var d;c.editorExpanded.get()&&!c.editorSyncSuspended&&a.post_content!==b.post_content&&(c.editorSyncSuspended=!0,d=tinyMCE.get("customize-posts-content"),d.setContent(wp.editor.autop(a.post_content)),c.editorSyncSuspended=!1)}),c.editorExpanded.bind(function(a){var d,f=b("#customize-posts-content");d=tinyMCE.get("customize-posts-content"),c.updateEditorToggleExpandButtonLabel(a),b(document.body).toggleClass("customize-posts-content-editor-pane-open",a),a?(d.setContent(wp.editor.autop(e().post_content)),d.on("input change keyup",c.onVisualEditorChange),f.on("input",c.onTextEditorChange)):(d.off("input change keyup",c.onVisualEditorChange),f.off("input",c.onTextEditorChange),d.execCommand("wp_link_cancel"),b(".mce-active").click())}),d.expanded.bind(function(b){b?a.Posts.postIdInput.val(d.params.post_id):(a.Posts.postIdInput.val(""),c.editorExpanded.set(!1))}),c.editorToggleExpandButton.on("click",function(){var a=tinyMCE.get("customize-posts-content");c.editorExpanded.set(!c.editorExpanded()),c.editorExpanded()&&a.focus()}),c.focus=function(b){var d=tinyMCE.get("customize-posts-content");a.controlConstructor.dynamic.prototype.focus.call(c,b),c.editorExpanded.set(!0),d.focus()},c.active.validate=function(){return!0},d.postFieldControls.post_content=c,a.control.add(c.id,c),c.deferred.embedded.done(function(){var a=c.container.find("textarea:first");a.hide(),c.editorToggleExpandButton.attr("id",a.attr("id")),a.attr("id",""),c.container.append(c.editorToggleExpandButton)}),c.settingValidationMessages&&(c.settingValidationMessages.remove(e.id),c.settingValidationMessages.add(c.id,new a.Value(""))),c},addExcerptControl:function(){var b,c=this,d=a(c.id);return b=new a.controlConstructor.dynamic(c.id+"[post_excerpt]",{params:{section:c.id,priority:30,label:a.Posts.data.l10n.fieldExcerptLabel,active:!0,settings:{"default":d.id},field_type:"textarea",setting_property:"post_excerpt"}}),b.active.validate=function(){return!0},c.postFieldControls.post_excerpt=b,a.control.add(b.id,b),b.settingValidationMessages&&(b.settingValidationMessages.remove(d.id),b.settingValidationMessages.add(b.id,new a.Value(""))),b},addDiscussionFieldsControl:function(){var b,c,d=this,e=a(d.id);return b=a.Posts.data.postTypes[d.params.post_type],c=new a.controlConstructor.post_discussion_fields(d.id+"[discussion_fields]",{params:{section:d.id,priority:60,label:a.Posts.data.l10n.fieldDiscussionLabel,active:!0,settings:{"default":e.id},post_type_supports:b.supports}}),c.active.validate=function(){return!0},d.postFieldControls.post_discussion_fields=c,a.control.add(c.id,c),c.settingValidationMessages&&(c.settingValidationMessages.remove(e.id),c.settingValidationMessages.add(c.id,new a.Value(""))),c},addAuthorControl:function(){var b,c=this,d=a(c.id);return b=new a.controlConstructor.dynamic(c.id+"[post_author]",{params:{section:c.id,priority:70,label:a.Posts.data.l10n.fieldAuthorLabel,active:!0,settings:{"default":d.id},field_type:"select",setting_property:"post_author",choices:a.Posts.data.authorChoices}}),b.active.validate=function(){return!0},c.postFieldControls.post_author=b,a.control.add(b.id,b),b.settingValidationMessages&&(b.settingValidationMessages.remove(d.id),b.settingValidationMessages.add(b.id,new a.Value(""))),b},setupSettingValidation:function(){var c=this,d=a(c.id);d.validationMessage&&(c.validationMessageElement=b('<div class="customize-setting-validation-message error" aria-live="assertive"></div>'),c.container.find(".customize-section-title").append(c.validationMessageElement),d.validationMessage.bind(function(a){var d=wp.template("customize-setting-validation-message");c.validationMessageElement.empty().append(b.trim(d({messages:[a]}))),a?c.validationMessageElement.slideDown("fast"):c.validationMessageElement.slideUp("fast"),c.container.toggleClass("customize-setting-invalid",""!==a)}),c.validationMessageElement.on("click",".override-post-conflict",function(a){var b;a.preventDefault(),b=_.clone(d.get()),b.post_modified_gmt="",d.set(b),c.resetPostFieldControlSettingValidationMessages()}),a.bind("error",function(e){var f,g,h,i=!1;e.update_conflicted_setting_values&&(f=e.update_conflicted_setting_values[d.id],f&&(g=d.get(),_.each(f,function(e,f){var j,k;"post_modified"!==f&&"post_modified_gmt"!==f&&e!==g[f]&&(j=a.control(d.id+"["+f+"]"),j&&j.settingValidationMessages&&j.settingValidationMessages.has(j.id)&&(k=a.Posts.data.l10n.theirChange.replace("%s",String(e)),j.settingValidationMessages(j.id).set(k),i||(h=b('<button class="button override-post-conflict" type="button"></button>'),h.text(a.Posts.data.l10n.overrideButtonText),c.validationMessageElement.find("li:first").prepend(h),i=!0)))})))}),a.bind("save",function(){c.resetPostFieldControlSettingValidationMessages()}))},resetPostFieldControlSettingValidationMessages:function(){var a=this;_.each(a.postFieldControls,function(a){a.settingValidationMessages&&a.settingValidationMessages.each(function(a){a.set("")})})},isContextuallyActive:function(){var a=this;return a.active()}})}(wp.customize,jQuery);
     1!function(a,b){"use strict";var c,d,e={};a.Posts||(a.Posts={}),c=a.Element.synchronizer.checkbox.update,d=a.Element.synchronizer.checkbox.refresh,_.extend(a.Element.synchronizer.checkbox,{update:function(a){var b;b=_.isUndefined(this.element.data("on-value"))||_.isUndefined(this.element.data("off-value"))?a:a===this.element.data("on-value"),c.call(this,b)},refresh:function(){return _.isUndefined(this.element.data("on-value"))||_.isUndefined(this.element.data("off-value"))?d.call(this):this.element.prop("checked")?this.element.data("on-value"):this.element.data("off-value")}}),a.Posts.PostSection=a.Section.extend({initialize:function(b,c){var d,f=this;if(d=c||{},d.params=d.params||{},!d.params.post_type||!a.Posts.data.postTypes[d.params.post_type])throw new Error("Missing post_type");if(_.isNaN(d.params.post_id))throw new Error("Missing post_id");if(!a.has(b))throw new Error("No setting id");d.params.title||(d.params.title=a(b).get().post_title),d.params.title||(d.params.title=a.Posts.data.l10n.noTitle),f.postFieldControls={},d.params.priority||(e[d.params.post_type]||(e[d.params.post_type]=a.Section.prototype.defaults.priority),e[d.params.post_type]+=1,d.params.priority=e[d.params.post_type]),a.Section.prototype.initialize.call(f,b,d),f.active.validate=function(b){var c=a(f.id);return c?c._dirty||b:!0}},ready:function(){var b=this;b.setupTitleUpdating(),b.setupSettingValidation(),b.setupPostNavigation(),b.setupControls(),a.Section.prototype.ready.call(b)},setupTitleUpdating:function(){var b,c,d,e,f=this,g=a(f.id);b=f.container.closest(".accordion-section"),c=b.find(".accordion-section-title:first"),d=b.find(".customize-section-title h3").first(),e=d.find(".customize-action").first(),g.bind(function(b,f){var g;b.post_title!==f.post_title&&"trash"!==b.post_status&&(g=b.post_title||a.Posts.data.l10n.noTitle,c.text(g),d.text(g),d.prepend(e))})},setupPostNavigation:function(){var c,d=this,e=d.container.closest(".accordion-section"),f=e.find(".customize-section-title:first"),g=wp.template("customize-posts-navigation"),h=a.Posts.data.postTypes[d.params.post_type];h["public"]&&(c=b(g({label:h.labels.singular_name})),f.append(c),a.previewer.bind("customized-posts",function(a){c.toggle(d.params.post_id!==a.queriedPostId)}),c.on("click",function(b){b.preventDefault(),a.previewer.previewUrl(a.Posts.getPreviewUrl(d.params))}))},setupControls:function(){var b,c=this;b=a.Posts.data.postTypes[c.params.post_type],b.supports.title&&c.addTitleControl(),(b.supports.title||b.supports.slug)&&c.addSlugControl(),"undefined"==typeof EditPostPreviewCustomize&&c.addPostStatusControl(),b.supports.editor&&c.addContentControl(),b.supports.excerpt&&c.addExcerptControl(),(b.supports.comments||b.supports.trackbacks)&&c.addDiscussionFieldsControl(),b.supports.author&&c.addAuthorControl()},addPostFieldControlNotification:function(b,c){return-1!==b.indexOf(":")?null:a.Values.prototype.add.call(this,b,c)},addTitleControl:function(){var b,c=this,d=a(c.id);return b=new a.controlConstructor.dynamic(c.id+"[post_title]",{params:{section:c.id,priority:10,label:a.Posts.data.l10n.fieldTitleLabel,active:!0,settings:{"default":d.id},field_type:"text",setting_property:"post_title"}}),b.active.validate=function(){return!0},c.postFieldControls.post_title=b,a.control.add(b.id,b),b.notifications&&(b.notifications.add=c.addPostFieldControlNotification),b},addSlugControl:function(){var b,c=this,d=a(c.id);return b=new a.controlConstructor.dynamic(c.id+"[post_name]",{params:{section:c.id,priority:15,label:a.Posts.data.l10n.fieldSlugLabel,active:!0,settings:{"default":d.id},field_type:"text",setting_property:"post_name"}}),b.deferred.embedded.done(function(){function c(){var b=a.Posts.sanitizeTitleWithDashes(d.get().post_title);e.prop("placeholder",b)}var e=b.container.find("input");c(),d.bind(c)}),b.active.validate=function(){return!0},c.postFieldControls.post_name=b,a.control.add(b.id,b),b.notifications&&(b.notifications.add=c.addPostFieldControlNotification),b},addPostStatusControl:function(){var b,c,d,e=this,f=a(e.id);return c=e.container.closest(".accordion-section"),d=c.find(".accordion-section-title:first"),b=new a.controlConstructor.dynamic(e.id+"[post_status]",{params:{section:e.id,priority:20,label:a.Posts.data.l10n.fieldPostStatusLabel,active:!0,settings:{"default":f.id},field_type:"select",setting_property:"post_status",choices:a.Posts.data.postStatusChoices}}),b.toggleTrash=function(a){c.toggleClass("is-trashed",a),!0===a?0===d.find(".customize-posts-trashed").length&&d.append(wp.template("customize-posts-trashed")()):c.find(".customize-posts-trashed").remove()},f.bind(function(a,c){a.post_status!==c.post_status&&b.toggleTrash("trash"===a.post_status)}),b.active.validate=function(){return!0},e.postFieldControls.post_status=b,a.control.add(b.id,b),a.panel("posts["+e.params.post_type+"]").expanded.bind(function(){b.toggleTrash("trash"===f.get().post_status)}),b.deferred.embedded.done(function(){var a=50;_.delay(function(){b.toggleTrash("trash"===f.get().post_status)},a)}),b.notifications&&(b.notifications.add=e.addPostFieldControlNotification),b},addContentControl:function(){var c,d,e=this,f=a(e.id),g=b("#customize-preview"),h=b("#customize-posts-content-editor-pane"),i=b("#customize-posts-content_ifr"),j=b("#wp-customize-posts-content-editor-tools"),k=b(".mce-toolbar-grp"),l=b(".mce-statusbar"),m=b("#customize-posts-content-editor-dragbar"),n=b(".collapse-sidebar");return c=new a.controlConstructor.dynamic(e.id+"[post_content]",{params:{section:e.id,priority:25,label:a.Posts.data.l10n.fieldContentLabel,active:!0,settings:{"default":f.id},field_type:"textarea",setting_property:"post_content"}}),c.editorExpanded=new a.Value(!1),c.editorToggleExpandButton=b('<button type="button" class="button"></button>'),c.updateEditorToggleExpandButtonLabel=function(b){c.editorToggleExpandButton.text(b?a.Posts.data.l10n.closeEditor:a.Posts.data.l10n.openEditor)},c.updateEditorToggleExpandButtonLabel(c.editorExpanded.get()),c.onVisualEditorChange=function(){var a,b;c.editorSyncSuspended||(b=tinyMCE.get("customize-posts-content"),a=wp.editor.removep(b.getContent()),c.editorSyncSuspended=!0,c.propertyElements[0].set(a),c.editorSyncSuspended=!1)},c.onTextEditorChange=function(){c.editorSyncSuspended||(c.editorSyncSuspended=!0,c.propertyElements[0].set(b(this).val()),c.editorSyncSuspended=!1)},f.bind(function(a,b){var d;c.editorExpanded.get()&&!c.editorSyncSuspended&&a.post_content!==b.post_content&&(c.editorSyncSuspended=!0,d=tinyMCE.get("customize-posts-content"),d.setContent(wp.editor.autop(a.post_content)),c.editorSyncSuspended=!1)}),c.editorExpanded.bind(function(a){var d,e=b("#customize-posts-content");d=tinyMCE.get("customize-posts-content"),c.updateEditorToggleExpandButtonLabel(a),b(document.body).toggleClass("customize-posts-content-editor-pane-open",a),a?(d.setContent(wp.editor.autop(f().post_content)),d.on("input change keyup",c.onVisualEditorChange),e.on("input",c.onTextEditorChange),c.resizeEditor(window.innerHeight-h.height())):(d.off("input change keyup",c.onVisualEditorChange),e.off("input",c.onTextEditorChange),d.execCommand("wp_link_cancel"),b(".mce-active").click(),g.css("bottom",""),n.css("bottom",""))}),e.expanded.bind(function(b){b?a.Posts.postIdInput.val(e.params.post_id):(a.Posts.postIdInput.val(""),c.editorExpanded.set(!1))}),c.editorToggleExpandButton.on("click",function(){var a=tinyMCE.get("customize-posts-content");c.editorExpanded.set(!c.editorExpanded()),c.editorExpanded()&&a.focus()}),c.focus=function(b){var d=tinyMCE.get("customize-posts-content");a.controlConstructor.dynamic.prototype.focus.call(c,b),c.editorExpanded.set(!0),d.focus()},c.resizeEditor=function(a){var c=window.innerHeight,e=window.innerWidth,f=b("[id^=accordion-panel-posts] ul.accordion-section-content"),m=40,o=1,p=782,q=56,r=8,s=4,t={};b(document.body).hasClass("customize-posts-content-editor-pane-open")&&(_.isNaN(a)||(d=c-a),t.height=d,t.components=j.outerHeight()+k.outerHeight()+l.outerHeight(),m>d&&(t.height=m),d>c-o&&(t.height=c-o),c<h.outerHeight()&&(t.height=c),g.css("bottom",t.height),h.css("height",t.height),i.css("height",t.height-t.components),n.css("bottom",t.height+r),q>c-t.height&&n.css("bottom",l.outerHeight()+s),p>=e?f.css("padding-bottom",t.height):f.css("padding-bottom",""))},m.on("mousedown",function(){e.expanded()&&b(document).on("mousemove.customize-posts-editor",function(a){a.preventDefault(),b(document.body).addClass("customize-posts-content-editor-pane-resize"),i.css("pointer-events","none"),c.resizeEditor(a.pageY)})}),m.on("mouseup",function(){e.expanded()&&(b(document).off("mousemove.customize-posts-editor"),b(document.body).removeClass("customize-posts-content-editor-pane-resize"),i.css("pointer-events",""))}),b(window).on("resize",function(){var a=50;e.expanded()&&_.delay(function(){c.resizeEditor(window.innerHeight-h.height())},a)}),c.active.validate=function(){return!0},e.postFieldControls.post_content=c,a.control.add(c.id,c),c.deferred.embedded.done(function(){var a=c.container.find("textarea:first");a.hide(),c.editorToggleExpandButton.attr("id",a.attr("id")),a.attr("id",""),c.container.append(c.editorToggleExpandButton)}),c.notifications&&(c.notifications.add=e.addPostFieldControlNotification),c},addExcerptControl:function(){var b,c=this,d=a(c.id);return b=new a.controlConstructor.dynamic(c.id+"[post_excerpt]",{params:{section:c.id,priority:30,label:a.Posts.data.l10n.fieldExcerptLabel,active:!0,settings:{"default":d.id},field_type:"textarea",setting_property:"post_excerpt"}}),b.active.validate=function(){return!0},c.postFieldControls.post_excerpt=b,a.control.add(b.id,b),b.notifications&&(b.notifications.add=c.addPostFieldControlNotification),b},addDiscussionFieldsControl:function(){var b,c,d=this,e=a(d.id);return b=a.Posts.data.postTypes[d.params.post_type],c=new a.controlConstructor.post_discussion_fields(d.id+"[discussion_fields]",{params:{section:d.id,priority:60,label:a.Posts.data.l10n.fieldDiscussionLabel,active:!0,settings:{"default":e.id},post_type_supports:b.supports}}),c.active.validate=function(){return!0},d.postFieldControls.post_discussion_fields=c,a.control.add(c.id,c),c.notifications&&(c.notifications.add=d.addPostFieldControlNotification),c},addAuthorControl:function(){var b,c=this,d=a(c.id);return b=new a.controlConstructor.dynamic(c.id+"[post_author]",{params:{section:c.id,priority:70,label:a.Posts.data.l10n.fieldAuthorLabel,active:!0,settings:{"default":d.id},field_type:"select",setting_property:"post_author",choices:a.Posts.data.authorChoices}}),b.active.validate=function(){return!0},c.postFieldControls.post_author=b,a.control.add(b.id,b),b.notifications&&(b.notifications.add=c.addPostFieldControlNotification),b},setupSettingValidation:function(){var c,d=this,e=a(d.id);e.notifications&&(d.notifications=new a.Values({defaultConstructor:a.Notification}),d.notificationsContainer=b('<div class="customize-control-notifications-container"></div>'),d.notificationsTemplate=wp.template("customize-post-section-notifications"),d.container.find(".customize-section-title").after(d.notificationsContainer),d.getNotificationsContainerElement=function(){return d.notificationsContainer},d.renderNotifications=a.Control.prototype.renderNotifications,e.notifications.bind("add",function(b){var c=new a.Notification(e.id+":"+b.code,b);d.notifications.add(c.code,c)}),e.notifications.bind("remove",function(a){d.notifications.remove(e.id+":"+a.code)}),c=_.debounce(function(){d.renderNotifications()}),d.notifications.bind("add",function(a){wp.a11y.speak(a.message,"assertive"),c()}),d.notifications.bind("remove",c),d.renderNotifications(),d.notificationsContainer.on("click",".override-post-conflict",function(a){var b;a.preventDefault(),b=_.clone(e.get()),b.post_modified_gmt="",e.set(b),_.each(d.postFieldControls,function(a){a.notifications&&a.notifications.remove("post_update_conflict")}),e.notifications.remove("post_update_conflict")}),a.bind("error",function(b){var c,d;b.update_conflicted_setting_values&&(c=b.update_conflicted_setting_values[e.id],c&&(d=e.get(),_.each(c,function(b,c){var f,g;"post_modified"!==c&&"post_modified_gmt"!==c&&b!==d[c]&&(f=a.control(e.id+"["+c+"]"),f&&f.notifications&&(g=new a.Notification("post_update_conflict",{message:a.Posts.data.l10n.theirChange.replace("%s",String(b))}),f.notifications.remove(g.code),f.notifications.add(g.code,g)))})))}),a.bind("save",function(){d.resetPostFieldControlErrorNotifications()}))},resetPostFieldControlErrorNotifications:function(){var a=this;_.each(a.postFieldControls,function(a){a.notifications&&a.notifications.each(function(b){"error"===b.type&&a.notifications.remove(b.code)})})},isContextuallyActive:function(){var a=this;return a.active()}})}(wp.customize,jQuery);
  • customize-posts/trunk/js/customize-posts-panel.js

    r1406933 r1430660  
    11/* global wp, jQuery */
    2 /* eslint consistent-this: [ "error", "panel" ] */
     2/* eslint consistent-this: [ "error", "panel" ], no-magic-numbers: [ "error", { "ignore": [0,500] } ] */
    33
    44(function( api, $ ) {
     
    4545                };
    4646
     47                panel.setupPostAddition();
     48
    4749                /*
    4850                 * Set the initial visibility state for rendered notice.
     
    6567
    6668        /**
     69         * Add new post stub, which builds the UI & listens for click events.
     70         *
     71         * @return {void}
     72         */
     73        setupPostAddition: function() {
     74            var panel = this, descriptionContainer, addNewButton, postObj;
     75
     76            descriptionContainer = panel.container.find( '.panel-meta:first' );
     77            addNewButton = wp.template( 'customize-posts-add-new' );
     78            postObj = api.Posts.data.postTypes[ panel.postType ];
     79
     80            if ( postObj.current_user_can.create_posts ) {
     81                descriptionContainer.after( addNewButton( {
     82                    label: postObj.labels.singular_name
     83                } ) );
     84
     85                panel.container.find( '.add-new-post-stub' ).on( 'click', function( event ) {
     86                    var postData, button = $( this ), promise;
     87                    event.preventDefault();
     88                    button.prop( 'disabled', true );
     89
     90                    postData = {
     91                        post_status: 'publish'
     92                    };
     93                    if ( postObj.supports.title ) {
     94                        postData.post_title = api.Posts.data.l10n.noTitle;
     95                    }
     96
     97                    promise = api.Posts.insertAutoDraftPost( panel.postType );
     98                    promise.done( function( data ) {
     99                        data.setting.set( _.extend(
     100                            {},
     101                            data.setting.get(),
     102                            postData
     103                        ) );
     104
     105                        // Navigate to the newly-created post if it is public; otherwise, refresh the preview.
     106                        if ( postObj['public'] && data.url ) {
     107                            api.previewer.previewUrl( data.url );
     108                        } else {
     109                            api.previewer.refresh();
     110                        }
     111
     112                        /**
     113                         * Perform a dance to focus on the first control in the section.
     114                         *
     115                         * There is a race condition where focusing on a control too
     116                         * early can result in the focus logic not being able to see
     117                         * any visible inputs to focus on.
     118                         *
     119                         * @returns {void}
     120                         */
     121                        function focusControlOnceFocusable() {
     122                            var firstControl = data.section.controls()[0];
     123                            function onChangeActive( isActive ) {
     124                                if ( isActive ) {
     125                                    data.section.active.unbind( onChangeActive );
     126                                    _.defer( function() {
     127                                        firstControl.focus( {
     128                                            completeCallback: function() {
     129                                                firstControl.container.find( 'input:first' ).select();
     130                                            }
     131                                        } );
     132                                    } );
     133                                }
     134                            }
     135                            if ( firstControl ) {
     136                                data.section.active.bind( onChangeActive );
     137                            }
     138                        }
     139
     140                        data.section.focus( {
     141                            completeCallback: focusControlOnceFocusable
     142                        } );
     143                    } );
     144                    promise.always( function() {
     145                        button.prop( 'disabled', false );
     146                    } );
     147                } );
     148            }
     149        },
     150
     151        /**
    67152         * Allow an active panel to be contextually active even when it has no active controls.
    68153         *
    69          * @returns {boolean}
     154         * @returns {boolean} Whether contextually active.
    70155         */
    71156        isContextuallyActive: function() {
  • customize-posts/trunk/js/customize-posts-panel.min.js

    r1381641 r1430660  
    1 !function(a,b){"use strict";a.Posts||(a.Posts={}),a.Posts.PostsPanel=a.Panel.extend({postType:"post",ready:function(){var c=this;a.Panel.prototype.ready.call(c),c.params.post_type&&(c.postType=c.params.post_type),c.deferred.embedded.done(function(){var d,e,f;d=c.container.find(".panel-meta:first"),e=b(b.trim(wp.template("customize-panel-posts-"+c.postType+"-notice")({message:c.params.noPostsLoadedMessage}))),d.append(e),f=function(){return 0===_.filter(c.sections(),function(a){return a.active()}).length},e.toggle(f()),a.previewer.deferred.active.done(function(){e.toggle(f())}),a.bind("pane-contents-reflowed",function(){var b="resolved"===a.previewer.deferred.active.state()?"fast":0;f()?e.slideDown(b):e.slideUp(b)})})},isContextuallyActive:function(){var a=this;return a.active()}})}(wp.customize,jQuery);
     1!function(a,b){"use strict";a.Posts||(a.Posts={}),a.Posts.PostsPanel=a.Panel.extend({postType:"post",ready:function(){var c=this;a.Panel.prototype.ready.call(c),c.params.post_type&&(c.postType=c.params.post_type),c.deferred.embedded.done(function(){var d,e,f;d=c.container.find(".panel-meta:first"),e=b(b.trim(wp.template("customize-panel-posts-"+c.postType+"-notice")({message:c.params.noPostsLoadedMessage}))),d.append(e),f=function(){return 0===_.filter(c.sections(),function(a){return a.active()}).length},c.setupPostAddition(),e.toggle(f()),a.previewer.deferred.active.done(function(){e.toggle(f())}),a.bind("pane-contents-reflowed",function(){var b="resolved"===a.previewer.deferred.active.state()?"fast":0;f()?e.slideDown(b):e.slideUp(b)})})},setupPostAddition:function(){var c,d,e,f=this;c=f.container.find(".panel-meta:first"),d=wp.template("customize-posts-add-new"),e=a.Posts.data.postTypes[f.postType],e.current_user_can.create_posts&&(c.after(d({label:e.labels.singular_name})),f.container.find(".add-new-post-stub").on("click",function(c){var d,g,h=b(this);c.preventDefault(),h.prop("disabled",!0),d={post_status:"publish"},e.supports.title&&(d.post_title=a.Posts.data.l10n.noTitle),g=a.Posts.insertAutoDraftPost(f.postType),g.done(function(b){function c(){function a(d){d&&(b.section.active.unbind(a),_.defer(function(){c.focus({completeCallback:function(){c.container.find("input:first").select()}})}))}var c=b.section.controls()[0];c&&b.section.active.bind(a)}b.setting.set(_.extend({},b.setting.get(),d)),e["public"]&&b.url?a.previewer.previewUrl(b.url):a.previewer.refresh(),b.section.focus({completeCallback:c})}),g.always(function(){h.prop("disabled",!1)})}))},isContextuallyActive:function(){var a=this;return a.active()}})}(wp.customize,jQuery);
  • customize-posts/trunk/js/customize-posts.js

    r1406933 r1430660  
    1 /*global jQuery, wp, _, _wpCustomizePostsExports, console */
     1/* global jQuery, wp, _, _wpCustomizePostsExports, console */
    22
    33(function( api, $ ) {
     
    5858
    5959    /**
     60     * Get the post preview URL.
     61     *
     62     * @param {object} params - Parameters to configure the preview URL.
     63     * @param {number} params.post_id - Post ID to preview.
     64     * @param {string} [params.post_type] - Post type to preview.
     65     * @return {string} Preview URL.
     66     */
     67    component.getPreviewUrl = function( params ) {
     68        var url = api.settings.url.home,
     69            args = {};
     70
     71        if ( ! params || ! params.post_id ) {
     72            throw new Error( 'Missing params' );
     73        }
     74
     75        args.preview = true;
     76        if ( 'page' === params.post_type ) {
     77            args.page_id = params.post_id;
     78        } else {
     79            args.p = params.post_id;
     80            if ( params.post_type && 'post' !== params.post_type ) {
     81                args.post_type = params.post_type;
     82            }
     83        }
     84
     85        return url + '?' + $.param( args );
     86    };
     87
     88    /**
     89     * Insert a new stubbed `auto-draft` post.
     90     *
     91     * @param {string} postType Post type to create.
     92     * @return {jQuery.promise} Promise resolved with the added section.
     93     */
     94    component.insertAutoDraftPost = function( postType ) {
     95        var request, deferred = $.Deferred();
     96
     97        request = wp.ajax.post( 'customize-posts-insert-auto-draft', {
     98            'customize-posts-nonce': api.Posts.data.nonce,
     99            'wp_customize': 'on',
     100            'post_type': postType
     101        } );
     102
     103        request.done( function( response ) {
     104            var sections = component.receivePreviewData( response );
     105            if ( 0 === sections.length ) {
     106                deferred.rejectWith( 'no_sections' );
     107            } else {
     108                deferred.resolve( _.extend(
     109                    {
     110                        section: sections[0],
     111                        setting: api( sections[0].id )
     112                    },
     113                    response
     114                ) );
     115            }
     116        } );
     117
     118        request.fail( function( response ) {
     119            var error = response || '';
     120
     121            if ( 'undefined' !== typeof response.message ) {
     122                error = response.message;
     123            }
     124
     125            console.error( error );
     126            deferred.rejectWith( error );
     127        } );
     128
     129        return deferred.promise();
     130    };
     131
     132    /**
    60133     * Handle receiving customized-posts messages from the preview.
    61134     *
    62      * @param {object} data
     135     * @param {object} data Data from preview.
     136     * @return {wp.customize.Section[]} Sections added.
    63137     */
    64138    component.receivePreviewData = function( data ) {
    65         _.each( data.settings, function( setting, id ) {
     139        var sections = [], section, setting;
     140
     141        _.each( data.settings, function( settingArgs, id ) {
    66142
    67143            if ( ! api.has( id ) ) {
    68                 api.create( id, id, setting.value, {
    69                     transport: setting.transport,
     144                setting = api.create( id, id, settingArgs.value, {
     145                    transport: settingArgs.transport,
    70146                    previewer: api.previewer,
    71                     dirty: setting.dirty
     147                    dirty: settingArgs.dirty
    72148                } );
    73             }
    74 
    75             if ( 'post' === setting.type ) {
    76                 component.addPostSection( id );
    77             }
    78         } );
     149                if ( settingArgs.dirty ) {
     150                    setting.callbacks.fireWith( setting, [ setting.get(), {} ] );
     151                }
     152            }
     153
     154            if ( 'post' === settingArgs.type ) {
     155                section = component.addPostSection( id );
     156                if ( section ) {
     157                    sections.push( section );
     158                }
     159            }
     160        } );
     161
     162        return sections;
    79163    };
    80164
     
    82166     * Handle adding post setting.
    83167     *
    84      * @param {string} id
     168     * @param {string} id - Section ID (same as post setting ID).
     169     * @return {wp.customize.Section|null} Added (or existing) section, or null if not able to be added.
    85170     */
    86171    component.addPostSection = function( id ) {
     
    92177                console.error( 'Unrecognized post type: ' + postType );
    93178            }
    94             return;
     179            return null;
     180        }
     181        if ( ! component.data.postTypes[ postType ].show_in_customizer ) {
     182            return null;
    95183        }
    96184        postId = parseInt( idParts[2], 10 );
     
    99187                console.error( 'Bad post id: ' + idParts[2] );
    100188            }
    101             return;
     189            return null;
    102190        }
    103191
     
    107195
    108196        if ( api.section.has( sectionId ) ) {
    109             return;
     197            return api.section( sectionId );
    110198        }
    111199
     
    124212        });
    125213        api.section.add( sectionId, section );
     214
     215        return section;
     216    };
     217
     218    /**
     219     * Emulate sanitize_title_with_dashes().
     220     *
     221     * @todo This can be more verbose, supporting Unicode.
     222     *
     223     * @param {string} title Title
     224     * @returns {string} slug
     225     */
     226    component.sanitizeTitleWithDashes = function sanitizeTitleWithDashes( title ) {
     227        var slug = $.trim( title ).toLowerCase();
     228        slug = slug.replace( /[^a-z0-9\-_]+/g, '-' );
     229        slug = slug.replace( /--+/g, '-' );
     230        slug = slug.replace( /^-+|-+$/g, '' );
     231        return slug;
     232    };
     233
     234    /**
     235     * Handle purging the trash after Customize `saved`.
     236     *
     237     * @returns {void}
     238     */
     239    component.purgeTrash = function purgeTrash() {
     240        api.section.each( function( section ) {
     241            if ( section.extended( component.PostSection ) && 'trash' === api( section.id ).get().post_status ) {
     242                api.section.remove( section.id );
     243                section.collapse();
     244                section.panel.set( false );
     245                if ( ! _.isUndefined( component.previewedQuery ) && true === component.previewedQuery.get().isSingular ) {
     246                    api.previewer.previewUrl( api.settings.url.home );
     247                }
     248            }
     249        } );
     250    };
     251
     252    /**
     253     * Update settings quietly.
     254     *
     255     * Update all of the settings without causing the overall dirty state to change.
     256     *
     257     * This was originally part of the Customize Setting Validation plugin.
     258     *
     259     * @link https://github.com/xwp/wp-customize-setting-validation/blob/2e5ddc66a870ad7b1aee5f8e414bad4b78e120d2/js/customize-setting-validation.js#L186-L209
     260     *
     261     * @param {object} settingValues Setting IDs mapped to values.
     262     * @return {void}
     263     */
     264    component.updateSettingsQuietly = function updateSettingsQuietly( settingValues ) {
     265        var wasSaved = api.state( 'saved' ).get();
     266        _.each( settingValues, function( value, settingId ) {
     267            var setting = api( settingId ), wasDirty;
     268            if ( setting && ! _.isEqual( setting.get(), value ) ) {
     269                wasDirty = setting._dirty;
     270                setting.set( value );
     271                setting._dirty = wasDirty;
     272            }
     273        } );
     274        api.state( 'saved' ).set( wasSaved );
    126275    };
    127276
     
    133282
    134283        api.previewer.bind( 'customized-posts', component.receivePreviewData );
     284
     285        // Track some of the recieved preview data from `customized-posts`.
     286        component.previewedQuery = new api.Value( {} );
     287        api.previewer.bind( 'customized-posts', function( data ) {
     288            var query = {};
     289            _.each( [ 'isSingular', 'isPostPreview', 'queriedPostId' ], function( key ) {
     290                if ( ! _.isUndefined( data[ key ] ) ) {
     291                    query[ key ] = data[ key ];
     292                }
     293            } );
     294            component.previewedQuery.set( query );
     295        } );
     296
     297        // Purge trashed posts and update client settings with saved values from server.
     298        api.bind( 'saved', function( data ) {
     299            if ( data.saved_post_setting_values ) {
     300                component.updateSettingsQuietly( data.saved_post_setting_values );
     301            }
     302
     303            component.purgeTrash();
     304        } );
    135305
    136306        /**
     
    147317
    148318        /**
    149          * Focus on the section requested from the preview.
     319         * Focus on the control requested from the preview.
    150320         *
    151321         * @todo This can be merged into Core to correspond with focus-control-for-setting.
  • customize-posts/trunk/js/customize-posts.min.js

    r1406933 r1430660  
    1 !function(a,b){"use strict";var c;a.Posts||(a.Posts={}),c=a.Posts,c.data={postTypes:{},l10n:{sectionCustomizeActionTpl:"",fieldTitleLabel:"",fieldContentLabel:"",fieldExcerptLabel:""},postIdInput:null},"undefined"!=typeof _wpCustomizePostsExports&&_.extend(c.data,_wpCustomizePostsExports),a.panelConstructor.posts=c.PostsPanel,a.sectionConstructor.post=c.PostSection,a.controlConstructor.post_discussion_fields=a.controlConstructor.dynamic.extend({initialize:function(b,c){c.params.type="post_discussion_fields",c.params.field_type="checkbox",a.controlConstructor.dynamic.prototype.initialize.call(this,b,c)}}),_.each(c.data.postTypes,function(b){var c,d;c="posts["+b.name+"]",a.panelConstructor[c]||(a.panelConstructor[c]=a.panelConstructor.posts.extend({postType:b})),d="post["+b.name+"]",a.sectionConstructor[d]||(a.sectionConstructor[d]=a.sectionConstructor.post.extend({postType:b}))}),c.receivePreviewData=function(b){_.each(b.settings,function(b,d){a.has(d)||a.create(d,d,b.value,{transport:b.transport,previewer:a.previewer,dirty:b.dirty}),"post"===b.type&&c.addPostSection(d)})},c.addPostSection=function(d){var e,f,g,h,i,j,k,l,m;return k=d.replace(/]/g,"").split("["),j=k[1],c.data.postTypes[j]?(i=parseInt(k[2],10))?(h="post["+j+"]",g="posts["+j+"]",f=d,void(a.section.has(f)||(l=a.sectionConstructor[h]||a.sectionConstructor.post,m=b("<div>").html(c.data.l10n.sectionCustomizeActionTpl.replace("%s",a.panel(g).params.title)),e=new l(f,{params:{id:f,panel:g,post_type:j,post_id:i,active:!0,customizeAction:m.text()}}),a.section.add(f,e)))):void("undefined"!=typeof console&&console.error&&console.error("Bad post id: "+k[2])):void("undefined"!=typeof console&&console.error&&console.error("Unrecognized post type: "+j))},a.bind("ready",function(){c.postIdInput=b('<input type="hidden" id="post_ID" name="post_ID">'),b("body").append(c.postIdInput),a.previewer.bind("customized-posts",c.receivePreviewData),a.previewer.bind("focus-section",function(b){var c=a.section(b);c&&c.focus()}),a.previewer.bind("focus-control",function(b){var c=a.control(b);c&&c.focus()})})}(wp.customize,jQuery);
     1!function(a,b){"use strict";var c;a.Posts||(a.Posts={}),c=a.Posts,c.data={postTypes:{},l10n:{sectionCustomizeActionTpl:"",fieldTitleLabel:"",fieldContentLabel:"",fieldExcerptLabel:""},postIdInput:null},"undefined"!=typeof _wpCustomizePostsExports&&_.extend(c.data,_wpCustomizePostsExports),a.panelConstructor.posts=c.PostsPanel,a.sectionConstructor.post=c.PostSection,a.controlConstructor.post_discussion_fields=a.controlConstructor.dynamic.extend({initialize:function(b,c){c.params.type="post_discussion_fields",c.params.field_type="checkbox",a.controlConstructor.dynamic.prototype.initialize.call(this,b,c)}}),_.each(c.data.postTypes,function(b){var c,d;c="posts["+b.name+"]",a.panelConstructor[c]||(a.panelConstructor[c]=a.panelConstructor.posts.extend({postType:b})),d="post["+b.name+"]",a.sectionConstructor[d]||(a.sectionConstructor[d]=a.sectionConstructor.post.extend({postType:b}))}),c.getPreviewUrl=function(c){var d=a.settings.url.home,e={};if(!c||!c.post_id)throw new Error("Missing params");return e.preview=!0,"page"===c.post_type?e.page_id=c.post_id:(e.p=c.post_id,c.post_type&&"post"!==c.post_type&&(e.post_type=c.post_type)),d+"?"+b.param(e)},c.insertAutoDraftPost=function(d){var e,f=b.Deferred();return e=wp.ajax.post("customize-posts-insert-auto-draft",{"customize-posts-nonce":a.Posts.data.nonce,wp_customize:"on",post_type:d}),e.done(function(b){var d=c.receivePreviewData(b);0===d.length?f.rejectWith("no_sections"):f.resolve(_.extend({section:d[0],setting:a(d[0].id)},b))}),e.fail(function(a){var b=a||"";"undefined"!=typeof a.message&&(b=a.message),console.error(b),f.rejectWith(b)}),f.promise()},c.receivePreviewData=function(b){var d,e,f=[];return _.each(b.settings,function(b,g){a.has(g)||(e=a.create(g,g,b.value,{transport:b.transport,previewer:a.previewer,dirty:b.dirty}),b.dirty&&e.callbacks.fireWith(e,[e.get(),{}])),"post"===b.type&&(d=c.addPostSection(g),d&&f.push(d))}),f},c.addPostSection=function(d){var e,f,g,h,i,j,k,l,m;return k=d.replace(/]/g,"").split("["),j=k[1],c.data.postTypes[j]?c.data.postTypes[j].show_in_customizer?(i=parseInt(k[2],10))?(h="post["+j+"]",g="posts["+j+"]",f=d,a.section.has(f)?a.section(f):(l=a.sectionConstructor[h]||a.sectionConstructor.post,m=b("<div>").html(c.data.l10n.sectionCustomizeActionTpl.replace("%s",a.panel(g).params.title)),e=new l(f,{params:{id:f,panel:g,post_type:j,post_id:i,active:!0,customizeAction:m.text()}}),a.section.add(f,e),e)):("undefined"!=typeof console&&console.error&&console.error("Bad post id: "+k[2]),null):null:("undefined"!=typeof console&&console.error&&console.error("Unrecognized post type: "+j),null)},c.sanitizeTitleWithDashes=function(a){var c=b.trim(a).toLowerCase();return c=c.replace(/[^a-z0-9\-_]+/g,"-"),c=c.replace(/--+/g,"-"),c=c.replace(/^-+|-+$/g,"")},c.purgeTrash=function(){a.section.each(function(b){b.extended(c.PostSection)&&"trash"===a(b.id).get().post_status&&(a.section.remove(b.id),b.collapse(),b.panel.set(!1),_.isUndefined(c.previewedQuery)||!0!==c.previewedQuery.get().isSingular||a.previewer.previewUrl(a.settings.url.home))})},c.updateSettingsQuietly=function(b){var c=a.state("saved").get();_.each(b,function(b,c){var d,e=a(c);e&&!_.isEqual(e.get(),b)&&(d=e._dirty,e.set(b),e._dirty=d)}),a.state("saved").set(c)},a.bind("ready",function(){c.postIdInput=b('<input type="hidden" id="post_ID" name="post_ID">'),b("body").append(c.postIdInput),a.previewer.bind("customized-posts",c.receivePreviewData),c.previewedQuery=new a.Value({}),a.previewer.bind("customized-posts",function(a){var b={};_.each(["isSingular","isPostPreview","queriedPostId"],function(c){_.isUndefined(a[c])||(b[c]=a[c])}),c.previewedQuery.set(b)}),a.bind("saved",function(a){a.saved_post_setting_values&&c.updateSettingsQuietly(a.saved_post_setting_values),c.purgeTrash()}),a.previewer.bind("focus-section",function(b){var c=a.section(b);c&&c.focus()}),a.previewer.bind("focus-control",function(b){var c=a.control(b);c&&c.focus()})})}(wp.customize,jQuery);
  • customize-posts/trunk/js/customize-preview-posts.js

    r1406933 r1430660  
    1 /*global wp, _wpCustomizePreviewPostsData, JSON */
     1/* global wp, _wpCustomizePreviewPostsData, JSON */
     2
    23( function( api, $ ) {
    34    'use strict';
     
    2728
    2829        _.each( settings, function( setting, id ) {
    29             var partial;
    3030
    3131            if ( ! api.has( id ) ) {
     
    3737            if ( 'post' === setting.type ) {
    3838
    39                 // Post field partial for post_title.
    40                 partial = new api.previewPosts.PostFieldPartial( id + '[post_title]', {
    41                     params: {
    42                         settings: [ id ]
     39                // Add the partials.
     40                _.each( api.previewPosts.partialSchema( id ), function( schema ) {
     41                    var partial, addPartial, matches, baseSelector, idPattern = /^post\[(.+?)]\[(-?\d+)]\[(.+?)](?:\[(.+?)])?$/;
     42
     43                    matches = schema.id.match( idPattern );
     44                    if ( ! matches ) {
     45                        throw new Error( 'Bad PostFieldPartial id. Expected post[:post_type][:post_id][:field_id]' );
     46                    }
     47
     48                    if ( schema.params.selector ) {
     49                        if ( ! schema.params.bodySelector ) {
     50                            baseSelector = '.hentry.post-' + String( parseInt( matches[2], 10 ) ) + '.type-' + matches[1];
     51                        } else {
     52                            baseSelector = '.postid-' + String( parseInt( matches[2], 10 ) ) + '.single-' + matches[1];
     53                        }
     54                        schema.params.selector = baseSelector + ' ' + schema.params.selector;
     55
     56                        addPartial =
     57                            ! schema.params.singularOnly && ! schema.params.archiveOnly ||
     58                            schema.params.singularOnly && api.previewPosts.data.isSingular ||
     59                            schema.params.archiveOnly && ! api.previewPosts.data.isSingular;
     60
     61                        if ( addPartial ) {
     62                            partial = new api.previewPosts.PostFieldPartial( schema.id, { params: schema.params } );
     63                            api.selectiveRefresh.partial.add( partial.id, partial );
     64                        }
     65                    } else {
     66                        partial = new api.previewPosts.PostFieldPartial( schema.id, { params: schema.params } );
     67
     68                        /**
     69                         * Suppress wasted partial refreshes for partials that lack selectors.
     70                         *
     71                         * For example, since the post_name field is not normally
     72                         * displayed, suppress refreshing changes.
     73                         *
     74                         * @returns {jQuery.promise} Promise.
     75                         */
     76                        partial.refresh = function refreshWithoutSelector() {
     77                            var deferred = $.Deferred();
     78                            if ( this.params.fallbackRefresh ) {
     79                                api.selectiveRefresh.requestFullRefresh();
     80                                deferred.resolve();
     81                            } else {
     82                                deferred.reject();
     83                            }
     84                            return deferred.promise();
     85                        };
     86                        api.selectiveRefresh.partial.add( partial.id, partial );
    4387                    }
    4488                } );
    45                 api.selectiveRefresh.partial.add( partial.id, partial );
    46 
    47                 // Post field partial for post_content.
    48                 partial = new api.previewPosts.PostFieldPartial( id + '[post_content]', {
    49                     params: {
    50                         settings: [ id ]
    51                     }
    52                 } );
    53                 api.selectiveRefresh.partial.add( partial.id, partial );
    54 
    55                 // Post field partial for post_excerpt.
    56                 partial = new api.previewPosts.PostFieldPartial( id + '[post_excerpt]', {
    57                     params: {
    58                         settings: [ id ]
    59                     }
    60                 } );
    61                 api.selectiveRefresh.partial.add( partial.id, partial );
    62 
    63                 // Post field partial for comment_status comments-area.
    64                 if ( api.previewPosts.data.isSingular ) {
    65                     partial = new api.previewPosts.PostFieldPartial( id + '[comment_status][comments-area]', {
    66                         params: {
    67                             settings: [ id ],
    68                             containerInclusive: true,
    69                             fallbackRefresh: true
    70                         }
    71                     } );
    72                     api.selectiveRefresh.partial.add( partial.id, partial );
    73                 }
    74 
    75                 // Post field partial for comment_status comments-link.
    76                 partial = new api.previewPosts.PostFieldPartial( id + '[comment_status][comments-link]', {
    77                     params: {
    78                         settings: [ id ],
    79                         containerInclusive: true,
    80                         fallbackRefresh: false
    81                     }
    82                 } );
    83                 partial.fallback = function() {
    84                     if ( ! this.params.fallbackRefresh && 0 === this.placements().length && ! api.previewPosts.data.isSingular ) {
    85                         api.selectiveRefresh.requestFullRefresh();
    86                     } else {
    87                         api.previewPosts.PostFieldPartial.prototype.refresh.call( this );
    88                     }
    89                 };
    90                 api.selectiveRefresh.partial.add( partial.id, partial );
    91 
    92                 // Post field partial for ping_status.
    93                 partial = new api.previewPosts.PostFieldPartial( id + '[ping_status]', {
    94                     params: {
    95                         settings: [ id ],
    96                         containerInclusive: true,
    97                         fallbackRefresh: false
    98                     }
    99                 } );
    100                 api.selectiveRefresh.partial.add( partial.id, partial );
    101 
    102                 // Post field partial for post_author author-bio.
    103                 partial = new api.previewPosts.PostFieldPartial( id + '[post_author][author-bio]', {
    104                     params: {
    105                         settings: [ id ],
    106                         containerInclusive: true,
    107                         fallbackRefresh: true
    108                     }
    109                 } );
    110                 api.selectiveRefresh.partial.add( partial.id, partial );
    111 
    112                 // Post field partial for post_author byline.
    113                 partial = new api.previewPosts.PostFieldPartial( id + '[post_author][byline]', {
    114                     params: {
    115                         settings: [ id ],
    116                         containerInclusive: true,
    117                         fallbackRefresh: false
    118                     }
    119                 } );
    120                 api.selectiveRefresh.partial.add( partial.id, partial );
    121 
    122                 // Post field partial for post_author avatar.
    123                 partial = new api.previewPosts.PostFieldPartial( id + '[post_author][avatar]', {
    124                     params: {
    125                         settings: [ id ],
    126                         containerInclusive: true,
    127                         fallbackRefresh: false
    128                     }
    129                 } );
    130                 api.selectiveRefresh.partial.add( partial.id, partial );
    13189            }
    13290
    13391            // @todo Trigger event for plugins and postmeta controllers.
    13492        } );
     93    };
     94
     95    /**
     96     * Geneate the partial schema.
     97     *
     98     * @param {string} id
     99     */
     100    api.previewPosts.partialSchema = function( id ) {
     101        var partialSchema = [];
     102
     103        _.each( api.previewPosts.data.partialSchema, function( params, key ) {
     104            var partialId, idParts;
     105
     106            idParts = key.replace( /]/g, '' ).split( /\[/ );
     107            partialId = id + '[' + idParts.join( '][' ) + ']';
     108
     109            params.settings = [ id ];
     110            partialSchema.push( {
     111                id: partialId,
     112                params: api.previewPosts.snakeToCamel( params )
     113            } );
     114        } );
     115
     116        return partialSchema;
     117    };
     118
     119    /**
     120     * Convert the schema snakecase params to camelcase.
     121     *
     122     * @param {object} params
     123     */
     124    api.previewPosts.snakeToCamel = function( params ) {
     125        var newParams = {};
     126
     127        _.each( params, function( value, key ) {
     128            var i = key.replace( /_\w/g, function( str ) {
     129                return str[1].toUpperCase();
     130            } );
     131            newParams[ i ] = value;
     132        } );
     133
     134        return newParams;
    135135    };
    136136
  • customize-posts/trunk/js/customize-preview-posts.min.js

    r1406933 r1430660  
    1 !function(a,b){"use strict";a.previewPosts||(a.previewPosts={}),a.previewPosts.data||(a.previewPosts.data={}),b(document.body).on("mousedown",function(a){a.shiftKey&&a.preventDefault()}),a.previewPosts.addPartials=function(b){_.each(b,function(b,c){var d;a.has(c)||a.create(c,b.value,{id:c}),"post"===b.type&&(d=new a.previewPosts.PostFieldPartial(c+"[post_title]",{params:{settings:[c]}}),a.selectiveRefresh.partial.add(d.id,d),d=new a.previewPosts.PostFieldPartial(c+"[post_content]",{params:{settings:[c]}}),a.selectiveRefresh.partial.add(d.id,d),d=new a.previewPosts.PostFieldPartial(c+"[post_excerpt]",{params:{settings:[c]}}),a.selectiveRefresh.partial.add(d.id,d),a.previewPosts.data.isSingular&&(d=new a.previewPosts.PostFieldPartial(c+"[comment_status][comments-area]",{params:{settings:[c],containerInclusive:!0,fallbackRefresh:!0}}),a.selectiveRefresh.partial.add(d.id,d)),d=new a.previewPosts.PostFieldPartial(c+"[comment_status][comments-link]",{params:{settings:[c],containerInclusive:!0,fallbackRefresh:!1}}),d.fallback=function(){this.params.fallbackRefresh||0!==this.placements().length||a.previewPosts.data.isSingular?a.previewPosts.PostFieldPartial.prototype.refresh.call(this):a.selectiveRefresh.requestFullRefresh()},a.selectiveRefresh.partial.add(d.id,d),d=new a.previewPosts.PostFieldPartial(c+"[ping_status]",{params:{settings:[c],containerInclusive:!0,fallbackRefresh:!1}}),a.selectiveRefresh.partial.add(d.id,d),d=new a.previewPosts.PostFieldPartial(c+"[post_author][author-bio]",{params:{settings:[c],containerInclusive:!0,fallbackRefresh:!0}}),a.selectiveRefresh.partial.add(d.id,d),d=new a.previewPosts.PostFieldPartial(c+"[post_author][byline]",{params:{settings:[c],containerInclusive:!0,fallbackRefresh:!1}}),a.selectiveRefresh.partial.add(d.id,d),d=new a.previewPosts.PostFieldPartial(c+"[post_author][avatar]",{params:{settings:[c],containerInclusive:!0,fallbackRefresh:!1}}),a.selectiveRefresh.partial.add(d.id,d))})},a.previewPosts.addSettings=function(b){a.previewPosts.addPartials(b),a.preview.send("customized-posts",{settings:b})},a.bind("preview-ready",function(){a.preview.bind("active",function(){var c={};_.extend(a.previewPosts.data,_wpCustomizePreviewPostsData),a.each(function(b){var d=a.previewPosts.data.settingProperties[b.id];d&&(c[b.id]={value:b.get(),dirty:Boolean(a.settings._dirty[b.id]),type:d.type,transport:d.transport})}),a.previewPosts.addPartials(c),a.preview.send("customized-posts",{isPostPreview:a.previewPosts.data.isPostPreview,isSingular:a.previewPosts.data.isSingular,queriedPostId:a.previewPosts.data.queriedPostId,settings:c}),b(document.body).on("click",".post-edit-link",function(c){var d,e=b(this);d=e.data("customize-post-setting-id"),c.preventDefault(),d&&a.preview.send("focus-section",d)})}),a.selectiveRefresh.bind("render-partials-response",function(b){b.customize_post_settings&&a.previewPosts.addSettings(b.customize_post_settings)}),b(document).ajaxSuccess(function(b,c,d,e){var f,g="POST"===d.type&&-1!==d.url.indexOf("infinity=scrolling");g&&(f="string"==typeof e?JSON.parse(e):e,f.customize_post_settings&&a.previewPosts.addSettings(f.customize_post_settings))})})}(wp.customize,jQuery);
     1!function(a,b){"use strict";a.previewPosts||(a.previewPosts={}),a.previewPosts.data||(a.previewPosts.data={}),b(document.body).on("mousedown",function(a){a.shiftKey&&a.preventDefault()}),a.previewPosts.addPartials=function(c){_.each(c,function(c,d){a.has(d)||a.create(d,c.value,{id:d}),"post"===c.type&&_.each(a.previewPosts.partialSchema(d),function(c){var d,e,f,g,h=/^post\[(.+?)]\[(-?\d+)]\[(.+?)](?:\[(.+?)])?$/;if(f=c.id.match(h),!f)throw new Error("Bad PostFieldPartial id. Expected post[:post_type][:post_id][:field_id]");c.params.selector?(g=c.params.bodySelector?".postid-"+String(parseInt(f[2],10))+".single-"+f[1]:".hentry.post-"+String(parseInt(f[2],10))+".type-"+f[1],c.params.selector=g+" "+c.params.selector,e=!c.params.singularOnly&&!c.params.archiveOnly||c.params.singularOnly&&a.previewPosts.data.isSingular||c.params.archiveOnly&&!a.previewPosts.data.isSingular,e&&(d=new a.previewPosts.PostFieldPartial(c.id,{params:c.params}),a.selectiveRefresh.partial.add(d.id,d))):(d=new a.previewPosts.PostFieldPartial(c.id,{params:c.params}),d.refresh=function(){var c=b.Deferred();return this.params.fallbackRefresh?(a.selectiveRefresh.requestFullRefresh(),c.resolve()):c.reject(),c.promise()},a.selectiveRefresh.partial.add(d.id,d))})})},a.previewPosts.partialSchema=function(b){var c=[];return _.each(a.previewPosts.data.partialSchema,function(d,e){var f,g;g=e.replace(/]/g,"").split(/\[/),f=b+"["+g.join("][")+"]",d.settings=[b],c.push({id:f,params:a.previewPosts.snakeToCamel(d)})}),c},a.previewPosts.snakeToCamel=function(a){var b={};return _.each(a,function(a,c){var d=c.replace(/_\w/g,function(a){return a[1].toUpperCase()});b[d]=a}),b},a.previewPosts.addSettings=function(b){a.previewPosts.addPartials(b),a.preview.send("customized-posts",{settings:b})},a.bind("preview-ready",function(){a.preview.bind("active",function(){var c={};_.extend(a.previewPosts.data,_wpCustomizePreviewPostsData),a.each(function(b){var d=a.previewPosts.data.settingProperties[b.id];d&&(c[b.id]={value:b.get(),dirty:Boolean(a.settings._dirty[b.id]),type:d.type,transport:d.transport})}),a.previewPosts.addPartials(c),a.preview.send("customized-posts",{isPostPreview:a.previewPosts.data.isPostPreview,isSingular:a.previewPosts.data.isSingular,queriedPostId:a.previewPosts.data.queriedPostId,settings:c}),b(document.body).on("click",".post-edit-link",function(c){var d,e=b(this);d=e.data("customize-post-setting-id"),c.preventDefault(),d&&a.preview.send("focus-section",d)})}),a.selectiveRefresh.bind("render-partials-response",function(b){b.customize_post_settings&&a.previewPosts.addSettings(b.customize_post_settings)}),b(document).ajaxSuccess(function(b,c,d,e){var f,g="POST"===d.type&&-1!==d.url.indexOf("infinity=scrolling");g&&(f="string"==typeof e?JSON.parse(e):e,f.customize_post_settings&&a.previewPosts.addSettings(f.customize_post_settings))})})}(wp.customize,jQuery);
  • customize-posts/trunk/js/edit-post-preview-admin.js

    r1406933 r1430660  
    7171                    editor.setContent( wp.editor.autop( data[ postSettingId ].post_content ) );
    7272                }
     73                // @todo Handle post-status sync.
    7374                $( '#content' ).val( data[ postSettingId ].post_content ).trigger( 'change' );
    7475                $( '#excerpt' ).val( data[ postSettingId ].post_excerpt ).trigger( 'change' );
  • customize-posts/trunk/php/class-customize-posts-plugin.php

    r1406933 r1430660  
    6565        add_filter( 'user_has_cap', array( $this, 'grant_customize_capability' ), 10, 3 );
    6666        add_filter( 'customize_loaded_components', array( $this, 'filter_customize_loaded_components' ), 100, 2 );
     67        add_action( 'customize_register', array( $this, 'load_support_classes' ) );
    6768
    6869        require_once dirname( __FILE__ ) . '/class-wp-customize-postmeta-controller.php';
     
    113114
    114115        return $components;
     116    }
     117
     118    /**
     119     * Load theme and plugin compatibility classes.
     120     *
     121     * @codeCoverageIgnore
     122     *
     123     * @param WP_Customize_Manager $wp_customize Manager.
     124     */
     125    function load_support_classes( $wp_customize ) {
     126
     127        // Theme & Plugin Support.
     128        require_once dirname( __FILE__ ) . '/class-customize-posts-support.php';
     129        require_once dirname( __FILE__ ) . '/class-customize-posts-plugin-support.php';
     130        require_once dirname( __FILE__ ) . '/class-customize-posts-theme-support.php';
     131
     132        foreach ( array( 'theme', 'plugin' ) as $type ) {
     133            foreach ( glob( dirname( __FILE__ ) . '/' . $type . '-support/class-*.php' ) as $file_path ) {
     134                require_once $file_path;
     135
     136                $class_name = str_replace( '-', '_', preg_replace( '/^class-(.+)\.php$/', '$1', basename( $file_path ) ) );
     137                if ( class_exists( $class_name ) ) {
     138                    $wp_customize->posts->add_support( new $class_name( $wp_customize->posts ) );
     139                }
     140            }
     141        }
    115142    }
    116143
     
    133160    public function register_scripts( WP_Scripts $wp_scripts ) {
    134161        $suffix = ( SCRIPT_DEBUG ? '' : '.min' ) . '.js';
    135         $plugin_dir_url = plugin_dir_url( dirname( __FILE__ ) );
    136162
    137163        $handle = 'customize-controls-patched-36521';
    138         $src = $plugin_dir_url . 'js/customize-controls-patched-36521' . $suffix;
     164        $src = plugins_url( 'js/customize-controls-patched-36521' . $suffix, dirname( __FILE__ ) );
    139165        $deps = array( 'customize-controls' );
    140166        $in_footer = 1;
     
    142168
    143169        $handle = 'customize-posts-panel';
    144         $src = $plugin_dir_url . 'js/customize-posts-panel' . $suffix;
     170        $src = plugins_url( 'js/customize-posts-panel' . $suffix, dirname( __FILE__ ) );
    145171        $deps = array( 'customize-controls' );
    146172        $in_footer = 1;
     
    148174
    149175        $handle = 'customize-post-section';
    150         $src = $plugin_dir_url . 'js/customize-post-section' . $suffix;
     176        $src = plugins_url( 'js/customize-post-section' . $suffix, dirname( __FILE__ ) );
    151177        $deps = array( 'customize-controls' );
    152178        $in_footer = 1;
     
    154180
    155181        $handle = 'customize-dynamic-control';
    156         $src = $plugin_dir_url . 'js/customize-dynamic-control' . $suffix;
     182        $src = plugins_url( 'js/customize-dynamic-control' . $suffix, dirname( __FILE__ ) );
    157183        $deps = array( 'customize-controls' );
    158184        $in_footer = 1;
     
    160186
    161187        $handle = 'customize-posts';
    162         $src = $plugin_dir_url . 'js/customize-posts' . $suffix;
     188        $src = plugins_url( 'js/customize-posts' . $suffix, dirname( __FILE__ ) );
    163189        $deps = array(
    164190            'jquery',
     
    177203
    178204        $handle = 'customize-post-field-partial';
    179         $src = $plugin_dir_url . 'js/customize-post-field-partial' . $suffix;
     205        $src = plugins_url( 'js/customize-post-field-partial' . $suffix, dirname( __FILE__ ) );
    180206        $deps = array( 'customize-selective-refresh' );
    181207        $in_footer = 1;
     
    183209
    184210        $handle = 'customize-preview-posts';
    185         $src = $plugin_dir_url . 'js/customize-preview-posts' . $suffix;
     211        $src = plugins_url( 'js/customize-preview-posts' . $suffix, dirname( __FILE__ ) );
    186212        $deps = array( 'jquery', 'customize-preview', 'customize-post-field-partial' );
    187213        $in_footer = 1;
     
    189215
    190216        $handle = 'edit-post-preview-admin';
    191         $src = $plugin_dir_url . 'js/edit-post-preview-admin' . $suffix;
     217        $src = plugins_url( 'js/edit-post-preview-admin' . $suffix, dirname( __FILE__ ) );
    192218        $deps = array( 'post' );
    193219        $in_footer = 1;
     
    195221
    196222        $handle = 'edit-post-preview-customize';
    197         $src = $plugin_dir_url . 'js/edit-post-preview-customize' . $suffix;
     223        $src = plugins_url( 'js/edit-post-preview-customize' . $suffix, dirname( __FILE__ ) );
    198224        $deps = array( 'customize-controls' );
    199225        $in_footer = 1;
     
    202228        // Page templates.
    203229        $handle = 'customize-page-template';
    204         $src = $plugin_dir_url . 'js/customize-page-template' . $suffix;
     230        $src = plugins_url( 'js/customize-page-template' . $suffix, dirname( __FILE__ ) );
    205231        $deps = array( 'customize-controls', 'customize-posts' );
    206232        $in_footer = 1;
     
    208234
    209235        $handle = 'edit-post-preview-admin-page-template';
    210         $src = $plugin_dir_url . 'js/edit-post-preview-admin-page-template' . $suffix;
     236        $src = plugins_url( 'js/edit-post-preview-admin-page-template' . $suffix, dirname( __FILE__ ) );
    211237        $deps = array( 'edit-post-preview-admin' );
    212238        $in_footer = 1;
     
    215241        // Featured images.
    216242        $handle = 'customize-featured-image';
    217         $src = $plugin_dir_url . 'js/customize-featured-image' . $suffix;
     243        $src = plugins_url( 'js/customize-featured-image' . $suffix, dirname( __FILE__ ) );
    218244        $deps = array( 'customize-controls', 'customize-posts' );
    219245        $in_footer = 1;
     
    221247
    222248        $handle = 'edit-post-preview-admin-featured-image';
    223         $src = $plugin_dir_url . 'js/edit-post-preview-admin-featured-image' . $suffix;
     249        $src = plugins_url( 'js/edit-post-preview-admin-featured-image' . $suffix, dirname( __FILE__ ) );
    224250        $deps = array( 'edit-post-preview-admin' );
    225251        $in_footer = 1;
     
    227253
    228254        $handle = 'customize-preview-featured-image';
    229         $src = $plugin_dir_url . 'js/customize-preview-featured-image' . $suffix;
     255        $src = plugins_url( 'js/customize-preview-featured-image' . $suffix, dirname( __FILE__ ) );
    230256        $deps = array( 'customize-preview', 'customize-selective-refresh' );
    231257        $in_footer = 1;
     
    240266    public function register_styles( WP_Styles $wp_styles ) {
    241267        $suffix = ( SCRIPT_DEBUG ? '' : '.min' ) . '.css';
    242         $plugin_dir_url = plugin_dir_url( dirname( __FILE__ ) );
    243268
    244269        $handle = 'customize-posts';
    245         $src = $plugin_dir_url . 'css/customize-posts' . $suffix;
     270        $src = plugins_url( 'css/customize-posts' . $suffix, dirname( __FILE__ ) );
    246271        $deps = array( 'wp-admin' );
    247272        $version = $this->version;
     
    249274
    250275        $handle = 'edit-post-preview-customize';
    251         $src = $plugin_dir_url . 'css/edit-post-preview-customize' . $suffix;
     276        $src = plugins_url( 'css/edit-post-preview-customize' . $suffix, dirname( __FILE__ ) );
    252277        $deps = array( 'customize-controls' );
    253278        $wp_styles->add( $handle, $src, $deps, $this->version );
  • customize-posts/trunk/php/class-edit-post-preview.php

    r1406933 r1430660  
    9595
    9696    /**
     97     * Generate a preview permalink for a post/page.
     98     *
     99     * @access public
     100     *
     101     * @param WP_Post $post The post in question.
     102     * @return string Edit post link.
     103     */
     104    public static function get_preview_post_link( $post ) {
     105        $permalink = '';
     106
     107        if ( $post instanceof WP_Post ) {
     108            $id_param = ( 'page' === $post->post_type ) ? 'page_id' : 'p';
     109            $args = array();
     110            $args['preview'] = true;
     111            $args[ $id_param ] = $post->ID;
     112            if ( 'page_id' !== $id_param && 'post' !== $post->post_type ) {
     113                $args['post_type'] = $post->post_type;
     114            }
     115            $permalink = get_preview_post_link( $post, $args, home_url( '/' ) );
     116        }
     117
     118        return $permalink;
     119    }
     120
     121    /**
    97122     * Enqueue scripts for post edit screen.
    98123     */
     
    104129        $post = $this->get_previewed_post();
    105130
    106         $id_param = ( 'page' === $post->post_type ) ? 'page_id' : 'p';
    107         $url = get_preview_post_link( $post, array(), home_url( '?preview=true&' . $id_param . '=' . $post->ID ) );
    108131        $customize_url = add_query_arg(
    109132            array(
    110                 'url' => urlencode( $url ),
     133                'url' => urlencode( self::get_preview_post_link( $post ) ),
    111134                'previewed_post' => $post->ID,
    112135                'autofocus[section]' => sprintf( 'post[%s][%d]', $post->post_type, $post->ID ),
  • customize-posts/trunk/php/class-wp-customize-dynamic-control.php

    r1406933 r1430660  
    144144            <# } #>
    145145        <# } #>
    146         <div class="customize-setting-validation-message error" aria-live="assertive"></div>
    147146        <?php
    148147    }
  • customize-posts/trunk/php/class-wp-customize-featured-image-controller.php

    r1406933 r1430660  
    379379     * @param string                        $attachment_id The value to sanitize.
    380380     * @param WP_Customize_Postmeta_Setting $setting       Setting.
    381      * @param bool                          $strict        Whether validation is being done. This is part of the proposed patch in in #34893.
    382      * @return mixed|null Null if an input isn't valid, otherwise the sanitized value.
    383      */
    384     public function sanitize_setting( $attachment_id, WP_Customize_Postmeta_Setting $setting, $strict = false ) {
     381     * @return mixed|WP_Error Sanitized value or `WP_Error` if invalid.
     382     */
     383    public function sanitize_setting( $attachment_id, WP_Customize_Postmeta_Setting $setting ) {
    385384        unset( $setting );
     385        $has_setting_validation = method_exists( 'WP_Customize_Setting', 'validate' );
    386386
    387387        $is_valid = (
     
    398398         * So $attachment_id is either a valid attachment ID, -1, or false.
    399399         */
    400         if ( $strict && ! $is_valid  ) {
    401             if ( $strict ) {
    402                 return new WP_Error( 'invalid_attachment_id', __( 'The attachment is invalid.', 'customize-posts' ) );
    403             } else {
    404                 return null;
    405             }
     400        if ( ! $is_valid  ) {
     401            return $has_setting_validation ? new WP_Error( 'invalid_attachment_id', __( 'The attachment is invalid.', 'customize-posts' ) ) : null;
    406402        }
    407403
  • customize-posts/trunk/php/class-wp-customize-page-template-controller.php

    r1406933 r1430660  
    105105     * @param string                        $page_template The value to sanitize.
    106106     * @param WP_Customize_Postmeta_Setting $setting       Setting.
    107      * @param bool                          $strict        Whether validation is being done. This is part of the proposed patch in in #34893.
    108      * @return mixed|null Null if an input isn't valid, otherwise the sanitized value.
     107     * @return mixed|WP_Error Sanitized value or WP_Error if invalid valid.
    109108     */
    110     public function sanitize_setting( $page_template, WP_Customize_Postmeta_Setting $setting, $strict = false ) {
     109    public function sanitize_setting( $page_template, WP_Customize_Postmeta_Setting $setting ) {
    111110        $post = get_post( $setting->post_id );
    112111        $page_templates = wp_get_theme()->get_page_templates( $post );
     112        $has_setting_validation = method_exists( 'WP_Customize_Setting', 'validate' );
    113113
    114114        if ( 'default' !== $page_template && ! isset( $page_templates[ $page_template ] ) ) {
    115             if ( $strict ) {
    116                 return new WP_Error( 'invalid_page_template', __( 'The page template is invalid.', 'customize-posts' ) );
    117             } else {
    118                 return null;
    119             }
     115            return $has_setting_validation ? new WP_Error( 'invalid_page_template', __( 'The page template is invalid.', 'customize-posts' ) ) : null;
    120116        }
    121117        return $page_template;
  • customize-posts/trunk/php/class-wp-customize-post-discussion-fields-control.php

    r1406933 r1430660  
    7474            </p>
    7575        <# } #>
    76 
    77         <div class="customize-setting-validation-message error" aria-live="assertive"></div>
    7876        <?php
    7977    }
  • customize-posts/trunk/php/class-wp-customize-post-field-partial.php

    r1406933 r1430660  
    4343     * Setting that this section is related to.
    4444     *
    45      * @var WP_Customize_Post_Setting
     45     * @var string
    4646     */
    4747    public $field_id;
     
    8484        $args['field_id'] = $matches['field_id'];
    8585        $args['placement'] = isset( $matches['placement'] ) ? $matches['placement'] : '';
    86 
    87         if ( ! empty( $args['placement'] ) ) {
    88             if ( ! isset( $args['container_inclusive'] ) ) {
    89                 $args['container_inclusive'] = true;
    90             }
    91             if ( ! isset( $args['fallback_refresh'] ) ) {
    92                 $args['fallback_refresh'] = false;
    93             }
    94         }
    9586
    9687        parent::__construct( $component, $id, $args );
     
    112103        }
    113104
    114         /**
    115          * Partial.
    116          *
    117          * @var WP_Customize_Post_Field_Partial $partial
    118          */
    119 
    120105        $GLOBALS['post'] = $post; // WPCS: override global ok.
    121106        setup_postdata( $post );
    122         if ( 'post_title' === $this->field_id ) {
    123             $rendered = $post->post_title;
    124 
    125             if ( ! empty( $post->post_password ) ) {
    126                 /** This filter is documented in wp-includes/post-template.php */
    127                 $protected_title_format = apply_filters( 'protected_title_format', __( 'Protected: %s', 'customize-posts' ), $post );
    128                 $rendered = sprintf( $protected_title_format, $rendered );
    129             } elseif ( isset( $post->post_status ) && 'private' === $post->post_status ) {
    130                 /** This filter is documented in wp-includes/post-template.php */
    131                 $private_title_format = apply_filters( 'private_title_format', __( 'Private: %s', 'customize-posts' ), $post );
    132                 $rendered = sprintf( $private_title_format, $rendered );
    133             }
    134 
     107
     108        $method = 'render_' . $this->field_id;
     109        if ( method_exists( $this, $method ) ) {
     110            $rendered = $this->$method( $partial, $context, $post );
     111        }
     112
     113        wp_reset_postdata();
     114        return $rendered;
     115    }
     116
     117    /**
     118     * Export data to JS.
     119     *
     120     * @return array
     121     */
     122    public function json() {
     123        $data = parent::json();
     124        $data['post_type'] = $this->post_type;
     125        $data['post_id'] = $this->post_id;
     126        $data['field_id'] = $this->field_id;
     127        $data['placement'] = $this->placement;
     128        return $data;
     129    }
     130
     131    /**
     132     * Render the post title.
     133     *
     134     * @param WP_Customize_Partial $partial Partial.
     135     * @param array                $context Context.
     136     * @param WP_Post              $post    Post object.
     137     * @return string
     138     */
     139    public function render_post_title( $partial, $context, $post ) {
     140        unset( $partial, $context );
     141        $rendered = $post->post_title;
     142
     143        if ( ! empty( $post->post_password ) ) {
    135144            /** This filter is documented in wp-includes/post-template.php */
    136             $rendered = apply_filters( 'the_title', $rendered, $post->ID );
    137 
    138             // @todo We need to pass whether a link is present as placement context.
    139             if ( ! is_single() ) {
    140                 $rendered = sprintf( '<a href="%s" rel="bookmark">%s</a>', esc_url( get_permalink( $post->ID ) ), $rendered );
    141             }
    142         } elseif ( 'post_content' === $this->field_id ) {
    143             $rendered = get_the_content();
    144 
     145            $protected_title_format = apply_filters( 'protected_title_format', __( 'Protected: %s', 'customize-posts' ), $post );
     146            $rendered = sprintf( $protected_title_format, $rendered );
     147        } elseif ( isset( $post->post_status ) && 'private' === $post->post_status ) {
    145148            /** This filter is documented in wp-includes/post-template.php */
    146             $rendered = apply_filters( 'the_content', $rendered );
    147             $rendered = str_replace( ']]>', ']]&gt;', $rendered );
    148         } elseif ( 'post_excerpt' === $partial->field_id ) {
    149             $rendered = get_the_excerpt();
    150 
    151             /** This filter is documented in wp-includes/post-template.php */
    152             $rendered = apply_filters( 'the_excerpt', $rendered );
    153         } elseif ( ( 'comment_status' === $partial->field_id && 'comments-area' === $this->placement ) || ( 'ping_status' === $partial->field_id ) && is_singular() ) {
     149            $private_title_format = apply_filters( 'private_title_format', __( 'Private: %s', 'customize-posts' ), $post );
     150            $rendered = sprintf( $private_title_format, $rendered );
     151        }
     152
     153        /** This filter is documented in wp-includes/post-template.php */
     154        $rendered = apply_filters( 'the_title', $rendered, $post->ID );
     155
     156        // @todo We need to pass whether a link is present as placement context.
     157        if ( ! is_single() ) {
     158            $rendered = sprintf( '<a href="%s" rel="bookmark">%s</a>', esc_url( get_permalink( $post->ID ) ), $rendered );
     159        }
     160
     161        return $rendered;
     162    }
     163
     164    /**
     165     * Render the post content.
     166     *
     167     * @param WP_Customize_Partial $partial Partial.
     168     * @param array                $context Context.
     169     * @param WP_Post              $post    Post object.
     170     * @return string
     171     */
     172    public function render_post_content( $partial, $context, $post ) {
     173        unset( $partial, $context, $post );
     174        $rendered = get_the_content();
     175
     176        /** This filter is documented in wp-includes/post-template.php */
     177        $rendered = apply_filters( 'the_content', $rendered );
     178        $rendered = str_replace( ']]>', ']]&gt;', $rendered );
     179
     180        return $rendered;
     181    }
     182
     183    /**
     184     * Render the post excerpt.
     185     *
     186     * @param WP_Customize_Partial $partial Partial.
     187     * @param array                $context Context.
     188     * @param WP_Post              $post    Post object.
     189     * @return string
     190     */
     191    public function render_post_excerpt( $partial, $context, $post ) {
     192        unset( $partial, $context, $post );
     193        $rendered = get_the_excerpt();
     194
     195        /** This filter is documented in wp-includes/post-template.php */
     196        $rendered = apply_filters( 'the_excerpt', $rendered );
     197
     198        return $rendered;
     199    }
     200
     201    /**
     202     * Render comments area & link.
     203     *
     204     * @param WP_Customize_Partial $partial Partial.
     205     * @param array                $context Context.
     206     * @param WP_Post              $post    Post object.
     207     * @return string|null
     208     */
     209    public function render_comment_status( $partial, $context, $post ) {
     210        unset( $partial, $context, $post );
     211        $rendered = null;
     212
     213        if ( 'comments-area' === $this->placement && is_singular() ) {
    154214            if ( comments_open() || get_comments_number() ) {
    155215                ob_start();
     
    160220                $rendered = '';
    161221            }
    162         } elseif ( 'comment_status' === $partial->field_id && 'comments-link' === $this->placement && ! is_single() && ! post_password_required() && ( comments_open() || get_comments_number() ) ) {
     222        } elseif ( 'comments-link' === $this->placement && ! is_single() && ! post_password_required() && ( comments_open() || get_comments_number() ) ) {
    163223            ob_start();
    164224            /* translators: %s: post title */
     
    169229                $rendered = '<span class="comments-link">' . $link . '</span>';
    170230            }
    171         } elseif ( 'post_author' === $this->field_id ) {
    172             if ( 'author-bio' === $this->placement && is_singular() && get_the_author_meta( 'description' ) ) {
    173 
    174                 $rendered = false;
    175                 $template_parts = array(
    176                     'template-parts/biography',
    177                     'author-bio',
    178                 );
    179                 foreach ( $template_parts as $template_part ) {
    180                     if ( '' !== locate_template( $template_part . '.php' ) ) {
    181                         ob_start();
    182                         get_template_part( $template_part );
    183                         $rendered = ob_get_contents();
    184                         ob_end_clean();
    185                         break;
    186                     }
    187                 }
    188             } elseif ( 'byline' === $this->placement && ( is_singular() || is_multi_author() ) ) {
    189                 $rendered = sprintf( '<a class="url fn n" href="%1$s">%2$s</a>',
    190                     esc_url( get_author_posts_url( get_the_author_meta( 'ID', $post->post_author ) ) ),
    191                     get_the_author_meta( 'display_name', $post->post_author )
    192                 );
    193             } elseif ( 'avatar' === $this->placement ) {
    194                 $size = isset( $context['size'] ) ? $context['size'] : 96;
    195                 $default = isset( $context['default'] ) ? $context['default'] : '';
    196                 $alt = isset( $context['alt'] ) ? $context['alt'] : '';
    197                 $rendered = get_avatar( get_the_author_meta( 'user_email' ), $size, $default, $alt, $context );
    198             }
    199         }
    200 
    201         wp_reset_postdata();
    202         return $rendered;
    203     }
    204 
    205     /**
    206      * Export data to JS.
    207      *
    208      * @return array
    209      */
    210     public function json() {
    211         $data = parent::json();
    212         $data['post_type'] = $this->post_type;
    213         $data['post_id'] = $this->post_id;
    214         $data['field_id'] = $this->field_id;
    215         $data['placement'] = $this->placement;
    216         return $data;
     231        }
     232
     233        return $rendered;
     234    }
     235
     236    /**
     237     * Render pings.
     238     *
     239     * @param WP_Customize_Partial $partial Partial.
     240     * @param array                $context Context.
     241     * @param WP_Post              $post    Post object.
     242     * @return string
     243     */
     244    public function render_ping_status( $partial, $context, $post ) {
     245        unset( $partial, $context, $post );
     246        if ( is_singular() && ( comments_open() || get_comments_number() ) ) {
     247            ob_start();
     248            comments_template();
     249            $rendered = ob_get_contents();
     250            ob_end_clean();
     251        } else {
     252            $rendered = '';
     253        }
     254
     255        return $rendered;
     256    }
     257
     258    /**
     259     * Render post author.
     260     *
     261     * @param WP_Customize_Partial $partial Partial.
     262     * @param array                $context Context.
     263     * @param WP_Post              $post    Post object.
     264     * @return string|bool
     265     */
     266    public function render_post_author( $partial, $context, $post ) {
     267        unset( $partial );
     268        $rendered = null;
     269
     270        if ( 'byline' === $this->placement && ( is_singular() || is_multi_author() ) ) {
     271            $rendered = sprintf( '<a class="url fn n" href="%1$s">%2$s</a>',
     272                esc_url( get_author_posts_url( get_the_author_meta( 'ID', $post->post_author ) ) ),
     273                get_the_author_meta( 'display_name', $post->post_author )
     274            );
     275        } elseif ( 'avatar' === $this->placement ) {
     276            $size = isset( $context['size'] ) ? $context['size'] : 96;
     277            $default = isset( $context['default'] ) ? $context['default'] : '';
     278            $alt = isset( $context['alt'] ) ? $context['alt'] : '';
     279            $rendered = get_avatar( get_the_author_meta( 'user_email' ), $size, $default, $alt, $context );
     280        }
     281
     282        return $rendered;
    217283    }
    218284}
  • customize-posts/trunk/php/class-wp-customize-post-setting.php

    r1406933 r1430660  
    183183        }
    184184
     185        $post_data = wp_array_slice_assoc(
     186            $post_data,
     187            array_keys( $this->default )
     188        );
     189
     190        if ( 'customize-draft' === $post_data['post_status'] || 'auto-draft' === $post_data['post_status'] ) {
     191            $post_data['post_status'] = 'publish';
     192        }
     193
    185194        return $post_data;
    186195    }
     
    200209
    201210        if ( $this->is_previewed ) {
    202             $post_data = array_merge( $post_data, $this->post_value( array() ) );
     211            $input_value = $this->post_value( array() );
     212            if ( null !== $input_value ) {
     213                $post_data = array_merge( $post_data, $input_value );
     214            }
    203215        }
    204216
     
    214226     */
    215227    public function get_post_data( WP_Post $post ) {
    216         $post_data = wp_array_slice_assoc(
    217             $post->to_array(),
    218             array_keys( $this->default )
    219         );
    220         $post_data = $this->normalize_post_data( $post_data );
     228        $post_data = $this->normalize_post_data( $post->to_array() );
    221229        return $post_data;
    222230    }
     
    248256     *
    249257     * @param array $post_data   The value to sanitize.
    250      * @param bool  $strict      Whether validation is being done. This is part of the proposed patch in in #34893.
    251      * @return string|array|null|WP_Error Null if an input isn't valid, otherwise the sanitized value. WP_Error returned if `$strict`.
    252      */
    253     public function sanitize( $post_data, $strict = false ) {
     258     * @return array|WP_Error|null Sanitized post array or WP_Error if invalid (or null if not WP 4.6-alpha).
     259     */
     260    public function sanitize( $post_data ) {
    254261        global $wpdb;
     262        $has_setting_validation = method_exists( 'WP_Customize_Setting', 'validate' );
    255263
    256264        $post_data = array_merge( $this->default, $post_data );
    257 
    258         // The customize_validate_settings action is part of the Customize Setting Validation plugin.
    259         if ( ! $strict && doing_action( 'customize_validate_settings' ) ) {
    260             $strict = true;
    261         }
    262265
    263266        $update = ( $this->post_id > 0 );
    264267        $post_type_obj = get_post_type_object( $this->post_type );
    265268
    266         if ( $strict && ! empty( $post_data['post_type'] ) && $post_data['post_type'] !== $this->post_type ) {
    267             return new WP_Error( 'bad_post_type' );
     269        if ( ! empty( $post_data['post_type'] ) && $post_data['post_type'] !== $this->post_type ) {
     270            return $has_setting_validation ? new WP_Error( 'bad_post_type' ) : null;
    268271        }
    269272        $post_data['post_type'] = $this->post_type;
    270273
    271         if ( $strict && $update ) {
     274        if ( $update && did_action( 'customize_save_validation_before' ) ) {
    272275            // Check post lock.
     276            require_once ABSPATH . 'wp-admin/includes/post.php';
    273277            $locked_user = wp_check_post_lock( $this->post_id );
    274278            if ( $locked_user ) {
     
    278282                    $user ? $user->display_name : __( '(unknown user)', 'customize-posts' )
    279283                );
    280                 return new WP_Error( 'post_locked', $error_message );
     284                return $has_setting_validation ? new WP_Error( 'post_locked', $error_message ) : null;
    281285            }
    282286
     
    299303                );
    300304                $this->posts_component->update_conflicted_settings[ $this->id ] = $this;
    301                 return new WP_Error( 'post_update_conflict', $error_message );
     305                return $has_setting_validation ? new WP_Error( 'post_update_conflict', $error_message ) : null;
    302306            }
    303307        }
     
    315319
    316320        /** This filter is documented in wp-includes/post.php */
    317         if ( $strict && apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $post_data ) ) {
    318             return new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.', 'customize-posts' ) );
     321        if ( 'trash' !== $post_data['post_status'] && apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $post_data ) ) {
     322            return $has_setting_validation ? new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.', 'customize-posts' ) ) : null;
    319323        }
    320324
     
    368372        $valid_date = wp_checkdate( $mm, $jj, $aa, $post_data['post_date'] );
    369373        if ( ! $valid_date ) {
    370             if ( $strict ) {
    371                 return new WP_Error( 'invalid_date', __( 'Whoops, the provided date is invalid.', 'customize-posts' ) );
    372             } else {
    373                 $post_data['post_date'] = '';
    374             }
     374            return $has_setting_validation ? new WP_Error( 'invalid_date', __( 'Whoops, the provided date is invalid.', 'customize-posts' ) ) : null;
    375375        }
    376376
     
    505505        $data['post_type'] = $this->post_type;
    506506
    507         $result = wp_update_post( wp_slash( $data ), true );
    508 
    509         if ( is_wp_error( $result ) ) {
    510             // @todo Amend customize_save_response
    511             return false;
    512         }
    513         return true;
     507        $is_trashed = 'trash' === $data['post_status'];
     508        if ( $is_trashed ) {
     509            add_filter( 'wp_insert_post_empty_content', '__return_false', 100 );
     510
     511            /*
     512             * Do not transition the post_status to trash, use the current value.
     513             *
     514             * If we were to unset `$data['post_status']`, the post would not be
     515             * properly purged from the Customizer pane. And if we transitioned the
     516             * status in `wp_update_post()` then `wp_trash_post()` would return false.
     517             */
     518            $data['post_status'] = get_post_status( $this->post_id );
     519        }
     520
     521        $r = wp_update_post( wp_slash( $data ), true );
     522        $result = ! is_wp_error( $r );
     523
     524        if ( $is_trashed ) {
     525            $result = wp_trash_post( $this->post_id );
     526            remove_filter( 'wp_insert_post_empty_content', '__return_false', 100 );
     527        }
     528
     529        return $result;
    514530    }
    515531}
  • customize-posts/trunk/php/class-wp-customize-postmeta-controller.php

    r1406933 r1430660  
    5757
    5858    /**
     59     * Setting validate callback.
     60     *
     61     * @var callable
     62     */
     63    public $validate_callback;
     64
     65    /**
    5966     * Setting transport.
    6067     *
     
    95102            $this->sanitize_js_callback = array( $this, 'js_value' );
    96103        }
     104        if ( ! isset( $this->validate_callback ) ) {
     105            $this->validate_callback = array( $this, 'validate_setting' );
     106        }
    97107
    98108        add_action( 'customize_posts_register_meta', array( $this, 'register_meta' ) );
     
    130140                'sanitize_callback' => $this->sanitize_callback,
    131141                'sanitize_js_callback' => $this->sanitize_js_callback,
     142                'validate_callback' => $this->validate_callback,
    132143                'transport' => $this->setting_transport,
    133144                'theme_supports' => $this->theme_supports,
     
    192203
    193204    /**
    194      * Sanitize (and validate) an input.
     205     * Sanitize an input.
    195206     *
    196207     * Callback for `customize_sanitize_post_meta_{$meta_key}` filter.
     
    200211     * @param string                        $meta_value The value to sanitize.
    201212     * @param WP_Customize_Postmeta_Setting $setting    Setting.
    202      * @param bool                          $strict     Whether validation is being done. This is part of the proposed patch in in #34893.
    203      * @return mixed|null Null if an input isn't valid, otherwise the sanitized value.
    204      */
    205     public function sanitize_setting( $meta_value, WP_Customize_Postmeta_Setting $setting, $strict = false ) {
    206         unset( $setting, $strict );
     213     * @return mixed|null Sanitized value or `null` if invalid.
     214     */
     215    public function sanitize_setting( $meta_value, WP_Customize_Postmeta_Setting $setting ) {
     216        unset( $setting );
    207217        return $meta_value;
     218    }
     219
     220    /**
     221     * Validate an input.
     222     *
     223     * Callback for `customize_validate_post_meta_{$meta_key}` filter.
     224     *
     225     * @see update_metadata()
     226     *
     227     * @param WP_Error                      $validity   Validity.
     228     * @param string                        $meta_value The value to sanitize.
     229     * @param WP_Customize_Postmeta_Setting $setting    Setting.
     230     * @return WP_Error Validity.
     231     */
     232    public function validate_setting( $validity, $meta_value, WP_Customize_Postmeta_Setting $setting ) {
     233        unset( $setting, $meta_value );
     234        return $validity;
    208235    }
    209236
  • customize-posts/trunk/php/class-wp-customize-postmeta-setting.php

    r1406933 r1430660  
    125125        $meta_key = $this->meta_key;
    126126        $object_id = $this->post_id;
    127         $single = true;
    128         $value = get_post_meta( $object_id, $meta_key, $single );
    129         if ( '' === $value ) {
     127        $single = false;
     128        $values = get_post_meta( $object_id, $meta_key, $single );
     129        $value = array_shift( $values );
     130        if ( ! isset( $value ) ) {
    130131            $value = $this->default;
    131132        }
     
    140141     *
    141142     * @param string $meta_value The value to sanitize.
    142      * @param bool   $strict     Whether validation is being done. This is part of the proposed patch in in #34893.
    143      * @return mixed|null Null if an input isn't valid, otherwise the sanitized value.
    144      */
    145     public function sanitize( $meta_value, $strict = false ) {
    146 
    147         // The customize_validate_settings action is part of the Customize Setting Validation plugin.
    148         if ( ! $strict && doing_action( 'customize_validate_settings' ) ) {
    149             $strict = true;
    150         }
     143     * @return mixed|WP_Error|null Sanitized post array or WP_Error if invalid (or null if not WP 4.6-alpha).
     144     */
     145    public function sanitize( $meta_value ) {
     146        $has_setting_validation = method_exists( 'WP_Customize_Setting', 'validate' );
    151147
    152148        $meta_type = 'post';
     
    160156         * @param mixed                $meta_value  Value of the setting.
    161157         * @param WP_Customize_Setting $this        WP_Customize_Setting instance.
    162          * @param bool                 $strict      Whether validation is being done. This is part of the proposed patch in in #34893.
    163158         */
    164         $meta_value = apply_filters( "customize_sanitize_{$this->id}", $meta_value, $this, $strict );
     159        $meta_value = apply_filters( "customize_sanitize_{$this->id}", $meta_value, $this );
    165160
    166161        // Apply sanitization if value didn't fail validation.
     
    168163            $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
    169164        }
     165        if ( is_wp_error( $meta_value ) ) {
     166            return $has_setting_validation ? $meta_value : null;
     167        }
    170168
    171169        /** This filter is documented in wp-includes/meta.php */
    172170        $check = apply_filters( "update_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $prev_value );
    173171        if ( null !== $check ) {
    174             if ( $strict ) {
    175                 return new WP_Error( 'not_allowed', sprintf( __( 'Update to post meta "%s" blocked.', 'customize-posts' ), $meta_key ) );
    176             } else {
    177                 return null;
    178             }
     172            return $has_setting_validation ? new WP_Error( 'not_allowed', sprintf( __( 'Update to post meta "%s" blocked.', 'customize-posts' ), $meta_key ) ) : null;
    179173        }
    180174
  • customize-posts/trunk/php/class-wp-customize-posts-preview.php

    r1406933 r1430660  
    6767        add_filter( 'comments_open', array( $this, 'filter_preview_comments_open' ), 10, 2 );
    6868        add_filter( 'pings_open', array( $this, 'filter_preview_pings_open' ), 10, 2 );
    69         add_filter( 'get_post_metadata', array( $this, 'filter_get_post_meta_to_add_dynamic_postmeta_settings' ), 1000, 4 );
     69        add_filter( 'get_post_metadata', array( $this, 'filter_get_post_meta_to_add_dynamic_postmeta_settings' ), 1000, 2 );
    7070        add_action( 'wp_footer', array( $this, 'export_preview_data' ), 10 );
    7171        add_filter( 'edit_post_link', array( $this, 'filter_edit_post_link' ), 10, 2 );
     
    8585        add_filter( 'the_posts', array( $this, 'filter_the_posts_to_preview_settings' ), 1000 );
    8686        add_action( 'the_post', array( $this, 'preview_setup_postdata' ) );
     87        add_filter( 'the_title', array( $this, 'filter_the_title' ), 1, 2 );
    8788        add_filter( 'get_post_metadata', array( $this, 'filter_get_post_meta_to_preview' ), 1000, 4 );
     89        add_filter( 'posts_where', array( $this, 'filter_posts_where_to_include_previewed_posts' ), 10, 2 );
     90        add_filter( 'wp_setup_nav_menu_item', array( $this, 'filter_nav_menu_item_to_set_url' ) );
    8891        $this->has_preview_filters = true;
    8992        return true;
     
    126129
    127130    /**
    128      * Create dynamic post settings and sections for posts queried in the page.
     131     * Retrieve post title and filter according to the current Customizer state.
     132     *
     133     * This is necessary because the is currently no filter yet in WP to mutate
     134     * the underling post object. This specifically was noticed in the `get_the_title()`
     135     * call in `WP_REST_Posts_Controller::prepare_item_for_response()`.
     136     *
     137     * @link https://github.com/xwp/wp-customize-posts/issues/96
     138     * @link https://core.trac.wordpress.org/ticket/12955
     139     *
     140     * @param string      $title Filtered title.
     141     * @param int|WP_Post $post Optional. Post ID or WP_Post object. Default is global $post.
     142     * @return string Title.
     143     */
     144    public function filter_the_title( $title, $post ) {
     145        if ( empty( $post ) ) {
     146            return $title;
     147        }
     148        $post = get_post( $post );
     149        if ( empty( $post ) ) {
     150            return $title;
     151        }
     152
     153        $setting_id = WP_Customize_Post_Setting::get_post_setting_id( $post );
     154        $setting = $this->component->manager->get_setting( $setting_id );
     155
     156        if ( ! ( $setting instanceof WP_Customize_Post_Setting ) ) {
     157            return $title;
     158        }
     159        $post_data = $setting->post_value();
     160        if ( ! is_array( $post_data ) || ! isset( $post_data['post_title'] ) ) {
     161            return $title;
     162        }
     163
     164        $title = $post_data['post_title'];
     165
     166        /*
     167         * Begin code modified from get_the_title():
     168         * https://github.com/xwp/wordpress-develop/blob/6792df6fab87063e0564148c6634aaa0ed3156b4/src/wp-includes/post-template.php#L113-L148
     169         */
     170
     171        if ( ! is_admin() ) {
     172            $mock_post = new WP_Post( (object) array_merge(
     173                $post->to_array(),
     174                $post_data
     175            ) );
     176
     177            if ( ! empty( $post_data['post_password'] ) ) {
     178
     179                /** This filter is documented in wp-includes/post-template.php */
     180                $protected_title_format = apply_filters( 'protected_title_format', __( 'Protected: %s', 'customize-posts' ), $mock_post );
     181                $title = sprintf( $protected_title_format, $title );
     182            } elseif ( isset( $post_data['post_status'] ) && 'private' === $post_data['post_status'] ) {
     183
     184                /** This filter is documented in wp-includes/post-template.php */
     185                $private_title_format = apply_filters( 'private_title_format', __( 'Private: %s', 'customize-posts' ), $mock_post );
     186                $title = sprintf( $private_title_format, $title );
     187            }
     188        }
     189
     190        return $title;
     191    }
     192
     193    /**
     194     * Create dynamic post/postmeta settings and sections for posts queried in the page.
    129195     *
    130196     * @param array $posts Posts.
     
    143209                $this->component->manager->add_section( $section );
    144210            }
     211            $this->component->register_post_type_meta_settings( $post->ID );
    145212        }
    146213        return $posts;
     
    165232
    166233    /**
     234     * Get current posts being previewed which should be included in the given query.
     235     *
     236     * @access public
     237     *
     238     * @param \WP_Query $query     The query.
     239     * @param boolean   $published Whether to return the published posts. Default 'true'.
     240     * @return array
     241     */
     242    public function get_previewed_posts_for_query( WP_Query $query, $published = true ) {
     243        $query_vars = $query->query_vars;
     244
     245        if ( empty( $query_vars['post_type'] ) ) {
     246            $query_vars['post_type'] = array( 'post' );
     247        } elseif ( is_string( $query_vars['post_type'] ) ) {
     248            $query_vars['post_type'] = explode( ',', $query_vars['post_type'] );
     249        }
     250
     251        if ( empty( $query_vars['post_status'] ) ) {
     252            $query_vars['post_status'] = array( 'publish' );
     253        } elseif ( is_string( $query_vars['post_status'] ) ) {
     254            $query_vars['post_status'] = explode( ',', $query_vars['post_status'] );
     255        }
     256
     257        $post_ids = array();
     258        $settings = $this->component->manager->unsanitized_post_values();
     259        if ( ! empty( $settings ) ) {
     260            foreach ( (array) $settings as $id => $post_data ) {
     261                if ( ! preg_match( WP_Customize_Post_Setting::SETTING_ID_PATTERN, $id, $matches ) ) {
     262                    continue;
     263                }
     264                $post_id = intval( $matches['post_id'] );
     265                $statuses = $query_vars['post_status'];
     266
     267                $post_type_match = (
     268                    empty( $query_vars['post_type'] )
     269                    ||
     270                    in_array( $matches['post_type'], $query_vars['post_type'], true )
     271                    ||
     272                    (
     273                        in_array( 'any', $query_vars['post_type'], true )
     274                        &&
     275                        in_array( $matches['post_type'], get_post_types( array( 'exclude_from_search' => false ) ), true )
     276                    )
     277                );
     278
     279                $post_type_obj = get_post_type_object( $matches['post_type'] );
     280                if ( $post_type_obj && current_user_can( $post_type_obj->cap->read_private_posts, $post_id ) ) {
     281                    $statuses[] = 'private';
     282                }
     283
     284                if ( empty( $query_vars['post_status'] ) ) {
     285                    $post_status_match = true;
     286                } elseif ( false === $published ) {
     287                    $post_status_match = ! in_array( $post_data['post_status'], array( 'publish', 'private' ), true );
     288                } else {
     289                    $post_status_match = in_array( $post_data['post_status'], $statuses, true );
     290                }
     291
     292                $post__in_match = empty( $query_vars['post__in'] ) || in_array( $post_id, $query_vars['post__in'], true );
     293                if ( $post_status_match && $post_type_match && $post__in_match ) {
     294                    $post_ids[] = $post_id;
     295                }
     296            }
     297        }
     298
     299        return $post_ids;
     300    }
     301
     302    /**
     303     * Include stubbed posts that are being previewed.
     304     *
     305     * @filter posts_where
     306     * @access public
     307     *
     308     * @param string   $where The WHERE clause of the query.
     309     * @param WP_Query $query The WP_Query instance (passed by reference).
     310     * @return string
     311     */
     312    public function filter_posts_where_to_include_previewed_posts( $where, $query ) {
     313        global $wpdb;
     314
     315        if ( ! $query->is_singular() ) {
     316            $post__not_in = implode( ',', array_map( 'absint', $this->get_previewed_posts_for_query( $query, false ) ) );
     317            if ( ! empty( $post__not_in ) ) {
     318                $where .= " AND {$wpdb->posts}.ID NOT IN ($post__not_in)";
     319            }
     320            $post__in = implode( ',', array_map( 'absint', $this->get_previewed_posts_for_query( $query, true ) ) );
     321            if ( ! empty( $post__in ) ) {
     322                $where .= " OR {$wpdb->posts}.ID IN ($post__in)";
     323            }
     324        }
     325
     326        return $where;
     327    }
     328
     329    /**
     330     * Filter a nav menu item for an added post to supply a URL field.
     331     *
     332     * This is probably a bug in Core where the `value_as_wp_post_nav_menu_item`
     333     * should be setting the url property.
     334     *
     335     * @access public
     336     * @see WP_Customize_Nav_Menu_Item_Setting::value_as_wp_post_nav_menu_item()
     337     *
     338     * @param WP_Post $nav_menu_item Nav menu item.
     339     * @return WP_Post Nav menu item.
     340     */
     341    public function filter_nav_menu_item_to_set_url( $nav_menu_item ) {
     342        if ( 'post_type' !== $nav_menu_item->type || $nav_menu_item->url || ! $nav_menu_item->object_id ) {
     343            return $nav_menu_item;
     344        }
     345
     346        $post = get_post( $nav_menu_item->object_id );
     347        if ( ! $post ) {
     348            return $nav_menu_item;
     349        }
     350        $setting_id = WP_Customize_Post_Setting::get_post_setting_id( $post );
     351        $setting = $this->component->manager->get_setting( $setting_id );
     352        if ( $setting ) {
     353            $nav_menu_item->url = get_permalink( $post->ID );
     354        }
     355        return $nav_menu_item;
     356    }
     357
     358    /**
    167359     * Filter the comments open for a previewed post.
    168360     *
     
    205397     * @param null|array|string $value     The value get_metadata() should return - a single metadata value, or an array of values.
    206398     * @param int               $object_id Object ID.
    207      * @param string            $meta_key  Meta key.
    208399     * @return mixed Value.
    209400     */
    210     public function filter_get_post_meta_to_add_dynamic_postmeta_settings( $value, $object_id, $meta_key ) {
    211         $post = get_post( $object_id );
    212         if ( ! $post || ! isset( $this->component->registered_post_meta[ $post->post_type ] ) ) {
    213             return $value;
    214         }
    215 
    216         if ( '' === $meta_key ) {
    217             if ( null !== $value ) {
    218                 $meta_keys = array_keys( $value );
    219             } else {
    220                 $meta_keys = array();
    221             }
    222         } else {
    223             $meta_keys = array( $meta_key );
    224         }
    225 
    226         $setting_ids = array();
    227         if ( ! empty( $meta_keys ) ) {
    228             foreach ( $meta_keys as $key ) {
    229                 if ( isset( $this->component->registered_post_meta[ $post->post_type ][ $key ] ) ) {
    230                     $setting_ids[] = WP_Customize_Postmeta_Setting::get_post_meta_setting_id( $post, $key );
    231                 }
    232             }
    233         }
    234         $this->component->manager->add_dynamic_settings( $setting_ids );
    235 
     401    public function filter_get_post_meta_to_add_dynamic_postmeta_settings( $value, $object_id ) {
     402        $this->component->register_post_type_meta_settings( $object_id );
    236403        return $value;
    237404    }
     
    327494            }
    328495            $args['type'] = WP_Customize_Post_Field_Partial::TYPE;
     496
     497            $field_id = $matches['field_id'];
     498            if ( ! empty( $matches['placement'] ) ) {
     499                $field_id .= '[' . $matches['placement'] . ']';
     500            }
     501            $schema = $this->get_post_field_partial_schema( $field_id );
     502            if ( ! empty( $schema ) ) {
     503                $args = array_merge( $args, $schema );
     504            }
    329505        }
    330506        return $args;
     
    420596
    421597    /**
     598     * Get the schema for dynamically registered partials.
     599     *
     600     * @param string $field_id The partial field ID.
     601     * @return array
     602     */
     603    public function get_post_field_partial_schema( $field_id = '' ) {
     604        $schema = array(
     605            'post_title' => array(
     606                'selector' => '.entry-title',
     607            ),
     608            'post_name' => array(
     609                'fallback_refresh' => false,
     610            ),
     611            'post_status' => array(
     612                'fallback_refresh' => true,
     613            ),
     614            'post_content' => array(
     615                'selector' => '.entry-content',
     616            ),
     617            'post_excerpt' => array(
     618                'selector' => '.entry-summary',
     619            ),
     620            'comment_status[comments-area]' => array(
     621                'selector' => '.comments-area',
     622                'body_selector' => true,
     623                'singular_only' => true,
     624                'container_inclusive' => true,
     625            ),
     626            'comment_status[comments-link]' => array(
     627                'selector' => '.comments-link',
     628                'archive_only' => true,
     629                'container_inclusive' => true,
     630            ),
     631            'ping_status' => array(
     632                'selector' => '.comments-area',
     633                'body_selector' => true,
     634                'singular_only' => true,
     635                'container_inclusive' => true,
     636            ),
     637            'post_author[byline]' => array(
     638                'selector' => '.vcard a.fn',
     639                'container_inclusive' => true,
     640                'fallback_refresh' => false,
     641            ),
     642            'post_author[avatar]' => array(
     643                'selector' => '.vcard img.avatar',
     644                'container_inclusive' => true,
     645                'fallback_refresh' => false,
     646            ),
     647        );
     648
     649        /**
     650         * Filter the schema for dynamically registered partials.
     651         *
     652         * @param array $schema Partial schema.
     653         * @return array
     654         */
     655        $schema = apply_filters( 'customize_posts_partial_schema', $schema );
     656
     657        // Return specific schema based on the field_id & placement.
     658        if ( ! empty( $field_id ) ) {
     659            if ( isset( $schema[ $field_id ] ) ) {
     660                return $schema[ $field_id ];
     661            } else {
     662                return array();
     663            }
     664        }
     665
     666        return $schema;
     667    }
     668
     669    /**
    422670     * Export data into the customize preview.
    423671     */
     
    443691        }
    444692
     693        $exported_partial_schema = array();
     694        foreach ( $this->get_post_field_partial_schema() as $key => $schema ) {
     695            unset( $schema['render_callback'] ); // PHP callbacks are generally not JSON-serializable.
     696            $exported_partial_schema[ $key ] = $schema;
     697        }
     698
    445699        $exported = array(
    446700            'isPostPreview' => is_preview(),
     
    448702            'queriedPostId' => $queried_post_id,
    449703            'settingProperties' => $setting_properties,
     704            'partialSchema' => $exported_partial_schema,
    450705        );
    451706
  • customize-posts/trunk/php/class-wp-customize-posts.php

    r1406933 r1430660  
    4444
    4545    /**
     46     * Registered support classes.
     47     *
     48     * @var array
     49     */
     50    public $supports = array();
     51
     52    /**
     53     * Whether the post link filters are being suppressed.
     54     *
     55     * @var bool
     56     */
     57    public $suppress_post_link_filters = false;
     58
     59    /**
     60     * Customize draft post IDs.
     61     *
     62     * @var array
     63     */
     64    public $customize_draft_post_ids = array();
     65
     66    /**
    4667     * Initial loader.
    4768     *
     
    7192        add_filter( 'customize_dynamic_setting_class', array( $this, 'filter_customize_dynamic_setting_class' ), 5, 3 );
    7293        add_filter( 'customize_save_response', array( $this, 'filter_customize_save_response_for_conflicts' ), 10, 2 );
     94        add_filter( 'customize_save_response', array( $this, 'filter_customize_save_response_to_export_saved_values' ), 10, 2 );
     95        add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_templates' ) );
     96        add_action( 'init', array( $this, 'register_customize_draft' ) );
     97        add_filter( 'customize_snapshot_save', array( $this, 'transition_customize_draft' ) );
     98        add_action( 'after_setup_theme', array( $this, 'preview_customize_draft_post_ids' ) );
     99        add_action( 'pre_get_posts', array( $this, 'preview_customize_draft' ) );
     100        add_filter( 'post_link', array( $this, 'post_link_draft' ), 10, 2 );
     101        add_filter( 'post_type_link', array( $this, 'post_link_draft' ), 10, 2 );
     102        add_filter( 'page_link', array( $this, 'post_link_draft' ), 10, 2 );
     103        add_action( 'wp_ajax_customize-posts-insert-auto-draft', array( $this, 'ajax_insert_auto_draft_post' ) );
    73104
    74105        $this->preview = new WP_Customize_Posts_Preview( $this );
     106    }
     107
     108    /**
     109     * Instantiate a Customize Posts support class.
     110     *
     111     * The support class must extend `Customize_Posts_Support` or one of it's subclasses.
     112     *
     113     * @param string|Customize_Posts_Support $support The support class name or object.
     114     */
     115    function add_support( $support ) {
     116        if ( is_string( $support ) && class_exists( $support, false ) ) {
     117            $support = new $support( $this );
     118        }
     119
     120        if ( $support instanceof Customize_Posts_Support ) {
     121            $class_name = get_class( $support );
     122            if ( ! isset( $this->supports[ $class_name ] ) ) {
     123                $this->supports[ $class_name ] = $support;
     124                $support->init();
     125            }
     126        }
    75127    }
    76128
     
    88140        $post_type_objects = get_post_types( array(), 'objects' );
    89141        foreach ( $post_type_objects as $post_type_object ) {
    90             $is_included = $post_type_object->show_ui;
    91             if ( isset( $post_type_object->show_in_customizer ) ) {
    92                 $is_included = $post_type_object->show_in_customizer;
    93             }
    94 
    95             if ( $is_included ) {
    96                 $post_type_object = clone $post_type_object;
    97                 $post_type_object->supports = get_all_post_type_supports( $post_type_object->name );
    98 
    99                 // Remove unnecessary properties.
    100                 unset( $post_type_object->register_meta_box_cb );
    101 
    102                 $post_types[ $post_type_object->name ] = $post_type_object;
    103             }
     142            $post_type_object = clone $post_type_object;
     143            if ( ! isset( $post_type_object->show_in_customizer ) ) {
     144                $post_type_object->show_in_customizer = $post_type_object->show_ui;
     145            }
     146            $post_type_object->supports = get_all_post_type_supports( $post_type_object->name );
     147
     148            // Remove unnecessary properties.
     149            unset( $post_type_object->register_meta_box_cb );
     150
     151            $post_types[ $post_type_object->name ] = $post_type_object;
    104152        }
    105153
     
    145193                'sanitize_callback' => null,
    146194                'sanitize_js_callback' => null,
     195                'validate_callback' => null,
    147196                'setting_class' => 'WP_Customize_Postmeta_Setting',
    148197            ),
     
    229278        $this->set_builtin_post_type_descriptions();
    230279        foreach ( $this->get_post_types() as $post_type_object ) {
     280            if ( empty( $post_type_object->show_in_customizer ) ) {
     281                continue;
     282            }
     283
    231284            $panel_id = sprintf( 'posts[%s]', $post_type_object->name );
    232285
     
    339392
    340393    /**
     394     * Add all postmeta settings for all registered postmeta for a given post type instance.
     395     *
     396     * @param int $post_id Post ID.
     397     * @return array
     398     */
     399    public function register_post_type_meta_settings( $post_id ) {
     400        $post = get_post( $post_id );
     401        $setting_ids = array();
     402        if ( ! empty( $post ) && isset( $this->registered_post_meta[ $post->post_type ] ) ) {
     403            foreach ( array_keys( $this->registered_post_meta[ $post->post_type ] ) as $key ) {
     404                $setting_ids[] = WP_Customize_Postmeta_Setting::get_post_meta_setting_id( $post, $key );
     405            }
     406        }
     407        $this->manager->add_dynamic_settings( $setting_ids );
     408
     409        return $setting_ids;
     410    }
     411
     412    /**
    341413     * When loading the customizer from a post, get the post.
    342414     *
     
    350422        $post = get_post( $post_id );
    351423        return $post;
     424    }
     425
     426    /**
     427     * Get the post status choices array.
     428     *
     429     * @return array
     430     */
     431    public function get_post_status_choices() {
     432        $choices = array(
     433            array(
     434                'value' => 'draft',
     435                'text'  => __( 'Draft', 'customize-posts' ),
     436            ),
     437            array(
     438                'value' => 'pending',
     439                'text'  => __( 'Pending Review', 'customize-posts' ),
     440            ),
     441            array(
     442                'value' => 'private',
     443                'text'  => __( 'Private', 'customize-posts' ),
     444            ),
     445            array(
     446                'value' => 'publish',
     447                'text'  => __( 'Published', 'customize-posts' ),
     448            ),
     449            array(
     450                'value' => 'trash',
     451                'text'  => __( 'Trash', 'customize-posts' ),
     452            ),
     453        );
     454
     455        return $choices;
    352456    }
    353457
     
    420524
    421525    /**
     526     * Return the saved sanitized values for posts and postmeta to update in the client.
     527     *
     528     * This was originally in the Customize Setting Validation plugin.
     529     *
     530     * @link https://github.com/xwp/wp-customize-setting-validation/blob/2e5ddc66a870ad7b1aee5f8e414bad4b78e120d2/php/class-plugin.php#L283-L317
     531     *
     532     * @param array $response Response.
     533     * @return array
     534     */
     535    public function filter_customize_save_response_to_export_saved_values( $response ) {
     536        $response['saved_post_setting_values'] = array();
     537        foreach ( array_keys( $this->manager->unsanitized_post_values() ) as $setting_id ) {
     538            $setting = $this->manager->get_setting( $setting_id );
     539            if ( $setting instanceof WP_Customize_Post_Setting || $setting instanceof WP_Customize_Postmeta_Setting ) {
     540                $response['saved_post_setting_values'][ $setting->id ] = $setting->value();
     541            }
     542        }
     543        return $response;
     544    }
     545
     546    /**
    422547     * Enqueue scripts and styles for Customize Posts.
    423548     */
     
    432557            }
    433558
    434             $post_types[ $post_type ] = wp_array_slice_assoc( (array) $post_type_obj, array(
    435                 'name',
    436                 'supports',
    437                 'labels',
    438                 'has_archive',
    439                 'menu_icon',
    440                 'description',
    441                 'hierarchical',
    442             ) );
     559            $post_types[ $post_type ] = array_merge(
     560                wp_array_slice_assoc( (array) $post_type_obj, array(
     561                    'name',
     562                    'supports',
     563                    'labels',
     564                    'has_archive',
     565                    'menu_icon',
     566                    'description',
     567                    'hierarchical',
     568                    'show_in_customizer',
     569                    'publicly_queryable',
     570                    'public',
     571                ) ),
     572                array(
     573                    'current_user_can' => array(
     574                        'create_posts' => current_user_can( $post_type_obj->cap->create_posts ),
     575                        'delete_posts' => current_user_can( $post_type_obj->cap->delete_posts ),
     576                    ),
     577                )
     578            );
    443579        }
    444580
    445581        $exports = array(
     582            'nonce' => wp_create_nonce( 'customize-posts' ),
    446583            'postTypes' => $post_types,
     584            'postStatusChoices' => $this->get_post_status_choices(),
    447585            'authorChoices' => $this->get_author_choices(),
    448586            'l10n' => array(
     
    450588                'sectionCustomizeActionTpl' => __( 'Customizing &#9656; %s', 'customize-posts' ),
    451589                'fieldTitleLabel' => __( 'Title', 'customize-posts' ),
     590                'fieldSlugLabel' => __( 'Slug', 'customize-posts' ),
     591                'fieldPostStatusLabel' => __( 'Post Status', 'customize-posts' ),
    452592                'fieldContentLabel' => __( 'Content', 'customize-posts' ),
    453593                'fieldExcerptLabel' => __( 'Excerpt', 'customize-posts' ),
     
    456596                'noTitle' => __( '(no title)', 'customize-posts' ),
    457597                'theirChange' => __( 'Their change: %s', 'customize-posts' ),
    458                 'overrideButtonText' => __( 'Override', 'customize-posts' ),
    459598                'openEditor' => __( 'Open Editor', 'customize-posts' ),
    460599                'closeEditor' => __( 'Close Editor', 'customize-posts' ),
     
    484623     */
    485624    public function render_editor() {
    486         echo '<div id="customize-posts-content-editor-pane">';
    487 
    488         // The settings passed in here are derived from those used in edit-form-advanced.php.
    489         wp_editor( '', 'customize-posts-content', array(
    490             '_content_editor_dfw' => false,
    491             'drag_drop_upload' => true,
    492             'tabfocus_elements' => 'content-html,save-post',
    493             'editor_height' => 200,
    494             'default_editor' => 'tinymce',
    495             'tinymce' => array(
    496                 'resize' => false,
    497                 'wp_autoresize_on' => false,
    498                 'add_unload_trigger' => false,
    499             ),
    500         ) );
    501 
    502         echo '</div>';
     625        ?>
     626        <div id="customize-posts-content-editor-pane">
     627            <div id="customize-posts-content-editor-dragbar">
     628                <span class="screen-reader-text"><?php _e( 'Resize Editor', 'customize-posts' ); ?></span>
     629            </div>
     630
     631            <?php
     632            // The settings passed in here are derived from those used in edit-form-advanced.php.
     633            wp_editor( '', 'customize-posts-content', array(
     634                '_content_editor_dfw' => false,
     635                'drag_drop_upload' => true,
     636                'tabfocus_elements' => 'content-html,save-post',
     637                'editor_height' => 200,
     638                'default_editor' => 'tinymce',
     639                'tinymce' => array(
     640                    'resize' => false,
     641                    'wp_autoresize_on' => false,
     642                    'add_unload_trigger' => false,
     643                ),
     644            ) );
     645            ?>
     646
     647        </div>
     648        <?php
    503649    }
    504650
     
    539685        return $value;
    540686    }
     687
     688    /**
     689     * Underscore (JS) templates.
     690     */
     691    public function render_templates() {
     692        ?>
     693        <script type="text/html" id="tmpl-customize-posts-add-new">
     694            <li class="customize-posts-add-new">
     695                <button class="button-secondary add-new-post-stub">
     696                    <?php esc_html_e( 'Add New', 'customize-posts' ); ?> {{ data.label }}
     697                </button>
     698            </li>
     699        </script>
     700
     701        <script id="tmpl-customize-posts-navigation" type="text/html">
     702            <button class="customize-posts-navigation dashicons dashicons-visibility" tabindex="0">
     703                <span class="screen-reader-text"><?php esc_html_e( 'Preview', 'customize-posts' ); ?> {{ data.label }}</span>
     704            </button>
     705        </script>
     706
     707        <script id="tmpl-customize-posts-trashed" type="text/html">
     708            <span class="customize-posts-trashed">(<?php esc_html_e( 'Trashed', 'customize-posts' ); ?>)</span>
     709        </script>
     710
     711        <script type="text/html" id="tmpl-customize-post-section-notifications">
     712            <ul>
     713                <# _.each( data.notifications, function( notification ) { #>
     714                    <li class="notice notice-{{ notification.type || 'info' }} {{ data.altNotice ? 'notice-alt' : '' }}" data-code="{{ notification.code }}" data-type="{{ notification.type }}">
     715                        <# if ( /post_update_conflict/.test( notification.code ) ) { #>
     716                            <button class="button override-post-conflict" type="button"><?php esc_html_e( 'Override', 'customize-posts' ); ?></button>
     717                        <# } #>
     718                        {{ notification.message || notification.code }}
     719                    </li>
     720                <# } ); #>
     721            </ul>
     722        </script>
     723        <?php
     724    }
     725
     726    /**
     727     * Register the `customize-draft` post status.
     728     *
     729     * @action init
     730     * @access public
     731     */
     732    public function register_customize_draft() {
     733        register_post_status( 'customize-draft', array(
     734            'label'                     => 'customize-draft',
     735            'public'                    => false,
     736            'internal'                  => true,
     737            'protected'                 => true,
     738            'exclude_from_search'       => true,
     739            'show_in_admin_all_list'    => false,
     740            'show_in_admin_status_list' => false,
     741        ) );
     742    }
     743
     744    /**
     745     * Transition the post status.
     746     *
     747     * This ensures unpublished new posts, which are added to a snapshot, are not
     748     * garbage collected during the `wp_scheduled_auto_draft_delete` action by
     749     * changing the default `auto-draft` post status to `customize-draft`.
     750     *
     751     * @filter customize_snapshot_save
     752     * @access public
     753     *
     754     * @param array $data Customizer settings and values.
     755     * @return array
     756     */
     757    public function transition_customize_draft( $data ) {
     758        foreach ( $data as $id => $setting ) {
     759            if ( ! preg_match( WP_Customize_Post_Setting::SETTING_ID_PATTERN, $id, $matches ) ) {
     760                continue;
     761            }
     762            if ( 'auto-draft' === get_post_status( $matches['post_id'] ) ) {
     763                add_filter( 'wp_insert_post_empty_content', '__return_false', 100 );
     764                $result = wp_update_post( array(
     765                    'ID' => intval( $matches['post_id'] ),
     766                    'post_status' => 'customize-draft',
     767                ), true );
     768                remove_filter( 'wp_insert_post_empty_content', '__return_false', 100 );
     769
     770                // @todo Amend customize_save_response if error.
     771            }
     772        }
     773
     774        return $data;
     775    }
     776
     777    /**
     778     * Set the previewed `customize-draft` post IDs within a Snapshot.
     779     *
     780     * @action after_setup_theme
     781     * @access public
     782     */
     783    public function preview_customize_draft_post_ids() {
     784        if ( isset( $_REQUEST['preview'] ) ) {
     785            $this->customize_draft_post_ids = array();
     786            foreach ( $this->manager->unsanitized_post_values() as $id => $post_data ) {
     787                if ( ! preg_match( WP_Customize_Post_Setting::SETTING_ID_PATTERN, $id, $matches ) ) {
     788                    continue;
     789                }
     790                $post_id = intval( $matches['post_id'] );
     791                if ( 'customize-draft' === get_post_status( $post_id ) ) {
     792                    $this->customize_draft_post_ids[] = $post_id;
     793                }
     794            }
     795        }
     796    }
     797
     798    /**
     799     * Allow the `customize-draft` status to be previewed in a Snapshot by all users.
     800     *
     801     * @action pre_get_posts
     802     * @access public
     803     *
     804     * @param WP_Query $query The WP_Query instance (passed by reference).
     805     */
     806    public function preview_customize_draft( $query ) {
     807        if ( $query->is_preview ) {
     808            $query_vars = $query->query_vars;
     809            $post_id = 0;
     810
     811            if ( ! empty( $query_vars['p'] ) ) {
     812                $post_id = $query_vars['p'];
     813            } elseif ( ! empty( $query_vars['page_id'] ) ) {
     814                $post_id = $query_vars['page_id'];
     815            }
     816
     817            if ( in_array( $post_id, $this->customize_draft_post_ids, true ) ) {
     818                $query->set( 'post_status', 'customize-draft' );
     819            }
     820        }
     821    }
     822
     823    /**
     824     * Filter the preview permalink for a post.
     825     *
     826     * @access public
     827     *
     828     * @param string      $permalink The post's permalink.
     829     * @param int|WP_Post $post      The post in question.
     830     * @return string
     831     */
     832    public function post_link_draft( $permalink, $post ) {
     833        if ( is_customize_preview() && ! $this->suppress_post_link_filters ) {
     834            $permalink = Edit_Post_Preview::get_preview_post_link( get_post( $post ) );
     835        }
     836        return $permalink;
     837    }
     838
     839    /**
     840     * Add a new `auto-draft` post.
     841     *
     842     * @access public
     843     *
     844     * @param string $post_type The post type.
     845     * @return WP_Post|WP_Error
     846     */
     847    public function insert_auto_draft_post( $post_type ) {
     848
     849        $post_type_obj = get_post_type_object( $post_type );
     850        if ( ! $post_type_obj ) {
     851            return new WP_Error( 'unknown_post_type', __( 'Unknown post type', 'customize-posts' ) );
     852        }
     853
     854        add_filter( 'wp_insert_post_empty_content', '__return_false', 100 );
     855        $this->suppress_post_link_filters = true;
     856        $date_local = current_time( 'mysql', 0 );
     857        $date_gmt = current_time( 'mysql', 1 );
     858        $args = array(
     859            'post_status' => 'auto-draft',
     860            'post_type' => $post_type,
     861            'post_date' => $date_local,
     862            'post_date_gmt' => $date_gmt,
     863            'post_modified' => $date_local,
     864            'post_modified_gmt' => $date_gmt,
     865        );
     866        $r = wp_insert_post( wp_slash( $args ), true );
     867        remove_filter( 'wp_insert_post_empty_content', '__return_false', 100 );
     868        $this->suppress_post_link_filters = false;
     869
     870        if ( is_wp_error( $r ) ) {
     871            return $r;
     872        } else {
     873            return get_post( $r );
     874        }
     875    }
     876
     877    /**
     878     * Ajax handler for adding a new post.
     879     *
     880     * @action wp_ajax_customize-posts-insert-auto-draft
     881     * @access public
     882     */
     883    public function ajax_insert_auto_draft_post() {
     884        if ( ! check_ajax_referer( 'customize-posts', 'customize-posts-nonce', false ) ) {
     885            status_header( 400 );
     886            wp_send_json_error( 'bad_nonce' );
     887        }
     888
     889        if ( ! current_user_can( 'customize' ) ) {
     890            status_header( 403 );
     891            wp_send_json_error( 'customize_not_allowed' );
     892        }
     893
     894        if ( empty( $_POST['post_type'] ) ) {
     895            status_header( 400 );
     896            wp_send_json_error( 'missing_post_type' );
     897        }
     898
     899        $post_type_object = get_post_type_object( wp_unslash( $_POST['post_type'] ) );
     900        if ( ! $post_type_object || ! current_user_can( $post_type_object->cap->create_posts ) ) {
     901            status_header( 403 );
     902            wp_send_json_error( 'insufficient_post_permissions' );
     903        }
     904        if ( ! empty( $post_type_object->labels->singular_name ) ) {
     905            $singular_name = $post_type_object->labels->singular_name;
     906        } else {
     907            $singular_name = __( 'Post', 'customize-posts' );
     908        }
     909
     910        $r = $this->insert_auto_draft_post( $post_type_object->name );
     911        if ( is_wp_error( $r ) ) {
     912            $error = $r;
     913            $data = array(
     914                'message' => sprintf( __( '%1$s could not be created: %2$s', 'customize-posts' ), $singular_name, $error->get_error_message() ),
     915            );
     916            wp_send_json_error( $data );
     917        } else {
     918            $post = $r;
     919            $exported_settings = array();
     920
     921            $post_setting_id = WP_Customize_Post_Setting::get_post_setting_id( $post );
     922            $setting_ids = array( $post_setting_id );
     923            $this->manager->add_dynamic_settings( $setting_ids );
     924            $post_setting = $this->manager->get_setting( $post_setting_id );
     925            if ( ! $post_setting ) {
     926                wp_send_json_error( array( 'message' => __( 'Failed to create setting', 'customize-posts' ) ) );
     927            }
     928
     929            $setting_ids = array_merge( $setting_ids, $this->register_post_type_meta_settings( $post->ID ) );
     930            foreach ( $setting_ids as $setting_id ) {
     931                $setting = $this->manager->get_setting( $setting_id );
     932                if ( ! $setting ) {
     933                    continue;
     934                }
     935                if ( preg_match( WP_Customize_Postmeta_Setting::SETTING_ID_PATTERN, $setting->id, $matches ) ) {
     936                    if ( isset( $matches['meta_key'] ) && isset( $params[ $matches['meta_key'] ] ) ) {
     937                        $this->manager->set_post_value( $setting_id, $params[ $matches['meta_key'] ] );
     938                        $setting->preview();
     939                    }
     940                }
     941                if ( method_exists( $setting, 'json' ) ) { // New in 4.6-alpha.
     942                    $exported_settings[ $setting->id ] = $setting->json();
     943                } else {
     944                    $exported_settings[ $setting->id ] = array(
     945                        'value' => $setting->js_value(),
     946                        'transport' => $setting->transport,
     947                        'dirty' => $setting->dirty,
     948                        'type' => $setting->type,
     949                    );
     950                }
     951                $exported_settings[ $setting->id ]['dirty'] = true;
     952            }
     953            $data = array(
     954                'postId' => $post->ID,
     955                'postSettingId' => $post_setting_id,
     956                'settings' => $exported_settings,
     957                'sectionId' => WP_Customize_Post_Setting::get_post_setting_id( $post ),
     958                'url' => Edit_Post_Preview::get_preview_post_link( $post ),
     959            );
     960            wp_send_json_success( $data );
     961        }
     962    }
    541963}
  • customize-posts/trunk/readme.txt

    r1406933 r1430660  
    44Requires at least: 4.5-beta2
    55Tested up to:      4.6-alpha
    6 Stable tag:        0.5.0
     6Stable tag:        0.6.0
    77License:           GPLv2 or later
    88License URI:       http://www.gnu.org/licenses/gpl-2.0.html
     9Text Domain:       customize-posts
    910
    1011Edit posts and postmeta in the Customizer. Stop editing your posts/postmeta blind!
     
    5859== Changelog ==
    5960
     61= 0.6.0 - 2016-06-02 =
     62
     63Added:
     64
     65 * Add the ability to create new posts and pages in the Customizer. Created posts get <code>auto-draft</code> status in the DB so they will be garbage-collected if the Customizer is never saved. A new view link appears in the post section allowing a newly-created post to be navigated to easily without having to find the created post linked to in the preview. (Issues <a href="https://github.com/xwp/wp-customize-posts/issues/48" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/48" data-id="139489948" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#48</a>, <a href="https://github.com/xwp/wp-customize-posts/issues/50" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/50" data-id="139490828" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#50</a>, PR <a href="https://github.com/xwp/wp-customize-posts/pull/134" class="issue-link js-issue-link" data-id="154290445" data-error-text="Failed to load issue title" data-permission-text="Issue title is private" title="Post Creation">#134</a>)
     66 * Add post status control and preview, with <code>trash</code> status support (Issues <a href="https://github.com/xwp/wp-customize-posts/issues/40" class="issue-link js-issue-link" data-id="138757986" data-error-text="Failed to load issue title" data-permission-text="Issue title is private" title="Add post status dropdown selection control and preview">#40</a>, <a href="https://github.com/xwp/wp-customize-posts/issues/137" class="issue-link js-issue-link" data-id="154582644" data-error-text="Failed to load issue title" data-permission-text="Issue title is private" title="Delete a post whilst in the Customizer">#137</a>, PR <a href="https://github.com/xwp/wp-customize-posts/pull/152" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/152" data-id="157317131" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#152</a>)
     67 * Add support for setting validation in WordPress 4.6-alpha, showing notifications if attempting to save when a post is locked or a conflicting update was previously made. (Issue <a href="https://github.com/xwp/wp-customize-posts/issues/142" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/142" data-id="156058078" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#142</a>, PR <a href="https://github.com/xwp/wp-customize-posts/pull/150" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/150" data-id="156916244" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#150</a>)
     68 * Add the ability to vertically resize the post editor (Issue <a href="https://github.com/xwp/wp-customize-posts/issues/136" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/136" data-id="154541791" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#136</a>, PR <a href="https://github.com/xwp/wp-customize-posts/pull/149" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/149" data-id="156898366" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#149</a>)
     69 * Add post slug control, wherein changes do not cause the preview to refresh by default since there is nothing to see (Issue <a href="https://github.com/xwp/wp-customize-posts/issues/63" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/63" data-id="140028525" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#63</a>, PR <a href="https://github.com/xwp/wp-customize-posts/pull/148" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/148" data-id="156877309" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#148</a>)
     70 * Posts data as saved will now be synced back into the Customizer interface, ensuring that if a post slug gets the infamous <code>-2</code> added, you’ll see that in the Control. Likewise, if a <code>wp_insert_post_data</code> filter or <code>content_save_pre</code> changes your data in some way, these will be shown in the post’s Customizer controls upon saving.
     71 * Add extendable theme &amp; plugin compatibility classes that can configure partial rendering. All Core themes &amp; Jetpack are currently supported. (Issues <a href="https://github.com/xwp/wp-customize-posts/issues/82" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/82" data-id="145302561" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#82</a>, <a href="https://github.com/xwp/wp-customize-posts/issues/103" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/103" data-id="150771094" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#103</a>, PR <a href="https://github.com/xwp/wp-customize-posts/pull/123" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/123" data-id="151805533" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#123</a>)
     72 * Use <code>plugins_url()</code> for each asset URL so that the plugin can be installed as a submodule without <code>SCRIPT_DEBUG</code> (Issue <a href="https://github.com/xwp/wp-customize-posts/pull/133" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/133" data-id="154007394" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#133</a>)
     73
     74Fixed:
     75
     76 * Add all postmeta settings for registered types not just the ones actually referenced (Issues <a href="https://github.com/xwp/wp-customize-posts/pull/141" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/141" data-id="155671511" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#141</a>, <a href="https://github.com/xwp/wp-customize-posts/issues/145" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/145" data-id="156801928" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#145</a>)
     77 * Export all registered post types to client, but only register panels if <code>show_in_customizer</code> (PR <a href="https://github.com/xwp/wp-customize-posts/pull/130" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/130" data-id="152866156" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#130</a>)
     78 * Ensure that control pane expand button is visible when editor is open and the Customizer pane is collapsed (Issue <a href="https://github.com/xwp/wp-customize-posts/issues/44" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/44" data-id="139484076" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#44</a>, PR <a href="https://github.com/xwp/wp-customize-posts/pull/126" class="issue-link js-issue-link" data-url="https://github.com/xwp/wp-customize-posts/issues/126" data-id="152710123" data-error-text="Failed to load issue title" data-permission-text="Issue title is private">#126</a>)
     79 * Improve compatibility with the Customize Snapshots plugin.
     80 * Improve compatibility with the WP REST API plugin.
     81 * Supply a default <code>(no title)</code> placeholder to the post title control for new posts.
     82 * Filter post and page links in the Customizer to return the preview URL.
     83
     84See full commit log: [`0.5.0...0.6.0`](https://github.com/xwp/wp-customize-posts/compare/0.5.0...0.6.0)
     85
     86Issues in milestone: [`milestone:0.6`](https://github.com/xwp/wp-customize-posts/issues?q=milestone%3A0.6)
     87
     88Props: Weston Ruter (<a href="https://github.com/westonruter" class="user-mention">@westonruter</a>), Derek Herman (<a href="https://github.com/valendesigns" class="user-mention">@valendesigns</a>), Philip Ingram (<a href="https://github.com/pingram3541" class="user-mention">@pingram3541</a>), Daniel Bachhuber (<a href="https://github.com/danielbachhuber" class="user-mention">@danielbachhuber</a>), Stuart Shields (<a href="https://github.com/stuartshields" class="user-mention">@stuartshields</a>)
     89
    6090= 0.5.0 - 2016-04-27 =
    6191
     
    79109* Export post/postmeta settings during selective refresh requests so that new posts added will appear in the panel, such as when adding the number of posts to show in the Recent Posts widget. (Issue #97, PR #99)
    80110* Improve compatibility with Customize Snapshots (PR #95)
     111
     112See [v0.5 release post](https://make.xwp.co/2016/04/29/customize-posts-v0-5-released/) on Make XWP.
    81113
    82114See full commit log: [`0.4.2...0.5.0`](https://github.com/xwp/wp-customize-posts/compare/0.4.2...0.5.0)
Note: See TracChangeset for help on using the changeset viewer.