'use strict' var config = require('./config.js') var util = require('./util.js') module.exports = { 'name': 'rtlcss', 'priority': 100, 'directives': { 'control': { 'ignore': { 'expect': { 'atrule': true, 'comment': true, 'decl': true, 'rule': true }, 'endNode': null, 'begin': function (node, metadata, context) { // find the ending node in case of self closing directive if (!this.endNode && metadata.begin && metadata.end) { var n = node while (n && n.nodes) { n = n.nodes[n.nodes.length - 1] } this.endNode = n } var prevent = true if (node.type === 'comment' && node.text.match(/^\s*!?\s*rtl:end:ignore/)) { prevent = false } return prevent }, 'end': function (node, metadata, context) { // end if: // 1. block directive and the node is comment // 2. self closing directive and node is endNode if (metadata.begin !== metadata.end && node.type === 'comment' || metadata.begin && metadata.end && node === this.endNode) { // clear ending node this.endNode = null return true } return false } }, 'rename': { 'expect': {'rule': true}, 'begin': function (node, metadata, context) { node.selector = context.util.applyStringMap(node.selector, false) return false }, 'end': function (node, context) { return true } }, 'raw': { 'expect': {'self': true}, 'begin': function (node, metadata, context) { var nodes = context.postcss.parse(metadata.param, { from: node.source.input.from }) node.parent.insertBefore(node, nodes) return true }, 'end': function (node, context) { return true } }, 'remove': { 'expect': {'atrule': true, 'rule': true, 'decl': true}, 'begin': function (node, metadata, context) { var prevent = false switch (node.type) { case 'atrule': case 'rule': case 'decl': prevent = true node.remove() } return prevent }, 'end': function (node, metadata, context) { return true } }, 'options': { 'expect': {'self': true}, 'stack': [], 'begin': function (node, metadata, context) { this.stack.push(util.extend({}, context.config)) var options try { options = JSON.parse(metadata.param) } catch (e) { throw node.error('Invlaid options object', { 'details': e }) } context.config = config.configure(options, context.config.plugins) context.util = util.configure(context.config) return true }, 'end': function (node, metadata, context) { var config = this.stack.pop() if (config && !metadata.begin) { context.config = config context.util = util.configure(context.config) } return true } }, 'config': { 'expect': {'self': true}, 'expr': { 'fn': /function([^\(]*)\(([^\(\)]*?)\)[^\{]*\{([^]*)\}/ig, 'rx': /\/([^\/]*)\/(.*)/ig }, 'stack': [], 'begin': function (node, metadata, context) { this.stack.push(util.extend({}, context.config)) var configuration try { configuration = eval('(' + metadata.param + ')') // eslint-disable-line no-eval } catch (e) { throw node.error('Invlaid config object', { 'details': e }) } context.config = config.configure(configuration.options, configuration.plugins) context.util = util.configure(context.config) return true }, 'end': function (node, metadata, context) { var config = this.stack.pop() if (config && !metadata.begin) { context.config = config context.util = util.configure(context.config) } return true } } }, 'value': [ { 'name': 'ignore', 'action': function (decl, expr, context) { return true } }, { 'name': 'prepend', 'action': function (decl, expr, context) { var prefix = '' decl.raws.value.raw.replace(expr, function (m, v) { prefix += v }) decl.value = decl.raws.value.raw = prefix + decl.raws.value.raw return true } }, { 'name': 'append', 'action': function (decl, expr, context) { decl.value = decl.raws.value.raw = decl.raws.value.raw.replace(expr, function (match, value) { return match + value }) return true } }, { 'name': 'insert', 'action': function (decl, expr, context) { decl.value = decl.raws.value.raw = decl.raws.value.raw.replace(expr, function (match, value) { return value + match }) return true } }, { 'name': '', 'action': function (decl, expr, context) { decl.raws.value.raw.replace(expr, function (match, value) { decl.value = decl.raws.value.raw = value + match }) return true } } ] }, 'processors': [ { 'name': 'variable', 'expr': /^--/im, 'action': function (prop, value) { return { 'prop': prop, 'value': value } } }, { 'name': 'direction', 'expr': /direction/im, 'action': function (prop, value, context) { return { 'prop': prop, 'value': context.util.swapLtrRtl(value) } } }, { 'name': 'left', 'expr': /left/im, 'action': function (prop, value, context) { return { 'prop': prop.replace(this.expr, function () { return 'right' }), 'value': value } } }, { 'name': 'right', 'expr': /right/im, 'action': function (prop, value, context) { return { 'prop': prop.replace(this.expr, function () { return 'left' }), 'value': value } } }, { 'name': 'four-value syntax', 'expr': /^(margin|padding|border-(color|style|width))$/ig, 'cache': null, 'action': function (prop, value, context) { if (this.cache === null) { this.cache = { 'match': /[^\s\uFFFD]+/g } } var state = context.util.guardFunctions(value) var result = state.value.match(this.cache.match) if (result && result.length === 4 && (state.store.length > 0 || result[1] !== result[3])) { var i = 0 state.value = state.value.replace(this.cache.match, function () { return result[(4 - i++) % 4] }) } return { 'prop': prop, 'value': context.util.unguardFunctions(state) } } }, { 'name': 'border radius', 'expr': /border-radius/ig, 'cache': null, 'flip': function (value) { var parts = value.match(this.cache.match) var i if (parts) { switch (parts.length) { case 2: i = 1 if (parts[0] !== parts[1]) { value = value.replace(this.cache.match, function () { return parts[i--] }) } break case 3: // preserve leading whitespace. value = value.replace(this.cache.white, function (m) { return m + parts[1] + ' ' }) break case 4: i = 0 if (parts[0] !== parts[1] || parts[2] !== parts[3]) { value = value.replace(this.cache.match, function () { return parts[(5 - i++) % 4] }) } break } } return value }, 'action': function (prop, value, context) { if (this.cache === null) { this.cache = { 'match': /[^\s\uFFFD]+/g, 'slash': /[^\/]+/g, 'white': /(^\s*)/ } } var state = context.util.guardFunctions(value) state.value = state.value.replace(this.cache.slash, function (m) { return this.flip(m) }.bind(this)) return { 'prop': prop, 'value': context.util.unguardFunctions(state) } } }, { 'name': 'shadow', 'expr': /shadow/ig, 'cache': null, 'action': function (prop, value, context) { if (this.cache === null) { this.cache = { 'replace': /[^,]+/g } } var colorSafe = context.util.guardHexColors(value) var funcSafe = context.util.guardFunctions(colorSafe.value) funcSafe.value = funcSafe.value.replace(this.cache.replace, function (m) { return context.util.negate(m) }) colorSafe.value = context.util.unguardFunctions(funcSafe) return { 'prop': prop, 'value': context.util.unguardHexColors(colorSafe) } } }, { 'name': 'transform and perspective origin', 'expr': /(?:transform|perspective)-origin/ig, 'cache': null, 'flip': function (value, context) { if (value === '0') { value = '100%' } else if (value.match(this.cache.percent)) { value = context.util.complement(value) } else if (value.match(this.cache.length)) { value = context.util.flipLength(value) } return value }, 'action': function (prop, value, context) { if (this.cache === null) { this.cache = { 'match': context.util.regex(['calc', 'percent', 'length'], 'g'), 'percent': context.util.regex(['calc', 'percent'], 'i'), 'length': context.util.regex(['length'], 'gi'), 'xKeyword': /(left|right)/i } } if (value.match(this.cache.xKeyword)) { value = context.util.swapLeftRight(value) } else { var state = context.util.guardFunctions(value) var parts = state.value.match(this.cache.match) if (parts && parts.length > 0) { parts[0] = this.flip(parts[0], context) state.value = state.value.replace(this.cache.match, function () { return parts.shift() }) value = context.util.unguardFunctions(state) } } return { 'prop': prop, 'value': value } } }, { 'name': 'transform', 'expr': /^(?!text\-).*?transform$/ig, 'cache': null, 'flip': function (value, process, context) { var i = 0 return value.replace(this.cache.unit, function (num) { return process(++i, num) }) }, 'flipMatrix': function (value, context) { return this.flip(value, function (i, num) { if (i === 2 || i === 3 || i === 5) { return context.util.negate(num) } return num }, context) }, 'flipMatrix3D': function (value, context) { return this.flip(value, function (i, num) { if (i === 2 || i === 4 || i === 5 || i === 13) { return context.util.negate(num) } return num }, context) }, 'flipRotate3D': function (value, context) { return this.flip(value, function (i, num) { if (i === 1 || i === 4) { return context.util.negate(num) } return num }, context) }, 'action': function (prop, value, context) { if (this.cache === null) { this.cache = { 'negatable': /((translate)(x|3d)?|rotate(z|y)?)$/ig, 'unit': context.util.regex(['calc', 'number'], 'g'), 'matrix': /matrix$/i, 'matrix3D': /matrix3d$/i, 'skewXY': /skew(x|y)?$/i, 'rotate3D': /rotate3d$/i } } var state = context.util.guardFunctions(value) return { 'prop': prop, 'value': context.util.unguardFunctions(state, function (v, n) { if (n.length) { if (n.match(this.cache.matrix3D)) { v = this.flipMatrix3D(v, context) } else if (n.match(this.cache.matrix)) { v = this.flipMatrix(v, context) } else if (n.match(this.cache.rotate3D)) { v = this.flipRotate3D(v, context) } else if (n.match(this.cache.skewXY)) { v = context.util.negateAll(v) } else if (n.match(this.cache.negatable)) { v = context.util.negate(v) } } return v }.bind(this)) } } }, { 'name': 'transition', 'expr': /transition(-property)?$/i, 'action': function (prop, value, context) { return { 'prop': prop, 'value': context.util.swapLeftRight(value) } } }, { 'name': 'background', 'expr': /background(-position(-x)?|-image)?$/i, 'cache': null, 'flip': function (value, context) { var state = util.saveTokens(value, true) var parts = state.value.match(this.cache.match) if (parts && parts.length > 0) { var keywords = (state.value.match(this.cache.position) || '').length if (/* edge offsets */ parts.length >= 3 || /* keywords only */ keywords === 2) { state.value = util.swapLeftRight(state.value) } else { parts[0] = parts[0] === '0' ? '100%' : (parts[0].match(this.cache.percent) ? context.util.complement(parts[0]) : (parts[0].match(this.cache.length) ? context.util.flipLength(parts[0]) : context.util.swapLeftRight(parts[0]))) state.value = state.value.replace(this.cache.match, function () { return parts.shift() }) } } return util.restoreTokens(state) }, 'update': function (context, value, name) { if (name.match(this.cache.gradient)) { value = context.util.swapLeftRight(value) if (value.match(this.cache.angle)) { value = context.util.negate(value) } } else if (context.config.processUrls === true || context.config.processUrls.decl === true && name.match(this.cache.url)) { value = context.util.applyStringMap(value, true) } return value }, 'action': function (prop, value, context) { if (this.cache === null) { this.cache = { 'match': context.util.regex(['position', 'percent', 'length', 'calc'], 'ig'), 'percent': context.util.regex(['calc', 'percent'], 'i'), 'position': context.util.regex(['position'], 'g'), 'length': context.util.regex(['length'], 'gi'), 'gradient': /gradient$/i, 'angle': /\d+(deg|g?rad|turn)/i, 'url': /^url/i } } var colorSafe = context.util.guardHexColors(value) var funcSafe = context.util.guardFunctions(colorSafe.value) var parts = funcSafe.value.split(',') var lprop = prop.toLowerCase() if (lprop !== 'background-image') { for (var x = 0; x < parts.length; x++) { parts[x] = this.flip(parts[x], context) } } funcSafe.value = parts.join(',') colorSafe.value = context.util.unguardFunctions(funcSafe, this.update.bind(this, context)) return { 'prop': prop, 'value': context.util.unguardHexColors(colorSafe) } } }, { 'name': 'keyword', 'expr': /float|clear|text-align/i, 'action': function (prop, value, context) { return { 'prop': prop, 'value': context.util.swapLeftRight(value) } } }, { 'name': 'cursor', 'expr': /cursor/i, 'cache': null, 'update': function (context, value, name) { if (context.config.processUrls === true || context.config.processUrls.decl === true && name.match(this.cache.url)) { value = context.util.applyStringMap(value, true) } return value }, 'flip': function (value) { return value.replace(this.cache.replace, function (s, m) { return s.replace(m, m.replace(this.cache.e, '*').replace(this.cache.w, 'e').replace(this.cache.star, 'w')) }.bind(this)) }, 'action': function (prop, value, context) { if (this.cache === null) { this.cache = { 'replace': /\b(ne|nw|se|sw|nesw|nwse)-resize/ig, 'url': /^url/i, 'e': /e/i, 'w': /w/i, 'star': /\*/i } } var state = context.util.guardFunctions(value) var parts = state.value.split(',') for (var x = 0; x < parts.length; x++) { parts[x] = this.flip(parts[x]) } state.value = parts.join(',') return { 'prop': prop, 'value': context.util.unguardFunctions(state, this.update.bind(this, context)) } } } ] }