Skip to content

Commit

Permalink
replace waypoints with JavaScript native code
Browse files Browse the repository at this point in the history
  • Loading branch information
pietheinstrengholt committed Jul 3, 2024
1 parent 258cd6e commit e5dc068
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 85 deletions.
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@
Copyright (c) 2024 Piethein Strengholt, [email protected]

### Background
RSSMonster is an easy to use web-based RSS aggregator, created as an alternative for Google Reader. RSSMonster features a lightweight fluid responsive design. It is written in JavaScript: Vue.js 3 for the frontend and Express for the backend. It uses Bootstrap for making the design responsive.

RSSMonster tries to mimic the behaviour of Google Reader. It marks items when you start scrolling. It knows what content is hot. It features progressive web app support, drag and drop support for managing feeds, dark mode, and more! RSSMonster is compatible with the Fever API. Feel free to add any contributions or new features.
RSSMonster is a user-friendly, web-based RSS aggregator developed as an alternative to one of my favorite tools, Google Reader. Motivated by the need to replace Google Reader for tracking RSS feeds, RSSMonster aims to replicate its functionality while offering modern enhancements.

![Screenshot](client/src/assets/screenshots/screenshot01.png)

### Features
- Lightweight and Responsive Design: Built using Vue.js 3 for the frontend and Express for the backend, RSSMonster leverages Bootstrap to ensure a fluid and responsive user experience.
- Google Reader-like Behavior: Mimics the behavior of Google Reader, such as marking items as read when you start scrolling and identifying trending content.
- Progressive Web App Support: Install RSSMonster as a PWA for a seamless experience across devices.
- Drag and Drop: Easily manage your feeds with intuitive drag and drop functionality.
- Dark Mode: Switch to dark mode for a comfortable reading experience in low-light environments.
- Fever API Compatibility: Fully compatible with the Fever API, allowing for integration with other RSS tools.

### Prerequisites
* NodeJS 16.x or higher
* Git
Expand Down Expand Up @@ -67,12 +73,14 @@ Any username and password will work.

![Screenshot Fever](client/src/assets/screenshots/fever.png)

### Contributions
I welcome contributions and new features from the community. Feel free to fork the repository and submit pull requests.

#### Credits
The following scripts and plug-ins are used within RSSMonster

