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
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,8 @@ Development caching behavior can be controlled via URL parameters:

### Implementation Planning Process
When asked to plan an implementation, follow the standardized process documented in `PLANNING.md`.

## Development Constraints

### Server Management
- Claude should never start or kill the dev server, the user handles that
19 changes: 18 additions & 1 deletion PLANNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,24 @@ Break implementation into phases with specific checkboxes:
- **Phase 3** - Testing and documentation
- **Phase 4** - Manual verification steps
- **Phase 5** - Quality assurance (make check, make test, regression testing)
- **Phase 6** - Dead code cleanup (for refactor projects only)

### 6. Document Future Maintenance
### 6. Dead Code Cleanup (Refactor Projects Only)
For projects that replace existing functionality, always include a dead code cleanup phase:

#### Identify Dead Code:
- **Functions** - Old functions replaced by new implementation
- **Exports** - Remove dead functions from `module.exports`
- **Tests** - Remove tests for deleted functions
- **Imports** - Remove dead imports from test files
- **References** - Search codebase for any remaining references

#### Verification Steps:
- All tests should still pass with same or fewer test count
- No linting or formatting errors
- No broken imports or undefined function references
- Functionality should work exactly the same as before cleanup

### 7. Document Future Maintenance
- Note any ongoing maintenance requirements
- Explain any new concepts that future developers need to understand.
18 changes: 17 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
{
"formatter": {
"indentStyle": "space",
"indentWidth": 2
"indentWidth": 2,
"includes": [
"public_html/**",
"tests/**",
"*.js",
"*.json",
"!test-results/**"
]
},
"linter": {
"includes": [
"public_html/**",
"tests/**",
"*.js",
"*.json",
"!test-results/**"
]
}
}
2 changes: 1 addition & 1 deletion public_html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ <h1>Just Bangs! Lite</h1>
</form>
<div class="settings-panel" aria-hidden="true">
<div class="settings-content">
<!-- Content will be generated by JavaScript -->
<settings-dialog></settings-dialog>
</div>
</div>
<footer class="main-footer">
Expand Down
271 changes: 199 additions & 72 deletions public_html/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,56 +224,15 @@ function initializePWA(windowObj = window) {
}

function toggleSettingsPanel(windowObj = window) {
const panel = windowObj.document.querySelector(".settings-panel");
if (!panel) return;

const isHidden = panel.getAttribute("aria-hidden") === "true";
panel.setAttribute("aria-hidden", isHidden ? "false" : "true");
}

function buildSettingsPanel(windowObj = window) {
const currentDefault = getDefaultBang(windowObj);

let html = `
<div class="settings-header">
<h2>
Settings
<button class="close-button" type="button" aria-label="Close settings">Γ—</button>
</h2>
<p class="save-message" id="save-message">βœ“ Changes saved automatically</p>
</div>
<div class="settings-body">
<h3>Default Search Engine</h3>
<div class="bang-list">
`;

const sortedBangs = Object.entries(bangs).sort(([a], [b]) =>
a.localeCompare(b),
);

for (const [bangKey, bangUrl] of sortedBangs) {
const isChecked = bangKey === currentDefault ? " checked" : "";
html += `
<div class="bang-trigger">${bangKey}!</div>
<div class="bang-url">${bangUrl}</div>
<input type="radio" name="default-bang" value="${bangKey}" class="bang-radio"${isChecked}>
`;
}

html += `
</div>
</div>
`;
return html;
}

function handleDefaultBangChange(event, windowObj = window) {
if (event.target.name === "default-bang") {
const storage = windowObj.localStorage;
if (storage) {
storage.setItem("default-bang", event.target.value);
showSaveMessage(windowObj);
}
const settingsDialog = windowObj.document.querySelector("settings-dialog");
if (settingsDialog && typeof settingsDialog.toggle === "function") {
settingsDialog.toggle();
} else {
// Fallback for when components aren't registered yet
const panel = windowObj.document.querySelector(".settings-panel");
if (!panel) return;
const isHidden = panel.getAttribute("aria-hidden") === "true";
panel.setAttribute("aria-hidden", isHidden ? "false" : "true");
}
}

Expand All @@ -287,40 +246,207 @@ function showSaveMessage(windowObj = window) {
}
}

