Biome: Fixes for extenstion repo

This commit is contained in:
Utkarsh-Patel-13 2025-07-27 12:01:11 -07:00
parent b70d46f732
commit b76419c6fc
29 changed files with 2668 additions and 2386 deletions

View file

@ -1,26 +0,0 @@
/**
* @type {import('prettier').Options}
*/
export default {
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: false,
trailingComma: "none",
bracketSpacing: true,
bracketSameLine: true,
plugins: ["@ianvs/prettier-plugin-sort-imports"],
importOrder: [
"<BUILTIN_MODULES>", // Node.js built-in modules
"<THIRD_PARTY_MODULES>", // Imports not matched by other special words or groups.
"", // Empty line
"^@plasmo/(.*)$",
"",
"^@plasmohq/(.*)$",
"",
"^~(.*)$",
"",
"^[./]"
]
}

View file

@ -1,77 +1,70 @@
import { initQueues, initWebHistory } from "~utils/commons" import { Storage } from "@plasmohq/storage";
import type { WebHistory } from "~utils/interfaces" import { getRenderedHtml, initQueues, initWebHistory } from "~utils/commons";
import { Storage } from "@plasmohq/storage" import type { WebHistory } from "~utils/interfaces";
import {getRenderedHtml} from '~utils/commons'
chrome.tabs.onCreated.addListener(async (tab: any) => { chrome.tabs.onCreated.addListener(async (tab: any) => {
try { try {
await initWebHistory(tab.id) await initWebHistory(tab.id);
await initQueues(tab.id) await initQueues(tab.id);
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
}) });
chrome.tabs.onUpdated.addListener( chrome.tabs.onUpdated.addListener(async (tabId: number, changeInfo: any, tab: any) => {
async (tabId: number, changeInfo: any, tab: any) => { if (changeInfo.status === "complete" && tab.url) {
if (changeInfo.status === "complete" && tab.url) { const storage = new Storage({ area: "local" });
const storage = new Storage({ area: "local" }) await initWebHistory(tab.id);
await initWebHistory(tab.id) await initQueues(tab.id);
await initQueues(tab.id)
const result = await chrome.scripting.executeScript({ const result = await chrome.scripting.executeScript({
// @ts-ignore // @ts-ignore
target: { tabId: tab.id }, target: { tabId: tab.id },
// @ts-ignore // @ts-ignore
func: getRenderedHtml func: getRenderedHtml,
}) });
let toPushInTabHistory: any = result[0].result // const { renderedHtml, title, url, entryTime } = result[0].result; const toPushInTabHistory: any = result[0].result; // const { renderedHtml, title, url, entryTime } = result[0].result;
let urlQueueListObj: any = await storage.get("urlQueueList") const urlQueueListObj: any = await storage.get("urlQueueList");
let timeQueueListObj: any = await storage.get("timeQueueList") const timeQueueListObj: any = await storage.get("timeQueueList");
urlQueueListObj.urlQueueList urlQueueListObj.urlQueueList
.find((data: WebHistory) => data.tabsessionId === tabId) .find((data: WebHistory) => data.tabsessionId === tabId)
.urlQueue.push(toPushInTabHistory.url) .urlQueue.push(toPushInTabHistory.url);
timeQueueListObj.timeQueueList timeQueueListObj.timeQueueList
.find((data: WebHistory) => data.tabsessionId === tabId) .find((data: WebHistory) => data.tabsessionId === tabId)
.timeQueue.push(toPushInTabHistory.entryTime) .timeQueue.push(toPushInTabHistory.entryTime);
await storage.set("urlQueueList", { await storage.set("urlQueueList", {
urlQueueList: urlQueueListObj.urlQueueList urlQueueList: urlQueueListObj.urlQueueList,
}) });
await storage.set("timeQueueList", { await storage.set("timeQueueList", {
timeQueueList: timeQueueListObj.timeQueueList timeQueueList: timeQueueListObj.timeQueueList,
}) });
} }
} });
)
chrome.tabs.onRemoved.addListener(async (tabId: number, removeInfo: object) => { chrome.tabs.onRemoved.addListener(async (tabId: number, removeInfo: object) => {
const storage = new Storage({ area: "local" }) const storage = new Storage({ area: "local" });
let urlQueueListObj: any = await storage.get("urlQueueList") const urlQueueListObj: any = await storage.get("urlQueueList");
let timeQueueListObj: any = await storage.get("timeQueueList") const timeQueueListObj: any = await storage.get("timeQueueList");
if (urlQueueListObj.urlQueueList && timeQueueListObj.timeQueueList) { if (urlQueueListObj.urlQueueList && timeQueueListObj.timeQueueList) {
const urlQueueListToSave = urlQueueListObj.urlQueueList.map( const urlQueueListToSave = urlQueueListObj.urlQueueList.map((element: WebHistory) => {
(element: WebHistory) => { if (element.tabsessionId !== tabId) {
if (element.tabsessionId !== tabId) { return element;
return element }
} });
} const timeQueueListSave = timeQueueListObj.timeQueueList.map((element: WebHistory) => {
) if (element.tabsessionId !== tabId) {
const timeQueueListSave = timeQueueListObj.timeQueueList.map( return element;
(element: WebHistory) => { }
if (element.tabsessionId !== tabId) { });
return element await storage.set("urlQueueList", {
} urlQueueList: urlQueueListToSave.filter((item: any) => item),
} });
) await storage.set("timeQueueList", {
await storage.set("urlQueueList", { timeQueueList: timeQueueListSave.filter((item: any) => item),
urlQueueList: urlQueueListToSave.filter((item: any) => item) });
}) }
await storage.set("timeQueueList", { });
timeQueueList: timeQueueListSave.filter((item: any) => item)
})
}
})

View file

@ -1,149 +1,150 @@
import type { PlasmoMessaging } from "@plasmohq/messaging" import type { PlasmoMessaging } from "@plasmohq/messaging";
import { Storage } from "@plasmohq/storage" import { Storage } from "@plasmohq/storage";
import { import { emptyArr, webhistoryToLangChainDocument } from "~utils/commons";
emptyArr,
webhistoryToLangChainDocument
} from "~utils/commons"
const clearMemory = async () => { const clearMemory = async () => {
try { try {
const storage = new Storage({ area: "local" }) const storage = new Storage({ area: "local" });
let webHistory: any = await storage.get("webhistory") const webHistory: any = await storage.get("webhistory");
let urlQueue: any = await storage.get("urlQueueList") const urlQueue: any = await storage.get("urlQueueList");
let timeQueue: any = await storage.get("timeQueueList") const timeQueue: any = await storage.get("timeQueueList");
if (!webHistory.webhistory) { if (!webHistory.webhistory) {
return return;
} }
//Main Cleanup COde //Main Cleanup COde
chrome.tabs.query({}, async (tabs) => { chrome.tabs.query({}, async (tabs) => {
//Get Active Tabs Ids //Get Active Tabs Ids
// console.log("Event Tabs",tabs) // console.log("Event Tabs",tabs)
let actives = tabs.map((tab) => { let actives = tabs.map((tab) => {
if (tab.id) { if (tab.id) {
return tab.id return tab.id;
} }
}) });
actives = actives.filter((item: any) => item) actives = actives.filter((item: any) => item);
//Only retain which is still active //Only retain which is still active
const newHistory = webHistory.webhistory.map((element: any) => { const newHistory = webHistory.webhistory.map((element: any) => {
//@ts-ignore //@ts-ignore
if (actives.includes(element.tabsessionId)) { if (actives.includes(element.tabsessionId)) {
return element return element;
} }
}) });
const newUrlQueue = urlQueue.urlQueueList.map((element: any) => { const newUrlQueue = urlQueue.urlQueueList.map((element: any) => {
//@ts-ignore //@ts-ignore
if (actives.includes(element.tabsessionId)) { if (actives.includes(element.tabsessionId)) {
return element return element;
} }
}) });
const newTimeQueue = timeQueue.timeQueueList.map((element: any) => { const newTimeQueue = timeQueue.timeQueueList.map((element: any) => {
//@ts-ignore //@ts-ignore
if (actives.includes(element.tabsessionId)) { if (actives.includes(element.tabsessionId)) {
return element return element;
} }
}) });
await storage.set("webhistory", { await storage.set("webhistory", {
webhistory: newHistory.filter((item: any) => item) webhistory: newHistory.filter((item: any) => item),
}) });
await storage.set("urlQueueList", { await storage.set("urlQueueList", {
urlQueueList: newUrlQueue.filter((item: any) => item) urlQueueList: newUrlQueue.filter((item: any) => item),
}) });
await storage.set("timeQueueList", { await storage.set("timeQueueList", {
timeQueueList: newTimeQueue.filter((item: any) => item) timeQueueList: newTimeQueue.filter((item: any) => item),
}) });
}) });
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
} };
const handler: PlasmoMessaging.MessageHandler = async (req, res) => { const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
try { try {
const storage = new Storage({ area: "local" }) const storage = new Storage({ area: "local" });
const webhistoryObj: any = await storage.get("webhistory") const webhistoryObj: any = await storage.get("webhistory");
const webhistory = webhistoryObj.webhistory const webhistory = webhistoryObj.webhistory;
if (webhistory) { if (webhistory) {
let toSaveFinally: any[] = [] const toSaveFinally: any[] = [];
let newHistoryAfterCleanup: any[] = [] const newHistoryAfterCleanup: any[] = [];
for (let i = 0; i < webhistory.length; i++) { for (let i = 0; i < webhistory.length; i++) {
const markdownFormat = webhistoryToLangChainDocument( const markdownFormat = webhistoryToLangChainDocument(
webhistory[i].tabsessionId, webhistory[i].tabsessionId,
webhistory[i].tabHistory webhistory[i].tabHistory
) );
toSaveFinally.push(...markdownFormat) toSaveFinally.push(...markdownFormat);
newHistoryAfterCleanup.push({ newHistoryAfterCleanup.push({
tabsessionId: webhistory[i].tabsessionId, tabsessionId: webhistory[i].tabsessionId,
tabHistory: emptyArr tabHistory: emptyArr,
}) });
} }
await storage.set("webhistory",{ webhistory: newHistoryAfterCleanup }); await storage.set("webhistory", { webhistory: newHistoryAfterCleanup });
// Log first item to debug metadata structure // Log first item to debug metadata structure
if (toSaveFinally.length > 0) { if (toSaveFinally.length > 0) {
console.log("First item metadata:", toSaveFinally[0].metadata); console.log("First item metadata:", toSaveFinally[0].metadata);
} }
// Create content array for documents in the format expected by the new API // Create content array for documents in the format expected by the new API
const content = toSaveFinally.map(item => ({ const content = toSaveFinally.map((item) => ({
metadata: { metadata: {
BrowsingSessionId: String(item.metadata.BrowsingSessionId || ""), BrowsingSessionId: String(item.metadata.BrowsingSessionId || ""),
VisitedWebPageURL: String(item.metadata.VisitedWebPageURL || ""), VisitedWebPageURL: String(item.metadata.VisitedWebPageURL || ""),
VisitedWebPageTitle: String(item.metadata.VisitedWebPageTitle || "No Title"), VisitedWebPageTitle: String(item.metadata.VisitedWebPageTitle || "No Title"),
VisitedWebPageDateWithTimeInISOString: String(item.metadata.VisitedWebPageDateWithTimeInISOString || ""), VisitedWebPageDateWithTimeInISOString: String(
VisitedWebPageReffererURL: String(item.metadata.VisitedWebPageReffererURL || ""), item.metadata.VisitedWebPageDateWithTimeInISOString || ""
VisitedWebPageVisitDurationInMilliseconds: String(item.metadata.VisitedWebPageVisitDurationInMilliseconds || "0") ),
}, VisitedWebPageReffererURL: String(item.metadata.VisitedWebPageReffererURL || ""),
pageContent: String(item.pageContent || "") VisitedWebPageVisitDurationInMilliseconds: String(
})); item.metadata.VisitedWebPageVisitDurationInMilliseconds || "0"
),
},
pageContent: String(item.pageContent || ""),
}));
const token = await storage.get("token"); const token = await storage.get("token");
const search_space_id = parseInt(await storage.get("search_space_id"), 10); const search_space_id = parseInt(await storage.get("search_space_id"), 10);
const toSend = { const toSend = {
document_type: "EXTENSION", document_type: "EXTENSION",
content: content, content: content,
search_space_id: search_space_id search_space_id: search_space_id,
} };
console.log("toSend", toSend) console.log("toSend", toSend);
const requestOptions = { const requestOptions = {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": `Bearer ${token}` Authorization: `Bearer ${token}`,
}, },
body: JSON.stringify(toSend) body: JSON.stringify(toSend),
} };
const response = await fetch( const response = await fetch(
`${process.env.PLASMO_PUBLIC_BACKEND_URL}/api/v1/documents/`, `${process.env.PLASMO_PUBLIC_BACKEND_URL}/api/v1/documents/`,
requestOptions requestOptions
) );
const resp = await response.json() const resp = await response.json();
if (resp) { if (resp) {
await clearMemory() await clearMemory();
res.send({ res.send({
message: "Save Job Started" message: "Save Job Started",
}) });
} }
} }
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
} };
export default handler export default handler;

View file

@ -1,145 +1,142 @@
import { DOMParser } from "linkedom" import type { PlasmoMessaging } from "@plasmohq/messaging";
import { Storage } from "@plasmohq/storage" import { Storage } from "@plasmohq/storage";
import type { PlasmoMessaging } from "@plasmohq/messaging" import { convertHtmlToMarkdown } from "dom-to-semantic-markdown";
import { DOMParser } from "linkedom";
import type { WebHistory } from "~utils/interfaces" import { getRenderedHtml, webhistoryToLangChainDocument } from "~utils/commons";
import { webhistoryToLangChainDocument, getRenderedHtml } from "~utils/commons" import type { WebHistory } from "~utils/interfaces";
import { convertHtmlToMarkdown } from "dom-to-semantic-markdown"
// @ts-ignore // @ts-ignore
global.Node = { global.Node = {
ELEMENT_NODE: 1, ELEMENT_NODE: 1,
ATTRIBUTE_NODE: 2, ATTRIBUTE_NODE: 2,
TEXT_NODE: 3, TEXT_NODE: 3,
CDATA_SECTION_NODE: 4, CDATA_SECTION_NODE: 4,
PROCESSING_INSTRUCTION_NODE: 7, PROCESSING_INSTRUCTION_NODE: 7,
COMMENT_NODE: 8, COMMENT_NODE: 8,
DOCUMENT_NODE: 9, DOCUMENT_NODE: 9,
DOCUMENT_TYPE_NODE: 10, DOCUMENT_TYPE_NODE: 10,
DOCUMENT_FRAGMENT_NODE: 11, DOCUMENT_FRAGMENT_NODE: 11,
}; };
const handler: PlasmoMessaging.MessageHandler = async (req, res) => { const handler: PlasmoMessaging.MessageHandler = async (req, res) => {
try { try {
chrome.tabs.query( chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
{ active: true, currentWindow: true }, const storage = new Storage({ area: "local" });
async function (tabs) { const tab = tabs[0];
const storage = new Storage({ area: "local" }) if (tab.id) {
const tab = tabs[0] const tabId: number = tab.id;
if (tab.id) { console.log("tabs", tabs);
const tabId: number = tab.id const result = await chrome.scripting.executeScript({
console.log("tabs", tabs) // @ts-ignore
const result = await chrome.scripting.executeScript({ target: { tabId: tab.id },
// @ts-ignore // @ts-ignore
target: { tabId: tab.id }, func: getRenderedHtml,
// @ts-ignore // world: "MAIN"
func: getRenderedHtml, });
// world: "MAIN"
})
console.log("SnapRes", result) console.log("SnapRes", result);
let toPushInTabHistory: any = result[0].result // const { renderedHtml, title, url, entryTime } = result[0].result; const toPushInTabHistory: any = result[0].result; // const { renderedHtml, title, url, entryTime } = result[0].result;
toPushInTabHistory.pageContentMarkdown = convertHtmlToMarkdown( toPushInTabHistory.pageContentMarkdown = convertHtmlToMarkdown(
toPushInTabHistory.renderedHtml, toPushInTabHistory.renderedHtml,
{ {
extractMainContent: true, extractMainContent: true,
enableTableColumnTracking: true, enableTableColumnTracking: true,
includeMetaData: false, includeMetaData: false,
overrideDOMParser: new DOMParser() overrideDOMParser: new DOMParser(),
} }
) );
delete toPushInTabHistory.renderedHtml delete toPushInTabHistory.renderedHtml;
console.log("toPushInTabHistory", toPushInTabHistory) console.log("toPushInTabHistory", toPushInTabHistory);
const urlQueueListObj: any = await storage.get("urlQueueList") const urlQueueListObj: any = await storage.get("urlQueueList");
const timeQueueListObj: any = await storage.get("timeQueueList") const timeQueueListObj: any = await storage.get("timeQueueList");
const isUrlQueueThere = urlQueueListObj.urlQueueList.find( const isUrlQueueThere = urlQueueListObj.urlQueueList.find(
(data: WebHistory) => data.tabsessionId === tabId (data: WebHistory) => data.tabsessionId === tabId
) );
const isTimeQueueThere = timeQueueListObj.timeQueueList.find( const isTimeQueueThere = timeQueueListObj.timeQueueList.find(
(data: WebHistory) => data.tabsessionId === tabId (data: WebHistory) => data.tabsessionId === tabId
) );
toPushInTabHistory.duration = toPushInTabHistory.duration =
toPushInTabHistory.entryTime - toPushInTabHistory.entryTime -
isTimeQueueThere.timeQueue[isTimeQueueThere.timeQueue.length - 1] isTimeQueueThere.timeQueue[isTimeQueueThere.timeQueue.length - 1];
if (isUrlQueueThere.urlQueue.length == 1) { if (isUrlQueueThere.urlQueue.length === 1) {
toPushInTabHistory.reffererUrl = "START" toPushInTabHistory.reffererUrl = "START";
} }
if (isUrlQueueThere.urlQueue.length > 1) { if (isUrlQueueThere.urlQueue.length > 1) {
toPushInTabHistory.reffererUrl = toPushInTabHistory.reffererUrl =
isUrlQueueThere.urlQueue[isUrlQueueThere.urlQueue.length - 2] isUrlQueueThere.urlQueue[isUrlQueueThere.urlQueue.length - 2];
} }
let toSaveFinally: any[] = [] const toSaveFinally: any[] = [];
const markdownFormat = webhistoryToLangChainDocument( const markdownFormat = webhistoryToLangChainDocument(tab.id, [toPushInTabHistory]);
tab.id, toSaveFinally.push(...markdownFormat);
[toPushInTabHistory]
)
toSaveFinally.push(...markdownFormat)
console.log("toSaveFinally", toSaveFinally)
// Log first item to debug metadata structure console.log("toSaveFinally", toSaveFinally);
if (toSaveFinally.length > 0) {
console.log("First item metadata:", toSaveFinally[0].metadata);
}
// Create content array for documents in the format expected by the new API // Log first item to debug metadata structure
// The metadata is already in the correct format in toSaveFinally if (toSaveFinally.length > 0) {
const content = toSaveFinally.map(item => ({ console.log("First item metadata:", toSaveFinally[0].metadata);
metadata: { }
BrowsingSessionId: String(item.metadata.BrowsingSessionId || ""),
VisitedWebPageURL: String(item.metadata.VisitedWebPageURL || ""),
VisitedWebPageTitle: String(item.metadata.VisitedWebPageTitle || "No Title"),
VisitedWebPageDateWithTimeInISOString: String(item.metadata.VisitedWebPageDateWithTimeInISOString || ""),
VisitedWebPageReffererURL: String(item.metadata.VisitedWebPageReffererURL || ""),
VisitedWebPageVisitDurationInMilliseconds: String(item.metadata.VisitedWebPageVisitDurationInMilliseconds || "0")
},
pageContent: String(item.pageContent || "")
}));
const token = await storage.get("token"); // Create content array for documents in the format expected by the new API
const search_space_id = parseInt(await storage.get("search_space_id"), 10); // The metadata is already in the correct format in toSaveFinally
const content = toSaveFinally.map((item) => ({
metadata: {
BrowsingSessionId: String(item.metadata.BrowsingSessionId || ""),
VisitedWebPageURL: String(item.metadata.VisitedWebPageURL || ""),
VisitedWebPageTitle: String(item.metadata.VisitedWebPageTitle || "No Title"),
VisitedWebPageDateWithTimeInISOString: String(
item.metadata.VisitedWebPageDateWithTimeInISOString || ""
),
VisitedWebPageReffererURL: String(item.metadata.VisitedWebPageReffererURL || ""),
VisitedWebPageVisitDurationInMilliseconds: String(
item.metadata.VisitedWebPageVisitDurationInMilliseconds || "0"
),
},
pageContent: String(item.pageContent || ""),
}));
const toSend = { const token = await storage.get("token");
document_type: "EXTENSION", const search_space_id = parseInt(await storage.get("search_space_id"), 10);
content: content,
search_space_id: search_space_id
}
const requestOptions = { const toSend = {
method: "POST", document_type: "EXTENSION",
headers: { content: content,
"Content-Type": "application/json", search_space_id: search_space_id,
"Authorization": `Bearer ${token}` };
},
body: JSON.stringify(toSend)
}
const response = await fetch( const requestOptions = {
`${process.env.PLASMO_PUBLIC_BACKEND_URL}/api/v1/documents/`, method: "POST",
requestOptions headers: {
) "Content-Type": "application/json",
const resp = await response.json() Authorization: `Bearer ${token}`,
if (resp) { },
res.send({ body: JSON.stringify(toSend),
message: "Snapshot Saved Successfully" };
})
}
}
}
)
} catch (error) {
console.log(error)
}
}
export default handler const response = await fetch(
`${process.env.PLASMO_PUBLIC_BACKEND_URL}/api/v1/documents/`,
requestOptions
);
const resp = await response.json();
if (resp) {
res.send({
message: "Snapshot Saved Successfully",
});
}
}
});
} catch (error) {
console.log(error);
}
};
export default handler;

