Skip to content

Commit c34b605

Browse files
jtranearl-warren
authored andcommitted
Fix task list checkbox toggle to work with YAML front matter (go-gitea#25184) (go-gitea#25236)
Backport go-gitea#25184 by @jtran Closes go-gitea#25225. Fixes go-gitea#25160. `data-source-position` of checkboxes in a task list was incorrect whenever there was YAML front matter. This would result in issue content or PR descriptions getting corrupted with random `x` or space characters when a user checked or unchecked a task. (cherry picked from commit 1650a26)
1 parent 38e5589 commit c34b605

File tree

6 files changed

+66
-7
lines changed

6 files changed

+66
-7
lines changed

modules/markup/markdown/ast.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ func IsSummary(node ast.Node) bool {
7676
// TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox
7777
type TaskCheckBoxListItem struct {
7878
*ast.ListItem
79-
IsChecked bool
79+
IsChecked bool
80+
SourcePosition int
8081
}
8182

8283
// KindTaskCheckBoxListItem is the NodeKind for TaskCheckBoxListItem
@@ -86,6 +87,7 @@ var KindTaskCheckBoxListItem = ast.NewNodeKind("TaskCheckBoxListItem")
8687
func (n *TaskCheckBoxListItem) Dump(source []byte, level int) {
8788
m := map[string]string{}
8889
m["IsChecked"] = strconv.FormatBool(n.IsChecked)
90+
m["SourcePosition"] = strconv.FormatInt(int64(n.SourcePosition), 10)
8991
ast.DumpHelper(n, source, level, m, nil)
9092
}
9193

modules/markup/markdown/goldmark.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
167167
newChild := NewTaskCheckBoxListItem(listItem)
168168
newChild.IsChecked = taskCheckBox.IsChecked
169169
newChild.SetAttributeString("class", []byte("task-list-item"))
170+
segments := newChild.FirstChild().Lines()
171+
if segments.Len() > 0 {
172+
segment := segments.At(0)
173+
newChild.SourcePosition = rc.metaLength + segment.Start
174+
}
170175
v.AppendChild(v, newChild)
171176
}
172177
}
@@ -441,12 +446,7 @@ func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byt
441446
} else {
442447
_, _ = w.WriteString("<li>")
443448
}
444-
_, _ = w.WriteString(`<input type="checkbox" disabled=""`)
445-
segments := node.FirstChild().Lines()
446-
if segments.Len() > 0 {
447-
segment := segments.At(0)
448-
_, _ = w.WriteString(fmt.Sprintf(` data-source-position="%d"`, segment.Start))
449-
}
449+
fmt.Fprintf(w, `<input type="checkbox" disabled="" data-source-position="%d"`, n.SourcePosition)
450450
if n.IsChecked {
451451
_, _ = w.WriteString(` checked=""`)
452452
}

modules/markup/markdown/markdown.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,22 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
173173
}
174174
buf = giteautil.NormalizeEOL(buf)
175175

176+
// Preserve original length.
177+
bufWithMetadataLength := len(buf)
178+
176179
rc := &RenderConfig{
177180
Meta: "table",
178181
Icon: "table",
179182
Lang: "",
180183
}
181184
buf, _ = ExtractMetadataBytes(buf, rc)
182185

186+
metaLength := bufWithMetadataLength - len(buf)
187+
if metaLength < 0 {
188+
metaLength = 0
189+
}
190+
rc.metaLength = metaLength
191+
183192
pc.Set(renderConfigKey, rc)
184193

185194
if err := converter.Convert(buf, lw, parser.WithContext(pc)); err != nil {

modules/markup/markdown/markdown_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,3 +519,40 @@ func TestMathBlock(t *testing.T) {
519519

520520
}
521521
}
522+
523+
func TestTaskList(t *testing.T) {
524+
testcases := []struct {
525+
testcase string
526+
expected string
527+
}{
528+
{
529+
// data-source-position should take into account YAML frontmatter.
530+
`---
531+
foo: bar
532+
---
533+
- [ ] task 1`,
534+
`<table>
535+
<thead>
536+
<tr>
537+
<th>foo</th>
538+
</tr>
539+
</thead>
540+
<tbody>
541+
<tr>
542+
<td>bar</td>
543+
</tr>
544+
</tbody>
545+
</table>
546+
<ul>
547+
<li class="task-list-item"><input type="checkbox" disabled="" data-source-position="19"/>task 1</li>
548+
</ul>
549+
`,
550+
},
551+
}
552+
553+
for _, test := range testcases {
554+
res, err := RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
555+
assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
556+
assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
557+
}
558+
}

modules/markup/markdown/renderconfig.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ type RenderConfig struct {
1818
TOC bool
1919
Lang string
2020
yamlNode *yaml.Node
21+
22+
// Used internally. Cannot be controlled by frontmatter.
23+
metaLength int
2124
}
2225

2326
// UnmarshalYAML implement yaml.v3 UnmarshalYAML

web_src/js/markup/tasklist.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ export function initMarkupTasklist() {
2929

3030
const encoder = new TextEncoder();
3131
const buffer = encoder.encode(oldContent);
32+
// Indexes may fall off the ends and return undefined.
33+
if (buffer[position - 1] !== '['.codePointAt(0) ||
34+
buffer[position] !== ' '.codePointAt(0) && buffer[position] !== 'x'.codePointAt(0) ||
35+
buffer[position + 1] !== ']'.codePointAt(0)) {
36+
// Position is probably wrong. Revert and don't allow change.
37+
checkbox.checked = !checkbox.checked;
38+
throw new Error(`Expected position to be space or x and surrounded by brackets, but it's not: position=${position}`);
39+
}
3240
buffer.set(encoder.encode(checkboxCharacter), position);
3341
const newContent = new TextDecoder().decode(buffer);
3442

0 commit comments

Comments
 (0)