Skip to content
Draft
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
14 changes: 14 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,17 @@ updates:
interval: weekly
cooldown:
default-days: 7
- package-ecosystem: npm
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
directory: /
schedule:
interval: weekly
cooldown:
default-days: 7
groups:
minor-python-dependencies:
update-types:
- "minor"
- "patch"
major-python-dependencies:
update-types:
- "major"
18 changes: 18 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@
contents: read

jobs:
vitest:
runs-on: macos-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

Check notice

Code scanning / zizmor

credential persistence through GitHub Actions artifacts Note test

credential persistence through GitHub Actions artifacts
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0

Check failure

Code scanning / zizmor

runtime artifacts potentially vulnerable to a cache poisoning attack Error test

runtime artifacts potentially vulnerable to a cache poisoning attack
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
with:
node-version-file: package.json
cache: npm
- name: Install Node dependencies
run: npm ci
- run: npm test
- name: Store coverage file
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: coverage-vitest
path: coverage/*
include-hidden-files: true

mysql:
runs-on: ubuntu-latest
strategy:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ venv
.envrc
.venv
.vscode

# Node
node_modules/
coverage/
4 changes: 4 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ repos:
hooks:
- id: django-upgrade
args: [--target-version, "4.2"]
- repo: https://github.com/codingjoe/esupgrade
rev: 2025.15.0
hooks:
- id: esupgrade
- repo: https://github.com/adamchainz/djade-pre-commit
rev: "1.9.0"
hooks:
Expand Down
39 changes: 19 additions & 20 deletions debug_toolbar/static/debug_toolbar/js/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const djdt = {
if (requestId && inner.children.length === 0) {
const url = new URL(
djDebug.dataset.renderPanelUrl,
window.location
globalThis.location
);
url.searchParams.append("request_id", requestId);
url.searchParams.append("panel_id", panelId);
Expand Down Expand Up @@ -166,8 +166,8 @@ const djdt = {

if (top < 0) {
top = 0;
} else if (top + handle.offsetHeight > window.innerHeight) {
top = window.innerHeight - handle.offsetHeight;
} else if (top + handle.offsetHeight > globalThis.innerHeight) {
top = globalThis.innerHeight - handle.offsetHeight;
}

handle.style.top = `${top}px`;
Expand Down Expand Up @@ -212,19 +212,20 @@ const djdt = {
djdt.updateOnAjax();
}

const prefersDark = window.matchMedia(
const prefersDark = globalThis.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
const themeList = prefersDark
? ["auto", "light", "dark"]
: ["auto", "dark", "light"];
const setTheme = (theme) => {

function setTheme(theme) {
djDebug.setAttribute(
"data-theme",
theme === "auto" ? (prefersDark ? "dark" : "light") : theme
);
djDebug.setAttribute("data-user-theme", theme);
};
}

// Updates the theme using user settings
let userTheme = localStorage.getItem("djdt.user-theme") || "auto";
Expand Down Expand Up @@ -253,7 +254,7 @@ const djdt = {
// set handle position
const handleTop = Math.min(
localStorage.getItem("djdt.top") || 265,
window.innerHeight - handle.offsetWidth
globalThis.innerHeight - handle.offsetWidth
);
handle.style.top = `${handleTop}px`;
},
Expand All @@ -265,7 +266,7 @@ const djdt = {
const handle = document.getElementById("djDebugToolbarHandle");
$$.show(handle);
djdt.ensureHandleVisibility();
window.addEventListener("resize", djdt.ensureHandleVisibility);
globalThis.addEventListener("resize", djdt.ensureHandleVisibility);
document.removeEventListener("keydown", onKeyDown);

localStorage.setItem("djdt.show", "false");
Expand All @@ -289,22 +290,20 @@ const djdt = {
$$.hide(document.getElementById("djDebugToolbarHandle"));
$$.show(document.getElementById("djDebugToolbar"));
localStorage.setItem("djdt.show", "true");
window.removeEventListener("resize", djdt.ensureHandleVisibility);
globalThis.removeEventListener("resize", djdt.ensureHandleVisibility);
},
updateOnAjax() {
const sidebarUrl =
document.getElementById("djDebug").dataset.sidebarUrl;
const slowjax = debounce(ajax, 200);

function handleAjaxResponse(requestId) {
const handleAjaxResponse = debounce(async (requestId) => {
const encodedRequestId = encodeURIComponent(requestId);
const dest = `${sidebarUrl}?request_id=${encodedRequestId}`;
slowjax(dest).then((data) => {
if (djdt.needUpdateOnFetch) {
replaceToolbarState(encodedRequestId, data);
}
});
}
const data = await ajax(dest);
if (djdt.needUpdateOnFetch) {
replaceToolbarState(encodedRequestId, data);
}
}, 100);

// Patch XHR / traditional AJAX requests
const origOpen = XMLHttpRequest.prototype.open;
Expand All @@ -324,8 +323,8 @@ const djdt = {
origOpen.apply(this, args);
};

const origFetch = window.fetch;
window.fetch = function (...args) {
const origFetch = globalThis.fetch;
globalThis.fetch = function (...args) {
// Heads up! Before modifying this code, please be aware of the
// possible unhandled errors that might arise from changing this.
// For details, see
Expand Down Expand Up @@ -388,7 +387,7 @@ const djdt = {
},
},
};
window.djdt = {
globalThis.djdt = {
show_toolbar: djdt.showToolbar,
hide_toolbar: djdt.hideToolbar,
init: djdt.init,
Expand Down
109 changes: 54 additions & 55 deletions debug_toolbar/static/debug_toolbar/js/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const $$ = {
export const $$ = {
on(root, eventName, selector, fn) {
root.removeEventListener(eventName, fn);
root.addEventListener(eventName, (event) => {
Expand All @@ -8,15 +8,15 @@ const $$ = {
}
});
},
/**
* This is a helper function to attach a handler for a `djdt.panel.render`
* event of a specific panel.
*
* root: The container element that the listener should be attached to.
* panelId: The Id of the panel.
* fn: A function to execute when the event is triggered.
*/
onPanelRender(root, panelId, fn) {
/*
This is a helper function to attach a handler for a `djdt.panel.render`
event of a specific panel.

root: The container element that the listener should be attached to.
panelId: The Id of the panel.
fn: A function to execute when the event is triggered.
*/
root.addEventListener("djdt.panel.render", (event) => {
if (event.detail.panelId === panelId) {
fn.call(event);
Expand Down Expand Up @@ -48,12 +48,12 @@ const $$ = {
document.head.appendChild(el);
}
},
/**
* Given a container element, apply styles set via data-djdt-styles attribute.
* The format is data-djdt-styles="styleName1:value;styleName2:value2"
* The style names should use the CSSStyleDeclaration camel cased names.
*/
applyStyles(container) {
/*
* Given a container element, apply styles set via data-djdt-styles attribute.
* The format is data-djdt-styles="styleName1:value;styleName2:value2"
* The style names should use the CSSStyleDeclaration camel cased names.
*/
for (const element of container.querySelectorAll(
"[data-djdt-styles]"
)) {
Expand All @@ -70,33 +70,31 @@ const $$ = {
},
};

function ajax(url, init) {
return fetch(url, Object.assign({ credentials: "same-origin" }, init))
.then((response) => {
if (response.ok) {
return response
.json()
.catch((error) =>
Promise.reject(
new Error(
`The response is a invalid Json object : ${error}`
)
)
);
}
return Promise.reject(
new Error(`${response.status}: ${response.statusText}`)
);
})
.catch((error) => {
const win = document.getElementById("djDebugWindow");
win.innerHTML = `<div class="djDebugPanelTitle"><h3>${error.message}</h3><button type="button" class="djDebugClose">»</button></div>`;
$$.show(win);
throw error;
export async function ajax(url, init) {
try {
const response = await fetch(url, {
credentials: "same-origin",
...init,
});
if (response.ok) {
try {
return response.json();
} catch (error) {
throw new Error(
`The response is a invalid Json object : ${error}`
);
}
}
throw new Error(`${response.status}: ${response.statusText}`);
} catch (error) {
const win = document.getElementById("djDebugWindow");
win.innerHTML = `<div class="djDebugPanelTitle"><h3>${error.message}</h3><button type="button" class="djDebugClose">»</button></div>`;
$$.show(win);
throw error;
}
}

function ajaxForm(element) {
export function ajaxForm(element) {
const form = element.closest("form");
const url = new URL(form.action);
const formData = new FormData(form);
Expand All @@ -109,7 +107,7 @@ function ajaxForm(element) {
return ajax(url, ajaxData);
}

function replaceToolbarState(newRequestId, data) {
export function replaceToolbarState(newRequestId, data) {
const djDebug = document.getElementById("djDebug");
djDebug.setAttribute("data-request-id", newRequestId);
// Check if response is empty, it could be due to an expired requestId.
Expand All @@ -123,22 +121,23 @@ function replaceToolbarState(newRequestId, data) {
}
}

function debounce(func, delay) {
let timer = null;
let resolves = [];

return (...args) => {
/**
* Debounce async functions.
*
* @param {Function} func - Function to be executed.
* @param {number} timeout - Time to wait before executing function in milliseconds.
* @returns {Function} - Debounced function.
*/
export function debounce(func, timeout) {
let timer;
return async (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
const result = func(...args);
for (const r of resolves) {
r(result);
}
resolves = [];
}, delay);

return new Promise((r) => resolves.push(r));
return await new Promise((resolve, reject) => {
timer = setTimeout(() => {
Promise.resolve(func.apply(this, [...args]))
.then(resolve)
.catch(reject);
}, timeout);
});
};
}

export { $$, ajax, ajaxForm, debounce, replaceToolbarState };
Loading
Loading