Skip to content

Commit 7632b4e

Browse files
committed
feat: improve listing of configs [wip]
1 parent 6bad48a commit 7632b4e

File tree

10 files changed

+493
-269
lines changed

10 files changed

+493
-269
lines changed

_includes/head_custom.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,13 @@
6363
async
6464
src="//gc.zgo.at/count.js"
6565
></script>
66+
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
67+
<symbol id="copy-icon" viewBox="0 0 16 16">
68+
<path
69+
d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"
70+
/>
71+
<path
72+
d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zM9 2H7v.5a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5V2z"
73+
/>
74+
</symbol>
75+
</svg>

assets/css/sass/base.scss

Lines changed: 70 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,9 @@
1616
--transition-shadow: box-shadow 0.2s ease-in-out;
1717
}
1818

19-
#configs input {
20-
box-sizing: border-box;
19+
#configs input[name="instance"],
20+
#configs input[name="search"] {
2121
width: 100%;
22-
padding: var(--spacing-xs);
23-
font-family: var(--font-family-mono);
24-
font-size: var(--font-size-base);
25-
line-height: var(--line-height-base);
26-
color: var(--color-text);
27-
appearance: none;
28-
outline: none;
29-
background-color: transparent;
30-
border: 0;
31-
border-radius: 0;
32-
-webkit-font-smoothing: antialiased;
33-
-moz-osx-font-smoothing: grayscale;
34-
}
35-
36-
.config__headline > span,
37-
.config__url {
38-
hyphens: none;
39-
text-wrap: wrap;
40-
overflow-wrap: break-word;
41-
}
42-
43-
.config__url em {
44-
padding-right: 0.125em;
45-
font-style: italic;
46-
letter-spacing: -0.05em;
47-
opacity: 0.75;
48-
}
49-
50-
.config__url .config__url-amp:last-child {
51-
display: none !important;
5222
}
5323