View file

@ -0,0 +1,115 @@
{
"$schema": "https://biomejs.dev/schemas/2.1.2/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": true,
"experimentalScannerIgnores": ["node_modules", ".git", ".next", "dist", "build", "coverage"],
"maxSize": 1048576
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 2,
"lineWidth": 100,
"lineEnding": "lf",
"formatWithErrors": false
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noExplicitAny": "warn",
"noArrayIndexKey": "warn"
},
"style": {
"useConst": "error",
"useTemplate": "warn"
},
"correctness": {
"useExhaustiveDependencies": "warn"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"jsxQuoteStyle": "double",
"quoteProperties": "asNeeded",
"trailingCommas": "es5",
"semicolons": "always",
"arrowParentheses": "always",
"bracketSameLine": false,
"bracketSpacing": true
},
"linter": {
"enabled": true
},
"assist": {
"enabled": true
}
},
"json": {
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 2,
"lineWidth": 100
},
"linter": {
"enabled": true
}
},
"css": {
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 2,
"lineWidth": 100,
"quoteStyle": "double"
},
"linter": {
"enabled": true
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
},
"overrides": [
{
"includes": ["*.json", "*.jsonc"],
"json": {
"parser": {
"allowComments": true,
"allowTrailingCommas": false
}
}
},
{
"includes": [".vscode/**/*.json"],
"json": {
"parser": {
"allowComments": true,
"allowTrailingCommas": true
}
}
},
{
"includes": ["**/*.config.*", "**/next.config.*"],
"javascript": {
"formatter": {
"semicolons": "always"
}
}
}
]
}

View file

@ -1,8 +1,7 @@
import type { PlasmoCSConfig } from "plasmo" import type { PlasmoCSConfig } from "plasmo";
export const config: PlasmoCSConfig = {
matches: ["<all_urls>"],
all_frames: true,
world: "MAIN"
}
export const config: PlasmoCSConfig = {
matches: ["<all_urls>"],
all_frames: true,
world: "MAIN",
};

View file

