grok-digest 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-THIRD-PARTY +24 -0
- package/README.md +12 -18
- package/dist/components/BookmarkCard.astro +82 -42
- package/dist/components/{BookmarkFilters.d.ts → BookmarkControls.d.ts} +1 -1
- package/dist/components/BookmarkControls.js +226 -0
- package/dist/components/ui/card.d.ts +1 -4
- package/dist/components/ui/card.js +1 -10
- package/dist/index.js +12 -1
- package/dist/lib/bookmarks.d.ts +12 -0
- package/dist/lib/bookmarks.js +7 -3
- package/dist/lib/feed.js +3 -3
- package/dist/lib/filter.d.ts +7 -6
- package/dist/lib/filter.js +10 -1
- package/dist/lib/load-bookmarks.d.ts +2 -0
- package/dist/lib/review.d.ts +17 -0
- package/dist/lib/review.js +50 -0
- package/dist/lib/search.d.ts +9 -3
- package/dist/lib/search.js +11 -4
- package/dist/pages/index.astro +11 -3
- package/dist/styles/global.css +1 -1
- package/dist/styles/shadcn-tailwind.css +529 -0
- package/package.json +3 -4
- package/CHANGELOG.md +0 -10
- package/dist/components/BookmarkFilters.js +0 -32
package/dist/lib/feed.js
CHANGED
|
@@ -25,13 +25,13 @@ export function topicFromParam(value) {
|
|
|
25
25
|
return value;
|
|
26
26
|
}
|
|
27
27
|
function feedItem(bookmark, page) {
|
|
28
|
-
const { author, summary, text, keyPoints, topic, url,
|
|
28
|
+
const { author, tldr, summary, text, keyPoints, topic, url, savedAt, id } = bookmark;
|
|
29
29
|
const link = new URL(page.href);
|
|
30
30
|
link.hash = `b-${id}`;
|
|
31
31
|
const points = keyPoints.map((point) => `<li>${escapeHtml(point)}</li>`).join("");
|
|
32
32
|
const quote = text === "" ? "" : `<blockquote><p>${escapeHtml(text)}</p></blockquote>`;
|
|
33
33
|
return {
|
|
34
|
-
title:
|
|
34
|
+
title: tldr,
|
|
35
35
|
link: link.href,
|
|
36
36
|
description: [`@${author.handle}`, summary, text].filter((part) => part !== "").join("\n\n"),
|
|
37
37
|
content: [
|
|
@@ -43,7 +43,7 @@ function feedItem(bookmark, page) {
|
|
|
43
43
|
]
|
|
44
44
|
.filter((part) => part !== "")
|
|
45
45
|
.join(""),
|
|
46
|
-
pubDate:
|
|
46
|
+
pubDate: savedAt,
|
|
47
47
|
categories: [TOPIC_LABELS[topic]],
|
|
48
48
|
};
|
|
49
49
|
}
|
package/dist/lib/filter.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { type Topic } from "./topics.ts";
|
|
2
|
-
export declare
|
|
3
|
-
export
|
|
2
|
+
export declare const VIEWS: readonly ["today", "inbox", "done", "all"];
|
|
3
|
+
export type View = (typeof VIEWS)[number];
|
|
4
|
+
export type Filter = {
|
|
4
5
|
query: string;
|
|
5
6
|
topic: Topic | null;
|
|
7
|
+
view: View;
|
|
6
8
|
};
|
|
7
|
-
export declare function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}): string;
|
|
9
|
+
export declare function isTopic(value: string | null | undefined): value is Topic;
|
|
10
|
+
export declare function parseFilter(search: string): Filter;
|
|
11
|
+
export declare function filterSearch({ query, topic, view }: Filter): string;
|
package/dist/lib/filter.js
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
import { TOPICS } from "./topics.js";
|
|
2
|
+
export const VIEWS = ["today", "inbox", "done", "all"];
|
|
3
|
+
const DEFAULT_VIEW = "today";
|
|
2
4
|
export function isTopic(value) {
|
|
3
5
|
return TOPICS.some((topic) => topic === value);
|
|
4
6
|
}
|
|
7
|
+
function isView(value) {
|
|
8
|
+
return VIEWS.some((view) => view === value);
|
|
9
|
+
}
|
|
5
10
|
export function parseFilter(search) {
|
|
6
11
|
const params = new URLSearchParams(search);
|
|
7
12
|
const topic = params.get("topic");
|
|
13
|
+
const view = params.get("view");
|
|
8
14
|
return {
|
|
9
15
|
query: params.get("q") ?? "",
|
|
10
16
|
topic: isTopic(topic) ? topic : null,
|
|
17
|
+
view: isView(view) ? view : DEFAULT_VIEW,
|
|
11
18
|
};
|
|
12
19
|
}
|
|
13
|
-
export function filterSearch({ query, topic }) {
|
|
20
|
+
export function filterSearch({ query, topic, view }) {
|
|
14
21
|
const params = new URLSearchParams();
|
|
15
22
|
const trimmed = query.trim();
|
|
16
23
|
if (trimmed !== "")
|
|
17
24
|
params.set("q", trimmed);
|
|
18
25
|
if (topic !== null)
|
|
19
26
|
params.set("topic", topic);
|
|
27
|
+
if (view !== DEFAULT_VIEW)
|
|
28
|
+
params.set("view", view);
|
|
20
29
|
const search = params.toString();
|
|
21
30
|
return search === "" ? "" : `?${search}`;
|
|
22
31
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const bookmarks: {
|
|
2
2
|
postedAt: Date;
|
|
3
|
+
savedAt: Date;
|
|
3
4
|
keyPoints: string[];
|
|
4
5
|
id: string;
|
|
5
6
|
url: string;
|
|
@@ -8,6 +9,7 @@ export declare const bookmarks: {
|
|
|
8
9
|
handle: string;
|
|
9
10
|
};
|
|
10
11
|
topic: "ai-agents" | "models-releases" | "dev-tools" | "startups-business" | "japan-tech" | "design" | "other";
|
|
12
|
+
tldr: string;
|
|
11
13
|
summary: string;
|
|
12
14
|
text: string;
|
|
13
15
|
}[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const REVIEW_SIZE = 5;
|
|
2
|
+
export type ReviewPick = {
|
|
3
|
+
id: string;
|
|
4
|
+
reason: string;
|
|
5
|
+
};
|
|
6
|
+
type Saved = {
|
|
7
|
+
id: string;
|
|
8
|
+
savedAt: Date;
|
|
9
|
+
};
|
|
10
|
+
export declare function localDay(date: Date, timeZone?: string): string;
|
|
11
|
+
export declare function dailyReview({ bookmarks, done, today, timeZone, }: {
|
|
12
|
+
bookmarks: readonly Saved[];
|
|
13
|
+
done: ReadonlySet<string>;
|
|
14
|
+
today: string;
|
|
15
|
+
timeZone?: string;
|
|
16
|
+
}): ReviewPick[];
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export const REVIEW_SIZE = 5;
|
|
2
|
+
const REVIEW_AGES = new Map([
|
|
3
|
+
[1, "Saved yesterday"],
|
|
4
|
+
[7, "Saved a week ago"],
|
|
5
|
+
[30, "Saved a month ago"],
|
|
6
|
+
[90, "Saved 3 months ago"],
|
|
7
|
+
]);
|
|
8
|
+
const BACKLOG_REASON = "From your backlog";
|
|
9
|
+
const DAY_MS = 86_400_000;
|
|
10
|
+
export function localDay(date, timeZone) {
|
|
11
|
+
return new Intl.DateTimeFormat("en-CA", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" }).format(date);
|
|
12
|
+
}
|
|
13
|
+
function daysBetween(from, to) {
|
|
14
|
+
return Math.round((Date.parse(to) - Date.parse(from)) / DAY_MS);
|
|
15
|
+
}
|
|
16
|
+
function anniversaryReason(saved, today) {
|
|
17
|
+
const years = Number(today.slice(0, 4)) - Number(saved.slice(0, 4));
|
|
18
|
+
if (years < 1 || saved.slice(5) !== today.slice(5))
|
|
19
|
+
return undefined;
|
|
20
|
+
return years === 1 ? "Saved a year ago today" : `Saved ${years} years ago today`;
|
|
21
|
+
}
|
|
22
|
+
function hash(text) {
|
|
23
|
+
let value = 0x811c9dc5;
|
|
24
|
+
for (const char of text) {
|
|
25
|
+
value ^= char.codePointAt(0) ?? 0;
|
|
26
|
+
value = Math.imul(value, 0x01000193) >>> 0;
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
export function dailyReview({ bookmarks, done, today, timeZone, }) {
|
|
31
|
+
const candidates = bookmarks
|
|
32
|
+
.filter(({ id }) => !done.has(id))
|
|
33
|
+
.map(({ id, savedAt }) => {
|
|
34
|
+
const saved = localDay(savedAt, timeZone);
|
|
35
|
+
const age = daysBetween(saved, today);
|
|
36
|
+
return { id, age, reason: REVIEW_AGES.get(age) ?? anniversaryReason(saved, today) };
|
|
37
|
+
})
|
|
38
|
+
.filter(({ age }) => age >= 1);
|
|
39
|
+
const due = candidates
|
|
40
|
+
.flatMap(({ id, age, reason }) => (reason === undefined ? [] : [{ id, age, reason }]))
|
|
41
|
+
.toSorted((a, b) => b.age - a.age)
|
|
42
|
+
.slice(0, REVIEW_SIZE)
|
|
43
|
+
.map(({ id, reason }) => ({ id, reason }));
|
|
44
|
+
const backlog = candidates
|
|
45
|
+
.filter(({ reason }) => reason === undefined)
|
|
46
|
+
.toSorted((a, b) => hash(`${today}:${a.id}`) - hash(`${today}:${b.id}`))
|
|
47
|
+
.slice(0, REVIEW_SIZE - due.length)
|
|
48
|
+
.map(({ id }) => ({ id, reason: BACKLOG_REASON }));
|
|
49
|
+
return [...due, ...backlog];
|
|
50
|
+
}
|
package/dist/lib/search.d.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import type { Bookmark } from "./bookmarks.ts";
|
|
2
|
+
import type { Filter } from "./filter.ts";
|
|
2
3
|
import { type Topic } from "./topics.ts";
|
|
3
4
|
export type SearchEntry = {
|
|
4
5
|
id: string;
|
|
5
6
|
topic: Topic;
|
|
7
|
+
savedAt: Date;
|
|
6
8
|
text: string;
|
|
7
9
|
};
|
|
8
10
|
export declare function toSearchEntry(bookmark: Bookmark): SearchEntry;
|
|
9
|
-
export
|
|
11
|
+
export type Triage = {
|
|
12
|
+
done: ReadonlySet<string>;
|
|
13
|
+
review: ReadonlySet<string>;
|
|
14
|
+
};
|
|
15
|
+
export declare function matchingIds({ entries, filter: { query, topic, view }, triage, }: {
|
|
10
16
|
entries: readonly SearchEntry[];
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
filter: Filter;
|
|
18
|
+
triage: Triage;
|
|
13
19
|
}): Set<string>;
|
package/dist/lib/search.js
CHANGED
|
@@ -3,17 +3,24 @@ function normalize(text) {
|
|
|
3
3
|
return text.normalize("NFKC").toLowerCase();
|
|
4
4
|
}
|
|
5
5
|
export function toSearchEntry(bookmark) {
|
|
6
|
-
const { id, topic, author, summary, text, keyPoints } = bookmark;
|
|
6
|
+
const { id, topic, savedAt, author, tldr, summary, text, keyPoints } = bookmark;
|
|
7
7
|
return {
|
|
8
8
|
id,
|
|
9
9
|
topic,
|
|
10
|
-
|
|
10
|
+
savedAt,
|
|
11
|
+
text: normalize([author.name, `@${author.handle}`, TOPIC_LABELS[topic], tldr, summary, text, ...keyPoints].join("\n")),
|
|
11
12
|
};
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
+
const IN_VIEW = {
|
|
15
|
+
today: (id, { review }) => review.has(id),
|
|
16
|
+
inbox: (id, { done }) => !done.has(id),
|
|
17
|
+
done: (id, { done }) => done.has(id),
|
|
18
|
+
all: () => true,
|
|
19
|
+
};
|
|
20
|
+
export function matchingIds({ entries, filter: { query, topic, view }, triage, }) {
|
|
14
21
|
const terms = normalize(query).split(/\s+/).filter(Boolean);
|
|
15
22
|
return new Set(entries
|
|
16
23
|
.filter((entry) => topic === null || entry.topic === topic)
|
|
17
|
-
.filter((entry) => terms.every((term) => entry.text.includes(term)))
|
|
24
|
+
.filter((entry) => terms.length === 0 ? IN_VIEW[view](entry.id, triage) : terms.every((term) => entry.text.includes(term)))
|
|
18
25
|
.map((entry) => entry.id));
|
|
19
26
|
}
|
package/dist/pages/index.astro
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import BookmarkCard from "../components/BookmarkCard.astro";
|
|
3
|
-
import {
|
|
3
|
+
import { BookmarkControls } from "../components/BookmarkControls";
|
|
4
4
|
import { ThemeToggle } from "../components/ThemeToggle";
|
|
5
5
|
import Layout from "../layouts/Layout.astro";
|
|
6
6
|
import { bookmarks } from "../lib/load-bookmarks";
|
|
@@ -22,11 +22,19 @@ const designFeedHref = `${base}rss/design.xml`;
|
|
|
22
22
|
<ThemeToggle client:load />
|
|
23
23
|
</header>
|
|
24
24
|
|
|
25
|
+
<noscript>
|
|
26
|
+
<style is:inline>
|
|
27
|
+
[data-bookmark-list] {
|
|
28
|
+
visibility: visible !important;
|
|
29
|
+
}
|
|
30
|
+
</style>
|
|
31
|
+
</noscript>
|
|
32
|
+
|
|
25
33
|
<main class="flex flex-1 flex-col gap-6 pb-16">
|
|
26
34
|
{bookmarks.length > 0 ? (
|
|
27
35
|
<>
|
|
28
|
-
<
|
|
29
|
-
<ul class="flex flex-col gap-4 sm:gap-5">
|
|
36
|
+
<BookmarkControls client:only="react" entries={entries} />
|
|
37
|
+
<ul data-bookmark-list class="flex flex-col gap-4 not-data-ready:invisible sm:gap-5">
|
|
30
38
|
{bookmarks.map((bookmark) => (
|
|
31
39
|
<BookmarkCard bookmark={bookmark} />
|
|
32
40
|
))}
|