Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 144 additions & 5 deletions shortcuts/doc/doc_media_insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
"bytes"
"context"
"fmt"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"io"
"path/filepath"
"strings"

Expand Down Expand Up @@ -55,6 +60,8 @@
{Name: "selection-with-ellipsis", Desc: "plain text (or 'start...end' to disambiguate) matching the target block's content. Media is inserted at the top-level ancestor of the matched block — i.e., when the selection is inside a callout, table cell, or nested list, media lands outside that container, not inside it. Pass 'start...end' (a unique prefix and suffix separated by '...') when the plain text appears in more than one block"},
{Name: "before", Type: "bool", Desc: "insert before the matched block instead of after (requires --selection-with-ellipsis)"},
{Name: "file-view", Desc: "file block rendering: card (default) | preview | inline; only applies when --type=file. preview renders audio/video as an inline player"},
{Name: "width", Type: "int", Desc: "image display width in pixels (only for --type=image); if --height is omitted it is auto-computed from the source image aspect ratio"},
{Name: "height", Type: "int", Desc: "image display height in pixels (only for --type=image); if --width is omitted it is auto-computed from the source image aspect ratio"},
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
filePath := runtime.Str("file")
Expand Down Expand Up @@ -93,6 +100,24 @@
return output.ErrValidation("--file-view only applies when --type=file")
}
}
widthChanged := runtime.Changed("width")
heightChanged := runtime.Changed("height")
if (widthChanged || heightChanged) && runtime.Str("type") != "image" {
return output.ErrValidation("--width/--height only apply when --type=image")
}
if widthChanged && runtime.Int("width") <= 0 {
Comment thread
fangshuyu-768 marked this conversation as resolved.
return output.ErrValidation("--width must be a positive integer")
}
if heightChanged && runtime.Int("height") <= 0 {
return output.ErrValidation("--height must be a positive integer")
}
const maxDimension = 10000
if widthChanged && runtime.Int("width") > maxDimension {
return output.ErrValidation("--width must not exceed %d pixels", maxDimension)

Check warning on line 116 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L116

Added line #L116 was not covered by tests
}
if heightChanged && runtime.Int("height") > maxDimension {
return output.ErrValidation("--height must not exceed %d pixels", maxDimension)

Check warning on line 119 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L119

Added line #L119 was not covered by tests
}
return nil
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
Expand Down Expand Up @@ -120,7 +145,25 @@
} else {
createBlockData["index"] = "<children_len>"
}
batchUpdateData := buildBatchUpdateData("<new_block_id>", mediaType, "<file_token>", runtime.Str("align"), caption)
// Best-effort dimension computation for dry-run.
dryWidth := runtime.Int("width")
dryHeight := runtime.Int("height")
widthChanged := runtime.Changed("width")
Comment thread
fangshuyu-768 marked this conversation as resolved.
heightChanged := runtime.Changed("height")

if (widthChanged || heightChanged) && !(widthChanged && heightChanged) {
if filePath == "<clipboard image>" {
fmt.Fprintf(runtime.IO().ErrOut, "Note: cannot detect clipboard image dimensions in dry-run; provide both --width and --height for accurate preview\n")
} else if nativeW, nativeH, err := detectImageDimensionsFromPath(runtime.FileIO(), filePath); err == nil {
dims := computeMissingDimension(dryWidth, dryHeight, nativeW, nativeH)
dryWidth = dims.width
dryHeight = dims.height
} else {
fmt.Fprintf(runtime.IO().ErrOut, "Note: unable to detect image dimensions from %s; provide both --width and --height to avoid failure at execution time\n", filePath)

Check warning on line 162 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L155-L162

Added lines #L155 - L162 were not covered by tests
}
}

batchUpdateData := buildBatchUpdateData("<new_block_id>", mediaType, "<file_token>", runtime.Str("align"), caption, dryWidth, dryHeight)

