Made an RSS feed widget

This commit is contained in:
Alicia Sykes 2021-12-14 16:58:29 +00:00
parent f61366ca48
commit 6df95c9387
4 changed files with 307 additions and 1 deletions

View File

@ -1,6 +1,6 @@
# Widgets
Dashy has support for displaying dynamic content in the form of widgets. There are several built-in widgets availible out-of-the-box (with more on the way!) as well as support for custom widgets to display stats from almost any service with an accessible API.
Dashy has support for displaying dynamic content in the form of widgets. There are several built-in widgets available out-of-the-box as well as support for custom widgets to display stats from almost any service with an API.
##### Contents
- [General Widgets](#general-widgets)
@ -9,6 +9,7 @@ Dashy has support for displaying dynamic content in the form of widgets. There a
- [Weather Forecast](#weather-forecast)
- [Crypto Watch List](#crypto-watch-list)
- [Crypto Price History](#crypto-token-price-history)
- [RSS Feed](#rss-feed)
- [XKCD Comics](#xkcd-comics)
- [TFL Status](#tfl-status)
- [Exchange Rates](#exchange-rates)
@ -47,6 +48,8 @@ A simple, live-updating time and date widget with time-zone support. All options
hideDate: false
```
---
### Weather
A simple, live-updating local weather component, showing temperature, conditions and more info.
@ -73,6 +76,8 @@ A simple, live-updating local weather component, showing temperature, conditions
hideDetails: false
```
---
### Weather Forecast
Displays the weather (temperature and conditions) for the next few days for a given location. Note that this requires either the free [OpenWeatherMap Student Plan](https://home.openweathermap.org/students), or the Premium Plan.
@ -100,6 +105,9 @@ Displays the weather (temperature and conditions) for the next few days for a gi
units: imperial
```
---
### Crypto Watch List
Keep track of price changes of your favorite crypto assets. Data is fetched from [CoinGecko](https://www.coingecko.com/)
@ -139,6 +147,8 @@ Or
- dogecoin
```
---
### Crypto Token Price History
Shows recent price history for a given crypto asset, using price data fetched from [CoinGecko](https://www.coingecko.com/)
@ -163,6 +173,35 @@ Shows recent price history for a given crypto asset, using price data fetched fr
numDays: 7
```
---
### RSS Feed
Display news and updates from any RSS-enabled service.
<p align="center"><img width="600" src="https://i.ibb.co/N9mvLh4/rss-feed.png" /></p>
##### Options
**Field** | **Type** | **Required** | **Description**
--- | --- | --- | ---
**`rssUrl`** | `string` | Required | The URL location of your RSS feed
**`apiKey`** | `string` | _Optional_ | An API key for [rss2json](https://rss2json.com/). It's free, and will allow you to make 10,000 requests per day, you can sign up [here](https://rss2json.com/sign-up)
**`limit`** | `number` | _Optional_ | The number of posts to return. If you haven't specified an API key, this will be limited to 10
**`orderBy`** | `string` | _Optional_ | How results should be sorted. Can be either `pubDate`, `author` or `title`. Defaults to `pubDate`
**`orderDirection`** | `string` | _Optional_ | Order direction of feed items to return. Can be either `asc` or `desc`. Defaults to `desc`
##### Example
```yaml
- type: rss-feed
options:
rssUrl: https://www.schneier.com/blog/atom.xml
apiKey: xxxx
```
---
### XKCD Comics
Have a laugh with the daily comic from [XKCD](https://xkcd.com/). A classic webcomic website covering everything from Linux, math, romance, science and language.
@ -183,6 +222,8 @@ Have a laugh with the daily comic from [XKCD](https://xkcd.com/). A classic webc
comic: latest
```
---
### TFL Status
Shows real-time tube status of the London Underground. All options are optional.
@ -214,6 +255,8 @@ Shows real-time tube status of the London Underground. All options are optional.
- Central
```
---
### Exchange Rates
Display current FX rates in your native currency
@ -242,6 +285,8 @@ Display current FX rates in your native currency
- KPW
```
---
### Stock Price History
Shows recent price history for a given publicly-traded stock or share
@ -265,6 +310,8 @@ Shows recent price history for a given publicly-traded stock or share
apiKey: PGUWSWD6CZTXMT8N
```
---
### Joke
Renders a programming or generic joke. Data is fetched from the [JokesAPI](https://github.com/Sv443/JokeAPI) by @Sv443
@ -289,6 +336,8 @@ Renders a programming or generic joke. Data is fetched from the [JokesAPI](https
category: Programming
```
---
### Flight Data
Displays airport departure and arrival flights, using data from [AeroDataBox](https://www.aerodatabox.com/). Useful if you live near an airport and often wonder where the flight overhead is going to. Hover over a row for more flight data.
@ -331,6 +380,16 @@ Embed any webpage into your dashboard as a widget.
--- | --- | --- | ---
**`url`** | `string` | Required | The URL to the webpage to embed
##### Example
```yaml
- type: iframe
options:
url: https://fiatleak.com/
```
---
### HTML Embedded Widget
Many websites and apps provide their own embeddable widgets. These can be used with Dashy using the Embed widget, which lets you dynamically embed and HTML, CSS or JavaScript contents.

View File

@ -0,0 +1,237 @@
<template>
<div class="rss-wrapper">
<!-- Feed Meta Info -->
<a class="meta-container" v-if="meta" :href="meta.link" :title="meta.description">
<img class="feed-icon" :src="meta.image" v-if="meta.image" />
<div class="feed-text">
<p class="feed-title">{{ meta.title }}</p>
<p class="feed-author" v-if="meta.author">By {{ meta.author }}</p>
</div>
</a>
<!-- Feed Content -->
<div class="post-wrapper" v-if="posts">
<div class="post-row" v-for="(post, indx) in posts" :key="indx">
<a class="post-top" :href="post.link">
<img class="post-img" :src="post.image" v-if="post.image">
<div class="post-title-wrap">
<p class="post-title">{{ post.title }}</p>
<p class="post-date">
{{ post.date | formatDate }} {{ post.author | formatAuthor }}
</p>
</div>
</a>
<div class="post-body" v-html="post.description"></div>
<a class="continue-reading-btn" :href="post.link">Continue Reading</a>
</div>
</div>
<!-- End Feed Content -->
</div>
</template>
<script>
import axios from 'axios';
import WidgetMixin from '@/mixins/WidgetMixin';
import { widgetApiEndpoints } from '@/utils/defaults';
export default {
mixins: [WidgetMixin],
components: {},
data() {
return {
meta: null,
posts: null,
};
},
mounted() {
this.fetchData();
},
computed: {
/* The URL to users atom-format RSS feed */
rssUrl() {
if (!this.options.rssUrl) this.error('Missing feed URL');
return encodeURIComponent(this.options.rssUrl || '');
},
apiKey() {
return this.options.apiKey;
},
limit() {
const usersChoice = this.options.limit;
if (usersChoice && !Number.isNaN(usersChoice)) return usersChoice;
return 10;
},
orderBy() {
const usersChoice = this.options.orderBy;
const options = ['title', 'pubDate', 'author'];
if (usersChoice && options.includes(usersChoice)) return usersChoice;
return 'pubDate';
},
orderDirection() {
const usersChoice = this.options.orderBy;
if (usersChoice && (usersChoice === 'desc' || usersChoice === 'asc')) return usersChoice;
return 'desc';
},
endpoint() {
const apiKey = this.apiKey ? `&api_key=${this.apiKey}` : '';
const limit = this.limit && this.apiKey ? `&count=${this.limit}` : '';
const orderBy = this.orderBy && this.apiKey ? `&order_by=${this.orderBy}` : '';
const direction = this.orderDirection ? `&order_dir=${this.orderDirection}` : '';
return `${widgetApiEndpoints.rssToJson}?rss_url=${this.rssUrl}`
+ `${apiKey}${limit}${orderBy}${direction}`;
},
},
filters: {
formatDate(timestamp) {
const localFormat = navigator.language;
const dateFormat = { weekday: 'short', day: 'numeric', month: 'short' };
const date = new Date(timestamp).toLocaleDateString(localFormat, dateFormat);
return date;
},
formatAuthor(author) {
return author ? `by ${author}` : '';
},
},
methods: {
/* Extends mixin, and updates data. Called by parent component */
update() {
this.startLoading();
this.fetchData();
},
/* Make GET request to Rss2Json */
fetchData() {
axios.get(this.endpoint)
.then((response) => {
this.processData(response.data);
})
.catch((error) => {
this.error('Unable to RSS feed', error);
})
.finally(() => {
this.finishLoading();
});
},
/* Assign data variables to the returned data */
processData(data) {
const { feed, items } = data;
this.meta = {
title: feed.title,
link: feed.link,
author: feed.author,
description: feed.description,
image: feed.image,
};
const posts = [];
items.forEach((post) => {
posts.push({
title: post.title,
description: post.description,
image: post.thumbnail,
author: post.author,
date: post.pubDate,
link: post.link,
});
});
this.posts = posts;
},
},
};
</script>
<style scoped lang="scss">
.rss-wrapper {
.meta-container {
display: flex;
align-items: center;
text-decoration: none;
margin: 0.25rem 0 0.5rem 0;
p.feed-title {
margin: 0;
font-size: 1.2rem;
font-weight: bold;
color: var(--widget-text-color);
}
p.feed-author {
margin: 0;
font-size: 0.8rem;
opacity: var(--dimming-factor);
color: var(--widget-text-color);
}
img.feed-icon {
border-radius: var(--curve-factor);
width: 2rem;
height: 2rem;
margin-right: 0.5rem;
}
}
.post-row {
border-top: 1px dashed var(--widget-text-color);
padding: 0.5rem 0 0.25rem 0;
.post-top {
display: flex;
align-items: center;
text-decoration: none;
.post-title-wrap {}
p.post-title {
margin: 0;
font-size: 1rem;
font-weight: bold;
color: var(--widget-text-color);
}
p.post-date {
font-size: 0.8rem;
margin: 0;
opacity: var(--dimming-factor);
color: var(--widget-text-color);
}
img.post-img {
border-radius: var(--curve-factor);
width: 2rem;
height: 2rem;
margin-right: 0.5rem;
}
}
.post-body {
font-size: 0.85rem;
color: var(--widget-text-color);
max-height: 400px;
overflow: hidden;
::v-deep p {
margin: 0.5rem 0;
}
::v-deep img {
max-width: 80%;
display: flex;
margin: 0 auto;
border-radius: var(--curve-factor);
}
::v-deep a {
color: var(--widget-text-color);
}
::v-deep svg path {
fill: var(--widget-text-color);
}
::v-deep blockquote {
margin-left: 0.5rem;
padding-left: 0.5rem;
border-left: 4px solid var(--widget-text-color);
}
::v-deep .avatar.avatar-user { display: none; }
}
a.continue-reading-btn {
width: 100%;
display: block;
font-size: 0.9rem;
text-align: right;
margin: 0 0 0.25rem;
padding: 0.1rem 0.25rem;
text-decoration: none;
opacity: var(--dimming-factor);
color: var(--widget-text-color);
&:hover, &:focus {
opacity: 1;
text-decoration: underline;
}
}
}
}
</style>

View File

@ -39,6 +39,13 @@
@error="handleError"
:ref="widgetRef"
/>
<RssFeed
v-else-if="widgetType === 'rss-feed'"
:options="widgetOptions"
@loading="setLoaderState"
@error="handleError"
:ref="widgetRef"
/>
<TflStatus
v-else-if="widgetType === 'tfl-status'"
:options="widgetOptions"
@ -127,6 +134,7 @@ import LoadingAnimation from '@/assets/interface-icons/loader.svg';
import Clock from '@/components/Widgets/Clock.vue';
import Weather from '@/components/Widgets/Weather.vue';
import WeatherForecast from '@/components/Widgets/WeatherForecast.vue';
import RssFeed from '@/components/Widgets/RssFeed.vue';
import TflStatus from '@/components/Widgets/TflStatus.vue';
import CryptoPriceChart from '@/components/Widgets/CryptoPriceChart.vue';
import CryptoWatchList from '@/components/Widgets/CryptoWatchList.vue';
@ -148,6 +156,7 @@ export default {
Clock,
Weather,
WeatherForecast,
RssFeed,
TflStatus,
CryptoPriceChart,
CryptoWatchList,

View File

@ -215,6 +215,7 @@ module.exports = {
stockPriceChart: 'https://www.alphavantage.co/query',
jokes: 'https://v2.jokeapi.dev/joke/',
flights: 'https://aerodatabox.p.rapidapi.com/flights/airports/icao/',
rssToJson: 'https://api.rss2json.com/v1/api.json',
},
/* URLs for web search engines */
searchEngineUrls: {