forked from dnotes/markdown-it-chords
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
216 lines (172 loc) · 6.46 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
'use strict'
// note half color num extended bass diagram
const CHORD_TEST = /^\[[A-G][b#♭♯]*(?:M|Δ|[Mm]aj|m|[Mm]in|-|–|[Dd]im|o|°|ø|[Aa]ug|\+|[Ss]usp?|[Aa]dd)?(?:1?[\d])?(?:(?:[\(\/,]?(?:[-–+Δob#♭♯]|[Mm]aj|[Mm]in|[Ss]usp?)?[0-9]+\)?)*)?(?:\/[A-G][b#♭♯]*)?(?:\|[XxOo\d,\(\)]{3,})?\]/
// note half color num extended bass diagram
const CHORD_REGEX = /^\[([A-G])([♭♯]*)(M|Δ|[Mm]aj|m|[Mm]in|‑|[Dd]im|°|ø|[Aa]ug|\+|[Ss]usp?|[Aa]dd)?(1?[\d])?((?:[\(\/,]?(?:[‑+Δ°♭♯]|[Mm]aj|[Mm]in|[Ss]usp?)?[0-9]+\)?)*)?(\/[A-G][♭♯]*)?(\|[XxOo\d,\(\)]{3,})?\]/
const EXTENDED_REGEX = /(?:[\(\/,1]*(?:[‑+Δ°♭♯]|[Mm]aj|[Mm]in|[Ss]usp?)?[0-9]+\)?)*/
const DIAGRAM_REGEX = /^\[(?:[XxOo\d,\(\)]{3,})\]/
function chords(state, silent) {
let tail,
chordMatch, // the initial match for the entire chord string
chordSplit, // the chord text split into chord name and diagram
chord, // the grouped match array for the chord name
diagram, // the match for the diagram part of the chord string
extended, // the array of extended color values
token, // placeholder for the token
classes, // the classes for the chord
pos = state.pos // the position in the state
if (state.src.charCodeAt(pos) !== 0x5B/* [ */) return false
tail = state.src.slice(pos)
// this should never happen I think?
/* istanbul ignore if */
if (!tail.length) return false
chordMatch = tail.match(CHORD_TEST)
if (chordMatch) {
chordSplit = chordMatch[0].split('|')
chord = (chordSplit[0] + ']').replace(/b/g, '♭').replace(/#/g, '♯').replace(/o/g, '°').replace(/[-–]/g, '‑').match(CHORD_REGEX)
diagram = parseDiagram(chordSplit[1])
}
else {
chordMatch = (tail.match(DIAGRAM_REGEX) || [''])
diagram = parseDiagram(chordMatch[0])
}
if (!chord && !diagram) return false
classes = chord ? 'chord' : 'chord diagram'
/* istanbul ignore else */
if (!silent) {
token = state.push('chord_open', 'span', 1)
token.attrs = [['class', classes]]
token = state.push('chord_inner_open', 'span', 1)
token.attrs = [['class', 'inner']]
// chord
if (chord) {
extended = chord[5] ? chord[5].match(EXTENDED_REGEX) : false
token = state.push('chord_i_open', 'i', 1)
token.attrs = [['class', 'name']]
token = state.push('text', '', 0)
// note
token.content = chord[1]
// half
if (chord[2]) token.content += chord[2]
// color
if (chord[3] === 'ø') { // handle half diminished, as the unicode character is not widely available
state.push('sup_open', 'sup', 1)
token = state.push('text', '', 0)
token.content = 'ø'
state.push('sup_close', 'sup', -1)
}
else if (chord[3]) { // for all other color
token.content += chord[3]
}
// num
if (chord[4]) {
state.push('sup_open', 'sup', 1)
token = state.push('text', '', 0)
token.content = chord[4]
state.push('sup_close', 'sup', -1)
}
// extended
if (extended) {
extended.forEach(v => {
state.push('sup_open', 'sup', 1)
token = state.push('text', '', 0)
token.content = v
state.push('sup_close', 'sup', -1)
})
}
// bass
if (chord[6]) {
token = state.push('text', '', 0)
token.content = chord[6]
}
token = state.push('chord_i_close', 'i', -1)
} // end chord
// diagram
if (diagram && diagram.length) {
token = state.push('chord_i_open', 'i', 1)
token.attrs = [['class', 'diagram']]
// render each line and then a <br> tag
diagram.forEach(line => {
token = state.push('text', '', 0)
token.content = line
state.push('br', 'br', 0)
})
token = state.push('chord_i_close', 'i', -1)
} // end diagram
token = state.push('chord_inner_close', 'span', -1)
token = state.push('chord_close', 'span', -1)
}
state.pos += chordMatch[0].length
return true
}
function parseDiagram(diagram) {
if (!diagram) return false
const fr = '|', // pipe
nt = String.fromCharCode(0x2016), // double pipe
str = String.fromCharCode(0x0336), // combining long stroke overlay
str0 = String.fromCharCode(0x0335), // combining short stroke overlay
sp = String.fromCharCode(0xa0), // non-breaking space
finger = String.fromCharCode(0x25cf), // black circle
optional = String.fromCharCode(0x25cb) // white circle
let min = 99, // minimum used fret
max = 0, // maximum used fret
fret, // fret number on which to place character
frets, // whether to show the frets for a line (first and last lines should not)
char, // character to place on fret
nut = str0, // the beginning of a line
line, // a single line
lines = [] // the rendered lines
// Remove wrappers and separators
diagram = diagram.replace(/[\[\]|]/g, '').replace(/[Oo]/g, '0')
// Split diagram (commas are used for frets beyond 9)
diagram = /,/.test(diagram) ? diagram.split(',') : diagram.match(/\(?[XxOo\d]\)?/g)
// Reverse the diagram
diagram.reverse()
// Gather raw numbers and get minimum fret
diagram = diagram.map(v => {
// get the fret number
fret = parseInt(v.replace(/[\(\)]/g, ''), 10)
// check for min and max
if (fret && fret < min) min = fret
if (fret && fret > max) max = fret
// get the proper character
if (isNaN(fret)) char = 'x'
else if (!fret) char = sp
else if (/\(/.test(v)) char = optional
else char = finger
// return the full data
return {
fret: fret || 0,
'char': char,
}
})
// don't capo if a chord is within the first four frets
if (max <= 4) min = 1
// make sure there are at least three frets
if (max - min < 2) max++
// render fret number and strings, if needed
/* istanbul ignore else */
if (min > 1) {
lines.push(`${sp}${min}fr`)
nut = str
}
// render each position of the diagram
diagram.forEach((o, idx) => {
frets = idx && (idx < (diagram.length - 1))
// initial space or x
line = o.char === 'x' ? 'x' : sp
// first fret of line
line += `${frets ? nt : sp}${nut}`
for (let i = min; i <= max; i++) {
// character in position
line += i === o.fret ? o.char : `${sp}${str}`
// string and fret after
line += `${frets ? fr : sp}${str}`
}
lines.push(`${line}`)
})
return lines
}
module.exports = function plugin(md) {
md.inline.ruler.push('chords', chords)
}