mirror of
https://github.com/linkwarden/browser-extension.git
synced 2026-06-23 04:10:26 +00:00
working adding links when addded to bookmark
This commit is contained in:
Generated
+31
@@ -10,6 +10,7 @@
|
|||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.2.0",
|
"@hookform/resolvers": "^3.2.0",
|
||||||
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.4",
|
"@radix-ui/react-dialog": "^1.0.4",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
@@ -1059,6 +1060,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-checkbox": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/primitive": "1.0.1",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1",
|
||||||
|
"@radix-ui/react-context": "1.0.1",
|
||||||
|
"@radix-ui/react-presence": "1.0.1",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||||
|
"@radix-ui/react-use-previous": "1.0.1",
|
||||||
|
"@radix-ui/react-use-size": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-collection": {
|
"node_modules/@radix-ui/react-collection": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.2.0",
|
"@hookform/resolvers": "^3.2.0",
|
||||||
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.4",
|
"@radix-ui/react-dialog": "^1.0.4",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
|||||||
@@ -132,9 +132,9 @@ const BookmarkForm = () => {
|
|||||||
const { handleSubmit, control } = form;
|
const { handleSubmit, control } = form;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getCurrentTabInfo().then((tabInfo) => {
|
getCurrentTabInfo().then(({ url, title }) => {
|
||||||
form.setValue('url', tabInfo.url);
|
form.setValue('url', url);
|
||||||
form.setValue('description', tabInfo.title);
|
form.setValue('description', title);
|
||||||
});
|
});
|
||||||
const getConfig = async () => {
|
const getConfig = async () => {
|
||||||
configured = await isConfigured();
|
configured = await isConfigured();
|
||||||
@@ -264,15 +264,15 @@ const BookmarkForm = () => {
|
|||||||
aria-expanded={openCollections}
|
aria-expanded={openCollections}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full justify-between',
|
'w-full justify-between',
|
||||||
!field.value.name && 'text-muted-foreground'
|
!field.value?.name && 'text-muted-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{loadingCollections
|
{loadingCollections
|
||||||
? 'Loading'
|
? 'Loading'
|
||||||
: field.value.name
|
: field.value?.name
|
||||||
? collections.response?.find(
|
? collections.response?.find(
|
||||||
(collection: { name: any }) =>
|
(collection: { name: string }) =>
|
||||||
collection.name === field.value.name
|
collection.name === field.value?.name
|
||||||
)?.name || 'Unorganized'
|
)?.name || 'Unorganized'
|
||||||
: 'Select a collection...'}
|
: 'Select a collection...'}
|
||||||
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
getSession,
|
getSession,
|
||||||
performLoginOrLogout,
|
performLoginOrLogout,
|
||||||
} from '../lib/auth/auth.ts';
|
} from '../lib/auth/auth.ts';
|
||||||
|
import { Checkbox } from './ui/CheckBox.tsx';
|
||||||
|
|
||||||
let HAD_PREVIOUS_SESSION = false;
|
let HAD_PREVIOUS_SESSION = false;
|
||||||
const OptionsForm = () => {
|
const OptionsForm = () => {
|
||||||
@@ -42,6 +43,7 @@ const OptionsForm = () => {
|
|||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
|
syncBookmarks: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -230,6 +232,25 @@ const OptionsForm = () => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={control}
|
||||||
|
name="syncBookmarks"
|
||||||
|
render={({field}) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Sync Bookmarks</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Sync your bookmarks with Linkwarden.
|
||||||
|
</FormDescription>
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<div>
|
<div>
|
||||||
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
|
||||||
@@ -237,7 +258,7 @@ const OptionsForm = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
onClick={onReset as any}
|
onClick={onReset as never}
|
||||||
disabled={resetLoading}
|
disabled={resetLoading}
|
||||||
>
|
>
|
||||||
Reset
|
Reset
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
import { cn } from '../lib/utils.ts';
|
||||||
|
|
||||||
interface WholeContainerProps {
|
interface WholeContainerProps extends React.HTMLAttributes<HTMLDivElement>{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WholeContainer: FC<WholeContainerProps> = ({ children }) => {
|
const WholeContainer: FC<WholeContainerProps> = ({ children, className }) => {
|
||||||
return (
|
return (
|
||||||
<div className="inset-0 w-full flex justify-center max-h-[600px] overflow-y-hidden relative">
|
<div className={cn('inset-0 w-full flex justify-center max-h-[600px] overflow-y-hidden relative', className)}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
|
import { CheckIcon } from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
import { cn } from '../../lib/utils.ts';
|
||||||
|
|
||||||
|
const Checkbox = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
className={cn("flex items-center justify-center text-current")}
|
||||||
|
>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
||||||
|
))
|
||||||
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Checkbox }
|
||||||
@@ -6,3 +6,15 @@ export async function postLink(baseUrl: string, data: bookmarkFormValues) {
|
|||||||
|
|
||||||
return await axios.post(url, data);
|
return await axios.post(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function postLinkFetch(baseUrl: string, data: bookmarkFormValues) {
|
||||||
|
const url = `${baseUrl}/api/v1/links`;
|
||||||
|
|
||||||
|
return await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -22,6 +22,12 @@ export async function getCsrfToken(url: string): Promise<string> {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getCsrfTokenFetch(url: string): Promise<string> {
|
||||||
|
const token = await fetch(`${url}/api/v1/auth/csrf`);
|
||||||
|
const { csrfToken } = await token.json();
|
||||||
|
return csrfToken;
|
||||||
|
}
|
||||||
|
|
||||||
export async function performLoginOrLogout(url: string, data: DataLogin | DataLogout) {
|
export async function performLoginOrLogout(url: string, data: DataLogin | DataLogout) {
|
||||||
const formBody = Object.entries(data)
|
const formBody = Object.entries(data)
|
||||||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||||
@@ -34,6 +40,20 @@ export async function performLoginOrLogout(url: string, data: DataLogin | DataLo
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function performLoginOrLogoutFetch(url: string, data: DataLogin | DataLogout) {
|
||||||
|
const formBody = Object.entries(data)
|
||||||
|
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||||||
|
.join('&');
|
||||||
|
|
||||||
|
return await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formBody,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function getSession(url: string) {
|
export async function getSession(url: string) {
|
||||||
const session = await axios.get(`${url}/api/v1/auth/session`);
|
const session = await axios.get(`${url}/api/v1/auth/session`);
|
||||||
return session.data.user;
|
return session.data.user;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const DEFAULTS: optionsFormValues = {
|
|||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
|
syncBookmarks: false,
|
||||||
};
|
};
|
||||||
const CONFIG_KEY = 'lw_config_key';
|
const CONFIG_KEY = 'lw_config_key';
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export const bookmarkFormSchema = z.object({
|
|||||||
id: z.number().optional(),
|
id: z.number().optional(),
|
||||||
ownerId: z.number().optional(),
|
ownerId: z.number().optional(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
}),
|
}).optional(),
|
||||||
tags: z
|
tags: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
export const optionsFormSchema = z.object({
|
export const optionsFormSchema = z.object({
|
||||||
baseUrl: z.string().url('This has to be a URL'),
|
baseUrl: z.string().url('This has to be a URL'),
|
||||||
username: z.string().nonempty('This cannot be empty'),
|
username: z.string().min(1, 'This cannot be empty'),
|
||||||
password: z.string().nonempty('This cannot be empty'),
|
password: z.string().min(1, 'This cannot be empty'),
|
||||||
|
syncBookmarks: z.boolean().default(false).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type optionsFormValues = z.infer<typeof optionsFormSchema>;
|
export type optionsFormValues = z.infer<typeof optionsFormSchema>;
|
||||||
+5
-1
@@ -24,7 +24,11 @@
|
|||||||
"48": "./48.png",
|
"48": "./48.png",
|
||||||
"128": "./128.png"
|
"128": "./128.png"
|
||||||
},
|
},
|
||||||
"permissions": ["storage", "activeTab", "tabs"],
|
"permissions": ["storage", "activeTab", "tabs", "bookmarks"],
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js",
|
||||||
|
"type": "module"
|
||||||
|
},
|
||||||
"host_permissions": ["*://*/*"],
|
"host_permissions": ["*://*/*"],
|
||||||
"commands": {
|
"commands": {
|
||||||
"_execute_action": {
|
"_execute_action": {
|
||||||
|
|||||||
@@ -1,2 +1,73 @@
|
|||||||
// Keeping this, maybe it will be needed somewhere in the future...
|
import { getBrowser } from '../../@/lib/utils.ts';
|
||||||
console.log('Background script running...');
|
import BookmarkTreeNode = chrome.bookmarks.BookmarkTreeNode;
|
||||||
|
import { getConfig } from '../../@/lib/config.ts';
|
||||||
|
import { postLinkFetch } from '../../@/lib/actions/links.ts';
|
||||||
|
import { getCsrfTokenFetch, performLoginOrLogoutFetch } from '../../@/lib/auth/auth.ts';
|
||||||
|
|
||||||
|
const browser = getBrowser();
|
||||||
|
|
||||||
|
const getCurrentBookmarks = async () => {
|
||||||
|
return await browser.bookmarks.getTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testing will remove later
|
||||||
|
const logBookmarks = (bookmarks: BookmarkTreeNode[]) => {
|
||||||
|
for (const bookmark of bookmarks) {
|
||||||
|
if (bookmark.url) {
|
||||||
|
const { url, title, parentId } = bookmark;
|
||||||
|
console.log(url, title, parentId);
|
||||||
|
}
|
||||||
|
else if (bookmark.children) {
|
||||||
|
logBookmarks(bookmark.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testing will remove later
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const { syncBookmarks } = await getConfig();
|
||||||
|
if (!syncBookmarks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [root] = await getCurrentBookmarks();
|
||||||
|
logBookmarks(root.children);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// This is the main function that will be called when a bookmark is created
|
||||||
|
// idk why wont work with axios...
|
||||||
|
browser.bookmarks.onCreated.addListener(async (_id: string, bookmark: BookmarkTreeNode) => {
|
||||||
|
try {
|
||||||
|
const { syncBookmarks, baseUrl, username, password } = await getConfig();
|
||||||
|
if (!syncBookmarks || !bookmark.url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const csrfToken = await getCsrfTokenFetch(baseUrl);
|
||||||
|
await performLoginOrLogoutFetch(
|
||||||
|
`${baseUrl}/api/v1/auth/callback/credentials`,
|
||||||
|
{
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
redirect: false,
|
||||||
|
csrfToken,
|
||||||
|
callbackUrl: `${baseUrl}/login`,
|
||||||
|
json: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await postLinkFetch(baseUrl, {
|
||||||
|
url: bookmark.url,
|
||||||
|
collection: {
|
||||||
|
name: "Unorganized",
|
||||||
|
},
|
||||||
|
tags: [],
|
||||||
|
name: bookmark.title,
|
||||||
|
description: bookmark.title,
|
||||||
|
});
|
||||||
|
console.log('Created', bookmark);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import OptionsForm from '../../@/components/OptionsForm.tsx';
|
|||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<WholeContainer>
|
<WholeContainer className="max-h-[625px]">
|
||||||
<Container>
|
<Container>
|
||||||
<div className="justify-center items-center p-2 flex">
|
<div className="justify-center items-center p-2 flex">
|
||||||
<h1 className="text-lg">Options configuration</h1>
|
<h1 className="text-lg">Options configuration</h1>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export default defineConfig({
|
|||||||
input: {
|
input: {
|
||||||
main: path.resolve(__dirname, 'index.html'),
|
main: path.resolve(__dirname, 'index.html'),
|
||||||
options: path.resolve(__dirname, 'src/pages/Options/options.html'),
|
options: path.resolve(__dirname, 'src/pages/Options/options.html'),
|
||||||
|
background: path.resolve(__dirname, 'src/pages/Background/index.ts'),
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
entryFileNames: '[name].js',
|
entryFileNames: '[name].js',
|
||||||
|
|||||||
Reference in New Issue
Block a user