Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: Pocket/web-client
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.145.0
Choose a base ref
...
head repository: Pocket/web-client
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Loading
Showing 978 changed files with 37,261 additions and 11,184 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -122,7 +122,7 @@ localhost.web-client.getpocket.com-key.pem
localhost.web-client.getpocket.com.pem

# Typescript
next-env.d.ts
# next-env.d.ts
schema.graphql

# turbo
6 changes: 0 additions & 6 deletions .lintstagedrc.json

This file was deleted.

31 changes: 31 additions & 0 deletions clients/extension/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
node_modules

# testing
coverage

# production
dist

# misc
.DS_Store

# lock files
yarn.lock
package-lock.json

# debug files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# extension.js
extension-env.d.ts

# keys
.env
.env.chrome
.env.firefox
.env.safari
31 changes: 31 additions & 0 deletions clients/extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Save to Pocket

> Save to Pocket from anywhere and access it when you need it wherever you are
## Available Scripts

In the project directory, you can run the following scripts:

### pnpm dev

**Development Mode**: This command runs your extension in development mode. It will launch a new browser instance with your extension loaded. The page will automatically reload whenever you make changes to your code, allowing for a smooth development experience.

```bash
pnpm dev
```

### pnpm start

**Production Preview**: This command runs your extension in production mode. It will launch a new browser instance with your extension loaded, simulating the environment and behavior of your extension as it will appear once published.

```bash
pnpm start
```

### pnpm build

**Build for Production**: This command builds your extension for production. It optimizes and bundles your extension, preparing it for deployment to the target browser's store.

```bash
pnpm build
```
143 changes: 143 additions & 0 deletions clients/extension/action/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { useEffect, useState } from 'react'
import { EXT_ACTIONS } from '../actions'
import { ActionContainer } from '../components/action-container'
import { sendMessage } from '../utilities/send-message'

import type { ExtItem, ExtMessage } from '../types'
import type { NoteEdge } from '@common/types/pocket'

export function App() {
const [isOpen, setIsOpen] = useState(false)
const [saveStatus, setSaveStatus] = useState('saving')
const [noteStatus, setNoteStatus] = useState<string | undefined>(undefined)
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)
const [item, setItem] = useState<ExtItem | undefined>(undefined)
const [notes, setNotes] = useState<NoteEdge[] | []>([])

/* Setup Listeners and show popup
–––––––––––––––––––––––––––––––––––––––––––––––––– */
useEffect(() => {
setIsOpen(true)
chrome.runtime.onMessage.addListener(handleMessages)
return () => {
setIsOpen(false)
chrome.runtime.onMessage.removeListener(handleMessages)
}
}, [])

/* Send a message on action activating
–––––––––––––––––––––––––––––––––––––––––––––––––– */
useEffect(() => {
if (isOpen) void sendMessage({ action: EXT_ACTIONS.BROWSER_ACTION })
}, [isOpen])

/* Handle incoming messages
–––––––––––––––––––––––––––––––––––––––––––––––––– */
function handleMessages(message: ExtMessage) {
const { action = 'Unknown Action' } = message || {}

switch (action) {
case EXT_ACTIONS.ADD_NOTE_SUCCESS:
case EXT_ACTIONS.SAVE_TO_POCKET_SUCCESS: {
const item = message?.item as ExtItem
setItem(item)
setNotes(item.savedItem.notes.edges)
setNoteStatus(undefined)
setSaveStatus('saved')
return
}

case EXT_ACTIONS.SAVE_TO_POCKET_FAILURE: {
const { error } = message ?? 'Unknown error'
setErrorMessage(error)
setSaveStatus('save_failed')
return
}

case EXT_ACTIONS.ADD_NOTE_FAILURE: {
const { error } = message ?? 'Unknown error'
setNoteStatus(undefined)
setErrorMessage(error)
return
}

case EXT_ACTIONS.DELETE_NOTE_SUCCESS: {
const { noteId } = message
if (noteId) {
setNoteStatus(undefined)
setNotes((notes) => {
return notes.filter((note) => note?.node?.id !== noteId)
})
}
return
}

case EXT_ACTIONS.REMOVE_ITEM_REQUEST: {
setSaveStatus('removing')
return
}

case EXT_ACTIONS.REMOVE_ITEM_SUCCESS: {
setSaveStatus('removed')
return
}

case EXT_ACTIONS.REMOVE_ITEM_FAILURE: {
setSaveStatus('remove_failed')
return
}

case EXT_ACTIONS.TAG_SYNC_REQUEST: {
setSaveStatus('tags_saving')
return
}

case EXT_ACTIONS.TAG_SYNC_SUCCESS: {
setSaveStatus('tags_saved')
return
}

case EXT_ACTIONS.TAG_SYNC_FAILURE: {
setSaveStatus('tags_failed')
return
}

case EXT_ACTIONS.UPDATE_TAG_ERROR: {
const { error } = message ?? 'Unknown error'
setErrorMessage(error)
return
}

case EXT_ACTIONS.AUTH_ERROR: {
setErrorMessage('Authorization Error')
return
}

default: {
return
}
}
}

/* Send messages
–––––––––––––––––––––––––––––––––––––––––––––––––– */
const actionUnSave = () => {}

const actionLogOut = () => {
void sendMessage({ action: EXT_ACTIONS.LOGGED_OUT_OF_POCKET })
}

return (
<ActionContainer
errorMessage={errorMessage}
saveStatus={saveStatus}
noteStatus={noteStatus}
notes={notes}
setNoteStatus={setNoteStatus}
isOpen={isOpen}
item={item}
actionUnSave={actionUnSave}
actionLogOut={actionLogOut}
/>
)
}
13 changes: 13 additions & 0 deletions clients/extension/action/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Save to Pocket</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this extension.</noscript>
<div id="root"></div>
</body>
<script src="./script.tsx"></script>
</html>
13 changes: 13 additions & 0 deletions clients/extension/action/script.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import '../public/styles/fonts.css'
import '../public/styles/global.css'
import { App } from './app'