function setupSettingsEventListeners(windowObj = window) {
const hamburgerButton = windowObj.document.querySelector(".hamburger-menu");
const settingsPanel = windowObj.document.querySelector(".settings-panel");
function initializeSettings(windowObj = window) {
registerSettingsComponents(windowObj);

const settingsDialog = windowObj.document.querySelector("settings-dialog");
if (settingsDialog) {
settingsDialog.setWindow(windowObj);
}

// Set up hamburger button listener
const hamburgerButton = windowObj.document.querySelector(".hamburger-menu");
if (hamburgerButton) {
hamburgerButton.addEventListener("click", () =>
toggleSettingsPanel(windowObj),
);
}
}

if (settingsPanel) {
settingsPanel.addEventListener("click", (event) => {
// Close on backdrop click
if (event.target === settingsPanel) {
toggleSettingsPanel(windowObj);
}
// Close on close button click
const SettingsDialogBase =
typeof HTMLElement !== "undefined" ? HTMLElement : class {};

class SettingsDialog extends SettingsDialogBase {
constructor() {
super();
this.windowObj = typeof window !== "undefined" ? window : null;
}

setWindow(windowObj) {
this.windowObj = windowObj;
}

connectedCallback() {
this.render();
this.setupEventListeners();
}

render() {
const currentDefault = getDefaultBang(this.windowObj);
const sortedBangs = Object.entries(bangs).sort(([a], [b]) =>
a.localeCompare(b),
);

let html = `
<div class="settings-header">
<h2>
Settings
<button class="close-button" type="button" aria-label="Close settings">Γ—</button>
</h2>
<save-message>βœ“ Changes saved automatically</save-message>
</div>
<div class="settings-body">
<h3>Default Search Engine</h3>
<div class="bang-list">
`;

for (const [bangKey, bangUrl] of sortedBangs) {
const isSelected = bangKey === currentDefault;
html += `<setting-option bang-key="${bangKey}" bang-url="${bangUrl}"${isSelected ? " selected" : ""}></setting-option>`;
}

html += `
</div>
</div>
`;

this.innerHTML = html;
}

setupEventListeners() {
this.addEventListener("click", (event) => {
if (event.target.classList.contains("close-button")) {
toggleSettingsPanel(windowObj);
this.hide();
}
});
}

settingsPanel.addEventListener("change", (event) => {
handleDefaultBangChange(event, windowObj);
});
show() {
this.closest(".settings-panel")?.setAttribute("aria-hidden", "false");
}

hide() {
this.closest(".settings-panel")?.setAttribute("aria-hidden", "true");
}

toggle() {
const panel = this.closest(".settings-panel");
if (!panel) return;
const isHidden = panel.getAttribute("aria-hidden") === "true";
panel.setAttribute("aria-hidden", isHidden ? "false" : "true");
}
}

function initializeSettings(windowObj = window) {
const settingsContent = windowObj.document.querySelector(".settings-content");
if (settingsContent) {
settingsContent.innerHTML = buildSettingsPanel(windowObj);
const SettingOptionBase =
typeof HTMLElement !== "undefined" ? HTMLElement : class {};

class SettingOption extends SettingOptionBase {
constructor() {
super();
this.windowObj = typeof window !== "undefined" ? window : null;
}

setWindow(windowObj) {
this.windowObj = windowObj;
}

connectedCallback() {
this.render();
this.setupEventListeners();
}

render() {
const bangKey = this.getAttribute("bang-key");
const bangUrl = this.getAttribute("bang-url");
const isSelected = this.hasAttribute("selected");

this.innerHTML = `
<div class="bang-trigger">${bangKey}!</div>
<div class="bang-url">${bangUrl}</div>
<input type="radio" name="default-bang" value="${bangKey}" class="bang-radio"${isSelected ? " checked" : ""}>
`;
}

setupEventListeners() {
const radio = this.querySelector('input[type="radio"]');
if (radio) {
radio.addEventListener("change", (event) => {
this.handleChange(event);
});
}
}

handleChange(event) {
if (event.target.name === "default-bang") {
const storage = this.windowObj.localStorage;
if (storage) {
storage.setItem("default-bang", event.target.value);
const saveMessage =
this.closest("settings-dialog")?.querySelector("save-message");
if (saveMessage && typeof saveMessage.show === "function") {
saveMessage.show();
}
}
}
}

setSelected(selected) {
// This method is called before render(), so just update the attribute
if (selected) {
this.setAttribute("selected", "");
} else {
this.removeAttribute("selected");
}
}

getValue() {
return this.getAttribute("bang-key");
}
}

const SaveMessageBase =
typeof HTMLElement !== "undefined" ? HTMLElement : class {};

class SaveMessage extends SaveMessageBase {
constructor() {
super();
this.windowObj = typeof window !== "undefined" ? window : null;
}

setWindow(windowObj) {
this.windowObj = windowObj;
}

connectedCallback() {
this.render();
}

render() {
this.classList.add("save-message");
this.id = "save-message";
if (!this.textContent) {
this.textContent = "βœ“ Changes saved automatically";
}
}

show() {
this.classList.add("visible");
this.windowObj.setTimeout(() => {
this.hide();
}, 2000);
}

hide() {
this.classList.remove("visible");
}
}

function registerSettingsComponents(windowObj = window) {
if (typeof windowObj.customElements !== "undefined") {
windowObj.customElements.define("settings-dialog", SettingsDialog);
windowObj.customElements.define("setting-option", SettingOption);
windowObj.customElements.define("save-message", SaveMessage);
}
setupSettingsEventListeners(windowObj);
}

if (typeof module !== "undefined" && module.exports) {
Expand All @@ -340,11 +466,12 @@ if (typeof module !== "undefined" && module.exports) {
registerServiceWorker,
initializePWA,
toggleSettingsPanel,
buildSettingsPanel,
handleDefaultBangChange,
showSaveMessage,
setupSettingsEventListeners,
initializeSettings,
shouldEnableCaching,
SettingsDialog,
SettingOption,
SaveMessage,
registerSettingsComponents,
};
}
2 changes: 1 addition & 1 deletion public_html/service-worker-lib.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const STATIC_CACHE_NAME = "just-bangs-static-v9";
const STATIC_CACHE_NAME = "just-bangs-static-v10";

const STATIC_ASSETS = [
"./",
Expand Down
Loading