Skip to content

Commit c02fdf7

Browse files
authored
fix(docs): preserve block replacement structure (#842)
1 parent a91cead commit c02fdf7

3 files changed

Lines changed: 84 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
### Fixed
1414

15+
- Docs: preserve the matched paragraph's list or heading structure on the first plain paragraph of a block Markdown replacement. (#838) — thanks @sebsnyk.
1516
- Calendar: report multi-calendar event truncation on stderr for text output and as per-calendar page tokens in JSON. (#831) — thanks @TurboTheTurtle.
1617
- Downloads: protect Drive downloads, Docs/Sheets/Slides exports, Docs tab exports, and Slides thumbnails from replacing existing files unless `--overwrite` is passed. (#827, #829) — thanks @WadydX.
1718
- Docs: update the Docker authentication example to persist file-keyring tokens with `GOG_HOME`. (#828, #830) — thanks @WadydX.

internal/cmd/docs_find_replace_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,75 @@ func TestDocsFindReplace_MarkdownResetsInheritedParagraphStyle(t *testing.T) {
536536
}
537537
}
538538

539+
func TestDocsFindReplace_MarkdownBlockPreservesFirstPlainParagraphStyle(t *testing.T) {
540+
for _, tc := range []struct {
541+
name string
542+
paragraphStyle map[string]any
543+
bullet map[string]any
544+
}{
545+
{
546+
name: "heading",
547+
paragraphStyle: map[string]any{
548+
"namedStyleType": "HEADING_2",
549+
},
550+
},
551+
{
552+
name: "bullet",
553+
paragraphStyle: map[string]any{
554+
"namedStyleType": "NORMAL_TEXT",
555+
"indentStart": map[string]any{
556+
"magnitude": 18,
557+
"unit": "PT",
558+
},
559+
},
560+
bullet: map[string]any{"listId": "list-1"},
561+
},
562+
} {
563+
t.Run(tc.name, func(t *testing.T) {
564+
var got docs.BatchUpdateDocumentRequest
565+
body := docBodyWithText("Section\n")
566+
paragraph := body["body"].(map[string]any)["content"].([]any)[1].(map[string]any)["paragraph"].(map[string]any)
567+
paragraph["paragraphStyle"] = tc.paragraphStyle
568+
if tc.bullet != nil {
569+
paragraph["bullet"] = tc.bullet
570+
}
571+
572+
docSvc, cleanup := newDocsServiceForTest(t, func(w http.ResponseWriter, r *http.Request) {
573+
w.Header().Set("Content-Type", "application/json")
574+
switch {
575+
case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/v1/documents/"):
576+
_ = json.NewEncoder(w).Encode(body)
577+
case r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, ":batchUpdate"):
578+
if err := json.NewDecoder(r.Body).Decode(&got); err != nil {
579+
t.Fatalf("decode batchUpdate: %v", err)
580+
}
581+
_ = json.NewEncoder(w).Encode(map[string]any{"documentId": "doc1"})
582+
default:
583+
http.NotFound(w, r)
584+
}
585+
})
586+
defer cleanup()
587+
588+
err := runKong(t, &DocsFindReplaceCmd{}, []string{
589+
"doc1", "Section", "First paragraph\n\nSecond paragraph", "--format", "markdown", "--first",
590+
}, newDocsFindReplaceTestContext(t, docSvc), &RootFlags{Account: "[email protected]"})
591+
if err != nil {
592+
t.Fatalf("docs find-replace --format markdown --first: %v", err)
593+
}
594+
595+
firstParagraphEnd := int64(1 + utf16Len("First paragraph\n"))
596+
for _, req := range got.Requests {
597+
if req.DeleteParagraphBullets != nil && req.DeleteParagraphBullets.Range.StartIndex < firstParagraphEnd {
598+
t.Fatalf("first paragraph bullet reset: %#v", req.DeleteParagraphBullets.Range)
599+
}
600+
if req.UpdateParagraphStyle != nil && req.UpdateParagraphStyle.Range.StartIndex < firstParagraphEnd {
601+
t.Fatalf("first paragraph style reset: %#v", req.UpdateParagraphStyle)
602+
}
603+
}
604+
})
605+
}
606+
}
607+
539608
func TestDocsFindReplace_MarkdownCodeBlockStartsFreshParagraphWhenInline(t *testing.T) {
540609
var batchCalls []docs.BatchUpdateDocumentRequest
541610
docSvc, cleanup := newDocsServiceForTest(t, func(w http.ResponseWriter, r *http.Request) {

internal/cmd/docs_mutation.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,10 @@ func replacePreparedDocsMarkdownRange(
179179
if docRangeCoversParagraphText(doc, startIdx, endIdx, tabID) {
180180
resetEnd++
181181
}
182-
requests = append(requests, resetDocsParagraphRequests(baseIndex, resetEnd, tabID)...)
182+
resetStart := markdownParagraphResetStart(elements, textToInsert, baseIndex)
183+
if resetStart < resetEnd {
184+
requests = append(requests, resetDocsParagraphRequests(resetStart, resetEnd, tabID)...)
185+
}
183186
}
184187
requests = append(requests, formattingRequests...)
185188
}
@@ -230,6 +233,16 @@ func markdownRangeReplacementIsInline(markdown string, elements []docsmarkdown.M
230233
elements[0].Type == docsmarkdown.MDParagraph
231234
}
232235

236+
func markdownParagraphResetStart(elements []docsmarkdown.MarkdownElement, text string, baseIndex int64) int64 {
237+
if len(elements) == 0 || elements[0].Type != docsmarkdown.MDParagraph {
238+
return baseIndex
239+
}
240+
if newline := strings.IndexByte(text, '\n'); newline >= 0 {
241+
return baseIndex + utf16Len(text[:newline+1])
242+
}
243+
return baseIndex + utf16Len(text)
244+
}
245+
233246
func resetDocsParagraphRequests(startIdx, endIdx int64, tabID string) []*docs.Request {
234247
rng := &docs.Range{StartIndex: startIdx, EndIndex: endIdx, TabId: tabID}
235248
return []*docs.Request{

0 commit comments

Comments
 (0)