Skip to content

Commit

Permalink
ThemeMagic: Add cssjanus to transform RTL language
Browse files Browse the repository at this point in the history
  • Loading branch information
joomlart committed Jul 4, 2013
1 parent 5af8c48 commit 1579c05
Show file tree
Hide file tree
Showing 4 changed files with 296 additions and 4 deletions.
277 changes: 277 additions & 0 deletions source/plg_system_t3/base/js/cssjanus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
/**
* Creates a CSSJanus object.
*
* CSSJanus transforms CSS rules with horizontal relevance so that a left-to-right stylesheet can
* become a right-to-left stylesheet automatically. Processing can be bypassed for an entire rule
* or a single property by adding a / * @noflip * / comment above the rule or property.
*
* @author "Trevor Parscal" <[email protected]>
* @author "Roan Kattouw" <[email protected]>
* @author "Lindsey Simon" <[email protected]>
* @author "Roozbeh Pournader" <[email protected]>
* @author "Bryon Engelhardt" <[email protected]>
*
* @class
* @constructor
* @param {RegExp} regex Regular expression whose matches to replace by a token
* @param {String} token Placeholder text
*/
function CSSJanus() {

/* Private Members */

var prepared = false,
// Tokens
temporaryToken = '`TMP`',
noFlipSingleToken = '`NOFLIP_SINGLE`',
noFlipClassToken = '`NOFLIP_CLASS`',
commentToken = '`COMMENT`',
// Patterns
nonAsciiPattern = '[^\\u0020-\\u007e]',
unicodePattern = '(?:(?:\\[0-9a-f]{1,6})(?:\\r\\n|\\s)?)',
numPattern = '(?:[0-9]*\\.[0-9]+|[0-9]+)',
unitPattern = '(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)',
directionPattern = 'direction\\s*:\\s*',
urlSpecialCharsPattern = '[!#$%&*-~]',
validAfterUriCharsPattern = '[\'"]?\\s*',
nonLetterPattern = '(^|[^a-zA-Z])',
charsWithinSelectorPattern = '[^\\}]*?',
noFlipPattern = '\\/\\*\\s*@noflip\\s*\\*\\/',
commentPattern = '\\/\\*[^*]*\\*+([^\\/*][^*]*\\*+)*\\/',
escapePattern = '(?:' + unicodePattern + '|\\\\[^\\r\\n\\f0-9a-f])',
nmstartPattern = '(?:[_a-z]|' + nonAsciiPattern + '|' + escapePattern + ')',
nmcharPattern = '(?:[_a-z0-9-]|' + nonAsciiPattern + '|' + escapePattern + ')',
identPattern = '-?' + nmstartPattern + nmcharPattern + '*',
quantPattern = numPattern + '(?:\\s*' + unitPattern + '|' + identPattern + ')?',
signedQuantPattern = '((?:-?' + quantPattern + ')|(?:inherit|auto))',
fourNotationQuantPropsPattern = '((?:margin|padding|border-width)\\s*:\\s*)',
fourNotationColorPropsPattern = '(-color\\s*:\\s*)',
colorPattern = '(#?' + nmcharPattern + '+)',
urlCharsPattern = '(?:' + urlSpecialCharsPattern + '|' + nonAsciiPattern + '|' + escapePattern + ')*',
lookAheadNotOpenBracePattern = '(?!(' + nmcharPattern + '|\\r?\\n|\\s|#|\\:|\\.|\\,|\\+|>)*?{)',
lookAheadNotClosingParenPattern = '(?!' + urlCharsPattern + '?' + validAfterUriCharsPattern + '\\))',
lookAheadForClosingParenPattern = '(?=' + urlCharsPattern + '?' + validAfterUriCharsPattern + '\\))',
// Regular expressions
temporaryTokenRegExp = new RegExp( '`TMP`', 'g' ),
commentRegExp = new RegExp( commentPattern, 'gi' ),
noFlipSingleRegExp = new RegExp( '(' + noFlipPattern + lookAheadNotOpenBracePattern + '[^;}]+;?)', 'gi' ),
noFlipClassRegExp = new RegExp( '(' + noFlipPattern + charsWithinSelectorPattern + '})', 'gi' ),
directionLtrRegExp = new RegExp( '(' + directionPattern + ')ltr', 'gi' ),
directionRtlRegExp = new RegExp( '(' + directionPattern + ')rtl', 'gi' ),
leftRegExp = new RegExp( nonLetterPattern + '(left)' + lookAheadNotClosingParenPattern + lookAheadNotOpenBracePattern, 'gi' ),
rightRegExp = new RegExp( nonLetterPattern + '(right)' + lookAheadNotClosingParenPattern + lookAheadNotOpenBracePattern, 'gi' ),
leftInUrlRegExp = new RegExp( nonLetterPattern + '(left)' + lookAheadForClosingParenPattern, 'gi' ),
rightInUrlRegExp = new RegExp( nonLetterPattern + '(right)' + lookAheadForClosingParenPattern, 'gi' ),
ltrInUrlRegExp = new RegExp( nonLetterPattern + '(ltr)' + lookAheadForClosingParenPattern, 'gi' ),
rtlInUrlRegExp = new RegExp( nonLetterPattern + '(rtl)' + lookAheadForClosingParenPattern, 'gi' ),
cursorEastRegExp = new RegExp( nonLetterPattern + '([ns]?)e-resize', 'gi' ),
cursorWestRegExp = new RegExp( nonLetterPattern + '([ns]?)w-resize', 'gi' ),
fourNotationQuantRegExp = new RegExp( fourNotationQuantPropsPattern + signedQuantPattern + '(\\s+)' + signedQuantPattern + '(\\s+)' + signedQuantPattern + '(\\s+)' + signedQuantPattern, 'gi' ),
fourNotationColorRegExp = new RegExp( fourNotationColorPropsPattern + colorPattern + '(\\s+)' + colorPattern + '(\\s+)' + colorPattern + '(\\s+)' + colorPattern, 'gi' ),
bgHorizontalPercentageRegExp = new RegExp( '(background(?:-position)?\\s*:\\s*[^%]*?)(-?' + numPattern + ')(%\\s*(?:' + quantPattern + '|' + identPattern + '))', 'gi' ),
bgHorizontalPercentageXRegExp = new RegExp( '(background-position-x\\s*:\\s*)(-?' + numPattern + ')(%)', 'gi' ),
borderRadiusRegExp = new RegExp( '(border-radius\\s*:\\s*)([^;]*)', 'gi' );

/* Private Methods */

/**
* Inverts the horizontal value of a background position property.
*
* @private
* @function
* @param {String} match Matched property
* @param {String} pre Text before value
* @param {String} value Horizontal value
* @param {String} post Text after value
* @return {String} Inverted property
*/
function calculateNewBackgroundPosition( match, pre, value, post ) {
return pre + ( 100 - Number( value ) ) + post;
}

/**
* Inverts the horizontal value of a background position property.
*
* @private
* @function
* @param {String} match Matched property
* @param {String} pre Text before value
* @param {String} value Horizontal value
* @param {String} post Text after value
* @return {String} Inverted property
*/
function calculateNewBorderRadius( match, pre, values ) {
values = values.split( /\s+/g );
switch ( values.length ) {
case 4:
values = [values[1], values[0], values[3], values[2]];
break;
case 3:
values = [values[1], values[0], values[2]];
break;
case 2:
values = [values[1], values[0]];
break;
}
return pre + values.join( ' ' );
}

/* Methods */

return {
/**
* Transform a left-to-right stylesheet to right-to-left.
*
* @method
* @param {String} css Stylesheet to transform
* @param {Boolean} swapLtrRtlInUrl Swap 'ltr' and 'rtl' in URLs
* @param {Boolean} swapLeftRightInUrl Swap 'left' and 'right' in URLs
* @return {String} Transformed stylesheet
*/
'transform': function( css, swapLtrRtlInUrl, swapLeftRightInUrl ) {
// Tokenizers
var noFlipSingleTokenizer = new Tokenizer( noFlipSingleRegExp, noFlipSingleToken ),
noFlipClassTokenizer = new Tokenizer( noFlipClassRegExp, noFlipClassToken ),
commentTokenizer = new Tokenizer( commentRegExp, commentToken );

// Tokenize
css = commentTokenizer.tokenize(
noFlipClassTokenizer.tokenize(
noFlipSingleTokenizer.tokenize(
// We wrap tokens in ` , not ~ like the original implementation does.
// This was done because ` is not a legal character in CSS and can only
// occur in URLs, where we escape it to %60 before inserting our tokens.
css.replace( '`', '%60' )
)
)
);

// Transform URLs
if ( swapLtrRtlInUrl ) {
// Replace 'ltr' with 'rtl' and vice versa in background URLs
css = css
.replace( ltrInUrlRegExp, '$1' + temporaryToken )
.replace( rtlInUrlRegExp, '$1ltr' )
.replace( temporaryTokenRegExp, 'rtl' );
}
if ( swapLeftRightInUrl ) {
// Replace 'left' with 'right' and vice versa in background URLs
css = css
.replace( leftInUrlRegExp, '$1' + temporaryToken )
.replace( rightInUrlRegExp, '$1left' )
.replace( temporaryTokenRegExp, 'right' );
}

// Transform rules
css = css
// Replace direction: ltr; with direction: rtl; and vice versa.
.replace( directionLtrRegExp, '$1' + temporaryToken )
.replace( directionRtlRegExp, '$1ltr' )
.replace( temporaryTokenRegExp, 'rtl' )
// Flip rules like left: , padding-right: , etc.
.replace( leftRegExp, '$1' + temporaryToken )
.replace( rightRegExp, '$1left' )
.replace( temporaryTokenRegExp, 'right' )
// Flip East and West in rules like cursor: nw-resize;
.replace( cursorEastRegExp, '$1$2' + temporaryToken )
.replace( cursorWestRegExp, '$1$2e-resize' )
.replace( temporaryTokenRegExp, 'w-resize' )
// Border radius
.replace( borderRadiusRegExp, calculateNewBorderRadius )
// Swap the second and fourth parts in four-part notation rules
// like padding: 1px 2px 3px 4px;
.replace( fourNotationQuantRegExp, '$1$2$3$8$5$6$7$4' )
.replace( fourNotationColorRegExp, '$1$2$3$8$5$6$7$4' )
// Flip horizontal background percentages
.replace( bgHorizontalPercentageRegExp, calculateNewBackgroundPosition )
.replace( bgHorizontalPercentageXRegExp, calculateNewBackgroundPosition );

// Detokenize
css = noFlipSingleTokenizer.detokenize(
noFlipClassTokenizer.detokenize(
commentTokenizer.detokenize( css )
)
);

return css;
}
};
}

