diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab249ae..47e2ec9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -83,6 +83,7 @@ All notable changes to `phalcon/debugbar` are documented here. The format is bas
- Explain in `Provider` why the boot request uses `get()` and the history request uses `getShared()`. [#98](https://github.com/phalcon/debugbar/issues/98)
- Show `true` and `false` for booleans in messages, show `[]`, `null` and the class name of an object in the grid, and time each query from its own start (0 when there is no start). [#99](https://github.com/phalcon/debugbar/issues/99)
- Debug page: show the previous exceptions, fix the API links, read each file one time, drop the arguments from the `debugVar()` backtrace, match `\vendor\`, and end the line of a skipped `Dump` key. [#100](https://github.com/phalcon/debugbar/issues/100)
+- Debug page: copy on `http://` pages, show "Copy failed" when a copy fails, and encode the paths in the PhpStorm and VS Code links. [#101](https://github.com/phalcon/debugbar/issues/101)
- Test HTML writes, the collapse state and `localStorage` errors in the JS tests, and keep every listener in the fake DOM. [#103](https://github.com/phalcon/debugbar/issues/103)
## [0.4.0](https://github.com/phalcon/debugbar/releases/tag/v0.4.0) (2026-07-14)
diff --git a/resources/assets/debug.js b/resources/assets/debug.js
index d5a7623..f543dc8 100644
--- a/resources/assets/debug.js
+++ b/resources/assets/debug.js
@@ -25,7 +25,9 @@
}
function escAttr(s) {
- return String(s).replace(/"/g, """);
+ return String(s).replace(/[&"]/g, function (c) {
+ return { "&": "&", "\"": """ }[c];
+ });
}
/* ---- PHP syntax highlighter (no dependencies) ---- */
@@ -88,10 +90,30 @@
toastTimer = setTimeout(function () { toastEl.classList.remove("show"); }, 1400);
}
+ /* The clipboard API is only in secure contexts (https, localhost). */
+ function copyWithTextarea(text) {
+ var area = document.createElement("textarea");
+ var copied = false;
+ area.value = text;
+ area.setAttribute("readonly", "");
+ area.style.position = "fixed";
+ area.style.opacity = "0";
+ document.body.appendChild(area);
+ area.select();
+ try { copied = document.execCommand("copy"); } catch (e) {}
+ document.body.removeChild(area);
+ return copied;
+ }
+
function copy(text, msg) {
if (navigator.clipboard && navigator.clipboard.writeText) {
- navigator.clipboard.writeText(text).then(function () { toast(msg); });
+ navigator.clipboard.writeText(text).then(
+ function () { toast(msg); },
+ function () { toast("Copy failed"); }
+ );
+ return;
}
+ toast(copyWithTextarea(text) ? msg : "Copy failed");
}
/* ---- theme ---- */
@@ -127,13 +149,16 @@
if (el.querySelector(".mini")) { continue; }
var file = el.getAttribute("data-file");
var line = el.getAttribute("data-line") || "";
- var enc = encodeURI(file);
+ /* PhpStorm takes the path as one query value; VS Code takes a path, so encode each segment. */
+ var phpstorm = "phpstorm://open?file=" + encodeURIComponent(file) + "&line=" + encodeURIComponent(line);
+ var vscode = "vscode://file" + file.split("/").map(encodeURIComponent).join("/") +
+ (line ? ":" + encodeURIComponent(line) : "");
var mini = document.createElement("span");
mini.className = "mini";
mini.innerHTML =
'' +
- 'PhpStorm' +
- 'VS Code';
+ 'PhpStorm' +
+ 'VS Code';
el.appendChild(mini);
}
}