diff --git a/.github/workflows/submit.yml b/.github/workflows/submit.yml new file mode 100644 index 0000000..891b63b --- /dev/null +++ b/.github/workflows/submit.yml @@ -0,0 +1,37 @@ +name: "Submit to Web Store" +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: apps/extension + steps: + - uses: actions/checkout@v3 + - name: Cache pnpm modules + uses: actions/cache@v3 + with: + path: ~/.pnpm-store + key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}- + - uses: pnpm/action-setup@v2.2.4 + with: + version: latest + run_install: true + - name: Use Node.js 16.x + uses: actions/setup-node@v3.4.1 + with: + node-version: 16.x + cache: "pnpm" + - name: Build the extension + run: pnpm build + - name: Package the extension into a zip artifact + run: pnpm package + - name: Browser Platform Publish + uses: PlasmoHQ/bpp@v3 + with: + keys: ${{ secrets.SUBMIT_KEYS }} + artifact: build/chrome-mv3-prod.zip diff --git a/apps/extension/.gitignore b/apps/extension/.gitignore new file mode 100644 index 0000000..54afb48 --- /dev/null +++ b/apps/extension/.gitignore @@ -0,0 +1,33 @@ + +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +out/ +build/ +dist/ + +# plasmo +.plasmo + +# typescript +.tsbuildinfo diff --git a/apps/extension/.prettierrc.mjs b/apps/extension/.prettierrc.mjs new file mode 100644 index 0000000..77f84c2 --- /dev/null +++ b/apps/extension/.prettierrc.mjs @@ -0,0 +1,26 @@ +/** + * @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: [ + "", // Node.js built-in modules + "", // Imports not matched by other special words or groups. + "", // Empty line + "^@plasmo/(.*)$", + "", + "^@plasmohq/(.*)$", + "", + "^~(.*)$", + "", + "^[./]" + ] +} diff --git a/apps/extension/README.md b/apps/extension/README.md new file mode 100644 index 0000000..ca9c259 --- /dev/null +++ b/apps/extension/README.md @@ -0,0 +1,33 @@ +This is a [Plasmo extension](https://docs.plasmo.com/) project bootstrapped with [`plasmo init`](https://www.npmjs.com/package/plasmo). + +## Getting Started + +First, run the development server: + +```bash +pnpm dev +# or +npm run dev +``` + +Open your browser and load the appropriate development build. For example, if you are developing for the chrome browser, using manifest v3, use: `build/chrome-mv3-dev`. + +You can start editing the popup by modifying `popup.tsx`. It should auto-update as you make changes. To add an options page, simply add a `options.tsx` file to the root of the project, with a react component default exported. Likewise to add a content page, add a `content.ts` file to the root of the project, importing some module and do some logic, then reload the extension on your browser. + +For further guidance, [visit our Documentation](https://docs.plasmo.com/) + +## Making production build + +Run the following: + +```bash +pnpm build +# or +npm run build +``` + +This should create a production bundle for your extension, ready to be zipped and published to the stores. + +## Submit to the webstores + +The easiest way to deploy your Plasmo extension is to use the built-in [bpp](https://bpp.browser.market) GitHub action. Prior to using this action however, make sure to build your extension and upload the first version to the store to establish the basic credentials. Then, simply follow [this setup instruction](https://docs.plasmo.com/framework/workflows/submit) and you should be on your way for automated submission! diff --git a/apps/extension/assets/icon.png b/apps/extension/assets/icon.png new file mode 100644 index 0000000..80b481b Binary files /dev/null and b/apps/extension/assets/icon.png differ diff --git a/apps/extension/components/InputSuggestionsPopup.tsx b/apps/extension/components/InputSuggestionsPopup.tsx new file mode 100644 index 0000000..317a61c --- /dev/null +++ b/apps/extension/components/InputSuggestionsPopup.tsx @@ -0,0 +1,129 @@ +import { useEffect, useState } from "react" + +interface InputSuggestionsPopupProps { + facts: string[] + isLoading: boolean + onFactSelect: (fact: string) => void + onClose: () => void +} + +export default function InputSuggestionsPopup({ + facts, + isLoading, + onFactSelect, + onClose +}: InputSuggestionsPopupProps) { + const [hoveredIndex, setHoveredIndex] = useState(null) + + // Auto-hide after 10 seconds + useEffect(() => { + const timer = setTimeout(() => { + onClose() + }, 10000) + + return () => clearTimeout(timer) + }, [onClose]) + + return ( +
+ {/* Header */} +
+ AI Suggestions + +
+ + {/* Content */} +
+ {isLoading ? ( +
+ Searching for relevant facts... +
+ ) : facts.length > 0 ? ( + facts.slice(0, 5).map((fact, index) => ( +
onFactSelect(fact)} + onMouseEnter={() => setHoveredIndex(index)} + onMouseLeave={() => setHoveredIndex(null)} + style={{ + padding: "8px 12px", + margin: "4px 0", + background: hoveredIndex === index ? "#e9ecef" : "#f8f9fa", + borderRadius: "4px", + cursor: "pointer", + border: "1px solid #e9ecef", + transition: "background-color 0.2s" + }}> +
+ {fact} +
+
+ )) + ) : ( +
+ No relevant suggestions found +
+ )} +
+
+ ) +} diff --git a/apps/extension/components/TextSelectionPopup.tsx b/apps/extension/components/TextSelectionPopup.tsx new file mode 100644 index 0000000..5654541 --- /dev/null +++ b/apps/extension/components/TextSelectionPopup.tsx @@ -0,0 +1,166 @@ +import { useEffect, useRef, useState } from "react" + +interface TextSelectionPopupProps { + selectedText: string + onSave: (text: string) => Promise + onClose: () => void + isSaving: boolean + saveStatus: "idle" | "saving" | "success" | "error" | "empty" +} + +export default function TextSelectionPopup({ + selectedText, + onSave, + onClose, + isSaving, + saveStatus +}: TextSelectionPopupProps) { + const [text, setText] = useState(selectedText) + const textareaRef = useRef(null) + + // Focus textarea on mount + useEffect(() => { + if (textareaRef.current) { + textareaRef.current.focus() + textareaRef.current.setSelectionRange( + textareaRef.current.value.length, + textareaRef.current.value.length + ) + } + }, []) + + const handleSave = async () => { + const textToSave = text.trim() + if (!textToSave) { + return + } + await onSave(textToSave) + } + + const getButtonText = () => { + switch (saveStatus) { + case "saving": + return "Saving..." + case "success": + return "Saved!" + case "error": + return "Error" + case "empty": + return "No text to save" + default: + return "Add to Memory" + } + } + + const getButtonColor = () => { + switch (saveStatus) { + case "success": + return "#10B981" + case "error": + case "empty": + return "#EF4444" + default: + return "#c15e50" + } + } + + return ( +
{ + e.stopPropagation() + e.preventDefault() + }} + onMouseDown={(e) => { + e.stopPropagation() + }} + style={{ + background: "white", + border: "1px solid #ddd", + borderRadius: "8px", + padding: "16px", + boxShadow: "0 4px 12px rgba(0,0,0,0.15)", + fontFamily: "-apple-system, BlinkMacSystemFont, sans-serif", + fontSize: "14px", + width: "350px" + }}> + {/* Header */} +
+ Add to Core Memory +
+ + {/* Textarea */} +