-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathscript.js
More file actions
355 lines (312 loc) · 11 KB
/
Copy pathscript.js
File metadata and controls
355 lines (312 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
// Register service worker for offline support
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('Service Worker registered'))
.catch(err => console.log('Service Worker registration failed:', err))
})
}
const commands = [];
let shuffled = [];
let todayIndex = 0;
let currentIndex = 0;
let isExpanded = false;
let isSharedVisitor = false;
let coffeeHasBeenShown = false;
const startDate = new Date('2025-01-01');
fetch('commands_en.json')
.then(r => {
if (!r.ok) throw new Error('Failed to load commands')
return r.json()
})
.then(data => {
commands.push(...data);
initApp();
})
.catch(err => {
console.error('Error loading commands:', err)
document.getElementById('command-name').textContent = 'Failed to load commands'
document.getElementById('command-name').style.color = '#dc3545'
});
function initApp() {
shuffled = shuffleArray([...commands], 12345);
const today = new Date();
const utcDate = Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
const utcStart = Date.UTC(2025, 0, 1);
const daysSince = Math.floor((utcDate - utcStart) / 86400000);
todayIndex = daysSince % shuffled.length;
currentIndex = todayIndex;
// Check if visitor came from a shared link
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.has('shared')) {
isSharedVisitor = true;
}
renderDay(todayIndex, 'command-name', 'explanation', false);
document.getElementById('today-btn').addEventListener('click', () => {
currentIndex = todayIndex;
isExpanded = true;
renderDay(todayIndex, 'command-name', 'explanation', true);
window.scrollTo({ top: 0, behavior: 'smooth' });
});
document.getElementById('share-btn').addEventListener('click', () => {
const cmd = shuffled[currentIndex];
const dayLabel = currentIndex === todayIndex ? 'Today' : 'Yesterday';
const text = `Daily Linux command: ${cmd.name}`;
const baseUrl = window.location.origin + window.location.pathname;
const url = `${baseUrl}?shared=true`;
if (navigator.share) {
navigator.share({ title: 'Daily Linux Command', text, url });
} else {
navigator.clipboard.writeText(`${text}\n${url}`);
alert('Link copied!');
}
});
}
function renderDay(index, nameElId, expElId, autoExpand = false) {
const i = (index + shuffled.length) % shuffled.length;
const cmd = shuffled[i];
const nameEl = document.getElementById(nameElId);
nameEl.textContent = cmd.name;
// Reset animation by removing and re-adding the class
nameEl.classList.remove('animate-in');
// Trigger reflow to restart animation
void nameEl.offsetWidth;
nameEl.classList.add('animate-in');
// Clear existing content first
const exp = document.getElementById(expElId);
const desc = exp.querySelector('p');
const list = exp.querySelector('ul');
const existingProtip = exp.querySelector('.protip');
if (existingProtip) existingProtip.remove();
// Update content
desc.textContent = cmd.description;
list.innerHTML = cmd.examples.map((ex, idx) => {
const command = typeof ex === 'string' ? ex : ex.cmd;
const description = typeof ex === 'object' && ex.desc ? ex.desc : '';
return `
<li class="${idx === 0 ? 'visible' : ''}">
<div class="example-content">
<code translate="no">${command}</code>
${description ? `<span class="example-desc">→ ${description}</span>` : ''}
</div>
<button class="copy-btn" onclick="copyExample('${command.replace(/'/g, "\\'")}', this)">Copy</button>
</li>
`}).join('');
// Add pro tip if exists
if (cmd.protip) {
const protipEl = document.createElement('div');
protipEl.className = 'protip';
protipEl.innerHTML = `<strong>💡 Pro tip:</strong> ${cmd.protip}`;
exp.appendChild(protipEl);
}
// Move nav container to the end (after protip if it exists)
const navContainer = exp.querySelector('.nav-container');
if (navContainer) {
exp.appendChild(navContainer);
}
// Setup click handler for expanding details
const terminalContainer = document.getElementById('terminal-container');
const commandClickHandler = (e) => {
// Only allow expanding when viewing Today, not collapsing
if (currentIndex !== todayIndex) return;
// Only expand if hidden, don't allow collapsing
if (exp.classList.contains('hidden')) {
exp.classList.remove('hidden');
isExpanded = true;
terminalContainer.classList.add('focused');
// Hide cursor immediately when clicking
const cursor = document.querySelector('.cursor');
if (cursor) {
cursor.classList.add('hidden-cursor');
}
// Animate examples sequentially with decreasing delays
const examples = list.querySelectorAll('li');
const protip = exp.querySelector('.protip');
const baseDelay = 3500; // First delay: 3.5s
const decrementPerStep = 700; // Decrease by 0.7s each time
let cumulativeDelay = 0;
examples.forEach((example, idx) => {
setTimeout(() => {
example.classList.add('visible');
}, cumulativeDelay);
// Calculate next delay (minimum 0)
const nextDelay = Math.max(0, baseDelay - (idx * decrementPerStep));
cumulativeDelay += nextDelay;
});
// Show protip and navigation after all examples
const protipDelay = cumulativeDelay;
if (protip) {
setTimeout(() => {
protip.classList.add('visible');
}, protipDelay);
}
// Show navigation at same time as protip
setTimeout(() => {
showNavigation();
updateNavigation();
updateTodayButton();
updateActiveTab();
}, protipDelay);
}
// Removed the else block - no collapsing allowed!
};
// Remove old listener and add new one - only on container
if (terminalContainer) {
terminalContainer.removeEventListener('click', window.commandClickHandler);
window.commandClickHandler = commandClickHandler;
terminalContainer.addEventListener('click', commandClickHandler);
}
// Show command text and hide prompt after typing animation completes
// typing animation is 1s with delay 1.5s = total 2.5s
// Add extra 800ms delay before prompt fades out
setTimeout(() => {
const cmdEl = document.getElementById(nameElId);
const promptEl = document.querySelector('.prompt-text');
const spaceEl = document.querySelector('.space');
if (cmdEl) {
cmdEl.classList.add('visible');
}
if (promptEl) {
promptEl.classList.add('hide');
}
if (spaceEl) {
spaceEl.classList.add('hide');
}
}, 3300);
// Auto-expand if requested or if already expanded
if (autoExpand || isExpanded) {
exp.classList.remove('hidden');
// Hide cursor immediately
const cursor = document.querySelector('.cursor');
if (cursor) {
cursor.classList.add('hidden-cursor');
}
// Show everything immediately without animation
const examples = list.querySelectorAll('li');
const protip = exp.querySelector('.protip');
examples.forEach((example) => {
example.classList.add('visible');
});
if (protip) {
protip.classList.add('visible');
}
// Show navigation immediately
showNavigation();
updateNavigation();
updateTodayButton();
updateActiveTab();
updateCoffeeLink();
}
}
function showNavigation() {
document.getElementById('prev-command').classList.add('visible');
document.getElementById('share-btn').classList.add('visible');
updateCoffeeLink();
}
function hideNavigation() {
document.getElementById('prev-command').classList.remove('visible');
document.getElementById('share-btn').classList.remove('visible');
updateCoffeeLink();
}
function updateCoffeeLink() {
const coffeeLink = document.getElementById('coffee-link');
// Once shown, keep it visible
if (coffeeHasBeenShown) {
coffeeLink.classList.add('visible');
return;
}
if (isSharedVisitor && isExpanded) {
// Shared visitors: show coffee link when they interact (expand any day)
coffeeLink.classList.add('visible');
coffeeHasBeenShown = true;
} else if (!isSharedVisitor && currentIndex === todayIndex - 1) {
// Fresh visitors: show on Yesterday and keep visible after that
coffeeLink.classList.add('visible');
coffeeHasBeenShown = true;
} else {
// Hide coffee link (only before it's been shown)
coffeeLink.classList.remove('visible');
}
}
function copyExample(text, button) {
navigator.clipboard.writeText(text).then(() => {
const originalText = button.textContent;
button.textContent = 'Copied!';
button.classList.add('copied');
setTimeout(() => {
button.textContent = originalText;
button.classList.remove('copied');
}, 2000);
});
}
function renderNavDay(index, elId, label = '', clickIndex = null) {
const i = (index + shuffled.length) % shuffled.length;
const el = document.getElementById(elId);
el.textContent = label;
if (clickIndex !== null) {
el.style.cursor = 'pointer';
el.onclick = () => {
currentIndex = clickIndex;
renderDay(clickIndex, 'command-name', 'explanation', true);
updateNavigation();
updateTodayButton();
};
} else {
el.onclick = null;
el.style.cursor = 'default';
}
}
function updateNavigation() {
const prevEl = document.getElementById('prev-command');
if (currentIndex === todayIndex) {
// We're on Today
prevEl.textContent = 'Yesterday';
prevEl.classList.add('visible');
prevEl.onclick = () => {
currentIndex = todayIndex - 1;
isExpanded = true;
renderDay(currentIndex, 'command-name', 'explanation', true);
window.scrollTo({ top: 0, behavior: 'smooth' });
};
} else if (currentIndex === todayIndex - 1) {
// We're on Yesterday
prevEl.classList.remove('visible');
prevEl.onclick = null;
}
}
function updateTodayButton() {
const todayBtn = document.getElementById('today-btn');
if (currentIndex === todayIndex) {
// We're viewing today - hide Today button
todayBtn.classList.remove('visible');
} else if (currentIndex < todayIndex) {
// We're viewing yesterday - show Today button
todayBtn.classList.add('visible');
}
}
function updateActiveTab() {
const prevEl = document.getElementById('prev-command');
const todayBtn = document.getElementById('today-btn');
// Remove underline from all
prevEl.style.textDecoration = 'none';
todayBtn.style.textDecoration = 'none';
// Add underline to active tab
if (currentIndex === todayIndex - 1) {
// Yesterday is active
prevEl.style.textDecoration = 'underline';
}
}
function shuffleArray(array, seed) {
let m = array.length, t, i;
let rand = seed;
while (m) {
i = Math.floor(random(seed) * m--);
t = array[m]; array[m] = array[i]; array[i] = t;
++seed;
}
return array;
function random(seed) {
const x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
}