From 5b221fb72d6b915a56df4c17ecc747bb6f15dee8 Mon Sep 17 00:00:00 2001
From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>
Date: Wed, 9 Apr 2025 14:21:50 +1000
Subject: feat: news headlines for alerts pane
Also handle news api errors
Also config num pages
---
scss/sidebar.scss | 70 +++++++++++-
src/config/defaults.ts | 1 +
src/config/types.ts | 1 +
src/modules/sidebar/alerts.tsx | 3 +
src/modules/sidebar/modules/headlines.tsx | 172 ++++++++++++++++++++++++++++++
src/modules/sidebar/modules/news.tsx | 2 +-
src/services/news.ts | 35 ++++--
7 files changed, 275 insertions(+), 9 deletions(-)
create mode 100644 src/modules/sidebar/modules/headlines.tsx
diff --git a/scss/sidebar.scss b/scss/sidebar.scss
index 9f5bd39..1fea682 100644
--- a/scss/sidebar.scss
+++ b/scss/sidebar.scss
@@ -752,7 +752,7 @@
min-height: lib.s(200);
.expanded {
- min-height: lib.s(400);
+ min-height: lib.s(500);
}
.empty {
@@ -792,6 +792,74 @@
}
}
+ .headlines {
+ min-height: lib.s(200);
+
+ .expanded {
+ min-height: lib.s(500);
+ }
+
+ .empty {
+ margin-top: lib.s(40);
+ }
+
+ .list {
+ @include lib.spacing(10, true);
+ }
+
+ .category {
+ @include lib.rounded(20);
+ @include lib.element-decel;
+
+ background-color: color.change(scheme.$surface0, $alpha: 0.5);
+ padding: lib.s(10) lib.s(15);
+
+ @include lib.spacing(5);
+
+ .icon {
+ font-size: lib.s(28);
+
+ &:not(:last-child) {
+ margin-right: lib.s(12);
+ }
+ }
+
+ .sublabel {
+ font-size: lib.s(14);
+ color: scheme.$subtext0;
+ }
+
+ .body {
+ margin-top: lib.s(10);
+ font-size: lib.s(14);
+
+ @include lib.spacing(8, true);
+ }
+ }
+
+ .article {
+ @include lib.rounded(20);
+ @include lib.element-decel;
+
+ background-color: color.change(scheme.$surface1, $alpha: 0.3);
+ padding: lib.s(10) lib.s(15);
+
+ .article-body {
+ @include font.reading;
+ @include lib.element-decel;
+
+ font-size: lib.s(15);
+ margin-top: lib.s(10);
+ color: scheme.$subtext1;
+ }
+
+ button:hover .article-body,
+ button:focus .article-body {
+ color: color.mix(scheme.$subtext0, scheme.$blue, 60%);
+ }
+ }
+ }
+
.time-date {
padding: lib.s(12) lib.s(8);
font-size: lib.s(48);
diff --git a/src/config/defaults.ts b/src/config/defaults.ts
index d5db290..70a4700 100644
--- a/src/config/defaults.ts
+++ b/src/config/defaults.ts
@@ -171,5 +171,6 @@ export default {
languages: ["en"], // A list of languages codes to filter by
domains: [] as string[], // A list of news domains to pull from
timezone: "", // A timezone to filter by, e.g. "America/New_York"
+ pages: 3, // Number of pages to pull (each page is 10 articles)
},
};
diff --git a/src/config/types.ts b/src/config/types.ts
index 2862612..0e9138d 100644
--- a/src/config/types.ts
+++ b/src/config/types.ts
@@ -100,4 +100,5 @@ export default {
"news.languages": ARR(STR),
"news.domains": ARR(STR),
"news.timezone": STR,
+ "news.pages": NUM,
} as { [k: string]: string | string[] | number[] };
diff --git a/src/modules/sidebar/alerts.tsx b/src/modules/sidebar/alerts.tsx
index 3dd4b5a..b669514 100644
--- a/src/modules/sidebar/alerts.tsx
+++ b/src/modules/sidebar/alerts.tsx
@@ -1,7 +1,10 @@
+import Headlines from "./modules/headlines";
import Notifications from "./modules/notifications";
export default () => (
+
+
);
diff --git a/src/modules/sidebar/modules/headlines.tsx b/src/modules/sidebar/modules/headlines.tsx
new file mode 100644
index 0000000..924e5b8
--- /dev/null
+++ b/src/modules/sidebar/modules/headlines.tsx
@@ -0,0 +1,172 @@
+import News, { type IArticle } from "@/services/news";
+import Palette, { type IPalette } from "@/services/palette";
+import { capitalize } from "@/utils/strings";
+import { setupCustomTooltip } from "@/utils/widgets";
+import { bind, execAsync, Variable } from "astal";
+import { Gtk } from "astal/gtk3";
+
+const fixGoogleNews = (colours: IPalette, title: string, desc: string) => {
+ // Add separator, bold and split at domain (domain is at the end of each headline)
+ const domain = title.split(" - ").at(-1);
+ if (domain) desc = desc.replaceAll(domain, `— ${domain}\n\n`);
+ // Add spaces between sentences
+ desc = desc.replace(/\.([A-Z])/g, ". $1");
+ // Split headlines
+ desc = desc.replace(/(( |\.)[^A-Z][a-z]+)([A-Z])/g, "$1\n\n$3");
+ desc = desc.replace(/( [A-Z]+)([A-Z](?![s])[a-z])/g, "$1\n\n$2");
+ // Add separator and bold domains
+ desc = desc.replace(/ ([a-zA-Z.]+)\n\n/g, ` — $1\n\n`);
+ desc = desc.replace(/ ([a-zA-Z.]+)$/, ` — $1`); // Last domain
+ return desc.trim();
+};
+
+const getCategoryIcon = (category: string) => {
+ if (category === "business") return "monitoring";
+ if (category === "crime") return "speed_camera";
+ if (category === "domestic") return "home";
+ if (category === "education") return "school";
+ if (category === "entertainment") return "tv";
+ if (category === "environment") return "eco";
+ if (category === "food") return "restaurant";
+ if (category === "health") return "health_and_safety";
+ if (category === "lifestyle") return "digital_wellbeing";
+ if (category === "politics") return "account_balance";
+ if (category === "science") return "science";
+ if (category === "sports") return "sports_basketball";
+ if (category === "technology") return "account_tree";
+ if (category === "top") return "breaking_news";
+ if (category === "tourism") return "travel";
+ if (category === "world") return "public";
+ return "newsmode";
+};
+
+const Article = ({ title, description, creator, pubDate, source_name, link }: IArticle) => {
+ const expanded = Variable(false);
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+const Category = ({ title, articles }: { title: string; articles: IArticle[] }) => {
+ const expanded = Variable(false);
+
+ return (
+
+
+
+
+ {articles.map(a => (
+
+ ))}
+
+
+
+ );
+};
+
+const List = () => (
+
+ {bind(News.get_default(), "categories").as(c =>
+ Object.entries(c).map(([k, v]) => )
+ )}
+
+);
+
+const NoNews = () => (
+
+
+
+
+
+
+);
+
+export default () => (
+
+
+
+
+
+ (a.length > 0 ? "list" : "empty"))}
+ >
+
+ (a.length > 0 ? "expanded" : ""))}
+ hscroll={Gtk.PolicyType.NEVER}
+ name="list"
+ >
+
+
+
+
+);
diff --git a/src/modules/sidebar/modules/news.tsx b/src/modules/sidebar/modules/news.tsx
index aba37c7..1ab2383 100644
--- a/src/modules/sidebar/modules/news.tsx
+++ b/src/modules/sidebar/modules/news.tsx
@@ -69,7 +69,7 @@ const List = () => (
const NoNews = () => (
-
+
diff --git a/src/services/news.ts b/src/services/news.ts
index 5845aff..3d56186 100644
--- a/src/services/news.ts
+++ b/src/services/news.ts
@@ -2,13 +2,15 @@ import { notify } from "@/utils/system";
import { execAsync, GLib, GObject, property, readFileAsync, register, writeFileAsync } from "astal";
import { news as config } from "config";
-export interface Article {
+export interface IArticle {
title: string;
link: string;
- keywords: string;
- creator: string;
- description: string;
+ keywords: string[] | null;
+ creator: string[] | null;
+ description: string | null;
pubDate: string;
+ source_name: string;
+ category: string[];
}
@register({ GTypeName: "News" })
@@ -24,7 +26,8 @@ export default class News extends GObject.Object {
#notified = false;
#loading: boolean = false;
- #articles: Article[] = [];
+ #articles: IArticle[] = [];
+ #categories: { [category: string]: IArticle[] } = {};
@property(Boolean)
get loading() {
@@ -36,6 +39,11 @@ export default class News extends GObject.Object {
return this.#articles;
}
+ @property(Object)
+ get categories() {
+ return this.#categories;
+ }
+
async getNews() {
if (!config.apiKey.get()) {
if (!this.#notified) {
@@ -77,11 +85,14 @@ export default class News extends GObject.Object {
const url = `https://newsdata.io/api/1/latest?apikey=${config.apiKey.get()}&${args}`;
try {
const res = JSON.parse(await execAsync(["curl", url]));
- this.#articles = res.results;
+ if (res.status !== "success") throw new Error(`Failed to get news: ${res.results.message}`);
+
+ this.#articles = [...res.results];
let page = res.nextPage;
- for (let i = 0; i < 3; i++) {
+ for (let i = 1; i < config.pages.get(); i++) {
const res = JSON.parse(await execAsync(["curl", `${url}&page=${page}`]));
+ if (res.status !== "success") throw new Error(`Failed to get news: ${res.results.message}`);
this.#articles.push(...res.results);
page = res.nextPage;
}
@@ -95,6 +106,15 @@ export default class News extends GObject.Object {
}
this.notify("articles");
+ this.#categories = {};
+ for (const article of this.#articles) {
+ for (const category of article.category) {
+ if (!this.#categories.hasOwnProperty(category)) this.#categories[category] = [];
+ this.#categories[category].push(article);
+ }
+ }
+ this.notify("categories");
+
this.#loading = false;
this.notify("loading");
}
@@ -109,5 +129,6 @@ export default class News extends GObject.Object {
config.languages.subscribe(() => this.getNews().catch(console.error));
config.domains.subscribe(() => this.getNews().catch(console.error));
config.timezone.subscribe(() => this.getNews().catch(console.error));
+ config.pages.subscribe(() => this.getNews().catch(console.error));
}
}
--
cgit v1.2.3-freya