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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Docs: add `--tab` and `--all-tabs` to `docs raw` for inspecting specific or complete multi-tab document content. (#697) — thanks @sebsnyk.
- Docs: add tab-aware table, image, heading, and paragraph enumerators with structured and plain output. (#719) — thanks @sebsnyk.
- Docs: style locally rendered fenced Markdown blocks with Roboto Mono, dark-green text, and existing paragraph shading. (#676, #724) — thanks @TurboTheTurtle.
- Docs: add `docs insert-image --url` for inserting public HTTPS images directly without Drive upload or temporary public sharing. (#675) — thanks @sebsnyk.

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion docs/commands.generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ Generated from `gog schema --json`.
- [`gog docs (doc) insert <docId> [<content>] [flags]`](commands/gog-docs-insert.md) - Insert text at a specific position
- [`gog docs (doc) insert-date-chip --date=STRING <docId> [flags]`](commands/gog-docs-insert-date-chip.md) - Insert a native date smart chip
- [`gog docs (doc) insert-file-chip (insert-rich-link) --file-id=STRING <docId> [flags]`](commands/gog-docs-insert-file-chip.md) - Insert a native Drive file smart chip
- [`gog docs (doc) insert-image --file=STRING <docId> [flags]`](commands/gog-docs-insert-image.md) - Upload a local image and insert it into a Google Doc
- [`gog docs (doc) insert-image <docId> [flags]`](commands/gog-docs-insert-image.md) - Insert a public image URL or upload a local image into a Google Doc
- [`gog docs (doc) insert-page-break (page-break,pb) <docId> [flags]`](commands/gog-docs-insert-page-break.md) - Insert a page break at a specific position (or end-of-doc with --at-end)
- [`gog docs (doc) insert-person --email=STRING <docId> [flags]`](commands/gog-docs-insert-person.md) - Insert a native person smart chip
- [`gog docs (doc) insert-table --rows=INT --cols=INT <docId> [flags]`](commands/gog-docs-insert-table.md) - Insert a native table at a specific position (or end-of-doc with --at-end), optionally populated via --values-json
Expand Down
2 changes: 1 addition & 1 deletion docs/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ Generated pages: 615.
- [gog docs insert](gog-docs-insert.md) - Insert text at a specific position
- [gog docs insert-date-chip](gog-docs-insert-date-chip.md) - Insert a native date smart chip
- [gog docs insert-file-chip](gog-docs-insert-file-chip.md) - Insert a native Drive file smart chip
- [gog docs insert-image](gog-docs-insert-image.md) - Upload a local image and insert it into a Google Doc
- [gog docs insert-image](gog-docs-insert-image.md) - Insert a public image URL or upload a local image into a Google Doc
- [gog docs insert-page-break](gog-docs-insert-page-break.md) - Insert a page break at a specific position (or end-of-doc with --at-end)
- [gog docs insert-person](gog-docs-insert-person.md) - Insert a native person smart chip
- [gog docs insert-table](gog-docs-insert-table.md) - Insert a native table at a specific position (or end-of-doc with --at-end), optionally populated via --values-json
Expand Down
5 changes: 3 additions & 2 deletions docs/commands/gog-docs-insert-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

> Generated from `gog schema --json`. Do not edit this page by hand; run `make docs-commands`.

Upload a local image and insert it into a Google Doc
Insert a public image URL or upload a local image into a Google Doc

## Usage

```bash
gog docs (doc) insert-image --file=STRING <docId> [flags]
gog docs (doc) insert-image <docId> [flags]
```

## Parent
Expand Down Expand Up @@ -42,6 +42,7 @@ gog docs (doc) insert-image --file=STRING <docId> [flags]
| `--results-only` | `bool` | | In JSON mode, emit only the primary result (drops envelope fields like nextPageToken) |
| `--select`<br>`--pick`<br>`--project` | `string` | | In JSON mode, select comma-separated fields (best-effort; supports dot paths). Desire path: use --fields for most commands. |
| `--tab` | `string` | | Target a specific tab by title or ID (see docs list-tabs) |
| `--url` | `string` | | Public HTTPS image URL to insert directly |
| `-v`<br>`--verbose` | `bool` | | Enable verbose logging |
| `--version` | `kong.VersionFlag` | | Print version and exit |
| `--width` | `float64` | 468 | Image width in points; default 468pt |
Expand Down
2 changes: 1 addition & 1 deletion docs/commands/gog-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ gog docs (doc) <command> [flags]
- [gog docs insert](gog-docs-insert.md) - Insert text at a specific position
- [gog docs insert-date-chip](gog-docs-insert-date-chip.md) - Insert a native date smart chip
- [gog docs insert-file-chip](gog-docs-insert-file-chip.md) - Insert a native Drive file smart chip
- [gog docs insert-image](gog-docs-insert-image.md) - Upload a local image and insert it into a Google Doc
- [gog docs insert-image](gog-docs-insert-image.md) - Insert a public image URL or upload a local image into a Google Doc
- [gog docs insert-page-break](gog-docs-insert-page-break.md) - Insert a page break at a specific position (or end-of-doc with --at-end)
- [gog docs insert-person](gog-docs-insert-person.md) - Insert a native person smart chip
- [gog docs insert-table](gog-docs-insert-table.md) - Insert a native table at a specific position (or end-of-doc with --at-end), optionally populated via --values-json
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type DocsCmd struct {
TableMerge DocsTableMergeCmd `cmd:"" name:"table-merge" help:"Merge a native table cell range"`
TableUnmerge DocsTableUnmergeCmd `cmd:"" name:"table-unmerge" aliases:"table-split" help:"Unmerge the region containing a native table cell"`
TableColumnWidth DocsTableColumnWidthCmd `cmd:"" name:"table-column-width" aliases:"table-width,column-width" help:"Set or reset native table column widths"`
InsertImage DocsInsertImageCmd `cmd:"" name:"insert-image" help:"Upload a local image and insert it into a Google Doc"`
InsertImage DocsInsertImageCmd `cmd:"" name:"insert-image" help:"Insert a public image URL or upload a local image into a Google Doc"`
InsertPerson DocsInsertPersonCmd `cmd:"" name:"insert-person" help:"Insert a native person smart chip"`
InsertFileChip DocsInsertFileChipCmd `cmd:"" name:"insert-file-chip" aliases:"insert-rich-link" help:"Insert a native Drive file smart chip"`
InsertDateChip DocsInsertDateChipCmd `cmd:"" name:"insert-date-chip" help:"Insert a native date smart chip"`
Expand Down
157 changes: 115 additions & 42 deletions internal/cmd/docs_insert_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import (

type DocsInsertImageCmd struct {
DocID string `arg:"" name:"docId" help:"Doc ID"`
File string `name:"file" required:"" help:"Local PNG, JPEG, or GIF image to upload and insert" type:"existingfile"`
File string `name:"file" help:"Local PNG, JPEG, or GIF image to upload and insert" type:"existingfile"`
URL string `name:"url" help:"Public HTTPS image URL to insert directly"`
At string `name:"at" help:"Placeholder text to replace, or 'end' to append" default:"end"`
Width float64 `name:"width" help:"Image width in points; default 468pt" default:"468"`
Height float64 `name:"height" help:"Image height in points (optional; width-only preserves aspect ratio)"`
Expand All @@ -41,49 +42,55 @@ type docsInsertImageResult struct {
requests int
revoked bool
fallbackLink bool
sourceURL string
}

type docsInsertImageSource struct {
localPath string
name string
mimeType string
imageURL string
}

func (c *DocsInsertImageCmd) Run(ctx context.Context, flags *RootFlags) error {
u := ui.FromContext(ctx)
docID := strings.TrimSpace(c.DocID)
if docID == "" {
return usage("empty docId")
}
if c.Width < 0 || c.Height < 0 {
return usage("--width and --height must be non-negative")
}
localPath, err := config.ExpandPath(c.File)
source, err := c.resolveSource()
if err != nil {
return err
}
mimeType := guessMimeType(localPath)
if !isDocsInsertImageMime(mimeType) {
return usage("--file must be a PNG, JPEG, or GIF image")
}
name := strings.TrimSpace(c.Name)
if name == "" {
name = filepath.Base(localPath)
}
at := strings.TrimSpace(c.At)
if at == "" {
return usage("empty --at")
}
if dryRunErr := dryRunExit(ctx, flags, "docs.insert-image", map[string]any{
"documentId": docID,
"file": localPath,
"name": name,
"mimeType": mimeType,
"at": at,
"width": c.Width,
"height": c.Height,
"parent": c.Parent,
"onRestricted": c.OnRestricted,
"tab": c.Tab,
}); dryRunErr != nil {
dryRunPayload := map[string]any{
"documentId": docID,
"at": at,
"width": c.Width,
"height": c.Height,
"tab": c.Tab,
}
if source.imageURL != "" {
dryRunPayload["url"] = source.imageURL
} else {
dryRunPayload["file"] = source.localPath
dryRunPayload["name"] = source.name
dryRunPayload["mimeType"] = source.mimeType
dryRunPayload["parent"] = c.Parent
dryRunPayload["onRestricted"] = c.OnRestricted
}
if dryRunErr := dryRunExit(ctx, flags, "docs.insert-image", dryRunPayload); dryRunErr != nil {
return dryRunErr
}
if confirmErr := confirmDestructiveChecked(ctx, flagsWithoutDryRun(flags), fmt.Sprintf("temporarily share uploaded image %s with anyone (public) so Google Docs can fetch it", name)); confirmErr != nil {
return confirmErr
if source.imageURL == "" {
if confirmErr := confirmDestructiveChecked(ctx, flagsWithoutDryRun(flags), fmt.Sprintf("temporarily share uploaded image %s with anyone (public) so Google Docs can fetch it", source.name)); confirmErr != nil {
return confirmErr
}
}

account, err := requireAccount(flags)
Expand All @@ -94,46 +101,108 @@ func (c *DocsInsertImageCmd) Run(ctx context.Context, flags *RootFlags) error {
if err != nil {
return err
}
driveSvc, err := newDriveService(ctx, account)

var result docsInsertImageResult
if source.imageURL != "" {
result, err = c.runURL(ctx, docsSvc, docID, source.imageURL, at)
} else {
driveSvc, driveErr := newDriveService(ctx, account)
if driveErr != nil {
return driveErr
}
result, err = c.runFile(ctx, docsSvc, driveSvc, docID, source.localPath, source.name, source.mimeType, at)
}
if err != nil {
return err
}
return writeDocsInsertImageResult(ctx, result)
}

func (c *DocsInsertImageCmd) resolveSource() (docsInsertImageSource, error) {
localFile := strings.TrimSpace(c.File)
imageURL := strings.TrimSpace(c.URL)
if localFile == "" && imageURL == "" {
return docsInsertImageSource{}, usage("required: --file or --url")
}
if localFile != "" && imageURL != "" {
return docsInsertImageSource{}, usage("--file and --url are mutually exclusive")
}
if imageURL != "" {
if strings.TrimSpace(c.Parent) != "" || strings.TrimSpace(c.Name) != "" || strings.EqualFold(c.OnRestricted, "link") {
return docsInsertImageSource{}, usage("--parent, --name, and --on-restricted=link require --file")
}
parsed, err := url.ParseRequestURI(imageURL)
if err != nil || !strings.EqualFold(parsed.Scheme, "https") || parsed.Host == "" || parsed.User != nil {
return docsInsertImageSource{}, usage("--url must be a public HTTPS image URL without embedded credentials")
}
return docsInsertImageSource{imageURL: parsed.String()}, nil
}

result, err := c.run(ctx, docsSvc, driveSvc, docID, localPath, name, mimeType, at)
localPath, err := config.ExpandPath(localFile)
if err != nil {
return err
return docsInsertImageSource{}, err
}
mimeType := guessMimeType(localPath)
if !isDocsInsertImageMime(mimeType) {
return docsInsertImageSource{}, usage("--file must be a PNG, JPEG, or GIF image")
}
name := strings.TrimSpace(c.Name)
if name == "" {
name = filepath.Base(localPath)
}
return docsInsertImageSource{
localPath: localPath,
name: name,
mimeType: mimeType,
}, nil
}

func writeDocsInsertImageResult(ctx context.Context, result docsInsertImageResult) error {
u := ui.FromContext(ctx)
if outfmt.IsJSON(ctx) {
payload := map[string]any{
"documentId": result.documentID,
"uploadedFileId": result.uploadedFileID,
"uploadedFileName": result.uploadedFileName,
"permissionId": result.permissionID,
"atIndex": result.atIndex,
"requests": result.requests,
"revoked": result.revoked,
"fallbackLink": result.fallbackLink,
"documentId": result.documentID,
"atIndex": result.atIndex,
"requests": result.requests,
}
if result.sourceURL != "" {
payload["url"] = result.sourceURL
} else {
payload["uploadedFileId"] = result.uploadedFileID
payload["uploadedFileName"] = result.uploadedFileName
payload["permissionId"] = result.permissionID
payload["revoked"] = result.revoked
payload["fallbackLink"] = result.fallbackLink
}
if result.tabID != "" {
payload["tabId"] = result.tabID
}
return outfmt.WriteJSON(ctx, os.Stdout, payload)
}
u.Out().Linef("documentId\t%s", result.documentID)
u.Out().Linef("uploadedFileId\t%s", result.uploadedFileID)
if result.sourceURL != "" {
u.Out().Linef("url\t%s", result.sourceURL)
} else {
u.Out().Linef("uploadedFileId\t%s", result.uploadedFileID)
u.Out().Linef("revoked\t%t", result.revoked)
if result.fallbackLink {
u.Out().Linef("fallbackLink\ttrue")
}
}
u.Out().Linef("atIndex\t%d", result.atIndex)
u.Out().Linef("requests\t%d", result.requests)
u.Out().Linef("revoked\t%t", result.revoked)
if result.fallbackLink {
u.Out().Linef("fallbackLink\ttrue")
}
if result.tabID != "" {
u.Out().Linef("tabId\t%s", result.tabID)
}
return nil
}

func (c *DocsInsertImageCmd) run(ctx context.Context, docsSvc *docs.Service, driveSvc *drive.Service, docID, localPath, name, mimeType, at string) (result docsInsertImageResult, err error) {
func (c *DocsInsertImageCmd) runURL(ctx context.Context, docsSvc *docs.Service, docID, imageURL, at string) (docsInsertImageResult, error) {
result := docsInsertImageResult{sourceURL: imageURL}
return c.insertImageURL(ctx, docsSvc, docID, imageURL, at, result)
}

func (c *DocsInsertImageCmd) runFile(ctx context.Context, docsSvc *docs.Service, driveSvc *drive.Service, docID, localPath, name, mimeType, at string) (result docsInsertImageResult, err error) {
uploaded, err := uploadDocsInlineImage(ctx, driveSvc, localPath, name, mimeType, strings.TrimSpace(c.Parent))
if err != nil {
return result, err
Expand Down Expand Up @@ -173,6 +242,10 @@ func (c *DocsInsertImageCmd) run(ctx context.Context, docsSvc *docs.Service, dri
}()

imageURL := driveImageDownloadURL(uploaded.Id)
return c.insertImageURL(ctx, docsSvc, docID, imageURL, at, result)
}

func (c *DocsInsertImageCmd) insertImageURL(ctx context.Context, docsSvc *docs.Service, docID, imageURL, at string, result docsInsertImageResult) (docsInsertImageResult, error) {
reqs, index, tabID, err := c.buildInsertRequests(ctx, docsSvc, docID, at, imageURL)
if err != nil {
return result, err
Expand Down
Loading
Loading