9 Commits
v0.1.0 ... main

19 changed files with 1029 additions and 1919 deletions

39
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
on:
release:
types: [published]
name: 🚀 Publish to Stores
jobs:
submit:
runs-on: ubuntu-latest
steps:
- name: 🚚 Checkout repository
uses: actions/checkout@v5
- name: 📦 Setup node
uses: actions/setup-node@v6
with:
node-version: 'lts/*'
cache: 'npm'
- name: 🛠️ Install dependencies
run: npm install
- name: 🤐 Zip extensions
run: |
npm run zip
npm run zip:firefox
- name: 🚀 Submit to stores
run: |
npm run submit -- --chrome-zip .output/*-chrome.zip \
--firefox-zip .output/*-firefox.zip --firefox-sources-zip .output/*-sources.zip
env:
CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
CHROME_SKIP_SUBMIT_REVIEW: true
FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }}
FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }}
FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }}

View File

@@ -9,10 +9,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 🚚 Checkout repository - name: 🚚 Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: 📦 Setup node - name: 📦 Setup node
uses: actions/setup-node@v4 uses: actions/setup-node@v6
with: with:
node-version: 'lts/*' node-version: 'lts/*'
cache: 'npm' cache: 'npm'
@@ -82,6 +82,5 @@ jobs:
.output/${{github.event.repository.name}}-${{env.version}}-firefox.zip .output/${{github.event.repository.name}}-${{env.version}}-firefox.zip
.output/${{github.event.repository.name}}-${{env.version}}-sources.zip .output/${{github.event.repository.name}}-${{env.version}}-sources.zip
latest.json latest.json
draft: false draft: true
prerelease: false prerelease: false
make_latest: true

View File

@@ -1,10 +1,12 @@
### ✨ Changelog ### ✨ Changelog
- initial MVP release v0.1.0 - Added keyboard shortcut indication
- Quick download is now renamed to quick search
- Other minor fixes and improvements
### ⬇️ Download Section ### ⬇️ Download Section
| Type\Browser | Chrome / Chromium based | Firefox / Firefox based | | Type | Chrome / Chromium base | Firefox base |
| :---- | :---- | :---- | | :---- | :---- | :---- |
| Store Listed (Recommended - Auto updates) | N/A | N/A | N/A | N/A | N/A | N/A | N/A | N/A | | Store Listed (Recommended - Auto updates) | [Get](https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf) | [Get](https://addons.mozilla.org/en-US/firefox/addon/neo-downloader-plus) |
| Unpackable ZIP (Manual updates) | [Download](https://github.com/neosubhamoy/neodlp-extension/releases/download/<release_tag>/neodlp-extension-<version>-chrome.zip) | [Download](https://github.com/neosubhamoy/neodlp-extension/releases/download/<release_tag>/neodlp-extension-<version>-firefox.zip) | | Unpackable ZIP (Manual updates) | [Download](https://github.com/neosubhamoy/neodlp-extension/releases/download/<release_tag>/neodlp-extension-<version>-chrome.zip) | [Download](https://github.com/neosubhamoy/neodlp-extension/releases/download/<release_tag>/neodlp-extension-<version>-firefox.zip) |

View File

@@ -6,15 +6,25 @@ NeoDLP (Neo Downloader Plus) Browser Integration
[![github tag](https://img.shields.io/github/v/tag/neosubhamoy/neodlp-extension?color=yellow)](https://github.com/neosubhamoy/neodlp-extension) [![github tag](https://img.shields.io/github/v/tag/neosubhamoy/neodlp-extension?color=yellow)](https://github.com/neosubhamoy/neodlp-extension)
[![PRs](https://img.shields.io/badge/PRs-welcome-blue.svg?style=flat)](https://github.com/neosubhamoy/neodlp-extension) [![PRs](https://img.shields.io/badge/PRs-welcome-blue.svg?style=flat)](https://github.com/neosubhamoy/neodlp-extension)
> [!TIP]
> **🥰 Liked this project? Please consider giving it a Star (🌟) on github to show us your appreciation and help the algorythm recommend this project to even more awesome people like you!** > **🥰 Liked this project? Please consider giving it a Star (🌟) on github to show us your appreciation and help the algorythm recommend this project to even more awesome people like you!**
### 📎 Pre-Requirements ## 📎 Pre-Requirements
- [NeoDLP](https://github.com/neosubhamoy/neodlp) (Installed and Running) - [NeoDLP](https://github.com/neosubhamoy/neodlp) (Installed and Running)
### ⬇️ Download and Installation ## ⬇️ Download and Installation
#### Manual Installation (in Google Chrome / Chromium based browsers) ### Direct Installation from Official Store Listing
[link-chrome]: https://chromewebstore.google.com/detail/neo-downloader-plus/mehopeailfjmiloiiohgicphlcgpompf "Version published on Chrome Web Store"
[link-firefox]: https://addons.mozilla.org/en-US/firefox/addon/neo-downloader-plus "Version published on Mozilla Add-ons"
[<img src="https://raw.githubusercontent.com/alrra/browser-logos/90fdf03c/src/chrome/chrome.svg" width="48" alt="Chrome" valign="middle">][link-chrome] [<img valign="middle" src="https://img.shields.io/chrome-web-store/v/mehopeailfjmiloiiohgicphlcgpompf.svg?label=%20">][link-chrome] also compatible with [<img src="https://raw.githubusercontent.com/alrra/browser-logos/90fdf03c/src/edge/edge.svg" width="24" alt="Edge" valign="middle">][link-chrome] [<img src="https://raw.githubusercontent.com/alrra/browser-logos/90fdf03c/src/opera/opera.svg" width="24" alt="Opera" valign="middle">][link-chrome]
[<img src="https://raw.githubusercontent.com/alrra/browser-logos/90fdf03c/src/firefox/firefox.svg" width="48" alt="Firefox" valign="middle">][link-firefox] [<img valign="middle" src="https://img.shields.io/amo/v/neo-downloader-plus.svg?label=%20">][link-firefox]
### Manual Installation (in Google Chrome / Chromium based browsers)
1. Download the `neodlp-extension-<version>-chrome.zip` file from the [latest release](https://github.com/neosubhamoy/neodlp-extension/releases). 1. Download the `neodlp-extension-<version>-chrome.zip` file from the [latest release](https://github.com/neosubhamoy/neodlp-extension/releases).
2. Extract the `.zip` file (using any zip extractor software. eg: 7zip, WinRAR etc.). 2. Extract the `.zip` file (using any zip extractor software. eg: 7zip, WinRAR etc.).
@@ -22,21 +32,21 @@ NeoDLP (Neo Downloader Plus) Browser Integration
4. Enable "Developer mode" in the top right corner. 4. Enable "Developer mode" in the top right corner.
5. Click on "Load unpacked" and select the newly extracted folder. Done! That's it...!! 5. Click on "Load unpacked" and select the newly extracted folder. Done! That's it...!!
#### Manual Installation (in Mozilla Firefox) ### Manual Installation (in Mozilla Firefox)
1. Download the `neodlp-extension-<version>-firefox.zip` file from the [latest release](https://github.com/neosubhamoy/neodlp-extension/releases). 1. Download the `neodlp-extension-<version>-firefox.zip` file from the [latest release](https://github.com/neosubhamoy/neodlp-extension/releases).
2. Open Firefox and navigate to `about:addons`. 2. Open Firefox and navigate to `about:addons`.
3. Click on the gear icon (top right) and select "Install Add-on From File...". 3. Click on the gear icon (top right) and select "Install Add-on From File...".
4. Select the downloaded `.zip` file. Done! That's it...!! 4. Select the downloaded `.zip` file. Done! That's it...!!
### ⚡ Technologies Used ## ⚡ Technologies Used
![WXT](https://img.shields.io/badge/WXT-67D55E.svg?style=for-the-badge&logo=WXT&logoColor=white) ![WXT](https://img.shields.io/badge/WXT-67D55E.svg?style=for-the-badge&logo=WXT&logoColor=white)
![React](https://img.shields.io/badge/React-61DAFB.svg?style=for-the-badge&logo=React&logoColor=black) ![React](https://img.shields.io/badge/React-61DAFB.svg?style=for-the-badge&logo=React&logoColor=black)
![TypeScript](https://img.shields.io/badge/TypeScript-3178C6.svg?style=for-the-badge&logo=TypeScript&logoColor=white) ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6.svg?style=for-the-badge&logo=TypeScript&logoColor=white)
![ShadCnUi](https://img.shields.io/badge/shadcn/ui-000000.svg?style=for-the-badge&logo=shadcn/ui&logoColor=white) ![ShadCnUi](https://img.shields.io/badge/shadcn/ui-000000.svg?style=for-the-badge&logo=shadcn/ui&logoColor=white)
### 🛠️ Contributing / Building from Source ## 🛠️ Contributing / Building from Source
Want to be part of this? Feel free to contribute...!! Pull Requests are always welcome...!! (^_^) Follow these simple steps to start building: Want to be part of this? Feel free to contribute...!! Pull Requests are always welcome...!! (^_^) Follow these simple steps to start building:
@@ -55,8 +65,8 @@ npm run zip:firefox # for production zip creation (firefox)
``` ```
5. Do the changes, Send a Pull Request with proper Description (NOTE: Pull Requests Without Proper Description will be Rejected) 5. Do the changes, Send a Pull Request with proper Description (NOTE: Pull Requests Without Proper Description will be Rejected)
**⭕ Noticed any Bugs or Want to give us some suggetions? Always feel free to open a GitHub Issue. We would love to hear from you...!!** **⭕ Noticed any Bug or Want to give us some suggetions? Always feel free to open a GitHub Issue. We would love to hear from you...!!**
### 📝 License ## 📝 License
NeoDLP Extension is Licensed under the [MIT license](https://github.com/neosubhamoy/neodlp-extension/blob/main/LICENSE). Anyone can view, modify, use (personal and commercial) or distribute it's sources without any attribution and extra permissions. NeoDLP Extension is Licensed under the [MIT license](https://github.com/neosubhamoy/neodlp-extension/blob/main/LICENSE). Anyone can view, modify, use (personal and commercial) or distribute it's sources without any attribution and extra permissions.

View File

@@ -9,14 +9,13 @@ const buttonVariants = cva(
{ {
variants: { variants: {
variant: { variant: {
default: default: "bg-primary text-primary-foreground hover:bg-primary/90",
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive: destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline: outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary: secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
@@ -26,6 +25,8 @@ const buttonVariants = cva(
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4", lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9", icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
}, },
}, },
defaultVariants: { defaultVariants: {

View File

@@ -20,7 +20,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="card-header" data-slot="card-header"
className={cn( className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className className
)} )}
{...props} {...props}

View File

@@ -1,3 +1,5 @@
"use client"
import * as React from "react" import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label" import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot" import { Slot } from "@radix-ui/react-slot"

28
components/ui/kbd.tsx Normal file
View File

@@ -0,0 +1,28 @@
import { cn } from "@/lib/utils"
function Kbd({ className, ...props }: React.ComponentProps<"kbd">) {
return (
<kbd
data-slot="kbd"
className={cn(
"bg-muted text-muted-foreground pointer-events-none inline-flex h-5 w-fit min-w-5 items-center justify-center gap-1 rounded-sm px-1 font-sans text-xs font-medium select-none",
"[&_svg:not([class*='size-'])]:size-3",
"[[data-slot=tooltip-content]_&]:bg-background/20 [[data-slot=tooltip-content]_&]:text-background dark:[[data-slot=tooltip-content]_&]:bg-background/10",
className
)}
{...props}
/>
)
}
function KbdGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<kbd
data-slot="kbd-group"
className={cn("inline-flex items-center gap-1", className)}
{...props}
/>
)
}
export { Kbd, KbdGroup }

View File

@@ -1,5 +1,3 @@
"use client"
import * as React from "react" import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label" import * as LabelPrimitive from "@radix-ui/react-label"

64
components/ui/tabs.tsx Normal file
View File

@@ -0,0 +1,64 @@
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
)
}
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
"bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]",
className
)}
{...props}
/>
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn("flex-1 outline-none", className)}
{...props}
/>
)
}
export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@@ -35,13 +35,13 @@ export default defineBackground(() => {
// Listen for the keyboard commands // Listen for the keyboard commands
browser.commands.onCommand.addListener(async (command) => { browser.commands.onCommand.addListener(async (command) => {
if (command === "neodlp:quick-download") { if (command === "neodlp:quick-search") {
try { try {
const tabs = await browser.tabs.query({ active: true, currentWindow: true }); const tabs = await browser.tabs.query({ active: true, currentWindow: true });
const activeTab = tabs[0]; const activeTab = tabs[0];
if (activeTab && activeTab.url) { if (activeTab && activeTab.url) {
console.log("Quick download triggered for URL:", activeTab.url); console.log("Quick search triggered for URL:", activeTab.url);
const response = await sendMessageToNativeHost({ const response = await sendMessageToNativeHost({
url: activeTab.url, url: activeTab.url,
@@ -49,12 +49,12 @@ export default defineBackground(() => {
argument: '' argument: ''
}); });
console.log("Quick download response:", response); console.log("Quick search response:", response);
} else { } else {
console.error("No active tab or URL found for quick download"); console.error("No active tab or URL found for quick search");
} }
} catch (error) { } catch (error) {
console.error("Error in quick download:", error); console.error("Error in quick search:", error);
} }
} }
}); });
@@ -63,50 +63,50 @@ export default defineBackground(() => {
browser.contextMenus.removeAll().then(() => { browser.contextMenus.removeAll().then(() => {
// Context menu for quick download // Context menu for quick download
browser.contextMenus.create({ browser.contextMenus.create({
id: "quick-download:page", id: "quick-search:page",
title: "Download with Neo Downloader Plus", title: "Search with Neo Downloader Plus",
contexts: ["page"] contexts: ["page"]
}); });
browser.contextMenus.create({ browser.contextMenus.create({
id: "quick-download:link", id: "quick-search:link",
title: "Download Link with Neo Downloader Plus", title: "Search with Neo Downloader Plus (Link)",
contexts: ["link"] contexts: ["link"]
}); });
browser.contextMenus.create({ browser.contextMenus.create({
id: "quick-download:media", id: "quick-search:media",
title: "Download Media with Neo Downloader Plus", title: "Search with Neo Downloader Plus (Media Source)",
contexts: ["video", "audio"] contexts: ["video", "audio"]
}); });
browser.contextMenus.create({ browser.contextMenus.create({
id: "quick-download:selection", id: "quick-search:selection",
title: "Download Selection with Neo Downloader Plus", title: "Search with Neo Downloader Plus (Selected Text)",
contexts: ["selection"] contexts: ["selection"]
}); });
}); });
browser.contextMenus.onClicked.addListener((info, tab) => { browser.contextMenus.onClicked.addListener((info, tab) => {
let url = ''; let url = '';
if (info.menuItemId === "quick-download:page") { if (info.menuItemId === "quick-search:page") {
if(!info.pageUrl) return; if(!info.pageUrl) return;
url = info.pageUrl; url = info.pageUrl;
} else if (info.menuItemId === "quick-download:link") { } else if (info.menuItemId === "quick-search:link") {
if(!info.linkUrl) return; if(!info.linkUrl) return;
url = info.linkUrl; url = info.linkUrl;
} else if (info.menuItemId === "quick-download:media") { } else if (info.menuItemId === "quick-search:media") {
if(!info.srcUrl) return; if(!info.srcUrl) return;
url = info.srcUrl; url = info.srcUrl;
} else if (info.menuItemId === "quick-download:selection") { } else if (info.menuItemId === "quick-search:selection") {
if(!info.selectionText) return; if(!info.selectionText) return;
url = info.selectionText; url = info.selectionText;
} }
if (!url) return; if (!url) return;
sendMessageToNativeHost({url: url, command: 'download', argument: ''}).then(response => { sendMessageToNativeHost({url: url, command: 'download', argument: ''}).then(response => {
console.log("Context menu download response:", response); console.log("Context menu search response:", response);
}).catch(error => { }).catch(error => {
console.error("Error in context menu download:", error); console.error("Error in context menu search:", error);
}); });
}); });
}); });

View File

@@ -4,77 +4,79 @@
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
:root { :root {
--radius: 0.625rem;
--background: oklch(1 0 0); --background: oklch(1 0 0);
--foreground: oklch(0.145 0 0); --foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0); --card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0); --card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0); --popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0); --popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.205 0 0); --primary: oklch(0.21 0.006 285.885);
--primary-foreground: oklch(0.985 0 0); --primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0); --secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.205 0 0); --secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.97 0 0); --muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.556 0 0); --muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.97 0 0); --accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.205 0 0); --accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325); --destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325); --border: oklch(0.92 0.004 286.32);
--border: oklch(0.922 0 0); --input: oklch(0.92 0.004 286.32);
--input: oklch(0.922 0 0); --ring: oklch(0.705 0.015 286.067);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116); --chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704); --chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392); --chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429); --chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08); --chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0); --sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0); --sidebar-foreground: oklch(0.141 0.005 285.823);
--sidebar-primary: oklch(0.205 0 0); --sidebar-primary: oklch(0.21 0.006 285.885);
--sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0); --sidebar-accent: oklch(0.967 0.001 286.375);
--sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
--sidebar-border: oklch(0.922 0 0); --sidebar-border: oklch(0.92 0.004 286.32);
--sidebar-ring: oklch(0.708 0 0); --sidebar-ring: oklch(0.705 0.015 286.067);
} }
.dark { .dark {
--background: oklch(0.145 0 0); --background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0); --foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0); --card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0); --card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0); --popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0); --popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0); --primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.205 0 0); --primary-foreground: oklch(0.21 0.006 285.885);
--secondary: oklch(0.269 0 0); --secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0); --secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0); --muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.708 0 0); --muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.269 0 0); --accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0); --accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723); --destructive: oklch(0.704 0.191 22.216);
--destructive-foreground: oklch(0.637 0.237 25.331); --border: oklch(1 0 0 / 10%);
--border: oklch(0.269 0 0); --input: oklch(1 0 0 / 15%);
--input: oklch(0.269 0 0); --ring: oklch(0.552 0.016 285.938);
--ring: oklch(0.439 0 0);
--chart-1: oklch(0.488 0.243 264.376); --chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48); --chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08); --chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9); --chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439); --chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0); --sidebar: oklch(0.21 0.006 285.885);
--sidebar-foreground: oklch(0.985 0 0); --sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0); --sidebar-accent: oklch(0.274 0.006 286.033);
--sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0); --sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.439 0 0); --sidebar-ring: oklch(0.552 0.016 285.938);
} }
@theme inline { @theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background); --color-background: var(--background);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--color-card: var(--card); --color-card: var(--card);
@@ -90,7 +92,6 @@
--color-accent: var(--accent); --color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground); --color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive); --color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border); --color-border: var(--border);
--color-input: var(--input); --color-input: var(--input);
--color-ring: var(--ring); --color-ring: var(--ring);
@@ -99,10 +100,6 @@
--color-chart-3: var(--chart-3); --color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4); --color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5); --color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar); --color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary: var(--sidebar-primary);

2445
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "neodlp-extension", "name": "neodlp-extension",
"description": "NeoDLP Browser Integration", "description": "NeoDLP Browser Integration",
"private": true, "private": true,
"version": "0.1.0", "version": "0.1.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "wxt", "dev": "wxt",
@@ -12,33 +12,34 @@
"zip": "wxt zip", "zip": "wxt zip",
"zip:firefox": "wxt zip -b firefox", "zip:firefox": "wxt zip -b firefox",
"compile": "tsc --noEmit", "compile": "tsc --noEmit",
"postinstall": "wxt prepare" "postinstall": "wxt prepare",
"submit": "wxt submit"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.0.1", "@hookform/resolvers": "^5.2.2",
"@radix-ui/react-label": "^2.1.4", "@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.2", "@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.8", "@radix-ui/react-tabs": "^1.1.13",
"@tailwindcss/vite": "^4.1.4", "@tailwindcss/vite": "^4.1.17",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.501.0", "lucide-react": "^0.553.0",
"react": "^19.1.0", "react": "^19.2.0",
"react-dom": "^19.1.0", "react-dom": "^19.2.0",
"react-hook-form": "^7.56.0", "react-hook-form": "^7.66.0",
"react-router-dom": "^7.5.1", "react-router-dom": "^7.9.5",
"tailwind-merge": "^3.2.0", "tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.4", "tailwindcss": "^4.1.17",
"tw-animate-css": "^1.2.7", "tw-animate-css": "^1.4.0",
"zod": "^3.24.3" "zod": "^4.1.12"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.14.1", "@types/node": "^24.10.0",
"@types/react": "^19.1.0", "@types/react": "^19.2.2",
"@types/react-dom": "^19.1.2", "@types/react-dom": "^19.2.2",
"@wxt-dev/module-react": "^1.1.3", "@wxt-dev/module-react": "^1.1.5",
"typescript": "^5.8.3", "typescript": "^5.9.3",
"wxt": "^0.20.0" "wxt": "^0.20.11"
} }
} }

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { AlertCircle, Download, Loader2, LucideIcon, Monitor, Moon, Sun } from "lucide-react"; import { AlertCircle, Loader2, LucideIcon, Monitor, Moon, Search, Sun } from "lucide-react";
import { type Browser } from 'wxt/browser'; import { type Browser } from 'wxt/browser';
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { z } from "zod"; import { z } from "zod";
@@ -11,16 +11,22 @@ import { Settings } from "@/types/settings";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { formatKeySymbol } from "@/utils";
import { useTheme } from "@/components/theme-provider"; import { useTheme } from "@/components/theme-provider";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Kbd, KbdGroup } from "@/components/ui/kbd";
const downloadFormSchema = z.object({ const searchFormSchema = z.object({
url: z.string().min(1, { message: "URL is required" }).url({message: "Invalid URL format." }), url: z.url({
error: (issue) => issue.input === undefined || issue.input === null || issue.input === ""
? "URL is required"
: "Invalid URL format"
}),
}); });
export default function HomePage() { export default function HomePage() {
const { setTheme } = useTheme(); const { setTheme } = useTheme();
const [isDownloading, setIsDownloading] = useState(false); const [isSearching, setIsSearching] = useState(false);
const [isLoadingSettings, setIsLoadingSettings] = useState(true); const [isLoadingSettings, setIsLoadingSettings] = useState(true);
const [isUpdatingSettings, setIsUpdatingSettings] = useState(false); const [isUpdatingSettings, setIsUpdatingSettings] = useState(false);
const [showNotRunningAlert, setShowNotRunningAlert] = useState(false); const [showNotRunningAlert, setShowNotRunningAlert] = useState(false);
@@ -28,6 +34,7 @@ export default function HomePage() {
theme: "system", theme: "system",
autofill_url: true, autofill_url: true,
}); });
const [shortcuts, setShortcuts] = useState<Browser.commands.Command[]>([]);
const themeOptions: { value: 'system' | 'dark' | 'light'; icon: LucideIcon; label: string }[] = [ const themeOptions: { value: 'system' | 'dark' | 'light'; icon: LucideIcon; label: string }[] = [
{ value: 'light', icon: Sun, label: 'Light' }, { value: 'light', icon: Sun, label: 'Light' },
@@ -35,17 +42,17 @@ export default function HomePage() {
{ value: 'system', icon: Monitor, label: 'System' }, { value: 'system', icon: Monitor, label: 'System' },
]; ];
const downloadForm = useForm<z.infer<typeof downloadFormSchema>>({ const searchForm = useForm<z.infer<typeof searchFormSchema>>({
resolver: zodResolver(downloadFormSchema), resolver: zodResolver(searchFormSchema),
defaultValues: { defaultValues: {
url: '', url: '',
}, },
mode: "onChange", mode: "onChange",
}); });
const watchedUrl = downloadForm.watch("url"); const watchedUrl = searchForm.watch("url");
const handleDownload = async (url?: string) => { const handleSearch = async (url?: string) => {
setIsDownloading(true); setIsSearching(true);
setShowNotRunningAlert(false); // Reset alert status at the beginning setShowNotRunningAlert(false); // Reset alert status at the beginning
// Create a timeout reference with undefined type // Create a timeout reference with undefined type
@@ -78,7 +85,7 @@ export default function HomePage() {
console.log('Response from background script:', response); console.log('Response from background script:', response);
} }
} catch (error) { } catch (error) {
console.error("Download failed", error); console.error("Search failed", error);
// Check if this was a timeout error // Check if this was a timeout error
if (error instanceof Error && error.message === 'TIMEOUT') { if (error instanceof Error && error.message === 'TIMEOUT') {
@@ -88,12 +95,12 @@ export default function HomePage() {
// Clear the timeout if it was some other error // Clear the timeout if it was some other error
if (timeoutId) clearTimeout(timeoutId); if (timeoutId) clearTimeout(timeoutId);
} finally { } finally {
setIsDownloading(false); setIsSearching(false);
} }
}; };
const handleDownloadSubmit = async (values: z.infer<typeof downloadFormSchema>) => { const handleSearchSubmit = async (values: z.infer<typeof searchFormSchema>) => {
await handleDownload(values.url); await handleSearch(values.url);
} }
const saveSettings = async <K extends keyof Settings>(key: K, value: Settings[K]) => { const saveSettings = async <K extends keyof Settings>(key: K, value: Settings[K]) => {
@@ -126,6 +133,13 @@ export default function HomePage() {
} }
}; };
// Fetch all Commands when the component mounts
useEffect(() => {
browser.commands.getAll().then(commands => {
setShortcuts(commands);
}).catch(console.error);
}, []);
// loading the settings from storage if available, overwriting the default values when the component mounts // loading the settings from storage if available, overwriting the default values when the component mounts
useEffect(() => { useEffect(() => {
const loadSettings = async () => { const loadSettings = async () => {
@@ -157,8 +171,8 @@ export default function HomePage() {
}); });
const activeTab = tabs[0]; const activeTab = tabs[0];
if (activeTab && activeTab.url) { if (activeTab && activeTab.url) {
downloadForm.setValue("url", activeTab.url); searchForm.setValue("url", activeTab.url);
await downloadForm.trigger("url"); await searchForm.trigger("url");
} }
} }
if (!isLoadingSettings && settings.autofill_url) { if (!isLoadingSettings && settings.autofill_url) {
@@ -169,12 +183,12 @@ export default function HomePage() {
// Listen for tab URL changes and update the form value accordingly (if autofill is enabled) // Listen for tab URL changes and update the form value accordingly (if autofill is enabled)
useEffect(() => { useEffect(() => {
if (isLoadingSettings || !settings.autofill_url) return; if (isLoadingSettings || !settings.autofill_url) return;
const handleTabUrlChange = async (tabId: number, changeInfo: Browser.tabs.TabChangeInfo) => { const handleTabUrlChange = async (tabId: number, changeInfo: Browser.tabs.OnUpdatedInfo) => {
if (changeInfo.status === "complete") { if (changeInfo.status === "complete") {
browser.tabs.get(tabId).then(async (tab) => { browser.tabs.get(tabId).then(async (tab) => {
if (tab.active && tab.url) { if (tab.active && tab.url) {
downloadForm.setValue("url", tab.url); searchForm.setValue("url", tab.url);
await downloadForm.trigger("url"); await searchForm.trigger("url");
} }
}); });
} }
@@ -222,12 +236,12 @@ export default function HomePage() {
/> />
<Label htmlFor="autofill-url">AutoFill Page URL</Label> <Label htmlFor="autofill-url">AutoFill Page URL</Label>
</div> </div>
<Form {...downloadForm}> <Form {...searchForm}>
<form onSubmit={downloadForm.handleSubmit(handleDownloadSubmit)} className="flex flex-col gap-4 w-full" autoComplete="off"> <form onSubmit={searchForm.handleSubmit(handleSearchSubmit)} className="flex flex-col gap-4 w-full" autoComplete="off">
<FormField <FormField
control={downloadForm.control} control={searchForm.control}
name="url" name="url"
disabled={isDownloading || isLoadingSettings || isUpdatingSettings} disabled={isSearching || isLoadingSettings || isUpdatingSettings}
render={({ field }) => ( render={({ field }) => (
<FormItem className="w-full"> <FormItem className="w-full">
<FormControl> <FormControl>
@@ -251,21 +265,44 @@ export default function HomePage() {
</FormItem> </FormItem>
)} )}
/> />
<Button className="w-full cursor-pointer" type="submit" disabled={isDownloading || isLoadingSettings || isUpdatingSettings || !watchedUrl || downloadForm.getFieldState("url").invalid}> <Button className="w-full cursor-pointer" type="submit" disabled={isSearching || isLoadingSettings || isUpdatingSettings || !watchedUrl || searchForm.getFieldState("url").invalid}>
{isDownloading ? ( {isSearching ? (
<> <>
<Loader2 className="w-4 h-4 animate-spin" /> <Loader2 className="w-4 h-4 animate-spin" />
Starting Initiating
</> </>
) : ( ) : (
<> <>
<Download className="w-4 h-4" /> <Search className="w-4 h-4" />
Download Search
</> </>
)} )}
</Button> </Button>
</form> </form>
</Form> </Form>
<div className="or-devider after:border-border relative text-center text-xs after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t mb-2">
<span className="bg-background text-muted-foreground relative z-10 px-2">
OR
</span>
</div>
<div className="quick-search-suggestion flex flex-col items-center justify-center space-y-2">
<p className="text-sm flex items-center gap-2"><span className="">Use, Shortcut</span>
{
shortcuts.find(cmd => cmd.name === "neodlp:quick-search")?.shortcut ? (
<KbdGroup>
{shortcuts.find(cmd => cmd.name === "neodlp:quick-search")?.shortcut?.split('+').map((key, index, arr) => (
<span key={index} className="flex items-center">
<Kbd>{formatKeySymbol(key.trim())}</Kbd>
{index < arr.length - 1 && <span className="ml-1">+</span>}
</span>
))}
</KbdGroup>
) : (
<Kbd> Not Set</Kbd>
)
}
</p>
</div>
</div> </div>
) )
} }

View File

@@ -3,7 +3,6 @@
"compilerOptions": { "compilerOptions": {
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"baseUrl": ".",
"paths": { "paths": {
"@/*": ["./*"] "@/*": ["./*"]
} }

1
types/os.ts Normal file
View File

@@ -0,0 +1 @@
export type OS = 'windows' | 'macos' | 'linux' | 'android' | 'ios' | 'unknown';

39
utils.ts Normal file
View File

@@ -0,0 +1,39 @@
import { OS } from "@/types/os";
export function getOS(): OS {
const userAgent = window.navigator.userAgent;
const platform = (window.navigator as any)?.userAgentData?.platform || window.navigator.platform;
const macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K', 'macOS'];
const windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'];
const iosPlatforms = ['iPhone', 'iPad', 'iPod'];
if (macosPlatforms.includes(platform)) {
return 'macos';
} else if (iosPlatforms.includes(platform)) {
return 'ios';
} else if (windowsPlatforms.includes(platform)) {
return 'windows';
} else if (/Android/.test(userAgent)) {
return 'android';
} else if (/Linux/.test(platform)) {
return 'linux';
} else {
return 'unknown';
}
}
export function formatKeySymbol(key: string): string {
switch (key.toLowerCase()) {
case 'option':
return '⌥';
case 'shift':
return '⇧';
case 'command':
return '⌘';
case 'macctrl':
return '⌃';
default:
return key.toUpperCase();
}
}

View File

@@ -20,7 +20,7 @@ export default defineConfig({
name: "Neo Downloader Plus", name: "Neo Downloader Plus",
description: "Neo Downloader Plus Browser Integration", description: "Neo Downloader Plus Browser Integration",
homepage_url: "https://neodlp.neosubhamoy.com", homepage_url: "https://neodlp.neosubhamoy.com",
version: "0.1.0", version: "0.1.1",
permissions: ["tabs", "storage", "contextMenus", "nativeMessaging"], permissions: ["tabs", "storage", "contextMenus", "nativeMessaging"],
} }
if (browser === 'chrome') { if (browser === 'chrome') {
@@ -33,8 +33,8 @@ export default defineConfig({
default: "Alt+Shift+N", default: "Alt+Shift+N",
} }
}, },
"neodlp:quick-download": { "neodlp:quick-search": {
description: "Quick Download", description: "Quick Search",
suggested_key: { suggested_key: {
default: "Alt+Shift+Q" default: "Alt+Shift+Q"
} }
@@ -55,8 +55,8 @@ export default defineConfig({
default: "Alt+Shift+N", default: "Alt+Shift+N",
} }
}, },
"neodlp:quick-download": { "neodlp:quick-search": {
description: "Quick Download", description: "Quick Search",
suggested_key: { suggested_key: {
default: "Alt+Shift+Q" default: "Alt+Shift+Q"
} }
@@ -72,8 +72,8 @@ export default defineConfig({
default: "Alt+Shift+N", default: "Alt+Shift+N",
} }
}, },
"neodlp:quick-download": { "neodlp:quick-search": {
description: "Quick Download", description: "Quick Search",
suggested_key: { suggested_key: {
default: "Alt+Shift+Q" default: "Alt+Shift+Q"
} }