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
31 changes: 0 additions & 31 deletions .github/ISSUE_TEMPLATE/bug_report.md

This file was deleted.

56 changes: 56 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Bug report
description: Report something that isn't working
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: "Thanks for the report! Every bug you flag makes TypeType better for everyone :)"
- type: textarea
id: what-happened
attributes:
label: What happened?
description: What went wrong, and what did you expect instead?
placeholder: When I ..., I expected ..., but ...
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
placeholder: |
1. Go to ...
2. Click ...
3. See ...
validations:
required: true
- type: dropdown
id: area
attributes:
label: Area
description: Which part is affected? Pick "Not sure" if you don't know.
options:
- Not sure
- Frontend (web app)
- Backend (API / extraction)
- Downloader
- Token / remote login
validations:
required: true
- type: dropdown
id: channel
attributes:
label: Channel
options:
- Not sure
- main (stable)
- beta
validations:
required: false
- type: textarea
id: more
attributes:
label: Anything else?
description: Logs, screenshots, the video link, or the provider (YouTube, NicoNico, BiliBili).
validations:
required: false
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Question or help
url: https://github.com/Priveetee/TypeType/discussions
about: "Ask questions and get help in Discussions ;)"
21 changes: 21 additions & 0 deletions .github/ISSUE_TEMPLATE/documentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Documentation
description: Report a docs problem or suggest an improvement
title: "[Docs]: "
labels: ["documentation"]
body:
- type: markdown
attributes:
value: "Thanks for helping the docs get better for everyone :p"
- type: textarea
id: issue
attributes:
label: What is wrong or missing?
validations:
required: true
- type: input
id: where
attributes:
label: Page or link
placeholder: https://...
validations:
required: false
43 changes: 43 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Feature request
description: Suggest an idea or an improvement
title: "[Feature]: "
labels: ["feature request"]
body:
- type: markdown
attributes:
value: "Thanks for your idea! It helps you and TypeType to grow :D"
- type: dropdown
id: kind
attributes:
label: Type of request
description: A new capability, or an improvement to something that already exists?
options:
- New feature
- Enhancement (improve existing UI/UX)
validations:
required: true
- type: textarea
id: problem
attributes:
label: What problem would this solve?
placeholder: I often can't ...
validations:
required: true
- type: textarea
id: idea
attributes:
label: What would you like to see?
validations:
required: false
- type: dropdown
id: area
attributes:
label: Area
options:
- Not sure
- Frontend (web app)
- Backend (API / extraction)
- Downloader
- Token / remote login
validations:
required: false
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ FROM --platform=$BUILDPLATFORM oven/bun:1.3.14-alpine AS builder

WORKDIR /app

RUN apk upgrade --no-cache libcrypto3 libssl3

COPY bun.lock package.json ./
COPY apps/web/package.json ./apps/web/

Expand All @@ -13,7 +15,7 @@ RUN bun run --cwd apps/web build

FROM nginx:1.31.0-alpine AS runner

RUN apk upgrade --no-cache libxml2 libcrypto3 libssl3
RUN apk upgrade --no-cache libxml2 libcrypto3 libssl3 libexpat

COPY --from=builder /app/apps/web/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
Expand Down
32 changes: 16 additions & 16 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,26 @@
"preview": "vite preview"
},
"dependencies": {
"@tanstack/react-query": "^5.100.8",
"@tanstack/react-router": "^1.169.1",
"@tanstack/react-query": "^5.101.0",
"@tanstack/react-router": "^1.170.16",
"@vidstack/react": "1.12.13",
"dashjs": "^5.1.1",
"lucide-react": "^1.14.0",
"media-icons": "^1.1.5",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"simple-icons": "^16.18.1",
"zustand": "^5.0.12"
"dashjs": "^5.2.0",
"lucide-react": "^1.21.0",
"media-icons": "1.1.5",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"simple-icons": "^16.24.0",
"zustand": "^5.0.14"
},
"devDependencies": {
"@tailwindcss/vite": "^4.3.0",
"@tanstack/router-plugin": "^1.167.32",
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@tailwindcss/vite": "^4.3.1",
"@tanstack/router-plugin": "^1.168.18",
"@types/node": "^26.0.0",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"tailwindcss": "^4.3.0",
"@vitejs/plugin-react": "^6.0.2",
"tailwindcss": "^4.3.1",
"typescript": "~6.0.3",
"vite": "^8.0.10"
"vite": "^8.0.16"
}
}
123 changes: 123 additions & 0 deletions apps/web/src/components/autoplay-countdown-overlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { Pause, Play, X } from "lucide-react";
import type { MouseEvent, PointerEvent } from "react";
import type { AutoplayTarget } from "../hooks/use-watch-ended-navigation";
import { proxyImage } from "../lib/proxy";

type Props = {
target: AutoplayTarget;
totalSeconds: number;
paused: boolean;
onPlayNow: () => void;
onCancel: () => void;
onPauseToggle: () => void;
};

