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
1 change: 1 addition & 0 deletions builtwithdjango/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

def posthog_template_context(request):
return {
"app_environment": getattr(settings, "ENVIRONMENT", ""),
"posthog_enabled": getattr(settings, "POSTHOG_ENABLED", False),
"posthog_api_key": getattr(settings, "POSTHOG_API_KEY", ""),
"posthog_api_host": getattr(settings, "POSTHOG_HOST", "https://us.i.posthog.com"),
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/application/hotwire.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import "../styles/tailwind.css";

import { Application } from "@hotwired/stimulus";
import { definitionsFromContext } from "@hotwired/stimulus-webpack-helpers";
import Dropdown from 'stimulus-dropdown';
import Reveal from 'stimulus-reveal-controller';


Expand All @@ -12,5 +11,4 @@ const application = Application.start();
const context = require.context("../controllers", true, /\.js$/);
application.load(definitionsFromContext(context));

application.register('dropdown', Dropdown);
application.register('reveal', Reveal);
47 changes: 47 additions & 0 deletions frontend/src/controllers/dropdown_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Dropdown from "stimulus-dropdown";

export default class extends Dropdown {
connect() {
super.connect();
this.boundCloseOnEscape = this.closeOnEscape.bind(this);
document.addEventListener("keydown", this.boundCloseOnEscape);
this.setExpanded(false);
}

disconnect() {
if (typeof super.disconnect === "function") {
super.disconnect();
}
document.removeEventListener("keydown", this.boundCloseOnEscape);
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.

toggle(event) {
super.toggle(event);
window.requestAnimationFrame(() => {
this.setExpanded(!this.menuTarget.classList.contains("hidden"));
});
}

hide(event) {
super.hide(event);
if (!this.element.contains(event.target)) {
this.setExpanded(false);
}
}

closeOnEscape(event) {
if (event.key !== "Escape" || this.menuTarget.classList.contains("hidden")) return;

this.leave();
this.setExpanded(false);
this.triggerElement?.focus();
}

setExpanded(isExpanded) {
this.triggerElement?.setAttribute("aria-expanded", isExpanded ? "true" : "false");
}

get triggerElement() {
return this.element.querySelector("[aria-expanded]");
}
}
43 changes: 21 additions & 22 deletions frontend/src/controllers/exit_intent_controller.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
import { Controller } from "@hotwired/stimulus";
import {enter, leave} from 'el-transition';
import { enter, leave } from "el-transition";

export default class extends Controller {
static targets = [ "modal", "closeButton" ];
static targets = ["modal", "closeButton"];

initialize() {
this.numOfOpens = 0;
}
initialize() {
this.numOfOpens = 0;
}

openModal(event) {
if(this.modalTarget.classList.contains('hidden') && this.numOfOpens === 0) {
if (!event.toElement && !event.relatedTarget) {
setTimeout(() => {
enter(this.modalTarget);
this.numOfOpens++;
if (window.bwdTrack) {
window.bwdTrack('exit intent modal opened');
}
}, 1000);
}
openModal(event) {
if (this.modalTarget.classList.contains("hidden") && this.numOfOpens === 0) {
if (!event.toElement && !event.relatedTarget) {
setTimeout(() => {
enter(this.modalTarget);
this.numOfOpens++;
if (window.bwdTrack) {
window.bwdTrack("exit intent modal opened");
}
}, 1000);
}
}
}

closeModal() {
console.log("not hidden");
leave(this.modalTarget);
if (window.bwdTrack) {
window.bwdTrack('exit intent modal closed');
}
closeModal() {
leave(this.modalTarget);
if (window.bwdTrack) {
window.bwdTrack("exit intent modal closed");
}
}
}
96 changes: 77 additions & 19 deletions frontend/src/controllers/html_formatter_controller.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,54 @@
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
static targets = [ "input", "result", "submitButton", "copyButton" ];
static targets = [ "input", "result", "submitButton", "copyButton", "status" ];

formatHTML() {
const input = this.inputTarget.value.trim();
if (!input) {
this.setError('Paste a Django template before formatting.');
return;
}

this.setLoading(true);
this.setStatus('Formatting template...');

const formData = new FormData();
formData.append('html_string', this.inputTarget.value);
formData.append('html_string', input);

fetch('/tools/api/format-html/', {
method: 'POST',
body: formData,
})
.then(response => {
.then(async response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json().catch(() => ({}));
throw new Error(data.error || `Request failed with status ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.formatted_html) {
this.resultTarget.value = data.formatted_html;
this.copyButtonTarget.classList.remove('hidden');
this.copyButtonTarget.disabled = false;
this.resetCopyButton();
this.setStatus('Formatted HTML is ready.');
this.capture('html formatted', {
input_length: this.inputTarget.value.length,
input_length: input.length,
output_length: data.formatted_html.length
});
} else if (data.error) {
this.resultTarget.value = 'Error: ' + data.error;
this.copyButtonTarget.classList.add('hidden');
this.setError(data.error);
this.capture('html formatter failed', {
input_length: this.inputTarget.value.length,
input_length: input.length,
error: data.error
});
}
})
.catch(error => {
this.resultTarget.value = 'Error: ' + error;
this.copyButtonTarget.classList.add('hidden');
this.setError(error.message || 'Could not format HTML. Check your connection and try again.');
this.capture('html formatter failed', {
input_length: this.inputTarget.value.length,
input_length: input.length,
error: error.message
});
})
Expand All @@ -49,31 +57,81 @@ export default class extends Controller {
});
}

copy() {
navigator.clipboard.writeText(this.resultTarget.value);
this.copyButtonTarget.classList.replace("bg-blue-500", "bg-green-500");
async copy() {
if (!this.resultTarget.value) {
this.setError('Format HTML before copying.');
return;
}

try {
await this.copyText(this.resultTarget.value);
} catch (error) {
this.setError('Could not copy automatically. Select the result and copy it manually.');
this.capture('formatted html copy failed', {
error: error.message
});
return;
}

this.copyButtonTarget.textContent = "Copied!";
this.setStatus('Formatted HTML copied.');
this.capture('formatted html copied', {
output_length: this.resultTarget.value.length
});
setTimeout(() => this.resetCopyButton(), 2000);
}

resetCopyButton() {
this.copyButtonTarget.classList.replace("bg-green-500", "bg-blue-500");
this.copyButtonTarget.textContent = "Copy";
}

setLoading(isLoading) {
this.submitButtonTarget.disabled = isLoading;
this.submitButtonTarget.textContent = isLoading ? 'Formatting...' : 'Format HTML';
this.submitButtonTarget.classList.toggle('opacity-50', isLoading);
this.submitButtonTarget.classList.toggle('cursor-not-allowed', isLoading);
}

setStatus(message) {
this.statusTarget.textContent = message;
this.statusTarget.classList.add('bw-muted');
this.statusTarget.classList.remove('bw-status-error');
}

setError(message) {
this.resultTarget.value = '';
this.copyButtonTarget.disabled = true;
this.statusTarget.textContent = message;
this.statusTarget.classList.remove('bw-muted');
this.statusTarget.classList.add('bw-status-error');
}

async copyText(value) {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(value);
return;
}

const textarea = document.createElement('textarea');
textarea.value = value;
Comment thread
greptile-apps[bot] marked this conversation as resolved.
textarea.setAttribute('readonly', '');
textarea.style.position = 'fixed';
textarea.style.top = '-9999px';
document.body.appendChild(textarea);
textarea.select();

try {
const copied = document.execCommand('copy');
if (!copied) {
throw new Error('Copy command was rejected');
}
} finally {
textarea.remove();
}
}

clearResult() {
this.resultTarget.value = '';
this.copyButtonTarget.classList.add('hidden');
this.copyButtonTarget.disabled = true;
this.setStatus('Result cleared.');
this.capture('html formatter cleared');
}

Expand Down
1 change: 0 additions & 1 deletion frontend/src/controllers/infinite_scroll_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export default class extends Controller {
}
})
.catch((error) => {
console.error('Error:', error);
if (window.bwdTrack) {
window.bwdTrack('infinite scroll failed', {
url,
Expand Down
3 changes: 1 addition & 2 deletions frontend/src/controllers/like_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ export default class extends Controller {
this.render();
this.trackChange(this.likedValue, previousLikeCount);
})
.catch((error) => {
console.log(error);
.catch(() => {
this.showError();
})
.finally(() => {
Expand Down
13 changes: 6 additions & 7 deletions frontend/src/controllers/search_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ export default class extends Controller {
updateSelection(results) {
results.forEach((result, index) => {
if (index === this.selectedIndex) {
result.classList.add("bg-gray-50");
result.classList.add("bw-search-result--active");
result.scrollIntoView({ block: "nearest" });
} else {
result.classList.remove("bg-gray-50");
result.classList.remove("bw-search-result--active");
}
});
}
Expand Down Expand Up @@ -116,7 +116,6 @@ export default class extends Controller {
has_results: data.length > 0
});
} catch (error) {
console.error("Search error:", error);
this.capture("project search failed", {
query_length: query.length,
error: error.message
Expand All @@ -128,7 +127,7 @@ export default class extends Controller {
showResults(results) {
if (!results.length) {
const emptyState = document.createElement("div");
emptyState.className = "p-4 text-sm text-gray-500";
emptyState.className = "bw-search-empty";
emptyState.textContent = "No projects found";
this.resultsTarget.replaceChildren(emptyState);
this.resultsTarget.classList.remove("hidden");
Expand All @@ -146,14 +145,14 @@ export default class extends Controller {
link.dataset.analyticsProjectId = String(result.id || "");
link.dataset.analyticsProjectTitle = result.title || "";
link.dataset.analyticsProjectSlug = result.slug || "";
link.className = "flex flex-col p-4 border-b border-gray-100 hover:bg-gray-50 last:border-b-0";
link.className = "bw-search-result";

const title = document.createElement("div");
title.className = "font-medium text-gray-900";
title.className = "bw-search-result__title";
title.textContent = result.title || "";

const description = document.createElement("div");
description.className = "text-sm text-gray-500";
description.className = "bw-search-result__description";
description.textContent = result.short_description || "";

link.append(title, description);
Expand Down
Loading
Loading