⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
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
9 changes: 5 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ Before you begin, ensure you have the following installed:

```json
{
"test-cloud": {
"apiKey": "your-test-api-key",
"apiSecret": "your-test-api-secret",
"uploadPreset": "your-test-upload-preset"
"your-cloud-name": {
"apiKey": "your-api-key",
"apiSecret": "your-api-secret"
}
}
```

> **Note:** The **cloud name is the key** (the property name). You can optionally add `"uploadPreset"` if you want a default preset.

4. **Build the extension**
```bash
Expand Down
18 changes: 7 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,15 @@ Auto-created with placeholder content on first use:

```json
{
"your-cloud-name-1": {
"apiKey": "<your-api-key>",
"apiSecret": "<your-api-secret>",
"uploadPreset": "<your-default-upload-preset>"
},
"your-cloud-name-2": {
"apiKey": "<your-api-key>",
"apiSecret": "<your-api-secret>",
"uploadPreset": "<your-default-upload-preset>"
"REPLACE_WITH_YOUR_CLOUD_NAME": {
"apiKey": "REPLACE_WITH_YOUR_API_KEY",
"apiSecret": "REPLACE_WITH_YOUR_API_SECRET"
}
}
```

> **Note:** The **cloud name is the key** (the property name in the JSON object). You can optionally add `"uploadPreset": "your-preset-name"` if you want to use a default upload preset.

### 2. **Workspace Config** (Optional override)
You can also include a project-specific config:
```
Expand Down Expand Up @@ -87,13 +83,13 @@ Once a valid configuration has been added, the active environment will be shown
- **File Browser** – Click "Browse Files" to select files from your system
- **Remote URL** – Paste a URL to upload from a remote source
- **Folder Selection** – Choose the destination folder from a dropdown
- **Upload Presets** – Select from your configured upload presets (view preset settings with the "Settings" toggle)
- **Upload Presets** – Optionally select from your configured upload presets (signed uploads work without a preset)
- **Custom Public ID** – Specify a custom public ID for single file uploads
- **Tags** – Add comma-separated tags to your uploads
- **Progress Tracking** – See real-time upload progress for each file
- **Uploaded Assets** – View thumbnails of uploaded assets, click to preview, copy URL or public ID

**Learn more**: See the [Cloudinary Upload Presets documentation](https://cloudinary.com/documentation/upload_presets) for details on creating and configuring upload presets.
**Learn more**: See the [Cloudinary Upload Presets documentation](https://cloudinary.com/documentation/upload_presets) for details on creating and configuring upload presets (optional).

![Uploading assets](https://res.cloudinary.com/demo/video/upload/w_1200/f_auto:animated/q_auto/e_accelerate:100/e_loop/docs/vscode-extension-vid3)

Expand Down
14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@
"title": " Refresh"
},
{
"command": "cloudinary.setResourceFilter",
"title": " Filter",
"command": "cloudinary.viewOptions",
"title": "View Options",
"icon": "$(filter)",
"category": "Cloudinary"
},
{
Expand All @@ -104,6 +105,7 @@
{
"command": "cloudinary.openGlobalConfig",
"title": "Config",
"icon": "$(gear)",
"category": "Cloudinary"
},
{
Expand All @@ -127,17 +129,17 @@
{
"command": "cloudinary.searchAssets",
"when": "view == cloudinaryMediaLibrary",
"group": "1_search@1"
"group": "navigation@3"
},
{
"command": "cloudinary.setResourceFilter",
"command": "cloudinary.viewOptions",
"when": "view == cloudinaryMediaLibrary",
"group": "1_search@2"
"group": "navigation@4"
},
{
"command": "cloudinary.openGlobalConfig",
"when": "view == cloudinaryMediaLibrary",
"group": "2_config@1"
"group": "navigation@5"
}
],
"view/item/context": [
Expand Down
47 changes: 0 additions & 47 deletions src/commands/loadMoreAssets.ts

This file was deleted.

6 changes: 2 additions & 4 deletions src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import * as vscode from "vscode";
import registerSearch from "./searchAssets";
import registerFilter from "./setResourceFilter";
import registerViewOptions from "./viewOptions";
import registerPreview from "./previewAsset";
import registerLoadMore from "./loadMoreAssets";
import registerUpload from "./uploadWidget";
import registerClipboard from "./copyCommands";
import registerSwitchEnv from "./switchEnvironment";
Expand Down Expand Up @@ -34,9 +33,8 @@ function registerAllCommands(

registerSearch(context, provider);
registerClearSearch(context, provider);
registerFilter(context, provider);
registerViewOptions(context, provider);
registerPreview(context);
registerLoadMore(context, provider);
registerUpload(context, provider);
registerClipboard(context);
registerSwitchEnv(context, provider, statusBar);
Expand Down
32 changes: 0 additions & 32 deletions src/commands/setResourceFilter.ts

This file was deleted.

12 changes: 9 additions & 3 deletions src/commands/switchEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { generateUserAgent } from "../utils/userAgent";
interface CloudinaryEnvironment {
apiKey: string;
apiSecret: string;
uploadPreset: string;
uploadPreset?: string; // Optional: Default upload preset
}

/**
Expand Down Expand Up @@ -44,7 +44,7 @@ function registerSwitchEnv(
provider.cloudName = selected;
provider.apiKey = env.apiKey;
provider.apiSecret = env.apiSecret;
provider.uploadPreset = env.uploadPreset;
provider.uploadPreset = env.uploadPreset || null;

const cacheKey = `cloudinary.dynamicFolders.${selected}`;
const cachedFolderMode = context.globalState.get(cacheKey) as boolean | undefined;
Expand All @@ -68,7 +68,13 @@ function registerSwitchEnv(
api_secret: env.apiSecret,
});

statusBar.text = `$(cloud) ${selected}`;
// Update status bar with folder mode indicator
const folderMode = provider.dynamicFolders ? "Dynamic" : "Fixed";
statusBar.text = `$(cloud) ${selected} $(folder) ${folderMode}`;
statusBar.tooltip = provider.dynamicFolders
? "Click to switch Cloudinary environment\n\nDynamic Folders: Assets can be organized independently of their public ID"
: "Click to switch Cloudinary environment\n\nFixed Folders: Asset folder is determined by public ID path";

provider.refresh({
folderPath: '',
nextCursor: null,
Expand Down
60 changes: 32 additions & 28 deletions src/commands/uploadWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,8 @@ function registerUpload(
context.subscriptions.push(
vscode.commands.registerCommand("cloudinary.openUploadWidget", async () => {
try {
// Fetch presets but don't require them - signed uploads work without presets
await provider.fetchUploadPresets();
const uploadPreset = provider.getCurrentUploadPreset();

if (!uploadPreset) {
vscode.window.showErrorMessage("No upload presets available. Please create one in your Cloudinary account.");
return;
}

openOrRevealUploadPanel("", provider, context);
} catch (err: any) {
vscode.window.showErrorMessage(`Failed to open upload widget: ${err.message}`);
Expand All @@ -65,14 +59,8 @@ function registerUpload(
"cloudinary.uploadToFolder",
async (folderItem: { label: string; data: { path?: string } }) => {
try {
// Fetch presets but don't require them - signed uploads work without presets
await provider.fetchUploadPresets();
const uploadPreset = provider.getCurrentUploadPreset();

if (!uploadPreset) {
vscode.window.showErrorMessage("No upload presets available. Please create one in your Cloudinary account.");
return;
}

const folderPath = folderItem.data.path || "";
openOrRevealUploadPanel(folderPath, provider, context);
} catch (err: any) {
Expand Down Expand Up @@ -234,12 +222,17 @@ function createUploadPanel(
}

// Get upload options based on current preset, folder, and optional overrides
const getUploadOptions = (presetName: string, folder: string, publicId?: string, tags?: string, fileName?: string) => {
// Upload preset is optional - signed uploads work without one
const getUploadOptions = (presetName: string | null | undefined, folder: string, publicId?: string, tags?: string, fileName?: string) => {
const options: Record<string, any> = {
upload_preset: presetName,
resource_type: "auto",
};

// Only add upload_preset if one is selected (not null, undefined, or empty string)
if (presetName && presetName.trim()) {
options.upload_preset = presetName;
}

// Add folder configuration
if (folder) {
if (provider.dynamicFolders) {
Expand Down Expand Up @@ -276,7 +269,8 @@ function createUploadPanel(
};

if (message.command === "uploadFile" && message.dataUri && message.fileId) {
const presetName = message.preset || currentPreset;
// Use nullish coalescing - empty string "" means "no preset" (signed upload)
const presetName = message.preset !== undefined ? message.preset : currentPreset;
const folder = message.folderPath !== undefined ? message.folderPath : currentFolderPath;
const options = getUploadOptions(presetName, folder, message.publicId, message.tags, message.fileName);

Expand Down Expand Up @@ -315,7 +309,8 @@ function createUploadPanel(
}

if (message.command === "uploadUrl" && message.url) {
const presetName = message.preset || currentPreset;
// Use nullish coalescing - empty string "" means "no preset" (signed upload)
const presetName = message.preset !== undefined ? message.preset : currentPreset;
const folder = message.folderPath !== undefined ? message.folderPath : currentFolderPath;
// Try to extract filename from URL for display_name
let urlFileName: string | undefined;
Expand Down Expand Up @@ -1009,10 +1004,11 @@ function getWebviewContent(
<!-- Preset Selector -->
<div class="setting-group">
<div class="preset-header">
<div class="setting-label">Upload Preset</div>
<button class="preset-details-toggle" id="presetDetailsToggle">Settings</button>
<div class="setting-label">Upload Preset <span style="opacity: 0.7; font-weight: normal;">(optional)</span></div>
${provider.uploadPresets.length > 0 ? '<button class="preset-details-toggle" id="presetDetailsToggle">Settings</button>' : ''}
</div>
<select id="presetSelect">
<option value="">No preset (signed upload)</option>
${provider.uploadPresets
.map(
(preset) => `
Expand Down Expand Up @@ -1146,7 +1142,13 @@ function getWebviewContent(
* Update preset details display
*/
function updatePresetDetails() {
const preset = presets.find(p => p.name === presetSelect.value);
const selectedValue = presetSelect.value;
if (!selectedValue) {
// No preset selected - using signed upload
presetDetails.textContent = 'Using signed upload (no preset required)';
return;
}
const preset = presets.find(p => p.name === selectedValue);
if (preset) {
presetDetails.textContent = formatPresetSettings(preset.settings);
}
Expand All @@ -1158,11 +1160,13 @@ function getWebviewContent(
/**
* Toggle preset details visibility
*/
presetDetailsToggle.addEventListener('click', () => {
const isVisible = presetDetails.classList.toggle('visible');
presetDetailsToggle.classList.toggle('expanded', isVisible);
presetDetailsToggle.textContent = isVisible ? 'Hide' : 'Settings';
});
if (presetDetailsToggle) {
presetDetailsToggle.addEventListener('click', () => {
const isVisible = presetDetails.classList.toggle('visible');
presetDetailsToggle.classList.toggle('expanded', isVisible);
presetDetailsToggle.textContent = isVisible ? 'Hide' : 'Settings';
});
}

/**
* Update preset details when selection changes
Expand All @@ -1185,10 +1189,10 @@ function getWebviewContent(
}

/**
* Get current preset
* Get current preset (returns null if no preset selected)
*/
function getCurrentPreset() {
return presetSelect.value;
return presetSelect.value || null;
}

/**
Expand Down
Loading