mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 22:34:47 +00:00
* feat: add auto-open functionality for collapsible sections and streamline unlinked account handling - Introduce `auto-open` Stimulus controller to auto-expand <details> elements based on URL params. - Update all settings sections and panels to support the new `auto_open_param` for seamless navigation. - Improve unlinked account logic for Coinbase, SimpleFIN, and SnapTrade, ensuring consistent and optimized handling. - Refactor sync warnings and badges for better readability and user experience. - Extend localization for additional menu items, warnings, and setup prompts. * fix: improve error handling and safe HTML usage in Coinbase and settings components - Log warning for unhandled exceptions in Coinbase unlinked account count fallback. - Escape `auto_open_param` in settings section for safe HTML injection. - Clean up URL params in `auto-open` controller after auto-expansion. --------- Co-authored-by: luckyPipewrench <luckypipewrench@proton.me>
80 lines
2.6 KiB
JavaScript
80 lines
2.6 KiB
JavaScript
import { Controller } from "@hotwired/stimulus";
|
|
|
|
// Connects to data-controller="lazy-load"
|
|
// Used with <details> elements to lazy-load content when expanded
|
|
// Use data-action="toggle->lazy-load#toggled" on the <details> element
|
|
// Optional: data-lazy-load-auto-open-param-value="paramName" to auto-open when ?paramName=1 is in URL
|
|
export default class extends Controller {
|
|
static targets = ["content", "loading", "frame"];
|
|
static values = { url: String, loaded: Boolean, autoOpenParam: String };
|
|
|
|
connect() {
|
|
// Check if we should auto-open based on URL param
|
|
if (this.hasAutoOpenParamValue && this.autoOpenParamValue) {
|
|
const params = new URLSearchParams(window.location.search);
|
|
if (params.get(this.autoOpenParamValue) === "1") {
|
|
this.element.open = true;
|
|
// Clean up the URL param after opening
|
|
params.delete(this.autoOpenParamValue);
|
|
const newUrl = params.toString()
|
|
? `${window.location.pathname}?${params.toString()}${window.location.hash}`
|
|
: `${window.location.pathname}${window.location.hash}`;
|
|
window.history.replaceState({}, "", newUrl);
|
|
}
|
|
}
|
|
|
|
// If already open on connect (browser restored state), load immediately
|
|
if (this.element.open && !this.loadedValue) {
|
|
this.load();
|
|
}
|
|
}
|
|
|
|
toggled() {
|
|
if (this.element.open && !this.loadedValue) {
|
|
this.load();
|
|
}
|
|
}
|
|
|
|
async load() {
|
|
if (this.loadedValue || this.loading) return;
|
|
this.loading = true;
|
|
|
|
try {
|
|
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
|
|
const response = await fetch(this.urlValue, {
|
|
headers: {
|
|
Accept: "text/html",
|
|
"X-Requested-With": "XMLHttpRequest",
|
|
"X-CSRF-Token": csrfToken,
|
|
},
|
|
credentials: "same-origin",
|
|
});
|
|
|
|
if (response.ok) {
|
|
const html = await response.text();
|
|
if (this.hasFrameTarget) {
|
|
this.frameTarget.innerHTML = html;
|
|
}
|
|
if (this.hasLoadingTarget) {
|
|
this.loadingTarget.classList.add("hidden");
|
|
}
|
|
this.loadedValue = true;
|
|
} else {
|
|
console.error("Lazy load failed:", response.status, response.statusText);
|
|
this.showError(`Failed to load (${response.status})`);
|
|
}
|
|
} catch (error) {
|
|
console.error("Lazy load error:", error);
|
|
this.showError("Network error");
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
}
|
|
|
|
showError(message) {
|
|
if (this.hasLoadingTarget) {
|
|
this.loadingTarget.innerHTML = `<p class="text-destructive text-sm">${message}</p>`;
|
|
}
|
|
}
|
|
}
|