f780ce0a49
Previously, `wp_encode_emoji()` and `wp_staticize_emoji()` used inaccurate regular expressions to find emoji, and transform then into HTML entities or `<img>`s, respectively. This would result in emoji not being correctly transformed, or occasionally, non-emoji being incorrectly transformed. This commit adds a new `grunt` task - `grunt precommit:emoji`. It finds the regex in `twemoji.js`, transforms it into a PHP-friendly version, and adds it to `formatting.php`. This task is also automatically run by `grunt precommit`, when it detects that `twemoji.js` has changed. The new regex requires features introduced in PCRE 8.32, which was introduced in PHP 5.4.14, though it was also backported to later releases of the PHP 5.3 series. For versions of PHP that don't support this, it will fall back to an updated version of the loose-matching regex. For short posts, the performance difference between the old and new regex is negligible. As the posts get longer, however, the new method is exponentially faster. Fixes #35293. git-svn-id: https://develop.svn.wordpress.org/trunk@41043 602fd350-edb4-49c9-b593-d223f7449a82
980 lines
24 KiB
JavaScript
980 lines
24 KiB
JavaScript
/* jshint node:true */
|
|
module.exports = function(grunt) {
|
|
var path = require('path'),
|
|
fs = require( 'fs' ),
|
|
SOURCE_DIR = 'src/',
|
|
BUILD_DIR = 'build/',
|
|
autoprefixer = require('autoprefixer'),
|
|
mediaConfig = {},
|
|
mediaBuilds = ['audiovideo', 'grid', 'models', 'views'];
|
|
|
|
// Load tasks.
|
|
require('matchdep').filterDev(['grunt-*', '!grunt-legacy-util']).forEach( grunt.loadNpmTasks );
|
|
// Load legacy utils
|
|
grunt.util = require('grunt-legacy-util');
|
|
|
|
mediaBuilds.forEach( function ( build ) {
|
|
var path = SOURCE_DIR + 'wp-includes/js/media';
|
|
mediaConfig[ build ] = { files : {} };
|
|
mediaConfig[ build ].files[ path + '-' + build + '.js' ] = [ path + '/' + build + '.manifest.js' ];
|
|
} );
|
|
|
|
// Project configuration.
|
|
grunt.initConfig({
|
|
postcss: {
|
|
options: {
|
|
processors: [
|
|
autoprefixer({
|
|
browsers: [
|
|
'Android >= 2.1',
|
|
'Chrome >= 21',
|
|
'Edge >= 12',
|
|
'Explorer >= 7',
|
|
'Firefox >= 17',
|
|
'Opera >= 12.1',
|
|
'Safari >= 6.0'
|
|
],
|
|
cascade: false
|
|
})
|
|
]
|
|
},
|
|
core: {
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
dest: SOURCE_DIR,
|
|
src: [
|
|
'wp-admin/css/*.css',
|
|
'wp-includes/css/*.css'
|
|
]
|
|
},
|
|
colors: {
|
|
expand: true,
|
|
cwd: BUILD_DIR,
|
|
dest: BUILD_DIR,
|
|
src: [
|
|
'wp-admin/css/colors/*/colors.css'
|
|
]
|
|
}
|
|
},
|
|
clean: {
|
|
all: [BUILD_DIR],
|
|
dynamic: {
|
|
dot: true,
|
|
expand: true,
|
|
cwd: BUILD_DIR,
|
|
src: []
|
|
},
|
|
tinymce: ['<%= concat.tinymce.dest %>'],
|
|
qunit: ['tests/qunit/compiled.html']
|
|
},
|
|
copy: {
|
|
files: {
|
|
files: [
|
|
{
|
|
dot: true,
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
src: [
|
|
'**',
|
|
'!wp-includes/js/media/**',
|
|
'!**/.{svn,git}/**', // Ignore version control directories.
|
|
// Ignore unminified versions of external libs we don't ship:
|
|
'!wp-includes/js/backbone.js',
|
|
'!wp-includes/js/underscore.js',
|
|
'!wp-includes/js/jquery/jquery.masonry.js',
|
|
'!wp-includes/js/jquery/ui/*.js',
|
|
'!wp-includes/js/tinymce/tinymce.js',
|
|
'!wp-includes/version.php' // Exclude version.php
|
|
],
|
|
dest: BUILD_DIR
|
|
},
|
|
{
|
|
src: 'wp-config-sample.php',
|
|
dest: BUILD_DIR
|
|
}
|
|
]
|
|
},
|
|
'wp-admin-css-compat-rtl': {
|
|
options: {
|
|
processContent: function( src ) {
|
|
return src.replace( /\.css/g, '-rtl.css' );
|
|
}
|
|
},
|
|
src: SOURCE_DIR + 'wp-admin/css/wp-admin.css',
|
|
dest: BUILD_DIR + 'wp-admin/css/wp-admin-rtl.css'
|
|
},
|
|
'wp-admin-css-compat-min': {
|
|
options: {
|
|
processContent: function( src ) {
|
|
return src.replace( /\.css/g, '.min.css' );
|
|
}
|
|
},
|
|
files: [
|
|
{
|
|
src: SOURCE_DIR + 'wp-admin/css/wp-admin.css',
|
|
dest: BUILD_DIR + 'wp-admin/css/wp-admin.min.css'
|
|
},
|
|
{
|
|
src: BUILD_DIR + 'wp-admin/css/wp-admin-rtl.css',
|
|
dest: BUILD_DIR + 'wp-admin/css/wp-admin-rtl.min.css'
|
|
}
|
|
]
|
|
},
|
|
version: {
|
|
options: {
|
|
processContent: function( src ) {
|
|
return src.replace( /^\$wp_version = '(.+?)';/m, function( str, version ) {
|
|
version = version.replace( /-src$/, '' );
|
|
|
|
// If the version includes an SVN commit (-12345), it's not a released alpha/beta. Append a timestamp.
|
|
version = version.replace( /-[\d]{5}$/, '-' + grunt.template.today( 'yyyymmdd.HHMMss' ) );
|
|
|
|
/* jshint quotmark: true */
|
|
return "$wp_version = '" + version + "';";
|
|
});
|
|
}
|
|
},
|
|
src: SOURCE_DIR + 'wp-includes/version.php',
|
|
dest: BUILD_DIR + 'wp-includes/version.php'
|
|
},
|
|
dynamic: {
|
|
dot: true,
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
dest: BUILD_DIR,
|
|
src: []
|
|
},
|
|
qunit: {
|
|
src: 'tests/qunit/index.html',
|
|
dest: 'tests/qunit/compiled.html',
|
|
options: {
|
|
processContent: function( src ) {
|
|
return src.replace( /(\".+?\/)src(\/.+?)(?:.min)?(.js\")/g , function( match, $1, $2, $3 ) {
|
|
// Don't add `.min` to files that don't have it.
|
|
return $1 + 'build' + $2 + ( /jquery$/.test( $2 ) ? '' : '.min' ) + $3;
|
|
} );
|
|
}
|
|
}
|
|
}
|
|
},
|
|
browserify: mediaConfig,
|
|
sass: {
|
|
colors: {
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
dest: BUILD_DIR,
|
|
ext: '.css',
|
|
src: ['wp-admin/css/colors/*/colors.scss'],
|
|
options: {
|
|
outputStyle: 'expanded'
|
|
}
|
|
}
|
|
},
|
|
cssmin: {
|
|
options: {
|
|
compatibility: 'ie7'
|
|
},
|
|
core: {
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
dest: BUILD_DIR,
|
|
ext: '.min.css',
|
|
src: [
|
|
'wp-admin/css/*.css',
|
|
'!wp-admin/css/wp-admin*.css',
|
|
'wp-includes/css/*.css',
|
|
'wp-includes/js/mediaelement/wp-mediaelement.css'
|
|
]
|
|
},
|
|
rtl: {
|
|
expand: true,
|
|
cwd: BUILD_DIR,
|
|
dest: BUILD_DIR,
|
|
ext: '.min.css',
|
|
src: [
|
|
'wp-admin/css/*-rtl.css',
|
|
'!wp-admin/css/wp-admin*.css',
|
|
'wp-includes/css/*-rtl.css'
|
|
]
|
|
},
|
|
colors: {
|
|
expand: true,
|
|
cwd: BUILD_DIR,
|
|
dest: BUILD_DIR,
|
|
ext: '.min.css',
|
|
src: [
|
|
'wp-admin/css/colors/*/*.css'
|
|
]
|
|
}
|
|
},
|
|
rtlcss: {
|
|
options: {
|
|
// rtlcss options
|
|
opts: {
|
|
clean: false,
|
|
processUrls: { atrule: true, decl: false },
|
|
stringMap: [
|
|
{
|
|
name: 'import-rtl-stylesheet',
|
|
priority: 10,
|
|
exclusive: true,
|
|
search: [ '.css' ],
|
|
replace: [ '-rtl.css' ],
|
|
options: {
|
|
scope: 'url',
|
|
ignoreCase: false
|
|
}
|
|
}
|
|
]
|
|
},
|
|
saveUnmodified: false,
|
|
plugins: [
|
|
{
|
|
name: 'swap-dashicons-left-right-arrows',
|
|
priority: 10,
|
|
directives: {
|
|
control: {},
|
|
value: []
|
|
},
|
|
processors: [
|
|
{
|
|
expr: /content/im,
|
|
action: function( prop, value ) {
|
|
if ( value === '"\\f141"' ) { // dashicons-arrow-left
|
|
value = '"\\f139"';
|
|
} else if ( value === '"\\f340"' ) { // dashicons-arrow-left-alt
|
|
value = '"\\f344"';
|
|
} else if ( value === '"\\f341"' ) { // dashicons-arrow-left-alt2
|
|
value = '"\\f345"';
|
|
} else if ( value === '"\\f139"' ) { // dashicons-arrow-right
|
|
value = '"\\f141"';
|
|
} else if ( value === '"\\f344"' ) { // dashicons-arrow-right-alt
|
|
value = '"\\f340"';
|
|
} else if ( value === '"\\f345"' ) { // dashicons-arrow-right-alt2
|
|
value = '"\\f341"';
|
|
}
|
|
return { prop: prop, value: value };
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
core: {
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
dest: BUILD_DIR,
|
|
ext: '-rtl.css',
|
|
src: [
|
|
'wp-admin/css/*.css',
|
|
'wp-includes/css/*.css',
|
|
|
|
// Exceptions
|
|
'!wp-includes/css/dashicons.css',
|
|
'!wp-includes/css/wp-embed-template.css',
|
|
'!wp-includes/css/wp-embed-template-ie.css'
|
|
]
|
|
},
|
|
colors: {
|
|
expand: true,
|
|
cwd: BUILD_DIR,
|
|
dest: BUILD_DIR,
|
|
ext: '-rtl.css',
|
|
src: [
|
|
'wp-admin/css/colors/*/colors.css'
|
|
]
|
|
},
|
|
dynamic: {
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
dest: BUILD_DIR,
|
|
ext: '-rtl.css',
|
|
src: []
|
|
}
|
|
},
|
|
jshint: {
|
|
options: grunt.file.readJSON('.jshintrc'),
|
|
grunt: {
|
|
src: ['Gruntfile.js']
|
|
},
|
|
tests: {
|
|
src: [
|
|
'tests/qunit/**/*.js',
|
|
'!tests/qunit/vendor/*',
|
|
'!tests/qunit/editor/**'
|
|
],
|
|
options: grunt.file.readJSON('tests/qunit/.jshintrc')
|
|
},
|
|
themes: {
|
|
expand: true,
|
|
cwd: SOURCE_DIR + 'wp-content/themes',
|
|
src: [
|
|
'twenty*/**/*.js',
|
|
'!twenty{eleven,twelve,thirteen}/**',
|
|
// Third party scripts
|
|
'!twenty{fourteen,fifteen,sixteen}/js/html5.js',
|
|
'!twentyseventeen/assets/js/html5.js',
|
|
'!twentyseventeen/assets/js/jquery.scrollTo.js'
|
|
]
|
|
},
|
|
media: {
|
|
options: {
|
|
browserify: true
|
|
},
|
|
src: [
|
|
SOURCE_DIR + 'wp-includes/js/media/**/*.js'
|
|
]
|
|
},
|
|
core: {
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
src: [
|
|
'wp-admin/js/**/*.js',
|
|
'wp-includes/js/*.js',
|
|
// Built scripts.
|
|
'!wp-includes/js/media-*',
|
|
// WordPress scripts inside directories
|
|
'wp-includes/js/jquery/jquery.table-hotkeys.js',
|
|
'wp-includes/js/mediaelement/wp-mediaelement.js',
|
|
'wp-includes/js/mediaelement/wp-playlist.js',
|
|
'wp-includes/js/plupload/handlers.js',
|
|
'wp-includes/js/plupload/wp-plupload.js',
|
|
'wp-includes/js/tinymce/plugins/wordpress/plugin.js',
|
|
'wp-includes/js/tinymce/plugins/wp*/plugin.js',
|
|
// Third party scripts
|
|
'!wp-admin/js/farbtastic.js',
|
|
'!wp-includes/js/backbone*.js',
|
|
'!wp-includes/js/swfobject.js',
|
|
'!wp-includes/js/underscore*.js',
|
|
'!wp-includes/js/colorpicker.js',
|
|
'!wp-includes/js/hoverIntent.js',
|
|
'!wp-includes/js/json2.js',
|
|
'!wp-includes/js/tw-sack.js',
|
|
'!wp-includes/js/twemoji.js',
|
|
'!**/*.min.js'
|
|
],
|
|
// Remove once other JSHint errors are resolved
|
|
options: {
|
|
curly: false,
|
|
eqeqeq: false
|
|
},
|
|
// Limit JSHint's run to a single specified file:
|
|
//
|
|
// grunt jshint:core --file=filename.js
|
|
//
|
|
// Optionally, include the file path:
|
|
//
|
|
// grunt jshint:core --file=path/to/filename.js
|
|
//
|
|
filter: function( filepath ) {
|
|
var index, file = grunt.option( 'file' );
|
|
|
|
// Don't filter when no target file is specified
|
|
if ( ! file ) {
|
|
return true;
|
|
}
|
|
|
|
// Normalize filepath for Windows
|
|
filepath = filepath.replace( /\\/g, '/' );
|
|
index = filepath.lastIndexOf( '/' + file );
|
|
|
|
// Match only the filename passed from cli
|
|
if ( filepath === file || ( -1 !== index && index === filepath.length - ( file.length + 1 ) ) ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
},
|
|
plugins: {
|
|
expand: true,
|
|
cwd: SOURCE_DIR + 'wp-content/plugins',
|
|
src: [
|
|
'**/*.js',
|
|
'!**/*.min.js'
|
|
],
|
|
// Limit JSHint's run to a single specified plugin directory:
|
|
//
|
|
// grunt jshint:plugins --dir=foldername
|
|
//
|
|
filter: function( dirpath ) {
|
|
var index, dir = grunt.option( 'dir' );
|
|
|
|
// Don't filter when no target folder is specified
|
|
if ( ! dir ) {
|
|
return true;
|
|
}
|
|
|
|
dirpath = dirpath.replace( /\\/g, '/' );
|
|
index = dirpath.lastIndexOf( '/' + dir );
|
|
|
|
// Match only the folder name passed from cli
|
|
if ( -1 !== index ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
},
|
|
qunit: {
|
|
files: [
|
|
'tests/qunit/**/*.html',
|
|
'!tests/qunit/editor/**'
|
|
]
|
|
},
|
|
phpunit: {
|
|
'default': {
|
|
cmd: 'phpunit',
|
|
args: ['--verbose', '-c', 'phpunit.xml.dist']
|
|
},
|
|
ajax: {
|
|
cmd: 'phpunit',
|
|
args: ['--verbose', '-c', 'phpunit.xml.dist', '--group', 'ajax']
|
|
},
|
|
multisite: {
|
|
cmd: 'phpunit',
|
|
args: ['--verbose', '-c', 'tests/phpunit/multisite.xml']
|
|
},
|
|
'external-http': {
|
|
cmd: 'phpunit',
|
|
args: ['--verbose', '-c', 'phpunit.xml.dist', '--group', 'external-http']
|
|
},
|
|
'restapi-jsclient': {
|
|
cmd: 'phpunit',
|
|
args: ['--verbose', '-c', 'phpunit.xml.dist', '--group', 'restapi-jsclient']
|
|
}
|
|
},
|
|
uglify: {
|
|
options: {
|
|
ASCIIOnly: true,
|
|
screwIE8: false
|
|
},
|
|
core: {
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
dest: BUILD_DIR,
|
|
ext: '.min.js',
|
|
src: [
|
|
'wp-admin/js/**/*.js',
|
|
'wp-includes/js/*.js',
|
|
'wp-includes/js/mediaelement/wp-mediaelement.js',
|
|
'wp-includes/js/mediaelement/wp-playlist.js',
|
|
'wp-includes/js/plupload/handlers.js',
|
|
'wp-includes/js/plupload/wp-plupload.js',
|
|
'wp-includes/js/tinymce/plugins/wordpress/plugin.js',
|
|
'wp-includes/js/tinymce/plugins/wp*/plugin.js',
|
|
|
|
// Exceptions
|
|
'!wp-admin/js/bookmarklet.*', // Minified and updated in /src with the precommit task. See uglify:bookmarklet.
|
|
'!wp-admin/js/custom-header.js', // Why? We should minify this.
|
|
'!wp-admin/js/farbtastic.js',
|
|
'!wp-admin/js/iris.min.js',
|
|
'!wp-includes/js/backbone.*',
|
|
'!wp-includes/js/masonry.min.js',
|
|
'!wp-includes/js/swfobject.js',
|
|
'!wp-includes/js/underscore.*',
|
|
'!wp-includes/js/zxcvbn.min.js',
|
|
'!wp-includes/js/wp-embed.js' // We have extra options for this, see uglify:embed
|
|
]
|
|
},
|
|
embed: {
|
|
options: {
|
|
compress: {
|
|
conditionals: false
|
|
}
|
|
},
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
dest: BUILD_DIR,
|
|
ext: '.min.js',
|
|
src: ['wp-includes/js/wp-embed.js']
|
|
},
|
|
media: {
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
dest: BUILD_DIR,
|
|
ext: '.min.js',
|
|
src: [
|
|
'wp-includes/js/media-audiovideo.js',
|
|
'wp-includes/js/media-grid.js',
|
|
'wp-includes/js/media-models.js',
|
|
'wp-includes/js/media-views.js'
|
|
]
|
|
},
|
|
jqueryui: {
|
|
options: {
|
|
// Preserve comments that start with a bang.
|
|
preserveComments: /^!/
|
|
},
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
dest: BUILD_DIR,
|
|
ext: '.min.js',
|
|
src: ['wp-includes/js/jquery/ui/*.js']
|
|
},
|
|
bookmarklet: {
|
|
options: {
|
|
compress: {
|
|
negate_iife: false
|
|
}
|
|
},
|
|
src: SOURCE_DIR + 'wp-admin/js/bookmarklet.js',
|
|
dest: SOURCE_DIR + 'wp-admin/js/bookmarklet.min.js'
|
|
},
|
|
masonry: {
|
|
options: {
|
|
// Preserve comments that start with a bang.
|
|
preserveComments: /^!/
|
|
},
|
|
src: SOURCE_DIR + 'wp-includes/js/jquery/jquery.masonry.js',
|
|
dest: SOURCE_DIR + 'wp-includes/js/jquery/jquery.masonry.min.js'
|
|
}
|
|
},
|
|
|
|
concat: {
|
|
tinymce: {
|
|
options: {
|
|
separator: '\n',
|
|
process: function( src, filepath ) {
|
|
return '// Source: ' + filepath.replace( BUILD_DIR, '' ) + '\n' + src;
|
|
}
|
|
},
|
|
src: [
|
|
BUILD_DIR + 'wp-includes/js/tinymce/tinymce.min.js',
|
|
BUILD_DIR + 'wp-includes/js/tinymce/themes/modern/theme.min.js',
|
|
BUILD_DIR + 'wp-includes/js/tinymce/plugins/*/plugin.min.js'
|
|
],
|
|
dest: BUILD_DIR + 'wp-includes/js/tinymce/wp-tinymce.js'
|
|
},
|
|
emoji: {
|
|
options: {
|
|
separator: '\n',
|
|
process: function( src, filepath ) {
|
|
return '// Source: ' + filepath.replace( BUILD_DIR, '' ) + '\n' + src;
|
|
}
|
|
},
|
|
src: [
|
|
BUILD_DIR + 'wp-includes/js/twemoji.min.js',
|
|
BUILD_DIR + 'wp-includes/js/wp-emoji.min.js'
|
|
],
|
|
dest: BUILD_DIR + 'wp-includes/js/wp-emoji-release.min.js'
|
|
}
|
|
},
|
|
compress: {
|
|
tinymce: {
|
|
options: {
|
|
mode: 'gzip',
|
|
level: 9
|
|
},
|
|
src: '<%= concat.tinymce.dest %>',
|
|
dest: BUILD_DIR + 'wp-includes/js/tinymce/wp-tinymce.js.gz'
|
|
}
|
|
},
|
|
jsvalidate:{
|
|
options: {
|
|
globals: {},
|
|
esprimaOptions:{},
|
|
verbose: false
|
|
},
|
|
build: {
|
|
files: {
|
|
src: [
|
|
BUILD_DIR + 'wp-{admin,includes}/**/*.js',
|
|
BUILD_DIR + 'wp-content/themes/twenty*/**/*.js'
|
|
]
|
|
}
|
|
}
|
|
},
|
|
imagemin: {
|
|
core: {
|
|
expand: true,
|
|
cwd: SOURCE_DIR,
|
|
src: [
|
|
'wp-{admin,includes}/images/**/*.{png,jpg,gif,jpeg}',
|
|
'wp-includes/js/tinymce/skins/wordpress/images/*.{png,jpg,gif,jpeg}'
|
|
],
|
|
dest: SOURCE_DIR
|
|
}
|
|
},
|
|
includes: {
|
|
emoji: {
|
|
src: BUILD_DIR + 'wp-includes/formatting.php',
|
|
dest: '.'
|
|
},
|
|
embed: {
|
|
src: BUILD_DIR + 'wp-includes/embed.php',
|
|
dest: '.'
|
|
}
|
|
},
|
|
replace: {
|
|
emojiRegex: {
|
|
options: {
|
|
patterns: [
|
|
{
|
|
match: /\/\/ START: emoji regex[\S\s]*\/\/ END: emoji regex/g,
|
|
replacement: function () {
|
|
var twemoji = grunt.file.read( SOURCE_DIR + 'wp-includes/js/twemoji.js' ),
|
|
found = twemoji.match( /re = \/(.*)\/g,/ ),
|
|
emojiRegex = found[1],
|
|
regex = '',
|
|
entities = '';
|
|
|
|
/*
|
|
* Twemoji does some nifty regex optimisations, splitting up surrogate pairs unit, searching by
|
|
* ranges of individual units, and compressing sets of individual units. This is super useful for
|
|
* reducing the size of the regex.
|
|
*
|
|
* Unfortunately, PCRE doesn't allow regexes to search for individual units, so we can't just
|
|
* blindly copy the Twemoji regex.
|
|
*
|
|
* The good news is, we don't have to worry about size restrictions, so we can just unravel the
|
|
* entire regex, and convert it to a PCRE-friendly format.
|
|
*/
|
|
|
|
// Convert ranges: "\udc68-\udc6a" becomes "\udc68\udc69\udc6a".
|
|
emojiRegex = emojiRegex.replace( /(\\u\w{4})\-(\\u\w{4})/g, function ( match, first, last ) {
|
|
var start = parseInt( first.substr( 2 ), 16 );
|
|
var end = parseInt( last.substr( 2 ), 16 );
|
|
|
|
var replace = '';
|
|
|
|
for( var counter = start; counter <= end; counter++ ) {
|
|
replace += '\\u' + counter.toString( 16 );
|
|
}
|
|
|
|
return replace;
|
|
} );
|
|
|
|
// Convert sets: "\u200d[\u2640\u2642]\ufe0f" becomes "\u200d\u2640\ufe0f|\u200d\u2642\ufe0f".
|
|
emojiRegex = emojiRegex.replace( /((?:\\u\w{4})*)\[((?:\\u\w{4})+)\]((?:\\u\w{4})*)/g, function ( match, before, middle, after ) {
|
|
//return params[1].split( '\\u' ).join( '|' + params[0] + '\\u' ).substr( 1 );
|
|
if ( ! before && ! after ) {
|
|
return match;
|
|
}
|
|
var set = middle.match( /.{1,6}/g );
|
|
|
|
return before + set.join( after + '|' + before ) + after;
|
|
} );
|
|
|
|
// Convert surrogate pairs to their equivalent unicode scalar: "\ud83d\udc68" becomes "\u1f468".
|
|
emojiRegex = emojiRegex.replace( /(\\ud[89a-f][0-9a-f]{2})(\\ud[89a-f][0-9a-f]{2})/g, function ( match, first, second ) {
|
|
var high = parseInt( first.substr( 2 ), 16 );
|
|
var low = parseInt( second.substr( 2 ), 16 );
|
|
|
|
var scalar = ( ( high - 0xD800 ) * 0x400 ) + ( low - 0xDC00 ) + 0x10000;
|
|
|
|
return '\\u' + scalar.toString( 16 );
|
|
} );
|
|
|
|
// Convert JavaScript-style code points to PHP-style: "\u1f468" becomes "\x{1f468}".
|
|
emojiRegex = emojiRegex.replace( /\\u(\w+)/g, '\\x{$1}' );
|
|
|
|
// Convert PHP-style code points to HTML entities: "\x{1f468}" becomes "👨".
|
|
entities = emojiRegex.replace( /\\x{(\w+)}/g, '&#x$1;' );
|
|
entities = entities.replace( /\[([^\]]+)\]/g, function( match, codepoint ) {
|
|
return '(?:' + codepoint.replace( /;&/g, ';|&' ) + ')';
|
|
} );
|
|
|
|
regex += '// START: emoji regex\n';
|
|
regex += '\t$codepoints = \'/(' + emojiRegex + ')/u\';\n';
|
|
regex += '\t$entities = \'/(' + entities + ')/u\';\n';
|
|
regex += '\t// END: emoji regex';
|
|
|
|
return regex;
|
|
}
|
|
}
|
|
]
|
|
},
|
|
files: [
|
|
{
|
|
expand: true,
|
|
flatten: true,
|
|
src: [
|
|
SOURCE_DIR + 'wp-includes/formatting.php'
|
|
],
|
|
dest: SOURCE_DIR + 'wp-includes/'
|
|
}
|
|
]
|
|
}
|
|
},
|
|
_watch: {
|
|
all: {
|
|
files: [
|
|
SOURCE_DIR + '**',
|
|
'!' + SOURCE_DIR + 'wp-includes/js/media/**',
|
|
// Ignore version control directories.
|
|
'!' + SOURCE_DIR + '**/.{svn,git}/**'
|
|
],
|
|
tasks: ['clean:dynamic', 'copy:dynamic'],
|
|
options: {
|
|
dot: true,
|
|
spawn: false,
|
|
interval: 2000
|
|
}
|
|
},
|
|
config: {
|
|
files: 'Gruntfile.js'
|
|
},
|
|
colors: {
|
|
files: [SOURCE_DIR + 'wp-admin/css/colors/**'],
|
|
tasks: ['sass:colors']
|
|
},
|
|
rtl: {
|
|
files: [
|
|
SOURCE_DIR + 'wp-admin/css/*.css',
|
|
SOURCE_DIR + 'wp-includes/css/*.css'
|
|
],
|
|
tasks: ['rtlcss:dynamic'],
|
|
options: {
|
|
spawn: false,
|
|
interval: 2000
|
|
}
|
|
},
|
|
test: {
|
|
files: [
|
|
'tests/qunit/**',
|
|
'!tests/qunit/editor/**'
|
|
],
|
|
tasks: ['qunit']
|
|
}
|
|
}
|
|
});
|
|
|
|
// Allow builds to be minimal
|
|
if( grunt.option( 'minimal-copy' ) ) {
|
|
var copyFilesOptions = grunt.config.get( 'copy.files.files' );
|
|
copyFilesOptions[0].src.push( '!wp-content/plugins/**' );
|
|
copyFilesOptions[0].src.push( '!wp-content/themes/!(twenty*)/**' );
|
|
grunt.config.set( 'copy.files.files', copyFilesOptions );
|
|
}
|
|
|
|
|
|
// Register tasks.
|
|
|
|
// RTL task.
|
|
grunt.registerTask('rtl', ['rtlcss:core', 'rtlcss:colors']);
|
|
|
|
// Color schemes task.
|
|
grunt.registerTask('colors', ['sass:colors', 'postcss:colors']);
|
|
|
|
// JSHint task.
|
|
grunt.registerTask( 'jshint:corejs', [
|
|
'jshint:grunt',
|
|
'jshint:tests',
|
|
'jshint:themes',
|
|
'jshint:core',
|
|
'jshint:media'
|
|
] );
|
|
|
|
grunt.registerTask( 'restapi-jsclient', [
|
|
'phpunit:restapi-jsclient',
|
|
'qunit:compiled'
|
|
] );
|
|
|
|
grunt.renameTask( 'watch', '_watch' );
|
|
|
|
grunt.registerTask( 'watch', function() {
|
|
if ( ! this.args.length || this.args.indexOf( 'browserify' ) > -1 ) {
|
|
grunt.config( 'browserify.options', {
|
|
browserifyOptions: {
|
|
debug: true
|
|
},
|
|
watch: true
|
|
} );
|
|
|
|
grunt.task.run( 'browserify' );
|
|
}
|
|
|
|
grunt.task.run( '_' + this.nameArgs );
|
|
} );
|
|
|
|
grunt.registerTask( 'precommit:image', [
|
|
'imagemin:core'
|
|
] );
|
|
|
|
grunt.registerTask( 'precommit:js', [
|
|
'browserify',
|
|
'jshint:corejs',
|
|
'uglify:bookmarklet',
|
|
'uglify:masonry',
|
|
'qunit:compiled'
|
|
] );
|
|
|
|
grunt.registerTask( 'precommit:css', [
|
|
'postcss:core'
|
|
] );
|
|
|
|
grunt.registerTask( 'precommit:php', [
|
|
'phpunit'
|
|
] );
|
|
|
|
grunt.registerTask( 'precommit:emoji', [
|
|
'replace:emojiRegex'
|
|
] );
|
|
|
|
grunt.registerTask( 'precommit', 'Runs test and build tasks in preparation for a commit', function() {
|
|
var done = this.async();
|
|
var map = {
|
|
svn: 'svn status --ignore-externals',
|
|
git: 'git status --short'
|
|
};
|
|
|
|
find( [
|
|
__dirname + '/.svn',
|
|
__dirname + '/.git',
|
|
path.dirname( __dirname ) + '/.svn'
|
|
] );
|
|
|
|
function find( set ) {
|
|
var dir;
|
|
|
|
if ( set.length ) {
|
|
fs.stat( dir = set.shift(), function( error ) {
|
|
error ? find( set ) : run( path.basename( dir ).substr( 1 ) );
|
|
} );
|
|
} else {
|
|
grunt.fatal( 'This WordPress install is not under version control.' );
|
|
}
|
|
}
|
|
|
|
function run( type ) {
|
|
var command = map[ type ].split( ' ' );
|
|
|
|
grunt.util.spawn( {
|
|
cmd: command.shift(),
|
|
args: command
|
|
}, function( error, result, code ) {
|
|
var taskList = [];
|
|
|
|
if ( code !== 0 ) {
|
|
grunt.fatal( 'The `' + map[ type ] + '` command returned a non-zero exit code.', code );
|
|
}
|
|
|
|
// Callback for finding modified paths.
|
|
function testPath( path ) {
|
|
var regex = new RegExp( ' ' + path + '$', 'm' );
|
|
return regex.test( result.stdout );
|
|
}
|
|
|
|
// Callback for finding modified files by extension.
|
|
function testExtension( extension ) {
|
|
var regex = new RegExp( '\.' + extension + '$', 'm' );
|
|
return regex.test( result.stdout );
|
|
}
|
|
|
|
if ( [ 'package.json', 'Gruntfile.js' ].some( testPath ) ) {
|
|
grunt.log.writeln( 'Configuration files modified. Running `prerelease`.' );
|
|
taskList.push( 'prerelease' );
|
|
} else {
|
|
if ( [ 'png', 'jpg', 'gif', 'jpeg' ].some( testExtension ) ) {
|
|
grunt.log.writeln( 'Image files modified. Minifying.' );
|
|
taskList.push( 'precommit:image' );
|
|
}
|
|
|
|
[ 'js', 'css', 'php' ].forEach( function( extension ) {
|
|
if ( testExtension( extension ) ) {
|
|
grunt.log.writeln( extension.toUpperCase() + ' files modified. ' + extension.toUpperCase() + ' tests will be run.' );
|
|
taskList.push( 'precommit:' + extension );
|
|
}
|
|
} );
|
|
|
|
if ( [ 'twemoji.js' ].some( testPath ) ) {
|
|
grunt.log.writeln( 'twemoji.js has updated. Running `precommit:emoji.' );
|
|
taskList.push( 'precommit:emoji' );
|
|
}
|
|
}
|
|
|
|
grunt.task.run( taskList );
|
|
|
|
done();
|
|
} );
|
|
}
|
|
} );
|
|
|
|
grunt.registerTask( 'copy:all', [
|
|
'copy:files',
|
|
'copy:wp-admin-css-compat-rtl',
|
|
'copy:wp-admin-css-compat-min',
|
|
'copy:version'
|
|
] );
|
|
|
|
grunt.registerTask( 'build', [
|
|
'clean:all',
|
|
'copy:all',
|
|
'cssmin:core',
|
|
'colors',
|
|
'rtl',
|
|
'cssmin:rtl',
|
|
'cssmin:colors',
|
|
'uglify:core',
|
|
'uglify:embed',
|
|
'uglify:jqueryui',
|
|
'concat:tinymce',
|
|
'compress:tinymce',
|
|
'clean:tinymce',
|
|
'concat:emoji',
|
|
'includes:emoji',
|
|
'includes:embed',
|
|
'jsvalidate:build'
|
|
] );
|
|
|
|
grunt.registerTask( 'prerelease', [
|
|
'precommit:php',
|
|
'precommit:js',
|
|
'precommit:css',
|
|
'precommit:image'
|
|
] );
|
|
|
|
// Testing tasks.
|
|
grunt.registerMultiTask('phpunit', 'Runs PHPUnit tests, including the ajax, external-http, and multisite tests.', function() {
|
|
grunt.util.spawn({
|
|
cmd: this.data.cmd,
|
|
args: this.data.args,
|
|
opts: {stdio: 'inherit'}
|
|
}, this.async());
|
|
});
|
|
|
|
grunt.registerTask('qunit:compiled', 'Runs QUnit tests on compiled as well as uncompiled scripts.',
|
|
['build', 'copy:qunit', 'qunit']);
|
|
|
|
grunt.registerTask('test', 'Runs all QUnit and PHPUnit tasks.', ['qunit:compiled', 'phpunit']);
|
|
|
|
// Travis CI tasks.
|
|
grunt.registerTask('travis:js', 'Runs Javascript Travis CI tasks.', [ 'jshint:corejs', 'qunit:compiled' ]);
|
|
grunt.registerTask('travis:phpunit', 'Runs PHPUnit Travis CI tasks.', 'phpunit');
|
|
|
|
// Patch task.
|
|
grunt.renameTask('patch_wordpress', 'patch');
|
|
|
|
// Add an alias `apply` of the `patch` task name.
|
|
grunt.registerTask('apply', 'patch');
|
|
|
|
// Default task.
|
|
grunt.registerTask('default', ['build']);
|
|
|
|
/*
|
|
* Automatically updates the `:dynamic` configurations
|
|
* so that only the changed files are updated.
|
|
*/
|
|
grunt.event.on('watch', function( action, filepath, target ) {
|
|
var src;
|
|
|
|
if ( [ 'all', 'rtl', 'browserify' ].indexOf( target ) === -1 ) {
|
|
return;
|
|
}
|
|
|
|
src = [ path.relative( SOURCE_DIR, filepath ) ];
|
|
|
|
if ( action === 'deleted' ) {
|
|
grunt.config( [ 'clean', 'dynamic', 'src' ], src );
|
|
} else {
|
|
grunt.config( [ 'copy', 'dynamic', 'src' ], src );
|
|
|
|
if ( target === 'rtl' ) {
|
|
grunt.config( [ 'rtlcss', 'dynamic', 'src' ], src );
|
|
}
|
|
}
|
|
});
|
|
};
|