summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonathan Tran <jon@allspice.io>2023-06-13 02:44:47 -0400
committerGitHub <noreply@github.com>2023-06-13 14:44:47 +0800
commitf62cd2f4738c1b3cf7c31e8b98702a709bdd4072 (patch)
tree17dac9b30739851d0a90b848f79f726c05659979
parent419804fd4d5cb655a51f245010b8eb1163b26bc2 (diff)
downloadgitea-f62cd2f4738c1b3cf7c31e8b98702a709bdd4072.tar.gz
gitea-f62cd2f4738c1b3cf7c31e8b98702a709bdd4072.zip
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.
-rw-r--r--modules/markup/markdown/ast.go4
-rw-r--r--modules/markup/markdown/goldmark.go12
-rw-r--r--modules/markup/markdown/markdown.go9
-rw-r--r--modules/markup/markdown/markdown_test.go37
-rw-r--r--modules/markup/markdown/renderconfig.go3
-rw-r--r--web_src/js/markup/tasklist.js8
6 files changed, 66 insertions, 7 deletions
diff --git a/modules/markup/markdown/ast.go b/modules/markup/markdown/ast.go
index e844f801c4..3e6e291ab2 100644
--- a/modules/markup/markdown/ast.go
+++ b/modules/markup/markdown/ast.go
@@ -76,7 +76,8 @@ func IsSummary(node ast.Node) bool {
// TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox
type TaskCheckBoxListItem struct {
*ast.ListItem
- IsChecked bool
+ IsChecked bool
+ SourcePosition int
}
// KindTaskCheckBoxListItem is the NodeKind for TaskCheckBoxListItem
@@ -86,6 +87,7 @@ var KindTaskCheckBoxListItem = ast.NewNodeKind("TaskCheckBoxListItem")
func (n *TaskCheckBoxListItem) Dump(source []byte, level int) {
m := map[string]string{}
m["IsChecked"] = strconv.FormatBool(n.IsChecked)
+ m["SourcePosition"] = strconv.FormatInt(int64(n.SourcePosition), 10)
ast.DumpHelper(n, source, level, m, nil)
}
diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go
index f03a780900..ff4e6b1bd0 100644
--- a/modules/markup/markdown/goldmark.go
+++ b/modules/markup/markdown/goldmark.go
@@ -177,6 +177,11 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
newChild := NewTaskCheckBoxListItem(listItem)
newChild.IsChecked = taskCheckBox.IsChecked
newChild.SetAttributeString("class", []byte("task-list-item"))
+ segments := newChild.FirstChild().Lines()
+ if segments.Len() > 0 {
+ segment := segments.At(0)
+ newChild.SourcePosition = rc.metaLength + segment.Start
+ }
v.AppendChild(v, newChild)
}
}
@@ -457,12 +462,7 @@ func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byt
} else {
_, _ = w.WriteString("<li>")
}
- _, _ = w.WriteString(`<input type="checkbox" disabled=""`)
- segments := node.FirstChild().Lines()
- if segments.Len() > 0 {
- segment := segments.At(0)
- _, _ = w.WriteString(fmt.Sprintf(` data-source-position="%d"`, segment.Start))
- }
+ fmt.Fprintf(w, `<input type="checkbox" disabled="" data-source-position="%d"`, n.SourcePosition)
if n.IsChecked {
_, _ = w.WriteString(` checked=""`)
}
diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go
index d4a7195dc5..43885889d1 100644
--- a/modules/markup/markdown/markdown.go
+++ b/modules/markup/markdown/markdown.go
@@ -178,6 +178,9 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
}
buf = giteautil.NormalizeEOL(buf)
+ // Preserve original length.
+ bufWithMetadataLength := len(buf)
+
rc := &RenderConfig{
Meta: renderMetaModeFromString(string(ctx.RenderMetaAs)),
Icon: "table",
@@ -185,6 +188,12 @@ func actualRender(ctx *markup.RenderContext, input io.Reader, output io.Writer)
}
buf, _ = ExtractMetadataBytes(buf, rc)
+ metaLength := bufWithMetadataLength - len(buf)
+ if metaLength < 0 {
+ metaLength = 0
+ }
+ rc.metaLength = metaLength
+
pc.Set(renderConfigKey, rc)
if err := converter.Convert(buf, lw, parser.WithContext(pc)); err != nil {
diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go
index e81869d7a4..4bd2ca8d41 100644
--- a/modules/markup/markdown/markdown_test.go
+++ b/modules/markup/markdown/markdown_test.go
@@ -520,3 +520,40 @@ func TestMathBlock(t *testing.T) {
}
}
+
+func TestTaskList(t *testing.T) {
+ testcases := []struct {
+ testcase string
+ expected string
+ }{
+ {
+ // data-source-position should take into account YAML frontmatter.
+ `---
+foo: bar
+---
+- [ ] task 1`,
+ `<details><summary><i class="icon table"></i></summary><table>
+<thead>
+<tr>
+<th>foo</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>bar</td>
+</tr>
+</tbody>
+</table>
+</details><ul>
+<li class="task-list-item"><input type="checkbox" disabled="" data-source-position="19"/>task 1</li>
+</ul>
+`,
+ },
+ }
+
+ for _, test := range testcases {
+ res, err := RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
+ assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
+ assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase)
+ }
+}
diff --git a/modules/markup/markdown/renderconfig.go b/modules/markup/markdown/renderconfig.go
index 691df74312..f4c48d1b3d 100644
--- a/modules/markup/markdown/renderconfig.go
+++ b/modules/markup/markdown/renderconfig.go
@@ -20,6 +20,9 @@ type RenderConfig struct {
TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view
Lang string
yamlNode *yaml.Node
+
+ // Used internally. Cannot be controlled by frontmatter.
+ metaLength int
}
func renderMetaModeFromString(s string) markup.RenderMetaMode {
diff --git a/web_src/js/markup/tasklist.js b/web_src/js/markup/tasklist.js
index 0f03837baa..ad1c6964a7 100644
--- a/web_src/js/markup/tasklist.js
+++ b/web_src/js/markup/tasklist.js
@@ -29,6 +29,14 @@ export function initMarkupTasklist() {
const encoder = new TextEncoder();
const buffer = encoder.encode(oldContent);
+ // Indexes may fall off the ends and return undefined.
+ if (buffer[position - 1] !== '['.codePointAt(0) ||
+ buffer[position] !== ' '.codePointAt(0) && buffer[position] !== 'x'.codePointAt(0) ||
+ buffer[position + 1] !== ']'.codePointAt(0)) {
+ // Position is probably wrong. Revert and don't allow change.
+ checkbox.checked = !checkbox.checked;
+ throw new Error(`Expected position to be space or x and surrounded by brackets, but it's not: position=${position}`);
+ }
buffer.set(encoder.encode(checkboxCharacter), position);
const newContent = new TextDecoder().decode(buffer);