export function AutoplayCountdownOverlay({
target,
totalSeconds,
paused,
onPlayNow,
onCancel,
onPauseToggle,
}: Props) {
const thumbnail = proxyImage(target.thumbnail);
const progressStyle = {
animationDuration: `${totalSeconds}s`,
animationPlayState: paused ? "paused" : "running",
};
const pauseButtonClass = paused
? "inline-flex h-9 w-9 items-center justify-center rounded-full bg-sky-500/20 text-sky-100 ring-1 ring-sky-300/45 transition hover:bg-sky-500/30 hover:ring-sky-200/60"
: "inline-flex h-9 w-9 items-center justify-center rounded-full bg-black/45 text-white ring-1 ring-white/20 transition hover:bg-black/60 hover:ring-white/35";

function stopOverlayEvent(event: MouseEvent | PointerEvent) {
event.preventDefault();
event.stopPropagation();
}

function stopPointerEvent(event: PointerEvent) {
event.stopPropagation();
}

function handlePlayNow(event: MouseEvent<HTMLButtonElement>) {
stopOverlayEvent(event);
onPlayNow();
}

function handleCancel(event: MouseEvent<HTMLButtonElement>) {
stopOverlayEvent(event);
onCancel();
}

function handlePauseToggle(event: MouseEvent<HTMLButtonElement>) {
stopOverlayEvent(event);
onPauseToggle();
}

return (
<div className="absolute inset-0 z-20 overflow-hidden bg-black text-white">
{thumbnail ? (
<img
src={thumbnail}
alt=""
className="absolute inset-0 h-full w-full object-cover opacity-75"
decoding="async"
/>
) : (
<div className="absolute inset-0 bg-zinc-950" />
)}
<div className="absolute inset-0 bg-black/45" />
<button
type="button"
onClick={handleCancel}
onPointerDown={stopPointerEvent}
className="absolute right-4 top-4 z-10 rounded-full p-1 text-white/70 transition hover:bg-white/10 hover:text-white sm:right-5 sm:top-5"
aria-label="Cancel autoplay"
>
<X className="h-6 w-6 sm:h-7 sm:w-7" aria-hidden="true" />
</button>
<div className="relative flex h-full items-end px-4 pb-5 sm:px-7 sm:pb-7">
<div className="max-w-2xl text-left">
<p className="text-[11px] font-medium uppercase tracking-[0.2em] text-white/55 sm:text-xs">
Up next
</p>
<h2 className="mt-2 line-clamp-2 text-base font-semibold leading-snug text-white sm:text-xl lg:text-2xl">
{target.title}
</h2>
{target.channelName && (
<p className="mt-1.5 text-sm font-medium text-white/65">{target.channelName}</p>
)}
<div className="mt-4 flex flex-wrap items-center gap-2">
<button
type="button"
onClick={handlePlayNow}
onPointerDown={stopPointerEvent}
className="inline-flex items-center gap-2 rounded-full bg-black/80 px-4 py-2 text-sm font-semibold text-white ring-1 ring-white/15 transition hover:bg-black/90 hover:ring-white/25"
>
<Play className="h-4 w-4 fill-current" aria-hidden="true" />
Play now
</button>
<button
type="button"
onClick={handlePauseToggle}
onPointerDown={stopPointerEvent}
className={pauseButtonClass}
aria-label={paused ? "Resume autoplay timer" : "Pause autoplay timer"}
>
{paused ? (
<Play className="h-4 w-4 fill-current" aria-hidden="true" />
) : (
<Pause className="h-4 w-4 fill-current" aria-hidden="true" />
)}
</button>
</div>
</div>
</div>
<div className="absolute inset-x-0 bottom-0 h-1 bg-sky-950/55">
<div
className="h-full origin-left bg-sky-400 [animation-fill-mode:forwards] [animation-name:autoplay-progress] [animation-timing-function:linear]"
style={progressStyle}
/>
</div>
</div>
);
}
3 changes: 2 additions & 1 deletion apps/web/src/components/channel-page-content.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useMemo } from "react";
import { useBlockedFilter } from "../hooks/use-blocked-filter";
import { useChannel } from "../hooks/use-channel";
import { useDocumentTitle } from "../hooks/use-document-title";
Expand Down Expand Up @@ -44,7 +45,7 @@ export function ChannelPageContent({ sourceUrl, sort, searchQuery, tab, onNaviga

const subscribed = isSubscribed(sourceUrl);
const searchAvailable = detectProvider(sourceUrl) === "youtube";
const visibleVideos = filter(videos);
const visibleVideos = useMemo(() => filter(videos), [filter, videos]);
const isInitialLoading = isLoading && !meta;
const isReplacingVideos = isFetching && !isFetchingNextPage && visibleVideos.length === 0;

Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/components/home-fallback-section.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useMemo } from "react";
import { useBlockedFilter } from "../hooks/use-blocked-filter";
import { useSubscriptionFeed } from "../hooks/use-subscription-feed";
import { useSubscriptions } from "../hooks/use-subscriptions";
Expand All @@ -9,10 +10,11 @@ function FeedSection() {
const { streams, isLoading, hasNextPage, isFetchingNextPage, fetchNextPage } =
useSubscriptionFeed();
const { filter } = useBlockedFilter();
const filtered = useMemo(() => filter(streams), [filter, streams]);
if (isLoading) return <VideoGridSkeleton idPrefix="home-feed" />;
return (
<>
<VideoGrid streams={filter(streams)} />
<VideoGrid streams={filtered} />
{isFetchingNextPage && <VideoGridSkeleton idPrefix="home-feed-next" />}
<ScrollSentinel onIntersect={fetchNextPage} enabled={hasNextPage && !isFetchingNextPage} />
</>
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/home-recommendations-section.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useMemo } from "react";
import { useBlockedFilter } from "../hooks/use-blocked-filter";
import { useHomeRecommendations } from "../hooks/use-home-recommendations";
import { HomeFallbackSection } from "./home-fallback-section";
Expand All @@ -9,7 +10,7 @@ export function HomeRecommendationsSection() {
const { streams, isLoading, isError, hasNextPage, isFetchingNextPage, fetchNextPage } =
useHomeRecommendations();
const { filter } = useBlockedFilter();
const filtered = filter(streams);
const filtered = useMemo(() => filter(streams), [filter, streams]);

if (isLoading) return <VideoGridSkeleton idPrefix="home-recommendations" />;
if (isError || filtered.length === 0) return <HomeFallbackSection />;
Expand Down
Loading