Add AnyClip integration tools and extracted source code
- Add authentication scripts with SubtleCrypto password encryption - Add sourcemap extraction pipeline (update-urls, download-sourcemaps, extract-sources) - Add Playwright API interception script for monetization endpoints - Document two-step auth flow with JWT tokens and dual cookies - Move extracted source from root to anyclip/ directory - Add project configuration (.env.example, .gitignore, CLAUDE.md)
This commit is contained in:
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ANYCLIP_EMAIL=your-email@example.com
|
||||||
|
ANYCLIP_PASSWORD=your-password
|
||||||
41
.gitignore
vendored
Normal file
41
.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# dependencies (bun install)
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# output
|
||||||
|
out
|
||||||
|
dist
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.eslintcache
|
||||||
|
.cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
captured-apis.json
|
||||||
|
session.json
|
||||||
|
|
||||||
|
# Raw sourcemaps (large binary files)
|
||||||
|
sourcemaps/
|
||||||
|
|
||||||
151
CLAUDE.md
Normal file
151
CLAUDE.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
---
|
||||||
|
description: AnyClip integration tools - use Bun, understand the scripts
|
||||||
|
globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json"
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Project: AnyClip Integration
|
||||||
|
|
||||||
|
Tools for integrating with AnyClip's Video Manager API and extracting source code from sourcemaps.
|
||||||
|
|
||||||
|
## Key Scripts
|
||||||
|
|
||||||
|
| Script | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `scripts/auth.ts` | Authenticate with AnyClip, returns session cookies |
|
||||||
|
| `scripts/crypto-subtle.ts` | Password encryption (AES-256-GCM, matches AnyClip's client) |
|
||||||
|
| `scripts/update-urls.ts` | Fetch JS URLs from build manifest → urls.txt |
|
||||||
|
| `scripts/download-sourcemaps.ts` | Download .map files for URLs in urls.txt |
|
||||||
|
| `scripts/extract-sources.ts` | Extract source from sourcemaps → anyclip/ |
|
||||||
|
|
||||||
|
## Authentication Flow
|
||||||
|
|
||||||
|
1. Password encrypted client-side with AES-256-GCM (salt: `$2b$04$wwky7rvtr6BFNaCqntwyie`)
|
||||||
|
2. POST to `videomanager-api.anyclip.com/public/auth/login` → returns `anyclip_2020` cookie + JWT
|
||||||
|
3. POST to `videomanager.anyclip.com/api/auth/login` → returns `session` cookie
|
||||||
|
4. Both cookies required for authenticated requests
|
||||||
|
|
||||||
|
## Source Extraction Workflow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun scripts/update-urls.ts # Scrape build manifest for JS URLs
|
||||||
|
bun scripts/download-sourcemaps.ts # Download .map files
|
||||||
|
bun scripts/extract-sources.ts # Extract to anyclip/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
- `anyclip/` - Extracted source (committed)
|
||||||
|
- `sourcemaps/` - Raw .map files (gitignored)
|
||||||
|
- `urls.txt` - JS URLs to download
|
||||||
|
- `session.json` - Auth session (gitignored)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Bun Usage
|
||||||
|
|
||||||
|
Default to using Bun instead of Node.js.
|
||||||
|
|
||||||
|
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
|
||||||
|
- Use `bun test` instead of `jest` or `vitest`
|
||||||
|
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
|
||||||
|
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
|
||||||
|
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
|
||||||
|
- Use `bunx <package> <command>` instead of `npx <package> <command>`
|
||||||
|
- Bun automatically loads .env, so don't use dotenv.
|
||||||
|
|
||||||
|
## APIs
|
||||||
|
|
||||||
|
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
|
||||||
|
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
|
||||||
|
- `Bun.redis` for Redis. Don't use `ioredis`.
|
||||||
|
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
|
||||||
|
- `WebSocket` is built-in. Don't use `ws`.
|
||||||
|
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
|
||||||
|
- Bun.$`ls` instead of execa.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Use `bun test` to run tests.
|
||||||
|
|
||||||
|
```ts#index.test.ts
|
||||||
|
import { test, expect } from "bun:test";
|
||||||
|
|
||||||
|
test("hello world", () => {
|
||||||
|
expect(1).toBe(1);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
|
||||||
|
|
||||||
|
Server:
|
||||||
|
|
||||||
|
```ts#index.ts
|
||||||
|
import index from "./index.html"
|
||||||
|
|
||||||
|
Bun.serve({
|
||||||
|
routes: {
|
||||||
|
"/": index,
|
||||||
|
"/api/users/:id": {
|
||||||
|
GET: (req) => {
|
||||||
|
return new Response(JSON.stringify({ id: req.params.id }));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// optional websocket support
|
||||||
|
websocket: {
|
||||||
|
open: (ws) => {
|
||||||
|
ws.send("Hello, world!");
|
||||||
|
},
|
||||||
|
message: (ws, message) => {
|
||||||
|
ws.send(message);
|
||||||
|
},
|
||||||
|
close: (ws) => {
|
||||||
|
// handle close
|
||||||
|
}
|
||||||
|
},
|
||||||
|
development: {
|
||||||
|
hmr: true,
|
||||||
|
console: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
|
||||||
|
|
||||||
|
```html#index.html
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Hello, world!</h1>
|
||||||
|
<script type="module" src="./frontend.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
With the following `frontend.tsx`:
|
||||||
|
|
||||||
|
```tsx#frontend.tsx
|
||||||
|
import React from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
|
||||||
|
// import .css files directly and it works
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
const root = createRoot(document.body);
|
||||||
|
|
||||||
|
export default function Frontend() {
|
||||||
|
return <h1>Hello, world!</h1>;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.render(<Frontend />);
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, run index.ts
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bun --hot ./index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
|
||||||
113
README.md
113
README.md
@@ -1,55 +1,78 @@
|
|||||||
# AnyClip Video Manager - Extracted Source
|
# AnyClip Integration
|
||||||
|
|
||||||
Source code extracted from sourcemaps of `videomanager.anyclip.com`.
|
Tools for integrating with AnyClip's Video Manager API.
|
||||||
|
|
||||||
## Overview
|
## Setup
|
||||||
|
|
||||||
Next.js application for video content management, analytics, and publishing.
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
## Structure
|
# Edit .env with your AnyClip credentials
|
||||||
|
bun install
|
||||||
```
|
|
||||||
├── src/
|
|
||||||
│ ├── modules/ # Feature modules (business logic)
|
|
||||||
│ ├── pages/ # Next.js page components
|
|
||||||
│ ├── client/ # Client-side utilities
|
|
||||||
│ ├── shared/ # Shared libraries
|
|
||||||
│ └── assets/
|
|
||||||
├── pages/ # Root Next.js pages (_app.tsx, _error.tsx)
|
|
||||||
├── client/ # Next.js client runtime
|
|
||||||
├── vendor/ # Bundled node_modules
|
|
||||||
└── sourcemaps/ # Original .map files
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Modules (`src/modules/`)
|
## Project Structure
|
||||||
|
|
||||||
| Module | Description |
|
```
|
||||||
|--------|-------------|
|
├── anyclip/ # Extracted source code (from sourcemaps)
|
||||||
| `analytics/` | Dashboards - monetization, video performance, custom reports |
|
├── docs/ # Documentation
|
||||||
| `editorial/` | Video editing - tagging, search, bulk actions, video details |
|
├── scripts/ # CLI tools
|
||||||
| `publishing/` | Content publishing and destination management |
|
├── sourcemaps/ # Raw .map files (gitignored)
|
||||||
| `marketplace/` | Marketplace accounts and dashboard |
|
└── urls.txt # JS file URLs to download
|
||||||
| `xRay/` | X-Ray - campaigns, creatives, line items |
|
```
|
||||||
| `hubs/` | Content hubs management |
|
|
||||||
| `users/` | User management |
|
|
||||||
| `invitations/` | User invitation system |
|
|
||||||
| `forms/` | Form builder/management |
|
|
||||||
| `uploaderNew/` | Video upload functionality |
|
|
||||||
| `userRulesSettings/` | User rules and settings |
|
|
||||||
| `layout/` | App layout and Redux state |
|
|
||||||
| `common/` | Shared components - forms, tables, lists, tag selectors |
|
|
||||||
|
|
||||||
## Pages (`src/pages/`)
|
## Scripts
|
||||||
|
|
||||||
- `/analytics` - Analytics dashboard
|
### Authentication
|
||||||
- `/studio` - Studio interface
|
|
||||||
- `/personal-settings` - User settings
|
|
||||||
- `/hubs`, `/users`, `/invitations`, `/forms` - Management pages
|
|
||||||
- `/x-ray/*` - Campaign, creative, and line item analytics
|
|
||||||
|
|
||||||
## Tech Stack
|
```bash
|
||||||
|
# Login and save session to session.json
|
||||||
|
bun scripts/auth.ts
|
||||||
|
```
|
||||||
|
|
||||||
- Next.js, React, TypeScript
|
Programmatic usage:
|
||||||
- Redux (state management)
|
|
||||||
- Material-UI (components)
|
```typescript
|
||||||
- Victory/D3 (charts)
|
import { login, getAuthHeaders } from './scripts/auth';
|
||||||
|
|
||||||
|
const session = await login(email, password);
|
||||||
|
const headers = getAuthHeaders(session);
|
||||||
|
// session.cookies contains both required cookies
|
||||||
|
```
|
||||||
|
|
||||||
|
See [docs/auth.md](docs/auth.md) for details on the two-step auth flow.
|
||||||
|
|
||||||
|
### Source Extraction
|
||||||
|
|
||||||
|
Extract AnyClip's frontend source from public sourcemaps:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Update urls.txt from build manifest
|
||||||
|
bun scripts/update-urls.ts
|
||||||
|
|
||||||
|
# 2. Download sourcemaps for all URLs
|
||||||
|
bun scripts/download-sourcemaps.ts
|
||||||
|
|
||||||
|
# 3. Extract source files to anyclip/
|
||||||
|
bun scripts/extract-sources.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Or run all three:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun scripts/update-urls.ts && bun scripts/download-sourcemaps.ts && bun scripts/extract-sources.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Script Options
|
||||||
|
|
||||||
|
**extract-sources.ts:**
|
||||||
|
```bash
|
||||||
|
bun scripts/extract-sources.ts [options]
|
||||||
|
--output, -o <dir> Output directory (default: anyclip)
|
||||||
|
--input, -i <dir> Sourcemaps directory (default: sourcemaps)
|
||||||
|
--verbose, -v Verbose output
|
||||||
|
--no-clean Don't delete output directory first
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [docs/auth.md](docs/auth.md) - Authentication system (two-step flow, cookies, encryption)
|
||||||
|
|||||||
35
anyclip/client/get-domain-locale.ts
Normal file
35
anyclip/client/get-domain-locale.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import type { DomainLocale } from '../server/config'
|
||||||
|
import type { normalizeLocalePath as NormalizeFn } from './normalize-locale-path'
|
||||||
|
import type { detectDomainLocale as DetectFn } from './detect-domain-locale'
|
||||||
|
import { normalizePathTrailingSlash } from './normalize-trailing-slash'
|
||||||
|
|
||||||
|
const basePath = (process.env.__NEXT_ROUTER_BASEPATH as string) || ''
|
||||||
|
|
||||||
|
export function getDomainLocale(
|
||||||
|
path: string,
|
||||||
|
locale?: string | false,
|
||||||
|
locales?: readonly string[],
|
||||||
|
domainLocales?: readonly DomainLocale[]
|
||||||
|
) {
|
||||||
|
if (process.env.__NEXT_I18N_SUPPORT) {
|
||||||
|
const normalizeLocalePath: typeof NormalizeFn = (
|
||||||
|
require('./normalize-locale-path') as typeof import('./normalize-locale-path')
|
||||||
|
).normalizeLocalePath
|
||||||
|
const detectDomainLocale: typeof DetectFn = (
|
||||||
|
require('./detect-domain-locale') as typeof import('./detect-domain-locale')
|
||||||
|
).detectDomainLocale
|
||||||
|
|
||||||
|
const target = locale || normalizeLocalePath(path, locales).detectedLocale
|
||||||
|
const domain = detectDomainLocale(domainLocales, undefined, target)
|
||||||
|
if (domain) {
|
||||||
|
const proto = `http${domain.http ? '' : 's'}://`
|
||||||
|
const finalLocale = target === domain.defaultLocale ? '' : `/${target}`
|
||||||
|
return `${proto}${domain.domain}${normalizePathTrailingSlash(
|
||||||
|
`${basePath}${finalLocale}${path}`
|
||||||
|
)}`
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
421
anyclip/client/image-component.tsx
Normal file
421
anyclip/client/image-component.tsx
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import React, {
|
||||||
|
useRef,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
forwardRef,
|
||||||
|
use,
|
||||||
|
} from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import Head from '../shared/lib/head'
|
||||||
|
import { getImgProps } from '../shared/lib/get-img-props'
|
||||||
|
import type {
|
||||||
|
ImageProps,
|
||||||
|
ImgProps,
|
||||||
|
OnLoad,
|
||||||
|
OnLoadingComplete,
|
||||||
|
PlaceholderValue,
|
||||||
|
} from '../shared/lib/get-img-props'
|
||||||
|
import type {
|
||||||
|
ImageConfigComplete,
|
||||||
|
ImageLoaderProps,
|
||||||
|
} from '../shared/lib/image-config'
|
||||||
|
import { imageConfigDefault } from '../shared/lib/image-config'
|
||||||
|
import { ImageConfigContext } from '../shared/lib/image-config-context.shared-runtime'
|
||||||
|
import { warnOnce } from '../shared/lib/utils/warn-once'
|
||||||
|
import { RouterContext } from '../shared/lib/router-context.shared-runtime'
|
||||||
|
|
||||||
|
// This is replaced by webpack alias
|
||||||
|
import defaultLoader from 'next/dist/shared/lib/image-loader'
|
||||||
|
import { useMergedRef } from './use-merged-ref'
|
||||||
|
|
||||||
|
// This is replaced by webpack define plugin
|
||||||
|
const configEnv = process.env.__NEXT_IMAGE_OPTS as any as ImageConfigComplete
|
||||||
|
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
;(globalThis as any).__NEXT_IMAGE_IMPORTED = true
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { ImageLoaderProps }
|
||||||
|
export type ImageLoader = (p: ImageLoaderProps) => string
|
||||||
|
|
||||||
|
type ImgElementWithDataProp = HTMLImageElement & {
|
||||||
|
'data-loaded-src': string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageElementProps = ImgProps & {
|
||||||
|
unoptimized: boolean
|
||||||
|
placeholder: PlaceholderValue
|
||||||
|
onLoadRef: React.MutableRefObject<OnLoad | undefined>
|
||||||
|
onLoadingCompleteRef: React.MutableRefObject<OnLoadingComplete | undefined>
|
||||||
|
setBlurComplete: (b: boolean) => void
|
||||||
|
setShowAltText: (b: boolean) => void
|
||||||
|
sizesInput: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://stackoverflow.com/q/39777833/266535 for why we use this ref
|
||||||
|
// handler instead of the img's onLoad attribute.
|
||||||
|
function handleLoading(
|
||||||
|
img: ImgElementWithDataProp,
|
||||||
|
placeholder: PlaceholderValue,
|
||||||
|
onLoadRef: React.MutableRefObject<OnLoad | undefined>,
|
||||||
|
onLoadingCompleteRef: React.MutableRefObject<OnLoadingComplete | undefined>,
|
||||||
|
setBlurComplete: (b: boolean) => void,
|
||||||
|
unoptimized: boolean,
|
||||||
|
sizesInput: string | undefined
|
||||||
|
) {
|
||||||
|
const src = img?.src
|
||||||
|
if (!img || img['data-loaded-src'] === src) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
img['data-loaded-src'] = src
|
||||||
|
const p = 'decode' in img ? img.decode() : Promise.resolve()
|
||||||
|
p.catch(() => {}).then(() => {
|
||||||
|
if (!img.parentElement || !img.isConnected) {
|
||||||
|
// Exit early in case of race condition:
|
||||||
|
// - onload() is called
|
||||||
|
// - decode() is called but incomplete
|
||||||
|
// - unmount is called
|
||||||
|
// - decode() completes
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (placeholder !== 'empty') {
|
||||||
|
setBlurComplete(true)
|
||||||
|
}
|
||||||
|
if (onLoadRef?.current) {
|
||||||
|
// Since we don't have the SyntheticEvent here,
|
||||||
|
// we must create one with the same shape.
|
||||||
|
// See https://reactjs.org/docs/events.html
|
||||||
|
const event = new Event('load')
|
||||||
|
Object.defineProperty(event, 'target', { writable: false, value: img })
|
||||||
|
let prevented = false
|
||||||
|
let stopped = false
|
||||||
|
onLoadRef.current({
|
||||||
|
...event,
|
||||||
|
nativeEvent: event,
|
||||||
|
currentTarget: img,
|
||||||
|
target: img,
|
||||||
|
isDefaultPrevented: () => prevented,
|
||||||
|
isPropagationStopped: () => stopped,
|
||||||
|
persist: () => {},
|
||||||
|
preventDefault: () => {
|
||||||
|
prevented = true
|
||||||
|
event.preventDefault()
|
||||||
|
},
|
||||||
|
stopPropagation: () => {
|
||||||
|
stopped = true
|
||||||
|
event.stopPropagation()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (onLoadingCompleteRef?.current) {
|
||||||
|
onLoadingCompleteRef.current(img)
|
||||||
|
}
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
const origSrc = new URL(src, 'http://n').searchParams.get('url') || src
|
||||||
|
if (img.getAttribute('data-nimg') === 'fill') {
|
||||||
|
if (!unoptimized && (!sizesInput || sizesInput === '100vw')) {
|
||||||
|
let widthViewportRatio =
|
||||||
|
img.getBoundingClientRect().width / window.innerWidth
|
||||||
|
if (widthViewportRatio < 0.6) {
|
||||||
|
if (sizesInput === '100vw') {
|
||||||
|
warnOnce(
|
||||||
|
`Image with src "${origSrc}" has "fill" prop and "sizes" prop of "100vw", but image is not rendered at full viewport width. Please adjust "sizes" to improve page performance. Read more: https://nextjs.org/docs/api-reference/next/image#sizes`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
warnOnce(
|
||||||
|
`Image with src "${origSrc}" has "fill" but is missing "sizes" prop. Please add it to improve page performance. Read more: https://nextjs.org/docs/api-reference/next/image#sizes`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (img.parentElement) {
|
||||||
|
const { position } = window.getComputedStyle(img.parentElement)
|
||||||
|
const valid = ['absolute', 'fixed', 'relative']
|
||||||
|
if (!valid.includes(position)) {
|
||||||
|
warnOnce(
|
||||||
|
`Image with src "${origSrc}" has "fill" and parent element with invalid "position". Provided "${position}" should be one of ${valid
|
||||||
|
.map(String)
|
||||||
|
.join(',')}.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (img.height === 0) {
|
||||||
|
warnOnce(
|
||||||
|
`Image with src "${origSrc}" has "fill" and a height value of 0. This is likely because the parent element of the image has not been styled to have a set height.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const heightModified =
|
||||||
|
img.height.toString() !== img.getAttribute('height')
|
||||||
|
const widthModified = img.width.toString() !== img.getAttribute('width')
|
||||||
|
if (
|
||||||
|
(heightModified && !widthModified) ||
|
||||||
|
(!heightModified && widthModified)
|
||||||
|
) {
|
||||||
|
warnOnce(
|
||||||
|
`Image with src "${origSrc}" has either width or height modified, but not the other. If you use CSS to change the size of your image, also include the styles 'width: "auto"' or 'height: "auto"' to maintain the aspect ratio.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDynamicProps(
|
||||||
|
fetchPriority?: string
|
||||||
|
): Record<string, string | undefined> {
|
||||||
|
if (Boolean(use)) {
|
||||||
|
// In React 19.0.0 or newer, we must use camelCase
|
||||||
|
// prop to avoid "Warning: Invalid DOM property".
|
||||||
|
// See https://github.com/facebook/react/pull/25927
|
||||||
|
return { fetchPriority }
|
||||||
|
}
|
||||||
|
// In React 18.2.0 or older, we must use lowercase prop
|
||||||
|
// to avoid "Warning: Invalid DOM property".
|
||||||
|
return { fetchpriority: fetchPriority }
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageElement = forwardRef<HTMLImageElement | null, ImageElementProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
src,
|
||||||
|
srcSet,
|
||||||
|
sizes,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
decoding,
|
||||||
|
className,
|
||||||
|
style,
|
||||||
|
fetchPriority,
|
||||||
|
placeholder,
|
||||||
|
loading,
|
||||||
|
unoptimized,
|
||||||
|
fill,
|
||||||
|
onLoadRef,
|
||||||
|
onLoadingCompleteRef,
|
||||||
|
setBlurComplete,
|
||||||
|
setShowAltText,
|
||||||
|
sizesInput,
|
||||||
|
onLoad,
|
||||||
|
onError,
|
||||||
|
...rest
|
||||||
|
},
|
||||||
|
forwardedRef
|
||||||
|
) => {
|
||||||
|
const ownRef = useCallback(
|
||||||
|
(img: ImgElementWithDataProp | null) => {
|
||||||
|
if (!img) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (onError) {
|
||||||
|
// If the image has an error before react hydrates, then the error is lost.
|
||||||
|
// The workaround is to wait until the image is mounted which is after hydration,
|
||||||
|
// then we set the src again to trigger the error handler (if there was an error).
|
||||||
|
// eslint-disable-next-line no-self-assign
|
||||||
|
img.src = img.src
|
||||||
|
}
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
if (!src) {
|
||||||
|
console.error(`Image is missing required "src" property:`, img)
|
||||||
|
}
|
||||||
|
if (img.getAttribute('alt') === null) {
|
||||||
|
console.error(
|
||||||
|
`Image is missing required "alt" property. Please add Alternative Text to describe the image for screen readers and search engines.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (img.complete) {
|
||||||
|
handleLoading(
|
||||||
|
img,
|
||||||
|
placeholder,
|
||||||
|
onLoadRef,
|
||||||
|
onLoadingCompleteRef,
|
||||||
|
setBlurComplete,
|
||||||
|
unoptimized,
|
||||||
|
sizesInput
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
src,
|
||||||
|
placeholder,
|
||||||
|
onLoadRef,
|
||||||
|
onLoadingCompleteRef,
|
||||||
|
setBlurComplete,
|
||||||
|
onError,
|
||||||
|
unoptimized,
|
||||||
|
sizesInput,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
const ref = useMergedRef(forwardedRef, ownRef)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
{...rest}
|
||||||
|
{...getDynamicProps(fetchPriority)}
|
||||||
|
// It's intended to keep `loading` before `src` because React updates
|
||||||
|
// props in order which causes Safari/Firefox to not lazy load properly.
|
||||||
|
// See https://github.com/facebook/react/issues/25883
|
||||||
|
loading={loading}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
decoding={decoding}
|
||||||
|
data-nimg={fill ? 'fill' : '1'}
|
||||||
|
className={className}
|
||||||
|
style={style}
|
||||||
|
// It's intended to keep `src` the last attribute because React updates
|
||||||
|
// attributes in order. If we keep `src` the first one, Safari will
|
||||||
|
// immediately start to fetch `src`, before `sizes` and `srcSet` are even
|
||||||
|
// updated by React. That causes multiple unnecessary requests if `srcSet`
|
||||||
|
// and `sizes` are defined.
|
||||||
|
// This bug cannot be reproduced in Chrome or Firefox.
|
||||||
|
sizes={sizes}
|
||||||
|
srcSet={srcSet}
|
||||||
|
src={src}
|
||||||
|
ref={ref}
|
||||||
|
onLoad={(event) => {
|
||||||
|
const img = event.currentTarget as ImgElementWithDataProp
|
||||||
|
handleLoading(
|
||||||
|
img,
|
||||||
|
placeholder,
|
||||||
|
onLoadRef,
|
||||||
|
onLoadingCompleteRef,
|
||||||
|
setBlurComplete,
|
||||||
|
unoptimized,
|
||||||
|
sizesInput
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
onError={(event) => {
|
||||||
|
// if the real image fails to load, this will ensure "alt" is visible
|
||||||
|
setShowAltText(true)
|
||||||
|
if (placeholder !== 'empty') {
|
||||||
|
// If the real image fails to load, this will still remove the placeholder.
|
||||||
|
setBlurComplete(true)
|
||||||
|
}
|
||||||
|
if (onError) {
|
||||||
|
onError(event)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function ImagePreload({
|
||||||
|
isAppRouter,
|
||||||
|
imgAttributes,
|
||||||
|
}: {
|
||||||
|
isAppRouter: boolean
|
||||||
|
imgAttributes: ImgProps
|
||||||
|
}) {
|
||||||
|
const opts: ReactDOM.PreloadOptions = {
|
||||||
|
as: 'image',
|
||||||
|
imageSrcSet: imgAttributes.srcSet,
|
||||||
|
imageSizes: imgAttributes.sizes,
|
||||||
|
crossOrigin: imgAttributes.crossOrigin,
|
||||||
|
referrerPolicy: imgAttributes.referrerPolicy,
|
||||||
|
...getDynamicProps(imgAttributes.fetchPriority),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAppRouter && ReactDOM.preload) {
|
||||||
|
ReactDOM.preload(imgAttributes.src, opts)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Head>
|
||||||
|
<link
|
||||||
|
key={
|
||||||
|
'__nimg-' +
|
||||||
|
imgAttributes.src +
|
||||||
|
imgAttributes.srcSet +
|
||||||
|
imgAttributes.sizes
|
||||||
|
}
|
||||||
|
rel="preload"
|
||||||
|
// Note how we omit the `href` attribute, as it would only be relevant
|
||||||
|
// for browsers that do not support `imagesrcset`, and in those cases
|
||||||
|
// it would cause the incorrect image to be preloaded.
|
||||||
|
//
|
||||||
|
// https://html.spec.whatwg.org/multipage/semantics.html#attr-link-imagesrcset
|
||||||
|
href={imgAttributes.srcSet ? undefined : imgAttributes.src}
|
||||||
|
{...opts}
|
||||||
|
/>
|
||||||
|
</Head>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `Image` component is used to optimize images.
|
||||||
|
*
|
||||||
|
* Read more: [Next.js docs: `Image`](https://nextjs.org/docs/app/api-reference/components/image)
|
||||||
|
*/
|
||||||
|
export const Image = forwardRef<HTMLImageElement | null, ImageProps>(
|
||||||
|
(props, forwardedRef) => {
|
||||||
|
const pagesRouter = useContext(RouterContext)
|
||||||
|
// We're in the app directory if there is no pages router.
|
||||||
|
const isAppRouter = !pagesRouter
|
||||||
|
|
||||||
|
const configContext = useContext(ImageConfigContext)
|
||||||
|
const config = useMemo(() => {
|
||||||
|
const c = configEnv || configContext || imageConfigDefault
|
||||||
|
const allSizes = [...c.deviceSizes, ...c.imageSizes].sort((a, b) => a - b)
|
||||||
|
const deviceSizes = c.deviceSizes.sort((a, b) => a - b)
|
||||||
|
const qualities = c.qualities?.sort((a, b) => a - b)
|
||||||
|
return { ...c, allSizes, deviceSizes, qualities }
|
||||||
|
}, [configContext])
|
||||||
|
|
||||||
|
const { onLoad, onLoadingComplete } = props
|
||||||
|
const onLoadRef = useRef(onLoad)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onLoadRef.current = onLoad
|
||||||
|
}, [onLoad])
|
||||||
|
|
||||||
|
const onLoadingCompleteRef = useRef(onLoadingComplete)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onLoadingCompleteRef.current = onLoadingComplete
|
||||||
|
}, [onLoadingComplete])
|
||||||
|
|
||||||
|
const [blurComplete, setBlurComplete] = useState(false)
|
||||||
|
const [showAltText, setShowAltText] = useState(false)
|
||||||
|
|
||||||
|
const { props: imgAttributes, meta: imgMeta } = getImgProps(props, {
|
||||||
|
defaultLoader,
|
||||||
|
imgConf: config,
|
||||||
|
blurComplete,
|
||||||
|
showAltText,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
<ImageElement
|
||||||
|
{...imgAttributes}
|
||||||
|
unoptimized={imgMeta.unoptimized}
|
||||||
|
placeholder={imgMeta.placeholder}
|
||||||
|
fill={imgMeta.fill}
|
||||||
|
onLoadRef={onLoadRef}
|
||||||
|
onLoadingCompleteRef={onLoadingCompleteRef}
|
||||||
|
setBlurComplete={setBlurComplete}
|
||||||
|
setShowAltText={setShowAltText}
|
||||||
|
sizesInput={props.sizes}
|
||||||
|
ref={forwardedRef}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{imgMeta.priority ? (
|
||||||
|
<ImagePreload
|
||||||
|
isAppRouter={isAppRouter}
|
||||||
|
imgAttributes={imgAttributes}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
717
anyclip/client/link.tsx
Normal file
717
anyclip/client/link.tsx
Normal file
@@ -0,0 +1,717 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
NextRouter,
|
||||||
|
PrefetchOptions as RouterPrefetchOptions,
|
||||||
|
} from '../shared/lib/router/router'
|
||||||
|
|
||||||
|
import React, { createContext, useContext } from 'react'
|
||||||
|
import type { UrlObject } from 'url'
|
||||||
|
import { resolveHref } from './resolve-href'
|
||||||
|
import { isLocalURL } from '../shared/lib/router/utils/is-local-url'
|
||||||
|
import { formatUrl } from '../shared/lib/router/utils/format-url'
|
||||||
|
import { isAbsoluteUrl } from '../shared/lib/utils'
|
||||||
|
import { addLocale } from './add-locale'
|
||||||
|
import { RouterContext } from '../shared/lib/router-context.shared-runtime'
|
||||||
|
import type { AppRouterInstance } from '../shared/lib/app-router-context.shared-runtime'
|
||||||
|
import { useIntersection } from './use-intersection'
|
||||||
|
import { getDomainLocale } from './get-domain-locale'
|
||||||
|
import { addBasePath } from './add-base-path'
|
||||||
|
import { useMergedRef } from './use-merged-ref'
|
||||||
|
import { errorOnce } from '../shared/lib/utils/error-once'
|
||||||
|
|
||||||
|
type Url = string | UrlObject
|
||||||
|
type RequiredKeys<T> = {
|
||||||
|
[K in keyof T]-?: {} extends Pick<T, K> ? never : K
|
||||||
|
}[keyof T]
|
||||||
|
type OptionalKeys<T> = {
|
||||||
|
[K in keyof T]-?: {} extends Pick<T, K> ? K : never
|
||||||
|
}[keyof T]
|
||||||
|
|
||||||
|
type OnNavigateEventHandler = (event: { preventDefault: () => void }) => void
|
||||||
|
|
||||||
|
type InternalLinkProps = {
|
||||||
|
/**
|
||||||
|
* The path or URL to navigate to. It can also be an object.
|
||||||
|
*
|
||||||
|
* @example https://nextjs.org/docs/api-reference/next/link#with-url-object
|
||||||
|
*/
|
||||||
|
href: Url
|
||||||
|
/**
|
||||||
|
* Optional decorator for the path that will be shown in the browser URL bar. Before Next.js 9.5.3 this was used for dynamic routes, check our [previous docs](https://github.com/vercel/next.js/blob/v9.5.2/docs/api-reference/next/link.md#dynamic-routes) to see how it worked. Note: when this path differs from the one provided in `href` the previous `href`/`as` behavior is used as shown in the [previous docs](https://github.com/vercel/next.js/blob/v9.5.2/docs/api-reference/next/link.md#dynamic-routes).
|
||||||
|
*/
|
||||||
|
as?: Url
|
||||||
|
/**
|
||||||
|
* Replace the current `history` state instead of adding a new url into the stack.
|
||||||
|
*
|
||||||
|
* @defaultValue `false`
|
||||||
|
*/
|
||||||
|
replace?: boolean
|
||||||
|
/**
|
||||||
|
* Whether to override the default scroll behavior
|
||||||
|
*
|
||||||
|
* @example https://nextjs.org/docs/api-reference/next/link#disable-scrolling-to-the-top-of-the-page
|
||||||
|
*
|
||||||
|
* @defaultValue `true`
|
||||||
|
*/
|
||||||
|
scroll?: boolean
|
||||||
|
/**
|
||||||
|
* Update the path of the current page without rerunning [`getStaticProps`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-props), [`getServerSideProps`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props) or [`getInitialProps`](/docs/pages/api-reference/functions/get-initial-props).
|
||||||
|
*
|
||||||
|
* @defaultValue `false`
|
||||||
|
*/
|
||||||
|
shallow?: boolean
|
||||||
|
/**
|
||||||
|
* Forces `Link` to send the `href` property to its child.
|
||||||
|
*
|
||||||
|
* @defaultValue `false`
|
||||||
|
*/
|
||||||
|
passHref?: boolean
|
||||||
|
/**
|
||||||
|
* Prefetch the page in the background.
|
||||||
|
* Any `<Link />` that is in the viewport (initially or through scroll) will be prefetched.
|
||||||
|
* Prefetch can be disabled by passing `prefetch={false}`. Prefetching is only enabled in production.
|
||||||
|
*
|
||||||
|
* In App Router:
|
||||||
|
* - "auto", null, undefined (default): For statically generated pages, this will prefetch the full React Server Component data. For dynamic pages, this will prefetch up to the nearest route segment with a [`loading.js`](https://nextjs.org/docs/app/api-reference/file-conventions/loading) file. If there is no loading file, it will not fetch the full tree to avoid fetching too much data.
|
||||||
|
* - `true`: This will prefetch the full React Server Component data for all route segments, regardless of whether they contain a segment with `loading.js`.
|
||||||
|
* - `false`: This will not prefetch any data, even on hover.
|
||||||
|
*
|
||||||
|
* In Pages Router:
|
||||||
|
* - `true` (default): The full route & its data will be prefetched.
|
||||||
|
* - `false`: Prefetching will not happen when entering the viewport, but will still happen on hover.
|
||||||
|
* @defaultValue `true` (pages router) or `null` (app router)
|
||||||
|
*/
|
||||||
|
prefetch?: boolean | 'auto' | null | 'unstable_forceStale'
|
||||||
|
/**
|
||||||
|
* The active locale is automatically prepended. `locale` allows for providing a different locale.
|
||||||
|
* When `false` `href` has to include the locale as the default behavior is disabled.
|
||||||
|
* Note: This is only available in the Pages Router.
|
||||||
|
*/
|
||||||
|
locale?: string | false
|
||||||
|
/**
|
||||||
|
* Enable legacy link behavior.
|
||||||
|
* @deprecated This will be removed in v16
|
||||||
|
* @defaultValue `false`
|
||||||
|
* @see https://github.com/vercel/next.js/commit/489e65ed98544e69b0afd7e0cfc3f9f6c2b803b7
|
||||||
|
*/
|
||||||
|
legacyBehavior?: boolean
|
||||||
|
/**
|
||||||
|
* Optional event handler for when the mouse pointer is moved onto Link
|
||||||
|
*/
|
||||||
|
onMouseEnter?: React.MouseEventHandler<HTMLAnchorElement>
|
||||||
|
/**
|
||||||
|
* Optional event handler for when Link is touched.
|
||||||
|
*/
|
||||||
|
onTouchStart?: React.TouchEventHandler<HTMLAnchorElement>
|
||||||
|
/**
|
||||||
|
* Optional event handler for when Link is clicked.
|
||||||
|
*/
|
||||||
|
onClick?: React.MouseEventHandler<HTMLAnchorElement>
|
||||||
|
/**
|
||||||
|
* Optional event handler for when the `<Link>` is navigated.
|
||||||
|
*/
|
||||||
|
onNavigate?: OnNavigateEventHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO-APP: Include the full set of Anchor props
|
||||||
|
// adding this to the publicly exported type currently breaks existing apps
|
||||||
|
|
||||||
|
// `RouteInferType` is a stub here to avoid breaking `typedRoutes` when the type
|
||||||
|
// isn't generated yet. It will be replaced when type generation runs.
|
||||||
|
// WARNING: This should be an interface to prevent TypeScript from inlining it
|
||||||
|
// in declarations of libraries dependending on Next.js.
|
||||||
|
// Not trivial to reproduce so only convert to an interface when needed.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export interface LinkProps<RouteInferType = any> extends InternalLinkProps {}
|
||||||
|
type LinkPropsRequired = RequiredKeys<LinkProps>
|
||||||
|
type LinkPropsOptional = OptionalKeys<InternalLinkProps>
|
||||||
|
|
||||||
|
const prefetched = new Set<string>()
|
||||||
|
|
||||||
|
type PrefetchOptions = RouterPrefetchOptions & {
|
||||||
|
/**
|
||||||
|
* bypassPrefetchedCheck will bypass the check to see if the `href` has
|
||||||
|
* already been fetched.
|
||||||
|
*/
|
||||||
|
bypassPrefetchedCheck?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function prefetch(
|
||||||
|
router: NextRouter,
|
||||||
|
href: string,
|
||||||
|
as: string,
|
||||||
|
options: PrefetchOptions
|
||||||
|
): void {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLocalURL(href)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should only dedupe requests when experimental.optimisticClientCache is
|
||||||
|
// disabled.
|
||||||
|
if (!options.bypassPrefetchedCheck) {
|
||||||
|
const locale =
|
||||||
|
// Let the link's locale prop override the default router locale.
|
||||||
|
typeof options.locale !== 'undefined'
|
||||||
|
? options.locale
|
||||||
|
: // Otherwise fallback to the router's locale.
|
||||||
|
'locale' in router
|
||||||
|
? router.locale
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
const prefetchedKey = href + '%' + as + '%' + locale
|
||||||
|
|
||||||
|
// If we've already fetched the key, then don't prefetch it again!
|
||||||
|
if (prefetched.has(prefetchedKey)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark this URL as prefetched.
|
||||||
|
prefetched.add(prefetchedKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefetch the JSON page if asked (only in the client)
|
||||||
|
// We need to handle a prefetch error here since we may be
|
||||||
|
// loading with priority which can reject but we don't
|
||||||
|
// want to force navigation since this is only a prefetch
|
||||||
|
router.prefetch(href, as, options).catch((err) => {
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
// rethrow to show invalid URL errors
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function isModifiedEvent(event: React.MouseEvent): boolean {
|
||||||
|
const eventTarget = event.currentTarget as HTMLAnchorElement | SVGAElement
|
||||||
|
const target = eventTarget.getAttribute('target')
|
||||||
|
return (
|
||||||
|
(target && target !== '_self') ||
|
||||||
|
event.metaKey ||
|
||||||
|
event.ctrlKey ||
|
||||||
|
event.shiftKey ||
|
||||||
|
event.altKey || // triggers resource download
|
||||||
|
(event.nativeEvent && event.nativeEvent.which === 2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkClicked(
|
||||||
|
e: React.MouseEvent,
|
||||||
|
router: NextRouter | AppRouterInstance,
|
||||||
|
href: string,
|
||||||
|
as: string,
|
||||||
|
replace?: boolean,
|
||||||
|
shallow?: boolean,
|
||||||
|
scroll?: boolean,
|
||||||
|
locale?: string | false,
|
||||||
|
onNavigate?: OnNavigateEventHandler
|
||||||
|
): void {
|
||||||
|
const { nodeName } = e.currentTarget
|
||||||
|
|
||||||
|
// anchors inside an svg have a lowercase nodeName
|
||||||
|
const isAnchorNodeName = nodeName.toUpperCase() === 'A'
|
||||||
|
|
||||||
|
if (
|
||||||
|
(isAnchorNodeName && isModifiedEvent(e)) ||
|
||||||
|
e.currentTarget.hasAttribute('download')
|
||||||
|
) {
|
||||||
|
// ignore click for browser’s default behavior
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLocalURL(href)) {
|
||||||
|
if (replace) {
|
||||||
|
// browser default behavior does not replace the history state
|
||||||
|
// so we need to do it manually
|
||||||
|
e.preventDefault()
|
||||||
|
location.replace(href)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore click for browser’s default behavior
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
const navigate = () => {
|
||||||
|
if (onNavigate) {
|
||||||
|
let isDefaultPrevented = false
|
||||||
|
|
||||||
|
onNavigate({
|
||||||
|
preventDefault: () => {
|
||||||
|
isDefaultPrevented = true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isDefaultPrevented) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the router is an NextRouter instance it will have `beforePopState`
|
||||||
|
const routerScroll = scroll ?? true
|
||||||
|
if ('beforePopState' in router) {
|
||||||
|
router[replace ? 'replace' : 'push'](href, as, {
|
||||||
|
shallow,
|
||||||
|
locale,
|
||||||
|
scroll: routerScroll,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
router[replace ? 'replace' : 'push'](as || href, {
|
||||||
|
scroll: routerScroll,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate()
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinkPropsReal = React.PropsWithChildren<
|
||||||
|
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof LinkProps> &
|
||||||
|
LinkProps
|
||||||
|
>
|
||||||
|
|
||||||
|
function formatStringOrUrl(urlObjOrString: UrlObject | string): string {
|
||||||
|
if (typeof urlObjOrString === 'string') {
|
||||||
|
return urlObjOrString
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatUrl(urlObjOrString)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A React component that extends the HTML `<a>` element to provide [prefetching](https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#2-prefetching)
|
||||||
|
* and client-side navigation between routes.
|
||||||
|
*
|
||||||
|
* It is the primary way to navigate between routes in Next.js.
|
||||||
|
*
|
||||||
|
* Read more: [Next.js docs: `<Link>`](https://nextjs.org/docs/app/api-reference/components/link)
|
||||||
|
*/
|
||||||
|
const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
|
||||||
|
function LinkComponent(props, forwardedRef) {
|
||||||
|
let children: React.ReactNode
|
||||||
|
|
||||||
|
const {
|
||||||
|
href: hrefProp,
|
||||||
|
as: asProp,
|
||||||
|
children: childrenProp,
|
||||||
|
prefetch: prefetchProp = null,
|
||||||
|
passHref,
|
||||||
|
replace,
|
||||||
|
shallow,
|
||||||
|
scroll,
|
||||||
|
locale,
|
||||||
|
onClick,
|
||||||
|
onNavigate,
|
||||||
|
onMouseEnter: onMouseEnterProp,
|
||||||
|
onTouchStart: onTouchStartProp,
|
||||||
|
legacyBehavior = false,
|
||||||
|
...restProps
|
||||||
|
} = props
|
||||||
|
|
||||||
|
children = childrenProp
|
||||||
|
|
||||||
|
if (
|
||||||
|
legacyBehavior &&
|
||||||
|
(typeof children === 'string' || typeof children === 'number')
|
||||||
|
) {
|
||||||
|
children = <a>{children}</a>
|
||||||
|
}
|
||||||
|
|
||||||
|
const router = React.useContext(RouterContext)
|
||||||
|
|
||||||
|
const prefetchEnabled = prefetchProp !== false
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
function createPropError(args: {
|
||||||
|
key: string
|
||||||
|
expected: string
|
||||||
|
actual: string
|
||||||
|
}) {
|
||||||
|
return new Error(
|
||||||
|
`Failed prop type: The prop \`${args.key}\` expects a ${args.expected} in \`<Link>\`, but got \`${args.actual}\` instead.` +
|
||||||
|
(typeof window !== 'undefined'
|
||||||
|
? // TODO: Remove this addendum if Owner Stacks are available
|
||||||
|
"\nOpen your browser's console to view the Component stack trace."
|
||||||
|
: '')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeScript trick for type-guarding:
|
||||||
|
const requiredPropsGuard: Record<LinkPropsRequired, true> = {
|
||||||
|
href: true,
|
||||||
|
} as const
|
||||||
|
const requiredProps: LinkPropsRequired[] = Object.keys(
|
||||||
|
requiredPropsGuard
|
||||||
|
) as LinkPropsRequired[]
|
||||||
|
requiredProps.forEach((key: LinkPropsRequired) => {
|
||||||
|
if (key === 'href') {
|
||||||
|
if (
|
||||||
|
props[key] == null ||
|
||||||
|
(typeof props[key] !== 'string' && typeof props[key] !== 'object')
|
||||||
|
) {
|
||||||
|
throw createPropError({
|
||||||
|
key,
|
||||||
|
expected: '`string` or `object`',
|
||||||
|
actual: props[key] === null ? 'null' : typeof props[key],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TypeScript trick for type-guarding:
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const _: never = key
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// TypeScript trick for type-guarding:
|
||||||
|
const optionalPropsGuard: Record<LinkPropsOptional, true> = {
|
||||||
|
as: true,
|
||||||
|
replace: true,
|
||||||
|
scroll: true,
|
||||||
|
shallow: true,
|
||||||
|
passHref: true,
|
||||||
|
prefetch: true,
|
||||||
|
locale: true,
|
||||||
|
onClick: true,
|
||||||
|
onMouseEnter: true,
|
||||||
|
onTouchStart: true,
|
||||||
|
legacyBehavior: true,
|
||||||
|
onNavigate: true,
|
||||||
|
} as const
|
||||||
|
const optionalProps: LinkPropsOptional[] = Object.keys(
|
||||||
|
optionalPropsGuard
|
||||||
|
) as LinkPropsOptional[]
|
||||||
|
optionalProps.forEach((key: LinkPropsOptional) => {
|
||||||
|
const valType = typeof props[key]
|
||||||
|
|
||||||
|
if (key === 'as') {
|
||||||
|
if (props[key] && valType !== 'string' && valType !== 'object') {
|
||||||
|
throw createPropError({
|
||||||
|
key,
|
||||||
|
expected: '`string` or `object`',
|
||||||
|
actual: valType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (key === 'locale') {
|
||||||
|
if (props[key] && valType !== 'string') {
|
||||||
|
throw createPropError({
|
||||||
|
key,
|
||||||
|
expected: '`string`',
|
||||||
|
actual: valType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
key === 'onClick' ||
|
||||||
|
key === 'onMouseEnter' ||
|
||||||
|
key === 'onTouchStart' ||
|
||||||
|
key === 'onNavigate'
|
||||||
|
) {
|
||||||
|
if (props[key] && valType !== 'function') {
|
||||||
|
throw createPropError({
|
||||||
|
key,
|
||||||
|
expected: '`function`',
|
||||||
|
actual: valType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
key === 'replace' ||
|
||||||
|
key === 'scroll' ||
|
||||||
|
key === 'shallow' ||
|
||||||
|
key === 'passHref' ||
|
||||||
|
key === 'legacyBehavior'
|
||||||
|
) {
|
||||||
|
if (props[key] != null && valType !== 'boolean') {
|
||||||
|
throw createPropError({
|
||||||
|
key,
|
||||||
|
expected: '`boolean`',
|
||||||
|
actual: valType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if (key === 'prefetch') {
|
||||||
|
if (
|
||||||
|
props[key] != null &&
|
||||||
|
valType !== 'boolean' &&
|
||||||
|
props[key] !== 'auto'
|
||||||
|
) {
|
||||||
|
throw createPropError({
|
||||||
|
key,
|
||||||
|
expected: '`boolean | "auto"`',
|
||||||
|
actual: valType,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TypeScript trick for type-guarding:
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const _: never = key
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { href, as } = React.useMemo(() => {
|
||||||
|
if (!router) {
|
||||||
|
const resolvedHref = formatStringOrUrl(hrefProp)
|
||||||
|
return {
|
||||||
|
href: resolvedHref,
|
||||||
|
as: asProp ? formatStringOrUrl(asProp) : resolvedHref,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [resolvedHref, resolvedAs] = resolveHref(router, hrefProp, true)
|
||||||
|
|
||||||
|
return {
|
||||||
|
href: resolvedHref,
|
||||||
|
as: asProp ? resolveHref(router, asProp) : resolvedAs || resolvedHref,
|
||||||
|
}
|
||||||
|
}, [router, hrefProp, asProp])
|
||||||
|
|
||||||
|
const previousHref = React.useRef<string>(href)
|
||||||
|
const previousAs = React.useRef<string>(as)
|
||||||
|
|
||||||
|
// This will return the first child, if multiple are provided it will throw an error
|
||||||
|
let child: any
|
||||||
|
if (legacyBehavior) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
if (onClick) {
|
||||||
|
console.warn(
|
||||||
|
`"onClick" was passed to <Link> with \`href\` of \`${hrefProp}\` but "legacyBehavior" was set. The legacy behavior requires onClick be set on the child of next/link`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (onMouseEnterProp) {
|
||||||
|
console.warn(
|
||||||
|
`"onMouseEnter" was passed to <Link> with \`href\` of \`${hrefProp}\` but "legacyBehavior" was set. The legacy behavior requires onMouseEnter be set on the child of next/link`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
child = React.Children.only(children)
|
||||||
|
} catch (err) {
|
||||||
|
if (!children) {
|
||||||
|
throw new Error(
|
||||||
|
`No children were passed to <Link> with \`href\` of \`${hrefProp}\` but one child is required https://nextjs.org/docs/messages/link-no-children`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Multiple children were passed to <Link> with \`href\` of \`${hrefProp}\` but only one child is supported https://nextjs.org/docs/messages/link-multiple-children` +
|
||||||
|
(typeof window !== 'undefined'
|
||||||
|
? " \nOpen your browser's console to view the Component stack trace."
|
||||||
|
: '')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
child = React.Children.only(children)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
if ((children as any)?.type === 'a') {
|
||||||
|
throw new Error(
|
||||||
|
'Invalid <Link> with <a> child. Please remove <a> or use <Link legacyBehavior>.\nLearn more: https://nextjs.org/docs/messages/invalid-new-link-with-extra-anchor'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const childRef: any = legacyBehavior
|
||||||
|
? child && typeof child === 'object' && child.ref
|
||||||
|
: forwardedRef
|
||||||
|
|
||||||
|
const [setIntersectionRef, isVisible, resetVisible] = useIntersection({
|
||||||
|
rootMargin: '200px',
|
||||||
|
})
|
||||||
|
|
||||||
|
const setIntersectionWithResetRef = React.useCallback(
|
||||||
|
(el: Element | null) => {
|
||||||
|
// Before the link getting observed, check if visible state need to be reset
|
||||||
|
if (previousAs.current !== as || previousHref.current !== href) {
|
||||||
|
resetVisible()
|
||||||
|
previousAs.current = as
|
||||||
|
previousHref.current = href
|
||||||
|
}
|
||||||
|
|
||||||
|
setIntersectionRef(el)
|
||||||
|
},
|
||||||
|
[as, href, resetVisible, setIntersectionRef]
|
||||||
|
)
|
||||||
|
|
||||||
|
const setRef = useMergedRef(setIntersectionWithResetRef, childRef)
|
||||||
|
|
||||||
|
// Prefetch the URL if we haven't already and it's visible.
|
||||||
|
React.useEffect(() => {
|
||||||
|
// in dev, we only prefetch on hover to avoid wasting resources as the prefetch will trigger compiling the page.
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!router) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we don't need to prefetch the URL, don't do prefetch.
|
||||||
|
if (!isVisible || !prefetchEnabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefetch the URL.
|
||||||
|
prefetch(router, href, as, { locale })
|
||||||
|
}, [as, href, isVisible, locale, prefetchEnabled, router?.locale, router])
|
||||||
|
|
||||||
|
const childProps: {
|
||||||
|
onTouchStart?: React.TouchEventHandler<HTMLAnchorElement>
|
||||||
|
onMouseEnter: React.MouseEventHandler<HTMLAnchorElement>
|
||||||
|
onClick: React.MouseEventHandler<HTMLAnchorElement>
|
||||||
|
href?: string
|
||||||
|
ref?: any
|
||||||
|
} = {
|
||||||
|
ref: setRef,
|
||||||
|
onClick(e) {
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
if (!e) {
|
||||||
|
throw new Error(
|
||||||
|
`Component rendered inside next/link has to pass click event to "onClick" prop.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!legacyBehavior && typeof onClick === 'function') {
|
||||||
|
onClick(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
legacyBehavior &&
|
||||||
|
child.props &&
|
||||||
|
typeof child.props.onClick === 'function'
|
||||||
|
) {
|
||||||
|
child.props.onClick(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!router) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.defaultPrevented) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
linkClicked(
|
||||||
|
e,
|
||||||
|
router,
|
||||||
|
href,
|
||||||
|
as,
|
||||||
|
replace,
|
||||||
|
shallow,
|
||||||
|
scroll,
|
||||||
|
locale,
|
||||||
|
onNavigate
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onMouseEnter(e) {
|
||||||
|
if (!legacyBehavior && typeof onMouseEnterProp === 'function') {
|
||||||
|
onMouseEnterProp(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
legacyBehavior &&
|
||||||
|
child.props &&
|
||||||
|
typeof child.props.onMouseEnter === 'function'
|
||||||
|
) {
|
||||||
|
child.props.onMouseEnter(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!router) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prefetch(router, href, as, {
|
||||||
|
locale,
|
||||||
|
priority: true,
|
||||||
|
// @see {https://github.com/vercel/next.js/discussions/40268?sort=top#discussioncomment-3572642}
|
||||||
|
bypassPrefetchedCheck: true,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onTouchStart: process.env.__NEXT_LINK_NO_TOUCH_START
|
||||||
|
? undefined
|
||||||
|
: function onTouchStart(e) {
|
||||||
|
if (!legacyBehavior && typeof onTouchStartProp === 'function') {
|
||||||
|
onTouchStartProp(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
legacyBehavior &&
|
||||||
|
child.props &&
|
||||||
|
typeof child.props.onTouchStart === 'function'
|
||||||
|
) {
|
||||||
|
child.props.onTouchStart(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!router) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prefetch(router, href, as, {
|
||||||
|
locale,
|
||||||
|
priority: true,
|
||||||
|
// @see {https://github.com/vercel/next.js/discussions/40268?sort=top#discussioncomment-3572642}
|
||||||
|
bypassPrefetchedCheck: true,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// If child is an <a> tag and doesn't have a href attribute, or if the 'passHref' property is
|
||||||
|
// defined, we specify the current 'href', so that repetition is not needed by the user.
|
||||||
|
// If the url is absolute, we can bypass the logic to prepend the domain and locale.
|
||||||
|
if (isAbsoluteUrl(as)) {
|
||||||
|
childProps.href = as
|
||||||
|
} else if (
|
||||||
|
!legacyBehavior ||
|
||||||
|
passHref ||
|
||||||
|
(child.type === 'a' && !('href' in child.props))
|
||||||
|
) {
|
||||||
|
const curLocale = typeof locale !== 'undefined' ? locale : router?.locale
|
||||||
|
|
||||||
|
// we only render domain locales if we are currently on a domain locale
|
||||||
|
// so that locale links are still visitable in development/preview envs
|
||||||
|
const localeDomain =
|
||||||
|
router?.isLocaleDomain &&
|
||||||
|
getDomainLocale(as, curLocale, router?.locales, router?.domainLocales)
|
||||||
|
|
||||||
|
childProps.href =
|
||||||
|
localeDomain ||
|
||||||
|
addBasePath(addLocale(as, curLocale, router?.defaultLocale))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (legacyBehavior) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
errorOnce(
|
||||||
|
'`legacyBehavior` is deprecated and will be removed in a future ' +
|
||||||
|
'release. A codemod is available to upgrade your components:\n\n' +
|
||||||
|
'npx @next/codemod@latest new-link .\n\n' +
|
||||||
|
'Learn more: https://nextjs.org/docs/app/building-your-application/upgrading/codemods#remove-a-tags-from-link-components'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return React.cloneElement(child, childProps)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a {...restProps} {...childProps}>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const LinkStatusContext = createContext<{
|
||||||
|
pending: boolean
|
||||||
|
}>({
|
||||||
|
// We do not support link status in the Pages Router, so we always return false
|
||||||
|
pending: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const useLinkStatus = () => {
|
||||||
|
// This behaviour is like React's useFormStatus. When the component is not under
|
||||||
|
// a <form> tag, it will get the default value, instead of throwing an error.
|
||||||
|
return useContext(LinkStatusContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Link
|
||||||
137
anyclip/client/use-intersection.tsx
Normal file
137
anyclip/client/use-intersection.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
|
import {
|
||||||
|
requestIdleCallback,
|
||||||
|
cancelIdleCallback,
|
||||||
|
} from './request-idle-callback'
|
||||||
|
|
||||||
|
type UseIntersectionObserverInit = Pick<
|
||||||
|
IntersectionObserverInit,
|
||||||
|
'rootMargin' | 'root'
|
||||||
|
>
|
||||||
|
|
||||||
|
type UseIntersection = { disabled?: boolean } & UseIntersectionObserverInit & {
|
||||||
|
rootRef?: React.RefObject<HTMLElement | null> | null
|
||||||
|
}
|
||||||
|
type ObserveCallback = (isVisible: boolean) => void
|
||||||
|
type Identifier = {
|
||||||
|
root: Element | Document | null
|
||||||
|
margin: string
|
||||||
|
}
|
||||||
|
type Observer = {
|
||||||
|
id: Identifier
|
||||||
|
observer: IntersectionObserver
|
||||||
|
elements: Map<Element, ObserveCallback>
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasIntersectionObserver = typeof IntersectionObserver === 'function'
|
||||||
|
|
||||||
|
const observers = new Map<Identifier, Observer>()
|
||||||
|
const idList: Identifier[] = []
|
||||||
|
|
||||||
|
function createObserver(options: UseIntersectionObserverInit): Observer {
|
||||||
|
const id = {
|
||||||
|
root: options.root || null,
|
||||||
|
margin: options.rootMargin || '',
|
||||||
|
}
|
||||||
|
const existing = idList.find(
|
||||||
|
(obj) => obj.root === id.root && obj.margin === id.margin
|
||||||
|
)
|
||||||
|
let instance: Observer | undefined
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
instance = observers.get(existing)
|
||||||
|
if (instance) {
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const elements = new Map<Element, ObserveCallback>()
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
const callback = elements.get(entry.target)
|
||||||
|
const isVisible = entry.isIntersecting || entry.intersectionRatio > 0
|
||||||
|
if (callback && isVisible) {
|
||||||
|
callback(isVisible)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, options)
|
||||||
|
instance = {
|
||||||
|
id,
|
||||||
|
observer,
|
||||||
|
elements,
|
||||||
|
}
|
||||||
|
|
||||||
|
idList.push(id)
|
||||||
|
observers.set(id, instance)
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
function observe(
|
||||||
|
element: Element,
|
||||||
|
callback: ObserveCallback,
|
||||||
|
options: UseIntersectionObserverInit
|
||||||
|
): () => void {
|
||||||
|
const { id, observer, elements } = createObserver(options)
|
||||||
|
elements.set(element, callback)
|
||||||
|
|
||||||
|
observer.observe(element)
|
||||||
|
return function unobserve(): void {
|
||||||
|
elements.delete(element)
|
||||||
|
observer.unobserve(element)
|
||||||
|
|
||||||
|
// Destroy observer when there's nothing left to watch:
|
||||||
|
if (elements.size === 0) {
|
||||||
|
observer.disconnect()
|
||||||
|
observers.delete(id)
|
||||||
|
const index = idList.findIndex(
|
||||||
|
(obj) => obj.root === id.root && obj.margin === id.margin
|
||||||
|
)
|
||||||
|
if (index > -1) {
|
||||||
|
idList.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useIntersection<T extends Element>({
|
||||||
|
rootRef,
|
||||||
|
rootMargin,
|
||||||
|
disabled,
|
||||||
|
}: UseIntersection): [(element: T | null) => void, boolean, () => void] {
|
||||||
|
const isDisabled: boolean = disabled || !hasIntersectionObserver
|
||||||
|
|
||||||
|
const [visible, setVisible] = useState(false)
|
||||||
|
const elementRef = useRef<T | null>(null)
|
||||||
|
const setElement = useCallback((element: T | null) => {
|
||||||
|
elementRef.current = element
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasIntersectionObserver) {
|
||||||
|
if (isDisabled || visible) return
|
||||||
|
|
||||||
|
const element = elementRef.current
|
||||||
|
if (element && element.tagName) {
|
||||||
|
const unobserve = observe(
|
||||||
|
element,
|
||||||
|
(isVisible) => isVisible && setVisible(isVisible),
|
||||||
|
{ root: rootRef?.current, rootMargin }
|
||||||
|
)
|
||||||
|
|
||||||
|
return unobserve
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!visible) {
|
||||||
|
const idleCallback = requestIdleCallback(() => setVisible(true))
|
||||||
|
return () => cancelIdleCallback(idleCallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [isDisabled, rootMargin, rootRef, visible, elementRef.current])
|
||||||
|
|
||||||
|
const resetVisible = useCallback(() => {
|
||||||
|
setVisible(false)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return [setElement, visible, resetVisible]
|
||||||
|
}
|
||||||
67
anyclip/client/use-merged-ref.ts
Normal file
67
anyclip/client/use-merged-ref.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { useCallback, useRef, type Ref } from 'react'
|
||||||
|
|
||||||
|
// This is a compatibility hook to support React 18 and 19 refs.
|
||||||
|
// In 19, a cleanup function from refs may be returned.
|
||||||
|
// In 18, returning a cleanup function creates a warning.
|
||||||
|
// Since we take userspace refs, we don't know ahead of time if a cleanup function will be returned.
|
||||||
|
// This implements cleanup functions with the old behavior in 18.
|
||||||
|
// We know refs are always called alternating with `null` and then `T`.
|
||||||
|
// So a call with `null` means we need to call the previous cleanup functions.
|
||||||
|
export function useMergedRef<TElement>(
|
||||||
|
refA: Ref<TElement>,
|
||||||
|
refB: Ref<TElement>
|
||||||
|
): Ref<TElement> {
|
||||||
|
const cleanupA = useRef<(() => void) | null>(null)
|
||||||
|
const cleanupB = useRef<(() => void) | null>(null)
|
||||||
|
|
||||||
|
// NOTE: In theory, we could skip the wrapping if only one of the refs is non-null.
|
||||||
|
// (this happens often if the user doesn't pass a ref to Link/Form/Image)
|
||||||
|
// But this can cause us to leak a cleanup-ref into user code (e.g. via `<Link legacyBehavior>`),
|
||||||
|
// and the user might pass that ref into ref-merging library that doesn't support cleanup refs
|
||||||
|
// (because it hasn't been updated for React 19)
|
||||||
|
// which can then cause things to blow up, because a cleanup-returning ref gets called with `null`.
|
||||||
|
// So in practice, it's safer to be defensive and always wrap the ref, even on React 19.
|
||||||
|
return useCallback(
|
||||||
|
(current: TElement | null): void => {
|
||||||
|
if (current === null) {
|
||||||
|
const cleanupFnA = cleanupA.current
|
||||||
|
if (cleanupFnA) {
|
||||||
|
cleanupA.current = null
|
||||||
|
cleanupFnA()
|
||||||
|
}
|
||||||
|
const cleanupFnB = cleanupB.current
|
||||||
|
if (cleanupFnB) {
|
||||||
|
cleanupB.current = null
|
||||||
|
cleanupFnB()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (refA) {
|
||||||
|
cleanupA.current = applyRef(refA, current)
|
||||||
|
}
|
||||||
|
if (refB) {
|
||||||
|
cleanupB.current = applyRef(refB, current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[refA, refB]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyRef<TElement>(
|
||||||
|
refA: NonNullable<Ref<TElement>>,
|
||||||
|
current: TElement
|
||||||
|
) {
|
||||||
|
if (typeof refA === 'function') {
|
||||||
|
const cleanup = refA(current)
|
||||||
|
if (typeof cleanup === 'function') {
|
||||||
|
return cleanup
|
||||||
|
} else {
|
||||||
|
return () => refA(null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
refA.current = current
|
||||||
|
return () => {
|
||||||
|
refA.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/android-chrome-192x192-1621329404738.9a2c292c.png","height":192,"width":192,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAOVBMVEVnZ2fIyMj////Ozs7///+UlJTX19deXl7u7u5MaXEwMDD///8CAgLV1dWSkpKgoKBUVFSvr6+Li4vsoX4EAAAADHRSTlPy/DD2LbPu/LIA+S9SUaEGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAP0lEQVR4nC3LUQrAIAwE0dVEE+smau9/2CL0b+AxGOqCpgO1p5Go8LSIhw1isU4hIDty3fD3p9lzk5gY6uXuH0i+AhIzA+vTAAAAAElFTkSuQmCC","blurWidth":8,"blurHeight":8};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/android-chrome-512x512-1621329404738.18838b10.png","height":512,"width":512,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAP1BMVEX///8wMDBlZWWZmZlnZ2deXl7X19fKysrv7+9MaXH///9hYWGPj4+QkJACAgLV1dWTk5Ofn59VVVWvr6+MjIwGk5XMAAAADnRSTlMv+eyx9v3v+rIALfm1spwOkf4AAAAJcEhZcwAACxMAAAsTAQCanBgAAAA/SURBVHicJcpbDoAgDAXRq/aJ2hZk/2sl6t8kZ+AwpQsOSHHEDlidmVscEM7+aBB0ZPU3bPJPkBrf7M2U7uYLUJgCVDLd5W0AAAAASUVORK5CYII=","blurWidth":8,"blurHeight":8};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/apple-touch-icon-1621329404738.718b739d.png","height":180,"width":180,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAQlBMVEVkZGTv7+9XV1fMzMxdXV3////X19f///9MaXGUlJQvLy/Jycnt7e1qamrIyMj///8CAgLW1taRkZGdnZ2wsLBQUFBGzLF2AAAAEHRSTlPrsv71+y/uLQCv+f6v9+ksuK1TXgAAAAlwSFlzAAALEwAACxMBAJqcGAAAAD9JREFUeJw1ykkSwCAIBdFvHICoEVDvf1UrC3dd9RpMIXYhRi7+qCIjeDJ7VVCSzVUViNt8/tEu0Z15tA9CfABaOgKVMNAZMgAAAABJRU5ErkJggg==","blurWidth":8,"blurHeight":8};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/favicon-16x16-1621329404738.428e8ba6.png","height":16,"width":16,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAQlBMVEVmZmb///+Ojo4vLy/V1dVeXl7////JyclMaXHs7Oz///+VlZX////q6uoCAgLV1dWSkpJcXFyhoaFTU1Ovr6+KiooSl+HfAAAADnRSTlPzLbT57/kv+wCzLrEssSJz7GwAAAAJcEhZcwAACxMAAAsTAQCanBgAAABBSURBVHicLctRDoAgDATRRSotiNsCev+rGhL/JnkZWKqKIxmKxCBRUON0z7wgw+ejBHR5zB33+1OTWCQarO+92wdWAwJ7fte5tAAAAABJRU5ErkJggg==","blurWidth":8,"blurHeight":8};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/favicon-32x32-1621329404738.a0439f9c.png","height":32,"width":32,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAOVBMVEX////Ly8uPj4+ZmZnV1dVMaXH///9nZ2ddXV3t7e0vLy8uLi4CAgLV1dWQkJCgoKDHx8dUVFSwsLDm6qi1AAAADHRSTlMu97Sx8QAw8/2y+vpPqVL3AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAPklEQVR4nDXLQQ6AIAwF0VEKBf0t4v0PazRxN8nLUBmb71SaZZe8MbJHHCpYj3lfcmxFzjc+OlXAckkO//4ATbECJt5kAZwAAAAASUVORK5CYII=","blurWidth":8,"blurHeight":8};
|
||||||
1
anyclip/src/assets/img/form-banner/blue.png
Normal file
1
anyclip/src/assets/img/form-banner/blue.png
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/blue.355e1246.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAABlBMVEUzjMs8ks+XDtMVAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAEUlEQVR4nGNgYGBgZGRkZAAAABcABdzbLC4AAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};
|
||||||
1
anyclip/src/assets/img/form-banner/coffee.png
Normal file
1
anyclip/src/assets/img/form-banner/coffee.png
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/coffee.7bec6fe0.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAACVBMVEXV5OTDzsytsKlKK1TwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVR4nGNgYGBgYmRgYAAAABcABNbHRhYAAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};
|
||||||
1
anyclip/src/assets/img/form-banner/desk.png
Normal file
1
anyclip/src/assets/img/form-banner/desk.png
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/desk.0278fd77.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAACVBMVEWUpKair7Cwvb2EUuI8AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVR4nGNgYGBgYmRgYAAAABcABNbHRhYAAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};
|
||||||
1
anyclip/src/assets/img/form-banner/flower.png
Normal file
1
anyclip/src/assets/img/form-banner/flower.png
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/flower.0669d83d.png","height":80,"width":1536,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAACVBMVEW327O0yq2+rafkqDzXAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVR4nGNgYGBkYmRgYAAAAB0ABRyOgNQAAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};
|
||||||
1
anyclip/src/assets/img/form-banner/green.png
Normal file
1
anyclip/src/assets/img/form-banner/green.png
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/green.356a9894.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAADFBMVEV5y69jwJ5au5JuxqgswQcvAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAEUlEQVR4nGNgYGBgZmRkYgIAACUACkFmRE0AAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};
|
||||||
1
anyclip/src/assets/img/form-banner/notebook.png
Normal file
1
anyclip/src/assets/img/form-banner/notebook.png
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/notebook.81224792.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAABlBMVEX0zWny3J+HstVbAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVR4nGNgYGBgZGBgYAAAAA4AAnyhWpcAAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};
|
||||||
1
anyclip/src/assets/img/form-banner/pink.png
Normal file
1
anyclip/src/assets/img/form-banner/pink.png
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/pink.9180c2dd.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAACVBMVEX9pLT9rrv9wMa2bv0rAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVR4nGNgZGBiYmRkYAAAAC4ACOhUCY8AAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};
|
||||||
1
anyclip/src/assets/img/form-banner/textureblue.png
Normal file
1
anyclip/src/assets/img/form-banner/textureblue.png
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/textureblue.77c9842d.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAABlBMVEViyspgycnqWTS1AAAACXBIWXMAAAsTAAALEwEAmpwYAAAADUlEQVR4nGNgYGQAAwAAEAACr3pF0AAAAABJRU5ErkJggg==","blurWidth":8,"blurHeight":1};
|
||||||
1
anyclip/src/assets/img/logo.png
Normal file
1
anyclip/src/assets/img/logo.png
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/logo.564beaa1.png","height":128,"width":507,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAACCAMAAABSSm3fAAAACVBMVEX///////////+OSuX+AAAAA3RSTlMlMVUZrOIcAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAF0lEQVR4nGNgZGJgZGRgYGBgZALRjAwAAIEADMddC4AAAAAASUVORK5CYII=","blurWidth":8,"blurHeight":2};
|
||||||
1
anyclip/src/assets/img/source-icons/instagram.svg
Normal file
1
anyclip/src/assets/img/source-icons/instagram.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/instagram.f8cc4a31.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};
|
||||||
1
anyclip/src/assets/img/source-icons/mrss.svg
Normal file
1
anyclip/src/assets/img/source-icons/mrss.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/mrss.245f1778.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};
|
||||||
1
anyclip/src/assets/img/source-icons/ms_stream.svg
Normal file
1
anyclip/src/assets/img/source-icons/ms_stream.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/ms_stream.9514a9d7.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};
|
||||||
1
anyclip/src/assets/img/source-icons/sharepoint.svg
Normal file
1
anyclip/src/assets/img/source-icons/sharepoint.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/sharepoint.44a77c14.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};
|
||||||
1
anyclip/src/assets/img/source-icons/teams.svg
Normal file
1
anyclip/src/assets/img/source-icons/teams.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/teams.ab654a7b.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};
|
||||||
1
anyclip/src/assets/img/source-icons/tiktok.svg
Normal file
1
anyclip/src/assets/img/source-icons/tiktok.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/tiktok.04e40fb5.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};
|
||||||
1
anyclip/src/assets/img/source-icons/zoom.svg
Normal file
1
anyclip/src/assets/img/source-icons/zoom.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/zoom.53b9653f.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export default {"src":"/_next/static/media/default-img-audio-thumbnail.0f14be28.jpg","height":672,"width":1196,"blurDataURL":"data:image/jpeg;base64,/9j/2wBDAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/v/2wBDAQoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/v/wgARCAAEAAgDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAACOg//EABoQAAICAwAAAAAAAAAAAAAAABETACECEjH/2gAIAQEAAT8AZktdDY8uf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z","blurWidth":8,"blurHeight":4};
|
||||||
33
anyclip/src/client/components/forbidden.ts
Normal file
33
anyclip/src/client/components/forbidden.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import {
|
||||||
|
HTTP_ERROR_FALLBACK_ERROR_CODE,
|
||||||
|
type HTTPAccessFallbackError,
|
||||||
|
} from './http-access-fallback/http-access-fallback'
|
||||||
|
|
||||||
|
// TODO: Add `forbidden` docs
|
||||||
|
/**
|
||||||
|
* @experimental
|
||||||
|
* This function allows you to render the [forbidden.js file](https://nextjs.org/docs/app/api-reference/file-conventions/forbidden)
|
||||||
|
* within a route segment as well as inject a tag.
|
||||||
|
*
|
||||||
|
* `forbidden()` can be used in
|
||||||
|
* [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components),
|
||||||
|
* [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers), and
|
||||||
|
* [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
|
||||||
|
*
|
||||||
|
* Read more: [Next.js Docs: `forbidden`](https://nextjs.org/docs/app/api-reference/functions/forbidden)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DIGEST = `${HTTP_ERROR_FALLBACK_ERROR_CODE};403`
|
||||||
|
|
||||||
|
export function forbidden(): never {
|
||||||
|
if (!process.env.__NEXT_EXPERIMENTAL_AUTH_INTERRUPTS) {
|
||||||
|
throw new Error(
|
||||||
|
`\`forbidden()\` is experimental and only allowed to be enabled when \`experimental.authInterrupts\` is enabled.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-throw-literal
|
||||||
|
const error = new Error(DIGEST) as HTTPAccessFallbackError
|
||||||
|
;(error as HTTPAccessFallbackError).digest = DIGEST
|
||||||
|
throw error
|
||||||
|
}
|
||||||
41
anyclip/src/client/components/navigation.react-server.ts
Normal file
41
anyclip/src/client/components/navigation.react-server.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/** @internal */
|
||||||
|
class ReadonlyURLSearchParamsError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
'Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReadonlyURLSearchParams extends URLSearchParams {
|
||||||
|
/** @deprecated Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams */
|
||||||
|
append() {
|
||||||
|
throw new ReadonlyURLSearchParamsError()
|
||||||
|
}
|
||||||
|
/** @deprecated Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams */
|
||||||
|
delete() {
|
||||||
|
throw new ReadonlyURLSearchParamsError()
|
||||||
|
}
|
||||||
|
/** @deprecated Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams */
|
||||||
|
set() {
|
||||||
|
throw new ReadonlyURLSearchParamsError()
|
||||||
|
}
|
||||||
|
/** @deprecated Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams */
|
||||||
|
sort() {
|
||||||
|
throw new ReadonlyURLSearchParamsError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unstable_isUnrecognizedActionError(): boolean {
|
||||||
|
throw new Error(
|
||||||
|
'`unstable_isUnrecognizedActionError` can only be used on the client.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { redirect, permanentRedirect } from './redirect'
|
||||||
|
export { RedirectType } from './redirect-error'
|
||||||
|
export { notFound } from './not-found'
|
||||||
|
export { forbidden } from './forbidden'
|
||||||
|
export { unauthorized } from './unauthorized'
|
||||||
|
export { unstable_rethrow } from './unstable-rethrow'
|
||||||
|
export { ReadonlyURLSearchParams }
|
||||||
287
anyclip/src/client/components/navigation.ts
Normal file
287
anyclip/src/client/components/navigation.ts
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
import type { FlightRouterState } from '../../server/app-render/types'
|
||||||
|
import type { Params } from '../../server/request/params'
|
||||||
|
|
||||||
|
import { useContext, useMemo } from 'react'
|
||||||
|
import {
|
||||||
|
AppRouterContext,
|
||||||
|
LayoutRouterContext,
|
||||||
|
type AppRouterInstance,
|
||||||
|
} from '../../shared/lib/app-router-context.shared-runtime'
|
||||||
|
import {
|
||||||
|
SearchParamsContext,
|
||||||
|
PathnameContext,
|
||||||
|
PathParamsContext,
|
||||||
|
} from '../../shared/lib/hooks-client-context.shared-runtime'
|
||||||
|
import { getSegmentValue } from './router-reducer/reducers/get-segment-value'
|
||||||
|
import { PAGE_SEGMENT_KEY, DEFAULT_SEGMENT_KEY } from '../../shared/lib/segment'
|
||||||
|
import { ReadonlyURLSearchParams } from './navigation.react-server'
|
||||||
|
|
||||||
|
const useDynamicRouteParams =
|
||||||
|
typeof window === 'undefined'
|
||||||
|
? (
|
||||||
|
require('../../server/app-render/dynamic-rendering') as typeof import('../../server/app-render/dynamic-rendering')
|
||||||
|
).useDynamicRouteParams
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
|
||||||
|
* that lets you *read* the current URL's search parameters.
|
||||||
|
*
|
||||||
|
* Learn more about [`URLSearchParams` on MDN](https://developer.mozilla.org/docs/Web/API/URLSearchParams)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* "use client"
|
||||||
|
* import { useSearchParams } from 'next/navigation'
|
||||||
|
*
|
||||||
|
* export default function Page() {
|
||||||
|
* const searchParams = useSearchParams()
|
||||||
|
* searchParams.get('foo') // returns 'bar' when ?foo=bar
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more: [Next.js Docs: `useSearchParams`](https://nextjs.org/docs/app/api-reference/functions/use-search-params)
|
||||||
|
*/
|
||||||
|
// Client components API
|
||||||
|
export function useSearchParams(): ReadonlyURLSearchParams {
|
||||||
|
const searchParams = useContext(SearchParamsContext)
|
||||||
|
|
||||||
|
// In the case where this is `null`, the compat types added in
|
||||||
|
// `next-env.d.ts` will add a new overload that changes the return type to
|
||||||
|
// include `null`.
|
||||||
|
const readonlySearchParams = useMemo(() => {
|
||||||
|
if (!searchParams) {
|
||||||
|
// When the router is not ready in pages, we won't have the search params
|
||||||
|
// available.
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ReadonlyURLSearchParams(searchParams)
|
||||||
|
}, [searchParams]) as ReadonlyURLSearchParams
|
||||||
|
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
// AsyncLocalStorage should not be included in the client bundle.
|
||||||
|
const { bailoutToClientRendering } =
|
||||||
|
require('./bailout-to-client-rendering') as typeof import('./bailout-to-client-rendering')
|
||||||
|
// TODO-APP: handle dynamic = 'force-static' here and on the client
|
||||||
|
bailoutToClientRendering('useSearchParams()')
|
||||||
|
}
|
||||||
|
|
||||||
|
return readonlySearchParams
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
|
||||||
|
* that lets you read the current URL's pathname.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* "use client"
|
||||||
|
* import { usePathname } from 'next/navigation'
|
||||||
|
*
|
||||||
|
* export default function Page() {
|
||||||
|
* const pathname = usePathname() // returns "/dashboard" on /dashboard?foo=bar
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more: [Next.js Docs: `usePathname`](https://nextjs.org/docs/app/api-reference/functions/use-pathname)
|
||||||
|
*/
|
||||||
|
// Client components API
|
||||||
|
export function usePathname(): string {
|
||||||
|
useDynamicRouteParams?.('usePathname()')
|
||||||
|
|
||||||
|
// In the case where this is `null`, the compat types added in `next-env.d.ts`
|
||||||
|
// will add a new overload that changes the return type to include `null`.
|
||||||
|
return useContext(PathnameContext) as string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client components API
|
||||||
|
export {
|
||||||
|
ServerInsertedHTMLContext,
|
||||||
|
useServerInsertedHTML,
|
||||||
|
} from '../../shared/lib/server-inserted-html.shared-runtime'
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* This hook allows you to programmatically change routes inside [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components).
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* "use client"
|
||||||
|
* import { useRouter } from 'next/navigation'
|
||||||
|
*
|
||||||
|
* export default function Page() {
|
||||||
|
* const router = useRouter()
|
||||||
|
* // ...
|
||||||
|
* router.push('/dashboard') // Navigate to /dashboard
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more: [Next.js Docs: `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router)
|
||||||
|
*/
|
||||||
|
// Client components API
|
||||||
|
export function useRouter(): AppRouterInstance {
|
||||||
|
const router = useContext(AppRouterContext)
|
||||||
|
if (router === null) {
|
||||||
|
throw new Error('invariant expected app router to be mounted')
|
||||||
|
}
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
|
||||||
|
* that lets you read a route's dynamic params filled in by the current URL.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* "use client"
|
||||||
|
* import { useParams } from 'next/navigation'
|
||||||
|
*
|
||||||
|
* export default function Page() {
|
||||||
|
* // on /dashboard/[team] where pathname is /dashboard/nextjs
|
||||||
|
* const { team } = useParams() // team === "nextjs"
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more: [Next.js Docs: `useParams`](https://nextjs.org/docs/app/api-reference/functions/use-params)
|
||||||
|
*/
|
||||||
|
// Client components API
|
||||||
|
export function useParams<T extends Params = Params>(): T {
|
||||||
|
useDynamicRouteParams?.('useParams()')
|
||||||
|
|
||||||
|
return useContext(PathParamsContext) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the canonical parameters from the current level to the leaf node. */
|
||||||
|
// Client components API
|
||||||
|
function getSelectedLayoutSegmentPath(
|
||||||
|
tree: FlightRouterState,
|
||||||
|
parallelRouteKey: string,
|
||||||
|
first = true,
|
||||||
|
segmentPath: string[] = []
|
||||||
|
): string[] {
|
||||||
|
let node: FlightRouterState
|
||||||
|
if (first) {
|
||||||
|
// Use the provided parallel route key on the first parallel route
|
||||||
|
node = tree[1][parallelRouteKey]
|
||||||
|
} else {
|
||||||
|
// After first parallel route prefer children, if there's no children pick the first parallel route.
|
||||||
|
const parallelRoutes = tree[1]
|
||||||
|
node = parallelRoutes.children ?? Object.values(parallelRoutes)[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node) return segmentPath
|
||||||
|
const segment = node[0]
|
||||||
|
|
||||||
|
let segmentValue = getSegmentValue(segment)
|
||||||
|
|
||||||
|
if (!segmentValue || segmentValue.startsWith(PAGE_SEGMENT_KEY)) {
|
||||||
|
return segmentPath
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentPath.push(segmentValue)
|
||||||
|
|
||||||
|
return getSelectedLayoutSegmentPath(
|
||||||
|
node,
|
||||||
|
parallelRouteKey,
|
||||||
|
false,
|
||||||
|
segmentPath
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
|
||||||
|
* that lets you read the active route segments **below** the Layout it is called from.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* 'use client'
|
||||||
|
*
|
||||||
|
* import { useSelectedLayoutSegments } from 'next/navigation'
|
||||||
|
*
|
||||||
|
* export default function ExampleClientComponent() {
|
||||||
|
* const segments = useSelectedLayoutSegments()
|
||||||
|
*
|
||||||
|
* return (
|
||||||
|
* <ul>
|
||||||
|
* {segments.map((segment, index) => (
|
||||||
|
* <li key={index}>{segment}</li>
|
||||||
|
* ))}
|
||||||
|
* </ul>
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more: [Next.js Docs: `useSelectedLayoutSegments`](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segments)
|
||||||
|
*/
|
||||||
|
// Client components API
|
||||||
|
export function useSelectedLayoutSegments(
|
||||||
|
parallelRouteKey: string = 'children'
|
||||||
|
): string[] {
|
||||||
|
useDynamicRouteParams?.('useSelectedLayoutSegments()')
|
||||||
|
|
||||||
|
const context = useContext(LayoutRouterContext)
|
||||||
|
// @ts-expect-error This only happens in `pages`. Type is overwritten in navigation.d.ts
|
||||||
|
if (!context) return null
|
||||||
|
|
||||||
|
return getSelectedLayoutSegmentPath(context.parentTree, parallelRouteKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
|
||||||
|
* that lets you read the active route segment **one level below** the Layout it is called from.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* 'use client'
|
||||||
|
* import { useSelectedLayoutSegment } from 'next/navigation'
|
||||||
|
*
|
||||||
|
* export default function ExampleClientComponent() {
|
||||||
|
* const segment = useSelectedLayoutSegment()
|
||||||
|
*
|
||||||
|
* return <p>Active segment: {segment}</p>
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more: [Next.js Docs: `useSelectedLayoutSegment`](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segment)
|
||||||
|
*/
|
||||||
|
// Client components API
|
||||||
|
export function useSelectedLayoutSegment(
|
||||||
|
parallelRouteKey: string = 'children'
|
||||||
|
): string | null {
|
||||||
|
useDynamicRouteParams?.('useSelectedLayoutSegment()')
|
||||||
|
|
||||||
|
const selectedLayoutSegments = useSelectedLayoutSegments(parallelRouteKey)
|
||||||
|
|
||||||
|
if (!selectedLayoutSegments || selectedLayoutSegments.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedLayoutSegment =
|
||||||
|
parallelRouteKey === 'children'
|
||||||
|
? selectedLayoutSegments[0]
|
||||||
|
: selectedLayoutSegments[selectedLayoutSegments.length - 1]
|
||||||
|
|
||||||
|
// if the default slot is showing, we return null since it's not technically "selected" (it's a fallback)
|
||||||
|
// and returning an internal value like `__DEFAULT__` would be confusing.
|
||||||
|
return selectedLayoutSegment === DEFAULT_SEGMENT_KEY
|
||||||
|
? null
|
||||||
|
: selectedLayoutSegment
|
||||||
|
}
|
||||||
|
|
||||||
|
export { unstable_isUnrecognizedActionError } from './unrecognized-action-error'
|
||||||
|
|
||||||
|
// Shared components APIs
|
||||||
|
export {
|
||||||
|
notFound,
|
||||||
|
forbidden,
|
||||||
|
unauthorized,
|
||||||
|
redirect,
|
||||||
|
permanentRedirect,
|
||||||
|
RedirectType,
|
||||||
|
ReadonlyURLSearchParams,
|
||||||
|
unstable_rethrow,
|
||||||
|
} from './navigation.react-server'
|
||||||
29
anyclip/src/client/components/not-found.ts
Normal file
29
anyclip/src/client/components/not-found.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import {
|
||||||
|
HTTP_ERROR_FALLBACK_ERROR_CODE,
|
||||||
|
type HTTPAccessFallbackError,
|
||||||
|
} from './http-access-fallback/http-access-fallback'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function allows you to render the [not-found.js file](https://nextjs.org/docs/app/api-reference/file-conventions/not-found)
|
||||||
|
* within a route segment as well as inject a tag.
|
||||||
|
*
|
||||||
|
* `notFound()` can be used in
|
||||||
|
* [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components),
|
||||||
|
* [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers), and
|
||||||
|
* [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
|
||||||
|
*
|
||||||
|
* - In a Server Component, this will insert a `<meta name="robots" content="noindex" />` meta tag and set the status code to 404.
|
||||||
|
* - In a Route Handler or Server Action, it will serve a 404 to the caller.
|
||||||
|
*
|
||||||
|
* Read more: [Next.js Docs: `notFound`](https://nextjs.org/docs/app/api-reference/functions/not-found)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DIGEST = `${HTTP_ERROR_FALLBACK_ERROR_CODE};404`
|
||||||
|
|
||||||
|
export function notFound(): never {
|
||||||
|
// eslint-disable-next-line no-throw-literal
|
||||||
|
const error = new Error(DIGEST) as HTTPAccessFallbackError
|
||||||
|
;(error as HTTPAccessFallbackError).digest = DIGEST
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
99
anyclip/src/client/components/redirect.ts
Normal file
99
anyclip/src/client/components/redirect.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { RedirectStatusCode } from './redirect-status-code'
|
||||||
|
import {
|
||||||
|
RedirectType,
|
||||||
|
type RedirectError,
|
||||||
|
isRedirectError,
|
||||||
|
REDIRECT_ERROR_CODE,
|
||||||
|
} from './redirect-error'
|
||||||
|
|
||||||
|
const actionAsyncStorage =
|
||||||
|
typeof window === 'undefined'
|
||||||
|
? (
|
||||||
|
require('../../server/app-render/action-async-storage.external') as typeof import('../../server/app-render/action-async-storage.external')
|
||||||
|
).actionAsyncStorage
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
export function getRedirectError(
|
||||||
|
url: string,
|
||||||
|
type: RedirectType,
|
||||||
|
statusCode: RedirectStatusCode = RedirectStatusCode.TemporaryRedirect
|
||||||
|
): RedirectError {
|
||||||
|
const error = new Error(REDIRECT_ERROR_CODE) as RedirectError
|
||||||
|
error.digest = `${REDIRECT_ERROR_CODE};${type};${url};${statusCode};`
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function allows you to redirect the user to another URL. It can be used in
|
||||||
|
* [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components),
|
||||||
|
* [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers), and
|
||||||
|
* [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
|
||||||
|
*
|
||||||
|
* - In a Server Component, this will insert a meta tag to redirect the user to the target page.
|
||||||
|
* - In a Route Handler or Server Action, it will serve a 307/303 to the caller.
|
||||||
|
* - In a Server Action, type defaults to 'push' and 'replace' elsewhere.
|
||||||
|
*
|
||||||
|
* Read more: [Next.js Docs: `redirect`](https://nextjs.org/docs/app/api-reference/functions/redirect)
|
||||||
|
*/
|
||||||
|
export function redirect(
|
||||||
|
/** The URL to redirect to */
|
||||||
|
url: string,
|
||||||
|
type?: RedirectType
|
||||||
|
): never {
|
||||||
|
type ??= actionAsyncStorage?.getStore()?.isAction
|
||||||
|
? RedirectType.push
|
||||||
|
: RedirectType.replace
|
||||||
|
|
||||||
|
throw getRedirectError(url, type, RedirectStatusCode.TemporaryRedirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function allows you to redirect the user to another URL. It can be used in
|
||||||
|
* [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components),
|
||||||
|
* [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers), and
|
||||||
|
* [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
|
||||||
|
*
|
||||||
|
* - In a Server Component, this will insert a meta tag to redirect the user to the target page.
|
||||||
|
* - In a Route Handler or Server Action, it will serve a 308/303 to the caller.
|
||||||
|
*
|
||||||
|
* Read more: [Next.js Docs: `redirect`](https://nextjs.org/docs/app/api-reference/functions/redirect)
|
||||||
|
*/
|
||||||
|
export function permanentRedirect(
|
||||||
|
/** The URL to redirect to */
|
||||||
|
url: string,
|
||||||
|
type: RedirectType = RedirectType.replace
|
||||||
|
): never {
|
||||||
|
throw getRedirectError(url, type, RedirectStatusCode.PermanentRedirect)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the encoded URL from the error if it's a RedirectError, null
|
||||||
|
* otherwise. Note that this does not validate the URL returned.
|
||||||
|
*
|
||||||
|
* @param error the error that may be a redirect error
|
||||||
|
* @return the url if the error was a redirect error
|
||||||
|
*/
|
||||||
|
export function getURLFromRedirectError(error: RedirectError): string
|
||||||
|
export function getURLFromRedirectError(error: unknown): string | null {
|
||||||
|
if (!isRedirectError(error)) return null
|
||||||
|
|
||||||
|
// Slices off the beginning of the digest that contains the code and the
|
||||||
|
// separating ';'.
|
||||||
|
return error.digest.split(';').slice(2, -2).join(';')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRedirectTypeFromError(error: RedirectError): RedirectType {
|
||||||
|
if (!isRedirectError(error)) {
|
||||||
|
throw new Error('Not a redirect error')
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.digest.split(';', 2)[1] as RedirectType
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRedirectStatusCodeFromError(error: RedirectError): number {
|
||||||
|
if (!isRedirectError(error)) {
|
||||||
|
throw new Error('Not a redirect error')
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(error.digest.split(';').at(-2))
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import type { Segment } from '../../../../server/app-render/types'
|
||||||
|
|
||||||
|
export function getSegmentValue(segment: Segment) {
|
||||||
|
return Array.isArray(segment) ? segment[1] : segment
|
||||||
|
}
|
||||||
34
anyclip/src/client/components/unauthorized.ts
Normal file
34
anyclip/src/client/components/unauthorized.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import {
|
||||||
|
HTTP_ERROR_FALLBACK_ERROR_CODE,
|
||||||
|
type HTTPAccessFallbackError,
|
||||||
|
} from './http-access-fallback/http-access-fallback'
|
||||||
|
|
||||||
|
// TODO: Add `unauthorized` docs
|
||||||
|
/**
|
||||||
|
* @experimental
|
||||||
|
* This function allows you to render the [unauthorized.js file](https://nextjs.org/docs/app/api-reference/file-conventions/unauthorized)
|
||||||
|
* within a route segment as well as inject a tag.
|
||||||
|
*
|
||||||
|
* `unauthorized()` can be used in
|
||||||
|
* [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components),
|
||||||
|
* [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers), and
|
||||||
|
* [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Read more: [Next.js Docs: `unauthorized`](https://nextjs.org/docs/app/api-reference/functions/unauthorized)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const DIGEST = `${HTTP_ERROR_FALLBACK_ERROR_CODE};401`
|
||||||
|
|
||||||
|
export function unauthorized(): never {
|
||||||
|
if (!process.env.__NEXT_EXPERIMENTAL_AUTH_INTERRUPTS) {
|
||||||
|
throw new Error(
|
||||||
|
`\`unauthorized()\` is experimental and only allowed to be used when \`experimental.authInterrupts\` is enabled.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-throw-literal
|
||||||
|
const error = new Error(DIGEST) as HTTPAccessFallbackError
|
||||||
|
;(error as HTTPAccessFallbackError).digest = DIGEST
|
||||||
|
throw error
|
||||||
|
}
|
||||||
34
anyclip/src/client/components/unrecognized-action-error.ts
Normal file
34
anyclip/src/client/components/unrecognized-action-error.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
export class UnrecognizedActionError extends Error {
|
||||||
|
constructor(...args: ConstructorParameters<typeof Error>) {
|
||||||
|
super(...args)
|
||||||
|
this.name = 'UnrecognizedActionError'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a server action call failed because the server action was not recognized by the server.
|
||||||
|
* This can happen if the client and the server are not from the same deployment.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* try {
|
||||||
|
* await myServerAction();
|
||||||
|
* } catch (err) {
|
||||||
|
* if (unstable_isUnrecognizedActionError(err)) {
|
||||||
|
* // The client is from a different deployment than the server.
|
||||||
|
* // Reloading the page will fix this mismatch.
|
||||||
|
* window.alert("Please refresh the page and try again");
|
||||||
|
* return;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* */
|
||||||
|
export function unstable_isUnrecognizedActionError(
|
||||||
|
error: unknown
|
||||||
|
): error is UnrecognizedActionError {
|
||||||
|
return !!(
|
||||||
|
error &&
|
||||||
|
typeof error === 'object' &&
|
||||||
|
error instanceof UnrecognizedActionError
|
||||||
|
)
|
||||||
|
}
|
||||||
12
anyclip/src/client/components/unstable-rethrow.browser.ts
Normal file
12
anyclip/src/client/components/unstable-rethrow.browser.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr'
|
||||||
|
import { isNextRouterError } from './is-next-router-error'
|
||||||
|
|
||||||
|
export function unstable_rethrow(error: unknown): void {
|
||||||
|
if (isNextRouterError(error) || isBailoutToCSRError(error)) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error && 'cause' in error) {
|
||||||
|
unstable_rethrow(error.cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
anyclip/src/client/components/unstable-rethrow.ts
Normal file
15
anyclip/src/client/components/unstable-rethrow.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* This function should be used to rethrow internal Next.js errors so that they can be handled by the framework.
|
||||||
|
* When wrapping an API that uses errors to interrupt control flow, you should use this function before you do any error handling.
|
||||||
|
* This function will rethrow the error if it is a Next.js error so it can be handled, otherwise it will do nothing.
|
||||||
|
*
|
||||||
|
* Read more: [Next.js Docs: `unstable_rethrow`](https://nextjs.org/docs/app/api-reference/functions/unstable_rethrow)
|
||||||
|
*/
|
||||||
|
export const unstable_rethrow =
|
||||||
|
typeof window === 'undefined'
|
||||||
|
? (
|
||||||
|
require('./unstable-rethrow.server') as typeof import('./unstable-rethrow.server')
|
||||||
|
).unstable_rethrow
|
||||||
|
: (
|
||||||
|
require('./unstable-rethrow.browser') as typeof import('./unstable-rethrow.browser')
|
||||||
|
).unstable_rethrow
|
||||||
14
anyclip/src/graphql/services/_helpers/common.js
Normal file
14
anyclip/src/graphql/services/_helpers/common.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export const getSafetyPath = (fileUrl) =>
|
||||||
|
fileUrl
|
||||||
|
.replace(/\\/g, '/')
|
||||||
|
.split('graphql/services/')
|
||||||
|
.pop()
|
||||||
|
.replace(/\..*?$/, '');
|
||||||
|
|
||||||
|
export const createModuleNameFromMetaUrl = (fileUrl) =>
|
||||||
|
fileUrl
|
||||||
|
.split(/graphql[\\/]services[\\/]/)
|
||||||
|
.pop()
|
||||||
|
.replace(/\..*?$/, '')
|
||||||
|
.replace(/[\\/]constants[\\/]index/, '')
|
||||||
|
.replace(/[\\/]/g, '_');
|
||||||
14
anyclip/src/graphql/services/accounts/constants/index.js
Normal file
14
anyclip/src/graphql/services/accounts/constants/index.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { createModuleNameFromMetaUrl } from '../../_helpers/common';
|
||||||
|
|
||||||
|
const MODULE_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
// queries
|
||||||
|
export const GET_ACCOUNTS = `${MODULE_NAME}_GET_ACCOUNTS`;
|
||||||
|
export const GET_ACCOUNT = `${MODULE_NAME}_GET_ACCOUNT`;
|
||||||
|
export const GET_SALESFORCE_DATA = `${MODULE_NAME}_GET_SALESFORCE_DATA`;
|
||||||
|
export const GET_CONTENT_OWNERS = `${MODULE_NAME}_GET_CONTENT_OWNERS`;
|
||||||
|
// mutation
|
||||||
|
export const UPDATE_ACCOUNT = `${MODULE_NAME}_UPDATE_ACCOUNT`;
|
||||||
|
export const CREATE_ACCOUNT = `${MODULE_NAME}_CREATE_ACCOUNT`;
|
||||||
|
export const UPDATE_CONTENT_OWNER = `${MODULE_NAME}_UPDATE_CONTENT_OWNER`;
|
||||||
|
export const DELETE_ALL_VIDEOS = `${MODULE_NAME}_DELETE_ALL_VIDEOS`;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
|
||||||
|
|
||||||
|
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: PAYLOAD_NAME,
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLInt, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
|
||||||
|
|
||||||
|
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: PAYLOAD_NAME,
|
||||||
|
fields: {
|
||||||
|
sortBy: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
sortOrder: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
searchText: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLInt, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
|
||||||
|
|
||||||
|
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: PAYLOAD_NAME,
|
||||||
|
fields: {
|
||||||
|
accountId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
comments: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
salesforceId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
callToAction: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
isPublic: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLInt, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
|
||||||
|
|
||||||
|
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: PAYLOAD_NAME,
|
||||||
|
fields: {
|
||||||
|
accountId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
sortBy: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
sortOrder: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
|
||||||
|
|
||||||
|
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: PAYLOAD_NAME,
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
211
anyclip/src/graphql/services/accounts/types/payload/item.js
Normal file
211
anyclip/src/graphql/services/accounts/types/payload/item.js
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import { GraphQLBoolean, GraphQLFloat, GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
|
||||||
|
|
||||||
|
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: PAYLOAD_NAME,
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
salesforceId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
accountManager: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
accountManagerEmail: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
publisherDemand: {
|
||||||
|
type: GraphQLBoolean,
|
||||||
|
},
|
||||||
|
adServingFeeBusinessModel: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
adServingFeeDisplay: {
|
||||||
|
type: GraphQLFloat,
|
||||||
|
},
|
||||||
|
adServingFeeVideo: {
|
||||||
|
type: GraphQLFloat,
|
||||||
|
},
|
||||||
|
salesManager: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
customLogoUrl: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
avatarUrl: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
type: new GraphQLInputObjectType({
|
||||||
|
name: `${PAYLOAD_NAME}accountsAvatar`,
|
||||||
|
fields: {
|
||||||
|
base64Url: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
mimeType: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
customLoadingMessage: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
customLoginPageUrl: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
subdomain: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
publisherRevShare: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
expenses: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
usersLimit: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
videoDuplicatesBy: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
accountsFeatures: {
|
||||||
|
type: new GraphQLList(
|
||||||
|
new GraphQLInputObjectType({
|
||||||
|
name: `${PAYLOAD_NAME}accountsFeatures`,
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: GraphQLBoolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
accountDashboards: {
|
||||||
|
type: new GraphQLInputObjectType({
|
||||||
|
name: `${PAYLOAD_NAME}accountDashboards`,
|
||||||
|
fields: {
|
||||||
|
general: {
|
||||||
|
type: new GraphQLList(
|
||||||
|
new GraphQLInputObjectType({
|
||||||
|
name: `${PAYLOAD_NAME}accountDashboardsGeneral`,
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
uiName: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
type: GraphQLBoolean,
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: new GraphQLInputObjectType({
|
||||||
|
name: `${PAYLOAD_NAME}accountDashboardsGeneralTooltip`,
|
||||||
|
fields: {
|
||||||
|
info: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
linkTitle: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
custom: {
|
||||||
|
type: new GraphQLList(
|
||||||
|
new GraphQLInputObjectType({
|
||||||
|
name: `${PAYLOAD_NAME}accountDashboardsCustom`,
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
uiName: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
enabled: {
|
||||||
|
type: GraphQLBoolean,
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
lookerReportId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
type: new GraphQLInputObjectType({
|
||||||
|
name: `${PAYLOAD_NAME}accountDashboardsCustomTooltip`,
|
||||||
|
fields: {
|
||||||
|
info: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
linkTitle: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
publisherCustomAnalytics: {
|
||||||
|
type: new GraphQLInputObjectType({
|
||||||
|
name: `${PAYLOAD_NAME}accountDashboardsCustomPublisherCustomAnalytics`,
|
||||||
|
fields: {
|
||||||
|
accountId: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
analyticsId: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
publisherId: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
publisherIds: {
|
||||||
|
type: new GraphQLList(GraphQLInt),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
|
||||||
|
|
||||||
|
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: PAYLOAD_NAME,
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { createModuleNameFromMetaUrl } from '../../_helpers/common';
|
||||||
|
|
||||||
|
const MODULE_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export const BULK_CHANGE_STATUS_ACTION = `${MODULE_NAME}_BULK_CHANGE_STATUS_ACTION`;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { GraphQLBoolean, GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
export const ITEM_INPUT_TYPE_NAME = 'AdServersPCNInputType';
|
||||||
|
|
||||||
|
const CreateInputType = new GraphQLInputObjectType({
|
||||||
|
name: ITEM_INPUT_TYPE_NAME,
|
||||||
|
description: 'Module AdServersPCN Create Input Type',
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
placementId: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
macro: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
comments: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
isDefaultForPlayerType: {
|
||||||
|
type: GraphQLBoolean,
|
||||||
|
},
|
||||||
|
playerTypesIds: {
|
||||||
|
type: new GraphQLList(GraphQLInt),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CreateInputType;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLInt, GraphQLList } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
|
||||||
|
|
||||||
|
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: PAYLOAD_NAME,
|
||||||
|
fields: {
|
||||||
|
ids: {
|
||||||
|
type: new GraphQLList(GraphQLInt),
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
12
anyclip/src/graphql/services/advertisers/constants/index.js
Normal file
12
anyclip/src/graphql/services/advertisers/constants/index.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { createModuleNameFromMetaUrl } from '../../_helpers/common';
|
||||||
|
|
||||||
|
const MODULE_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
// queries
|
||||||
|
export const GET_ADVERTISERS = `${MODULE_NAME}_GET_ADVERTISERS`;
|
||||||
|
export const GET_ADVERTISER = `${MODULE_NAME}_GET_ADVERTISER`;
|
||||||
|
export const GET_ADVERTISER_DM_ACCOUNTS_OPTIONS = `${MODULE_NAME}GET_ADVERTISER_DM_ACCOUNTS_OPTIONS`;
|
||||||
|
|
||||||
|
// mutations
|
||||||
|
export const CREATE_ADVERTISER = `${MODULE_NAME}_CREATE_ADVERTISER`;
|
||||||
|
export const UPDATE_ADVERTISER = `${MODULE_NAME}_UPDATE_ADVERTISER`;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLInt } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
|
||||||
|
|
||||||
|
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: PAYLOAD_NAME,
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLInt, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
|
||||||
|
|
||||||
|
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: PAYLOAD_NAME,
|
||||||
|
fields: {
|
||||||
|
sortBy: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
sortOrder: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
page: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
pageSize: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
searchText: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLInt, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
|
||||||
|
|
||||||
|
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: PAYLOAD_NAME,
|
||||||
|
fields: {
|
||||||
|
id: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
demandAccountId: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createModuleNameFromMetaUrl } from '../../_helpers/common';
|
||||||
|
|
||||||
|
const MODULE_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
// queries
|
||||||
|
export const GET_AIW_TAGLOG_DATA = `${MODULE_NAME}_GET_AIW_TAGLOG_DATA`;
|
||||||
|
export const GET_AIW_TAGLOG_TAG_INFO = `${MODULE_NAME}_GET_AIW_TAGLOG_TAG_INFO`;
|
||||||
|
// mutations
|
||||||
|
export const UPSERT_TAGS = `${MODULE_NAME}_UPSERT_TAGS`;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { createModuleNameFromMetaUrl } from '../../_helpers/common';
|
||||||
|
|
||||||
|
const MODULE_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
// queries
|
||||||
|
export const GET_AIW_THUMBNAIL_DATA = `${MODULE_NAME}_GET_AIW_THUMBNAIL_DATA`;
|
||||||
|
// mutations
|
||||||
|
export const GENERATE_AIW_THUMBNAIL_OPTIONS = `${MODULE_NAME}_GENERATE_AIW_THUMBNAIL_OPTIONS`;
|
||||||
|
export const SET_AIW_THUMBNAIL_DRAFT = `${MODULE_NAME}_SET_AIW_THUMBNAIL_DRAFT`;
|
||||||
|
export const PUBLISH_AIW_THUMBNAIL = `${MODULE_NAME}_PUBLISH_AIW_THUMBNAIL`;
|
||||||
|
export const SET_AIW_THUMBNAIL_FROM_VIDEO_FRAME = `${MODULE_NAME}_SET_AIW_THUMBNAIL_FROM_VIDEO_FRAME`;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLInt, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '@/graphql/services/_helpers/common';
|
||||||
|
|
||||||
|
export const GET_AIW_TAGLOG_DATA_PAYLOAD = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: GET_AIW_TAGLOG_DATA_PAYLOAD,
|
||||||
|
fields: {
|
||||||
|
videoId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
startTime: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
next: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
previous: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { GraphQLBoolean, GraphQLInputObjectType, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '@/graphql/services/_helpers/common';
|
||||||
|
|
||||||
|
export const GET_AIW_TAGLOG_TAG_INFO_PAYLOAD = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: GET_AIW_TAGLOG_TAG_INFO_PAYLOAD,
|
||||||
|
fields: {
|
||||||
|
videoId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
tagId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
uid: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
labelId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
labelValue: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
isIab: {
|
||||||
|
type: GraphQLBoolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '../../../../_helpers/common';
|
||||||
|
|
||||||
|
export const UPSERT_TAGS_PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: UPSERT_TAGS_PAYLOAD_NAME,
|
||||||
|
fields: {
|
||||||
|
videoId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
logRowUid: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
startTime: {
|
||||||
|
type: GraphQLInt,
|
||||||
|
},
|
||||||
|
keywords: {
|
||||||
|
type: new GraphQLList(
|
||||||
|
new GraphQLInputObjectType({
|
||||||
|
name: `${UPSERT_TAGS_PAYLOAD_NAME}_keywords`,
|
||||||
|
fields: {
|
||||||
|
category: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
id: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
labelId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
labelName: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { GraphQLBoolean, GraphQLInputObjectType, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '@/graphql/services/_helpers/common';
|
||||||
|
|
||||||
|
export const GENERATE_AIW_THUMBNAIL_OPTIONS_PAYLOAD = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: GENERATE_AIW_THUMBNAIL_OPTIONS_PAYLOAD,
|
||||||
|
fields: {
|
||||||
|
videoId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
notifyByEmail: {
|
||||||
|
type: GraphQLBoolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { GraphQLBoolean, GraphQLInputObjectType, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '@/graphql/services/_helpers/common';
|
||||||
|
|
||||||
|
export const PUBLISH_AIW_THUMBNAIL_PAYLOAD = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: PUBLISH_AIW_THUMBNAIL_PAYLOAD,
|
||||||
|
fields: {
|
||||||
|
videoId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
publish: {
|
||||||
|
type: GraphQLBoolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { GraphQLInputObjectType, GraphQLString } from 'graphql';
|
||||||
|
|
||||||
|
import { createModuleNameFromMetaUrl } from '@/graphql/services/_helpers/common';
|
||||||
|
|
||||||
|
export const SET_AIW_THUMBNAIL_DRAFT_PAYLOAD = createModuleNameFromMetaUrl(import.meta.url);
|
||||||
|
|
||||||
|
export default new GraphQLInputObjectType({
|
||||||
|
name: SET_AIW_THUMBNAIL_DRAFT_PAYLOAD,
|
||||||
|
fields: {
|
||||||
|
videoId: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
uid: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
thumbnail: {
|
||||||
|
type: GraphQLString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user