wishthis/node_modules/rtlcss/lib/plugin.js

544 lines
17 KiB
JavaScript
Raw Normal View History

2022-01-21 08:28:41 +00:00
'use strict'
2022-04-07 07:06:43 +00:00
const config = require('./config.js')
const util = require('./util.js')
2022-01-21 08:28:41 +00:00
module.exports = {
2022-04-07 07:06:43 +00:00
name: 'rtlcss',
priority: 100,
directives: {
control: {
ignore: {
expect: { atrule: true, comment: true, decl: true, rule: true },
endNode: null,
begin (node, metadata, context) {
2022-01-21 08:28:41 +00:00
// find the ending node in case of self closing directive
if (!this.endNode && metadata.begin && metadata.end) {
2022-04-07 07:06:43 +00:00
let n = node
2022-01-21 08:28:41 +00:00
while (n && n.nodes) {
n = n.nodes[n.nodes.length - 1]
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
this.endNode = n
}
2022-04-07 07:06:43 +00:00
let prevent = true
2022-01-21 08:28:41 +00:00
if (node.type === 'comment' && node.text.match(/^\s*!?\s*rtl:end:ignore/)) {
prevent = false
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
return prevent
},
2022-04-07 07:06:43 +00:00
end (node, metadata, context) {
2022-01-21 08:28:41 +00:00
// end if:
// 1. block directive and the node is comment
// 2. self closing directive and node is endNode
2022-04-07 07:06:43 +00:00
if ((metadata.begin !== metadata.end && node.type === 'comment') || (metadata.begin && metadata.end && node === this.endNode)) {
2022-01-21 08:28:41 +00:00
// clear ending node
this.endNode = null
return true
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
return false
}
},
2022-04-07 07:06:43 +00:00
rename: {
expect: { rule: true },
begin (node, metadata, context) {
2022-01-21 08:28:41 +00:00
node.selector = context.util.applyStringMap(node.selector, false)
return false
},
2022-04-07 07:06:43 +00:00
end (node, context) {
2022-01-21 08:28:41 +00:00
return true
}
},
2022-04-07 07:06:43 +00:00
raw: {
expect: { self: true },
begin (node, metadata, context) {
const nodes = context.postcss.parse(metadata.param, { from: node.source.input.from })
nodes.walk((node) => {
node[context.symbol] = true
})
2022-01-21 08:28:41 +00:00
node.parent.insertBefore(node, nodes)
return true
},
2022-04-07 07:06:43 +00:00
end (node, context) {
2022-01-21 08:28:41 +00:00
return true
}
},
2022-04-07 07:06:43 +00:00
remove: {
expect: { atrule: true, rule: true, decl: true },
begin (node, metadata, context) {
let prevent = false
2022-01-21 08:28:41 +00:00
switch (node.type) {
case 'atrule':
case 'rule':
case 'decl':
prevent = true
node.remove()
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
return prevent
},
2022-04-07 07:06:43 +00:00
end (node, metadata, context) {
2022-01-21 08:28:41 +00:00
return true
}
},
2022-04-07 07:06:43 +00:00
options: {
expect: { self: true },
stack: [],
begin (node, metadata, context) {
2022-01-21 08:28:41 +00:00
this.stack.push(util.extend({}, context.config))
2022-04-07 07:06:43 +00:00
let options
2022-01-21 08:28:41 +00:00
try {
options = JSON.parse(metadata.param)
} catch (e) {
2022-04-07 07:06:43 +00:00
throw node.error('Invalid options object', { details: e })
2022-01-21 08:28:41 +00:00
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
context.config = config.configure(options, context.config.plugins)
context.util = util.configure(context.config)
return true
},
2022-04-07 07:06:43 +00:00
end (node, metadata, context) {
const config = this.stack.pop()
2022-01-21 08:28:41 +00:00
if (config && !metadata.begin) {
context.config = config
context.util = util.configure(context.config)
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
return true
}
},
2022-04-07 07:06:43 +00:00
config: {
expect: { self: true },
stack: [],
begin (node, metadata, context) {
2022-01-21 08:28:41 +00:00
this.stack.push(util.extend({}, context.config))
2022-04-07 07:06:43 +00:00
let configuration
2022-01-21 08:28:41 +00:00
try {
2022-04-07 07:06:43 +00:00
configuration = eval(`(${metadata.param})`) // eslint-disable-line no-eval
2022-01-21 08:28:41 +00:00
} catch (e) {
2022-04-07 07:06:43 +00:00
throw node.error('Invalid config object', { details: e })
2022-01-21 08:28:41 +00:00
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
context.config = config.configure(configuration.options, configuration.plugins)
context.util = util.configure(context.config)
return true
},
2022-04-07 07:06:43 +00:00
end (node, metadata, context) {
const config = this.stack.pop()
2022-01-21 08:28:41 +00:00
if (config && !metadata.begin) {
context.config = config
context.util = util.configure(context.config)
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
return true
}
}
},
2022-04-07 07:06:43 +00:00
value: [
2022-01-21 08:28:41 +00:00
{
2022-04-07 07:06:43 +00:00
name: 'ignore',
action (decl, expr, context) {
2022-01-21 08:28:41 +00:00
return true
}
},
{
2022-04-07 07:06:43 +00:00
name: 'prepend',
action (decl, expr, context) {
let prefix = ''
const hasRawValue = decl.raws.value && decl.raws.value.raw
const raw = `${decl.raws.between.substr(1).trim()}${hasRawValue ? decl.raws.value.raw : decl.value}${decl.important ? decl.raws.important.substr(9).trim() : ''}`
raw.replace(expr, (m, v) => {
2022-01-21 08:28:41 +00:00
prefix += v
})
2022-04-07 07:06:43 +00:00
decl.value = hasRawValue
? (decl.raws.value.raw = prefix + decl.raws.value.raw)
: prefix + decl.value
2022-01-21 08:28:41 +00:00
return true
}
},
{
2022-04-07 07:06:43 +00:00
name: 'append',
action (decl, expr, context) {
let suffix = ''
const hasRawValue = decl.raws.value && decl.raws.value.raw
const raw = `${decl.raws.between.substr(1).trim()}${hasRawValue ? decl.raws.value.raw : decl.value}${decl.important ? decl.raws.important.substr(9).trim() : ''}`
raw.replace(expr, (m, v) => {
suffix = v + suffix
2022-01-21 08:28:41 +00:00
})
2022-04-07 07:06:43 +00:00
decl.value = hasRawValue ? (decl.raws.value.raw += suffix) : decl.value + suffix
2022-01-21 08:28:41 +00:00
return true
}
},
{
2022-04-07 07:06:43 +00:00
name: 'insert',
action (decl, expr, context) {
const hasRawValue = decl.raws.value && decl.raws.value.raw
const raw = `${decl.raws.between.substr(1).trim()}${hasRawValue ? decl.raws.value.raw : decl.value}${decl.important ? decl.raws.important.substr(9).trim() : ''}`
const result = raw.replace(expr, (match, value) => hasRawValue ? value + match : value)
decl.value = hasRawValue ? (decl.raws.value.raw = result) : result
2022-01-21 08:28:41 +00:00
return true
}
},
{
2022-04-07 07:06:43 +00:00
name: '',
action (decl, expr, context) {
const hasRawValue = decl.raws.value && decl.raws.value.raw
const raw = `${decl.raws.between.substr(1).trim()}${hasRawValue ? decl.raws.value.raw : ''}${decl.important ? decl.raws.important.substr(9).trim() : ''}`
raw.replace(expr, (match, value) => {
decl.value = hasRawValue
? (decl.raws.value.raw = value + match)
: value
2022-01-21 08:28:41 +00:00
})
return true
}
}
]
},
2022-04-07 07:06:43 +00:00
processors: [
2022-01-21 08:28:41 +00:00
{
2022-04-07 07:06:43 +00:00
name: 'variable',
expr: /^--/im,
action (prop, value) {
return { prop, value }
2022-01-21 08:28:41 +00:00
}
},
{
2022-04-07 07:06:43 +00:00
name: 'direction',
expr: /direction/im,
action (prop, value, context) {
return { prop, value: context.util.swapLtrRtl(value) }
2022-01-21 08:28:41 +00:00
}
},
{
2022-04-07 07:06:43 +00:00
name: 'left',
expr: /left/im,
action (prop, value, context) {
return { prop: prop.replace(this.expr, () => 'right'), value }
2022-01-21 08:28:41 +00:00
}
},
{
2022-04-07 07:06:43 +00:00
name: 'right',
expr: /right/im,
action (prop, value, context) {
return { prop: prop.replace(this.expr, () => 'left'), value }
2022-01-21 08:28:41 +00:00
}
},
{
2022-04-07 07:06:43 +00:00
name: 'four-value syntax',
expr: /^(margin|padding|border-(color|style|width))$/ig,
cache: null,
action (prop, value, context) {
2022-01-21 08:28:41 +00:00
if (this.cache === null) {
this.cache = {
2022-04-07 07:06:43 +00:00
match: /[^\s\uFFFD]+/g
2022-01-21 08:28:41 +00:00
}
}
2022-04-07 07:06:43 +00:00
const state = context.util.guardFunctions(value)
const result = state.value.match(this.cache.match)
2022-01-21 08:28:41 +00:00
if (result && result.length === 4 && (state.store.length > 0 || result[1] !== result[3])) {
2022-04-07 07:06:43 +00:00
let i = 0
state.value = state.value.replace(this.cache.match, () => result[(4 - i++) % 4])
2022-01-21 08:28:41 +00:00
}
2022-04-07 07:06:43 +00:00
return { prop, value: context.util.unguardFunctions(state) }
2022-01-21 08:28:41 +00:00
}
},
{
2022-04-07 07:06:43 +00:00
name: 'border radius',
expr: /border-radius/ig,
cache: null,
flip (value) {
const parts = value.match(this.cache.match)
let i
2022-01-21 08:28:41 +00:00
if (parts) {
switch (parts.length) {
case 2:
i = 1
if (parts[0] !== parts[1]) {
2022-04-07 07:06:43 +00:00
value = value.replace(this.cache.match, () => parts[i--])
2022-01-21 08:28:41 +00:00
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
break
case 3:
// preserve leading whitespace.
2022-04-07 07:06:43 +00:00
value = value.replace(this.cache.white, (m) => `${m + parts[1]} `)
2022-01-21 08:28:41 +00:00
break
case 4:
i = 0
if (parts[0] !== parts[1] || parts[2] !== parts[3]) {
2022-04-07 07:06:43 +00:00
value = value.replace(this.cache.match, () => parts[(5 - i++) % 4])
2022-01-21 08:28:41 +00:00
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
break
}
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
return value
},
2022-04-07 07:06:43 +00:00
action (prop, value, context) {
2022-01-21 08:28:41 +00:00
if (this.cache === null) {
this.cache = {
2022-04-07 07:06:43 +00:00
match: /[^\s\uFFFD]+/g,
slash: /[^/]+/g,
white: /(^\s*)/
2022-01-21 08:28:41 +00:00
}
}
2022-04-07 07:06:43 +00:00
const state = context.util.guardFunctions(value)
state.value = state.value.replace(this.cache.slash, (m) => this.flip(m))
return { prop, value: context.util.unguardFunctions(state) }
2022-01-21 08:28:41 +00:00
}
},
{
2022-04-07 07:06:43 +00:00
name: 'shadow',
expr: /shadow/ig,
cache: null,
action (prop, value, context) {
2022-01-21 08:28:41 +00:00
if (this.cache === null) {
this.cache = {
2022-04-07 07:06:43 +00:00
replace: /[^,]+/g
2022-01-21 08:28:41 +00:00
}
}
2022-04-07 07:06:43 +00:00
const colorSafe = context.util.guardHexColors(value)
const funcSafe = context.util.guardFunctions(colorSafe.value)
funcSafe.value = funcSafe.value.replace(this.cache.replace, (m) => context.util.negate(m))
2022-01-21 08:28:41 +00:00
colorSafe.value = context.util.unguardFunctions(funcSafe)
2022-04-07 07:06:43 +00:00
return { prop, value: context.util.unguardHexColors(colorSafe) }
2022-01-21 08:28:41 +00:00
}
},
{
2022-04-07 07:06:43 +00:00
name: 'transform and perspective origin',
expr: /(?:transform|perspective)-origin/ig,
cache: null,
flip (value, context) {
2022-01-21 08:28:41 +00:00
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)
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
return value
},
2022-04-07 07:06:43 +00:00
action (prop, value, context) {
2022-01-21 08:28:41 +00:00
if (this.cache === null) {
this.cache = {
2022-04-07 07:06:43 +00:00
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
2022-01-21 08:28:41 +00:00
}
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
if (value.match(this.cache.xKeyword)) {
value = context.util.swapLeftRight(value)
} else {
2022-04-07 07:06:43 +00:00
const state = context.util.guardFunctions(value)
const parts = state.value.match(this.cache.match)
2022-01-21 08:28:41 +00:00
if (parts && parts.length > 0) {
parts[0] = this.flip(parts[0], context)
2022-04-07 07:06:43 +00:00
state.value = state.value.replace(this.cache.match, () => parts.shift())
2022-01-21 08:28:41 +00:00
value = context.util.unguardFunctions(state)
}
}
2022-04-07 07:06:43 +00:00
return { prop, value }
2022-01-21 08:28:41 +00:00
}
},
{
2022-04-07 07:06:43 +00:00
name: 'transform',
expr: /^(?!text-).*?transform$/ig,
cache: null,
flip (value, process, context) {
let i = 0
return value.replace(this.cache.unit, (num) => process(++i, num))
2022-01-21 08:28:41 +00:00
},
2022-04-07 07:06:43 +00:00
flipMatrix (value, context) {
return this.flip(value, (i, num) => {
2022-01-21 08:28:41 +00:00
if (i === 2 || i === 3 || i === 5) {
return context.util.negate(num)
}
return num
}, context)
},
2022-04-07 07:06:43 +00:00
flipMatrix3D (value, context) {
return this.flip(value, (i, num) => {
2022-01-21 08:28:41 +00:00
if (i === 2 || i === 4 || i === 5 || i === 13) {
return context.util.negate(num)
}
return num
}, context)
},
2022-04-07 07:06:43 +00:00
flipRotate3D (value, context) {
return this.flip(value, (i, num) => {
2022-01-21 08:28:41 +00:00
if (i === 1 || i === 4) {
return context.util.negate(num)
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
return num
}, context)
},
2022-04-07 07:06:43 +00:00
action (prop, value, context) {
2022-01-21 08:28:41 +00:00
if (this.cache === null) {
this.cache = {
2022-04-07 07:06:43 +00:00
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
2022-01-21 08:28:41 +00:00
}
}
2022-04-07 07:06:43 +00:00
const state = context.util.guardFunctions(value)
2022-01-21 08:28:41 +00:00
return {
2022-04-07 07:06:43 +00:00
prop,
value: context.util.unguardFunctions(state, (v, n) => {
2022-01-21 08:28:41 +00:00
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
2022-04-07 07:06:43 +00:00
})
2022-01-21 08:28:41 +00:00
}
}
},
{
2022-04-07 07:06:43 +00:00
name: 'transition',
expr: /transition(-property)?$/i,
action (prop, value, context) {
return { prop, value: context.util.swapLeftRight(value) }
2022-01-21 08:28:41 +00:00
}
},
{
2022-04-07 07:06:43 +00:00
name: 'background',
expr: /(background|object)(-position(-x)?|-image)?$/i,
cache: null,
flip (value, context) {
const state = util.saveTokens(value, true)
const parts = state.value.match(this.cache.match)
2022-01-21 08:28:41 +00:00
if (parts && parts.length > 0) {
2022-04-07 07:06:43 +00:00
const keywords = (state.value.match(this.cache.position) || '').length
2022-01-21 08:28:41 +00:00
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)
2022-04-07 07:06:43 +00:00
? 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, () => parts.shift())
2022-01-21 08:28:41 +00:00
}
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
return util.restoreTokens(state)
},
2022-04-07 07:06:43 +00:00
update (context, value, name) {
2022-01-21 08:28:41 +00:00
if (name.match(this.cache.gradient)) {
value = context.util.swapLeftRight(value)
if (value.match(this.cache.angle)) {
value = context.util.negate(value)
}
2022-04-07 07:06:43 +00:00
} else if ((context.config.processUrls === true || context.config.processUrls.decl === true) && name.match(this.cache.url)) {
2022-01-21 08:28:41 +00:00
value = context.util.applyStringMap(value, true)
}
return value
},
2022-04-07 07:06:43 +00:00
action (prop, value, context) {
2022-01-21 08:28:41 +00:00
if (this.cache === null) {
this.cache = {
2022-04-07 07:06:43 +00:00
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
2022-01-21 08:28:41 +00:00
}
}
2022-04-07 07:06:43 +00:00
const colorSafe = context.util.guardHexColors(value)
const funcSafe = context.util.guardFunctions(colorSafe.value)
const parts = funcSafe.value.split(',')
const lprop = prop.toLowerCase()
2022-01-21 08:28:41 +00:00
if (lprop !== 'background-image') {
2022-04-07 07:06:43 +00:00
for (let x = 0; x < parts.length; x++) {
2022-01-21 08:28:41 +00:00
parts[x] = this.flip(parts[x], context)
}
}
2022-04-07 07:06:43 +00:00
2022-01-21 08:28:41 +00:00
funcSafe.value = parts.join(',')
colorSafe.value = context.util.unguardFunctions(funcSafe, this.update.bind(this, context))
return {
2022-04-07 07:06:43 +00:00
prop,
value: context.util.unguardHexColors(colorSafe)
2022-01-21 08:28:41 +00:00
}
}
},
{
2022-04-07 07:06:43 +00:00
name: 'keyword',
expr: /float|clear|text-align/i,
action (prop, value, context) {
return { prop, value: context.util.swapLeftRight(value) }
2022-01-21 08:28:41 +00:00
}
},
{
2022-04-07 07:06:43 +00:00
name: 'cursor',
expr: /cursor/i,
cache: null,
update (context, value, name) {
if ((context.config.processUrls === true || context.config.processUrls.decl === true) && name.match(this.cache.url)) {
2022-01-21 08:28:41 +00:00
value = context.util.applyStringMap(value, true)
}
return value
},
2022-04-07 07:06:43 +00:00
flip (value) {
return value.replace(this.cache.replace, (s, m) => {
return s.replace(m, m.replace(this.cache.e, '*')
.replace(this.cache.w, 'e')
.replace(this.cache.star, 'w'))
})
2022-01-21 08:28:41 +00:00
},
2022-04-07 07:06:43 +00:00
action (prop, value, context) {
2022-01-21 08:28:41 +00:00
if (this.cache === null) {
this.cache = {
2022-04-07 07:06:43 +00:00
replace: /\b(ne|nw|se|sw|nesw|nwse)-resize/ig,
url: /^url/i,
e: /e/i,
w: /w/i,
star: /\*/i
2022-01-21 08:28:41 +00:00
}
}
2022-04-07 07:06:43 +00:00
const state = context.util.guardFunctions(value)
state.value = state.value.split(',')
.map((part) => this.flip(part))
.join(',')
2022-01-21 08:28:41 +00:00
return {
2022-04-07 07:06:43 +00:00
prop,
value: context.util.unguardFunctions(state, this.update.bind(this, context))
2022-01-21 08:28:41 +00:00
}
}
}
]
}