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.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
├── 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
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your AnyClip credentials
|
||||
bun install
|
||||
```
|
||||
|
||||
## Modules (`src/modules/`)
|
||||
## Project Structure
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| `analytics/` | Dashboards - monetization, video performance, custom reports |
|
||||
| `editorial/` | Video editing - tagging, search, bulk actions, video details |
|
||||
| `publishing/` | Content publishing and destination management |
|
||||
| `marketplace/` | Marketplace accounts and dashboard |
|
||||
| `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 |
|
||||
```
|
||||
├── anyclip/ # Extracted source code (from sourcemaps)
|
||||
├── docs/ # Documentation
|
||||
├── scripts/ # CLI tools
|
||||
├── sourcemaps/ # Raw .map files (gitignored)
|
||||
└── urls.txt # JS file URLs to download
|
||||
```
|
||||
|
||||
## Pages (`src/pages/`)
|
||||
## Scripts
|
||||
|
||||
- `/analytics` - Analytics dashboard
|
||||
- `/studio` - Studio interface
|
||||
- `/personal-settings` - User settings
|
||||
- `/hubs`, `/users`, `/invitations`, `/forms` - Management pages
|
||||
- `/x-ray/*` - Campaign, creative, and line item analytics
|
||||
### Authentication
|
||||
|
||||
## Tech Stack
|
||||
```bash
|
||||
# Login and save session to session.json
|
||||
bun scripts/auth.ts
|
||||
```
|
||||
|
||||
- Next.js, React, TypeScript
|
||||
- Redux (state management)
|
||||
- Material-UI (components)
|
||||
- Victory/D3 (charts)
|
||||
Programmatic usage:
|
||||
|
||||
```typescript
|
||||
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