@ -1,10 +1,11 @@
@font-face { @font-face {
font-family: "Fascinate"; font-family: "Fascinate";
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
font-display: swap; font-display: swap;
src: url(data-base64:~assets/Fascinate.woff2) format("woff2"); src: url(data-base64:~assets/Fascinate.woff2) format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, unicode-range:
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
U+FEFF, U+FFFD; U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
} U+FEFF, U+FFFD;
}

View file

@ -1,6 +1,6 @@
import { clsx, type ClassValue } from "clsx" import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge" import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs));
} }

View file

@ -1,62 +1,62 @@
{ {
"name": "surfsense_browser_extension", "name": "surfsense_browser_extension",
"displayName": "Surfsense Browser Extension", "displayName": "Surfsense Browser Extension",
"version": "0.0.7", "version": "0.0.7",
"description": "Extension to collect Browsing History for SurfSense.", "description": "Extension to collect Browsing History for SurfSense.",
"author": "https://github.com/MODSetter", "author": "https://github.com/MODSetter",
"scripts": { "scripts": {
"dev": "plasmo dev", "dev": "plasmo dev",
"build": "plasmo build", "build": "plasmo build",
"package": "plasmo package" "package": "plasmo package"
}, },
"dependencies": { "dependencies": {
"@plasmohq/messaging": "^0.6.2", "@plasmohq/messaging": "^0.6.2",
"@plasmohq/storage": "^1.11.0", "@plasmohq/storage": "^1.11.0",
"@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-slot": "^1.1.0",
"class-variance-authority": "^0.7.0", "@radix-ui/react-toast": "^1.2.2",
"clsx": "^2.1.1", "class-variance-authority": "^0.7.0",
"cmdk": "^1.0.3", "clsx": "^2.1.1",
"dom-to-semantic-markdown": "^1.2.11", "cmdk": "^1.0.3",
"linkedom": "0.1.34", "dom-to-semantic-markdown": "^1.2.11",
"lucide-react": "^0.454.0", "linkedom": "0.1.34",
"plasmo": "0.89.4", "lucide-react": "^0.454.0",
"postcss-loader": "^8.1.1", "plasmo": "0.89.4",
"radix-ui": "^1.0.1", "postcss-loader": "^8.1.1",
"react": "18.2.0", "radix-ui": "^1.0.1",
"react-dom": "18.2.0", "react": "18.2.0",
"react-hooks-global-state": "^2.1.0", "react-dom": "18.2.0",
"react-router-dom": "^6.26.1", "react-hooks-global-state": "^2.1.0",
"tailwind-merge": "^2.5.4", "react-router-dom": "^6.26.1",
"tailwindcss-animate": "^1.0.7" "tailwind-merge": "^2.5.4",
}, "tailwindcss-animate": "^1.0.7"
"devDependencies": { },
"@ianvs/prettier-plugin-sort-imports": "4.1.1", "devDependencies": {
"@types/chrome": "0.0.258", "@biomejs/biome": "2.1.2",
"@types/node": "20.11.5", "@types/chrome": "0.0.258",
"@types/react": "18.2.48", "@types/node": "20.11.5",
"@types/react-dom": "18.2.18", "@types/react": "18.2.48",
"autoprefixer": "^10.4.20", "@types/react-dom": "18.2.18",
"postcss": "^8.4.41", "autoprefixer": "^10.4.20",
"prettier": "3.2.4", "postcss": "^8.4.41",
"tailwindcss": "^3.4.10", "tailwindcss": "^3.4.10",
"typescript": "5.3.3" "typescript": "5.3.3"
}, },
"manifest": { "manifest": {
"host_permissions": [ "host_permissions": [
"<all_urls>" "<all_urls>"
], ],
"name": "SurfSense", "name": "SurfSense",
"description": "Extension to collect Browsing History for SurfSense.", "description": "Extension to collect Browsing History for SurfSense.",
"version": "0.0.3" "version": "0.0.3"
}, },
"permissions": [ "permissions": [
"storage", "storage",
"scripting", "scripting",
"unlimitedStorage", "unlimitedStorage",
"activeTab" "activeTab"
] ]
} }

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,14 @@
import { MemoryRouter } from "react-router-dom" import { MemoryRouter } from "react-router-dom";
import { Toaster } from "@/routes/ui/toaster";
import { Routing } from "~routes" import { Routing } from "~routes";
import { Toaster } from "@/routes/ui/toaster"
function IndexPopup() { function IndexPopup() {
return ( return (
<MemoryRouter> <MemoryRouter>
<Routing /> <Routing />
<Toaster /> <Toaster />
</MemoryRouter> </MemoryRouter>
) );
} }
export default IndexPopup export default IndexPopup;

View file

@ -1,6 +1,6 @@
module.exports = { module.exports = {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
}; };

View file

@ -1,13 +1,12 @@
import { Route, Routes } from "react-router-dom" import { Route, Routes } from "react-router-dom";
import ApiKeyForm from "./pages/ApiKeyForm"
import HomePage from "./pages/HomePage"
import '../tailwind.css'
import ApiKeyForm from "./pages/ApiKeyForm";
import HomePage from "./pages/HomePage";
import "../tailwind.css";
export const Routing = () => ( export const Routing = () => (
<Routes> <Routes>
<Route path="/" element={<HomePage />} /> <Route path="/" element={<HomePage />} />
<Route path="/login" element={<ApiKeyForm />} /> <Route path="/login" element={<ApiKeyForm />} />
</Routes> </Routes>
) );

View file

@ -1,123 +1,122 @@
import React, { useState } from "react"; import icon from "data-base64:~assets/icon.png";
import { useNavigate } from "react-router-dom" import { Storage } from "@plasmohq/storage";
import icon from "data-base64:~assets/icon.png" import { ReloadIcon } from "@radix-ui/react-icons";
import { Storage } from "@plasmohq/storage" import { useState } from "react";
import { Button } from "~/routes/ui/button" import { useNavigate } from "react-router-dom";
import { ReloadIcon } from "@radix-ui/react-icons" import { Button } from "~/routes/ui/button";
const ApiKeyForm = () => { const ApiKeyForm = () => {
const navigation = useNavigate() const navigation = useNavigate();
const [apiKey, setApiKey] = useState(''); const [apiKey, setApiKey] = useState("");
const [error, setError] = useState(''); const [error, setError] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const storage = new Storage({ area: "local" }) const storage = new Storage({ area: "local" });
const validateForm = () => { const validateForm = () => {
if (!apiKey) { if (!apiKey) {
setError('API key is required'); setError("API key is required");
return false; return false;
} }
setError(''); setError("");
return true; return true;
}; };
const handleSubmit = async (event: { preventDefault: () => void; }) => { const handleSubmit = async (event: { preventDefault: () => void }) => {
event.preventDefault(); event.preventDefault();
if (!validateForm()) return; if (!validateForm()) return;
setLoading(true); setLoading(true);
try { try {
// Verify token is valid by making a request to the API // Verify token is valid by making a request to the API
const response = await fetch(`${process.env.PLASMO_PUBLIC_BACKEND_URL}/verify-token`, { const response = await fetch(`${process.env.PLASMO_PUBLIC_BACKEND_URL}/verify-token`, {
method: 'GET', method: "GET",
headers: { headers: {
'Authorization': `Bearer ${apiKey}`, Authorization: `Bearer ${apiKey}`,
} },
}); });
setLoading(false); setLoading(false);
if (response.ok) { if (response.ok) {
// Store the API key as the token // Store the API key as the token
await storage.set('token', apiKey); await storage.set("token", apiKey);
navigation("/") navigation("/");
} else { } else {
setError('Invalid API key. Please check and try again.'); setError("Invalid API key. Please check and try again.");
} }
} catch (error) { } catch (error) {
setLoading(false); setLoading(false);
setError('An error occurred. Please try again later.'); setError("An error occurred. Please try again later.");
} }
}; };
return ( return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 flex flex-col items-center justify-center p-6"> <div className="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800 flex flex-col items-center justify-center p-6">
<div className="w-full max-w-md mx-auto space-y-8"> <div className="w-full max-w-md mx-auto space-y-8">
<div className="flex flex-col items-center space-y-2"> <div className="flex flex-col items-center space-y-2">
<div className="bg-gray-800 p-3 rounded-full ring-2 ring-gray-700 shadow-lg"> <div className="bg-gray-800 p-3 rounded-full ring-2 ring-gray-700 shadow-lg">
<img className="w-12 h-12" src={icon} alt="SurfSense" /> <img className="w-12 h-12" src={icon} alt="SurfSense" />
</div> </div>
<h1 className="text-3xl font-semibold tracking-tight text-white mt-4">SurfSense</h1> <h1 className="text-3xl font-semibold tracking-tight text-white mt-4">SurfSense</h1>
</div> </div>
<div className="bg-gray-800/70 backdrop-blur-sm rounded-xl shadow-xl border border-gray-700 p-6"> <div className="bg-gray-800/70 backdrop-blur-sm rounded-xl shadow-xl border border-gray-700 p-6">
<div className="space-y-6"> <div className="space-y-6">
<h2 className="text-xl font-medium text-white">Enter your API Key</h2> <h2 className="text-xl font-medium text-white">Enter your API Key</h2>
<p className="text-gray-400 text-sm"> <p className="text-gray-400 text-sm">
Your API key connects this extension to the SurfSense. Your API key connects this extension to the SurfSense.
</p> </p>
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<label htmlFor="apiKey" className="text-sm font-medium text-gray-300"> <label htmlFor="apiKey" className="text-sm font-medium text-gray-300">
API Key API Key
</label> </label>
<input <input
type="text" type="text"
id="apiKey" id="apiKey"
value={apiKey} value={apiKey}
onChange={(e) => setApiKey(e.target.value)} onChange={(e) => setApiKey(e.target.value)}
className="w-full px-3 py-2 bg-gray-900/50 border border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-teal-500 text-white placeholder:text-gray-500" className="w-full px-3 py-2 bg-gray-900/50 border border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-teal-500 text-white placeholder:text-gray-500"
placeholder="Enter your API key" placeholder="Enter your API key"
/> />
{error && ( {error && <p className="text-red-400 text-sm mt-1">{error}</p>}
<p className="text-red-400 text-sm mt-1">{error}</p> </div>
)}
</div>
<Button <Button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full bg-teal-600 hover:bg-teal-500 text-white py-2 px-4 rounded-md transition-colors" className="w-full bg-teal-600 hover:bg-teal-500 text-white py-2 px-4 rounded-md transition-colors"
> >
{loading ? ( {loading ? (
<> <>
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" /> <ReloadIcon className="mr-2 h-4 w-4 animate-spin" />
Verifying... Verifying...
</> </>
) : ( ) : (
"Connect" "Connect"
)} )}
</Button> </Button>
</form> </form>
<div className="text-center mt-4"> <div className="text-center mt-4">
<p className="text-sm text-gray-400"> <p className="text-sm text-gray-400">
Need an API key?{" "} Need an API key?{" "}
<a <a
href="https://www.surfsense.net" href="https://www.surfsense.net"
target="_blank" target="_blank"
className="text-teal-400 hover:text-teal-300 hover:underline" className="text-teal-400 hover:text-teal-300 hover:underline"
> rel="noopener"
Sign up >
</a> Sign up
</p> </a>
</div> </p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); </div>
} );
};
export default ApiKeyForm export default ApiKeyForm;

View file

