diff --git a/inc/lessc.inc.php b/inc/lessc.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..5c81ad2a9178d20c5db380dc2ab31560d001e43d --- /dev/null +++ b/inc/lessc.inc.php @@ -0,0 +1,3481 @@ +<?php + +/** + * lessphp v0.3.9 + * http://leafo.net/lessphp + * + * LESS css compiler, adapted from http://lesscss.org + * + * Copyright 2012, Leaf Corcoran <leafot@gmail.com> + * Licensed under MIT or GPLv3, see LICENSE + */ + + +/** + * The less compiler and parser. + * + * Converting LESS to CSS is a three stage process. The incoming file is parsed + * by `lessc_parser` into a syntax tree, then it is compiled into another tree + * representing the CSS structure by `lessc`. The CSS tree is fed into a + * formatter, like `lessc_formatter` which then outputs CSS as a string. + * + * During the first compile, all values are *reduced*, which means that their + * types are brought to the lowest form before being dump as strings. This + * handles math equations, variable dereferences, and the like. + * + * The `parse` function of `lessc` is the entry point. + * + * In summary: + * + * The `lessc` class creates an intstance of the parser, feeds it LESS code, + * then transforms the resulting tree to a CSS tree. This class also holds the + * evaluation context, such as all available mixins and variables at any given + * time. + * + * The `lessc_parser` class is only concerned with parsing its input. + * + * The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string, + * handling things like indentation. + */ +class lessc { + static public $VERSION = "v0.3.9"; + static protected $TRUE = array("keyword", "true"); + static protected $FALSE = array("keyword", "false"); + + protected $libFunctions = array(); + protected $registeredVars = array(); + protected $preserveComments = false; + + public $vPrefix = '@'; // prefix of abstract properties + public $mPrefix = '$'; // prefix of abstract blocks + public $parentSelector = '&'; + + public $importDisabled = false; + public $importDir = ''; + + protected $numberPrecision = null; + + // set to the parser that generated the current line when compiling + // so we know how to create error messages + protected $sourceParser = null; + protected $sourceLoc = null; + + static public $defaultValue = array("keyword", ""); + + static protected $nextImportId = 0; // uniquely identify imports + + // attempts to find the path of an import url, returns null for css files + protected function findImport($url) { + foreach ((array)$this->importDir as $dir) { + $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url; + if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) { + return $file; + } + } + + return null; + } + + protected function fileExists($name) { + return is_file($name); + } + + static public function compressList($items, $delim) { + if (!isset($items[1]) && isset($items[0])) return $items[0]; + else return array('list', $delim, $items); + } + + static public function preg_quote($what) { + return preg_quote($what, '/'); + } + + protected function tryImport($importPath, $parentBlock, $out) { + if ($importPath[0] == "function" && $importPath[1] == "url") { + $importPath = $this->flattenList($importPath[2]); + } + + $str = $this->coerceString($importPath); + if ($str === null) return false; + + $url = $this->compileValue($this->lib_e($str)); + + // don't import if it ends in css + if (substr_compare($url, '.css', -4, 4) === 0) return false; + + $realPath = $this->findImport($url); + if ($realPath === null) return false; + + if ($this->importDisabled) { + return array(false, "/* import disabled */"); + } + + $this->addParsedFile($realPath); + $parser = $this->makeParser($realPath); + $root = $parser->parse(file_get_contents($realPath)); + + // set the parents of all the block props + foreach ($root->props as $prop) { + if ($prop[0] == "block") { + $prop[1]->parent = $parentBlock; + } + } + + // copy mixins into scope, set their parents + // bring blocks from import into current block + // TODO: need to mark the source parser these came from this file + foreach ($root->children as $childName => $child) { + if (isset($parentBlock->children[$childName])) { + $parentBlock->children[$childName] = array_merge( + $parentBlock->children[$childName], + $child); + } else { + $parentBlock->children[$childName] = $child; + } + } + + $pi = pathinfo($realPath); + $dir = $pi["dirname"]; + + list($top, $bottom) = $this->sortProps($root->props, true); + $this->compileImportedProps($top, $parentBlock, $out, $parser, $dir); + + return array(true, $bottom, $parser, $dir); + } + + protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) { + $oldSourceParser = $this->sourceParser; + + $oldImport = $this->importDir; + + // TODO: this is because the importDir api is stupid + $this->importDir = (array)$this->importDir; + array_unshift($this->importDir, $importDir); + + foreach ($props as $prop) { + $this->compileProp($prop, $block, $out); + } + + $this->importDir = $oldImport; + $this->sourceParser = $oldSourceParser; + } + + /** + * Recursively compiles a block. + * + * A block is analogous to a CSS block in most cases. A single LESS document + * is encapsulated in a block when parsed, but it does not have parent tags + * so all of it's children appear on the root level when compiled. + * + * Blocks are made up of props and children. + * + * Props are property instructions, array tuples which describe an action + * to be taken, eg. write a property, set a variable, mixin a block. + * + * The children of a block are just all the blocks that are defined within. + * This is used to look up mixins when performing a mixin. + * + * Compiling the block involves pushing a fresh environment on the stack, + * and iterating through the props, compiling each one. + * + * See lessc::compileProp() + * + */ + protected function compileBlock($block) { + switch ($block->type) { + case "root": + $this->compileRoot($block); + break; + case null: + $this->compileCSSBlock($block); + break; + case "media": + $this->compileMedia($block); + break; + case "directive": + $name = "@" . $block->name; + if (!empty($block->value)) { + $name .= " " . $this->compileValue($this->reduce($block->value)); + } + + $this->compileNestedBlock($block, array($name)); + break; + default: + $this->throwError("unknown block type: $block->type\n"); + } + } + + protected function compileCSSBlock($block) { + $env = $this->pushEnv(); + + $selectors = $this->compileSelectors($block->tags); + $env->selectors = $this->multiplySelectors($selectors); + $out = $this->makeOutputBlock(null, $env->selectors); + + $this->scope->children[] = $out; + $this->compileProps($block, $out); + + $block->scope = $env; // mixins carry scope with them! + $this->popEnv(); + } + + protected function compileMedia($media) { + $env = $this->pushEnv($media); + $parentScope = $this->mediaParent($this->scope); + + $query = $this->compileMediaQuery($this->multiplyMedia($env)); + + $this->scope = $this->makeOutputBlock($media->type, array($query)); + $parentScope->children[] = $this->scope; + + $this->compileProps($media, $this->scope); + + if (count($this->scope->lines) > 0) { + $orphanSelelectors = $this->findClosestSelectors(); + if (!is_null($orphanSelelectors)) { + $orphan = $this->makeOutputBlock(null, $orphanSelelectors); + $orphan->lines = $this->scope->lines; + array_unshift($this->scope->children, $orphan); + $this->scope->lines = array(); + } + } + + $this->scope = $this->scope->parent; + $this->popEnv(); + } + + protected function mediaParent($scope) { + while (!empty($scope->parent)) { + if (!empty($scope->type) && $scope->type != "media") { + break; + } + $scope = $scope->parent; + } + + return $scope; + } + + protected function compileNestedBlock($block, $selectors) { + $this->pushEnv($block); + $this->scope = $this->makeOutputBlock($block->type, $selectors); + $this->scope->parent->children[] = $this->scope; + + $this->compileProps($block, $this->scope); + + $this->scope = $this->scope->parent; + $this->popEnv(); + } + + protected function compileRoot($root) { + $this->pushEnv(); + $this->scope = $this->makeOutputBlock($root->type); + $this->compileProps($root, $this->scope); + $this->popEnv(); + } + + protected function compileProps($block, $out) { + foreach ($this->sortProps($block->props) as $prop) { + $this->compileProp($prop, $block, $out); + } + } + + protected function sortProps($props, $split = false) { + $vars = array(); + $imports = array(); + $other = array(); + + foreach ($props as $prop) { + switch ($prop[0]) { + case "assign": + if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) { + $vars[] = $prop; + } else { + $other[] = $prop; + } + break; + case "import": + $id = self::$nextImportId++; + $prop[] = $id; + $imports[] = $prop; + $other[] = array("import_mixin", $id); + break; + default: + $other[] = $prop; + } + } + + if ($split) { + return array(array_merge($vars, $imports), $other); + } else { + return array_merge($vars, $imports, $other); + } + } + + protected function compileMediaQuery($queries) { + $compiledQueries = array(); + foreach ($queries as $query) { + $parts = array(); + foreach ($query as $q) { + switch ($q[0]) { + case "mediaType": + $parts[] = implode(" ", array_slice($q, 1)); + break; + case "mediaExp": + if (isset($q[2])) { + $parts[] = "($q[1]: " . + $this->compileValue($this->reduce($q[2])) . ")"; + } else { + $parts[] = "($q[1])"; + } + break; + case "variable": + $parts[] = $this->compileValue($this->reduce($q)); + break; + } + } + + if (count($parts) > 0) { + $compiledQueries[] = implode(" and ", $parts); + } + } + + $out = "@media"; + if (!empty($parts)) { + $out .= " " . + implode($this->formatter->selectorSeparator, $compiledQueries); + } + return $out; + } + + protected function multiplyMedia($env, $childQueries = null) { + if (is_null($env) || + !empty($env->block->type) && $env->block->type != "media") + { + return $childQueries; + } + + // plain old block, skip + if (empty($env->block->type)) { + return $this->multiplyMedia($env->parent, $childQueries); + } + + $out = array(); + $queries = $env->block->queries; + if (is_null($childQueries)) { + $out = $queries; + } else { + foreach ($queries as $parent) { + foreach ($childQueries as $child) { + $out[] = array_merge($parent, $child); + } + } + } + + return $this->multiplyMedia($env->parent, $out); + } + + protected function expandParentSelectors(&$tag, $replace) { + $parts = explode("$&$", $tag); + $count = 0; + foreach ($parts as &$part) { + $part = str_replace($this->parentSelector, $replace, $part, $c); + $count += $c; + } + $tag = implode($this->parentSelector, $parts); + return $count; + } + + protected function findClosestSelectors() { + $env = $this->env; + $selectors = null; + while ($env !== null) { + if (isset($env->selectors)) { + $selectors = $env->selectors; + break; + } + $env = $env->parent; + } + + return $selectors; + } + + + // multiply $selectors against the nearest selectors in env + protected function multiplySelectors($selectors) { + // find parent selectors + + $parentSelectors = $this->findClosestSelectors(); + if (is_null($parentSelectors)) { + // kill parent reference in top level selector + foreach ($selectors as &$s) { + $this->expandParentSelectors($s, ""); + } + + return $selectors; + } + + $out = array(); + foreach ($parentSelectors as $parent) { + foreach ($selectors as $child) { + $count = $this->expandParentSelectors($child, $parent); + + // don't prepend the parent tag if & was used + if ($count > 0) { + $out[] = trim($child); + } else { + $out[] = trim($parent . ' ' . $child); + } + } + } + + return $out; + } + + // reduces selector expressions + protected function compileSelectors($selectors) { + $out = array(); + + foreach ($selectors as $s) { + if (is_array($s)) { + list(, $value) = $s; + $out[] = trim($this->compileValue($this->reduce($value))); + } else { + $out[] = $s; + } + } + + return $out; + } + + protected function eq($left, $right) { + return $left == $right; + } + + protected function patternMatch($block, $callingArgs) { + // match the guards if it has them + // any one of the groups must have all its guards pass for a match + if (!empty($block->guards)) { + $groupPassed = false; + foreach ($block->guards as $guardGroup) { + foreach ($guardGroup as $guard) { + $this->pushEnv(); + $this->zipSetArgs($block->args, $callingArgs); + + $negate = false; + if ($guard[0] == "negate") { + $guard = $guard[1]; + $negate = true; + } + + $passed = $this->reduce($guard) == self::$TRUE; + if ($negate) $passed = !$passed; + + $this->popEnv(); + + if ($passed) { + $groupPassed = true; + } else { + $groupPassed = false; + break; + } + } + + if ($groupPassed) break; + } + + if (!$groupPassed) { + return false; + } + } + + $numCalling = count($callingArgs); + + if (empty($block->args)) { + return $block->isVararg || $numCalling == 0; + } + + $i = -1; // no args + // try to match by arity or by argument literal + foreach ($block->args as $i => $arg) { + switch ($arg[0]) { + case "lit": + if (empty($callingArgs[$i]) || !$this->eq($arg[1], $callingArgs[$i])) { + return false; + } + break; + case "arg": + // no arg and no default value + if (!isset($callingArgs[$i]) && !isset($arg[2])) { + return false; + } + break; + case "rest": + $i--; // rest can be empty + break 2; + } + } + + if ($block->isVararg) { + return true; // not having enough is handled above + } else { + $numMatched = $i + 1; + // greater than becuase default values always match + return $numMatched >= $numCalling; + } + } + + protected function patternMatchAll($blocks, $callingArgs) { + $matches = null; + foreach ($blocks as $block) { + if ($this->patternMatch($block, $callingArgs)) { + $matches[] = $block; + } + } + + return $matches; + } + + // attempt to find blocks matched by path and args + protected function findBlocks($searchIn, $path, $args, $seen=array()) { + if ($searchIn == null) return null; + if (isset($seen[$searchIn->id])) return null; + $seen[$searchIn->id] = true; + + $name = $path[0]; + + if (isset($searchIn->children[$name])) { + $blocks = $searchIn->children[$name]; + if (count($path) == 1) { + $matches = $this->patternMatchAll($blocks, $args); + if (!empty($matches)) { + // This will return all blocks that match in the closest + // scope that has any matching block, like lessjs + return $matches; + } + } else { + $matches = array(); + foreach ($blocks as $subBlock) { + $subMatches = $this->findBlocks($subBlock, + array_slice($path, 1), $args, $seen); + + if (!is_null($subMatches)) { + foreach ($subMatches as $sm) { + $matches[] = $sm; + } + } + } + + return count($matches) > 0 ? $matches : null; + } + } + + if ($searchIn->parent === $searchIn) return null; + return $this->findBlocks($searchIn->parent, $path, $args, $seen); + } + + // sets all argument names in $args to either the default value + // or the one passed in through $values + protected function zipSetArgs($args, $values) { + $i = 0; + $assignedValues = array(); + foreach ($args as $a) { + if ($a[0] == "arg") { + if ($i < count($values) && !is_null($values[$i])) { + $value = $values[$i]; + } elseif (isset($a[2])) { + $value = $a[2]; + } else $value = null; + + $value = $this->reduce($value); + $this->set($a[1], $value); + $assignedValues[] = $value; + } + $i++; + } + + // check for a rest + $last = end($args); + if ($last[0] == "rest") { + $rest = array_slice($values, count($args) - 1); + $this->set($last[1], $this->reduce(array("list", " ", $rest))); + } + + $this->env->arguments = $assignedValues; + } + + // compile a prop and update $lines or $blocks appropriately + protected function compileProp($prop, $block, $out) { + // set error position context + $this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1; + + switch ($prop[0]) { + case 'assign': + list(, $name, $value) = $prop; + if ($name[0] == $this->vPrefix) { + $this->set($name, $value); + } else { + $out->lines[] = $this->formatter->property($name, + $this->compileValue($this->reduce($value))); + } + break; + case 'block': + list(, $child) = $prop; + $this->compileBlock($child); + break; + case 'mixin': + list(, $path, $args, $suffix) = $prop; + + $args = array_map(array($this, "reduce"), (array)$args); + $mixins = $this->findBlocks($block, $path, $args); + + if ($mixins === null) { + // fwrite(STDERR,"failed to find block: ".implode(" > ", $path)."\n"); + break; // throw error here?? + } + + foreach ($mixins as $mixin) { + if ($mixin === $block && !$args) { + continue; + } + + $haveScope = false; + if (isset($mixin->parent->scope)) { + $haveScope = true; + $mixinParentEnv = $this->pushEnv(); + $mixinParentEnv->storeParent = $mixin->parent->scope; + } + + $haveArgs = false; + if (isset($mixin->args)) { + $haveArgs = true; + $this->pushEnv(); + $this->zipSetArgs($mixin->args, $args); + } + + $oldParent = $mixin->parent; + if ($mixin != $block) $mixin->parent = $block; + + foreach ($this->sortProps($mixin->props) as $subProp) { + if ($suffix !== null && + $subProp[0] == "assign" && + is_string($subProp[1]) && + $subProp[1]{0} != $this->vPrefix) + { + $subProp[2] = array( + 'list', ' ', + array($subProp[2], array('keyword', $suffix)) + ); + } + + $this->compileProp($subProp, $mixin, $out); + } + + $mixin->parent = $oldParent; + + if ($haveArgs) $this->popEnv(); + if ($haveScope) $this->popEnv(); + } + + break; + case 'raw': + $out->lines[] = $prop[1]; + break; + case "directive": + list(, $name, $value) = $prop; + $out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';'; + break; + case "comment": + $out->lines[] = $prop[1]; + break; + case "import"; + list(, $importPath, $importId) = $prop; + $importPath = $this->reduce($importPath); + + if (!isset($this->env->imports)) { + $this->env->imports = array(); + } + + $result = $this->tryImport($importPath, $block, $out); + + $this->env->imports[$importId] = $result === false ? + array(false, "@import " . $this->compileValue($importPath).";") : + $result; + + break; + case "import_mixin": + list(,$importId) = $prop; + $import = $this->env->imports[$importId]; + if ($import[0] === false) { + $out->lines[] = $import[1]; + } else { + list(, $bottom, $parser, $importDir) = $import; + $this->compileImportedProps($bottom, $block, $out, $parser, $importDir); + } + + break; + default: + $this->throwError("unknown op: {$prop[0]}\n"); + } + } + + + /** + * Compiles a primitive value into a CSS property value. + * + * Values in lessphp are typed by being wrapped in arrays, their format is + * typically: + * + * array(type, contents [, additional_contents]*) + * + * The input is expected to be reduced. This function will not work on + * things like expressions and variables. + */ + protected function compileValue($value) { + switch ($value[0]) { + case 'list': + // [1] - delimiter + // [2] - array of values + return implode($value[1], array_map(array($this, 'compileValue'), $value[2])); + case 'raw_color': + if (!empty($this->formatter->compressColors)) { + return $this->compileValue($this->coerceColor($value)); + } + return $value[1]; + case 'keyword': + // [1] - the keyword + return $value[1]; + case 'number': + list(, $num, $unit) = $value; + // [1] - the number + // [2] - the unit + if ($this->numberPrecision !== null) { + $num = round($num, $this->numberPrecision); + } + return $num . $unit; + case 'string': + // [1] - contents of string (includes quotes) + list(, $delim, $content) = $value; + foreach ($content as &$part) { + if (is_array($part)) { + $part = $this->compileValue($part); + } + } + return $delim . implode($content) . $delim; + case 'color': + // [1] - red component (either number or a %) + // [2] - green component + // [3] - blue component + // [4] - optional alpha component + list(, $r, $g, $b) = $value; + $r = round($r); + $g = round($g); + $b = round($b); + + if (count($value) == 5 && $value[4] != 1) { // rgba + return 'rgba('.$r.','.$g.','.$b.','.$value[4].')'; + } + + $h = sprintf("#%02x%02x%02x", $r, $g, $b); + + if (!empty($this->formatter->compressColors)) { + // Converting hex color to short notation (e.g. #003399 to #039) + if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) { + $h = '#' . $h[1] . $h[3] . $h[5]; + } + } + + return $h; + + case 'function': + list(, $name, $args) = $value; + return $name.'('.$this->compileValue($args).')'; + default: // assumed to be unit + $this->throwError("unknown value type: $value[0]"); + } + } + + protected function lib_isnumber($value) { + return $this->toBool($value[0] == "number"); + } + + protected function lib_isstring($value) { + return $this->toBool($value[0] == "string"); + } + + protected function lib_iscolor($value) { + return $this->toBool($this->coerceColor($value)); + } + + protected function lib_iskeyword($value) { + return $this->toBool($value[0] == "keyword"); + } + + protected function lib_ispixel($value) { + return $this->toBool($value[0] == "number" && $value[2] == "px"); + } + + protected function lib_ispercentage($value) { + return $this->toBool($value[0] == "number" && $value[2] == "%"); + } + + protected function lib_isem($value) { + return $this->toBool($value[0] == "number" && $value[2] == "em"); + } + + protected function lib_isrem($value) { + return $this->toBool($value[0] == "number" && $value[2] == "rem"); + } + + protected function lib_rgbahex($color) { + $color = $this->coerceColor($color); + if (is_null($color)) + $this->throwError("color expected for rgbahex"); + + return sprintf("#%02x%02x%02x%02x", + isset($color[4]) ? $color[4]*255 : 255, + $color[1],$color[2], $color[3]); + } + + protected function lib_argb($color){ + return $this->lib_rgbahex($color); + } + + // utility func to unquote a string + protected function lib_e($arg) { + switch ($arg[0]) { + case "list": + $items = $arg[2]; + if (isset($items[0])) { + return $this->lib_e($items[0]); + } + return self::$defaultValue; + case "string": + $arg[1] = ""; + return $arg; + case "keyword": + return $arg; + default: + return array("keyword", $this->compileValue($arg)); + } + } + + protected function lib__sprintf($args) { + if ($args[0] != "list") return $args; + $values = $args[2]; + $string = array_shift($values); + $template = $this->compileValue($this->lib_e($string)); + + $i = 0; + if (preg_match_all('/%[dsa]/', $template, $m)) { + foreach ($m[0] as $match) { + $val = isset($values[$i]) ? + $this->reduce($values[$i]) : array('keyword', ''); + + // lessjs compat, renders fully expanded color, not raw color + if ($color = $this->coerceColor($val)) { + $val = $color; + } + + $i++; + $rep = $this->compileValue($this->lib_e($val)); + $template = preg_replace('/'.self::preg_quote($match).'/', + $rep, $template, 1); + } + } + + $d = $string[0] == "string" ? $string[1] : '"'; + return array("string", $d, array($template)); + } + + protected function lib_floor($arg) { + $value = $this->assertNumber($arg); + return array("number", floor($value), $arg[2]); + } + + protected function lib_ceil($arg) { + $value = $this->assertNumber($arg); + return array("number", ceil($value), $arg[2]); + } + + protected function lib_round($arg) { + $value = $this->assertNumber($arg); + return array("number", round($value), $arg[2]); + } + + protected function lib_unit($arg) { + if ($arg[0] == "list") { + list($number, $newUnit) = $arg[2]; + return array("number", $this->assertNumber($number), + $this->compileValue($this->lib_e($newUnit))); + } else { + return array("number", $this->assertNumber($arg), ""); + } + } + + /** + * Helper function to get arguments for color manipulation functions. + * takes a list that contains a color like thing and a percentage + */ + protected function colorArgs($args) { + if ($args[0] != 'list' || count($args[2]) < 2) { + return array(array('color', 0, 0, 0), 0); + } + list($color, $delta) = $args[2]; + $color = $this->assertColor($color); + $delta = floatval($delta[1]); + + return array($color, $delta); + } + + protected function lib_darken($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[3] = $this->clamp($hsl[3] - $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_lighten($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[3] = $this->clamp($hsl[3] + $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_saturate($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[2] = $this->clamp($hsl[2] + $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_desaturate($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + $hsl[2] = $this->clamp($hsl[2] - $delta, 100); + return $this->toRGB($hsl); + } + + protected function lib_spin($args) { + list($color, $delta) = $this->colorArgs($args); + + $hsl = $this->toHSL($color); + + $hsl[1] = $hsl[1] + $delta % 360; + if ($hsl[1] < 0) $hsl[1] += 360; + + return $this->toRGB($hsl); + } + + protected function lib_fadeout($args) { + list($color, $delta) = $this->colorArgs($args); + $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100); + return $color; + } + + protected function lib_fadein($args) { + list($color, $delta) = $this->colorArgs($args); + $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100); + return $color; + } + + protected function lib_hue($color) { + $hsl = $this->toHSL($this->assertColor($color)); + return round($hsl[1]); + } + + protected function lib_saturation($color) { + $hsl = $this->toHSL($this->assertColor($color)); + return round($hsl[2]); + } + + protected function lib_lightness($color) { + $hsl = $this->toHSL($this->assertColor($color)); + return round($hsl[3]); + } + + // get the alpha of a color + // defaults to 1 for non-colors or colors without an alpha + protected function lib_alpha($value) { + if (!is_null($color = $this->coerceColor($value))) { + return isset($color[4]) ? $color[4] : 1; + } + } + + // set the alpha of the color + protected function lib_fade($args) { + list($color, $alpha) = $this->colorArgs($args); + $color[4] = $this->clamp($alpha / 100.0); + return $color; + } + + protected function lib_percentage($arg) { + $num = $this->assertNumber($arg); + return array("number", $num*100, "%"); + } + + // mixes two colors by weight + // mix(@color1, @color2, [@weight: 50%]); + // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method + protected function lib_mix($args) { + if ($args[0] != "list" || count($args[2]) < 2) + $this->throwError("mix expects (color1, color2, weight)"); + + list($first, $second) = $args[2]; + $first = $this->assertColor($first); + $second = $this->assertColor($second); + + $first_a = $this->lib_alpha($first); + $second_a = $this->lib_alpha($second); + + if (isset($args[2][2])) { + $weight = $args[2][2][1] / 100.0; + } else { + $weight = 0.5; + } + + $w = $weight * 2 - 1; + $a = $first_a - $second_a; + + $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0; + $w2 = 1.0 - $w1; + + $new = array('color', + $w1 * $first[1] + $w2 * $second[1], + $w1 * $first[2] + $w2 * $second[2], + $w1 * $first[3] + $w2 * $second[3], + ); + + if ($first_a != 1.0 || $second_a != 1.0) { + $new[] = $first_a * $weight + $second_a * ($weight - 1); + } + + return $this->fixColor($new); + } + + protected function lib_contrast($args) { + if ($args[0] != 'list' || count($args[2]) < 3) { + return array(array('color', 0, 0, 0), 0); + } + + list($inputColor, $darkColor, $lightColor) = $args[2]; + + $inputColor = $this->assertColor($inputColor); + $darkColor = $this->assertColor($darkColor); + $lightColor = $this->assertColor($lightColor); + $hsl = $this->toHSL($inputColor); + + if ($hsl[3] > 50) { + return $darkColor; + } + + return $lightColor; + } + + protected function assertColor($value, $error = "expected color value") { + $color = $this->coerceColor($value); + if (is_null($color)) $this->throwError($error); + return $color; + } + + protected function assertNumber($value, $error = "expecting number") { + if ($value[0] == "number") return $value[1]; + $this->throwError($error); + } + + protected function toHSL($color) { + if ($color[0] == 'hsl') return $color; + + $r = $color[1] / 255; + $g = $color[2] / 255; + $b = $color[3] / 255; + + $min = min($r, $g, $b); + $max = max($r, $g, $b); + + $L = ($min + $max) / 2; + if ($min == $max) { + $S = $H = 0; + } else { + if ($L < 0.5) + $S = ($max - $min)/($max + $min); + else + $S = ($max - $min)/(2.0 - $max - $min); + + if ($r == $max) $H = ($g - $b)/($max - $min); + elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min); + elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min); + + } + + $out = array('hsl', + ($H < 0 ? $H + 6 : $H)*60, + $S*100, + $L*100, + ); + + if (count($color) > 4) $out[] = $color[4]; // copy alpha + return $out; + } + + protected function toRGB_helper($comp, $temp1, $temp2) { + if ($comp < 0) $comp += 1.0; + elseif ($comp > 1) $comp -= 1.0; + + if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp; + if (2 * $comp < 1) return $temp2; + if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6; + + return $temp1; + } + + /** + * Converts a hsl array into a color value in rgb. + * Expects H to be in range of 0 to 360, S and L in 0 to 100 + */ + protected function toRGB($color) { + if ($color[0] == 'color') return $color; + + $H = $color[1] / 360; + $S = $color[2] / 100; + $L = $color[3] / 100; + + if ($S == 0) { + $r = $g = $b = $L; + } else { + $temp2 = $L < 0.5 ? + $L*(1.0 + $S) : + $L + $S - $L * $S; + + $temp1 = 2.0 * $L - $temp2; + + $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2); + $g = $this->toRGB_helper($H, $temp1, $temp2); + $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2); + } + + // $out = array('color', round($r*255), round($g*255), round($b*255)); + $out = array('color', $r*255, $g*255, $b*255); + if (count($color) > 4) $out[] = $color[4]; // copy alpha + return $out; + } + + protected function clamp($v, $max = 1, $min = 0) { + return min($max, max($min, $v)); + } + + /** + * Convert the rgb, rgba, hsl color literals of function type + * as returned by the parser into values of color type. + */ + protected function funcToColor($func) { + $fname = $func[1]; + if ($func[2][0] != 'list') return false; // need a list of arguments + $rawComponents = $func[2][2]; + + if ($fname == 'hsl' || $fname == 'hsla') { + $hsl = array('hsl'); + $i = 0; + foreach ($rawComponents as $c) { + $val = $this->reduce($c); + $val = isset($val[1]) ? floatval($val[1]) : 0; + + if ($i == 0) $clamp = 360; + elseif ($i < 3) $clamp = 100; + else $clamp = 1; + + $hsl[] = $this->clamp($val, $clamp); + $i++; + } + + while (count($hsl) < 4) $hsl[] = 0; + return $this->toRGB($hsl); + + } elseif ($fname == 'rgb' || $fname == 'rgba') { + $components = array(); + $i = 1; + foreach ($rawComponents as $c) { + $c = $this->reduce($c); + if ($i < 4) { + if ($c[0] == "number" && $c[2] == "%") { + $components[] = 255 * ($c[1] / 100); + } else { + $components[] = floatval($c[1]); + } + } elseif ($i == 4) { + if ($c[0] == "number" && $c[2] == "%") { + $components[] = 1.0 * ($c[1] / 100); + } else { + $components[] = floatval($c[1]); + } + } else break; + + $i++; + } + while (count($components) < 3) $components[] = 0; + array_unshift($components, 'color'); + return $this->fixColor($components); + } + + return false; + } + + protected function reduce($value, $forExpression = false) { + switch ($value[0]) { + case "interpolate": + $reduced = $this->reduce($value[1]); + $var = $this->compileValue($reduced); + $res = $this->reduce(array("variable", $this->vPrefix . $var)); + + if (empty($value[2])) $res = $this->lib_e($res); + + return $res; + case "variable": + $key = $value[1]; + if (is_array($key)) { + $key = $this->reduce($key); + $key = $this->vPrefix . $this->compileValue($this->lib_e($key)); + } + + $seen =& $this->env->seenNames; + + if (!empty($seen[$key])) { + $this->throwError("infinite loop detected: $key"); + } + + $seen[$key] = true; + $out = $this->reduce($this->get($key, self::$defaultValue)); + $seen[$key] = false; + return $out; + case "list": + foreach ($value[2] as &$item) { + $item = $this->reduce($item, $forExpression); + } + return $value; + case "expression": + return $this->evaluate($value); + case "string": + foreach ($value[2] as &$part) { + if (is_array($part)) { + $strip = $part[0] == "variable"; + $part = $this->reduce($part); + if ($strip) $part = $this->lib_e($part); + } + } + return $value; + case "escape": + list(,$inner) = $value; + return $this->lib_e($this->reduce($inner)); + case "function": + $color = $this->funcToColor($value); + if ($color) return $color; + + list(, $name, $args) = $value; + if ($name == "%") $name = "_sprintf"; + $f = isset($this->libFunctions[$name]) ? + $this->libFunctions[$name] : array($this, 'lib_'.$name); + + if (is_callable($f)) { + if ($args[0] == 'list') + $args = self::compressList($args[2], $args[1]); + + $ret = call_user_func($f, $this->reduce($args, true), $this); + + if (is_null($ret)) { + return array("string", "", array( + $name, "(", $args, ")" + )); + } + + // convert to a typed value if the result is a php primitive + if (is_numeric($ret)) $ret = array('number', $ret, ""); + elseif (!is_array($ret)) $ret = array('keyword', $ret); + + return $ret; + } + + // plain function, reduce args + $value[2] = $this->reduce($value[2]); + return $value; + case "unary": + list(, $op, $exp) = $value; + $exp = $this->reduce($exp); + + if ($exp[0] == "number") { + switch ($op) { + case "+": + return $exp; + case "-": + $exp[1] *= -1; + return $exp; + } + } + return array("string", "", array($op, $exp)); + } + + if ($forExpression) { + switch ($value[0]) { + case "keyword": + if ($color = $this->coerceColor($value)) { + return $color; + } + break; + case "raw_color": + return $this->coerceColor($value); + } + } + + return $value; + } + + + // coerce a value for use in color operation + protected function coerceColor($value) { + switch($value[0]) { + case 'color': return $value; + case 'raw_color': + $c = array("color", 0, 0, 0); + $colorStr = substr($value[1], 1); + $num = hexdec($colorStr); + $width = strlen($colorStr) == 3 ? 16 : 256; + + for ($i = 3; $i > 0; $i--) { // 3 2 1 + $t = $num % $width; + $num /= $width; + + $c[$i] = $t * (256/$width) + $t * floor(16/$width); + } + + return $c; + case 'keyword': + $name = $value[1]; + if (isset(self::$cssColors[$name])) { + $rgba = explode(',', self::$cssColors[$name]); + + if(isset($rgba[3])) + return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]); + + return array('color', $rgba[0], $rgba[1], $rgba[2]); + } + return null; + } + } + + // make something string like into a string + protected function coerceString($value) { + switch ($value[0]) { + case "string": + return $value; + case "keyword": + return array("string", "", array($value[1])); + } + return null; + } + + // turn list of length 1 into value type + protected function flattenList($value) { + if ($value[0] == "list" && count($value[2]) == 1) { + return $this->flattenList($value[2][0]); + } + return $value; + } + + protected function toBool($a) { + if ($a) return self::$TRUE; + else return self::$FALSE; + } + + // evaluate an expression + protected function evaluate($exp) { + list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp; + + $left = $this->reduce($left, true); + $right = $this->reduce($right, true); + + if ($leftColor = $this->coerceColor($left)) { + $left = $leftColor; + } + + if ($rightColor = $this->coerceColor($right)) { + $right = $rightColor; + } + + $ltype = $left[0]; + $rtype = $right[0]; + + // operators that work on all types + if ($op == "and") { + return $this->toBool($left == self::$TRUE && $right == self::$TRUE); + } + + if ($op == "=") { + return $this->toBool($this->eq($left, $right) ); + } + + if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) { + return $str; + } + + // type based operators + $fname = "op_${ltype}_${rtype}"; + if (is_callable(array($this, $fname))) { + $out = $this->$fname($op, $left, $right); + if (!is_null($out)) return $out; + } + + // make the expression look it did before being parsed + $paddedOp = $op; + if ($whiteBefore) $paddedOp = " " . $paddedOp; + if ($whiteAfter) $paddedOp .= " "; + + return array("string", "", array($left, $paddedOp, $right)); + } + + protected function stringConcatenate($left, $right) { + if ($strLeft = $this->coerceString($left)) { + if ($right[0] == "string") { + $right[1] = ""; + } + $strLeft[2][] = $right; + return $strLeft; + } + + if ($strRight = $this->coerceString($right)) { + array_unshift($strRight[2], $left); + return $strRight; + } + } + + + // make sure a color's components don't go out of bounds + protected function fixColor($c) { + foreach (range(1, 3) as $i) { + if ($c[$i] < 0) $c[$i] = 0; + if ($c[$i] > 255) $c[$i] = 255; + } + + return $c; + } + + protected function op_number_color($op, $lft, $rgt) { + if ($op == '+' || $op == '*') { + return $this->op_color_number($op, $rgt, $lft); + } + } + + protected function op_color_number($op, $lft, $rgt) { + if ($rgt[0] == '%') $rgt[1] /= 100; + + return $this->op_color_color($op, $lft, + array_fill(1, count($lft) - 1, $rgt[1])); + } + + protected function op_color_color($op, $left, $right) { + $out = array('color'); + $max = count($left) > count($right) ? count($left) : count($right); + foreach (range(1, $max - 1) as $i) { + $lval = isset($left[$i]) ? $left[$i] : 0; + $rval = isset($right[$i]) ? $right[$i] : 0; + switch ($op) { + case '+': + $out[] = $lval + $rval; + break; + case '-': + $out[] = $lval - $rval; + break; + case '*': + $out[] = $lval * $rval; + break; + case '%': + $out[] = $lval % $rval; + break; + case '/': + if ($rval == 0) $this->throwError("evaluate error: can't divide by zero"); + $out[] = $lval / $rval; + break; + default: + $this->throwError('evaluate error: color op number failed on op '.$op); + } + } + return $this->fixColor($out); + } + + function lib_red($color){ + $color = $this->coerceColor($color); + if (is_null($color)) { + $this->throwError('color expected for red()'); + } + + return $color[1]; + } + + function lib_green($color){ + $color = $this->coerceColor($color); + if (is_null($color)) { + $this->throwError('color expected for green()'); + } + + return $color[2]; + } + + function lib_blue($color){ + $color = $this->coerceColor($color); + if (is_null($color)) { + $this->throwError('color expected for blue()'); + } + + return $color[3]; + } + + + // operator on two numbers + protected function op_number_number($op, $left, $right) { + $unit = empty($left[2]) ? $right[2] : $left[2]; + + $value = 0; + switch ($op) { + case '+': + $value = $left[1] + $right[1]; + break; + case '*': + $value = $left[1] * $right[1]; + break; + case '-': + $value = $left[1] - $right[1]; + break; + case '%': + $value = $left[1] % $right[1]; + break; + case '/': + if ($right[1] == 0) $this->throwError('parse error: divide by zero'); + $value = $left[1] / $right[1]; + break; + case '<': + return $this->toBool($left[1] < $right[1]); + case '>': + return $this->toBool($left[1] > $right[1]); + case '>=': + return $this->toBool($left[1] >= $right[1]); + case '=<': + return $this->toBool($left[1] <= $right[1]); + default: + $this->throwError('parse error: unknown number operator: '.$op); + } + + return array("number", $value, $unit); + } + + + /* environment functions */ + + protected function makeOutputBlock($type, $selectors = null) { + $b = new stdclass; + $b->lines = array(); + $b->children = array(); + $b->selectors = $selectors; + $b->type = $type; + $b->parent = $this->scope; + return $b; + } + + // the state of execution + protected function pushEnv($block = null) { + $e = new stdclass; + $e->parent = $this->env; + $e->store = array(); + $e->block = $block; + + $this->env = $e; + return $e; + } + + // pop something off the stack + protected function popEnv() { + $old = $this->env; + $this->env = $this->env->parent; + return $old; + } + + // set something in the current env + protected function set($name, $value) { + $this->env->store[$name] = $value; + } + + + // get the highest occurrence entry for a name + protected function get($name, $default=null) { + $current = $this->env; + + $isArguments = $name == $this->vPrefix . 'arguments'; + while ($current) { + if ($isArguments && isset($current->arguments)) { + return array('list', ' ', $current->arguments); + } + + if (isset($current->store[$name])) + return $current->store[$name]; + else { + $current = isset($current->storeParent) ? + $current->storeParent : $current->parent; + } + } + + return $default; + } + + // inject array of unparsed strings into environment as variables + protected function injectVariables($args) { + $this->pushEnv(); + $parser = new lessc_parser($this, __METHOD__); + foreach ($args as $name => $strValue) { + if ($name{0} != '@') $name = '@'.$name; + $parser->count = 0; + $parser->buffer = (string)$strValue; + if (!$parser->propertyValue($value)) { + throw new Exception("failed to parse passed in variable $name: $strValue"); + } + + $this->set($name, $value); + } + } + + /** + * Initialize any static state, can initialize parser for a file + * $opts isn't used yet + */ + public function __construct($fname = null) { + if ($fname !== null) { + // used for deprecated parse method + $this->_parseFile = $fname; + } + } + + public function compile($string, $name = null) { + $locale = setlocale(LC_NUMERIC, 0); + setlocale(LC_NUMERIC, "C"); + + $this->parser = $this->makeParser($name); + $root = $this->parser->parse($string); + + $this->env = null; + $this->scope = null; + + $this->formatter = $this->newFormatter(); + + if (!empty($this->registeredVars)) { + $this->injectVariables($this->registeredVars); + } + + $this->sourceParser = $this->parser; // used for error messages + $this->compileBlock($root); + + ob_start(); + $this->formatter->block($this->scope); + $out = ob_get_clean(); + setlocale(LC_NUMERIC, $locale); + return $out; + } + + public function compileFile($fname, $outFname = null) { + if (!is_readable($fname)) { + throw new Exception('load error: failed to find '.$fname); + } + + $pi = pathinfo($fname); + + $oldImport = $this->importDir; + + $this->importDir = (array)$this->importDir; + $this->importDir[] = $pi['dirname'].'/'; + + $this->allParsedFiles = array(); + $this->addParsedFile($fname); + + $out = $this->compile(file_get_contents($fname), $fname); + + $this->importDir = $oldImport; + + if ($outFname !== null) { + return file_put_contents($outFname, $out); + } + + return $out; + } + + // compile only if changed input has changed or output doesn't exist + public function checkedCompile($in, $out) { + if (!is_file($out) || filemtime($in) > filemtime($out)) { + $this->compileFile($in, $out); + return true; + } + return false; + } + + /** + * Execute lessphp on a .less file or a lessphp cache structure + * + * The lessphp cache structure contains information about a specific + * less file having been parsed. It can be used as a hint for future + * calls to determine whether or not a rebuild is required. + * + * The cache structure contains two important keys that may be used + * externally: + * + * compiled: The final compiled CSS + * updated: The time (in seconds) the CSS was last compiled + * + * The cache structure is a plain-ol' PHP associative array and can + * be serialized and unserialized without a hitch. + * + * @param mixed $in Input + * @param bool $force Force rebuild? + * @return array lessphp cache structure + */ + public function cachedCompile($in, $force = false) { + // assume no root + $root = null; + + if (is_string($in)) { + $root = $in; + } elseif (is_array($in) and isset($in['root'])) { + if ($force or ! isset($in['files'])) { + // If we are forcing a recompile or if for some reason the + // structure does not contain any file information we should + // specify the root to trigger a rebuild. + $root = $in['root']; + } elseif (isset($in['files']) and is_array($in['files'])) { + foreach ($in['files'] as $fname => $ftime ) { + if (!file_exists($fname) or filemtime($fname) > $ftime) { + // One of the files we knew about previously has changed + // so we should look at our incoming root again. + $root = $in['root']; + break; + } + } + } + } else { + // TODO: Throw an exception? We got neither a string nor something + // that looks like a compatible lessphp cache structure. + return null; + } + + if ($root !== null) { + // If we have a root value which means we should rebuild. + $out = array(); + $out['root'] = $root; + $out['compiled'] = $this->compileFile($root); + $out['files'] = $this->allParsedFiles(); + $out['updated'] = time(); + return $out; + } else { + // No changes, pass back the structure + // we were given initially. + return $in; + } + + } + + // parse and compile buffer + // This is deprecated + public function parse($str = null, $initialVariables = null) { + if (is_array($str)) { + $initialVariables = $str; + $str = null; + } + + $oldVars = $this->registeredVars; + if ($initialVariables !== null) { + $this->setVariables($initialVariables); + } + + if ($str == null) { + if (empty($this->_parseFile)) { + throw new exception("nothing to parse"); + } + + $out = $this->compileFile($this->_parseFile); + } else { + $out = $this->compile($str); + } + + $this->registeredVars = $oldVars; + return $out; + } + + protected function makeParser($name) { + $parser = new lessc_parser($this, $name); + $parser->writeComments = $this->preserveComments; + + return $parser; + } + + public function setFormatter($name) { + $this->formatterName = $name; + } + + protected function newFormatter() { + $className = "lessc_formatter_lessjs"; + if (!empty($this->formatterName)) { + if (!is_string($this->formatterName)) + return $this->formatterName; + $className = "lessc_formatter_$this->formatterName"; + } + + return new $className; + } + + public function setPreserveComments($preserve) { + $this->preserveComments = $preserve; + } + + public function registerFunction($name, $func) { + $this->libFunctions[$name] = $func; + } + + public function unregisterFunction($name) { + unset($this->libFunctions[$name]); + } + + public function setVariables($variables) { + $this->registeredVars = array_merge($this->registeredVars, $variables); + } + + public function unsetVariable($name) { + unset($this->registeredVars[$name]); + } + + public function setImportDir($dirs) { + $this->importDir = (array)$dirs; + } + + public function addImportDir($dir) { + $this->importDir = (array)$this->importDir; + $this->importDir[] = $dir; + } + + public function allParsedFiles() { + return $this->allParsedFiles; + } + + protected function addParsedFile($file) { + $this->allParsedFiles[realpath($file)] = filemtime($file); + } + + /** + * Uses the current value of $this->count to show line and line number + */ + protected function throwError($msg = null) { + if ($this->sourceLoc >= 0) { + $this->sourceParser->throwError($msg, $this->sourceLoc); + } + throw new exception($msg); + } + + // compile file $in to file $out if $in is newer than $out + // returns true when it compiles, false otherwise + public static function ccompile($in, $out, $less = null) { + if ($less === null) { + $less = new self; + } + return $less->checkedCompile($in, $out); + } + + public static function cexecute($in, $force = false, $less = null) { + if ($less === null) { + $less = new self; + } + return $less->cachedCompile($in, $force); + } + + static protected $cssColors = array( + 'aliceblue' => '240,248,255', + 'antiquewhite' => '250,235,215', + 'aqua' => '0,255,255', + 'aquamarine' => '127,255,212', + 'azure' => '240,255,255', + 'beige' => '245,245,220', + 'bisque' => '255,228,196', + 'black' => '0,0,0', + 'blanchedalmond' => '255,235,205', + 'blue' => '0,0,255', + 'blueviolet' => '138,43,226', + 'brown' => '165,42,42', + 'burlywood' => '222,184,135', + 'cadetblue' => '95,158,160', + 'chartreuse' => '127,255,0', + 'chocolate' => '210,105,30', + 'coral' => '255,127,80', + 'cornflowerblue' => '100,149,237', + 'cornsilk' => '255,248,220', + 'crimson' => '220,20,60', + 'cyan' => '0,255,255', + 'darkblue' => '0,0,139', + 'darkcyan' => '0,139,139', + 'darkgoldenrod' => '184,134,11', + 'darkgray' => '169,169,169', + 'darkgreen' => '0,100,0', + 'darkgrey' => '169,169,169', + 'darkkhaki' => '189,183,107', + 'darkmagenta' => '139,0,139', + 'darkolivegreen' => '85,107,47', + 'darkorange' => '255,140,0', + 'darkorchid' => '153,50,204', + 'darkred' => '139,0,0', + 'darksalmon' => '233,150,122', + 'darkseagreen' => '143,188,143', + 'darkslateblue' => '72,61,139', + 'darkslategray' => '47,79,79', + 'darkslategrey' => '47,79,79', + 'darkturquoise' => '0,206,209', + 'darkviolet' => '148,0,211', + 'deeppink' => '255,20,147', + 'deepskyblue' => '0,191,255', + 'dimgray' => '105,105,105', + 'dimgrey' => '105,105,105', + 'dodgerblue' => '30,144,255', + 'firebrick' => '178,34,34', + 'floralwhite' => '255,250,240', + 'forestgreen' => '34,139,34', + 'fuchsia' => '255,0,255', + 'gainsboro' => '220,220,220', + 'ghostwhite' => '248,248,255', + 'gold' => '255,215,0', + 'goldenrod' => '218,165,32', + 'gray' => '128,128,128', + 'green' => '0,128,0', + 'greenyellow' => '173,255,47', + 'grey' => '128,128,128', + 'honeydew' => '240,255,240', + 'hotpink' => '255,105,180', + 'indianred' => '205,92,92', + 'indigo' => '75,0,130', + 'ivory' => '255,255,240', + 'khaki' => '240,230,140', + 'lavender' => '230,230,250', + 'lavenderblush' => '255,240,245', + 'lawngreen' => '124,252,0', + 'lemonchiffon' => '255,250,205', + 'lightblue' => '173,216,230', + 'lightcoral' => '240,128,128', + 'lightcyan' => '224,255,255', + 'lightgoldenrodyellow' => '250,250,210', + 'lightgray' => '211,211,211', + 'lightgreen' => '144,238,144', + 'lightgrey' => '211,211,211', + 'lightpink' => '255,182,193', + 'lightsalmon' => '255,160,122', + 'lightseagreen' => '32,178,170', + 'lightskyblue' => '135,206,250', + 'lightslategray' => '119,136,153', + 'lightslategrey' => '119,136,153', + 'lightsteelblue' => '176,196,222', + 'lightyellow' => '255,255,224', + 'lime' => '0,255,0', + 'limegreen' => '50,205,50', + 'linen' => '250,240,230', + 'magenta' => '255,0,255', + 'maroon' => '128,0,0', + 'mediumaquamarine' => '102,205,170', + 'mediumblue' => '0,0,205', + 'mediumorchid' => '186,85,211', + 'mediumpurple' => '147,112,219', + 'mediumseagreen' => '60,179,113', + 'mediumslateblue' => '123,104,238', + 'mediumspringgreen' => '0,250,154', + 'mediumturquoise' => '72,209,204', + 'mediumvioletred' => '199,21,133', + 'midnightblue' => '25,25,112', + 'mintcream' => '245,255,250', + 'mistyrose' => '255,228,225', + 'moccasin' => '255,228,181', + 'navajowhite' => '255,222,173', + 'navy' => '0,0,128', + 'oldlace' => '253,245,230', + 'olive' => '128,128,0', + 'olivedrab' => '107,142,35', + 'orange' => '255,165,0', + 'orangered' => '255,69,0', + 'orchid' => '218,112,214', + 'palegoldenrod' => '238,232,170', + 'palegreen' => '152,251,152', + 'paleturquoise' => '175,238,238', + 'palevioletred' => '219,112,147', + 'papayawhip' => '255,239,213', + 'peachpuff' => '255,218,185', + 'peru' => '205,133,63', + 'pink' => '255,192,203', + 'plum' => '221,160,221', + 'powderblue' => '176,224,230', + 'purple' => '128,0,128', + 'red' => '255,0,0', + 'rosybrown' => '188,143,143', + 'royalblue' => '65,105,225', + 'saddlebrown' => '139,69,19', + 'salmon' => '250,128,114', + 'sandybrown' => '244,164,96', + 'seagreen' => '46,139,87', + 'seashell' => '255,245,238', + 'sienna' => '160,82,45', + 'silver' => '192,192,192', + 'skyblue' => '135,206,235', + 'slateblue' => '106,90,205', + 'slategray' => '112,128,144', + 'slategrey' => '112,128,144', + 'snow' => '255,250,250', + 'springgreen' => '0,255,127', + 'steelblue' => '70,130,180', + 'tan' => '210,180,140', + 'teal' => '0,128,128', + 'thistle' => '216,191,216', + 'tomato' => '255,99,71', + 'transparent' => '0,0,0,0', + 'turquoise' => '64,224,208', + 'violet' => '238,130,238', + 'wheat' => '245,222,179', + 'white' => '255,255,255', + 'whitesmoke' => '245,245,245', + 'yellow' => '255,255,0', + 'yellowgreen' => '154,205,50' + ); +} + +// responsible for taking a string of LESS code and converting it into a +// syntax tree +class lessc_parser { + static protected $nextBlockId = 0; // used to uniquely identify blocks + + static protected $precedence = array( + '=<' => 0, + '>=' => 0, + '=' => 0, + '<' => 0, + '>' => 0, + + '+' => 1, + '-' => 1, + '*' => 2, + '/' => 2, + '%' => 2, + ); + + static protected $whitePattern; + static protected $commentMulti; + + static protected $commentSingle = "//"; + static protected $commentMultiLeft = "/*"; + static protected $commentMultiRight = "*/"; + + // regex string to match any of the operators + static protected $operatorString; + + // these properties will supress division unless it's inside parenthases + static protected $supressDivisionProps = + array('/border-radius$/i', '/^font$/i'); + + protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport"); + protected $lineDirectives = array("charset"); + + /** + * if we are in parens we can be more liberal with whitespace around + * operators because it must evaluate to a single value and thus is less + * ambiguous. + * + * Consider: + * property1: 10 -5; // is two numbers, 10 and -5 + * property2: (10 -5); // should evaluate to 5 + */ + protected $inParens = false; + + // caches preg escaped literals + static protected $literalCache = array(); + + public function __construct($lessc, $sourceName = null) { + $this->eatWhiteDefault = true; + // reference to less needed for vPrefix, mPrefix, and parentSelector + $this->lessc = $lessc; + + $this->sourceName = $sourceName; // name used for error messages + + $this->writeComments = false; + + if (!self::$operatorString) { + self::$operatorString = + '('.implode('|', array_map(array('lessc', 'preg_quote'), + array_keys(self::$precedence))).')'; + + $commentSingle = lessc::preg_quote(self::$commentSingle); + $commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft); + $commentMultiRight = lessc::preg_quote(self::$commentMultiRight); + + self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight; + self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais'; + } + } + + public function parse($buffer) { + $this->count = 0; + $this->line = 1; + + $this->env = null; // block stack + $this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer); + $this->pushSpecialBlock("root"); + $this->eatWhiteDefault = true; + $this->seenComments = array(); + + // trim whitespace on head + // if (preg_match('/^\s+/', $this->buffer, $m)) { + // $this->line += substr_count($m[0], "\n"); + // $this->buffer = ltrim($this->buffer); + // } + $this->whitespace(); + + // parse the entire file + $lastCount = $this->count; + while (false !== $this->parseChunk()); + + if ($this->count != strlen($this->buffer)) + $this->throwError(); + + // TODO report where the block was opened + if (!is_null($this->env->parent)) + throw new exception('parse error: unclosed block'); + + return $this->env; + } + + /** + * Parse a single chunk off the head of the buffer and append it to the + * current parse environment. + * Returns false when the buffer is empty, or when there is an error. + * + * This function is called repeatedly until the entire document is + * parsed. + * + * This parser is most similar to a recursive descent parser. Single + * functions represent discrete grammatical rules for the language, and + * they are able to capture the text that represents those rules. + * + * Consider the function lessc::keyword(). (all parse functions are + * structured the same) + * + * The function takes a single reference argument. When calling the + * function it will attempt to match a keyword on the head of the buffer. + * If it is successful, it will place the keyword in the referenced + * argument, advance the position in the buffer, and return true. If it + * fails then it won't advance the buffer and it will return false. + * + * All of these parse functions are powered by lessc::match(), which behaves + * the same way, but takes a literal regular expression. Sometimes it is + * more convenient to use match instead of creating a new function. + * + * Because of the format of the functions, to parse an entire string of + * grammatical rules, you can chain them together using &&. + * + * But, if some of the rules in the chain succeed before one fails, then + * the buffer position will be left at an invalid state. In order to + * avoid this, lessc::seek() is used to remember and set buffer positions. + * + * Before parsing a chain, use $s = $this->seek() to remember the current + * position into $s. Then if a chain fails, use $this->seek($s) to + * go back where we started. + */ + protected function parseChunk() { + if (empty($this->buffer)) return false; + $s = $this->seek(); + + // setting a property + if ($this->keyword($key) && $this->assign() && + $this->propertyValue($value, $key) && $this->end()) + { + $this->append(array('assign', $key, $value), $s); + return true; + } else { + $this->seek($s); + } + + + // look for special css blocks + if ($this->literal('@', false)) { + $this->count--; + + // media + if ($this->literal('@media')) { + if (($this->mediaQueryList($mediaQueries) || true) + && $this->literal('{')) + { + $media = $this->pushSpecialBlock("media"); + $media->queries = is_null($mediaQueries) ? array() : $mediaQueries; + return true; + } else { + $this->seek($s); + return false; + } + } + + if ($this->literal("@", false) && $this->keyword($dirName)) { + if ($this->isDirective($dirName, $this->blockDirectives)) { + if (($this->openString("{", $dirValue, null, array(";")) || true) && + $this->literal("{")) + { + $dir = $this->pushSpecialBlock("directive"); + $dir->name = $dirName; + if (isset($dirValue)) $dir->value = $dirValue; + return true; + } + } elseif ($this->isDirective($dirName, $this->lineDirectives)) { + if ($this->propertyValue($dirValue) && $this->end()) { + $this->append(array("directive", $dirName, $dirValue)); + return true; + } + } + } + + $this->seek($s); + } + + // setting a variable + if ($this->variable($var) && $this->assign() && + $this->propertyValue($value) && $this->end()) + { + $this->append(array('assign', $var, $value), $s); + return true; + } else { + $this->seek($s); + } + + if ($this->import($importValue)) { + $this->append($importValue, $s); + return true; + } + + // opening parametric mixin + if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) && + ($this->guards($guards) || true) && + $this->literal('{')) + { + $block = $this->pushBlock($this->fixTags(array($tag))); + $block->args = $args; + $block->isVararg = $isVararg; + if (!empty($guards)) $block->guards = $guards; + return true; + } else { + $this->seek($s); + } + + // opening a simple block + if ($this->tags($tags) && $this->literal('{')) { + $tags = $this->fixTags($tags); + $this->pushBlock($tags); + return true; + } else { + $this->seek($s); + } + + // closing a block + if ($this->literal('}', false)) { + try { + $block = $this->pop(); + } catch (exception $e) { + $this->seek($s); + $this->throwError($e->getMessage()); + } + + $hidden = false; + if (is_null($block->type)) { + $hidden = true; + if (!isset($block->args)) { + foreach ($block->tags as $tag) { + if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) { + $hidden = false; + break; + } + } + } + + foreach ($block->tags as $tag) { + if (is_string($tag)) { + $this->env->children[$tag][] = $block; + } + } + } + + if (!$hidden) { + $this->append(array('block', $block), $s); + } + + // this is done here so comments aren't bundled into he block that + // was just closed + $this->whitespace(); + return true; + } + + // mixin + if ($this->mixinTags($tags) && + ($this->argumentValues($argv) || true) && + ($this->keyword($suffix) || true) && $this->end()) + { + $tags = $this->fixTags($tags); + $this->append(array('mixin', $tags, $argv, $suffix), $s); + return true; + } else { + $this->seek($s); + } + + // spare ; + if ($this->literal(';')) return true; + + return false; // got nothing, throw error + } + + protected function isDirective($dirname, $directives) { + // TODO: cache pattern in parser + $pattern = implode("|", + array_map(array("lessc", "preg_quote"), $directives)); + $pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i'; + + return preg_match($pattern, $dirname); + } + + protected function fixTags($tags) { + // move @ tags out of variable namespace + foreach ($tags as &$tag) { + if ($tag{0} == $this->lessc->vPrefix) + $tag[0] = $this->lessc->mPrefix; + } + return $tags; + } + + // a list of expressions + protected function expressionList(&$exps) { + $values = array(); + + while ($this->expression($exp)) { + $values[] = $exp; + } + + if (count($values) == 0) return false; + + $exps = lessc::compressList($values, ' '); + return true; + } + + /** + * Attempt to consume an expression. + * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code + */ + protected function expression(&$out) { + if ($this->value($lhs)) { + $out = $this->expHelper($lhs, 0); + + // look for / shorthand + if (!empty($this->env->supressedDivision)) { + unset($this->env->supressedDivision); + $s = $this->seek(); + if ($this->literal("/") && $this->value($rhs)) { + $out = array("list", "", + array($out, array("keyword", "/"), $rhs)); + } else { + $this->seek($s); + } + } + + return true; + } + return false; + } + + /** + * recursively parse infix equation with $lhs at precedence $minP + */ + protected function expHelper($lhs, $minP) { + $this->inExp = true; + $ss = $this->seek(); + + while (true) { + $whiteBefore = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + + // If there is whitespace before the operator, then we require + // whitespace after the operator for it to be an expression + $needWhite = $whiteBefore && !$this->inParens; + + if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) { + if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) { + foreach (self::$supressDivisionProps as $pattern) { + if (preg_match($pattern, $this->env->currentProperty)) { + $this->env->supressedDivision = true; + break 2; + } + } + } + + + $whiteAfter = isset($this->buffer[$this->count - 1]) && + ctype_space($this->buffer[$this->count - 1]); + + if (!$this->value($rhs)) break; + + // peek for next operator to see what to do with rhs + if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) { + $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]); + } + + $lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter); + $ss = $this->seek(); + + continue; + } + + break; + } + + $this->seek($ss); + + return $lhs; + } + + // consume a list of values for a property + public function propertyValue(&$value, $keyName = null) { + $values = array(); + + if ($keyName !== null) $this->env->currentProperty = $keyName; + + $s = null; + while ($this->expressionList($v)) { + $values[] = $v; + $s = $this->seek(); + if (!$this->literal(',')) break; + } + + if ($s) $this->seek($s); + + if ($keyName !== null) unset($this->env->currentProperty); + + if (count($values) == 0) return false; + + $value = lessc::compressList($values, ', '); + return true; + } + + protected function parenValue(&$out) { + $s = $this->seek(); + + // speed shortcut + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") { + return false; + } + + $inParens = $this->inParens; + if ($this->literal("(") && + ($this->inParens = true) && $this->expression($exp) && + $this->literal(")")) + { + $out = $exp; + $this->inParens = $inParens; + return true; + } else { + $this->inParens = $inParens; + $this->seek($s); + } + + return false; + } + + // a single value + protected function value(&$value) { + $s = $this->seek(); + + // speed shortcut + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") { + // negation + if ($this->literal("-", false) && + (($this->variable($inner) && $inner = array("variable", $inner)) || + $this->unit($inner) || + $this->parenValue($inner))) + { + $value = array("unary", "-", $inner); + return true; + } else { + $this->seek($s); + } + } + + if ($this->parenValue($value)) return true; + if ($this->unit($value)) return true; + if ($this->color($value)) return true; + if ($this->func($value)) return true; + if ($this->string($value)) return true; + + if ($this->keyword($word)) { + $value = array('keyword', $word); + return true; + } + + // try a variable + if ($this->variable($var)) { + $value = array('variable', $var); + return true; + } + + // unquote string (should this work on any type? + if ($this->literal("~") && $this->string($str)) { + $value = array("escape", $str); + return true; + } else { + $this->seek($s); + } + + // css hack: \0 + if ($this->literal('\\') && $this->match('([0-9]+)', $m)) { + $value = array('keyword', '\\'.$m[1]); + return true; + } else { + $this->seek($s); + } + + return false; + } + + // an import statement + protected function import(&$out) { + $s = $this->seek(); + if (!$this->literal('@import')) return false; + + // @import "something.css" media; + // @import url("something.css") media; + // @import url(something.css) media; + + if ($this->propertyValue($value)) { + $out = array("import", $value); + return true; + } + } + + protected function mediaQueryList(&$out) { + if ($this->genericList($list, "mediaQuery", ",", false)) { + $out = $list[2]; + return true; + } + return false; + } + + protected function mediaQuery(&$out) { + $s = $this->seek(); + + $expressions = null; + $parts = array(); + + if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) { + $prop = array("mediaType"); + if (isset($only)) $prop[] = "only"; + if (isset($not)) $prop[] = "not"; + $prop[] = $mediaType; + $parts[] = $prop; + } else { + $this->seek($s); + } + + + if (!empty($mediaType) && !$this->literal("and")) { + // ~ + } else { + $this->genericList($expressions, "mediaExpression", "and", false); + if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]); + } + + if (count($parts) == 0) { + $this->seek($s); + return false; + } + + $out = $parts; + return true; + } + + protected function mediaExpression(&$out) { + $s = $this->seek(); + $value = null; + if ($this->literal("(") && + $this->keyword($feature) && + ($this->literal(":") && $this->expression($value) || true) && + $this->literal(")")) + { + $out = array("mediaExp", $feature); + if ($value) $out[] = $value; + return true; + } elseif ($this->variable($variable)) { + $out = array('variable', $variable); + return true; + } + + $this->seek($s); + return false; + } + + // an unbounded string stopped by $end + protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + $stop = array("'", '"', "@{", $end); + $stop = array_map(array("lessc", "preg_quote"), $stop); + // $stop[] = self::$commentMulti; + + if (!is_null($rejectStrs)) { + $stop = array_merge($stop, $rejectStrs); + } + + $patt = '(.*?)('.implode("|", $stop).')'; + + $nestingLevel = 0; + + $content = array(); + while ($this->match($patt, $m, false)) { + if (!empty($m[1])) { + $content[] = $m[1]; + if ($nestingOpen) { + $nestingLevel += substr_count($m[1], $nestingOpen); + } + } + + $tok = $m[2]; + + $this->count-= strlen($tok); + if ($tok == $end) { + if ($nestingLevel == 0) { + break; + } else { + $nestingLevel--; + } + } + + if (($tok == "'" || $tok == '"') && $this->string($str)) { + $content[] = $str; + continue; + } + + if ($tok == "@{" && $this->interpolation($inter)) { + $content[] = $inter; + continue; + } + + if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) { + break; + } + + $content[] = $tok; + $this->count+= strlen($tok); + } + + $this->eatWhiteDefault = $oldWhite; + + if (count($content) == 0) return false; + + // trim the end + if (is_string(end($content))) { + $content[count($content) - 1] = rtrim(end($content)); + } + + $out = array("string", "", $content); + return true; + } + + protected function string(&$out) { + $s = $this->seek(); + if ($this->literal('"', false)) { + $delim = '"'; + } elseif ($this->literal("'", false)) { + $delim = "'"; + } else { + return false; + } + + $content = array(); + + // look for either ending delim , escape, or string interpolation + $patt = '([^\n]*?)(@\{|\\\\|' . + lessc::preg_quote($delim).')'; + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + while ($this->match($patt, $m, false)) { + $content[] = $m[1]; + if ($m[2] == "@{") { + $this->count -= strlen($m[2]); + if ($this->interpolation($inter, false)) { + $content[] = $inter; + } else { + $this->count += strlen($m[2]); + $content[] = "@{"; // ignore it + } + } elseif ($m[2] == '\\') { + $content[] = $m[2]; + if ($this->literal($delim, false)) { + $content[] = $delim; + } + } else { + $this->count -= strlen($delim); + break; // delim + } + } + + $this->eatWhiteDefault = $oldWhite; + + if ($this->literal($delim)) { + $out = array("string", $delim, $content); + return true; + } + + $this->seek($s); + return false; + } + + protected function interpolation(&$out) { + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = true; + + $s = $this->seek(); + if ($this->literal("@{") && + $this->openString("}", $interp, null, array("'", '"', ";")) && + $this->literal("}", false)) + { + $out = array("interpolate", $interp); + $this->eatWhiteDefault = $oldWhite; + if ($this->eatWhiteDefault) $this->whitespace(); + return true; + } + + $this->eatWhiteDefault = $oldWhite; + $this->seek($s); + return false; + } + + protected function unit(&$unit) { + // speed shortcut + if (isset($this->buffer[$this->count])) { + $char = $this->buffer[$this->count]; + if (!ctype_digit($char) && $char != ".") return false; + } + + if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) { + $unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]); + return true; + } + return false; + } + + // a # color + protected function color(&$out) { + if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) { + if (strlen($m[1]) > 7) { + $out = array("string", "", array($m[1])); + } else { + $out = array("raw_color", $m[1]); + } + return true; + } + + return false; + } + + // consume a list of property values delimited by ; and wrapped in () + protected function argumentValues(&$args, $delim = ',') { + $s = $this->seek(); + if (!$this->literal('(')) return false; + + $values = array(); + while (true) { + if ($this->expressionList($value)) $values[] = $value; + if (!$this->literal($delim)) break; + else { + if ($value == null) $values[] = null; + $value = null; + } + } + + if (!$this->literal(')')) { + $this->seek($s); + return false; + } + + $args = $values; + return true; + } + + // consume an argument definition list surrounded by () + // each argument is a variable name with optional value + // or at the end a ... or a variable named followed by ... + protected function argumentDef(&$args, &$isVararg, $delim = ',') { + $s = $this->seek(); + if (!$this->literal('(')) return false; + + $values = array(); + + $isVararg = false; + while (true) { + if ($this->literal("...")) { + $isVararg = true; + break; + } + + if ($this->variable($vname)) { + $arg = array("arg", $vname); + $ss = $this->seek(); + if ($this->assign() && $this->expressionList($value)) { + $arg[] = $value; + } else { + $this->seek($ss); + if ($this->literal("...")) { + $arg[0] = "rest"; + $isVararg = true; + } + } + $values[] = $arg; + if ($isVararg) break; + continue; + } + + if ($this->value($literal)) { + $values[] = array("lit", $literal); + } + + if (!$this->literal($delim)) break; + } + + if (!$this->literal(')')) { + $this->seek($s); + return false; + } + + $args = $values; + + return true; + } + + // consume a list of tags + // this accepts a hanging delimiter + protected function tags(&$tags, $simple = false, $delim = ',') { + $tags = array(); + while ($this->tag($tt, $simple)) { + $tags[] = $tt; + if (!$this->literal($delim)) break; + } + if (count($tags) == 0) return false; + + return true; + } + + // list of tags of specifying mixin path + // optionally separated by > (lazy, accepts extra >) + protected function mixinTags(&$tags) { + $s = $this->seek(); + $tags = array(); + while ($this->tag($tt, true)) { + $tags[] = $tt; + $this->literal(">"); + } + + if (count($tags) == 0) return false; + + return true; + } + + // a bracketed value (contained within in a tag definition) + protected function tagBracket(&$value) { + // speed shortcut + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") { + return false; + } + + $s = $this->seek(); + if ($this->literal('[') && $this->to(']', $c, true) && $this->literal(']', false)) { + $value = '['.$c.']'; + // whitespace? + if ($this->whitespace()) $value .= " "; + + // escape parent selector, (yuck) + $value = str_replace($this->lessc->parentSelector, "$&$", $value); + return true; + } + + $this->seek($s); + return false; + } + + protected function tagExpression(&$value) { + $s = $this->seek(); + if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) { + $value = array('exp', $exp); + return true; + } + + $this->seek($s); + return false; + } + + // a space separated list of selectors + protected function tag(&$tag, $simple = false) { + if ($simple) + $chars = '^@,:;{}\][>\(\) "\''; + else + $chars = '^@,;{}["\''; + + $s = $this->seek(); + + if (!$simple && $this->tagExpression($tag)) { + return true; + } + + $hasExpression = false; + $parts = array(); + while ($this->tagBracket($first)) $parts[] = $first; + + $oldWhite = $this->eatWhiteDefault; + $this->eatWhiteDefault = false; + + while (true) { + if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) { + $parts[] = $m[1]; + if ($simple) break; + + while ($this->tagBracket($brack)) { + $parts[] = $brack; + } + continue; + } + + if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") { + if ($this->interpolation($interp)) { + $hasExpression = true; + $interp[2] = true; // don't unescape + $parts[] = $interp; + continue; + } + + if ($this->literal("@")) { + $parts[] = "@"; + continue; + } + } + + if ($this->unit($unit)) { // for keyframes + $parts[] = $unit[1]; + $parts[] = $unit[2]; + continue; + } + + break; + } + + $this->eatWhiteDefault = $oldWhite; + if (!$parts) { + $this->seek($s); + return false; + } + + if ($hasExpression) { + $tag = array("exp", array("string", "", $parts)); + } else { + $tag = trim(implode($parts)); + } + + $this->whitespace(); + return true; + } + + // a css function + protected function func(&$func) { + $s = $this->seek(); + + if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) { + $fname = $m[1]; + + $sPreArgs = $this->seek(); + + $args = array(); + while (true) { + $ss = $this->seek(); + // this ugly nonsense is for ie filter properties + if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) { + $args[] = array("string", "", array($name, "=", $value)); + } else { + $this->seek($ss); + if ($this->expressionList($value)) { + $args[] = $value; + } + } + + if (!$this->literal(',')) break; + } + $args = array('list', ',', $args); + + if ($this->literal(')')) { + $func = array('function', $fname, $args); + return true; + } elseif ($fname == 'url') { + // couldn't parse and in url? treat as string + $this->seek($sPreArgs); + if ($this->openString(")", $string) && $this->literal(")")) { + $func = array('function', $fname, $string); + return true; + } + } + } + + $this->seek($s); + return false; + } + + // consume a less variable + protected function variable(&$name) { + $s = $this->seek(); + if ($this->literal($this->lessc->vPrefix, false) && + ($this->variable($sub) || $this->keyword($name))) + { + if (!empty($sub)) { + $name = array('variable', $sub); + } else { + $name = $this->lessc->vPrefix.$name; + } + return true; + } + + $name = null; + $this->seek($s); + return false; + } + + /** + * Consume an assignment operator + * Can optionally take a name that will be set to the current property name + */ + protected function assign($name = null) { + if ($name) $this->currentProperty = $name; + return $this->literal(':') || $this->literal('='); + } + + // consume a keyword + protected function keyword(&$word) { + if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) { + $word = $m[1]; + return true; + } + return false; + } + + // consume an end of statement delimiter + protected function end() { + if ($this->literal(';')) { + return true; + } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') { + // if there is end of file or a closing block next then we don't need a ; + return true; + } + return false; + } + + protected function guards(&$guards) { + $s = $this->seek(); + + if (!$this->literal("when")) { + $this->seek($s); + return false; + } + + $guards = array(); + + while ($this->guardGroup($g)) { + $guards[] = $g; + if (!$this->literal(",")) break; + } + + if (count($guards) == 0) { + $guards = null; + $this->seek($s); + return false; + } + + return true; + } + + // a bunch of guards that are and'd together + // TODO rename to guardGroup + protected function guardGroup(&$guardGroup) { + $s = $this->seek(); + $guardGroup = array(); + while ($this->guard($guard)) { + $guardGroup[] = $guard; + if (!$this->literal("and")) break; + } + + if (count($guardGroup) == 0) { + $guardGroup = null; + $this->seek($s); + return false; + } + + return true; + } + + protected function guard(&$guard) { + $s = $this->seek(); + $negate = $this->literal("not"); + + if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) { + $guard = $exp; + if ($negate) $guard = array("negate", $guard); + return true; + } + + $this->seek($s); + return false; + } + + /* raw parsing functions */ + + protected function literal($what, $eatWhitespace = null) { + if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault; + + // shortcut on single letter + if (!isset($what[1]) && isset($this->buffer[$this->count])) { + if ($this->buffer[$this->count] == $what) { + if (!$eatWhitespace) { + $this->count++; + return true; + } + // goes below... + } else { + return false; + } + } + + if (!isset(self::$literalCache[$what])) { + self::$literalCache[$what] = lessc::preg_quote($what); + } + + return $this->match(self::$literalCache[$what], $m, $eatWhitespace); + } + + protected function genericList(&$out, $parseItem, $delim="", $flatten=true) { + $s = $this->seek(); + $items = array(); + while ($this->$parseItem($value)) { + $items[] = $value; + if ($delim) { + if (!$this->literal($delim)) break; + } + } + + if (count($items) == 0) { + $this->seek($s); + return false; + } + + if ($flatten && count($items) == 1) { + $out = $items[0]; + } else { + $out = array("list", $delim, $items); + } + + return true; + } + + + // advance counter to next occurrence of $what + // $until - don't include $what in advance + // $allowNewline, if string, will be used as valid char set + protected function to($what, &$out, $until = false, $allowNewline = false) { + if (is_string($allowNewline)) { + $validChars = $allowNewline; + } else { + $validChars = $allowNewline ? "." : "[^\n]"; + } + if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false; + if ($until) $this->count -= strlen($what); // give back $what + $out = $m[1]; + return true; + } + + // try to match something on head of buffer + protected function match($regex, &$out, $eatWhitespace = null) { + if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault; + + $r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais'; + if (preg_match($r, $this->buffer, $out, null, $this->count)) { + $this->count += strlen($out[0]); + if ($eatWhitespace && $this->writeComments) $this->whitespace(); + return true; + } + return false; + } + + // match some whitespace + protected function whitespace() { + if ($this->writeComments) { + $gotWhite = false; + while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) { + if (isset($m[1]) && empty($this->commentsSeen[$this->count])) { + $this->append(array("comment", $m[1])); + $this->commentsSeen[$this->count] = true; + } + $this->count += strlen($m[0]); + $gotWhite = true; + } + return $gotWhite; + } else { + $this->match("", $m); + return strlen($m[0]) > 0; + } + } + + // match something without consuming it + protected function peek($regex, &$out = null, $from=null) { + if (is_null($from)) $from = $this->count; + $r = '/'.$regex.'/Ais'; + $result = preg_match($r, $this->buffer, $out, null, $from); + + return $result; + } + + // seek to a spot in the buffer or return where we are on no argument + protected function seek($where = null) { + if ($where === null) return $this->count; + else $this->count = $where; + return true; + } + + /* misc functions */ + + public function throwError($msg = "parse error", $count = null) { + $count = is_null($count) ? $this->count : $count; + + $line = $this->line + + substr_count(substr($this->buffer, 0, $count), "\n"); + + if (!empty($this->sourceName)) { + $loc = "$this->sourceName on line $line"; + } else { + $loc = "line: $line"; + } + + // TODO this depends on $this->count + if ($this->peek("(.*?)(\n|$)", $m, $count)) { + throw new exception("$msg: failed at `$m[1]` $loc"); + } else { + throw new exception("$msg: $loc"); + } + } + + protected function pushBlock($selectors=null, $type=null) { + $b = new stdclass; + $b->parent = $this->env; + + $b->type = $type; + $b->id = self::$nextBlockId++; + + $b->isVararg = false; // TODO: kill me from here + $b->tags = $selectors; + + $b->props = array(); + $b->children = array(); + + $this->env = $b; + return $b; + } + + // push a block that doesn't multiply tags + protected function pushSpecialBlock($type) { + return $this->pushBlock(null, $type); + } + + // append a property to the current block + protected function append($prop, $pos = null) { + if ($pos !== null) $prop[-1] = $pos; + $this->env->props[] = $prop; + } + + // pop something off the stack + protected function pop() { + $old = $this->env; + $this->env = $this->env->parent; + return $old; + } + + // remove comments from $text + // todo: make it work for all functions, not just url + protected function removeComments($text) { + $look = array( + 'url(', '//', '/*', '"', "'" + ); + + $out = ''; + $min = null; + while (true) { + // find the next item + foreach ($look as $token) { + $pos = strpos($text, $token); + if ($pos !== false) { + if (!isset($min) || $pos < $min[1]) $min = array($token, $pos); + } + } + + if (is_null($min)) break; + + $count = $min[1]; + $skip = 0; + $newlines = 0; + switch ($min[0]) { + case 'url(': + if (preg_match('/url\(.*?\)/', $text, $m, 0, $count)) + $count += strlen($m[0]) - strlen($min[0]); + break; + case '"': + case "'": + if (preg_match('/'.$min[0].'.*?'.$min[0].'/', $text, $m, 0, $count)) + $count += strlen($m[0]) - 1; + break; + case '//': + $skip = strpos($text, "\n", $count); + if ($skip === false) $skip = strlen($text) - $count; + else $skip -= $count; + break; + case '/*': + if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) { + $skip = strlen($m[0]); + $newlines = substr_count($m[0], "\n"); + } + break; + } + + if ($skip == 0) $count += strlen($min[0]); + + $out .= substr($text, 0, $count).str_repeat("\n", $newlines); + $text = substr($text, $count + $skip); + + $min = null; + } + + return $out.$text; + } + +} + +class lessc_formatter_classic { + public $indentChar = " "; + + public $break = "\n"; + public $open = " {"; + public $close = "}"; + public $selectorSeparator = ", "; + public $assignSeparator = ":"; + + public $openSingle = " { "; + public $closeSingle = " }"; + + public $disableSingle = false; + public $breakSelectors = false; + + public $compressColors = false; + + public function __construct() { + $this->indentLevel = 0; + } + + public function indentStr($n = 0) { + return str_repeat($this->indentChar, max($this->indentLevel + $n, 0)); + } + + public function property($name, $value) { + return $name . $this->assignSeparator . $value . ";"; + } + + protected function isEmpty($block) { + if (empty($block->lines)) { + foreach ($block->children as $child) { + if (!$this->isEmpty($child)) return false; + } + + return true; + } + return false; + } + + public function block($block) { + if ($this->isEmpty($block)) return; + + $inner = $pre = $this->indentStr(); + + $isSingle = !$this->disableSingle && + is_null($block->type) && count($block->lines) == 1; + + if (!empty($block->selectors)) { + $this->indentLevel++; + + if ($this->breakSelectors) { + $selectorSeparator = $this->selectorSeparator . $this->break . $pre; + } else { + $selectorSeparator = $this->selectorSeparator; + } + + echo $pre . + implode($selectorSeparator, $block->selectors); + if ($isSingle) { + echo $this->openSingle; + $inner = ""; + } else { + echo $this->open . $this->break; + $inner = $this->indentStr(); + } + + } + + if (!empty($block->lines)) { + $glue = $this->break.$inner; + echo $inner . implode($glue, $block->lines); + if (!$isSingle && !empty($block->children)) { + echo $this->break; + } + } + + foreach ($block->children as $child) { + $this->block($child); + } + + if (!empty($block->selectors)) { + if (!$isSingle && empty($block->children)) echo $this->break; + + if ($isSingle) { + echo $this->closeSingle . $this->break; + } else { + echo $pre . $this->close . $this->break; + } + + $this->indentLevel--; + } + } +} + +class lessc_formatter_compressed extends lessc_formatter_classic { + public $disableSingle = true; + public $open = "{"; + public $selectorSeparator = ","; + public $assignSeparator = ":"; + public $break = ""; + public $compressColors = true; + + public function indentStr($n = 0) { + return ""; + } +} + +class lessc_formatter_lessjs extends lessc_formatter_classic { + public $disableSingle = true; + public $breakSelectors = true; + public $assignSeparator = ": "; + public $selectorSeparator = ","; +} + + diff --git a/inc/load.php b/inc/load.php index 7fb328215be28cbd46ebf85a75941b8ed6c7e185..923671296c298e127fceb5b11ca10c8965aa465a 100644 --- a/inc/load.php +++ b/inc/load.php @@ -86,12 +86,13 @@ function load_autoload($name){ 'Crypt_Rijndael' => DOKU_INC.'inc/phpseclib/Crypt_Rijndael.php', 'Crypt_AES' => DOKU_INC.'inc/phpseclib/Crypt_AES.php', 'Crypt_Hash' => DOKU_INC.'inc/phpseclib/Crypt_Hash.php', + 'lessc' => DOKU_INC.'inc/lessc.inc.php', 'DokuWiki_Action_Plugin' => DOKU_PLUGIN.'action.php', 'DokuWiki_Admin_Plugin' => DOKU_PLUGIN.'admin.php', 'DokuWiki_Syntax_Plugin' => DOKU_PLUGIN.'syntax.php', 'DokuWiki_Remote_Plugin' => DOKU_PLUGIN.'remote.php', - 'DokuWiki_Auth_Plugin' => DOKU_PLUGIN.'auth.php', + 'DokuWiki_Auth_Plugin' => DOKU_PLUGIN.'auth.php', ); diff --git a/lib/exe/css.php b/lib/exe/css.php index 768c8eda486328feaa222f8fcbc1621b8aaab1ca..83972d4078a7d649bc1e3e184fa2aa7b8774e4fb 100644 --- a/lib/exe/css.php +++ b/lib/exe/css.php @@ -131,6 +131,8 @@ function css_out(){ // load files $css_content = ''; foreach($files[$mediatype] as $file => $location){ + $display = str_replace(fullpath(DOKU_INC), '', fullpath($file)); + $css_content .= "\n/* XXXXXXXXX $display XXXXXXXXX */\n"; $css_content .= css_loadfile($file, $location); } switch ($mediatype) { @@ -154,7 +156,10 @@ function css_out(){ // apply style replacements $css = css_applystyle($css,$tplinc); - // place all @import statements at the top of the file + // parse less + $css = css_parseless($css); + + // place all remaining @import statements at the top of the file $css = css_moveimports($css); // compress whitespace and comments @@ -171,17 +176,88 @@ function css_out(){ http_cached_finish($cache->cache, $css); } +/** + * Uses phpless to parse LESS in our CSS + * + * most of this function is error handling to show a nice useful error when + * LESS compilation fails + * + * @param $css + * @return string + */ +function css_parseless($css) { + $less = new lessc(); + try { + return $less->compile($css); + } catch(Exception $e) { + // get exception message + $msg = str_replace(array("\n", "\r", "'"), array(), $e->getMessage()); + + // try to use line number to find affected file + if(preg_match('/line: (\d+)$/', $msg, $m)){ + $msg = substr($msg, 0, -1* strlen($m[0])); //remove useless linenumber + $lno = $m[1]; + + // walk upwards to last include + $lines = explode("\n", $css); + $count = count($lines); + for($i=$lno-1; $i>=0; $i--){ + if(preg_match('/\/(\* XXXXXXXXX )(.*?)( XXXXXXXXX \*)\//', $lines[$i], $m)){ + // we found it, add info to message + $msg .= ' in '.$m[2].' at line '.($lno-$i); + break; + } + } + } + + // something went wrong + $error = 'A fatal error occured during compilation of the CSS files. '. + 'If you recently installed a new plugin or template it '. + 'might be broken and you should try disabling it again. ['.$msg.']'; + + echo ".dokuwiki:before { + content: '$error'; + background-color: red; + display: block; + background-color: #fcc; + border-color: #ebb; + color: #000; + padding: 0.5em; + }"; + + exit; + } +} + /** * Does placeholder replacements in the style according to * the ones defined in a templates style.ini file * + * This also adds the ini defined placeholders as less variables + * (sans the surrounding __ and with a ini_ prefix) + * * @author Andreas Gohr <andi@splitbrain.org> */ function css_applystyle($css,$tplinc){ $styleini = css_styleini($tplinc); if($styleini){ - $css = strtr($css,$styleini['replacements']); + // we convert ini replacements to LESS variable names + // and build a list of variable: value; pairs + $less = ''; + foreach($styleini['replacements'] as $key => $value){ + $lkey = trim($key, '_'); + $lkey = '@ini_'.$lkey; + $less .= "$lkey: $value;\n"; + + $styleini['replacements'][$key] = $lkey; + } + + // we now replace all old ini replacements with LESS variables + $css = strtr($css, $styleini['replacements']); + + // now prepend the list of LESS variables as the very first thing + $css = $less.$css; } return $css; } @@ -333,9 +409,11 @@ function css_pluginstyles($mediatype='screen'){ $plugins = plugin_list(); foreach ($plugins as $p){ $list[DOKU_PLUGIN."$p/$mediatype.css"] = DOKU_BASE."lib/plugins/$p/"; + $list[DOKU_PLUGIN."$p/$mediatype.less"] = DOKU_BASE."lib/plugins/$p/"; // alternative for screen.css if ($mediatype=='screen') { $list[DOKU_PLUGIN."$p/style.css"] = DOKU_BASE."lib/plugins/$p/"; + $list[DOKU_PLUGIN."$p/style.less"] = DOKU_BASE."lib/plugins/$p/"; } // @deprecated 2012-04-09: rtl will cease to be a mode of its own, // please use "[dir=rtl]" in any css file in all, screen or print mode instead diff --git a/lib/tpl/dokuwiki/css/_admin.css b/lib/tpl/dokuwiki/css/_admin.css index c8f3694b5b387028e1bdfe4e56bb75e239057633..a9518d0edad78816caabd780e9bff409796ef1ec 100644 --- a/lib/tpl/dokuwiki/css/_admin.css +++ b/lib/tpl/dokuwiki/css/_admin.css @@ -50,7 +50,7 @@ .dokuwiki #admin__version { clear: left; float: right; - color: __text_neu__; + color: @ini_text_neu; background-color: inherit; } [dir=rtl] .dokuwiki #admin__version { diff --git a/lib/tpl/dokuwiki/css/_diff.css b/lib/tpl/dokuwiki/css/_diff.css index 58c24b5c79b840595350f6cff7f62ae99b40ca59..b7c82d82914d2252142b45682a272f311e5083c8 100644 --- a/lib/tpl/dokuwiki/css/_diff.css +++ b/lib/tpl/dokuwiki/css/_diff.css @@ -19,7 +19,7 @@ /* table header */ .dokuwiki table.diff th { - border-bottom: 1px solid __border__; + border-bottom: 1px solid @ini_border; font-size: 110%; font-weight: normal; } diff --git a/lib/tpl/dokuwiki/css/_edit.css b/lib/tpl/dokuwiki/css/_edit.css index 92ce621269070ffde1334d777c1101aeaa4c8b06..f40aaa89199e383750a30872e97a8884e397743e 100644 --- a/lib/tpl/dokuwiki/css/_edit.css +++ b/lib/tpl/dokuwiki/css/_edit.css @@ -16,7 +16,7 @@ } #draft__status { float: right; - color: __text_alt__; + color: @ini_text_alt; background-color: inherit; } [dir=rtl] #draft__status { @@ -35,8 +35,8 @@ /* picker popups (outside of .dokuwiki) */ div.picker { width: 300px; - border: 1px solid __border__; - background-color: __background_alt__; + border: 1px solid @ini_border; + background-color: @ini_background_alt; color: inherit; } /* picker for headlines */ @@ -106,7 +106,7 @@ div.picker button.toolbutton { } /* change background colour if summary is missing */ .dokuwiki .editBar .summary input.missing { - color: __text__; + color: @ini_text; background-color: #ffcccc; } @@ -114,7 +114,7 @@ div.picker button.toolbutton { ********************************************************************/ .dokuwiki div.preview { - border: dotted __border__; + border: dotted @ini_border; border-width: .2em 0; padding: 1.4em 0; margin-bottom: 1.4em; @@ -138,6 +138,6 @@ div.picker button.toolbutton { .dokuwiki div.section_highlight { margin: 0 -1em; /* negative side margin = side padding + side border */ padding: 0 .5em; - border: solid __background_alt__; + border: solid @ini_background_alt; border-width: 0 .5em; } diff --git a/lib/tpl/dokuwiki/css/_fileuploader.css b/lib/tpl/dokuwiki/css/_fileuploader.css index 42004de2832715b4ce25a8bee098377cde6440e8..3c2cd4683ab6379c6a7454f3add6cd1a2d9e9a3b 100644 --- a/lib/tpl/dokuwiki/css/_fileuploader.css +++ b/lib/tpl/dokuwiki/css/_fileuploader.css @@ -42,8 +42,8 @@ height: 100%; min-height: 70px; z-index: 2; - background: __background_neu__; - color: __text__; + background: @ini_background_neu; + color: @ini_text; text-align: center; } @@ -57,7 +57,7 @@ } .qq-upload-drop-area-active { - background: __background_alt__; + background: @ini_background_alt; } /* list of files to upload */ @@ -70,7 +70,7 @@ div.qq-uploader ul { .qq-uploader li { margin: 0 0 5px; - color: __text__; + color: @ini_text; } .qq-uploader li span, diff --git a/lib/tpl/dokuwiki/css/_footnotes.css b/lib/tpl/dokuwiki/css/_footnotes.css index a20f2964ebb829a67c7ed7159ef33bf3e25d1a4a..5d5f7ca300d8adc6b88de5bde13600984f3240f4 100644 --- a/lib/tpl/dokuwiki/css/_footnotes.css +++ b/lib/tpl/dokuwiki/css/_footnotes.css @@ -16,7 +16,7 @@ div.insitu-footnote { /*____________ footnotes at the bottom of the page ____________*/ .dokuwiki div.footnotes { - border-top: 1px solid __border__; + border-top: 1px solid @ini_border; padding: .5em 0 0 0; margin: 1em 0 0 0; clear: both; diff --git a/lib/tpl/dokuwiki/css/_forms.css b/lib/tpl/dokuwiki/css/_forms.css index 84b7db8e169b42ec1bbe58558025d5865609099a..4d3f2b97afcb9bedda515075ad52689f00274ff2 100644 --- a/lib/tpl/dokuwiki/css/_forms.css +++ b/lib/tpl/dokuwiki/css/_forms.css @@ -48,7 +48,7 @@ .dokuwiki fieldset { width: 400px; text-align: center; - border: 1px solid __border__; + border: 1px solid @ini_border; padding: 0.5em; margin: auto; } diff --git a/lib/tpl/dokuwiki/css/_media_fullscreen.css b/lib/tpl/dokuwiki/css/_media_fullscreen.css index 8d5e1e8ca9be631aacdd93ecb09539bd0edeb918..28e347882ecbed8773af0c38170a32300a0c88d8 100644 --- a/lib/tpl/dokuwiki/css/_media_fullscreen.css +++ b/lib/tpl/dokuwiki/css/_media_fullscreen.css @@ -38,7 +38,7 @@ } #mediamanager__page .panelHeader { - background-color: __background_alt__; + background-color: @ini_background_alt; margin: 0 10px 10px 0; padding: 10px 10px 8px; text-align: left; @@ -68,7 +68,7 @@ background: transparent url(../../images/resizecol.png) center center no-repeat; } #mediamanager__page .ui-resizable-e:hover { - background-color: __background_alt__; + background-color: @ini_background_alt; } @@ -99,10 +99,10 @@ margin: 0 0 0 .3em; border-radius: .5em .5em 0 0; font-weight: normal; - background-color: __background_alt__; - color: __text__; - border: 1px solid __border__; - border-bottom-color: __background_alt__; + background-color: @ini_background_alt; + color: @ini_text; + border: 1px solid @ini_border; + border-bottom-color: @ini_background_alt; line-height: 1.4em; position: relative; bottom: -1px; @@ -118,7 +118,7 @@ right: 10px; } #mediamanager__page .namespaces .panelHeader { - border-top: 1px solid __border__; + border-top: 1px solid @ini_border; z-index: 1; } @@ -164,7 +164,7 @@ padding: 0; } #mediamanager__page .panelHeader ul li { - color: __text__; + color: @ini_text; float: left; line-height: 1; padding-left: 3px; @@ -205,7 +205,7 @@ } #mediamanager__page .filelist .panelContent ul li:hover { - background-color: __background_alt__; + background-color: @ini_background_alt; } #mediamanager__page .filelist li dt a { @@ -231,8 +231,8 @@ display: -moz-inline-stack; /* the right margin should visually be 10px, but because of its inline-block nature the whitespace inbetween is about 4px more */ margin: 0 6px 10px 0; - background-color: __background_neu__; - color: __text__; + background-color: @ini_background_neu; + color: @ini_text; padding: 5px; vertical-align: top; text-align: center; @@ -287,13 +287,13 @@ max-height: 50px; margin: 0; margin-bottom: 3px; - background-color: __background__; - color: __text__; + background-color: @ini_background; + color: @ini_text; overflow: hidden; } #mediamanager__page .filelist .rows li:nth-child(2n+1) { - background-color: __background_neu__; + background-color: @ini_background_neu; } #mediamanager__page .filelist .rows li dt { @@ -372,11 +372,11 @@ #mediamanager__page .file dl dt { font-weight: bold; display: block; - background-color: __background_alt__; + background-color: @ini_background_alt; } #mediamanager__page .file dl dd { display: block; - background-color: __background_neu__; + background-color: @ini_background_neu; } @@ -415,7 +415,7 @@ #mediamanager__page #page__revisions ul li div.li div { font-size: 90%; - color: __text_neu__; + color: @ini_text_neu; padding-left: 18px; } @@ -438,7 +438,7 @@ padding: 0; vertical-align: top; text-align: left; - border-color: __background__; + border-color: @ini_background; } [dir=rtl] #mediamanager__diff td, [dir=rtl] #mediamanager__diff th { @@ -447,7 +447,7 @@ #mediamanager__diff th { font-weight: normal; - background-color: __background__; + background-color: @ini_background; line-height: 1.2; } #mediamanager__diff th a { @@ -459,7 +459,7 @@ #mediamanager__diff dl dd strong{ background-color: __highlight__; - color: __text__; + color: @ini_text; font-weight: normal; } diff --git a/lib/tpl/dokuwiki/css/_media_popup.css b/lib/tpl/dokuwiki/css/_media_popup.css index c776e6b8a929e8d8a024bee039213f1987255663..1fefd68b60b7434f12055b2981f0476fc049321a 100644 --- a/lib/tpl/dokuwiki/css/_media_popup.css +++ b/lib/tpl/dokuwiki/css/_media_popup.css @@ -20,13 +20,13 @@ html.popup { overflow: auto; position: absolute; left: 0; - border-right: 1px solid __border__; + border-right: 1px solid @ini_border; } [dir=rtl] #mediamgr__aside { left: auto; right: 0; border-right-width: 0; - border-left: 1px solid __border__; + border-left: 1px solid @ini_border; } #mediamgr__aside .pad { padding: .5em; @@ -52,7 +52,7 @@ html.popup { font-size: 1.5em; margin-bottom: .5em; padding-bottom: .2em; - border-bottom: 1px solid __border__; + border-bottom: 1px solid @ini_border; } /* left side @@ -141,13 +141,13 @@ html.popup { padding: .5em; } #media__content .odd { - background-color: __background_alt__; + background-color: @ini_background_alt; } #media__content .even { } /* highlight newly uploaded or edited file */ #media__content #scroll__here { - border: 1px dashed __border__; + border: 1px dashed @ini_border; } /* link which inserts media file */ @@ -167,7 +167,7 @@ html.popup { /* info how to insert media, if JS disabled */ #media__content div.example { - color: __text_neu__; + color: @ini_text_neu; margin-left: 1em; } diff --git a/lib/tpl/dokuwiki/css/_modal.css b/lib/tpl/dokuwiki/css/_modal.css index a3d3be1943110819e2ca0d3a00a7a02a77081904..a46dff30e0370555b919bcd3e696c15feeb51264 100644 --- a/lib/tpl/dokuwiki/css/_modal.css +++ b/lib/tpl/dokuwiki/css/_modal.css @@ -18,11 +18,11 @@ } #link__wiz_result { - background-color: __background__; + background-color: @ini_background; width: 293px; height: 193px; overflow: auto; - border: 1px solid __border__; + border: 1px solid @ini_border; margin: 3px auto; text-align: left; line-height: 1; @@ -57,16 +57,16 @@ } #link__wiz_result div.even { - background-color: __background_neu__; + background-color: @ini_background_neu; } #link__wiz_result div.selected { - background-color: __background_alt__; + background-color: @ini_background_alt; } #link__wiz_result span { display: block; - color: __text_neu__; + color: @ini_text_neu; margin-left: 22px; } diff --git a/lib/tpl/dokuwiki/css/_search.css b/lib/tpl/dokuwiki/css/_search.css index 0090308c933384cb23ea07d64df382e866b6afbc..a8972ae725868eea71fa4d3742b2d412b509f11a 100644 --- a/lib/tpl/dokuwiki/css/_search.css +++ b/lib/tpl/dokuwiki/css/_search.css @@ -44,14 +44,14 @@ } /* search snippet */ .dokuwiki dl.search_results dd { - color: __text_alt__; + color: @ini_text_alt; background-color: inherit; margin: 0 0 1.2em 0; } /* search hit in normal text */ .dokuwiki .search_hit { - color: __text__; + color: @ini_text; background-color: __highlight__; } /* search hit in search results */ @@ -60,7 +60,7 @@ } /* ellipsis separating snippets */ .dokuwiki .search_results .search_sep { - color: __text__; + color: @ini_text; background-color: inherit; } diff --git a/lib/tpl/dokuwiki/css/_tabs.css b/lib/tpl/dokuwiki/css/_tabs.css index 845ec9a57b73be892fc0884b225d0c2643952f0a..860545a27c62bde34fe2d67c1793cbfdce30338d 100644 --- a/lib/tpl/dokuwiki/css/_tabs.css +++ b/lib/tpl/dokuwiki/css/_tabs.css @@ -17,7 +17,7 @@ width: 100%; bottom: 0; left: 0; - border-bottom: 1px solid __border__; + border-bottom: 1px solid @ini_border; z-index: 1; } @@ -39,9 +39,9 @@ display: inline-block; padding: .3em .8em; margin: 0 0 0 .3em; - background-color: __background_neu__; - color: __text__; - border: 1px solid __border__; + background-color: @ini_background_neu; + color: @ini_text; + border: 1px solid @ini_border; border-radius: .5em .5em 0 0; position: relative; z-index: 0; @@ -68,8 +68,8 @@ .dokuwiki ul.tabs li a:active, .dokuwiki ul.tabs li a:focus, .dokuwiki ul.tabs li strong { - background-color: __background_alt__; - color: __text__; + background-color: @ini_background_alt; + color: @ini_text; text-decoration: none; font-weight: normal; } @@ -78,5 +78,5 @@ .dokuwiki .tabs > ul li .active a, .dokuwiki ul.tabs li strong { z-index: 2; - border-bottom-color: __background_alt__; + border-bottom-color: @ini_background_alt; } diff --git a/lib/tpl/dokuwiki/css/_toc.css b/lib/tpl/dokuwiki/css/_toc.css index 1226b5b5bb77073f67c3e7d186ae8a7baf38a64c..469e9278cba85f017aa7b1f12c702300bd756874 100644 --- a/lib/tpl/dokuwiki/css/_toc.css +++ b/lib/tpl/dokuwiki/css/_toc.css @@ -11,7 +11,7 @@ float: right; margin: 0 0 1.4em 1.4em; width: 12em; - background-color: __background_alt__; + background-color: @ini_background_alt; color: inherit; } [dir=rtl] #dw__toc { diff --git a/lib/tpl/dokuwiki/css/basic.css b/lib/tpl/dokuwiki/css/basic.less similarity index 78% rename from lib/tpl/dokuwiki/css/basic.css rename to lib/tpl/dokuwiki/css/basic.less index ba263f4c5bacfbd3bc11f7e8fedb0fed20fd081d..f0ce10b51f7ff2ed3f039a036bc9a6e17f0ce92e 100644 --- a/lib/tpl/dokuwiki/css/basic.css +++ b/lib/tpl/dokuwiki/css/basic.less @@ -14,8 +14,8 @@ html { } html, body { - color: __text__; - background: __background_site__ url(images/page-gradient.png) top left repeat-x; + color: @ini_text; + background: @ini_background_site url(images/page-gradient.png) top left repeat-x; margin: 0; padding: 0; } @@ -160,7 +160,7 @@ table { border-collapse: collapse; empty-cells: show; border-spacing: 0; - border: 1px solid __border__; + border: 1px solid @ini_border; } caption { @@ -176,11 +176,11 @@ td { padding: .3em .5em; margin: 0; vertical-align: top; - border: 1px solid __border__; + border: 1px solid @ini_border; } th { font-weight: bold; - background-color: __background_alt__; + background-color: @ini_background_alt; text-align: left; } [dir=rtl] th { @@ -196,7 +196,7 @@ a { a:link, a:visited { text-decoration: none; - color: __link__; + color: @ini_link; } a:link:hover, a:visited:hover, @@ -233,8 +233,8 @@ button img { } hr { - border-top: solid __border__; - border-bottom: solid __background__; + border-top: solid @ini_border; + border-bottom: solid @ini_background; border-width: 1px 0; height: 0; text-align: center; @@ -253,7 +253,7 @@ em abbr { } mark { - background-color: __highlight__; + background-color: @ini_highlight; color: inherit; } @@ -266,23 +266,23 @@ kbd { font-size: 1em; direction: ltr; text-align: left; - background-color: __background_site__; - color: __text__; - box-shadow: inset 0 0 .3em __border__; + background-color: @ini_background_site; + color: @ini_text; + box-shadow: inset 0 0 .3em @ini_border; border-radius: 2px; } pre { overflow: auto; word-wrap: normal; - border: 1px solid __border__; + border: 1px solid @ini_border; border-radius: 2px; - box-shadow: inset 0 0 .5em __border__; + box-shadow: inset 0 0 .5em @ini_border; padding: .7em 1em; } blockquote { padding: 0 .5em; - border: solid __border__; + border: solid @ini_border; border-width: 0 0 0 .25em; } [dir=rtl] blockquote { @@ -321,7 +321,7 @@ form { fieldset { padding: .7em 1em 0; padding: .7rem 1rem; /* for those browsers understanding :last-child */ - border: 1px solid __text_alt__; + border: 1px solid @ini_text_alt; } fieldset > :last-child { margin-bottom: 0; @@ -405,12 +405,8 @@ button, .qq-upload-button { color: #333; background-color: #eee; - background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPgo8bGluZWFyR3JhZGllbnQgaWQ9Imc4MjQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRkZGRkYiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNGNEY0RjQiIG9mZnNldD0iMC4zIi8+PHN0b3Agc3RvcC1jb2xvcj0iI0VFRUVFRSIgb2Zmc2V0PSIwLjk5Ii8+PHN0b3Agc3RvcC1jb2xvcj0iI0NDQ0NDQyIgb2Zmc2V0PSIuOTkiLz4KPC9saW5lYXJHcmFkaWVudD4KPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNnODI0KSIgLz4KPC9zdmc+'); - background: -moz-linear-gradient( top, #ffffff 0%, #f4f4f4 30%, #eeeeee 99%, #cccccc 99%); - background: -webkit-linear-gradient(top, #ffffff 0%, #f4f4f4 30%, #eeeeee 99%, #cccccc 99%); - background: -o-linear-gradient( top, #ffffff 0%, #f4f4f4 30%, #eeeeee 99%, #cccccc 99%); - background: -ms-linear-gradient( top, #ffffff 0%, #f4f4f4 30%, #eeeeee 99%, #cccccc 99%); - background: linear-gradient( top, #ffffff 0%, #f4f4f4 30%, #eeeeee 99%, #cccccc 99%); + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPgo8bGluZWFyR3JhZGllbnQgaWQ9Imc4MjQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRkZGRkYiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNGNEY0RjQiIG9mZnNldD0iMC4zIi8+PHN0b3Agc3RvcC1jb2xvcj0iI0VFRUVFRSIgb2Zmc2V0PSIwLjk5Ii8+PHN0b3Agc3RvcC1jb2xvcj0iI0NDQ0NDQyIgb2Zmc2V0PSIuOTkiLz4KPC9saW5lYXJHcmFkaWVudD4KPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNnODI0KSIgLz4KPC9zdmc+); + .linear-gradient("top, #ffffff 0%, #f4f4f4 30%, #eeeeee 99%, #cccccc 99%"); border: 1px solid #ccc; border-radius: 2px; padding: .1em .5em; @@ -443,12 +439,17 @@ button:focus, .qq-upload-button:hover { border-color: #999; background-color: #ddd; +<<<<<<< HEAD:lib/tpl/dokuwiki/css/basic.css background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPgo8bGluZWFyR3JhZGllbnQgaWQ9Imc2NzAiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRkZGRkYiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNGNEY0RjQiIG9mZnNldD0iMC4zIi8+PHN0b3Agc3RvcC1jb2xvcj0iI0RERERERCIgb2Zmc2V0PSIwLjk5Ii8+PHN0b3Agc3RvcC1jb2xvcj0iI0JCQkJCQiIgb2Zmc2V0PSIuOTkiLz4KPC9saW5lYXJHcmFkaWVudD4KPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNnNjcwKSIgLz4KPC9zdmc+'); background: -moz-linear-gradient( top, #ffffff 0%, #f4f4f4 30%, #dddddd 99%, #bbbbbb 99%); background: -webkit-linear-gradient(top, #ffffff 0%, #f4f4f4 30%, #dddddd 99%, #bbbbbb 99%); background: -o-linear-gradient( top, #ffffff 0%, #f4f4f4 30%, #dddddd 99%, #bbbbbb 99%); background: -ms-linear-gradient( top, #ffffff 0%, #f4f4f4 30%, #dddddd 99%, #bbbbbb 99%); background: linear-gradient( top, #ffffff 0%, #f4f4f4 30%, #dddddd 99%, #bbbbbb 99%); +======= + background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHZpZXdCb3g9IjAgMCAxIDEiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiPgo8bGluZWFyR3JhZGllbnQgaWQ9Imc2NzAiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjAlIiB5Mj0iMTAwJSI+CjxzdG9wIHN0b3AtY29sb3I9IiNGRkZGRkYiIG9mZnNldD0iMCIvPjxzdG9wIHN0b3AtY29sb3I9IiNGNEY0RjQiIG9mZnNldD0iMC4zIi8+PHN0b3Agc3RvcC1jb2xvcj0iI0RERERERCIgb2Zmc2V0PSIwLjk5Ii8+PHN0b3Agc3RvcC1jb2xvcj0iI0JCQkJCQiIgb2Zmc2V0PSIuOTkiLz4KPC9saW5lYXJHcmFkaWVudD4KPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNnNjcwKSIgLz4KPC9zdmc+); + .linear-gradient("top, #ffffff 0%, #f4f4f4 30%, #dddddd 99%, #bbbbbb 99%"); +>>>>>>> less:lib/tpl/dokuwiki/css/basic.less } input::-moz-focus-inner, diff --git a/lib/tpl/dokuwiki/css/content.css b/lib/tpl/dokuwiki/css/content.less similarity index 56% rename from lib/tpl/dokuwiki/css/content.css rename to lib/tpl/dokuwiki/css/content.less index b1498d4deb8187a164a389a52f8555c4db7cff19..56551fe3be3667b099f5b79791da773ff0f030fd 100644 --- a/lib/tpl/dokuwiki/css/content.css +++ b/lib/tpl/dokuwiki/css/content.less @@ -32,54 +32,56 @@ */ /* hx margin-left = (1 / font-size) * .levelx-margin */ - /*____________ links to wiki pages (addition to _links) ____________*/ /* existing wikipage */ .dokuwiki a.wikilink1 { - color: __existing__; + color: @ini_existing; background-color: inherit; } + /* not existing wikipage */ .dokuwiki a.wikilink2 { - color: __missing__; + color: @ini_missing; background-color: inherit; } - /*____________ images ____________*/ /* embedded images (styles are already partly set in lib/styles/all.css) */ .dokuwiki img.media { margin: .2em 0; } + .dokuwiki img.medialeft { margin: .2em 1em .2em 0; } + .dokuwiki img.mediaright { margin: .2em 0 .2em 1em; } + .dokuwiki img.mediacenter { margin: .2em auto; } - /*____________ lists ____________*/ #dokuwiki__content ul li, #dokuwiki__aside ul li { - color: __text_alt__; + color: @ini_text_alt; } + #dokuwiki__content ol li, #dokuwiki__aside ol li { - color: __text_neu__; + color: @ini_text_neu; } + #dokuwiki__content li .li, #dokuwiki__aside li .li { - color: __text__; + color: @ini_text; } - /*____________ tables ____________*/ /* div around each table */ @@ -87,6 +89,7 @@ overflow-x: auto; margin-bottom: 1.4em; } + .dokuwiki div.table table { margin-bottom: 0; } @@ -94,14 +97,15 @@ .dokuwiki table.inline { min-width: 50%; } + .dokuwiki table.inline tr:hover td { - background-color: __background_alt__; + background-color: @ini_background_alt; } + .dokuwiki table.inline tr:hover th { - background-color: __border__; + background-color: @ini_border; } - /*____________ code ____________*/ /* fix if background-color hides underlining */ @@ -116,66 +120,61 @@ /* filenames for downloadable file and code blocks */ .dokuwiki dl.code, .dokuwiki dl.file { + dt { + background-color: @ini_background_site; + .linear-gradient(~"top, @{ini_background_alt} 0%, @{ini_background_site} 100%"); + color: inherit; + border: 1px solid @ini_border; + border-bottom-color: @ini_background_site; + border-top-left-radius: .3em; + border-top-right-radius: .3em; + padding: .3em .6em .1em; + margin-bottom: -1px; + float: left; + + a { + background-color: transparent; + font-size: 0.875em; + font-weight: normal; + display: block; + min-height: 16px; + } + } + + dd { + margin: 0; + clear: left; + min-height: 1px; /* for IE7 */ + } + + pre { + box-shadow: inset -4px -4px .5em -.3em @ini_border; + } +} + +[dir=rtl] .dokuwiki dl.code, +[dir=rtl] .dokuwiki dl.file { + dt { + float: right; + } + + dd { + clear: right; + } } -.dokuwiki dl.code dt, -.dokuwiki dl.file dt { - background-color: __background_site__; - background: -moz-linear-gradient( top, __background_alt__ 0%, __background_site__ 100%); - background: -webkit-linear-gradient(top, __background_alt__ 0%, __background_site__ 100%); - background: -o-linear-gradient( top, __background_alt__ 0%, __background_site__ 100%); - background: -ms-linear-gradient( top, __background_alt__ 0%, __background_site__ 100%); - background: linear-gradient( top, __background_alt__ 0%, __background_site__ 100%); - color: inherit; - border: 1px solid __border__; - border-bottom-color: __background_site__; - border-top-left-radius: .3em; - border-top-right-radius: .3em; - padding: .3em .6em .1em; - margin-bottom: -1px; - float: left; -} -[dir=rtl] .dokuwiki dl.code dt, -[dir=rtl] .dokuwiki dl.file dt { - float: right; -} -.dokuwiki dl.code dt a, -.dokuwiki dl.file dt a { - background-color: transparent; - font-size: 0.875em; - font-weight: normal; - display: block; - min-height: 16px; -} - -.dokuwiki dl.code dd, -.dokuwiki dl.file dd { - margin: 0; - clear: left; - min-height: 1px; /* for IE7 */ -} -[dir=rtl] .dokuwiki dl.code dd, -[dir=rtl] .dokuwiki dl.file dd { - clear: right; -} - -.dokuwiki dl.code pre, -.dokuwiki dl.file pre { - box-shadow: inset -4px -4px .5em -.3em __border__; -} - - /*____________ JS popup ____________*/ .JSpopup { - background-color: __background__; - color: __text__; - border: 1px solid __border__; - box-shadow: .1em .1em .1em __border__; + background-color: @ini_background; + color: @ini_text; + border: 1px solid @ini_border; + box-shadow: .1em .1em .1em @ini_border; border-radius: 2px; padding: .3em .5em; font-size: .9em; } + .dokuwiki form.search div.ajax_qsearch { top: -.35em; font-size: 1em; @@ -186,12 +185,12 @@ .JSpopup ol { padding-left: 0; } + [dir=rtl] .JSpopup ul, [dir=rtl] .JSpopup ol { padding-right: 0; } - /* changes to underscored CSS files ********************************************************************/ @@ -202,41 +201,49 @@ #dokuwiki__content span.curid a { font-weight: normal; } + #dokuwiki__content strong span.curid a { font-weight: bold; } - /*____________ changes to _edit ____________*/ -.dokuwiki div.toolbar button.toolbutton { - border-radius: 0; - border-left-width: 0; - padding: .1em .35em; -} -.dokuwiki div.toolbar button.toolbutton:first-child { - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - border-left-width: 1px; -} -[dir=rtl] .dokuwiki div.toolbar button.toolbutton:first-child { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - border-left-width: 0; - border-right-width: 1px; -} -.dokuwiki div.toolbar button.toolbutton:last-child { - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; -} -[dir=rtl] .dokuwiki div.toolbar button.toolbutton:last-child { - border-top-left-radius: 4px; - border-bottom-left-radius: 4px; - border-top-right-radius: 0; - border-bottom-right-radius: 0; - border-left-width: 1px; +.dokuwiki div.toolbar { + button.toolbutton { + border-radius: 0; + border-left-width: 0; + padding: .1em .35em; + } + + button.toolbutton:first-child { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + border-left-width: 1px; + } + + button.toolbutton:last-child { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } +} + +[dir=rtl] .dokuwiki div.toolbar { + button.toolbutton:last-child { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-left-width: 1px; + } + + button.toolbutton:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + border-left-width: 0; + border-right-width: 1px; + } } .dokuwiki div.section_highlight { @@ -253,31 +260,34 @@ margin: 0 -2em; padding: 0 2em; } + .dokuwiki.hasSidebar div.preview { - border-right: __sidebar_width__ solid __background_alt__; + border-right: @ini_sidebar_width solid @ini_background_alt; } + [dir=rtl] .dokuwiki.hasSidebar div.preview { border-right-width: 0; - border-left: __sidebar_width__ solid __background_alt__; + border-left: @ini_sidebar_width solid @ini_background_alt; } + .dokuwiki div.preview div.pad { padding: 1.556em 0 2em; } - /*____________ changes to _toc ____________*/ #dw__toc { - margin: -1.556em -2em .5em 1.4em; - width: __sidebar_width__; - border-left: 1px solid __border__; - background: __background__; + margin: -1.556em -2em .5em 1.4em; + width: @ini_sidebar_width; + border-left: 1px solid @ini_border; + background: @ini_background; color: inherit; } + [dir=rtl] #dw__toc { margin: -1.556em 1.4em .5em -2em; border-left-width: 0; - border-right: 1px solid __border__; + border-right: 1px solid @ini_border; } .dokuwiki h3.toggle { @@ -286,6 +296,7 @@ font-size: .875em; letter-spacing: .1em; } + #dokuwiki__aside h3.toggle { display: none; } @@ -296,6 +307,7 @@ height: 5px; margin: .4em 0 0; } + .dokuwiki .toggle.closed strong { background-position: 0 -5px; } @@ -304,59 +316,72 @@ display: none; } +#dw__toc { + > div { + font-size: 0.875em; + padding: .5em 1em 1em; + } -#dw__toc > div { - font-size: 0.875em; - padding: .5em 1em 1em; -} -#dw__toc ul { - padding: 0 0 0 1.2em; + ul { + padding: 0 0 0 1.2em; + + li { + list-style-image: url(images/toc-bullet.png); + } + } + + ul li.clear { + list-style: none; + } + + ul li div.li { + padding: .2em 0; + } } + [dir=rtl] #dw__toc ul { padding: 0 1.5em 0 0; } -#dw__toc ul li { - list-style-image: url(images/toc-bullet.png); -} -#dw__toc ul li.clear { - list-style: none; -} -#dw__toc ul li div.li { - padding: .2em 0; -} - /*____________ changes to _imgdetail ____________*/ #dokuwiki__detail { padding: 0; -} -#dokuwiki__detail img { - float: none; - margin-bottom: 1.4em; -} -#dokuwiki__detail div.img_detail { - float: none; -} -#dokuwiki__detail div.img_detail dl { - overflow: hidden; -} -#dokuwiki__detail div.img_detail dl dt { - float: left; - width: 9em; - text-align: right; - clear: left; -} -[dir=rtl] #dokuwiki__detail div.img_detail dl dt { - float: right; - text-align: left; - clear: right; -} -#dokuwiki__detail div.img_detail dl dd { - margin-left: 9.5em; -} -[dir=rtl] #dokuwiki__detail div.img_detail dl dd { - margin-left: 0; - margin-right: 9.5em; -} + img { + float: none; + margin-bottom: 1.4em; + } + + div.img_detail { + float: none; + } + + div.img_detail dl { + overflow: hidden; + } + + div.img_detail dl dt { + float: left; + width: 9em; + text-align: right; + clear: left; + } + + div.img_detail dl dd { + margin-left: 9.5em; + } +} + +[dir=rtl] #dokuwiki__detail div.img_detail { + dl dt { + float: right; + text-align: left; + clear: right; + } + + dl dd { + margin-left: 0; + margin-right: 9.5em; + } +} \ No newline at end of file diff --git a/lib/tpl/dokuwiki/css/design.css b/lib/tpl/dokuwiki/css/design.css deleted file mode 100644 index 457414839edd33bdd87da32875bd414b237e1b11..0000000000000000000000000000000000000000 --- a/lib/tpl/dokuwiki/css/design.css +++ /dev/null @@ -1,405 +0,0 @@ -/** - * This file provides the main design styles for the - * bits that surround the content. - * - * @author Anika Henke <anika@selfthinker.org> - * @author Andreas Gohr <andi@splitbrain.org> - * @author Clarence Lee <clarencedglee@gmail.com> - */ - -/* header -********************************************************************/ - -#dokuwiki__header { - padding: 2em 0 1.5em; -} - -#dokuwiki__header .headings, -#dokuwiki__header .tools { - margin-bottom: 1.5em; - width: 49%; -} -#dokuwiki__header h1 img { - float: left; - margin-right: .5em; -} -[dir=rtl] #dokuwiki__header h1 img { - float: right; - margin-left: .5em; - margin-right: 0; -} -#dokuwiki__header h1 span { - display: block; - padding-top: 10px; -} -#dokuwiki__header h1 { - margin: 0; - font-size: 1.5em; - font-weight: normal; -} -#dokuwiki__header h1 a { - text-decoration: none; - color: __text__; - background-color: inherit; -} -#dokuwiki__header h1 a:hover, -#dokuwiki__header h1 a:active, -#dokuwiki__header h1 a:focus { -} -#dokuwiki__header p.claim { - margin-bottom: 0; - font-size: 0.875em; -} - -#dokuwiki__header .tools { - margin-top: .2em; -} - - -/* tools -********************************************************************/ - -/* highlight selected tool */ -.mode_admin a.action.admin, -.mode_login a.action.login, -.mode_register a.action.register, -.mode_profile a.action.profile, -.mode_recent a.action.recent, -.mode_index a.action.index, -.mode_media a.action.media, -.mode_revisions a.action.revs, -.mode_backlink a.action.backlink, -.mode_subscribe a.action.subscribe { - font-weight: bold; -} - -#dokuwiki__header .tools ul { - padding-left: 0; - margin-bottom: 0; -} -#dokuwiki__header .tools li { - font-size: 0.875em; - margin-left: 1em; - list-style: none; - display: inline; -} -[dir=rtl] #dokuwiki__header .tools li { - margin-right: 1em; - margin-left: 0; -} -#dokuwiki__header .tools form.search div.ajax_qsearch li { - font-size: 1em; - margin-left: 0; - display: block; - overflow: hidden; - text-overflow: ellipsis; -} - -#dokuwiki__usertools a.action { - padding-left: 20px; - background: transparent url(images/usertools.png) no-repeat 0 0; -} -[dir=rtl] #dokuwiki__usertools a.action { - padding-left: 0; - padding-right: 20px; -} -[dir=rtl] #IE7 #dokuwiki__usertools a.action { - display: inline-block; -} - - -#dokuwiki__header .mobileTools { - display: none; /* hide mobile tools dropdown to only show in mobile view */ -} - -/*____________ user tools ____________*/ - -#dokuwiki__usertools { - position: absolute; - top: .5em; - right: .5em; - text-align: right; - width: 100%; -} -[dir=rtl] #dokuwiki__usertools { - text-align: left; - left: 40px; - right: auto; -} -#dokuwiki__usertools ul { - margin: 0 auto; - padding: 0; - max-width: __site_width__; -} -#dokuwiki__usertools ul li.user { -} - -#dokuwiki__usertools a.action.admin { - background-position: left 0; -} -[dir=rtl] #dokuwiki__usertools a.action.admin { - background-position: right 0; -} -#dokuwiki__usertools a.action.profile { - background-position: left -32px; -} -[dir=rtl] #dokuwiki__usertools a.action.profile { - background-position: right -32px; -} -#dokuwiki__usertools a.action.register { - background-position: left -64px; -} -[dir=rtl] #dokuwiki__usertools a.action.register { - background-position: right -64px; -} -#dokuwiki__usertools a.action.login { - background-position: left -96px; -} -[dir=rtl] #dokuwiki__usertools a.action.login { - background-position: right -96px; -} -#dokuwiki__usertools a.action.logout { - background-position: left -128px; -} -[dir=rtl] #dokuwiki__usertools a.action.logout { - background-position: right -128px; -} - - -/*____________ site tools ____________*/ - -#dokuwiki__sitetools { - text-align: right; -} -[dir=rtl] #dokuwiki__sitetools { - text-align: left; -} - -#dokuwiki__sitetools form.search { - display: block; - font-size: 0.875em; - position: relative; -} -#IE7 #dokuwiki__sitetools form.search { - min-height: 1px; - z-index: 21; -} -#dokuwiki__sitetools form.search input.edit { - width: 18em; - padding: .35em 22px .35em .1em; -} -[dir=rtl] #dokuwiki__sitetools form.search input.edit { - padding: .35em .1em .35em 22px; -} -#dokuwiki__sitetools form.search input.button { - background: transparent url(images/search.png) no-repeat 0 0; - border-width: 0; - width: 19px; - height: 14px; - text-indent: -99999px; - margin-left: -20px; - box-shadow: none; - padding: 0; -} -[dir=rtl] #dokuwiki__sitetools form.search input.button { - background-position: 5px 0; - margin-left: 0; - margin-right: -20px; - position: relative; -} - -#dokuwiki__sitetools ul { - margin-top: 0.5em; -} -#dokuwiki__sitetools li { -} - -/*____________ breadcrumbs ____________*/ - -.dokuwiki div.breadcrumbs { - border-top: 1px solid __border__; - border-bottom: 1px solid __background__; - margin-bottom: .5em; - font-size: 0.875em; - clear: both; -} -.dokuwiki div.breadcrumbs div { - padding: .1em .35em; -} - -.dokuwiki div.breadcrumbs div:only-child { - border-top: 1px solid __background__; - border-bottom: 1px solid __border__; -} -.dokuwiki div.breadcrumbs div:first-child { - border-top: 1px solid __background__; -} -#IE7 .dokuwiki div.breadcrumbs div, -#IE8 .dokuwiki div.breadcrumbs div { - border-bottom: 1px solid __border__; -} -.dokuwiki div.breadcrumbs div:last-child { - border-bottom: 1px solid __border__; -} - -.dokuwiki div.breadcrumbs a { - color: __link__; - background-color: inherit; -} -.dokuwiki div.breadcrumbs .bcsep { - font-size: 0.75em; -} - - -/* sidebar -********************************************************************/ - -#dokuwiki__aside { -} -#dokuwiki__aside > .pad { - font-size: 0.875em; - overflow: hidden; - word-wrap: break-word; -} - -/* make sidebar more condensed */ - -#dokuwiki__aside h1 { - font-size: 1.714em; - margin-bottom: .292em; -} -#dokuwiki__aside h2 { - margin-bottom: .333em; -} -#dokuwiki__aside h3 { - margin-bottom: .444em; -} -#dokuwiki__aside h4 { - margin-bottom: .5em; -} -#dokuwiki__aside h5 { - margin-bottom: .5714em; -} - -#dokuwiki__aside p, -#dokuwiki__aside ul, -#dokuwiki__aside ol, -#dokuwiki__aside dl, -#dokuwiki__aside pre, -#dokuwiki__aside table, -#dokuwiki__aside fieldset, -#dokuwiki__aside hr, -#dokuwiki__aside blockquote, -#dokuwiki__aside address { - margin-bottom: .7em; -} - -#dokuwiki__aside ul, -#dokuwiki__aside ol { - padding-left: .5em; -} -[dir=rtl] #dokuwiki__aside ul, -[dir=rtl] #dokuwiki__aside ol { - padding-right: .5em; -} -#dokuwiki__aside li ul, -#dokuwiki__aside li ol { - margin-bottom: 0; - padding: 0; -} - -#dokuwiki__aside a:link, -#dokuwiki__aside a:visited { - color: __link__; - background-color: inherit; -} - - -/* content -********************************************************************/ - -#dokuwiki__content { -} - -.dokuwiki .pageId { - position: absolute; - top: -2.3em; - right: -1em; - overflow: hidden; - padding: 1em 1em 0; -} -[dir=rtl] .dokuwiki .pageId { - right: auto; - left: -1em; -} -.dokuwiki .pageId span { - font-size: 0.875em; - border: solid __background_alt__; - border-width: 1px 1px 0; - background-color: __background__; - color: __text_alt__; - padding: .1em .35em; - border-top-left-radius: 2px; - border-top-right-radius: 2px; - box-shadow: 0 0 .5em __text_alt__; - display: block; -} - -.dokuwiki div.page { - background: __background__; - color: inherit; - border: 1px solid __background_alt__; - box-shadow: 0 0 .5em __text_alt__; - border-radius: 2px; - padding: 1.556em 2em 2em; - margin-bottom: .5em; - overflow: hidden; - word-wrap: break-word; -} - -.dokuwiki .docInfo { - font-size: 0.875em; - text-align: right; -} -[dir=rtl] .dokuwiki .docInfo { - text-align: left; -} - -/* license note under edit window */ -.dokuwiki div.license { - font-size: 93.75%; -} - - -/* footer -********************************************************************/ - -.dokuwiki .wrapper { - margin-bottom: 1.4em; -} - -#dokuwiki__footer { - margin-bottom: 1em; - text-align: center; -} -#dokuwiki__footer > .pad { - font-size: 0.875em; -} - -#dokuwiki__footer div.license { - margin-bottom: 0.5em; - font-size: 100%; -} - -[dir=rtl] #dokuwiki__footer .license img { - margin: 0 0 0 .5em; -} - -#dokuwiki__footer div.buttons a img { - opacity: 0.5; -} -#dokuwiki__footer div.buttons a:hover img, -#dokuwiki__footer div.buttons a:active img, -#dokuwiki__footer div.buttons a:focus img { - opacity: 1; -} diff --git a/lib/tpl/dokuwiki/css/design.less b/lib/tpl/dokuwiki/css/design.less new file mode 100644 index 0000000000000000000000000000000000000000..42292de4998a1852f821a571bed2757a6bedc762 --- /dev/null +++ b/lib/tpl/dokuwiki/css/design.less @@ -0,0 +1,439 @@ +/** + * This file provides the main design styles for the + * bits that surround the content. + * + * @author Anika Henke <anika@selfthinker.org> + * @author Andreas Gohr <andi@splitbrain.org> + * @author Clarence Lee <clarencedglee@gmail.com> + */ + +/* header +********************************************************************/ + +#dokuwiki__header { + padding: 2em 0 1.5em; + + .headings, + .tools { + margin-bottom: 1.5em; + width: 49%; + } + .tools { + margin-top: .2em; + } + + h1 { + margin: 0; + font-size: 1.5em; + font-weight: normal; + + img { + float: left; + margin-right: .5em; + } + + span { + display: block; + padding-top: 10px; + } + + a { + text-decoration: none; + color: @ini_text; + background-color: inherit; + } + } + + p.claim { + margin-bottom: 0; + font-size: 0.875em; + } +} + +[dir=rtl] #dokuwiki__header h1 img { + float: right; + margin-left: .5em; + margin-right: 0; +} + +/* tools +********************************************************************/ + +/* highlight selected tool */ +.mode_admin a.action.admin, +.mode_login a.action.login, +.mode_register a.action.register, +.mode_profile a.action.profile, +.mode_recent a.action.recent, +.mode_index a.action.index, +.mode_media a.action.media, +.mode_revisions a.action.revs, +.mode_backlink a.action.backlink, +.mode_subscribe a.action.subscribe { + font-weight: bold; +} + +#dokuwiki__header .tools { + ul { + padding-left: 0; + margin-bottom: 0; + } + + li { + font-size: 0.875em; + margin-left: 1em; + list-style: none; + display: inline; + } + + form.search div.ajax_qsearch li { + font-size: 1em; + margin-left: 0; + display: block; + overflow: hidden; + text-overflow: ellipsis; + } +} + +[dir=rtl] #dokuwiki__header .tools li { + margin-right: 1em; + margin-left: 0; +} + +#dokuwiki__usertools a.action { + padding-left: 20px; + background: transparent url(images/usertools.png) no-repeat 0 0; +} + +[dir=rtl] #dokuwiki__usertools a.action { + padding-left: 0; + padding-right: 20px; +} + +[dir=rtl] #IE7 #dokuwiki__usertools a.action { + display: inline-block; +} + +#dokuwiki__header .mobileTools { + display: none; /* hide mobile tools dropdown to only show in mobile view */ +} + +/*____________ user tools ____________*/ + +#dokuwiki__usertools { + position: absolute; + top: .5em; + right: .5em; + text-align: right; + width: 100%; + + ul { + margin: 0 auto; + padding: 0; + max-width: @ini_site_width; + } + + a.action.admin { + background-position: left 0; + } + + a.action.profile { + background-position: left -32px; + } + + a.action.register { + background-position: left -64px; + } + + a.action.login { + background-position: left -96px; + } + + a.action.logout { + background-position: left -128px; + } +} + +[dir=rtl] #dokuwiki__usertools { + text-align: left; + left: 40px; + right: auto; + + a.action.admin { + background-position: right 0; + } + + a.action.profile { + background-position: right -32px; + } + + a.action.register { + background-position: right -64px; + } + + a.action.login { + background-position: right -96px; + } + + a.action.logout { + background-position: right -128px; + } +} + +/*____________ site tools ____________*/ + +#dokuwiki__sitetools { + text-align: right; + + form.search { + display: block; + font-size: 0.875em; + position: relative; + + input.edit { + width: 18em; + padding: .35em 22px .35em .1em; + } + + input.button { + background: transparent url(images/search.png) no-repeat 0 0; + border-width: 0; + width: 19px; + height: 14px; + text-indent: -99999px; + margin-left: -20px; + box-shadow: none; + padding: 0; + } + } + + ul { + margin-top: 0.5em; + } +} + +[dir=rtl] #dokuwiki__sitetools { + text-align: left; + + form.search { + input.edit { + padding: .35em .1em .35em 22px; + } + + input.button { + background-position: 5px 0; + margin-left: 0; + margin-right: -20px; + position: relative; + } + } +} + +#IE7 #dokuwiki__sitetools form.search { + min-height: 1px; + z-index: 21; +} + +/*____________ breadcrumbs ____________*/ + +.dokuwiki div.breadcrumbs { + border-top: 1px solid @ini_border; + border-bottom: 1px solid @ini_background; + margin-bottom: .5em; + font-size: 0.875em; + clear: both; + + div { + padding: .1em .35em; + } + + div:only-child { + border-top: 1px solid @ini_background; + border-bottom: 1px solid @ini_border; + } + + div:first-child { + border-top: 1px solid @ini_background; + } + + div:last-child { + border-bottom: 1px solid @ini_border; + } + + a { + color: @ini_link; + background-color: inherit; + } + + .bcsep { + font-size: 0.75em; + } +} + +#IE7 .dokuwiki div.breadcrumbs div, +#IE8 .dokuwiki div.breadcrumbs div { + border-bottom: 1px solid @ini_border; +} + +/* sidebar +********************************************************************/ + +#dokuwiki__aside { + + > .pad { + font-size: 0.875em; + overflow: hidden; + word-wrap: break-word; + } + + /* make sidebar more condensed */ + + h1 { + font-size: 1.714em; + margin-bottom: .292em; + } + + h2 { + margin-bottom: .333em; + } + + h3 { + margin-bottom: .444em; + } + + h4 { + margin-bottom: .5em; + } + + h5 { + margin-bottom: .5714em; + } + + p, + ul, + ol, + dl, + pre, + table, + fieldset, + hr, + blockquote, + address { + margin-bottom: .7em; + } + + ul, + ol { + padding-left: .5em; + } + + li ul, + li ol { + margin-bottom: 0; + padding: 0; + } + + a:link, + a:visited { + color: @ini_link; + background-color: inherit; + } +} + +[dir=rtl] #dokuwiki__aside ul, +[dir=rtl] #dokuwiki__aside ol { + padding-right: .5em; +} + +/* content +********************************************************************/ + +.dokuwiki .pageId { + position: absolute; + top: -2.3em; + right: -1em; + overflow: hidden; + padding: 1em 1em 0; + + span { + font-size: 0.875em; + border: solid @ini_background_alt; + border-width: 1px 1px 0; + background-color: @ini_background; + color: @ini_text_alt; + padding: .1em .35em; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + box-shadow: 0 0 .5em @ini_text_alt; + display: block; + } +} + +.dokuwiki div.page { + background: @ini_background; + color: inherit; + border: 1px solid @ini_background_alt; + box-shadow: 0 0 .5em @ini_text_alt; + border-radius: 2px; + padding: 1.556em 2em 2em; + margin-bottom: .5em; + overflow: hidden; + word-wrap: break-word; +} + +.dokuwiki .docInfo { + font-size: 0.875em; + text-align: right; +} + +/* license note under edit window */ +.dokuwiki div.license { + font-size: 93.75%; +} + +[dir=rtl] .dokuwiki .docInfo { + text-align: left; +} + +[dir=rtl] .dokuwiki .pageId { + right: auto; + left: -1em; +} + +/* footer +********************************************************************/ + +.dokuwiki .wrapper { + margin-bottom: 1.4em; +} + +#dokuwiki__footer { + margin-bottom: 1em; + text-align: center; + + > .pad { + font-size: 0.875em; + } + + div.license { + margin-bottom: 0.5em; + font-size: 100%; + } + + div.buttons a { + img { + opacity: 0.5; + } + + &:hover img, + &:active img, + &:focus img { + opacity: 1; + } + } + +} + +[dir=rtl] #dokuwiki__footer .license img { + margin: 0 0 0 .5em; +} diff --git a/lib/tpl/dokuwiki/css/includes.css b/lib/tpl/dokuwiki/css/includes.css deleted file mode 100644 index bc189962fba0d8c38f790aa2015ffc3dbcb658e7..0000000000000000000000000000000000000000 --- a/lib/tpl/dokuwiki/css/includes.css +++ /dev/null @@ -1,4 +0,0 @@ -/** - * This file provides styles for included seperate html files - * (added through "include hooks"). - */ diff --git a/lib/tpl/dokuwiki/css/mixins.less b/lib/tpl/dokuwiki/css/mixins.less new file mode 100644 index 0000000000000000000000000000000000000000..a88767e9706909d254d3b1083713aeb48304b59e --- /dev/null +++ b/lib/tpl/dokuwiki/css/mixins.less @@ -0,0 +1,10 @@ +/** + * linear gradient x-browser support + */ +.linear-gradient(@declaration: 0) { + background: -moz-linear-gradient( @declaration); + background: -webkit-linear-gradient(@declaration); + background: -o-linear-gradient( @declaration); + background: -ms-linear-gradient( @declaration); + background: linear-gradient( @declaration); +} \ No newline at end of file diff --git a/lib/tpl/dokuwiki/css/mobile.css b/lib/tpl/dokuwiki/css/mobile.less similarity index 87% rename from lib/tpl/dokuwiki/css/mobile.css rename to lib/tpl/dokuwiki/css/mobile.less index 71f80599d2bef02e1aa207349e312316e823c412..289f5afa38cf84c41f5bd42fba9f88177b835bb3 100644 --- a/lib/tpl/dokuwiki/css/mobile.css +++ b/lib/tpl/dokuwiki/css/mobile.less @@ -13,7 +13,7 @@ /* for screen widths in the tablet range ********************************************************************/ -@media only screen and (max-width: __tablet_width__) { +@media only screen and (max-width: @ini_tablet_width) { #screen__mode { z-index: 1; /* for detecting media queries in JavaScript (see script.js) */ @@ -29,10 +29,10 @@ [dir=rtl] #dokuwiki__aside > .pad { margin: 0 0 .5em; /* style like .page */ - background: __background__; + background: @ini_background; color: inherit; border: 1px solid #eee; - box-shadow: 0 0 .5em __text_alt__; + box-shadow: 0 0 .5em @ini_text_alt; border-radius: 2px; padding: 1em; margin-bottom: .5em; @@ -40,22 +40,24 @@ #dokuwiki__aside h3.toggle { font-size: 1em; -} -#dokuwiki__aside h3.toggle.closed { - margin-bottom: 0; - padding-bottom: 0; -} -#dokuwiki__aside h3.toggle.open { - border-bottom: 1px solid __border__; + + &.closed { + margin-bottom: 0; + padding-bottom: 0; + } + &.open { + border-bottom: 1px solid @ini_border; + } } .showSidebar #dokuwiki__content { float: none; margin-left: 0; width: 100%; -} -.showSidebar #dokuwiki__content > .pad { - margin-left: 0; + + > .pad { + margin-left: 0; + } } [dir=rtl] .showSidebar #dokuwiki__content, @@ -69,7 +71,7 @@ margin: 0 0 1em 0; width: auto; border-left-width: 0; - border-bottom: 1px solid __border__; + border-bottom: 1px solid @ini_border; } [dir=rtl] #dw__toc { float: none; @@ -119,7 +121,7 @@ /* for screen widths in the smartphone range ********************************************************************/ -@media only screen and (max-width: __phone_width__) { +@media only screen and (max-width: @ini_phone_width) { #screen__mode { z-index: 2; /* for detecting media queries in JavaScript (see script.js) */ @@ -133,9 +135,10 @@ body { #dokuwiki__site { max-width: 100%; -} -#dokuwiki__site > .site { - padding: 0 .5em; + + > .site { + padding: 0 .5em; + } } #dokuwiki__header { padding: .5em 0; @@ -154,19 +157,21 @@ body { list-style: none; padding-left: 0; margin: 0; + + li { + margin-left: .35em; + display: inline; + } } [dir=rtl] #dokuwiki__header ul.a11y.skip { left: auto !important; right: 0 !important; float: left; padding-right: 0; -} -#dokuwiki__header ul.a11y.skip li { - margin-left: .35em; - display: inline; -} -[dir=rtl] #dokuwiki__header ul.a11y.skip li { - margin: 0 .35em 0 0; + + li { + margin: 0 .35em 0 0; + } } #dokuwiki__header .headings, @@ -264,13 +269,14 @@ body { .dokuwiki label.block { text-align: left; + + span { + display: block; + } } [dir=rtl] .dokuwiki label.block { text-align: right; } -.dokuwiki label.block span { - display: block; -} /* _edit */ .dokuwiki div.section_highlight { diff --git a/lib/tpl/dokuwiki/css/pagetools.css b/lib/tpl/dokuwiki/css/pagetools.less similarity index 98% rename from lib/tpl/dokuwiki/css/pagetools.css rename to lib/tpl/dokuwiki/css/pagetools.less index 98e4ff1fc878cf14a82a1b83bc1d31f2670cb161..850e75d7a7dd595b80137844c480dd25a2add1e2 100644 --- a/lib/tpl/dokuwiki/css/pagetools.css +++ b/lib/tpl/dokuwiki/css/pagetools.less @@ -101,10 +101,10 @@ #dokuwiki__pagetools:hover ul, #dokuwiki__pagetools ul li a:focus, #dokuwiki__pagetools ul li a:active { - background-color: __background__; - border-color: __border__; + background-color: @ini_background; + border-color: @ini_border; border-radius: 2px; - box-shadow: 2px 2px 2px __text_alt__; + box-shadow: 2px 2px 2px @ini_text_alt; } #dokuwiki__pagetools:hover ul li a, @@ -124,7 +124,7 @@ [dir=rtl] #dokuwiki__pagetools:hover ul, [dir=rtl] #dokuwiki__pagetools ul li a:focus { - box-shadow: -2px 2px 2px __text_alt__; + box-shadow: -2px 2px 2px @ini_text_alt; } [dir=rtl] #dokuwiki__pagetools:hover li a, @@ -158,7 +158,7 @@ text-decoration: none; } #dokuwiki__pagetools ul li a:hover { - background-color: __background_alt__; + background-color: @ini_background_alt; } /*____________ all available icons in sprite ____________*/ diff --git a/lib/tpl/dokuwiki/css/structure.css b/lib/tpl/dokuwiki/css/structure.css deleted file mode 100644 index 00642e90b505b3a73c13008335c88a1c3baffcfc..0000000000000000000000000000000000000000 --- a/lib/tpl/dokuwiki/css/structure.css +++ /dev/null @@ -1,81 +0,0 @@ -/** - * This file provides styles for the general layout structure. - * - * @author Anika Henke <anika@selfthinker.org> - */ - -body { - margin: 0 auto; -} -#dokuwiki__site { - margin: 0 auto; - max-width: __site_width__; -} -#dokuwiki__site > .site { - padding: 0 .5em; -} - -#dokuwiki__header { - width: 100%; -} -#dokuwiki__header > .pad { -} - #dokuwiki__header .headings { - float: left; - } - [dir=rtl] #dokuwiki__header .headings { - float: right; - text-align: right; - } - #dokuwiki__header .tools { - float: right; - text-align: right; - } - [dir=rtl] #dokuwiki__header .tools { - float: left; - text-align: left; - } - -#dokuwiki__site .wrapper { - position: relative; -} - - #dokuwiki__aside { - width: __sidebar_width__; - float: left; - position: relative; - display: block; - } - [dir=rtl] #dokuwiki__aside { - float: right; - } - #dokuwiki__aside > .pad { - margin: 0 1.5em 0 0; - } - [dir=rtl] #dokuwiki__aside > .pad { - margin: 0 0 0 1.5em; - } - - .showSidebar #dokuwiki__content { - float: right; - margin-left: -__sidebar_width__; - width: 100%; - } - [dir=rtl] .showSidebar #dokuwiki__content { - float: left; - margin-left: 0; - margin-right: -__sidebar_width__; - } - .showSidebar #dokuwiki__content > .pad { - margin-left: __sidebar_width__; - } - [dir=rtl] .showSidebar #dokuwiki__content > .pad { - margin-left: 0; - margin-right: __sidebar_width__; - } - -#dokuwiki__footer { - clear: both; -} -#dokuwiki__footer > .pad { -} diff --git a/lib/tpl/dokuwiki/css/structure.less b/lib/tpl/dokuwiki/css/structure.less new file mode 100644 index 0000000000000000000000000000000000000000..3ea2f83eb63a27941bfb25307cf354b82ca7f1ea --- /dev/null +++ b/lib/tpl/dokuwiki/css/structure.less @@ -0,0 +1,89 @@ +/** + * This file provides styles for the general layout structure. + * + * @author Anika Henke <anika@selfthinker.org> + */ +body { + margin: 0 auto; +} + +#dokuwiki__site { + margin: 0 auto; + max-width: @ini_site_width; +} + +#dokuwiki__site > .site { + padding: 0 .5em; +} + +#dokuwiki__header { + width: 100%; + + .headings { + float: left; + } + + .tools { + float: right; + text-align: right; + } +} + +[dir=rtl] #dokuwiki__header { + .headings { + float: right; + text-align: right; + } + + .tools { + float: left; + text-align: left; + } +} + +#dokuwiki__site .wrapper { + position: relative; +} + +#dokuwiki__aside { + width: @ini_sidebar_width; + float: left; + position: relative; + display: block; + + > .pad { + margin: 0 1.5em 0 0; + } +} + +[dir=rtl] #dokuwiki__aside { + float: right; + > .pad { + margin: 0 0 0 1.5em; + } +} + +.showSidebar #dokuwiki__content { + float: right; + margin-left: (-1 * @ini_sidebar_width); + width: 100%; + + > .pad { + margin-left: @ini_sidebar_width; + } +} + +[dir=rtl] .showSidebar #dokuwiki__content { + float: left; + margin-left: 0; + margin-right: (-1 * @ini_sidebar_width); + + > .pad { + margin-left: 0; + margin-right: @ini_sidebar_width; + } +} + +#dokuwiki__footer { + clear: both; +} diff --git a/lib/tpl/dokuwiki/style.ini b/lib/tpl/dokuwiki/style.ini index 77bb98236b5bf12a8a7ca018786eb1ba5a260b9c..897b6e6da4583000c8809e48afb0445229e3c912 100644 --- a/lib/tpl/dokuwiki/style.ini +++ b/lib/tpl/dokuwiki/style.ini @@ -9,10 +9,15 @@ ; Define the stylesheets your template uses here. The second value ; defines for which output media the style should be loaded. Currently ; print, screen and all are supported. +; You can reference CSS and LESS files here. Files referenced here will +; be checked for updates when considering a cache rebuild while files +; included through LESS' @import statements are not [stylesheets] -css/basic.css = screen +css/mixins.less = screen + +css/basic.less = screen css/_imgdetail.css = screen css/_media_popup.css = screen css/_media_fullscreen.css = screen @@ -28,19 +33,20 @@ css/_edit.css = screen css/_modal.css = screen css/_forms.css = screen css/_admin.css = screen -css/structure.css = screen -css/design.css = screen -css/pagetools.css = screen -css/content.css = screen -css/includes.css = screen +css/structure.less = screen +css/design.less = screen +css/pagetools.less = screen +css/content.less = screen -css/mobile.css = all +css/mobile.less = all css/print.css = print ; This section is used to configure some placeholder values used in ; the stylesheets. Changing this file is the simplest method to ; give your wiki a new look. +; Placeholders defined here will also be made available as LESS variables +; (with surrounding underscores removed, and the prefix @ini_ added) [replacements] @@ -48,32 +54,32 @@ css/print.css = print ;------ guaranteed dokuwiki color placeholders that every plugin can use ; main text and background colors -__text__ = "#333" -__background__ = "#fff" +__text__ = "#333" ; @ini_text +__background__ = "#fff" ; @ini_background ; alternative text and background colors -__text_alt__ = "#999" -__background_alt__ = "#eee" +__text_alt__ = "#999" ; @ini_text_alt +__background_alt__ = "#eee" ; @ini_background_alt ; neutral text and background colors -__text_neu__ = "#666" -__background_neu__ = "#ddd" +__text_neu__ = "#666" ; @ini_text_neu +__background_neu__ = "#ddd" ; @ini_background_neu ; border color -__border__ = "#ccc" +__border__ = "#ccc" ; @ini_border ; highlighted text (e.g. search snippets) -__highlight__ = "#ff9" +__highlight__ = "#ff9" ; @ini_highlight ;-------------------------------------------------------------------------- -__background_site__ = "#fbfaf9" +__background_site__ = "#fbfaf9" ; @ini_background_site ; these are used for links -__link__ = "#2b73b7" -__existing__ = "#080" -__missing__ = "#d30" +__link__ = "#2b73b7" ; @ini_link +__existing__ = "#080" ; @ini_existing +__missing__ = "#d30" ; @ini_missing ; site and sidebar widths -__site_width__ = "75em" -__sidebar_width__ = "16em" +__site_width__ = "75em" ; @ini_site_width +__sidebar_width__ = "16em" ; @ini_sidebar_width ; cut off points for mobile devices -__tablet_width__ = "800px" -__phone_width__ = "480px" +__tablet_width__ = "800px" ; @ini_tablet_width +__phone_width__ = "480px" ; @ini_phone_width