BlueSky & more on desktop lazurite.stormlightlabs.org/
tauri rust typescript bluesky appview atproto solid
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: external link in browser

* better moderation/labeler rendering (show names & handles with did)

+67 -2
+4
CHANGELOG.md
··· 2 2 3 3 ## v0.1.0 - Unreleased 4 4 5 + ### 2025-04-15 6 + 7 + - Open external links in your default browser 8 + 5 9 ### 2025-04-12 6 10 7 11 - Added labels to posts & profiles
+19 -1
src/components/feeds/embeds/ExternalEmbed.tsx
··· 1 + import { normalizeError } from "$/lib/utils/text"; 2 + import * as logger from "@tauri-apps/plugin-log"; 3 + import { openUrl } from "@tauri-apps/plugin-opener"; 1 4 import { Show } from "solid-js"; 2 5 3 6 export function ExternalEmbed(props: { description?: string; thumb?: string; title?: string; uri?: string }) { 7 + function handleClick(event: MouseEvent) { 8 + event.stopPropagation(); 9 + 10 + const uri = props.uri?.trim(); 11 + if (!uri) { 12 + event.preventDefault(); 13 + return; 14 + } 15 + 16 + event.preventDefault(); 17 + void openUrl(uri).catch((error) => { 18 + logger.warn("failed to open external embed URL", { keyValues: { error: normalizeError(error), uri } }); 19 + }); 20 + } 21 + 4 22 return ( 5 23 <a 6 24 class="ui-input-strong grid min-w-0 gap-3 overflow-hidden rounded-2xl p-3 text-inherit no-underline shadow-(--inset-shadow) transition duration-150 ease-out hover:bg-surface-bright" 7 25 href={props.uri} 8 26 rel="noreferrer" 9 27 target="_blank" 10 - onClick={(event) => event.stopPropagation()}> 28 + onClick={handleClick}> 11 29 <Show when={props.thumb}> 12 30 {(thumb) => <img class="max-h-64 w-full rounded-2xl object-cover" src={thumb()} alt="" />} 13 31 </Show>
+22
src/components/feeds/tests/ExternalEmbed.test.tsx
··· 1 + import { fireEvent, render, screen, waitFor } from "@solidjs/testing-library"; 2 + import { beforeEach, describe, expect, it, vi } from "vitest"; 3 + import { ExternalEmbed } from "../embeds/ExternalEmbed"; 4 + 5 + const openUrlMock = vi.hoisted(() => vi.fn()); 6 + 7 + vi.mock("@tauri-apps/plugin-opener", () => ({ openUrl: openUrlMock })); 8 + 9 + describe("ExternalEmbed", () => { 10 + beforeEach(() => { 11 + vi.resetAllMocks(); 12 + openUrlMock.mockResolvedValue(void 0); 13 + }); 14 + 15 + it("opens embed links with the system browser via the opener plugin", async () => { 16 + render(() => <ExternalEmbed title="External article" uri="https://example.com/article" />); 17 + 18 + fireEvent.click(screen.getByRole("link", { name: /external article/i })); 19 + 20 + await waitFor(() => expect(openUrlMock).toHaveBeenCalledWith("https://example.com/article")); 21 + }); 22 + });
+11 -1
src/components/settings/SettingsModeration.tsx
··· 263 263 return null; 264 264 } 265 265 266 + function getLabelerSummary(did: string) { 267 + const policy = policyByDid().get(did); 268 + const displayName = policy?.labelerDisplayName?.trim() || policy?.labelerHandle?.trim() || "Unknown labeler"; 269 + const handle = policy?.labelerHandle?.trim(); 270 + const normalizedHandle = handle ? `@${handle.replace(/^@/, "")}` : "@unknown"; 271 + return `${displayName} | ${normalizedHandle} | ${did}`; 272 + } 273 + 266 274 function isMasBuild() { 267 275 return distributionChannel() === "mac_app_store"; 268 276 } ··· 377 385 378 386 return ( 379 387 <details class="rounded-xl ui-input-strong px-3 py-2" open> 380 - <summary class="cursor-pointer select-none text-xs font-medium text-on-surface">{did}</summary> 388 + <summary class="cursor-pointer select-none break-all text-xs font-medium text-on-surface"> 389 + {getLabelerSummary(did)} 390 + </summary> 381 391 <div class="mt-3 grid gap-2"> 382 392 <Show 383 393 when={entries().length > 0}
+11
src/components/settings/tests/SettingsModeration.test.tsx
··· 42 42 unsubscribeLabelerMock.mockResolvedValue(void 0); 43 43 getLabelerPolicyDefinitionsMock.mockResolvedValue([{ 44 44 labelerDid: "did:plc:ar7c4by46qjdydhdevvrndac", 45 + labelerHandle: "moderation.bsky.app", 46 + labelerDisplayName: "Bluesky Moderation", 45 47 definitions: [{ identifier: "graphic-media", adultOnly: false, severity: "alert", blurs: "media", locales: [] }], 46 48 }, { 47 49 labelerDid: "did:plc:custom-labeler", 50 + labelerHandle: "custom-labeler.test", 51 + labelerDisplayName: "Custom Labeler", 48 52 definitions: [{ identifier: "porn", adultOnly: true, severity: "alert", blurs: "media", locales: [] }], 49 53 }]); 50 54 getDistributionChannelMock.mockResolvedValue("github"); ··· 84 88 await screen.findByText("Subscribed labelers"); 85 89 fireEvent.click(screen.getByRole("button", { name: "Remove" })); 86 90 await waitFor(() => expect(unsubscribeLabelerMock).toHaveBeenCalledWith("did:plc:custom-labeler")); 91 + }); 92 + 93 + it("shows labeler identity metadata in label preference headings", async () => { 94 + render(() => <SettingsModeration />); 95 + 96 + expect(await screen.findByText("Custom Labeler | @custom-labeler.test | did:plc:custom-labeler")) 97 + .toBeInTheDocument(); 87 98 }); 88 99 89 100 it("adds a label visibility override", async () => {