├── .husky ├── pre-commit ├── pre-push └── commit-msg ├── loadash.d.ts ├── src ├── config │ └── test-setup.ts ├── static │ ├── icon │ │ ├── 16 x 16.png │ │ ├── 32 x 32.png │ │ ├── 48 x 48.png │ │ └── 128 x 128.png │ └── manifest.json ├── styles │ ├── images │ │ ├── note-it.png │ │ └── noteIt_cover.png │ ├── scrollbar.css │ ├── editor.css │ └── tailwind.css ├── background │ ├── background.ts │ ├── activeWindow.ts │ ├── messageListener.ts │ └── contextMenu.ts ├── utils │ ├── types │ │ ├── MessageRequest.ts │ │ └── Note.ts │ ├── render.ts │ ├── tesseractLanguage.ts │ ├── getCurrentTab.ts │ ├── storage.ts │ └── image.ts ├── icons │ ├── CodeArrows.svg │ ├── Italic.svg │ ├── Underline.svg │ ├── Code Arrows.svg │ ├── Search.svg │ ├── Quote.svg │ ├── X.svg │ ├── BoxDownload.svg │ ├── Star2.svg │ ├── H.svg │ ├── Redo.svg │ ├── Undo.svg │ ├── Bold.svg │ ├── AddCamera.svg │ ├── ArrowUp.tsx │ ├── ArrowDown.tsx │ ├── Camera.svg │ ├── ClearFormatting.svg │ ├── Folder.tsx │ ├── Delete.svg │ ├── Save.svg │ ├── BulletPointsDots.svg │ ├── Star.svg │ ├── Upload.svg │ ├── PasteFromClipboard.svg │ ├── settings-icon.tsx │ ├── ColorPalette.svg │ ├── OcrIcon.tsx │ ├── Feather.tsx │ ├── note-it.svg │ ├── CameraRetake.svg │ └── Logo.svg ├── components │ ├── Elements │ │ ├── index.ts │ │ ├── Spinner │ │ │ ├── __tests__ │ │ │ │ └── spinner.test.tsx │ │ │ └── Spinner.tsx │ │ ├── Tab │ │ │ ├── Tab.tsx │ │ │ ├── __tests__ │ │ │ │ └── tabContent.test.tsx │ │ │ └── TabContent.tsx │ │ ├── Input │ │ │ └── Input.tsx │ │ ├── SelectField │ │ │ ├── __tests__ │ │ │ │ └── selectFieldSpinner.test.tsx │ │ │ ├── SelectField.tsx │ │ │ └── SelectFieldSpinner.tsx │ │ └── Button │ │ │ └── Button.tsx │ ├── NotesFolder │ │ ├── SearchNotes.tsx │ │ ├── utils │ │ │ └── filterNotes.ts │ │ ├── Note │ │ │ ├── ConfirmNoteDeletion.tsx │ │ │ ├── NoteContent.tsx │ │ │ └── NoteHeader.tsx │ │ └── NotesFolder.tsx │ ├── NoteEditor │ │ ├── utils │ │ │ └── pasteHandler.ts │ │ ├── MenuBar │ │ │ ├── MenuItem │ │ │ │ ├── ScreenshotArea.tsx │ │ │ │ ├── DownloadNote.tsx │ │ │ │ ├── Heading.tsx │ │ │ │ └── SaveNote.tsx │ │ │ └── MenuBar.tsx │ │ └── NoteEditor.tsx │ ├── Dashboard │ │ └── Dashboard.tsx │ ├── settings │ │ └── settings.tsx │ └── Ocr │ │ ├── ImageDropzone.tsx │ │ └── Ocr.tsx ├── provider │ └── tabContext.tsx ├── popup │ └── popup.tsx ├── contentScript │ ├── frameScript.tsx │ ├── cropArea.tsx │ └── frameContent.tsx └── hooks │ └── useStore.ts ├── commitlint.config.js ├── svg.d.ts ├── .vscode └── settings.json ├── postcss.config.js ├── webpack ├── webpack.prod.js ├── webpack.dev.js └── webpack.common.js ├── renovate.json ├── .eslintignore ├── .whitesource ├── .prettierrc ├── .gitignore ├── vite.config.ts ├── release.config.js ├── tsconfig.json ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── main.yml ├── tailwind.config.js ├── README.md ├── .eslintrc.json ├── package.json └── LICENSE /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | npx lint-staged 3 | -------------------------------------------------------------------------------- /loadash.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'lodash.debounce'; 2 | -------------------------------------------------------------------------------- /src/config/test-setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run test 5 | -------------------------------------------------------------------------------- /src/static/icon/16 x 16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSmaili/note-it/HEAD/src/static/icon/16 x 16.png -------------------------------------------------------------------------------- /src/static/icon/32 x 32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSmaili/note-it/HEAD/src/static/icon/32 x 32.png -------------------------------------------------------------------------------- /src/static/icon/48 x 48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSmaili/note-it/HEAD/src/static/icon/48 x 48.png -------------------------------------------------------------------------------- /src/static/icon/128 x 128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSmaili/note-it/HEAD/src/static/icon/128 x 128.png -------------------------------------------------------------------------------- /src/styles/images/note-it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSmaili/note-it/HEAD/src/styles/images/note-it.png -------------------------------------------------------------------------------- /svg.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const content: ReactComponent; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/sh 3 | . "$(dirname "$0")/_/husky.sh" 4 | 5 | npx --no -- commitlint --edit "${1}" 6 | -------------------------------------------------------------------------------- /src/background/background.ts: -------------------------------------------------------------------------------- 1 | import './messageListener'; 2 | import './contextMenu'; 3 | import './activeWindow'; 4 | -------------------------------------------------------------------------------- /src/styles/images/noteIt_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MSmaili/note-it/HEAD/src/styles/images/noteIt_cover.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode" 4 | } 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/utils/types/MessageRequest.ts: -------------------------------------------------------------------------------- 1 | export enum MessageRequest { 2 | CROP_SCREEN, 3 | CAPTURE_SCREEN, 4 | INSERT_FRAME, 5 | } 6 | -------------------------------------------------------------------------------- /webpack/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | mode: 'production', 6 | }); 7 | -------------------------------------------------------------------------------- /src/icons/CodeArrows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/utils/render.ts: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | 3 | const container = document.createElement('div'); 4 | document.body.appendChild(container); 5 | 6 | export const rootRender = createRoot(container); 7 | -------------------------------------------------------------------------------- /src/icons/Italic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /webpack/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge'); 2 | const common = require('./webpack.common.js'); 3 | 4 | module.exports = merge(common, { 5 | mode: 'development', 6 | devtool: 'cheap-module-source-map', 7 | }); 8 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "timezone": "Europe/Helsinki", 5 | "schedule": ["before 3am on the first day of the month"], 6 | "dependencyDashboard": false 7 | } 8 | -------------------------------------------------------------------------------- /src/icons/Underline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | postcss.config.js 2 | tailwind.config.js 3 | eslintrc.json 4 | tailwind.css 5 | editor.css 6 | package.json 7 | package-lock.json 8 | webpack.*.js 9 | tsconfig.json 10 | manifest.json 11 | *.svg 12 | commitlint.config.js 13 | release.config.js 14 | -------------------------------------------------------------------------------- /src/icons/Code Arrows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/icons/Search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/utils/types/Note.ts: -------------------------------------------------------------------------------- 1 | import { JSONContent } from '@tiptap/react'; 2 | 3 | export interface Note { 4 | id: number | undefined; 5 | title: string; 6 | isFavorite: boolean; 7 | noteContent: JSONContent | string; 8 | created?: string; 9 | updated?: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/icons/Quote.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/X.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff", 8 | "useMendCheckNames": true 9 | }, 10 | "issueSettings": { 11 | "minSeverityLevel": "LOW", 12 | "issueType": "DEPENDENCY" 13 | } 14 | } -------------------------------------------------------------------------------- /src/components/Elements/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Button/Button'; 2 | export * from './Spinner/Spinner'; 3 | export * from './SelectField/SelectField'; 4 | export * from './SelectField/SelectFieldSpinner'; 5 | export * from './Input/Input'; 6 | export { default as Tab } from './Tab/Tab'; 7 | export { default as TabContent } from './Tab/TabContent'; 8 | -------------------------------------------------------------------------------- /src/icons/BoxDownload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/Star2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/H.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/icons/Redo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/icons/Undo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/icons/Bold.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 120, 5 | "tabWidth": 2, 6 | "semi": true, 7 | "bracketSpacing": true, 8 | "arrowParens": "always", 9 | "endOfLine": "auto", 10 | "overrides": [ 11 | { 12 | "files": "*.svg", 13 | "options": { 14 | "parser": "html" 15 | } 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/icons/AddCamera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/Elements/Spinner/__tests__/spinner.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import { Spinner } from '../Spinner'; 3 | 4 | describe('spinner component', () => { 5 | it('should render with loading text ', async () => { 6 | render(); 7 | 8 | const result = screen.queryByText('Loading'); 9 | 10 | expect(result).toBeInTheDocument(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/icons/ArrowUp.tsx: -------------------------------------------------------------------------------- 1 | import { SVGProps } from 'react'; 2 | 3 | const ArrowUp = (props: SVGProps) => ( 4 | 5 | 11 | 12 | ); 13 | 14 | export default ArrowUp; 15 | -------------------------------------------------------------------------------- /src/icons/ArrowDown.tsx: -------------------------------------------------------------------------------- 1 | import { SVGProps } from 'react'; 2 | 3 | const ArrowDown = (props: SVGProps) => ( 4 | 5 | 11 | 12 | ); 13 | 14 | export default ArrowDown; 15 | -------------------------------------------------------------------------------- /src/background/activeWindow.ts: -------------------------------------------------------------------------------- 1 | import { localStorage } from '@utils/storage'; 2 | 3 | // active tab changed 4 | // Needs to save last two active tabs 5 | // so we can access the last active tab and not the extension tab 6 | let activeWindows: Array<{ windowId: number }> = []; 7 | chrome.windows.onFocusChanged.addListener((windowId) => { 8 | activeWindows = activeWindows.slice(0, 1); 9 | activeWindows.unshift({ windowId }); 10 | localStorage.set('activeWindow', activeWindows); 11 | }); 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | *.pem 17 | 18 | # debug 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | .pnpm-debug.log* 23 | 24 | # local env files 25 | .env*.local 26 | 27 | # typescript 28 | *.tsbuildinfo 29 | 30 | # pnpm ignore 31 | pnpm-lock.yaml -------------------------------------------------------------------------------- /src/icons/Camera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/icons/ClearFormatting.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/icons/Folder.tsx: -------------------------------------------------------------------------------- 1 | import { SVGProps } from 'react'; 2 | 3 | const FolderIcon = ({ active, ...props }: SVGProps & { active: boolean }) => ( 4 | 5 | 9 | 10 | 11 | ); 12 | 13 | export default FolderIcon; 14 | -------------------------------------------------------------------------------- /src/icons/Delete.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/styles/scrollbar.css: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar { 2 | width: 5px; 3 | height: 5px; 4 | } 5 | 6 | ::-webkit-scrollbar:hover { 7 | cursor: pointer; 8 | } 9 | 10 | ::-webkit-scrollbar-track { 11 | border-radius: 100vh; 12 | /* background: theme('colors.blue.prussian' / 20%); */ 13 | } 14 | 15 | ::-webkit-scrollbar-thumb { 16 | /* background: theme('colors.blue.prussian' / 80%); */ 17 | border-radius: 100vh; 18 | /* border: 3px solid theme('colors.blue.prussian' / 80%); */ 19 | } 20 | 21 | ::-webkit-scrollbar-thumb:hover { 22 | /* background: theme('colors.blue.prussian'); */ 23 | cursor: pointer; 24 | } 25 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import react from '@vitejs/plugin-react'; 4 | import path from 'path'; 5 | import { defineConfig } from 'vite'; 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [react()], 10 | test: { 11 | globals: true, 12 | environment: 'jsdom', 13 | setupFiles: './src/config/test-setup.ts', 14 | }, 15 | resolve: { 16 | alias: { 17 | // eslint-disable-next-line @typescript-eslint/naming-convention 18 | '@icons': path.resolve('/src/icons'), 19 | }, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /src/utils/tesseractLanguage.ts: -------------------------------------------------------------------------------- 1 | import { SelectOptionType } from '@components/Elements'; 2 | 3 | export const tesseractLanguages: SelectOptionType[] = [ 4 | { value: 'eng', label: 'English' }, 5 | 6 | { value: 'deu', label: 'German' }, 7 | 8 | { value: 'fin', label: 'Finnish' }, 9 | 10 | { value: 'sqi', label: 'Albanian' }, 11 | 12 | { value: 'nep', label: 'Nepali' }, 13 | 14 | { value: 'swe', label: 'Swedish' }, 15 | 16 | { value: 'tur', label: 'Turkish' }, 17 | 18 | { value: 'fra', label: 'French' }, 19 | 20 | { value: 'ita', label: 'Italian' }, 21 | 22 | { value: 'ara', label: 'Arabic' }, 23 | ]; 24 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branches: 'main', 3 | repositoryUrl: 'https://github.com/MuhametSmaili/note-it', 4 | plugins: [ 5 | '@semantic-release/commit-analyzer', 6 | '@semantic-release/release-notes-generator', 7 | [ 8 | 'semantic-release-chrome', 9 | { 10 | asset: 'note-it.zip', 11 | distFolder: 'build', 12 | extensionId: 'gkfjolpbhbinhoaehiejoglclongclld', 13 | }, 14 | ], 15 | [ 16 | '@semantic-release/github', 17 | { 18 | assets: [{ name: 'note-it-${nextRelease.version}.zip', path: 'note-it.zip' }], 19 | }, 20 | ], 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /src/icons/Save.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/utils/getCurrentTab.ts: -------------------------------------------------------------------------------- 1 | import { localStorage } from './storage'; 2 | 3 | export async function getCurrentTab() { 4 | const isPopup = await localStorage.get('settings'); 5 | const activatedTabs = await localStorage.get('activeWindow'); 6 | const queryOptions: any = { 7 | active: true, 8 | windowType: 'normal', 9 | url: 'https://*/*', 10 | }; 11 | 12 | if (isPopup.windowType && isPopup.windowType === 'popup') { 13 | queryOptions.currentWindow = true; 14 | } else { 15 | if (activatedTabs) { 16 | queryOptions.windowId = activatedTabs[1].windowId; 17 | } 18 | } 19 | 20 | const [tab] = await chrome.tabs.query(queryOptions); 21 | return tab; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/NotesFolder/SearchNotes.tsx: -------------------------------------------------------------------------------- 1 | import { ChangeEvent } from 'react'; 2 | import { Input } from '@components/Elements'; 3 | // icons 4 | import Search from '@icons/Search.svg'; 5 | 6 | type SearchNotesProps = { onSearchHandler: (e: ChangeEvent) => void; value?: string }; 7 | 8 | export const SearchNotes = ({ onSearchHandler, value }: SearchNotesProps) => { 9 | return ( 10 |
11 | 12 |
13 | 14 |
15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/icons/BulletPointsDots.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/Elements/Tab/Tab.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | 3 | type TabProps = { 4 | isActive?: boolean; 5 | onClickHandler: () => void; 6 | className?: string; 7 | children?: JSX.Element[] | JSX.Element; 8 | }; 9 | const Tab = ({ isActive = false, onClickHandler, children, className }: TabProps) => ( 10 | 22 | ); 23 | 24 | export default Tab; 25 | -------------------------------------------------------------------------------- /src/components/NotesFolder/utils/filterNotes.ts: -------------------------------------------------------------------------------- 1 | import { Note } from '@utils/types/Note'; 2 | 3 | export type Filter = { 4 | showFavorites: boolean; 5 | title?: string; 6 | }; 7 | 8 | export const getFilteredNotes = (filter: Filter, notes?: Array) => { 9 | if (filter.showFavorites && filter.title) { 10 | return notes?.filter( 11 | (note) => note.isFavorite === true && note.title.toLowerCase().startsWith(filter.title?.toLowerCase() || ''), 12 | ); 13 | } else if (filter.showFavorites) { 14 | return notes?.filter((note) => note.isFavorite === true); 15 | } else if (filter.title) { 16 | return notes?.filter((note) => note.title.toLowerCase().startsWith(filter.title?.toLowerCase() || '')); 17 | } 18 | return notes; 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/Elements/Tab/__tests__/tabContent.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import TabContent from '../TabContent'; 3 | 4 | describe('TabContent', () => { 5 | it('should not render children if it`s inactive', () => { 6 | render( 7 | 8 |

Should not be visible

9 |
, 10 | ); 11 | 12 | expect(screen.queryByText('Should not be visible')).not.toBeInTheDocument(); 13 | }); 14 | 15 | it('should render children for active props', () => { 16 | render( 17 | 18 |

Should be visible

19 |
, 20 | ); 21 | 22 | expect(screen.queryByText('Should be visible')).toBeInTheDocument(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/Elements/Input/Input.tsx: -------------------------------------------------------------------------------- 1 | import { InputHTMLAttributes } from 'react'; 2 | 3 | import clsx from 'clsx'; 4 | 5 | type InputProps = { 6 | className?: string; 7 | } & Partial>; 8 | 9 | export const Input = (props: InputProps): JSX.Element => { 10 | const { className, value, ...rest } = props; 11 | 12 | return ( 13 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/icons/Star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/Elements/Tab/TabContent.tsx: -------------------------------------------------------------------------------- 1 | import React, { LazyExoticComponent } from 'react'; 2 | import { Spinner } from '../Spinner/Spinner'; 3 | 4 | type TabContentProps = { 5 | component?: LazyExoticComponent<() => JSX.Element>; 6 | isActive: boolean; 7 | children: JSX.Element; 8 | }; 9 | 10 | const TabContent = (props: TabContentProps) => { 11 | return ( 12 | 15 | 16 | 17 | } 18 | > 19 | {props.isActive && ( 20 |
21 | {props.children} 22 |
23 | )} 24 |
25 | ); 26 | }; 27 | 28 | export default TabContent; 29 | -------------------------------------------------------------------------------- /src/components/NoteEditor/utils/pasteHandler.ts: -------------------------------------------------------------------------------- 1 | // Handle paste image into note editor 2 | export function paste(view: any, event: any) { 3 | // USED when we paste image from clipboard 4 | let hasFiles = false; 5 | const reader = new FileReader(); 6 | reader.onload = function (event) { 7 | const imageUrl = event?.target?.result; 8 | const node = view.state.schema.nodes.image.create({ src: imageUrl }); 9 | const transaction = view.state.tr.replaceSelectionWith(node); 10 | view.dispatch(transaction); 11 | }; 12 | 13 | Array.from(event?.clipboardData?.files) 14 | .filter((item: any) => item.type.startsWith('image')) 15 | .forEach((item: any) => { 16 | reader.readAsDataURL(item); 17 | hasFiles = true; 18 | }); 19 | 20 | if (hasFiles) { 21 | event.preventDefault(); 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/NotesFolder/Note/ConfirmNoteDeletion.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@components/Elements'; 2 | 3 | type ConfirmDeleteProps = { 4 | onConfirmHandler: () => void; 5 | onDeclineHandler: () => void; 6 | }; 7 | 8 | export const ConfirmNoteDeletion = ({ onConfirmHandler, onDeclineHandler }: ConfirmDeleteProps) => { 9 | return ( 10 |
11 |

Are you sure you want to delete this note?

12 |
13 | 16 | 19 |
20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Note It - Notes & OCR", 3 | "description": "A feature-packed note-taking extension with OCR support.", 4 | "author": "Muhamet Smaili", 5 | "version": "0.0.1", 6 | "manifest_version": 3, 7 | "icons": { 8 | "16": "./icon/16 x 16.png", 9 | "32": "./icon/32 x 32.png", 10 | "48": "./icon/48 x 48.png", 11 | "128": "./icon/128 x 128.png" 12 | }, 13 | "action": { 14 | "default_title": "NoteIt" 15 | }, 16 | "background": { 17 | "service_worker": "background.js" 18 | }, 19 | "permissions": ["tabs", "activeTab", "scripting", "clipboardWrite", "storage", "unlimitedStorage", "contextMenus"], 20 | "host_permissions": [""], 21 | "web_accessible_resources": [ 22 | { 23 | "resources": ["./libraries/worker.min.js", "frameContent.html"], 24 | "matches": [""] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "allowSyntheticDefaultImports": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "resolveJsonModule": true, 13 | "isolatedModules": false, 14 | "jsx": "react-jsx", 15 | "incremental": true, 16 | "baseUrl": ".", 17 | "types": ["vitest/globals", "chrome"], 18 | "paths": { 19 | "@components/*": ["./src/components/*"], 20 | "@utils/*": ["./src/utils/*"], 21 | "@styles/*": ["./src/styles/*"], 22 | "@icons/*": ["./src/icons/*"], 23 | "@hooks/*": ["./src/hooks/*"] 24 | } 25 | }, 26 | "include": ["**/*.ts", "**/*.tsx"], 27 | "exclude": ["node_modules", "build"] 28 | } 29 | -------------------------------------------------------------------------------- /src/components/NoteEditor/MenuBar/MenuItem/ScreenshotArea.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@components/Elements'; 2 | import { getCurrentTab } from '@utils/getCurrentTab'; 3 | import { MessageRequest } from '@utils/types/MessageRequest'; 4 | import Camera from '@icons/Camera.svg'; 5 | import { localStorage } from '@utils/storage'; 6 | 7 | export const ScreenshotArea = () => { 8 | const screenshotHandler = async () => { 9 | const isWindow = await localStorage.get('settings'); 10 | const tab = await getCurrentTab(); 11 | if (isWindow.windowType && isWindow.windowType === 'popup') { 12 | setTimeout(() => { 13 | window.close(); 14 | }, 200); 15 | } 16 | 17 | chrome.runtime.sendMessage({ 18 | message: MessageRequest.CROP_SCREEN, 19 | tab: tab, 20 | }); 21 | }; 22 | 23 | return ( 24 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/NoteEditor/MenuBar/MenuItem/DownloadNote.tsx: -------------------------------------------------------------------------------- 1 | import { Editor } from '@tiptap/react'; 2 | import htmlToPdfMake from 'html-to-pdfmake'; 3 | import * as pdfMake from 'pdfmake/build/pdfmake'; 4 | import * as pdfFonts from 'pdfmake/build/vfs_fonts'; 5 | import { Button } from '@components/Elements'; 6 | import Download from '@icons/BoxDownload.svg'; 7 | 8 | export const DownloadNote = ({ editor }: { editor: Editor }) => { 9 | const downloadHandler = async () => { 10 | const pdfMakeObject = htmlToPdfMake(editor.getHTML()); 11 | (pdfMake as typeof pdfMake).vfs = pdfFonts.pdfMake.vfs; 12 | pdfMake 13 | .createPdf( 14 | { 15 | content: pdfMakeObject, 16 | info: { author: 'NoteIt', creationDate: new Date(), title: 'note' }, 17 | }, 18 | undefined, 19 | ) 20 | .download('note-it.pdf'); 21 | }; 22 | 23 | return ( 24 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/icons/Upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/icons/PasteFromClipboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug, enhancement 6 | assignees: MuhametSmaili 7 | 8 | --- 9 | 10 | ## Describe the bug 11 | A clear and concise description of what the bug is. 12 | 13 | ## Steps to Reproduce 14 | Steps to reproduce the behavior: 15 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error (and so on) 20 | 21 | ## Expected behavior 22 | A clear and concise description of what you expected to happen. 23 | 24 | ## Screenshots (optional) 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | ## Environment 28 | **Please complete the following information** 29 | 30 | * Browser 31 | - [ ] Chrome 32 | - [ ] Firefox * 33 | - [ ] Edge ** 34 | 35 | - OS 36 | - [ ] Linux 37 | - [ ] Windows 38 | - [ ] MacOS 39 | 40 | --- 41 | (*) Currently, we do not support Firefox (maybe in the future we will) 42 | 43 | (**) Edge now uses chromium, so the extension should work (BUT we have not tested the extension there) 44 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | branches: [main] 5 | push: 6 | branches: [main] 7 | 8 | jobs: 9 | pre-build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - name: Cache dependencies 15 | uses: actions/cache@v3 16 | with: 17 | path: ~/.npm 18 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 19 | restore-keys: | 20 | ${{ runner.os }}-node- 21 | 22 | - name: Use NodeJS 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: '20.8.1' 26 | - run: npm ci 27 | - run: npx lint-staged 28 | - run: npm run test 29 | env: 30 | CI: true 31 | 32 | - name: Build project 33 | if: github.event_name == 'push' 34 | run: npm run build 35 | 36 | - name: Create a release 37 | if: github.event_name == 'push' 38 | run: npx semantic-release 39 | env: 40 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 41 | GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} 42 | GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} 43 | GOOGLE_REFRESH_TOKEN: ${{ secrets.GOOGLE_REFRESH_TOKEN }} 44 | -------------------------------------------------------------------------------- /src/components/Elements/Spinner/Spinner.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | 3 | const sizes = { 4 | sm: 'h-4 w-4', 5 | md: 'h-8 w-8', 6 | }; 7 | 8 | const variants = { 9 | light: 'text-white', 10 | primary: 'text-blue-prussian', 11 | }; 12 | 13 | export type SpinnerProps = { 14 | size?: keyof typeof sizes; 15 | variant?: keyof typeof variants; 16 | className?: string; 17 | hidden?: number; 18 | }; 19 | 20 | export const Spinner = ({ size = 'md', variant = 'primary', className = '', hidden = 1 }: SpinnerProps) => { 21 | return ( 22 |
23 | 30 | 31 | 36 | 37 | Loading 38 |
39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /src/provider/tabContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | //@es-ignore 4 | enum Actions { 5 | TAB_HANDLER = 'TAB_HANDLER', 6 | } 7 | 8 | type Dispatch = (action: Action) => void; 9 | type State = { activeTab: number }; 10 | type ActiveTabProviderProps = { children: React.ReactNode }; 11 | interface Action { 12 | type: keyof typeof Actions; 13 | payload: number; 14 | } 15 | 16 | const ActiveTabStateContext = React.createContext<{ state: State; dispatch: Dispatch } | undefined>(undefined); 17 | 18 | function activeTabProvider(state: State, action: Action) { 19 | switch (action.type) { 20 | case 'TAB_HANDLER': { 21 | return { activeTab: action.payload }; 22 | } 23 | default: { 24 | throw new Error(`Unhandled action type: ${action.type}`); 25 | } 26 | } 27 | } 28 | 29 | const ActiveTabProvider = ({ children }: ActiveTabProviderProps) => { 30 | const [state, dispatch] = React.useReducer(activeTabProvider, { activeTab: 0 }); 31 | const value = { state, dispatch }; 32 | return {children}; 33 | }; 34 | 35 | function useTab() { 36 | const context = React.useContext(ActiveTabStateContext); 37 | if (context === undefined) { 38 | throw new Error('useTab must be used within the TabProvider'); 39 | } 40 | return context; 41 | } 42 | 43 | export { ActiveTabProvider, useTab }; 44 | -------------------------------------------------------------------------------- /src/icons/settings-icon.tsx: -------------------------------------------------------------------------------- 1 | import { SVGProps } from 'react'; 2 | 3 | const SettingsIcon = ({ active, ...props }: SVGProps & { active: boolean }) => ( 4 | 5 | 11 | 17 | 18 | ); 19 | 20 | export default SettingsIcon; 21 | -------------------------------------------------------------------------------- /src/background/messageListener.ts: -------------------------------------------------------------------------------- 1 | import { localStorage } from '@utils/storage'; 2 | import { MessageRequest } from '@utils/types/MessageRequest'; 3 | // Communicating with worker/client 4 | chrome.runtime.onMessage.addListener((request, sender, _response) => { 5 | if (request.message === MessageRequest.CROP_SCREEN) { 6 | // Executing the crop script for cropping inside window 7 | // Checking for tabId, because when we retake screen from frameContent the tabId comes from sender 8 | const query: QueryTab = { tabId: 0, windowId: 0 }; 9 | 10 | if (!request.tab) { 11 | request.tab = sender.tab; 12 | } 13 | 14 | query.tabId = Number(request.tab?.id); 15 | query.windowId = Number(request.tab?.windowId); 16 | 17 | // TODO needs checking for better implementation for passing the 18 | chrome.tabs.captureVisibleTab(query.windowId, {}, function (dataUrl) { 19 | localStorage.set('screenshot', { capturedImage: dataUrl, cropArea: { height: 0, width: 0, x: 0, y: 0 } }); 20 | chrome.scripting.executeScript({ 21 | target: { tabId: Number(query.tabId) }, 22 | files: ['cropArea.js'], 23 | }); 24 | }); 25 | return true; 26 | } else if (request.message === MessageRequest.INSERT_FRAME) { 27 | chrome.scripting.executeScript({ 28 | target: { tabId: Number(sender.tab?.id) }, 29 | files: ['frameScript.js'], 30 | }); 31 | return true; 32 | } 33 | }); 34 | 35 | type QueryTab = { 36 | tabId: number; 37 | windowId: number; 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/Elements/SelectField/__tests__/selectFieldSpinner.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import { SelectOptionType, SelectValue } from '../SelectField'; 3 | import { SelectFieldSpinner } from '../SelectFieldSpinner'; 4 | 5 | describe('SelectFieldSpinner', () => { 6 | it('should increase Value in button down', async () => { 7 | const options: Array = [ 8 | { label: 'first', value: 1 }, 9 | { label: 'second', value: 2 }, 10 | ]; 11 | 12 | let result; 13 | 14 | const onChangeHandler = (selectedValue: SelectValue) => { 15 | result = selectedValue; 16 | }; 17 | 18 | render(); 19 | 20 | const incrementButton = screen.getByRole('button', { name: /increment/i }); 21 | incrementButton.click(); 22 | 23 | expect(result).toBe(options[1].value); 24 | }); 25 | 26 | it('should decrease Value in button up', () => { 27 | const options: Array = [ 28 | { label: 'first', value: 1 }, 29 | { label: 'second', value: 2 }, 30 | ]; 31 | 32 | let result; 33 | 34 | const onChangeHandler = (selectedValue: SelectValue) => { 35 | result = selectedValue; 36 | }; 37 | 38 | render(); 39 | 40 | const decrementButton = screen.getByRole('button', { name: /decrement/i }); 41 | decrementButton.click(); 42 | 43 | expect(result).toBe(options[0].value); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/components/NoteEditor/MenuBar/MenuItem/Heading.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Editor } from '@tiptap/react'; 3 | import clsx from 'clsx'; 4 | import { SelectFieldSpinner } from '@components/Elements'; 5 | import H from '@icons/H.svg'; 6 | 7 | type Level = 1 | 2 | 3; //| 4 | 5 | 6 8 | export const Heading = ({ editor }: { editor: Editor }) => { 9 | const [value, setValue] = useState(1); 10 | return ( 11 |
17 |
19 | editor 20 | .chain() 21 | .focus() 22 | .toggleHeading({ level: Number(value) as Level }) 23 | .run() 24 | } 25 | > 26 | 27 |
28 | { 34 | setValue(Number(val)); 35 | editor 36 | .chain() 37 | .focus() 38 | .toggleHeading({ level: Number(val) as Level }) 39 | .run(); 40 | }} 41 | /> 42 |
43 | ); 44 | }; 45 | 46 | const headingStyle = [ 47 | { 48 | value: 1, 49 | label: '', 50 | }, 51 | { 52 | value: 2, 53 | label: '', 54 | }, 55 | { 56 | value: 3, 57 | label: '', 58 | }, 59 | ]; 60 | -------------------------------------------------------------------------------- /src/components/Elements/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonHTMLAttributes, forwardRef } from 'react'; 2 | import clsx from 'clsx'; 3 | import { Spinner } from '@components/Elements'; 4 | 5 | const variants = { 6 | primary: 'bg-light border border-primary text-secondary', 7 | inverse: 'bg-white text-blue-prussian active:bg-gray-true', 8 | }; 9 | 10 | const sizes = { 11 | sm: ' text-sm', //py-1 px-1 12 | md: 'py-1 px-2 text-md', 13 | }; 14 | 15 | type ButtonProps = { 16 | variant?: keyof typeof variants; 17 | size?: keyof typeof sizes; 18 | isLoading?: boolean; 19 | active?: boolean; 20 | icon?: JSX.Element; 21 | } & ButtonHTMLAttributes; 22 | 23 | export const Button = forwardRef( 24 | ( 25 | { className = '', icon, variant = 'primary', size = 'md', isLoading = false, active = false, disabled, ...props }, 26 | ref, 27 | ) => { 28 | return ( 29 | 46 | ); 47 | }, 48 | ); 49 | 50 | Button.displayName = 'Button'; 51 | -------------------------------------------------------------------------------- /src/components/Dashboard/Dashboard.tsx: -------------------------------------------------------------------------------- 1 | import { Tab } from '@components/Elements'; 2 | import clsx from 'clsx'; 3 | // ICONS 4 | import Feather from '@icons/Feather'; 5 | import FolderIcon from '@icons/Folder'; 6 | import Logo from '@icons/Logo.svg'; 7 | import OcrIcon from '@icons/OcrIcon'; 8 | import SettingsIcon from '@icons/settings-icon'; 9 | import { useTab } from '../../provider/tabContext'; 10 | 11 | const Dashboard = () => { 12 | const { 13 | dispatch, 14 | state: { activeTab }, 15 | } = useTab(); 16 | 17 | const onTabClick = (tab: number) => { 18 | dispatch({ type: 'TAB_HANDLER', payload: tab }); 19 | }; 20 | 21 | return ( 22 |
30 |
31 | 32 |
33 | onTabClick(0)} isActive={activeTab === 0}> 34 | 35 | 36 | onTabClick(1)} isActive={activeTab === 1}> 37 | 38 | 39 | onTabClick(2)} isActive={activeTab === 2}> 40 | 41 | 42 | onTabClick(3)} isActive={activeTab === 3} className="mt-auto mb-2"> 43 | 44 | 45 |
46 | ); 47 | }; 48 | 49 | export default Dashboard; 50 | -------------------------------------------------------------------------------- /src/styles/editor.css: -------------------------------------------------------------------------------- 1 | /* Basic editor styles */ 2 | @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,300;0,400;0,700;1,300;1,400;1,700&display=swap'); 3 | 4 | .ProseMirror { 5 | font-family: 'Poppins', sans-serif; 6 | min-width: 658px; 7 | min-height: 480px; 8 | max-height: calc(100vh - 70px); 9 | overflow-y: auto; 10 | padding-right: 5px; 11 | font-size: 1rem; 12 | @apply border-none text-body; 13 | } 14 | 15 | .ProseMirror-focused { 16 | border: none; 17 | outline: none; 18 | } 19 | 20 | .ProseMirror p { 21 | margin-top: 2px; 22 | } 23 | 24 | .ProseMirror.ProseMirror-focused { 25 | border: none; 26 | focus: none; 27 | } 28 | 29 | .ProseMirror code { 30 | display: block; 31 | /* background: theme('colors.blue.prussian' / 90%); */ 32 | width: 100%; 33 | padding: 1rem; 34 | @apply rounded-sm text-light bg-acent; 35 | } 36 | 37 | .ProseMirror img { 38 | border-radius: 5px; 39 | } 40 | 41 | .ProseMirror ul { 42 | @apply pl-7 list-disc; 43 | } 44 | 45 | .ProseMirror blockquote { 46 | @apply pl-7 ml-5 border-l-2 border-solid italic border-primary text-light2; 47 | /* border-color: theme('colors.blue.prussian' / 20%); */ 48 | /* color: theme('colors.blue.prussian' / 80%); */ 49 | } 50 | 51 | .ProseMirror h1 { 52 | @apply text-xl font-bold text-light; 53 | } 54 | 55 | .ProseMirror h2 { 56 | @apply text-md font-bold text-light; 57 | } 58 | 59 | .ProseMirror h3 { 60 | @apply text-sm font-bold text-light; 61 | } 62 | 63 | .ProseMirror a { 64 | /* color: theme('colors.blue.prussian'); */ 65 | text-decoration: underline; 66 | @apply text-light; 67 | } 68 | 69 | .ProseMirror a:hover { 70 | cursor: pointer; 71 | } 72 | -------------------------------------------------------------------------------- /src/icons/ColorPalette.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/popup/popup.tsx: -------------------------------------------------------------------------------- 1 | import '@styles/scrollbar.css'; 2 | import '@styles/tailwind.css'; 3 | import { rootRender } from '@utils/render'; 4 | import React from 'react'; 5 | 6 | import Dashboard from '@components/Dashboard/Dashboard'; 7 | import { TabContent } from '@components/Elements'; 8 | 9 | import { Settings } from '@components/settings/settings'; 10 | import { preloadSettings, useStorage } from '@hooks/useStore'; 11 | import { ActiveTabProvider, useTab } from '../provider/tabContext'; 12 | // Tab contents 13 | const NoteEditor = React.lazy(() => import(/* webpackPrefetch: true */ '@components/NoteEditor/NoteEditor')); 14 | const FolderNotes = React.lazy(() => import('@components/NotesFolder/NotesFolder')); 15 | const Ocr = React.lazy(() => import('@components/Ocr/Ocr')); 16 | 17 | const App: React.FC = () => { 18 | const { 19 | state: { activeTab }, 20 | } = useTab(); 21 | 22 | const [settings] = useStorage('settings', { theme: 'light' }); 23 | 24 | return ( 25 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | ); 45 | }; 46 | 47 | await preloadSettings(); 48 | rootRender.render( 49 | 50 | 51 | 52 | 53 | , 54 | ); 55 | -------------------------------------------------------------------------------- /src/contentScript/frameScript.tsx: -------------------------------------------------------------------------------- 1 | import { rootRender } from '@utils/render'; 2 | import { MessageRequest } from '@utils/types/MessageRequest'; 3 | import '@styles/tailwind.css'; 4 | // ICONS 5 | import Close from '@icons/X.svg'; 6 | import CameraRetake from '@icons/CameraRetake.svg'; 7 | import { localStorage } from '@utils/storage'; 8 | 9 | const FrameScript = () => { 10 | const handleClose = () => { 11 | document.getElementById(`OCRFrame`)?.remove(); 12 | localStorage.remove('screenshot'); 13 | rootRender.unmount(); 14 | }; 15 | 16 | const onRetakeImageHandler = async () => { 17 | handleClose(); 18 | setTimeout(() => { 19 | chrome.runtime.sendMessage({ 20 | message: MessageRequest.CROP_SCREEN, 21 | }); 22 | }, 0); 23 | }; 24 | 25 | return ( 26 |
27 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 |