@ -1,476 +1,478 @@
import React, { useEffect, useState } from "react"; import brain from "data-base64:~assets/brain.png";
import { useNavigate } from "react-router-dom" import icon from "data-base64:~assets/icon.png";
import icon from "data-base64:~assets/icon.png" import { sendToBackground } from "@plasmohq/messaging";
import { Storage } from "@plasmohq/storage";
import {
CrossCircledIcon,
DiscIcon,
ExitIcon,
FileIcon,
ReloadIcon,
UploadIcon,
} from "@radix-ui/react-icons";
import { convertHtmlToMarkdown } from "dom-to-semantic-markdown"; import { convertHtmlToMarkdown } from "dom-to-semantic-markdown";
import type { WebHistory } from "~utils/interfaces"; import { Check, ChevronsUpDown } from "lucide-react";
import { getRenderedHtml } from "~utils/commons"; import React, { useEffect, useState } from "react";
import Loading from "./Loading"; import { useNavigate } from "react-router-dom";
import brain from "data-base64:~assets/brain.png" import { cn } from "~/lib/utils";
import { Storage } from "@plasmohq/storage" import { Button } from "~/routes/ui/button";
import { sendToBackground } from "@plasmohq/messaging"
import { Check, ChevronsUpDown } from "lucide-react"
import { cn } from "~/lib/utils"
import { Button } from "~/routes/ui/button"
import { import {
Command, Command,
CommandEmpty, CommandEmpty,
CommandGroup, CommandGroup,
CommandInput, CommandInput,
CommandItem, CommandItem,
CommandList, CommandList,
} from "~/routes/ui/command" } from "~/routes/ui/command";
import { import { Popover, PopoverContent, PopoverTrigger } from "~/routes/ui/popover";
Popover, import { Label } from "~routes/ui/label";
PopoverContent,
PopoverTrigger,
} from "~/routes/ui/popover"
import { useToast } from "~routes/ui/use-toast"; import { useToast } from "~routes/ui/use-toast";
import { import { getRenderedHtml } from "~utils/commons";
CircleIcon, import type { WebHistory } from "~utils/interfaces";
CrossCircledIcon, import Loading from "./Loading";
DiscIcon,
ExitIcon,
FileIcon,
ReloadIcon,
ResetIcon,
UploadIcon
} from "@radix-ui/react-icons"
const HomePage = () => { const HomePage = () => {
const { toast } = useToast() const { toast } = useToast();
const navigation = useNavigate() const navigation = useNavigate();
const [noOfWebPages, setNoOfWebPages] = useState<number>(0); const [noOfWebPages, setNoOfWebPages] = useState<number>(0);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [open, setOpen] = React.useState(false) const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState<string>("") const [value, setValue] = React.useState<string>("");
const [searchspaces, setSearchSpaces] = useState([]) const [searchspaces, setSearchSpaces] = useState([]);
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
useEffect(() => { useEffect(() => {
const checkSearchSpaces = async () => { const checkSearchSpaces = async () => {
const storage = new Storage({ area: "local" }) const storage = new Storage({ area: "local" });
const token = await storage.get('token'); const token = await storage.get("token");
try { try {
const response = await fetch( const response = await fetch(
`${process.env.PLASMO_PUBLIC_BACKEND_URL}/api/v1/searchspaces/`, `${process.env.PLASMO_PUBLIC_BACKEND_URL}/api/v1/searchspaces/`,
{ {
headers: { headers: {
'Authorization': `Bearer ${token}` Authorization: `Bearer ${token}`,
} },
} }
); );
if (!response.ok) { if (!response.ok) {
throw new Error("Token verification failed"); throw new Error("Token verification failed");
} else { } else {
const res = await response.json() const res = await response.json();
console.log(res) console.log(res);
setSearchSpaces(res) setSearchSpaces(res);
} }
} catch (error) { } catch (error) {
await storage.remove('token'); await storage.remove("token");
await storage.remove('showShadowDom'); await storage.remove("showShadowDom");
navigation("/login") navigation("/login");
} }
}; };
checkSearchSpaces(); checkSearchSpaces();
setLoading(false); setLoading(false);
}, []); }, []);
useEffect(() => {
async function onLoad() {
try {
chrome.storage.onChanged.addListener((changes: any, areaName: string) => {
if (changes.webhistory) {
const webhistory = JSON.parse(changes.webhistory.newValue);
console.log("webhistory", webhistory);
useEffect(() => { let sum = 0;
async function onLoad() { webhistory.webhistory.forEach((element: any) => {
try { sum = sum + element.tabHistory.length;
chrome.storage.onChanged.addListener( });
(changes: any, areaName: string) => {
if (changes.webhistory) {
const webhistory = JSON.parse(changes.webhistory.newValue);
console.log("webhistory", webhistory)
let sum = 0 setNoOfWebPages(sum);
webhistory.webhistory.forEach((element: any) => { }
sum = sum + element.tabHistory.length });
});
setNoOfWebPages(sum) const storage = new Storage({ area: "local" });
} const searchspace = await storage.get("search_space");
}
);
const storage = new Storage({ area: "local" }) if (searchspace) {
const searchspace = await storage.get("search_space"); setValue(searchspace);
}
if(searchspace){ await storage.set("showShadowDom", true);
setValue(searchspace)
}
await storage.set("showShadowDom", true) const webhistoryObj: any = await storage.get("webhistory");
if (webhistoryObj.webhistory.length) {
const webhistory = webhistoryObj.webhistory;
const webhistoryObj: any = await storage.get("webhistory"); if (webhistoryObj) {
if (webhistoryObj.webhistory.length) { let sum = 0;
const webhistory = webhistoryObj.webhistory; webhistory.forEach((element: any) => {
sum = sum + element.tabHistory.length;
});
setNoOfWebPages(sum);
}
} else {
setNoOfWebPages(0);
}
} catch (error) {
console.log(error);
}
}
if (webhistoryObj) { onLoad();
let sum = 0 }, []);
webhistory.forEach((element: any) => {
sum = sum + element.tabHistory.length
});
setNoOfWebPages(sum)
}
} else {
setNoOfWebPages(0)
}
} catch (error) {
console.log(error);
}
}
onLoad() async function clearMem(): Promise<void> {
}, []); try {
const storage = new Storage({ area: "local" });
async function clearMem(): Promise<void> { const webHistory: any = await storage.get("webhistory");
try { const urlQueue: any = await storage.get("urlQueueList");
const storage = new Storage({ area: "local" }) const timeQueue: any = await storage.get("timeQueueList");
let webHistory: any = await storage.get("webhistory");
let urlQueue: any = await storage.get("urlQueueList");
let timeQueue: any = await storage.get("timeQueueList");
if (!webHistory.webhistory) {
return
}
//Main Cleanup COde
chrome.tabs.query({}, async (tabs) => {
//Get Active Tabs Ids
let actives = tabs.map((tab) => {
if (tab.id) {
return tab.id
}
})
actives = actives.filter((item: any) => item)
//Only retain which is still active
const newHistory = webHistory.webhistory.map((element: any) => {
//@ts-ignore
if (actives.includes(element.tabsessionId)) {
return element
}
})
const newUrlQueue = urlQueue.urlQueueList.map((element: any) => {
//@ts-ignore
if (actives.includes(element.tabsessionId)) {
return element
}
})
const newTimeQueue = timeQueue.timeQueueList.map((element: any) => {
//@ts-ignore
if (actives.includes(element.tabsessionId)) {
return element
}
})
await storage.set("webhistory", { webhistory: newHistory.filter((item: any) => item) });
await storage.set("urlQueueList", { urlQueueList: newUrlQueue.filter((item: any) => item) });
await storage.set("timeQueueList", { timeQueueList: newTimeQueue.filter((item: any) => item) });
toast({
title: "History store cleared",
description: "Inactive history sessions have been removed",
variant: "destructive",
})
});
} catch (error) {
console.log(error);
}
}
async function saveCurrSnapShot(): Promise<void> { if (!webHistory.webhistory) {
chrome.tabs.query({ active: true, currentWindow: true }, async function (tabs) { return;
const storage = new Storage({ area: "local" }) }
const tab = tabs[0];
if (tab.id) {
const tabId: number = tab.id
const result = await chrome.scripting.executeScript({
// @ts-ignore
target: { tabId: tab.id },
// @ts-ignore
func: getRenderedHtml,
});
let toPushInTabHistory: any = result[0].result; //Main Cleanup COde
chrome.tabs.query({}, async (tabs) => {
//Get Active Tabs Ids
let actives = tabs.map((tab) => {
if (tab.id) {
return tab.id;
}
});
//Updates 'tabhistory' actives = actives.filter((item: any) => item);
let webhistoryObj: any = await storage.get("webhistory");
const webHistoryOfTabId = webhistoryObj.webhistory.filter( //Only retain which is still active
(data: WebHistory) => { const newHistory = webHistory.webhistory.map((element: any) => {
return data.tabsessionId === tab.id; //@ts-ignore
} if (actives.includes(element.tabsessionId)) {
); return element;
}
});
toPushInTabHistory.pageContentMarkdown = convertHtmlToMarkdown( const newUrlQueue = urlQueue.urlQueueList.map((element: any) => {
toPushInTabHistory.renderedHtml, //@ts-ignore
{ if (actives.includes(element.tabsessionId)) {
extractMainContent: true, return element;
includeMetaData: false, }
enableTableColumnTracking: true });
}
)
delete toPushInTabHistory.renderedHtml const newTimeQueue = timeQueue.timeQueueList.map((element: any) => {
//@ts-ignore
if (actives.includes(element.tabsessionId)) {
return element;
}
});
let tabhistory = webHistoryOfTabId[0].tabHistory; await storage.set("webhistory", { webhistory: newHistory.filter((item: any) => item) });
await storage.set("urlQueueList", {
urlQueueList: newUrlQueue.filter((item: any) => item),
});
await storage.set("timeQueueList", {
timeQueueList: newTimeQueue.filter((item: any) => item),
});
toast({
title: "History store cleared",
description: "Inactive history sessions have been removed",
variant: "destructive",
});
});
} catch (error) {
console.log(error);
}
}
const urlQueueListObj: any = await storage.get("urlQueueList"); async function saveCurrSnapShot(): Promise<void> {
const timeQueueListObj: any = await storage.get("timeQueueList"); chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
const storage = new Storage({ area: "local" });
const tab = tabs[0];
if (tab.id) {
const tabId: number = tab.id;
const result = await chrome.scripting.executeScript({
// @ts-ignore
target: { tabId: tab.id },
// @ts-ignore
func: getRenderedHtml,
});
const isUrlQueueThere = urlQueueListObj.urlQueueList.find((data: WebHistory) => data.tabsessionId === tabId) const toPushInTabHistory: any = result[0].result;
const isTimeQueueThere = timeQueueListObj.timeQueueList.find((data: WebHistory) => data.tabsessionId === tabId)
toPushInTabHistory.duration = toPushInTabHistory.entryTime - isTimeQueueThere.timeQueue[isTimeQueueThere.timeQueue.length - 1] //Updates 'tabhistory'
if (isUrlQueueThere.urlQueue.length == 1) { const webhistoryObj: any = await storage.get("webhistory");
toPushInTabHistory.reffererUrl = 'START'
}
if (isUrlQueueThere.urlQueue.length > 1) {
toPushInTabHistory.reffererUrl = isUrlQueueThere.urlQueue[isUrlQueueThere.urlQueue.length - 2];
}
webHistoryOfTabId[0].tabHistory.push(toPushInTabHistory); const webHistoryOfTabId = webhistoryObj.webhistory.filter((data: WebHistory) => {
return data.tabsessionId === tab.id;
await storage.set("webhistory", webhistoryObj); });
toast({
title: "Snapshot saved",
description: `Captured: ${toPushInTabHistory.title}`,
})
}
}); toPushInTabHistory.pageContentMarkdown = convertHtmlToMarkdown(
} toPushInTabHistory.renderedHtml,
{
extractMainContent: true,
includeMetaData: false,
enableTableColumnTracking: true,
}
);
const saveDatamessage = async () => { delete toPushInTabHistory.renderedHtml;
if (value === "") {
toast({
title: "Select a SearchSpace !",
})
return
}
const storage = new Storage({ area: "local" })
const search_space_id = await storage.get("search_space_id");
if (!search_space_id) {
toast({
title: "Invalid SearchSpace selected!",
variant: "destructive",
})
return
}
setIsSaving(true); const tabhistory = webHistoryOfTabId[0].tabHistory;
toast({
title: "Save job running",
description: "Saving captured content to SurfSense",
})
try { const urlQueueListObj: any = await storage.get("urlQueueList");
const resp = await sendToBackground({ const timeQueueListObj: any = await storage.get("timeQueueList");
// @ts-ignore
name: "savedata",
})
toast({ const isUrlQueueThere = urlQueueListObj.urlQueueList.find(
title: resp.message, (data: WebHistory) => data.tabsessionId === tabId
}) );
} catch (error) { const isTimeQueueThere = timeQueueListObj.timeQueueList.find(
toast({ (data: WebHistory) => data.tabsessionId === tabId
title: "Error saving data", );
description: "Please try again",
variant: "destructive",
})
} finally {
setIsSaving(false);
}
}
async function logOut(): Promise<void> { toPushInTabHistory.duration =
const storage = new Storage({ area: "local" }) toPushInTabHistory.entryTime -
await storage.remove('token'); isTimeQueueThere.timeQueue[isTimeQueueThere.timeQueue.length - 1];
await storage.remove('showShadowDom'); if (isUrlQueueThere.urlQueue.length === 1) {
navigation("/login") toPushInTabHistory.reffererUrl = "START";
} }
if (isUrlQueueThere.urlQueue.length > 1) {
toPushInTabHistory.reffererUrl =
isUrlQueueThere.urlQueue[isUrlQueueThere.urlQueue.length - 2];
}
if (loading) { webHistoryOfTabId[0].tabHistory.push(toPushInTabHistory);
return <Loading />;
} else {
return searchspaces.length === 0 ? (
<div className="flex min-h-screen flex-col bg-gradient-to-br from-gray-900 to-gray-800">
<div className="flex flex-1 items-center justify-center p-4">
<div className="w-full max-w-md space-y-8">
<div className="flex flex-col items-center space-y-2 text-center">
<div className="rounded-full bg-gray-800 p-3 shadow-lg ring-2 ring-gray-700">
<img className="h-12 w-12" src={icon} alt="SurfSense" />
</div>
<h1 className="mt-4 text-3xl font-semibold tracking-tight text-white">SurfSense</h1>
<div className="mt-4 rounded-lg border border-yellow-500/20 bg-yellow-500/10 p-4 text-yellow-300">
<p className="text-sm">Please create a Search Space to continue</p>
</div>
</div>
<div className="mt-6 flex justify-center">
<Button
onClick={logOut}
variant="outline"
className="flex items-center space-x-2 border-gray-700 bg-gray-800 text-gray-200 hover:bg-gray-700"
>
<ExitIcon className="h-4 w-4" />
<span>Sign Out</span>
</Button>
</div>
</div>
</div>
</div>
) : (
<div className="flex min-h-screen flex-col bg-gradient-to-br from-gray-900 to-gray-800">
<div className="container mx-auto max-w-md p-4">
<div className="flex items-center justify-between border-b border-gray-700 pb-4">
<div className="flex items-center space-x-3">
<div className="rounded-full bg-gray-800 p-2 shadow-md ring-1 ring-gray-700">
<img className="h-6 w-6" src={icon} alt="SurfSense" />
</div>
<h1 className="text-xl font-semibold text-white">SurfSense</h1>
</div>
<Button
variant="ghost"
size="icon"
onClick={logOut}
className="rounded-full text-gray-400 hover:bg-gray-800 hover:text-white"
>
<ExitIcon className="h-4 w-4" />
<span className="sr-only">Log out</span>
</Button>
</div>
<div className="space-y-3 py-4"> await storage.set("webhistory", webhistoryObj);
<div className="flex flex-col items-center justify-center rounded-lg border border-gray-700 bg-gray-800/50 p-6 backdrop-blur-sm">
<div className="flex h-28 w-28 items-center justify-center rounded-full bg-gradient-to-br from-gray-700 to-gray-800 shadow-inner">
<div className="flex flex-col items-center">
<img className="mb-2 h-10 w-10 opacity-80" src={brain} alt="brain" />
<span className="text-2xl font-semibold text-white">{noOfWebPages}</span>
</div>
</div>
<p className="mt-4 text-sm text-gray-400">Captured web pages</p>
</div>
<div className="rounded-lg border border-gray-700 bg-gray-800/50 p-4 backdrop-blur-sm"> toast({
<label className="mb-2 block text-sm font-medium text-gray-300"> title: "Snapshot saved",
Search Space description: `Captured: ${toPushInTabHistory.title}`,
</label> });
<Popover open={open} onOpenChange={setOpen}> }
<PopoverTrigger asChild> });
<Button }
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between border-gray-700 bg-gray-900 text-white hover:bg-gray-700"
>
{value
? searchspaces.find((space) => space.name === value)?.name
: "Select Search Space..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full border-gray-700 bg-gray-800/90 p-0 backdrop-blur-sm">
<Command className="bg-transparent">
<CommandInput placeholder="Search spaces..." className="border-gray-700 bg-gray-900 text-gray-200" />
<CommandList>
<CommandEmpty>No search spaces found.</CommandEmpty>
<CommandGroup>
{searchspaces.map((space) => (
<CommandItem
key={space.name}
value={space.name}
onSelect={async (currentValue) => {
const storage = new Storage({ area: "local" })
if (currentValue === value) {
await storage.set("search_space", "");
await storage.set("search_space_id", 0);
} else {
const selectedSpace = searchspaces.find((space) => space.name === currentValue);
await storage.set("search_space", currentValue);
await storage.set("search_space_id", selectedSpace.id);
}
setValue(currentValue === value ? "" : currentValue)
setOpen(false)
}}
className="aria-selected:bg-gray-700"
>
<Check
className={cn(
"mr-2 h-4 w-4",
value === space.name ? "opacity-100" : "opacity-0"
)}
/>
<div className="flex items-center">
<DiscIcon className="mr-2 h-4 w-4 text-teal-400" />
{space.name}
</div>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="grid gap-3"> const saveDatamessage = async () => {
<Button if (value === "") {
variant="destructive" toast({
className="group flex w-full items-center justify-center space-x-2 bg-red-500/90 text-white hover:bg-red-600" title: "Select a SearchSpace !",
onClick={() => clearMem()} });
> return;
<CrossCircledIcon className="h-4 w-4 transition-transform group-hover:scale-110" /> }
<span>Clear Inactive History</span>
</Button> const storage = new Storage({ area: "local" });
const search_space_id = await storage.get("search_space_id");
<Button
variant="outline" if (!search_space_id) {
className="group flex w-full items-center justify-center space-x-2 border-amber-500/50 bg-amber-500/10 text-amber-200 hover:bg-amber-500/20" toast({
onClick={() => saveCurrSnapShot()} title: "Invalid SearchSpace selected!",
> variant: "destructive",
<FileIcon className="h-4 w-4 transition-transform group-hover:scale-110" /> });
<span>Save Current Page</span> return;
</Button> }
<Button setIsSaving(true);
variant="default" toast({
className="group flex w-full items-center justify-center space-x-2 bg-gradient-to-r from-teal-500 to-emerald-500 text-white transition-all hover:from-teal-600 hover:to-emerald-600" title: "Save job running",
onClick={() => saveDatamessage()} description: "Saving captured content to SurfSense",
disabled={isSaving} });
>
{isSaving ? ( try {
<> const resp = await sendToBackground({
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" /> // @ts-ignore
<span>Saving to SurfSense...</span> name: "savedata",
</> });
) : (
<> toast({
<UploadIcon className="h-4 w-4 transition-transform group-hover:scale-110" /> title: resp.message,
<span>Save to SurfSense</span> });
</> } catch (error) {
)} toast({
</Button> title: "Error saving data",
</div> description: "Please try again",
</div> variant: "destructive",
</div> });
</div> } finally {
); setIsSaving(false);
} }
};
async function logOut(): Promise<void> {
const storage = new Storage({ area: "local" });
await storage.remove("token");
await storage.remove("showShadowDom");
navigation("/login");
}
if (loading) {
return <Loading />;
} else {
return searchspaces.length === 0 ? (
<div className="flex min-h-screen flex-col bg-gradient-to-br from-gray-900 to-gray-800">
<div className="flex flex-1 items-center justify-center p-4">
<div className="w-full max-w-md space-y-8">
<div className="flex flex-col items-center space-y-2 text-center">
<div className="rounded-full bg-gray-800 p-3 shadow-lg ring-2 ring-gray-700">
<img className="h-12 w-12" src={icon} alt="SurfSense" />
</div>
<h1 className="mt-4 text-3xl font-semibold tracking-tight text-white">SurfSense</h1>
<div className="mt-4 rounded-lg border border-yellow-500/20 bg-yellow-500/10 p-4 text-yellow-300">
<p className="text-sm">Please create a Search Space to continue</p>
</div>
</div>
<div className="mt-6 flex justify-center">
<Button
onClick={logOut}
variant="outline"
className="flex items-center space-x-2 border-gray-700 bg-gray-800 text-gray-200 hover:bg-gray-700"
>
<ExitIcon className="h-4 w-4" />
<span>Sign Out</span>
</Button>
</div>
</div>
</div>
</div>
) : (
<div className="flex min-h-screen flex-col bg-gradient-to-br from-gray-900 to-gray-800">
<div className="container mx-auto max-w-md p-4">
<div className="flex items-center justify-between border-b border-gray-700 pb-4">
<div className="flex items-center space-x-3">
<div className="rounded-full bg-gray-800 p-2 shadow-md ring-1 ring-gray-700">
<img className="h-6 w-6" src={icon} alt="SurfSense" />
</div>
<h1 className="text-xl font-semibold text-white">SurfSense</h1>
</div>
<Button
variant="ghost"
size="icon"
onClick={logOut}
className="rounded-full text-gray-400 hover:bg-gray-800 hover:text-white"
>
<ExitIcon className="h-4 w-4" />
<span className="sr-only">Log out</span>
</Button>
</div>
<div className="space-y-3 py-4">
<div className="flex flex-col items-center justify-center rounded-lg border border-gray-700 bg-gray-800/50 p-6 backdrop-blur-sm">
<div className="flex h-28 w-28 items-center justify-center rounded-full bg-gradient-to-br from-gray-700 to-gray-800 shadow-inner">
<div className="flex flex-col items-center">
<img className="mb-2 h-10 w-10 opacity-80" src={brain} alt="brain" />
<span className="text-2xl font-semibold text-white">{noOfWebPages}</span>
</div>
</div>
<p className="mt-4 text-sm text-gray-400">Captured web pages</p>
</div>
<div className="rounded-lg border border-gray-700 bg-gray-800/50 p-4 backdrop-blur-sm">
<Label className="mb-2 block text-sm font-medium text-gray-300">Search Space</Label>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
aria-expanded={open}
className="w-full justify-between border-gray-700 bg-gray-900 text-white hover:bg-gray-700"
>
{value
? searchspaces.find((space) => space.name === value)?.name
: "Select Search Space..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full border-gray-700 bg-gray-800/90 p-0 backdrop-blur-sm">
<Command className="bg-transparent">
<CommandInput
placeholder="Search spaces..."
className="border-gray-700 bg-gray-900 text-gray-200"
/>
<CommandList>
<CommandEmpty>No search spaces found.</CommandEmpty>
<CommandGroup>
{searchspaces.map((space) => (
<CommandItem
key={space.name}
value={space.name}
onSelect={async (currentValue) => {
const storage = new Storage({ area: "local" });
if (currentValue === value) {
await storage.set("search_space", "");
await storage.set("search_space_id", 0);
} else {
const selectedSpace = searchspaces.find(
(space) => space.name === currentValue
);
await storage.set("search_space", currentValue);
await storage.set("search_space_id", selectedSpace.id);
}
setValue(currentValue === value ? "" : currentValue);
setOpen(false);
}}
className="aria-selected:bg-gray-700"
>
<Check
className={cn(
"mr-2 h-4 w-4",
value === space.name ? "opacity-100" : "opacity-0"
)}
/>
<div className="flex items-center">
<DiscIcon className="mr-2 h-4 w-4 text-teal-400" />
{space.name}
</div>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
<div className="grid gap-3">
<Button
variant="destructive"
className="group flex w-full items-center justify-center space-x-2 bg-red-500/90 text-white hover:bg-red-600"
onClick={() => clearMem()}
>
<CrossCircledIcon className="h-4 w-4 transition-transform group-hover:scale-110" />
<span>Clear Inactive History</span>
</Button>
<Button
variant="outline"
className="group flex w-full items-center justify-center space-x-2 border-amber-500/50 bg-amber-500/10 text-amber-200 hover:bg-amber-500/20"
onClick={() => saveCurrSnapShot()}
>
<FileIcon className="h-4 w-4 transition-transform group-hover:scale-110" />
<span>Save Current Page</span>
</Button>
<Button
variant="default"
className="group flex w-full items-center justify-center space-x-2 bg-gradient-to-r from-teal-500 to-emerald-500 text-white transition-all hover:from-teal-600 hover:to-emerald-600"
onClick={() => saveDatamessage()}
disabled={isSaving}
>
{isSaving ? (
<>
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" />
<span>Saving to SurfSense...</span>
</>
) : (
<>
<UploadIcon className="h-4 w-4 transition-transform group-hover:scale-110" />
<span>Save to SurfSense</span>
</>
)}
</Button>
</div>
</div>
</div>
</div>
);
}
}; };
export default HomePage export default HomePage;

View file

@ -1,38 +1,37 @@
import React from 'react' import icon from "data-base64:~assets/icon.png";
import icon from "data-base64:~assets/icon.png" import { ReloadIcon } from "@radix-ui/react-icons";
import { ReloadIcon } from "@radix-ui/react-icons"
const Loading = () => { const Loading = () => {
return ( return (
<div className="min-h-screen flex flex-col items-center justify-center bg-gradient-to-br from-gray-900 to-gray-800"> <div className="min-h-screen flex flex-col items-center justify-center bg-gradient-to-br from-gray-900 to-gray-800">
<div className="w-full max-w-md mx-auto space-y-8"> <div className="w-full max-w-md mx-auto space-y-8">
<div className="flex flex-col items-center space-y-2"> <div className="flex flex-col items-center space-y-2">
<div className="bg-gray-800 p-3 rounded-full ring-2 ring-gray-700 shadow-lg"> <div className="bg-gray-800 p-3 rounded-full ring-2 ring-gray-700 shadow-lg">
<img className="w-12 h-12" src={icon} alt="SurfSense" /> <img className="w-12 h-12" src={icon} alt="SurfSense" />
</div> </div>
<h1 className="text-3xl font-semibold tracking-tight text-white mt-4">SurfSense</h1> <h1 className="text-3xl font-semibold tracking-tight text-white mt-4">SurfSense</h1>
</div> </div>
<div className="flex flex-col items-center mt-8">
<ReloadIcon className="h-10 w-10 text-teal-400 animate-spin" />
<div className="mt-6 text-lg text-gray-300 flex space-x-1">
{Array.from("LOADING").map((letter, i) => (
<span
key={i}
className="inline-block animate-pulse text-teal-400"
style={{
animationDelay: `${i * 0.1}s`,
animationDuration: '1.5s'
}}
>
{letter}
</span>
))}
</div>
</div>
</div>
</div>
)
}
export default Loading <div className="flex flex-col items-center mt-8">
<ReloadIcon className="h-10 w-10 text-teal-400 animate-spin" />
<div className="mt-6 text-lg text-gray-300 flex space-x-1">
{Array.from("LOADING").map((letter, i) => (
<span
key={i}
className="inline-block animate-pulse text-teal-400"
style={{
animationDelay: `${i * 0.1}s`,
animationDuration: "1.5s",
}}
>
{letter}
</span>
))}
</div>
</div>
</div>
</div>
);
};
export default Loading;

View file

@ -1,56 +1,49 @@
import * as React from "react" import { Slot } from "@radix-ui/react-slot";
import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from "class-variance-authority";
import { cva, type VariantProps } from "class-variance-authority" import * as React from "react";
import { cn } from "~/lib/utils" import { cn } from "~/lib/utils";
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90", default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
"bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
outline: secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
"border border-input bg-background hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-accent hover:text-accent-foreground",
secondary: link: "text-primary underline-offset-4 hover:underline",
"bg-secondary text-secondary-foreground hover:bg-secondary/80", },
ghost: "hover:bg-accent hover:text-accent-foreground", size: {
link: "text-primary underline-offset-4 hover:underline", default: "h-10 px-4 py-2",
}, sm: "h-9 rounded-md px-3",
size: { lg: "h-11 rounded-md px-8",
default: "h-10 px-4 py-2", icon: "h-10 w-10",
sm: "h-9 rounded-md px-3", },
lg: "h-11 rounded-md px-8", },
icon: "h-10 w-10", defaultVariants: {
}, variant: "default",
}, size: "default",
defaultVariants: { },
variant: "default", }
size: "default", );
},
}
)
export interface ButtonProps export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>, extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> { VariantProps<typeof buttonVariants> {
asChild?: boolean asChild?: boolean;
} }
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => { ({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : "button";
return ( return (
<Comp <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
className={cn(buttonVariants({ variant, size, className }))} );
ref={ref} }
{...props} );
/> Button.displayName = "Button";
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants } export { Button, buttonVariants };

View file

@ -1,155 +1,145 @@
"use client" "use client";
import * as React from "react" import type { DialogProps } from "@radix-ui/react-dialog";
import { type DialogProps } from "@radix-ui/react-dialog" import { Command as CommandPrimitive } from "cmdk";
import { Command as CommandPrimitive } from "cmdk" import { Search } from "lucide-react";
import { Search } from "lucide-react" import * as React from "react";
import { cn } from "~/lib/utils" import { cn } from "~/lib/utils";
import { Dialog, DialogContent } from "~/routes/ui/dialog" import { Dialog, DialogContent } from "~/routes/ui/dialog";
const Command = React.forwardRef< const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>, React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive> React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<CommandPrimitive <CommandPrimitive
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground", "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className className
)} )}
{...props} {...props}
/> />
)) ));
Command.displayName = CommandPrimitive.displayName Command.displayName = CommandPrimitive.displayName;
interface CommandDialogProps extends DialogProps {} interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => { const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return ( return (
<Dialog {...props}> <Dialog {...props}>
<DialogContent className="overflow-hidden p-0 shadow-lg"> <DialogContent className="overflow-hidden p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"> <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children} {children}
</Command> </Command>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) );
} };
const CommandInput = React.forwardRef< const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>, React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input> React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper=""> <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" /> <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input <CommandPrimitive.Input
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50", "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
{...props} {...props}
/> />
</div> </div>
)) ));
CommandInput.displayName = CommandPrimitive.Input.displayName CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef< const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>, React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List> React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<CommandPrimitive.List <CommandPrimitive.List
ref={ref} ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)} className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props} {...props}
/> />
)) ));
CommandList.displayName = CommandPrimitive.List.displayName CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef< const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>, React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty> React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => ( >((props, ref) => (
<CommandPrimitive.Empty <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />
ref={ref} ));
className="py-6 text-center text-sm"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef< const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>, React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group> React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<CommandPrimitive.Group <CommandPrimitive.Group
ref={ref} ref={ref}
className={cn( className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground", "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className className
)} )}
{...props} {...props}
/> />
)) ));
CommandGroup.displayName = CommandPrimitive.Group.displayName CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef< const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>, React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator> React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<CommandPrimitive.Separator <CommandPrimitive.Separator
ref={ref} ref={ref}
className={cn("-mx-1 h-px bg-border", className)} className={cn("-mx-1 h-px bg-border", className)}
{...props} {...props}
/> />
)) ));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef< const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>, React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item> React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<CommandPrimitive.Item <CommandPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", "relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
className className
)} )}
{...props} {...props}
/> />
)) ));
CommandItem.displayName = CommandPrimitive.Item.displayName CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({ const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
className, return (
...props <span
}: React.HTMLAttributes<HTMLSpanElement>) => { className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
return ( {...props}
<span />
className={cn( );
"ml-auto text-xs tracking-widest text-muted-foreground", };
className CommandShortcut.displayName = "CommandShortcut";
)}
{...props}
/>
)
}
CommandShortcut.displayName = "CommandShortcut"
export { export {
Command, Command,
CommandDialog, CommandDialog,
CommandInput, CommandInput,
CommandList, CommandList,
CommandEmpty, CommandEmpty,
CommandGroup, CommandGroup,
CommandItem, CommandItem,
CommandShortcut, CommandShortcut,
CommandSeparator, CommandSeparator,
} };

