Skip to content

Commit 9f99362

Browse files
committed
i
1 parent a918c73 commit 9f99362

9 files changed

Lines changed: 89 additions & 107 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ node yt-ids.js
7171
<h3 id="shuffle.js"><a href="/yt/shuffle.js">shuffle.js</a></h3>
7272

7373
- proccessAndCombine combines all video IDs, (limiting how many come from each playlist), from the music section into one `<y-t>` element, at the top of [/edu/vibes](https://y0.netlify.app/edu/vibes).
74-
- shuffleAndDraw shuffles an array of playlists & videos extracted from the music section, with its own 'next' button. It then "draws" from the shuffled array, cloning the drawn `<y-t>` element into the 'draw' `<div>`. [Demo at top of main page](https://y0.netlify.app/)
74+
- shuffleAndDraw shuffles an array of playlists & videos extracted from the music section, with its own 'next' button. It then "draws" from the shuffled array, cloning the drawn `<y-t>` element into the 'deck' `<div>`. [Demo at top of main page](https://y0.netlify.app/)
7575
- The order of the elements is shuffled at each page load, as well as the video IDs within each element.
7676
- Limited to the `<div>` with id "music" so only music is included and playlist order is preserved in other sections of the site.
7777
- Since shuffled playlists are constructed with each video ID in the URL, there is a limit of 150 per playlist, although each shuffle picks from the entire list of video IDs (in this case about 20,000 for the combined playlist)
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/bundle-1d603.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/bundle-4ee7d.js

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

dist/index.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/css/dark-mode.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ a:hover.dark-mode {text-decoration: dotted underline;
1717
border: .1rem rgba(22,22,22,.1) ridge;
1818
background: linear-gradient(to top, rgba(22,22,222, .5), rgba(222, 222, 222, .1));}
1919
a, a.dark-mode, button {font-weight:600; opacity: .8;}
20-
#draw.dark-mode a {color: #00eeff;
20+
#deck.dark-mode a {color: #00eeff;
2121
text-shadow: 1px 1px 2px red, 0 0 1rem blue, 0 0 0.2rem blue;
2222
filter: drop-shadow(0 0 .1rem rgba(255, 100, 100, .3));
2323
text-decoration: underline dashed;
@@ -29,7 +29,7 @@ a, a.dark-mode, button {font-weight:600; opacity: .8;}
2929
border: .1rem rgba(22,22,22,.15) groove;
3030
background: linear-gradient(to top, rgba(22,222,222, .1), rgba(222, 222, 222, 0.1));
3131
border-radius: 1rem;}
32-
#draw.dark-mode a:hover {text-decoration: dotted underline;
32+
#deck.dark-mode a:hover {text-decoration: dotted underline;
3333
text-decoration-thickness: 2px;
3434
border: .1rem rgba(22,22,22,.1) ridge;
3535
background: linear-gradient(to top, rgba(22,22,222, .5), rgba(222, 222, 222, .1));}

src/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ <h1><span id="site-title"><a href="/" class="no-bg" title="Home"><img alt="Home"
4242
<div id="site-info" class="article-content" style="padding:0; margin:auto;"><div class="width95 opaq80" style="margin-bottom: -.5em;">
4343
<h5 style="margin: .1em 0 -.8em"><i>About i&i:</i></h5></div><div class="width95"><p class="no-indent center" style="padding-left:0;">
4444
Designed for max efficiency, freedom, & functionality. Full code & more info at <a href="https://github.com/i1li/i">GitHub</a>. Best enjoyed with good music, here's some <a href="https://github.com/i1li/i#shufflejs">shuffled</a> favorites:
45-
</p><blockquote class="center" style="margin-bottom:-.5em;"><button id="next" title="Next Video">⏭️ Next</button><span id="draw"></span></blockquote>
45+
</p><blockquote class="center" style="margin-bottom:-.5em;"><button id="next" title="Next Video">⏭️ Next</button><span id="deck"></span></blockquote>
4646
<p style="padding:.7rem 0 .2rem;" class="no-indent italic90">(More music <a href="/edu/vibes">here.</a>)</p>
4747
<p style="padding-left:0;padding-top:0;" class="no-indent">
4848
Posts are generally listed in recommended reading order, so you can start by scrolling down, or click a post title:

src/js/spa.js

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ const toTop = document.getElementById("toTop");
55
function goToTop() {window.scrollTo(0, 0);}
66
let lastUpdatedPath = '';
77
let isInitial = true;
8+
let historyStack = new Map();
9+
let currentHistoryIndex = -1;
10+
history.scrollRestoration = 'manual';
811
function scrollSPA(arg, instantScrollNoHeaderOffset = false) {
912
if (arg && arg.nodeType && !(arg instanceof Event)) {
1013
const behavior = instantScrollNoHeaderOffset ? 'instant' : 'smooth';
@@ -14,6 +17,7 @@ function scrollSPA(arg, instantScrollNoHeaderOffset = false) {
1417
window.scrollTo({ top: offsetTop, behavior });
1518
return;
1619
}
20+
if (currentHistoryIndex >= 0) historyStack.get(currentHistoryIndex).scrollY = window.scrollY;
1721
if (window.scrollY > headerHeight * 0.6) {
1822
header.classList.add('scrolled-down');
1923
} else {
@@ -50,7 +54,10 @@ function scrollSPA(arg, instantScrollNoHeaderOffset = false) {
5054
path += `/${currentSection?.id}`;
5155
}
5256
if (path !== lastUpdatedPath) {
53-
history.replaceState(null, '', `/${path}`);
57+
const newSectionId = currentSection ? currentSection.id : undefined;
58+
historyStack.get(currentHistoryIndex).sectionId = newSectionId;
59+
historyStack.get(currentHistoryIndex).path = path;
60+
history.replaceState({historyIndex: currentHistoryIndex}, '', `/${path}`);
5461
lastUpdatedPath = path;
5562
}
5663
}
@@ -83,7 +90,7 @@ function showBottomNav(container) {
8390
}
8491
}
8592
const welcome = document.getElementById('welcome');
86-
function showAllArticles() {
93+
function showAllArticles(fromHistory = false) {
8794
articles.forEach((article, index) => {
8895
article.style.display = 'block';
8996
if (index !== 0) {
@@ -94,9 +101,9 @@ function showAllArticles() {
94101
if (navBottom) navBottom.remove();
95102
});
96103
welcome.style.display = 'block';
97-
goToTop();
104+
if (!fromHistory) goToTop();
98105
}
99-
function showArticle(articleId, sectionId, isInitial = false) {
106+
function showArticle(articleId, sectionId, isInitial = false, fromHistory = false) {
100107
articles.forEach(container => {
101108
if (container.id !== articleId) {
102109
container.style.display = 'none';
@@ -107,17 +114,25 @@ function showArticle(articleId, sectionId, isInitial = false) {
107114
const contentEl = container.querySelector('.article-content');
108115
if (contentEl) contentEl.style.display = 'block';
109116
showBottomNav(container);
110-
scrollSPA(sectionId ? container.querySelector(`.section-title#${sectionId}`) : container, isInitial && !!sectionId);
111-
const newPath = `/${articleId}${sectionId ? `/${sectionId}` : ''}`;
112-
history.replaceState({ articleId, sectionId }, '', newPath);
117+
if (fromHistory) {
118+
const sectionEl = sectionId ? container.querySelector(`.section-title#${sectionId}`) : container;
119+
const scrollTop = sectionEl.getBoundingClientRect().top + window.scrollY - headerHeight;
120+
window.scrollTo({top: scrollTop, behavior: 'instant'});
121+
} else {
122+
scrollSPA(sectionId ? container.querySelector(`.section-title#${sectionId}`) : container, isInitial && !!sectionId);
123+
}
113124
});
114125
}
115126
const initialPath = window.location.pathname.substring(1);
116127
const initialHash = window.location.hash.substring(1);
128+
let initialArticleId = null;
129+
let initialSectionId = null;
117130
if (initialPath) {
118131
const [articleId, sectionId] = initialPath.split('/');
119132
if (articleId && document.getElementById(articleId)) {
120133
showArticle(articleId, sectionId, true);
134+
initialArticleId = articleId;
135+
initialSectionId = sectionId;
121136
} else {
122137
showAllArticles();
123138
}
@@ -126,21 +141,31 @@ if (initialPath) {
126141
} else {
127142
showAllArticles();
128143
}
144+
currentHistoryIndex = 0;
145+
historyStack.set(0, {path: initialPath, articleId: initialArticleId, sectionId: initialSectionId, scrollY: 0});
146+
history.replaceState({historyIndex: 0}, '', window.location.pathname);
147+
document.body.setAttribute(`data-history-${currentHistoryIndex}`, 'true');
129148
scrollSPA();
130149
window.onpopstate = (event) => {
131-
if (event.state?.articleId) {
132-
showArticle(event.state.articleId, event.state.sectionId, false);
150+
if (event.state && event.state.historyIndex !== undefined) {
151+
currentHistoryIndex = event.state.historyIndex;
152+
document.body.setAttribute(`data-history-${currentHistoryIndex}`, 'true');
153+
const entry = historyStack.get(currentHistoryIndex);
154+
if (entry.articleId) {
155+
showArticle(entry.articleId, entry.sectionId, false, true);
156+
} else {
157+
showAllArticles(true);
158+
window.scrollTo({top: entry.scrollY, behavior: 'instant'});
159+
}
133160
} else {
134161
showAllArticles();
135162
}
136163
};
137164
function handleHash(hash) {
138-
if (hash === 'now-playing' || !document.getElementById(hash)) {
139-
showAllArticles();
140-
history.replaceState({}, '', window.location.origin);
165+
if (hash === 'now-playing' || !document.getElementById(hash.split('/')[0])) {
166+
navigateSPA('/');
141167
} else {
142-
const [articleId, sectionId] = hash.split('/');
143-
showArticle(articleId, sectionId, false);
168+
navigateSPA(hash);
144169
}
145170
}
146171
window.addEventListener('hashchange', () => handleHash(window.location.hash.substring(1)));
@@ -152,11 +177,18 @@ function navigateSPA(pathOrEvent, path) {
152177
handleHash(actualPath.substring(1));
153178
return;
154179
}
155-
const [articleId, sectionId] = actualPath.split('/');
156-
if (articleId) {
157-
showArticle(articleId, sectionId, false);
180+
const [newArticleId, newSectionId] = actualPath.split('/');
181+
const newPath = actualPath;
182+
if (newPath === historyStack.get(currentHistoryIndex)?.path) return;
183+
if (currentHistoryIndex >= 0) historyStack.get(currentHistoryIndex).scrollY = window.scrollY;
184+
currentHistoryIndex++;
185+
for (let key of Array.from(historyStack.keys()).sort((a,b)=>a-b)) {if (key > currentHistoryIndex) historyStack.delete(key);}
186+
historyStack.set(currentHistoryIndex, {path: newPath, articleId: newArticleId, sectionId: newSectionId, scrollY: 0});
187+
history.pushState({historyIndex: currentHistoryIndex}, '', newPath ? `/${newPath}` : '/');
188+
document.body.setAttribute(`data-history-${currentHistoryIndex}`, 'true');
189+
if (newArticleId) {
190+
showArticle(newArticleId, newSectionId, false, false);
158191
} else {
159192
showAllArticles();
160-
history.replaceState({}, '', window.location.origin);
161193
}
162194
}

src/yt/youtube-embed.js

Lines changed: 31 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ class YTEmbed extends HTMLElement {
1414
this.initFromV();
1515
}
1616
attributeChangedCallback(name, oldValue, newValue) {
17-
if (name === 'v' && oldValue !== newValue) {
18-
this.initFromV();
19-
}
17+
if (name === 'v' && oldValue !== newValue) {this.initFromV();}
2018
}
2119
isPlaylistID(id) {
2220
return id.length > 11 && (id.startsWith('PL') || id.startsWith('TL') || id.startsWith('OL') || id.startsWith('FL') || id.startsWith('UU'));
@@ -110,12 +108,8 @@ class YTEmbed extends HTMLElement {
110108
const article = this.closest('article');
111109
const section = this.closest('section');
112110
let path = '/';
113-
if (article) {
114-
path += article.id;
115-
if (section) {
116-
path += `/${section.id}`;
117-
}
118-
}
111+
if (article) { path += article.id;
112+
if (section) { path += `/${section.id}`; }}
119113
this.setAttribute('data-video-path', path);
120114
this.createRemoteControl();
121115
this.setupViewportCheck();
@@ -130,25 +124,8 @@ class YTEmbed extends HTMLElement {
130124
}
131125
}
132126
closeOtherVideos() {
133-
const playingVideos = document.querySelectorAll('y-t[data-now-playing]');
134-
playingVideos.forEach(video => {
135-
if (video !== this) {
136-
const button = video.querySelector('button.showHideButton');
137-
if (button && button.textContent === '⏹️ Stop') {
138-
video.removeAttribute('data-now-playing');
139-
video.removeAttribute('data-video-path');
140-
const wrapper = video.querySelector('.yt-wrapper');
141-
if (wrapper) {
142-
const iframe = wrapper.querySelector('iframe');
143-
if (iframe) {
144-
wrapper.removeChild(iframe);
145-
}
146-
wrapper.style.display = 'none';
147-
}
148-
button.textContent = '▶️ Play';
149-
button.title = 'Play Video';
150-
}
151-
}
127+
document.querySelectorAll('y-t[data-now-playing]').forEach(video => {
128+
if (video !== this) video.button.click();
152129
});
153130
}
154131
createRemoteControl() {
@@ -191,10 +168,7 @@ class YTEmbed extends HTMLElement {
191168
const linkToVideo = document.createElement('a');
192169
linkToVideo.textContent = 'Go to video';
193170
linkToVideo.style.display = 'block';
194-
linkToVideo.onclick = (e) => {
195-
e.preventDefault();
196-
this.navigateToNowPlaying();
197-
};
171+
linkToVideo.onclick=(e)=>{e.preventDefault();this.navigateToNowPlaying();};
198172
this.updateNowPlayingLink(linkToVideo);
199173
remoteControl.appendChild(stopButton);
200174
remoteControl.appendChild(linkToVideo);
@@ -205,25 +179,13 @@ class YTEmbed extends HTMLElement {
205179
const expandIcon = document.getElementById('expand-icon');
206180
const nowPlayingText = document.getElementById('now-playing-text');
207181
if (this.isOutOfViewport()) {
208-
if (remoteControl) {
209-
remoteControl.style.display = 'none';
210-
}
211-
if (expandIcon) {
212-
expandIcon.style.display = 'block';
213-
}
214-
if (nowPlayingText) {
215-
nowPlayingText.style.display = 'block';
216-
}
182+
if (remoteControl) {remoteControl.style.display = 'none';}
183+
if (expandIcon) {expandIcon.style.display = 'block';}
184+
if (nowPlayingText) {nowPlayingText.style.display = 'block';}
217185
} else {
218-
if (remoteControl) {
219-
remoteControl.style.display = 'none';
220-
}
221-
if (expandIcon) {
222-
expandIcon.style.display = 'none';
223-
}
224-
if (nowPlayingText) {
225-
nowPlayingText.style.display = 'none';
226-
}
186+
if (remoteControl) {remoteControl.style.display = 'none';}
187+
if (expandIcon) {expandIcon.style.display = 'none';}
188+
if (nowPlayingText) {nowPlayingText.style.display = 'none';}
227189
}
228190
}
229191
setupViewportCheck() {
@@ -232,18 +194,11 @@ class YTEmbed extends HTMLElement {
232194
}
233195
isOutOfViewport() {
234196
const rect = this.getBoundingClientRect();
235-
return (
236-
rect.bottom <= 0 ||
237-
rect.right <= 0 ||
238-
rect.left >= window.innerWidth ||
239-
rect.top >= window.innerHeight
240-
);
197+
return (rect.bottom <= 0 || rect.right <= 0 || rect.left >= window.innerWidth || rect.top >= window.innerHeight);
241198
}
242199
updateNowPlayingLink(linkElement) {
243200
const path = this.getAttribute('data-video-path');
244-
if (path) {
245-
linkElement.href = `${path}#now-playing`;
246-
}
201+
if (path) {linkElement.href = `${path}#now-playing`;}
247202
}
248203
navigateToNowPlaying() {
249204
const nowPlaying = document.querySelector('y-t[data-now-playing]');
@@ -263,23 +218,18 @@ class YTEmbed extends HTMLElement {
263218
}
264219
removeRemoteControl() {
265220
const remoteControl = document.getElementById('remote-control');
266-
if (remoteControl) {
267-
document.body.removeChild(remoteControl);
268-
}
221+
if (remoteControl) {document.body.removeChild(remoteControl);}
269222
const expandIcon = document.getElementById('expand-icon');
270-
if (expandIcon) {
271-
document.body.removeChild(expandIcon);
272-
}
223+
if (expandIcon) {document.body.removeChild(expandIcon);}
273224
const nowPlayingText = document.getElementById('now-playing-text');
274-
if (nowPlayingText) {
275-
document.body.removeChild(nowPlayingText);
276-
}
225+
if (nowPlayingText) {document.body.removeChild(nowPlayingText);}
277226
this.removeEventListeners()
278227
}
279228
}
280229
customElements.define('y-t', YTEmbed);
230+
281231
const shuffleDiv = document.getElementById('shuffle');
282-
const elements = [...shuffleDiv.querySelectorAll('y-t')];
232+
const shuffleDivElements = [...shuffleDiv.querySelectorAll('y-t')];
283233
let combinedElements = [];
284234
let elementIdsMap = new Map();
285235
function processAndCombine(element, index) {
@@ -292,8 +242,8 @@ function processAndCombine(element, index) {
292242
const shuffledArray = shuffle(elementIds, 150);
293243
element.setAttribute('v', shuffledArray.join(','));
294244
processedElements.add(index);
295-
if (index < elements.length - 1) {
296-
processAndCombine(elements[index + 1], index + 1);
245+
if (index < shuffleDivElements.length - 1) {
246+
processAndCombine(shuffleDivElements[index + 1], index + 1);
297247
}
298248
else {
299249
const combo = document.getElementById('combined-list');
@@ -302,21 +252,21 @@ function processAndCombine(element, index) {
302252
}
303253
}
304254
let processedElements = new Set();
305-
processAndCombine(elements[0], 0);
255+
processAndCombine(shuffleDivElements[0], 0);
306256
function shuffleAndDraw() {
307-
shuffle(elements);
257+
shuffle(shuffleDivElements);
308258
let currentIndex = 0;
309-
let draw = document.getElementById('draw');
310-
const clonedElement = elements[currentIndex].cloneNode(false);
259+
let deck = document.getElementById('deck');
260+
const clonedElement = shuffleDivElements[currentIndex].cloneNode(false);
311261
processAndCombine(clonedElement, currentIndex);
312-
draw.appendChild(clonedElement);
262+
deck.appendChild(clonedElement);
313263
let next = document.querySelector("#next");
314264
next.addEventListener('click', function() {
315-
currentIndex = (currentIndex + 1) % elements.length;
316-
draw.innerHTML = '';
317-
const clonedElement = elements[currentIndex].cloneNode(false);
265+
currentIndex = (currentIndex + 1) % shuffleDivElements.length;
266+
deck.innerHTML = '';
267+
const clonedElement = shuffleDivElements[currentIndex].cloneNode(false);
318268
processAndCombine(clonedElement, currentIndex);
319-
draw.appendChild(clonedElement);
269+
deck.appendChild(clonedElement);
320270
});
321271
}
322-
shuffleAndDraw();
272+
shuffleAndDraw();

0 commit comments

Comments
 (0)