cozy/src/components/Library.astro

210 lines
6.2 KiB
Text

---
import Serialize from '@ayco/astro-resume';
export interface Props {
skipSave?: boolean
}
---
<div id="library">
<span id="heading"></span>
<ul id="post-list"></ul>
</div>
<Serialize id="preferences" data={{...Astro.props}} />
<script>
import { deserialize } from '@ayco/astro-resume';
import type { Props } from './Library.astro';
import type { AppConfig } from '../pages/index.astro';
import { getPostCard, renderPost } from '../utils/library'
import { cozify } from '../utils/sanitizer';
import { logError, logInfo } from '../utils/logger.mjs';
const cache = await caches.open('cozy-reader');
const baseUrl = window.location.origin;
let url= new URL(window.location.href);
// only cached unencoded url param
const urlParam = url.searchParams.get('url')
if (urlParam) {
url = new URL(`${url.origin}/?url=${urlParam}`);
}
const { skipSave } = deserialize<Props>('preferences');
const { routerOutlet } = deserialize<AppConfig>('app-config');
const includesAppURL = urlParam?.includes(baseUrl) ?? false;
try {
if (url.href.slice(0, url.href.length - 1) !== baseUrl && !skipSave && !includesAppURL) {
logInfo('adding one to cache', {context: 'cozy-reader', data: url})
await cache.add(url);
}
} catch(error) {
logError('ERR', {context: 'cozy-reader', data: error})
}
const cachedRequests = (await cache.keys())
.filter(request => {
const urlObj = new URL(request.url);
const urlParam = urlObj.searchParams.get('url');
return urlObj.search !== ''
&& !urlParam?.startsWith(baseUrl)
&& urlParam !== ''
&& urlParam !== 'null';
});
if(cachedRequests?.length && routerOutlet !== null) {
const list = document.querySelector('#post-list');
const heading = document.querySelector('#library span#heading') as HTMLHeadingElement;
heading.innerHTML = 'History';
cachedRequests
.reverse()
.forEach(async (request) => {
const {url} = request;
const link = document.createElement('a');
let responseText;
const fullResponse = await cache.match(url)
try {
const responseFromNetwork = await fetch(url, {method: 'GET'});
if (responseFromNetwork && url.slice(0, url.length - 1) !== baseUrl && !skipSave && !includesAppURL) {
logInfo('updating cached', {context: 'cozy-reader', data: url})
await cache.put(url, responseFromNetwork);
}
} catch(error) {
logError('failed to update cached', {context: 'cozy-reader', data: {url, error}})
}
fullResponse?.text().then(async data => {
responseText = data;
const cleanedResponse = await cozify(responseText, baseUrl)
const html = document.createElement('html');
html.innerHTML = cleanedResponse;
const title = html.querySelector('meta[property="cozy:title"]')?.getAttribute('content');
if (title === 'Something is not right') {
cache.delete(url);
return; // temporary fix for deleting cached errors
}
const postCard = getPostCard(html);
link.innerHTML = postCard;
link.href = url;
link.onclick = async (e) => {
e.preventDefault();
localStorage.setItem('scrollPosition', window.scrollY.toString());
scrollTo(0,0);
logInfo('using cached response', {context: 'cozy-reader', data: url})
renderPost(cleanedResponse, url, routerOutlet)
}
const item = document.createElement('li');
item.appendChild(link);
list?.appendChild(item);
});
});
window.addEventListener('popstate', async (data) => {
let url = data.state?.url;
let isHome = false;
if (!url) {
url = window.location.href;
isHome = true;
} else {
// replace scrollPosition
localStorage.setItem('scrollPosition', window.scrollY.toString());
}
const fullResponse = await cache.match(url)
fullResponse?.text().then(async (data) => {
const responseText = data;
const cleanedResponse = await cozify(responseText, baseUrl);
logInfo('using cached response', {context: 'cozy-reader', data: url})
renderPost(cleanedResponse, url, routerOutlet, true);
if (isHome) {
const scrollPosition = localStorage.getItem('scrollPosition');
scrollTo(0, scrollPosition ? parseInt(scrollPosition) : 0);
}
});
});
}
</script>
<style lang="scss">
#library {
span#heading {
color: #555;
font-size: small;
text-transform: uppercase;
}
}
#post-list {
/**
`:global` is needed for elements not generated by Astro
- can be improved by CSS in JS, but... this is fine
*/
:global(li) {
list-style: none;
width: calc(100% + 40px);
margin-left: -40px;
:global(a) {
text-decoration: none;
color: #000;
:global(h3) {
text-decoration: underline;
}
:global(.post-card) {
padding-bottom: 1rem;
:global(.post-card__image) {
float: left;
margin: 0.25rem 0.5rem 0.25rem 0;
:global(img, svg) {
width: 70px;
height: 70px;
object-fit: cover;
border-radius: 5px;
border: 1px solid #eee;
}
:global(svg) {
color: #ccc;
padding: 0.5rem;
}
}
}
:global(.post-card__content) {
display: flex;
flex-direction: column;
justify-content: center;
min-height: calc(70px + 0.5rem);
}
:global(.post-card__title, .post-card__description) {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
:global(.post-card__meta, .post-card__description){
font-size: smaller;
color: #555;
}
:global(.post-card__meta) {
display: flex;
justify-content: space-between;
* {
flex: 1;
}
:global(.post-card__source) {
font-weight: bold;
}
}
}
}
}
</style>