Compare commits
6 Commits
68e9418c12
...
1fc26bfdfe
Author | SHA1 | Date | |
---|---|---|---|
1fc26bfdfe | |||
22db062fd9 | |||
ab390c9d47 | |||
6b58e9115b | |||
126820ade6 | |||
122bfd666a |
@ -59,6 +59,11 @@ func RefreshFeed(url string) Feed {
|
|||||||
imageURL = f.Image.URL
|
imageURL = f.Image.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createdTime := item.PublishedParsed
|
||||||
|
if createdTime == nil {
|
||||||
|
createdTime = item.UpdatedParsed
|
||||||
|
}
|
||||||
|
|
||||||
feed.Items = append(feed.Items, Item{
|
feed.Items = append(feed.Items, Item{
|
||||||
ID: strings.ReplaceAll(base64.StdEncoding.EncodeToString([]byte(item.GUID)), "/", ""),
|
ID: strings.ReplaceAll(base64.StdEncoding.EncodeToString([]byte(item.GUID)), "/", ""),
|
||||||
Title: item.Title,
|
Title: item.Title,
|
||||||
@ -67,7 +72,7 @@ func RefreshFeed(url string) Feed {
|
|||||||
URL: item.Link,
|
URL: item.Link,
|
||||||
ImageURL: imageURL,
|
ImageURL: imageURL,
|
||||||
LastUpdated: item.UpdatedParsed,
|
LastUpdated: item.UpdatedParsed,
|
||||||
Created: item.PublishedParsed,
|
Created: createdTime,
|
||||||
GUID: item.GUID,
|
GUID: item.GUID,
|
||||||
FeedID: feed.ID,
|
FeedID: feed.ID,
|
||||||
})
|
})
|
||||||
|
@ -33,6 +33,11 @@ func (fs *FeedStore) GetFeed(id string) *Feed {
|
|||||||
return feed
|
return feed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *FeedStore) DeleteFeed(id string) {
|
||||||
|
fs.getDB().Delete(Feed{ID: id})
|
||||||
|
fs.getDB().Unscoped().Delete(Item{}, "feed_id = ?", id)
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *FeedStore) GetItem(id string) *Item {
|
func (fs *FeedStore) GetItem(id string) *Item {
|
||||||
item := &Item{}
|
item := &Item{}
|
||||||
fs.getDB().Where("id = ?", id).First(item)
|
fs.getDB().Where("id = ?", id).First(item)
|
||||||
|
@ -17,6 +17,11 @@ func (a *API) GetFeed(c *fiber.Ctx) error {
|
|||||||
return c.JSON(a.FeedStore.GetFeed(c.Params("id")))
|
return c.JSON(a.FeedStore.GetFeed(c.Params("id")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *API) DeleteFeed(c *fiber.Ctx) error {
|
||||||
|
a.FeedStore.DeleteFeed(c.Params("id"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *API) PostFeed(c *fiber.Ctx) error {
|
func (a *API) PostFeed(c *fiber.Ctx) error {
|
||||||
url := ""
|
url := ""
|
||||||
if err := c.BodyParser(&url); err != nil {
|
if err := c.BodyParser(&url); err != nil {
|
||||||
|
@ -50,6 +50,7 @@ func Start(port string) error {
|
|||||||
app.Get("/api/feeds", api.GetFeeds)
|
app.Get("/api/feeds", api.GetFeeds)
|
||||||
app.Post("/api/feeds", api.PostFeed)
|
app.Post("/api/feeds", api.PostFeed)
|
||||||
app.Get("/api/feed/:id", api.GetFeed)
|
app.Get("/api/feed/:id", api.GetFeed)
|
||||||
|
app.Delete("/api/feed/:id", api.DeleteFeed)
|
||||||
app.Get("/api/item/:id", api.GetItem)
|
app.Get("/api/item/:id", api.GetItem)
|
||||||
app.Post("/api/item/:id/save", api.SaveItem)
|
app.Post("/api/item/:id/save", api.SaveItem)
|
||||||
app.Get("/api/unread", api.GetUnread)
|
app.Get("/api/unread", api.GetUnread)
|
||||||
|
@ -61,8 +61,13 @@
|
|||||||
All ({{unread}})
|
All ({{unread}})
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-for="feed in feeds" :class="{strong: unreadCounts[feed.ID], 'alert': true, 'alert-success': selectedFeed == feed.ID }" :data-feed="feed.FeedURL" v-on:click="loadFeed(feed.ID)">
|
<div v-for="feed in sortedFeeds" :class="{strong: unreadCounts[feed.ID], 'alert': true, 'alert-success': selectedFeed == feed.ID }" :data-feed="feed.FeedURL" v-on:click="loadFeed(feed.ID)">
|
||||||
<img :src="feedIcon(feed)" style="height: 16px; width: 16px;" onerror="this.style.visibility = 'hidden'" /> {{feed.Title}} ({{unreadCounts[feed.ID] || '0'}})
|
<img :src="feedIcon(feed)" style="height: 16px; width: 16px;" onerror="this.style.visibility = 'hidden'" /> {{feed.Title}} ({{unreadCounts[feed.ID] || '0'}})
|
||||||
|
<div style="float:right">
|
||||||
|
<button title="Delete Feed" v-on:click="deleteFeed(feed)" :disabled="isBusy">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16px" width="16px" viewBox="0 0 473 473" enable-background="new 0 0 473 473"><g><path d="M317.7 214.4l5.6-86.4h21V38h-98.4V0H132.7v38H34.3v90h21l20 305h140.5a129.6 129.6 0 00223-89.4c0-68.6-53.7-124.8-121.1-129.2zM162.7 30h53.2v8h-53.2v-8zM64.3 68h250v30h-250V68zm39 335l-18-275h208l-5.8 88a129.6 129.6 0 00-93.2 187h-91zm206 40a99.5 99.5 0 010-198.9 99.5 99.5 0 010 198.9z"/><path d="M342.2 289.4l-33 33-32.9-33-21.2 21.2 33 33-33 33 21.2 21.1 33-33 33 33 21.2-21.2-33-33 33-32.9z"/></g><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="{ strong: items.length, alert: true, 'alert-success': selectedFeed == 'SAVED'}" v-on:click="loadFeed('SAVED')">
|
<div :class="{ strong: items.length, alert: true, 'alert-success': selectedFeed == 'SAVED'}" v-on:click="loadFeed('SAVED')">
|
||||||
@ -111,6 +116,9 @@
|
|||||||
<div class="card item-content" :data-id="item.ID" v-if="item.ID == selectedItem">
|
<div class="card item-content" :data-id="item.ID" v-if="item.ID == selectedItem">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
|
<button title="Show IFrame" v-on:click="showIframe(item)" :disabled="isBusy">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"width="24" height="24" viewBox="0 0 426 426" enable-background="new 0 0 426.001 426.001"><g><path d="M406.8 54.2H19.2A19.2 19.2 0 000 73.4v279.2c0 10.6 8.6 19.2 19.2 19.2h387.6c10.6 0 19.2-8.6 19.2-19.2V73.4c0-10.6-8.6-19.2-19.2-19.2zM368.4 82a17.8 17.8 0 110 35.7 17.8 17.8 0 010-35.7zm-48 0a17.8 17.8 0 110 35.7 17.8 17.8 0 010-35.7zm-48 0a17.8 17.8 0 110 35.7 17.8 17.8 0 010-35.7zm115.2 251.5H38.4V141.6h349.2v191.8z"/></g><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/><g/></svg>
|
||||||
|
</button>
|
||||||
<button title="Save" v-on:click="saveItem(item)" :disabled="isBusy">
|
<button title="Save" v-on:click="saveItem(item)" :disabled="isBusy">
|
||||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M12.8 5.6l-.8.8-.8-.8a5.4 5.4 0 00-7.6 7.6l7.9 7.9c.3.3.7.3 1 0l8-8a5.4 5.4 0 10-7.7-7.5z" :style="{'fill': item.Save ? '#ff2e88' : '' }" fill-rule="nonzero"/></svg>
|
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M12.8 5.6l-.8.8-.8-.8a5.4 5.4 0 00-7.6 7.6l7.9 7.9c.3.3.7.3 1 0l8-8a5.4 5.4 0 10-7.7-7.5z" :style="{'fill': item.Save ? '#ff2e88' : '' }" fill-rule="nonzero"/></svg>
|
||||||
</button>
|
</button>
|
||||||
@ -156,6 +164,9 @@
|
|||||||
return this.items.filter(item => item.ID == this.selectedItem || item.FeedID === this.selectedFeed && (!item.Read || item.Read === this.showRead));
|
return this.items.filter(item => item.ID == this.selectedItem || item.FeedID === this.selectedFeed && (!item.Read || item.Read === this.showRead));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
sortedFeeds() {
|
||||||
|
return this.feeds.sort((a, b) => a.Title.toLowerCase() > b.Title.toLowerCase());
|
||||||
|
},
|
||||||
unread() {
|
unread() {
|
||||||
return this.items.filter(item => !item.Read && !item.PendingRead).length;
|
return this.items.filter(item => !item.Read && !item.PendingRead).length;
|
||||||
},
|
},
|
||||||
@ -216,6 +227,20 @@
|
|||||||
this.setBusy(false);
|
this.setBusy(false);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
showIframe(item) {
|
||||||
|
document.querySelector(`feed-item[item-id='${item.ID}'`).showIframe();
|
||||||
|
},
|
||||||
|
deleteFeed(feed) {
|
||||||
|
if (confirm(`Are you sure you want to remove '${feed.Title}'`)) {
|
||||||
|
this.setBusy(true);
|
||||||
|
fetch(`/api/feed/${feed.ID}`, {method: "DELETE"})
|
||||||
|
.then(() => {
|
||||||
|
this.feeds = this.feeds.filter(f => f.ID != feed.ID);
|
||||||
|
this.items = this.items.filter(i => i.FeedID != feed.ID);
|
||||||
|
this.setBusy(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
nextItem() {
|
nextItem() {
|
||||||
let currentItem = -1;
|
let currentItem = -1;
|
||||||
if (this.selectedItem != undefined) {
|
if (this.selectedItem != undefined) {
|
||||||
@ -392,7 +417,8 @@
|
|||||||
.then(items => {
|
.then(items => {
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
if (!this.items.some(i => i.ID == item.ID)) {
|
if (!this.items.some(i => i.ID == item.ID)) {
|
||||||
this.items.push(item);
|
this.items.unshift(item);
|
||||||
|
this.setPageTitle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -35,12 +35,20 @@ class FeedItem extends HTMLElement {
|
|||||||
img {
|
img {
|
||||||
margin: auto auto !important;
|
margin: auto auto !important;
|
||||||
}
|
}
|
||||||
|
h1, h2, h3, h4 {
|
||||||
|
margin-top: 1.3em;
|
||||||
|
}
|
||||||
|
h1:first-of-type {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
p, a {
|
||||||
|
line-height: 1.2em;
|
||||||
|
}
|
||||||
p {
|
p {
|
||||||
font-family: charter, Georgia, "Times New Roman", Times, serif;
|
font-family: charter, Georgia, "Times New Roman", Times, serif;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
letter-spacing: -0.063px;
|
letter-spacing: -0.063px;
|
||||||
line-height: 32px
|
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #333;
|
color: #333;
|
||||||
@ -53,13 +61,34 @@ class FeedItem extends HTMLElement {
|
|||||||
color: #ff2e88;
|
color: #ff2e88;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow-x: scroll;
|
||||||
|
padding: 8px;
|
||||||
|
background: #62848463;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
p code {
|
||||||
|
background: #62848463;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 600px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
fetch(`/api/item/${this.getAttribute('item-id')}`)
|
fetch(`/api/item/${this.getAttribute('item-id')}`)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(item => {
|
.then(item => {
|
||||||
template.innerHTML += item.Content || item.Description;
|
template.innerHTML += `<h1><a href="${item.URL}" target="_blank" rel="noopener">${item.Title}</a></h1>`;
|
||||||
|
template.innerHTML += `<div class="feedContent">${item.Content || item.Description}</div>`;
|
||||||
|
template.innerHTML += `<iframe style="display: none;" data-src="${item.URL}"></iframe>`
|
||||||
this.shadowRoot.appendChild(template.content.cloneNode(true));
|
this.shadowRoot.appendChild(template.content.cloneNode(true));
|
||||||
[...this.shadowRoot.querySelectorAll('a[href^=http]')].forEach(a => {
|
[...this.shadowRoot.querySelectorAll('a[href^=http]')].forEach(a => {
|
||||||
a.setAttribute("target", "_blank");
|
a.setAttribute("target", "_blank");
|
||||||
@ -85,7 +114,12 @@ class FeedItem extends HTMLElement {
|
|||||||
a.href = url.origin +'/'+ a.getAttribute('src');
|
a.href = url.origin +'/'+ a.getAttribute('src');
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
showIframe() {
|
||||||
|
this.shadowRoot.querySelector(".feedContent").style.display = "none";
|
||||||
|
this.shadowRoot.querySelector("iframe").src = this.shadowRoot.querySelector("iframe").dataset.src;
|
||||||
|
this.shadowRoot.querySelector("iframe").style.display = "block";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define('feed-item', FeedItem);
|
customElements.define('feed-item', FeedItem);
|
||||||
|
Loading…
Reference in New Issue
Block a user