/**
* Creates a tokenizer object.
*
* This utility class is used by CSSJanus to protect strings by replacing them temporarily with
* tokens and later transforming them back.
*
* @author Trevor Parscal
* @author Roan Kattouw
*
* @class
* @constructor
* @param {RegExp} regex Regular expression whose matches to replace by a token
* @param {String} token Placeholder text
*/
Tokenizer = function( regex, token ) {

/* Private Members */

var matches = [],
index = 0;

/* Private Methods */

/**
* Adds a match.
*
* @private
* @function
* @param {String} match Matched string
* @returns {String} Token to leave in the matched string's place
*/
function tokenizeCallback( match ) {
matches.push( match );
return token;
}

/**
* Gets a match.
*
* @private
* @function
* @param {String} token Matched token
* @returns {String} Original matched string to restore
*/
function detokenizeCallback( token ) {
return matches[index++];
}

/* Methods */

return {
/**
* Replace matching strings with tokens.
*
* @method
* @param {String} str String to tokenize
* @return {String} Tokenized string
*/
'tokenize': function( str ) {
return str.replace( regex, tokenizeCallback );
},
/**
* Restores tokens to their original values.
*
* @method
* @param {String} str String previously run through tokenize()
* @return {String} Original string
*/
'detokenize': function( str ) {
return str.replace( new RegExp( '(' + token + ')', 'g' ), detokenizeCallback );
}
};
};