const root = ReactDOM.createRoot(document.getElementById('root')!)

root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)
37 changes: 37 additions & 0 deletions clients/extension/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export enum EXT_ACTIONS {
AUTH_CODE_RECEIVED = 'AUTH_CODE_RECEIVED',
AUTH_ERROR = 'AUTH_ERROR',
BROWSER_ACTION = 'BROWSER_ACTION',
COLOR_MODE_CHANGE = 'COLOR_MODE_CHANGE',
LOGGED_OUT_OF_POCKET = 'LOGGED_OUT_OF_POCKET',
OPEN_OPTIONS = 'OPEN_OPTIONS',
OPEN_POCKET = 'OPEN_POCKET',
POPUP_CLOSED = 'POPUP_CLOSED',
REMOVE_ITEM = 'REMOVE_ITEM',
REMOVE_ITEM_FAILURE = 'REMOVE_ITEM_FAILURE',
REMOVE_ITEM_REQUEST = 'REMOVE_ITEM_REQUEST',
REMOVE_ITEM_SUCCESS = 'REMOVE_ITEM_SUCCESS',
RESAVE_ITEM = 'RESAVE_ITEM',
SAVE_TO_POCKET_FAILURE = 'SAVE_TO_POCKET_FAILURE',
SAVE_TO_POCKET_REQUEST = 'SAVE_TO_POCKET_REQUEST',
SAVE_TO_POCKET_SUCCESS = 'SAVE_TO_POCKET_SUCCESS',
SELECTION_ADD = 'SELECTION_ADD',
SEND_TAG_ERROR = 'SEND_TAG_ERROR',
SUGGESTED_TAGS_FAILURE = 'SUGGESTED_TAGS_FAILURE',
SUGGESTED_TAGS_SUCCESS = 'SUGGESTED_TAGS_SUCCESS',
TAG_SYNC_FAILURE = 'TAG_SYNC_FAILURE',
TAG_SYNC_REQUEST = 'TAG_SYNC_REQUEST',
TAG_SYNC_SUCCESS = 'TAG_SYNC_SUCCESS',
TAGS_SYNC = 'TAGS_SYNC',
UNKNOWN_ACTION = 'UNKNOWN_ACTION',
UPDATE_ITEM_PREVIEW = 'UPDATE_ITEM_PREVIEW',
UPDATE_STORED_TAGS = 'UPDATE_STORED_TAGS',
UPDATE_TAG_ERROR = 'UPDATE_TAG_ERROR',
USER_LOG_IN = 'USER_LOG_IN',
ADD_NOTE_REQUEST = 'ADD_NOTE_REQUEST',
ADD_NOTE_SUCCESS = 'ADD_NOTE_SUCCESS',
ADD_NOTE_FAILURE = 'ADD_NOTE_FAILURE',
DELETE_NOTE_REQUEST = 'DELETE_NOTE_REQUEST',
DELETE_NOTE_SUCCESS = 'DELETE_NOTE_SUCCESS',
DELETE_NOTE_FAILURE = 'DELETE_NOTE_FAILURE'
}
75 changes: 75 additions & 0 deletions clients/extension/background/api/add-note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { extensionRequest } from '../../utilities/request'
import { verifySession } from '../auth'
import { FRAGMENT_ITEM_PREVIEW } from './fragments/preview'
import { FRAGMENT_SAVED_ITEM } from './fragments/saved-item'

import type { ExtItem, ExtSavedItem } from '../../types'
import type { CreateNoteMarkdownInput } from '@common/types/pocket'

interface ExtCreateNoteMarkdown {
archived: boolean
contentPreview: string
createdAt: string
deleted: boolean
docMarkdown: boolean
id: string
source: string | undefined
title: string | undefined
updatedAt: string
savedItem: ExtSavedItem
}

const gql = String.raw
const query = gql`
mutation CreateNoteMarkdown($input: CreateNoteMarkdownInput!) {
createNoteMarkdown(input: $input) {
archived
contentPreview
createdAt
deleted
docMarkdown
id
source
title
updatedAt
savedItem {
...ItemSaved
item {
... on Item {
preview {
...ItemPreview
}
}
}
}
}
}
${FRAGMENT_SAVED_ITEM}
${FRAGMENT_ITEM_PREVIEW}
`

export async function addNote(input: CreateNoteMarkdownInput) {
// We need a token to save. This will create a session if need be
// be requesting a new bearer if the current one is expired
const token = await verifySession()

// Set up the variables we need for this request
const data = { variables: { input }, query }

// Make the request and return it
const response = await extensionRequest<{ createNoteMarkdown: ExtCreateNoteMarkdown }>(
data,
token
)

const responseItem = response?.createNoteMarkdown?.savedItem
const { item, ...savedItem } = responseItem
const preview = item?.preview

// We have got a pending item back from the server ... we should handle it
if (!item || !preview) throw new Error(`Trouble creating note`)

const validItem: ExtItem = { savedItem, preview }
return validItem
}
30 changes: 30 additions & 0 deletions clients/extension/background/api/fragments/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const gql = String.raw

export const FRAGMENT_ITEM_PREVIEW = gql`
fragment ItemPreview on PocketMetadata {
... on ItemSummary {
previewId: id
id
image {
caption
credit
url
cachedImages(imageOptions: [{ width: 80, id: "ext", height: 80, fileType: PNG }]) {
url
id
}
}
excerpt
title
authors {
name
}
domain {
name
}
datePublished
url
source
}
}
`
Loading