1 | const fs = require('fs');
|
2 | const util = require('./util');
|
3 | const glob = require('glob');
|
4 |
|
5 | const {write} = util;
|
6 |
|
7 | const themeMixins = {};
|
8 | const coreMixins = {};
|
9 | const themeVar = {};
|
10 | const coreVar = {};
|
11 |
|
12 |
|
13 | const mixinTemplate = `//
|
14 | // Component: Mixin
|
15 | // Description: Defines mixins which are used across all components
|
16 | //
|
17 | // ========================================================================
|
18 |
|
19 |
|
20 | // SVG
|
21 | // ========================================================================
|
22 |
|
23 | /// Replace \`$search\` with \`$replace\` in \`$string\`
|
24 | /// @author Hugo Giraudel
|
25 | /// @param {String} $string - Initial string
|
26 | /// @param {String} $search - Substring to replace
|
27 | /// @param {String} $replace ('') - New value
|
28 | /// @return {String} - Updated string
|
29 | @function str-replace($string, $search, $replace: '') {
|
30 | $index: str-index($string, $search);
|
31 |
|
32 | @if $index {
|
33 | @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
|
34 | }
|
35 |
|
36 | @return $string;
|
37 | }
|
38 |
|
39 | @mixin svg-fill($src, $color-default, $color-new){
|
40 |
|
41 | $replace-src: str-replace($src, $color-default, $color-new) !default;
|
42 | $replace-src: str-replace($replace-src, "#", "%23");
|
43 | background-image: url(quote($replace-src));
|
44 | }`;
|
45 |
|
46 |
|
47 | const inverseTemplate = ` @include hook-inverse-component-base();
|
48 | @include hook-inverse-component-link();
|
49 | @include hook-inverse-component-heading();
|
50 | @include hook-inverse-component-divider();
|
51 | @include hook-inverse-component-list();
|
52 | @include hook-inverse-component-icon();
|
53 | @include hook-inverse-component-form();
|
54 | @include hook-inverse-component-button();
|
55 | @include hook-inverse-component-grid();
|
56 | @include hook-inverse-component-close();
|
57 | @include hook-inverse-component-totop();
|
58 | @include hook-inverse-component-badge();
|
59 | @include hook-inverse-component-label();
|
60 | @include hook-inverse-component-article();
|
61 | @include hook-inverse-component-search();
|
62 | @include hook-inverse-component-nav();
|
63 | @include hook-inverse-component-navbar();
|
64 | @include hook-inverse-component-subnav();
|
65 | @include hook-inverse-component-breadcrumb();
|
66 | @include hook-inverse-component-pagination();
|
67 | @include hook-inverse-component-tab();
|
68 | @include hook-inverse-component-slidenav();
|
69 | @include hook-inverse-component-dotnav();
|
70 | @include hook-inverse-component-accordion();
|
71 | @include hook-inverse-component-iconnav();
|
72 | @include hook-inverse-component-text();
|
73 | @include hook-inverse-component-column();
|
74 | @include hook-inverse-component-utility();`;
|
75 |
|
76 |
|
77 | glob.sync('src/less/**/*.less').forEach(file => {
|
78 |
|
79 | const data = fs.readFileSync(file, 'utf8');
|
80 |
|
81 | let scssData = data.replace(/\/less\//g, '/scss/')
|
82 | .replace(/\.less/g, '.scss')
|
83 | .replace(/@/g, '$')
|
84 | .replace(/--uk-[^\s]+: (\$[^\s]+);/g, (exp, name) => exp.replace(name, `#{${name}}`))
|
85 | .replace(/\\\$/g, '\\@')
|
86 | .replace(/ e\(/g, ' unquote(')
|
87 | .replace(/\.([\w-]*)\s*\((.*)\)\s*{/g, '@mixin $1($2){')
|
88 | .replace(/(\$[\w-]*)\s*:(.*);/g, '$1: $2 !default;')
|
89 | .replace(/@mixin ([\w-]*)\s*\((.*)\)\s*{\s*}/g, '// @mixin $1($2){}')
|
90 | .replace(/\.(hook[a-zA-Z\-\d]+);/g, '@if(mixin-exists($1)) {@include $1();}')
|
91 | .replace(/\$(import|supports|media|font-face|page|-ms-viewport|keyframes|-webkit-keyframes|-moz-document)/g, '@$1')
|
92 | .replace(/tint\((\$[\w-]+),\s([^)]*)\)/g, 'mix(white, $1, $2)')
|
93 | .replace(/fade\((\$[\w-]*), ([0-9]+)%\)/g, (match, p1, p2) => { return `rgba(${p1}, ${p2 / 100})`;}) // replace LESS function fade with rgba
|
94 | .replace(/fadeout\((\$[\w-]*), ([0-9]+)%\)/g, (match, p1, p2) => { return `fade-out(${p1}, ${p2 / 100})`;}) // replace LESS function fadeout with fade-out
|
95 | .replace(/\.svg-fill/g, '@include svg-fill')
|
96 | .replace(/(.*):extend\((\.[\w-]*) all\) when \((\$[\w-]*) = ([\w]+)\) {}/g, '@if ( $3 == $4 ) { $1 { @extend $2 !optional;} }')
|
97 | .replace(/(\.[\w-]+)\s*when\s*\((\$[\w-]*)\s*=\s*(\w+)\)\s*{\s*@if\(mixin-exists\(([\w-]*)\)\) {@include\s([\w-]*)\(\);\s*}\s*}/g, '@if ($2 == $3) { $1 { @if (mixin-exists($4)) {@include $4();}}}')
|
98 | .replace(/(\.[\w-]+)\s*when\s*\((\$[\w-]*)\s*=\s*(\w+)\)\s*({\s*.*?\s*})/gs, '@if ($2 == $3) {\n$1 $4\n}')
|
99 | .replace(/\${/g, '#{$')
|
100 | .replace(/[^(](-\$[\w-]*)/g, ' ($1)')
|
101 | .replace(/~('[^']+')/g, 'unquote($1)');
|
102 |
|
103 |
|
104 | const [filename] = file.split('/').pop().split('.less');
|
105 |
|
106 | if (filename !== 'inverse') {
|
107 | scssData = scssData.replace(/hook-inverse(?!-)/g, `hook-inverse-component-${filename}`);
|
108 | } else {
|
109 | const joinedHook = `@mixin hook-inverse(){\n${inverseTemplate}\n}\n`;
|
110 | scssData = scssData.replace(/\*\//, '*/\n' + joinedHook);
|
111 | }
|
112 |
|
113 |
|
114 | scssData = getMixinsFromFile(file, scssData);
|
115 |
|
116 |
|
117 | if (filename !== 'mixin') {
|
118 | scssData = getVariablesFromFile(file, scssData);
|
119 | }
|
120 |
|
121 | if (filename === 'uikit.theme') {
|
122 |
|
123 | scssData = scssData.replace(/\/\/\n\/\/ Theme\n\/\/\n\n@import "theme\/_import.scss";/, '');
|
124 |
|
125 | scssData = scssData.replace(/\/\/ Core\n\/\//g, '// Theme\n//\n\n@import "theme/_import.scss";');
|
126 | }
|
127 |
|
128 |
|
129 | if (filename === 'mixin') {
|
130 | scssData = mixinTemplate;
|
131 | }
|
132 |
|
133 | return write(file.replace(/less/g, 'scss').replace('.theme.', '-theme.'), scssData);
|
134 |
|
135 | });
|
136 |
|
137 |
|
138 |
|
139 |
|
140 | const mixins_theme = Object.keys(themeMixins).map(function (key) { return themeMixins[key]; });
|
141 | write('src/scss/mixins-theme.scss', mixins_theme.join('\n'));
|
142 |
|
143 | const mixins_core = Object.keys(coreMixins).map(function (key) { return coreMixins[key]; });
|
144 | write('src/scss/mixins.scss', mixins_core.join('\n'));
|
145 |
|
146 |
|
147 | const compactCoreVar = new Set();
|
148 | Object.keys(coreVar).map(key => getAllDependencies(coreVar, key).forEach(dependency => compactCoreVar.add(dependency)));
|
149 |
|
150 | write('src/scss/variables.scss', Array.from(compactCoreVar).join('\n'));
|
151 |
|
152 |
|
153 | const compactThemeVar = new Set();
|
154 | Object.keys(themeVar).map(key => getAllDependencies(themeVar, key).forEach(dependency => compactThemeVar.add(dependency)));
|
155 |
|
156 | write('src/scss/variables-theme.scss', Array.from(compactThemeVar).join('\n'));
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | function getAllDependencies(allVariables, currentKey, dependencies = new Set()) {
|
163 |
|
164 | if (!allVariables[currentKey].dependencies.length) {
|
165 |
|
166 | dependencies.add(`${currentKey}: ${allVariables[currentKey].value}`);
|
167 | return Array.from(dependencies);
|
168 | } else {
|
169 |
|
170 | allVariables[currentKey].dependencies.forEach(dependecy => {
|
171 | getAllDependencies(allVariables, dependecy, dependencies).forEach(newDependency => dependencies.add(newDependency));
|
172 | });
|
173 |
|
174 | dependencies.add(`${currentKey}: ${allVariables[currentKey].value}`);
|
175 | return Array.from(dependencies);
|
176 | }
|
177 | }
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 | function getMixinsFromFile(file, data) {
|
184 |
|
185 |
|
186 | let regex = /@include ([a-z0-9-]+)/g;
|
187 | let match = regex.exec(data);
|
188 |
|
189 | while (match) {
|
190 | if (!(match[1] in themeMixins)) { themeMixins[match[1]] = `@mixin ${match[1]}(){}`; }
|
191 | if (!(match[1] in coreMixins)) { coreMixins[match[1]] = `@mixin ${match[1]}(){}`; }
|
192 |
|
193 | match = regex.exec(data);
|
194 | }
|
195 |
|
196 |
|
197 | regex = /@mixin ([\w-]*)\s*\((.*)\)\s*{\n(\s+[\w\W]+?)(?=\n})\n}/g;
|
198 | match = regex.exec(data);
|
199 |
|
200 | while (match) {
|
201 | themeMixins[match[1]] = match[0];
|
202 | if (file.indexOf('theme/') < 0) {
|
203 | coreMixins[match[1]] = match[0];
|
204 | }
|
205 | match = regex.exec(data);
|
206 | }
|
207 |
|
208 |
|
209 | regex = /@mixin ([\w-]*)\s*\((.*)\)\s*{( [^\n]+)}/g;
|
210 | match = regex.exec(data);
|
211 |
|
212 | while (match) {
|
213 | themeMixins[match[1]] = match[0];
|
214 | if (file.indexOf('theme/') < 0) {
|
215 | coreMixins[match[1]] = match[0];
|
216 | }
|
217 |
|
218 | match = regex.exec(data);
|
219 | }
|
220 |
|
221 |
|
222 | return data
|
223 | .replace(/@mixin ([\w-]*)\s*\((.*)\)\s*{\n(\s+[\w\W]+?)(?=\n})\n}/g, '')
|
224 | .replace(/@mixin ([\w-]*)\s*\((.*)\)\s*{( [^\n]+)}/g, '');
|
225 | }
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 | function getVariablesFromFile(file, data) {
|
232 | const regex = /(\$[\w-]*)\s*:\s*(.*);/g;
|
233 | let match = regex.exec(data);
|
234 |
|
235 | while (match) {
|
236 |
|
237 |
|
238 | if (match[0].indexOf('../../images/backgrounds') >= 0) {
|
239 |
|
240 | const iconregex = /(\$[\w-]+)\s*:\s*"\.\.\/\.\.\/images\/backgrounds\/([\w./-]+)" !default;/g;
|
241 | const iconmatch = iconregex.exec(match[0]);
|
242 | let svg = fs.readFileSync(`src/images/backgrounds/${iconmatch[2]}`).toString();
|
243 | svg = `"${svg.replace(/\r?\n|\r/g, '%0A')
|
244 | .replace(/"/g, '\'')
|
245 | .replace(/\s/g, '%20')
|
246 | .replace(/</g, '%3C')
|
247 | .replace(/=/g, '%3D')
|
248 | .replace(/'/g, '%22')
|
249 | .replace(/:/g, '%3A')
|
250 | .replace(/\//g, '%2F')
|
251 | .replace(/>/g, '%3E')
|
252 | .replace(/%3Csvg/, 'data:image/svg+xml;charset=UTF-8,%3Csvg')}"`;
|
253 |
|
254 |
|
255 | if (file.indexOf('theme/') < 0) {
|
256 | coreVar[iconmatch[1]] = {value: `${svg} !default;`, dependencies: []};
|
257 | }
|
258 |
|
259 | themeVar[iconmatch[1]] = {value: `${svg} !default;`, dependencies: []};
|
260 |
|
261 |
|
262 | const inlineSVG = `${iconmatch[1]}: ${svg} !default;`;
|
263 | data = data.replace(match[0], inlineSVG);
|
264 |
|
265 |
|
266 | } else {
|
267 |
|
268 | const variablesRegex = /(\$[\w-]+)/g;
|
269 | let variablesMatch = variablesRegex.exec(match[2]);
|
270 | const dependencies = [];
|
271 |
|
272 | while (variablesMatch) {
|
273 | dependencies.push(variablesMatch[1]);
|
274 | variablesMatch = variablesRegex.exec(match[2]);
|
275 | }
|
276 |
|
277 |
|
278 | if (file.indexOf('theme/') < 0) {
|
279 | coreVar[match[1]] = {value: `${match[2]};`, dependencies: Array.from(dependencies)};
|
280 | }
|
281 |
|
282 | themeVar[match[1]] = {value: `${match[2]};`, dependencies: Array.from(dependencies)};
|
283 | }
|
284 |
|
285 | match = regex.exec(data);
|
286 | }
|
287 |
|
288 | return data;
|
289 | }
|