* NodeJS https://nodejs.org/en/
* Twitter bootstrap: https://twitter.github.io/bootstrap/
* Feedparser: https://github.com/danmactough/node-feedparser/
* VueJS: https://vuejs.org/
* Vue infinite scrolling: https://github.com/PeachScript/vue-infinite-loading
* Waypoints: https://github.com/imakewebthings/waypoints
* Vue infinite scrolling: https://github.com/PeachScript/vue-infinite-loading
8 changes: 1 addition & 7 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
"register-service-worker": "^1.7.2",
"vue": "^3.4.31",
"vue-infinite-loading": "github:nvitius/vue-infinite-loading#next",
"vuedraggable": "^4.1.0",
"waypoints": "^4.0.1"
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.5",
Expand Down
123 changes: 52 additions & 71 deletions client/src/components/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ div.infinite-loading-container {

<script>
import Article from "./Article.vue";
import "waypoints/lib/noframework.waypoints.js";
import InfiniteLoading from "vue-infinite-loading";
import axios from 'axios';
Expand All @@ -91,8 +90,6 @@ export default {
container: [],
//is used to keep track of which articles are already flagged as read
pool: [],
//is used to keep track of which articles are already set with the waypoint method
waypointPool: [],
firstLoad: false,
prevScroll: 0,
prevDirection: "down"
Expand Down Expand Up @@ -185,6 +182,25 @@ export default {
}
}
//calculate the scroll position and mark items as read when scrolling down
if ((direction === "down") && (this.store.currentSelection.status === "unread")) {
if (document.getElementById('articles')) {
//set initial screen height to the current scroll position
var screenHeight = Math.ceil(document.documentElement.scrollTop);
//loop through all articles and check if they are in the viewport
for (const child of document.getElementById('articles').children) {
//check if the article is still in the viewport
screenHeight = screenHeight - document.getElementById(child.id).offsetHeight;
//if the article is no longer in the viewport, mark it as read
if (screenHeight > 0) {
if (!this.pool.includes(child.id)) {
this.markArticleRead(child.id);
}
}
}
}
}
//overwrite the prevScroll and prevDirection after doing the comparison
this.prevScroll = curScroll;
this.prevDirection = direction;
Expand Down Expand Up @@ -216,20 +232,6 @@ export default {
this.articles = this.articles.concat(response.data);
//set state to loaded
$state.loaded();
//add waypoint to every article
setTimeout(() => {
for (var key in this.articles) {
var article = this.articles[key];
//only add triggers if the status is unread
if (article.status == "unread") {
//make sure only one waypoint per article is set, check the waypointPool for this
if (!this.waypointPool.includes(article.id)) {
this.waypointCreate(article.id);
this.waypointPool.push(article.id);
}
}
}
}, 50);
//if the returned set has a length of less than fetchCount set the state to complete
if (response.data.length < this.fetchCount) {
$state.complete();
Expand Down Expand Up @@ -263,63 +265,42 @@ export default {
this.container = [];
this.distance = 0;
},
waypointCreate(article) {
//add additional check to fix error: https://stackoverflow.com/questions/40252534/waypoints-no-element-option-passed-to-waypoint-constructor
if (document.getElementById(article)) {
// eslint-disable-next-line
const waypoint = new Waypoint({
element: document.getElementById(article),
offset: -150,
//use the ES2015 arrow syntax to avoid error Cannot read property 'post' of undefined
handler: direction => {
if (direction == "down") {
//make ajax request to change bookmark status
this.markArticleRead(article);
//destroy after the article has been marked as read
waypoint.destroy();
}
}
});
}
},
async markArticleRead(article) {
if (this.store.currentSelection.status === "unread") {
//make ajax request to change read status
await axios.post(import.meta.env.VITE_VUE_APP_HOSTNAME + "/api/manager/marktoread/" + article).then(
response => {
if (!this.pool.includes(article)) {
//push id to the pool
this.pool.push(article);
async markArticleRead(articleId) {
//make ajax request to change read status
await axios.post(import.meta.env.VITE_VUE_APP_HOSTNAME + "/api/manager/marktoread/" + articleId).then(
response => {
if (!this.pool.includes(articleId)) {
//push articleId to the pool
this.pool.push(articleId);
//decrease the unread count
var categoryIndex = this.store.categories.findIndex(
category => category.id === response.data.feed.categoryId
);
//avoid having any negative numbers
if (this.store.categories[categoryIndex].unreadCount != 0) {
this.store.categories[categoryIndex].unreadCount = this.store.categories[categoryIndex].unreadCount - 1;
this.store.categories[categoryIndex].readCount = this.store.categories[categoryIndex].readCount + 1;
}
var feedIndex = this.store.categories[categoryIndex].feeds.findIndex(feed => feed.id === response.data.feedId);
//avoid having any negative numbers
if (this.store.categories[categoryIndex].feeds[feedIndex].unreadCount != 0) {
this.store.categories[categoryIndex].feeds[feedIndex].unreadCount = this.store.categories[categoryIndex].feeds[feedIndex].unreadCount - 1;
this.store.categories[categoryIndex].feeds[feedIndex].readCount = this.store.categories[categoryIndex].feeds[feedIndex].readCount + 1;
}
//also increase total count
if (this.store.unreadCount != 0) {
this.store.readCount = this.store.readCount + 1;
this.store.unreadCount = this.store.unreadCount - 1;
}
//decrease the unread count
var categoryIndex = this.store.categories.findIndex(
category => category.id === response.data.feed.categoryId
);
//avoid having any negative numbers
if (this.store.categories[categoryIndex].unreadCount != 0) {
this.store.categories[categoryIndex].unreadCount = this.store.categories[categoryIndex].unreadCount - 1;
this.store.categories[categoryIndex].readCount = this.store.categories[categoryIndex].readCount + 1;
}
var feedIndex = this.store.categories[categoryIndex].feeds.findIndex(feed => feed.id === response.data.feedId);
//avoid having any negative numbers
if (this.store.categories[categoryIndex].feeds[feedIndex].unreadCount != 0) {
this.store.categories[categoryIndex].feeds[feedIndex].unreadCount = this.store.categories[categoryIndex].feeds[feedIndex].unreadCount - 1;
this.store.categories[categoryIndex].feeds[feedIndex].readCount = this.store.categories[categoryIndex].feeds[feedIndex].readCount + 1;
}
//also increase total count
if (this.store.unreadCount != 0) {
this.store.readCount = this.store.readCount + 1;
this.store.unreadCount = this.store.unreadCount - 1;
}
},
response => {
/* eslint-disable no-console */
console.log("oops something went wrong", response);
/* eslint-enable no-console */
}
);
}
},
response => {
/* eslint-disable no-console */
console.log("oops something went wrong", response);
/* eslint-enable no-console */
}
);
}
}
};
Expand Down

0 comments on commit e5dc068

Please sign in to comment.