Skip to content

Commit b758ac4

Browse files
committed
* Added setting for SCSS-style grouping (&__element) and possibility to
* change the grouping token (&) * Added possibility to set output tokens before/after declarations * (enables SASS-like indented style output) * Removed classless elements from the output (caused more confusion than * good) * JSX support (dumb, just looking for className attribute as a fallback * for class) * Refactored a bit
1 parent f454fba commit b758ac4

File tree

7 files changed

+177
-39
lines changed

7 files changed

+177
-39
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
1+
## 0.2.2 - More configuration
2+
* Added setting for SCSS-style grouping (&__element) and possibility to change the grouping token (&)
3+
* Added possibility to set output tokens before/after declarations (enables SASS-like indented style output)
4+
* Removed classless elements from the output (caused more confusion than good)
5+
* JSX support (dumb, just looking for className attribute as a fallback for class)
6+
17
## 0.1.0 - First Release
28
* Initial version

README.md

+83-9
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,103 @@
22

33
Generates CSS boilerplate based on the selected HTML.
44

5-
Early & opinionated version:
65
- Only cares about classes, ignores id's/other possible selectors
7-
- Outputs flat CSS structure
8-
- ... with optional classless first-child element nesting (turned on by default)
9-
- Indents with 2 spaces
10-
- Made primarily to simplify working with BEM methodology
6+
- Supports CSS, SCSS, Sass, LESS and possibly other syntaxes
7+
- BEM support (actually, more like a *BE* support for now. Modifiers support will be added if needed)
8+
- Configurable nesting, grouping and formatting
9+
- "Supports" JSX (recognizes the `className` attribute)
10+
- Is *forgiving* (meaning it should work even with invalid HTML code)
1111

1212
# demo
1313

