Landing page improvements and various supporting improvements
9
.github/workflows/run-tests.yaml
vendored
@ -11,10 +11,16 @@ on:
|
|||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'apple/**'
|
- 'apple/**'
|
||||||
|
|
||||||
|
env:
|
||||||
|
NEXT_PUBLIC_APP_ENV: prod
|
||||||
|
NEXT_PUBLIC_BASE_URL: http://localhost:3000
|
||||||
|
NEXT_PUBLIC_SERVER_BASE_URL: http://localhost:4000
|
||||||
|
NEXT_PUBLIC_HIGHLIGHTS_BASE_URL: http://localhost:3000
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-code-tests:
|
run-code-tests:
|
||||||
name: Run Codebase tests
|
name: Run Codebase tests
|
||||||
runs-on: ubuntu-latest-m
|
runs-on: ${{ github.repository_owner == 'omnivore-app' && 'ubuntu-latest-m' || 'ubuntu-latest' }}
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: ankane/pgvector
|
image: ankane/pgvector
|
||||||
@ -71,6 +77,7 @@ jobs:
|
|||||||
- name: TypeScript Build and Lint
|
- name: TypeScript Build and Lint
|
||||||
run: |
|
run: |
|
||||||
source ~/.nvm/nvm.sh
|
source ~/.nvm/nvm.sh
|
||||||
|
env
|
||||||
yarn build
|
yarn build
|
||||||
yarn lint
|
yarn lint
|
||||||
- name: Tests
|
- name: Tests
|
||||||
|
|||||||
2
.gitignore
vendored
@ -69,3 +69,5 @@ data.json
|
|||||||
# android
|
# android
|
||||||
*.aab
|
*.aab
|
||||||
*.apk
|
*.apk
|
||||||
|
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
|||||||
@ -10,5 +10,8 @@
|
|||||||
"express": "^4.18.1",
|
"express": "^4.18.1",
|
||||||
"express-graphql": "^0.12.0",
|
"express-graphql": "^0.12.0",
|
||||||
"graphql": "^16.8.1"
|
"graphql": "^16.8.1"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
package.json
@ -6,16 +6,16 @@
|
|||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"license": "UNLICENSED",
|
"license": "AGPL-3.0-only",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "lerna run --no-bail test --ignore @omnivore/web",
|
"test": "lerna run --no-bail test",
|
||||||
"lint": "lerna run lint --ignore @omnivore/web",
|
"lint": "lerna run lint",
|
||||||
"build": "lerna run build --ignore @omnivore/web",
|
"build": "lerna run build",
|
||||||
"bootstrap": "lerna bootstrap",
|
|
||||||
"test:scoped:example": "lerna run test --scope={@omnivore/pdf-handler,@omnivore/web}",
|
"test:scoped:example": "lerna run test --scope={@omnivore/pdf-handler,@omnivore/web}",
|
||||||
"gql-typegen": "graphql-codegen",
|
"gql-typegen": "graphql-codegen",
|
||||||
"deploy:web": "vercel --prod"
|
"deploy:web": "vercel --prod"
|
||||||
},
|
},
|
||||||
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ardatan/aggregate-error": "^0.0.6",
|
"@ardatan/aggregate-error": "^0.0.6",
|
||||||
"@graphql-codegen/cli": "^2.6.2",
|
"@graphql-codegen/cli": "^2.6.2",
|
||||||
@ -33,11 +33,10 @@
|
|||||||
"graphql-tag": "^2.11.0",
|
"graphql-tag": "^2.11.0",
|
||||||
"lerna": "^7.4.1",
|
"lerna": "^7.4.1",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"typescript": "^4.4.3"
|
"typescript": "4.5.2"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "18.16.1",
|
"node": "18.16.1",
|
||||||
"yarn": "1.22.19"
|
"yarn": "1.22.19"
|
||||||
},
|
}
|
||||||
"dependencies": {}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
"start": "node dist/server.js",
|
"start": "node dist/server.js",
|
||||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||||
"lint:fix": "eslint src --fix --ext ts,js,tsx,jsx",
|
"lint:fix": "eslint src --fix --ext ts,js,tsx,jsx",
|
||||||
|
"test:typecheck": "tsc --noEmit",
|
||||||
"test": "nyc mocha -r ts-node/register --config mocha-config.json --timeout 10000",
|
"test": "nyc mocha -r ts-node/register --config mocha-config.json --timeout 10000",
|
||||||
"copy-files": "copyfiles -u 1 src/**/*.html dist/"
|
"copy-files": "copyfiles -u 1 src/**/*.html dist/"
|
||||||
},
|
},
|
||||||
@ -145,7 +146,6 @@
|
|||||||
"node": "18.16.1"
|
"node": "18.16.1"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "18.16.1",
|
"extends": "../../package.json"
|
||||||
"yarn": "1.22.19"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,6 @@
|
|||||||
"webpack-dev-server": "^4.7.4"
|
"webpack-dev-server": "^4.7.4"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "18.16.0",
|
"extends": "../../package.json"
|
||||||
"yarn": "1.22.10"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,6 +60,11 @@ const config: Configuration = {
|
|||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.tsx', '.ts', '.js'],
|
extensions: ['.tsx', '.ts', '.js'],
|
||||||
|
fallback: {
|
||||||
|
stream: false,
|
||||||
|
fs: false,
|
||||||
|
zlib: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'build'),
|
path: path.resolve(__dirname, 'build'),
|
||||||
|
|||||||
@ -20,5 +20,8 @@
|
|||||||
"start_gcf": "npx functions-framework --port=9090 --target=puppeteer",
|
"start_gcf": "npx functions-framework --port=9090 --target=puppeteer",
|
||||||
"start_preview": "npx functions-framework --target=preview",
|
"start_preview": "npx functions-framework --target=preview",
|
||||||
"test": "mocha test/*.js"
|
"test": "mocha test/*.js"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||||
|
"test:typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"build": "tsc"
|
"build": "tsc"
|
||||||
@ -38,5 +39,8 @@
|
|||||||
"redis": "^4.3.1",
|
"redis": "^4.3.1",
|
||||||
"underscore": "^1.13.6",
|
"underscore": "^1.13.6",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -228,7 +228,7 @@ const getTweetIds = async (
|
|||||||
timeout: 60000, // 60 seconds
|
timeout: 60000, // 60 seconds
|
||||||
})
|
})
|
||||||
|
|
||||||
return (await page.evaluate(async (author) => {
|
return await page.evaluate(async (author) => {
|
||||||
/**
|
/**
|
||||||
* Wait for `ms` amount of milliseconds
|
* Wait for `ms` amount of milliseconds
|
||||||
* @param {number} ms
|
* @param {number} ms
|
||||||
@ -278,7 +278,7 @@ const getTweetIds = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return ids
|
return ids
|
||||||
}, author)) as string[]
|
}, author)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting tweets', error)
|
console.error('Error getting tweets', error)
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,6 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "18.16.1",
|
"extends": "../../package.json"
|
||||||
"yarn": "1.22.10"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"migrate": "ts-node ./migrate.ts",
|
"migrate": "ts-node ./migrate.ts",
|
||||||
|
"test:typecheck": "tsc --noEmit",
|
||||||
"generate": "plop"
|
"generate": "plop"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||||
|
"test:typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"build": "tsc && yarn copy-files",
|
"build": "tsc && yarn copy-files",
|
||||||
@ -52,5 +53,8 @@
|
|||||||
"unzip-stream": "^0.3.1",
|
"unzip-stream": "^0.3.1",
|
||||||
"urlsafe-base64": "^1.0.0",
|
"urlsafe-base64": "^1.0.0",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
"keywords": [],
|
"keywords": [],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||||
|
"test:typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
@ -42,5 +43,8 @@
|
|||||||
"parse-multipart-data": "^1.2.1",
|
"parse-multipart-data": "^1.2.1",
|
||||||
"rfc2047": "^4.0.1",
|
"rfc2047": "^4.0.1",
|
||||||
"showdown": "^2.1.0"
|
"showdown": "^2.1.0"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
"keywords": [],
|
"keywords": [],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||||
|
"test:typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
@ -33,5 +34,8 @@
|
|||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"concurrently": "^7.0.0",
|
"concurrently": "^7.0.0",
|
||||||
"pdfjs-dist": "^2.9.359"
|
"pdfjs-dist": "^2.9.359"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,5 +25,8 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha test/*.js"
|
"test": "mocha test/*.js"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||||
|
"test:typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
@ -28,5 +29,8 @@
|
|||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"jsonwebtoken": "^8.5.1"
|
"jsonwebtoken": "^8.5.1"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,5 +41,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"html-entities": "^2.3.2",
|
"html-entities": "^2.3.2",
|
||||||
"parse-srcset": "^1.0.2"
|
"parse-srcset": "^1.0.2"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||||
|
"test:typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
@ -28,5 +29,8 @@
|
|||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"rss-parser": "^3.13.0"
|
"rss-parser": "^3.13.0"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||||
|
"test:typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
@ -26,5 +27,8 @@
|
|||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"search-query-parser": "^1.6.0"
|
"search-query-parser": "^1.6.0"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
"keywords": [],
|
"keywords": [],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||||
|
"test:typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
@ -45,5 +46,8 @@
|
|||||||
"natural": "^6.2.0",
|
"natural": "^6.2.0",
|
||||||
"redis": "^4.3.1",
|
"redis": "^4.3.1",
|
||||||
"underscore": "^1.13.4"
|
"underscore": "^1.13.4"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
"test": "yarn mocha -r ts-node/register --config mocha-config.json",
|
||||||
|
"test:typecheck": "tsc --noEmit",
|
||||||
"lint": "eslint src --ext ts,js,tsx,jsx",
|
"lint": "eslint src --ext ts,js,tsx,jsx",
|
||||||
"compile": "tsc",
|
"compile": "tsc",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
@ -30,5 +31,8 @@
|
|||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"linkedom": "^0.14.26",
|
"linkedom": "^0.14.26",
|
||||||
"urlsafe-base64": "^1.0.0"
|
"urlsafe-base64": "^1.0.0"
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"extends": "../../package.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,11 +28,9 @@ export const Button = styled('button', {
|
|||||||
color: '#3D3D3D',
|
color: '#3D3D3D',
|
||||||
bg: '#FFEA9F',
|
bg: '#FFEA9F',
|
||||||
p: '10px 15px',
|
p: '10px 15px',
|
||||||
'&:hover': {
|
'&:hover, &:focus': {
|
||||||
bg: '$omnivoreCtaYellow',
|
bg: '$omnivoreCtaYellow',
|
||||||
},
|
outline: '1px solid $omnivoreCtaYellow',
|
||||||
'&:focus': {
|
|
||||||
border: '1px solid $omnivoreCtaYellow',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cancelGeneric: {
|
cancelGeneric: {
|
||||||
@ -45,18 +43,13 @@ export const Button = styled('button', {
|
|||||||
border: '1px solid transparent',
|
border: '1px solid transparent',
|
||||||
p: '10px 15px',
|
p: '10px 15px',
|
||||||
bg: 'transparent',
|
bg: 'transparent',
|
||||||
'&:hover': {
|
'&:hover, &:focus': {
|
||||||
bg: '#EBEBEB',
|
bg: '#EBEBEB',
|
||||||
},
|
outline: '1px solid $omnivoreCtaYellow',
|
||||||
'&:focus': {
|
|
||||||
outline: 'none !important',
|
|
||||||
border: '1px solid $omnivoreCtaYellow',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ctaOutlineYellow: {
|
ctaOutlineYellow: {
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
'-moz-box-sizing': 'border-box',
|
|
||||||
'-webkit-box-sizing': 'border-box',
|
|
||||||
borderColor: 'unset',
|
borderColor: 'unset',
|
||||||
border: '1px solid $omnivoreCtaYellow',
|
border: '1px solid $omnivoreCtaYellow',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
@ -159,7 +152,6 @@ export const Button = styled('button', {
|
|||||||
outlineColor: 'rgba(0, 0, 0, 0)',
|
outlineColor: 'rgba(0, 0, 0, 0)',
|
||||||
border: '1px solid rgba(0, 0, 0, 0.06)',
|
border: '1px solid rgba(0, 0, 0, 0.06)',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
'&:focus': { outline: 'none' },
|
|
||||||
},
|
},
|
||||||
ctaModal: {
|
ctaModal: {
|
||||||
height: '32px',
|
height: '32px',
|
||||||
@ -172,7 +164,6 @@ export const Button = styled('button', {
|
|||||||
border: '1px solid $grayBorder',
|
border: '1px solid $grayBorder',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
'&:focus': { outline: 'none' },
|
|
||||||
},
|
},
|
||||||
ctaSecondary: {
|
ctaSecondary: {
|
||||||
color: '$grayText',
|
color: '$grayText',
|
||||||
@ -245,7 +236,6 @@ export const Button = styled('button', {
|
|||||||
'&:hover': {
|
'&:hover': {
|
||||||
opacity: 0.7,
|
opacity: 0.7,
|
||||||
},
|
},
|
||||||
'&:focus': { outline: 'none' },
|
|
||||||
},
|
},
|
||||||
highlightBarIcon: {
|
highlightBarIcon: {
|
||||||
p: '0px',
|
p: '0px',
|
||||||
@ -257,7 +247,6 @@ export const Button = styled('button', {
|
|||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
},
|
},
|
||||||
|
|
||||||
'&:focus': { outline: 'none' },
|
|
||||||
},
|
},
|
||||||
articleActionIcon: {
|
articleActionIcon: {
|
||||||
bg: 'transparent',
|
bg: 'transparent',
|
||||||
|
|||||||
@ -11,37 +11,35 @@ type HeaderNavLinkProps = {
|
|||||||
|
|
||||||
export function HeaderNavLink(props: HeaderNavLinkProps): JSX.Element {
|
export function HeaderNavLink(props: HeaderNavLinkProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Link passHref href={props.href}>
|
<Link passHref href={props.href} style={{ textDecoration: 'none' }}>
|
||||||
<a style={{ textDecoration: 'none' }}>
|
<VStack
|
||||||
<VStack
|
alignment="center"
|
||||||
alignment="center"
|
distribution="center"
|
||||||
distribution="center"
|
css={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
px: '$3',
|
||||||
|
color: 'inherit',
|
||||||
|
textDecoration: 'inherit',
|
||||||
|
fontFamily: 'inherit',
|
||||||
|
fontSize: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledText style="navLink">{props.text}</StyledText>
|
||||||
|
<Box
|
||||||
css={{
|
css={{
|
||||||
cursor: 'pointer',
|
width: '100%',
|
||||||
px: '$3',
|
bg: 'rgb(255, 210, 52)',
|
||||||
color: 'inherit',
|
height: '2px',
|
||||||
textDecoration: 'inherit',
|
opacity: props.isActive ? 1 : 0,
|
||||||
fontFamily: 'inherit',
|
display: props.isActive ? 'unset' : 'none',
|
||||||
fontSize: '100%',
|
mt: '4px',
|
||||||
|
animation: `${expandWidthAnim('0%', '100%')} 0.2s ease-out`,
|
||||||
|
'&:hover': {
|
||||||
|
opacity: props.isActive ? 0.7 : 0,
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<StyledText style="navLink">{props.text}</StyledText>
|
</VStack>
|
||||||
<Box
|
|
||||||
css={{
|
|
||||||
width: '100%',
|
|
||||||
bg: 'rgb(255, 210, 52)',
|
|
||||||
height: '2px',
|
|
||||||
opacity: props.isActive ? 1 : 0,
|
|
||||||
display: props.isActive ? 'unset' : 'none',
|
|
||||||
mt: '4px',
|
|
||||||
animation: `${expandWidthAnim('0%', '100%')} 0.2s ease-out`,
|
|
||||||
'&:hover': {
|
|
||||||
opacity: props.isActive ? 0.7 : 0,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</VStack>
|
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useRef, useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import { styled } from '../tokens/stitches.config'
|
import { styled } from '../tokens/stitches.config'
|
||||||
import { Box, HStack } from './LayoutPrimitives'
|
import { Box, HStack } from './LayoutPrimitives'
|
||||||
import { StyledText } from './StyledText'
|
import { StyledText } from './StyledText'
|
||||||
@ -6,9 +6,14 @@ import {
|
|||||||
LabelColorDropdownProps,
|
LabelColorDropdownProps,
|
||||||
LabelOptionProps,
|
LabelOptionProps,
|
||||||
} from '../../utils/settings-page/labels/types'
|
} from '../../utils/settings-page/labels/types'
|
||||||
import { TwitterPicker } from 'react-color'
|
import { TwitterPicker as TwitterPicker_, TwitterPickerProps } from 'react-color'
|
||||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
||||||
|
|
||||||
|
// TwitterPicker is a Class component, but the types are broken in React 18.
|
||||||
|
// TODO: Maybe move away from this component, since it hasn't been updated for 3 years.
|
||||||
|
// https://github.com/casesandberg/react-color/issues/883
|
||||||
|
const TwitterPicker = TwitterPicker_ as unknown as React.FunctionComponent<TwitterPickerProps>
|
||||||
|
|
||||||
const DropdownMenuContent = styled(DropdownMenuPrimitive.Content, {
|
const DropdownMenuContent = styled(DropdownMenuPrimitive.Content, {
|
||||||
borderRadius: 6,
|
borderRadius: 6,
|
||||||
backgroundColor: '$grayBg',
|
backgroundColor: '$grayBg',
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import AutosizeInput from 'react-input-autosize'
|
import AutosizeInput_, { AutosizeInputProps } from 'react-input-autosize'
|
||||||
import { Box, SpanBox } from './LayoutPrimitives'
|
import { Box, SpanBox } from './LayoutPrimitives'
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { Label } from '../../lib/networking/fragments/labelFragment'
|
import { Label } from '../../lib/networking/fragments/labelFragment'
|
||||||
@ -8,6 +8,11 @@ import { EditLabelChip } from './EditLabelChip'
|
|||||||
import { LabelsDispatcher } from '../../lib/hooks/useSetPageLabels'
|
import { LabelsDispatcher } from '../../lib/hooks/useSetPageLabels'
|
||||||
import { EditLabelChipStack } from './EditLabelChipStack'
|
import { EditLabelChipStack } from './EditLabelChipStack'
|
||||||
|
|
||||||
|
// AutosizeInput is a Class component, but the types are broken in React 18.
|
||||||
|
// TODO: Maybe move away from this component, since it hasn't been updated for 3 years.
|
||||||
|
// https://github.com/JedWatson/react-input-autosize/issues
|
||||||
|
const AutosizeInput = AutosizeInput_ as unknown as React.FunctionComponent<AutosizeInputProps>
|
||||||
|
|
||||||
const MaxUnstackedLabels = 7
|
const MaxUnstackedLabels = 7
|
||||||
|
|
||||||
type LabelsPickerProps = {
|
type LabelsPickerProps = {
|
||||||
|
|||||||
@ -40,14 +40,14 @@ const InternalOrExternalLink = (props: InternalOrExternalLinkProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!isExternal ? (
|
{!isExternal ? (
|
||||||
<Link href={props.link}>{props.children}</Link>
|
<Link href={props.link} legacyBehavior>{props.children}</Link>
|
||||||
) : (
|
) : (
|
||||||
<a href={props.link} target="_blank" rel="noreferrer">
|
<a href={props.link} target="_blank" rel="noreferrer">
|
||||||
{props.children}
|
{props.children}
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
</SpanBox>
|
</SpanBox>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SuggestionBox = (props: SuggestionBoxProps) => {
|
export const SuggestionBox = (props: SuggestionBoxProps) => {
|
||||||
|
|||||||
@ -53,6 +53,7 @@ export const TooltipContent = StyledContent
|
|||||||
export const TooltipArrow = StyledArrow
|
export const TooltipArrow = StyledArrow
|
||||||
|
|
||||||
type TooltipWrappedProps = {
|
type TooltipWrappedProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
tooltipContent: string;
|
tooltipContent: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
tooltipSide?: TooltipPrimitive.TooltipContentProps['side']
|
tooltipSide?: TooltipPrimitive.TooltipContentProps['side']
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
|
|
||||||
export function ChromeIcon(): JSX.Element {
|
export function ChromeIcon(): JSX.Element {
|
||||||
return <Image src="/static/icons/chrome@2x.png" width="24" height="24" />
|
return <Image src="/static/icons/chrome@2x.png" width="24" height="24" alt="" />
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
|
|
||||||
export function EdgeIcon(): JSX.Element {
|
export function EdgeIcon(): JSX.Element {
|
||||||
return <Image src="/static/icons/edge@2x.png" width="24" height="24" />
|
return <Image src="/static/icons/edge@2x.png" width="24" height="24" alt="" />
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
|
|
||||||
export function FirefoxIcon(): JSX.Element {
|
export function FirefoxIcon(): JSX.Element {
|
||||||
return <Image src="/static/icons/firefox@2x.png" width="24" height="24" />
|
return <Image src="/static/icons/firefox@2x.png" width="24" height="24" alt="" />
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,20 +14,22 @@ export function OmnivoreFestiveLogo(
|
|||||||
const href = props.href || '/home'
|
const href = props.href || '/home'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link passHref href={href}>
|
(<Link
|
||||||
<a
|
passHref
|
||||||
style={{
|
href={href}
|
||||||
textDecoration: 'none',
|
style={{
|
||||||
display: 'flex',
|
textDecoration: 'none',
|
||||||
alignItems: 'center',
|
display: 'flex',
|
||||||
}}
|
alignItems: 'center',
|
||||||
>
|
}}>
|
||||||
<Image
|
|
||||||
src="/static/images/omnivore-logo-santa.png"
|
<Image
|
||||||
width="27"
|
src="/static/images/omnivore-logo-santa.png"
|
||||||
height="27"
|
width="27"
|
||||||
/>
|
height="27"
|
||||||
</a>
|
alt=""
|
||||||
</Link>
|
/>
|
||||||
)
|
|
||||||
|
</Link>)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { ReactChildren } from 'react'
|
|
||||||
import { config } from '../../tokens/stitches.config'
|
|
||||||
|
|
||||||
export type OmnivoreLogoBaseProps = {
|
export type OmnivoreLogoBaseProps = {
|
||||||
color?: string
|
color?: string
|
||||||
href?: string
|
href?: string
|
||||||
@ -15,23 +12,25 @@ export function OmnivoreLogoBase(props: OmnivoreLogoBaseProps): JSX.Element {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link passHref href={href}>
|
<Link
|
||||||
<a
|
passHref
|
||||||
style={{
|
href={href}
|
||||||
textDecoration: 'none',
|
style={{
|
||||||
display: 'flex',
|
textDecoration: 'none',
|
||||||
alignItems: 'center',
|
display: 'flex',
|
||||||
}}
|
alignItems: 'center',
|
||||||
onClick={(event) => {
|
}}
|
||||||
const query = window.sessionStorage.getItem('q')
|
onClick={(event) => {
|
||||||
if (query) {
|
const query = window.sessionStorage.getItem('q')
|
||||||
router.push(`/home?${query}`)
|
if (query) {
|
||||||
event.preventDefault()
|
router.push(`/home?${query}`)
|
||||||
}
|
event.preventDefault()
|
||||||
}}
|
}
|
||||||
>
|
}}
|
||||||
{props.children}
|
tabIndex={-1}
|
||||||
</a>
|
aria-label="Omnivore logo"
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
|
|
||||||
export function SafariIcon(): JSX.Element {
|
export function SafariIcon(): JSX.Element {
|
||||||
return <Image src="/static/icons/safari@2x.png" width="24" height="24" />
|
return <Image src="/static/icons/safari@2x.png" width="24" height="24" alt="" />
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,7 +40,7 @@ type NoteSectionProps = {
|
|||||||
|
|
||||||
export function ArticleNotes(props: NoteSectionProps): JSX.Element {
|
export function ArticleNotes(props: NoteSectionProps): JSX.Element {
|
||||||
const saveText = useCallback(
|
const saveText = useCallback(
|
||||||
(text) => {
|
(text: string) => {
|
||||||
props.saveText(text)
|
props.saveText(text)
|
||||||
},
|
},
|
||||||
[props]
|
[props]
|
||||||
@ -78,7 +78,7 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element {
|
|||||||
const [lastSaved, setLastSaved] = useState<Date | undefined>(undefined)
|
const [lastSaved, setLastSaved] = useState<Date | undefined>(undefined)
|
||||||
|
|
||||||
const saveText = useCallback(
|
const saveText = useCallback(
|
||||||
(text) => {
|
(text: string) => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
const success = await updateHighlightMutation({
|
const success = await updateHighlightMutation({
|
||||||
annotation: text,
|
annotation: text,
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export function HighlightViewNote(props: HighlightViewNoteProps): JSX.Element {
|
|||||||
const [errorSaving, setErrorSaving] = useState<string | undefined>(undefined)
|
const [errorSaving, setErrorSaving] = useState<string | undefined>(undefined)
|
||||||
|
|
||||||
const saveText = useCallback(
|
const saveText = useCallback(
|
||||||
(text, updateTime, interactive) => {
|
(text: string, updateTime: Date, interactive: boolean) => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
const success = await updateHighlightMutation({
|
const success = await updateHighlightMutation({
|
||||||
annotation: text,
|
annotation: text,
|
||||||
|
|||||||
@ -30,10 +30,10 @@ export function LibraryHighlightGridCard(
|
|||||||
props: LibraryHighlightGridCardProps
|
props: LibraryHighlightGridCardProps
|
||||||
): JSX.Element {
|
): JSX.Element {
|
||||||
const [expanded, setExpanded] = useState(false)
|
const [expanded, setExpanded] = useState(false)
|
||||||
const higlightCount = props.item.highlights?.length ?? 0
|
const highlightCount = props.item.highlights?.length ?? 0
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const viewInReader = useCallback(
|
const viewInReader = useCallback(
|
||||||
(highlightId) => {
|
(highlightId: string) => {
|
||||||
if (!router || !router.isReady || !props.viewer) {
|
if (!router || !router.isReady || !props.viewer) {
|
||||||
showErrorToast('Error navigating to highlight')
|
showErrorToast('Error navigating to highlight')
|
||||||
return
|
return
|
||||||
@ -197,7 +197,7 @@ export function LibraryHighlightGridCard(
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{`View ${higlightCount} highlight${higlightCount > 1 ? 's' : ''}`}
|
{`View ${highlightCount} highlight${highlightCount > 1 ? 's' : ''}`}
|
||||||
<CaretDown
|
<CaretDown
|
||||||
size={10}
|
size={10}
|
||||||
weight="bold"
|
weight="bold"
|
||||||
|
|||||||
@ -33,13 +33,14 @@ export function About(props: AboutProps): JSX.Element {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
|
as="p"
|
||||||
css={{
|
css={{
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
color: '#3D3D3D',
|
color: '#3D3D3D',
|
||||||
fontSize: 45,
|
fontSize: 45,
|
||||||
lineHeight: '53px',
|
lineHeight: '53px',
|
||||||
padding: '10px',
|
padding: '10px 10px 0',
|
||||||
paddingBottom: '0px',
|
margin: 0,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -49,9 +50,11 @@ export function About(props: AboutProps): JSX.Element {
|
|||||||
readers.`}
|
readers.`}
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
|
as="p"
|
||||||
css={{
|
css={{
|
||||||
color: 'rgb(125, 125, 125)',
|
color: 'rgb(125, 125, 125)',
|
||||||
padding: '10px',
|
padding: '10px',
|
||||||
|
margin: 0,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
@ -64,9 +67,11 @@ export function About(props: AboutProps): JSX.Element {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
|
as="p"
|
||||||
css={{
|
css={{
|
||||||
color: 'rgb(125, 125, 125)',
|
color: 'rgb(125, 125, 125)',
|
||||||
padding: '10px',
|
padding: '10px',
|
||||||
|
margin: 0,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -32,11 +32,11 @@ export function ErrorLayout(props: ErrorLayoutProps): JSX.Element {
|
|||||||
</StyledText>
|
</StyledText>
|
||||||
</HStack>
|
</HStack>
|
||||||
<SpanBox css={{ height: '64px' }} />
|
<SpanBox css={{ height: '64px' }} />
|
||||||
<Link passHref href={viewerData?.me ? '/home' : '/login'}>
|
<Link passHref href={viewerData?.me ? '/home' : '/login'} legacyBehavior>
|
||||||
<Button style="ctaDarkYellow">
|
<Button style="ctaDarkYellow">
|
||||||
{viewerData?.me ? 'Go Home' : 'Login'}
|
{viewerData?.me ? 'Go Home' : 'Login'}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</VStack>
|
</VStack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,21 +50,21 @@ export function LoginForm(props: LoginFormProps): JSX.Element {
|
|||||||
>
|
>
|
||||||
Save articles and read them later in our distraction-free reader.
|
Save articles and read them later in our distraction-free reader.
|
||||||
</StyledText>
|
</StyledText>
|
||||||
<Link passHref href="/about">
|
<Link passHref href="/about" style={{ textDecoration: 'none' }}>
|
||||||
<a style={{ textDecoration: 'none' }}>
|
|
||||||
<StyledText
|
<StyledText
|
||||||
css={{
|
css={{
|
||||||
fontStyle: 'normal',
|
fontStyle: 'normal',
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
fontSize: '18px',
|
fontSize: '18px',
|
||||||
lineHeight: '120%',
|
lineHeight: '120%',
|
||||||
m: '0px',
|
m: '0px',
|
||||||
color: '$omnivoreGray',
|
color: '$omnivoreGray',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Learn More ->
|
Learn More ->
|
||||||
</StyledText>
|
</StyledText>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<SpanBox css={{ height: '24px' }} />
|
<SpanBox css={{ height: '24px' }} />
|
||||||
@ -103,7 +103,7 @@ export function LoginForm(props: LoginFormProps): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Link href="/auth/email-login" passHref>
|
<Link href="/auth/email-login" passHref legacyBehavior>
|
||||||
<StyledTextSpan
|
<StyledTextSpan
|
||||||
style="actionLink"
|
style="actionLink"
|
||||||
css={{ color: '$omnivoreGray', pt: '12px' }}
|
css={{ color: '$omnivoreGray', pt: '12px' }}
|
||||||
@ -114,7 +114,7 @@ export function LoginForm(props: LoginFormProps): JSX.Element {
|
|||||||
</VStack>
|
</VStack>
|
||||||
<TermAndConditionsFooter />
|
<TermAndConditionsFooter />
|
||||||
</VStack>
|
</VStack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function GoogleAuthButton() {
|
function GoogleAuthButton() {
|
||||||
@ -154,17 +154,17 @@ export function TermAndConditionsFooter(): JSX.Element {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
By signing up, you agree to Omnivore’s{' '}
|
By signing up, you agree to Omnivore’s{' '}
|
||||||
<Link href="/terms" passHref>
|
<Link href="/terms" passHref legacyBehavior>
|
||||||
<StyledTextSpan style="captionLink" css={{ color: '$omnivoreGray' }}>
|
<StyledTextSpan style="captionLink" css={{ color: '$omnivoreGray' }}>
|
||||||
Terms of Service
|
Terms of Service
|
||||||
</StyledTextSpan>
|
</StyledTextSpan>
|
||||||
</Link>{' '}
|
</Link>{' '}
|
||||||
and{' '}
|
and{' '}
|
||||||
<Link href="/privacy" passHref>
|
<Link href="/privacy" passHref legacyBehavior>
|
||||||
<StyledTextSpan style="captionLink" css={{ color: '$omnivoreGray' }}>
|
<StyledTextSpan style="captionLink" css={{ color: '$omnivoreGray' }}>
|
||||||
Privacy Policy
|
Privacy Policy
|
||||||
</StyledTextSpan>
|
</StyledTextSpan>
|
||||||
</Link>
|
</Link>
|
||||||
</StyledText>
|
</StyledText>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { unstable_getImgProps as getImgProps } from 'next/image'
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
HStack,
|
HStack,
|
||||||
@ -9,6 +10,9 @@ import type { LoginFormProps } from './LoginForm'
|
|||||||
import { OmnivoreNameLogo } from '../elements/images/OmnivoreNameLogo'
|
import { OmnivoreNameLogo } from '../elements/images/OmnivoreNameLogo'
|
||||||
import { theme } from '../tokens/stitches.config'
|
import { theme } from '../tokens/stitches.config'
|
||||||
|
|
||||||
|
import featureFullWidthImage from '../../public/static/images/login/login-feature-image-full.png'
|
||||||
|
import featureHalfWidthImage from '../../public/static/images/login/login-feature-image-half.png'
|
||||||
|
|
||||||
export function LoginLayout(props: LoginFormProps): JSX.Element {
|
export function LoginLayout(props: LoginFormProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -86,7 +90,30 @@ function MediumLoginLayout(props: LoginFormProps) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const srcSetToImageSet = (srcFallback: string, srcSet?: string): string => {
|
||||||
|
if (!srcSet) return `url(${srcFallback})`
|
||||||
|
|
||||||
|
return `image-set( ${srcSet
|
||||||
|
.split(', ')
|
||||||
|
.map((subSrc) => {
|
||||||
|
const [src, resolution] = subSrc.split(' ')
|
||||||
|
return `url("${decodeURIComponent(src)}") ${resolution}`
|
||||||
|
})
|
||||||
|
.join(',')}
|
||||||
|
)`
|
||||||
|
}
|
||||||
|
|
||||||
function OmnivoreIllustration() {
|
function OmnivoreIllustration() {
|
||||||
|
const { props: halfWidthImgProps } = getImgProps({
|
||||||
|
src: featureHalfWidthImage,
|
||||||
|
alt: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const { props: fullWidthImgProps } = getImgProps({
|
||||||
|
src: featureFullWidthImage,
|
||||||
|
alt: '',
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
css={{
|
css={{
|
||||||
@ -95,14 +122,12 @@ function OmnivoreIllustration() {
|
|||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
backgroundRepeat: 'no-repeat',
|
backgroundRepeat: 'no-repeat',
|
||||||
backgroundImage: `-webkit-image-set(
|
backgroundPosition: 'left',
|
||||||
url('/static/images/landingPage-feature@1x.png') 1x,
|
backgroundImage: srcSetToImageSet(halfWidthImgProps.src, halfWidthImgProps.srcSet),
|
||||||
url('/static/landing/landingPage-feature@2x.png') 2x
|
|
||||||
)`,
|
'@media (min-aspect-ratio: 2/1)': {
|
||||||
'background-image': `image-set(
|
backgroundImage: srcSetToImageSet(fullWidthImgProps.src, fullWidthImgProps.srcSet),
|
||||||
url('/static/images/landingPage-feature@1x.png') 1x,
|
},
|
||||||
url('/static/landing/landingPage-feature@2x.png') 2x
|
|
||||||
)`,
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -204,7 +204,7 @@ function SettingsButton(props: SettingsButtonProps): JSX.Element {
|
|||||||
}, [props, router])
|
}, [props, router])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={props.destination} passHref title={props.name}>
|
<Link href={props.destination} passHref title={props.name} legacyBehavior>
|
||||||
<SpanBox
|
<SpanBox
|
||||||
css={{
|
css={{
|
||||||
mx: '10px',
|
mx: '10px',
|
||||||
@ -249,5 +249,5 @@ function SettingsButton(props: SettingsButtonProps): JSX.Element {
|
|||||||
{props.name}
|
{props.name}
|
||||||
</SpanBox>
|
</SpanBox>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,7 +110,7 @@ export function UploadModal(props: UploadModalProps): JSX.Element {
|
|||||||
const dropzoneRef = useRef<DropzoneRef | null>(null)
|
const dropzoneRef = useRef<DropzoneRef | null>(null)
|
||||||
|
|
||||||
const openDialog = useCallback(
|
const openDialog = useCallback(
|
||||||
(event) => {
|
(event: React.MouseEvent) => {
|
||||||
if (dropzoneRef.current) {
|
if (dropzoneRef.current) {
|
||||||
dropzoneRef.current.open()
|
dropzoneRef.current.open()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -163,7 +163,7 @@ export function NotebookContent(props: NotebookContentProps): JSX.Element {
|
|||||||
}, [highlights])
|
}, [highlights])
|
||||||
|
|
||||||
const handleSaveNoteText = useCallback(
|
const handleSaveNoteText = useCallback(
|
||||||
(text) => {
|
(text: string) => {
|
||||||
const changeTime = new Date()
|
const changeTime = new Date()
|
||||||
|
|
||||||
setLastChanged(changeTime)
|
setLastChanged(changeTime)
|
||||||
|
|||||||
@ -49,7 +49,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element {
|
|||||||
props.onClose(allAnnotations ?? [], deletedHighlights ?? [])
|
props.onClose(allAnnotations ?? [], deletedHighlights ?? [])
|
||||||
}, [props, allAnnotations])
|
}, [props, allAnnotations])
|
||||||
|
|
||||||
const handleAnnotationsChange = useCallback((allAnnotations) => {
|
const handleAnnotationsChange = useCallback((allAnnotations: Highlight[]) => {
|
||||||
setAllAnnotations(allAnnotations)
|
setAllAnnotations(allAnnotations)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ export function NotebookModal(props: NotebookModalProps): JSX.Element {
|
|||||||
}, [allAnnotations])
|
}, [allAnnotations])
|
||||||
|
|
||||||
const viewInReader = useCallback(
|
const viewInReader = useCallback(
|
||||||
(highlightId) => {
|
(highlightId: string) => {
|
||||||
props.viewHighlightInReader(highlightId)
|
props.viewHighlightInReader(highlightId)
|
||||||
handleClose()
|
handleClose()
|
||||||
},
|
},
|
||||||
|
|||||||
@ -292,7 +292,7 @@ function FontControls(props: FontControlsProps): JSX.Element {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleFontSizeChange = useCallback(
|
const handleFontSizeChange = useCallback(
|
||||||
(value) => {
|
(value: number) => {
|
||||||
readerSettings.actionHandler('setFontSize', value)
|
readerSettings.actionHandler('setFontSize', value)
|
||||||
},
|
},
|
||||||
[readerSettings]
|
[readerSettings]
|
||||||
@ -399,7 +399,7 @@ function LayoutControls(props: LayoutControlsProps): JSX.Element {
|
|||||||
const { readerSettings } = props
|
const { readerSettings } = props
|
||||||
|
|
||||||
const handleMarginWidthChange = useCallback(
|
const handleMarginWidthChange = useCallback(
|
||||||
(value) => {
|
(value: number) => {
|
||||||
readerSettings.setMarginWidth(value)
|
readerSettings.setMarginWidth(value)
|
||||||
},
|
},
|
||||||
[readerSettings]
|
[readerSettings]
|
||||||
|
|||||||
@ -122,7 +122,7 @@ export function EmailLogin(): JSX.Element {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Don't have an account?{' '}
|
Don't have an account?{' '}
|
||||||
<Link href="/auth/email-signup" passHref>
|
<Link href="/auth/email-signup" passHref legacyBehavior>
|
||||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
||||||
Sign up
|
Sign up
|
||||||
</StyledTextSpan>
|
</StyledTextSpan>
|
||||||
@ -140,7 +140,7 @@ export function EmailLogin(): JSX.Element {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Forgot your password?{' '}
|
Forgot your password?{' '}
|
||||||
<Link href="/auth/forgot-password" passHref>
|
<Link href="/auth/forgot-password" passHref legacyBehavior>
|
||||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
||||||
Click here
|
Click here
|
||||||
</StyledTextSpan>
|
</StyledTextSpan>
|
||||||
@ -148,5 +148,5 @@ export function EmailLogin(): JSX.Element {
|
|||||||
</StyledText>
|
</StyledText>
|
||||||
</VStack>
|
</VStack>
|
||||||
</form>
|
</form>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -206,7 +206,7 @@ export function EmailSignup(): JSX.Element {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Already have an account?{' '}
|
Already have an account?{' '}
|
||||||
<Link href="/auth/email-login" passHref>
|
<Link href="/auth/email-login" passHref legacyBehavior>
|
||||||
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
<StyledTextSpan style="actionLink" css={{ color: '$omnivoreGray' }}>
|
||||||
Login instead
|
Login instead
|
||||||
</StyledTextSpan>
|
</StyledTextSpan>
|
||||||
@ -215,5 +215,5 @@ export function EmailSignup(): JSX.Element {
|
|||||||
<TermAndConditionsFooter />
|
<TermAndConditionsFooter />
|
||||||
</VStack>
|
</VStack>
|
||||||
</form>
|
</form>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,7 +108,7 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
|||||||
<DropdownSeparator />
|
<DropdownSeparator />
|
||||||
<Link
|
<Link
|
||||||
href={`/${props.viewer.profile.username}/${props.item.slug}#${props.highlight.id}`}
|
href={`/${props.viewer.profile.username}/${props.item.slug}#${props.highlight.id}`}
|
||||||
>
|
legacyBehavior>
|
||||||
<StyledLinkItem
|
<StyledLinkItem
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
console.log('event.ctrlKey: ', event.ctrlKey, event.metaKey)
|
console.log('event.ctrlKey: ', event.ctrlKey, event.metaKey)
|
||||||
@ -129,7 +129,7 @@ export function HighlightsMenu(props: HighlightsMenuProps): JSX.Element {
|
|||||||
</Link>
|
</Link>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</VStack>
|
</VStack>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortHighlights = (highlights: Highlight[]) => {
|
const sortHighlights = (highlights: Highlight[]) => {
|
||||||
|
|||||||
@ -368,7 +368,7 @@ function HighlightList(props: HighlightListProps): JSX.Element {
|
|||||||
}, [props.item.node.highlights])
|
}, [props.item.node.highlights])
|
||||||
|
|
||||||
const viewInReader = useCallback(
|
const viewInReader = useCallback(
|
||||||
(highlightId) => {
|
(highlightId: string) => {
|
||||||
if (!router || !router.isReady || !props.viewer) {
|
if (!router || !router.isReady || !props.viewer) {
|
||||||
showErrorToast('Error navigating to highlight')
|
showErrorToast('Error navigating to highlight')
|
||||||
return
|
return
|
||||||
|
|||||||
@ -351,7 +351,7 @@ export function HomeFeedContainer(): JSX.Element {
|
|||||||
}, [libraryItems, activeCardId])
|
}, [libraryItems, activeCardId])
|
||||||
|
|
||||||
const getItem = useCallback(
|
const getItem = useCallback(
|
||||||
(itemId) => {
|
(itemId: string) => {
|
||||||
return libraryItems.find((item) => item.node.id === itemId)
|
return libraryItems.find((item) => item.node.id === itemId)
|
||||||
},
|
},
|
||||||
[libraryItems]
|
[libraryItems]
|
||||||
|
|||||||
@ -553,7 +553,7 @@ type EditButtonProps = {
|
|||||||
|
|
||||||
function EditButton(props: EditButtonProps): JSX.Element {
|
function EditButton(props: EditButtonProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Link href={props.destination} passHref>
|
<Link href={props.destination} passHref legacyBehavior>
|
||||||
<SpanBox
|
<SpanBox
|
||||||
css={{
|
css={{
|
||||||
ml: '10px',
|
ml: '10px',
|
||||||
@ -584,5 +584,5 @@ function EditButton(props: EditButtonProps): JSX.Element {
|
|||||||
{props.title}
|
{props.title}
|
||||||
</SpanBox>
|
</SpanBox>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,7 +140,7 @@ export function Webhooks(): JSX.Element {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h3>{item.method}</h3>
|
<h3>{item.method}</h3>
|
||||||
<p>{item.createdAt}</p>
|
<p>{item.createdAt?.toLocaleDateString()}</p>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { HStack, VStack } from '../../elements/LayoutPrimitives'
|
import { Box, HStack, VStack } from '../../elements/LayoutPrimitives'
|
||||||
import { styled } from '../../tokens/stitches.config'
|
import { styled } from '../../tokens/stitches.config'
|
||||||
import { StyledText } from '../../elements/StyledText'
|
import { StyledText } from '../../elements/StyledText'
|
||||||
|
|
||||||
@ -30,11 +30,29 @@ export function LandingFooter(): JSX.Element {
|
|||||||
textDecoration: 'underline',
|
textDecoration: 'underline',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'@mdDown': {
|
||||||
|
columns: 2,
|
||||||
|
width: '100%',
|
||||||
|
marginTop: "8px",
|
||||||
|
marginBottom: "30px",
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack css={containerStyles}>
|
<HStack css={containerStyles}>
|
||||||
<HStack css={{ width: '100%', maxWidth: '1224px' }}>
|
<Box
|
||||||
|
css={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '1024px',
|
||||||
|
'@md': {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
<VStack>
|
<VStack>
|
||||||
<StyledText style="aboutFooter">Install</StyledText>
|
<StyledText style="aboutFooter">Install</StyledText>
|
||||||
<FooterList>
|
<FooterList>
|
||||||
@ -77,7 +95,7 @@ export function LandingFooter(): JSX.Element {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="mailto:feedback@omnivore.app">Contact us via email</a>
|
<a href="mailto:feedback@omnivore.app">Contact us via email</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://discord.gg/h2z5rppzz9">
|
<a href="https://discord.gg/h2z5rppzz9">
|
||||||
@ -112,7 +130,7 @@ export function LandingFooter(): JSX.Element {
|
|||||||
</li>
|
</li>
|
||||||
</FooterList>
|
</FooterList>
|
||||||
</VStack>
|
</VStack>
|
||||||
</HStack>
|
</Box>
|
||||||
</HStack>
|
</HStack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
import { Box } from '../../elements/LayoutPrimitives'
|
import { Box } from '../../elements/LayoutPrimitives'
|
||||||
import { OmnivoreNameLogo } from '../../elements/images/OmnivoreNameLogo'
|
import { OmnivoreNameLogo } from '../../elements/images/OmnivoreNameLogo'
|
||||||
import { Button } from '../../elements/Button'
|
import { Button } from '../../elements/Button'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
const LoginButton = (): JSX.Element => {
|
const LoginButton = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
as={Link}
|
||||||
|
href="/login"
|
||||||
style="ctaDarkYellow"
|
style="ctaDarkYellow"
|
||||||
css={{
|
css={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
border: 'unset',
|
|
||||||
background: 'unset',
|
background: 'unset',
|
||||||
color: '#3D3D3D',
|
color: '#3D3D3D',
|
||||||
height: '42px',
|
height: '42px',
|
||||||
@ -19,10 +21,8 @@ const LoginButton = (): JSX.Element => {
|
|||||||
fontWeight: 'normal',
|
fontWeight: 'normal',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
}}
|
textDecoration: "none",
|
||||||
onClick={(e) => {
|
transition: "all ease-in 50ms"
|
||||||
document.location.href = '/login'
|
|
||||||
e.preventDefault()
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Login
|
Login
|
||||||
|
|||||||
@ -1,50 +1,11 @@
|
|||||||
import { HStack, VStack, Box } from '../../elements/LayoutPrimitives'
|
import { HStack, VStack, Box } from '../../elements/LayoutPrimitives'
|
||||||
|
|
||||||
type LandingSectionProps = {
|
export interface LandingSectionProps {
|
||||||
titleText: string
|
titleText: string
|
||||||
descriptionText: React.ReactElement
|
descriptionText: React.ReactElement | string | number
|
||||||
icon?: React.ReactElement
|
icon?: React.ReactElement
|
||||||
image: React.ReactElement
|
image: React.ReactElement
|
||||||
}
|
imagePosition?: 'left' | 'right'
|
||||||
|
|
||||||
const titleTextStyles = {
|
|
||||||
fontWeight: '700',
|
|
||||||
color: '#3D3D3D',
|
|
||||||
lineHeight: 1.25,
|
|
||||||
'@mdDown': {
|
|
||||||
fontSize: 24,
|
|
||||||
},
|
|
||||||
'@md': {
|
|
||||||
fontSize: '$5',
|
|
||||||
},
|
|
||||||
'@xl': {
|
|
||||||
fontSize: 45,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageContainerStyles = {
|
|
||||||
display: 'flex',
|
|
||||||
width: '49%',
|
|
||||||
alignSelf: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
'@md': {
|
|
||||||
marginBottom: '60px',
|
|
||||||
},
|
|
||||||
'@mdDown': {
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const layoutStyles = {
|
|
||||||
width: '49%',
|
|
||||||
alignSelf: 'start',
|
|
||||||
'@mdDown': {
|
|
||||||
width: '100%',
|
|
||||||
paddingTop: '30px',
|
|
||||||
},
|
|
||||||
paddingLeft: '30px',
|
|
||||||
paddingRight: '30px',
|
|
||||||
paddingBottom: '30px',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LandingSection(props: LandingSectionProps): JSX.Element {
|
export function LandingSection(props: LandingSectionProps): JSX.Element {
|
||||||
@ -53,24 +14,72 @@ export function LandingSection(props: LandingSectionProps): JSX.Element {
|
|||||||
css={{
|
css={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
flexDirection: 'row-reverse',
|
flexDirection: (props?.imagePosition ?? 'left') === 'left' ? 'row-reverse' : 'row',
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
'@mdDown': {
|
'@mdDown': {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<VStack distribution="center" alignment={'center'} css={layoutStyles}>
|
<VStack
|
||||||
<Box css={titleTextStyles}>{props.titleText}</Box>
|
distribution="center"
|
||||||
|
alignment="center"
|
||||||
|
css={{
|
||||||
|
width: '49%',
|
||||||
|
alignSelf: 'start',
|
||||||
|
'@mdDown': {
|
||||||
|
width: '100%',
|
||||||
|
paddingTop: '30px',
|
||||||
|
},
|
||||||
|
paddingLeft: '30px',
|
||||||
|
paddingRight: '30px',
|
||||||
|
paddingBottom: '30px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
|
as="h2"
|
||||||
css={{
|
css={{
|
||||||
color: 'rgb(125, 125, 125)',
|
fontWeight: '700',
|
||||||
|
color: '#3D3D3D',
|
||||||
|
lineHeight: 1.25,
|
||||||
|
'@mdDown': {
|
||||||
|
fontSize: 24,
|
||||||
|
},
|
||||||
|
'@md': {
|
||||||
|
fontSize: '$5',
|
||||||
|
},
|
||||||
|
'@xl': {
|
||||||
|
fontSize: 45,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.titleText}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
as="p"
|
||||||
|
css={{
|
||||||
|
color: '#666',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.descriptionText}
|
{props.descriptionText}
|
||||||
</Box>
|
</Box>
|
||||||
</VStack>
|
</VStack>
|
||||||
<Box css={imageContainerStyles}>{props.image}</Box>
|
<Box
|
||||||
|
css={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '49%',
|
||||||
|
alignSelf: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
'@md': {
|
||||||
|
marginBottom: '60px',
|
||||||
|
},
|
||||||
|
'@mdDown': {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.image}
|
||||||
|
</Box>
|
||||||
</HStack>
|
</HStack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +1,35 @@
|
|||||||
|
import Image from 'next/image'
|
||||||
import { VStack, Box } from '../../elements/LayoutPrimitives'
|
import { VStack, Box } from '../../elements/LayoutPrimitives'
|
||||||
import { Button } from '../../elements/Button'
|
import { Button } from '../../elements/Button'
|
||||||
import { LandingSection } from './LandingSection'
|
import { LandingSection } from './LandingSection'
|
||||||
|
|
||||||
type GetStartedButtonProps = {
|
import landingPageHeroImage from '../../../public/static/images/landing/landing-00-hero.png'
|
||||||
lang: 'en' | 'zh'
|
import landingSection1Image from '../../../public/static/images/landing/landing-01-save-it-now.png'
|
||||||
}
|
import landingSection2Image from '../../../public/static/images/landing/landing-02-newsletters.png'
|
||||||
|
import landingSection3Image from '../../../public/static/images/landing/landing-03-organisation.png'
|
||||||
|
import landingSection4Image from '../../../public/static/images/landing/landing-04-highlights-and-notes.png'
|
||||||
|
import landingSection5Image from '../../../public/static/images/landing/landing-05-sync.png'
|
||||||
|
import landingSection6Image from '../../../public/static/images/landing/landing-06-tts.png'
|
||||||
|
import landingSection7Image from '../../../public/static/images/landing/landing-07-oss.png'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
export function GetStartedButton(props: GetStartedButtonProps): JSX.Element {
|
export function GetStartedButton(props: { lang: 'en' | 'zh' }): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
as={Link}
|
||||||
|
href="/login"
|
||||||
style="ctaDarkYellow"
|
style="ctaDarkYellow"
|
||||||
css={{
|
css={{
|
||||||
display: 'flex',
|
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
background: 'rgb(255, 210, 52)',
|
background: '$omnivoreCtaYellow',
|
||||||
|
padding: '12px 25px',
|
||||||
color: '#3D3D3D',
|
color: '#3D3D3D',
|
||||||
width: '172px',
|
|
||||||
height: '42px',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
}}
|
textDecoration: 'none',
|
||||||
onClick={(e) => {
|
transition: 'background-color ease-out 50ms',
|
||||||
document.location.href = '/login'
|
'&:hover': {
|
||||||
e.preventDefault()
|
backgroundColor: '$omnivoreYellow',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.lang == 'zh' ? `免费注册` : `Sign Up for Free`}
|
{props.lang == 'zh' ? `免费注册` : `Sign Up for Free`}
|
||||||
@ -50,38 +56,6 @@ const containerStyles = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const callToActionStyles = {
|
|
||||||
background: 'white',
|
|
||||||
borderRadius: '24px',
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
border: '1px solid #D8D7D5',
|
|
||||||
boxShadow:
|
|
||||||
'0px 7px 8px rgba(32, 31, 29, 0.03), 0px 18px 24px rgba(32, 31, 29, 0.03)',
|
|
||||||
padding: 40,
|
|
||||||
marginTop: 64,
|
|
||||||
minheight: 330,
|
|
||||||
width: 'inherit',
|
|
||||||
|
|
||||||
'@md': {
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
'@xl': {
|
|
||||||
width: '95%',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const callToActionText = {
|
|
||||||
color: '#3D3D3D',
|
|
||||||
fontWeight: '700',
|
|
||||||
fontSize: 64,
|
|
||||||
lineHeight: '1.25',
|
|
||||||
textAlign: 'center',
|
|
||||||
paddingBottom: '20px',
|
|
||||||
'@mdDown': {
|
|
||||||
fontSize: '32px',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type LandingSectionsContainerProps = {
|
type LandingSectionsContainerProps = {
|
||||||
lang: 'en' | 'zh'
|
lang: 'en' | 'zh'
|
||||||
}
|
}
|
||||||
@ -98,7 +72,8 @@ const sections = [
|
|||||||
titleText: `先保存,后阅读`,
|
titleText: `先保存,后阅读`,
|
||||||
descriptionText: `看到有趣的内容,但没时间阅读?不论是文章、PDF或是推特线程,只需将它们保存下来,等稍后有空再阅读。Omnivore 应用程序适用于iOS、Android 和主要网络浏览扩展程序。`,
|
descriptionText: `看到有趣的内容,但没时间阅读?不论是文章、PDF或是推特线程,只需将它们保存下来,等稍后有空再阅读。Omnivore 应用程序适用于iOS、Android 和主要网络浏览扩展程序。`,
|
||||||
},
|
},
|
||||||
imageIdx: `03`,
|
image: landingSection1Image,
|
||||||
|
imageAlt: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
en: {
|
en: {
|
||||||
@ -111,7 +86,8 @@ const sections = [
|
|||||||
titleText: `集合邮件订阅`,
|
titleText: `集合邮件订阅`,
|
||||||
descriptionText: `您不再需要到不同收件箱提取订阅的邮件,只要将它们发送到 Omnivore Library,即可在同一处随心阅读,不受其他电子邮件或 substack 的干扰。`,
|
descriptionText: `您不再需要到不同收件箱提取订阅的邮件,只要将它们发送到 Omnivore Library,即可在同一处随心阅读,不受其他电子邮件或 substack 的干扰。`,
|
||||||
},
|
},
|
||||||
imageIdx: `04`,
|
image: landingSection2Image,
|
||||||
|
imageAlt: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
en: {
|
en: {
|
||||||
@ -125,7 +101,8 @@ const sections = [
|
|||||||
titleText: `按喜好组织阅读系统`,
|
titleText: `按喜好组织阅读系统`,
|
||||||
descriptionText: `我们不会限定您如何组织系统,只提供您所需的工具,如标签、过滤器和完整的文本索引搜索,让您按自己的喜好和需求设定组织规则。`,
|
descriptionText: `我们不会限定您如何组织系统,只提供您所需的工具,如标签、过滤器和完整的文本索引搜索,让您按自己的喜好和需求设定组织规则。`,
|
||||||
},
|
},
|
||||||
imageIdx: `05`,
|
image: landingSection3Image,
|
||||||
|
imageAlt: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
en: {
|
en: {
|
||||||
@ -139,7 +116,8 @@ const sections = [
|
|||||||
titleText: `添加高亮和注释`,
|
titleText: `添加高亮和注释`,
|
||||||
descriptionText: `想提高阅读效率?积极动用大脑,为关键的段落添加高亮或注释,能提高您阅读记忆的保留。这些标注将永久保存在文件里,方便您随时搜索使用。`,
|
descriptionText: `想提高阅读效率?积极动用大脑,为关键的段落添加高亮或注释,能提高您阅读记忆的保留。这些标注将永久保存在文件里,方便您随时搜索使用。`,
|
||||||
},
|
},
|
||||||
imageIdx: `06`,
|
image: landingSection4Image,
|
||||||
|
imageAlt: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
en: {
|
en: {
|
||||||
@ -152,7 +130,8 @@ const sections = [
|
|||||||
titleText: `与您的“第二大脑”同步`,
|
titleText: `与您的“第二大脑”同步`,
|
||||||
descriptionText: `Omnivore 应用程序能与个人知识管理系统如 Logseq 和 Obsidian 同步,让您轻而易举地综合所有保存文章、高亮和注释。`,
|
descriptionText: `Omnivore 应用程序能与个人知识管理系统如 Logseq 和 Obsidian 同步,让您轻而易举地综合所有保存文章、高亮和注释。`,
|
||||||
},
|
},
|
||||||
imageIdx: `07`,
|
image: landingSection5Image,
|
||||||
|
imageAlt: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
en: {
|
en: {
|
||||||
@ -165,8 +144,8 @@ const sections = [
|
|||||||
titleText: `使用 text-to-speech 功能聆听阅读`,
|
titleText: `使用 text-to-speech 功能聆听阅读`,
|
||||||
descriptionText: `使用TTS逼真、自然的人工智能声音为您阅读待读列表中的读物,让眼睛好好休息一下。这便是我们iOS 版Omnivore 应用程序的独家功能。`,
|
descriptionText: `使用TTS逼真、自然的人工智能声音为您阅读待读列表中的读物,让眼睛好好休息一下。这便是我们iOS 版Omnivore 应用程序的独家功能。`,
|
||||||
},
|
},
|
||||||
imageIdx: `08`,
|
image: landingSection6Image,
|
||||||
maxWidth: '85%',
|
imageAlt: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
en: {
|
en: {
|
||||||
@ -180,7 +159,8 @@ const sections = [
|
|||||||
titleText: `开源软件给予您控制权`,
|
titleText: `开源软件给予您控制权`,
|
||||||
descriptionText: `阅读是终身的活动,不应担心失去自己多年辛苦建立的图书馆。我们的开源平台,就是为了确保您的阅读不会受限于任何专有系统。`,
|
descriptionText: `阅读是终身的活动,不应担心失去自己多年辛苦建立的图书馆。我们的开源平台,就是为了确保您的阅读不会受限于任何专有系统。`,
|
||||||
},
|
},
|
||||||
imageIdx: `09`,
|
image: landingSection7Image,
|
||||||
|
imageAlt: '',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
export function LandingSectionsContainer(
|
export function LandingSectionsContainer(
|
||||||
@ -198,42 +178,65 @@ export function LandingSectionsContainer(
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<Image
|
||||||
height="647"
|
src={landingPageHeroImage}
|
||||||
width="1015"
|
alt="Hero image"
|
||||||
srcSet="/static/landing/landingPage-feature@1x.png,
|
sizes="(max-width: 1024px) 100vw"
|
||||||
/static/landing/landingPage-feature@2x.png 2x,
|
|
||||||
/static/landing/landingPage-feature@3x.png 3x"
|
|
||||||
alt="landingHero-1"
|
|
||||||
style={{
|
style={{
|
||||||
width: '85%',
|
maxWidth: '85%',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
}}
|
}}
|
||||||
|
priority
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{sections.map((section) => {
|
{sections.map((section, sectionIndex) => {
|
||||||
return (
|
return (
|
||||||
<LandingSection
|
<LandingSection
|
||||||
key={section.imageIdx}
|
key={sectionIndex}
|
||||||
titleText={section[props.lang].titleText}
|
titleText={section[props.lang].titleText}
|
||||||
descriptionText={<p>{section[props.lang].descriptionText}</p>}
|
descriptionText={section[props.lang].descriptionText}
|
||||||
|
imagePosition={sectionIndex % 2 ? 'left' : 'right'}
|
||||||
image={
|
image={
|
||||||
<img
|
<Image
|
||||||
srcSet={`/static/landing/landingPage-${section.imageIdx}@1x.png,
|
alt={section.imageAlt}
|
||||||
/static/landing/landingPage-${section.imageIdx}@2x.png 2x,
|
src={section.image}
|
||||||
/static/landing/landingPage-${section.imageIdx}@3x.png 3x`}
|
sizes="(max-width: 512px) 50vw, (max-width: 512px) 100vw"
|
||||||
alt={`landing-${section.imageIdx}`}
|
style={{ maxWidth: '100%', height: 'auto' }}
|
||||||
style={{ maxWidth: section.maxWidth ?? '100%' }}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<VStack alignment="center" css={callToActionStyles}>
|
<VStack alignment="center" css={{
|
||||||
|
width: "100vw",
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
paddingBottom: "40px",
|
||||||
|
marginTop: "40px",
|
||||||
|
borderTop: "1px solid var(--colors-omnivoreYellow)",
|
||||||
|
borderBottom: "1px solid var(--colors-omnivoreYellow)",
|
||||||
|
"@md": {
|
||||||
|
marginTop: 0,
|
||||||
|
}
|
||||||
|
}}>
|
||||||
{props.lang == 'en' && (
|
{props.lang == 'en' && (
|
||||||
<Box css={callToActionText}>Get Started With Omnivore Today</Box>
|
<Box
|
||||||
|
as="p"
|
||||||
|
css={{
|
||||||
|
color: '#3D3D3D',
|
||||||
|
fontWeight: '700',
|
||||||
|
fontSize: '2.5rem',
|
||||||
|
lineHeight: '1.25',
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: '40px',
|
||||||
|
'@mdDown': {
|
||||||
|
fontSize: '2rem',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Get Started With Omnivore Today
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
<GetStartedButton lang={props.lang} />
|
<GetStartedButton lang={props.lang} />
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|||||||
@ -269,7 +269,7 @@ const darkThemeSpec = {
|
|||||||
|
|
||||||
labelButtonsBg: '#5F5E58',
|
labelButtonsBg: '#5F5E58',
|
||||||
|
|
||||||
// New theme, special naming to keep things straigh
|
// New theme, special naming to keep things straight
|
||||||
// once all switch over, we will rename
|
// once all switch over, we will rename
|
||||||
// DARK
|
// DARK
|
||||||
colorScheme: 'dark',
|
colorScheme: 'dark',
|
||||||
|
|||||||
@ -23,7 +23,7 @@ function disableWordSnap(str: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isWhitespace = (c?: string): boolean => {
|
const isWhitespace = (c?: string): boolean => {
|
||||||
return !!c && /\u2014|\u2013|,|\s/.test(c)
|
return !!c && /\u2014|\u2013|,|\s/.test(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findNextWord(
|
function findNextWord(
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export function useSelection(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const handleFinishTouch = useCallback(
|
const handleFinishTouch = useCallback(
|
||||||
async (mouseEvent) => {
|
async (mouseEvent: any) => {
|
||||||
let wasDragEvent = false
|
let wasDragEvent = false
|
||||||
const tapAttributes = {
|
const tapAttributes = {
|
||||||
tapX: mouseEvent.clientX,
|
tapX: mouseEvent.clientX,
|
||||||
|
|||||||
@ -105,7 +105,7 @@ export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const keydownListener = useCallback(
|
const keydownListener = useCallback(
|
||||||
(keydownEvent) => {
|
(keydownEvent: any) => {
|
||||||
const { target } = keydownEvent
|
const { target } = keydownEvent
|
||||||
if (!keydownEvent.key) return
|
if (!keydownEvent.key) return
|
||||||
const key = keydownEvent.key.toLowerCase()
|
const key = keydownEvent.key.toLowerCase()
|
||||||
@ -129,7 +129,7 @@ export const useKeyboardShortcuts = (commands: KeyboardCommand[]): void => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const keyupListener = useCallback(
|
const keyupListener = useCallback(
|
||||||
(keyupEvent) => {
|
(keyupEvent: any) => {
|
||||||
if (!keyupEvent.key) return
|
if (!keyupEvent.key) return
|
||||||
const key = keyupEvent.key.toLowerCase()
|
const key = keyupEvent.key.toLowerCase()
|
||||||
if (keys[key] === undefined) return
|
if (keys[key] === undefined) return
|
||||||
|
|||||||
@ -17,6 +17,7 @@ const ContentSecurityPolicy = `
|
|||||||
|
|
||||||
const moduleExports = {
|
const moduleExports = {
|
||||||
images: {
|
images: {
|
||||||
|
formats: ['image/avif', 'image/webp'],
|
||||||
domains: [
|
domains: [
|
||||||
'proxy-demo.omnivore-image-cache.app',
|
'proxy-demo.omnivore-image-cache.app',
|
||||||
'proxy-dev.omnivore-image-cache.app',
|
'proxy-dev.omnivore-image-cache.app',
|
||||||
@ -56,7 +57,7 @@ const moduleExports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
},
|
},
|
||||||
async redirects() {
|
async redirects() {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:build": "jest && next build",
|
"test:build": "jest && next build",
|
||||||
|
"test:typecheck": "tsc --noEmit",
|
||||||
"upgrade-psdpdfkit": "cp -R '../../node_modules/pspdfkit/dist/pspdfkit-lib' public/pspdfkit-lib",
|
"upgrade-psdpdfkit": "cp -R '../../node_modules/pspdfkit/dist/pspdfkit-lib' public/pspdfkit-lib",
|
||||||
"storybook": "start-storybook -p 6006 -s ./public",
|
"storybook": "start-storybook -p 6006 -s ./public",
|
||||||
"build-storybook": "build-storybook -s public"
|
"build-storybook": "build-storybook -s public"
|
||||||
@ -32,7 +33,6 @@
|
|||||||
"@radix-ui/react-tooltip": "^0.1.7",
|
"@radix-ui/react-tooltip": "^0.1.7",
|
||||||
"@sentry/nextjs": "^7.42.0",
|
"@sentry/nextjs": "^7.42.0",
|
||||||
"@stitches/react": "^1.2.5",
|
"@stitches/react": "^1.2.5",
|
||||||
"@types/react-input-autosize": "^2.2.1",
|
|
||||||
"antd": "4.24.3",
|
"antd": "4.24.3",
|
||||||
"axios": "^1.2.0",
|
"axios": "^1.2.0",
|
||||||
"color2k": "^2.0.0",
|
"color2k": "^2.0.0",
|
||||||
@ -47,16 +47,16 @@
|
|||||||
"markdown-it": "^13.0.1",
|
"markdown-it": "^13.0.1",
|
||||||
"match-sorter": "^6.3.1",
|
"match-sorter": "^6.3.1",
|
||||||
"nanoid": "^3.1.29",
|
"nanoid": "^3.1.29",
|
||||||
"next": "^12.1.0",
|
"next": "^13.5.6",
|
||||||
"node-html-markdown": "^1.3.0",
|
"node-html-markdown": "^1.3.0",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"phosphor-react": "^1.4.0",
|
"phosphor-react": "^1.4.0",
|
||||||
"posthog-js": "^1.78.2",
|
"posthog-js": "^1.78.2",
|
||||||
"pspdfkit": "^2022.2.3",
|
"pspdfkit": "^2022.2.3",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-colorful": "^5.5.1",
|
"react-colorful": "^5.5.1",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-hot-toast": "^2.1.1",
|
"react-hot-toast": "^2.1.1",
|
||||||
"react-input-autosize": "^3.0.0",
|
"react-input-autosize": "^3.0.0",
|
||||||
@ -68,6 +68,7 @@
|
|||||||
"react-super-responsive-table": "^5.2.1",
|
"react-super-responsive-table": "^5.2.1",
|
||||||
"react-topbar-progress-indicator": "^4.1.1",
|
"react-topbar-progress-indicator": "^4.1.1",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
|
"sharp": "^0.32.6",
|
||||||
"swr": "^1.0.1",
|
"swr": "^1.0.1",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"yet-another-react-lightbox": "^3.12.0"
|
"yet-another-react-lightbox": "^3.12.0"
|
||||||
@ -93,14 +94,14 @@
|
|||||||
"@types/lodash.debounce": "^4.0.6",
|
"@types/lodash.debounce": "^4.0.6",
|
||||||
"@types/markdown-it": "^12.2.3",
|
"@types/markdown-it": "^12.2.3",
|
||||||
"@types/papaparse": "^5.3.7",
|
"@types/papaparse": "^5.3.7",
|
||||||
"@types/react": "17.0.2",
|
"@types/react": "^18.2.0",
|
||||||
"@types/react-color": "^3.0.6",
|
"@types/react-color": "^3.0.9",
|
||||||
"@types/react-dom": "^17.0.2",
|
"@types/react-dom": "^18.2.0",
|
||||||
"@types/react-input-autosize": "^2.2.1",
|
"@types/react-input-autosize": "^2.2.1",
|
||||||
"@types/uuid": "^8.3.1",
|
"@types/uuid": "^8.3.1",
|
||||||
"babel-jest": "^27.4.5",
|
"babel-jest": "^27.4.5",
|
||||||
"babel-loader": "^8.2.3",
|
"babel-loader": "^8.2.3",
|
||||||
"eslint-config-next": "12.0.7",
|
"eslint-config-next": "^13.5.6",
|
||||||
"eslint-plugin-functional": "^4.0.2",
|
"eslint-plugin-functional": "^4.0.2",
|
||||||
"eslint-plugin-react": "^7.28.0",
|
"eslint-plugin-react": "^7.28.0",
|
||||||
"graphql": "^15.6.1",
|
"graphql": "^15.6.1",
|
||||||
@ -108,7 +109,6 @@
|
|||||||
"storybook-addon-next-router": "^3.1.1"
|
"storybook-addon-next-router": "^3.1.1"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "18.16.1",
|
"extends": "../../package.json"
|
||||||
"yarn": "1.22.10"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export default function LandingPage(): JSX.Element {
|
|||||||
description="Omnivore is the free, open source, read-it-later app for serious readers."
|
description="Omnivore is the free, open source, read-it-later app for serious readers."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<About lang="en" />
|
<About lang="zh" />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,8 +4,11 @@ import { locale, timeZone } from '../../lib/dateFormatting'
|
|||||||
import { SaveResponseData } from '../../lib/networking/mutations/saveUrlMutation'
|
import { SaveResponseData } from '../../lib/networking/mutations/saveUrlMutation'
|
||||||
import { ssrFetcher } from '../../lib/networking/networkHelpers'
|
import { ssrFetcher } from '../../lib/networking/networkHelpers'
|
||||||
|
|
||||||
|
type Request = NextApiRequest & { cookies: { [key: string]: string } }
|
||||||
|
type Response = NextApiResponse
|
||||||
|
|
||||||
const saveUrl = async (
|
const saveUrl = async (
|
||||||
req: NextApiRequest,
|
req: Request,
|
||||||
url: URL,
|
url: URL,
|
||||||
labels: string[] | undefined,
|
labels: string[] | undefined,
|
||||||
state: string | undefined,
|
state: string | undefined,
|
||||||
@ -53,11 +56,7 @@ const saveUrl = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-anonymous-default-export
|
export default async function handler(req: Request, res: Response) {
|
||||||
export default async (
|
|
||||||
req: NextApiRequest,
|
|
||||||
res: NextApiResponse
|
|
||||||
): Promise<void> => {
|
|
||||||
const urlStr = req.query['url']
|
const urlStr = req.query['url']
|
||||||
if (req.query['labels'] && typeof req.query['labels'] === 'string') {
|
if (req.query['labels'] && typeof req.query['labels'] === 'string') {
|
||||||
req.query['labels'] = [req.query['labels']]
|
req.query['labels'] = [req.query['labels']]
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export default function InvitePage(): JSX.Element {
|
|||||||
}, [isLoading, router, viewerData, viewerDataError])
|
}, [isLoading, router, viewerData, viewerDataError])
|
||||||
|
|
||||||
const acceptClicked = useCallback(
|
const acceptClicked = useCallback(
|
||||||
(event) => {
|
(event: any) => {
|
||||||
event?.stopPropagation()
|
event?.stopPropagation()
|
||||||
|
|
||||||
if (!router.isReady) {
|
if (!router.isReady) {
|
||||||
@ -63,108 +63,106 @@ export default function InvitePage(): JSX.Element {
|
|||||||
[router, inviteCode]
|
[router, inviteCode]
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return <>
|
||||||
<>
|
<PageMetaData title="Accept Invite - Omnivore" path="/invite" />
|
||||||
<PageMetaData title="Accept Invite - Omnivore" path="/invite" />
|
<ProfileLayout>
|
||||||
<ProfileLayout>
|
<VStack
|
||||||
<VStack
|
alignment="center"
|
||||||
alignment="center"
|
css={{
|
||||||
|
padding: '16px',
|
||||||
|
background: 'white',
|
||||||
|
minWidth: '340px',
|
||||||
|
width: '70vw',
|
||||||
|
maxWidth: '576px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
border: '1px solid #3D3D3D',
|
||||||
|
boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>
|
||||||
|
You're invited
|
||||||
|
</StyledText>
|
||||||
|
|
||||||
|
<StyledText
|
||||||
|
style="action"
|
||||||
css={{
|
css={{
|
||||||
padding: '16px',
|
mt: '0px',
|
||||||
background: 'white',
|
pt: '4px',
|
||||||
minWidth: '340px',
|
width: '100%',
|
||||||
width: '70vw',
|
color: '$omnivoreLightGray',
|
||||||
maxWidth: '576px',
|
textAlign: 'center',
|
||||||
borderRadius: '8px',
|
whiteSpace: 'normal',
|
||||||
border: '1px solid #3D3D3D',
|
|
||||||
boxShadow: '#B1B1B1 9px 9px 9px -9px',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StyledText style="subHeadline" css={{ color: '$omnivoreGray' }}>
|
You have been invited to join a recommendation group on Omnivore.
|
||||||
You're invited
|
Recommendation groups allow you to share articles with other group
|
||||||
</StyledText>
|
members.
|
||||||
|
</StyledText>
|
||||||
|
{errorMessage && (
|
||||||
<StyledText
|
<StyledText
|
||||||
style="action"
|
style="error"
|
||||||
css={{
|
css={{
|
||||||
mt: '0px',
|
mt: '0px',
|
||||||
pt: '4px',
|
pt: '4px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
color: '$omnivoreLightGray',
|
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
You have been invited to join a recommendation group on Omnivore.
|
{errorMessage}
|
||||||
Recommendation groups allow you to share articles with other group
|
|
||||||
members.
|
|
||||||
</StyledText>
|
</StyledText>
|
||||||
{errorMessage && (
|
)}
|
||||||
<StyledText
|
|
||||||
style="error"
|
|
||||||
css={{
|
|
||||||
mt: '0px',
|
|
||||||
pt: '4px',
|
|
||||||
width: '100%',
|
|
||||||
textAlign: 'center',
|
|
||||||
whiteSpace: 'normal',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errorMessage}
|
|
||||||
</StyledText>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<HStack
|
<HStack
|
||||||
alignment="center"
|
alignment="center"
|
||||||
distribution="center"
|
distribution="center"
|
||||||
css={{
|
css={{
|
||||||
gap: '10px',
|
gap: '10px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '80px',
|
height: '80px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{viewerData?.me ? (
|
{viewerData?.me ? (
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
style={'ctaDarkYellow'}
|
style={'ctaDarkYellow'}
|
||||||
onClick={acceptClicked}
|
onClick={acceptClicked}
|
||||||
>
|
>
|
||||||
Accept Invite
|
Accept Invite
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
style={'ctaDarkYellow'}
|
style={'ctaDarkYellow'}
|
||||||
onClick={() => router.push('/login')}
|
onClick={() => router.push('/login')}
|
||||||
>
|
>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
<StyledText
|
<StyledText
|
||||||
style="action"
|
style="action"
|
||||||
css={{
|
css={{
|
||||||
m: '0px',
|
m: '0px',
|
||||||
pt: '16px',
|
pt: '16px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
color: '$omnivoreLightGray',
|
color: '$omnivoreLightGray',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Don't have an Omnivore account?{' '}
|
Don't have an Omnivore account?{' '}
|
||||||
<Link href="/login" passHref>
|
<Link href="/login" passHref legacyBehavior>
|
||||||
<StyledTextSpan
|
<StyledTextSpan
|
||||||
style="actionLink"
|
style="actionLink"
|
||||||
css={{ color: '$omnivoreGray' }}
|
css={{ color: '$omnivoreGray' }}
|
||||||
>
|
>
|
||||||
Signup
|
Signup
|
||||||
</StyledTextSpan>
|
</StyledTextSpan>
|
||||||
</Link>
|
</Link>
|
||||||
</StyledText>
|
</StyledText>
|
||||||
</VStack>
|
</VStack>
|
||||||
<div data-testid="invite-page-tag" />
|
<div data-testid="invite-page-tag" />
|
||||||
</ProfileLayout>
|
</ProfileLayout>
|
||||||
</>
|
</>;
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -114,145 +114,143 @@ export default function EmailsPage(): JSX.Element {
|
|||||||
return emailAddresses.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
|
return emailAddresses.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
|
||||||
}, [emailAddresses])
|
}, [emailAddresses])
|
||||||
|
|
||||||
return (
|
return <>
|
||||||
<>
|
<SettingsTable
|
||||||
<SettingsTable
|
pageId="settings-emails-tag"
|
||||||
pageId="settings-emails-tag"
|
pageInfoLink="https://docs.omnivore.app/using/inbox.html"
|
||||||
pageInfoLink="https://docs.omnivore.app/using/inbox.html"
|
headerTitle="Address"
|
||||||
headerTitle="Address"
|
createTitle="Create a new email address"
|
||||||
createTitle="Create a new email address"
|
createAction={createEmail}
|
||||||
createAction={createEmail}
|
suggestionInfo={{
|
||||||
suggestionInfo={{
|
title: 'Subscribe to newsletters with an Omnivore Email Address',
|
||||||
title: 'Subscribe to newsletters with an Omnivore Email Address',
|
message:
|
||||||
message:
|
'Create an Omnivore email address and use it to subscribe to newsletters or send yourself documents. Newsletters and documents will be categorized and added to your library when we receive a message. View all received emails with the "Recently Received Emails" link at the bottom of this page.',
|
||||||
'Create an Omnivore email address and use it to subscribe to newsletters or send yourself documents. Newsletters and documents will be categorized and added to your library when we receive a message. View all received emails with the "Recently Received Emails" link at the bottom of this page.',
|
docs: 'https://docs.omnivore.app/using/inbox.html',
|
||||||
docs: 'https://docs.omnivore.app/using/inbox.html',
|
key: '--settings-emails-show-help',
|
||||||
key: '--settings-emails-show-help',
|
CTAText: 'Create an email address',
|
||||||
CTAText: 'Create an email address',
|
onClickCTA: () => {
|
||||||
onClickCTA: () => {
|
createEmail()
|
||||||
createEmail()
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{sortedEmailAddresses.length > 0 ? (
|
||||||
|
sortedEmailAddresses.map((email, i) => {
|
||||||
|
return (
|
||||||
|
<SettingsTableRow
|
||||||
|
key={email.address}
|
||||||
|
title={email.address}
|
||||||
|
isLast={i === sortedEmailAddresses.length - 1}
|
||||||
|
onDelete={() => setConfirmDeleteEmailId(email.id)}
|
||||||
|
deleteTitle="Delete"
|
||||||
|
sublineElement={
|
||||||
|
<StyledText
|
||||||
|
css={{
|
||||||
|
my: '5px',
|
||||||
|
fontSize: '11px',
|
||||||
|
a: {
|
||||||
|
color: '$omnivoreCtaYellow',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{`created ${formattedShortDate(email.createdAt)}, `}
|
||||||
|
<Link href="/settings/subscriptions" legacyBehavior>{`${email.subscriptionCount} subscriptions`}</Link>
|
||||||
|
</StyledText>
|
||||||
|
}
|
||||||
|
titleElement={
|
||||||
|
<CopyTextBtnWrapper
|
||||||
|
css={{
|
||||||
|
marginLeft: '20px',
|
||||||
|
'@mdDown': {
|
||||||
|
marginRight: '10px',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CopyTextButton
|
||||||
|
text={email.address}
|
||||||
|
type={TextType.EmailAddress}
|
||||||
|
/>
|
||||||
|
</CopyTextBtnWrapper>
|
||||||
|
}
|
||||||
|
extraElement={
|
||||||
|
email.confirmationCode ? (
|
||||||
|
<HStack
|
||||||
|
alignment="start"
|
||||||
|
distribution="center"
|
||||||
|
css={{
|
||||||
|
width: '100%',
|
||||||
|
backgroundColor: '$grayBgActive',
|
||||||
|
borderRadius: '6px',
|
||||||
|
padding: '4px 4px 4px 0px',
|
||||||
|
'@md': {
|
||||||
|
width: '30%',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<StyledText
|
||||||
|
css={{
|
||||||
|
fontSize: '11px',
|
||||||
|
'@md': {
|
||||||
|
marginTop: '5px',
|
||||||
|
},
|
||||||
|
'@mdDown': {
|
||||||
|
marginLeft: 'auto',
|
||||||
|
},
|
||||||
|
marginRight: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{`Gmail: ${email.confirmationCode}`}
|
||||||
|
</StyledText>
|
||||||
|
<Box>
|
||||||
|
<CopyTextBtnWrapper>
|
||||||
|
<CopyTextButton
|
||||||
|
text={email.confirmationCode || ''}
|
||||||
|
type={TextType.ConfirmationCode}
|
||||||
|
/>
|
||||||
|
</CopyTextBtnWrapper>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
</HStack>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<EmptySettingsRow
|
||||||
|
text={isValidating ? '-' : 'No Email Addresses Found'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<SpanBox
|
||||||
|
css={{
|
||||||
|
pt: '15px',
|
||||||
|
fontSize: '12px',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
a: {
|
||||||
|
color: '$omnivoreCtaYellow',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{sortedEmailAddresses.length > 0 ? (
|
<Link href="/settings/emails/recent">
|
||||||
sortedEmailAddresses.map((email, i) => {
|
View recently received emails
|
||||||
return (
|
</Link>
|
||||||
<SettingsTableRow
|
</SpanBox>
|
||||||
key={email.address}
|
</SettingsTable>
|
||||||
title={email.address}
|
|
||||||
isLast={i === sortedEmailAddresses.length - 1}
|
|
||||||
onDelete={() => setConfirmDeleteEmailId(email.id)}
|
|
||||||
deleteTitle="Delete"
|
|
||||||
sublineElement={
|
|
||||||
<StyledText
|
|
||||||
css={{
|
|
||||||
my: '5px',
|
|
||||||
fontSize: '11px',
|
|
||||||
a: {
|
|
||||||
color: '$omnivoreCtaYellow',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{`created ${formattedShortDate(email.createdAt)}, `}
|
|
||||||
<Link href="/settings/subscriptions">{`${email.subscriptionCount} subscriptions`}</Link>
|
|
||||||
</StyledText>
|
|
||||||
}
|
|
||||||
titleElement={
|
|
||||||
<CopyTextBtnWrapper
|
|
||||||
css={{
|
|
||||||
marginLeft: '20px',
|
|
||||||
'@mdDown': {
|
|
||||||
marginRight: '10px',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CopyTextButton
|
|
||||||
text={email.address}
|
|
||||||
type={TextType.EmailAddress}
|
|
||||||
/>
|
|
||||||
</CopyTextBtnWrapper>
|
|
||||||
}
|
|
||||||
extraElement={
|
|
||||||
email.confirmationCode ? (
|
|
||||||
<HStack
|
|
||||||
alignment="start"
|
|
||||||
distribution="center"
|
|
||||||
css={{
|
|
||||||
width: '100%',
|
|
||||||
backgroundColor: '$grayBgActive',
|
|
||||||
borderRadius: '6px',
|
|
||||||
padding: '4px 4px 4px 0px',
|
|
||||||
'@md': {
|
|
||||||
width: '30%',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
<StyledText
|
|
||||||
css={{
|
|
||||||
fontSize: '11px',
|
|
||||||
'@md': {
|
|
||||||
marginTop: '5px',
|
|
||||||
},
|
|
||||||
'@mdDown': {
|
|
||||||
marginLeft: 'auto',
|
|
||||||
},
|
|
||||||
marginRight: '10px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{`Gmail: ${email.confirmationCode}`}
|
|
||||||
</StyledText>
|
|
||||||
<Box>
|
|
||||||
<CopyTextBtnWrapper>
|
|
||||||
<CopyTextButton
|
|
||||||
text={email.confirmationCode || ''}
|
|
||||||
type={TextType.ConfirmationCode}
|
|
||||||
/>
|
|
||||||
</CopyTextBtnWrapper>
|
|
||||||
</Box>
|
|
||||||
</>
|
|
||||||
</HStack>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<EmptySettingsRow
|
|
||||||
text={isValidating ? '-' : 'No Email Addresses Found'}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<SpanBox
|
|
||||||
css={{
|
|
||||||
pt: '15px',
|
|
||||||
fontSize: '12px',
|
|
||||||
marginLeft: 'auto',
|
|
||||||
a: {
|
|
||||||
color: '$omnivoreCtaYellow',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Link href="/settings/emails/recent">
|
|
||||||
View recently received emails
|
|
||||||
</Link>
|
|
||||||
</SpanBox>
|
|
||||||
</SettingsTable>
|
|
||||||
|
|
||||||
{confirmDeleteEmailId ? (
|
{confirmDeleteEmailId ? (
|
||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
message={
|
message={
|
||||||
'Are you sure? You will stop receiving emails sent to this address.'
|
'Are you sure? You will stop receiving emails sent to this address.'
|
||||||
}
|
}
|
||||||
onAccept={async () => {
|
onAccept={async () => {
|
||||||
await deleteEmail(confirmDeleteEmailId)
|
await deleteEmail(confirmDeleteEmailId)
|
||||||
setConfirmDeleteEmailId(undefined)
|
setConfirmDeleteEmailId(undefined)
|
||||||
}}
|
}}
|
||||||
onOpenChange={() => setConfirmDeleteEmailId(undefined)}
|
onOpenChange={() => setConfirmDeleteEmailId(undefined)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>;
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export default function SubscriptionsPage(): JSX.Element {
|
|||||||
at{' '}
|
at{' '}
|
||||||
<Link
|
<Link
|
||||||
href={`/settings/emails?address=${subscription.newsletterEmail}`}
|
href={`/settings/emails?address=${subscription.newsletterEmail}`}
|
||||||
>
|
legacyBehavior>
|
||||||
{subscription.newsletterEmail}
|
{subscription.newsletterEmail}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
@ -101,7 +101,7 @@ export default function SubscriptionsPage(): JSX.Element {
|
|||||||
</StyledText>
|
</StyledText>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
<EmptySettingsRow
|
<EmptySettingsRow
|
||||||
@ -125,5 +125,5 @@ export default function SubscriptionsPage(): JSX.Element {
|
|||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
</SettingsTable>
|
</SettingsTable>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 345 KiB After Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 487 KiB After Width: | Height: | Size: 487 KiB |
|
Before Width: | Height: | Size: 264 KiB After Width: | Height: | Size: 264 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 400 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 626 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 346 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 839 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 982 KiB |
|
Before Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 286 KiB |
|
Before Width: | Height: | Size: 68 KiB |