mirror of
https://github.com/MODSetter/SurfSense.git
synced 2025-09-01 18:19:08 +00:00
Biome: Fixes for extenstion repo
This commit is contained in:
parent
b70d46f732
commit
b76419c6fc
29 changed files with 2668 additions and 2386 deletions
|
@ -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/(.*)$",
|
|
||||||
"",
|
|
||||||
"^~(.*)$",
|
|
||||||
"",
|
|
||||||
"^[./]"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
115
surfsense_browser_extension/biome.json
Normal file
115
surfsense_browser_extension/biome.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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",
|
||||||
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
1282
surfsense_browser_extension/pnpm-lock.yaml
generated
1282
surfsense_browser_extension/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
)
|
);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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,
|
||||||
}
|
};
|
||||||
|
|
|
@ -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,
|
||||||
}
|
};
|
||||||
|
|
21
surfsense_browser_extension/routes/ui/label.tsx
Normal file
21
surfsense_browser_extension/routes/ui/label.tsx
Normal 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 };
|
|
@ -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 };
|
||||||
|
|
|
@ -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,
|
||||||
}
|
};
|
||||||
|
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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")],
|
||||||
}
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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": "."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export interface WebHistory {
|
export interface WebHistory {
|
||||||
tabsessionId: number;
|
tabsessionId: number;
|
||||||
tabHistory: any[];
|
tabHistory: any[];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue