loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix task list checkbox toggle to work with YAML front matter (#25184)

Fixes #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.

authored by

Jonathan Tran and committed by
GitHub
f62cd2f4 419804fd

+66 -7
+3 -1
modules/markup/markdown/ast.go
··· 76 76 // TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox 77 77 type TaskCheckBoxListItem struct { 78 78 *ast.ListItem 79 - IsChecked bool 79 + IsChecked bool 80 + SourcePosition int 80 81 } 81 82 82 83 // KindTaskCheckBoxListItem is the NodeKind for TaskCheckBoxListItem ··· 86 87 func (n *TaskCheckBoxListItem) Dump(source []byte, level int) { 87 88 m := map[string]string{} 88 89 m["IsChecked"] = strconv.FormatBool(n.IsChecked) 90 + m["SourcePosition"] = strconv.FormatInt(int64(n.SourcePosition), 10) 89 91 ast.DumpHelper(n, source, level, m, nil) 90 92 } 91 93
+6 -6
modules/markup/markdown/goldmark.go
··· 177 177 newChild := NewTaskCheckBoxListItem(listItem) 178 178 newChild.IsChecked = taskCheckBox.IsChecked 179 179 newChild.SetAttributeString("class", []byte("task-list-item")) 180 + segments := newChild.FirstChild().Lines() 181 + if segments.Len() > 0 { 182 + segment := segments.At(0) 183 + newChild.SourcePosition = rc.metaLength + segment.Start 184 + } 180 185 v.AppendChild(v, newChild) 181 186 } 182 187 } ··· 457 462 } else { 458 463 _, _ = w.WriteString("<li>") 459 464 } 460 - _, _ = w.WriteString(`<input type="checkbox" disabled=""`) 461 - segments := node.FirstChild().Lines() 462 - if segments.Len() > 0 { 463 - segment := segments.At(0) 464 - _, _ = w.WriteString(fmt.Sprintf(` data-source-position="%d"`, segment.Start)) 465 - } 465 + fmt.Fprintf(w, `<input type="checkbox" disabled="" data-source-position="%d"`, n.SourcePosition) 466 466 if n.IsChecked { 467 467 _, _ = w.WriteString(` checked=""`) 468 468 }
+9
modules/markup/markdown/markdown.go
··· 178 178 } 179 179 buf = giteautil.NormalizeEOL(buf) 180 180 181 + // Preserve original length. 182 + bufWithMetadataLength := len(buf) 183 + 181 184 rc := &RenderConfig{ 182 185 Meta: renderMetaModeFromString(string(ctx.RenderMetaAs)), 183 186 Icon: "table", 184 187 Lang: "", 185 188 } 186 189 buf, _ = ExtractMetadataBytes(buf, rc) 190 + 191 + metaLength := bufWithMetadataLength - len(buf) 192 + if metaLength < 0 { 193 + metaLength = 0 194 + } 195 + rc.metaLength = metaLength 187 196 188 197 pc.Set(renderConfigKey, rc) 189 198
+37
modules/markup/markdown/markdown_test.go
··· 520 520 521 521 } 522 522 } 523 + 524 + func TestTaskList(t *testing.T) { 525 + testcases := []struct { 526 + testcase string 527 + expected string 528 + }{ 529 + { 530 + // data-source-position should take into account YAML frontmatter. 531 + `--- 532 + foo: bar 533 + --- 534 + - [ ] task 1`, 535 + `<details><summary><i class="icon table"></i></summary><table> 536 + <thead> 537 + <tr> 538 + <th>foo</th> 539 + </tr> 540 + </thead> 541 + <tbody> 542 + <tr> 543 + <td>bar</td> 544 + </tr> 545 + </tbody> 546 + </table> 547 + </details><ul> 548 + <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="19"/>task 1</li> 549 + </ul> 550 + `, 551 + }, 552 + } 553 + 554 + for _, test := range testcases { 555 + res, err := RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) 556 + assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) 557 + assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase) 558 + } 559 + }
+3
modules/markup/markdown/renderconfig.go
··· 20 20 TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view 21 21 Lang string 22 22 yamlNode *yaml.Node 23 + 24 + // Used internally. Cannot be controlled by frontmatter. 25 + metaLength int 23 26 } 24 27 25 28 func renderMetaModeFromString(s string) markup.RenderMetaMode {
+8
web_src/js/markup/tasklist.js
··· 29 29 30 30 const encoder = new TextEncoder(); 31 31 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 + } 32 40 buffer.set(encoder.encode(checkboxCharacter), position); 33 41 const newContent = new TextDecoder().decode(buffer); 34 42