5424
.js__copy-element {
@@ -80,7 +50,6 @@ button[data-bind-click="copy"] {
8050
line-height: 1.2;
8151
color: var(--color-text);
8252
cursor: pointer;
83-
background-color: #fff;
8453
border: 2px solid transparent;
8554
border-radius: var(--border-radius-sm);
8655
transition: var(--transition-border), var(--transition-shadow);
@@ -97,3 +66,71 @@ button[data-bind-click="copy"].copied:active {
9766
border-color: var(--color-accent);
9867
box-shadow: inset 0 2px 4px var(--color-accent-bg);
9968
}
69+
70+
.card-deck {
71+
display: flex;
72+
flex-wrap: wrap;
73+
}
74+
75+
.card {
76+
width: 100%;
77+
}
78+
.card-header {
79+
padding: 0.75rem 1.25rem;
80+
margin-bottom: 0;
81+
background-color: var(--table-background-color);
82+
border-bottom: 1px solid var(--border-color);
83+
position: relative;
84+
}
85+
86+
.card-body {
87+
padding: 1.25rem;
88+
}
89+
.badge {
90+
position: absolute;
91+
right: 0;
92+
animation: 0;
93+
94+
display: inline-block;
95+
padding: 0.2em 0.4em;
96+
font-size: 75%;
97+
border-radius: 0.25rem;
98+
vertical-align: middle;
99+
white-space: nowrap;
100+
}
101+
102+
.btn-icon {
103+
padding: 0.1rem 0.2rem;
104+
font-size: 0.8rem;
105+
line-height: 1;
106+
border-radius: var(--border-radius);
107+
background-color: transparent;
108+
border: 1px solid transparent;
109+
color: var(--link-color);
110+
}
111+
112+
.btn-icon svg {
113+
width: 1em;
114+
height: 1em;
115+
vertical-align: -0.125em;
116+
fill: currentColor;
117+
}
118+
119+
.d-flex-justify-content-between {
120+
display: flex;
121+
justify-content: space-between;
122+
align-items: center;
123+
}
124+
125+
details {
126+
transition: all 0.2s ease-in-out;
127+
}
128+
129+
summary {
130+
cursor: pointer;
131+
outline: none;
132+
}
133+
134+
summary:hover {
135+
color: var(--link-color);
136+
}

assets/js/configs/index.js

Lines changed: 0 additions & 66 deletions
This file was deleted.

assets/js/feed-directory/index.js

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
const App = {
2+
init() {
3+
this.instanceEl = document.querySelector('input[name="instance"]');
4+
if (!this.instanceEl) return;
5+
6+
this.searchEl = document.querySelector('input[name="search"]');
7+
this.configsEl = document.querySelector("#configs");
8+
this.cards = this.configsEl.querySelectorAll(".card");
9+
this.bindEvents();
10+
this.updateInstanceUrl();
11+
this.initDynamicForms();
12+
},
13+
14+
bindEvents() {
15+
this.searchEl.addEventListener("input", this.filterConfigs.bind(this));
16+
this.configsEl.addEventListener("click", (event) => {
17+
const { bindClick } = event.target.dataset;
18+
if (bindClick === "show") {
19+
this.handleShowClick(event);
20+
} else if (bindClick === "copy") {
21+
this.handleCopyClick(event);
22+
}
23+
});
24+
25+
this.instanceEl.addEventListener("blur", this.updateInstanceUrl.bind(this));
26+
},
27+
28+
filterConfigs() {
29+
const query = this.searchEl.value.toLowerCase();
30+
this.cards.forEach((card) => {
31+
const title = card.querySelector(".card-title").innerText.toLowerCase();
32+
if (title.includes(query)) {
33+
card.style.display = "";
34+
} else {
35+
card.style.display = "none";
36+
}
37+
});
38+
},
39+
40+
getInstanceUrl() {
41+
const url = this.instanceEl.value;
42+
return url.endsWith("/") ? url : `${url}/`;
43+
},
44+
45+
updateInstanceUrl() {
46+
const url = this.getInstanceUrl();
47+
document.querySelectorAll(".instance").forEach((el) => {
48+
if (el instanceof HTMLElement) {
49+
el.innerText = url;
50+
}
51+
});
52+
},
53+
54+
handleShowClick(event) {
55+
const { target } = event;
56+
const path = this.getPath(target);
57+
if (path) {
58+
target.href = `${this.getInstanceUrl()}${path}`;
59+
} else {
60+
event.preventDefault();
61+
}
62+
},
63+
64+
async handleCopyClick(event) {
65+
const { target } = event;
66+
const path = this.getPath(target);
67+
if (!path) return;
68+
69+
const href = `${this.getInstanceUrl()}${path}`;
70+
71+
try {
72+
await navigator.clipboard.writeText(href);
73+
this.showCopiedState(target);
74+
} catch (err) {
75+
console.error("Failed to copy: ", err);
76+
}
77+
},
78+
79+
showCopiedState(triggerEl) {
80+
triggerEl.classList.add("copied");
81+
setTimeout(() => {
82+
triggerEl.classList.remove("copied");
83+
triggerEl.blur();
84+
}, 1000);
85+
},
86+
87+
initDynamicForms() {
88+
document
89+
.querySelectorAll("[data-dynamic-form]")
90+
.forEach((formContainer) => {
91+
const form = formContainer.querySelector("form");
92+
const pathPreview = formContainer.querySelector("[data-path-preview]");
93+
const actionButtons = formContainer.querySelectorAll(
94+
"[data-path-template]",
95+
);
96+
97+
if (!form || !pathPreview || !(pathPreview instanceof HTMLElement))
98+
return;
99+
100+
const updateDynamicPath = () => {
101+
const formData = new FormData(form);
102+
const firstButton = actionButtons[0];
103+
if (!(firstButton instanceof HTMLElement)) return;
104+
105+
let pathTemplate = firstButton.dataset.pathTemplate || "";
106+
let pathPreviewTemplate = pathTemplate;
107+
108+
for (const [key, value] of formData.entries()) {
109+
if (typeof value === "string") {
110+
const placeholder = `{${key}}`;
111+
pathTemplate = pathTemplate.replace(
112+
placeholder,
113+
encodeURIComponent(value),
114+
);
115+
pathPreviewTemplate = pathPreviewTemplate.replace(
116+
placeholder,
117+
value || `{${key}}`,
118+
);
119+
}
120+
}
121+
122+
actionButtons.forEach((button) => {
123+
if (button instanceof HTMLElement) {
124+
button.dataset.path = pathTemplate;
125+
}
126+
});
127+
pathPreview.innerText = pathPreviewTemplate;
128+
};
129+
130+
form.addEventListener("input", updateDynamicPath);
131+
updateDynamicPath(); // Initial update
132+
});
133+
},
134+
135+
getPath(target) {
136+
if (!(target instanceof HTMLElement)) return null;
137+
138+
if (target.dataset.path) {
139+
return target.dataset.path;
140+
}
141+
142+
if (target.dataset.pathTemplate) {
143+
const formContainer = target.closest("[data-dynamic-form]");
144+
if (!formContainer) return null;
145+
146+
const form = formContainer.querySelector("form");
147+
if (!form) return null;
148+
149+
const formData = new FormData(form);
150+
let path = target.dataset.pathTemplate;
151+
let allParamsFilled = true;
152+
153+
for (const [key, value] of formData.entries()) {
154+
if (!value) {
155+
allParamsFilled = false;
156+
}
157+
if (typeof value === "string") {
158+
path = path.replace(`{${key}}`, encodeURIComponent(value));
159+
}
160+
}
161+
162+
return allParamsFilled ? path : null;
163+
}
164+
165+
return null;
166+
},
167+
};
168+
169+
document.addEventListener("DOMContentLoaded", () => {
170+
App.init();
171+
});

configs.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
permalink: /configs/
3+
---
4+
5+
<!DOCTYPE html>
6+
<html>
7+
<head>
8+
<meta http-equiv="refresh" content="0; url=/feed-directory/" />
9+
</head>
10+
<body>
11+
<p>If you are not redirected automatically, follow this <a href="/feed-directory/">link to the feed directory</a>.</p>
12+
</body>
13+
</html>

0 commit comments

Comments
 (0)