plugin.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. 'use strict'
  2. var config = require('./config.js')
  3. var util = require('./util.js')
  4. module.exports = {
  5. 'name': 'rtlcss',
  6. 'priority': 100,
  7. 'directives': {
  8. 'control': {
  9. 'ignore': {
  10. 'expect': { 'atrule': true, 'comment': true, 'decl': true, 'rule': true },
  11. 'endNode': null,
  12. 'begin': function (node, metadata, context) {
  13. // find the ending node in case of self closing directive
  14. if (!this.endNode && metadata.begin && metadata.end) {
  15. var n = node
  16. while (n && n.nodes) {
  17. n = n.nodes[n.nodes.length - 1]
  18. }
  19. this.endNode = n
  20. }
  21. var prevent = true
  22. if (node.type === 'comment' && (node.text === '!rtl:end:ignore' || node.text === 'rtl:end:ignore')) {
  23. prevent = false
  24. }
  25. return prevent
  26. },
  27. 'end': function (node, metadata, context) {
  28. // end if:
  29. // 1. block directive and the node is comment
  30. // 2. self closing directive and node is endNode
  31. if (metadata.begin !== metadata.end && node.type === 'comment' || metadata.begin && metadata.end && node === this.endNode) {
  32. // clear ending node
  33. this.endNode = null
  34. return true
  35. }
  36. return false
  37. }
  38. },
  39. 'rename': {
  40. 'expect': {'rule': true},
  41. 'begin': function (node, metadata, context) {
  42. node.selector = context.util.applyStringMap(node.selector, false)
  43. return false
  44. },
  45. 'end': function (node, context) {
  46. return true
  47. }
  48. },
  49. 'raw': {
  50. 'expect': {'self': true},
  51. 'begin': function (node, metadata, context) {
  52. var nodes = context.postcss.parse(metadata.param)
  53. node.parent.insertBefore(node, nodes)
  54. return true
  55. },
  56. 'end': function (node, context) {
  57. return true
  58. }
  59. },
  60. 'remove': {
  61. 'expect': {'atrule': true, 'rule': true, 'decl': true},
  62. 'begin': function (node, metadata, context) {
  63. var prevent = false
  64. switch (node.type) {
  65. case 'atrule':
  66. case 'rule':
  67. case 'decl':
  68. prevent = true
  69. node.remove()
  70. }
  71. return prevent
  72. },
  73. 'end': function (node, metadata, context) {
  74. return true
  75. }
  76. },
  77. 'options': {
  78. 'expect': {'self': true},
  79. 'stack': [],
  80. 'begin': function (node, metadata, context) {
  81. this.stack.push(util.extend({}, context.config))
  82. var options
  83. try {
  84. options = JSON.parse(metadata.param)
  85. } catch (e) {
  86. throw node.error('Invlaid options object', { 'details': e })
  87. }
  88. context.config = config.configure(options, context.config.plugins)
  89. context.util = util.configure(context.config)
  90. return true
  91. },
  92. 'end': function (node, metadata, context) {
  93. var config = this.stack.pop()
  94. if (config && !metadata.begin) {
  95. context.config = config
  96. context.util = util.configure(context.config)
  97. }
  98. return true
  99. }
  100. },
  101. 'config': {
  102. 'expect': {'self': true},
  103. 'expr': {
  104. 'fn': /function([^\(]*)\(([^\(\)]*?)\)[^\{]*\{([^]*)\}/ig,
  105. 'rx': /\/([^\/]*)\/(.*)/ig
  106. },
  107. 'stack': [],
  108. 'begin': function (node, metadata, context) {
  109. this.stack.push(util.extend({}, context.config))
  110. var configuration
  111. try {
  112. configuration = eval('(' + metadata.param + ')') // eslint-disable-line no-eval
  113. } catch (e) {
  114. throw node.error('Invlaid config object', { 'details': e })
  115. }
  116. context.config = config.configure(configuration.options, configuration.plugins)
  117. context.util = util.configure(context.config)
  118. return true
  119. },
  120. 'end': function (node, metadata, context) {
  121. var config = this.stack.pop()
  122. if (config && !metadata.begin) {
  123. context.config = config
  124. context.util = util.configure(context.config)
  125. }
  126. return true
  127. }
  128. }
  129. },
  130. 'value': [
  131. {
  132. 'name': 'ignore',
  133. 'action': function (decl, expr, context) {
  134. return true
  135. }
  136. },
  137. {
  138. 'name': 'prepend',
  139. 'action': function (decl, expr, context) {
  140. var prefix = ''
  141. decl.raws.value.raw.replace(expr, function (m, v) {
  142. prefix += v
  143. })
  144. decl.value = decl.raws.value.raw = prefix + decl.raws.value.raw
  145. return true
  146. }
  147. },
  148. {
  149. 'name': 'append',
  150. 'action': function (decl, expr, context) {
  151. decl.value = decl.raws.value.raw = decl.raws.value.raw.replace(expr, function (match, value) {
  152. return match + value
  153. })
  154. return true
  155. }
  156. },
  157. {
  158. 'name': 'insert',
  159. 'action': function (decl, expr, context) {
  160. decl.value = decl.raws.value.raw = decl.raws.value.raw.replace(expr, function (match, value) {
  161. return value + match
  162. })
  163. return true
  164. }
  165. },
  166. {
  167. 'name': '',
  168. 'action': function (decl, expr, context) {
  169. decl.raws.value.raw.replace(expr, function (match, value) {
  170. decl.value = decl.raws.value.raw = value + match
  171. })
  172. return true
  173. }
  174. }
  175. ]
  176. },
  177. 'processors': [
  178. {
  179. 'name': 'direction',
  180. 'expr': /direction/im,
  181. 'action': function (prop, value, context) {
  182. return { 'prop': prop, 'value': context.util.swapLtrRtl(value) }
  183. }
  184. },
  185. {
  186. 'name': 'left',
  187. 'expr': /left/im,
  188. 'action': function (prop, value, context) {
  189. return { 'prop': prop.replace(this.expr, function () { return 'right' }), 'value': value }
  190. }
  191. },
  192. {
  193. 'name': 'right',
  194. 'expr': /right/im,
  195. 'action': function (prop, value, context) {
  196. return { 'prop': prop.replace(this.expr, function () { return 'left' }), 'value': value }
  197. }
  198. },
  199. {
  200. 'name': 'four-value syntax',
  201. 'expr': /^(margin|padding|border-(color|style|width))$/ig,
  202. 'cache': null,
  203. 'action': function (prop, value, context) {
  204. if (this.cache === null) {
  205. this.cache = {
  206. 'match': /[^\s\uFFFD]+/g
  207. }
  208. }
  209. var state = context.util.guardFunctions(value)
  210. var result = state.value.match(this.cache.match)
  211. if (result && result.length === 4 && (state.store.length > 0 || result[1] !== result[3])) {
  212. var i = 0
  213. state.value = state.value.replace(this.cache.match, function () {
  214. return result[(4 - i++) % 4]
  215. })
  216. }
  217. return { 'prop': prop, 'value': context.util.unguardFunctions(state) }
  218. }
  219. },
  220. {
  221. 'name': 'border radius',
  222. 'expr': /border-radius/ig,
  223. 'cache': null,
  224. 'flip': function (value) {
  225. var parts = value.match(this.cache.match)
  226. var i
  227. if (parts) {
  228. switch (parts.length) {
  229. case 2:
  230. i = 1
  231. if (parts[0] !== parts[1]) {
  232. value = value.replace(this.cache.match, function () {
  233. return parts[i--]
  234. })
  235. }
  236. break
  237. case 3:
  238. // preserve leading whitespace.
  239. value = value.replace(this.cache.white, function (m) {
  240. return m + parts[1] + ' '
  241. })
  242. break
  243. case 4:
  244. i = 0
  245. if (parts[0] !== parts[1] || parts[2] !== parts[3]) {
  246. value = value.replace(this.cache.match, function () {
  247. return parts[(5 - i++) % 4]
  248. })
  249. }
  250. break
  251. }
  252. }
  253. return value
  254. },
  255. 'action': function (prop, value, context) {
  256. if (this.cache === null) {
  257. this.cache = {
  258. 'match': /[^\s\uFFFD]+/g,
  259. 'slash': /[^\/]+/g,
  260. 'white': /(^\s*)/
  261. }
  262. }
  263. var state = context.util.guardFunctions(value)
  264. state.value = state.value.replace(this.cache.slash, function (m) {
  265. return this.flip(m)
  266. }.bind(this))
  267. return { 'prop': prop, 'value': context.util.unguardFunctions(state) }
  268. }
  269. },
  270. {
  271. 'name': 'shadow',
  272. 'expr': /shadow/ig,
  273. 'cache': null,
  274. 'action': function (prop, value, context) {
  275. if (this.cache === null) {
  276. this.cache = {
  277. 'replace': /[^,]+/g
  278. }
  279. }
  280. var colorSafe = context.util.guardHexColors(value)
  281. var funcSafe = context.util.guardFunctions(colorSafe.value)
  282. funcSafe.value = funcSafe.value.replace(this.cache.replace, function (m) { return context.util.negate(m) })
  283. colorSafe.value = context.util.unguardFunctions(funcSafe)
  284. return { 'prop': prop, 'value': context.util.unguardHexColors(colorSafe) }
  285. }
  286. },
  287. {
  288. 'name': 'transform origin',
  289. 'expr': /transform-origin/ig,
  290. 'cache': null,
  291. 'flip': function (value, context) {
  292. if (value === '0') {
  293. value = '100%'
  294. } else if (value.match(this.cache.percent)) {
  295. value = context.util.complement(value)
  296. }
  297. return value
  298. },
  299. 'action': function (prop, value, context) {
  300. if (this.cache === null) {
  301. this.cache = {
  302. 'match': context.util.regex(['calc', 'percent', 'length'], 'g'),
  303. 'percent': context.util.regex(['calc', 'percent'], 'i'),
  304. 'xKeyword': /(left|right)/i
  305. }
  306. }
  307. if (value.match(this.cache.xKeyword)) {
  308. value = context.util.swapLeftRight(value)
  309. } else {
  310. var state = context.util.guardFunctions(value)
  311. var parts = state.value.match(this.cache.match)
  312. if (parts && parts.length > 0) {
  313. parts[0] = this.flip(parts[0], context)
  314. state.value = state.value.replace(this.cache.match, function () { return parts.shift() })
  315. value = context.util.unguardFunctions(state)
  316. }
  317. }
  318. return { 'prop': prop, 'value': value }
  319. }
  320. },
  321. {
  322. 'name': 'transform',
  323. 'expr': /^(?!text\-).*?transform$/ig,
  324. 'cache': null,
  325. 'flip': function (value, process, context) {
  326. var i = 0
  327. return value.replace(this.cache.unit, function (num) {
  328. return process(++i, num)
  329. })
  330. },
  331. 'flipMatrix': function (value, context) {
  332. return this.flip(value, function (i, num) {
  333. if (i === 2 || i === 3 || i === 5) {
  334. return context.util.negate(num)
  335. }
  336. return num
  337. }, context)
  338. },
  339. 'flipMatrix3D': function (value, context) {
  340. return this.flip(value, function (i, num) {
  341. if (i === 2 || i === 4 || i === 5 || i === 13) {
  342. return context.util.negate(num)
  343. }
  344. return num
  345. }, context)
  346. },
  347. 'flipRotate3D': function (value, context) {
  348. return this.flip(value, function (i, num) {
  349. if (i === 2 || i === 4) {
  350. return context.util.negate(num)
  351. }
  352. return num
  353. }, context)
  354. },
  355. 'action': function (prop, value, context) {
  356. if (this.cache === null) {
  357. this.cache = {
  358. 'negatable': /((translate)(x|3d)?|rotate(z)?)$/ig,
  359. 'unit': context.util.regex(['calc', 'number'], 'g'),
  360. 'matrix': /matrix$/i,
  361. 'matrix3D': /matrix3d$/i,
  362. 'skewXY': /skew(x|y)?$/i,
  363. 'rotate3D': /rotate3d$/i
  364. }
  365. }
  366. var state = context.util.guardFunctions(value)
  367. return {
  368. 'prop': prop,
  369. 'value': context.util.unguardFunctions(state, function (v, n) {
  370. if (n.length) {
  371. if (n.match(this.cache.matrix3D)) {
  372. v = this.flipMatrix3D(v, context)
  373. } else if (n.match(this.cache.matrix)) {
  374. v = this.flipMatrix(v, context)
  375. } else if (n.match(this.cache.rotate3D)) {
  376. v = this.flipRotate3D(v, context)
  377. } else if (n.match(this.cache.skewXY)) {
  378. v = context.util.negateAll(v)
  379. } else if (n.match(this.cache.negatable)) {
  380. v = context.util.negate(v)
  381. }
  382. }
  383. return v
  384. }.bind(this))
  385. }
  386. }
  387. },
  388. {
  389. 'name': 'transition',
  390. 'expr': /transition(-property)?$/i,
  391. 'action': function (prop, value, context) {
  392. return { 'prop': prop, 'value': context.util.swapLeftRight(value) }
  393. }
  394. },
  395. {
  396. 'name': 'background',
  397. 'expr': /background(-position(-x)?|-image)?$/i,
  398. 'cache': null,
  399. 'flip': function (value, context, isPosition) {
  400. var state = util.saveTokens(value, true)
  401. var parts = state.value.match(this.cache.match)
  402. if (parts && parts.length > 0) {
  403. if (isPosition && parts.length >= 3) {
  404. state.value = util.swapLeftRight(state.value)
  405. } else {
  406. parts[0] = parts[0] === '0'
  407. ? '100%'
  408. : (parts[0].match(this.cache.percent)
  409. ? context.util.complement(parts[0])
  410. : context.util.swapLeftRight(parts[0]))
  411. state.value = state.value.replace(this.cache.match, function () { return parts.shift() })
  412. }
  413. }
  414. return util.restoreTokens(state)
  415. },
  416. 'update': function (context, value, name) {
  417. if (name.match(this.cache.gradient)) {
  418. value = context.util.swapLeftRight(value)
  419. if (value.match(this.cache.angle)) {
  420. value = context.util.negate(value)
  421. }
  422. } else if (context.config.processUrls === true || context.config.processUrls.decl === true && name.match(this.cache.url)) {
  423. value = context.util.applyStringMap(value, true)
  424. }
  425. return value
  426. },
  427. 'action': function (prop, value, context) {
  428. if (this.cache === null) {
  429. this.cache = {
  430. 'match': context.util.regex(['position', 'percent', 'length', 'calc'], 'ig'),
  431. 'percent': context.util.regex(['calc', 'percent'], 'i'),
  432. 'gradient': /gradient$/i,
  433. 'angle': /\d+(deg|g?rad|turn)/i,
  434. 'url': /^url/i
  435. }
  436. }
  437. var colorSafe = context.util.guardHexColors(value)
  438. var funcSafe = context.util.guardFunctions(colorSafe.value)
  439. var parts = funcSafe.value.split(',')
  440. var lprop = prop.toLowerCase()
  441. if (lprop !== 'background-image') {
  442. var isPosition = lprop === 'background-position'
  443. for (var x = 0; x < parts.length; x++) {
  444. parts[x] = this.flip(parts[x], context, isPosition)
  445. }
  446. }
  447. funcSafe.value = parts.join(',')
  448. colorSafe.value = context.util.unguardFunctions(funcSafe, this.update.bind(this, context))
  449. return {
  450. 'prop': prop,
  451. 'value': context.util.unguardHexColors(colorSafe)
  452. }
  453. }
  454. },
  455. {
  456. 'name': 'keyword',
  457. 'expr': /float|clear|text-align/i,
  458. 'action': function (prop, value, context) {
  459. return { 'prop': prop, 'value': context.util.swapLeftRight(value) }
  460. }
  461. },
  462. {
  463. 'name': 'cursor',
  464. 'expr': /cursor/i,
  465. 'cache': null,
  466. 'update': function (context, value, name) {
  467. if (context.config.processUrls === true || context.config.processUrls.decl === true && name.match(this.cache.url)) {
  468. value = context.util.applyStringMap(value, true)
  469. }
  470. return value
  471. },
  472. 'flip': function (value) {
  473. return value.replace(this.cache.replace, function (s, m) {
  474. return s.replace(m, m.replace(this.cache.e, '*').replace(this.cache.w, 'e').replace(this.cache.star, 'w'))
  475. }.bind(this))
  476. },
  477. 'action': function (prop, value, context) {
  478. if (this.cache === null) {
  479. this.cache = {
  480. 'replace': /\b(ne|nw|se|sw|nesw|nwse)-resize/ig,
  481. 'url': /^url/i,
  482. 'e': /e/i,
  483. 'w': /w/i,
  484. 'star': /\*/i
  485. }
  486. }
  487. var state = context.util.guardFunctions(value)
  488. var parts = state.value.split(',')
  489. for (var x = 0; x < parts.length; x++) {
  490. parts[x] = this.flip(parts[x])
  491. }
  492. state.value = parts.join(',')
  493. return {
  494. 'prop': prop,
  495. 'value': context.util.unguardFunctions(state, this.update.bind(this, context))
  496. }
  497. }
  498. }
  499. ]
  500. }