View file

@ -1,122 +1,104 @@
"use client" "use client";
import * as React from "react" import * as DialogPrimitive from "@radix-ui/react-dialog";
import * as DialogPrimitive from "@radix-ui/react-dialog" import { X } from "lucide-react";
import { X } from "lucide-react" import * as React from "react";
import { cn } from "~/lib/utils" import { cn } from "~/lib/utils";
const Dialog = DialogPrimitive.Root const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef< const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>, React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay <DialogPrimitive.Overlay
ref={ref} ref={ref}
className={cn( className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className className
)} )}
{...props} {...props}
/> />
)) ));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef< const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>, React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => ( >(({ className, children, ...props }, ref) => (
<DialogPortal> <DialogPortal>
<DialogOverlay /> <DialogOverlay />
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className className
)} )}
{...props} {...props}
> >
{children} {children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" /> <X className="h-4 w-4" />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</DialogPrimitive.Close> </DialogPrimitive.Close>
</DialogPrimitive.Content> </DialogPrimitive.Content>
</DialogPortal> </DialogPortal>
)) ));
DialogContent.displayName = DialogPrimitive.Content.displayName DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({ const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
className, <div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
...props );
}: React.HTMLAttributes<HTMLDivElement>) => ( DialogHeader.displayName = "DialogHeader";
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({ const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
className, <div
...props className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
}: React.HTMLAttributes<HTMLDivElement>) => ( {...props}
<div />
className={cn( );
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", DialogFooter.displayName = "DialogFooter";
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef< const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>, React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Title <DialogPrimitive.Title
ref={ref} ref={ref}
className={cn( className={cn("text-lg font-semibold leading-none tracking-tight", className)}
"text-lg font-semibold leading-none tracking-tight", {...props}
className />
)} ));
{...props} DialogTitle.displayName = DialogPrimitive.Title.displayName;
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef< const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>, React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description> React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Description <DialogPrimitive.Description
ref={ref} ref={ref}
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)) ));
DialogDescription.displayName = DialogPrimitive.Description.displayName DialogDescription.displayName = DialogPrimitive.Description.displayName;
export { export {
Dialog, Dialog,
DialogPortal, DialogPortal,
DialogOverlay, DialogOverlay,
DialogClose, DialogClose,
DialogTrigger, DialogTrigger,
DialogContent, DialogContent,
DialogHeader, DialogHeader,
DialogFooter, DialogFooter,
DialogTitle, DialogTitle,
DialogDescription, DialogDescription,
} };

