diff --git a/src/App.vue b/src/App.vue
index c02d2271..93693dd3 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -55,7 +55,7 @@ export default {
});
if ("indexedDB" in window) {
- const request = indexedDB.open("piped-db", 3);
+ const request = indexedDB.open("piped-db", 4);
request.onupgradeneeded = ev => {
const db = request.result;
console.log("Upgrading object store.");
@@ -73,6 +73,10 @@ export default {
store.createIndex("playlist_id_idx", "playlistId", { unique: true });
store.createIndex("id_idx", "id", { unique: true, autoIncrement: true });
}
+ if (!db.objectStoreNames.contains("channel_groups")) {
+ const store = db.createObjectStore("channel_groups", { keyPath: "groupName" });
+ store.createIndex("groupName", "groupName", { unique: true });
+ }
};
request.onsuccess = e => {
window.db = e.target.result;
diff --git a/src/components/DefaultValueCheckbox.vue b/src/components/DefaultValueCheckbox.vue
new file mode 100644
index 00000000..8378f75f
--- /dev/null
+++ b/src/components/DefaultValueCheckbox.vue
@@ -0,0 +1,22 @@
+// Wrapper around v-model to allow default values without requiring to use a v-model inside the calling component
+
+
+
+
+
+
diff --git a/src/components/FeedPage.vue b/src/components/FeedPage.vue
index 36749650..41029e07 100644
--- a/src/components/FeedPage.vue
+++ b/src/components/FeedPage.vue
@@ -18,6 +18,19 @@
+
+
+
videos.sort(order)" />
@@ -25,7 +38,7 @@
-
+
@@ -50,6 +63,8 @@ export default {
videos: [],
availableFilters: ["all", "shorts", "videos"],
selectedFilter: "all",
+ selectedGroupName: "",
+ channelGroups: [],
};
},
computed: {
@@ -57,6 +72,12 @@ export default {
if (_this.authenticated) return _this.authApiUrl() + "/feed/rss?authToken=" + _this.getAuthToken();
else return _this.authApiUrl() + "/feed/unauthenticated/rss?channels=" + _this.getUnauthenticatedChannels();
},
+ filteredVideos(_this) {
+ const selectedGroup = _this.channelGroups.filter(group => group.groupName == _this.selectedGroupName);
+ return _this.selectedGroupName == ""
+ ? _this.videos
+ : _this.videos.filter(video => selectedGroup[0].channels.includes(video.uploaderUrl.substr(-11)));
+ },
},
mounted() {
this.fetchFeed().then(videos => {
@@ -66,6 +87,20 @@ export default {
});
this.selectedFilter = this.getPreferenceString("feedFilter") ?? "all";
+
+ if (!window.db) return;
+
+ const cursor = this.getChannelGroupsCursor();
+ cursor.onsuccess = e => {
+ const cursor = e.target.result;
+ if (cursor) {
+ const group = cursor.value;
+ this.channelGroups = this.channelGroups.concat({
+ groupName: group.groupName,
+ channels: JSON.parse(group.channels),
+ });
+ }
+ };
},
activated() {
document.title = this.$t("titles.feed") + " - Piped";
diff --git a/src/components/SubscriptionsPage.vue b/src/components/SubscriptionsPage.vue
index 63c44d10..6f0210f9 100644
--- a/src/components/SubscriptionsPage.vue
+++ b/src/components/SubscriptionsPage.vue
@@ -15,12 +15,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ selectedGroup.groupName }}
+
+
+
+ {{ subscription.name }}
+
+
+
+
+
+
+
+
diff --git a/src/locales/en.json b/src/locales/en.json
index 4ac9def8..bae2c35b 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -13,7 +13,8 @@
"player": "Player",
"livestreams": "Livestreams",
"channels": "Channels",
- "bookmarks": "Bookmarks"
+ "bookmarks": "Bookmarks",
+ "channel_groups": "Channel groups"
},
"player": {
"watch_on": "Watch on {0}"
@@ -131,7 +132,9 @@
"playlist_bookmarked": "Bookmarked",
"dismiss": "Dismiss",
"show_more": "Show more",
- "show_less": "Show less"
+ "show_less": "Show less",
+ "create_group": "Create group",
+ "group_name": "Group name"
},
"comment": {
"pinned_by": "Pinned by {author}",
diff --git a/src/main.js b/src/main.js
index 62fd86d8..71b916d4 100644
--- a/src/main.js
+++ b/src/main.js
@@ -21,6 +21,7 @@ import {
faServer,
faDonate,
faBookmark,
+ faEdit,
} from "@fortawesome/free-solid-svg-icons";
import { faGithub, faBitcoin, faYoutube } from "@fortawesome/free-brands-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
@@ -48,6 +49,7 @@ library.add(
faServer,
faDonate,
faBookmark,
+ faEdit,
);
import router from "@/router/router.js";
@@ -271,6 +273,25 @@ const mixin = {
)
.replaceAll("\n", "
");
},
+ getChannelGroupsCursor() {
+ if (!window.db) return;
+ var tx = window.db.transaction("channel_groups", "readonly");
+ var store = tx.objectStore("channel_groups");
+ return store.index("groupName").openCursor();
+ },
+ createOrUpdateChannelGroup(group) {
+ var tx = window.db.transaction("channel_groups", "readwrite");
+ var store = tx.objectStore("channel_groups");
+ store.put({
+ groupName: group.groupName,
+ channels: JSON.stringify(group.channels),
+ });
+ },
+ deleteChannelGroup(groupName) {
+ var tx = window.db.transaction("channel_groups", "readwrite");
+ var store = tx.objectStore("channel_groups");
+ store.delete(groupName);
+ },
},
computed: {
authenticated(_this) {