d := common.NewDryRunAPI()
totalSteps := 4
Expand Down Expand Up @@ -188,6 +231,9 @@
if runtime.Bool("from-clipboard") {
d.Set("upload_size_note", "clipboard size unknown; single-part vs multipart decision deferred to runtime")
}
if runtime.Bool("from-clipboard") && (widthChanged || heightChanged) && !(widthChanged && heightChanged) {
d.Set("dimension_note", "clipboard dimensions unknown; aspect-ratio calculation deferred to runtime")

Check warning on line 235 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L235

Added line #L235 was not covered by tests
}
return d
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
Expand Down Expand Up @@ -314,6 +360,42 @@
// interface stays a true nil for the --file path. Passing a typed-nil
// *bytes.Reader here would make the downstream `if cfg.Content != nil`
// check incorrectly take the clipboard branch and crash on Read.
// Resolve display dimensions before upload to fail fast on unreadable images.
var finalWidth, finalHeight int
if mediaType == "image" {
userWidth := runtime.Int("width")
userHeight := runtime.Int("height")
widthChanged := runtime.Changed("width")
heightChanged := runtime.Changed("height")

if widthChanged && heightChanged {
finalWidth = userWidth
finalHeight = userHeight

Check warning on line 373 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L372-L373

Added lines #L372 - L373 were not covered by tests
} else if widthChanged || heightChanged {
Comment thread
fangshuyu-768 marked this conversation as resolved.
var nativeW, nativeH int
var dimErr error
if clipboardContent != nil {
nativeW, nativeH, dimErr = detectImageDimensions(bytes.NewReader(clipboardContent))
} else {
f, openErr := runtime.FileIO().Open(filePath)
if openErr != nil {
return withRollbackWarning(output.ErrValidation(
"unable to detect image dimensions from %s for aspect-ratio calculation; provide both --width and --height", fileName))

Check warning on line 383 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L375-L383

Added lines #L375 - L383 were not covered by tests
}
nativeW, nativeH, dimErr = detectImageDimensions(f)
f.Close()

Check warning on line 386 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L385-L386

Added lines #L385 - L386 were not covered by tests
}
if dimErr != nil {
return withRollbackWarning(output.ErrValidation(
"unable to detect image dimensions from %s for aspect-ratio calculation; provide both --width and --height", fileName))

Check warning on line 390 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L388-L390

Added lines #L388 - L390 were not covered by tests
}
dims := computeMissingDimension(userWidth, userHeight, nativeW, nativeH)
finalWidth = dims.width
finalHeight = dims.height
fmt.Fprintf(runtime.IO().ErrOut, "Image dimensions: %dx%d (native: %dx%d)\n", finalWidth, finalHeight, nativeW, nativeH)

Check warning on line 395 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L392-L395

Added lines #L392 - L395 were not covered by tests
}
}

uploadCfg := UploadDocMediaFileConfig{
FilePath: filePath,
FileName: fileName,
Expand All @@ -337,16 +419,23 @@

if _, err := runtime.CallAPI("PATCH",
fmt.Sprintf("/open-apis/docx/v1/documents/%s/blocks/batch_update", validate.EncodePathSegment(documentID)),
nil, buildBatchUpdateData(replaceBlockID, mediaType, fileToken, alignStr, caption)); err != nil {
nil, buildBatchUpdateData(replaceBlockID, mediaType, fileToken, alignStr, caption, finalWidth, finalHeight)); err != nil {
return withRollbackWarning(err)
}

runtime.Out(map[string]interface{}{
outData := map[string]interface{}{
"document_id": documentID,
"block_id": blockId,
"file_token": fileToken,
"type": mediaType,
}, nil)
}
if finalWidth > 0 {
outData["width"] = finalWidth

Check warning on line 433 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L433

Added line #L433 was not covered by tests
}
if finalHeight > 0 {
outData["height"] = finalHeight

Check warning on line 436 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L436

Added line #L436 was not covered by tests
}
runtime.Out(outData, nil)
return nil
},
}
Expand Down Expand Up @@ -453,7 +542,51 @@
}
}

func buildBatchUpdateData(blockID, mediaType, fileToken, alignStr, caption string) map[string]interface{} {
type imageDimensions struct {
width int
height int
}

func computeMissingDimension(userWidth, userHeight, nativeWidth, nativeHeight int) imageDimensions {
Comment thread
fangshuyu-768 marked this conversation as resolved.
if nativeWidth <= 0 || nativeHeight <= 0 {
return imageDimensions{width: userWidth, height: userHeight}
}
if userWidth > 0 && userHeight == 0 {
return imageDimensions{
width: userWidth,
height: (userWidth*nativeHeight + nativeWidth/2) / nativeWidth,
}
}
if userHeight > 0 && userWidth == 0 {
return imageDimensions{
width: (userHeight*nativeWidth + nativeHeight/2) / nativeHeight,
height: userHeight,
}
}
return imageDimensions{width: userWidth, height: userHeight}
}

func detectImageDimensions(r io.Reader) (width, height int, err error) {
cfg, _, err := image.DecodeConfig(r)
if err != nil {
return 0, 0, err

Check warning on line 572 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L569-L572

Added lines #L569 - L572 were not covered by tests
}
return cfg.Width, cfg.Height, nil

Check warning on line 574 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L574

Added line #L574 was not covered by tests
}

func detectImageDimensionsFromPath(fio fileio.FileIO, filePath string) (int, int, error) {
if _, err := validate.SafeInputPath(filePath); err != nil {
return 0, 0, err

Check warning on line 579 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L577-L579

Added lines #L577 - L579 were not covered by tests
}
f, err := fio.Open(filePath)
if err != nil {
return 0, 0, err

Check warning on line 583 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L581-L583

Added lines #L581 - L583 were not covered by tests
}
defer f.Close()
return detectImageDimensions(f)

Check warning on line 586 in shortcuts/doc/doc_media_insert.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/doc_media_insert.go#L585-L586

Added lines #L585 - L586 were not covered by tests
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

func buildBatchUpdateData(blockID, mediaType, fileToken, alignStr, caption string, width, height int) map[string]interface{} {
request := map[string]interface{}{
"block_id": blockID,
}
Expand All @@ -465,6 +598,12 @@
replaceImage := map[string]interface{}{
"token": fileToken,
}
if width > 0 {
replaceImage["width"] = width
}
if height > 0 {
replaceImage["height"] = height
}
if alignVal, ok := alignMap[alignStr]; ok {
replaceImage["align"] = alignVal
}
Expand Down
Loading
Loading