View file

@ -0,0 +1,21 @@
"use client";
import * as LabelPrimitive from "@radix-ui/react-label";
import type * as React from "react";
import { cn } from "@/lib/utils";
function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className
)}
{...props}
/>
);
}
export { Label };

View file

@ -1,31 +1,31 @@
"use client" "use client";
import * as React from "react" import * as PopoverPrimitive from "@radix-ui/react-popover";
import * as PopoverPrimitive from "@radix-ui/react-popover" import * as React from "react";
import { cn } from "~/lib/utils" import { cn } from "~/lib/utils";
const Popover = PopoverPrimitive.Root const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef< const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>, React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal> <PopoverPrimitive.Portal>
<PopoverPrimitive.Content <PopoverPrimitive.Content
ref={ref} ref={ref}
align={align} align={align}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className
)} )}
{...props} {...props}
/> />
</PopoverPrimitive.Portal> </PopoverPrimitive.Portal>
)) ));
PopoverContent.displayName = PopoverPrimitive.Content.displayName PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent } export { Popover, PopoverTrigger, PopoverContent };

View file

@ -1,129 +1,124 @@
"use client" "use client";
import * as React from "react" import * as ToastPrimitives from "@radix-ui/react-toast";
import * as ToastPrimitives from "@radix-ui/react-toast" import { cva, type VariantProps } from "class-variance-authority";
import { cva, type VariantProps } from "class-variance-authority" import { X } from "lucide-react";
import { X } from "lucide-react" import * as React from "react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const ToastProvider = ToastPrimitives.Provider const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef< const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>, React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport> React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport <ToastPrimitives.Viewport
ref={ref} ref={ref}
className={cn( className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]", "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className className
)} )}
{...props} {...props}
/> />
)) ));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva( const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{ {
variants: { variants: {
variant: { variant: {
default: "border bg-background text-foreground", default: "border bg-background text-foreground",
destructive: destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground", "destructive group border-destructive bg-destructive text-destructive-foreground",
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
} }
) );
const Toast = React.forwardRef< const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>, React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => { >(({ className, variant, ...props }, ref) => {
return ( return (
<ToastPrimitives.Root <ToastPrimitives.Root
ref={ref} ref={ref}
className={cn(toastVariants({ variant }), className)} className={cn(toastVariants({ variant }), className)}
{...props} {...props}
/> />
) );
}) });
Toast.displayName = ToastPrimitives.Root.displayName Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef< const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>, React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action> React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ToastPrimitives.Action <ToastPrimitives.Action
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive", "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className className
)} )}
{...props} {...props}
/> />
)) ));
ToastAction.displayName = ToastPrimitives.Action.displayName ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef< const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>, React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close> React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ToastPrimitives.Close <ToastPrimitives.Close
ref={ref} ref={ref}
className={cn( className={cn(
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600", "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className className
)} )}
toast-close="" toast-close=""
{...props} {...props}
> >
<X className="h-4 w-4" /> <X className="h-4 w-4" />
</ToastPrimitives.Close> </ToastPrimitives.Close>
)) ));
ToastClose.displayName = ToastPrimitives.Close.displayName ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef< const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>, React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title> React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ToastPrimitives.Title <ToastPrimitives.Title ref={ref} className={cn("text-sm font-semibold", className)} {...props} />
ref={ref} ));
className={cn("text-sm font-semibold", className)} ToastTitle.displayName = ToastPrimitives.Title.displayName;
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastDescription = React.forwardRef< const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>, React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description> React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ToastPrimitives.Description <ToastPrimitives.Description
ref={ref} ref={ref}
className={cn("text-sm opacity-90", className)} className={cn("text-sm opacity-90", className)}
{...props} {...props}
/> />
)) ));
ToastDescription.displayName = ToastPrimitives.Description.displayName ToastDescription.displayName = ToastPrimitives.Description.displayName;
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast> type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction> type ToastActionElement = React.ReactElement<typeof ToastAction>;
export { export {
type ToastProps, type ToastProps,
type ToastActionElement, type ToastActionElement,
ToastProvider, ToastProvider,
ToastViewport, ToastViewport,
Toast, Toast,
ToastTitle, ToastTitle,
ToastDescription, ToastDescription,
ToastClose, ToastClose,
ToastAction, ToastAction,
} };

