Initial commit
This commit is contained in:
108
views/index.html
Normal file
108
views/index.html
Normal file
@@ -0,0 +1,108 @@
|
||||
|
||||
<div id="app">
|
||||
<div class="feeds">
|
||||
<div class="alert alert-success">All <span class="{{if (gt (len .Unread) 0)}}strong{{end}}">({{len .Unread}})</span></div>
|
||||
{{range .Feeds}}
|
||||
<div class="alert" data-feed="{{.HomepageURL}}">{{.Title}} <span class="{{if (gt .UnreadCount 0)}}strong{{end}}">({{.UnreadCount}})</span></div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div class="items">
|
||||
{{range .Unread}}
|
||||
<div class="alert alert-info item-heading" data-feed="{{.FeedHomepageURL}}" data-id="{{.ID}}" id="{{.ID}}">
|
||||
<span class="feed-title">{{.FeedTitle}}</span>
|
||||
<span class="date" title="{{.Created}}">{{humanDate .Created}}</span>
|
||||
<h3 class="item-title"><a href="{{.URL}}">{{.Title}}</a></h3>
|
||||
</div>
|
||||
<div class="card item-content hide" data-id="{{.ID}}">
|
||||
<div class="card-content">
|
||||
<div class="loading"></div>
|
||||
<feed-item item-id="{{.ID}}">
|
||||
</feed-item>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const vm = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
feeds: [],
|
||||
items: [],
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
},
|
||||
async created() {
|
||||
this.feeds = (await fetch(`/api/feeds`)).json();
|
||||
this.items = (await fetch(`/api/unread`)).json();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
function reset() {
|
||||
[...document.querySelectorAll('.items [data-feed]')].forEach(item => {
|
||||
item.style.display = 'none';
|
||||
});
|
||||
[...document.querySelectorAll('.feeds .alert')].forEach(el => {
|
||||
el.classList.remove('alert-success');
|
||||
});
|
||||
[...document.querySelectorAll('.items .item-content')].forEach(el => {
|
||||
el.classList.add('hide');
|
||||
});
|
||||
}
|
||||
|
||||
function markAsRead(id) {
|
||||
fetch(`/api/read/${id}`, {method: "POST"})
|
||||
.then(res => {
|
||||
if (res.status < 400) {
|
||||
document.querySelector('.items .alert[data-id="'+id+'"]').classList.remove('alert-info');
|
||||
}
|
||||
})
|
||||
.catch(err => console.log)
|
||||
}
|
||||
|
||||
[...document.querySelectorAll('.feeds .alert')].forEach(feed => {
|
||||
feed.addEventListener('click', () => {
|
||||
reset();
|
||||
if (!feed.dataset.feed) {
|
||||
[...document.querySelectorAll('.items [data-feed]')].forEach(item => {
|
||||
item.style.display = 'block';
|
||||
});
|
||||
} else {
|
||||
if (document.querySelector('.items [data-feed="'+feed.dataset.feed+'"]')) {
|
||||
[...document.querySelectorAll('.items [data-feed="'+feed.dataset.feed+'"]')].forEach(item => {
|
||||
item.style.display = 'block';
|
||||
});
|
||||
}
|
||||
}
|
||||
feed.classList.add('alert-success');
|
||||
});
|
||||
});
|
||||
|
||||
[...document.querySelectorAll('.items [data-feed]')].forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
window.location.hash = item.dataset.id;
|
||||
|
||||
[...document.querySelectorAll('.items .item-content:not([data-id="'+item.dataset.id+'"])')].forEach(el => {
|
||||
el.classList.add('hide');
|
||||
});
|
||||
|
||||
document.querySelector('.items .item-content[data-id="'+item.dataset.id+'"]').classList.toggle('hide');
|
||||
document.querySelector('.items .item-content[data-id="'+item.dataset.id+'"] feed-item').load()
|
||||
.then(() => {
|
||||
document.querySelector('.items .item-content[data-id="'+item.dataset.id+'"] .loading').remove();
|
||||
markAsRead(item.dataset.id);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
</script>
|
103
views/layouts/main.html
Normal file
103
views/layouts/main.html
Normal file
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Gopherss</title>
|
||||
<script src="/feed-item.js" defer></script>
|
||||
|
||||
<script src="https://unpkg.com/vue@2.5.17/dist/vue.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js" integrity="sha256-T/f7Sju1ZfNNfBh7skWn0idlCBcI3RwdLSS4/I7NQKQ=" crossorigin="anonymous"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/hack@0.8.1/dist/hack.css">
|
||||
<link rel="stylesheet" href="https://unpkg.com/hack@0.8.1/dist/dark.css">
|
||||
<link rel="stylesheet" href="https://unpkg.com/hack@0.8.1/dist/dark-grey.css">
|
||||
|
||||
<style>
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
.strong {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.loading {
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: min(50%, 100em);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.item-heading .item-title {
|
||||
|
||||
margin-bottom: 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.item-heading {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.item-heading .date {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.item-heading:after {
|
||||
content: '';
|
||||
clear: both;
|
||||
display: table;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 3px;
|
||||
}
|
||||
.item:nth-of-type(even) {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
margin-bottom: 1.75rem;
|
||||
padding: 15px;
|
||||
margin-top: -2px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.feeds {
|
||||
position: absolute;
|
||||
left: max(-25vw, -500px);
|
||||
width: min(25vw, 500px);
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.feeds .alert {
|
||||
margin-bottom: 1px !important;
|
||||
}
|
||||
|
||||
.alert:hover:not(.alert-success) {
|
||||
border-color: #ff2e88;
|
||||
color: #00bcd4;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 700px) {
|
||||
.feeds{ display: none !important; }
|
||||
.container {
|
||||
max-width: 100em;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="hack">
|
||||
<div class="container">
|
||||
<h1 class="title">
|
||||
Gopherss
|
||||
</h1>
|
||||
{{embed}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
35
views/static/feed-item.js
Normal file
35
views/static/feed-item.js
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
class FeedItem extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({mode: 'open'});
|
||||
}
|
||||
|
||||
load() {
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = `
|
||||
<style>
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
* {
|
||||
max-width: 100%;
|
||||
}
|
||||
img {
|
||||
margin: auto auto;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
if (!this.loaded) {
|
||||
return fetch(`/api/item/${this.getAttribute('item-id')}`)
|
||||
.then(res => res.json())
|
||||
.then(item => {
|
||||
template.innerHTML += item.Content || item.Description;
|
||||
this.shadowRoot.appendChild(template.content.cloneNode(true));
|
||||
})
|
||||
.then(() => this.loaded = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('feed-item', FeedItem);
|
Reference in New Issue
Block a user