Add support for list item checkboxes (e.g. [X])

see https://orgmode.org/manual/Checkboxes.html
We're deviating from Org mode regarding the assigned css classes but the chosen
classes feel better than (on, off, trans) and it's not like the export matches
1:1 otherwise.
This commit is contained in:
Niklas Fasching 2018-12-18 14:15:35 +01:00
parent a60f844e38
commit 7331d24452
7 changed files with 55 additions and 35 deletions

View file

@ -10,15 +10,7 @@ Take a look at [[https://niklasfasching.github.io/go-org/][github pages]] for so
- see [[https://github.com/kaushalmodi/ox-hugo/blob/8472cf2d8667754c9da3728255634e8001a1da6d/ox-hugo.el#L1785-L1850][ox-hugo]] for auto generation - see [[https://github.com/kaushalmodi/ox-hugo/blob/8472cf2d8667754c9da3728255634e8001a1da6d/ox-hugo.el#L1785-L1850][ox-hugo]] for auto generation
- improve list parsing - improve list parsing
- handle list items with empty first line - handle list items with empty first line
- handle checkboxes & statistic cookies
- handle ordered list overrides [@10] - handle ordered list overrides [@10]
** TODO list checkboxes & statistics [0/2]
https://orgmode.org/manual/Checkboxes.html
1. [ ] parse checkbox of list item
- convert to html radio button unchecked, indeterminate (visual only), checked
2. [ ] parse statistic cookies of headlines & first line of list items
-convert to =[sub 1<sup>1</sup>&frasl;<sub>2</sub>] || [90%]=
* differences to goorgeous * differences to goorgeous
To get a feeling take a look at goorgeous vs go-org html rendering of the examples [[https://niklasfasching.github.io/go-org/go-org-vs-goorgeous][comparison]]. To get a feeling take a look at goorgeous vs go-org html rendering of the examples [[https://niklasfasching.github.io/go-org/go-org-vs-goorgeous][comparison]].
Please note that a visual comparison is not fair to goorgeous as the stylesheet is not adapted to it. Please note that a visual comparison is not fair to goorgeous as the stylesheet is not adapted to it.

View file

@ -34,6 +34,12 @@ var listTags = map[string][]string{
"descriptive": []string{"<dl>", "</dl>"}, "descriptive": []string{"<dl>", "</dl>"},
} }
var listItemStatuses = map[string]string{
" ": "unchecked",
"-": "indeterminate",
"X": "checked",
}
func NewHTMLWriter() *HTMLWriter { func NewHTMLWriter() *HTMLWriter {
return &HTMLWriter{ return &HTMLWriter{
htmlEscape: true, htmlEscape: true,
@ -283,13 +289,22 @@ func (w *HTMLWriter) writeList(l List) {
} }
func (w *HTMLWriter) writeListItem(li ListItem) { func (w *HTMLWriter) writeListItem(li ListItem) {
w.WriteString("<li>\n") if li.Status != "" {
w.WriteString(fmt.Sprintf("<li class=\"%s\">\n", listItemStatuses[li.Status]))
} else {
w.WriteString("<li>\n")
}
w.writeNodes(li.Children...) w.writeNodes(li.Children...)
w.WriteString("</li>\n") w.WriteString("</li>\n")
} }
func (w *HTMLWriter) writeDescriptiveListItem(di DescriptiveListItem) { func (w *HTMLWriter) writeDescriptiveListItem(di DescriptiveListItem) {
w.WriteString("<dt>\n") if di.Status != "" {
w.WriteString(fmt.Sprintf("<dt class=\"%s\">\n", listItemStatuses[di.Status]))
} else {
w.WriteString("<dt>\n")
}
if len(di.Term) != 0 { if len(di.Term) != 0 {
w.writeNodes(di.Term...) w.writeNodes(di.Term...)
} else { } else {

View file

@ -14,11 +14,13 @@ type List struct {
type ListItem struct { type ListItem struct {
Bullet string Bullet string
Status string
Children []Node Children []Node
} }
type DescriptiveListItem struct { type DescriptiveListItem struct {
Bullet string Bullet string
Status string
Term []Node Term []Node
Details []Node Details []Node
} }
@ -26,6 +28,7 @@ type DescriptiveListItem struct {
var unorderedListRegexp = regexp.MustCompile(`^(\s*)([-]|[+]|[*])\s(.*)`) var unorderedListRegexp = regexp.MustCompile(`^(\s*)([-]|[+]|[*])\s(.*)`)
var orderedListRegexp = regexp.MustCompile(`^(\s*)(([0-9]+|[a-zA-Z])[.)])\s+(.*)`) var orderedListRegexp = regexp.MustCompile(`^(\s*)(([0-9]+|[a-zA-Z])[.)])\s+(.*)`)
var descriptiveListItemRegexp = regexp.MustCompile(`\s::(\s|$)`) var descriptiveListItemRegexp = regexp.MustCompile(`\s::(\s|$)`)
var listItemStatusRegexp = regexp.MustCompile(`\[( |X|-)\]\s`)
func lexList(line string) (token, bool) { func lexList(line string) (token, bool) {
if m := unorderedListRegexp.FindStringSubmatch(line); m != nil { if m := unorderedListRegexp.FindStringSubmatch(line); m != nil {
@ -79,12 +82,16 @@ func (d *Document) parseList(i int, parentStop stopFn) (int, Node) {
func (d *Document) parseListItem(l List, i int, parentStop stopFn) (int, Node) { func (d *Document) parseListItem(l List, i int, parentStop stopFn) (int, Node) {
start, nodes, bullet := i, []Node{}, d.tokens[i].matches[2] start, nodes, bullet := i, []Node{}, d.tokens[i].matches[2]
minIndent, dterm, content := d.tokens[i].lvl+len(bullet), "", d.tokens[i].content minIndent, dterm, content, status := d.tokens[i].lvl+len(bullet), "", d.tokens[i].content, ""
if m := listItemStatusRegexp.FindStringSubmatch(content); m != nil {
status, content = m[1], content[len("[ ] "):]
}
if l.Kind == "descriptive" { if l.Kind == "descriptive" {
if m := descriptiveListItemRegexp.FindStringIndex(content); m != nil { if m := descriptiveListItemRegexp.FindStringIndex(content); m != nil {
dterm, content = content[:m[0]], content[m[1]:] dterm, content = content[:m[0]], content[m[1]:]
} }
} }
d.tokens[i] = tokenize(strings.Repeat(" ", minIndent) + content) d.tokens[i] = tokenize(strings.Repeat(" ", minIndent) + content)
stop := func(d *Document, i int) bool { stop := func(d *Document, i int) bool {
if parentStop(d, i) { if parentStop(d, i) {
@ -99,7 +106,7 @@ func (d *Document) parseListItem(l List, i int, parentStop stopFn) (int, Node) {
nodes = append(nodes, node) nodes = append(nodes, node)
} }
if l.Kind == "descriptive" { if l.Kind == "descriptive" {
return i - start, DescriptiveListItem{bullet, d.parseInline(dterm), nodes} return i - start, DescriptiveListItem{bullet, status, d.parseInline(dterm), nodes}
} }
return i - start, ListItem{bullet, nodes} return i - start, ListItem{bullet, status, nodes}
} }

View file

@ -203,6 +203,9 @@ func (w *OrgWriter) writeListItem(li ListItem) {
liWriter.writeNodes(li.Children...) liWriter.writeNodes(li.Children...)
content := strings.TrimPrefix(liWriter.String(), liWriter.indent) content := strings.TrimPrefix(liWriter.String(), liWriter.indent)
w.WriteString(w.indent + li.Bullet) w.WriteString(w.indent + li.Bullet)
if li.Status != "" {
w.WriteString(fmt.Sprintf(" [%s]", li.Status))
}
if len(content) > 0 && content[0] == '\n' { if len(content) > 0 && content[0] == '\n' {
w.WriteString(content) w.WriteString(content)
} else { } else {
@ -212,6 +215,9 @@ func (w *OrgWriter) writeListItem(li ListItem) {
func (w *OrgWriter) writeDescriptiveListItem(di DescriptiveListItem) { func (w *OrgWriter) writeDescriptiveListItem(di DescriptiveListItem) {
w.WriteString(w.indent + di.Bullet) w.WriteString(w.indent + di.Bullet)
if di.Status != "" {
w.WriteString(fmt.Sprintf(" [%s]", di.Status))
}
indent := w.indent + strings.Repeat(" ", len(di.Bullet)+1) indent := w.indent + strings.Repeat(" ", len(di.Bullet)+1)
if len(di.Term) != 0 { if len(di.Term) != 0 {
term := w.nodesAsString(di.Term...) term := w.nodesAsString(di.Term...)

View file

@ -2,14 +2,14 @@
Simple Headline <code class="statistic">[1/2]</code> Simple Headline <code class="statistic">[1/2]</code>
</h1> </h1>
<ul> <ul>
<li> <li class="checked">
<p> <p>
[X] checked checked
</p> </p>
</li> </li>
<li> <li class="unchecked">
<p> <p>
[ ] unchecked unchecked
</p> </p>
</li> </li>
<li> <li>

View file

@ -1,5 +1,5 @@
<ul> <ul>
<li> <li class="unchecked">
<p> <p>
unordered list item 1 unordered list item 1
</p> </p>
@ -9,22 +9,22 @@ unordered list item 1
unordered list item 2 - with <code>inline</code> <em>markup</em> unordered list item 2 - with <code>inline</code> <em>markup</em>
</p> </p>
<ol> <ol>
<li> <li class="indeterminate">
<p> <p>
ordered sublist item 1 ordered sublist item 1
</p> </p>
<ol> <ol>
<li> <li class="checked">
<p> <p>
ordered sublist item 1 ordered sublist item 1
</p> </p>
</li> </li>
<li> <li class="unchecked">
<p> <p>
ordered sublist item 2 ordered sublist item 2
</p> </p>
</li> </li>
<li> <li class="checked">
<p> <p>
ordered sublist item 3 ordered sublist item 3
</p> </p>
@ -38,7 +38,7 @@ ordered sublist item 2
</li> </li>
</ol> </ol>
</li> </li>
<li> <li class="checked">
<p> <p>
unordered list item 3 - and a <a href="https://example.com">link</a> unordered list item 3 - and a <a href="https://example.com">link</a>
and some lines of text and some lines of text
@ -95,20 +95,20 @@ that spans multiple lines
descriptive lists descriptive lists
</p> </p>
<dl> <dl>
<dt> <dt class="unchecked">
term<dd> term<dd>
<p> <p>
details details
continued details continued details
</p> </p>
<dd> <dd>
<dt> <dt class="unchecked">
?<dd> ?<dd>
<p> <p>
details without a term details without a term
</p> </p>
<dd> <dd>
<dt> <dt class="checked">
term<dd> term<dd>
<p> <p>
details on a new line details on a new line

View file

@ -1,11 +1,11 @@
- unordered list item 1 - [ ] unordered list item 1
- unordered list item 2 - with ~inline~ /markup/ - unordered list item 2 - with ~inline~ /markup/
1. ordered sublist item 1 1. [-] ordered sublist item 1
a) ordered sublist item 1 a) [X] ordered sublist item 1
b) ordered sublist item 2 b) [ ] ordered sublist item 2
c) ordered sublist item 3 c) [X] ordered sublist item 3
2. ordered sublist item 2 2. ordered sublist item 2
- unordered list item 3 - and a [[https://example.com][link]] - [X] unordered list item 3 - and a [[https://example.com][link]]
and some lines of text and some lines of text
1. and another subitem 1. and another subitem
#+BEGIN_SRC sh #+BEGIN_SRC sh
@ -24,10 +24,10 @@
descriptive lists descriptive lists
- term :: details - [ ] term :: details
continued details continued details
- details without a term - [ ] details without a term
- term :: - [X] term ::
details on a new line details on a new line
- term :: - term ::