1414
![atom html to css](http://dracul.kill.pl/~ard/htmltocss.gif)
1515

16+
# settings
17+
18+
## BEM grouping
19+
key: `html-to-css.bem-group`, type: `boolean`, default: `false`
20+
21+
Should BEM-style declarations be grouped and nested in a SCSS-style?
22+
23+
When *true*:
24+
25+
<section class="introduction">
26+
<h1 class="introduction__header"></h1>
27+
<p class="introduction__text"></p>
28+
</section>
29+
30+
&darr;
31+
32+
.introduction {
33+
34+
&__header {
35+
36+
}
37+
38+
&__text {
39+
40+
}
41+
42+
}
43+
44+
When *false*, the same HTML code becomes:
45+
46+
.introduction {
47+
48+
}
49+
50+
.introduction__header {
51+
52+
}
53+
54+
.introduction__text {
55+
56+
}
57+
58+
## BEM separator token
59+
key: `html-to-css.bem-separator`, type: `string`, default: `__`
60+
61+
Character(s) used as a BLOCK*__*ELEMENT separator in BEM.
62+
63+
## rulelist open token
64+
key: `html-to-css.rulelist-open`, type: `string`, default: ` {\n\n`
65+
66+
Character(s) to output when opening rulelists (including whitespaces)
67+
68+
## rulelist close token
69+
key: `html-to-css.rulelist-close`, type: `string`, default: `}\n\n`
70+
71+
Character(s) to output when opening rulelists (including whitespaces)
72+
73+
You can change them to fine-tune the output format to your own liking. Just remove `{` and `}` to support SASS-like indented syntax.
74+
75+
## Grouping character
76+
key: `html-to-css.grouping-character`, type: `string`, default: `&`
77+
78+
Character(s) to be used in output when referring to the parent element while nesting declarations SCSS-style.
79+
1680
# key bindings
1781

18-
To avoid conflicts and promote peace, this package doesn't register any keys by default. Do it
19-
yourself, or just paste in `~/.atom/keymap.cson` the following lines:
82+
To avoid conflicts and promote peace, this package doesn't register any keys by default. Do it yourself, or just paste the following lines in `~/.atom/keymap.cson`:
2083

2184
'atom-text-editor':
2285
'alt-x': 'html-to-css:generate'
2386

24-
It'll try to register `alt-x` key shortcut.
87+
It'll try to register <kbd>alt</kbd>+<kbd>x</kbd> key shortcut.
88+
89+
# indentation
90+
91+
`\t` is used to indent, but Atom seems to be clever enough to convert it to your default style when pasting. Let me know if it's not doing that.
92+
93+
# parsing
94+
95+
due to the forgiving nature of the excellent [htmlparser2](https://github.com/fb55/htmlparser2) used under the hood, this plugin is able to deal with:
96+
* incomplete selections (wouldn't it be faster if you could select opening tags only? well, you can)
97+
* not-really-valid-HTML-code (JSX, anyone?)
98+
* general mess (really. you can hit <kbd>cmd</kbd>+<kbd>a</kbd> in this markdown file and it'll still parse the few HTML lines from the code sample)
2599

26100
# TODO
27101

28-
- extend it with different transformers/formatters as settings.
102+
- ~extend it with different transformers/formatters as settings.~
29103

30104
Contributions welcomed.

lib/formatter.js

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
1+
'use babel';
2+
3+
function applyWhitespace(str) {
4+
return str.replace(/\\n/g, "\n").replace(/\\t/g, "\t");
5+
}
6+
17
function saveElement(css, el) {
2-
css = css + "." + el.name + " {\n\n";
8+
const RULELIST_OPEN = applyWhitespace(atom.config.get('html-to-css.rulelist-open'));
9+
const RULELIST_CLOSE = applyWhitespace(atom.config.get('html-to-css.rulelist-close'));
10+
const GROUPING_CHAR = applyWhitespace(atom.config.get('html-to-css.grouping-character'));
11+
12+
css = css + `.${el.name}${RULELIST_OPEN}`;
313
if (el.children && el.children.length) {
414
el.children.map((ch) => {
5-
css = css + "\t" + ch + " {\n\n\t}\n";
15+
const name = ch.name.replace(el.name, GROUPING_CHAR);
16+
css = css + `\t${name}${RULELIST_OPEN}\t${RULELIST_CLOSE}`;
617
});
718
}
8-
css = css + "}\n\n";
19+
css = css + RULELIST_CLOSE;
920
return css;
1021
}
1122

lib/grouper.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use babel';
2+
3+
import partial from 'partial-any';
4+
5+
const isBlock = (separator, str) => str.indexOf(separator) === -1;
6+
7+
function notInTheTree(needle, pile) {
8+
return needle.name !== pile.name && pile.children.every(partial(notInTheTree, needle));
9+
}
10+
11+
exports.group = function(json) {
12+
const _isBlock = partial(isBlock, atom.config.get('html-to-css.bem-separator'));
13+
let list = json
14+
.filter((candidate) => _isBlock(candidate.name))
15+
.map((block) => {
16+
block.children = block.children.concat(json
17+
.filter((candidate) => !_isBlock(candidate.name))
18+
.filter((element) => element.name.startsWith(block.name)))
19+
return block;
20+
});
21+
22+
return list.concat(json
23+
.filter((el) => {
24+
return list.every(partial(notInTheTree, el));
25+
}));
26+
}

lib/html-to-css.js

+35-5
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,42 @@ import htmlparser from 'htmlparser2';
44
import { CompositeDisposable } from 'atom';
55
import { transform } from './transformer';
66
import { format } from './formatter';
7+
import { group } from './grouper';
78

89
export default {
910

1011
config: {
11-
"nest-classless": {
12+
"bem-group": {
13+
"title": "BEM grouping",
1214
"type": "boolean",
13-
"default": true,
14-
"description": "Nest first-child classless elements (.someClass { p {} }})"
15-
}
15+
"default": false,
16+
"description": "Nest & group BEM components (.block { &__element })"
17+
},
18+
"bem-separator": {
19+
"title": "BEM separator token",
20+
"type": "string",
21+
"default": "__",
22+
"description": "Character(s) to separate BEM block from element (BLOCK__ELEMENT)"
23+
},
24+
"rulelist-open": {
25+
"title": "Rulelist open token",
26+
"type": "string",
27+
"default": " {\\n\\n",
28+
"description": "Character(s) to output when opening rulelists (including whitespaces)"
29+
},
30+
"rulelist-close": {
31+
"title": "Rulelist close token",
32+
"type": "string",
33+
"default": "}\\n\\n",
34+
"description": "Character(s) to output when closing rulelists (including whitespaces)"
35+
},
36+
"grouping-character": {
37+
"title": "Grouping token",
38+
"type": "string",
39+
"default": "&",
40+
"description": "Character(s) to output when grouping nested definitions"
41+
},
42+
1643
},
1744

1845
subscriptions: null,
@@ -36,7 +63,10 @@ export default {
3663
const editor = pane.getActiveEditor();
3764
const text = editor.getSelectedText();
3865
const htmlJson = htmlparser.parseDOM(text);
39-
const preparedJson = transform(htmlJson);
66+
let preparedJson = transform(htmlJson);
67+
if (atom.config.get('html-to-css.bem-group')) {
68+
preparedJson = group(preparedJson);
69+
}
4070
const css = format(preparedJson);
4171
if (css) {
4272
atom.clipboard.write(css);

lib/transformer.js

+11-21
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,28 @@
1+
'use babel';
2+
3+
import partial from 'partial-any';
4+
15
function parseArray(arr, tree) {
2-
return arr.map((el) => {
3-
parseNode(tree, el);
4-
});
6+
return arr.map(partial(parseNode, tree));
57
}
68

79
function splitClasses(classNames) {
810
return classNames.split(" ");
911
}
1012

11-
function storeElement(store, className, obj) {
12-
13+
function storeElement(store, className) {
1314
const ret = {
14-
name: className
15+
name: className,
16+
children: []
1517
};
16-
17-
let val = false;
18-
if (obj.children && atom.config.get('html-to-css.nest-classless')) {
19-
val = obj.children.filter((el) => {
20-
return el.type === 'tag' && !el.attribs.class
21-
}).reduce((acc, cur) => {
22-
return [cur.name, ...acc];
23-
}, []);
24-
ret.children = [ ...new Set(val) ];
25-
}
2618
if (!store.find((el) => el.name === ret.name)) store.push(ret);
2719
}
2820

2921
function parseNode(store, node) {
3022
if (node.type !== 'tag') return;
31-
if (node.attribs.class) {
32-
const classes = splitClasses(node.attribs.class);
33-
classes.map((className) => {
34-
storeElement(store, className, node);
35-
});
23+
if (node.attribs.class || node.attribs.classname) {
24+
const classes = splitClasses(node.attribs.class || node.attribs.classname);
25+
classes.map(partial(storeElement, store));
3626
}
3727
if (node.children.length) {
3828
return parseArray(node.children, store);

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"atom": ">=1.0.0 <2.0.0"
2020
},
2121
"dependencies": {
22-
"htmlparser2": "^3.9.0"
22+
"htmlparser2": "^3.9.0",
23+
"partial-any": "0.0.2"
2324
}
2425
}

0 commit comments

Comments
 (0)