View file

@ -1,35 +1,31 @@
"use client" "use client";
import { useToast } from "@/routes/ui/use-toast"
import { import {
Toast, Toast,
ToastClose, ToastClose,
ToastDescription, ToastDescription,
ToastProvider, ToastProvider,
ToastTitle, ToastTitle,
ToastViewport, ToastViewport,
} from "@/routes/ui/toast" } from "@/routes/ui/toast";
import { useToast } from "@/routes/ui/use-toast";
export function Toaster() { export function Toaster() {
const { toasts } = useToast() const { toasts } = useToast();
return ( return (
<ToastProvider> <ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) { {toasts.map(({ id, title, description, action, ...props }) => (
return ( <Toast key={id} {...props}>
<Toast key={id} {...props}> <div className="grid gap-1">
<div className="grid gap-1"> {title && <ToastTitle>{title}</ToastTitle>}
{title && <ToastTitle>{title}</ToastTitle>} {description && <ToastDescription>{description}</ToastDescription>}
{description && ( </div>
<ToastDescription>{description}</ToastDescription> {action}
)} <ToastClose />
</div> </Toast>
{action} ))}
<ToastClose /> <ToastViewport />
</Toast> </ToastProvider>
) );
})}
<ToastViewport />
</ToastProvider>
)
} }

View file

@ -1,194 +1,189 @@
"use client" "use client";
// Inspired by react-hot-toast library // Inspired by react-hot-toast library
import * as React from "react" import * as React from "react";
import type { import type { ToastActionElement, ToastProps } from "@/routes/ui/toast";
ToastActionElement,
ToastProps,
} from "@/routes/ui/toast"
const TOAST_LIMIT = 1 const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000 const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & { type ToasterToast = ToastProps & {
id: string id: string;
title?: React.ReactNode title?: React.ReactNode;
description?: React.ReactNode description?: React.ReactNode;
action?: ToastActionElement action?: ToastActionElement;
} };
const actionTypes = { const actionTypes = {
ADD_TOAST: "ADD_TOAST", ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST", UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST", DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST", REMOVE_TOAST: "REMOVE_TOAST",
} as const } as const;
let count = 0 let count = 0;
function genId() { function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER count = (count + 1) % Number.MAX_SAFE_INTEGER;
return count.toString() return count.toString();
} }
type ActionType = typeof actionTypes type ActionType = typeof actionTypes;
type Action = type Action =
| { | {
type: ActionType["ADD_TOAST"] type: ActionType["ADD_TOAST"];
toast: ToasterToast toast: ToasterToast;
} }
| { | {
type: ActionType["UPDATE_TOAST"] type: ActionType["UPDATE_TOAST"];
toast: Partial<ToasterToast> toast: Partial<ToasterToast>;
} }
| { | {
type: ActionType["DISMISS_TOAST"] type: ActionType["DISMISS_TOAST"];
toastId?: ToasterToast["id"] toastId?: ToasterToast["id"];
} }
| { | {
type: ActionType["REMOVE_TOAST"] type: ActionType["REMOVE_TOAST"];
toastId?: ToasterToast["id"] toastId?: ToasterToast["id"];
} };
interface State { interface State {
toasts: ToasterToast[] toasts: ToasterToast[];
} }
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>() const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const addToRemoveQueue = (toastId: string) => { const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) { if (toastTimeouts.has(toastId)) {
return return;
} }
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
toastTimeouts.delete(toastId) toastTimeouts.delete(toastId);
dispatch({ dispatch({
type: "REMOVE_TOAST", type: "REMOVE_TOAST",
toastId: toastId, toastId: toastId,
}) });
}, TOAST_REMOVE_DELAY) }, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout) toastTimeouts.set(toastId, timeout);
} };
export const reducer = (state: State, action: Action): State => { export const reducer = (state: State, action: Action): State => {
switch (action.type) { switch (action.type) {
case "ADD_TOAST": case "ADD_TOAST":
return { return {
...state, ...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
} };
case "UPDATE_TOAST": case "UPDATE_TOAST":
return { return {
...state, ...state,
toasts: state.toasts.map((t) => toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
t.id === action.toast.id ? { ...t, ...action.toast } : t };
),
}
case "DISMISS_TOAST": { case "DISMISS_TOAST": {
const { toastId } = action const { toastId } = action;
// ! Side effects ! - This could be extracted into a dismissToast() action, // ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity // but I'll keep it here for simplicity
if (toastId) { if (toastId) {
addToRemoveQueue(toastId) addToRemoveQueue(toastId);
} else { } else {
state.toasts.forEach((toast) => { state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id) addToRemoveQueue(toast.id);
}) });
} }
return { return {
...state, ...state,
toasts: state.toasts.map((t) => toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined t.id === toastId || toastId === undefined
? { ? {
...t, ...t,
open: false, open: false,
} }
: t : t
), ),
} };
} }
case "REMOVE_TOAST": case "REMOVE_TOAST":
if (action.toastId === undefined) { if (action.toastId === undefined) {
return { return {
...state, ...state,
toasts: [], toasts: [],
} };
} }
return { return {
...state, ...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId), toasts: state.toasts.filter((t) => t.id !== action.toastId),
} };
} }
} };
const listeners: Array<(state: State) => void> = [] const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] } let memoryState: State = { toasts: [] };
function dispatch(action: Action) { function dispatch(action: Action) {
memoryState = reducer(memoryState, action) memoryState = reducer(memoryState, action);
listeners.forEach((listener) => { listeners.forEach((listener) => {
listener(memoryState) listener(memoryState);
}) });
} }
type Toast = Omit<ToasterToast, "id"> type Toast = Omit<ToasterToast, "id">;
function toast({ ...props }: Toast) { function toast({ ...props }: Toast) {
const id = genId() const id = genId();
const update = (props: ToasterToast) => const update = (props: ToasterToast) =>
dispatch({ dispatch({
type: "UPDATE_TOAST", type: "UPDATE_TOAST",
toast: { ...props, id }, toast: { ...props, id },
}) });
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
dispatch({ dispatch({
type: "ADD_TOAST", type: "ADD_TOAST",
toast: { toast: {
...props, ...props,
id, id,
open: true, open: true,
onOpenChange: (open) => { onOpenChange: (open) => {
if (!open) dismiss() if (!open) dismiss();
}, },
}, },
}) });
return { return {
id: id, id: id,
dismiss, dismiss,
update, update,
} };
} }
function useToast() { function useToast() {
const [state, setState] = React.useState<State>(memoryState) const [state, setState] = React.useState<State>(memoryState);
React.useEffect(() => { React.useEffect(() => {
listeners.push(setState) listeners.push(setState);
return () => { return () => {
const index = listeners.indexOf(setState) const index = listeners.indexOf(setState);
if (index > -1) { if (index > -1) {
listeners.splice(index, 1) listeners.splice(index, 1);
} }
} };
}, [state]) }, [state]);
return { return {
...state, ...state,
toast, toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
} };
} }
export { useToast, toast } export { useToast, toast };

View file

@ -1,76 +1,76 @@
const { fontFamily } = require("tailwindcss/defaultTheme") const { fontFamily } = require("tailwindcss/defaultTheme");
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
darkMode: ["class"], darkMode: ["class"],
content: ["./*.{js,jsx,ts,tsx}","./routes/*.tsx","./routes/**/*.tsx"], content: ["./*.{js,jsx,ts,tsx}", "./routes/*.tsx", "./routes/**/*.tsx"],
theme: { theme: {
container: { container: {
center: true, center: true,
padding: "2rem", padding: "2rem",
screens: { screens: {
"2xl": "1400px", "2xl": "1400px",
}, },
}, },
extend: { extend: {
colors: { colors: {
border: "hsl(var(--border))", border: "hsl(var(--border))",
input: "hsl(var(--input))", input: "hsl(var(--input))",
ring: "hsl(var(--ring))", ring: "hsl(var(--ring))",
background: "hsl(var(--background))", background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))", foreground: "hsl(var(--foreground))",
primary: { primary: {
DEFAULT: "hsl(var(--primary))", DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))", foreground: "hsl(var(--primary-foreground))",
}, },
secondary: { secondary: {
DEFAULT: "hsl(var(--secondary))", DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))", foreground: "hsl(var(--secondary-foreground))",
}, },
destructive: { destructive: {
DEFAULT: "hsl(var(--destructive))", DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))", foreground: "hsl(var(--destructive-foreground))",
}, },
muted: { muted: {
DEFAULT: "hsl(var(--muted))", DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))", foreground: "hsl(var(--muted-foreground))",
}, },
accent: { accent: {
DEFAULT: "hsl(var(--accent))", DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))", foreground: "hsl(var(--accent-foreground))",
}, },
popover: { popover: {
DEFAULT: "hsl(var(--popover))", DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))", foreground: "hsl(var(--popover-foreground))",
}, },
card: { card: {
DEFAULT: "hsl(var(--card))", DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))", foreground: "hsl(var(--card-foreground))",
}, },
}, },
borderRadius: { borderRadius: {
lg: `var(--radius)`, lg: `var(--radius)`,
md: `calc(var(--radius) - 2px)`, md: `calc(var(--radius) - 2px)`,
sm: "calc(var(--radius) - 4px)", sm: "calc(var(--radius) - 4px)",
}, },
fontFamily: { fontFamily: {
sans: ["var(--font-sans)", ...fontFamily.sans], sans: ["var(--font-sans)", ...fontFamily.sans],
}, },
keyframes: { keyframes: {
"accordion-down": { "accordion-down": {
from: { height: "0" }, from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" }, to: { height: "var(--radix-accordion-content-height)" },
}, },
"accordion-up": { "accordion-up": {
from: { height: "var(--radix-accordion-content-height)" }, from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" }, to: { height: "0" },
}, },
}, },
animation: { animation: {
"accordion-down": "accordion-down 0.2s ease-out", "accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out",
}, },
}, },
}, },
plugins: [require("tailwindcss-animate")], plugins: [require("tailwindcss-animate")],
} };

