Actually get the feeds

master
Hamcha 2019-07-16 16:43:29 +02:00
parent 3d7f0bba10
commit 8257347a09
11 changed files with 160 additions and 18 deletions

View File

@ -13,6 +13,7 @@
"postcss-nested": "^4.1.2",
"postcss-smart-import": "^0.7.6",
"register-service-worker": "^1.6.2",
"rss-parser": "^3.7.2",
"tailwindcss": "^1.0.4",
"vue": "^2.6.10",
"vue-class-component": "^7.0.2",
@ -22,6 +23,7 @@
"vuex-class": "^0.3.2"
},
"devDependencies": {
"@types/xml2js": "^0.4.4",
"@vue/cli-plugin-babel": "^3.8.0",
"@vue/cli-plugin-eslint": "^3.8.0",
"@vue/cli-plugin-pwa": "^3.8.0",

View File

@ -0,0 +1,22 @@
<template>
<div class="card">{{ event.data.guid }}</div>
</template>
<style lang="postcss" scoped>
.card {
@apply p-2;
}
</style>
<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import { FeedEvent } from "@/store/types";
@Component({
components: {}
})
export default class EventCard extends Vue {
@Prop()
private event!: FeedEvent;
}
</script>

View File

@ -0,0 +1,27 @@
<template>
<div class="list">
<EventCard v-for="event in events" :key="event.data.guid" :event="event" />
</div>
</template>
<style lang="postcss" scoped>
.list {
@apply p-2;
}
</style>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { Getter } from "vuex-class";
import EventCard from "./EventCard.vue";
import { FeedEvent } from "@/store/types";
@Component({
components: {
EventCard
}
})
export default class ListView extends Vue {
@Getter("events") private events!: FeedEvent[];
}
</script>

View File

@ -1,9 +1,12 @@
import { ActionTree } from "vuex";
import { AppState } from "./types";
import { AppState, EventFeed, FeedEvent } from "./types";
import { toEvent } from "./feed";
import Parser from "rss-parser";
import { mustArray } from "@/utils";
const actions: ActionTree<AppState, AppState> = {
// Add one or more sources
addSources({ commit, dispatch }, sources: EventSource[]) {
addSources({ commit, dispatch }, sources: EventFeed[]) {
// Commit to state immediately
commit("appendSources", sources);
// Start fetching from each new source
@ -12,7 +15,27 @@ const actions: ActionTree<AppState, AppState> = {
// Fetch events from a source url
async fetchFromSource({ commit }, url: string) {
//TODO
try {
const parser = new Parser();
const feed = await parser.parseURL(url);
// Parse events
const events: FeedEvent[] = mustArray(feed.items)
.map(toEvent)
.map(x => ({
source: url,
data: x
}));
commit("setSourceStatus", {
url,
info: { title: feed.title, description: feed.description },
status: { fetched: true, events }
});
} catch (e) {
commit("setSourceStatus", {
url,
status: { fetched: true, error: e }
});
}
}
};

View File

@ -0,0 +1,8 @@
import { FeedEvent, FeedEventData } from "./types";
import { Item } from "rss-parser";
export function toEvent(item: Item): FeedEventData {
return {
guid: item.guid || item.link || item.title || Math.random().toString(32)
};
}

View File

@ -1,15 +1,15 @@
import { GetterTree } from "vuex";
import { AppState, Event, EventSource } from "./types";
import { AppState, FeedEvent, EventFeed } from "./types";
const getters: GetterTree<AppState, AppState> = {
events(state): Event[] {
events(state): FeedEvent[] {
return Object.values(state.status)
.filter(x => x.fetched && x.error == null)
.map(x => x.events)
.flat();
},
sources(state): EventSource[] {
sources(state): EventFeed[] {
return state.sources;
}
};

View File

@ -1,8 +1,8 @@
import { MutationTree } from "vuex";
import { AppState, FetchStatus, EventSource } from "./types";
import { AppState, FetchStatus, EventFeed, FeedInfo } from "./types";
const mutations: MutationTree<AppState> = {
appendSources(state, payload: EventSource[]) {
appendSources(state, payload: EventFeed[]) {
// Add sources to list
state.sources = state.sources.concat(payload);
@ -12,8 +12,17 @@ const mutations: MutationTree<AppState> = {
});
},
setSourceStatus(state, payload: { url: string; status: FetchStatus }) {
setSourceStatus(
state,
payload: { url: string; info?: FeedInfo; status: FetchStatus }
) {
state.status[payload.url] = payload.status;
if (payload.info) {
let source = state.sources.find(x => x.url == payload.url);
if (source) {
source.info = payload.info;
}
}
}
};

View File

@ -1,21 +1,26 @@
import { Dict } from "@/types";
export interface AppState {
sources: EventSource[];
sources: EventFeed[];
status: Dict<FetchStatus>;
}
export interface EventSource {
export interface EventFeed {
url: string;
info?: FeedInfo;
}
export interface Event {
export interface FeedInfo {
title: string;
}
export interface FeedEvent {
source: string; // Source URL
data: EventData;
data: FeedEventData;
}
export interface EventData {
//TODO
export interface FeedEventData {
guid: string;
}
export interface FetchStatus {

13
src/utils.ts 100644
View File

@ -0,0 +1,13 @@
export function must<T>(val?: T): T {
if (val) {
return val;
}
throw "must assertion failed";
}
export function mustArray<T>(val?: T[]): T[] {
if (val) {
return val;
}
return [];
}

View File

@ -1,12 +1,17 @@
<template>
<div class="home"></div>
<div class="home">
<ListView />
</div>
</template>
<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import ListView from "@/components/EventList/ListView.vue";
@Component({
components: {}
components: {
ListView
}
})
export default class Home extends Vue {}
</script>

View File

@ -774,6 +774,13 @@
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.9.tgz#a67287861c928ebf4159a908d1fb1a2a34d4097a"
integrity sha512-p8zp5xqkly3g4cCmo2mKOHI9+Z/kObmDj0BmjbDDJQlgDTiEGTbm17MEwTAusV6XceCy+bNw9q/ZHXHyKo3zkg==
"@types/xml2js@^0.4.4":
version "0.4.4"
resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.4.tgz#2093d94359a201806d997dccefc80153db311c66"
integrity sha512-O6Xgai01b9PB3IGA0lRIp1Ex3JBcxGDhdO0n3NIIpCyDOAjxcIGQFmkvgJpP8anTrthxOUQjBfLdRRi0Zn/TXA==
dependencies:
"@types/node" "*"
"@typescript-eslint/eslint-plugin@^1.1.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.11.0.tgz#870f752c520db04db6d3668af7479026a6f2fb9a"
@ -7149,6 +7156,14 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
rss-parser@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/rss-parser/-/rss-parser-3.7.2.tgz#9f5b7d4944d4f7a190b469e31a8353aedb17c052"
integrity sha512-kx0VIFelgwBk5qA4n32U6cx40anAU7TwlRXjyxLDFgMlg8/UcJ64x+Hj5oRX1Kjos+OeFGOmnd5YXH5ES+bmzg==
dependencies:
entities "^1.1.1"
xml2js "^0.4.19"
run-async@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
@ -7199,7 +7214,7 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sax@^1.2.4, sax@~1.2.4:
sax@>=0.6.0, sax@^1.2.4, sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
@ -8834,6 +8849,19 @@ ws@^6.0.0:
dependencies:
async-limiter "~1.0.0"
xml2js@^0.4.19:
version "0.4.19"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
dependencies:
sax ">=0.6.0"
xmlbuilder "~9.0.1"
xmlbuilder@~9.0.1:
version "9.0.7"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
xtend@^4.0.0, xtend@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"