Skip to content

Commit 17dfce5

Browse files
Allow customising formatter CSS classes (#1552)
1 parent b35194f commit 17dfce5

13 files changed

+816
-251
lines changed

src/formatter/formatter.ts

+16-16
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,22 @@ class Formatter {
77
configuration: Configuration;
88

99
/**
10-
* Instantiate
11-
* @param {Object} [configuration={}] options
12-
* @param {boolean} [configuration.evaluate=false] Whether or not to evaluate meta expressions.
13-
* For more info about meta expressions, see: https://bit.ly/2SC9c2u
14-
* @param {object} [configuration.metadata={}]
15-
* @param {string} [configuration.metadata.separator=", "] The separator to be used when rendering a
16-
* metadata value that has multiple values. See: https://bit.ly/2SC9c2u
17-
* @param {Key|string} [configuration.key=null] The key to use for rendering. The chord sheet will be
18-
* transposed from the song's original key (as indicated by the `{key}` directive) to the specified key.
19-
* Note that transposing will only work if the original song key is set.
20-
* @param {boolean} [configuration.expandChorusDirective=false] Whether or not to expand `{chorus}` directives
21-
* by rendering the last defined chorus inline after the directive.
22-
* @param {boolean} [configuration.useUnicodeModifiers=false] Whether or not to use unicode flat and sharp
23-
* symbols.
24-
* @param {boolean} [configuration.normalizeChords=true] Whether or not to automatically normalize chords
25-
*/
10+
* Instantiate
11+
* @param {Object} [configuration={}] options
12+
* @param {boolean} [configuration.evaluate=false] Whether or not to evaluate meta expressions.
13+
* For more info about meta expressions, see: https://bit.ly/2SC9c2u
14+
* @param {object} [configuration.metadata={}]
15+
* @param {string} [configuration.metadata.separator=", "] The separator to be used when rendering a
16+
* metadata value that has multiple values. See: https://bit.ly/2SC9c2u
17+
* @param {Key|string} [configuration.key=null] The key to use for rendering. The chord sheet will be
18+
* transposed from the song's original key (as indicated by the `{key}` directive) to the specified key.
19+
* Note that transposing will only work if the original song key is set.
20+
* @param {boolean} [configuration.expandChorusDirective=false] Whether or not to expand `{chorus}` directives
21+
* by rendering the last defined chorus inline after the directive.
22+
* @param {boolean} [configuration.useUnicodeModifiers=false] Whether or not to use unicode flat and sharp
23+
* symbols.
24+
* @param {boolean} [configuration.normalizeChords=true] Whether or not to automatically normalize chords
25+
*/
2626
constructor(configuration: ConfigurationProperties = {}) {
2727
this.configuration = configure(configuration);
2828
}

src/formatter/html_div_formatter.ts

+31-32
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,35 @@
1-
import HtmlFormatter, { CSS, Template } from './html_formatter';
1+
import HtmlFormatter, { CSS, HtmlTemplateCssClasses, Template } from './html_formatter';
22
import template from './templates/html_div_formatter';
3-
import { scopeCss } from '../utilities';
43

5-
const defaultCss: CSS = {
6-
'.chord:not(:last-child)': {
7-
paddingRight: '10px',
8-
},
9-
10-
'.paragraph': {
11-
marginBottom: '1em',
12-
},
13-
14-
'.row': {
15-
display: 'flex',
16-
},
17-
18-
'.chord:after': {
19-
content: '\'\\200b\'',
20-
},
21-
22-
'.lyrics:after': {
23-
content: '\'\\200b\'',
24-
},
25-
};
26-
27-
/**
28-
* Generates basic CSS, scoped within the provided selector, to use with output generated by {@link HtmlTableFormatter}
29-
* @param scope the CSS scope to use, for example `.chordSheetViewer`
30-
* @returns {string} the CSS string
31-
*/
32-
export function scopedCss(scope: string): string {
33-
return scopeCss(defaultCss, scope);
4+
function defaultCss(cssClasses: HtmlTemplateCssClasses): CSS {
5+
const {
6+
chord,
7+
lyrics,
8+
paragraph,
9+
row,
10+
} = cssClasses;
11+
12+
return {
13+
[`.${chord}:not(:last-child)`]: {
14+
paddingRight: '10px',
15+
},
16+
17+
[`.${paragraph}`]: {
18+
marginBottom: '1em',
19+
},
20+
21+
[`.${row}`]: {
22+
display: 'flex',
23+
},
24+
25+
[`.${chord}:after`]: {
26+
content: '\'\\200b\'',
27+
},
28+
29+
[`.${lyrics}:after`]: {
30+
content: '\'\\200b\'',
31+
},
32+
};
3433
}
3534

3635
/**
@@ -42,7 +41,7 @@ class HtmlDivFormatter extends HtmlFormatter {
4241
}
4342

4443
get defaultCss(): CSS {
45-
return defaultCss;
44+
return defaultCss(this.cssClasses);
4645
}
4746
}
4847

src/formatter/html_formatter.ts

+78-2
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,98 @@
11
import Formatter from './formatter';
2-
import Configuration from './configuration';
2+
import Configuration, { ConfigurationProperties } from './configuration';
33
import Song from '../chord_sheet/song';
44
import { scopeCss } from '../utilities';
55
import Paragraph from '../chord_sheet/paragraph';
66

7+
export interface HtmlTemplateCssClasses {
8+
annotation: string,
9+
chord: string,
10+
chordSheet: string,
11+
column: string,
12+
comment: string,
13+
emptyLine: string,
14+
label: string,
15+
labelWrapper: string,
16+
line: string,
17+
literal: string,
18+
literalContents: string,
19+
lyrics: string,
20+
paragraph: string,
21+
row: string,
22+
subtitle: string,
23+
title: string,
24+
}
25+
726
export interface HtmlTemplateArgs {
827
configuration: Configuration;
928
song: Song;
1029
renderBlankLines?: boolean;
1130
bodyParagraphs: Paragraph[],
31+
cssClasses: HtmlTemplateCssClasses,
1232
}
1333

1434
export type Template = (_args: HtmlTemplateArgs) => string;
1535
export type CSS = Record<string, Record<string, string>>;
1636

37+
export const defaultCssClasses: HtmlTemplateCssClasses = {
38+
annotation: 'annotation',
39+
chord: 'chord',
40+
chordSheet: 'chord-sheet',
41+
column: 'column',
42+
comment: 'comment',
43+
emptyLine: 'empty-line',
44+
label: 'label',
45+
labelWrapper: 'label-wrapper',
46+
line: 'line',
47+
literal: 'literal',
48+
literalContents: 'contents',
49+
lyrics: 'lyrics',
50+
paragraph: 'paragraph',
51+
row: 'row',
52+
subtitle: 'subtitle',
53+
title: 'title',
54+
};
55+
1756
/**
1857
* Acts as a base class for HTML formatters
1958
*/
2059
abstract class HtmlFormatter extends Formatter {
60+
cssClasses: HtmlTemplateCssClasses;
61+
62+
/**
63+
* Instantiate the formatter. For all options see {@link Formatter}
64+
* @param {Object} [configuration={}] options
65+
* @param {object} [configuration.cssClasses={}] CSS classes to use in the HTML output. The default classes are
66+
* defined in {@link defaultCssClasses}. You can override them by providing your own classes here:
67+
* @example
68+
* ```javascript
69+
* {
70+
* cssClasses: {
71+
* annotation: 'my-annotation',
72+
* chord: 'my-chord',
73+
* chordSheet: 'my-chord-sheet',
74+
* column: 'my-column',
75+
* comment: 'my-comment',
76+
* emptyLine: 'my-empty-line',
77+
* label: 'my-label',
78+
* labelWrapper: 'my-label-wrapper',
79+
* line: 'my-line',
80+
* literal: 'my-literal',
81+
* literalContents: 'my-contents',
82+
* lyrics: 'my-lyrics',
83+
* paragraph: 'my-paragraph',
84+
* row: 'my-row',
85+
* subtitle: 'my-subtitle',
86+
* title: 'my-title',
87+
* }
88+
* }
89+
* ```
90+
*/
91+
constructor(configuration: ConfigurationProperties & { cssClasses?: Partial<HtmlTemplateCssClasses> } = {}) {
92+
super(configuration);
93+
this.cssClasses = { ...defaultCssClasses, ...configuration.cssClasses };
94+
}
95+
2196
/**
2297
* Formats a song into HTML.
2398
* @param {Song} song The song to be formatted
@@ -31,6 +106,7 @@ abstract class HtmlFormatter extends Formatter {
31106
song,
32107
configuration: this.configuration,
33108
bodyParagraphs: this.configuration.expandChorusDirective ? expandedBodyParagraphs : bodyParagraphs,
109+
cssClasses: this.cssClasses,
34110
},
35111
);
36112
}
@@ -48,7 +124,7 @@ abstract class HtmlFormatter extends Formatter {
48124
* @returns {string} the CSS string
49125
*/
50126
cssString(scope = ''): string {
51-
return scopeCss(this.defaultCss, scope);
127+
return scopeCss(this.cssObject, scope);
52128
}
53129

54130
/**

src/formatter/html_table_formatter.ts

+38-31
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,43 @@
1-
import HtmlFormatter, { Template, CSS } from './html_formatter';
1+
import HtmlFormatter, { Template, CSS, HtmlTemplateCssClasses } from './html_formatter';
22
import template from './templates/html_table_formatter';
3-
import { scopeCss } from '../utilities';
43

5-
const defaultCss: CSS = {
6-
'h1': {
7-
fontSize: '1.5em',
8-
},
9-
'h2': {
10-
fontSize: '1.1em',
11-
},
12-
'table': {
13-
borderSpacing: '0',
14-
color: 'inherit',
15-
},
16-
'td': {
17-
padding: '3px 0',
18-
},
19-
'.chord:not(:last-child)': {
20-
paddingRight: '10px',
21-
},
22-
'.paragraph': {
23-
marginBottom: '1em',
24-
},
25-
};
4+
function defaultCss(cssClasses: HtmlTemplateCssClasses): CSS {
5+
const {
6+
annotation,
7+
chord,
8+
comment,
9+
labelWrapper,
10+
line,
11+
literal,
12+
literalContents,
13+
lyrics,
14+
paragraph,
15+
row,
16+
subtitle,
17+
title,
18+
} = cssClasses;
2619

27-
/**
28-
* Generates basic CSS, scoped within the provided selector, to use with output generated by {@link HtmlTableFormatter}
29-
* @param scope the CSS scope to use, for example `.chordSheetViewer`
30-
* @returns {string} the CSS string
31-
*/
32-
export function scopedCss(scope: string): string {
33-
return scopeCss(defaultCss, scope);
20+
return {
21+
[`.${title}`]: {
22+
fontSize: '1.5em',
23+
},
24+
[`.${subtitle}`]: {
25+
fontSize: '1.1em',
26+
},
27+
[`.${row}, .${line}, .${literal}`]: {
28+
borderSpacing: '0',
29+
color: 'inherit',
30+
},
31+
[`.${annotation}, .${chord}, .${comment}, .${literalContents}, .${labelWrapper}, .${literal}, .${lyrics}`]: {
32+
padding: '3px 0',
33+
},
34+
[`.${chord}:not(:last-child)`]: {
35+
paddingRight: '10px',
36+
},
37+
[`.${paragraph}`]: {
38+
marginBottom: '1em',
39+
},
40+
};
3441
}
3542

3643
/**
@@ -43,7 +50,7 @@ class HtmlTableFormatter extends HtmlFormatter {
4350
}
4451

4552
get defaultCss(): CSS {
46-
return defaultCss;
53+
return defaultCss(this.cssClasses);
4754
}
4855
}
4956

src/formatter/templates/html_div_formatter.ts

+19-18
Original file line numberDiff line numberDiff line change
@@ -31,35 +31,36 @@ export default (
3131
metadata,
3232
},
3333
bodyParagraphs,
34+
cssClasses: c,
3435
}: HtmlTemplateArgs,
3536
): string => stripHTML(`
36-
${ when(title, () => `<h1>${ title }</h1>`) }
37-
${ when(subtitle, () => `<h2>${ subtitle }</h2>`) }
37+
${ when(title, () => `<h1 class="${ c.title }">${ title }</h1>`) }
38+
${ when(subtitle, () => `<h2 class="${ c.subtitle }">${ subtitle }</h2>`) }
3839
39-
<div class="chord-sheet">
40+
<div class="${ c.chordSheet }">
4041
${ each(bodyParagraphs, (paragraph) => `
41-
<div class="${ paragraphClasses(paragraph) }">
42+
<div class="${ paragraphClasses(paragraph, c) }">
4243
${ when(paragraph.isLiteral(), () => `
4344
${ when(isPresent(paragraph.label), () => `
44-
<div class="row">
45-
<h3 class="label">${ paragraph.label }</h3>
45+
<div class="${ c.row }">
46+
<h3 class="${ c.label }">${ paragraph.label }</h3>
4647
</div>
4748
`) }
4849
49-
<div class="row">
50-
<div class="literal">${ newlinesToBreaks(renderSection(paragraph, configuration)) }</div>
50+
<div class="${ c.row }">
51+
<div class="${ c.literal }">${ newlinesToBreaks(renderSection(paragraph, configuration)) }</div>
5152
</div>
5253
`).else(() => `
5354
${ each(paragraph.lines, (line) => `
5455
${ when(renderBlankLines || lineHasContents(line), () => `
55-
<div class="${ lineClasses(line) }">
56+
<div class="${ lineClasses(line, c) }">
5657
${ each(line.items, (item) => `
5758
${ when(isChordLyricsPair(item), () => `
58-
<div class="column">
59+
<div class="${ c.column }">
5960
${ when(item.annotation).then(() => `
60-
<div class="annotation"${ fontStyleTag(line.chordFont) }>${ item.annotation }</div>
61+
<div class="${ c.annotation }"${ fontStyleTag(line.chordFont) }>${ item.annotation }</div>
6162
`).else(() => `
62-
<div class="chord"${ fontStyleTag(line.chordFont) }>
63+
<div class="${ c.chord }"${ fontStyleTag(line.chordFont) }>
6364
${ renderChord(
6465
item.chords,
6566
line,
@@ -73,20 +74,20 @@ export default (
7374
) }
7475
</div>
7576
`) }
76-
<div class="lyrics"${ fontStyleTag(line.textFont) }>${ item.lyrics }</div>
77+
<div class="${ c.lyrics }"${ fontStyleTag(line.textFont) }>${ item.lyrics }</div>
7778
</div>
7879
`).elseWhen(isTag(item), () => `
7980
${ when(isComment(item), () => `
80-
<div class="comment">${ item.value }</div>
81+
<div class="${ c.comment }">${ item.value }</div>
8182
`) }
8283
8384
${ when(item.hasRenderableLabel(), () => `
84-
<h3 class="label">${ item.label }</h3>
85+
<h3 class="${ c.label }">${ item.label }</h3>
8586
`) }
8687
`).elseWhen(isEvaluatable(item), () => `
87-
<div class="column">
88-
<div class="chord"></div>
89-
<div class="lyrics"${ fontStyleTag(line.textFont) }>
88+
<div class="${ c.column }">
89+
<div class="${ c.chord }"></div>
90+
<div class="${ c.lyrics }"${ fontStyleTag(line.textFont) }>
9091
${ evaluate(item, metadata, configuration) }
9192
</div>
9293
</div>

0 commit comments

Comments
 (0)