/* Initialization */

var cssjanus = new CSSJanus();
4 changes: 4 additions & 0 deletions source/plg_system_t3/base/js/less-1.3.3.js
Original file line number Diff line number Diff line change
Expand Up @@ -4246,6 +4246,10 @@ function createCSS(styles, sheet, lastModified) {
(nextEl && nextEl.parentNode || document.getElementsByTagName('head')[0]).insertBefore(css, nextEl);
}

if(typeof cssjanus != 'undefined'){
styles = cssjanus.transform(styles);
}

if (css.styleSheet) { // IE
try {
css.styleSheet.cssText = styles;
Expand Down
18 changes: 14 additions & 4 deletions source/plg_system_t3/includes/core/less.php
Original file line number Diff line number Diff line change
Expand Up @@ -401,15 +401,25 @@ public static function addStylesheet($lesspath)
}

$app = JFactory::getApplication();
$doc = JFactory::getDocument();
$tpl = $app->getTemplate(true);
$theme = $tpl->params->get('theme');

$doc = JFactory::getDocument();

if (defined('T3_THEMER')) {
// in Themer mode, using js to parse less for faster
$doc->addStylesheet(JURI::base(true) . '/' . T3Path::cleanPath($lesspath), 'text/less');
// Add lessjs to process lesscss
$doc->addScript(T3_URL . '/js/less-1.3.3.js');

if(!defined('LESS_JS')){
// Add lessjs to process lesscss
$doc->addScript(T3_URL . '/js/less-1.3.3.js');

if($doc->direction == 'rtl'){
$doc->addScript(T3_URL . '/js/cssjanus.js');
}

define('LESS_JS', 1);
}

} else {
// in development mode, using php to compile less for a better view of development
if (preg_match('#(template(-responsive)?.less)#', $lesspath)) {
Expand Down
1 change: 1 addition & 0 deletions source/tpl_t3_blank/templateDetails.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<filename>favicon.ico</filename>
<filename>index.html</filename>
<filename>index.php</filename>
<filename>thememagic.xml</filename>
<filename>templateInfo.php</filename>
<filename>template_preview.png</filename>
<filename>template_thumbnail.png</filename>
Expand Down

0 comments on commit 1579c05

Please sign in to comment.