├── .ci-env.sh ├── .coveralls.yml ├── .david-dev ├── .gitignore ├── .gitmodules ├── .jscsrc ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── Gruntfile.js ├── composer.json ├── contributing.md ├── css └── customizer-resizer.css ├── customize-pane-resizer.php ├── images ├── grip.png └── handle-horz.png ├── instance.php ├── js └── customizer-resizer.js ├── package.json ├── php ├── class-exception.php ├── class-plugin-base.php └── class-plugin.php ├── phpcs.ruleset.xml ├── phpunit.xml.dist ├── readme.md ├── readme.txt └── tests ├── php ├── test-class-plugin-base.php └── test-class-plugin.php └── test-customize-pane-resizer.php /.ci-env.sh: -------------------------------------------------------------------------------- 1 | PATH_INCLUDES='*.php *.js *.json php/* tests/* js/* css/*' 2 | PHPCS_IGNORE=vendor 3 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | src_dir: . 3 | coverage_clover: build/logs/clover.xml 4 | json_path: build/logs/coveralls-upload.json 5 | -------------------------------------------------------------------------------- /.david-dev: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwp/wp-customize-pane-resizer/a5f6e62f35cd14a528bb75170a019ab9b1fdfd11/.david-dev -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Grunt 4 | /build/ 5 | /node_modules/ 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dev-lib"] 2 | path = dev-lib 3 | url = https://github.com/xwp/wp-dev-lib.git 4 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "wordpress", 3 | "excludeFiles": [ 4 | "**/*.min.js", 5 | "**/node_modules/**", 6 | "**/vendor/**" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | **/*.min.js 2 | **/node_modules/** 3 | **/vendor/** -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "eqnull": true, 6 | "es3": true, 7 | "expr": true, 8 | "immed": true, 9 | "noarg": true, 10 | "onevar": true, 11 | "quotmark": "single", 12 | "trailing": true, 13 | "undef": true, 14 | "unused": true, 15 | 16 | "browser": true, 17 | 18 | "globals": { 19 | "_": false, 20 | "Backbone": false, 21 | "jQuery": false, 22 | "wp": false 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: 4 | - php 5 | - node_js 6 | 7 | php: 8 | - 5.4 9 | - 5.6 10 | 11 | node_js: 12 | - 0.10 13 | 14 | env: 15 | - WP_VERSION=latest WP_MULTISITE=0 16 | - WP_VERSION=latest WP_MULTISITE=1 17 | - WP_VERSION=trunk WP_MULTISITE=0 18 | - WP_VERSION=trunk WP_MULTISITE=1 19 | 20 | before_script: 21 | - export DEV_LIB_PATH=dev-lib 22 | - if [ ! -e "$DEV_LIB_PATH" ] && [ -L .travis.yml ]; then export DEV_LIB_PATH=$( dirname $( readlink .travis.yml ) ); fi 23 | - source $DEV_LIB_PATH/travis.before_script.sh 24 | 25 | script: 26 | - $DEV_LIB_PATH/travis.script.sh 27 | 28 | after_script: 29 | - $DEV_LIB_PATH/travis.after_script.sh 30 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | module.exports = function( grunt ) { 3 | 'use strict'; 4 | 5 | grunt.initConfig( { 6 | 7 | pkg: grunt.file.readJSON( 'package.json' ), 8 | 9 | // JavaScript linting with JSHint. 10 | jshint: { 11 | options: { 12 | jshintrc: '.jshintrc' 13 | }, 14 | all: [ 15 | 'Gruntfile.js', 16 | 'js/*.js' 17 | ] 18 | }, 19 | 20 | // Check textdomain errors. 21 | checktextdomain: { 22 | options:{ 23 | text_domain: '<%= pkg.name %>', 24 | keywords: [ 25 | '__:1,2d', 26 | '_e:1,2d', 27 | '_x:1,2c,3d', 28 | 'esc_html__:1,2d', 29 | 'esc_html_e:1,2d', 30 | 'esc_html_x:1,2c,3d', 31 | 'esc_attr__:1,2d', 32 | 'esc_attr_e:1,2d', 33 | 'esc_attr_x:1,2c,3d', 34 | '_ex:1,2c,3d', 35 | '_n:1,2,4d', 36 | '_nx:1,2,4c,5d', 37 | '_n_noop:1,2,3d', 38 | '_nx_noop:1,2,3c,4d' 39 | ] 40 | }, 41 | files: { 42 | src: [ 43 | '**/*.php', // Include all files 44 | '!build/**', // Exclude build/ 45 | '!node_modules/**', // Exclude node_modules/ 46 | '!tests/**' // Exclude tests/ 47 | ], 48 | expand: true 49 | } 50 | }, 51 | 52 | // Build a deploy-able plugin 53 | copy: { 54 | build: { 55 | src: [ 56 | '**', 57 | '!.*', 58 | '!.*/**', 59 | '!.DS_Store', 60 | '!build/**', 61 | '!composer.json', 62 | '!contributing.md', 63 | '!dev-lib/**', 64 | '!Gruntfile.js', 65 | '!node_modules/**', 66 | '!npm-debug.log', 67 | '!package.json', 68 | '!phpcs.ruleset.xml', 69 | '!phpunit.xml.dist', 70 | '!readme.md', 71 | '!tests/**' 72 | ], 73 | dest: 'build', 74 | expand: true, 75 | dot: true 76 | } 77 | }, 78 | 79 | // Clean up the build 80 | clean: { 81 | build: { 82 | src: [ 'build' ] 83 | } 84 | }, 85 | 86 | // VVV (Varying Vagrant Vagrants) Paths 87 | vvv: { 88 | 'plugin': '/srv/www/wordpress-develop/src/wp-content/plugins/<%= pkg.name %>', 89 | 'coverage': '/srv/www/default/coverage/<%= pkg.name %>' 90 | }, 91 | 92 | // Shell actions 93 | shell: { 94 | options: { 95 | stdout: true, 96 | stderr: true 97 | }, 98 | readme: { 99 | command: 'cd ./dev-lib && ./generate-markdown-readme' // Genrate the readme.md 100 | }, 101 | phpunit: { 102 | command: 'vagrant ssh -c "cd <%= vvv.plugin %> && phpunit"' 103 | }, 104 | phpunit_c: { 105 | command: 'vagrant ssh -c "cd <%= vvv.plugin %> && phpunit --coverage-html <%= vvv.coverage %>"' 106 | } 107 | }, 108 | 109 | // Deploys a git Repo to the WordPress SVN repo 110 | wp_deploy: { 111 | deploy: { 112 | options: { 113 | plugin_slug: '<%= pkg.name %>', 114 | build_dir: 'build' 115 | } 116 | } 117 | }, 118 | 119 | watch: { 120 | scripts: { 121 | files: ['js/*.js', '!js/*.min.js'], 122 | tasks: ['js'], 123 | options: { 124 | debounceDelay: 500 125 | } 126 | } 127 | } 128 | 129 | } ); 130 | 131 | // Load tasks 132 | grunt.loadNpmTasks( 'grunt-contrib-watch' ); 133 | grunt.loadNpmTasks( 'grunt-checktextdomain' ); 134 | grunt.loadNpmTasks( 'grunt-contrib-clean' ); 135 | grunt.loadNpmTasks( 'grunt-contrib-copy' ); 136 | grunt.loadNpmTasks( 'grunt-contrib-jshint' ); 137 | grunt.loadNpmTasks( 'grunt-shell' ); 138 | grunt.loadNpmTasks( 'grunt-wp-deploy' ); 139 | 140 | // Register tasks 141 | grunt.registerTask( 'js', [ 142 | 'jshint' 143 | ] ); 144 | 145 | grunt.registerTask( 'default', [ 146 | 'js', 147 | 'checktextdomain' 148 | ] ); 149 | 150 | grunt.registerTask( 'readme', [ 151 | 'shell:readme' 152 | ] ); 153 | 154 | grunt.registerTask( 'phpunit', [ 155 | 'shell:phpunit' 156 | ] ); 157 | 158 | grunt.registerTask( 'phpunit_c', [ 159 | 'shell:phpunit_c' 160 | ] ); 161 | 162 | grunt.registerTask( 'dev', [ 163 | 'default', 164 | 'readme' 165 | ] ); 166 | 167 | grunt.registerTask( 'build', [ 168 | 'default', 169 | 'readme', 170 | 'copy' 171 | ] ); 172 | 173 | grunt.registerTask( 'deploy', [ 174 | 'build', 175 | 'wp_deploy', 176 | 'clean' 177 | ] ); 178 | 179 | }; 180 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require-dev": { 3 | "satooshi/php-coveralls": "dev-master" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Customize Pane Resizer Contributing Guide 2 | 3 | Before submitting your contribution, please make sure to take a moment and read through the following guidelines. 4 | 5 | ## Issue Reporting Guidelines 6 | 7 | - The issue list of this repo is **exclusively** for bug reports and feature requests. 8 | - Try to search for your issue, it may have already been answered or even fixed in the `wip` (Work in Progress) branch. 9 | - Check if the issue is reproducible with the latest stable version. If you are using a pre-release, please indicate the specific version you are using. 10 | - It is **required** that you clearly describe the steps necessary to reproduce the issue you are running into. Issues without clear reproducible steps will be closed immediately. 11 | - If your issue is resolved but still open, don't hesitate to close it. In case you found a solution by yourself, it could be helpful to explain how you fixed it. 12 | 13 | ## Pull Request Guidelines 14 | 15 | - Checkout a topic branch from `wip` and merge back against `wip`. 16 | - If you are not familiar with branching please read [_A successful Git branching model_](http://nvie.com/posts/a-successful-git-branching-model/) before you go any further. 17 | - **DO NOT** check-in the `build` directory with your commits. 18 | - Follow the [WordPress Coding Standards](https://make.wordpress.org/core/handbook/coding-standards/). 19 | - Make sure the default grunt task passes. (see [development setup](#development-setup)) 20 | - If adding a new feature: 21 | - Add accompanying test case. 22 | - Provide convincing reason to add this feature. Ideally you should open a suggestion issue first and have it green-lit before working on it. 23 | - If fixing a bug: 24 | - Provide detailed description of the bug in the PR. Live demo preferred. 25 | - Add appropriate test coverage if applicable. 26 | 27 | ## Development Setup 28 | 29 | You will need [Node.js](http://nodejs.org), [Grunt](http://gruntjs.com), & [PHPUnit](https://phpunit.de/getting-started.html) installed on your system. To run the unit tests you must be developing within the WordPress Core. The simplest method to get a testing environment up is by using [Varying Vagrant Vagrants](https://github.com/Varying-Vagrant-Vagrants/VVV). However, if you are using MAMP then the following command will clone `trunk`. 30 | 31 | To clone the WordPress Core 32 | 33 | ``` bash 34 | $ git clone https://github.com/xwp/wordpress-develop.git 35 | ``` 36 | 37 | To clone this repository 38 | ``` bash 39 | $ git clone --recursive git@github.com:xwp/wp-customize-pane-resizer.git customize-pane-resizer 40 | ``` 41 | 42 | To install packages 43 | 44 | ``` bash 45 | # npm install -g grunt-cli 46 | $ npm install 47 | ``` 48 | 49 | To lint: 50 | 51 | ``` bash 52 | $ grunt jshint 53 | ``` 54 | 55 | To check the text domain: 56 | 57 | ``` bash 58 | $ grunt checktextdomain 59 | ``` 60 | 61 | To create a pot file: 62 | 63 | ``` bash 64 | $ grunt makepot 65 | ``` 66 | 67 | The default task (simply running `grunt`) will do the following: `jshint -> checktextdomain`. 68 | 69 | ### PHPUnit Testing 70 | 71 | Run tests: 72 | 73 | ``` bash 74 | $ phpunit 75 | ``` 76 | 77 | Run tests with an HTML coverage report: 78 | 79 | ``` bash 80 | $ phpunit --coverage-html /tmp/report 81 | ``` 82 | 83 | Travis CI will run the unit tests and perform sniffs against the WordPress Coding Standards whenever you push changes to your PR. Tests are required to pass successfully for a merge to be considered. -------------------------------------------------------------------------------- /css/customizer-resizer.css: -------------------------------------------------------------------------------- 1 | .customizer-resizer { 2 | display: none; 3 | } 4 | 5 | /* TODO: UI experts to style adequately. */ 6 | .resizable .customizer-resizer { 7 | display: block; 8 | height: 100%; 9 | width: 17px; 10 | position: absolute; 11 | right: -11px; 12 | cursor: pointer; 13 | cursor: col-resize; 14 | z-index: 9999999; 15 | background: none; 16 | } 17 | 18 | .resizable.fullwidth-customizer .customizer-resizer { 19 | width: 27px; 20 | right: -27px; 21 | } 22 | 23 | .resizable .customizer-resizer:before, 24 | .fullwidth-customizer.resizable .wp-full-overlay .customizer-resizer:after, 25 | .resizable .wp-full-overlay.collapsed .customizer-resizer:after { 26 | content: ''; 27 | display: block; 28 | position: absolute; 29 | height: 100%; 30 | width: 6px; 31 | left: 6px; 32 | background: url(../images/grip.png) no-repeat center center; 33 | 34 | -webkit-transition-property: width; 35 | transition-property: width; 36 | -webkit-transition-duration: 0.4s; 37 | transition-duration: 0.4s; 38 | } 39 | 40 | .fullwidth-customizer.resizable .wp-full-overlay .customizer-resizer:before, 41 | .resizable .wp-full-overlay.collapsed .customizer-resizer:before, 42 | .fullwidth-customizer.resizable .wp-full-overlay .customizer-resizer:after, 43 | .resizable .wp-full-overlay.collapsed .customizer-resizer:after { 44 | background-color: #ddd; /* Match border-right for customizer */ 45 | width: 27px; 46 | left: -1px; 47 | } 48 | 49 | .fullwidth-customizer.resizable .wp-full-overlay .customizer-resizer:before, 50 | .resizable .wp-full-overlay.collapsed .customizer-resizer:before { 51 | background: #ddd; /* Match border-right for customizer */ 52 | } 53 | 54 | .fullwidth-customizer.resizable .wp-full-overlay .customizer-resizer:after, 55 | .resizable .wp-full-overlay.collapsed .customizer-resizer:after { 56 | background: url(../images/grip.png) repeat-x center center; 57 | width: 15px; 58 | left: 5px; 59 | } 60 | 61 | .fullwidth-customizer.resizable .wp-full-overlay .customizer-resizer:after { 62 | left: 6px; 63 | } 64 | 65 | .fullwidth-customizer.resizable .wp-full-overlay .customizer-resizer:before { 66 | width: 15px; 67 | left: 5px; 68 | } 69 | 70 | .resizable .wp-full-overlay-sidebar { 71 | /* 72 | Wider border to help indicate "grabbable" 73 | Example UI (Trac): http://b.ustin.co/1aSsJ 74 | */ 75 | border-right-width: 7px; 76 | } 77 | 78 | .resizable.fullwidth-customizer .wp-full-overlay, 79 | .resizable.fullwidth-customizer .wp-full-overlay-sidebar { 80 | border-right-width: 27px; 81 | } 82 | 83 | .resizable .wp-full-overlay.collapsed .wp-full-overlay-sidebar { 84 | margin-left: -286px; 85 | } 86 | 87 | .resizable.wp-core-ui .wp-full-overlay.collapsed .collapse-sidebar { 88 | /* Bump button in so it's hovering over our newly widened border */ 89 | left: 1px; 90 | z-index: 99999999; 91 | } 92 | 93 | .no-animation.wp-full-overlay, 94 | .no-animation.wp-full-overlay-sidebar, 95 | .no-animation.wp-full-overlay .collapse-sidebar, 96 | .no-animation.wp-full-overlay-main { 97 | /* No animation to slow down dragging responsiveness */ 98 | -webkit-transition: none; 99 | transition: none; 100 | } 101 | 102 | /* 103 | Mimic @media screen and ( max-width: 640px ) styles when body has .fullwidth-customizer 104 | */ 105 | 106 | .fullwidth-customizer #customize-controls { 107 | width: 100%; 108 | } 109 | 110 | .fullwidth-customizer .wp-full-overlay.expanded { 111 | margin-left: 0; 112 | } 113 | 114 | .fullwidth-customizer .wp-full-overlay-sidebar .wp-full-overlay-sidebar-content { 115 | bottom: 0; 116 | } 117 | 118 | .fullwidth-customizer .customize-controls-preview-toggle { 119 | display: block; 120 | position: absolute; 121 | top: 0; 122 | left: 48px; 123 | line-height: 45px; 124 | font-size: 14px; 125 | padding: 0 12px 0 12px; 126 | margin: 0; 127 | height: 45px; 128 | background: #eee; 129 | border-right: 1px solid #ddd; 130 | color: #444; 131 | cursor: pointer; 132 | -webkit-transition: color .1s ease-in-out, background .1s ease-in-out; 133 | transition: color .1s ease-in-out, background .1s ease-in-out; 134 | } 135 | 136 | .fullwidth-customizer #customize-footer-actions, 137 | .fullwidth-customizer #customize-preview, 138 | .fullwidth-customizer .customize-controls-preview-toggle .controls, 139 | .fullwidth-customizer .preview-only .wp-full-overlay-sidebar-content, 140 | .fullwidth-customizer .preview-only .customize-controls-preview-toggle .preview { 141 | display: none; 142 | } 143 | 144 | .fullwidth-customizer .customize-controls-preview-toggle .preview:before, 145 | .fullwidth-customizer .customize-controls-preview-toggle .controls:before { 146 | font: normal 20px/1 dashicons; 147 | content: "\f177"; 148 | position: relative; 149 | top: 4px; 150 | margin-right: 6px; 151 | } 152 | 153 | .fullwidth-customizer .customize-controls-preview-toggle .controls:before { 154 | content: "\f540"; 155 | } 156 | 157 | .fullwidth-customizer .preview-only #customize-controls { 158 | height: 45px; 159 | } 160 | 161 | .fullwidth-customizer .preview-only #customize-preview, 162 | .fullwidth-customizer .preview-only .customize-controls-preview-toggle .controls { 163 | display: block; 164 | } 165 | 166 | .fullwidth-customizer #customize-preview { 167 | top: 45px; 168 | bottom: 0; 169 | height: auto; 170 | } 171 | 172 | .fullwidth-customizer.wp-core-ui.wp-customizer .button { 173 | padding: 6px 14px; 174 | line-height: normal; 175 | font-size: 14px; 176 | vertical-align: middle; 177 | height: auto; 178 | margin-bottom: 4px; 179 | } 180 | 181 | .fullwidth-customizer #customize-header-actions .button-primary { 182 | margin-top: 6px; 183 | } 184 | 185 | #customize-controls .customize-control-site_icon img { 186 | display: block; 187 | max-width: 256px; 188 | margin: 0 auto; 189 | } 190 | -------------------------------------------------------------------------------- /customize-pane-resizer.php: -------------------------------------------------------------------------------- 1 | =' ) ) { 34 | require_once __DIR__ . '/instance.php'; 35 | } else { 36 | if ( defined( 'WP_CLI' ) ) { 37 | WP_CLI::warning( _customize_pane_resizer_php_version_text() ); 38 | } else { 39 | add_action( 'admin_notices', '_customize_pane_resizer_php_version_error' ); 40 | } 41 | } 42 | 43 | /** 44 | * Admin notice for incompatible versions of PHP. 45 | */ 46 | function _customize_pane_resizer_php_version_error() { 47 | printf( '

%s

', esc_html( _customize_pane_resizer_php_version_text() ) ); 48 | } 49 | 50 | /** 51 | * String describing the minimum PHP version. 52 | * 53 | * @return string 54 | */ 55 | function _customize_pane_resizer_php_version_text() { 56 | return __( 'Customize Pane Resizer plugin error: Your version of PHP is too old to run this plugin. You must be running PHP 5.3 or higher.', 'customize-pane-resizer' ); 57 | } 58 | -------------------------------------------------------------------------------- /images/grip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwp/wp-customize-pane-resizer/a5f6e62f35cd14a528bb75170a019ab9b1fdfd11/images/grip.png -------------------------------------------------------------------------------- /images/handle-horz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xwp/wp-customize-pane-resizer/a5f6e62f35cd14a528bb75170a019ab9b1fdfd11/images/handle-horz.png -------------------------------------------------------------------------------- /instance.php: -------------------------------------------------------------------------------- 1 | ' ); 54 | 55 | // Cache default customizer width 56 | width = app.$.customizer.outerWidth(); 57 | 58 | // Cache selector. 59 | app.$.resizer = $( '.customizer-resizer' ); 60 | 61 | // Checks window width to determine if customizer is resizable 62 | app.checkWindowWidth(); 63 | 64 | // Disable customizer sizing animation. 65 | app.$.customizer.addClass( 'no-animation' ); 66 | app.snapToStored(); 67 | 68 | // Re-enable customizer sizing animation. 69 | app.$.customizer.removeClass( 'no-animation' ); 70 | 71 | app.asap.done = true; 72 | }; 73 | 74 | app.init = function() { 75 | // If elements were not available previously, re-initiate the caching/sizing. 76 | if ( ! app.cache.cached ) { 77 | app.asap(); 78 | } 79 | 80 | // We need the iframe to bubble up mouse events. 81 | app.initIframeMouseEvents(); 82 | 83 | app.$.resizer.on( 'mousedown', app.resizerEngage ); 84 | $( '[aria-expanded]' ).on( 'click', app.toggleExpansion ); 85 | 86 | $( window ).resize( _.debounce( app.checkWindowWidth, 50 ) ); 87 | }; 88 | 89 | app.toggleExpansion = function() { 90 | var isExpanded = 'true' === $( this ).attr( 'aria-expanded' ); 91 | if ( isExpanded ) { 92 | app.snapToStored(); 93 | } else { 94 | app.snapToDefault( true ); 95 | } 96 | }; 97 | 98 | app.checkWindowWidth = function() { 99 | var winWidth = app.$.window.width(); 100 | 101 | // The breakpoint where mobile view is triggered. 102 | if ( winWidth < 640 ) { 103 | expanded = false; 104 | app.$.body.removeClass( 'resizable' ); 105 | return app.snapToDefault(); 106 | } 107 | 108 | if ( ! expanded ) { 109 | app.$.body.addClass( 'resizable' ); 110 | expanded = true; 111 | } 112 | }; 113 | 114 | app.resizerEngage = function( evt ) { 115 | if ( 1 !== evt.which ) { 116 | return; 117 | } 118 | 119 | var winWidth = app.$.window.width(); 120 | var iframeWidth = winWidth - app.$.customizer.width(); 121 | 122 | evt.preventDefault(); 123 | 124 | if ( iframeWidth < 100 ) { 125 | 126 | // Decrease customizer width below threshold where it snaps to full-width. 127 | app.sizeCustomizer( winWidth - 320 ); 128 | app.fullWidth( 'no' ); 129 | } else { 130 | 131 | // Disable customizer sizing animation. 132 | app.$.customizer.addClass( 'no-animation' ); 133 | 134 | // Add event listeners for the dragging duration. 135 | $( document ).on( 'mousemove', app.resizerMovement ); 136 | $( document ).on( 'mouseup', app.resizerDisengage ); 137 | } 138 | }; 139 | 140 | app.resizerMovement = function( evt ) { 141 | 142 | // Check if the customizer is expanding (vs shrinking). 143 | var expanding = mouseLeft < evt.pageX; 144 | 145 | // Re-cache mouseLeft. 146 | mouseLeft = evt.pageX; 147 | 148 | var iframeWidth = app.$.window.width() - mouseLeft; 149 | 150 | // If iframe width is less than a workable width, snap full-screen. 151 | if ( iframeWidth < 290 && iframeWidth > 100 ) { 152 | app.snapToDefault(); 153 | app.resizerDisengage(); 154 | 155 | return app.fullWidth( 'yes' ); 156 | } 157 | 158 | app.fullWidth( 'no' ); 159 | 160 | // If we're expanding larger than default, increae the width. 161 | if ( mouseLeft >= ( width + 20 ) || mouseLeft >= width && expanding ) { 162 | 163 | // Offset by 3 pixels to put the cursor in the middle of the resizer bar. 164 | return app.sizeCustomizer( mouseLeft - 3 ); 165 | } 166 | 167 | // If we're condensing, and close to our default, snap to it. 168 | if ( ! expanding && mouseLeft > ( width - 30 ) && mouseLeft < ( width + 20 ) ) { 169 | return app.snapToDefault(); 170 | } 171 | 172 | // If we're condensing past our default, just trigger the collapse. 173 | if ( mouseLeft < ( width - 30 ) ) { 174 | app.snapToDefault(); 175 | app.resizerDisengage(); 176 | app.$.collapser.trigger( 'click' ); 177 | } 178 | }; 179 | 180 | app.resizerDisengage = function() { 181 | 182 | // Remove temp. event listeners. 183 | $( document ).off( 'mousemove', app.resizerMovement ); 184 | $( document ).off( 'mouseup', app.resizerDisengage ); 185 | 186 | // Re-enable customizer sizing animation. 187 | app.$.customizer.removeClass( 'no-animation' ); 188 | }; 189 | 190 | app.fullWidth = function( makeFull ) { 191 | var method = 'yes' === makeFull ? 'addClass' : 'removeClass'; 192 | app.$.body[ method ]( 'fullwidth-customizer' ); 193 | }; 194 | 195 | app.snapToStored = function() { 196 | 197 | // If we have sessionStorage and a stored size, load that. 198 | if ( app.getStorage() ) { 199 | app.sizeCustomizer( app.size, true ); 200 | } 201 | }; 202 | 203 | app.snapToDefault = function( bypassStore ) { 204 | app.sizeCustomizer( '', bypassStore ); 205 | }; 206 | 207 | app.sizeCustomizer = function( size, bypassStore ) { 208 | bypassStore = bypassStore || false; 209 | app.size = size ? parseInt( size, 10 ) : ''; 210 | 211 | // Overlay margin needs to be nudged (give more space). 212 | app.$.overlay.css({ 'margin-left' : app.size }); 213 | 214 | // And of course, resize the customizer. 215 | app.$.customizer.width( app.size ); 216 | 217 | // Unless told not to: 218 | if ( ! bypassStore ) { 219 | 220 | // Store the new value (if we have sessionStorage). 221 | app.setStorage(); 222 | } 223 | }; 224 | 225 | app.initIframeMouseEvents = function() { 226 | 227 | // Will recursively check for existence of iframe. 228 | setTimeout( function() { 229 | var $iframe = app.$.overlay.find( 'iframe' ); 230 | 231 | if ( $iframe.length ) { 232 | 233 | // Init iframe event bubbling. 234 | app.bubbleMouseEvent( $iframe[0], 'mousemove' ); 235 | app.bubbleMouseEvent( $iframe[0], 'mouseup' ); 236 | } else { 237 | app.initIframeMouseEvents(); 238 | } 239 | }, 100 ); 240 | }; 241 | 242 | app.bubbleMouseEvent = function( iframe, evtName ) { 243 | var longName = 'on' + evtName; 244 | 245 | // Save any previous handler. 246 | var existingMouseEvt = iframe.contentWindow[ longName ]; 247 | 248 | // Attach a new listener. 249 | iframe.contentWindow[ longName ] = function( evt ) { 250 | 251 | // Fire existing listener. 252 | if ( existingMouseEvt ) { 253 | existingMouseEvt( evt ); 254 | } 255 | 256 | // Create a new event for the this window. 257 | var newEvt = document.createEvent( 'MouseEvents' ); 258 | 259 | // We'll need this to offset the mouse appropriately. 260 | var boundingClientRect = iframe.getBoundingClientRect(); 261 | 262 | // Initialize the event, copying exiting event values (for the most part). 263 | newEvt.initMouseEvent( 264 | evtName, 265 | true, // bubbles 266 | false, // not cancelable 267 | window, 268 | evt.detail, 269 | evt.screenX, 270 | evt.screenY, 271 | evt.clientX + boundingClientRect.left, 272 | evt.clientY + boundingClientRect.top, 273 | evt.ctrlKey, 274 | evt.altKey, 275 | evt.shiftKey, 276 | evt.metaKey, 277 | evt.button, 278 | null // no related element 279 | ); 280 | 281 | // Dispatch the event on the iframe element. 282 | iframe.dispatchEvent( newEvt ); 283 | }; 284 | }; 285 | 286 | /** 287 | * Check if the browser supports sessionStorage and it's not disabled. 288 | * 289 | * Storage methods stolen from wp-includes/js/autosave.js. 290 | * We should consider globally accessible sessionStorage methods. 291 | * 292 | * @return {bool} Whether browser supports sessionStorage. 293 | */ 294 | app.checkStorage = function() { 295 | if ( null !== hasStorage ) { 296 | return hasStorage; 297 | } 298 | 299 | var test = Math.random().toString(); 300 | hasStorage = false; 301 | 302 | try { 303 | window.sessionStorage.setItem( 'wp-test', test ); 304 | hasStorage = window.sessionStorage.getItem( 'wp-test' ) === test; 305 | window.sessionStorage.removeItem( 'wp-test' ); 306 | } catch(e) {} 307 | 308 | return hasStorage; 309 | }; 310 | 311 | /** 312 | * Initialize the local storage 313 | * 314 | * @return mixed False if no sessionStorage in the browser or an Object containing all postData for this blog 315 | */ 316 | app.getStorage = function() { 317 | 318 | // Separate local storage containers for each blog_id 319 | if ( app.checkStorage() ) { 320 | app.size = sessionStorage.getItem( 'wp-customizer-width' ); 321 | } 322 | 323 | return app.size; 324 | }; 325 | 326 | /** 327 | * Set the storage for this blog 328 | * 329 | * Confirms that the data was saved successfully. 330 | * 331 | * @return bool 332 | */ 333 | app.setStorage = function() { 334 | if ( app.checkStorage() && app.size && 'NaN' !== app.size ) { 335 | sessionStorage.setItem( 'wp-customizer-width', app.size ); 336 | return sessionStorage.getItem( 'wp-customizer-width' ) !== null; 337 | } 338 | 339 | return false; 340 | }; 341 | 342 | // Run items which will benefit from asap loading (width of customizer from storage). 343 | app.asap(); 344 | 345 | // Bind to customizer ready for the rest. 346 | wp.customize.bind( 'ready', app.init ); 347 | 348 | } )( window, document, jQuery, window.wp ); 349 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "customize-pane-resizer", 3 | "title": "Customize Pane Resizer", 4 | "homepage": "https://github.com/xwp/wp-customize-pane-resizer", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/xwp/wp-customize-pane-resizer.git" 8 | }, 9 | "author": "WordPress.org", 10 | "license": "GPL-2.0+", 11 | "devDependencies": { 12 | "grunt": "~0.4.5", 13 | "grunt-checktextdomain": "~1.0.0", 14 | "grunt-contrib-clean": "^0.7.0", 15 | "grunt-contrib-copy": "~0.8.2", 16 | "grunt-contrib-cssmin": "^0.14.0", 17 | "grunt-contrib-jshint": "~0.11.3", 18 | "grunt-contrib-uglify": "^0.11.0", 19 | "grunt-contrib-watch": "^0.6.1", 20 | "grunt-shell": "~1.1.2", 21 | "grunt-wp-deploy": "^1.1.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /php/class-exception.php: -------------------------------------------------------------------------------- 1 | locate_plugin(); 55 | $this->slug = $location['dir_basename']; 56 | $this->dir_path = $location['dir_path']; 57 | $this->dir_url = $location['dir_url']; 58 | spl_autoload_register( array( $this, 'autoload' ) ); 59 | } 60 | 61 | /** 62 | * Get reflection object for this class. 63 | * 64 | * @return \ReflectionObject 65 | */ 66 | public function get_object_reflection() { 67 | static $reflection; 68 | if ( empty( $reflection ) ) { 69 | $reflection = new \ReflectionObject( $this ); 70 | } 71 | return $reflection; 72 | } 73 | 74 | /** 75 | * Autoload matches cache. 76 | * 77 | * @var array 78 | */ 79 | protected $autoload_matches_cache = array(); 80 | 81 | /** 82 | * Autoload for classes that are in the same namespace as $this. 83 | * 84 | * @param string $class Class name. 85 | * @return void 86 | */ 87 | public function autoload( $class ) { 88 | if ( ! isset( $this->autoload_matches_cache[ $class ] ) ) { 89 | if ( ! preg_match( '/^(?P.+)\\\\(?P[^\\\\]+)$/', $class, $matches ) ) { 90 | $matches = false; 91 | } 92 | $this->autoload_matches_cache[ $class ] = $matches; 93 | } else { 94 | $matches = $this->autoload_matches_cache[ $class ]; 95 | } 96 | if ( empty( $matches ) ) { 97 | return; 98 | } 99 | if ( $this->get_object_reflection()->getNamespaceName() !== $matches['namespace'] ) { 100 | return; 101 | } 102 | $class_name = $matches['class']; 103 | 104 | $class_path = \trailingslashit( $this->dir_path ); 105 | if ( $this->autoload_class_dir ) { 106 | $class_path .= \trailingslashit( $this->autoload_class_dir ); 107 | } 108 | $class_path .= sprintf( 'class-%s.php', strtolower( str_replace( '_', '-', $class_name ) ) ); 109 | if ( is_readable( $class_path ) ) { 110 | require_once $class_path; 111 | } 112 | } 113 | 114 | /** 115 | * Version of plugin_dir_url() which works for plugins installed in the plugins directory, 116 | * and for plugins bundled with themes. 117 | * 118 | * @throws \Exception If the plugin is not located in the expected location. 119 | * @return array 120 | */ 121 | public function locate_plugin() { 122 | $file_name = $this->get_object_reflection()->getFileName(); 123 | if ( '/' !== \DIRECTORY_SEPARATOR ) { 124 | $file_name = str_replace( \DIRECTORY_SEPARATOR, '/', $file_name ); // Windows compat. 125 | } 126 | $plugin_dir = preg_replace( '#(.*plugins[^/]*/[^/]+)(/.*)?#', '$1', $file_name, 1, $count ); 127 | if ( 0 === $count ) { 128 | throw new \Exception( "Class not located within a directory tree containing 'plugins': $file_name" ); 129 | } 130 | 131 | // Make sure that we can reliably get the relative path inside of the content directory. 132 | $content_dir = trailingslashit( WP_CONTENT_DIR ); 133 | if ( '/' !== \DIRECTORY_SEPARATOR ) { 134 | $content_dir = str_replace( \DIRECTORY_SEPARATOR, '/', $content_dir ); // Windows compat. 135 | } 136 | if ( 0 !== strpos( $plugin_dir, $content_dir ) ) { 137 | throw new \Exception( 'Plugin dir is not inside of WP_CONTENT_DIR' ); 138 | } 139 | $content_sub_path = substr( $plugin_dir, strlen( $content_dir ) ); 140 | $dir_url = content_url( trailingslashit( $content_sub_path ) ); 141 | $dir_path = $plugin_dir; 142 | $dir_basename = basename( $plugin_dir ); 143 | return compact( 'dir_url', 'dir_path', 'dir_basename' ); 144 | } 145 | 146 | /** 147 | * Return whether we're on WordPress.com VIP production. 148 | * 149 | * @return bool 150 | */ 151 | public function is_wpcom_vip_prod() { 152 | return ( defined( '\WPCOM_IS_VIP_ENV' ) && \WPCOM_IS_VIP_ENV ); 153 | } 154 | 155 | /** 156 | * Call trigger_error() if not on VIP production. 157 | * 158 | * @param string $message Warning message. 159 | * @param int $code Warning code. 160 | */ 161 | public function trigger_warning( $message, $code = \E_USER_WARNING ) { 162 | if ( ! $this->is_wpcom_vip_prod() ) { 163 | trigger_error( esc_html( get_class( $this ) . ': ' . $message ), $code ); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /php/class-plugin.php: -------------------------------------------------------------------------------- 1 | config = apply_filters( 'customize_pane_resizer_plugin_config', $this->config, $this ); 39 | 40 | add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqeue_scripts_styles' ) ); 41 | } 42 | 43 | /** 44 | * Register scripts. 45 | * 46 | * @action wp_default_scripts 47 | */ 48 | public function register_scripts() { 49 | } 50 | 51 | /** 52 | * Register styles. 53 | * 54 | * @action wp_default_styles 55 | */ 56 | public function register_styles() { 57 | } 58 | 59 | /** 60 | * Enqueue our script to be loaded when the customizer is loaded. 61 | * 62 | * @action customize_controls_enqueue_scripts 63 | */ 64 | public function enqeue_scripts_styles() { 65 | wp_register_script( 'customizer-resizer', "{$this->dir_url}js/customizer-resizer{$this->min}.js", array( 'customize-controls', 'jquery-ui-resizable' ), time(), 1 ); 66 | 67 | wp_register_style( 'customizer-resizer', "{$this->dir_url}css/customizer-resizer{$this->min}.css", array( 'customize-controls' ), time() ); 68 | 69 | wp_enqueue_script( 'customizer-resizer' ); 70 | wp_enqueue_style( 'customizer-resizer' ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /phpcs.ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Generally-applicable sniffs for WordPress plugins 4 | 5 | 6 | 7 | 8 | 9 | */dev-lib/* 10 | */node_modules/* 11 | */vendor/* 12 | 13 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | dev-lib/phpunit-plugin.xml -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Customize Pane Resizer 3 | 4 | Feature plugin for WordPress Trac #32296: Allow the customizer be made wider 5 | 6 | **Contributors:** [jtsternberg](https://profiles.wordpress.org/jtsternberg), [westonruter](https://profiles.wordpress.org/westonruter), [wordpressdotorg](https://profiles.wordpress.org/wordpressdotorg) 7 | **Requires at least:** 4.4 8 | **Tested up to:** 4.4 9 | **Stable tag:** trunk (master) 10 | **License:** [GPLv2 or later](http://www.gnu.org/licenses/gpl-2.0.html) 11 | 12 | [![Build Status](https://travis-ci.org/xwp/wp-customize-pane-resizer.svg?branch=master)](https://travis-ci.org/xwp/wp-customize-pane-resizer) [![Coverage Status](https://coveralls.io/repos/xwp/wp-customize-pane-resizer/badge.svg?branch=master)](https://coveralls.io/github/xwp/wp-customize-pane-resizer) [![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](http://gruntjs.com) [![devDependency Status](https://david-dm.org/xwp/wp-customize-pane-resizer/dev-status.svg)](https://david-dm.org/xwp/wp-customize-pane-resizer#info=devDependencies) 13 | 14 | ## Description ## 15 | 16 | Feature plugin for WordPress Trac [#32296](https://core.trac.wordpress.org/ticket/32296): Allow the customizer be made wider 17 | 18 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Customize Pane Resizer === 2 | Contributors: jtsternberg, westonruter, wordpressdotorg 3 | Requires at least: 4.4 4 | Tested up to: 4.4 5 | Stable tag: trunk 6 | License: GPLv2 or later 7 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 8 | 9 | Feature plugin for WordPress Trac #32296: Allow the customizer be made wider 10 | 11 | == Description == 12 | 13 | Feature plugin for WordPress Trac [#32296](https://core.trac.wordpress.org/ticket/32296): Allow the customizer be made wider 14 | -------------------------------------------------------------------------------- /tests/php/test-class-plugin-base.php: -------------------------------------------------------------------------------- 1 | plugin = get_plugin_instance(); 32 | } 33 | 34 | /** 35 | * Test locate_plugin. 36 | * 37 | * @see Plugin_Base::locate_plugin() 38 | */ 39 | public function test_locate_plugin() { 40 | $location = $this->plugin->locate_plugin(); 41 | $this->assertEquals( 'customize-pane-resizer', $location['dir_basename'] ); 42 | $this->assertContains( 'plugins/customize-pane-resizer', $location['dir_path'] ); 43 | $this->assertContains( 'plugins/customize-pane-resizer', $location['dir_url'] ); 44 | } 45 | 46 | /** 47 | * Tests for trigger_warning(). 48 | * 49 | * @see Plugin_Base::trigger_warning() 50 | */ 51 | public function test_trigger_warning() { 52 | $obj = $this; 53 | set_error_handler( function ( $errno, $errstr ) use ( $obj ) { 54 | $obj->assertEquals( 'CustomizePaneResizer\Plugin: Param is 0!', $errstr ); 55 | $obj->assertEquals( \E_USER_WARNING, $errno ); 56 | } ); 57 | $this->plugin->trigger_warning( 'Param is 0!', \E_USER_WARNING ); 58 | restore_error_handler(); 59 | } 60 | 61 | /** 62 | * Test is_wpcom_vip_prod(). 63 | * 64 | * @see Plugin_Base::is_wpcom_vip_prod() 65 | */ 66 | public function test_is_wpcom_vip_prod() { 67 | $this->assertFalse( $this->plugin->is_wpcom_vip_prod() ); 68 | } 69 | 70 | /** 71 | * Test is_wpcom_vip_prod_true(). 72 | * 73 | * @see Plugin_Base::is_wpcom_vip_prod() 74 | */ 75 | public function test_is_wpcom_vip_prod_true() { 76 | define( 'WPCOM_IS_VIP_ENV', true ); 77 | $this->assertTrue( $this->plugin->is_wpcom_vip_prod() ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/php/test-class-plugin.php: -------------------------------------------------------------------------------- 1 | assertEquals( 9, has_action( 'after_setup_theme', array( $plugin, 'init' ) ) ); 25 | } 26 | 27 | /** 28 | * Test for init() method. 29 | * 30 | * @see Plugin::init() 31 | */ 32 | public function test_init() { 33 | $plugin = get_plugin_instance(); 34 | 35 | add_filter( 'customize_pane_resizer_plugin_config', array( $this, 'filter_config' ), 10, 2 ); 36 | $plugin->init(); 37 | 38 | $this->assertInternalType( 'array', $plugin->config ); 39 | $this->assertArrayHasKey( 'foo', $plugin->config ); 40 | $this->assertEquals( 10, has_action( 'customize_controls_enqueue_scripts', array( $plugin, 'enqeue_scripts_styles' ) ) ); 41 | } 42 | 43 | /** 44 | * Filter to test 'customize_pane_resizer_plugin_config'. 45 | * 46 | * @see Plugin::init() 47 | * @param array $config Plugin config. 48 | * @param Plugin_Base $plugin Plugin instance. 49 | * @return array 50 | */ 51 | public function filter_config( $config, $plugin ) { 52 | unset( $config, $plugin ); // Test should actually use these. 53 | return array( 'foo' => 'bar' ); 54 | } 55 | 56 | /* Put other test functions here... */ 57 | } 58 | -------------------------------------------------------------------------------- /tests/test-customize-pane-resizer.php: -------------------------------------------------------------------------------- 1 | assertContains( '
', $buffer ); 27 | } 28 | 29 | /** 30 | * Test _customize_pane_resizer_php_version_text(). 31 | * 32 | * @see _customize_pane_resizer_php_version_text() 33 | */ 34 | public function test_customize_pane_resizer_php_version_text() { 35 | $this->assertContains( 'Customize Pane Resizer plugin error:', _customize_pane_resizer_php_version_text() ); 36 | } 37 | } 38 | --------------------------------------------------------------------------------