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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Semhub
# SemHub

## Development

Expand Down Expand Up @@ -84,7 +84,7 @@ To set up a GitHub App:
- In terms of permissions:
- Select the following read-only Repository permissions: Metadata (mandatory), Discussions, Issues, Pull Requests, Contents. (These should be tracked in code via `github-app.ts`.)
- Select the following read-only User permissions: Emails (actually would've gotten the user's email from the login process)
- Select the following read-only Organization permissions: Members (to enable Semhub to work for users in the same organization after it has been installed by an admin)
- Select the following read-only Organization permissions: Members (to enable SemHub to work for users in the same organization after it has been installed by an admin)
- Leave unchecked the box that says "Request user authorization (OAuth) during installation". Our app handles user login + creation.
- Select redirect on update and use the frontend `/repos` page as the Setup URL
- Local dev: `https://local.semhub.dev:3001/repos`
Expand Down
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion packages/core/migrations/0033_vector-plain-storage.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
pg (or pgvector?) uses EXTENDED storage by default for for all columns bigger than 2KB

Changes vector storage to PLAIN if vector operation is in hot path (which it is for Semhub):
Changes vector storage to PLAIN if vector operation is in hot path (which it is for SemHub):
- this only works if your row size is within PG page limit of 8KB
- you cannot increase page limit beyond 8KB for index

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/email/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export async function sendEmail(
envPrefix: string,
) {
const { data, error } = await client.emails.send({
from: `${envPrefix ? `${envPrefix} ` : ""}Semhub <[email protected]>`,
from: `${envPrefix ? `${envPrefix} ` : ""}SemHub <[email protected]>`,
to,
subject,
html,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/github/permission/github-app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type Permissions } from "../schema.webhook";

// this file tracks the current permissions requested by Semhub Github App
// this file tracks the current permissions requested by SemHub Github App
// however, the actual permissions are configured via UI on GitHub

export const CURRENT_REQUESTED_PERMISSIONS: Permissions = {
Expand Down
39 changes: 39 additions & 0 deletions packages/core/src/repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,4 +392,43 @@ export const Repo = {
.set({ issuesLastUpdatedAt: result.lastUpdated })
.where(eq(repos.id, repoId));
},

readyForPublicSearch: async ({
owner,
name,
db,
}: {
owner: string;
name: string;
db: DbClient;
}) => {
const [result] = await db
.select({
initStatus: repos.initStatus,
syncStatus: repos.syncStatus,
lastSyncedAt: repos.lastSyncedAt,
issuesLastUpdatedAt: repos.issuesLastUpdatedAt,
avatarUrl: repos.ownerAvatarUrl,
})
.from(repos)
.where(
and(
eq(repos.ownerLogin, owner),
eq(repos.name, name),
eq(repos.isPrivate, false),
),
);

if (!result) {
return null;
}

return {
initStatus: result.initStatus,
lastSyncedAt: result.lastSyncedAt,
issuesLastUpdatedAt: result.issuesLastUpdatedAt,
syncStatus: result.syncStatus,
avatarUrl: result.avatarUrl,
};
},
};
1 change: 1 addition & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-toast": "^1.2.4",
Expand Down
Binary file added packages/web/public/sounds/dark-mode.mp3
Binary file not shown.
13 changes: 11 additions & 2 deletions packages/web/src/components/navbar/DarkModeToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { useThemeToggle } from "@/lib/hooks/useThemeToggle";
import { Button } from "@/components/ui/button";

export function DarkModeToggle() {
interface DarkModeToggleProps {
onToggleCountChange: () => void;
}

export function DarkModeToggle({ onToggleCountChange }: DarkModeToggleProps) {
const { ThemeIcon, handleThemeChange } = useThemeToggle();

const handleClick = () => {
onToggleCountChange();
handleThemeChange();
};

return (
<Button variant="ghost" size="icon" onClick={handleThemeChange}>
<Button variant="ghost" size="icon" onClick={handleClick}>
<ThemeIcon className="size-[1.2rem] animate-in fade-in" />
<span className="sr-only">Toggle theme</span>
</Button>
Expand Down
72 changes: 57 additions & 15 deletions packages/web/src/components/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,73 @@
import { Link } from "@tanstack/react-router";
import { useTheme } from "next-themes";
import { useEffect, useRef, useState } from "react";

import { useSession } from "@/lib/hooks/useSession";
import { useAudio } from "@/hooks/useAudio";
import { DarkModeToggle } from "@/components/navbar/DarkModeToggle";
import { LoginButton } from "@/components/navbar/LoginButton";
import { UserNav } from "@/components/navbar/UserNav";

export function Navbar() {
const { isAuthenticated, user } = useSession();
const [toggleCount, setToggleCount] = useState(0);
const { theme, systemTheme } = useTheme();
const audio = useAudio("/sounds/dark-mode.mp3");
const prevThemeRef = useRef(theme);
const prevSystemThemeRef = useRef(systemTheme);
const showEasterEgg = toggleCount >= 8;

useEffect(() => {
const wasInDarkMode =
prevThemeRef.current === "dark" ||
(prevThemeRef.current === "system" &&
prevSystemThemeRef.current === "dark");
const isInDarkMode =
theme === "dark" || (theme === "system" && systemTheme === "dark");

if (!wasInDarkMode && isInDarkMode && showEasterEgg) {
audio.play().catch(console.error);
}

prevThemeRef.current = theme;
prevSystemThemeRef.current = systemTheme;
}, [theme, systemTheme, audio, showEasterEgg]);

const handleToggleCount = () => {
setToggleCount((prev) => prev + 1);
};

const standardLogo = (
<>
<span className="text-blue-600">Sem</span>
<span className="text-orange-500">Hub</span>
<span className="animate-cursor-slow text-blue-600">_</span>
</>
);
const lightModeLogo = (
<h1 className="flex items-center font-serif text-2xl dark:hidden">
{standardLogo}
</h1>
);
const darkModeLogo = (
<h1 className="hidden items-center font-serif text-2xl dark:flex">
{standardLogo}
</h1>
);
const darkModeEasterEggLogo = (
<h1 className="hidden items-center font-sans text-2xl dark:flex">
<span className="font-semibold text-white">Sem</span>
<span className="rounded-lg bg-[#F0931B] font-semibold text-black">
Hub
</span>
</h1>
);
return (
<div className="flex h-16 w-full items-center justify-between px-4">
<div className="flex items-center gap-2">
<Link to="/" className="flex items-center">
<h1 className="flex items-center font-sans text-2xl dark:hidden">
<span className="font-semibold text-blue-500">S</span>
<span className="font-semibold text-red-500">e</span>
<span className="font-semibold text-yellow-500">m</span>
<span className="font-semibold text-blue-500">H</span>
<span className="font-semibold text-green-500">u</span>
<span className="font-semibold text-red-500">b</span>
</h1>
<h1 className="hidden items-center font-sans text-2xl dark:flex">
<span className="font-semibold text-white">Sem</span>
<span className="rounded-lg bg-[#F0931B] font-semibold text-black">
Hub
</span>
</h1>
{lightModeLogo}
{showEasterEgg ? darkModeEasterEggLogo : darkModeLogo}
</Link>
</div>
<nav className="flex items-center gap-4">
Expand All @@ -42,7 +84,7 @@ export function Navbar() {
) : (
<>
<LoginButton />
<DarkModeToggle />
<DarkModeToggle onToggleCountChange={handleToggleCount} />
</>
)}
</nav>
Expand Down
6 changes: 4 additions & 2 deletions packages/web/src/components/search/MeSearchBars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ export function MyReposResultsSearchBar({
handleBlur,
commandValue,
setCommandValue,
} = useSearchBar(initialQuery);
} = useSearchBar({
initialQuery,
});
const { handleSearch } = useMeSearch(setQuery);

return (
Expand Down Expand Up @@ -104,7 +106,7 @@ export function MyReposSearchBar() {
commandValue,
setCommandValue,
setQuery,
} = useSearchBar();
} = useSearchBar({});
const { handleSearch } = useMeSearch(setQuery);

return (
Expand Down
18 changes: 14 additions & 4 deletions packages/web/src/components/search/PublicSearchBars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ function SearchFilters({
export function ResultsSearchBar({ query: initialQuery }: { query: string }) {
const [selectedOrg, setSelectedOrg] = useState("all");
const [selectedRepo, setSelectedRepo] = useState("all");
const removedOperators = ["collection"] as SearchOperator[];
const {
query,
inputRef,
Expand All @@ -238,8 +239,11 @@ export function ResultsSearchBar({ query: initialQuery }: { query: string }) {
commandValue,
setCommandValue,
setQuery,
} = useSearchBar(initialQuery);
const { handleSearch } = usePublicSearch(setQuery);
} = useSearchBar({
initialQuery,
removedOperators,
});
const { handleSearch } = usePublicSearch({ mode: "search", setQuery });

const handleOrgChange = (org: string) => {
setSelectedOrg(org);
Expand Down Expand Up @@ -306,6 +310,7 @@ export function ResultsSearchBar({ query: initialQuery }: { query: string }) {
handleValueSelect={handleValueSelect}
commandValue={commandValue}
setCommandValue={setCommandValue}
removedOperators={removedOperators}
/>
</div>
)}
Expand All @@ -318,6 +323,7 @@ export function HomepageSearchBar() {
const { theme } = useTheme();
const [selectedOrg, setSelectedOrg] = useState("all");
const [selectedRepo, setSelectedRepo] = useState("all");
const removedOperators = ["collection"] as SearchOperator[];
const {
query,
inputRef,
Expand All @@ -336,8 +342,11 @@ export function HomepageSearchBar() {
commandValue,
setCommandValue,
setQuery,
} = useSearchBar();
const { handleSearch, handleLuckySearch } = usePublicSearch(setQuery);
} = useSearchBar({ removedOperators });
const { handleSearch, handleLuckySearch } = usePublicSearch({
mode: "search",
setQuery,
});
const placeholderText = usePlaceholderAnimation();

const handleOrgChange = (org: string) => {
Expand Down Expand Up @@ -401,6 +410,7 @@ export function HomepageSearchBar() {
handleValueSelect={handleValueSelect}
commandValue={commandValue}
setCommandValue={setCommandValue}
removedOperators={removedOperators}
/>
</div>
)}
Expand Down
Loading
Loading