View file

@ -3,96 +3,97 @@
@tailwind utilities; @tailwind utilities;
@layer base { @layer base {
:root { :root {
--background: 240 10% 3.9%; --background: 240 10% 3.9%;
--foreground: 0 0% 98%; --foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 180 100% 37%;
--primary-foreground: 0 0% 98%;
--secondary: 240 5.9% 10%;
--secondary-foreground: 0 0% 98%;
--muted: 240 5.9% 10%;
--muted-foreground: 240 5% 64.9%;
--accent: 169 97% 37%;
--accent-foreground: 0 0% 98%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 24%; --card: 240 10% 3.9%;
--input: 240 5.9% 10%; --card-foreground: 0 0% 98%;
--ring: 180 100% 37%;
--radius: 0.5rem;
}
.dark { --popover: 240 10% 3.9%;
--background: 224 71% 4%; --popover-foreground: 0 0% 98%;
--foreground: 213 31% 91%;
--muted: 223 47% 11%; --primary: 180 100% 37%;
--muted-foreground: 215.4 16.3% 56.9%; --primary-foreground: 0 0% 98%;
--accent: 216 34% 17%; --secondary: 240 5.9% 10%;
--accent-foreground: 210 40% 98%; --secondary-foreground: 0 0% 98%;
--popover: 224 71% 4%; --muted: 240 5.9% 10%;
--popover-foreground: 215 20.2% 65.1%; --muted-foreground: 240 5% 64.9%;
--border: 216 34% 17%; --accent: 169 97% 37%;
--input: 216 34% 17%; --accent-foreground: 0 0% 98%;
--card: 224 71% 4%; --destructive: 0 84.2% 60.2%;
--card-foreground: 213 31% 91%; --destructive-foreground: 0 0% 98%;
--primary: 210 40% 98%; --border: 240 5.9% 24%;
--primary-foreground: 222.2 47.4% 1.2%; --input: 240 5.9% 10%;
--ring: 180 100% 37%;
--secondary: 222.2 47.4% 11.2%; --radius: 0.5rem;
--secondary-foreground: 210 40% 98%; }
--destructive: 0 63% 31%; .dark {
--destructive-foreground: 210 40% 98%; --background: 224 71% 4%;
--foreground: 213 31% 91%;
--ring: 216 34% 17%; --muted: 223 47% 11%;
--muted-foreground: 215.4 16.3% 56.9%;
--radius: 0.5rem; --accent: 216 34% 17%;
} --accent-foreground: 210 40% 98%;
--popover: 224 71% 4%;
--popover-foreground: 215 20.2% 65.1%;
--border: 216 34% 17%;
--input: 216 34% 17%;
--card: 224 71% 4%;
--card-foreground: 213 31% 91%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 1.2%;
--secondary: 222.2 47.4% 11.2%;
--secondary-foreground: 210 40% 98%;
--destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%;
--ring: 216 34% 17%;
--radius: 0.5rem;
}
} }
body { body {
min-width: 380px; min-width: 380px;
min-height: 580px; min-height: 580px;
} }
@layer base { @layer base {
* { * {
@apply border-border; @apply border-border;
} }
body { body {
@apply bg-background text-foreground; @apply bg-background text-foreground;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, font-family:
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
} "Helvetica Neue", sans-serif;
}
/* Styling for shadcn/ui components */ /* Styling for shadcn/ui components */
.command-dialog { .command-dialog {
@apply dark; @apply dark;
} }
} }
/* Popup page dimensions */ /* Popup page dimensions */
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
body { body {
@apply bg-slate-950 text-white; @apply bg-slate-950 text-white;
} }
} }

View file

@ -1,20 +1,12 @@
{ {
"extends": "plasmo/templates/tsconfig.base", "extends": "plasmo/templates/tsconfig.base.json",
"exclude": [ "exclude": ["node_modules"],
"node_modules" "include": [".plasmo/index.d.ts", "./**/*.ts", "./**/*.tsx"],
], "compilerOptions": {
"include": [ "paths": {
".plasmo/index.d.ts", "~*": ["./*"],
"./**/*.ts", "@/*": ["./*"]
"./**/*.tsx" },
], "baseUrl": "."
"compilerOptions": { }
"paths": {
"~*": [
"./*"
],
"@/*": ["./*"]
},
"baseUrl": "."
}
} }

View file

@ -1,144 +1,137 @@
import { Storage } from "@plasmohq/storage" import { Storage } from "@plasmohq/storage";
import type { WebHistory } from "./interfaces" import type { WebHistory } from "./interfaces";
export const emptyArr: any[] = [] export const emptyArr: any[] = [];
export const initQueues = async (tabId: number) => { export const initQueues = async (tabId: number) => {
const storage = new Storage({ area: "local" }) const storage = new Storage({ area: "local" });
let urlQueueListObj: any = await storage.get("urlQueueList") const urlQueueListObj: any = await storage.get("urlQueueList");
let timeQueueListObj: any = await storage.get("timeQueueList") const timeQueueListObj: any = await storage.get("timeQueueList");
if (!urlQueueListObj && !timeQueueListObj) { if (!urlQueueListObj && !timeQueueListObj) {
await storage.set("urlQueueList", { await storage.set("urlQueueList", {
urlQueueList: [{ tabsessionId: tabId, urlQueue: [] }] urlQueueList: [{ tabsessionId: tabId, urlQueue: [] }],
}) });
await storage.set("timeQueueList", { await storage.set("timeQueueList", {
timeQueueList: [{ tabsessionId: tabId, timeQueue: [] }] timeQueueList: [{ tabsessionId: tabId, timeQueue: [] }],
}) });
return return;
} }
if (urlQueueListObj.urlQueueList && timeQueueListObj.timeQueueList) { if (urlQueueListObj.urlQueueList && timeQueueListObj.timeQueueList) {
const isUrlQueueThere = urlQueueListObj.urlQueueList.find( const isUrlQueueThere = urlQueueListObj.urlQueueList.find(
(data: WebHistory) => data.tabsessionId === tabId (data: WebHistory) => data.tabsessionId === tabId
) );
const isTimeQueueThere = timeQueueListObj.timeQueueList.find( const isTimeQueueThere = timeQueueListObj.timeQueueList.find(
(data: WebHistory) => data.tabsessionId === tabId (data: WebHistory) => data.tabsessionId === tabId
) );
if (!isUrlQueueThere) { if (!isUrlQueueThere) {
urlQueueListObj.urlQueueList.push({ tabsessionId: tabId, urlQueue: [] }) urlQueueListObj.urlQueueList.push({ tabsessionId: tabId, urlQueue: [] });
await storage.set("urlQueueList", { await storage.set("urlQueueList", {
urlQueueList: urlQueueListObj.urlQueueList urlQueueList: urlQueueListObj.urlQueueList,
}) });
} }
if (!isTimeQueueThere) { if (!isTimeQueueThere) {
timeQueueListObj.timeQueueList.push({ timeQueueListObj.timeQueueList.push({
tabsessionId: tabId, tabsessionId: tabId,
timeQueue: [] timeQueue: [],
}) });
await storage.set("timeQueueList", { await storage.set("timeQueueList", {
timeQueueList: timeQueueListObj.timeQueueList timeQueueList: timeQueueListObj.timeQueueList,
}) });
} }
return return;
} }
} };
export function getRenderedHtml() { export function getRenderedHtml() {
return { return {
url: window.location.href, url: window.location.href,
entryTime: Date.now(), entryTime: Date.now(),
title: document.title, title: document.title,
renderedHtml: document.documentElement.outerHTML renderedHtml: document.documentElement.outerHTML,
} };
} }
export const initWebHistory = async (tabId: number) => { export const initWebHistory = async (tabId: number) => {
const storage = new Storage({ area: "local" }) const storage = new Storage({ area: "local" });
const result: any = await storage.get("webhistory") const result: any = await storage.get("webhistory");
if (result === undefined) { if (result === undefined) {
await storage.set("webhistory", { webhistory: emptyArr }) await storage.set("webhistory", { webhistory: emptyArr });
return return;
} }
const ifIdExists = result.webhistory.find( const ifIdExists = result.webhistory.find((data: WebHistory) => data.tabsessionId === tabId);
(data: WebHistory) => data.tabsessionId === tabId
)
if (ifIdExists === undefined) { if (ifIdExists === undefined) {
let webHistory = result.webhistory const webHistory = result.webhistory;
const initData = { const initData = {
tabsessionId: tabId, tabsessionId: tabId,
tabHistory: emptyArr tabHistory: emptyArr,
} };
webHistory.push(initData) webHistory.push(initData);
try { try {
await storage.set("webhistory", { webhistory: webHistory }) await storage.set("webhistory", { webhistory: webHistory });
return return;
} catch (error) { } catch (error) {
console.log(error) console.log(error);
} }
} else { } else {
return return;
} }
} };
export function toIsoString(date: Date) { export function toIsoString(date: Date) {
var tzo = -date.getTimezoneOffset(), var tzo = -date.getTimezoneOffset(),
dif = tzo >= 0 ? "+" : "-", dif = tzo >= 0 ? "+" : "-",
pad = function (num: number) { pad = (num: number) => (num < 10 ? "0" : "") + num;
return (num < 10 ? "0" : "") + num
}
return ( return (
date.getFullYear() + date.getFullYear() +
"-" + "-" +
pad(date.getMonth() + 1) + pad(date.getMonth() + 1) +
"-" + "-" +
pad(date.getDate()) + pad(date.getDate()) +
"T" + "T" +
pad(date.getHours()) + pad(date.getHours()) +
":" + ":" +
pad(date.getMinutes()) + pad(date.getMinutes()) +
":" + ":" +
pad(date.getSeconds()) + pad(date.getSeconds()) +
dif + dif +
pad(Math.floor(Math.abs(tzo) / 60)) + pad(Math.floor(Math.abs(tzo) / 60)) +
":" + ":" +
pad(Math.abs(tzo) % 60) pad(Math.abs(tzo) % 60)
) );
} }
export const webhistoryToLangChainDocument = ( export const webhistoryToLangChainDocument = (tabId: number, tabHistory: any[]) => {
tabId: number, const toSaveFinally = [];
tabHistory: any[] for (let j = 0; j < tabHistory.length; j++) {
) => { const mtadata = {
let toSaveFinally = [] BrowsingSessionId: `${tabId}`,
for (let j = 0; j < tabHistory.length; j++) { VisitedWebPageURL: `${tabHistory[j].url}`,
const mtadata = { VisitedWebPageTitle: `${tabHistory[j].title}`,
BrowsingSessionId: `${tabId}`, VisitedWebPageDateWithTimeInISOString: `${toIsoString(new Date(tabHistory[j].entryTime))}`,
VisitedWebPageURL: `${tabHistory[j].url}`, VisitedWebPageReffererURL: `${tabHistory[j].reffererUrl}`,
VisitedWebPageTitle: `${tabHistory[j].title}`, VisitedWebPageVisitDurationInMilliseconds: tabHistory[j].duration,
VisitedWebPageDateWithTimeInISOString: `${toIsoString(new Date(tabHistory[j].entryTime))}`, };
VisitedWebPageReffererURL: `${tabHistory[j].reffererUrl}`,
VisitedWebPageVisitDurationInMilliseconds: tabHistory[j].duration
}
toSaveFinally.push({ toSaveFinally.push({
metadata: mtadata, metadata: mtadata,
pageContent: tabHistory[j].pageContentMarkdown pageContent: tabHistory[j].pageContentMarkdown,
}) });
} }
return toSaveFinally return toSaveFinally;
} };

View file

@ -1,4 +1,4 @@
export interface WebHistory { export interface WebHistory {
tabsessionId: number; tabsessionId: number;
tabHistory: any[]; tabHistory: any[];
} }