diff --git a/.changeset/config.json b/.changeset/config.json index de3152d1e..1951f2917 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -29,15 +29,12 @@ "@penx/check-list", "@penx/divider", "@penx/image", - "@penx/paragraph", "@penx/word-count", "@penx/block-selector", "@penx/code-block", "@penx/file", "@penx/link", "@penx/storage-estimate", - "@penx/editor-shared", - "@penx/extension-dev-server", "@penx/local-db", "@penx/session", "@penx/app", @@ -45,52 +42,31 @@ "@penx/mnemonic", "@penx/math", "@penx/sync-server-client", - "@penx/dnd-projection", - "@penx/editor-transforms", - "@penx/extension-list", "@penx/model", "@penx/shared", "@penx/google-translate", "@penx/unique-id", - "@penx/autoformat", "@penx/editor", - "@penx/editor-types", - "@penx/extension-typings", "@penx/model-types", "@penx/slate-lists", - "@penx/worker", "@penx/catalogue", - "@penx/editor-common", "@penx/encryption", "@penx/hooks", "@penx/remark-slate", "@penx/store", "@penx/cmdk", - "@penx/editor-composition", "@penx/eslint-config", "@penx/icons", "@penx/sdk", - "@penx/sync", "@penx/constants", - "@penx/editor-leaf", "eslint-config-custom", - "@penx/serializer", "@penx/trpc-client", - "@penx/context-menu", - "@penx/editor-queries", "@penx/event", "@penx/loader", - "@penx/service", "@penx/tsconfig", - "@penx/database-ui", - "@penx/cell-fields", "@penx/types", "@penx/table-search", - "@penx/database-context", "@penx/storage", - "@penx/node-normalizer", - "@penx/node-hooks", - "@penx/extension-store", "@penx/google-drive", "uikit" ] diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 4fe5494c3..2cb985f61 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -21,11 +21,8 @@ "@octokit/auth-app": "^6.0.1", "@octokit/oauth-app": "^6.0.0", "@octokit/rest": "^20.0.2", - "@penx/app": "workspace:*", - "@penx/cell-fields": "workspace:*", "@penx/cmdk": "workspace:*", "@penx/constants": "workspace:*", - "@penx/database-context": "workspace:*", "@penx/encryption": "workspace:*", "@penx/event": "workspace:*", "@penx/hooks": "workspace:*", @@ -42,7 +39,6 @@ "@penx/trpc-client": "workspace:*", "@penx/unique-id": "workspace:*", "@penx/widget": "workspace:*", - "@penx/worker": "workspace:*", "@penxio/api": "workspace:*", "@penxio/preset-ui": "workspace:*", "@tanstack/react-query": "^5.45.1", diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 37ebad4b9..65e2237bc 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -7,19 +7,19 @@ import { TrpcProvider } from '@penx/trpc-client' import '@glideapps/glide-data-grid/dist/index.css' import { Fomir } from 'fomir' import { fixPathEnv } from 'tauri-plugin-shellx-api' -import { registerDefaultAppHotkey } from '@penx/app' import { handleEscape } from './common/handleEscape' import { initApplicationCommands } from './common/initApplicationCommands' import { initFower } from './common/initFower' import { initHotkeys } from './common/initHotkeys' import { installBuiltinCreations } from './common/installBuiltinCreations' +import { watchCreationDevChange } from './common/watchCreationDevChange' import { watchDesktopLogin } from './common/watchDesktopLogin' -import { watchCreationDevChange } from './common/watchExtensionDevChange' import { useInitThemeMode } from './hooks/useInitThemeMode' import { MainApp } from './MainApp' import '~/styles/globals.css' import '~/styles/command.scss' import FomirUIkit from './fomir-uikit' +import { registerDefaultAppHotkey } from './components/BindAppHotkey' import { config } from './config' initFower() diff --git a/apps/desktop/src/MainApp.tsx b/apps/desktop/src/MainApp.tsx index c3bd73b61..9847bb65c 100644 --- a/apps/desktop/src/MainApp.tsx +++ b/apps/desktop/src/MainApp.tsx @@ -65,13 +65,7 @@ export function MainApp() { if (isLoading) return null return ( - - {session && } + <> } - + ) } diff --git a/apps/desktop/src/components/AuthProvider.tsx b/apps/desktop/src/components/AuthProvider.tsx deleted file mode 100644 index aa4911d59..000000000 --- a/apps/desktop/src/components/AuthProvider.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { PropsWithChildren, useEffect, useState } from 'react' -import { SessionProvider, useSession } from '@penx/session' -import { getLocalSession, setLocalSession } from '@penx/storage' - -function OnlineProvider({ children }: PropsWithChildren) { - const { data: session } = useSession() - - useEffect(() => { - // TODO: - // setLocalSession(session) - }, [session]) - - return ( - - {children} - - ) -} - -function OfflineProvider({ children }: PropsWithChildren) { - const [loading, setLoading] = useState(true) - const [session, setSession] = useState() - - useEffect(() => { - getLocalSession().then((session) => { - if (!session) { - // TODO: - // go to login page - return - } - setSession(session) - setLoading(false) - }) - }, []) - - if (loading || !session) return null - - return ( - - {children} - - ) -} - -export function AuthProvider({ children }: PropsWithChildren) { - const isOnline = navigator.onLine - - if (isOnline) return {children} - return {children} -} diff --git a/packages/app/src/components/BindAppHotkey/constants.ts b/apps/desktop/src/components/BindAppHotkey/constants.ts similarity index 100% rename from packages/app/src/components/BindAppHotkey/constants.ts rename to apps/desktop/src/components/BindAppHotkey/constants.ts diff --git a/packages/app/src/components/BindAppHotkey/hooks/useAppHotkey.ts b/apps/desktop/src/components/BindAppHotkey/hooks/useAppHotkey.ts similarity index 100% rename from packages/app/src/components/BindAppHotkey/hooks/useAppHotkey.ts rename to apps/desktop/src/components/BindAppHotkey/hooks/useAppHotkey.ts diff --git a/packages/app/src/components/BindAppHotkey/index.ts b/apps/desktop/src/components/BindAppHotkey/index.ts similarity index 100% rename from packages/app/src/components/BindAppHotkey/index.ts rename to apps/desktop/src/components/BindAppHotkey/index.ts diff --git a/packages/app/src/components/BindAppHotkey/ui/BindAppHotkey.tsx b/apps/desktop/src/components/BindAppHotkey/ui/BindAppHotkey.tsx similarity index 100% rename from packages/app/src/components/BindAppHotkey/ui/BindAppHotkey.tsx rename to apps/desktop/src/components/BindAppHotkey/ui/BindAppHotkey.tsx diff --git a/packages/app/src/components/BindAppHotkey/utils.ts b/apps/desktop/src/components/BindAppHotkey/utils.ts similarity index 100% rename from packages/app/src/components/BindAppHotkey/utils.ts rename to apps/desktop/src/components/BindAppHotkey/utils.ts diff --git a/apps/desktop/src/components/CommandPalette/CommandApp/CommandApp.tsx b/apps/desktop/src/components/CommandPalette/CommandApp/CommandApp.tsx index a043ff5c5..d96ab9f7b 100644 --- a/apps/desktop/src/components/CommandPalette/CommandApp/CommandApp.tsx +++ b/apps/desktop/src/components/CommandPalette/CommandApp/CommandApp.tsx @@ -10,7 +10,6 @@ import { commandLoadingAtom } from '~/hooks/useCommandAppLoading' import { CommandAppUI } from '~/hooks/useCommandAppUI' import { AboutApp } from './AboutApp' import { ClipboardHistoryApp } from './ClipboardHistoryApp' -import { DatabaseApp } from './DatabaseApp/DatabaseApp' import { FormApp } from './FormApp/FormApp' import { GeneralSettings } from './GeneralSettings/GeneralSettings' import { InstalledCreationsApp } from './InstalledCreationsApp/InstalledCreationsApp' @@ -58,10 +57,6 @@ export const CommandApp = memo( // return // } - // if (ui.type === 'database') { - // return - // } - // if (ui.type === 'clipboard-history') { // return // } diff --git a/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/AddRowForm.tsx b/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/AddRowForm.tsx deleted file mode 100644 index 16a9e3cd3..000000000 --- a/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/AddRowForm.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Box } from '@fower/react' -import { Button, Input } from 'uikit' -import { - ICellNode, - IColumnNode, - IDatabaseNode, - IOptionNode, - IRowNode, - IViewNode, -} from '@penx/model-types' -import { mappedByKey } from '@penx/shared' -import { FieldIcon } from './FieldIcon' - -interface Props { - database: IDatabaseNode - views: IViewNode[] - columns: IColumnNode[] - rows: IRowNode[] - cells: ICellNode[] - options: IOptionNode[] -} - -export function AddRowForm(props: Props) { - const { columns, views, cells } = props - const currentView = views[0] - const columnMap = mappedByKey(columns, 'id') - const { viewColumns = [] } = currentView.props - const sortedColumns = viewColumns.map(({ columnId }) => columnMap[columnId]) - - return ( - - - {sortedColumns.map((column, index) => { - return ( - - - - {column.props.displayName} - - - - ) - })} - - - - - ) -} diff --git a/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/DatabaseApp.tsx b/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/DatabaseApp.tsx deleted file mode 100644 index 7eaba5fb6..000000000 --- a/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/DatabaseApp.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import { db } from '@penx/local-db' -import { useCurrentDatabase } from '~/hooks/useCurrentDatabase' -import { useIsAddRow } from '~/hooks/useIsAddRow' -import { useSearch } from '~/hooks/useSearch' -import { StyledCommandGroup } from '../../CommandComponents' -import { AddRowForm } from './AddRowForm' -import { DatabaseDetail } from './DatabaseDetail' - -export function DatabaseApp() { - const { database } = useCurrentDatabase() - const { search } = useSearch() - const { isAddRow } = useIsAddRow() - - const { data, isLoading } = useQuery({ - queryKey: ['database', database.id], - queryFn: () => db.getDatabase(database.id), - }) - - if (isLoading || !data) { - return - } - - // if (isAddRow) return - return -} diff --git a/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/DatabaseDetail.tsx b/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/DatabaseDetail.tsx deleted file mode 100644 index 3609b6033..000000000 --- a/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/DatabaseDetail.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useEffect, useMemo } from 'react' -import { Box } from '@fower/react' -import { Divider } from 'uikit' -import { DatabaseProvider } from '@penx/database-context' -import { - ICellNode, - IColumnNode, - IDatabaseNode, - IOptionNode, - IRowNode, - IViewNode, -} from '@penx/model-types' -import { mappedByKey } from '@penx/shared' -import { useValue } from '~/hooks/useValue' -import { StyledCommandEmpty, StyledCommandGroup } from '../../CommandComponents' -import { CommandItemUI } from '../../CommandItemUI' -import { RowForm } from './RowForm' - -interface Props { - text: string - database: IDatabaseNode - views: IViewNode[] - columns: IColumnNode[] - rows: IRowNode[] - cells: ICellNode[] - options: IOptionNode[] -} - -interface Item { - row: IRowNode - cell: ICellNode - rowCells: ICellNode[] -} - -export function DatabaseDetail(props: Props) { - const { value, setValue } = useValue() - const { text, ...rest } = props - const { columns, rows, views, cells } = rest - const currentView = views[0] - const { viewColumns = [] } = currentView.props - const columnMap = mappedByKey(columns, 'id') - const sortedColumns = viewColumns.map(({ columnId }) => columnMap[columnId]) - - const filteredRows: Item[] = useMemo(() => { - const items = rows - .map((row) => { - const rowCells = cells.filter((cell) => cell.props.rowId === row.id) - - if (!text) { - const cell = rowCells.find((cell) => cell.props.columnId === sortedColumns[0].id)! - return { row, rowCells, cell } - } - - const cell = rowCells.find((cell) => { - // console.log('cell-----:', cell.props.data) - const data = String(cell.props?.data || '').toLowerCase() - return data.includes(text.toLowerCase()) - })! - return { row, rowCells, cell } - }) - .filter((item) => !!item.cell) - .slice(0, 20) - return items - }, [rows, cells, text, sortedColumns]) - - useEffect(() => { - if (!isUuidV4(value) && filteredRows.length) { - setValue(filteredRows[0].row.id) - } - }, [filteredRows, value, setValue]) - - // console.log('=======filteredRows:', filteredRows, 'value:', value) - const currentItem = filteredRows.find((item) => item.row.id === value) - - if (!filteredRows.length) - return ( - - No results found. - - ) - - return ( - - - {filteredRows.map((item, index) => { - // console.log('=======item:', item) - - // TODO: handle any - const listItem = { - title: dataToString(item.cell.props.data), - value: item.row.id, - } as any - - return - })} - - - - - - {currentItem && ( - - - - )} - - - ) -} - -function dataToString(data: any) { - if (!data) return 'Untitled' - if (typeof data === 'string') return data - if (typeof data === 'number') return data.toString() - if (Array.isArray(data)) return data.join(',') - return JSON.stringify(data, null) -} - -function isUuidV4(uuid: string): boolean { - const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i - return uuidV4Regex.test(uuid) -} diff --git a/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/FieldIcon.tsx b/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/FieldIcon.tsx deleted file mode 100644 index 60be63437..000000000 --- a/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/FieldIcon.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Box } from '@fower/react' -import { - CalendarDays, - CheckCircle2, - Hash, - Home, - Key, - Link, - ListChecks, - Text, -} from 'lucide-react' -import { FieldType } from '@penx/model-types' - -interface Props { - index?: number - fieldType: `${FieldType}` - size?: number -} - -export const FieldIcon = ({ fieldType, size = 16, index }: Props) => { - const iconsMap: Record = { - [FieldType.TEXT]: Text, - [FieldType.NUMBER]: Hash, - [FieldType.URL]: Link, - [FieldType.PASSWORD]: Key, - [FieldType.SINGLE_SELECT]: CheckCircle2, - [FieldType.MULTIPLE_SELECT]: ListChecks, - [FieldType.MARKDOWN]: Text, - [FieldType.DATE]: CalendarDays, - [FieldType.CREATED_AT]: CalendarDays, - [FieldType.UPDATED_AT]: CalendarDays, - } - let Icon = iconsMap[fieldType] - - if (index === 0) Icon = Home - - if (Icon) - return ( - - - - ) - return null -} diff --git a/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/RowForm.tsx b/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/RowForm.tsx deleted file mode 100644 index d4846e499..000000000 --- a/apps/desktop/src/components/CommandPalette/CommandApp/DatabaseApp/RowForm.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { forwardRef } from 'react' -import { Box } from '@fower/react' -import { CellField } from '@penx/cell-fields' -import { - ICellNode, - IColumnNode, - IDatabaseNode, - IOptionNode, - IRowNode, - IViewNode, -} from '@penx/model-types' -import { mappedByKey } from '@penx/shared' -import { FieldIcon } from './FieldIcon' - -interface Props { - rowId: string - database: IDatabaseNode - views: IViewNode[] - columns: IColumnNode[] - rows: IRowNode[] - cells: ICellNode[] - options: IOptionNode[] -} - -export const RowForm = forwardRef( - function TagForm(props, ref) { - const { columns, views, cells, rowId } = props - - // console.log('========cells;', cells, 'rowId:', rowId) - - const currentView = views[0] - - const columnMap = mappedByKey(columns, 'id') - const { viewColumns = [] } = currentView.props - const sortedColumns = viewColumns.map(({ columnId }) => columnMap[columnId]) - - const rowCells = sortedColumns.map((column) => { - return cells.find( - (cell) => - cell.props.rowId === rowId && cell.props.columnId === column.id, - )! - }) - - // console.log('========rowCells:', rowCells) - - return ( - - {rowCells.map((cell, index) => { - const column = columns.find((col) => col.id === cell.props.columnId)! - - if (!column) return null - - return ( - - - - {column.props.displayName} - - - - ) - })} - - ) - }, -) diff --git a/apps/desktop/src/components/CommandPalette/CommandApp/GeneralSettings/GeneralSettings.tsx b/apps/desktop/src/components/CommandPalette/CommandApp/GeneralSettings/GeneralSettings.tsx index 5ca6ac0e6..80041184d 100644 --- a/apps/desktop/src/components/CommandPalette/CommandApp/GeneralSettings/GeneralSettings.tsx +++ b/apps/desktop/src/components/CommandPalette/CommandApp/GeneralSettings/GeneralSettings.tsx @@ -1,6 +1,6 @@ import { Box, styled } from '@fower/react' import { Checkbox } from 'uikit' -import { BindAppHotkey } from '@penx/app' +import { BindAppHotkey } from '~/components/BindAppHotkey' import { ThemeModeSelect } from './ThemeModeSelect' interface Props {} diff --git a/apps/desktop/src/components/CommandPalette/SearchBar/SearchBar.tsx b/apps/desktop/src/components/CommandPalette/SearchBar/SearchBar.tsx index 78abdd74c..13636dbe4 100644 --- a/apps/desktop/src/components/CommandPalette/SearchBar/SearchBar.tsx +++ b/apps/desktop/src/components/CommandPalette/SearchBar/SearchBar.tsx @@ -1,6 +1,4 @@ import { Box } from '@fower/react' -import { BaseDirectory, resolve } from '@tauri-apps/api/path' -import { create, writeTextFile } from '@tauri-apps/plugin-fs' import { appEmitter } from '@penx/event' import { useCommandPosition } from '~/hooks/useCommandPosition' import { useCurrentCommand } from '~/hooks/useCurrentCommand' @@ -28,8 +26,6 @@ export const SearchBar = ({ searchBarHeight }: Props) => { const currentCommandName = currentCommand?.name const isMarketplaceDetail = currentCommandName === 'marketplace' && isCommandAppDetail - const isDatabaseApp = currentCommand?.isDatabase - return ( { > {isCommandApp && } - {isDatabaseApp && } - {isDatabaseApp && } - {!isMarketplaceDetail && ( { - const { data, loading } = useSession() - - if (loading) return null - - if (!navigator.onLine) return <>{children} - console.log('data=========:', data) - - // not logged in - if (!data) { - return {children} - } - - return null -} - -export function EditorModeApp() { - const { isLoading, data, refetch } = useQuery({ - queryKey: ['session'], - queryFn: async () => { - const session = await getLocalSession() - return session ? session : null - }, - }) - - useEffect(() => { - appEmitter.on('LOGIN_BY_PERSONAL_TOKEN_SUCCESSFULLY', () => { - refetch() - }) - }, [refetch]) - - return ( - - - - - - - - ) -} diff --git a/apps/extension/.env.development b/apps/extension/.env.development deleted file mode 100644 index 39c03d831..000000000 --- a/apps/extension/.env.development +++ /dev/null @@ -1,3 +0,0 @@ -PLASMO_PUBLIC_BASE_URL=http://localhost:3000 - -PLASMO_PUBLIC_PLATFORM=EXTENSION \ No newline at end of file diff --git a/apps/extension/.env.production b/apps/extension/.env.production deleted file mode 100644 index 607553c54..000000000 --- a/apps/extension/.env.production +++ /dev/null @@ -1,3 +0,0 @@ -PLASMO_PUBLIC_BASE_URL=https://penx.io - -PLASMO_PUBLIC_PLATFORM=EXTENSION \ No newline at end of file diff --git a/apps/extension/.gitignore b/apps/extension/.gitignore deleted file mode 100644 index dc721e6b9..000000000 --- a/apps/extension/.gitignore +++ /dev/null @@ -1,44 +0,0 @@ - -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -#cache -.turbo - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# local env files -# .env* - -out/ -build/ -dist/ - -# vercel -.vercel -/.next/ -/out/ - -# plasmo - https://www.plasmo.com -.plasmo - -# bpp - http://bpp.browser.market/ -keys.json - -# typescript -.tsbuildinfo diff --git a/apps/extension/.prettierrc.mjs b/apps/extension/.prettierrc.mjs deleted file mode 100644 index 8b4ac8449..000000000 --- a/apps/extension/.prettierrc.mjs +++ /dev/null @@ -1,24 +0,0 @@ -/** - * @type {import('prettier').Options} - */ -export default { - printWidth: 80, - tabWidth: 2, - useTabs: false, - semi: false, - singleQuote: true, - trailingComma: 'all', - 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 - '^@penx/(.*)$', - '', - '^~(.*)$', - '', - '^[./]', - ], -} diff --git a/apps/extension/README.md b/apps/extension/README.md deleted file mode 100644 index ca9c259f3..000000000 --- a/apps/extension/README.md +++ /dev/null @@ -1,33 +0,0 @@ -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 deleted file mode 100644 index 383c1c169..000000000 Binary files a/apps/extension/assets/icon.png and /dev/null differ diff --git a/apps/extension/assets/logo.png b/apps/extension/assets/logo.png deleted file mode 100644 index 383c1c169..000000000 Binary files a/apps/extension/assets/logo.png and /dev/null differ diff --git a/apps/extension/next-env.d.ts b/apps/extension/next-env.d.ts deleted file mode 100644 index 4f11a03dc..000000000 --- a/apps/extension/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/extension/next.config.js b/apps/extension/next.config.js deleted file mode 100644 index aa966781d..000000000 --- a/apps/extension/next.config.js +++ /dev/null @@ -1,56 +0,0 @@ -const path = require('path') - -module.exports = { - reactStrictMode: true, - transpilePackages: [ - '@penx/app', - '@penx/constants', - '@penx/hooks', - '@penx/node-hooks', - '@penx/local-db', - '@penx/editor', - '@penx/editor-queries', - '@penx/editor-shared', - '@penx/editor-transforms', - '@penx/editor-types', - '@penx/editor-common', - '@penx/editor-composition', - '@penx/icons', - '@penx/shared', - '@penx/model', - '@penx/context-menu', - '@penx/service', - '@penx/serializer', - '@penx/extension-store', - '@penx/store', - '@penx/session', - '@penx/model-types', - '@penx/cmdk', - '@penx/event', - '@penx/storage-estimate', - '@penx/word-count', - '@penx/blockquote', - '@penx/divider', - '@penx/check-list', - '@penx/auto-format', - '@penx/paragraph', - '@penx/list', - '@penx/image', - '@penx/file', - '@penx/link', - '@penx/bidirectional-link', - '@penx/table', - '@penx/database', - '@penx/dnd-projection', - '@penx/block-selector', - '@penx/editor-leaf', - '@penx/trpc-client', - '@penx/sync', - '@penx/unique-id', - '@penx/extension-list', - '@penx/worker', - '@penx/widget', - 'uikit', - '@penx/slate-lists', - ], -} diff --git a/apps/extension/package.json b/apps/extension/package.json deleted file mode 100644 index c972fd827..000000000 --- a/apps/extension/package.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "name": "extension", - "displayName": "PenX", - "version": "0.0.6", - "private": true, - "description": "A cross-platform productivity App", - "scripts": { - "start": "next start", - "dev": "run-p dev:*", - "dev:plasmo": "plasmo dev", - "dev:next": "next dev --port 1947", - "build": "run-p build:*", - "build:plasmo": "plasmo build", - "build:next": "next build" - }, - "dependencies": { - "@dnd-kit/core": "^6.1.0", - "@dnd-kit/sortable": "^7.0.2", - "@dnd-kit/utilities": "^3.2.2", - "@emoji-mart/data": "^1.1.2", - "@emoji-mart/react": "^1.1.1", - "@floating-ui/react": "^0.26.3", - "@fower/atomic-props": "^2.1.1", - "@fower/react": "^2.1.1", - "@hookform/resolvers": "^3.3.2", - "@mozilla/readability": "^0.4.4", - "@penx/app": "workspace:*", - "@penx/auto-format": "workspace:*", - "@penx/bidirectional-link": "workspace:*", - "@penx/block-selector": "workspace:*", - "@penx/blockquote": "workspace:*", - "@penx/check-list": "workspace:*", - "@penx/code-block": "workspace:*", - "@penx/constants": "workspace:*", - "@penx/database": "workspace:*", - "@penx/divider": "workspace:*", - "@penx/editor": "workspace:*", - "@penx/editor-common": "workspace:*", - "@penx/editor-shared": "workspace:*", - "@penx/extension-list": "workspace:*", - "@penx/extension-typings": "workspace:*", - "@penx/file": "workspace:*", - "@penx/heading": "workspace:*", - "@penx/hooks": "workspace:*", - "@penx/icons": "workspace:*", - "@penx/image": "workspace:*", - "@penx/link": "workspace:*", - "@penx/list": "workspace:*", - "@penx/loader": "workspace:*", - "@penx/local-db": "workspace:*", - "@penx/model": "workspace:*", - "@penx/model-types": "workspace:*", - "@penx/paragraph": "workspace:*", - "@penx/serializer": "workspace:*", - "@penx/session": "workspace:*", - "@penx/storage-estimate": "workspace:*", - "@penx/store": "workspace:*", - "@penx/table": "workspace:*", - "@penx/trpc-client": "workspace:*", - "@penx/word-count": "workspace:*", - "@penx/widget": "workspace:*", - "@plasmohq/storage": "^1.9.0", - "@radix-ui/react-hover-card": "^1.0.7", - "@radix-ui/react-slot": "^1.0.2", - "@t3-oss/env-nextjs": "^0.3.1", - "@tanstack/react-query": "^5.45.1", - "@trpc/client": "11.0.0-rc.421", - "@trpc/react-query": "11.0.0-rc.421", - "addressparser": "^1.0.1", - "axios": "^1.6.2", - "class-variance-authority": "^0.7.0", - "classnames": "^2.3.2", - "clsx": "^2.0.0", - "cookies-next": "^2.1.2", - "crypto-js": "^4.2.0", - "date-fns": "^2.30.0", - "emoji-mart": "^5.5.2", - "emoji-picker-react": "^4.5.16", - "framer-motion": "^10.16.14", - "immer": "^10.0.3", - "is-hotkey": "^0.2.0", - "jotai": "^2.6.0", - "jsonwebtoken": "^9.0.2", - "ky": "^1.1.3", - "linkedom": "^0.15.6", - "lodash": "^4.17.21", - "lucide-react": "^0.344.0", - "luxon": "^3.4.4", - "markdown-it": "^13.0.2", - "mime": "^3.0.0", - "mitt": "^3.0.1", - "next": "14.1.4", - "octokit": "^3.1.2", - "plasmo": "^0.84.1", - "prismjs": "^1.29.0", - "puppeteer-core": "^21.6.0", - "rc-table": "^7.36.0", - "re-resizable": "^6.9.11", - "react": "^18.2.0", - "react-datepicker": "^4.24.0", - "react-dom": "^18.2.0", - "react-fast-compare": "^3.2.2", - "react-hook-form": "^7.48.2", - "react-intersection-observer": "^9.5.3", - "react-merge-refs": "^2.1.1", - "slate": "^0.103.0", - "slate-history": "0.100.0", - "slate-react": "0.102.0", - "stook": "^1.17.0", - "superjson": "1.9.1", - "tinykeys": "^2.1.0", - "turndown": "^7.1.2", - "uikit": "workspace:*", - "underscore": "^1.13.6", - "use-debounce": "^9.0.4", - "uuid": "^9.0.1", - "zod": "^3.22.4" - }, - "devDependencies": { - "@ianvs/prettier-plugin-sort-imports": "4.1.0", - "@plasmohq/rps": "^1.8.7", - "@plasmohq/storage": "^1.9.0", - "@types/chrome": "0.0.245", - "@types/node": "^20.10.3", - "@types/react": "^18.2.61", - "@types/react-datepicker": "^4.19.4", - "@types/react-dom": "^18.2.17", - "@types/turndown": "^5.0.4", - "prettier": "3.0.3", - "typescript": "5.2.2" - }, - "manifest": { - "host_permissions": [ - "https://*/*" - ], - "permissions": [ - "tabs" - ] - } -} diff --git a/apps/extension/src/background.ts b/apps/extension/src/background.ts deleted file mode 100644 index f340926c2..000000000 --- a/apps/extension/src/background.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { Storage } from '@plasmohq/storage' - -import { db } from '@penx/local-db' -import { api } from '@penx/trpc-client' - -import { BACKGROUND_EVENTS } from '~/common/action' -import { - FAIL, - spacesKey, - SUCCESS, - type MsgRes, - type TabInfo, -} from '~/common/helper' -import { parsePreparedContent } from '~/common/parser' - -const storage = new Storage() - -async function setMessageToFrontEnd( - type: keyof typeof BACKGROUND_EVENTS | string, - payload: any, -) { - try { - const data = await chrome.runtime.sendMessage({ - type, - payload, - }) - - console.log('bgjs-setMessageToFrontEnd', data) - return data - } catch (error) { - console.log('bgjs-send msg error', error) - } -} - -/** - * Newly install or refresh the plug-in - * */ -chrome.runtime.onInstalled.addListener(() => { - console.log('bgjs-clipper-extension-init') -}) - -/** - * Receive and process events from each page - */ -chrome.runtime.onMessage.addListener( - async ( - message: MsgRes, - sender, - sendResponse, - ) => { - console.log('%c=bgjs-onMessage.addListener-0:', 'color:red', message) - switch (message.type) { - case BACKGROUND_EVENTS.QueryTab: { - saveCurrentPage(message.payload) - break - } - case BACKGROUND_EVENTS.SCREEN_SHOT: { - console.log( - '%c=bgjs-onMessage.addListener-2:', - 'color:red', - BACKGROUND_EVENTS.SCREEN_SHOT, - ) - chrome.tabs.query({ lastFocusedWindow: true }, (res) => { - chrome.tabs.captureVisibleTab(res[0].windowId as number, (url) => { - console.log('%c=bgjs-onMessage.addListener-2-1::', 'color:red', url) - sendResponse(url) - }) - }) - break - } - case BACKGROUND_EVENTS.SUBMIT_CONTENT: { - console.log('========request.payload:', message.payload) - const nodes = message.payload.nodes - - try { - const response = await fetch('http://localhost:65432/add-nodes', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ nodes }), - }) - - await response.json() - sendResponse({ msg: 'ok', code: SUCCESS }) - } catch (error: any) { - if (error.code === 'ECONNREFUSED') { - sendResponse({ msg: error, code: FAIL }) - } else { - sendResponse({ msg: error, code: FAIL }) - } - } - - break - } - case BACKGROUND_EVENTS.INT_POPUP: { - try { - const mySpaces = await api.space.mySpaces.query() - if (mySpaces?.length) { - storage.set(spacesKey, mySpaces) - } - sendResponse({ msg: 'ok', code: SUCCESS }) - } catch (error) { - sendResponse({ msg: error, code: FAIL }) - } - break - } - } - - return true - }, -) - -async function saveCurrentPage(tabInfo: TabInfo) { - if (tabInfo.status !== 'complete') { - // show message to user on page yet to complete load - setMessageToFrontEnd(BACKGROUND_EVENTS.TabNotComplete, { - text: 'Page loading...', - }) - } else if (tabInfo.status === 'complete') { - await getPageContent(tabInfo) - } -} - -async function getPageContent(tabInfo: TabInfo) { - try { - const res = await chrome.tabs.sendMessage(tabInfo.id, { - type: BACKGROUND_EVENTS.GetPageContent, - payload: {}, - }) - - console.log('%c=bgjs-savePage 1:', 'color:green', { - url: tabInfo.url, - document: res.document, - }) - const document = await parsePreparedContent(tabInfo.url, res.document) - console.log('%c=bgjs-savePage document-3:', 'color:green', { - document, - content: document?.content, - res, - }) - - // TODO something - // ... - - // TODO: test Send response to content and then parse markdownwn - await chrome.tabs.sendMessage(tabInfo.id, { - type: BACKGROUND_EVENTS.EndOfGetPageContent, - payload: { ...document }, - }) - } catch (error) { - console.log('set tabs msg err:', error) - } -} diff --git a/apps/extension/src/command.scss b/apps/extension/src/command.scss deleted file mode 100644 index 0a8d2bbac..000000000 --- a/apps/extension/src/command.scss +++ /dev/null @@ -1,272 +0,0 @@ -.command-panel { - [cmdk-root] { - max-width: 640px; - width: 100%; - background: var(--gray1); - border-radius: 12px; - padding: 8px 0; - font-family: var(--font-sans); - box-shadow: var(--cmdk-shadow); - border: 1px solid var(--gray6); - position: relative; - - .dark & { - background: var(--gray2); - border: 0; - - &:after { - content: ''; - background: linear-gradient( - to right, - var(--gray6) 20%, - var(--gray6) 40%, - var(--gray10) 50%, - var(--gray10) 55%, - var(--gray6) 70%, - var(--gray6) 100% - ); - z-index: -1; - position: absolute; - border-radius: 12px; - top: -1px; - left: -1px; - width: calc(100% + 2px); - height: calc(100% + 2px); - animation: shine 3s ease forwards 0.1s; - background-size: 200% auto; - } - - &:before { - content: ''; - z-index: -1; - position: absolute; - border-radius: 12px; - top: -1px; - left: -1px; - width: calc(100% + 2px); - height: calc(100% + 2px); - box-shadow: 0 0 0 1px transparent; - animation: border 1s linear forwards 0.5s; - } - } - } - - [cmdk-input] { - font-family: var(--font-sans); - border: none; - width: 100%; - font-size: 15px; - padding: 8px 16px; - outline: none; - background: var(--bg); - color: var(--gray12); - - &::placeholder { - color: var(--gray9); - } - } - - [cmdk-item] { - content-visibility: auto; - - cursor: pointer; - height: 40px; - border-radius: 8px; - font-size: 14px; - display: flex; - align-items: center; - gap: 8px; - padding: 0 8px; - color: var(--gray12); - user-select: none; - will-change: background, color; - transition: all 150ms ease; - transition-property: none; - - &[data-selected='true'] { - background: var(--gray4); - color: var(--gray12); - } - - &[data-disabled='true'] { - color: var(--gray8); - cursor: not-allowed; - } - - &:active { - transition-property: background; - background: var(--gray4); - } - - &:first-child { - margin-top: 8px; - } - - & + [cmdk-item] { - margin-top: 4px; - } - - svg { - width: 18px; - height: 18px; - } - } - - [cmdk-raycast-meta] { - margin-left: auto; - color: var(--gray11); - font-size: 13px; - } - - [cmdk-list] { - padding: 0 8px; - height: 393px; - overflow: auto; - overscroll-behavior: contain; - scroll-padding-block-end: 40px; - transition: 100ms ease; - transition-property: height; - padding-bottom: 40px; - } - - [cmdk-raycast-open-trigger], - [cmdk-raycast-subcommand-trigger] { - color: var(--gray11); - padding: 0px 4px 0px 8px; - border-radius: 6px; - font-weight: 500; - font-size: 12px; - height: 28px; - letter-spacing: -0.25px; - } - - [cmdk-raycast-clipboard-icon], - [cmdk-raycast-hammer-icon] { - width: 20px; - height: 20px; - border-radius: 6px; - display: flex; - align-items: center; - justify-content: center; - color: #ffffff; - - svg { - width: 14px; - height: 14px; - } - } - - [cmdk-raycast-clipboard-icon] { - background: linear-gradient(to bottom, #f55354, #eb4646); - } - - [cmdk-raycast-hammer-icon] { - background: linear-gradient(to bottom, #6cb9a3, #2c6459); - } - - [cmdk-raycast-open-trigger] { - display: flex; - align-items: center; - color: var(--gray12); - } - - [cmdk-raycast-subcommand-trigger] { - display: flex; - align-items: center; - gap: 4px; - right: 8px; - bottom: 8px; - - svg { - width: 14px; - height: 14px; - } - - hr { - height: 100%; - background: var(--gray6); - border: 0; - width: 1px; - } - } - - [cmdk-separator] { - height: 1px; - width: 100%; - background: var(--gray5); - margin: 4px 0; - } - - *:not([hidden]) + [cmdk-group] { - margin-top: 8px; - } - - [cmdk-group-heading] { - user-select: none; - font-size: 12px; - color: var(--gray11); - padding: 0 8px; - display: flex; - align-items: center; - } - - [cmdk-raycast-footer] { - display: flex; - height: 40px; - align-items: center; - width: 100%; - position: absolute; - background: var(--gray1); - bottom: 0; - padding: 8px; - border-top: 1px solid var(--gray6); - border-radius: 0 0 12px 12px; - - svg { - width: 20px; - height: 20px; - filter: grayscale(1); - margin-right: auto; - } - - hr { - height: 12px; - width: 1px; - border: 0; - background: var(--gray6); - margin: 0 4px 0px 12px; - } - - @media (prefers-color-scheme: dark) { - background: var(--gray2); - } - } - - [cmdk-dialog] { - z-index: var(--layer-portal); - position: fixed; - left: 50%; - top: var(--page-top); - transform: translateX(-50%); - - [cmdk] { - width: 640px; - transform-origin: center center; - animation: dialogIn var(--transition-fast) forwards; - } - - &[data-state='closed'] [cmdk] { - animation: dialogOut var(--transition-fast) forwards; - } - } - - [cmdk-empty] { - font-size: 14px; - display: flex; - align-items: center; - justify-content: center; - height: 64px; - white-space: pre-wrap; - color: var(--gray11); - } -} diff --git a/apps/extension/src/common/action.ts b/apps/extension/src/common/action.ts deleted file mode 100644 index 3b8a27469..000000000 --- a/apps/extension/src/common/action.ts +++ /dev/null @@ -1,16 +0,0 @@ -export const ACTIONS = { - EnterManually: 'ENTER_MANUALLY', - AreaSelect: 'AREA_SELECT', -} - -export const BACKGROUND_EVENTS = { - QueryTab: 'QUERY_TAB', - TabNotComplete: 'TAB_NOT_COMPLETE', - GetPageContent: 'GET_PAGE_CONTENT', - EndOfGetPageContent: 'ENDOF_GET_PAGECONTENT', - SCREEN_SHOT: 'background/screen-shot', - SUBMIT_CONTENT: 'background/submit-content', - INT_POPUP: 'background/int_popup', - - ADD_NODES_TO_TODAY: 'background/add_nodes_to_today', -} diff --git a/apps/extension/src/common/content-handler/content-handler.ts b/apps/extension/src/common/content-handler/content-handler.ts deleted file mode 100644 index d51e6475c..000000000 --- a/apps/extension/src/common/content-handler/content-handler.ts +++ /dev/null @@ -1,184 +0,0 @@ -import addressparser from 'addressparser' -import axios from 'axios' -import { parseHTML } from 'linkedom' -import { Browser } from 'puppeteer-core' -import { v4 as uuid } from 'uuid' - -interface Unsubscribe { - mailTo?: string - httpUrl?: string -} - -export interface NewsletterInput { - from: string - to: string - subject: string - html: string - headers: Record -} - -export interface NewsletterResult { - email: string - content: string - url: string - title: string - author: string - unsubMailTo?: string - unsubHttpUrl?: string -} - -export interface PreHandleResult { - url?: string - title?: string - content?: string - contentType?: string - dom?: Document -} - -export const FAKE_URL_PREFIX = 'https://dcode.com/no_url?q=' -export const generateUniqueUrl = () => FAKE_URL_PREFIX + uuid() - -export abstract class ContentHandler { - protected senderRegex: RegExp - protected urlRegex: RegExp - name: string - - protected constructor() { - this.senderRegex = new RegExp(/NEWSLETTER_SENDER_REGEX/) - this.urlRegex = new RegExp(/NEWSLETTER_URL_REGEX/) - this.name = 'Handler name' - } - - shouldResolve(url: string): boolean { - return false - } - - async resolve(url: string): Promise { - return Promise.resolve(url) - } - - shouldPreHandle(url: string): boolean { - return false - } - - async preHandle(url: string, browser?: Browser): Promise { - return Promise.resolve({ url }) - } - - shouldPreParse(url: string, dom: Document): boolean { - return false - } - - async preParse(url: string, dom: Document): Promise { - return Promise.resolve(dom) - } - - async isNewsletter(input: { - from: string - html: string - headers: Record - dom: Document - }): Promise { - const re = new RegExp(this.senderRegex) - const postHeader = input.headers['list-post'] - const unSubHeader = input.headers['list-unsubscribe'] - return Promise.resolve( - re.test(input.from) && (!!postHeader || !!unSubHeader), - ) - } - - findNewsletterHeaderHref(dom: Document): string | undefined { - return undefined - } - - // Given an HTML blob tries to find a URL to use for - // a canonical URL. - async findNewsletterUrl(html: string): Promise { - const dom = parseHTML(html).document - - // Check if this is a substack newsletter - const href = this.findNewsletterHeaderHref(dom) - if (href) { - // Try to make a HEAD request, so we get the redirected URL, since these - // will usually be behind tracking url redirects - try { - const response = await axios.head(href, { timeout: 5000 }) - return Promise.resolve( - response.request.res.responseUrl as string | undefined, - ) - } catch (e) { - return Promise.resolve(href) - } - } - - return Promise.resolve(undefined) - } - - async parseNewsletterUrl( - headers: Record, - html: string, - ): Promise { - // get url from dom - const url = await this.findNewsletterUrl(html) - if (url) { - return url - } - // get newsletter url from html - const matches = html.match(this.urlRegex) - if (matches) { - return matches[1] - } - return undefined - } - - parseAuthor(from: string): string { - // get author name from email - // e.g. 'Jackson Harper from Omnivore App ' - // or 'Mike Allen ' - const parsed = addressparser(from) - if (parsed.length > 0 && parsed[0].name) { - return parsed[0].name - } - return from - } - - parseUnsubscribe(unSubHeader: string): Unsubscribe { - // parse list-unsubscribe header - return { - httpUrl: unSubHeader.match(/<(https?:\/\/[^>]*)>/)?.[1], - mailTo: unSubHeader.match(/]*)>/)?.[1], - } - } - - async handleNewsletter({ - from, - to, - subject, - html, - headers, - }: NewsletterInput): Promise { - if (!from || !html || !subject || !to) { - console.warn('invalid newsletter email') - throw new Error('invalid newsletter email') - } - - // fallback to default url if newsletter url does not exist - // assign a random uuid to the default url to avoid duplicate url - const url = - (await this.parseNewsletterUrl(headers, html)) || generateUniqueUrl() - const author = this.parseAuthor(from) - const unsubscribe = headers['list-unsubscribe'] - ? this.parseUnsubscribe(headers['list-unsubscribe'].toString()) - : undefined - - return { - email: to, - content: html, - url, - title: subject, - author, - unsubMailTo: unsubscribe?.mailTo || '', - unsubHttpUrl: unsubscribe?.httpUrl || '', - } - } -} diff --git a/apps/extension/src/common/content-handler/index.ts b/apps/extension/src/common/content-handler/index.ts deleted file mode 100644 index 5f65fb2a3..000000000 --- a/apps/extension/src/common/content-handler/index.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { parseHTML } from 'linkedom' -import { Browser } from 'puppeteer-core' - -import type { - ContentHandler, - NewsletterInput, - NewsletterResult, - PreHandleResult, -} from './content-handler' -import { AxiosHandler } from './newsletters/axios-handler' -import { BeehiivHandler } from './newsletters/beehiiv-handler' -import { BloombergNewsletterHandler } from './newsletters/bloomberg-newsletter-handler' -import { ConvertkitHandler } from './newsletters/convertkit-handler' -import { CooperPressHandler } from './newsletters/cooper-press-handler' -import { EnergyWorldHandler } from './newsletters/energy-world' -import { EveryIoHandler } from './newsletters/every-io-handler' -import { GenericHandler } from './newsletters/generic-handler' -import { GhostHandler } from './newsletters/ghost-handler' -import { GolangHandler } from './newsletters/golang-handler' -import { HeyWorldHandler } from './newsletters/hey-world-handler' -import { IndiaTimesHandler } from './newsletters/india-times-handler' -import { MorningBrewHandler } from './newsletters/morning-brew-handler' -import { RevueHandler } from './newsletters/revue-handler' -import { SubstackHandler } from './newsletters/substack-handler' -import { AppleNewsHandler } from './websites/apple-news-handler' -import { ArsTechnicaHandler } from './websites/ars-technica-handler' -import { BloombergHandler } from './websites/bloomberg-handler' -import { DerstandardHandler } from './websites/derstandard-handler' -import { GitHubHandler } from './websites/github-handler' -import { ImageHandler } from './websites/image-handler' -import { MediumHandler } from './websites/medium-handler' -import { PdfHandler } from './websites/pdf-handler' -import { PipedVideoHandler } from './websites/piped-video-handler' -import { ScrapingBeeHandler } from './websites/scrapingBee-handler' -import { StackOverflowHandler } from './websites/stack-overflow-handler' -import { TDotCoHandler } from './websites/t-dot-co-handler' -import { TheAtlanticHandler } from './websites/the-atlantic-handler' -import { WeixinQqHandler } from './websites/weixin-qq-handler' -import { WikipediaHandler } from './websites/wikipedia-handler' -import { YoutubeHandler } from './websites/youtube-handler' -import { ZhihuHandler } from './websites/zhihu-handler' - -const validateUrlString = (url: string): boolean => { - const u = new URL(url) - // Make sure the URL is http or https - if (u.protocol !== 'http:' && u.protocol !== 'https:') { - throw new Error('Invalid URL protocol check failed') - } - // Make sure the domain is not localhost - if (u.hostname === 'localhost' || u.hostname === '0.0.0.0') { - throw new Error('Invalid URL is localhost') - } - // Make sure the domain is not a private IP - if (/^(10|172\.16|192\.168)\..*/.test(u.hostname)) { - throw new Error('Invalid URL is private ip') - } - - return true -} - -const contentHandlers: ContentHandler[] = [ - new ArsTechnicaHandler(), - new TheAtlanticHandler(), - new AppleNewsHandler(), - new BloombergHandler(), - new DerstandardHandler(), - new ImageHandler(), - new MediumHandler(), - new PdfHandler(), - new ScrapingBeeHandler(), - new TDotCoHandler(), - new YoutubeHandler(), - new WikipediaHandler(), - new GitHubHandler(), - new AxiosHandler(), - new GolangHandler(), - new MorningBrewHandler(), - new BloombergNewsletterHandler(), - new SubstackHandler(), - new StackOverflowHandler(), - new EnergyWorldHandler(), - new PipedVideoHandler(), - new WeixinQqHandler(), - new ZhihuHandler(), -] - -const newsletterHandlers: ContentHandler[] = [ - new AxiosHandler(), - new BloombergNewsletterHandler(), - new GolangHandler(), - new SubstackHandler(), - new MorningBrewHandler(), - new BeehiivHandler(), - new ConvertkitHandler(), - new RevueHandler(), - new GhostHandler(), - new CooperPressHandler(), - new HeyWorldHandler(), - new GenericHandler(), - new EveryIoHandler(), - new EnergyWorldHandler(), - new IndiaTimesHandler(), -] - -export const preHandleContent = async ( - url: string, - browser: Browser, -): Promise => { - // Before we run the regular handlers we check to see if we need tp - // pre-resolve the URL. TODO: This should probably happen recursively, - // so URLs can be pre-resolved, handled, pre-resolved, handled, etc. - for (const handler of contentHandlers) { - if (handler.shouldResolve(url)) { - try { - const resolvedUrl = await handler.resolve(url) - if (resolvedUrl && validateUrlString(resolvedUrl)) { - url = resolvedUrl - } - } catch (err) { - console.warn('error resolving url with handler', handler.name, err) - } - break - } - } - // Before we fetch the page we check the handlers, to see if they want - // to perform a prefetch action that can modify our requests. - // enumerate the handlers and see if any of them want to handle the request - for (const handler of contentHandlers) { - if (handler.shouldPreHandle(url)) { - return handler.preHandle(url, browser) - } - } - return undefined -} - -export const preParseContent = async ( - url: string, - dom: Document, -): Promise => { - // Before we parse the page we check the handlers, to see if they want - // to perform a preParse action that can modify our dom. - // enumerate the handlers and see if any of them want to handle the dom - for (const handler of contentHandlers) { - if (handler.shouldPreParse(url, dom)) { - return handler.preParse(url, dom) - } - } - return undefined -} - -export const getNewsletterHandler = async (input: { - from: string - html: string - headers: Record -}): Promise => { - const dom = parseHTML(input.html).document - for (const handler of newsletterHandlers) { - if (await handler.isNewsletter({ ...input, dom })) { - return handler - } - } - - return undefined -} - -export const handleNewsletter = async ( - input: NewsletterInput, -): Promise => { - const handler = await getNewsletterHandler(input) - if (handler) { - return handler.handleNewsletter(input) - } - - return undefined -} - -module.exports = { - preHandleContent, - handleNewsletter, - preParseContent, - getNewsletterHandler, -} diff --git a/apps/extension/src/common/content-handler/newsletters/axios-handler.ts b/apps/extension/src/common/content-handler/newsletters/axios-handler.ts deleted file mode 100644 index 24cedd8db..000000000 --- a/apps/extension/src/common/content-handler/newsletters/axios-handler.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class AxiosHandler extends ContentHandler { - constructor() { - super() - this.senderRegex = /<.+@axios.com>/ - this.urlRegex = /View in browser at (.*)<\/a>/ - this.name = 'axios' - } - - shouldPreParse(url: string, dom: Document): boolean { - const host = this.name + '.com' - // check if url ends with axios.com - return new URL(url).hostname.endsWith(host) - } - - async preParse(url: string, dom: Document): Promise { - const body = dom.querySelector('table') - - let isFooter = false - // this removes ads and replaces table with a div - body?.querySelectorAll('table').forEach((el) => { - // remove the footer and the ads - if (!el.textContent || el.textContent.length < 20 || isFooter) { - el.remove() - } else { - // removes the first few rows of the table (the header) - // remove the last two rows of the table (they are ads) - el.querySelectorAll('tr').forEach((tr, i) => { - if (i <= 7 || i >= el.querySelectorAll('tr').length - 2) { - tr.remove() - } - }) - // replace the table with a div - const div = dom.createElement('div') - div.innerHTML = el.innerHTML - el.parentNode?.replaceChild(div, el) - // set the isFooter flag to true because the next table is the footer - isFooter = true - } - }) - - return Promise.resolve(dom) - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/beehiiv-handler.ts b/apps/extension/src/common/content-handler/newsletters/beehiiv-handler.ts deleted file mode 100644 index 232267957..000000000 --- a/apps/extension/src/common/content-handler/newsletters/beehiiv-handler.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class BeehiivHandler extends ContentHandler { - constructor() { - super() - this.name = 'beehiiv' - } - - async isNewsletter(input: { - from: string - headers: Record - }): Promise { - return Promise.resolve( - input.headers['x-beehiiv-type']?.toString() === 'newsletter', - ) - } - - async parseNewsletterUrl( - headers: Record, - html: string, - ): Promise { - return Promise.resolve(headers['x-newsletter']?.toString()) - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/bloomberg-newsletter-handler.ts b/apps/extension/src/common/content-handler/newsletters/bloomberg-newsletter-handler.ts deleted file mode 100644 index 5ccd31466..000000000 --- a/apps/extension/src/common/content-handler/newsletters/bloomberg-newsletter-handler.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class BloombergNewsletterHandler extends ContentHandler { - constructor() { - super() - this.senderRegex = /<.+@mail.bloomberg.*.com>/ - this.urlRegex = / { - const body = dom.querySelector('.wrapper') - - // this removes header - body?.querySelector('.sailthru-variables')?.remove() - body?.querySelector('.preview-text')?.remove() - body?.querySelector('.logo-wrapper')?.remove() - body?.querySelector('.by-the-number-wrapper')?.remove() - // this removes footer - body?.querySelector('.quote-box-wrapper')?.remove() - body?.querySelector('.header-wrapper')?.remove() - body?.querySelector('.component-wrapper')?.remove() - body?.querySelector('.footer')?.remove() - - return Promise.resolve(dom) - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/convertkit-handler.ts b/apps/extension/src/common/content-handler/newsletters/convertkit-handler.ts deleted file mode 100644 index fd10b60e7..000000000 --- a/apps/extension/src/common/content-handler/newsletters/convertkit-handler.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class ConvertkitHandler extends ContentHandler { - constructor() { - super() - this.name = 'convertkit' - } - - findNewsletterHeaderHref(dom: Document): string | undefined { - const readOnline = dom.querySelectorAll('a') - let res: string | undefined = undefined - readOnline.forEach((e) => { - if ( - e.textContent === 'View this email in your browser' || - e.textContent === 'Read on FS' - ) { - res = e.getAttribute('href') || undefined - } - }) - return res - } - - async isNewsletter(input: { - from: string - dom: Document - headers: Record - }): Promise { - const dom = input.dom - const icons = dom.querySelectorAll( - 'img[src*="convertkit.com"], img[src*="convertkit-mail"]', - ) - if (icons.length === 0) { - return Promise.resolve(false) - } - // ignore newsletters that have a confirmation link to the newsletter in the body - const links = dom.querySelectorAll( - 'a[href*="convertkit.com"], a[href*="convertkit-mail"]', - ) - const isConfirmation = Array.from(links).some((e) => { - return e.textContent === 'Confirm your subscription' - }) - - return Promise.resolve(!isConfirmation) - } - - async parseNewsletterUrl( - headers: Record, - html: string, - ): Promise { - return this.findNewsletterUrl(html) - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/cooper-press-handler.ts b/apps/extension/src/common/content-handler/newsletters/cooper-press-handler.ts deleted file mode 100644 index ab4ef4025..000000000 --- a/apps/extension/src/common/content-handler/newsletters/cooper-press-handler.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class CooperPressHandler extends ContentHandler { - constructor() { - super() - this.name = 'cooper-press' - } - - findNewsletterHeaderHref(dom: Document): string | undefined { - const readOnline = dom.querySelectorAll('a') - let res: string | undefined = undefined - readOnline.forEach((e) => { - if (e.textContent === 'Read on the Web') { - res = e.getAttribute('href') || undefined - } - }) - return res - } - - async isNewsletter(input: { - from: string - dom: Document - headers: Record - }): Promise { - const dom = input.dom - return Promise.resolve( - dom.querySelectorAll('a[href*="cooperpress.com"]').length > 0, - ) - } - - async parseNewsletterUrl( - headers: Record, - html: string, - ): Promise { - return this.findNewsletterUrl(html) - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/energy-world.ts b/apps/extension/src/common/content-handler/newsletters/energy-world.ts deleted file mode 100644 index 3c7484d4c..000000000 --- a/apps/extension/src/common/content-handler/newsletters/energy-world.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class EnergyWorldHandler extends ContentHandler { - constructor() { - super() - this.name = 'Energy World' - } - - async isNewsletter(input: { - from: string - html: string - headers: Record - dom: Document - }): Promise { - return Promise.resolve( - input.from === 'ETEnergyworld Latest News', - ) - } - - shouldPreParse(url: string, dom: Document): boolean { - return dom.querySelectorAll('img[src*="etenergyworld.png"]').length > 0 - } - - async preParse(url: string, dom: Document): Promise { - // get the main content - const main = dom.querySelector('table[class="nletter-wrap"]') - if (!main) { - return Promise.resolve(dom) - } - - // create a new dom - const newDom = dom.createDocumentFragment() - - // add the content to the new dom - main.querySelectorAll('table[class="multi-cols"] tr').forEach((tr) => { - const p = dom.createElement('p') - p.innerHTML = tr.innerHTML - newDom.appendChild(p) - }) - dom.body.replaceChildren(newDom) - - return Promise.resolve(dom) - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/every-io-handler.ts b/apps/extension/src/common/content-handler/newsletters/every-io-handler.ts deleted file mode 100644 index 3ac836f00..000000000 --- a/apps/extension/src/common/content-handler/newsletters/every-io-handler.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class EveryIoHandler extends ContentHandler { - constructor() { - super() - this.name = 'Every.io' - } - - async isNewsletter(input: { - from: string - html: string - headers: Record - dom: Document - }): Promise { - return Promise.resolve(input.from === 'Every ') - } - - findNewsletterHeaderHref(dom: Document): string | undefined { - const readOnline = dom.querySelector('.newsletter-email .title a') - return readOnline?.getAttribute('href') || undefined - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/generic-handler.ts b/apps/extension/src/common/content-handler/newsletters/generic-handler.ts deleted file mode 100644 index 83d2c1ab5..000000000 --- a/apps/extension/src/common/content-handler/newsletters/generic-handler.ts +++ /dev/null @@ -1,50 +0,0 @@ -import addressparser from 'addressparser' - -import { ContentHandler } from '../content-handler' - -export class GenericHandler extends ContentHandler { - // newsletter url text regex for newsletters that don't have a newsletter header - NEWSLETTER_URL_TEXT_REGEX = - /((View|Read)(.*)(email|post)?(.*)(in your browser|online|on (FS|the Web))|Lire en ligne)/i - - constructor() { - super() - this.name = 'Generic Newsletter' - } - - async isNewsletter(input: { - from: string - html: string - headers: Record - dom: Document - }): Promise { - const postHeader = input.headers['list-post'] || input.headers['list-id'] - const unSubHeader = input.headers['list-unsubscribe'] - return Promise.resolve(!!postHeader || !!unSubHeader) - } - - findNewsletterHeaderHref(dom: Document): string | undefined { - const readOnline = dom.querySelectorAll('a') - let res: string | undefined = undefined - readOnline.forEach((e) => { - if (e.textContent && this.NEWSLETTER_URL_TEXT_REGEX.test(e.textContent)) { - res = e.getAttribute('href') || undefined - } - }) - return res - } - - async parseNewsletterUrl( - headers: Record, - html: string, - ): Promise { - // raw SubStack newsletter url is like - // we need to get the real url from the raw url - const postHeader = headers['list-post']?.toString() - if (postHeader && addressparser(postHeader).length > 0) { - return addressparser(postHeader)[0].name - } - - return this.findNewsletterUrl(html) - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/ghost-handler.ts b/apps/extension/src/common/content-handler/newsletters/ghost-handler.ts deleted file mode 100644 index 41c65885a..000000000 --- a/apps/extension/src/common/content-handler/newsletters/ghost-handler.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class GhostHandler extends ContentHandler { - constructor() { - super() - this.name = 'ghost' - } - - findNewsletterHeaderHref(dom: Document): string | undefined { - const readOnline = dom.querySelector('.view-online-link') - return readOnline?.getAttribute('href') || undefined - } - - async isNewsletter(input: { - from: string - dom: Document - headers: Record - }): Promise { - const dom = input.dom - return Promise.resolve( - dom.querySelectorAll('img[src*="ghost.org"]').length > 0, - ) - } - - async parseNewsletterUrl( - headers: Record, - html: string, - ): Promise { - return this.findNewsletterUrl(html) - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/golang-handler.ts b/apps/extension/src/common/content-handler/newsletters/golang-handler.ts deleted file mode 100644 index aeafec852..000000000 --- a/apps/extension/src/common/content-handler/newsletters/golang-handler.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class GolangHandler extends ContentHandler { - constructor() { - super() - this.senderRegex = /<.+@golangweekly.com>/ - this.urlRegex = /Read on the Web<\/a>/ - this.name = 'golangweekly' - } - - shouldPreParse(url: string, dom: Document): boolean { - const host = this.name + '.com' - // check if url ends with golangweekly.com - return new URL(url).hostname.endsWith(host) - } - - async preParse(url: string, dom: Document): Promise { - const body = dom.querySelector('body') - - // this removes the "Subscribe" button - body?.querySelector('.el-splitbar')?.remove() - // this removes the title - body?.querySelector('.el-masthead')?.remove() - - return Promise.resolve(dom) - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/hey-world-handler.ts b/apps/extension/src/common/content-handler/newsletters/hey-world-handler.ts deleted file mode 100644 index 6c86cb3d7..000000000 --- a/apps/extension/src/common/content-handler/newsletters/hey-world-handler.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class HeyWorldHandler extends ContentHandler { - constructor() { - super() - this.name = 'hey-world' - this.senderRegex = /<.+@world.hey.com>/ - } - - findNewsletterHeaderHref(dom: Document): string | undefined { - const readOnline = dom.querySelectorAll('a') - let res: string | undefined = undefined - readOnline.forEach((e) => { - if (e.textContent === 'View this post online') { - res = e.getAttribute('href') || undefined - } - }) - return res - } - - async parseNewsletterUrl( - headers: Record, - html: string, - ): Promise { - return this.findNewsletterUrl(html) - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/india-times-handler.ts b/apps/extension/src/common/content-handler/newsletters/india-times-handler.ts deleted file mode 100644 index e562a76bd..000000000 --- a/apps/extension/src/common/content-handler/newsletters/india-times-handler.ts +++ /dev/null @@ -1,34 +0,0 @@ -import addressparser from 'addressparser' - -import { ContentHandler } from '../content-handler' - -export class IndiaTimesHandler extends ContentHandler { - constructor() { - super() - this.name = 'India Times' - } - - async isNewsletter(input: { - from: string - html: string - headers: Record - dom: Document - }): Promise { - return Promise.resolve( - addressparser(input.from).some( - (e) => e.address === 'newsletters@timesofindia.com', - ), - ) - } - - findNewsletterHeaderHref(dom: Document): string | undefined { - const readOnline = dom.querySelectorAll('a') - let res: string | undefined = undefined - readOnline.forEach((e) => { - if (e.textContent === 'view in browser') { - res = e.getAttribute('href') || undefined - } - }) - return res - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/morning-brew-handler.ts b/apps/extension/src/common/content-handler/newsletters/morning-brew-handler.ts deleted file mode 100644 index 0a69292fe..000000000 --- a/apps/extension/src/common/content-handler/newsletters/morning-brew-handler.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class MorningBrewHandler extends ContentHandler { - constructor() { - super() - this.senderRegex = /Morning Brew / - this.urlRegex = /View Online<\/a>/ - this.name = 'morningbrew' - } - - shouldPreParse(url: string, dom: Document): boolean { - const host = this.name + '.com' - // check if url ends with morningbrew.com - return new URL(url).hostname.endsWith(host) - } - - async preParse(url: string, dom: Document): Promise { - // retain the width of the cells in the table of market info - dom.querySelectorAll('.markets-arrow-cell').forEach((td) => { - const table = td.closest('table') - if (table) { - const bubbleTable = table.querySelector('.markets-bubble') - if (bubbleTable) { - // replace the nested table with the text - const e = bubbleTable.querySelector('.markets-table-text') - e && bubbleTable.parentNode?.replaceChild(e, bubbleTable) - } - // set custom class for the table - table.className = 'morning-brew-markets' - } - }) - - return Promise.resolve(dom) - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/revue-handler.ts b/apps/extension/src/common/content-handler/newsletters/revue-handler.ts deleted file mode 100644 index 3b8319639..000000000 --- a/apps/extension/src/common/content-handler/newsletters/revue-handler.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class RevueHandler extends ContentHandler { - constructor() { - super() - this.name = 'revue' - } - - findNewsletterHeaderHref(dom: Document): string | undefined { - const viewOnline = dom.querySelectorAll('table tr td a[target="_blank"]') - let res: string | undefined = undefined - viewOnline.forEach((e) => { - if (e.textContent === 'View online') { - res = e.getAttribute('href') || undefined - } - }) - return res - } - - async isNewsletter(input: { - from: string - dom: Document - headers: Record - }): Promise { - const dom = input.dom - if ( - dom.querySelectorAll('img[src*="getrevue.co"], img[src*="revue.email"]') - .length > 0 - ) { - const getrevueUrl = this.findNewsletterHeaderHref(dom) - if (getrevueUrl) { - return Promise.resolve(true) - } - } - return false - } - - async parseNewsletterUrl( - headers: Record, - html: string, - ): Promise { - return this.findNewsletterUrl(html) - } -} diff --git a/apps/extension/src/common/content-handler/newsletters/substack-handler.ts b/apps/extension/src/common/content-handler/newsletters/substack-handler.ts deleted file mode 100644 index 54c4e3704..000000000 --- a/apps/extension/src/common/content-handler/newsletters/substack-handler.ts +++ /dev/null @@ -1,140 +0,0 @@ -import addressparser from 'addressparser' - -import { ContentHandler } from '../content-handler' - -export class SubstackHandler extends ContentHandler { - constructor() { - super() - this.name = 'substack' - } - - shouldPreParse(url: string, dom: Document): boolean { - const host = this.name + '.com' - const cdnHost = 'substackcdn.com' - // check if url ends with substack.com - // or has a profile image hosted at substack.com or substackcdn.com - return ( - new URL(url).hostname.endsWith(host) || - !!dom - .querySelector('.email-body img') - ?.getAttribute('src') - ?.includes(host || cdnHost) - ) - } - - async preParse(url: string, dom: Document): Promise { - const body = dom.querySelector('.email-body-container') - - // this removes header and profile avatar - body?.querySelector('.header')?.remove() - body?.querySelector('.preamble')?.remove() - body?.querySelector('.meta-author-wrap')?.remove() - // this removes meta button - body?.querySelector('.post-meta')?.remove() - // this removes footer - body?.querySelector('.post-cta')?.remove() - body?.querySelector('.container-border')?.remove() - body?.querySelector('.footer')?.remove() - // this removes the "restack" button - body?.querySelector('.email-ufi-2-bottom')?.remove() - // this removes the "share" button - body?.querySelector('.email-ufi-2-top')?.remove() - - dom = this.fixupStaticTweets(dom) - - return Promise.resolve(dom) - } - - findNewsletterHeaderHref(dom: Document): string | undefined { - // Substack header links - const postLink = dom.querySelector('h1 a') - if (postLink) { - return postLink.getAttribute('href') || undefined - } - - return undefined - } - - async isNewsletter({ - headers, - dom, - }: { - from: string - headers: Record - dom: Document - }): Promise { - if (headers['list-post']) { - return Promise.resolve(true) - } - // substack newsletter emails have tables with a *post-meta class - if (dom.querySelector('table[class$="post-meta"]')) { - return true - } - // If the article has a header link, and substack icons its probably a newsletter - const href = this.findNewsletterHeaderHref(dom) - const oldHeartIcon = dom.querySelector( - 'table tbody td span a img[src*="HeartIcon"]', - ) - const oldRecommendIcon = dom.querySelector( - 'table tbody td span a img[src*="RecommendIconRounded"]', - ) - const heartIcon = dom.querySelector('a img[src*="LucideHeart"]') - const commentsIcon = dom.querySelector('a img[src*="LucideComments"]') - return Promise.resolve( - !!( - href && - (oldHeartIcon || oldRecommendIcon || heartIcon || commentsIcon) - ), - ) - } - - async parseNewsletterUrl( - headers: Record, - html: string, - ): Promise { - // raw SubStack newsletter url is like - // we need to get the real url from the raw url - const postHeader = headers['list-post']?.toString() - if (postHeader && addressparser(postHeader).length > 0) { - return Promise.resolve(addressparser(postHeader)[0].name) - } - return this.findNewsletterUrl(html) - } - - fixupStaticTweets(dom: Document): Document { - const preClassName = '_dcode-static-' - const staticTweets = dom.querySelectorAll('div[class="tweet static"]') - - if (staticTweets.length < 1) { - return dom - } - - const recurse = (node: Element, f: (node: Element) => void) => { - for (let i = 0; i < node.children.length; i++) { - const child = node.children[i] - recurse(child, f) - f(child) - } - } - - for (const tweet of Array.from(staticTweets)) { - tweet.className = preClassName + 'tweet' - tweet.removeAttribute('style') - - // get all children, rename their class, remove style - // elements (style will be handled in the reader) - recurse(tweet, (n: Element) => { - const className = n.className - if ( - className.startsWith('tweet-') || - className.startsWith('quote-tweet') - ) { - n.className = preClassName + className - } - n.removeAttribute('style') - }) - } - - return dom - } -} diff --git a/apps/extension/src/common/content-handler/websites/apple-news-handler.ts b/apps/extension/src/common/content-handler/websites/apple-news-handler.ts deleted file mode 100644 index 544149ecf..000000000 --- a/apps/extension/src/common/content-handler/websites/apple-news-handler.ts +++ /dev/null @@ -1,32 +0,0 @@ -import axios from 'axios' -import { parseHTML } from 'linkedom' - -import { ContentHandler, PreHandleResult } from '../content-handler' - -export class AppleNewsHandler extends ContentHandler { - constructor() { - super() - this.name = 'Apple News' - } - - shouldPreHandle(url: string): boolean { - const u = new URL(url) - return u.hostname === 'apple.news' - } - - async preHandle(url: string): Promise { - const MOBILE_USER_AGENT = - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36' - const response = await axios.get(url, { - headers: { 'User-Agent': MOBILE_USER_AGENT }, - }) - const data = response.data as string - const dom = parseHTML(data).document - // make sure it's a valid URL by wrapping in new URL - const href = dom - .querySelector('span.click-here') - ?.parentElement?.getAttribute('href') - const u = href ? new URL(href) : undefined - return { url: u?.href } - } -} diff --git a/apps/extension/src/common/content-handler/websites/ars-technica-handler.ts b/apps/extension/src/common/content-handler/websites/ars-technica-handler.ts deleted file mode 100644 index e4e2256e1..000000000 --- a/apps/extension/src/common/content-handler/websites/ars-technica-handler.ts +++ /dev/null @@ -1,87 +0,0 @@ -import axios from 'axios' -import { parseHTML } from 'linkedom' - -import { ContentHandler, PreHandleResult } from '../content-handler' - -/** - * Some of the content on Ars Technica is split over several pages. - * If this is the case we should unfurl the entire article into one. l - */ -export class ArsTechnicaHandler extends ContentHandler { - constructor() { - super() - this.name = 'ArsTechnica' - } - - shouldPreHandle(url: string): boolean { - const u = new URL(url) - return u.hostname.endsWith('arstechnica.com') - } - - hasMultiplePages(document: Document): boolean { - return document.querySelectorAll('nav.page-numbers')?.length != 0 - } - - async grabContentFromUrl(url: string): Promise { - const response = await axios.get(url) - const data = response.data as string - return parseHTML(data).document - } - - async extractArticleContentsFromLink(url: string): Promise { - const dom = await this.grabContentFromUrl(url) - const articleContent = dom.querySelector('[itemprop="articleBody"]') - return [].slice.call(articleContent?.childNodes || []) - } - - async expandLinksAndCombine(document: Document): Promise { - const pageNumbers = document.querySelector('nav.page-numbers') - const articleBody = document.querySelector('[itemprop="articleBody"]') - - if (!pageNumbers || !articleBody) { - // We shouldn't ever really get here, but sometimes weird things happen. - return document - } - - const pageLinkNodes = pageNumbers.querySelectorAll('a') - // Remove the "Next" Link, as it will duplicate some content. - const pageLinks = - Array.from(pageLinkNodes) - ?.slice(0, pageLinkNodes.length - 1) - ?.map(({ href }) => href) ?? [] - - const pageContents = await Promise.all( - pageLinks.map(this.extractArticleContentsFromLink.bind(this)), - ) - - for (const articleContents of pageContents) { - // We place all the content in a span to indicate that a page has been parsed. - const span = document.createElement('SPAN') - span.className = 'nextPageContents' - span.append(...(articleContents as any)) - articleBody.append(span) - } - pageNumbers.remove() - - return document - } - - async preHandle(url: string): Promise { - // We simply retrieve the article without Javascript enabled using a GET command. - const dom = await this.grabContentFromUrl(url) - if (!this.hasMultiplePages(dom)) { - return { - content: dom.body.outerHTML, - title: dom.title, - dom, - } - } - - const expandedDom = await this.expandLinksAndCombine(dom) - return { - content: expandedDom.body.outerHTML, - title: dom.title, - dom: expandedDom, - } - } -} diff --git a/apps/extension/src/common/content-handler/websites/bloomberg-handler.ts b/apps/extension/src/common/content-handler/websites/bloomberg-handler.ts deleted file mode 100644 index 1a103c105..000000000 --- a/apps/extension/src/common/content-handler/websites/bloomberg-handler.ts +++ /dev/null @@ -1,42 +0,0 @@ -import axios from 'axios' -import { parseHTML } from 'linkedom' - -import { ContentHandler, PreHandleResult } from '../content-handler' - -export class BloombergHandler extends ContentHandler { - constructor() { - super() - this.name = 'Bloomberg' - } - - shouldPreHandle(url: string): boolean { - const BLOOMBERG_URL_MATCH = - /https?:\/\/(www\.)?bloomberg.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)/ - return BLOOMBERG_URL_MATCH.test(url.toString()) - } - - async preHandle(url: string): Promise { - console.log('prehandling bloomberg url', url) - - try { - const response = await axios.get('https://app.scrapingbee.com/api/v1', { - params: { - api_key: process.env.SCRAPINGBEE_API_KEY, - url: url, - return_page_source: true, - block_ads: true, - block_resources: false, - }, - }) - const dom = parseHTML(response.data).document - return { - title: dom.title, - content: dom.querySelector('body')?.innerHTML, - url: url, - } - } catch (error) { - console.error('error prehandling bloomberg url', error) - throw error - } - } -} diff --git a/apps/extension/src/common/content-handler/websites/derstandard-handler.ts b/apps/extension/src/common/content-handler/websites/derstandard-handler.ts deleted file mode 100644 index 58e660cca..000000000 --- a/apps/extension/src/common/content-handler/websites/derstandard-handler.ts +++ /dev/null @@ -1,35 +0,0 @@ -import axios from 'axios' -import { parseHTML } from 'linkedom' - -import { ContentHandler, PreHandleResult } from '../content-handler' - -export class DerstandardHandler extends ContentHandler { - constructor() { - super() - this.name = 'Derstandard' - } - - shouldPreHandle(url: string): boolean { - const u = new URL(url) - return u.hostname === 'www.derstandard.at' - } - - async preHandle(url: string): Promise { - const response = await axios.get(url, { - // set cookie to give consent to get the article - headers: { - cookie: `DSGVO_ZUSAGE_V1=true; consentUUID=2bacb9c1-1e80-4be0-9f7b-ee987cf4e7b0_6`, - }, - }) - const content = response.data as string - - const dom = parseHTML(content).document - const titleElement = dom.querySelector('.article-title') - titleElement && titleElement.remove() - - return { - content: dom.body.outerHTML, - title: titleElement?.textContent || undefined, - } - } -} diff --git a/apps/extension/src/common/content-handler/websites/github-handler.ts b/apps/extension/src/common/content-handler/websites/github-handler.ts deleted file mode 100644 index f305c9e18..000000000 --- a/apps/extension/src/common/content-handler/websites/github-handler.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class GitHubHandler extends ContentHandler { - constructor() { - super() - this.name = 'github' - } - - shouldPreParse(url: string, dom: Document): boolean { - return new URL(url).hostname.endsWith('github.com') - } - - async preParse(url: string, dom: Document): Promise { - const body = dom.querySelector('body') - const article = dom.querySelector('article') - const twitterTitle = dom.querySelector(`meta[name='twitter:title']`) - const linkAuthor = dom.querySelector(`span[itemprop='author']`) - - if (body && article) { - body.replaceChildren(article) - - // Attempt to set the author also. This is available on repo homepages - // but not on things like PRs. Ideally we want PRs and issues to have - // author set to the author of the PR/issue. - if (linkAuthor && linkAuthor.textContent) { - const author = dom.createElement('span') - author.setAttribute('rel', 'author') - author.innerHTML = linkAuthor.textContent - article.appendChild(author) - } - } - - // Remove the GitHub - and repo org from the title - const twitterTitleContent = twitterTitle?.getAttribute('content') - if (twitterTitle && twitterTitleContent) { - twitterTitle.setAttribute( - 'content', - twitterTitleContent.replace(/GitHub - (.*?)\//, ''), - ) - } - - return Promise.resolve(dom) - } -} diff --git a/apps/extension/src/common/content-handler/websites/image-handler.ts b/apps/extension/src/common/content-handler/websites/image-handler.ts deleted file mode 100644 index 9872f4c21..000000000 --- a/apps/extension/src/common/content-handler/websites/image-handler.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ContentHandler, PreHandleResult } from '../content-handler' - -export class ImageHandler extends ContentHandler { - constructor() { - super() - this.name = 'Image' - } - - shouldPreHandle(url: string): boolean { - const IMAGE_URL_PATTERN = /(https?:\/\/.*\.(?:jpg|jpeg|png|webp))/i - return IMAGE_URL_PATTERN.test(url.toString()) - } - - async preHandle(url: string): Promise { - const title = url.toString().split('/').pop() || 'Image' - const content = ` - - - ${title} - - - - - -
- ${title} -
- - ` - - return Promise.resolve({ title, content }) - } -} diff --git a/apps/extension/src/common/content-handler/websites/medium-handler.ts b/apps/extension/src/common/content-handler/websites/medium-handler.ts deleted file mode 100644 index 2befef68b..000000000 --- a/apps/extension/src/common/content-handler/websites/medium-handler.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ContentHandler, PreHandleResult } from '../content-handler' - -export class MediumHandler extends ContentHandler { - constructor() { - super() - this.name = 'Medium' - } - - shouldPreHandle(url: string): boolean { - const u = new URL(url) - return u.hostname.endsWith('medium.com') - } - - async preHandle(url: string): Promise { - try { - const res = new URL(url) - res.searchParams.delete('source') - return Promise.resolve({ url: res.toString() }) - } catch (error) { - console.error('error prehandling medium url', error) - throw error - } - } -} diff --git a/apps/extension/src/common/content-handler/websites/pdf-handler.ts b/apps/extension/src/common/content-handler/websites/pdf-handler.ts deleted file mode 100644 index b321bb103..000000000 --- a/apps/extension/src/common/content-handler/websites/pdf-handler.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ContentHandler, PreHandleResult } from '../content-handler' - -export class PdfHandler extends ContentHandler { - constructor() { - super() - this.name = 'PDF' - } - - shouldPreHandle(url: string): boolean { - const u = new URL(url) - const path = u.pathname.replace(u.search, '') - return path.endsWith('.pdf') - } - - async preHandle(url: string): Promise { - return Promise.resolve({ contentType: 'application/pdf' }) - } -} diff --git a/apps/extension/src/common/content-handler/websites/piped-video-handler.ts b/apps/extension/src/common/content-handler/websites/piped-video-handler.ts deleted file mode 100644 index 4313a45dd..000000000 --- a/apps/extension/src/common/content-handler/websites/piped-video-handler.ts +++ /dev/null @@ -1,84 +0,0 @@ -import axios from 'axios' -import _ from 'underscore' - -import { ContentHandler, PreHandleResult } from '../content-handler' - -export class PipedVideoHandler extends ContentHandler { - // https://piped.video/watch?v={videoId} - PIPED_URL_MATCH = /^((?:https?:)?\/\/)?piped\.video\/watch\?v=[^&]+/ - - constructor() { - super() - this.name = 'Piped-video' - } - - getYoutubeVideoId = (url: string) => { - const u = new URL(url) - return u.searchParams.get('v') - } - - escapeTitle = (title: string) => { - return _.escape(title) - } - - shouldPreHandle(url: string): boolean { - return this.PIPED_URL_MATCH.test(url.toString()) - } - - async preHandle(url: string): Promise { - const videoId = this.getYoutubeVideoId(url) - if (!videoId) { - return {} - } - const baseUrl = 'https://api-piped.mha.fi' - const apiUrl = `${baseUrl}/streams/${videoId}` - const metadata = (await axios.get(apiUrl)).data as { - title: string - thumbnailUrl: string - uploader: string - uploaderUrl: string - uploadDate: string - description: string - videoStreams: { - width: number - height: number - url: string - }[] - } - const videoStreams = metadata.videoStreams - if (!videoStreams || videoStreams.length == 0) { - return {} - } - const videoStream = videoStreams[0] - const src = `https://piped.mha.fi/embed/${videoId}` - // escape html entities in title - const title = metadata.title - const escapedTitle = this.escapeTitle(title) - const ratio = videoStream.width / videoStream.height - const thumbnail = metadata.thumbnailUrl - const height = 350 - const width = height * ratio - const authorName = _.escape(metadata.uploader) - const content = ` - - - ${escapedTitle} - - - - - - - - - - - -

${escapedTitle}

- - - ` - - return { content, title } - } -} diff --git a/apps/extension/src/common/content-handler/websites/scrapingBee-handler.ts b/apps/extension/src/common/content-handler/websites/scrapingBee-handler.ts deleted file mode 100644 index 944cce564..000000000 --- a/apps/extension/src/common/content-handler/websites/scrapingBee-handler.ts +++ /dev/null @@ -1,39 +0,0 @@ -import axios from 'axios' -import { parseHTML } from 'linkedom' - -import { ContentHandler, PreHandleResult } from '../content-handler' - -export class ScrapingBeeHandler extends ContentHandler { - constructor() { - super() - this.name = 'ScrapingBee' - } - - shouldPreHandle(url: string): boolean { - const u = new URL(url) - const hostnames = ['nytimes.com', 'news.google.com', 'fool.ca'] - - return hostnames.some((h) => u.hostname.endsWith(h)) - } - - async preHandle(url: string): Promise { - console.log('prehandling url with scrapingbee', url) - - try { - const response = await axios.get('https://app.scrapingbee.com/api/v1', { - params: { - api_key: process.env.SCRAPINGBEE_API_KEY, - url: url, - return_page_source: true, - block_ads: true, - block_resources: false, - }, - }) - const dom = parseHTML(response.data).document - return { title: dom.title, content: response.data as string, url: url } - } catch (error) { - console.error('error prehandling url w/scrapingbee', error) - throw error - } - } -} diff --git a/apps/extension/src/common/content-handler/websites/stack-overflow-handler.ts b/apps/extension/src/common/content-handler/websites/stack-overflow-handler.ts deleted file mode 100644 index 99619ad43..000000000 --- a/apps/extension/src/common/content-handler/websites/stack-overflow-handler.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class StackOverflowHandler extends ContentHandler { - constructor() { - super() - this.name = 'stackoverflow' - } - - parseText(element: Element, title: string) { - const newText = element.ownerDocument.createElement('div') - const text = element.querySelector(`div[itemprop='text']`) - if (text) { - const votes = element - .querySelector(`div[itemprop='upvoteCount']`) - ?.getAttribute('data-value') - - if (votes) { - newText.innerHTML = `

${title}: ${votes} vote${ - votes === '1' ? '' : 's' - }

${text.innerHTML}` - } - } - return newText - } - - parseComments(element: Element) { - const dom = element.ownerDocument - const newComments = dom.createElement('div') - - // comments - const commentsDiv = element.querySelector(`.comments`) - if (commentsDiv) { - const comments = commentsDiv.querySelectorAll(`.comment`) - if (comments.length > 0) { - newComments.innerHTML = `

Comments

` - - comments.forEach((comment) => { - const author = comment.querySelector(`.comment-user`) - const text = comment.querySelector(`.comment-copy`)?.textContent - const authorHref = author?.getAttribute('href') - const date = comment.querySelector(`.relativetime-clean`)?.textContent - if (author && text && authorHref && date) { - const newComment = dom.createElement('p') - newComment.innerHTML = `${author.innerHTML}: ${text} - ${date}` - newComments.appendChild(newComment) - } - }) - } - } - - return newComments - } - - parseAuthors(element: Element) { - const dom = element.ownerDocument - const newAuthors = dom.createElement('div') - - const authors = element.querySelectorAll(`.post-signature`) - authors.forEach((author) => { - const isOwner = author.classList.contains('owner') - const name = author.querySelector(`.user-details a`)?.textContent - const link = author.querySelector(`.user-details a`)?.getAttribute('href') - const reputation = author.querySelector(`.reputation-score`)?.textContent - const badges = Array.from( - author.querySelectorAll(`span[title*='badges']`), - ) - .map((badge) => badge.getAttribute('title')) - .join(', ') - const date = author.querySelector(`.user-action-time`)?.textContent - if (name && link && reputation && date) { - const newAuthor = dom.createElement('p') - newAuthor.innerHTML = `${name} - ${reputation} reputation - ${ - badges || 'no badge' - } - ${date}` - if (isOwner) { - const author = dom.createElement('span') - author.setAttribute('rel', 'author') - author.innerHTML = name - newAuthor.appendChild(author) - } - newAuthors.appendChild(newAuthor) - } - }) - - return newAuthors - } - - shouldPreParse(url: string, dom: Document): boolean { - return new URL(url).hostname.endsWith('stackoverflow.com') - } - - async preParse(url: string, dom: Document): Promise { - const mainEntity = dom.querySelector(`div[itemprop='mainEntity']`) - if (mainEntity) { - const newMainEntity = dom.createElement('div') - const question = mainEntity.querySelector('#question') - if (question) { - newMainEntity.appendChild(this.parseText(question, 'Question')) - newMainEntity.appendChild(this.parseAuthors(question)) - newMainEntity.appendChild(this.parseComments(question)) - } - - const answersDiv = mainEntity.querySelector('#answers') - if (answersDiv) { - const answers = answersDiv.querySelectorAll(`.answer`) - answers.forEach((answer) => { - const title = answer.classList.contains('accepted-answer') - ? 'Accepted Answer' - : 'Answer' - newMainEntity.appendChild(this.parseText(answer, title)) - newMainEntity.appendChild(this.parseAuthors(answer)) - newMainEntity.appendChild(this.parseComments(answer)) - }) - } - - dom.body.replaceChildren(newMainEntity) - } - - return Promise.resolve(dom) - } -} diff --git a/apps/extension/src/common/content-handler/websites/t-dot-co-handler.ts b/apps/extension/src/common/content-handler/websites/t-dot-co-handler.ts deleted file mode 100644 index 0fdd9a29a..000000000 --- a/apps/extension/src/common/content-handler/websites/t-dot-co-handler.ts +++ /dev/null @@ -1,27 +0,0 @@ -import axios from 'axios' - -import { ContentHandler } from '../content-handler' - -export class TDotCoHandler extends ContentHandler { - constructor() { - super() - this.name = 't.co' - } - - shouldResolve(url: string): boolean { - const T_DOT_CO_URL_MATCH = /^https:\/\/(?:www\.)?t\.co\/.*$/ - return T_DOT_CO_URL_MATCH.test(url) - } - - async resolve(url: string) { - return axios - .get(url, { maxRedirects: 0, validateStatus: null }) - .then((res) => { - return new URL(res.headers.location).href - }) - .catch((err) => { - console.warn('err with t.co url', err) - return undefined - }) - } -} diff --git a/apps/extension/src/common/content-handler/websites/the-atlantic-handler.ts b/apps/extension/src/common/content-handler/websites/the-atlantic-handler.ts deleted file mode 100644 index 1b1885abd..000000000 --- a/apps/extension/src/common/content-handler/websites/the-atlantic-handler.ts +++ /dev/null @@ -1,60 +0,0 @@ -import axios from 'axios' -import { parseHTML } from 'linkedom' - -import { ContentHandler, PreHandleResult } from '../content-handler' - -export class TheAtlanticHandler extends ContentHandler { - constructor() { - super() - this.name = 'The Atlantic' - } - - shouldPreHandle(url: string): boolean { - const u = new URL(url) - return u.hostname.endsWith('theatlantic.com') - } - - removeRelatedContentLinks(articleContent: Element): Node[] { - const content = Array.from(articleContent.children) - return content.filter( - (paragraph) => !paragraph.className.startsWith('ArticleRelated'), - ) - } - - unfurlContent(content: Document): Document { - const articleContentSection = content.querySelector( - '[data-event-module="article body"]', - ) - - // Remove the audio player. - content.querySelector('[data-event-module="audio player"]')?.remove() - - if (!articleContentSection) { - return content - } - - const articleContent = this.removeRelatedContentLinks(articleContentSection) - const divOverArticle = content.createElement('div') - divOverArticle.setAttribute('id', 'prehandled') - articleContent.forEach((it) => divOverArticle.appendChild(it)) - - content.insertBefore(divOverArticle, articleContentSection) - articleContentSection.remove() - - return content - } - - async preHandle(url: string): Promise { - // We simply retrieve the article without Javascript enabled using a GET command. - const response = await axios.get(url) - const data = response.data as string - const dom = parseHTML(data).document - const editedDom = this.unfurlContent(dom) - - return { - content: editedDom.body.outerHTML, - title: dom.title, - dom: editedDom, - } - } -} diff --git a/apps/extension/src/common/content-handler/websites/twitter-handler.ts b/apps/extension/src/common/content-handler/websites/twitter-handler.ts deleted file mode 100644 index 9dace75fc..000000000 --- a/apps/extension/src/common/content-handler/websites/twitter-handler.ts +++ /dev/null @@ -1,389 +0,0 @@ -import axios from 'axios' -import { truncate } from 'lodash' -import { DateTime } from 'luxon' -import { Browser, BrowserContext } from 'puppeteer-core' -import _ from 'underscore' - -import { ContentHandler, PreHandleResult } from '../content-handler' - -interface TweetIncludes { - users: { - id: string - name: string - profile_image_url: string - username: string - }[] - media?: { - preview_image_url: string - type: string - url: string - media_key: string - }[] -} - -interface TweetMeta { - result_count: number -} - -interface TweetData { - author_id: string - text: string - entities: { - urls: { - url: string - expanded_url: string - display_url: string - }[] - } - created_at: string - referenced_tweets: { - type: string - id: string - }[] - conversation_id: string - attachments?: { - media_keys: string[] - } -} - -interface Tweet { - data: TweetData - includes: TweetIncludes -} - -interface Tweets { - data: TweetData[] - includes: TweetIncludes - meta: TweetMeta -} - -const TWITTER_BEARER_TOKEN = process.env.TWITTER_BEARER_TOKEN -const TWITTER_URL_MATCH = - /twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)(?:\/.*)?/ -const MAX_THREAD_DEPTH = 100 - -const getTweetFields = () => { - const TWEET_FIELDS = - '&tweet.fields=attachments,author_id,conversation_id,created_at,' + - 'entities,geo,in_reply_to_user_id,lang,possibly_sensitive,public_metrics,referenced_tweets,' + - 'source,withheld' - const EXPANSIONS = '&expansions=author_id,attachments.media_keys' - const USER_FIELDS = - '&user.fields=created_at,description,entities,location,pinned_tweet_id,profile_image_url,protected,public_metrics,url,verified,withheld' - const MEDIA_FIELDS = - '&media.fields=duration_ms,height,preview_image_url,url,media_key,public_metrics,width' - - return `${TWEET_FIELDS}${EXPANSIONS}${USER_FIELDS}${MEDIA_FIELDS}` -} - -// unroll recent tweet thread -const getTweetThread = async (conversationId: string): Promise => { - const BASE_ENDPOINT = 'https://api.twitter.com/2/tweets/search/recent' - const apiUrl = new URL( - BASE_ENDPOINT + - '?query=' + - encodeURIComponent(`conversation_id:${conversationId}`) + - getTweetFields() + - `&max_results=${MAX_THREAD_DEPTH}`, - ) - - if (!TWITTER_BEARER_TOKEN) { - throw new Error('No Twitter bearer token found') - } - - const response = await axios.get(apiUrl.toString(), { - headers: { - Authorization: `Bearer ${TWITTER_BEARER_TOKEN}`, - redirect: 'follow', - }, - }) - return response.data -} - -const getTweetById = async (id: string): Promise => { - const BASE_ENDPOINT = 'https://api.twitter.com/2/tweets/' - const apiUrl = new URL(BASE_ENDPOINT + id + '?' + getTweetFields()) - - if (!TWITTER_BEARER_TOKEN) { - throw new Error('No Twitter bearer token found') - } - - const response = await axios.get(apiUrl.toString(), { - headers: { - Authorization: `Bearer ${TWITTER_BEARER_TOKEN}`, - redirect: 'follow', - }, - }) - - return response.data -} - -const getTweetsByIds = async (ids: string[]): Promise => { - const BASE_ENDPOINT = 'https://api.twitter.com/2/tweets?ids=' - const apiUrl = new URL(BASE_ENDPOINT + ids.join() + getTweetFields()) - - if (!TWITTER_BEARER_TOKEN) { - throw new Error('No Twitter bearer token found') - } - - const response = await axios.get(apiUrl.toString(), { - headers: { - Authorization: `Bearer ${TWITTER_BEARER_TOKEN}`, - redirect: 'follow', - }, - }) - - return response.data -} - -const titleForTweet = (author: { name: string }, text: string) => { - return `${author.name} on Twitter: ${truncate(text.replace(/http\S+/, ''), { - length: 100, - })}` -} - -const tweetIdFromStatusUrl = (url: string): string | undefined => { - const match = url.toString().match(TWITTER_URL_MATCH) - return match?.[2] -} - -const formatTimestamp = (timestamp: string) => { - return DateTime.fromJSDate(new Date(timestamp)).toLocaleString( - DateTime.DATETIME_FULL, - ) -} - -const getTweetsFromResponse = (response: Tweets): Tweet[] => { - const tweets: any[] = [] - for (const t of response.data) { - const media = response.includes.media?.filter( - (m) => t.attachments?.media_keys?.includes(m.media_key), - ) - const tweet: Tweet = { - data: t, - includes: { - users: response.includes.users, - media, - }, - } - tweets.push(tweet) - } - return tweets -} - -const getOldTweets = async ( - browser: Browser, - conversationId: string, - username: string, -): Promise => { - const tweetIds = await getTweetIds(browser, conversationId, username) - if (tweetIds.length === 0) { - return [] - } - const response = await getTweetsByIds(tweetIds) - return getTweetsFromResponse(response) -} - -const getRecentTweets = async (conversationId: string): Promise => { - const thread = await getTweetThread(conversationId) - if (thread.meta.result_count === 0) { - return [] - } - // tweets are in reverse chronological order in the thread - return getTweetsFromResponse(thread).reverse() -} - -/** - * Wait for `ms` amount of milliseconds - * @param {number} ms - */ -const waitFor = (ms: number) => - new Promise((resolve) => setTimeout(resolve, ms)) - -/** - * Get tweets(even older than 7 days) using puppeteer - * @param browser - * @param {string} tweetId - * @param {string} author - */ -const getTweetIds = async ( - browser: Browser, - tweetId: string, - author: string, -): Promise => { - const pageURL = `https://twitter.com/${author}/status/${tweetId}` - - let context: BrowserContext | undefined - try { - context = await browser.createIncognitoBrowserContext() - const page = await context.newPage() - - // Modify this variable to control the size of viewport - const deviceScaleFactor = 0.2 - const height = Math.floor(2000 / deviceScaleFactor) - const width = Math.floor(1700 / deviceScaleFactor) - await page.setViewport({ width, height, deviceScaleFactor }) - - await page.goto(pageURL, { - waitUntil: 'networkidle0', - timeout: 60000, // 60 seconds - }) - - return (await page.evaluate(async (author) => { - /** - * Wait for `ms` amount of milliseconds - * @param {number} ms - */ - const waitFor = (ms: number) => - new Promise((resolve) => setTimeout(resolve, ms)) - - const ids: any[] = [] - - // Find the first Show thread button and click it - const showRepliesButton = Array.from( - document.querySelectorAll('div[dir]'), - ) - .filter( - (node) => node.children[0] && node.children[0].tagName === 'SPAN', - ) - .find((node) => node.children[0].innerHTML === 'Show replies') - - if (showRepliesButton) { - ;(showRepliesButton as HTMLElement).click() - - await waitFor(2000) - } - - const timeNodes = Array.from(document.querySelectorAll('time')) - - for (const timeNode of timeNodes) { - /** @type {HTMLAnchorElement | HTMLSpanElement} */ - const timeContainerAnchor: HTMLAnchorElement | HTMLSpanElement | null = - timeNode.parentElement - if (!timeContainerAnchor) continue - - if (timeContainerAnchor.tagName === 'SPAN') continue - - const href = timeContainerAnchor.getAttribute('href') - if (!href) continue - - // Get the tweet id and username from the href: https://twitter.com/username/status/1234567890 - const match = href.match(/\/([^/]+)\/status\/(\d+)/) - if (!match) continue - - const id = match[2] - const username = match[1] - - // skip non-author replies - username === author && ids.push(id) - } - - return ids - }, author)) as string[] - } catch (error) { - console.error('Error getting tweets', error) - - return [] - } finally { - if (context) { - await context.close() - } - } -} - -export class TwitterHandler extends ContentHandler { - constructor() { - super() - this.name = 'Twitter' - } - - shouldPreHandle(url: string): boolean { - return !!TWITTER_BEARER_TOKEN && TWITTER_URL_MATCH.test(url.toString()) - } - - async preHandle(url: string, browser: Browser): Promise { - const tweetId = tweetIdFromStatusUrl(url) - if (!tweetId) { - throw new Error('could not find tweet id in url') - } - let tweet = await getTweetById(tweetId) - const conversationId = tweet.data.conversation_id - if (conversationId !== tweetId) { - // this is a reply, so we need to get the referenced tweet - tweet = await getTweetById(conversationId) - } - - const tweetData = tweet.data - const authorId = tweetData.author_id - const author = tweet.includes.users.filter((u) => (u.id = authorId))[0] - // escape html entities in title - const title = titleForTweet(author, tweetData.text) - const escapedTitle = _.escape(title) - const authorImage = author.profile_image_url.replace('_normal', '_400x400') - const description = _.escape(tweetData.text) - - // use puppeteer to get all tweet replies in the thread - const tweets = await getOldTweets(browser, conversationId, author.username) - - let tweetsContent = '' - for (const tweet of tweets) { - const tweetData = tweet.data - let text = tweetData.text - if (tweetData.entities && tweetData.entities.urls) { - for (const urlObj of tweetData.entities.urls) { - text = text.replace( - urlObj.url, - `${urlObj.display_url}`, - ) - } - } - - const includesHtml = - tweet.includes.media - ?.map((m) => { - const linkUrl = m.type == 'photo' ? m.url : url - const previewUrl = m.type == 'photo' ? m.url : m.preview_image_url - return ` - - - - ` - }) - .join('\n') ?? '' - - tweetsContent += ` -

${text}

- ${includesHtml} - ` - } - - const tweetUrl = ` - — ${ - author.username - } ${formatTimestamp(tweetData.created_at)} - ` - - const content = ` - - - - - - - - - - - -
- ${tweetsContent} - ${tweetUrl} -
- -` - - return { content, url, title } - } -} diff --git a/apps/extension/src/common/content-handler/websites/weixin-qq-handler.ts b/apps/extension/src/common/content-handler/websites/weixin-qq-handler.ts deleted file mode 100644 index f7b3bf824..000000000 --- a/apps/extension/src/common/content-handler/websites/weixin-qq-handler.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { DateTime } from 'luxon' - -import { ContentHandler } from '../content-handler' - -export class WeixinQqHandler extends ContentHandler { - constructor() { - super() - this.name = 'Weixin QQ' - } - - shouldPreParse(url: string, dom: Document): boolean { - return new URL(url).hostname.endsWith('weixin.qq.com') - } - - async preParse(url: string, dom: Document): Promise { - // Retrieve the publish time - const publishTime = dom.querySelector('#publish_time')?.textContent - if (publishTime) { - const dateTimeFormat = 'yyyy-LL-dd HH:mm' - // published time is in UTC+8 - const publishTimeISO = DateTime.fromFormat(publishTime, dateTimeFormat, { - zone: 'Asia/Shanghai', - }).toISO() - - // create a meta node to store the publish time in ISO format - const metaNode = dom.createElement('meta') - metaNode.setAttribute('name', 'date') - metaNode.setAttribute('content', publishTimeISO) - dom.querySelector('head')?.appendChild(metaNode) - } - // This replace the class name of the article info to preserve the block - dom - .querySelector('.rich_media_meta_list') - ?.setAttribute('class', '_dcode_rich_media_meta_list') - - // This removes the title - dom.querySelector('.rich_media_title')?.remove() - - // This removes the profile info - dom.querySelector('.profile_container')?.remove() - - // This removes the footer - dom.querySelector('#content_bottom_area')?.remove() - dom.querySelector('.rich_media_area_extra')?.remove() - dom.querySelector('#js_pc_qr_code')?.remove() - - return Promise.resolve(dom) - } -} diff --git a/apps/extension/src/common/content-handler/websites/wikipedia-handler.ts b/apps/extension/src/common/content-handler/websites/wikipedia-handler.ts deleted file mode 100644 index db872ba27..000000000 --- a/apps/extension/src/common/content-handler/websites/wikipedia-handler.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class WikipediaHandler extends ContentHandler { - constructor() { - super() - this.name = 'wikipedia' - } - - shouldPreParse(url: string, dom: Document): boolean { - return new URL(url).hostname.endsWith('wikipedia.org') - } - - async preParse(url: string, dom: Document): Promise { - // This removes the [edit] anchors from wikipedia pages - dom.querySelectorAll('.mw-editsection').forEach((e) => e.remove()) - - // Remove footnotes - dom.querySelectorAll('sup[class="reference"]').forEach((e) => e.remove()) - - // this removes the sidebar - dom.querySelector('.infobox')?.remove() - return Promise.resolve(dom) - } -} diff --git a/apps/extension/src/common/content-handler/websites/wired-handler.ts b/apps/extension/src/common/content-handler/websites/wired-handler.ts deleted file mode 100644 index 5248ed85a..000000000 --- a/apps/extension/src/common/content-handler/websites/wired-handler.ts +++ /dev/null @@ -1,60 +0,0 @@ -import axios from 'axios' -import { parseHTML } from 'linkedom' - -import { ContentHandler, PreHandleResult } from '../content-handler' - -export class WiredHandler extends ContentHandler { - constructor() { - super() - this.name = 'Wired' - } - - // We check if this is a paywalled document, as paywalled documents will have

tags - // in the body. - isPaywalledContent(document: Document): boolean { - return document.getElementsByClassName('paywall').length > 0 - } - - removeNonArticleNodes(document: Document): Document { - const genericCallouts = Array.from( - document.querySelectorAll('[data-testid="GenericCallout"]'), - ) - const ads = Array.from(document.querySelectorAll('.ad__slot')).map( - (it) => it.parentElement, - ) - const mostPopularArticles = Array.from( - document.querySelectorAll('[data-most-popular-id]'), - ) - - ;[...genericCallouts, ...ads, ...mostPopularArticles].forEach( - (it) => it?.remove(), - ) - - return document - } - - shouldPreHandle(url: string): boolean { - const u = new URL(url) - return u.hostname.endsWith('wired.com') - } - - async preHandle(url: string): Promise { - const response = await axios.get(url) - const data = response.data as string - const dom = parseHTML(data).document - - if (!this.isPaywalledContent(dom)) { - // This is just to ensure that the currently working articles don't break. - // Looking further into this, they might all have paywalls? - return {} - } - - const cleanedArticleDom = this.removeNonArticleNodes(dom) - - return { - content: cleanedArticleDom.body.outerHTML, - title: dom.title, - dom: cleanedArticleDom, - } - } -} diff --git a/apps/extension/src/common/content-handler/websites/youtube-handler.ts b/apps/extension/src/common/content-handler/websites/youtube-handler.ts deleted file mode 100644 index 441296487..000000000 --- a/apps/extension/src/common/content-handler/websites/youtube-handler.ts +++ /dev/null @@ -1,98 +0,0 @@ -import axios from 'axios' -import _ from 'underscore' - -import { ContentHandler, PreHandleResult } from '../content-handler' - -const YOUTUBE_URL_MATCH = - /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w-]+\?v=|embed\/|v\/)?)([\w-]+)(\S+)?$/ - -export const getYoutubeVideoId = (url: string) => { - const u = new URL(url) - const videoId = u.searchParams.get('v') - if (!videoId) { - const match = url.toString().match(YOUTUBE_URL_MATCH) - if (match === null || match.length < 6 || !match[5]) { - return undefined - } - return match[5] - } - return videoId -} - -export const getYoutubePlaylistId = (url: string) => { - const u = new URL(url) - return u.searchParams.get('list') -} - -export const escapeTitle = (title: string) => { - return _.escape(title) -} - -export class YoutubeHandler extends ContentHandler { - constructor() { - super() - this.name = 'Youtube' - } - - shouldPreHandle(url: string): boolean { - return YOUTUBE_URL_MATCH.test(url.toString()) - } - - async preHandle(url: string): Promise { - const BaseUrl = 'https://www.youtube.com' - const embedBaseUrl = 'https://www.youtube.com/embed' - let urlToEncode: string - let src: string - const playlistId = getYoutubePlaylistId(url) - if (playlistId) { - urlToEncode = `${BaseUrl}/playlist?list=${playlistId}` - src = `${embedBaseUrl}/videoseries?list=${playlistId}` - } else { - const videoId = getYoutubeVideoId(url) - if (!videoId) { - return {} - } - urlToEncode = `${BaseUrl}/watch?v=${videoId}` - src = `${embedBaseUrl}/${videoId}` - } - - const oembedUrl = - `https://www.youtube.com/oembed?format=json&url=` + - encodeURIComponent(urlToEncode) - const oembed = (await axios.get(oembedUrl.toString())).data as { - title: string - width: number - height: number - thumbnail_url: string - author_name: string - author_url: string - } - // escape html entities in title - const title = oembed.title - const escapedTitle = escapeTitle(title) - const ratio = oembed.width / oembed.height - const thumbnail = oembed.thumbnail_url - const height = 350 - const width = height * ratio - const authorName = _.escape(oembed.author_name) - const content = ` - - ${escapedTitle} - - - - - - - - - - -

${escapedTitle}

- - - ` - - return { content, title } - } -} diff --git a/apps/extension/src/common/content-handler/websites/zhihu-handler.ts b/apps/extension/src/common/content-handler/websites/zhihu-handler.ts deleted file mode 100644 index c919b1dd0..000000000 --- a/apps/extension/src/common/content-handler/websites/zhihu-handler.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { ContentHandler } from '../content-handler' - -export class ZhihuHandler extends ContentHandler { - constructor() { - super() - this.name = 'zhihu' - } - - parseQuestion(element: Element) { - const newQuestion = element.ownerDocument.createElement('div') - const question = element.querySelector(`.QuestionHeader-main`) - if (question) { - const votes = element - .querySelector(`div[itemprop='upvoteCount']`) - ?.getAttribute('data-value') - - if (votes) { - newQuestion.innerHTML = `

问题: ${votes} vote${ - votes === '1' ? '' : 's' - }

${question.innerHTML}` - } - } - return newQuestion - } - - parseComments(element: Element) { - const dom = element.ownerDocument - const newComments = dom.createElement('div') - - // comments - const commentsDiv = element.querySelector(`.comments`) - if (commentsDiv) { - const comments = commentsDiv.querySelectorAll(`.comment`) - if (comments.length > 0) { - newComments.innerHTML = `

Comments

` - - comments.forEach((comment) => { - const author = comment.querySelector(`.comment-user`) - const text = comment.querySelector(`.comment-copy`)?.textContent - const authorHref = author?.getAttribute('href') - const date = comment.querySelector(`.relativetime-clean`)?.textContent - if (author && text && authorHref && date) { - const newComment = dom.createElement('p') - newComment.innerHTML = `${author.innerHTML}: ${text} - ${date}` - newComments.appendChild(newComment) - } - }) - } - } - - return newComments - } - - parseAuthors(element: Element) { - const dom = element.ownerDocument - const newAuthors = dom.createElement('div') - - const authors = element.querySelectorAll(`.post-signature`) - authors.forEach((author) => { - const isOwner = author.classList.contains('owner') - const name = author.querySelector(`.user-details a`)?.textContent - const link = author.querySelector(`.user-details a`)?.getAttribute('href') - const reputation = author.querySelector(`.reputation-score`)?.textContent - const badges = Array.from( - author.querySelectorAll(`span[title*='badges']`), - ) - .map((badge) => badge.getAttribute('title')) - .join(', ') - const date = author.querySelector(`.user-action-time`)?.textContent - if (name && link && reputation && date) { - const newAuthor = dom.createElement('p') - newAuthor.innerHTML = `${name} - ${reputation} reputation - ${ - badges || 'no badge' - } - ${date}` - if (isOwner) { - const author = dom.createElement('span') - author.setAttribute('rel', 'author') - author.innerHTML = name - newAuthor.appendChild(author) - } - newAuthors.appendChild(newAuthor) - } - }) - - return newAuthors - } - - shouldPreParse(url: string, dom: Document): boolean { - return new URL(url).hostname.endsWith('zhihu.com') - } - - async preParse(url: string, dom: Document): Promise { - const mainEntity = dom.querySelector(`div[itemprop='mainEntity']`) - if (mainEntity) { - const newMainEntity = dom.createElement('div') - const question = mainEntity.querySelector('.QuestionHeader') - if (question) { - question.className = '_dcode_zhihu_question' - newMainEntity.appendChild(question) - } - - const answers = mainEntity.querySelectorAll('.ContentItem.AnswerItem') - answers.forEach((answer) => { - answer - .querySelector('.AuthorInfo') - ?.setAttribute('class', '_dcode_zhihu_author') - - answer.className = '_dcode_zhihu_answer' - newMainEntity.appendChild(answer) - }) - - dom.body.replaceChildren(newMainEntity) - } - - return Promise.resolve(dom) - } -} diff --git a/apps/extension/src/common/helper.ts b/apps/extension/src/common/helper.ts deleted file mode 100644 index dcd984dfb..000000000 --- a/apps/extension/src/common/helper.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { ACTIONS } from '~/common/action' - -export type TabInfo = { - active: boolean - audible: boolean - autoDiscardable: boolean - discarded: boolean - favIconUrl: string - groupId: number - height: number - highlighted: boolean - id: number - incognito: boolean - index: number - mutedInfo: { - muted: boolean - } - pinned: boolean - selected: boolean - status: string - title: string - url: string - width: number - windowId: number -} - -type ActionKeys = (typeof ACTIONS)[keyof typeof ACTIONS] - -export interface MsgRes { - type: T - payload: I -} - -export const storageDocKey = 'PENX-DOC' -export const selectedSpaceKey = 'SELECTED-SPACE' -export const spacesKey = 'SPACES' - -export const SUCCESS = 'SUCCESS' -export const FAIL = 'FAIL' diff --git a/apps/extension/src/common/initFower.ts b/apps/extension/src/common/initFower.ts deleted file mode 100644 index 2bdafedd9..000000000 --- a/apps/extension/src/common/initFower.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { composeAtom, setConfig, setTheme } from '@fower/react' - -export function initFower() { - composeAtom('heading1', { - text3XL: true, - fontSemibold: true, - mb: 20, - }) - - composeAtom('heading2', { - textXL: true, - fontSemibold: true, - mb: 8, - }) - - setConfig({ - inline: true, - mode: { - currentMode: 'light', - autoDarkMode: { - enabled: false, - mappings: { - black: 'gray100', - bgWhite: 'gray900', - bgSlate100: 'gray900', - brand500: 'brand500', - }, - }, - }, - }) - - setTheme({ - colors: { - brand50: '#eef2ff', - brand100: '#e0e7ff', - brand200: '#c7d2fe', - brand300: '#a5b4fc', - brand400: '#818cf8', - brand500: '#6B37FF', - brand600: '#4f46e5', - brand700: '#4338ca', - brand800: '#5b21b6', - brand900: '#4c1d95', - }, - shadows: { - popover: - '0 0 0 1px rgba(0,0,0,.08),0px 1px 1px rgba(0,0,0,.02),0px 4px 8px -4px rgba(0,0,0,.04),0px 16px 24px -8px rgba(0,0,0,.06)', - }, - }) -} diff --git a/apps/extension/src/common/parser.ts b/apps/extension/src/common/parser.ts deleted file mode 100644 index b3b552ca3..000000000 --- a/apps/extension/src/common/parser.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Readability } from '@mozilla/readability' -import { parseHTML } from 'linkedom' - -import { preParseContent } from './content-handler' - -const DEBUG_MODE = false - -export const parsePreparedContent = async (url: string, html: string) => { - let dom: Document | null = parseHTML(html).document - // Attempt to parse the article - // console.log('%c=parsePreparedContent', 'color:green', { url, html, dom }) - - dom = (await preParseContent(url, dom)) || dom - - const content = await getReadabilityResult(url, html, dom, false) - - // console.log('%c=getReadabilityResult:', 'color:green', content) - - return content -} - -async function getReadabilityResult( - url: string, - html: string, - dom: Document, - isNewsletter?: boolean, -) { - try { - const article = new Readability(dom, { - debug: DEBUG_MODE, - keepClasses: false, - }).parse() - - if (article) { - return article - } - } catch (error) { - console.log('parsing error for url', { url, error }) - } -} diff --git a/apps/extension/src/common/prepare-content.ts b/apps/extension/src/common/prepare-content.ts deleted file mode 100644 index 5e574e3ff..000000000 --- a/apps/extension/src/common/prepare-content.ts +++ /dev/null @@ -1,178 +0,0 @@ -export async function prepareContent() { - const url = window.location.href - - await scrollPage(url) - - clearExistingBackdrops() - - return prepareContentPostScroll() -} - -function prepareContentPostScroll() { - const contentCopyEl = document.createElement('div') - contentCopyEl.style.position = 'absolute' - contentCopyEl.style.left = '-2000px' - contentCopyEl.style.zIndex = '-2000' - contentCopyEl.innerHTML = document.body.innerHTML - - // Appending copy of the content to the DOM to enable computed styles capturing ability - // Without adding that copy to the DOM the `window.getComputedStyle` method will always return undefined. - document.documentElement.appendChild(contentCopyEl) - - Array.from(contentCopyEl.getElementsByTagName('*')).forEach( - prepareContentPostItem, - ) - - /* - * Grab head and body separately as using clone on entire document into a div - * removes the head and body tags while grabbing html in them. Instead we - * capture them separately and concatenate them here with head and body tags - * preserved. - */ - const contentCopyHtml = `${document.head.innerHTML}${contentCopyEl.innerHTML}` - // Cleaning up the copy element - contentCopyEl.remove() - return contentCopyHtml -} - -function prepareContentPostItem(itemEl) { - const lowerTagName = itemEl.tagName.toLowerCase() - - /* - if (lowerTagName === 'iframe') { - const frameHtml = iframes[itemEl.src] - if (!frameHtml) return - - const containerEl = document.createElement('div') - containerEl.className = 'omnivore-instagram-embed' - containerEl.innerHTML = frameHtml - - const parentEl = itemEl.parentNode - if (!parentEl) return - - parentEl.replaceChild(containerEl, itemEl) - - return - } - */ - - if (lowerTagName === 'img' || lowerTagName === 'image') { - // Removing blurred images since they are mostly the copies of lazy loaded ones - const style = window.getComputedStyle(itemEl) - const filter = style.getPropertyValue('filter') - if (filter.indexOf('blur(') === -1) return - itemEl.remove() - return - } - - const style = window.getComputedStyle(itemEl) - const backgroundImage = style.getPropertyValue('background-image') - - // convert all nodes with background image to img nodes - const noBackgroundImage = !backgroundImage || backgroundImage === 'none' - if (!noBackgroundImage) return - - const filter = style.getPropertyValue('filter') - // avoiding image nodes with a blur effect creation - if (filter && filter.indexOf('blur(') !== -1) { - itemEl.remove() - return - } - - // Replacing element only of there are no content inside, b/c might remove important div with content. - // Article example: http://www.josiahzayner.com/2017/01/genetic-designer-part-i.html - // DIV with class "content-inner" has `url("https://resources.blogblog.com/blogblog/data/1kt/travel/bg_container.png")` background image. - - if (itemEl.src) return - if (itemEl.innerHTML.length > 24) return - - const BI_SRC_REGEXP = /url\("(.+?)"\)/gi - const matchedSRC = BI_SRC_REGEXP.exec(backgroundImage) - // Using "g" flag with a regex we have to manually break down lastIndex to zero after every usage - // More details here: https://stackoverflow.com/questions/1520800/why-does-a-regexp-with-global-flag-give-wrong-results - BI_SRC_REGEXP.lastIndex = 0 - - const targetSrc = matchedSRC && matchedSRC[1] - if (!targetSrc) return - - const imgEl = document.createElement('img') - imgEl.src = targetSrc - const parentEl = itemEl.parentNode - if (!parentEl) return - - parentEl.replaceChild(imgEl, itemEl) -} - -async function scrollPage(url: string) { - const scrollingEl = document.scrollingElement || document.body - const lastScrollPos = scrollingEl.scrollTop - const currentScrollHeight = scrollingEl.scrollHeight - - /* add blurred overlay while scrolling */ - clearExistingBackdrops() - - const backdropEl = createBackdrop() - document.body.appendChild(backdropEl) - - /* - * check below compares scrollTop against initial page height to handle - * pages with infinite scroll else we shall be infinitely scrolling here. - * stop scrolling if the url has changed in the meantime. - */ - while ( - scrollingEl.scrollTop <= currentScrollHeight - 500 && - window.location.href === url - ) { - const prevScrollTop = scrollingEl.scrollTop - scrollingEl.scrollTop += 500 - /* sleep upon scrolling position change for event loop to handle events from scroll */ - await new Promise((resolve) => { - setTimeout(resolve, 10) - }) - if (scrollingEl.scrollTop === prevScrollTop) { - /* break out scroll loop if we are not able to scroll for any reason */ - // console.log('breaking out scroll loop', scrollingEl.scrollTop, currentScrollHeight); - break - } - } - scrollingEl.scrollTop = lastScrollPos - /* sleep upon scrolling position change for event loop to handle events from scroll */ - await new Promise((resolve) => { - setTimeout(resolve, 10) - }) -} - -function clearExistingBackdrops() { - console.log('clearExistingBackdrops->') - const backdropCol = document.querySelectorAll('.webext-acweb-backdrop') - for (let i = 0; i < backdropCol.length; i++) { - const backdropEl = backdropCol[i] as any - backdropEl.style.setProperty('opacity', '0', 'important') - } - - setTimeout(() => { - for (let i = 0; i < backdropCol.length; i++) { - backdropCol[i].remove() - } - }, 0.5e3) -} - -function createBackdrop() { - console.log('createBackdrop==>') - const backdropEl = document.createElement('div') - backdropEl.className = 'webext-acweb-backdrop' - backdropEl.style.cssText = `all: initial !important; - position: fixed !important; - top: 0 !important; - right: 0 !important; - bottom: 0 !important; - left: 0 !important; - z-index: 99999 !important; - background: #fff !important; - opacity: 0.8 !important; - transition: opacity 0.3s !important; - -webkit-backdrop-filter: blur(4px) !important; - backdrop-filter: blur(4px) !important; - ` - return backdropEl -} diff --git a/apps/extension/src/common/trpc.ts b/apps/extension/src/common/trpc.ts deleted file mode 100644 index a3eb4c73c..000000000 --- a/apps/extension/src/common/trpc.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createTRPCReact } from '@trpc/react-query' - -export const trpc = createTRPCReact() diff --git a/apps/extension/src/components/TrpcProvider.tsx b/apps/extension/src/components/TrpcProvider.tsx deleted file mode 100644 index 2f01a48ed..000000000 --- a/apps/extension/src/components/TrpcProvider.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { httpBatchLink } from '@trpc/client' -import React, { PropsWithChildren, useState } from 'react' -import superjson from 'superjson' - -import { trpc } from '../common/trpc' - -interface Props { - token: string -} - -export function TrpcProvider({ children, token }: PropsWithChildren) { - const [queryClient] = useState(() => new QueryClient()) - const [trpcClient] = useState(() => - trpc.createClient({ - links: [ - httpBatchLink({ - transformer: superjson, - url: `${process.env.PLASMO_PUBLIC_BASE_URL}/api/trpc`, - // You can pass any HTTP headers you wish here - async headers() { - return { - // authorization: token, - } - }, - }), - ], - }), - ) - - return ( - - {children} - - ) -} diff --git a/apps/extension/src/components/content/ContentView.tsx b/apps/extension/src/components/content/ContentView.tsx deleted file mode 100644 index ac2b22c10..000000000 --- a/apps/extension/src/components/content/ContentView.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { Box } from '@fower/react' -import { useEffect, useState } from 'react' -import { tinykeys } from 'tinykeys' -import TurndownService from 'turndown' - -import { ACTIONS, BACKGROUND_EVENTS } from '~/common/action' -import type { MsgRes } from '~/common/helper' -import { prepareContent } from '~/common/prepare-content' - -import { ContentAppType } from './constants' -import { useContentApp } from './hooks/useAppType' -import { QuickAddEditor } from './QuickAddEditor/QuickAddEditor' -import { updateText, useText } from './stores/text.store' -import { - getThumbnailState, - hideThumbnail, - showThumbnail, - useThumbnail, -} from './stores/thumbnail.store' -import { Thumbnail } from './Thumbnail' - -// import { ContentAppType } from './components/content/constants' - -document.addEventListener('mouseup', async (event) => { - const currentElement = event.target - const targetElement = document.querySelector('.ai-translator-content') - - if (targetElement && targetElement.contains(currentElement as any)) { - return - } - - const selectedText = window.getSelection()?.toString() as string - - if (selectedText !== '') { - const { pageX: x, pageY: y } = event - // const { clientX: x, clientY: y } = event - // console.log('====event:', event) - const clientX = Math.min(event.clientX, window.innerWidth - event.clientX) - const clientY = Math.min(event.clientY, window.innerHeight - event.clientY) - - setTimeout(() => { - updateText(selectedText) - showThumbnail(x, y, clientX, clientY) - }, 10) - } -}) - -document.addEventListener('click', (event) => { - const currentElement = event.target - const targetElement = document.querySelector('.penx-editor-content') - - if (targetElement && targetElement.contains(currentElement as any)) { - return - } - const store = getThumbnailState() - if (store?.visible) { - hideThumbnail() - } -}) - -export const ContentView = () => { - const { x, y, clientX, clientY, visible } = useThumbnail() - const { text } = useText() - const { type, setType } = useContentApp() - - useEffect(() => { - chrome.runtime.onMessage.addListener( - ( - request: MsgRes, - sender, - sendResponse, - ) => { - console.log('%c=contentjs onMessage:', 'color:red', request) - switch (request.type) { - case BACKGROUND_EVENTS.GetPageContent: - prepareContent() - .then((document) => { - sendResponse({ document }) - }) - .catch((error) => { - console.log('prepare error', error) - }) - break - - case BACKGROUND_EVENTS.EndOfGetPageContent: - const turndownService = new TurndownService() - const markdownContent = turndownService.turndown( - request.payload.content, - ) - - console.log( - '%c=contentjs onMessage EndOfGetPageContent parse markdownwn results:', - 'color:yellow', - { markdownContent }, - ) - break - - case ACTIONS.EnterManually: - // TODO: - break - - case ACTIONS.AreaSelect: - setType(request.payload.action as ContentAppType) - break - - default: - break - } - - return true - }, - ) - }, []) - - useEffect(() => { - let unsubscribe = tinykeys(window, { - // 'Shift+D': () => { - // console.log('open penx....') - // setType(ContentAppType.draggableEditor) - // }, - - 'Alt+Space': () => { - console.log('open penx....') - setType(ContentAppType.draggableEditor) - }, - - Escape: () => { - hideThumbnail() - setType('') - }, - }) - return () => { - unsubscribe() - } - }, [setType]) - - // - return ( - - {/* {visible && text && !type && } */} - {type === ContentAppType.draggableEditor && ( - - // - )} - - ) -} diff --git a/apps/extension/src/components/content/QuickAddEditor/ContentEditor.tsx b/apps/extension/src/components/content/QuickAddEditor/ContentEditor.tsx deleted file mode 100644 index 09e1a2a20..000000000 --- a/apps/extension/src/components/content/QuickAddEditor/ContentEditor.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { Box } from '@fower/react' -import { KeyboardEvent, PropsWithChildren, useEffect, useState } from 'react' -import { Descendant } from 'slate' - -import { ELEMENT_LI, ELEMENT_LIC, ELEMENT_P, ELEMENT_UL } from '@penx/constants' -import { QuickInputEditor } from '@penx/editor' -import { PenxEditor } from '@penx/editor-common' -import { genId } from '@penx/editor-shared' -import { StoreProvider } from '@penx/store' - -import { useText } from '../stores/text.store' -import { extensionList } from './extensionList' -import { penx } from './penx' - -function getDefaultContent(text = '') { - const content = [ - { - type: ELEMENT_UL, - children: [ - { - type: ELEMENT_LI, - children: [ - { - id: genId(), - type: ELEMENT_LIC, - children: [ - { - type: 'p', - children: [{ text }], - }, - ], - }, - ], - }, - ], - }, - ] - return content -} - -interface Props { - onChange?: (value: Descendant[], editor: PenxEditor) => void - onKeyDown?: (e: KeyboardEvent, editor?: PenxEditor) => void -} - -export const ContentEditor = ({ onChange, onKeyDown }: Props) => { - const { text } = useText() - return ( - - - - - - ) -} - -function ExtensionLoader({ children }: PropsWithChildren) { - useEffect(() => { - for (const item of extensionList) { - const ctx = Object.create(penx, { - pluginId: { - writable: false, - configurable: false, - value: item.id, - }, - }) - item.activate(ctx) - } - }, []) - - return <>{children} -} diff --git a/apps/extension/src/components/content/QuickAddEditor/QuickAddEditor.tsx b/apps/extension/src/components/content/QuickAddEditor/QuickAddEditor.tsx deleted file mode 100644 index c50a768cc..000000000 --- a/apps/extension/src/components/content/QuickAddEditor/QuickAddEditor.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { Box, styled } from '@fower/react' -import { motion, useMotionValue } from 'framer-motion' -import { SendHorizontal, X } from 'lucide-react' -import React, { forwardRef, useState } from 'react' -import { Button } from 'uikit' - -import { slateToNodes } from '@penx/serializer' - -import { BACKGROUND_EVENTS } from '~/common/action' -import { SUCCESS } from '~/common/helper' -import { getActiveSpaceId } from '~/hooks/useLocalSpaces' - -import * as styles from '../content.module.scss' -import { useContentApp } from '../hooks/useAppType' -import { ContentEditor } from './ContentEditor' - -const MotionBox = styled(motion(Box)) - -interface Props { - x?: number - y?: number -} - -export const QuickAddEditor = forwardRef( - function QuickAddEditor({ x, y }, propsRef) { - const { destroy } = useContentApp() - const [value, setValue] = useState([]) - - const onSubmit = async (editorValue?: any[]) => { - const nodes = slateToNodes([, editorValue?.[0] || value[0]]) - - console.log('content nodes', nodes) - - const data = await chrome.runtime.sendMessage({ - type: BACKGROUND_EVENTS.SUBMIT_CONTENT, - payload: { - nodes, - }, - }) - - // console.log('======x data:', data) - - destroy() - - // TODO: - if (data.code === SUCCESS) { - destroy() - } else { - alert('PenX agent is not running, Please download and run PenX agent') - } - console.log('onSubmit res:', data) - } - - const boxWidth = 560 - const boxHeight = 200 - - const posX = x || window.innerWidth / 2 - boxWidth / 2 - const posY = y || window.innerHeight * 0.2 - - // const posX = window.innerWidth / 2 - boxWidth / 2 - // const posY = window.innerHeight * 0.2 - // console.log('posX:', posX, 'posY:', posY) - const containerX = useMotionValue(0) - const containerY = useMotionValue(0) - - return ( - - { - containerX.set(containerX.get() + info.delta.x) - containerY.set(containerY.get() + info.delta.y) - }}> - - Quick add - - - destroy()}> - - - - - - { - setValue(value) - }} - onKeyDown={(e, editor) => { - const canSave = (e.ctrlKey || e.metaKey) && e.key === 'Enter' - if (canSave) { - onSubmit(editor?.children) - } - }} - /> - - - - - - - ) - }, -) diff --git a/apps/extension/src/components/content/QuickAddEditor/extensionList.ts b/apps/extension/src/components/content/QuickAddEditor/extensionList.ts deleted file mode 100644 index 0659c145b..000000000 --- a/apps/extension/src/components/content/QuickAddEditor/extensionList.ts +++ /dev/null @@ -1,82 +0,0 @@ -import * as autoFormat from '@penx/auto-format' -import * as bidirectionalLink from '@penx/bidirectional-link' -import * as blockSelector from '@penx/block-selector' -import * as blockquote from '@penx/blockquote' -import * as checkList from '@penx/check-list' -import * as codeBlock from '@penx/code-block' -import * as database from '@penx/database' -import * as divider from '@penx/divider' -import * as File from '@penx/file' -import * as heading from '@penx/heading' -import * as image from '@penx/image' -import * as link from '@penx/link' -import * as list from '@penx/list' -import * as paragraph from '@penx/paragraph' -import * as storageEstimate from '@penx/storage-estimate' -import * as table from '@penx/table' -import * as wordCount from '@penx/word-count' - -export const extensionList = [ - // { - // id: 'block-selector', - // activate: blockSelector.activate, - // }, - - { - id: 'check-list', - activate: checkList.activate, - }, - - { - id: 'paragraph', - activate: paragraph.activate, - }, - - { - id: 'heading', - activate: heading.activate, - }, - - { - id: 'blockquote', - activate: blockquote.activate, - }, - { - id: 'divider', - activate: divider.activate, - }, - { - id: 'auto-format', - activate: autoFormat.activate, - }, - { - id: 'list', - activate: list.activate, - }, - { - id: 'code-block', - activate: codeBlock.activate, - }, - // { - // id: 'image', - // activate: image.activate, - // }, - { - id: 'link', - activate: link.activate, - }, - // { id: 'bidirectional-link', activate: bidirectionalLink.activate }, - // { - // id: 'table', - // activate: table.activate, - // }, - // { - // id: 'file', - // activate: File.activate, - // }, - - // { - // id: 'database', - // activate: database.activate, - // }, -] diff --git a/apps/extension/src/components/content/QuickAddEditor/penx.ts b/apps/extension/src/components/content/QuickAddEditor/penx.ts deleted file mode 100644 index ba07654fd..000000000 --- a/apps/extension/src/components/content/QuickAddEditor/penx.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { produce } from 'immer' - -import { ExtensionContext } from '@penx/extension-typings' -import { commandsAtom, extensionStoreAtom, store } from '@penx/store' - -export const penx: ExtensionContext = { - pluginId: undefined, - - registerCommand(options) { - const commands = store.get(commandsAtom) - store.set(commandsAtom, [...commands, options]) - }, - - executeCommand(id) { - // - }, - - defineSettings(schema) { - const extensionStore = store.get(extensionStoreAtom) - const newStore = produce(extensionStore, (draft) => { - if (!draft[this.pluginId!]) draft[this.pluginId!] = {} as any - draft[this.pluginId!].settingsSchema = schema - }) - store.set(extensionStoreAtom, newStore) - }, - - registerComponent({ at, component }) { - const extensionStore = store.get(extensionStoreAtom) - const newStore = produce(extensionStore, (draft) => { - if (!draft[this.pluginId!]) { - draft[this.pluginId!] = {} as any - } - - if (!draft[this.pluginId!].components?.length) { - draft[this.pluginId!] = {} as any - draft[this.pluginId!].components = [] - } - - draft[this.pluginId!].components.push({ at, component }) - }) - store.set(extensionStoreAtom, newStore) - }, - - registerBlock(options) { - const extensionStore = store.get(extensionStoreAtom) - const newStore = produce(extensionStore, (draft) => { - if (!draft[this.pluginId!]) draft[this.pluginId!] = {} as any - - draft[this.pluginId!].block = options - }) - store.set(extensionStoreAtom, newStore) - }, - notify() { - // - }, -} diff --git a/apps/extension/src/components/content/Thumbnail.tsx b/apps/extension/src/components/content/Thumbnail.tsx deleted file mode 100644 index 933e79293..000000000 --- a/apps/extension/src/components/content/Thumbnail.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Box, FowerHTMLProps } from '@fower/react' -import { FC, forwardRef, useCallback, useMemo } from 'react' - -import { IconLogo, IconLogoLight } from '@penx/icons' - -import { ContentAppType } from './constants' -import { useContentApp } from './hooks/useAppType' - -interface ThumbnailProps extends Omit, 'children'> { - x: number - y: number -} - -export const Thumbnail = forwardRef( - function Thumbnail({ x, y, ...rest }: ThumbnailProps, ref) { - const { setType } = useContentApp() - - return ( - { - e.stopPropagation() - e.preventDefault() - // TODO... - setType(ContentAppType.draggableEditor) - }}> - - - ) - }, -) diff --git a/apps/extension/src/components/content/area-selector/common/transform-dom.ts b/apps/extension/src/components/content/area-selector/common/transform-dom.ts deleted file mode 100644 index a1ac66ed5..000000000 --- a/apps/extension/src/components/content/area-selector/common/transform-dom.ts +++ /dev/null @@ -1,318 +0,0 @@ -function isPenxContent(element: Element) { - if ( - element.closest('.ne-viewer-body') || - document.querySelector('.ne-viewer-body') - ) { - return true - } - return false -} - -export async function transformDOM(domArray: Element[]) { - const penxDOMIndex: number[] = [] - - const clonedDOMArray: Element[] = [] - - for (const dom of domArray) { - if (isPenxContent(dom)) { - clonedDOMArray.push(dom) - continue - } - const cloneDom = dom.cloneNode(true) as Element - const div = document.createElement('div') - if (cloneDom.tagName === 'CODE') { - const pre = document.createElement('pre') - pre.appendChild(cloneDom) - div.appendChild(pre) - } else { - div.appendChild(cloneDom) - } - clonedDOMArray.push(div) - } - - for ( - let clonedDOMIndex = 0; - clonedDOMIndex < clonedDOMArray.length; - clonedDOMIndex++ - ) { - let clonedDOM = clonedDOMArray[clonedDOMIndex] - - if (isPenxContent(clonedDOM)) { - try { - clonedDOMArray[clonedDOMIndex] = (await transformPenxContent( - clonedDOM, - )) as Element - penxDOMIndex.push(clonedDOMIndex) - continue - } catch (error) { - // If parsing fails, default processing will be performed. - const div = document.createElement('div') - div.appendChild(clonedDOM.cloneNode(true)) - clonedDOM = div - } - } - - const originDom = domArray[clonedDOMIndex] - - // Replace link with a tag - const linkElements = clonedDOM.querySelectorAll('a') - linkElements.forEach((a) => { - a.setAttribute('href', a.href) - }) - - // Link to replace img tag - const imgElements = clonedDOM.querySelectorAll('img') - imgElements.forEach((img) => { - img.setAttribute('src', img.src) - }) - - // Remove brothers under pre code - commonCodeBlock(clonedDOM) - - // Processing hexo code - hexoCodeBlock(clonedDOM) - - await transformVideoToImage(clonedDOM, originDom) - - // Convert canvas to img - transformCanvasToImage(clonedDOM, originDom) - } - - return clonedDOMArray.map((item, index) => { - if (penxDOMIndex.includes(index)) { - return item - } - return transformHTML(item.innerHTML) - }) -} - -function transformHTML(html: string): string { - // Clean the space tags between span tags - return html.replace(/<\/span> +  { - const onMessage = (e: MessageEvent) => { - if (e.data?.key !== 'tarnsfromPenxContentValue') { - return - } - window.removeEventListener('message', onMessage) - const result = e.data?.data?.result - if (!result || !result?.length) { - transformError('result is empty') - } - const title = element.querySelector('#article-title')?.outerHTML - resolve(`${title || ''}${e.data?.data?.result?.join('\n')}`) - } - - // Listen for messages - window.addEventListener('message', onMessage) - - const transformError = (params: any) => { - window.removeEventListener('message', onMessage) - rejected(params) - } - - setTimeout(() => { - transformError('transform timeout') - }, 3000) - - await new Promise((resolve1) => { - let script = document.querySelector( - '#penx-content-transform-script', - ) as HTMLScriptElement - if (script) { - return resolve1(true) - } - script = document.createElement('script') as HTMLScriptElement - - // TODO:fix - // const file = Chrome.runtime.getURL('penx-transform-script.js'); - const file = 'xxxxx' - - script.id = 'penx-content-transform-script' - script.setAttribute('src', file) - document.body.append(script) - script.onload = () => { - resolve1(true) - } - }) - - try { - const ids: string[] = [] - if (element.classList.contains('ne-viewer-body')) { - element.childNodes.forEach((item) => { - const id = (item as Element).id - if (id) { - ids.push(id) - } - }) - } else if (element.closest('.ne-viewer-body')) { - const id = findPenxNeTag(element)?.id - if (id) { - ids.push(id) - } - } else if (element.querySelector('.ne-viewer-body')) { - element.querySelector('.ne-viewer-body')?.childNodes.forEach((item) => { - const id = (item as Element).id - if (id) { - ids.push(id) - } - }) - } - - window.postMessage( - { - key: 'tarnsfromPenxContent', - data: { ids }, - }, - '*', - ) - } catch (error) { - transformError(error) - } - }) -} - -function findPenxNeTag(element: Element): any { - // Checks if the current element starts with a tag "ne" - if (element.tagName.toLowerCase().startsWith('ne')) { - return element - } - - // Find parent element recursively - if (element.parentNode) { - return findPenxNeTag(element.parentNode as Element) - } - - // If no matching tag is found, returns null - return null -} - -function commonCodeBlock(node: Element) { - const preElements = node.querySelectorAll('pre') - preElements.forEach((pre) => { - const codeElement = pre.querySelector('code') - if (codeElement) { - const childNodes = pre.childNodes - const needRemoveNodes: ChildNode[] = [] - const needMergeNodes: ChildNode[] = [] - childNodes.forEach((item) => { - if ((item as Element)?.tagName === 'CODE' && item !== codeElement) { - needMergeNodes.push(item) - } - if (item !== codeElement) { - needRemoveNodes.push(item) - } - }) - // Remove non-code - needRemoveNodes.forEach((item) => { - pre.removeChild(item) - }) - // Combine multiple codes into one DOM - needMergeNodes.forEach((item) => { - codeElement.appendChild(document.createElement('br')) - item.childNodes.forEach((codeChild) => - codeElement.appendChild(codeChild), - ) - }) - } - }) -} - -function hexoCodeBlock(cloneNode: Element) { - const figures = cloneNode.querySelectorAll('figure') - const processingCodeBlock = (node: HTMLElement) => { - const gutter = node.querySelector('td.gutter') - const code = node.querySelector('td.code') - if (!gutter || !code) { - return - } - const codeElement = code.querySelector('pre') - if (codeElement) { - node.parentNode?.appendChild(codeElement) - } - node.parentNode?.removeChild(node) - } - figures.forEach((figure) => { - processingCodeBlock(figure) - }) - if (figures.length === 0) { - const tables = cloneNode.querySelectorAll('table') - tables.forEach((table) => { - processingCodeBlock(table) - }) - } -} - -async function transformVideoToImage(element: Element, originDom: Element) { - /* - const videoMapArray = generateOriginAndCloneDomArray(element, originDom, 'video'); - - for (const videoMap of videoMapArray) { - const rect = videoMap.origin.getBoundingClientRect(); - const canvas = await screenShot({ - x: rect.x, - y: rect.top, - width: rect.width, - height: rect.height, - }); - - await new Promise(resolve => { - const image = document.createElement('img'); - image.src = canvas.toDataURL('image/jpeg'); - videoMap.clone.parentNode?.replaceChild(image, videoMap.clone); - - resolve(true); - }); - } - */ -} - -function transformCanvasToImage(element: Element, originDom: Element) { - const canvasMapArray = generateOriginAndCloneDomArray( - element, - originDom, - 'canvas', - ) - - for (const canvasMap of canvasMapArray) { - const imageElement = document.createElement('img') - imageElement.src = (canvasMap.origin as HTMLCanvasElement).toDataURL() - canvasMap.clone.parentNode?.replaceChild(imageElement, canvasMap.clone) - } -} - -interface IOriginAndCloneDomItem { - origin: Element - clone: Element -} - -function generateOriginAndCloneDomArray( - cloneElement: Element, - originElement: Element, - name: keyof HTMLElementTagNameMap, -): Array { - const originDoms = originElement.querySelectorAll(name) - const cloneDoms = cloneElement.querySelectorAll(name) - const result: Array = [] - if (originDoms.length < cloneDoms.length) { - for (let i = 0; i < cloneDoms.length; i++) { - const cloneDom = cloneDoms[i] - const originDom = i === 0 ? originElement : originDoms[i - 1] - result.push({ - origin: originDom, - clone: cloneDom, - }) - } - } else { - originDoms.forEach((originDom, index) => { - result.push({ - origin: originDom, - clone: cloneDoms[index], - }) - }) - } - return result -} diff --git a/apps/extension/src/components/content/area-selector/index.tsx b/apps/extension/src/components/content/area-selector/index.tsx deleted file mode 100644 index 06445f82f..000000000 --- a/apps/extension/src/components/content/area-selector/index.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import classnames from 'classnames' -import { - forwardRef, - useCallback, - useEffect, - useImperativeHandle, - useRef, - useState, -} from 'react' - -import { ContentAppType } from '../constants' -import * as styles from '../content.module.scss' -import { useForceUpdate } from '../hooks' -import { transformDOM } from './common/transform-dom' - -type Rect = Pick - -export interface ISelectorRef { - onSave: () => Promise -} - -interface ISelectorProps { - destroySelectArea: (isOpenEditor?: boolean) => void -} - -const AreaSelector = forwardRef( - function ScreenShotComponent(props, propsRef) { - const { forceUpdate } = useForceUpdate() - const targetRectListRef = useRef([]) - const targetRectRef = useRef() - const targetRef = useRef() - const targetListRef = useRef>([]) - const [saving, setSaving] = useState(false) - const ref = useRef(null) - - const onScreenshot = useCallback(async () => { - // Implementation - }, []) - - useImperativeHandle( - propsRef, - () => ({ - onSave: onScreenshot, - }), - [onScreenshot], - ) - - const onSave = useCallback(async () => { - setSaving(true) - const selections = targetListRef.current.filter((item) => item) || [] - const selectAreaElements = await transformDOM(selections) - const combinedString = ( - Array.from(selectAreaElements) as string[] - ).reduce((prev, current) => prev + current, '') - - props.destroySelectArea(true) - }, []) - - useImperativeHandle( - propsRef, - () => ({ - onSave: async () => { - onSave() - }, - type: ContentAppType.areaSelect, - }), - [onSave], - ) - - useEffect(() => { - function handleMouseOver(e: MouseEvent) { - const target = document.elementFromPoint(e.clientX, e.clientY) - // Skip the mask layer itself - if (target === ref.current || !target) { - return - } - - // If the selected element is already selected, it is no longer selected. - if (targetListRef.current.find((item) => item === target)) { - return - } - - // If the selected target is in the background, it will not be selected. - if (target?.closest('.select-inner')) { - return - } - - if (typeof target?.getBoundingClientRect !== 'function') { - return - } - - const scrollbarHeight = document.documentElement.scrollTop - const scrollbarWidth = document.documentElement.scrollLeft - - const { width, height, left, top } = target.getBoundingClientRect() - targetRef.current = target - targetRectRef.current = { - width, - height, - left: left + scrollbarWidth, - top: top + scrollbarHeight, - } - forceUpdate() - } - - const onToggleSelect = (e: MouseEvent) => { - e.stopImmediatePropagation() - e.preventDefault() - const target = e.target as Element - if (target.closest('.select-confirm')) { - onSave() - } else if (target?.closest('.select-inner')) { - const key = parseInt( - target.getAttribute('data-select-index') as string, - ) - targetRectListRef.current = targetRectListRef.current.filter( - (__, index) => key !== index, - ) - targetListRef.current = targetListRef.current.filter( - (__, index) => key !== index, - ) - } else { - if (!targetRectRef.current || !targetRef.current) { - return - } - targetRectListRef.current = [ - ...targetRectListRef.current.filter((__, index) => { - return !targetRef.current?.contains(targetListRef.current[index]) - }), - targetRectRef.current, - ] - targetListRef.current = [ - ...targetListRef.current.filter((__, index) => { - return !targetRef.current?.contains(targetListRef.current[index]) - }), - targetRef.current, - ] - targetRef.current = null - targetRectRef.current = null - } - forceUpdate() - setTimeout(() => { - window.focus() - }, 200) - } - - window.addEventListener('mouseover', handleMouseOver) - window.addEventListener('click', onToggleSelect, true) - return () => { - window.removeEventListener('mouseover', handleMouseOver) - window.removeEventListener('click', onToggleSelect, true) - } - }, [onSave]) - - if (saving) { - return null - } - - return ( - <> -
- Click an area to select it, click it again to deselect it. ESC exits, - Enter completes - {!!targetRectListRef.current.length && ( -
- Confirm selection -
- )} -
- - {targetRectListRef.current.map((item, index) => { - return ( - item?.width && ( -
- ) - ) - })} - - {targetRectRef.current?.width && ( -
- )} - - ) - }, -) - -AreaSelector.displayName = 'AreaSelector' - -export default AreaSelector diff --git a/apps/extension/src/components/content/constants.ts b/apps/extension/src/components/content/constants.ts deleted file mode 100644 index 1e9700400..000000000 --- a/apps/extension/src/components/content/constants.ts +++ /dev/null @@ -1,26 +0,0 @@ -export enum ContentAppType { - areaSelect = 'areaSelect', - screenShot = 'screenShot', - draggableEditor = 'draggableEditor', -} - -// selection container id -export const PENX_SELECTION_CONTAINER = 'penx-selection-container' - -// sandbox iframe id -export const PENX_SANDBOX_BOARD_IFRAME = 'penx-sandbox-board-iframe' - -export enum SandBoxMessageType { - getSelectedHtml = 'getSelectedHtml', - - initSandbox = 'initSandbox', - - startOcr = 'startOcr', -} - -export enum ClippingTypeEnum { - // selection content - area = 'area', - // selection website - website = 'website', -} diff --git a/apps/extension/src/components/content/content.module.scss b/apps/extension/src/components/content/content.module.scss deleted file mode 100644 index 3706d1dc8..000000000 --- a/apps/extension/src/components/content/content.module.scss +++ /dev/null @@ -1,176 +0,0 @@ -div { - outline: none !important; - font-family: 'Inter-local', 'Helvetica Neue', Helvetica, Arial, 'Roboto', - sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', - 'Noto Color Emoji'; -} - -#selection-container { - pointer-events: none; - position: absolute; - width: 100vw; - height: 100vh; - top: 0; -} - -.selectInner { - border: 2px dashed #00a82d; - box-sizing: border-box; - position: absolute; - width: 100%; - height: 100%; - z-index: 2147483645; - pointer-events: none; -} - -.selected { - border: 2px solid #00a82d; -} - -.mask { - display: flex; - box-sizing: border-box; - align-items: center; - position: fixed; - top: 10%; - left: 50%; - height: 40px; - padding: 0 12px; - font-size: 14px; - transform: translateX(-50%); - background-color: rgba(0, 0, 0, 0.65); - z-index: 2147483646; - border-radius: 10px; - text-align: center; - pointer-events: none; - color: #fff; -} - -.confirm { - height: 24px; - line-height: 24px; - margin-left: 42px; - background-color: #00b96b; - box-sizing: border-box; - border-color: #00b96b; - border-radius: 4px; - cursor: pointer; - display: inline-block; - color: #fff; - font-size: 12px; - font-weight: 400; - padding: 0 8px; - text-align: center; - touch-action: manipulation; - user-select: none; - vertical-align: middle; - white-space: nowrap; - text-decoration: none; - pointer-events: all; -} - -/* screenShot start */ -.screenShotWrapper { - position: fixed; - left: 0; - top: 0; - right: 0; - bottom: 0; - z-index: 2147483645; -} - -.screenBackgroundLeft, -.screenBackgroundTop, -.screenBackgroundRight, -.screenBackgroundBottom { - background-color: rgba(0, 0, 0, 0.27); - position: absolute; -} - -.area { - position: absolute; - background-color: transparent; - border: 2px solid #00b96b; - box-sizing: border-box; -} - -.operateBar { - display: flex; - height: 32px; - position: absolute; - gap: 4px; - align-items: center; - background-color: rgba(0, 0, 0, 0.65); - border-radius: 8px; -} - -.operateItem { - height: 100%; - width: 32px; - justify-content: center; - align-items: center; - cursor: pointer; - color: white; - display: flex; -} - -.operateItem :hover { - color: #00b96b; -} - -/* screenShot end */ - -/* drag-line */ -.topLine { - border-top: 6px solid transparent; - border-bottom: 6px solid transparent; - position: absolute; - top: 0; - width: 100%; - cursor: row-resize; - transform: translateY(-50%); -} - -.bottomLine { - border-top: 6px solid transparent; - border-bottom: 6px solid transparent; - position: absolute; - bottom: 0; - width: 100%; - cursor: row-resize; - transform: translateY(50%); -} - -.rightLine { - border-left: 6px solid transparent; - border-right: 6px solid transparent; - position: absolute; - right: 0; - height: 100%; - cursor: col-resize; - transform: translateX(50%); -} - -.leftLine { - border-left: 6px solid transparent; - border-right: 6px solid transparent; - position: absolute; - left: 0; - height: 100%; - cursor: col-resize; - transform: translateX(-50%); -} - -.dragBarMask { - position: fixed; - width: 100vw; - height: 100vh; - left: 0; - top: 0; - bottom: 0; - z-index: 2147483646; -} - -.editorBtn { - border: none; -} diff --git a/apps/extension/src/components/content/hooks.ts b/apps/extension/src/components/content/hooks.ts deleted file mode 100644 index b85b5f227..000000000 --- a/apps/extension/src/components/content/hooks.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useStorage } from '@plasmohq/storage/hook' -import React, { useState } from 'react' -import { useStore } from 'stook' - -import { ISpace } from '@penx/model-types' - -import { selectedSpaceKey, spacesKey, storageDocKey } from '~/common/helper' - -export function useSelectedSpace() { - const [selectedSpace, setSelectedSpace] = useStorage(selectedSpaceKey, '') - return { selectedSpace, setSelectedSpace } -} - -export function useMySpaces() { - const [mySpaces, setMySpaces] = useStorage(spacesKey, []) - return { mySpaces, setMySpaces } -} - -export function useForceUpdate() { - const [_, setCount] = useState(0) - - const forceUpdate = () => { - setCount((count) => count + 1) - } - - return { - forceUpdate, - } -} diff --git a/apps/extension/src/components/content/hooks/useAppType.ts b/apps/extension/src/components/content/hooks/useAppType.ts deleted file mode 100644 index 11fe1ef07..000000000 --- a/apps/extension/src/components/content/hooks/useAppType.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { atom, useAtom } from 'jotai' - -import { hideThumbnail } from '../stores/thumbnail.store' - -const appTypeAtom = atom('') - -export function useContentApp() { - const [type, setType] = useAtom(appTypeAtom) - function destroy() { - setType('') - hideThumbnail() - } - - return { type, setType, destroy } -} diff --git a/apps/extension/src/components/content/screen-shot/common/screen-shot.ts b/apps/extension/src/components/content/screen-shot/common/screen-shot.ts deleted file mode 100644 index 8d402c717..000000000 --- a/apps/extension/src/components/content/screen-shot/common/screen-shot.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { BACKGROUND_EVENTS } from '~/common/action' - -interface IScreenShotOptions { - width: number - height: number - x: number - y: number -} - -export function blobToBase64(blob: Blob): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader() - reader.onloadend = () => { - const base64String = reader.result as string - resolve(base64String) - } - reader.onerror = reject - reader.readAsDataURL(blob) - }) -} - -export async function screenShot( - options: IScreenShotOptions, -): Promise { - return new Promise((resolve, rejected) => { - chrome.runtime.sendMessage( - { - type: BACKGROUND_EVENTS.SCREEN_SHOT, - payload: {}, - }, - (base64) => { - try { - const image = new Image() - image.src = base64 - image.onload = () => { - const imageWidthRatio = image.width / window.innerWidth - const imageHeightRatio = image.height / window.innerHeight - const canvas = document.createElement('canvas') - const context = canvas.getContext('2d') - - /** - * Set the coordinates, width and height of the interception area - */ - // The x-coordinate of the upper left corner of the area - const x = options.x * imageWidthRatio - // The y coordinate of the upper left corner of the area - const y = options.y * imageHeightRatio - // area width - const width = options.width * imageWidthRatio - // area height - const height = options.height * imageHeightRatio - // Draw the intercepted area on canvas - canvas.width = width - canvas.height = height - context?.drawImage(image, x, y, width, height, 0, 0, width, height) - - resolve(canvas) - } - } catch (error) { - rejected(error) - } - }, - ) - }) -} diff --git a/apps/extension/src/components/content/screen-shot/drag-line.tsx b/apps/extension/src/components/content/screen-shot/drag-line.tsx deleted file mode 100644 index 72fdfcb17..000000000 --- a/apps/extension/src/components/content/screen-shot/drag-line.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useState } from 'react' - -import * as styles from '../content.module.scss' - -export type DragDirection = 'top' | 'bottom' | 'left' | 'right' - -interface IDragLineProps { - direction: DragDirection - width: number - height: number - updatePosition: ( - event: React.DragEvent, - resetPosition?: boolean, - ) => void - handleDragEnd: () => void -} - -const DragLine = (props: IDragLineProps) => { - const { direction, width, height, updatePosition } = props - const [dragging, setDragging] = useState(false) - - const handleDragStart = (event: React.DragEvent) => { - setDragging(true) - } - - const handleDrag = (event: React.DragEvent) => { - if (!dragging || !event.clientX || !event.clientY) return - updatePosition(event, false) - } - - const handleDragEnd = (event: React.DragEvent) => { - setDragging(false) - updatePosition(event, true) - props.handleDragEnd?.() - } - - return ( - <> -
- {dragging && ( -
{ - setDragging(false) - props.handleDragEnd?.() - }} - /> - )} - - ) -} - -export default React.memo(DragLine) diff --git a/apps/extension/src/components/content/screen-shot/index.tsx b/apps/extension/src/components/content/screen-shot/index.tsx deleted file mode 100644 index 9a9942c93..000000000 --- a/apps/extension/src/components/content/screen-shot/index.tsx +++ /dev/null @@ -1,331 +0,0 @@ -import classnames from 'classnames' -import { Check, X } from 'lucide-react' -import React, { - forwardRef, - useCallback, - useEffect, - useImperativeHandle, - useRef, - useState, -} from 'react' - -import { ContentAppType } from '../constants' -import * as styles from '../content.module.scss' -import { useForceUpdate } from '../hooks' -import { blobToBase64, screenShot } from './common/screen-shot' -import DragLine from './drag-line' - -type DragDirection = 'top' | 'bottom' | 'left' | 'right' - -export interface IScreenShotRef { - onSave: () => Promise -} - -interface IScreenShotProps { - destroySelectArea: () => void -} - -const ScreenShot = forwardRef( - function ScreenShotComponent(props, propsRef) { - const isScreenshot = useRef(false) - const loadingRef = useRef(false) - const ref = useRef(null) - const [screenShowAreaIsInit, setScreenShowAreaIsInit] = useState(false) - const startRef = useRef({ left: 0, top: 0 }) - const endRef = useRef({ left: 0, top: 0 }) - const { forceUpdate } = useForceUpdate() - const [isDragging, setIsDragging] = useState(false) - const selectAreaWidth = Math.abs( - endRef.current.left - startRef.current.left, - ) - const selectAreaHeight = Math.abs(endRef.current.top - startRef.current.top) - const selectAreaLeft = Math.min(startRef.current.left, endRef.current.left) - const selectAreaTop = Math.min(startRef.current.top, endRef.current.top) - const selectAreaRight = selectAreaLeft + selectAreaWidth - const selectAreaBottom = selectAreaTop + selectAreaHeight - const operatePosition = { - left: Math.max(selectAreaRight - 72, 0), - top: - selectAreaBottom + 40 > window.innerHeight - ? selectAreaTop + 8 - : selectAreaBottom + 8, - } - - useEffect(() => { - if (screenShowAreaIsInit) { - return - } - const onMouseDown = (e: MouseEvent) => { - if (e.button === 2) { - return - } - isScreenshot.current = true - startRef.current = { - left: e.clientX, - top: e.clientY, - } - } - const onMouseUp = () => { - if (isScreenshot.current && endRef.current.left && endRef.current.top) { - setScreenShowAreaIsInit(true) - } - isScreenshot.current = false - } - - const onMouseMove = (e: MouseEvent) => { - if (!isScreenshot.current) { - return - } - endRef.current = { - left: e.clientX, - top: e.clientY, - } - forceUpdate() - } - ref.current?.addEventListener('mousedown', onMouseDown) - ref.current?.addEventListener('mouseup', onMouseUp) - ref.current?.addEventListener('mousemove', onMouseMove) - ref.current?.addEventListener('mouseleave', onMouseUp) - - return () => { - ref.current?.removeEventListener('mousedown', onMouseDown) - ref.current?.removeEventListener('mousemove', onMouseMove) - ref.current?.removeEventListener('mouseup', onMouseUp) - ref.current?.addEventListener('mouseleave', onMouseUp) - } - }, [screenShowAreaIsInit]) - - const onDrag = useCallback( - ( - e: React.DragEvent, - direction: DragDirection, - resetPosition?: boolean, - ) => { - setIsDragging(true) - switch (direction) { - case 'left': { - startRef.current.left = e.clientX - break - } - case 'right': { - endRef.current.left = e.clientX || window.innerWidth - break - } - case 'top': { - startRef.current.top = e.clientY - break - } - case 'bottom': { - endRef.current.top = e.clientY || window.innerHeight - break - } - default: - break - } - if (resetPosition) { - // After the calculation is completed, perform a data correction on endRef and startRef. - const endPosition = { - left: Math.max(endRef.current.left, startRef.current.left), - top: Math.max(endRef.current.top, startRef.current.top), - } - const startPosition = { - left: Math.min(endRef.current.left, startRef.current.left), - top: Math.min(endRef.current.top, startRef.current.top), - } - endRef.current = endPosition - startRef.current = startPosition - } - forceUpdate() - }, - [], - ) - - const handleDragEnd = () => { - setIsDragging(false) - } - - const onScreenshot = useCallback(async () => { - if (loadingRef.current) { - return - } - loadingRef.current = true - forceUpdate() - - // Delay 100ms to hide the prompt to avoid taking screenshots. - await new Promise((resolve) => { - setTimeout(() => { - resolve(true) - }, 100) - }) - - try { - const canvas = await screenShot({ - x: startRef.current.left, - y: startRef.current.top, - width: Math.abs(endRef.current.left - startRef.current.left), - height: Math.abs(endRef.current.top - startRef.current.top), - }) - - canvas.toBlob((res) => { - /* - sendMessageToSandBox(SandBoxMessageType.startOcr, { - blob: res, - }); - */ - blobToBase64(res!) - .then((base64String) => { - console.log('sendMessageToSandBox-toBlob-2:', { - canvas, - base64String, - }) - }) - .catch((error) => { - console.error(error) - }) - - loadingRef.current = false - forceUpdate() - props.destroySelectArea() - }) - } catch (error) { - loadingRef.current = false - forceUpdate() - /* - sendMessageToSandBox(SandBoxMessageType.startOcr, { - blob: '', - }); - */ - - props.destroySelectArea() - } - }, []) - - useImperativeHandle( - propsRef, - () => ({ - onSave: onScreenshot, - type: ContentAppType.screenShot, - }), - [onScreenshot], - ) - - if (loadingRef.current) { - return - } - - return ( -
-
-
-
-
-
- - onDrag(e, 'top', resetPosition) - } - direction="top" - handleDragEnd={handleDragEnd} - /> - - onDrag(e, 'bottom', resetPosition) - } - direction="bottom" - handleDragEnd={handleDragEnd} - /> - - - onDrag(e, 'left', resetPosition) - } - direction="left" - key="left" - handleDragEnd={handleDragEnd} - /> - - onDrag(e, 'right', resetPosition) - } - direction="right" - key="right" - handleDragEnd={handleDragEnd} - /> -
- {!isDragging && screenShowAreaIsInit && ( -
-
- -
-
- -
-
- )} -
- ) - }, -) - -ScreenShot.displayName = 'ScreenShot' - -export default ScreenShot diff --git a/apps/extension/src/components/content/stores/text.store.ts b/apps/extension/src/components/content/stores/text.store.ts deleted file mode 100644 index 1a8b5f803..000000000 --- a/apps/extension/src/components/content/stores/text.store.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { mutate, useStore } from 'stook' - -const key = 'TEXT_SELECTED' - -type State = { - text: string - selected?: boolean -} - -export function useText() { - const [state, setState] = useStore(key, { text: '' } as State) - const setText = (value: string) => { - setState((state) => { - state.text = value - }) - } - return { - ...state, - setText, - } -} - -export function updateText(text: string) { - mutate(key, { text }) -} diff --git a/apps/extension/src/components/content/stores/thumbnail.store.ts b/apps/extension/src/components/content/stores/thumbnail.store.ts deleted file mode 100644 index ecdb21d61..000000000 --- a/apps/extension/src/components/content/stores/thumbnail.store.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { getState, mutate, useStore } from 'stook' - -const key = 'Thumbnail' - -type State = { - x: number - y: number - clientX: number - clientY: number - visible: boolean -} - -export function useThumbnail() { - const [state, setThumbnail] = useStore(key, { - visible: false, - } as State) - return { - visible: state.visible, - x: state.x, - y: state.y, - clientX: state.clientX, - clientY: state.clientY, - setState: setThumbnail, - } -} - -export function showThumbnail( - x: number, - y: number, - clientX: number, - clientY: number, -) { - mutate(key, (state: State) => { - state.visible = true - state.x = x - state.y = y - state.clientX = clientX - state.clientX = clientY - }) -} - -export function hideThumbnail() { - mutate(key, (state: State) => { - state.visible = false - }) -} - -export function getThumbnailState(): State { - return getState(key) -} diff --git a/apps/extension/src/components/popup/LocalSpacesSelect.tsx b/apps/extension/src/components/popup/LocalSpacesSelect.tsx deleted file mode 100644 index d25f88039..000000000 --- a/apps/extension/src/components/popup/LocalSpacesSelect.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Box } from '@fower/react' -import { - Select, - SelectContent, - SelectIcon, - SelectItem, - SelectTrigger, - SelectValue, -} from 'uikit' - -import { useInitLocalSpaces, useLocalSpaces } from '~/hooks/useLocalSpaces' - -export function LocalSpacesSelect() { - useInitLocalSpaces() - const { loading, activeSpaceId, spaces, setActiveSpaceId } = useLocalSpaces() - - if (loading) return null - - return ( - - - Add to space - - - - ) -} diff --git a/apps/extension/src/components/popup/Popup.tsx b/apps/extension/src/components/popup/Popup.tsx deleted file mode 100644 index 142882731..000000000 --- a/apps/extension/src/components/popup/Popup.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import { Box } from '@fower/react' -import { - Monitor, - Scissors, - Text, - Volume2, - type LucideProps, -} from 'lucide-react' -import { useEffect, useState, type ComponentType } from 'react' -import { Button } from 'uikit' - -import { Logo } from '@penx/widget' - -import { ACTIONS, BACKGROUND_EVENTS } from '~/common/action' -import type { MsgRes, TabInfo } from '~/common/helper' -import { ContentAppType } from '~/components/content/constants' -import styles from '~/components/popup/popup.module.css' -import { SpacesSelect } from '~/components/popup/SpacesSelect' -import { UserProfile } from '~/components/popup/UserProfile' - -import { LocalSpacesSelect } from './LocalSpacesSelect' - -interface FeatureEntryProps { - name: string - type: ContentAppType - icon: ComponentType -} - -function FeatureEntry({ name, type, icon: Icon }: FeatureEntryProps) { - const disabled = type !== ContentAppType.draggableEditor - const onAreaSelect = async () => { - if (disabled) return - window.close() - - const [tab] = await chrome.tabs.query({ - active: true, - currentWindow: true, - }) - - chrome.tabs.sendMessage(tab.id!, { - type: ACTIONS.AreaSelect, - payload: { - action: type, - }, - }) - } - return ( - - - {name} - - - - - - ) -} - -export function Popup() { - const [tab, setTab] = useState(null as any) - - const getCurrentTab = async () => { - const [tab] = await chrome.tabs.query({ - active: true, - currentWindow: true, - }) - - setTab(tab as TabInfo) - } - - const onClipEntirePage = async () => { - try { - if (tab) { - const data = await chrome.runtime.sendMessage({ - type: BACKGROUND_EVENTS.QueryTab, - payload: tab, - }) - - console.log('popup.tsx onClipEntirePage-res', data) - } else { - console.warn('popup.tsx No tab information') - } - } catch (error) { - console.error(error) - } - } - - const initTabsListener = () => { - chrome.runtime.onMessage.addListener( - (request: MsgRes, sender, sendResponse) => { - console.log('%c=popup.tsx add Listener:', 'color:gold', request) - - switch (request.type) { - case BACKGROUND_EVENTS.TabNotComplete: - // Todo - console.log( - '%c=popup.tsx-add Listener TabNotComplete:', - 'color: gold', - ) - break - case BACKGROUND_EVENTS.GetPageContent: - console.log( - '%c=popup.tsx-add Listener GetPageContent:', - 'color: gold', - ) - // sendResponse({ staus: 'loading' }) - return Promise.resolve({ response: 'Hi from content script' }) - default: - // Handle other cases here - break - } - - return true - }, - ) - } - - const initPopup = async () => { - await chrome.runtime.sendMessage({ - type: BACKGROUND_EVENTS.INT_POPUP, - payload: {}, - }) - } - - useEffect(() => { - initTabsListener() - getCurrentTab() - initPopup() - }, []) - - return ( - - - - {/* */} - - {/* */} - {/* your currentUrl is: {tab?.url} */} - - - - - - - - {/* */} - {/* */} - - ) -} diff --git a/apps/extension/src/components/popup/SpacesSelect.tsx b/apps/extension/src/components/popup/SpacesSelect.tsx deleted file mode 100644 index 11668a634..000000000 --- a/apps/extension/src/components/popup/SpacesSelect.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Box } from '@fower/react' -import { useEffect, useState } from 'react' -import { - Select, - SelectContent, - SelectIcon, - SelectItem, - SelectTrigger, - SelectValue, -} from 'uikit' - -import { useMySpaces, useSelectedSpace } from '../content/hooks' - -interface SpacesSelectProps {} - -export function SpacesSelect(props: SpacesSelectProps) { - const [value, setValue] = useState('') - const { selectedSpace, setSelectedSpace } = useSelectedSpace() - const { mySpaces } = useMySpaces() - - const onChange = (v: string) => { - setValue(v) - setSelectedSpace(v) - } - - useEffect(() => { - setValue(selectedSpace) - }, [selectedSpace]) - - return ( - - Save to space: - - - ) -} diff --git a/apps/extension/src/components/popup/UserProfile.tsx b/apps/extension/src/components/popup/UserProfile.tsx deleted file mode 100644 index 2b22608e6..000000000 --- a/apps/extension/src/components/popup/UserProfile.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Box } from '@fower/react' -import { Avatar, AvatarImage } from 'uikit' - -import { useSession } from '~/hooks/useSession' - -export function UserProfile() { - const { loading, data } = useSession() - if (loading) return null - - return ( - - - - - {data.user.email} - - ) -} diff --git a/apps/extension/src/components/popup/globals.module.css b/apps/extension/src/components/popup/globals.module.css deleted file mode 100644 index 9b8b80b4a..000000000 --- a/apps/extension/src/components/popup/globals.module.css +++ /dev/null @@ -1,3 +0,0 @@ -body{ - margin: 0px; -} \ No newline at end of file diff --git a/apps/extension/src/components/popup/login.module.css b/apps/extension/src/components/popup/login.module.css deleted file mode 100644 index 9f92c986e..000000000 --- a/apps/extension/src/components/popup/login.module.css +++ /dev/null @@ -1,43 +0,0 @@ -.container{ - position: relative; - display: flex; - align-items: center; - flex-direction: column; - width: 300px; - height: 288px; - padding: 24px; -} - -.welcome{ - margin-top: 20px; -} - -.btnContainer{ - position: absolute; - display: flex; - bottom: 0px; - height: 100px; - width: 100%; - justify-content: center; - align-items: center; - flex-direction: column; -} - -.loginBtn{ - width: 70%; - height: 38px; - background: #262525; - color: #fff; - border-radius: 3px; - border: none; - cursor: pointer; -} - -.loginBtn:hover { - background: #000000; - cursor: pointer; -} - -.loginBtn:active { - background: #262525; -} \ No newline at end of file diff --git a/apps/extension/src/components/popup/login.tsx b/apps/extension/src/components/popup/login.tsx deleted file mode 100644 index fce57bf71..000000000 --- a/apps/extension/src/components/popup/login.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { IconLogo } from '@penx/icons' - -import { trpc } from '~/common/trpc' - -import styles from './login.module.css' - -interface LoginProps { - loginCallback(): void -} - -export function Login(props: LoginProps) { - const onLogin = () => { - window.open(process.env.PLASMO_PUBLIC_BASE_URL) - } - - return ( -
-
- -

Welcome

-
-
- -
-
- ) -} diff --git a/apps/extension/src/components/popup/popup.module.css b/apps/extension/src/components/popup/popup.module.css deleted file mode 100644 index e349e71e7..000000000 --- a/apps/extension/src/components/popup/popup.module.css +++ /dev/null @@ -1,23 +0,0 @@ -.item { - display: flex; - box-sizing: border-box; - align-items: center; - height: 32px; - list-style-type: none; - transition: - border-color 0.3s, - background 0.3s, - padding 0.2s cubic-bezier(0.215, 0.61, 0.355, 1); - border-radius: 8px; - padding: 8px; -} - -.item:hover { - background-color: rgb(226, 223, 223); - cursor: pointer; -} - -.item-selected { - background-color: #e1faeb; - color: #9c27b0; -} diff --git a/apps/extension/src/content.tsx b/apps/extension/src/content.tsx deleted file mode 100644 index de645f6d7..000000000 --- a/apps/extension/src/content.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import styles from 'data-text:./components/content/content.module.scss' -import type { PlasmoCSConfig } from 'plasmo' - -import { initFower } from './common/initFower' -import { ContentView } from './components/content/ContentView' - -initFower() - -export const getStyle = () => { - const style = document.createElement('style') - style.textContent = styles - - return style -} - -export const config: PlasmoCSConfig = { - matches: [''], -} - -export default ContentView diff --git a/apps/extension/src/globals.css b/apps/extension/src/globals.css deleted file mode 100644 index 83e570ece..000000000 --- a/apps/extension/src/globals.css +++ /dev/null @@ -1,217 +0,0 @@ -html, -body { - padding: 0; - margin: 0; - /* font-size: 14px; */ - - font-family: 'Inter-local', 'Helvetica Neue', Helvetica, Arial, 'Roboto', - sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', - 'Noto Color Emoji'; -} - -h1, -h2, -h3, -h4, -h5, -h6, -ul, -ol, -li { - line-height: 1; - padding: 0; - margin: 0; -} - -div, -span { - line-height: 1; -} - -html { - scroll-padding-top: 120px; /* height of your navbar */ -} - -/** - * prism.js default theme for JavaScript, CSS and HTML - * Based on dabblet (http://dabblet.com) - * @author Lea Verou - */ -code[class*='language-'], -pre[class*='language-'] { - color: black; - background: none; - text-shadow: 0 1px white; - font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; - font-size: 1em; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - line-height: 1.5; - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; -} - -pre[class*='language-']::-moz-selection, -pre[class*='language-'] ::-moz-selection, -code[class*='language-']::-moz-selection, -code[class*='language-'] ::-moz-selection { - text-shadow: none; - background: #b3d4fc; -} - -pre[class*='language-']::selection, -pre[class*='language-'] ::selection, -code[class*='language-']::selection, -code[class*='language-'] ::selection { - text-shadow: none; - background: #b3d4fc; -} - -@media print { - code[class*='language-'], - pre[class*='language-'] { - text-shadow: none; - } -} - -/* Code blocks */ -pre[class*='language-'] { - padding: 1em; - margin: 0.5em 0; - overflow: auto; -} - -:not(pre) > code[class*='language-'], -pre[class*='language-'] { - background: #f5f2f0; -} - -/* Inline code */ -:not(pre) > code[class*='language-'] { - padding: 0.1em; - border-radius: 0.3em; - white-space: normal; -} - -.token.comment, -.token.prolog, -.token.doctype, -.token.cdata { - color: slategray; -} - -.token.punctuation { - color: #999; -} - -.token.namespace { - opacity: 0.7; -} - -.token.property, -.token.tag, -.token.boolean, -.token.number, -.token.constant, -.token.symbol, -.token.deleted { - color: #905; -} - -.token.selector, -.token.attr-name, -.token.string, -.token.char, -.token.builtin, -.token.inserted { - color: #690; -} - -.token.operator, -.token.entity, -.token.url, -.language-css .token.string, -.style .token.string { - color: #9a6e3a; - /* This background color was intended by the author of this theme. */ - /* background: hsla(0, 0%, 100%, 0.5); */ -} - -.token.atrule, -.token.attr-value, -.token.keyword { - color: #07a; -} - -.token.function, -.token.class-name { - color: #dd4a68; -} - -.token.regex, -.token.important, -.token.variable { - color: #e90; -} - -.token.important, -.token.bold { - font-weight: bold; -} -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} - -.react-datepicker__triangle { - display: none; -} - -.react-datepicker { - border: none; - box-shadow: - 0 0 0 1px rgba(0, 0, 0, 0.08), - 0px 1px 1px rgba(0, 0, 0, 0.02), - 0px 4px 8px -4px rgba(0, 0, 0, 0.04), - 0px 16px 24px -8px rgba(0, 0, 0, 0.06); -} - -.react-datepicker__header { - background: #fff; - border-bottom: 1px solid #dfdfdf; -} - -.react-datepicker__day--selected { - background-color: #efefef; - color: black; - font-weight: bold; -} - -.react-datepicker__day--selected:hover { - background-color: #ddd; - color: black; - font-weight: bold; -} - -.react-datepicker__day--keyboard-selected { - background-color: #fff; - color: black; -} - -.react-datepicker__day--keyboard-selected:hover { - background-color: #ddd; - color: black; -} diff --git a/apps/extension/src/hooks/useLocalSpaces.ts b/apps/extension/src/hooks/useLocalSpaces.ts deleted file mode 100644 index 6511149e1..000000000 --- a/apps/extension/src/hooks/useLocalSpaces.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Storage } from '@plasmohq/storage' -import { useStorage } from '@plasmohq/storage/hook' -import { atom, useAtom } from 'jotai' -import { useEffect } from 'react' - -import { db } from '@penx/local-db' -import type { ISpace } from '@penx/model-types' - -const ACTIVE_SPACE_ID = 'ACTIVE_SPACE_ID' - -const spacesAtom = atom([]) -const loadingAtom = atom(true) - -const storage = new Storage() - -export async function getActiveSpaceId() { - const spaceId = await storage.get(ACTIVE_SPACE_ID) - return spaceId -} - -export const useInitLocalSpaces = () => { - const { setSpaces, setActiveSpaceId, setLoading } = useLocalSpaces() - async function loadSpaces() { - const spaces = await db.listSpaces() - setSpaces(spaces) - - const spaceId = await storage.get(ACTIVE_SPACE_ID) - - if (spaces.length && !spaceId) { - await setActiveSpaceId(spaces[0].id) - } - - setLoading(false) - } - - // on mounted - useEffect(() => { - loadSpaces() - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) -} - -export function useLocalSpaces() { - const [spaces, setSpaces] = useAtom(spacesAtom) - const [activeSpaceId, setActiveSpaceId] = useStorage(ACTIVE_SPACE_ID) - const [loading, setLoading] = useAtom(loadingAtom) - - return { - spaces, - setSpaces, - activeSpaceId, - setActiveSpaceId, - loading, - setLoading, - } -} diff --git a/apps/extension/src/hooks/useSession.ts b/apps/extension/src/hooks/useSession.ts deleted file mode 100644 index 4aa47fd44..000000000 --- a/apps/extension/src/hooks/useSession.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { useAtom } from 'jotai' -import { atomWithStorage } from 'jotai/utils' -import ky from 'ky' -import { useEffect } from 'react' - -import type { Session, SessionContextValue } from '@penx/session' - -const KEY = 'PENX_SESSION' - -const sessionAtom = atomWithStorage(KEY, { - loading: true, - data: null as any, -}) - -export function useSession() { - const [value, setValue] = useAtom(sessionAtom) - - useEffect(() => { - // const localSession = getLocalSession() - - // if (!value.data && !localSession?.data?.accessToken) { - if (!value.data) { - ky(`${process.env.PLASMO_PUBLIC_BASE_URL}/api/auth/session`) - .json() - .then((data) => { - setValue({ - loading: false, - data: Object.keys(data).length - ? (data as Session) - : (null as Session), - }) - }) - } - }, [value.data, setValue]) - return value -} diff --git a/apps/extension/src/pages/index.tsx b/apps/extension/src/pages/index.tsx deleted file mode 100644 index 9feb235e0..000000000 --- a/apps/extension/src/pages/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -function PageIndex() { - return
Page index
-} - -export default PageIndex diff --git a/apps/extension/src/popup/index.tsx b/apps/extension/src/popup/index.tsx deleted file mode 100644 index b9564a090..000000000 --- a/apps/extension/src/popup/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react' - -import { initFower } from '@penx/app' - -import { Popup } from '~/components/popup/Popup' - -import '../components/popup/globals.module.css' - -initFower() - -function IndexPopup() { - return -} - -export default IndexPopup diff --git a/apps/extension/tsconfig.json b/apps/extension/tsconfig.json deleted file mode 100644 index 9ba44913c..000000000 --- a/apps/extension/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "plasmo/templates/tsconfig.base", - "exclude": ["node_modules"], - "include": [".plasmo/index.d.ts", "next-env.d.ts", "./**/*.ts", "./**/*.tsx"], - "compilerOptions": { - "strictPropertyInitialization": false, - "paths": { - "~/*": ["./src/*"] - }, - "baseUrl": ".", - "isolatedModules": true, - "verbatimModuleSyntax": false, - "strict": false - } -} diff --git a/apps/mobile/.env b/apps/mobile/.env deleted file mode 100644 index 7e36395c5..000000000 --- a/apps/mobile/.env +++ /dev/null @@ -1,6 +0,0 @@ - -NEXT_PUBLIC_NEXTAUTH_URL=https://develop.penx.io - -NEXT_PUBLIC_PLATFORM=DESKTOP - -NEXT_PUBLIC_DEPLOY_MODE=PLATFORM \ No newline at end of file diff --git a/apps/mobile/.eslintrc b/apps/mobile/.eslintrc deleted file mode 100644 index 15b1ed91a..000000000 --- a/apps/mobile/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next" -} diff --git a/apps/mobile/.gitignore b/apps/mobile/.gitignore deleted file mode 100644 index 9cfae31ec..000000000 --- a/apps/mobile/.gitignore +++ /dev/null @@ -1,35 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -tsconfig.tsbuildinfo - -# local env files -.env.local -.env.development.local -.env.test.local -.env.production.local - -# vercel -.vercel diff --git a/apps/mobile/.gradle/5.6.4/executionHistory/executionHistory.bin b/apps/mobile/.gradle/5.6.4/executionHistory/executionHistory.bin deleted file mode 100644 index 7c2e6cd73..000000000 Binary files a/apps/mobile/.gradle/5.6.4/executionHistory/executionHistory.bin and /dev/null differ diff --git a/apps/mobile/.gradle/5.6.4/executionHistory/executionHistory.lock b/apps/mobile/.gradle/5.6.4/executionHistory/executionHistory.lock deleted file mode 100644 index 8861c2d33..000000000 Binary files a/apps/mobile/.gradle/5.6.4/executionHistory/executionHistory.lock and /dev/null differ diff --git a/apps/mobile/.gradle/5.6.4/fileChanges/last-build.bin b/apps/mobile/.gradle/5.6.4/fileChanges/last-build.bin deleted file mode 100644 index f76dd238a..000000000 Binary files a/apps/mobile/.gradle/5.6.4/fileChanges/last-build.bin and /dev/null differ diff --git a/apps/mobile/.gradle/5.6.4/fileHashes/fileHashes.lock b/apps/mobile/.gradle/5.6.4/fileHashes/fileHashes.lock deleted file mode 100644 index 3506cd6e1..000000000 Binary files a/apps/mobile/.gradle/5.6.4/fileHashes/fileHashes.lock and /dev/null differ diff --git a/apps/mobile/.gradle/5.6.4/gc.properties b/apps/mobile/.gradle/5.6.4/gc.properties deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/mobile/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/apps/mobile/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index 2f7018f63..000000000 Binary files a/apps/mobile/.gradle/buildOutputCleanup/buildOutputCleanup.lock and /dev/null differ diff --git a/apps/mobile/.gradle/buildOutputCleanup/cache.properties b/apps/mobile/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index 3ed669ca9..000000000 --- a/apps/mobile/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Wed Jan 13 17:35:11 CST 2021 -gradle.version=5.6.4 diff --git a/apps/mobile/.npmrc b/apps/mobile/.npmrc deleted file mode 100644 index d9ca8860b..000000000 --- a/apps/mobile/.npmrc +++ /dev/null @@ -1,2 +0,0 @@ -engine-strict=true -save-exact=true diff --git a/apps/mobile/.nvmrc b/apps/mobile/.nvmrc deleted file mode 100644 index 209e3ef4b..000000000 --- a/apps/mobile/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -20 diff --git a/apps/mobile/README.md b/apps/mobile/README.md deleted file mode 100644 index f704b6ae4..000000000 --- a/apps/mobile/README.md +++ /dev/null @@ -1 +0,0 @@ -# Mobile diff --git a/apps/mobile/android/.gitignore b/apps/mobile/android/.gitignore deleted file mode 100644 index 63c86fe30..000000000 --- a/apps/mobile/android/.gitignore +++ /dev/null @@ -1,96 +0,0 @@ -# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore - -# Built application files -*.apk -*.aar -*.ap_ -*.aab - -# Files for the ART/Dalvik VM -*.dex - -# Java class files -*.class - -# Generated files -bin/ -gen/ -out/ -# Uncomment the following line in case you need and you don't have the release build type files in your app -# release/ - -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Proguard folder generated by Eclipse -proguard/ - -# Log Files -*.log - -# Android Studio Navigation editor temp files -.navigation/ - -# Android Studio captures folder -captures/ - -# IntelliJ -*.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/assetWizardSettings.xml -.idea/dictionaries -.idea/libraries -# Android Studio 3 in .gitignore file. -.idea/caches -.idea/modules.xml -# Comment next line if keeping position of elements in Navigation Editor is relevant for you -.idea/navEditor.xml - -# Keystore files -# Uncomment the following lines if you do not want to check your keystore files in. -#*.jks -#*.keystore - -# External native build folder generated in Android Studio 2.2 and later -.externalNativeBuild -.cxx/ - -# Google Services (e.g. APIs or Firebase) -# google-services.json - -# Freeline -freeline.py -freeline/ -freeline_project_description.json - -# fastlane -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots -fastlane/test_output -fastlane/readme.md - -# Version control -vcs.xml - -# lint -lint/intermediates/ -lint/generated/ -lint/outputs/ -lint/tmp/ -# lint/reports/ - -# Android Profiling -*.hprof - -# Cordova plugins for Capacitor -capacitor-cordova-android-plugins - -# Copied web assets -app/src/main/assets/public diff --git a/apps/mobile/android/app/.gitignore b/apps/mobile/android/app/.gitignore deleted file mode 100644 index 043df802a..000000000 --- a/apps/mobile/android/app/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build/* -!/build/.npmkeep diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle deleted file mode 100644 index dee2c436b..000000000 --- a/apps/mobile/android/app/build.gradle +++ /dev/null @@ -1,54 +0,0 @@ -apply plugin: 'com.android.application' - -android { - namespace "com.example.app" - compileSdkVersion rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "com.example.app" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - aaptOptions { - // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. - // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 - ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' - } - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -repositories { - flatDir{ - dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' - } -} - -dependencies { - implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" - implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" - implementation project(':capacitor-android') - testImplementation "junit:junit:$junitVersion" - androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" - androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" - implementation project(':capacitor-cordova-android-plugins') -} - -apply from: 'capacitor.build.gradle' - -try { - def servicesJSON = file('google-services.json') - if (servicesJSON.text) { - apply plugin: 'com.google.gms.google-services' - } -} catch(Exception e) { - logger.warn("google-services.json not found, google-services plugin not applied. Push Notifications won't work") -} diff --git a/apps/mobile/android/app/capacitor.build.gradle b/apps/mobile/android/app/capacitor.build.gradle deleted file mode 100644 index 52f8ca0cb..000000000 --- a/apps/mobile/android/app/capacitor.build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN - -android { - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } -} - -apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" -dependencies { - implementation project(':capacitor-status-bar') - -} - - -if (hasProperty('postBuildExtras')) { - postBuildExtras() -} diff --git a/apps/mobile/android/app/proguard-rules.pro b/apps/mobile/android/app/proguard-rules.pro deleted file mode 100644 index f1b424510..000000000 --- a/apps/mobile/android/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/apps/mobile/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java b/apps/mobile/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java deleted file mode 100644 index f2c2217ef..000000000 --- a/apps/mobile/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.getcapacitor.myapp; - -import static org.junit.Assert.*; - -import android.content.Context; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - - @Test - public void useAppContext() throws Exception { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertEquals("com.getcapacitor.app", appContext.getPackageName()); - } -} diff --git a/apps/mobile/android/app/src/main/AndroidManifest.xml b/apps/mobile/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 62bad3188..000000000 --- a/apps/mobile/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/mobile/android/app/src/main/assets/capacitor.config.json b/apps/mobile/android/app/src/main/assets/capacitor.config.json deleted file mode 100644 index 3ecb51b73..000000000 --- a/apps/mobile/android/app/src/main/assets/capacitor.config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "appId": "com.example.app", - "appName": "nextjs-tailwind-capacitor", - "bundledWebRuntime": false, - "npmClient": "npm", - "webDir": "out", - "plugins": { - "SplashScreen": { - "launchShowDuration": 0 - } - }, - "cordova": {} -} diff --git a/apps/mobile/android/app/src/main/assets/capacitor.plugins.json b/apps/mobile/android/app/src/main/assets/capacitor.plugins.json deleted file mode 100644 index 527aee454..000000000 --- a/apps/mobile/android/app/src/main/assets/capacitor.plugins.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "pkg": "@capacitor/status-bar", - "classpath": "com.capacitorjs.plugins.statusbar.StatusBarPlugin" - } -] diff --git a/apps/mobile/android/app/src/main/java/com/example/app/MainActivity.java b/apps/mobile/android/app/src/main/java/com/example/app/MainActivity.java deleted file mode 100644 index 966f32d39..000000000 --- a/apps/mobile/android/app/src/main/java/com/example/app/MainActivity.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.app; - -import com.getcapacitor.BridgeActivity; - -public class MainActivity extends BridgeActivity {} diff --git a/apps/mobile/android/app/src/main/res/drawable-land-hdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-hdpi/splash.png deleted file mode 100644 index e31573b4f..000000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-land-hdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-land-mdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-mdpi/splash.png deleted file mode 100644 index f7a64923e..000000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-land-mdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png deleted file mode 100644 index 807725501..000000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png deleted file mode 100644 index 14c6c8fe3..000000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-land-xxxhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-xxxhdpi/splash.png deleted file mode 100644 index 244ca2506..000000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-land-xxxhdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-port-hdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-port-hdpi/splash.png deleted file mode 100644 index 74faaa583..000000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-port-hdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-port-mdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-port-mdpi/splash.png deleted file mode 100644 index e944f4ad4..000000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-port-mdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-port-xhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-port-xhdpi/splash.png deleted file mode 100644 index 564a82ff9..000000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-port-xhdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-port-xxhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-port-xxhdpi/splash.png deleted file mode 100644 index bfabe6871..000000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-port-xxhdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png deleted file mode 100644 index 692907126..000000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/apps/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index c7bd21dbd..000000000 --- a/apps/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/apps/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml b/apps/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index d5fccc538..000000000 --- a/apps/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/mobile/android/app/src/main/res/drawable/splash.png b/apps/mobile/android/app/src/main/res/drawable/splash.png deleted file mode 100644 index f7a64923e..000000000 Binary files a/apps/mobile/android/app/src/main/res/drawable/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/layout/activity_main.xml b/apps/mobile/android/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index b5ad13870..000000000 --- a/apps/mobile/android/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 036d09bc5..000000000 --- a/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 036d09bc5..000000000 --- a/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index c023e5059..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 2127973b2..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index b441f37d6..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 72905b854..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 8ed0605c2..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 9502e47a2..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 4d1e07710..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index df0f15880..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 853db043d..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 6cdf97c11..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 2960cbb61..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 8e3093a86..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 46de6e255..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index d2ea9abed..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index a40d73e9c..000000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml b/apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index c5d5899fd..000000000 --- a/apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #FFFFFF - \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/values/strings.xml b/apps/mobile/android/app/src/main/res/values/strings.xml deleted file mode 100644 index 725941f8f..000000000 --- a/apps/mobile/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - nextjs-tailwind-capacitor - nextjs-tailwind-capacitor - com.example.app - com.example.app - diff --git a/apps/mobile/android/app/src/main/res/values/styles.xml b/apps/mobile/android/app/src/main/res/values/styles.xml deleted file mode 100644 index be874e54a..000000000 --- a/apps/mobile/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/xml/config.xml b/apps/mobile/android/app/src/main/res/xml/config.xml deleted file mode 100644 index 1b1b0e0dc..000000000 --- a/apps/mobile/android/app/src/main/res/xml/config.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/xml/file_paths.xml b/apps/mobile/android/app/src/main/res/xml/file_paths.xml deleted file mode 100644 index bd0c4d80d..000000000 --- a/apps/mobile/android/app/src/main/res/xml/file_paths.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/mobile/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java b/apps/mobile/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java deleted file mode 100644 index 029732784..000000000 --- a/apps/mobile/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.getcapacitor.myapp; - -import static org.junit.Assert.*; - -import org.junit.Test; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} diff --git a/apps/mobile/android/build.gradle b/apps/mobile/android/build.gradle deleted file mode 100644 index 37d63ae4f..000000000 --- a/apps/mobile/android/build.gradle +++ /dev/null @@ -1,30 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:8.0.0' - classpath 'com.google.gms:google-services:4.3.15' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -apply from: "variables.gradle" - -allprojects { - repositories { - google() - mavenCentral() - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} - diff --git a/apps/mobile/android/capacitor.settings.gradle b/apps/mobile/android/capacitor.settings.gradle deleted file mode 100644 index 8d0e32cb6..000000000 --- a/apps/mobile/android/capacitor.settings.gradle +++ /dev/null @@ -1,6 +0,0 @@ -// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN -include ':capacitor-android' -project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@5.7.2_@capacitor+core@5.7.2/node_modules/@capacitor/android/capacitor') - -include ':capacitor-status-bar' -project(':capacitor-status-bar').projectDir = new File('../../../node_modules/.pnpm/@capacitor+status-bar@5.0.7_@capacitor+core@5.7.2/node_modules/@capacitor/status-bar/android') diff --git a/apps/mobile/android/gradle.properties b/apps/mobile/android/gradle.properties deleted file mode 100644 index 92710f31d..000000000 --- a/apps/mobile/android/gradle.properties +++ /dev/null @@ -1,23 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true - diff --git a/apps/mobile/android/gradle/wrapper/gradle-wrapper.jar b/apps/mobile/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index ccebba771..000000000 Binary files a/apps/mobile/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties b/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 761b8f088..000000000 --- a/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip -networkTimeout=10000 -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/apps/mobile/android/gradlew b/apps/mobile/android/gradlew deleted file mode 100755 index 79a61d421..000000000 --- a/apps/mobile/android/gradlew +++ /dev/null @@ -1,244 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/apps/mobile/android/gradlew.bat b/apps/mobile/android/gradlew.bat deleted file mode 100644 index 6689b85be..000000000 --- a/apps/mobile/android/gradlew.bat +++ /dev/null @@ -1,92 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/apps/mobile/android/settings.gradle b/apps/mobile/android/settings.gradle deleted file mode 100644 index 3b4431d77..000000000 --- a/apps/mobile/android/settings.gradle +++ /dev/null @@ -1,5 +0,0 @@ -include ':app' -include ':capacitor-cordova-android-plugins' -project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') - -apply from: 'capacitor.settings.gradle' \ No newline at end of file diff --git a/apps/mobile/android/variables.gradle b/apps/mobile/android/variables.gradle deleted file mode 100644 index df5c0f24d..000000000 --- a/apps/mobile/android/variables.gradle +++ /dev/null @@ -1,16 +0,0 @@ -ext { - minSdkVersion = 22 - compileSdkVersion = 33 - targetSdkVersion = 33 - androidxActivityVersion = '1.7.0' - androidxAppCompatVersion = '1.6.1' - androidxCoordinatorLayoutVersion = '1.2.0' - androidxCoreVersion = '1.10.0' - androidxFragmentVersion = '1.5.6' - junitVersion = '4.13.2' - androidxJunitVersion = '1.1.5' - androidxEspressoCoreVersion = '3.5.1' - cordovaAndroidVersion = '10.1.1' - coreSplashScreenVersion = '1.0.0' - androidxWebkitVersion = '1.6.1' -} diff --git a/apps/mobile/app/[...all]/page.tsx b/apps/mobile/app/[...all]/page.tsx deleted file mode 100644 index defd15bd2..000000000 --- a/apps/mobile/app/[...all]/page.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import dynamic from 'next/dynamic'; -import { lists } from '../../mock'; - -const App = dynamic(() => import('../../components/AppShell'), { - ssr: false, -}); - -export async function generateStaticParams() { - return [ - { all: ['feed'] }, - { all: ['lists'] }, - ...lists.map(list => ({ all: ['lists', list.id] })), - { all: ['settings'] }, - ]; -} - -export default function Page() { - return ; -} diff --git a/apps/mobile/app/layout.tsx b/apps/mobile/app/layout.tsx deleted file mode 100644 index 5054d1033..000000000 --- a/apps/mobile/app/layout.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import type { Metadata, Viewport } from 'next'; -import Script from 'next/script'; - -import 'tailwindcss/tailwind.css'; -/* Core CSS required for Ionic components to work properly */ -import '@ionic/react/css/core.css'; - -/* Basic CSS for apps built with Ionic */ -import '@ionic/react/css/normalize.css'; -import '@ionic/react/css/structure.css'; -import '@ionic/react/css/typography.css'; - -/* Optional CSS utils that can be commented out */ -import '@ionic/react/css/padding.css'; -import '@ionic/react/css/float-elements.css'; -import '@ionic/react/css/text-alignment.css'; -import '@ionic/react/css/text-transformation.css'; -import '@ionic/react/css/flex-utils.css'; -import '@ionic/react/css/display.css'; - -import '@glideapps/glide-data-grid/dist/index.css'; - -import '../styles/global.css'; -import '../styles/variables.css'; - -export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -}; - -export const viewport: Viewport = { - initialScale: 1, - width: 'device-width', - viewportFit: 'cover', -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {children} -