diff --git a/lesscpy/lessc/scope.py b/lesscpy/lessc/scope.py index 6172a4b..928a242 100644 --- a/lesscpy/lessc/scope.py +++ b/lesscpy/lessc/scope.py @@ -63,6 +63,14 @@ class Scope(list): self[-1]['__blocks__'].append(block) self[-1]['__names__'].append(block.raw()) + def remove_block(self, block, index="-1"): + """Remove block element from scope + Args: + block (Block): Block object + """ + self[index]["__blocks__"].remove(block) + self[index]["__names__"].remove(block.raw()) + def add_mixin(self, mixin): """Add mixin to scope Args: diff --git a/lesscpy/plib/block.py b/lesscpy/plib/block.py index 99f2284..bcbe90d 100644 --- a/lesscpy/plib/block.py +++ b/lesscpy/plib/block.py @@ -10,6 +10,7 @@ import re from .node import Node from lesscpy.lessc import utility +from lesscpy.plib.identifier import Identifier class Block(Node): @@ -41,8 +42,77 @@ class Block(Node): if not inner: inner = [] inner = list(utility.flatten([p.parse(scope) for p in inner if p])) - self.parsed = [p for p in inner if p and not isinstance(p, Block)] - self.inner = [p for p in inner if p and isinstance(p, Block)] + self.parsed = [] + self.inner = [] + if not hasattr(self, "inner_media_queries"): + self.inner_media_queries = [] + for p in inner: + if p is not None: + if isinstance(p, Block): + if p.name.tokens[0] == '@media' and not self.name.raw().startswith("@media"): + # Inner block @media ... { ... } is a nested media + # query. But double-nested media queries have to be + # removed and marked as well. While parsing ".foo", + # both nested "@media print" and double-nested + # "@media all" will be handled as we have to + # re-arrange the scope and block layout quite a bit: + # + # .foo { + # @media print { + # color: blue; + # @media screen { font-size: 12em; } + # } + # } + # + # Expected result: + # + # @media print { + # .foo { color: blue; } + # } + # @media print and screen { + # .foo { font-size: 12 em; } + # } + append_list = [] + reparse_p = False + for child in p.tokens[1]: + if isinstance(child, Block) and child.name.raw().startswith("@media"): + # Double-nested media query found. We remove it from 'p' and add + # it to this block with a new 'name'. + part_a = p.name.tokens[2:][0][0][0] + part_b = child.name.tokens[2:][0][0] + new_ident_tokens = ['@media', ' ', [part_a, (' ', 'and', ' '), part_b]] + # Remove child from the nested media query, it will be re-added to + # the parent with 'merged' media query (see above example). + p.tokens[1].remove(child) + reparse_p = True + # Parse child again with new @media $BLA {} part + child.tokens[0] = Identifier(new_ident_tokens) + child.parsed = None + child = child.parse(scope) + append_list.append(child) + if reparse_p: + p.parsed = None + p = p.parse(scope) + append_list.insert(0, p) # This media query should occur before it's children + for media_query in append_list: + self.inner_media_queries.append(media_query) + # NOTE(saschpe): The code is not recursive but we hope that people + # wont use triple-nested media queries. + else: + self.inner.append(p) + else: + self.parsed.append(p) + if len(self.inner_media_queries) > 0: + # Nested media queries, we have to remove self from scope and + # push all nested @media ... {} blocks. + scope.remove_block(self, index=-2) + for mb in self.inner_media_queries: + # New inner block with current name and media block contents + cb = Block([self.tokens[0], mb.tokens[1]]).parse(scope) + # Replace inner block contents with new block + new_mb = Block([mb.tokens[0], [cb]]).parse(scope) + self.inner.append(new_mb) + scope.add_block(new_mb) scope.real.pop() scope.pop() return self diff --git a/lesscpy/plib/identifier.py b/lesscpy/plib/identifier.py index a012665..4205ce4 100644 --- a/lesscpy/plib/identifier.py +++ b/lesscpy/plib/identifier.py @@ -115,6 +115,8 @@ class Identifier(Node): parsed.extend(parent) else: parsed.append(n) + elif name[0] == "@media": + parsed.extend(name) else: parsed.extend(parent) if parent[-1] != ' ': diff --git a/lesscpy/test/css/media.css b/lesscpy/test/css/media.css index efbb9bc..7691c87 100644 --- a/lesscpy/test/css/media.css +++ b/lesscpy/test/css/media.css @@ -52,3 +52,31 @@ @media only screen and (min--moz-device-pixel-ratio:2.5),only screen and (-o-min-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2),only screen and (min-resolution:192dpi),only screen and (min-resolution:2dppx) { background-size: 10px; } +@media (width:768px) { + .lead { + font-size: 21px; + } +} +@media (width:400px) { + .one { + font-size: 1.2em; + } +} +@media (width:400px) and print and (color) { + .one { + color: blue; + } +} +.two { + width: 100px; +} +@media (width:400px) { + .two { + font-size: 1.2em; + } +} +@media print and (color) { + .two { + color: blue; + } +} diff --git a/lesscpy/test/css/media.min.css b/lesscpy/test/css/media.min.css index bd06df8..4f01cbb 100644 --- a/lesscpy/test/css/media.min.css +++ b/lesscpy/test/css/media.min.css @@ -10,3 +10,9 @@ body{max-width:35em;margin:0 auto;}} @media (min-width:12px){body{margin:0 auto;}} @media (width:767px){.visible-xs{display:block;}} @media only screen and (min--moz-device-pixel-ratio:2.5),only screen and (-o-min-device-pixel-ratio:2),only screen and (min-device-pixel-ratio:2),only screen and (min-resolution:192dpi),only screen and (min-resolution:2dppx){background-size:10px;} +@media (width:768px){.lead{font-size:21px;}} +@media (width:400px){.one{font-size:1.2em;}} +@media (width:400px) and print and (color){.one{color:blue;}} +.two{width:100px;} +@media (width:400px){.two{font-size:1.2em;}} +@media print and (color){.two{color:blue;}} diff --git a/lesscpy/test/less/media.less b/lesscpy/test/less/media.less index ea09670..35a7711 100644 --- a/lesscpy/test/less/media.less +++ b/lesscpy/test/less/media.less @@ -70,3 +70,28 @@ only screen and ( min-resolution: 2dppx) { background-size: 10px; } +/* + Nested media queries +*/ +.lead { + @media (width: 768px) { + font-size: 21px; + } +} +.one { + @media (width: 400px){ + font-size: 1.2em; + @media print and (color) { + color: blue; + } + } +} +.two { + @media (width: 400px){ + font-size: 1.2em; + } + @media print and (color) { + color: blue; + } + width: 100px; +}