diff --git a/.gitignore b/.gitignore
index 2058881..e29aa63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,6 +46,8 @@ Thumbs.db
*.swo
*~
.cursor/
+.claude/
+CLAUDE.md
# Temporary files
*.tmp
diff --git a/src/commands/uploadWidget.ts b/src/commands/uploadWidget.ts
index ba1bd83..d283a76 100644
--- a/src/commands/uploadWidget.ts
+++ b/src/commands/uploadWidget.ts
@@ -246,7 +246,7 @@ function createUploadPanel(
const presetsJson = JSON.stringify(provider.uploadPresets);
const initScript = `
initUploadWidget({
- cloudName: "${escapeHtml(cloudName)}",
+ cloudName: ${JSON.stringify(cloudName)},
presets: ${presetsJson}
});
`;
diff --git a/src/webview/client/upload-widget.ts b/src/webview/client/upload-widget.ts
index b047c71..afd73ec 100644
--- a/src/webview/client/upload-widget.ts
+++ b/src/webview/client/upload-widget.ts
@@ -297,15 +297,28 @@ function addToQueue(fileId: string, fileName: string): void {
const displayName = truncateString(fileName, 30, "middle");
- item.innerHTML = `
- ${displayName}
-
- Pending...
- `;
+ const nameEl = document.createElement("span");
+ nameEl.className = "queue-item__name";
+ nameEl.title = fileName;
+ nameEl.textContent = displayName;
+
+ const progressBar = document.createElement("div");
+ progressBar.className = "progress__bar";
+ progressBar.style.width = "0%";
+ const progress = document.createElement("div");
+ progress.className = "progress";
+ progress.appendChild(progressBar);
+ const progressWrapper = document.createElement("div");
+ progressWrapper.className = "queue-item__progress";
+ progressWrapper.appendChild(progress);
+
+ const statusEl = document.createElement("span");
+ statusEl.className = "queue-item__status";
+ statusEl.textContent = "Pending...";
+
+ item.appendChild(nameEl);
+ item.appendChild(progressWrapper);
+ item.appendChild(statusEl);
uploadQueue.appendChild(item);
}
@@ -378,55 +391,80 @@ function renderUploadedAsset(asset: AssetData): void {
const card = document.createElement("div");
card.className = "asset-card";
- const fileIcon =
- "";
+ // Static SVG icon — no user data
+ const fileIconSvg = "";
+
+ // Thumbnail wrapper
+ const thumbnailWrapper = document.createElement("div");
+ thumbnailWrapper.className = "asset-card__thumbnail";
+ thumbnailWrapper.setAttribute("data-asset-id", asset.public_id);
- let mediaHtml: string;
if (thumbnailUrl) {
- mediaHtml = `
-
-

-
${fileIcon}
-
- `;
+ const img = document.createElement("img");
+ img.className = "asset-card__image";
+ img.src = thumbnailUrl;
+ img.alt = "Thumbnail";
+ const fallbackEl = document.createElement("div");
+ fallbackEl.className = "asset-card__icon fallback";
+ fallbackEl.style.display = "none";
+ fallbackEl.innerHTML = fileIconSvg;
+ img.addEventListener("error", function () {
+ this.style.display = "none";
+ fallbackEl.style.display = "flex";
+ });
+ thumbnailWrapper.appendChild(img);
+ thumbnailWrapper.appendChild(fallbackEl);
} else {
- mediaHtml = `
-
- `;
+ const iconEl = document.createElement("div");
+ iconEl.className = "asset-card__icon";
+ iconEl.innerHTML = fileIconSvg;
+ thumbnailWrapper.appendChild(iconEl);
}
+ // Folder
+ const folderEl = document.createElement("div");
+ folderEl.className = "asset-card__folder";
+ const folderCode = document.createElement("code");
+ folderCode.textContent = folderDisplay;
+ folderEl.appendChild(document.createTextNode("📂 "));
+ folderEl.appendChild(folderCode);
+
+ // Asset ID
const displayId = truncateString(asset.public_id, 25, "start");
+ const idEl = document.createElement("div");
+ idEl.className = "asset-card__id";
+ idEl.title = asset.public_id;
+ idEl.textContent = displayId;
+
+ // Action buttons
+ const actionsEl = document.createElement("div");
+ actionsEl.className = "asset-card__actions";
+
+ const copyUrlBtn = document.createElement("button");
+ copyUrlBtn.className = "btn btn--secondary btn--sm btn--copy";
+ copyUrlBtn.setAttribute("data-copy", asset.secure_url);
+ copyUrlBtn.textContent = "Copy URL";
- card.innerHTML = `
- ${mediaHtml}
- 📂 ${folderDisplay}
- ${displayId}
-
-
-
-
- `;
+ const copyIdBtn = document.createElement("button");
+ copyIdBtn.className = "btn btn--secondary btn--sm btn--copy";
+ copyIdBtn.setAttribute("data-copy", asset.public_id);
+ copyIdBtn.textContent = "Copy ID";
+
+ actionsEl.appendChild(copyUrlBtn);
+ actionsEl.appendChild(copyIdBtn);
+
+ card.appendChild(thumbnailWrapper);
+ card.appendChild(folderEl);
+ card.appendChild(idEl);
+ card.appendChild(actionsEl);
// Click thumbnail to preview
- const thumbnailWrapper = card.querySelector(".asset-card__thumbnail");
- if (thumbnailWrapper && vscode) {
+ if (vscode) {
thumbnailWrapper.addEventListener("click", () => {
vscode.postMessage({ command: "openAsset", asset: asset });
});
}
- // Handle image load errors
- const img = card.querySelector(".asset-card__image");
- if (img) {
- img.addEventListener("error", function () {
- this.style.display = "none";
- const fallback = this.parentElement?.querySelector(".fallback");
- if (fallback) {fallback.style.display = "flex";}
- });
- }
-
// Initialize copy buttons
card.querySelectorAll(".btn--copy[data-copy]").forEach((btn) => {
btn.addEventListener("click", async (e) => {
diff --git a/src/webview/index.ts b/src/webview/index.ts
index 2dc888f..c5d3331 100644
--- a/src/webview/index.ts
+++ b/src/webview/index.ts
@@ -52,7 +52,6 @@ export * from "./components";
// Utility scripts
export * from "./utils";
-import { escapeHtml } from "./utils/helpers";
// Icons
export {
@@ -115,65 +114,4 @@ export function getCompleteScripts(): string {
].join("\n");
}
-/**
- * Creates a complete HTML document for a webview panel.
- *
- * @param options - Document configuration
- * @returns Complete HTML string
- *
- * @example
- * ```typescript
- * panel.webview.html = createWebviewDocument({
- * title: 'Upload to Cloudinary',
- * body: createPanel({ content: '...' }),
- * additionalStyles: '.custom { color: red; }',
- * additionalScripts: 'console.log("Hello");'
- * });
- * ```
- */
-export function createWebviewDocument(options: {
- /** Document title */
- title: string;
- /** Body content (HTML string) */
- body: string;
- /** Additional CSS to include */
- additionalStyles?: string;
- /** Additional JavaScript to include */
- additionalScripts?: string;
- /** Whether to include all component scripts (default: true) */
- includeScripts?: boolean;
-}): string {
- const {
- title,
- body,
- additionalStyles = "",
- additionalScripts = "",
- includeScripts = true,
- } = options;
-
- const styles = getCompleteStyles();
- const scripts = includeScripts ? getCompleteScripts() : "";
-
- return `
-
-
-
-
-
- ${escapeHtml(title)}
-
-
-
- ${body}
-
-
-
- `;
-}