Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ API server runs on http://localhost:8081

```bash
cd web
npm install
npm run dev
bun install
bun run dev
```

Dev server runs on http://localhost:4321 and proxies API requests to the backend.
Expand All @@ -70,9 +70,9 @@ Built with [WXT](https://wxt.dev):

```bash
cd extension
npm install
npm run dev # Chrome dev mode
npm run dev:firefox # Firefox dev mode
bun install
bun run dev # Chrome dev mode
bun run dev:firefox # Firefox dev mode
```

## Architecture
Expand Down
2 changes: 2 additions & 0 deletions backend/internal/firehose/ingester.go
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,7 @@ func (i *Ingester) handlePreferences(event *FirehoseEvent) {
ExternalLinkSkippedHostnames []string `json:"externalLinkSkippedHostnames"`
SubscribedLabelers json.RawMessage `json:"subscribedLabelers"`
LabelPreferences json.RawMessage `json:"labelPreferences"`
DisableExternalLinkWarning *bool `json:"disableExternalLinkWarning,omitempty"`
CreatedAt string `json:"createdAt"`
}

Expand Down Expand Up @@ -837,6 +838,7 @@ func (i *Ingester) handlePreferences(event *FirehoseEvent) {
AuthorDID: event.Repo,
ExternalLinkSkippedHostnames: skippedHostnamesPtr,
SubscribedLabelers: subscribedLabelersPtr,
DisableExternalLinkWarning: record.DisableExternalLinkWarning,
LabelPreferences: labelPrefsPtr,
CreatedAt: createdAt,
IndexedAt: time.Now(),
Expand Down
72 changes: 72 additions & 0 deletions web/bun.lock

Large diffs are not rendered by default.

21 changes: 12 additions & 9 deletions web/src/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { atom } from "nanostores";
import type {
UserProfile,
FeedResponse,
AnnotationItem,
Collection,
FeedResponse,
HydratedLabel,
NotificationItem,
Target,
Selector,
HydratedLabel,
Target,
UserProfile,
} from "../types";

export type { Collection } from "../types";

export const sessionAtom = atom<UserProfile | null>(null);
Expand Down Expand Up @@ -264,7 +265,9 @@ export async function getFeed({
});
if (!res.ok) throw new Error("Failed to fetch feed");
const data = await res.json();
const normalizedItems = (data.items || []).map(normalizeItem);
const normalizedItems: AnnotationItem[] = (data.items || []).map(
normalizeItem,
);

const groupedItems: AnnotationItem[] = [];
if (normalizedItems.length > 0) {
Expand Down Expand Up @@ -292,7 +295,6 @@ export async function getFeed({
}

return {
cursor: data.cursor,
items: groupedItems,
hasMore: normalizedItems.length >= limit,
fetchedCount: normalizedItems.length,
Expand Down Expand Up @@ -1046,10 +1048,11 @@ export async function getUserTargetItems(
return { annotations: [], highlights: [] };
}
}

import type {
LabelerInfo,
LabelerSubscription,
LabelPreference,
LabelerInfo,
} from "../types";

export interface PreferencesResponse {
Expand Down Expand Up @@ -1104,10 +1107,10 @@ export async function getLabelerInfo(): Promise<LabelerInfo | null> {
}

import type {
ModerationRelationship,
BlockedUser,
MutedUser,
ModerationRelationship,
ModerationReport,
MutedUser,
ReportReasonType,
} from "../types";

Expand Down
13 changes: 6 additions & 7 deletions web/src/components/feed/MasonryFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { useStore as useNanoStore, useStore } from "@nanostores/react";
import { Loader2 } from "lucide-react";
import React, { useEffect, useState } from "react";
import { getFeed } from "../../api/client";
import Card from "../common/Card";
import { Loader2 } from "lucide-react";
import { useStore } from "@nanostores/react";
import { $user } from "../../store/auth";
import { $feedLayout } from "../../store/feedLayout";
import type { AnnotationItem } from "../../types";
import { Tabs, EmptyState } from "../ui";
import Card from "../common/Card";
import { EmptyState, Tabs } from "../ui";
import LayoutToggle from "../ui/LayoutToggle";
import { useStore as useNanoStore } from "@nanostores/react";
import { $feedLayout } from "../../store/feedLayout";

interface MasonryFeedProps {
motivation?: string;
Expand Down Expand Up @@ -50,7 +49,7 @@ function MasonryContent({
getFeed(params)
.then((data) => {
if (cancelled) return;
setItems(data?.items || []);
setItems(data.items);
setLoading(false);
})
.catch((e) => {
Expand Down
34 changes: 22 additions & 12 deletions web/src/components/navigation/MobileNav.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import React, { useState, useEffect } from "react";
import { Link, useLocation } from "react-router-dom";
import { useStore } from "@nanostores/react";
import { $user, logout } from "../../store/auth";
import { getUnreadNotificationCount } from "../../api/client";
import {
Home,
Search,
Bell,
Bookmark,
Folder,
User,
Highlighter,
Home,
LogOut,
MessageSquareText,
MoreHorizontal,
PenSquare,
Bookmark,
Search,
Settings,
MoreHorizontal,
LogOut,
Bell,
Highlighter,
User,
X,
} from "lucide-react";
import React, { useEffect, useState } from "react";
import { Link, useLocation } from "react-router-dom";
import { getUnreadNotificationCount } from "../../api/client";
import { $user, logout } from "../../store/auth";
import { AppleIcon } from "../common/Icons";

export default function MobileNav() {
Expand Down Expand Up @@ -84,6 +85,15 @@ export default function MobileNav() {

<div className="h-px bg-surface-200 dark:bg-surface-700 my-2" />

<Link
to="/annotations"
className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200"
onClick={closeMenu}
>
<MessageSquareText size={20} />
<span>Annotations</span>
</Link>

<Link
to="/highlights"
className="flex items-center gap-3 p-3 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors text-surface-700 dark:text-surface-200"
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/ui/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { clsx } from "clsx";
import React from "react";

interface Tab {
id: string;
Expand All @@ -23,7 +23,7 @@ export default function Tabs({
return (
<div
className={clsx(
"flex gap-1 bg-surface-100 dark:bg-surface-800 p-1 rounded-lg w-fit",
"flex max-w-full overflow-x-auto gap-1 bg-surface-100 dark:bg-surface-800 p-1 rounded-lg w-fit",
className,
)}
>
Expand Down
3 changes: 2 additions & 1 deletion web/src/lib/og.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@ export async function fetchBookmarkOG(uri: string): Promise<OGData | null> {
const domain = extractDomain(source);

const title = item.title || item.target?.title || "Bookmark on Margin";
let description = item.description || extractBody(item.body) || item.bodyValue || "";
let description =
item.description || extractBody(item.body) || item.bodyValue || "";
if (!description) description = "A saved bookmark on Margin";
if (domain) description += ` from ${domain}`;
description = truncate(description, 200);
Expand Down
3 changes: 2 additions & 1 deletion web/src/pages/og-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ async function fetchRecordData(uri: string): Promise<RecordData | null> {
: "";
const selectorText =
item.target?.selector?.exact || item.selector?.exact || "";
const bodyText = extractBody(item.body) || item.bodyValue || item.text || "";
const bodyText =
extractBody(item.body) || item.bodyValue || item.text || "";
const motivation = item.motivation || "";
const targetTitle = item.target?.title || item.title || "";

Expand Down
5 changes: 2 additions & 3 deletions web/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,9 @@ export interface AnnotationItem {
export type ActorSearchItem = UserProfile;

export interface FeedResponse {
cursor?: string;
items: AnnotationItem[];
hasMore?: boolean;
fetchedCount?: number;
hasMore: boolean;
fetchedCount: number;
}

export interface NotificationItem {
Expand Down
40 changes: 16 additions & 24 deletions web/src/views/core/Feed.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import React, { useEffect, useState, useCallback } from "react";
import { useSearchParams } from "react-router-dom";
import { getFeed } from "../../api/client";
import Card from "../../components/common/Card";
import { useStore } from "@nanostores/react";
import { clsx } from "clsx";
import {
Loader2,
Clock,
Bookmark,
Clock,
Highlighter,
Loader2,
MessageSquareText,
} from "lucide-react";
import { useStore } from "@nanostores/react";
import { $user } from "../../store/auth";
import type { AnnotationItem } from "../../types";
import { Tabs, EmptyState, Button } from "../../components/ui";
import React, { useCallback, useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom";
import { getFeed } from "../../api/client";
import Card from "../../components/common/Card";
import { Button, EmptyState, Tabs } from "../../components/ui";
import LayoutToggle from "../../components/ui/LayoutToggle";
import { $user } from "../../store/auth";
import { $feedLayout } from "../../store/feedLayout";
import { clsx } from "clsx";
import type { AnnotationItem } from "../../types";

interface FeedProps {
initialType?: string;
Expand Down Expand Up @@ -51,14 +51,10 @@ function FeedContent({
getFeed({ type, motivation, tag, limit: LIMIT, offset: 0 })
.then((data) => {
if (cancelled) return;
const fetched = data?.items || [];
const fetched = data.items;
setItems(fetched);
if (data?.hasMore !== undefined) {
setHasMore(data.hasMore);
} else {
setHasMore(fetched.length >= LIMIT);
}
setOffset(data?.fetchedCount ?? fetched.length);
setHasMore(data.hasMore);
setOffset(data.fetchedCount);
setLoading(false);
})
.catch((e) => {
Expand Down Expand Up @@ -86,12 +82,8 @@ function FeedContent({
});
const fetched = data?.items || [];
setItems((prev) => [...prev, ...fetched]);
if (data?.hasMore !== undefined) {
setHasMore(data.hasMore);
} else {
setHasMore(fetched.length >= LIMIT);
}
setOffset((prev) => prev + (data?.fetchedCount ?? fetched.length));
setHasMore(data.hasMore);
setOffset((prev) => prev + data.fetchedCount);
} catch (e) {
console.error(e);
} finally {
Expand Down
Loading