Merge branch 'main' into ai-image-generator
* main: (328 commits) Remove temporary migration.md @uppy/examples: fix .stackblitzrc install command (#5919) [ci] release (#5918) Fix changeset for @uppy/utils major Fix changeset Update paths-ignore in ci.yml (#5914) @uppy/examples: fix workspace dependencies for StackBlitz environment (#5910) Ignore angular & @uppy-dev/dev for changesets fixup! Fix changesets Fix changesets Remove example-redux from .changesets/config Fix Companion build output and Dockerfile (#5917) Remove example-react-native-expo from .changeset/config.json Hopefully fix playwright in CI update use external sync store , fix yarn warnings @uppy/svelte: rename "check" script to "typecheck" remove chooseFiles from all the locales remove dropHereOr from all the locales update yarnrc lint fix ...
8
.changeset/README.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
33
.changeset/config.json
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
||||
},
|
||||
"ignore": [
|
||||
"angular",
|
||||
"@uppy-dev/dev",
|
||||
"example-angular",
|
||||
"example-aws-companion",
|
||||
"example-aws-nodejs",
|
||||
"example-aws-php",
|
||||
"example-cdn",
|
||||
"example-companion",
|
||||
"example-companion-custom-provider",
|
||||
"example-companion-digitalocean-spaces",
|
||||
"example-react",
|
||||
"example-sveltekit",
|
||||
"example-transloadit",
|
||||
"example-vue",
|
||||
"example-xhr-bundle",
|
||||
"example-xhr-node",
|
||||
"example-xhr-php",
|
||||
"example-xhr-python"
|
||||
]
|
||||
}
|
||||
114
.cursor/rules/headless-components.mdc
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
---
|
||||
description:
|
||||
globs: packages/@uppy/components/src/**
|
||||
alwaysApply: false
|
||||
---
|
||||
# Headless components
|
||||
|
||||
You are an expert at making headless UI components in Preact, similar to libraries like shadcn, except we don't rely on packages like radix.
|
||||
|
||||
## Goal
|
||||
|
||||
Making headless components in Preact for the open source Uppy file uploader and framework specific hooks. We want to give flexbility to users of this library by rendering sensible UI defaults and hooks that abstract Uppy functionality to completely build your own UI.
|
||||
|
||||
Another way to give flexibility is to add data attributes selectively to some HTML elements. These can be used with CSS selectors by users of this library to conditionally apply styles:
|
||||
|
||||
```html
|
||||
<button
|
||||
type="button"
|
||||
data-uppy-element="upload-button"
|
||||
data-state={ctx.status}
|
||||
></button>
|
||||
```
|
||||
|
||||
## How to build these components
|
||||
|
||||
It's important to understand that an automated build script (bin/build-components.mjs) generates framework-specific wrappers for these Preact components.
|
||||
|
||||
Here is an example from the React wrapper created by the script.
|
||||
|
||||
```tsx
|
||||
import { useEffect, useRef, useContext, createElement as h } from 'react'
|
||||
import {
|
||||
Dropzone as PreactDropzone,
|
||||
type DropzoneProps,
|
||||
} from '@uppy/components'
|
||||
import { h as preactH } from 'preact'
|
||||
import { render as preactRender } from 'preact/compat'
|
||||
import { UppyContext } from './UppyContextProvider.js'
|
||||
|
||||
export default function Dropzone(props: Omit<DropzoneProps, 'ctx' | 'render'>) {
|
||||
const ref = useRef(null)
|
||||
const ctx = useContext(UppyContext)
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
preactRender(
|
||||
preactH(PreactDropzone, {
|
||||
...props,
|
||||
ctx,
|
||||
} satisfies DropzoneProps),
|
||||
ref.current,
|
||||
)
|
||||
}
|
||||
}, [ctx, props])
|
||||
|
||||
return <div ref={ref} />
|
||||
}
|
||||
```
|
||||
|
||||
You don't have to worry about how these wrappers are created but it's important to know when building the Preact components that you will always receive a `ctx` prop to work with. This is its type:
|
||||
|
||||
```ts
|
||||
import type Uppy from '@uppy/core'
|
||||
export type UploadStatus =
|
||||
| 'init'
|
||||
| 'ready'
|
||||
| 'uploading'
|
||||
| 'paused'
|
||||
| 'error'
|
||||
| 'complete'
|
||||
export type UppyContext = {
|
||||
uppy: Uppy | undefined
|
||||
status: UploadStatus
|
||||
progress: number
|
||||
}
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
Styling is done with Tailwind CSS 4.x. There is no Tailwind config file.
|
||||
|
||||
**IMPORTANT**: all classes have the `uppy:` prefix. Example: `bg-red-500` should become `uppy:bg-red-500`.
|
||||
|
||||
Use `clsx` for conditional styles.
|
||||
|
||||
```js
|
||||
import clsx from 'clsx';
|
||||
// or
|
||||
import { clsx } from 'clsx';
|
||||
|
||||
// Strings (variadic)
|
||||
clsx('foo', true && 'bar', 'baz');
|
||||
//=> 'foo bar baz'
|
||||
|
||||
// Objects
|
||||
clsx({ foo:true, bar:false, baz:isTrue() });
|
||||
//=> 'foo baz'
|
||||
|
||||
// Objects (variadic)
|
||||
clsx({ foo:true }, { bar:false }, null, { '--foobar':'hello' });
|
||||
//=> 'foo --foobar'
|
||||
|
||||
// Arrays
|
||||
clsx(['foo', 0, false, 'bar']);
|
||||
//=> 'foo bar'
|
||||
|
||||
// Arrays (variadic)
|
||||
clsx(['foo'], ['', 0, false, 'bar'], [['baz', [['hello'], 'there']]]);
|
||||
//=> 'foo bar baz hello there'
|
||||
|
||||
// Kitchen sink (with nesting)
|
||||
clsx('foo', [1 && 'bar', { baz:false, bat:null }, ['hello', ['world']]], 'cya');
|
||||
//=> 'foo bar hello world cya'
|
||||
```
|
||||
78
.cursor/rules/svelte.mdc
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
---
|
||||
description:
|
||||
globs: packages/@uppy/svelte/**,examples/sveltekit/**
|
||||
alwaysApply: false
|
||||
---
|
||||
I'm using svelte 5 instead of svelte 4 here is an overview of the changes.
|
||||
|
||||
## Overview of Changes
|
||||
|
||||
Svelte 5 introduces runes, a set of advanced primitives for controlling reactivity. The runes replace certain non-runes features and provide more explicit control over state and effects.
|
||||
|
||||
Snippets, along with render tags, help create reusable chunks of markup inside your components, reducing duplication and enhancing maintainability.
|
||||
|
||||
## Event Handlers in Svelte 5
|
||||
|
||||
In Svelte 5, event handlers are treated as standard HTML properties rather than Svelte-specific directives, simplifying their use and integrating them more closely with the rest of the properties in the component.
|
||||
|
||||
### Svelte 4 vs. Svelte 5:
|
||||
|
||||
**Before (Svelte 4):**
|
||||
```html
|
||||
<script>
|
||||
let count = 0;
|
||||
$: double = count * 2;
|
||||
$: {
|
||||
if (count > 10) alert('Too high!');
|
||||
}
|
||||
</script>
|
||||
<button on:click={() => count++}> {count} / {double}</button>
|
||||
```
|
||||
|
||||
**After (Svelte 5):**
|
||||
```html
|
||||
<script>
|
||||
import { $state, $effect, $derived } from 'svelte';
|
||||
|
||||
// Define state with runes
|
||||
let count = $state(0);
|
||||
|
||||
// Option 1: Using $derived for computed values
|
||||
let double = $derived(count * 2);
|
||||
|
||||
// Reactive effects using runes
|
||||
$effect(() => {
|
||||
if (count > 10) alert('Too high!');
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Standard HTML event attributes instead of Svelte directives -->
|
||||
<button onclick={() => count++}>
|
||||
{count} / {double}
|
||||
</button>
|
||||
|
||||
<!-- Alternatively, you can compute values inline -->
|
||||
<!-- <button onclick={() => count++}>
|
||||
{count} / {count * 2}
|
||||
</button> -->
|
||||
```
|
||||
|
||||
## Key Differences:
|
||||
|
||||
1. **Reactivity is Explicit**:
|
||||
- Svelte 5 uses `$state()` to explicitly mark reactive variables
|
||||
- `$derived()` replaces `$:` for computed values
|
||||
- `$effect()` replaces `$: {}` blocks for side effects
|
||||
|
||||
2. **Event Handling is Standardized**:
|
||||
- Svelte 4: `on:click={handler}`
|
||||
- Svelte 5: `onclick={handler}`
|
||||
|
||||
3. **Import Runes**:
|
||||
- All runes must be imported from 'svelte': `import { $state, $effect, $derived, $props, $slots } from 'svelte';`
|
||||
|
||||
4. **No More Event Modifiers**:
|
||||
- Svelte 4: `on:click|preventDefault={handler}`
|
||||
- Svelte 5: `onclick={e => { e.preventDefault(); handler(e); }}`
|
||||
|
||||
This creates clearer, more maintainable components compared to Svelte 4's previous syntax by making reactivity explicit and using standardized web platform features.
|
||||
149
.cursor/rules/uppy-core.mdc
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
---
|
||||
description: Read this when you are about to use the Uppy class from @uppy/core
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
# Uppy Core Summary
|
||||
|
||||
If you need to reference or work with the `Uppy` class from `@uppy/core`, here is an high-level overview of the class.
|
||||
|
||||
## `Uppy<M extends Meta, B extends Body>` Class
|
||||
|
||||
A modular file uploader. Manages plugins, state, events, and file handling.
|
||||
|
||||
**Generics:**
|
||||
|
||||
* `M`: Metadata object shape associated with files (`Meta`).
|
||||
* `B`: Body object shape associated with files (`Body`).
|
||||
|
||||
**Key Public Properties:**
|
||||
|
||||
* `opts: NonNullableUppyOptions<M, B>`: The fully resolved Uppy options.
|
||||
* `store: Store<State<M, B>>`: The state management store (defaults to `DefaultStore`).
|
||||
* `i18n: I18n`: The translation function.
|
||||
* `i18nArray: Translator['translateArray']`: The array translation function.
|
||||
* `locale: Locale`: The active locale object.
|
||||
|
||||
**Constructor:**
|
||||
|
||||
* `constructor(opts?: UppyOptionsWithOptionalRestrictions<M, B>)`
|
||||
|
||||
**Core Public Methods:**
|
||||
|
||||
* `use<T extends typeof BasePlugin<any, M, B>>(Plugin: T, ...args: OmitFirstArg<ConstructorParameters<T>>): this`: Adds a plugin instance.
|
||||
* `getPlugin<T extends UnknownPlugin<M, B> = UnknownPlugin<M, B>>(id: string): T | undefined`: Retrieves a plugin instance by ID.
|
||||
* `iteratePlugins(method: (plugin: UnknownPlugin<M, B>) => void): void`: Executes a function on all installed plugins.
|
||||
* `removePlugin(instance: UnknownPlugin<M, B>): void`: Removes a plugin instance.
|
||||
* `addFile(file: File | MinimalRequiredUppyFile<M, B>): UppyFile<M, B>['id']`: Adds a single file.
|
||||
* `addFiles(fileDescriptors: MinimalRequiredUppyFile<M, B>[]): void`: Adds multiple files.
|
||||
* `removeFile(fileID: string): void`: Removes a file by ID.
|
||||
* `removeFiles(fileIDs: string[]): void`: Removes multiple files by ID.
|
||||
* `getFile(fileID: string): UppyFile<M, B>`: Retrieves a file object by ID.
|
||||
* `getFiles(): UppyFile<M, B>[]`: Retrieves all file objects as an array.
|
||||
* `setOptions(newOpts: MinimalRequiredOptions<M, B>): void`: Updates Uppy options.
|
||||
* `setState(patch?: Partial<State<M, B>>): void`: Updates the Uppy state.
|
||||
* `getState(): State<M, B>`: Retrieves the current Uppy state.
|
||||
* `setFileState(fileID: string, state: Partial<UppyFile<M, B>>): void`: Updates the state for a specific file.
|
||||
* `setMeta(data: Partial<M>): void`: Merges metadata into the global `state.meta` and all file `meta` objects.
|
||||
* `setFileMeta(fileID: string, data: Partial<M>): void`: Merges metadata into a specific file's `meta` object.
|
||||
* `upload(): Promise<NonNullable<UploadResult<M, B>> | undefined>`: Starts the upload process for all new files.
|
||||
* `retryUpload(fileID: string): Promise<UploadResult<M, B> | undefined>`: Retries a failed upload for a specific file.
|
||||
* `retryAll(): Promise<UploadResult<M, B> | undefined>`: Retries all failed uploads.
|
||||
* `cancelAll(): void`: Cancels all uploads and removes all files.
|
||||
* `pauseResume(fileID: string): boolean | undefined`: Toggles pause/resume state for a resumable upload.
|
||||
* `pauseAll(): void`: Pauses all resumable uploads.
|
||||
* `resumeAll(): void`: Resumes all paused uploads.
|
||||
* `info(message: string | { message: string; details?: string | Record<string, string> }, type?: LogLevel, duration?: number): void`: Displays an informational message via UI plugins.
|
||||
* `hideInfo(): void`: Hides the oldest info message.
|
||||
* `log(message: unknown, type?: 'error' | 'warning'): void`: Logs a message using the configured logger.
|
||||
* `on<K extends keyof UppyEventMap<M, B>>(event: K, callback: UppyEventMap<M, B>[K]): this`: Registers an event listener.
|
||||
* `off<K extends keyof UppyEventMap<M, B>>(event: K, callback: UppyEventMap<M, B>[K]): this`: Unregisters an event listener.
|
||||
* `emit<T extends keyof UppyEventMap<M, B>>(event: T, ...args: Parameters<UppyEventMap<M, B>[T]>): void`: Emits an event.
|
||||
* `destroy(): void`: Uninstalls all plugins and cleans up the Uppy instance.
|
||||
|
||||
**Internal Lifecycle Methods (Called by Uppy):**
|
||||
|
||||
* `addPreProcessor(fn: Processor): void`
|
||||
* `removePreProcessor(fn: Processor): boolean`
|
||||
* `addPostProcessor(fn: Processor): void`
|
||||
* `removePostProcessor(fn: Processor): boolean`
|
||||
* `addUploader(fn: Processor): void`
|
||||
* `removeUploader(fn: Processor): boolean`
|
||||
|
||||
---
|
||||
|
||||
## Key Associated Types
|
||||
|
||||
* **`UppyFile<M extends Meta, B extends Body>`**: Represents a file within Uppy.
|
||||
* `id: string`: Unique file ID.
|
||||
* `source?: string`: Source plugin ID.
|
||||
* `name: string`: File name.
|
||||
* `type?: string`: MIME type.
|
||||
* `data: Blob | File`: The actual file data.
|
||||
* `meta: M & { name: string, type: string }`: User and Uppy metadata.
|
||||
* `size: number | null`: File size in bytes.
|
||||
* `isRemote: boolean`: If the file is from a remote source (Companion).
|
||||
* `remote?: { requestClientId: string, ... }`: Remote source details.
|
||||
* `progress: FileProgressStarted | FileProgressNotStarted`: Upload progress state.
|
||||
* `error?: string`: Error message if upload failed.
|
||||
* `isPaused?: boolean`: If upload is paused (for resumable uploads).
|
||||
* `isGhost?: boolean`: If file is restored without data (Golden Retriever).
|
||||
* `response?: { status: number, body: B, uploadURL?: string, ... }`: Upload response details.
|
||||
* `preview?: string`: URL to a preview image.
|
||||
* `[key: string]: any`: Extensible with plugin-specific state.
|
||||
|
||||
* **`State<M extends Meta, B extends Body>`**: The main Uppy state object.
|
||||
* `files: { [fileID: string]: UppyFile<M, B> }`: Object map of files by ID.
|
||||
* `currentUploads: { [uploadID: string]: CurrentUpload<M, B> }`: Map of active uploads.
|
||||
* `capabilities: { uploadProgress: boolean, individualCancellation: boolean, resumableUploads: boolean, ... }`: Detected/configured capabilities.
|
||||
* `totalProgress: number`: Overall upload progress (0-100).
|
||||
* `meta: M`: Global metadata.
|
||||
* `info: Array<{ type: LogLevel, message: string, details?: string | Record<string, string> }>`: Info messages for UI display.
|
||||
* `error: string | null`: Global error message.
|
||||
* `allowNewUpload: boolean`: Whether new uploads can be started.
|
||||
* `plugins: { [pluginID: string]: Record<string, unknown> }`: State managed by plugins.
|
||||
* `recoveredState: null | { files: ..., currentUploads: ... }`: State recovered by Golden Retriever.
|
||||
|
||||
* **`UppyOptions<M extends Meta, B extends Body>`**: Options for the Uppy constructor.
|
||||
* `id?: string`: Instance ID (default: 'uppy').
|
||||
* `autoProceed?: boolean`: Start upload automatically after files are added (default: false).
|
||||
* `allowMultipleUploadBatches?: boolean`: Allow adding files while an upload is in progress (default: true).
|
||||
* `debug?: boolean`: Enable debug logging (default: false).
|
||||
* `restrictions: Restrictions`: File restrictions (type, size, number).
|
||||
* `meta?: M`: Initial global metadata.
|
||||
* `onBeforeFileAdded?: (currentFile: UppyFile<M, B>, files: { ... }) => UppyFile<M, B> | boolean | undefined`: Hook before a file is added.
|
||||
* `onBeforeUpload?: (files: { ... }) => { ... } | boolean`: Hook before an upload starts.
|
||||
* `locale?: Locale`: Custom locale strings.
|
||||
* `store?: Store<State<M, B>>`: Custom state store.
|
||||
* `logger?: { debug, warn, error }`: Custom logger.
|
||||
* `infoTimeout?: number`: Duration for info messages (ms).
|
||||
|
||||
* **`Restrictions`**: File restriction options.
|
||||
* `maxFileSize?: number | null`: Max individual file size (bytes).
|
||||
* `minFileSize?: number | null`: Min individual file size (bytes).
|
||||
* `maxTotalFileSize?: number | null`: Max total size of all files (bytes).
|
||||
* `maxNumberOfFiles?: number | null`: Max number of files allowed.
|
||||
* `minNumberOfFiles?: number | null`: Min number of files required.
|
||||
* `allowedFileTypes?: string[] | null`: Allowed MIME types or extensions (e.g., `['image/*', '.pdf']`).
|
||||
* `requiredMetaFields?: string[]`: Meta fields that must be present before upload.
|
||||
|
||||
* **`UppyEventMap<M extends Meta, B extends Body>`**: Map of event names to their callback signatures (includes events like `file-added`, `upload-progress`, `upload-success`, `complete`, `error`, `restriction-failed`, etc.).
|
||||
|
||||
* **`UploadResult<M extends Meta, B extends Body>`**: The object returned when an upload completes.
|
||||
* `successful?: UppyFile<M, B>[]`: Array of successfully uploaded files.
|
||||
* `failed?: UppyFile<M, B>[]`: Array of files that failed to upload.
|
||||
* `uploadID?: string`: The ID of the upload batch.
|
||||
|
||||
* **`BasePlugin<...> `**: The base class for all Uppy plugins.
|
||||
* `id: string`: Unique plugin ID.
|
||||
* `type: string`: Plugin type ('acquirer', 'modifier', 'uploader', 'presenter', 'orchestrator', 'logger').
|
||||
* `uppy: Uppy<M, B>`: Reference to the Uppy instance.
|
||||
* `install(): void`: Called when the plugin is added.
|
||||
* `uninstall(): void`: Called when the plugin is removed.
|
||||
* `update(state: Partial<State<M, B>>): void`: Called on every Uppy state update.
|
||||
|
||||
* **`Processor`**: Type for pre/post/upload processor functions `(fileIDs: string[], uploadID: string) => Promise<unknown> | void`.
|
||||
|
||||
* **`LogLevel`**: `'info' | 'warning' | 'error' | 'success'`.
|
||||
|
||||
* **`PartialTree`**: Type used by Provider plugins (like Google Drive) to represent folder structures (`(PartialTreeFile | PartialTreeFolder)[]`).
|
||||
10
.env.example
|
|
@ -15,6 +15,9 @@ COMPANION_PREAUTH_SECRET=development2
|
|||
# NOTE: Only enable this in development. Enabling it in production is a security risk
|
||||
COMPANION_ALLOW_LOCAL_URLS=true
|
||||
|
||||
COMPANION_ENABLE_URL_ENDPOINT=true
|
||||
COMPANION_ENABLE_GOOGLE_PICKER_ENDPOINT=true
|
||||
|
||||
# to enable S3
|
||||
COMPANION_AWS_KEY="YOUR AWS KEY"
|
||||
COMPANION_AWS_SECRET="YOUR AWS SECRET"
|
||||
|
|
@ -89,3 +92,10 @@ VITE_TRANSLOADIT_TEMPLATE=***
|
|||
VITE_TRANSLOADIT_SERVICE_URL=https://api2.transloadit.com
|
||||
# Fill in if you want requests sent to Transloadit to be signed:
|
||||
# VITE_TRANSLOADIT_SECRET=***
|
||||
|
||||
# For Google Photos Picker and Google Drive Picker:
|
||||
VITE_GOOGLE_PICKER_CLIENT_ID=***
|
||||
|
||||
# For Google Drive Picker
|
||||
VITE_GOOGLE_PICKER_API_KEY=***
|
||||
VITE_GOOGLE_PICKER_APP_ID=***
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
node_modules
|
||||
lib
|
||||
dist
|
||||
coverage
|
||||
test/lib/**
|
||||
test/endtoend/*/build
|
||||
examples/svelte-example/public/build/
|
||||
bundle-legacy.js
|
||||
523
.eslintrc.js
|
|
@ -1,523 +0,0 @@
|
|||
/* eslint-disable quote-props */
|
||||
|
||||
'use strict'
|
||||
|
||||
const svgPresentationAttributes = [
|
||||
'alignment-baseline', 'baseline-shift', 'class', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolatio', 'color-interpolatio-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'display', 'dominant-baseline', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'overflow', 'pointer-events', 'shape-rendering', 'stop-color', 'stop-opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'transform', 'transform-origin', 'unicode-bidi', 'vector-effect', 'visibility', 'word-spacing', 'writing-mod',
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['transloadit', 'prettier'],
|
||||
env: {
|
||||
es6: true,
|
||||
jest: true,
|
||||
node: true,
|
||||
// extra:
|
||||
browser: true,
|
||||
},
|
||||
globals: {
|
||||
globalThis: true,
|
||||
hexo: true,
|
||||
window: true,
|
||||
},
|
||||
plugins: [
|
||||
'@babel/eslint-plugin',
|
||||
'jest',
|
||||
'markdown',
|
||||
'node',
|
||||
'prefer-import',
|
||||
'promise',
|
||||
'react',
|
||||
// extra:
|
||||
'compat',
|
||||
'jsdoc',
|
||||
'no-only-tests',
|
||||
'unicorn',
|
||||
],
|
||||
parser: '@babel/eslint-parser',
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
ecmaVersion: 2022,
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// transloadit rules we are actually ok with in the uppy repo
|
||||
'import/extensions': 'off',
|
||||
'object-shorthand': ['error', 'always'],
|
||||
'strict': 'off',
|
||||
'key-spacing': 'off',
|
||||
'max-classes-per-file': ['error', 2],
|
||||
'react/no-unknown-property': ['error', {
|
||||
ignore: svgPresentationAttributes,
|
||||
}],
|
||||
|
||||
// Special rules for CI:
|
||||
...(process.env.CI && {
|
||||
// Some imports are available only after a full build, which we don't do on CI.
|
||||
'import/no-unresolved': 'off',
|
||||
}),
|
||||
|
||||
// rules we want to enforce
|
||||
'array-callback-return': 'error',
|
||||
'func-names': 'error',
|
||||
'import/no-dynamic-require': 'error',
|
||||
'import/no-extraneous-dependencies': 'error',
|
||||
'max-len': 'error',
|
||||
'no-empty': 'error',
|
||||
'no-bitwise': 'error',
|
||||
'no-continue': 'error',
|
||||
'no-lonely-if': 'error',
|
||||
'no-nested-ternary': 'error',
|
||||
'no-restricted-properties': 'error',
|
||||
'no-return-assign': 'error',
|
||||
'no-underscore-dangle': 'error',
|
||||
'no-unused-expressions': 'error',
|
||||
'no-unused-vars': 'error',
|
||||
'no-useless-concat': 'error',
|
||||
'no-var': 'error',
|
||||
'node/handle-callback-err': 'error',
|
||||
'prefer-destructuring': 'error',
|
||||
'prefer-spread': 'error',
|
||||
'unicorn/prefer-node-protocol': 'error',
|
||||
|
||||
'react/button-has-type': 'error',
|
||||
'react/forbid-prop-types': 'error',
|
||||
'react/no-access-state-in-setstate': 'error',
|
||||
'react/no-array-index-key': 'error',
|
||||
'react/no-deprecated': 'error',
|
||||
'react/no-this-in-sfc': 'error',
|
||||
'react/no-will-update-set-state': 'error',
|
||||
'react/prefer-stateless-function': 'error',
|
||||
'react/require-default-props': ['error', {
|
||||
forbidDefaultForRequired: true,
|
||||
functions: 'ignore',
|
||||
}],
|
||||
'react/sort-comp': 'error',
|
||||
'react/static-property-placement': 'off',
|
||||
'react/style-prop-object': 'error',
|
||||
|
||||
// accessibility
|
||||
'jsx-a11y/alt-text': 'error',
|
||||
'jsx-a11y/anchor-has-content': 'error',
|
||||
'jsx-a11y/click-events-have-key-events': 'error',
|
||||
'jsx-a11y/control-has-associated-label': 'error',
|
||||
'jsx-a11y/label-has-associated-control': 'error',
|
||||
'jsx-a11y/media-has-caption': 'error',
|
||||
'jsx-a11y/mouse-events-have-key-events': 'error',
|
||||
'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error',
|
||||
'jsx-a11y/no-noninteractive-element-interactions': 'error',
|
||||
'jsx-a11y/no-static-element-interactions': 'error',
|
||||
|
||||
// compat
|
||||
'compat/compat': ['error'],
|
||||
|
||||
// jsdoc
|
||||
'jsdoc/check-alignment': 'error',
|
||||
'jsdoc/check-examples': 'off', // cannot yet be supported for ESLint 8, see https://github.com/eslint/eslint/issues/14745
|
||||
'jsdoc/check-param-names': 'error',
|
||||
'jsdoc/check-syntax': 'error',
|
||||
'jsdoc/check-tag-names': ['error', { jsxTags: true }],
|
||||
'jsdoc/check-types': 'error',
|
||||
'jsdoc/valid-types': 'error',
|
||||
'jsdoc/check-indentation': ['off'],
|
||||
},
|
||||
|
||||
settings: {
|
||||
'import/core-modules': ['tsd'],
|
||||
react: {
|
||||
pragma: 'h',
|
||||
},
|
||||
jsdoc: {
|
||||
mode: 'typescript',
|
||||
},
|
||||
polyfills: [
|
||||
'Promise',
|
||||
'fetch',
|
||||
'Object.assign',
|
||||
'document.querySelector',
|
||||
],
|
||||
},
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'*.jsx',
|
||||
'*.tsx',
|
||||
'packages/@uppy/react-native/**/*.js',
|
||||
],
|
||||
parser: 'espree',
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
{
|
||||
name: '__filename',
|
||||
message: 'Use import.meta.url instead',
|
||||
},
|
||||
{
|
||||
name: '__dirname',
|
||||
message: 'Not available in ESM',
|
||||
},
|
||||
{
|
||||
name: 'exports',
|
||||
message: 'Not available in ESM',
|
||||
},
|
||||
{
|
||||
name: 'module',
|
||||
message: 'Not available in ESM',
|
||||
},
|
||||
{
|
||||
name: 'require',
|
||||
message: 'Use import instead',
|
||||
},
|
||||
{
|
||||
name: 'JSX',
|
||||
message: 'Use h.JSX.Element, ComponentChild, or ComponentChildren from Preact',
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
message: 'Import the value instead of relying on global.React.',
|
||||
},
|
||||
],
|
||||
'import/extensions': ['error', 'ignorePackages'],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'*.mjs',
|
||||
'e2e/clients/**/*.js',
|
||||
'examples/aws-companion/*.js',
|
||||
'examples/aws-php/*.js',
|
||||
'examples/bundled/*.js',
|
||||
'examples/custom-provider/client/*.js',
|
||||
'examples/digitalocean-spaces/*.js',
|
||||
'examples/multiple-instances/*.js',
|
||||
'examples/node-xhr/*.js',
|
||||
'examples/php-xhr/*.js',
|
||||
'examples/python-xhr/*.js',
|
||||
'examples/react-example/*.js',
|
||||
'examples/redux/*.js',
|
||||
'examples/transloadit/*.js',
|
||||
'examples/transloadit-markdown-bin/*.js',
|
||||
'examples/xhr-bundle/*.js',
|
||||
'private/dev/*.js',
|
||||
'private/release/*.js',
|
||||
'private/remark-lint-uppy/*.js',
|
||||
'packages/@uppy/!(companion|react-native)/**/*.js',
|
||||
],
|
||||
parser: 'espree',
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: false,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'import/named': 'off', // Disabled because that rule tries and fails to parse JSX dependencies.
|
||||
'import/no-named-as-default': 'off', // Disabled because that rule tries and fails to parse JSX dependencies.
|
||||
'import/no-named-as-default-member': 'off', // Disabled because that rule tries and fails to parse JSX dependencies.
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
{
|
||||
name: '__filename',
|
||||
message: 'Use import.meta.url instead',
|
||||
},
|
||||
{
|
||||
name: '__dirname',
|
||||
message: 'Not available in ESM',
|
||||
},
|
||||
{
|
||||
name: 'exports',
|
||||
message: 'Not available in ESM',
|
||||
},
|
||||
{
|
||||
name: 'module',
|
||||
message: 'Not available in ESM',
|
||||
},
|
||||
{
|
||||
name: 'require',
|
||||
message: 'Use import instead',
|
||||
},
|
||||
{
|
||||
name: 'JSX',
|
||||
message: 'Use h.JSX.Element, ComponentChild, or ComponentChildren from Preact',
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
message: 'Import the value instead of relying on global.React.',
|
||||
},
|
||||
],
|
||||
'import/extensions': ['error', 'ignorePackages'],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/uppy/*.mjs'],
|
||||
rules: {
|
||||
'import/first': 'off',
|
||||
'import/newline-after-import': 'off',
|
||||
'import/no-extraneous-dependencies': ['error', {
|
||||
devDependencies: true,
|
||||
}],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'packages/@uppy/*/types/*.d.ts',
|
||||
],
|
||||
rules : {
|
||||
'import/no-unresolved': 'off',
|
||||
'max-classes-per-file': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'packages/@uppy/dashboard/src/components/**/*.jsx',
|
||||
],
|
||||
rules: {
|
||||
'react/destructuring-assignment': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
// Those need looser rules, and cannot be made part of the stricter rules above.
|
||||
// TODO: update those to more modern code when switch to ESM is complete
|
||||
'examples/react-native-expo/*.js',
|
||||
'examples/svelte-example/**/*.js',
|
||||
'examples/vue/**/*.js',
|
||||
'examples/vue3/**/*.js',
|
||||
],
|
||||
rules: {
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
'varsIgnorePattern': 'React',
|
||||
},
|
||||
],
|
||||
},
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['./packages/@uppy/companion/**/*.js'],
|
||||
rules: {
|
||||
'no-underscore-dangle': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'*.test.js',
|
||||
'*.test.ts',
|
||||
'test/endtoend/*.js',
|
||||
'bin/**.js',
|
||||
],
|
||||
rules: {
|
||||
'compat/compat': ['off'],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'bin/**.js',
|
||||
'bin/**.mjs',
|
||||
'examples/**/*.cjs',
|
||||
'examples/**/*.js',
|
||||
'packages/@uppy/companion/test/**/*.js',
|
||||
'test/**/*.js',
|
||||
'test/**/*.ts',
|
||||
'*.test.js',
|
||||
'*.test.ts',
|
||||
'*.test-d.ts',
|
||||
'*.test-d.tsx',
|
||||
'postcss.config.js',
|
||||
'.eslintrc.js',
|
||||
'private/**/*.js',
|
||||
'private/**/*.mjs',
|
||||
],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'import/no-extraneous-dependencies': ['error', {
|
||||
devDependencies: true,
|
||||
}],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
files: [
|
||||
'packages/@uppy/locales/src/*.js',
|
||||
'packages/@uppy/locales/template.js',
|
||||
],
|
||||
rules: {
|
||||
camelcase: ['off'],
|
||||
'quote-props': ['error', 'as-needed', { 'numbers': true }],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
files: ['test/endtoend/*/*.mjs', 'test/endtoend/*/*.ts'],
|
||||
rules: {
|
||||
// we mostly import @uppy stuff in these files.
|
||||
'import/no-extraneous-dependencies': ['off'],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['test/endtoend/*/*.js'],
|
||||
env: {
|
||||
mocha: true,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
files: ['packages/@uppy/react/src/**/*.js'],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': ['error', {
|
||||
peerDependencies: true,
|
||||
}],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
files: ['**/*.md', '*.md'],
|
||||
processor: 'markdown/markdown',
|
||||
},
|
||||
{
|
||||
files: ['docs/**/*.md/*.js'],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.md/*.js', '**/*.md/*.javascript'],
|
||||
excludedFiles: ["docs/**/*"],
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
'react/destructuring-assignment': 'off',
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
{
|
||||
name: '__filename',
|
||||
message: 'Use import.meta.url instead',
|
||||
},
|
||||
{
|
||||
name: '__dirname',
|
||||
message: 'Not available in ESM',
|
||||
},
|
||||
{
|
||||
name: 'exports',
|
||||
message: 'Not available in ESM',
|
||||
},
|
||||
{
|
||||
name: 'module',
|
||||
message: 'Not available in ESM',
|
||||
},
|
||||
{
|
||||
name: 'require',
|
||||
message: 'Use import instead',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.ts', '**/*.md/*.ts', '**/*.md/*.typescript', '**/*.tsx', '**/*.md/*.tsx'],
|
||||
excludedFiles: ['examples/angular-example/**/*.ts', 'packages/@uppy/angular/**/*.ts'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
],
|
||||
rules: {
|
||||
'no-extra-semi': 'off',
|
||||
'no-restricted-syntax': ['error', {
|
||||
selector: 'ImportDeclaration[importKind="type"][source.value=/^@uppy\\x2F[a-z-0-9]+\\x2F/]:not([source.value=/^@uppy\\x2Futils\\x2F/]):not([source.value=/\\.js$/])',
|
||||
message: 'Use ".js" file extension for import type declarations from a different package',
|
||||
}, {
|
||||
selector: 'ImportDeclaration[importKind="type"][source.value=/^\\.\\.?\\x2F.+\\.js$/]',
|
||||
message: 'Do not use ".js" file extension for relative import type declarations',
|
||||
}, {
|
||||
selector: 'ImportDeclaration[source.value=/^@uppy\\x2Futils\\x2Flib\\x2F.+\\.[mc]?[jt]sx?$/]',
|
||||
message: 'Do not use file extension when importing from @uppy/utils',
|
||||
}],
|
||||
'import/extensions': ['error', 'ignorePackages'],
|
||||
'import/prefer-default-export': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-extra-semi': 'off',
|
||||
'@typescript-eslint/no-namespace': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['packages/@uppy/*/src/**/*.ts'],
|
||||
excludedFiles: ['packages/@uppy/**/*.test.ts', 'packages/@uppy/core/src/mocks/*.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.md/*.*'],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
'no-console': 'off',
|
||||
'no-undef': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/react/*.md/*.js', '**/react.md/*.js', '**/react-*.md/*.js', '**/react/**/*.test-d.tsx'],
|
||||
settings: {
|
||||
react: { pragma: 'React' },
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/react/**/*.test-d.tsx'],
|
||||
rules: {
|
||||
'import/extensions': 'off',
|
||||
'import/no-useless-path-segments': 'off',
|
||||
'no-alert': 'off',
|
||||
'no-inner-declarations': 'off',
|
||||
'no-lone-blocks': 'off',
|
||||
'no-unused-expressions': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['e2e/**/*.ts'],
|
||||
extends: ['plugin:cypress/recommended'],
|
||||
},
|
||||
{
|
||||
files: ['e2e/**/*.ts', 'e2e/**/*.js', 'e2e/**/*.jsx', 'e2e/**/*.mjs'],
|
||||
rules: {
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'no-console': 'off',
|
||||
'no-only-tests/no-only-tests': 'error',
|
||||
'no-unused-expressions': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["packages/@uppy/vue/**"],
|
||||
rules: {
|
||||
'react-hooks/rules-of-hooks': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
446
.github/CONTRIBUTING.md
vendored
|
|
@ -7,7 +7,7 @@ instructions.
|
|||
After you have successfully forked the repository, clone it locally.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/transloadit/uppy.git
|
||||
git clone https://github.com/[your-username]/uppy.git
|
||||
cd uppy
|
||||
```
|
||||
|
||||
|
|
@ -24,24 +24,37 @@ corepack enable
|
|||
|
||||
## Development
|
||||
|
||||
First of all, install Uppy dependencies:
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Basic
|
||||
|
||||
To run a basic development version of Uppy, run:
|
||||
Build all packages:
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
yarn build
|
||||
```
|
||||
|
||||
and go to http://localhost:5174 (or whatever link the yarn command outputted).
|
||||
As you edit Uppy code, the browser will live reload the changes.
|
||||
### Development Commands
|
||||
|
||||
### Companion
|
||||
- `yarn dev` - Start development server at http://localhost:5174
|
||||
- `yarn dev:with-companion` - Start dev server with Companion for cloud integrations
|
||||
- `yarn start:companion` - Start only Companion server at http://localhost:3020
|
||||
- `yarn build:watch` - Build packages in watch mode
|
||||
- `yarn test` - Run tests for all packages
|
||||
- `yarn test:watch` - Run tests in watch mode
|
||||
- `yarn typecheck` - Run TypeScript type checking
|
||||
- `yarn check` - Run Biome linting and formatting
|
||||
|
||||
### Headless components
|
||||
|
||||
When adding a new component to `@uppy/components`, you have to run `yarn migrate:components` from root
|
||||
to migrate the Preact components to React, Svelte, and Vue.
|
||||
|
||||
This is not needed for changing existing components.
|
||||
|
||||
## Companion
|
||||
|
||||
If you’d like to work on features that the basic development version of Uppy
|
||||
doesn’t support, such as Uppy integrations with Instagram/Google Drive/Facebook
|
||||
|
|
@ -62,12 +75,6 @@ This would get the Companion instance running on `http://localhost:3020`. It
|
|||
uses [nodemon](https://github.com/remy/nodemon) so it will automatically restart
|
||||
when files are changed.
|
||||
|
||||
### Live example
|
||||
|
||||
An example server is running at <https://companion.uppy.io>, which is deployed
|
||||
with
|
||||
[Kubernetes](https://github.com/transloadit/uppy/blob/main/packages/%40uppy/companion/KUBERNETES.md)
|
||||
|
||||
### How the Authentication and Token mechanism works
|
||||
|
||||
This section describes how Authentication works between Companion and Providers.
|
||||
|
|
@ -101,415 +108,6 @@ Authenticates and Uploads from Dropbox through Companion:
|
|||
- Companion reports progress to Uppy, as if it were a local upload.
|
||||
- Completed!
|
||||
|
||||
### Requiring files
|
||||
|
||||
- If we are `require()`ing a file from the same subpackage, we can freely use
|
||||
relative imports as long as the required file is under the `src` directory
|
||||
(for example to import `@uppy/dashboard/src/utils/hi.js` from
|
||||
`@uppy/dashboard/src/index.js`, use `require('./utils/hi.js')`).
|
||||
- But if we want to `require()` some file from another subpackage - we should
|
||||
use global @uppy requires, and they should always be in the form of
|
||||
`@uppy/:packageName/(lib instead of src)/(same path).js`
|
||||
|
||||
## Tests
|
||||
|
||||
### Unit tests
|
||||
|
||||
Unit tests are using Jest and can be run with:
|
||||
|
||||
```bash
|
||||
yarn test:unit
|
||||
```
|
||||
|
||||
### End-to-End tests
|
||||
|
||||
We use [Cypress](https://www.cypress.io/) for our e2e test suite. Be sure to
|
||||
checkout
|
||||
“[Writing your first test](https://docs.cypress.io/guides/getting-started/writing-your-first-test#Add-a-test-file)”
|
||||
and the
|
||||
“[Introduction to Cypress](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Cypress-Can-Be-Simple-Sometimes)”.
|
||||
You should also be aware of the
|
||||
“[Best Practices](https://docs.cypress.io/guides/references/best-practices)”.
|
||||
|
||||
To get started make sure you have your `.env` set up. Copy the contents of
|
||||
`.env.example` to a file named `.env` and add the values relevant for the
|
||||
test(s) you are trying to run.
|
||||
|
||||
To start the testing suite run:
|
||||
|
||||
```
|
||||
yarn e2e
|
||||
```
|
||||
|
||||
This will run Cypress in watch-mode, and it will pick up and rebuild any changes
|
||||
to JS files. If you need to change other files (like CSS for example), you need
|
||||
to run the respective `yarn build:*` scripts.
|
||||
|
||||
Alternatively the following command is the same as the above, except it doesn’t
|
||||
run `build` first:
|
||||
|
||||
```
|
||||
yarn e2e:skip-build
|
||||
```
|
||||
|
||||
To generate the boilerplate for a new test run:
|
||||
|
||||
```
|
||||
yarn e2e:generate
|
||||
```
|
||||
|
||||
## Releases
|
||||
|
||||
Releases are managed by GitHub Actions, here’s an overview of the process to
|
||||
release a new Uppy version:
|
||||
|
||||
- Run `yarn release` on your local machine.
|
||||
- Follow the instructions and select what packages to release. **Warning:**
|
||||
skipping packages results in those changes being “lost”, meaning they won’t be
|
||||
picked up in the changelog automatically next release. Always try to release
|
||||
all.
|
||||
- Before committing, check if the generated files look good.
|
||||
- When asked to edit the next CHANGELOG, only include changes related to the
|
||||
package(s) you selected for release.
|
||||
- Push to the Transloadit repository using the command given by the tool. Do not
|
||||
open a PR yourself, the GitHub Actions will create one and assign you to it.
|
||||
- Wait for all the GitHub Actions checks to pass. If one fails, try to figure
|
||||
out why. Do not go ahead without consulting the rest of the team.
|
||||
- Review the PR thoroughly, and if everything looks good to you, approve the PR.
|
||||
Do not merge it manually!
|
||||
- After the PR is automatically merged, the demos on transloadit.com should also
|
||||
be updated. Check that some things work locally:
|
||||
- the demos in the demo section work (try one that uses an import robot, and
|
||||
one that you need to upload to)
|
||||
- the demos on the homepage work and can import from Google Drive, Instagram,
|
||||
Dropbox, etc.
|
||||
|
||||
If you don’t have access to the transloadit.com source code ping @arturi or
|
||||
@goto-bus-stop and we’ll pick it up. :sparkles:
|
||||
|
||||
### Releasing hotfix patch
|
||||
|
||||
#### Companion hotfix
|
||||
|
||||
First checkout the tag of the version you want to patch:
|
||||
|
||||
```bash
|
||||
git checkout @uppy/companion@x.y.z
|
||||
```
|
||||
|
||||
Now create a branch for your hotfix:
|
||||
|
||||
```bash
|
||||
git checkout -b x.y.z-hotfix
|
||||
```
|
||||
|
||||
Run yarn to make sure all packages are consistent:
|
||||
|
||||
```bash
|
||||
corepack yarn
|
||||
```
|
||||
|
||||
Now navigate to the Companion workspace:
|
||||
|
||||
```bash
|
||||
cd packages/@uppy/companion
|
||||
```
|
||||
|
||||
**Now cherry pick your desired commits**.
|
||||
|
||||
Next edit `CHANGELOG.md` and then commit it:
|
||||
|
||||
```bash
|
||||
git add CHANGELOG.md
|
||||
git commit -m 'Update changelog'
|
||||
```
|
||||
|
||||
Now let’s create the version & tag:
|
||||
|
||||
```bash
|
||||
mkdir -p .git && npm version --workspaces-update=false --tag-version-prefix='@uppy/companion@' patch
|
||||
```
|
||||
|
||||
**Important:** Build Companion lib folder
|
||||
|
||||
```bash
|
||||
yarn run build
|
||||
```
|
||||
|
||||
Run a “dry-run” first:
|
||||
|
||||
```bash
|
||||
corepack yarn pack
|
||||
```
|
||||
|
||||
If the earlier command succeeded, let’s publish!
|
||||
|
||||
```bash
|
||||
corepack yarn npm publish --access public --tag=none
|
||||
```
|
||||
|
||||
Now we can push our branch and tags.
|
||||
|
||||
```bash
|
||||
git push && git push --tags
|
||||
```
|
||||
|
||||
#### Hotfix other packages
|
||||
|
||||
For other Uppy packages, the process should be like Companion, but hasn’t been
|
||||
documented yet. Make sure to remember to run `yarn` as well as building the
|
||||
package first, then you can release it. If you do release any other packages,
|
||||
please update this doc.
|
||||
|
||||
## CSS guidelines
|
||||
|
||||
The CSS standards followed in this project closely resemble those from
|
||||
[Medium’s CSS Guidelines](https://gist.github.com/fat/a47b882eb5f84293c4ed). If
|
||||
something is not mentioned here, follow their guidelines.
|
||||
|
||||
### Naming conventions
|
||||
|
||||
This project uses naming conventions adopted from the SUIT CSS framework.
|
||||
[Read about them here](https://github.com/suitcss/suit/blob/master/doc/naming-conventions.md).
|
||||
|
||||
To quickly summarize:
|
||||
|
||||
#### Utilities
|
||||
|
||||
Syntax: `u-[sm-|md-|lg-]<utilityName>`
|
||||
|
||||
```css
|
||||
.u-utilityName
|
||||
.u-floatLeft
|
||||
.u-lg-col6
|
||||
```
|
||||
|
||||
#### Components
|
||||
|
||||
Syntax: `[<namespace>-]<ComponentName>[-descendentName][--modifierName]`
|
||||
|
||||
```css
|
||||
.twt-Button /* Namespaced component */
|
||||
.MyComponent /* Components pascal cased */
|
||||
.Button--default /* Modified button style */
|
||||
.Button--large
|
||||
|
||||
.Tweet
|
||||
.Tweet-header /* Descendents */
|
||||
.Tweet-bodyText
|
||||
|
||||
.Accordion.is-collapsed /* State of component */
|
||||
.Accordion.is-expanded
|
||||
```
|
||||
|
||||
### SASS
|
||||
|
||||
This project uses SASS, with some limitations on nesting. One-level-deep nesting
|
||||
is allowed, but nesting may not extend a selector by using the `&` operator. For
|
||||
example:
|
||||
|
||||
```sass
|
||||
/* BAD */
|
||||
.Button {
|
||||
&--disabled {
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
/* GOOD */
|
||||
.Button {
|
||||
...
|
||||
}
|
||||
|
||||
.Button--disabled {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Mobile-first responsive approach
|
||||
|
||||
Style to the mobile breakpoint with your selectors, then use `min-width` media
|
||||
queries to add any styles to the tablet or desktop breakpoints.
|
||||
|
||||
### Selector, rule ordering
|
||||
|
||||
- All selectors are sorted alphabetically and by type.
|
||||
- HTML elements go above classes and IDs in a file.
|
||||
- Rules are sorted alphabetically.
|
||||
|
||||
```scss
|
||||
/* BAD */
|
||||
.wrapper {
|
||||
width: 940px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.article {
|
||||
width: 100%;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
/* GOOD */
|
||||
h1 {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.article {
|
||||
padding: 32px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
margin: auto;
|
||||
width: 940px;
|
||||
}
|
||||
```
|
||||
|
||||
## Adding a new integration
|
||||
|
||||
Before opening a pull request for the new integration, open an issue to discuss
|
||||
said integration with the Uppy team. After discussing the integration, you can
|
||||
get started on it. First off, you need to construct the basic components for
|
||||
your integration. The following components are the current standard:
|
||||
|
||||
- `Dashboard`: Inline Dashboard (`inline: true`)
|
||||
- `DashboardModal`: Dashboard as a modal
|
||||
- `DragDrop`
|
||||
- `ProgressBar`
|
||||
- `StatusBar`
|
||||
|
||||
All these components should function as references to the normal component.
|
||||
Depending on how the framework you’re using handles references to the DOM, your
|
||||
approach to creating these may be different. For example, in React, you can
|
||||
assign a property of the component to the reference of a component
|
||||
([see here](https://github.com/transloadit/uppy/blob/425f9ecfbc8bc48ce6b734e4fc14fa60d25daa97/packages/%40uppy/react/src/Dashboard.js#L47-L54)).
|
||||
This may differ in your framework, but from what we’ve found, the concepts are
|
||||
generally pretty similar.
|
||||
|
||||
If you’re familiar with React, Vue or soon Svelte, it might be useful to read
|
||||
through the code of those integrations, as they lay out a pretty good structure.
|
||||
After the basic components have been built, here are a few more important tasks
|
||||
to get done:
|
||||
|
||||
- Add TypeScript support in some capacity (if possible)
|
||||
- Write documentation
|
||||
- Add an example
|
||||
- Configuring the build system
|
||||
|
||||
### Common issues
|
||||
|
||||
Before going into these tasks, here are a few common gotchas that you should be
|
||||
aware of.
|
||||
|
||||
#### Dependencies
|
||||
|
||||
Your `package.json` should resemble something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@uppy/framework",
|
||||
"dependencies": {
|
||||
"@uppy/dashboard": "workspace:^",
|
||||
"@uppy/drag-drop": "workspace:^",
|
||||
"@uppy/progress-bar": "workspace:^",
|
||||
"@uppy/status-bar": "workspace:^",
|
||||
"@uppy/utils": "workspace:^",
|
||||
"prop-types": "^15.6.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "workspace:^"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The most important part about this is that `@uppy/core` is a peer dependency. If
|
||||
your framework complains about `@uppy/core` not being resolved, you can also add
|
||||
it as a dev dependency
|
||||
|
||||
### Adding TypeScript Support
|
||||
|
||||
This section won’t be too in-depth, because TypeScript depends on your
|
||||
framework. As general advice, prefer using `d.ts` files and vanilla JavaScript
|
||||
over TypeScript files. This is circumstantial, but it makes handling the build
|
||||
system a lot easier when TypeScript doesn’t have to transpiled. The version of
|
||||
typescript in the monorepo is `4.1`.
|
||||
|
||||
### Writing docs
|
||||
|
||||
Generally, documentation for integrations can be broken down into a few pieces
|
||||
that apply to every component, and then documentation for each component. The
|
||||
structure should look something like this:
|
||||
|
||||
- Installation
|
||||
- Initializing Uppy (may vary depending on how the framework handles reactivity)
|
||||
- Usage
|
||||
- _For each component_
|
||||
- Loading CSS
|
||||
- Props
|
||||
|
||||
It may be easier to copy the documentation of earlier integrations and change
|
||||
the parts that need to be changed rather than writing this from scratch.
|
||||
Preferably, keep the documentation to one page. For the front-matter, write
|
||||
something like:
|
||||
|
||||
```markdown
|
||||
title: Framework Name type: docs module: "@uppy/framework" order: 0 category:
|
||||
"Other Integrations"
|
||||
```
|
||||
|
||||
This data is used to generate Uppy’s website.
|
||||
|
||||
Any change of the documentation that involves a security best practice must
|
||||
substantiated with an external reference. See
|
||||
[#3565](https://github.com/transloadit/uppy/issues/3565).
|
||||
|
||||
### Adding an example
|
||||
|
||||
You can likely use whatever code generation tool for your framework (ex.
|
||||
`create-react-app`) to create this example. Make sure you add the same version
|
||||
of `@uppy/core` to this as your peer dependency required, or you may run into
|
||||
strange issues. Try to include all the components are some of their
|
||||
functionality.
|
||||
[The React example](https://github.com/transloadit/uppy/blob/main/examples/react-example/App.js)
|
||||
is a great... well example of how to do this well.
|
||||
|
||||
### Integrating the build system
|
||||
|
||||
The biggest part of this is understanding Uppy’s build system. The high level
|
||||
description is that `babel` goes through almost all the packages and transpiles
|
||||
all the Javascript files in the `src` directory to more compatible JavaScript in
|
||||
the `lib` folder. If you’re using vanilla JavaScript for your integration (like
|
||||
React and Vue do), then you can use this build system and use the files
|
||||
generated as your entry points.
|
||||
|
||||
If you’re using some kind of more abstract file format (like Svelte), then you
|
||||
probably want do to a few things: add the directory name to
|
||||
[this `IGNORE` regex](https://github.com/transloadit/uppy/blob/425f9ecfbc8bc48ce6b734e4fc14fa60d25daa97/bin/build-lib.js#L15);
|
||||
add all your build dependencies to the root `package.json` (try to keep this
|
||||
small); add a new `build:framework` script to the root `package.json`. This
|
||||
script usually looks something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"build:framework": "cd framework && yarn run build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then, add this script to the `build:js` script. Try running the `build:js`
|
||||
script and make sure it does not error. It may also be of use to make sure that
|
||||
global dependencies aren’t being used (ex. not having rollup locally and relying
|
||||
on a global install), as these dependencies won’t be present on the machine’s
|
||||
handling building.
|
||||
|
||||
## I18n and locales
|
||||
|
||||
For more information about how to contribute to translations, see
|
||||
|
|
|
|||
17
.github/stale.yml
vendored
|
|
@ -1,17 +0,0 @@
|
|||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 365
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: false
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- Keep open
|
||||
- 🔐 Security
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: Stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. If the issue is still relevant, please upvote or leave a
|
||||
comment. Thank you for your contribution!
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
42
.github/workflows/bundlers.yml
vendored
|
|
@ -1,4 +1,4 @@
|
|||
name: Test different bundlers with Uppy
|
||||
name: Bundlers
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
|
|
@ -42,14 +42,12 @@ jobs:
|
|||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
run:
|
||||
corepack yarn workspaces focus $(corepack yarn workspaces list --json
|
||||
| jq -r .name | awk '/^@uppy-example/{ next } { if ($0!="uppy.io")
|
||||
print $0 }')
|
||||
corepack yarn install
|
||||
env:
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
- name: Build lib
|
||||
run: corepack yarn run build:lib
|
||||
- name: Build
|
||||
run: corepack yarn run build
|
||||
- name: Make Uppy bundle use local version
|
||||
run: |
|
||||
node <<'EOF'
|
||||
|
|
@ -97,12 +95,13 @@ jobs:
|
|||
- name: Add Rollup as a dev dependency
|
||||
run: >-
|
||||
npm i --save-dev @rollup/plugin-commonjs @rollup/plugin-node-resolve
|
||||
rollup@${{matrix.bundler-version}}
|
||||
@rollup/plugin-json rollup@${{matrix.bundler-version}}
|
||||
- run: npx rollup --version
|
||||
- name: Create Rollup config file
|
||||
run: >-
|
||||
echo ' import cjs from "@rollup/plugin-commonjs"; import { nodeResolve
|
||||
} from "@rollup/plugin-node-resolve";
|
||||
} from "@rollup/plugin-node-resolve"; import json from
|
||||
"@rollup/plugin-json";
|
||||
|
||||
export default {
|
||||
input: "./lib/index.js",
|
||||
|
|
@ -110,6 +109,7 @@ jobs:
|
|||
file: "/dev/null",
|
||||
},
|
||||
plugins: [
|
||||
json(),
|
||||
cjs(),
|
||||
nodeResolve({ browser: true, exportConditions: ["browser"] }),
|
||||
],
|
||||
|
|
@ -136,10 +136,26 @@ jobs:
|
|||
run: npm i --save-dev webpack-cli webpack@${{matrix.bundler-version}}
|
||||
- run: npx webpack --version
|
||||
- name: Create Webpack config file
|
||||
run:
|
||||
echo 'export default
|
||||
{mode:"production",target:"web",entry:"./lib/index.js"}' >
|
||||
webpack.config.js
|
||||
run: |
|
||||
echo 'export default {
|
||||
mode: "production",
|
||||
target: "web",
|
||||
entry: "./lib/index.js",
|
||||
resolve: {
|
||||
fallback: {
|
||||
fs: false,
|
||||
path: false,
|
||||
stream: false,
|
||||
util: false,
|
||||
assert: false,
|
||||
constants: false,
|
||||
crypto: false,
|
||||
http: false,
|
||||
https: false,
|
||||
url: false
|
||||
}
|
||||
}
|
||||
}' > webpack.config.js
|
||||
- name: Bundle
|
||||
run: npx webpack
|
||||
|
||||
|
|
|
|||
66
.github/workflows/ci.yml
vendored
|
|
@ -6,10 +6,6 @@ on:
|
|||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'assets/**'
|
||||
- 'e2e/**'
|
||||
- 'examples/**'
|
||||
- 'private/**'
|
||||
- 'website/**'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/ci.yml'
|
||||
pull_request:
|
||||
|
|
@ -17,11 +13,6 @@ on:
|
|||
types: [opened, synchronize, reopened]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'assets/**'
|
||||
- 'e2e/**'
|
||||
- 'examples/**'
|
||||
- 'private/**'
|
||||
- 'website/**'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/ci.yml'
|
||||
|
||||
|
|
@ -30,14 +21,14 @@ env:
|
|||
|
||||
jobs:
|
||||
unit_tests:
|
||||
name: Unit tests
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x, 20.x]
|
||||
node-version: [lts/*]
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
|
|
@ -56,21 +47,25 @@ jobs:
|
|||
node-version: ${{matrix.node-version}}
|
||||
- name: Install dependencies
|
||||
run:
|
||||
corepack yarn workspaces focus $(corepack yarn workspaces list --json
|
||||
| jq -r .name | awk '/^@uppy-example/{ next } { if ($0!="uppy.io")
|
||||
print $0 }')
|
||||
env:
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
corepack yarn install
|
||||
- name: Install Playwright Browsers
|
||||
run: corepack yarn workspace @uppy/dashboard playwright install --with-deps
|
||||
- name: Build
|
||||
run: corepack yarn run build
|
||||
- name: Run tests
|
||||
run: corepack yarn run test:unit
|
||||
run: corepack yarn run test
|
||||
env:
|
||||
COMPANION_DATADIR: ./output
|
||||
COMPANION_DOMAIN: localhost:3020
|
||||
COMPANION_PROTOCOL: http
|
||||
COMPANION_REDIS_URL: redis://localhost:6379
|
||||
|
||||
types:
|
||||
name: Type tests
|
||||
name: Types
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
|
|
@ -89,11 +84,24 @@ jobs:
|
|||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
run:
|
||||
corepack yarn workspaces focus $(corepack yarn workspaces list --json
|
||||
| jq -r .name | awk '/^@uppy-example/{ next } { if ($0!="uppy.io")
|
||||
print $0 }')
|
||||
env:
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
- name: Attempt building TS packages
|
||||
run: corepack yarn run build:ts
|
||||
corepack yarn install
|
||||
- run: corepack yarn run typecheck
|
||||
|
||||
lint_js:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SKIP_YARN_COREPACK_CHECK: true
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
run:
|
||||
corepack yarn workspaces focus @uppy-dev/build
|
||||
- name: Run linter
|
||||
run: corepack yarn run check:ci
|
||||
|
|
|
|||
20
.github/workflows/companion-deploy.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Set SHA commit in version
|
||||
run:
|
||||
(cd packages/@uppy/companion && node -e 'const
|
||||
|
|
@ -46,24 +46,24 @@ jobs:
|
|||
COMPOSE_DOCKER_CLI_BUILD: 0
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
with:
|
||||
images: transloadit/companion
|
||||
tags: |
|
||||
type=edge
|
||||
type=raw,value=latest,enable=false
|
||||
- uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
|
||||
- uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Log in to DockerHub
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||
with:
|
||||
username: ${{secrets.DOCKER_USERNAME}}
|
||||
password: ${{secrets.DOCKER_PASSWORD}}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
|
||||
with:
|
||||
push: true
|
||||
context: .
|
||||
|
|
@ -77,12 +77,16 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Alter dockerfile
|
||||
run: |
|
||||
sed -i 's/^EXPOSE 3020$/EXPOSE $PORT/g' Dockerfile
|
||||
# https://github.com/AkhileshNS/heroku-deploy/issues/188
|
||||
- name: Install Heroku CLI
|
||||
run: |
|
||||
curl https://cli-assets.heroku.com/install.sh | sh
|
||||
- name: Deploy to heroku
|
||||
uses: akhileshns/heroku-deploy@581dd286c962b6972d427fcf8980f60755c15520 # v3.13.15
|
||||
uses: akhileshns/heroku-deploy@e3eb99d45a8e2ec5dca08735e089607befa4bf28 # v3.14.15
|
||||
with:
|
||||
heroku_api_key: ${{secrets.HEROKU_API_KEY}}
|
||||
heroku_app_name: companion-demo
|
||||
|
|
|
|||
51
.github/workflows/companion.yml
vendored
|
|
@ -1,51 +0,0 @@
|
|||
name: Companion
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- yarn.lock
|
||||
- 'packages/@uppy/companion/**'
|
||||
- '.github/workflows/companion.yml'
|
||||
pull_request:
|
||||
# We want all branches so we configure types to be the GH default again
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- yarn.lock
|
||||
- 'packages/@uppy/companion/**'
|
||||
- '.github/workflows/companion.yml'
|
||||
|
||||
env:
|
||||
YARN_ENABLE_GLOBAL_CACHE: false
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Unit tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x, 20.x, latest]
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{matrix.node-version}}
|
||||
- name: Install dependencies
|
||||
run: corepack yarn workspaces focus @uppy/companion
|
||||
- name: Run tests
|
||||
run: corepack yarn run test:companion
|
||||
- name: Run type checks in focused workspace
|
||||
run: corepack yarn run build:companion
|
||||
217
.github/workflows/e2e.yml
vendored
|
|
@ -1,4 +1,5 @@
|
|||
name: End-to-end tests
|
||||
name: Output
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
|
@ -10,8 +11,8 @@ on:
|
|||
- 'website/**'
|
||||
- '.github/**'
|
||||
- '!.github/workflows/e2e.yml'
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.d.ts'
|
||||
|
|
@ -19,19 +20,6 @@ on:
|
|||
- 'private/**'
|
||||
- 'website/**'
|
||||
- '.github/**'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- .github/workflows/e2e.yml
|
||||
|
||||
concurrency:
|
||||
group:
|
||||
${{ github.workflow }}--${{ github.event.pull_request.head.repo.full_name ||
|
||||
github.repository }} -- ${{ github.head_ref || github.ref }}
|
||||
cancel-in-progress:
|
||||
# For PRs coming from forks, we need the previous job to run until the end
|
||||
# to be sure it can remove the `safe to test` label before it affects the next run.
|
||||
${{ github.event.pull_request.head.repo.full_name == github.repository }}
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
|
@ -40,30 +28,18 @@ env:
|
|||
|
||||
jobs:
|
||||
compare_diff:
|
||||
name: Diff lib folders
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DIFF_BUILDER: true
|
||||
outputs:
|
||||
diff: ${{ steps.diff.outputs.OUTPUT_DIFF }}
|
||||
is_accurate_diff: ${{ steps.diff.outputs.IS_ACCURATE_DIFF }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 2
|
||||
ref:
|
||||
${{ github.event.pull_request && format('refs/pull/{0}/merge',
|
||||
github.event.pull_request.number) || github.sha }}
|
||||
- name: Check if there are "unsafe" changes
|
||||
id: build_chain_changes
|
||||
# If there are changes in JS script that generates the output, we cannot
|
||||
# test them here without human review to make sure they don't contain
|
||||
# someting "nasty".
|
||||
run: |
|
||||
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
|
||||
echo "MIGHT_CONTAIN_OTHER_CHANGES<<$EOF" >> "$GITHUB_OUTPUT"
|
||||
git --no-pager diff HEAD^ --name-only bin package.json yarn.lock babel.config.js >> "$GITHUB_OUTPUT"
|
||||
echo "$EOF" >> "$GITHUB_OUTPUT"
|
||||
ref: ${{ github.event.pull_request && format('refs/pull/{0}/merge', github.event.pull_request.number) || github.sha }}
|
||||
- run: git reset HEAD^ --hard
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
|
|
@ -81,50 +57,21 @@ jobs:
|
|||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
run:
|
||||
corepack yarn workspaces focus $(corepack yarn workspaces list --json
|
||||
| jq -r .name | awk '/^@uppy-example/{ next } { if ($0!="uppy.io")
|
||||
print $0 }')
|
||||
- run: corepack yarn install
|
||||
env:
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
- run: corepack yarn build:js:typeless
|
||||
- run: corepack yarn build
|
||||
- name: Store output file
|
||||
run: tar cf /tmp/previousVersion.tar packages/@uppy/*/lib
|
||||
- name: Fetch source from the PR
|
||||
if: steps.build_chain_changes.outputs.MIGHT_CONTAIN_OTHER_CHANGES == ''
|
||||
run: |
|
||||
git checkout FETCH_HEAD -- packages
|
||||
echo 'IS_ACCURATE_DIFF=true' >> "$GITHUB_ENV"
|
||||
- name: Fetch source from the PR
|
||||
if:
|
||||
steps.build_chain_changes.outputs.MIGHT_CONTAIN_OTHER_CHANGES != '' &&
|
||||
(!github.event.pull_request || (github.event.action == 'labeled' &&
|
||||
github.event.label.name == 'safe to test' &&
|
||||
github.event.pull_request.state == 'open') ||
|
||||
(github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event.event_name != 'labeled'))
|
||||
run: |
|
||||
git reset FETCH_HEAD --hard
|
||||
corepack yarn workspaces focus $(\
|
||||
corepack yarn workspaces list --json | \
|
||||
jq -r .name | \
|
||||
awk '/^@uppy-example/{ next } { if ($0!="uppy.io") print $0 }'\
|
||||
)
|
||||
echo 'IS_ACCURATE_DIFF=true' >> "$GITHUB_ENV"
|
||||
- run: corepack yarn install --no-immutable
|
||||
env:
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
- name: Fetch source from the PR
|
||||
if:
|
||||
steps.build_chain_changes.outputs.MIGHT_CONTAIN_OTHER_CHANGES != '' &&
|
||||
github.event.pull_request.head.repo.full_name != github.repository &&
|
||||
(github.event.action != 'labeled' || github.event.label.name != 'safe
|
||||
to test')
|
||||
run: |
|
||||
git checkout FETCH_HEAD -- packages
|
||||
- run: corepack yarn build:js:typeless
|
||||
- run: corepack yarn build
|
||||
- name: Store output file
|
||||
run: tar cf /tmp/newVersion.tar packages/@uppy/*/lib
|
||||
- name: Setup git
|
||||
|
|
@ -154,7 +101,6 @@ jobs:
|
|||
echo "OUTPUT_DIFF<<$EOF" >> "$GITHUB_OUTPUT"
|
||||
cd /tmp/uppy && git --no-pager diff >> "$GITHUB_OUTPUT"
|
||||
echo "$EOF" >> "$GITHUB_OUTPUT"
|
||||
echo "IS_ACCURATE_DIFF=$IS_ACCURATE_DIFF" >> "$GITHUB_OUTPUT"
|
||||
- name: Add/update comment
|
||||
if: github.event.pull_request
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
|
|
@ -166,145 +112,4 @@ jobs:
|
|||
${{ steps.diff.outputs.OUTPUT_DIFF || 'No diff' }}
|
||||
```
|
||||
|
||||
${{ env.IS_ACCURATE_DIFF != 'true' && format(fromJson('"The following build files have been modified and might affect the actual diff:\n\n```\n{0}\n```"'), steps.build_chain_changes.outputs.MIGHT_CONTAIN_OTHER_CHANGES) || '' }}
|
||||
|
||||
</details>
|
||||
- name: Remove 'safe to test' label if cancelled
|
||||
if:
|
||||
cancelled() && github.event.pull_request &&
|
||||
github.event.pull_request.head.repo.full_name != github.repository
|
||||
run: gh pr edit "$NUMBER" --remove-label 'safe to test'
|
||||
env:
|
||||
NUMBER: ${{ github.event.pull_request.number }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
toggle-pending-e2e-label:
|
||||
# Add the 'pending end-to-end tests' label for PRs that come from forks.
|
||||
# For those PRs, we want to review the code before running e2e tests.
|
||||
# See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/.
|
||||
needs: [compare_diff]
|
||||
if:
|
||||
github.event.pull_request.state == 'open' &&
|
||||
github.event.pull_request.head.repo.full_name != github.repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Add label
|
||||
if:
|
||||
(needs.compare_diff.outputs.diff != '' ||
|
||||
!needs.compare_diff.outputs.is_accurate_diff) && (github.event.action
|
||||
!= 'labeled' || github.event.label.name != 'safe to test')
|
||||
env:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run:
|
||||
gh pr edit "$PR_URL" --repo ${{ github.repository }} --add-label
|
||||
'pending end-to-end tests'
|
||||
- name: Remove label
|
||||
if:
|
||||
needs.compare_diff.outputs.diff == '' &&
|
||||
needs.compare_diff.outputs.is_accurate_diff && github.event.action ==
|
||||
'labeled' && github.event.label.name == 'safe to test'
|
||||
env:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run:
|
||||
gh pr edit "$PR_URL" --remove-label 'safe to test' --remove-label
|
||||
'pending end-to-end tests'
|
||||
|
||||
e2e:
|
||||
needs: [compare_diff]
|
||||
if:
|
||||
${{ needs.compare_diff.outputs.diff != '' && (!github.event.pull_request
|
||||
|| (github.event.action == 'labeled' && github.event.label.name == 'safe
|
||||
to test' && github.event.pull_request.state == 'open') ||
|
||||
(github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event.event_name != 'labeled')) }}
|
||||
name: Browser tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Create cache folder for Cypress
|
||||
id: cypress-cache-dir-path
|
||||
run: echo "dir=$(mktemp -d)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.cypress-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-cypress
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Start Redis
|
||||
uses: supercharge/redis-github-action@ea9b21c6ecece47bd99595c532e481390ea0f044 # 1.8.0
|
||||
with:
|
||||
redis-version: 7
|
||||
|
||||
- name: Install dependencies
|
||||
run: corepack yarn install --immutable
|
||||
env:
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Binary-cache
|
||||
CYPRESS_CACHE_FOLDER: ${{ steps.cypress-cache-dir-path.outputs.dir }}
|
||||
- name: Build Uppy packages
|
||||
run: corepack yarn build
|
||||
- name: Run end-to-end browser tests
|
||||
run: corepack yarn run e2e:ci
|
||||
env:
|
||||
COMPANION_DATADIR: ./output
|
||||
COMPANION_DOMAIN: localhost:3020
|
||||
COMPANION_PROTOCOL: http
|
||||
COMPANION_REDIS_URL: redis://localhost:6379
|
||||
COMPANION_UNSPLASH_KEY: ${{secrets.COMPANION_UNSPLASH_KEY}}
|
||||
COMPANION_UNSPLASH_SECRET: ${{secrets.COMPANION_UNSPLASH_SECRET}}
|
||||
COMPANION_AWS_KEY: ${{secrets.COMPANION_AWS_KEY}}
|
||||
COMPANION_AWS_SECRET: ${{secrets.COMPANION_AWS_SECRET}}
|
||||
COMPANION_AWS_BUCKET: ${{secrets.COMPANION_AWS_BUCKET}}
|
||||
COMPANION_AWS_REGION: ${{secrets.COMPANION_AWS_REGION}}
|
||||
VITE_COMPANION_URL: http://localhost:3020
|
||||
VITE_TRANSLOADIT_KEY: ${{secrets.TRANSLOADIT_KEY}}
|
||||
VITE_TRANSLOADIT_SECRET: ${{secrets.TRANSLOADIT_SECRET}}
|
||||
VITE_TRANSLOADIT_TEMPLATE: ${{secrets.TRANSLOADIT_TEMPLATE}}
|
||||
VITE_TRANSLOADIT_SERVICE_URL: ${{secrets.TRANSLOADIT_SERVICE_URL}}
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Binary-cache
|
||||
CYPRESS_CACHE_FOLDER: ${{ steps.cypress-cache-dir-path.outputs.dir }}
|
||||
- name: Upload videos in case of failure
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: videos-and-screenshots
|
||||
path: |
|
||||
e2e/cypress/videos/
|
||||
e2e/cypress/screenshots/
|
||||
- name: Remove labels
|
||||
# Remove the 'pending end-to-end tests' label if tests ran successfully
|
||||
if:
|
||||
github.event.pull_request &&
|
||||
contains(github.event.pull_request.labels.*.name, 'pending end-to-end
|
||||
tests')
|
||||
run: gh pr edit "$NUMBER" --remove-label 'pending end-to-end tests'
|
||||
env:
|
||||
NUMBER: ${{ github.event.pull_request.number }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Remove 'safe to test' label
|
||||
if:
|
||||
always() && github.event.pull_request &&
|
||||
contains(github.event.pull_request.labels.*.name, 'safe to test')
|
||||
run: gh pr edit "$NUMBER" --remove-label 'safe to test'
|
||||
env:
|
||||
NUMBER: ${{ github.event.pull_request.number }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
100
.github/workflows/linters.yml
vendored
|
|
@ -1,100 +0,0 @@
|
|||
name: Linters
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- '!.github/workflows/linters.yml'
|
||||
- '!.github/CONTRIBUTING.md'
|
||||
pull_request:
|
||||
# We want all branches so we configure types to be the GH default again
|
||||
types: [opened, synchronize, reopened]
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- '!.github/workflows/linters.yml'
|
||||
- '!.github/CONTRIBUTING.md'
|
||||
|
||||
env:
|
||||
YARN_ENABLE_GLOBAL_CACHE: false
|
||||
|
||||
jobs:
|
||||
lint_js:
|
||||
name: Lint JavaScript/TypeScript
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
# List all projects that use a custom ESLint config:
|
||||
run:
|
||||
corepack yarn workspaces focus @uppy/angular @uppy-example/angular
|
||||
@uppy-example/react-native-expo @uppy/react-native @uppy-dev/build
|
||||
- name: Run linter
|
||||
run: corepack yarn run lint
|
||||
- name: Run Prettier
|
||||
run: corepack yarn run format:check-diff
|
||||
|
||||
lint_md:
|
||||
name: Lint Markdown
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
run: corepack yarn workspaces focus @uppy-dev/build
|
||||
- name: Run linter
|
||||
run: corepack yarn run lint:markdown
|
||||
|
||||
lint_docs:
|
||||
name: Lint Docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Uppy.io sources
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: transloadit/uppy.io
|
||||
- run: rm -rf docs # the other PR has not landed
|
||||
- name: Checkout docs
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: uppy
|
||||
- run: mv uppy /tmp/uppy && ln -s /tmp/uppy/docs docs
|
||||
- name: Install dependencies
|
||||
run: corepack yarn --immutable
|
||||
- name: Lint files
|
||||
run: corepack yarn lint
|
||||
- name: Test build website
|
||||
run: corepack yarn build
|
||||
2
.github/workflows/lockfile_check.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
|
|
|
|||
8
.github/workflows/manual-cdn.yml
vendored
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
|
|
@ -44,7 +44,7 @@ jobs:
|
|||
node-version: lts/*
|
||||
- name: Install upload-to-cdn dependencies
|
||||
if: ${{ inputs.version }}
|
||||
run: corepack yarn workspaces focus @uppy-dev/upload-to-cdn
|
||||
run: corepack yarn workspaces focus uppy
|
||||
env:
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
|
@ -59,7 +59,7 @@ jobs:
|
|||
run: corepack yarn run build
|
||||
- name: Upload "${{ inputs.package }}" to CDN
|
||||
if: ${{ !inputs.force }}
|
||||
run: corepack yarn run uploadcdn "$PACKAGE" "$VERSION"
|
||||
run: corepack yarn workspace uppy node upload-to-cdn.js "$PACKAGE" "$VERSION"
|
||||
env:
|
||||
PACKAGE: ${{inputs.package}}
|
||||
VERSION: ${{inputs.version}}
|
||||
|
|
@ -67,7 +67,7 @@ jobs:
|
|||
EDGLY_SECRET: ${{secrets.EDGLY_SECRET}}
|
||||
- name: Upload "${{ inputs.package }}" to CDN
|
||||
if: ${{ inputs.force }}
|
||||
run: corepack yarn run uploadcdn "$PACKAGE" "$VERSION" -- --force
|
||||
run: corepack yarn workspace uppy node upload-to-cdn.js "$PACKAGE" "$VERSION" --force
|
||||
env:
|
||||
PACKAGE: ${{inputs.package}}
|
||||
VERSION: ${{inputs.version}}
|
||||
|
|
|
|||
94
.github/workflows/release-candidate.yml
vendored
|
|
@ -1,94 +0,0 @@
|
|||
name: Release candidate
|
||||
on:
|
||||
push:
|
||||
branches: release
|
||||
|
||||
env:
|
||||
YARN_ENABLE_GLOBAL_CACHE: false
|
||||
|
||||
jobs:
|
||||
prepare-release:
|
||||
name: Prepare release candidate Pull Request
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
branch: release
|
||||
- name: Rebase
|
||||
run: |
|
||||
git fetch origin HEAD --depth=1
|
||||
git config --global user.email "actions@github.com"
|
||||
git config --global user.name "GitHub Actions"
|
||||
git rebase FETCH_HEAD
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
run: corepack yarn install --immutable
|
||||
env:
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
- name: Bump candidate packages version
|
||||
run: corepack yarn version apply --all --json | jq -s > releases.json
|
||||
- name: Prepare changelog
|
||||
run:
|
||||
corepack yarn workspace @uppy-dev/release update-changelogs
|
||||
releases.json | xargs git add
|
||||
- name: Update contributors table
|
||||
run:
|
||||
corepack yarn contributors:save && corepack yarn remark -foq README.md
|
||||
&& corepack yarn prettier -w README.md && git add README.md
|
||||
- name: Update CDN URLs
|
||||
run:
|
||||
corepack yarn workspace @uppy-dev/release update-version-URLs | xargs
|
||||
git add
|
||||
- name: Stage changes and remove temp files
|
||||
run: |
|
||||
git rm -rf .yarn/versions
|
||||
git rm CHANGELOG.next.md
|
||||
jq -r 'map(.cwd) | join("\n")' < releases.json | awk '{ print "git add " $0 "/package.json" }' | sh
|
||||
- name: Commit
|
||||
run: |
|
||||
echo "Release: uppy@$(jq -r 'map(select(.ident == "uppy"))[0].newVersion' < releases.json)" > commitMessage
|
||||
echo >> commitMessage
|
||||
echo "This is a release candidate for the following packages:" >> commitMessage
|
||||
echo >> commitMessage
|
||||
jq -r 'map("- `"+.ident+"`: "+.oldVersion+" -> "+.newVersion) | join("\n") ' < releases.json >> commitMessage
|
||||
git commit -n --amend --file commitMessage
|
||||
- name: Open Pull Request
|
||||
id: pr_opening
|
||||
run: |
|
||||
git push origin HEAD:release-candidate
|
||||
gh api repos/${{ github.repository }}/pulls \
|
||||
-F base="$(gh api /repos/${{ github.repository }} | jq -r .default_branch)" \
|
||||
-F head="release-candidate" \
|
||||
-F title="$(head -1 commitMessage)" \
|
||||
-F body="$(git --no-pager diff HEAD^ -- CHANGELOG.md | awk '{ if( substr($0,0,1) == "+" && $1 != "+##" && $1 != "+Released:" && $1 != "+++" ) { print substr($0,2) } }')" \
|
||||
--jq '.number | tostring | "pr_number="+.' >> $GITHUB_OUTPUT
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Assign to the releaser
|
||||
run:
|
||||
echo '{"assignees":[${{ toJSON(github.actor) }}]}' | gh api repos/${{
|
||||
github.repository }}/issues/${{ steps.pr_opening.outputs.pr_number
|
||||
}}/assignees --input -
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Enable Release workflow
|
||||
run: gh workflow enable Release --repo ${{ github.repository }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
180
.github/workflows/release.yml
vendored
|
|
@ -1,149 +1,99 @@
|
|||
name: Release
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
name: release
|
||||
|
||||
env:
|
||||
YARN_ENABLE_GLOBAL_CACHE: false
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Publish releases
|
||||
if:
|
||||
${{ github.event.review.state == 'approved' && github.event.sender.login
|
||||
== github.event.pull_request.assignee.login &&
|
||||
github.event.pull_request.head.ref == 'release-candidate' }}
|
||||
outputs:
|
||||
companionWasReleased:
|
||||
${{ steps.checkIfCompanionWasReleased.outputs.version }}
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
companionWasReleased: ${{ steps.checkIfCompanionWasReleased.outputs.version }}
|
||||
published: ${{ steps.changesets.outputs.published }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run:
|
||||
echo "dir=$(corepack yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install Node.js
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
# Allow yarn to make changes during release
|
||||
- run: corepack yarn config set enableHardenedMode false
|
||||
- run: corepack yarn --mode=update-lockfile
|
||||
|
||||
- name: Install dependencies
|
||||
run: corepack yarn install --immutable
|
||||
env:
|
||||
# https://docs.cypress.io/guides/references/advanced-installation#Skipping-installation
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
- name: Get CHANGELOG diff
|
||||
run:
|
||||
git --no-pager diff HEAD^ -- CHANGELOG.md | awk '{ if( substr($0,0,1)
|
||||
== "+" && $1 != "+##" && $1 != "+Released:" && $1 != "+++" ) { print
|
||||
substr($0,2) } }' > CHANGELOG.diff.md
|
||||
- name: Copy README for `uppy` package
|
||||
run: cp README.md packages/uppy/.
|
||||
- name: Build before publishing
|
||||
run: corepack yarn run build
|
||||
- name: Hack to allow the publish of the Angular package
|
||||
|
||||
- name: Build
|
||||
run: corepack yarn build
|
||||
|
||||
- name: '@uppy/angular prepublish'
|
||||
run: corepack yarn workspace @uppy/angular prepublishOnly
|
||||
- name: Publish to the npm registry
|
||||
run:
|
||||
corepack yarn workspaces foreach --all --no-private npm publish
|
||||
--access public --tolerate-republish
|
||||
|
||||
- run: |
|
||||
echo '' >> .yarnrc.yml
|
||||
echo 'npmAuthToken: "${NPM_TOKEN}"' >> .yarnrc.yml
|
||||
|
||||
- name: Create Release Pull Request or Publish
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
version: corepack yarn run version
|
||||
publish: corepack yarn run release
|
||||
commit: '[ci] release'
|
||||
title: '[ci] release'
|
||||
env:
|
||||
YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
- name: Merge PR
|
||||
id: merge
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Remove npmAuthToken from yarnrc
|
||||
run: |
|
||||
gh api -X PUT repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/merge \
|
||||
-F merge_method="squash" \
|
||||
-F commit_message="$(cat CHANGELOG.diff.md)" \
|
||||
--jq 'if .merged then "sha="+.sha else error("not merged") end' >> $GITHUB_OUTPUT
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create tags
|
||||
run: |
|
||||
git --no-pager diff --name-only HEAD^ | awk '$0 ~ /^packages\/.+\/package\.json$/ { print "jq -r '"'"'\"gh api /repos/{owner}/{repo}/git/refs -f ref=\\\"refs/tags/\"+.name+\"@\"+.version+\"\\\" -f sha=${{ steps.merge.outputs.sha }}\"'"'"' < " $0 }' > createTags.sh
|
||||
cat createTags.sh
|
||||
sh createTags.sh | sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Get Uppy version number
|
||||
id: uppyVersion
|
||||
run:
|
||||
jq -r '"version="+.version' < packages/uppy/package.json >>
|
||||
$GITHUB_OUTPUT
|
||||
- name: Create GitHub release
|
||||
run:
|
||||
gh release create uppy@${{ steps.uppyVersion.outputs.version }} -t
|
||||
"Uppy ${{ steps.uppyVersion.outputs.version }}" -F CHANGELOG.diff.md
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Upload `uppy` to CDN
|
||||
run: corepack yarn run uploadcdn uppy
|
||||
env:
|
||||
EDGLY_KEY: ${{secrets.EDGLY_KEY}}
|
||||
EDGLY_SECRET: ${{secrets.EDGLY_SECRET}}
|
||||
- name: Upload `@uppy/locales` to CDN if it was released
|
||||
run:
|
||||
git diff --exit-code --quiet HEAD^ --
|
||||
packages/@uppy/locales/package.json ||corepack yarn run uploadcdn
|
||||
@uppy/locales
|
||||
env:
|
||||
EDGLY_KEY: ${{secrets.EDGLY_KEY}}
|
||||
EDGLY_SECRET: ${{secrets.EDGLY_SECRET}}
|
||||
sed -i '/npmAuthToken:/d' .yarnrc.yml
|
||||
|
||||
- name: Check if Companion was released
|
||||
id: checkIfCompanionWasReleased
|
||||
run:
|
||||
git diff --exit-code --quiet HEAD^ --
|
||||
packages/@uppy/companion/package.json || echo "version=$(jq -r
|
||||
.version < packages/@uppy/companion/package.json)" >> $GITHUB_OUTPUT
|
||||
- name: Remove release-candidate branch
|
||||
run:
|
||||
gh api -X DELETE repos/${{ github.repository
|
||||
}}/git/refs/heads/release-candidate || echo "Already deleted"
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
run: |
|
||||
git diff --exit-code --quiet HEAD^ -- packages/@uppy/companion/package.json || echo "version=$(jq -r .version < packages/@uppy/companion/package.json)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload `uppy` to CDN
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
run: node packages/uppy/upload-to-cdn.js uppy
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Remove release branch
|
||||
run:
|
||||
gh api -X DELETE repos/${{ github.repository }}/git/refs/heads/release
|
||||
EDGLY_KEY: ${{secrets.EDGLY_KEY}}
|
||||
EDGLY_SECRET: ${{secrets.EDGLY_SECRET}}
|
||||
|
||||
- name: Upload `@uppy/locales` to CDN if it was released
|
||||
if: steps.changesets.outputs.published == 'true'
|
||||
run: node packages/uppy/upload-to-cdn.js @uppy/locales
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Disable Release workflow
|
||||
run: gh workflow disable Release --repo ${{ github.repository }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: In case of failure
|
||||
if: ${{ failure() }}
|
||||
run:
|
||||
gh pr comment ${{ github.event.pull_request.number }} --body "Release
|
||||
job failed, please take action."
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
EDGLY_KEY: ${{secrets.EDGLY_KEY}}
|
||||
EDGLY_SECRET: ${{secrets.EDGLY_SECRET}}
|
||||
|
||||
|
||||
# See also companion-deploy.yml
|
||||
docker:
|
||||
name: DockerHub
|
||||
needs: release
|
||||
if: ${{ needs.release.outputs.companionWasReleased }}
|
||||
if: ${{ needs.release.outputs.published == 'true' && needs.release.outputs.companionWasReleased }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCKER_BUILDKIT: 0
|
||||
COMPOSE_DOCKER_CLI_BUILD: 0
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
- name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
with:
|
||||
images: transloadit/companion
|
||||
tags: |
|
||||
|
|
@ -151,15 +101,15 @@ jobs:
|
|||
type=semver,pattern={{version}},value=${{ needs.release.outputs.companionWasReleased }}
|
||||
# set latest tag for default branch
|
||||
type=raw,value=latest,enable=true
|
||||
- uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
|
||||
- uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- name: Log in to DockerHub
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||
with:
|
||||
username: ${{secrets.DOCKER_USERNAME}}
|
||||
password: ${{secrets.DOCKER_PASSWORD}}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
|
||||
with:
|
||||
push: true
|
||||
context: .
|
||||
|
|
|
|||
5
.gitignore
vendored
|
|
@ -16,9 +16,14 @@ yarn-error.log
|
|||
.env
|
||||
tsconfig.tsbuildinfo
|
||||
tsconfig.build.tsbuildinfo
|
||||
.svelte-kit
|
||||
.turbo
|
||||
__screenshots__
|
||||
|
||||
dist/
|
||||
lib/
|
||||
# @uppy/svelte needs lib inside src
|
||||
!src/lib
|
||||
coverage/
|
||||
examples/dev/bundle.js
|
||||
examples/aws-php/vendor/*
|
||||
|
|
|
|||
1
.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
node_modules/
|
||||
*.js
|
||||
*.jsx
|
||||
*.cjs
|
||||
*.mjs
|
||||
!private/js2ts/*
|
||||
!examples/svelte-example/*
|
||||
*.lock
|
||||
CHANGELOG.md
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
/** @type {import("prettier").Config} */
|
||||
module.exports = {
|
||||
proseWrap: 'always',
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
semi: false,
|
||||
experimentalTernaries: true,
|
||||
overrides: [
|
||||
{
|
||||
files: 'packages/@uppy/angular/**',
|
||||
options: {
|
||||
semi: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: 'docs/**',
|
||||
options: {
|
||||
semi: true,
|
||||
useTabs: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['tsconfig.json'],
|
||||
options: {
|
||||
parser: 'jsonc',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
website/src/_posts/201*
|
||||
website/src/_posts/2020-*
|
||||
website/src/_posts/2021-0*
|
||||
examples/
|
||||
CHANGELOG.md
|
||||
CHANGELOG.next.md
|
||||
BACKLOG.md
|
||||
node_modules/
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"extends": [
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-standard-scss",
|
||||
"stylelint-config-rational-order"
|
||||
],
|
||||
"rules": {
|
||||
"at-rule-no-unknown": null,
|
||||
"scss/at-rule-no-unknown": true
|
||||
},
|
||||
"defaultSeverity": "warning"
|
||||
}
|
||||
3
.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["biomejs.biome"]
|
||||
}
|
||||
20
.vscode/uppy.code-workspace
vendored
|
|
@ -1,29 +1,23 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "..",
|
||||
},
|
||||
"path": ".."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"workbench.colorCustomizations": {
|
||||
"titleBar.activeForeground": "#ffffff",
|
||||
"titleBar.activeBackground": "#ff009d",
|
||||
"titleBar.activeBackground": "#ff009d"
|
||||
},
|
||||
"search.exclude": {
|
||||
"website/public/": true,
|
||||
"node_modules/": true,
|
||||
"website/node_modules/": true,
|
||||
"dist/": true,
|
||||
"lib/": true,
|
||||
"package-lock.json": true,
|
||||
"website/package-lock.json": true,
|
||||
"yarn-error.log": true,
|
||||
"website/.deploy_git": true,
|
||||
"npm-debug.log": true,
|
||||
"website/npm-debug.log": true,
|
||||
"website/debug.log": true,
|
||||
"nohup.out": true,
|
||||
"yarn.lock": true,
|
||||
},
|
||||
},
|
||||
"yarn.lock": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
.vscode/uppy.code-workspace.bak
vendored
|
|
@ -1,29 +0,0 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"workbench.colorCustomizations": {
|
||||
"titleBar.activeForeground": "#ffffff",
|
||||
"titleBar.activeBackground": "#ff009d",
|
||||
},
|
||||
"search.exclude": {
|
||||
"website/public/": true,
|
||||
"node_modules/": true,
|
||||
"website/node_modules/": true,
|
||||
"dist/": true,
|
||||
"lib/": true,
|
||||
"package-lock.json": true,
|
||||
"website/package-lock.json": true,
|
||||
"yarn-error.log": true,
|
||||
"website/.deploy_git": true,
|
||||
"npm-debug.log": true,
|
||||
"website/npm-debug.log": true,
|
||||
"website/debug.log": true,
|
||||
"nohup.out": true,
|
||||
"yarn.lock": true
|
||||
}
|
||||
}
|
||||
}
|
||||
52
.yarn/patches/@changesets-cli-npm-2.29.5-68d8030bf3.patch
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
diff --git a/dist/changesets-cli.cjs.js b/dist/changesets-cli.cjs.js
|
||||
index 82ed39a6b2a38fe0aaabb71c2ec745c502dbe06a..c779498212942c5ee822501a5630c3755e335db8 100644
|
||||
--- a/dist/changesets-cli.cjs.js
|
||||
+++ b/dist/changesets-cli.cjs.js
|
||||
@@ -634,9 +634,8 @@ async function getPublishTool(cwd) {
|
||||
const pm = await packageManagerDetector.detect({
|
||||
cwd
|
||||
});
|
||||
- if (!pm || pm.name !== "pnpm") return {
|
||||
- name: "npm"
|
||||
- };
|
||||
+ if (!pm) return { name: "npm" };
|
||||
+ if (pm.name === "yarn") return { name: "yarn" };
|
||||
try {
|
||||
let result = await spawn__default["default"]("pnpm", ["--version"], {
|
||||
cwd
|
||||
@@ -771,6 +770,9 @@ async function internalPublish(packageJson, opts, twoFactorState) {
|
||||
} = publishTool.name === "pnpm" ? await spawn__default["default"]("pnpm", ["publish", "--json", ...publishFlags], {
|
||||
env: Object.assign({}, process.env, envOverride),
|
||||
cwd: opts.cwd
|
||||
+ }) : publishTool.name === "yarn" ? await spawn__default["default"]("yarn", ["npm", "publish", ...publishFlags], {
|
||||
+ env: Object.assign({}, process.env, envOverride),
|
||||
+ cwd: opts.cwd,
|
||||
}) : await spawn__default["default"](publishTool.name, ["publish", opts.publishDir, "--json", ...publishFlags], {
|
||||
env: Object.assign({}, process.env, envOverride)
|
||||
});
|
||||
diff --git a/dist/changesets-cli.esm.js b/dist/changesets-cli.esm.js
|
||||
index 1e945455b39b9c6424b26960b3d26a095ad980e4..3a7339db5d9d1ffac16c0ef5a7d56a9d506483a4 100644
|
||||
--- a/dist/changesets-cli.esm.js
|
||||
+++ b/dist/changesets-cli.esm.js
|
||||
@@ -596,9 +596,8 @@ async function getPublishTool(cwd) {
|
||||
const pm = await detect({
|
||||
cwd
|
||||
});
|
||||
- if (!pm || pm.name !== "pnpm") return {
|
||||
- name: "npm"
|
||||
- };
|
||||
+ if (!pm) return { name: "npm" };
|
||||
+ if (pm.name === "yarn") return { name: "yarn" };
|
||||
try {
|
||||
let result = await spawn$1("pnpm", ["--version"], {
|
||||
cwd
|
||||
@@ -733,6 +732,9 @@ async function internalPublish(packageJson, opts, twoFactorState) {
|
||||
} = publishTool.name === "pnpm" ? await spawn$1("pnpm", ["publish", "--json", ...publishFlags], {
|
||||
env: Object.assign({}, process.env, envOverride),
|
||||
cwd: opts.cwd
|
||||
+ }) : publishTool.name === "yarn" ? await spawn$1("yarn", ["npm", "publish", ...publishFlags], {
|
||||
+ env: Object.assign({}, process.env, envOverride),
|
||||
+ cwd: opts.cwd,
|
||||
}) : await spawn$1(publishTool.name, ["publish", opts.publishDir, "--json", ...publishFlags], {
|
||||
env: Object.assign({}, process.env, envOverride)
|
||||
});
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
diff --git a/dist/error.d.ts b/dist/error.d.ts
|
||||
index bd657d5311ff3d255dc57a2f7224301d532a3177..8924e0982c64c566f4d9808cde62292d7ed334ac 100644
|
||||
--- a/dist/error.d.ts
|
||||
+++ b/dist/error.d.ts
|
||||
@@ -1,9 +1,9 @@
|
||||
import { D as DiffOptions } from './types-widbdqe5.js';
|
||||
import 'pretty-format';
|
||||
|
||||
-declare function serializeError(val: any, seen?: WeakMap<WeakKey, any>): any;
|
||||
+declare function serializeError(val: any, seen?: WeakMap<object, any>): any;
|
||||
declare function processError(err: any, diffOptions?: DiffOptions): any;
|
||||
-declare function replaceAsymmetricMatcher(actual: any, expected: any, actualReplaced?: WeakSet<WeakKey>, expectedReplaced?: WeakSet<WeakKey>): {
|
||||
+declare function replaceAsymmetricMatcher(actual: any, expected: any, actualReplaced?: WeakSet<object>, expectedReplaced?: WeakSet<object>): {
|
||||
replacedActual: any;
|
||||
replacedExpected: any;
|
||||
};
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
diff --git a/package.json b/package.json
|
||||
index d4133284745349488cf0e623b983c78fd7cc7fb7..52433fa0ecea99ebbfe5b94a8023f73350896f7d 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -6,6 +6,7 @@
|
||||
"repository": "sindresorhus/p-queue",
|
||||
"funding": "https://github.com/sponsors/sindresorhus",
|
||||
"type": "module",
|
||||
+ "main": "./dist/index.js",
|
||||
"exports": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
diff --git a/index.js b/index.js
|
||||
index a20646d922945004cb737918ef6b6d063bb3c2a4..a44863e9555abdaa569f309b1197fddc8dd244a5 100644
|
||||
--- a/index.js
|
||||
+++ b/index.js
|
||||
@@ -147,7 +147,7 @@ Hook.prototype.log = function log(lines, exit) {
|
||||
* @api private
|
||||
*/
|
||||
Hook.prototype.initialize = function initialize() {
|
||||
- ['git', 'npm'].forEach(function each(binary) {
|
||||
+ ['git', 'corepack'].forEach(function each(binary) {
|
||||
try { this[binary] = which.sync(binary); }
|
||||
catch (e) {}
|
||||
}, this);
|
||||
@@ -159,9 +159,9 @@ Hook.prototype.initialize = function initialize() {
|
||||
if (!this.npm) {
|
||||
try {
|
||||
process.env.PATH += path.delimiter + path.dirname(process.env._);
|
||||
- this.npm = which.sync('npm');
|
||||
+ this.npm = which.sync('corepack');
|
||||
} catch (e) {
|
||||
- return this.log(this.format(Hook.log.binary, 'npm'), 0);
|
||||
+ return this.log(this.format(Hook.log.binary, 'corepack'), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ Hook.prototype.run = function runner() {
|
||||
// this doesn't have the required `isAtty` information that libraries use to
|
||||
// output colors resulting in script output that doesn't have any color.
|
||||
//
|
||||
- spawn(hooked.npm, ['run', script, '--silent'], {
|
||||
+ spawn(hooked.npm, ['yarn', script], {
|
||||
env: process.env,
|
||||
cwd: hooked.root,
|
||||
stdio: [0, 1, 2]
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
diff --git a/debug/package.json b/debug/package.json
|
||||
index 054944f5478a0a5cf7b6b8791950c595f956157b..06a4fe2719605eb42c5ee795101c21cfd10b59ce 100644
|
||||
--- a/debug/package.json
|
||||
+++ b/debug/package.json
|
||||
@@ -9,6 +9,7 @@
|
||||
"umd:main": "dist/debug.umd.js",
|
||||
"source": "src/index.js",
|
||||
"license": "MIT",
|
||||
+ "type": "module",
|
||||
"mangle": {
|
||||
"regex": "^(?!_renderer)^_"
|
||||
},
|
||||
diff --git a/devtools/package.json b/devtools/package.json
|
||||
index 09b04a77690bdfba01083939ff9eaf987dd50bcb..92c159fbb3cf312c6674202085fb237d6fb921ad 100644
|
||||
--- a/devtools/package.json
|
||||
+++ b/devtools/package.json
|
||||
@@ -10,6 +10,7 @@
|
||||
"source": "src/index.js",
|
||||
"license": "MIT",
|
||||
"types": "src/index.d.ts",
|
||||
+ "type": "module",
|
||||
"peerDependencies": {
|
||||
"preact": "^10.0.0"
|
||||
},
|
||||
diff --git a/hooks/package.json b/hooks/package.json
|
||||
index 74807025bf3de273ebada2cd355428a2c972503d..98501726ffbfe55ffa09928e56a9dcafb9a348ff 100644
|
||||
--- a/hooks/package.json
|
||||
+++ b/hooks/package.json
|
||||
@@ -10,6 +10,7 @@
|
||||
"source": "src/index.js",
|
||||
"license": "MIT",
|
||||
"types": "src/index.d.ts",
|
||||
+ "type": "module",
|
||||
"scripts": {
|
||||
"build": "microbundle build --raw",
|
||||
"dev": "microbundle watch --raw --format cjs",
|
||||
diff --git a/jsx-runtime/package.json b/jsx-runtime/package.json
|
||||
index 7a4027831223f16519a74e3028c34f2f8f5f011a..6b58d17dbacce81894467ef43c0a8e2435e388c4 100644
|
||||
--- a/jsx-runtime/package.json
|
||||
+++ b/jsx-runtime/package.json
|
||||
@@ -10,6 +10,7 @@
|
||||
"source": "src/index.js",
|
||||
"types": "src/index.d.ts",
|
||||
"license": "MIT",
|
||||
+ "type": "module",
|
||||
"peerDependencies": {
|
||||
"preact": "^10.0.0"
|
||||
},
|
||||
diff --git a/package.json b/package.json
|
||||
index 60279c24a08b808ffbf7dc64a038272bddb6785d..088f35fb2c92f2e9b7248557857af2839988d1aa 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -9,6 +9,7 @@
|
||||
"umd:main": "dist/preact.umd.js",
|
||||
"unpkg": "dist/preact.min.js",
|
||||
"source": "src/index.js",
|
||||
+ "type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.d.ts",
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
diff --git a/src/index.d.ts b/src/index.d.ts
|
||||
index 74aacc0526ff554e9248c3f6fb44c353b5465efc..1b236d215a9db4cbc1c83f4d8bce24add202483e 100644
|
||||
--- a/src/index.d.ts
|
||||
+++ b/src/index.d.ts
|
||||
@@ -1,14 +1,3 @@
|
||||
-interface DOMRectReadOnly {
|
||||
- readonly x: number;
|
||||
- readonly y: number;
|
||||
- readonly width: number;
|
||||
- readonly height: number;
|
||||
- readonly top: number;
|
||||
- readonly right: number;
|
||||
- readonly bottom: number;
|
||||
- readonly left: number;
|
||||
-}
|
||||
-
|
||||
declare global {
|
||||
interface ResizeObserverCallback {
|
||||
(entries: ResizeObserverEntry[], observer: ResizeObserver): void
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
diff --git a/src/utils.js b/src/utils.js
|
||||
index 1f636c6617a71a68318dc587a1c9e6081020f9aa..b28e840ed08f26a4eadd242a6f541fbaefea0eda 100644
|
||||
--- a/src/utils.js
|
||||
+++ b/src/utils.js
|
||||
@@ -112,7 +112,7 @@ const getArguments = cliArgs => {
|
||||
}
|
||||
|
||||
function normalizeCommand (command) {
|
||||
- return UTILS.isPackageScriptName(command) ? `npm run ${command}` : command
|
||||
+ return UTILS.isPackageScriptName(command) ? `corepack yarn ${command}` : command
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
diff --git a/package.json b/package.json
|
||||
index f0ab3711ee4f490cbf961ebe6283ce2a28b6824b..644235a3ef52c974e946403a3fcdd137d01fad0c 100644
|
||||
--- a/package.json
|
||||
+++ b/package.json
|
||||
@@ -25,6 +25,7 @@
|
||||
"require": "./dist/index.js",
|
||||
"import": "./wrapper.mjs"
|
||||
},
|
||||
+ "jest": "./dist/index.js",
|
||||
"default": "./dist/esm-browser/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
|
|
@ -8,3 +8,6 @@ compressionLevel: mixed
|
|||
initScope: uppy
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
npmPublishAccess: public
|
||||
|
||||
|
|
|
|||
143
BACKLOG.md
|
|
@ -1,143 +0,0 @@
|
|||
# Backlog
|
||||
|
||||
<!--lint disable no-literal-urls no-undefined-references-->
|
||||
|
||||
These are ideas that are planned for specific versions or act as a backlog
|
||||
without a clear date. PRs are welcome! Please do open an issue to discuss first
|
||||
if it's a big feature, priorities may have changed after something was added
|
||||
here.
|
||||
|
||||
## `3.0.0`
|
||||
|
||||
- [x] Switch to ES Modules (ESM)
|
||||
- [x] @uppy/image-editor: Remove silly hack to work around non-ESM.
|
||||
- [ ] Some not too breaking breaking changes. Go through TODOs (@arturi,
|
||||
@aduh95, @Murderlon)
|
||||
- [ ] Companion breaking changes, like S3 keys (@mifi)
|
||||
- [x] New remote-sources preset
|
||||
- [x] Deprecate Robodog
|
||||
- [x] Remove from 3.x branch (@aduh95)
|
||||
- [x] Update docs that refer to Robodog (@arturi)
|
||||
- [ ] Update Transloadit.com examples and docs to use @uppy/transloadit +
|
||||
@uppy/remote-sources plugins instead of @uppy/robodog (@arturi)
|
||||
|
||||
## `4.0.0`
|
||||
|
||||
- [ ] core: change the preprocessing --> uploading flow to allow for files to
|
||||
start uploading right away after their preprocessing step has finished.
|
||||
See #1738 (@goto-but-stop)
|
||||
- [ ] companion: add more reliable tests to catch edge cases in companion. For
|
||||
example testing that oauth works for multiple companion instances that use
|
||||
a master Oauth domain.
|
||||
- [ ] Consider updating the name of @uppy/aws-s3 and @uppy/aws-s3-multipart to
|
||||
reflect it also supports Google Cloud Storage, Wasabi, and other cloud
|
||||
providers.
|
||||
- [ ] Consider fixing all locale files to follow the bcp-47 standard (nl_NL -->
|
||||
nl-NL)
|
||||
|
||||
## Unplanned
|
||||
|
||||
### Core
|
||||
|
||||
- [ ] Make sure Uppy works well in VR
|
||||
- [ ] normalize file names when uploading from iOS? Can we do it with meta data?
|
||||
date? `image-${index}`? #678
|
||||
- [ ] Can Uppy upload a lot of files at once? Seems to fail now:
|
||||
https://github.com/transloadit/uppy/issues/3313 (@aduh95, @Murderlon)
|
||||
- [ ] Consider how we can make Uppy smaller. Replace some packages with smaller
|
||||
alternatives. Talk about Socket.io again (@aduh95)
|
||||
- [ ] Better events — more data, consistency, naming (@Murderlon)
|
||||
|
||||
### Dashboard
|
||||
|
||||
- [ ] Dashboard UI should support 20 providers (@arturi)
|
||||
- [ ] Allow minimizing the Dashboard during upload (Uppy then becomes just a
|
||||
tiny progress indicator) (@arturi)
|
||||
- [ ] Display data like image resolution on file cards. should be done by
|
||||
thumbnail generator maybe #783
|
||||
- [ ] Possibility to edit/delete more than one file at once. example: add
|
||||
copyrigh info to 1000 files #118, #97
|
||||
- [ ] Possibility to work on already uploaded / in progress files. We'll just
|
||||
provide the `fileId` to the `file-edit-complete` event so that folks can
|
||||
more easily roll out custom code for this themselves #112, #113, #2063
|
||||
- [ ] Focus jumps weirdly if you remove a file
|
||||
https://github.com/transloadit/uppy/pull/2161#issuecomment-613565486
|
||||
- [ ] A mini UI that features drop & progress (may involve a `mini: true`
|
||||
options for dashboard, may involve drop+progress or new plugin) (@arturi)
|
||||
- [ ] Add a Load More button so you don't have to TAB endlessly to get to the
|
||||
upload button (https://github.com/transloadit/uppy/issues/1419)
|
||||
|
||||
### New plugins
|
||||
|
||||
- [ ] WordPress Back-end plugin. Should be another Transloadit Integration based
|
||||
on Robodog Dashboard(?) we should add a provider, and possibly offer
|
||||
already-uploaded content
|
||||
- [ ] WordPress Front-end Gravity Forms Uppy plugin so one form field could be
|
||||
an Uppy-powered file input
|
||||
- [ ] A WakeLock based plugin that keeps your phone from going to sleep while an
|
||||
upload is ongoing https://github.com/transloadit/uppy/issues/1725
|
||||
- [ ] Improve image editor: filters for images, no crashes (@aduh95)
|
||||
|
||||
### New providers
|
||||
|
||||
- [ ] Google Photos (#2163)
|
||||
- [ ] MediaLibrary provider which shows you files that have already been
|
||||
uploaded #450, #1121, #1112 #362
|
||||
- [ ] Giphy image search (on top of Unsplash plugin) ()
|
||||
- [ ] Image search (via Google or Bing or DuckDuckGo): use duckduckgo-images-api
|
||||
or Google Search API (@arturi)
|
||||
- [ ] Vimeo #2872
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- [ ] goldenretriever: make it work with aws multipart
|
||||
https://community.transloadit.com/t/resumable-aws-s3-multipart-integration/14888
|
||||
(@goto-bus-stop)
|
||||
- [ ] provider: add sorting (by date) #254
|
||||
- [ ] qa: add one integration test (or add to existing test) that uses more
|
||||
exotic (tus) options such as `useFastRemoteRetry` or
|
||||
`removeFingerprintOnSuccess`
|
||||
https://github.com/transloadit/uppy/issues/1327 (@arturi,
|
||||
@ifedapoolarewaju)
|
||||
- [x] react: Add a React Hook to manage an Uppy instance
|
||||
https://github.com/transloadit/uppy/pull/1247#issuecomment-458063951
|
||||
(@goto-bus-stop)
|
||||
- [ ] rn: Uppy React Native works with Expo, now let's make it work without
|
||||
- [ ] rn: Uppy React Native works with Url Plugin, now let's make it work with
|
||||
Instagram
|
||||
- [ ] security: consider iframe / more security for Transloadit/Uppy integration
|
||||
widget and Uppy itself. Page can’t get files from Google Drive if its an
|
||||
iframe
|
||||
- [ ] statusbar: Add a confirmation of the cancel action
|
||||
(https://github.com/transloadit/uppy/issues/1418) as well as ask the user
|
||||
if they really want to navigate away while an upload is in progress via
|
||||
`onbeforeunload` (@arturi)
|
||||
- [ ] uploaders: consider not showing progress updates from the server after an
|
||||
upload’s been paused. Perhaps the button can be disabled and say
|
||||
`Pausing..` until Companion has actually stopped transmitting updates
|
||||
(@arturi, @ifedapoolarewaju)
|
||||
- [ ] xhr: allow sending custom headers per file (as proposed in #785)
|
||||
- [ ] website: It would be nice in the long run to have a dynamic package
|
||||
builder here right on the website where you can select the plugins you
|
||||
need/want and it builds and downloads a minified version of them? Sort of
|
||||
like jQuery UI: https://jqueryui.com/download/
|
||||
- [ ] webcam: Specify the resolution of the webcam images/video. We should add a
|
||||
way to specify any custom 'constraints' (aspect ratio, resolution,
|
||||
mimetype (`/video/mp4;codec=h264`), bits per second, etc) to the Webcam
|
||||
plugin #876
|
||||
- [ ] Constructor to build Uppy with what you need, “Dashboard example meets
|
||||
Transloadit Wizard”. Select language, modes, providers — get code ready to
|
||||
use. Maybe integrate Transloadit Wizard in there as well (@arturi,
|
||||
@Murderlon)
|
||||
|
||||
### Needs research
|
||||
|
||||
- [ ] Add a prepublish test that checks if `npm pack` is not massive
|
||||
(@goto-bus-stop)
|
||||
- [ ] Add https://github.com/pa11y/pa11y for automated accessibility testing?
|
||||
- [ ] Add lighthouse for automated performance testing?
|
||||
- [ ] Switch one existing e2e test to use Parcel (create-react-app already using
|
||||
webpack) (@arturi)
|
||||
- [ ] Add typescript with JSDoc for @uppy/core
|
||||
https://github.com/Microsoft/TypeScript/wiki/Type-Checking-JavaScript-Files
|
||||
(@arturi)
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Hi, thanks for trying out the bundled version of the Uppy File Uploader. You can
|
||||
use this from a CDN
|
||||
(`<script src="https://releases.transloadit.com/uppy/v4.3.0/uppy.min.js"></script>`)
|
||||
(`<script src="https://releases.transloadit.com/uppy/v5.0.0/uppy.min.js"></script>`)
|
||||
or bundle it with your webapp.
|
||||
|
||||
Note that the recommended way to use Uppy is to install it with yarn/npm and use
|
||||
|
|
|
|||
538
CHANGELOG.md
|
|
@ -12,6 +12,542 @@ Please add your entries in this format:
|
|||
|
||||
In the current stage we aim to release a new version at least every month.
|
||||
|
||||
## 4.18.0
|
||||
|
||||
Released: 2025-06-30
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| -------------------------- | ------- | -------------------------- | ------- |
|
||||
| @uppy/components | 0.2.0 | @uppy/remote-sources | 2.3.4 |
|
||||
| @uppy/core | 4.4.7 | @uppy/screen-capture | 4.3.1 |
|
||||
| @uppy/google-drive-picker | 0.3.6 | @uppy/svelte | 4.5.0 |
|
||||
| @uppy/google-photos-picker | 0.3.6 | @uppy/vue | 2.3.0 |
|
||||
| @uppy/locales | 4.6.0 | @uppy/webcam | 4.2.1 |
|
||||
| @uppy/provider-views | 4.4.5 | uppy | 4.18.0 |
|
||||
| @uppy/react | 4.4.0 | | |
|
||||
|
||||
- meta: Remove remark reference from CI (Murderlon)
|
||||
- @uppy/components,@uppy/screen-capture: useScreenCapture fixes (Prakash / #5793)
|
||||
- examples: Add useRemoteSource (Merlijn Vos / #5778)
|
||||
- @uppy/components: Use webcam fixes2 (Mikael Finstad / #5791)
|
||||
- meta: Remove remark (Merlijn Vos / #5790)
|
||||
- meta: Delete old, unused files (Merlijn Vos / #5788)
|
||||
- meta: Sort package.json (Murderlon)
|
||||
- examples: Headless Hooks: Add useScreenCapture (Prakash / #5784)
|
||||
- @uppy/locales: Update pt_BR localization (Gabriel Pereira / #5780)
|
||||
- e2e: Skip for now then (Murderlon)
|
||||
- e2e: fixup! Fix CI for now (Murderlon)
|
||||
- e2e: Fix CI for now (Murderlon)
|
||||
- examples: Add useWebcam (Merlijn Vos / #5741)
|
||||
- @uppy/react,@uppy/svelte,@uppy/vue: Add useDropzone & useFileInput (Merlijn Vos / #5735)
|
||||
- meta: build(deps): bump base-x from 3.0.9 to 3.0.11 (dependabot[bot] / #5772)
|
||||
- @uppy/provider-views: improve metadata handling in Google Photos Picker (ben rosenbaum / #5769)
|
||||
|
||||
|
||||
## 4.17.0
|
||||
|
||||
Released: 2025-06-02
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| -------------------- | ------- | -------------------- | ------- |
|
||||
| @uppy/companion | 5.8.0 | @uppy/screen-capture | 4.3.0 |
|
||||
| @uppy/components | 0.1.0 | @uppy/svelte | 4.4.0 |
|
||||
| @uppy/core | 4.4.6 | @uppy/utils | 6.1.5 |
|
||||
| @uppy/locales | 4.5.3 | @uppy/vue | 2.2.0 |
|
||||
| @uppy/provider-views | 4.4.4 | @uppy/webcam | 4.2.0 |
|
||||
| @uppy/react | 4.3.0 | uppy | 4.17.0 |
|
||||
|
||||
- @uppy/provider-views: fix: handle pagination for Google Photos picker (fixes #5765) (ben rosenbaum / #5768)
|
||||
- @uppy/companion: add max filename length env var (Freeman / #5763)
|
||||
- @uppy/core: fix missing required meta field error not updating (Prakash / #5766)
|
||||
- @uppy/screen-capture: add screenshot button (Prakash / #5737)
|
||||
- @uppy/locales: Update cs_CZ.ts (Martin Štorek / #5749)
|
||||
- examples: Headless components (Merlijn Vos / #5727)
|
||||
- @uppy/companion: fix cookie maxAge to milliseconds (zolotarov@brights.io / #5746)
|
||||
- meta: document how to fix a broken release (Mikael Finstad / #5755)
|
||||
- @uppy/companion: improve Zoom folder structure (Merlijn Vos / #5739)
|
||||
|
||||
|
||||
## 4.16.0
|
||||
|
||||
Released: 2025-05-18
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| -------------------------- | ------- | -------------------------- | ------- |
|
||||
| @uppy/audio | 2.1.3 | @uppy/image-editor | 3.3.3 |
|
||||
| @uppy/box | 3.2.3 | @uppy/instagram | 4.2.3 |
|
||||
| @uppy/companion | 5.7.0 | @uppy/onedrive | 4.2.4 |
|
||||
| @uppy/companion-client | 4.4.2 | @uppy/remote-sources | 2.3.3 |
|
||||
| @uppy/core | 4.4.5 | @uppy/screen-capture | 4.2.3 |
|
||||
| @uppy/dashboard | 4.3.4 | @uppy/unsplash | 4.3.4 |
|
||||
| @uppy/drag-drop | 4.1.3 | @uppy/url | 4.2.4 |
|
||||
| @uppy/dropbox | 4.2.3 | @uppy/utils | 6.1.4 |
|
||||
| @uppy/facebook | 4.2.3 | @uppy/webcam | 4.1.3 |
|
||||
| @uppy/file-input | 4.1.3 | @uppy/webdav | 0.3.3 |
|
||||
| @uppy/google-drive | 4.3.3 | @uppy/zoom | 3.2.3 |
|
||||
| @uppy/google-drive-picker | 0.3.5 | uppy | 4.16.0 |
|
||||
| @uppy/google-photos-picker | 0.3.5 | | |
|
||||
|
||||
- meta: Revert "Release: uppy@4.16.0 (#5750)" (Mikael Finstad)
|
||||
- meta: force cdn upload (Mikael Finstad)
|
||||
- meta: fix invalid brach option (now ref) (Mikael Finstad)
|
||||
- meta: improve release script output (Mikael Finstad)
|
||||
- meta: fix error (Mikael Finstad)
|
||||
- meta: Release: uppy@4.16.0 (github-actions[bot] / #5750)
|
||||
- meta: Fix node versions (Mikael Finstad / #5740)
|
||||
- @uppy/companion-client: don't reject on incorrect origin (Mikael Finstad / #5736)
|
||||
- @uppy/companion: implement credentials param `transloadit_gateway` (Mikael Finstad / #5725)
|
||||
- @uppy/companion: Fix AES key wear-out (Florian Maury / #5724)
|
||||
- @uppy/core: fix undefined reference when cancelling an upload (Prakash / #5730)
|
||||
- @uppy/audio,@uppy/box,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/google-drive-picker,@uppy/google-drive,@uppy/google-photos-picker,@uppy/image-editor,@uppy/instagram,@uppy/onedrive,@uppy/remote-sources,@uppy/screen-capture,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/webcam,@uppy/webdav,@uppy/zoom: ts: make locale strings optional (Merlijn Vos / #5728)
|
||||
|
||||
|
||||
## 4.15.0
|
||||
|
||||
Released: 2025-04-14
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| -------------------- | ------- | -------------------- | ------- |
|
||||
| @uppy/angular | 0.8.0 | uppy | 4.15.0 |
|
||||
| @uppy/provider-views | 4.4.3 | | |
|
||||
|
||||
- @uppy/provider-views: Fix google photos picker (Mikael Finstad / #5717)
|
||||
- meta: build(deps): bump nanoid from 3.3.7 to 5.1.2 (dependabot[bot] / #5664)
|
||||
- examples: build(deps-dev): bump vite from 5.4.16 to 5.4.17 (dependabot[bot] / #5707)
|
||||
- @uppy/utils: Fix type check ci (Mikael Finstad / #5714)
|
||||
- @uppy/angular: Support Angular 19 (#5709) (Arnaud Flaesch / #5715)
|
||||
- meta: simplify e2e (Mikael Finstad / #5711)
|
||||
- meta: fix ready to commit (Mikael Finstad / #5713)
|
||||
|
||||
|
||||
## 4.14.0
|
||||
|
||||
Released: 2025-04-08
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| -------------------------- | ------- | -------------------------- | ------- |
|
||||
| @uppy/audio | 2.1.2 | @uppy/locales | 4.5.2 |
|
||||
| @uppy/box | 3.2.2 | @uppy/onedrive | 4.2.3 |
|
||||
| @uppy/companion | 5.6.0 | @uppy/react | 4.2.3 |
|
||||
| @uppy/core | 4.4.4 | @uppy/remote-sources | 2.3.2 |
|
||||
| @uppy/dashboard | 4.3.3 | @uppy/screen-capture | 4.2.2 |
|
||||
| @uppy/drag-drop | 4.1.2 | @uppy/status-bar | 4.1.3 |
|
||||
| @uppy/dropbox | 4.2.2 | @uppy/transloadit | 4.2.2 |
|
||||
| @uppy/facebook | 4.2.2 | @uppy/unsplash | 4.3.3 |
|
||||
| @uppy/file-input | 4.1.2 | @uppy/url | 4.2.3 |
|
||||
| @uppy/google-drive | 4.3.2 | @uppy/utils | 6.1.3 |
|
||||
| @uppy/google-drive-picker | 0.3.4 | @uppy/webcam | 4.1.2 |
|
||||
| @uppy/google-photos-picker | 0.3.4 | @uppy/webdav | 0.3.2 |
|
||||
| @uppy/image-editor | 3.3.2 | @uppy/zoom | 3.2.2 |
|
||||
| @uppy/instagram | 4.2.2 | uppy | 4.14.0 |
|
||||
|
||||
- @uppy/core: dry retryAll() and upload() (Mikael Finstad / #5691)
|
||||
- @uppy/angular: Revert "Support Angular 19" (Mikael Finstad / #5710)
|
||||
- @uppy/angular: Support Angular 19 (Arnaud Flaesch / #5709)
|
||||
- @uppy/companion: implement dropbox business teams (Mikael Finstad / #5708)
|
||||
- @uppy/utils: add msg mimetype (Merlijn Vos / #5699)
|
||||
- @uppy/core: fix locale type for plugins (Merlijn Vos / #5700)
|
||||
- examples: build(deps-dev): bump vite from 5.4.14 to 5.4.15 (dependabot[bot] / #5703)
|
||||
- @uppy/locales: Update nb_NO.ts (Tore Sinding Bekkedal / #5678)
|
||||
- meta: build(deps): bump docker/login-action from 3.3.0 to 3.4.0 (dependabot[bot] / #5689)
|
||||
- examples: @uppy-example/aws-nodejs: fix fileType not present in S3 objects (Prakash / #5680)
|
||||
- @uppy/core: fix events when retrying with upload() (Prakash / #5696)
|
||||
- meta: Fix locales building (Mikael Finstad / #5693)
|
||||
- @uppy/google-photos: remove google photos 😢 (Mikael Finstad / #5690)
|
||||
- @uppy/locales: Update cs_CZ.ts (David Petrásek / #5658)
|
||||
|
||||
|
||||
## 4.13.4
|
||||
|
||||
Released: 2025-03-13
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| ----------- | ------- | ----------- | ------- |
|
||||
| @uppy/core | 4.4.3 | uppy | 4.13.4 |
|
||||
| @uppy/react | 4.2.2 | | |
|
||||
|
||||
- @uppy/core: make upload() idempotent (Merlijn Vos / #5677)
|
||||
- @uppy/react: pass getServerSnapshot to useSyncExternalStoreWithSelector (Merlijn Vos / #5685)
|
||||
- meta: Fix BasePlugin export for CDN bundle (Merlijn Vos / #5684)
|
||||
- meta: build(deps): bump docker/build-push-action from 6.14.0 to 6.15.0 (dependabot[bot] / #5673)
|
||||
- meta: build(deps): bump docker/setup-qemu-action from 3.4.0 to 3.6.0 (dependabot[bot] / #5675)
|
||||
- meta: build(deps): bump docker/metadata-action from 5.6.1 to 5.7.0 (dependabot[bot] / #5674)
|
||||
|
||||
|
||||
## 4.13.3
|
||||
|
||||
Released: 2025-02-25
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| ---------------- | ------- | ---------------- | ------- |
|
||||
| @uppy/companion | 5.5.2 | @uppy/xhr-upload | 4.3.3 |
|
||||
| @uppy/dashboard | 4.3.2 | uppy | 4.13.3 |
|
||||
| @uppy/status-bar | 4.1.2 | | |
|
||||
|
||||
- @uppy/dashboard: do not allow drag&drop of file preview (Merlijn Vos / #5650)
|
||||
- @uppy/xhr-upload: fix when responseType is set to JSON (Merlijn Vos / #5651)
|
||||
- meta: build(deps): bump akhileshns/heroku-deploy from 3.13.15 to 3.14.15 (dependabot[bot] / #5659)
|
||||
- meta: build(deps): bump docker/build-push-action from 6.13.0 to 6.14.0 (dependabot[bot] / #5660)
|
||||
- @uppy/status-bar: fix aria-hidden warning (Merlijn Vos / #5663)
|
||||
- @uppy/companion: log when tus uploaded size differs (Mikael Finstad / #5647)
|
||||
- @uppy/companion: remove redundant HEAD request for file size (Mikael Finstad / #5648)
|
||||
- meta: build(deps): bump elliptic from 6.6.0 to 6.6.1 (dependabot[bot] / #5649)
|
||||
- examples: build(deps-dev): bump esbuild from 0.21.5 to 0.25.0 (dependabot[bot] / #5643)
|
||||
- meta: build(deps-dev): bump vitest from 1.6.0 to 1.6.1 (dependabot[bot] / #5641)
|
||||
- meta: build(deps): bump docker/setup-qemu-action from 3.3.0 to 3.4.0 (dependabot[bot] / #5640)
|
||||
|
||||
|
||||
## 4.13.2
|
||||
|
||||
Released: 2025-02-03
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| -------------------------- | ------- | -------------------------- | ------- |
|
||||
| @uppy/core | 4.4.2 | @uppy/provider-views | 4.4.2 |
|
||||
| @uppy/google-drive-picker | 0.3.3 | @uppy/url | 4.2.2 |
|
||||
| @uppy/google-photos-picker | 0.3.3 | @uppy/utils | 6.1.2 |
|
||||
| @uppy/locales | 4.5.1 | uppy | 4.13.2 |
|
||||
|
||||
- @uppy/utils: do not strip www in getSocketHost (Merlijn Vos / #5621)
|
||||
- @uppy/url: skip drag/dropped local files (Merlijn Vos / #5626)
|
||||
- @uppy/provider-views: fix google photos picker videos (Mikael Finstad / #5635)
|
||||
- @uppy/core,@uppy/google-drive-picker,@uppy/google-photos-picker,@uppy/provider-views:
|
||||
fix google picker i18n (Mikael Finstad / #5632)
|
||||
|
||||
|
||||
## 4.13.1
|
||||
|
||||
Released: 2025-01-22
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| --------------- | ------- | --------------- | ------- |
|
||||
| @uppy/aws-s3 | 4.2.3 | @uppy/tus | 4.2.2 |
|
||||
| @uppy/companion | 5.5.1 | uppy | 4.13.1 |
|
||||
|
||||
- @uppy/tus: fix resumeFromPreviousUpload race condition (Merlijn Vos / #5616)
|
||||
- @uppy/aws-s3: Fixed default shouldUseMultipart (Mika Laitinen / #5613)
|
||||
- meta: build(deps): bump docker/build-push-action from 6.11.0 to 6.12.0 (dependabot[bot] / #5611)
|
||||
- @uppy/aws-s3: remove console.error (Mikael Finstad / #5607)
|
||||
- @uppy/companion: unify http error responses (Mikael Finstad / #5595)
|
||||
|
||||
|
||||
## 4.13.0
|
||||
|
||||
Released: 2025-01-15
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| --------------- | ------- | --------------- | ------- |
|
||||
| @uppy/aws-s3 | 4.2.2 | @uppy/unsplash | 4.3.2 |
|
||||
| @uppy/companion | 5.5.0 | uppy | 4.13.0 |
|
||||
|
||||
- @uppy/aws-s3: always set S3 meta to UppyFile & include key (Merlijn Vos / #5602)
|
||||
- @uppy/companion: fix forcePathStyle boolean conversion (Mikael Finstad / #5308)
|
||||
- meta: Fix Webpack CI (Merlijn Vos / #5604)
|
||||
- @uppy/aws-s3: allow uploads to fail/succeed independently (Merlijn Vos / #5603)
|
||||
- meta: Add types for css files (Merlijn Vos / #5591)
|
||||
- @uppy/unsplash: make utmSource optional (Merlijn Vos / #5601)
|
||||
- meta: build(deps): bump docker/setup-qemu-action from 3.2.0 to 3.3.0 (dependabot[bot] / #5599)
|
||||
- meta: build(deps): bump docker/build-push-action from 6.10.0 to 6.11.0 (dependabot[bot] / #5600)
|
||||
- @uppy/companion: add COMPANION_TUS_DEFERRED_UPLOAD_LENGTH (Dominik Schmidt / #5561)
|
||||
|
||||
|
||||
## 4.12.2
|
||||
|
||||
Released: 2025-01-09
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| -------------------------- | ------- | -------------------------- | ------- |
|
||||
| @uppy/audio | 2.1.1 | @uppy/instagram | 4.2.1 |
|
||||
| @uppy/aws-s3 | 4.2.1 | @uppy/onedrive | 4.2.2 |
|
||||
| @uppy/box | 3.2.1 | @uppy/progress-bar | 4.2.1 |
|
||||
| @uppy/companion-client | 4.4.1 | @uppy/provider-views | 4.4.1 |
|
||||
| @uppy/compressor | 2.2.1 | @uppy/react | 4.2.1 |
|
||||
| @uppy/core | 4.4.1 | @uppy/remote-sources | 2.3.1 |
|
||||
| @uppy/dashboard | 4.3.1 | @uppy/screen-capture | 4.2.1 |
|
||||
| @uppy/drag-drop | 4.1.1 | @uppy/status-bar | 4.1.1 |
|
||||
| @uppy/drop-target | 3.1.1 | @uppy/thumbnail-generator | 4.1.1 |
|
||||
| @uppy/dropbox | 4.2.1 | @uppy/transloadit | 4.2.1 |
|
||||
| @uppy/facebook | 4.2.1 | @uppy/tus | 4.2.1 |
|
||||
| @uppy/file-input | 4.1.1 | @uppy/unsplash | 4.3.1 |
|
||||
| @uppy/form | 4.1.1 | @uppy/url | 4.2.1 |
|
||||
| @uppy/golden-retriever | 4.1.1 | @uppy/vue | 2.1.1 |
|
||||
| @uppy/google-drive | 4.3.1 | @uppy/webcam | 4.1.1 |
|
||||
| @uppy/google-drive-picker | 0.3.2 | @uppy/webdav | 0.3.1 |
|
||||
| @uppy/google-photos | 0.5.1 | @uppy/xhr-upload | 4.3.2 |
|
||||
| @uppy/google-photos-picker | 0.3.2 | @uppy/zoom | 3.2.1 |
|
||||
| @uppy/image-editor | 3.3.1 | uppy | 4.12.2 |
|
||||
| @uppy/informer | 4.2.1 | | |
|
||||
|
||||
- @uppy/provider-views: Import types consistently from @uppy/core (Merlijn Vos / #5589)
|
||||
- @uppy/status-bar: fix double upload progress (Mikael Finstad / #5587)
|
||||
- @uppy/provider-views: fix incorrect import (Merlijn Vos / #5588)
|
||||
|
||||
|
||||
## 4.12.1
|
||||
|
||||
Released: 2025-01-08
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| --------------- | ------- | --------------- | ------- |
|
||||
| @uppy/companion | 5.4.1 | uppy | 4.12.1 |
|
||||
|
||||
- @uppy/companion: upgrade express & express-session (Merlijn Vos / #5582)
|
||||
|
||||
|
||||
## 4.12.0
|
||||
|
||||
Released: 2025-01-08
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| -------------------------- | ------- | -------------------------- | ------- |
|
||||
| @uppy/google-drive-picker | 0.3.1 | @uppy/unsplash | 4.3.0 |
|
||||
| @uppy/google-photos-picker | 0.3.1 | @uppy/utils | 6.1.1 |
|
||||
| @uppy/onedrive | 4.2.1 | @uppy/xhr-upload | 4.3.1 |
|
||||
| @uppy/provider-views | 4.4.0 | uppy | 4.12.0 |
|
||||
| @uppy/svelte | 4.3.0 | | |
|
||||
|
||||
- @uppy/unsplash,@uppy/provider-views: add utmSource option (Merlijn Vos / #5580)
|
||||
- @uppy/xhr-upload: allow custom error message in onAfterResponse (Merlijn Vos / #5578)
|
||||
- @uppy/onedrive: fix AsyncStore import (Merlijn Vos / #5579)
|
||||
- @uppy/google-drive-picker,@uppy/google-photos-picker: Fix Google Picker plugins locale (Merlijn Vos / #5575)
|
||||
|
||||
|
||||
## 4.11.0
|
||||
|
||||
Released: 2025-01-06
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| -------------------------- | ------- | -------------------------- | ------- |
|
||||
| @uppy/audio | 2.1.0 | @uppy/onedrive | 4.2.0 |
|
||||
| @uppy/aws-s3 | 4.2.0 | @uppy/progress-bar | 4.2.0 |
|
||||
| @uppy/box | 3.2.0 | @uppy/provider-views | 4.3.0 |
|
||||
| @uppy/companion-client | 4.4.0 | @uppy/react | 4.2.0 |
|
||||
| @uppy/compressor | 2.2.0 | @uppy/remote-sources | 2.3.0 |
|
||||
| @uppy/core | 4.4.0 | @uppy/screen-capture | 4.2.0 |
|
||||
| @uppy/dashboard | 4.3.0 | @uppy/status-bar | 4.1.0 |
|
||||
| @uppy/drag-drop | 4.1.0 | @uppy/store-default | 4.2.0 |
|
||||
| @uppy/drop-target | 3.1.0 | @uppy/svelte | 4.2.0 |
|
||||
| @uppy/dropbox | 4.2.0 | @uppy/thumbnail-generator | 4.1.0 |
|
||||
| @uppy/facebook | 4.2.0 | @uppy/transloadit | 4.2.0 |
|
||||
| @uppy/file-input | 4.1.0 | @uppy/tus | 4.2.0 |
|
||||
| @uppy/form | 4.1.0 | @uppy/unsplash | 4.2.0 |
|
||||
| @uppy/golden-retriever | 4.1.0 | @uppy/url | 4.2.0 |
|
||||
| @uppy/google-drive | 4.3.0 | @uppy/utils | 6.1.0 |
|
||||
| @uppy/google-drive-picker | 0.3.0 | @uppy/vue | 2.1.0 |
|
||||
| @uppy/google-photos | 0.5.0 | @uppy/webcam | 4.1.0 |
|
||||
| @uppy/google-photos-picker | 0.3.0 | @uppy/webdav | 0.3.0 |
|
||||
| @uppy/image-editor | 3.3.0 | @uppy/xhr-upload | 4.3.0 |
|
||||
| @uppy/informer | 4.2.0 | @uppy/zoom | 3.2.0 |
|
||||
| @uppy/instagram | 4.2.0 | uppy | 4.11.0 |
|
||||
| @uppy/locales | 4.5.0 | | |
|
||||
|
||||
- meta: build(deps): bump docker/metadata-action from 5.5.1 to 5.6.1 (dependabot[bot] / #5525)
|
||||
- examples,@uppy/svelte: build(deps-dev): bump @sveltejs/kit from 2.5.17 to 2.8.3 (dependabot[bot] / #5526)
|
||||
- meta: build(deps): bump docker/build-push-action from 6.9.0 to 6.10.0 (dependabot[bot] / #5531)
|
||||
- meta: build(deps): bump elliptic from 6.5.7 to 6.6.0 (dependabot[bot] / #5498)
|
||||
- @uppy/utils: Use .js(x) for all imports instead .ts(x) (Merlijn Vos / #5573)
|
||||
- @uppy/angular,@uppy/audio,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive-picker,@uppy/google-drive,@uppy/google-photos-picker,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/vue,@uppy/webcam,@uppy/webdav,@uppy/xhr-upload,@uppy/zoom: Remove "paths" from all tsconfig's (Merlijn Vos / #5572)
|
||||
- @uppy/tus: fix onBeforeRequest type (Dominik Schmidt / #5566)
|
||||
|
||||
|
||||
## 4.10.0
|
||||
|
||||
Released: 2025-01-06
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| -------------------- | ------- | -------------------- | ------- |
|
||||
| @uppy/companion | 5.4.0 | @uppy/store-redux | 4.0.2 |
|
||||
| @uppy/core | 4.3.2 | @uppy/url | 4.1.3 |
|
||||
| @uppy/dashboard | 4.2.0 | @uppy/webdav | 0.2.0 |
|
||||
| @uppy/provider-views | 4.2.1 | uppy | 4.10.0 |
|
||||
| @uppy/react | 4.1.0 | | |
|
||||
|
||||
- @uppy/react: allow React 19 as peer dependency (Shubs / #5556)
|
||||
- @uppy/webdav: add plugin icon (Merlijn Vos / #5555)
|
||||
- @uppy/companion: pass fetched origins to window.postMessage() (Merlijn Vos / #5529)
|
||||
- @uppy/core,@uppy/dashboard,@uppy/provider-views,@uppy/store-redux,@uppy/url: build(deps): bump nanoid from 5.0.7 to 5.0.9 (dependabot[bot] / #5544)
|
||||
|
||||
|
||||
## 4.9.0
|
||||
|
||||
Released: 2024-12-17
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| -------------------------- | ------- | -------------------------- | ------- |
|
||||
| @uppy/companion | 5.3.0 | @uppy/progress-bar | 4.1.0 |
|
||||
| @uppy/companion-client | 4.3.0 | @uppy/provider-views | 4.2.0 |
|
||||
| @uppy/core | 4.3.1 | @uppy/status-bar | 4.0.6 |
|
||||
| @uppy/google-drive-picker | 0.2.1 | @uppy/utils | 6.0.6 |
|
||||
| @uppy/google-photos-picker | 0.2.1 | @uppy/webdav | 0.1.0 |
|
||||
| @uppy/locales | 4.4.0 | uppy | 4.9.0 |
|
||||
|
||||
- @uppy/webdav: Add @uppy/webdav (Merlijn Vos / #5551)
|
||||
- @uppy/google-drive-picker,@uppy/google-photos-picker,@uppy/locales: Add missing Google Picker locale entries (Merlijn Vos / #5552)
|
||||
- @uppy/core: bring back validateRestrictions (Merlijn Vos / #5538)
|
||||
- @uppy/google-drive-picker,@uppy/google-photos-picker: Fix TS generics on new Google Picker plugins (Merlijn Vos / #5550)
|
||||
- @uppy/locales: Add missing French locale entries (Steven SAN / #5549)
|
||||
- e2e,@uppy/status-bar,@uppy/utils: Companion stream upload unknown size files (Mikael Finstad / #5489)
|
||||
|
||||
|
||||
## 4.8.0
|
||||
|
||||
Released: 2024-12-05
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| -------------------------- | ------- | -------------------------- | ------- |
|
||||
| @uppy/audio | 2.0.2 | @uppy/instagram | 4.1.2 |
|
||||
| @uppy/aws-s3 | 4.1.3 | @uppy/locales | 4.3.1 |
|
||||
| @uppy/box | 3.1.2 | @uppy/onedrive | 4.1.2 |
|
||||
| @uppy/companion | 5.2.0 | @uppy/progress-bar | 4.0.2 |
|
||||
| @uppy/companion-client | 4.2.0 | @uppy/provider-views | 4.1.0 |
|
||||
| @uppy/compressor | 2.1.1 | @uppy/react | 4.0.4 |
|
||||
| @uppy/core | 4.3.0 | @uppy/remote-sources | 2.2.1 |
|
||||
| @uppy/dashboard | 4.1.3 | @uppy/screen-capture | 4.1.2 |
|
||||
| @uppy/drag-drop | 4.0.5 | @uppy/status-bar | 4.0.5 |
|
||||
| @uppy/drop-target | 3.0.2 | @uppy/store-default | 4.1.2 |
|
||||
| @uppy/dropbox | 4.1.2 | @uppy/thumbnail-generator | 4.0.2 |
|
||||
| @uppy/facebook | 4.1.2 | @uppy/transloadit | 4.1.4 |
|
||||
| @uppy/file-input | 4.0.4 | @uppy/tus | 4.1.5 |
|
||||
| @uppy/form | 4.0.2 | @uppy/unsplash | 4.1.2 |
|
||||
| @uppy/golden-retriever | 4.0.2 | @uppy/url | 4.1.2 |
|
||||
| @uppy/google-drive | 4.2.0 | @uppy/utils | 6.0.5 |
|
||||
| @uppy/google-drive-picker | 0.2.0 | @uppy/vue | 2.0.3 |
|
||||
| @uppy/google-photos | 0.4.0 | @uppy/webcam | 4.0.3 |
|
||||
| @uppy/google-photos-picker | 0.2.0 | @uppy/xhr-upload | 4.2.3 |
|
||||
| @uppy/image-editor | 3.2.1 | @uppy/zoom | 3.1.2 |
|
||||
| @uppy/informer | 4.1.2 | uppy | 4.8.0 |
|
||||
|
||||
- @uppy/companion-client: Fix allowed origins (Mikael Finstad / #5536)
|
||||
- meta: Build lib refactor to esm (Mikael Finstad / #5537)
|
||||
- @uppy/provider-views: Google picker scope (Mikael Finstad / #5535)
|
||||
- @uppy/core,@uppy/provider-views: move useStore out of core (Mikael Finstad / #5533)
|
||||
- @uppy/companion,@uppy/google-drive-picker,@uppy/google-photos-picker: Google Picker (Mikael Finstad / #5443)
|
||||
- @uppy/aws-s3: console.error instead of throw for missing etag (Merlijn Vos / #5521)
|
||||
- docs: Put docs back in uppy.io repository where they belong (Merlijn Vos / #5527)
|
||||
- docs: typo (Azhar Rizqullah / #5523)
|
||||
- @uppy/audio,@uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/compressor,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/drop-target,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react,@uppy/remote-sources,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: cleanup tsconfig (Mikael Finstad / #5520)
|
||||
- meta: fix missing lint (Mikael Finstad / #5519)
|
||||
- docs: Add Next.js docs (Merlijn Vos / #5502)
|
||||
- e2e: try to fix flaky test (Mikael Finstad / #5512)
|
||||
- meta: Fix broken lint on CI (Mikael Finstad / #5507)
|
||||
|
||||
|
||||
## 4.7.0
|
||||
|
||||
Released: 2024-11-11
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| --------------- | ------- | --------------- | ------- |
|
||||
| @uppy/aws-s3 | 4.1.2 | @uppy/tus | 4.1.4 |
|
||||
| @uppy/companion | 5.1.4 | uppy | 4.7.0 |
|
||||
| @uppy/locales | 4.3.0 | | |
|
||||
|
||||
- @uppy/aws-s3: clarify and warn when incorrect buckets settings are used (Mikael Finstad / #5505)
|
||||
- @uppy/tus: fix event upload-success response.body.xhr (ItsOnlyBinary / #5503)
|
||||
- @uppy/companion: Enable CSRF protection in grant (OAuth2) (Mikael Finstad / #5504)
|
||||
- @uppy/locales: Add ms_MY (Malay) locale (Salimi / #5488)
|
||||
|
||||
|
||||
## 4.6.0
|
||||
|
||||
Released: 2024-10-31
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| ------------------------- | ------- | ------------------------- | ------- |
|
||||
| @uppy/aws-s3 | 4.1.1 | @uppy/provider-views | 4.0.2 |
|
||||
| @uppy/box | 3.1.1 | @uppy/react | 4.0.3 |
|
||||
| @uppy/companion | 5.1.3 | @uppy/react-native | 0.6.1 |
|
||||
| @uppy/companion-client | 4.1.1 | @uppy/redux-dev-tools | 4.0.1 |
|
||||
| @uppy/core | 4.2.3 | @uppy/screen-capture | 4.1.1 |
|
||||
| @uppy/dashboard | 4.1.2 | @uppy/status-bar | 4.0.4 |
|
||||
| @uppy/drag-drop | 4.0.4 | @uppy/store-default | 4.1.1 |
|
||||
| @uppy/dropbox | 4.1.1 | @uppy/store-redux | 4.0.1 |
|
||||
| @uppy/facebook | 4.1.1 | @uppy/svelte | 4.1.1 |
|
||||
| @uppy/file-input | 4.0.3 | @uppy/thumbnail-generator | 4.0.1 |
|
||||
| @uppy/form | 4.0.1 | @uppy/transloadit | 4.1.3 |
|
||||
| @uppy/golden-retriever | 4.0.1 | @uppy/tus | 4.1.3 |
|
||||
| @uppy/google-drive | 4.1.1 | @uppy/unsplash | 4.1.1 |
|
||||
| @uppy/google-photos | 0.3.1 | @uppy/url | 4.1.1 |
|
||||
| @uppy/image-editor | 3.2.0 | @uppy/utils | 6.0.4 |
|
||||
| @uppy/informer | 4.1.1 | @uppy/vue | 2.0.2 |
|
||||
| @uppy/instagram | 4.1.1 | @uppy/webcam | 4.0.2 |
|
||||
| @uppy/locales | 4.2.1 | @uppy/xhr-upload | 4.2.2 |
|
||||
| @uppy/onedrive | 4.1.1 | @uppy/zoom | 3.1.1 |
|
||||
| @uppy/progress-bar | 4.0.1 | uppy | 4.6.0 |
|
||||
|
||||
- @uppy/xhr-upload: fix stale file references in events (Merlijn Vos / #5499)
|
||||
- @uppy/image-editor: upgrade cropperjs (Merlijn Vos / #5497)
|
||||
- @uppy/aws-s3,@uppy/box,@uppy/companion-client,@uppy/core,@uppy/dashboard,@uppy/drag-drop,@uppy/dropbox,@uppy/facebook,@uppy/file-input,@uppy/form,@uppy/golden-retriever,@uppy/google-drive,@uppy/google-photos,@uppy/image-editor,@uppy/informer,@uppy/instagram,@uppy/locales,@uppy/onedrive,@uppy/progress-bar,@uppy/provider-views,@uppy/react-native,@uppy/react,@uppy/redux-dev-tools,@uppy/screen-capture,@uppy/status-bar,@uppy/store-default,@uppy/store-redux,@uppy/svelte,@uppy/thumbnail-generator,@uppy/transloadit,@uppy/tus,@uppy/unsplash,@uppy/url,@uppy/utils,@uppy/vue,@uppy/webcam,@uppy/xhr-upload,@uppy/zoom: Fix links (Anthony Veaudry / #5492)
|
||||
- docs,@uppy/companion: disallow corsOrigins "*" (Mikael Finstad / #5496)
|
||||
|
||||
|
||||
## 4.5.0
|
||||
|
||||
Released: 2024-10-15
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| ---------------- | ------- | ---------------- | ------- |
|
||||
| @uppy/companion | 5.1.2 | @uppy/svelte | 4.1.0 |
|
||||
| @uppy/core | 4.2.2 | @uppy/tus | 4.1.2 |
|
||||
| @uppy/dashboard | 4.1.1 | @uppy/utils | 6.0.3 |
|
||||
| @uppy/drag-drop | 4.0.3 | @uppy/xhr-upload | 4.2.1 |
|
||||
| @uppy/file-input | 4.0.2 | uppy | 4.5.0 |
|
||||
| @uppy/locales | 4.2.0 | | |
|
||||
|
||||
- @uppy/dashboard: Dashboard - convert some files to typescript (Evgenia Karunus / #5367)
|
||||
- @uppy/dashboard,@uppy/drag-drop,@uppy/file-input: `.handleInputChange()` - use `.currentTarget`; clear the input using `''` (Evgenia Karunus / #5381)
|
||||
- meta: build(deps): bump @blakeembrey/template from 1.1.0 to 1.2.0 (dependabot[bot] / #5448)
|
||||
- @uppy/locales: Update packages/@uppy/locales/src/fr_FR.ts (Zéfyx / #5472)
|
||||
- @uppy/svelte: use SvelteKit as the build tool (Merlijn Vos / #5484)
|
||||
- @uppy/xhr-upload: add response to upload-error callback (Caleb Hardin / #5486)
|
||||
- @uppy/tus: tus: Avoid duplicate `upload-error` event (Marius / #5485)
|
||||
- @uppy/companion: Fix redis emitter (Mikael Finstad / #5474)
|
||||
- meta: build(deps): bump docker/build-push-action from 6.8.0 to 6.9.0 (dependabot[bot] / #5483)
|
||||
|
||||
|
||||
## 4.4.1
|
||||
|
||||
Released: 2024-09-30
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| ----------------- | ------- | ----------------- | ------- |
|
||||
| @uppy/core | 4.2.1 | uppy | 4.4.1 |
|
||||
| @uppy/transloadit | 4.1.2 | | |
|
||||
|
||||
- @uppy/transloadit: fix multiple upload batches & run again (Merlijn Vos / #5478)
|
||||
- meta: build(deps): bump docker/build-push-action from 6.7.0 to 6.8.0 (dependabot[bot] / #5477)
|
||||
- meta: build(deps): bump vite from 5.2.11 to 5.4.8 (dependabot[bot] / #5471)
|
||||
- @uppy/svelte: build(deps-dev): bump rollup from 4.18.0 to 4.22.4 (dependabot[bot] / #5470)
|
||||
- meta: build(deps): bump vite from 5.2.11 to 5.4.6 (dependabot[bot] / #5466)
|
||||
|
||||
|
||||
## 4.4.0
|
||||
|
||||
Released: 2024-09-20
|
||||
|
||||
| Package | Version | Package | Version |
|
||||
| ----------------- | ------- | ----------------- | ------- |
|
||||
| @uppy/companion | 5.1.1 | @uppy/tus | 4.1.1 |
|
||||
| @uppy/svelte | 4.0.2 | @uppy/xhr-upload | 4.2.0 |
|
||||
| @uppy/transloadit | 4.1.1 | uppy | 4.4.0 |
|
||||
|
||||
- @uppy/tus: fix retry check for status code 400 (Merlijn Vos / #5461)
|
||||
- meta: Merge branch 'main' of https://github.com/transloadit/uppy (Murderlon)
|
||||
- meta: fix AwsS3 endpoint option in private/dev (Murderlon)
|
||||
- examples: build(deps): bump body-parser from 1.20.2 to 1.20.3 (dependabot[bot] / #5462)
|
||||
- examples: build(deps-dev): bump vite from 5.3.1 to 5.3.6 (dependabot[bot] / #5459)
|
||||
- @uppy/tus: set response from tus-js-client (Merlijn Vos / #5456)
|
||||
- docs: fix assemblyOptions example for React (Merlijn Vos / #5450)
|
||||
- docs: rename Edgly to Smart CDN (Merlijn Vos / #5449)
|
||||
- @uppy/tus: correctly type tus on UppyFile (Merlijn Vos / #5454)
|
||||
- docs: remove old legacy CDN reference (Murderlon)
|
||||
- @uppy/xhr-upload: pass files to onBeforeRequest (Merlijn Vos / #5447)
|
||||
- @uppy/svelte: fix generated module to not bundle Svelte (Antoine du Hamel / #5446)
|
||||
- examples,@uppy/svelte: Bump svelte from 4.2.18 to 4.2.19 (dependabot[bot] / #5440)
|
||||
- meta: bump Yarn to 4.4.1 (Antoine du Hamel / #5445)
|
||||
- docs: fix broken links in locale docs (Serghei Cebotari / #5441)
|
||||
|
||||
|
||||
## 4.3.0
|
||||
|
||||
Released: 2024-08-29
|
||||
|
|
@ -6154,7 +6690,7 @@ Released: 2018-02-11.
|
|||
- dashboard: Use more accessible tip lib microtip (#536 / @arturi)
|
||||
- docs: Add PHP snippets to XHRUpload docs (#567 / @goto-bus-stop)
|
||||
- meta: Added instruction to fork the repo first (#512 / muhammadInam)
|
||||
- meta: Automatically host releases on edgly and use that as our main CDN (#558 / @kvz)
|
||||
- meta: Automatically host releases on Smart CDN and use that as our main CDN (#558 / @kvz)
|
||||
- meta: Dependency version updates (#523 / @goto-bus-stop)
|
||||
- meta: Remove unused files from published package (#586 / @goto-bus-stop)
|
||||
- s3: Respect `limit` option for upload parameter requests too; fix isXml() check when no content-type is available (#545, #544, #528 / @goto-bus-stop)
|
||||
|
|
|
|||
130
CLAUDE.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Repository Overview
|
||||
|
||||
Uppy is a modular JavaScript file uploader that integrates seamlessly with any application. It's built using a monorepo structure with Yarn workspaces, Turbo for build orchestration, and Biome for linting/formatting.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Core Commands
|
||||
- `yarn build` - Build all packages (uses Turbo)
|
||||
- `yarn build:watch` - Watch mode for building packages
|
||||
- `yarn test` - Run tests for all packages
|
||||
- `yarn test:watch` - Run tests in watch mode
|
||||
- `yarn typecheck` - Type checking across all packages
|
||||
- `yarn check` - Run Biome linting and formatting
|
||||
- `yarn check:ci` - Run Biome in CI mode (no writes)
|
||||
|
||||
### Development Server
|
||||
- `yarn dev` - Start development server (from private/dev workspace)
|
||||
- `yarn dev:with-companion` - Start dev server with Companion backend
|
||||
- `yarn start:companion` - Start only the Companion server
|
||||
|
||||
### Single Test Execution
|
||||
To run tests for a specific package:
|
||||
```bash
|
||||
yarn workspace @uppy/[package-name] test
|
||||
# Example: yarn workspace @uppy/core test
|
||||
```
|
||||
|
||||
### Individual Package Development
|
||||
```bash
|
||||
yarn workspace @uppy/[package-name] build
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Monorepo Structure
|
||||
- `packages/@uppy/` - Core Uppy packages (plugins, utilities)
|
||||
- `packages/uppy/` - Main bundle package
|
||||
- `examples/` - Example implementations for various frameworks
|
||||
- `private/` - Internal development tools
|
||||
|
||||
### Core Architecture
|
||||
- **Uppy Core** (`@uppy/core`) - Main class that manages plugins, state, and events
|
||||
- **Plugins** - Modular components for different functionalities:
|
||||
- **UI Plugins**: Dashboard, Drag & Drop, File Input, Webcam, etc.
|
||||
- **Provider Plugins**: Google Drive, Dropbox, Instagram, etc. (require Companion)
|
||||
- **Uploader Plugins**: Tus (resumable), XHR Upload, AWS S3, etc.
|
||||
- **Utility Plugins**: Golden Retriever (recovery), Thumbnail Generator, etc.
|
||||
|
||||
### Plugin Types
|
||||
1. **Acquirer** - Gets files (File Input, Webcam, Google Drive)
|
||||
2. **Modifier** - Modifies files (Image Editor, Compressor)
|
||||
3. **Uploader** - Uploads files (Tus, XHR, AWS S3)
|
||||
4. **Presenter** - Shows UI (Dashboard, Progress Bar, Status Bar)
|
||||
5. **Orchestrator** - Manages workflow
|
||||
6. **Logger** - Handles logging
|
||||
|
||||
### Key Packages
|
||||
- `@uppy/core` - Main Uppy class and plugin system
|
||||
- `@uppy/dashboard` - Complete UI with file management
|
||||
- `@uppy/companion` - Server-side component for remote providers
|
||||
- `@uppy/companion-client` - Client for communicating with Companion
|
||||
- `@uppy/utils` - Shared utilities
|
||||
- `@uppy/components` - Headless Preact components (with framework wrappers)
|
||||
|
||||
## Build System
|
||||
|
||||
### Turbo Configuration
|
||||
The build system uses Turbo (turbo.json) for task orchestration:
|
||||
- Builds have dependency ordering (`dependsOn: ["^build"]`)
|
||||
- TypeScript builds output to `lib/` and `dist/` directories
|
||||
- CSS builds process SCSS files to CSS
|
||||
- Special handling for the main `uppy` package bundle
|
||||
|
||||
### TypeScript Configuration
|
||||
- Shared config in `tsconfig.shared.json`
|
||||
- Individual packages have `tsconfig.json` and `tsconfig.build.json`
|
||||
- Build outputs include declaration files (.d.ts) and source maps
|
||||
|
||||
## Code Standards
|
||||
|
||||
### Linting and Formatting
|
||||
- **Biome** is used for linting and formatting (replaces ESLint/Prettier)
|
||||
- Configuration in `biome.json`
|
||||
- Use single quotes, semicolons as needed
|
||||
- 2-space indentation
|
||||
|
||||
### Framework Integration
|
||||
- **Preact** for UI components with framework wrappers generated for React, Vue, Svelte
|
||||
- **TypeScript** throughout the codebase
|
||||
- Components in `@uppy/components` use Tailwind CSS with `uppy:` prefix
|
||||
|
||||
### Plugin Development
|
||||
- All plugins extend `BasePlugin` or `UIPlugin`
|
||||
- Plugins must have unique IDs and specify their type
|
||||
- State management through Uppy's store system
|
||||
- Event-driven architecture using namespace-emitter
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Framework
|
||||
- **Vitest** for unit tests (migrated from Jest)
|
||||
- **Vitest Browser Mode** for browser testing (migrated from Cypress)
|
||||
- Unit tests use `.test.{ts,tsx}` extensions and browser tests `.browser.test.{ts,tsx}`
|
||||
|
||||
### Running Tests
|
||||
- Tests run in parallel across packages via Turbo
|
||||
- Examples also include tests (React, Vue, SvelteKit examples)
|
||||
- Use `yarn test` for development
|
||||
|
||||
## Companion Server
|
||||
|
||||
The Companion server enables integration with remote file sources:
|
||||
- Handles OAuth flows for providers like Google Drive, Dropbox
|
||||
- Proxies file downloads to avoid CORS issues
|
||||
- Provides search functionality for providers
|
||||
- Located in `packages/@uppy/companion/`
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Never commit sensitive information (API keys, tokens)
|
||||
- Follow existing code conventions and patterns within each package
|
||||
- Use the `@uppy/utils` package for shared functionality
|
||||
- UI components should be accessible and support internationalization
|
||||
- Always run `yarn check` and `yarn typecheck` before committing
|
||||
- When adding a new component to `@uppy/components`, you have to run `yarn migrate:components` from root.
|
||||
This is not needed for changing existing components.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
FROM node:18.17.1-alpine as build
|
||||
FROM node:22.18.0-alpine AS build
|
||||
|
||||
# Create link to node on amd64 so that corepack can find it
|
||||
RUN if [ "$(uname -m)" == "aarch64" ]; then mkdir -p /usr/local/sbin/ && ln -s /usr/local/bin/node /usr/local/sbin/node; fi
|
||||
|
|
@ -17,7 +17,7 @@ RUN cd /app && corepack yarn workspace @uppy/companion build
|
|||
# Now remove all non-prod dependencies for a leaner image
|
||||
RUN cd /app && corepack yarn workspaces focus @uppy/companion --production
|
||||
|
||||
FROM node:18.17.1-alpine
|
||||
FROM node:22.18.0-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
FROM node:18.17.1-alpine
|
||||
|
||||
COPY package.json /app/package.json
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk --update add --virtual native-dep \
|
||||
make gcc g++ python3 libgcc libstdc++ git && \
|
||||
corepack yarn install && \
|
||||
apk del native-dep
|
||||
RUN apk add bash
|
||||
|
||||
COPY . /app
|
||||
RUN npm install -g nodemon
|
||||
CMD ["npm","test"]
|
||||
43
Makefile
|
|
@ -1,43 +0,0 @@
|
|||
# Licensed under MIT.
|
||||
# Copyright (2016) by Kevin van Zonneveld https://twitter.com/kvz
|
||||
#
|
||||
# https://www.npmjs.com/package/fakefile
|
||||
#
|
||||
# Please do not edit this file directly, but propose changed upstream instead:
|
||||
# https://github.com/kvz/fakefile/blob/master/Makefile
|
||||
#
|
||||
# This Makefile offers convience shortcuts into any Node.js project that utilizes npm scripts.
|
||||
# It functions as a wrapper around the actual listed in `package.json`
|
||||
# So instead of typing:
|
||||
#
|
||||
# $ npm script build:assets
|
||||
#
|
||||
# you could also type:
|
||||
#
|
||||
# $ make build-assets
|
||||
#
|
||||
# Notice that colons (:) are replaced by dashes for Makefile compatibility.
|
||||
#
|
||||
# The benefits of this wrapper are:
|
||||
#
|
||||
# - You get to keep the the scripts package.json, which is more portable
|
||||
# (Makefiles & Windows are harder to mix)
|
||||
# - Offer a polite way into the project for developers coming from different
|
||||
# languages (npm scripts is obviously very Node centric)
|
||||
# - Profit from better autocomplete (make <TAB><TAB>) than npm currently offers.
|
||||
# OSX users will have to install bash-completion
|
||||
# (http://davidalger.com/development/bash-completion-on-os-x-with-brew/)
|
||||
|
||||
define npm_script_targets
|
||||
TARGETS := $(shell node -e 'for (var k in require("./package.json").scripts) {console.log(k.replace(/:/g, "-"));}')
|
||||
$$(TARGETS):
|
||||
npm run $(subst -,:,$(MAKECMDGOALS))
|
||||
|
||||
.PHONY: $$(TARGETS)
|
||||
endef
|
||||
|
||||
$(eval $(call npm_script_targets))
|
||||
|
||||
# These npm run scripts are available, without needing to be mentioned in `package.json`
|
||||
install:
|
||||
npm install
|
||||
121
README.md
|
|
@ -30,7 +30,7 @@ Uppy is being developed by the folks at [Transloadit](https://transloadit.com),
|
|||
a versatile API to handle any file in your app.
|
||||
|
||||
<table>
|
||||
<tr><th>Tests</th><td><img src="https://github.com/transloadit/uppy/workflows/CI/badge.svg" alt="CI status for Uppy tests"></td><td><img src="https://github.com/transloadit/uppy/workflows/Companion/badge.svg" alt="CI status for Companion tests"></td><td><img src="https://github.com/transloadit/uppy/workflows/End-to-end%20tests/badge.svg" alt="CI status for browser tests"></td></tr>
|
||||
<tr><th>Tests</th><td><img src="https://github.com/transloadit/uppy/workflows/CI/badge.svg" alt="CI status for Uppy tests"></td><td><img src="https://github.com/transloadit/uppy/workflows/Companion/badge.svg" alt="CI status for Companion tests"></td></tr>
|
||||
<tr><th>Deploys</th><td><img src="https://github.com/transloadit/uppy/workflows/Release/badge.svg" alt="CI status for CDN deployment"></td><td><img src="https://github.com/transloadit/uppy/workflows/Companion%20Edge%20Deploy/badge.svg" alt="CI status for Companion deployment"></td><td><img src="https://github.com/transloadit/uppy.io/workflows/Deploy%20to%20GitHub%20Pages/badge.svg" alt="CI status for website deployment"></td></tr>
|
||||
</table>
|
||||
|
||||
|
|
@ -61,6 +61,22 @@ const uppy = new Uppy()
|
|||
**[read the docs](https://uppy.io/docs)** for more details on how to use Uppy
|
||||
and its plugins.
|
||||
|
||||
## Integrations
|
||||
|
||||
Uppy has first-class support for plain JS/HTML,
|
||||
[React](https://uppy.io/docs/react/), [Svelte](https://uppy.io/docs/svelte/),
|
||||
[Vue](https://uppy.io/docs/vue/), and [Angular](https://uppy.io/docs/angular/).
|
||||
|
||||
For the supported frameworks (except Angular) Uppy offers three ways to build user interfaces:
|
||||
|
||||
1. **Pre-composed, plug-and-play components.** Mainly `<Dashboard />`.
|
||||
The downside is that you can’t customize the UI.
|
||||
2. **Headless components.** Smaller components, easier to override the styles
|
||||
or compose them together with your own components.
|
||||
3. **Hooks.** Attach our logic to your own components, no restrictions, create a
|
||||
tailor-made UI.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- Lightweight, modular plugin-based architecture, light on dependencies :zap:
|
||||
|
|
@ -87,13 +103,12 @@ npm install @uppy/core @uppy/dashboard @uppy/tus
|
|||
```
|
||||
|
||||
Add CSS
|
||||
[uppy.min.css](https://releases.transloadit.com/uppy/v4.3.0/uppy.min.css),
|
||||
[uppy.min.css](https://releases.transloadit.com/uppy/v5.0.0/uppy.min.css),
|
||||
either to your HTML page’s `<head>` or include in JS, if your bundler of choice
|
||||
supports it.
|
||||
|
||||
Alternatively, you can also use a pre-built bundle from Transloadit’s CDN:
|
||||
Edgly. In that case `Uppy` will attach itself to the global `window.Uppy`
|
||||
object.
|
||||
Alternatively, you can also use a pre-built bundle from Transloadit’s CDN: Smart
|
||||
CDN. In that case `Uppy` will attach itself to the global `window.Uppy` object.
|
||||
|
||||
> ⚠️ The bundle consists of most Uppy plugins, so this method is not recommended
|
||||
> for production, as your users will have to download all plugins when you are
|
||||
|
|
@ -102,7 +117,7 @@ object.
|
|||
```html
|
||||
<!-- 1. Add CSS to `<head>` -->
|
||||
<link
|
||||
href="https://releases.transloadit.com/uppy/v4.3.0/uppy.min.css"
|
||||
href="https://releases.transloadit.com/uppy/v5.0.0/uppy.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
|
|
@ -113,7 +128,7 @@ object.
|
|||
Uppy,
|
||||
Dashboard,
|
||||
Tus,
|
||||
} from 'https://releases.transloadit.com/uppy/v4.3.0/uppy.min.mjs'
|
||||
} from 'https://releases.transloadit.com/uppy/v5.0.0/uppy.min.mjs'
|
||||
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Dashboard, { target: '#files-drag-drop' })
|
||||
|
|
@ -139,14 +154,7 @@ object.
|
|||
- [`Dashboard`](https://uppy.io/docs/dashboard/) — universal UI with previews,
|
||||
progress bars, metadata editor and all the cool stuff. Required for most UI
|
||||
plugins like Webcam and Instagram
|
||||
- [`Progress Bar`](https://uppy.io/docs/progress-bar/) — minimal progress bar
|
||||
that fills itself when upload progresses
|
||||
- [`Status Bar`](https://uppy.io/docs/status-bar/) — more detailed progress,
|
||||
pause/resume/cancel buttons, percentage, speed, uploaded/total sizes (included
|
||||
by default with `Dashboard`)
|
||||
- [`Informer`](https://uppy.io/docs/informer/) — send notifications like “smile”
|
||||
before taking a selfie or “upload failed” when all is lost (also included by
|
||||
default with `Dashboard`)
|
||||
- Headless components ([react](https://uppy.io/docs/react/), [svelte](https://uppy.io/docs/svelte/), [vue](https://uppy.io/docs/vue/))
|
||||
|
||||
### Sources
|
||||
|
||||
|
|
@ -178,8 +186,6 @@ server-side component, is needed for a plugin to work.
|
|||
backend out there (like Apache, Nginx)
|
||||
- [`AWS S3`](https://uppy.io/docs/aws-s3/) — plain upload to AWS S3 or
|
||||
compatible services
|
||||
- [`AWS S3 Multipart`](https://uppy.io/docs/aws-s3-multipart/) — S3-style
|
||||
“Multipart” upload to AWS or compatible services
|
||||
|
||||
### File Processing
|
||||
|
||||
|
|
@ -195,15 +201,6 @@ server-side component, is needed for a plugin to work.
|
|||
image previews (included by default with `Dashboard`)
|
||||
- [`Form`](https://uppy.io/docs/form/) — collects metadata from `<form>` right
|
||||
before an Uppy upload, then optionally appends results back to the form
|
||||
- [`Redux`](https://uppy.io/docs/redux/) — for your emerging
|
||||
[time traveling](https://github.com/gaearon/redux-devtools) needs
|
||||
|
||||
## React
|
||||
|
||||
- [React](https://uppy.io/docs/react/) — components to integrate Uppy UI plugins
|
||||
with React apps
|
||||
- [React Native](./examples/react-native-expo/) — basic Uppy component for React
|
||||
Native with Expo
|
||||
|
||||
## Browser Support
|
||||
|
||||
|
|
@ -296,77 +293,7 @@ Use Uppy in your project?
|
|||
|
||||
## Contributors
|
||||
|
||||
<table id="contributors_table">
|
||||
<tr><td><a href=https://github.com/arturi><img width="117" alt="arturi" src="https://avatars.githubusercontent.com/u/1199054?v=4&s=117"></a></td><td><a href=https://github.com/goto-bus-stop><img width="117" alt="goto-bus-stop" src="https://avatars.githubusercontent.com/u/1006268?v=4&s=117"></a></td><td><a href=https://github.com/kvz><img width="117" alt="kvz" src="https://avatars.githubusercontent.com/u/26752?v=4&s=117"></a></td><td><a href=https://github.com/aduh95><img width="117" alt="aduh95" src="https://avatars.githubusercontent.com/u/14309773?v=4&s=117"></a></td><td><a href=https://github.com/ifedapoolarewaju><img width="117" alt="ifedapoolarewaju" src="https://avatars.githubusercontent.com/u/8383781?v=4&s=117"></a></td><td><a href=https://github.com/Murderlon><img width="117" alt="Murderlon" src="https://avatars.githubusercontent.com/u/9060226?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/hedgerh><img width="117" alt="hedgerh" src="https://avatars.githubusercontent.com/u/2524280?v=4&s=117"></a></td><td><a href=https://github.com/mifi><img width="117" alt="mifi" src="https://avatars.githubusercontent.com/u/402547?v=4&s=117"></a></td><td><a href=https://github.com/nqst><img width="117" alt="nqst" src="https://avatars.githubusercontent.com/u/375537?v=4&s=117"></a></td><td><a href=https://github.com/AJvanLoon><img width="117" alt="AJvanLoon" src="https://avatars.githubusercontent.com/u/15716628?v=4&s=117"></a></td><td><a href=https://github.com/apps/github-actions><img width="117" alt="github-actions[bot]" src="https://avatars.githubusercontent.com/in/15368?v=4&s=117"></a></td><td><a href=https://github.com/lakesare><img width="117" alt="lakesare" src="https://avatars.githubusercontent.com/u/7578559?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/apps/dependabot><img width="117" alt="dependabot[bot]" src="https://avatars.githubusercontent.com/in/29110?v=4&s=117"></a></td><td><a href=https://github.com/kiloreux><img width="117" alt="kiloreux" src="https://avatars.githubusercontent.com/u/6282557?v=4&s=117"></a></td><td><a href=https://github.com/samuelayo><img width="117" alt="samuelayo" src="https://avatars.githubusercontent.com/u/14964486?v=4&s=117"></a></td><td><a href=https://github.com/sadovnychyi><img width="117" alt="sadovnychyi" src="https://avatars.githubusercontent.com/u/193864?v=4&s=117"></a></td><td><a href=https://github.com/richardwillars><img width="117" alt="richardwillars" src="https://avatars.githubusercontent.com/u/291004?v=4&s=117"></a></td><td><a href=https://github.com/ajkachnic><img width="117" alt="ajkachnic" src="https://avatars.githubusercontent.com/u/44317699?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/zcallan><img width="117" alt="zcallan" src="https://avatars.githubusercontent.com/u/13760738?v=4&s=117"></a></td><td><a href=https://github.com/YukeshShr><img width="117" alt="YukeshShr" src="https://avatars.githubusercontent.com/u/71844521?v=4&s=117"></a></td><td><a href=https://github.com/janko><img width="117" alt="janko" src="https://avatars.githubusercontent.com/u/795488?v=4&s=117"></a></td><td><a href=https://github.com/oliverpool><img width="117" alt="oliverpool" src="https://avatars.githubusercontent.com/u/3864879?v=4&s=117"></a></td><td><a href=https://github.com/Botz><img width="117" alt="Botz" src="https://avatars.githubusercontent.com/u/2706678?v=4&s=117"></a></td><td><a href=https://github.com/mcallistertyler><img width="117" alt="mcallistertyler" src="https://avatars.githubusercontent.com/u/14939210?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/mokutsu-coursera><img width="117" alt="mokutsu-coursera" src="https://avatars.githubusercontent.com/u/65177495?v=4&s=117"></a></td><td><a href=https://github.com/dschmidt><img width="117" alt="dschmidt" src="https://avatars.githubusercontent.com/u/448487?v=4&s=117"></a></td><td><a href=https://github.com/DJWassink><img width="117" alt="DJWassink" src="https://avatars.githubusercontent.com/u/1822404?v=4&s=117"></a></td><td><a href=https://github.com/mrbatista><img width="117" alt="mrbatista" src="https://avatars.githubusercontent.com/u/6544817?v=4&s=117"></a></td><td><a href=https://github.com/taoqf><img width="117" alt="taoqf" src="https://avatars.githubusercontent.com/u/15901911?v=4&s=117"></a></td><td><a href=https://github.com/timodwhit><img width="117" alt="timodwhit" src="https://avatars.githubusercontent.com/u/2761203?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/tuoxiansp><img width="117" alt="tuoxiansp" src="https://avatars.githubusercontent.com/u/3960056?v=4&s=117"></a></td><td><a href=https://github.com/eltociear><img width="117" alt="eltociear" src="https://avatars.githubusercontent.com/u/22633385?v=4&s=117"></a></td><td><a href=https://github.com/tim-kos><img width="117" alt="tim-kos" src="https://avatars.githubusercontent.com/u/15005?v=4&s=117"></a></td><td><a href=https://github.com/pauln><img width="117" alt="pauln" src="https://avatars.githubusercontent.com/u/574359?v=4&s=117"></a></td><td><a href=https://github.com/MikeKovarik><img width="117" alt="MikeKovarik" src="https://avatars.githubusercontent.com/u/3995401?v=4&s=117"></a></td><td><a href=https://github.com/toadkicker><img width="117" alt="toadkicker" src="https://avatars.githubusercontent.com/u/523330?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/dominiceden><img width="117" alt="dominiceden" src="https://avatars.githubusercontent.com/u/6367692?v=4&s=117"></a></td><td><a href=https://github.com/ap--><img width="117" alt="ap--" src="https://avatars.githubusercontent.com/u/1463443?v=4&s=117"></a></td><td><a href=https://github.com/tranvansang><img width="117" alt="tranvansang" src="https://avatars.githubusercontent.com/u/13043196?v=4&s=117"></a></td><td><a href=https://github.com/LiviaMedeiros><img width="117" alt="LiviaMedeiros" src="https://avatars.githubusercontent.com/u/74449973?v=4&s=117"></a></td><td><a href=https://github.com/bertho-zero><img width="117" alt="bertho-zero" src="https://avatars.githubusercontent.com/u/8525267?v=4&s=117"></a></td><td><a href=https://github.com/juliangruber><img width="117" alt="juliangruber" src="https://avatars.githubusercontent.com/u/10247?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/Hawxy><img width="117" alt="Hawxy" src="https://avatars.githubusercontent.com/u/975824?v=4&s=117"></a></td><td><a href=https://github.com/elenalape><img width="117" alt="elenalape" src="https://avatars.githubusercontent.com/u/22844059?v=4&s=117"></a></td><td><a href=https://github.com/gavboulton><img width="117" alt="gavboulton" src="https://avatars.githubusercontent.com/u/3900826?v=4&s=117"></a></td><td><a href=https://github.com/mejiaej><img width="117" alt="mejiaej" src="https://avatars.githubusercontent.com/u/4699893?v=4&s=117"></a></td><td><a href=https://github.com/Acconut><img width="117" alt="Acconut" src="https://avatars.githubusercontent.com/u/1375043?v=4&s=117"></a></td><td><a href=https://github.com/jhen0409><img width="117" alt="jhen0409" src="https://avatars.githubusercontent.com/u/3001525?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/mkabatek><img width="117" alt="mkabatek" src="https://avatars.githubusercontent.com/u/1764486?v=4&s=117"></a></td><td><a href=https://github.com/stephentuso><img width="117" alt="stephentuso" src="https://avatars.githubusercontent.com/u/11889560?v=4&s=117"></a></td><td><a href=https://github.com/bencergazda><img width="117" alt="bencergazda" src="https://avatars.githubusercontent.com/u/5767697?v=4&s=117"></a></td><td><a href=https://github.com/a-kriya><img width="117" alt="a-kriya" src="https://avatars.githubusercontent.com/u/26761352?v=4&s=117"></a></td><td><a href=https://github.com/yonahforst><img width="117" alt="yonahforst" src="https://avatars.githubusercontent.com/u/1440796?v=4&s=117"></a></td><td><a href=https://github.com/stanislav-cervenak><img width="117" alt="stanislav-cervenak" src="https://avatars.githubusercontent.com/u/6931349?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/sksavant><img width="117" alt="sksavant" src="https://avatars.githubusercontent.com/u/1040701?v=4&s=117"></a></td><td><a href=https://github.com/ogtfaber><img width="117" alt="ogtfaber" src="https://avatars.githubusercontent.com/u/320955?v=4&s=117"></a></td><td><a href=https://github.com/nndevstudio><img width="117" alt="nndevstudio" src="https://avatars.githubusercontent.com/u/22050968?v=4&s=117"></a></td><td><a href=https://github.com/MatthiasKunnen><img width="117" alt="MatthiasKunnen" src="https://avatars.githubusercontent.com/u/16807587?v=4&s=117"></a></td><td><a href=https://github.com/dargmuesli><img width="117" alt="dargmuesli" src="https://avatars.githubusercontent.com/u/4778485?v=4&s=117"></a></td><td><a href=https://github.com/manuelkiessling><img width="117" alt="manuelkiessling" src="https://avatars.githubusercontent.com/u/206592?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/johnnyperkins><img width="117" alt="johnnyperkins" src="https://avatars.githubusercontent.com/u/16482282?v=4&s=117"></a></td><td><a href=https://github.com/ofhope><img width="117" alt="ofhope" src="https://avatars.githubusercontent.com/u/1826459?v=4&s=117"></a></td><td><a href=https://github.com/yaegor><img width="117" alt="yaegor" src="https://avatars.githubusercontent.com/u/3315?v=4&s=117"></a></td><td><a href=https://github.com/zhuangya><img width="117" alt="zhuangya" src="https://avatars.githubusercontent.com/u/499038?v=4&s=117"></a></td><td><a href=https://github.com/sparanoid><img width="117" alt="sparanoid" src="https://avatars.githubusercontent.com/u/96356?v=4&s=117"></a></td><td><a href=https://github.com/ThomasG77><img width="117" alt="ThomasG77" src="https://avatars.githubusercontent.com/u/642120?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/subha1206><img width="117" alt="subha1206" src="https://avatars.githubusercontent.com/u/36275153?v=4&s=117"></a></td><td><a href=https://github.com/schonert><img width="117" alt="schonert" src="https://avatars.githubusercontent.com/u/2185697?v=4&s=117"></a></td><td><a href=https://github.com/SlavikTraktor><img width="117" alt="SlavikTraktor" src="https://avatars.githubusercontent.com/u/11923751?v=4&s=117"></a></td><td><a href=https://github.com/scottbessler><img width="117" alt="scottbessler" src="https://avatars.githubusercontent.com/u/293802?v=4&s=117"></a></td><td><a href=https://github.com/jrschumacher><img width="117" alt="jrschumacher" src="https://avatars.githubusercontent.com/u/46549?v=4&s=117"></a></td><td><a href=https://github.com/rosenfeld><img width="117" alt="rosenfeld" src="https://avatars.githubusercontent.com/u/32246?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/rdimartino><img width="117" alt="rdimartino" src="https://avatars.githubusercontent.com/u/11539300?v=4&s=117"></a></td><td><a href=https://github.com/ahmedkandel><img width="117" alt="ahmedkandel" src="https://avatars.githubusercontent.com/u/28398523?v=4&s=117"></a></td><td><a href=https://github.com/Youssef1313><img width="117" alt="Youssef1313" src="https://avatars.githubusercontent.com/u/31348972?v=4&s=117"></a></td><td><a href=https://github.com/allenfantasy><img width="117" alt="allenfantasy" src="https://avatars.githubusercontent.com/u/1009294?v=4&s=117"></a></td><td><a href=https://github.com/Zyclotrop-j><img width="117" alt="Zyclotrop-j" src="https://avatars.githubusercontent.com/u/4939546?v=4&s=117"></a></td><td><a href=https://github.com/anark><img width="117" alt="anark" src="https://avatars.githubusercontent.com/u/101184?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/bdirito><img width="117" alt="bdirito" src="https://avatars.githubusercontent.com/u/8117238?v=4&s=117"></a></td><td><a href=https://github.com/darthf1><img width="117" alt="darthf1" src="https://avatars.githubusercontent.com/u/17253332?v=4&s=117"></a></td><td><a href=https://github.com/fortrieb><img width="117" alt="fortrieb" src="https://avatars.githubusercontent.com/u/4126707?v=4&s=117"></a></td><td><a href=https://github.com/frederikhors><img width="117" alt="frederikhors" src="https://avatars.githubusercontent.com/u/41120635?v=4&s=117"></a></td><td><a href=https://github.com/heocoi><img width="117" alt="heocoi" src="https://avatars.githubusercontent.com/u/13751011?v=4&s=117"></a></td><td><a href=https://github.com/jarey><img width="117" alt="jarey" src="https://avatars.githubusercontent.com/u/5025224?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/muhammadInam><img width="117" alt="muhammadInam" src="https://avatars.githubusercontent.com/u/7801708?v=4&s=117"></a></td><td><a href=https://github.com/rettgerst><img width="117" alt="rettgerst" src="https://avatars.githubusercontent.com/u/11684948?v=4&s=117"></a></td><td><a href=https://github.com/jukakoski><img width="117" alt="jukakoski" src="https://avatars.githubusercontent.com/u/52720967?v=4&s=117"></a></td><td><a href=https://github.com/olemoign><img width="117" alt="olemoign" src="https://avatars.githubusercontent.com/u/11632871?v=4&s=117"></a></td><td><a href=https://github.com/5idereal><img width="117" alt="5idereal" src="https://avatars.githubusercontent.com/u/30827929?v=4&s=117"></a></td><td><a href=https://github.com/btrice><img width="117" alt="btrice" src="https://avatars.githubusercontent.com/u/4358225?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/AndrwM><img width="117" alt="AndrwM" src="https://avatars.githubusercontent.com/u/565743?v=4&s=117"></a></td><td><a href=https://github.com/behnammodi><img width="117" alt="behnammodi" src="https://avatars.githubusercontent.com/u/1549069?v=4&s=117"></a></td><td><a href=https://github.com/BePo65><img width="117" alt="BePo65" src="https://avatars.githubusercontent.com/u/6582465?v=4&s=117"></a></td><td><a href=https://github.com/bradedelman><img width="117" alt="bradedelman" src="https://avatars.githubusercontent.com/u/124367?v=4&s=117"></a></td><td><a href=https://github.com/camiloforero><img width="117" alt="camiloforero" src="https://avatars.githubusercontent.com/u/6606686?v=4&s=117"></a></td><td><a href=https://github.com/command-tab><img width="117" alt="command-tab" src="https://avatars.githubusercontent.com/u/3069?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/craig-jennings><img width="117" alt="craig-jennings" src="https://avatars.githubusercontent.com/u/1683368?v=4&s=117"></a></td><td><a href=https://github.com/davekiss><img width="117" alt="davekiss" src="https://avatars.githubusercontent.com/u/1256071?v=4&s=117"></a></td><td><a href=https://github.com/denysdesign><img width="117" alt="denysdesign" src="https://avatars.githubusercontent.com/u/1041797?v=4&s=117"></a></td><td><a href=https://github.com/ethanwillis><img width="117" alt="ethanwillis" src="https://avatars.githubusercontent.com/u/182492?v=4&s=117"></a></td><td><a href=https://github.com/frobinsonj><img width="117" alt="frobinsonj" src="https://avatars.githubusercontent.com/u/16726902?v=4&s=117"></a></td><td><a href=https://github.com/richmeij><img width="117" alt="richmeij" src="https://avatars.githubusercontent.com/u/9741858?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/richartkeil><img width="117" alt="richartkeil" src="https://avatars.githubusercontent.com/u/8680858?v=4&s=117"></a></td><td><a href=https://github.com/paescuj><img width="117" alt="paescuj" src="https://avatars.githubusercontent.com/u/5363448?v=4&s=117"></a></td><td><a href=https://github.com/msand><img width="117" alt="msand" src="https://avatars.githubusercontent.com/u/1131362?v=4&s=117"></a></td><td><a href=https://github.com/martiuslim><img width="117" alt="martiuslim" src="https://avatars.githubusercontent.com/u/17944339?v=4&s=117"></a></td><td><a href=https://github.com/Martin005><img width="117" alt="Martin005" src="https://avatars.githubusercontent.com/u/10096404?v=4&s=117"></a></td><td><a href=https://github.com/mskelton><img width="117" alt="mskelton" src="https://avatars.githubusercontent.com/u/25914066?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/mactavishz><img width="117" alt="mactavishz" src="https://avatars.githubusercontent.com/u/12948083?v=4&s=117"></a></td><td><a href=https://github.com/lafe><img width="117" alt="lafe" src="https://avatars.githubusercontent.com/u/4070008?v=4&s=117"></a></td><td><a href=https://github.com/dogrocker><img width="117" alt="dogrocker" src="https://avatars.githubusercontent.com/u/8379027?v=4&s=117"></a></td><td><a href=https://github.com/jedwood><img width="117" alt="jedwood" src="https://avatars.githubusercontent.com/u/369060?v=4&s=117"></a></td><td><a href=https://github.com/jasonbosco><img width="117" alt="jasonbosco" src="https://avatars.githubusercontent.com/u/458383?v=4&s=117"></a></td><td><a href=https://github.com/ghasrfakhri><img width="117" alt="ghasrfakhri" src="https://avatars.githubusercontent.com/u/4945963?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/geertclerx><img width="117" alt="geertclerx" src="https://avatars.githubusercontent.com/u/1381327?v=4&s=117"></a></td><td><a href=https://github.com/JimmyLv><img width="117" alt="JimmyLv" src="https://avatars.githubusercontent.com/u/4997466?v=4&s=117"></a></td><td><a href=https://github.com/rart><img width="117" alt="rart" src="https://avatars.githubusercontent.com/u/3928341?v=4&s=117"></a></td><td><a href=https://github.com/rossng><img width="117" alt="rossng" src="https://avatars.githubusercontent.com/u/565371?v=4&s=117"></a></td><td><a href=https://github.com/scherroman><img width="117" alt="scherroman" src="https://avatars.githubusercontent.com/u/7923938?v=4&s=117"></a></td><td><a href=https://github.com/robwilson1><img width="117" alt="robwilson1" src="https://avatars.githubusercontent.com/u/7114944?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/SxDx><img width="117" alt="SxDx" src="https://avatars.githubusercontent.com/u/2004247?v=4&s=117"></a></td><td><a href=https://github.com/refo><img width="117" alt="refo" src="https://avatars.githubusercontent.com/u/1114116?v=4&s=117"></a></td><td><a href=https://github.com/raulibanez><img width="117" alt="raulibanez" src="https://avatars.githubusercontent.com/u/1070825?v=4&s=117"></a></td><td><a href=https://github.com/luarmr><img width="117" alt="luarmr" src="https://avatars.githubusercontent.com/u/817416?v=4&s=117"></a></td><td><a href=https://github.com/eman8519><img width="117" alt="eman8519" src="https://avatars.githubusercontent.com/u/2380804?v=4&s=117"></a></td><td><a href=https://github.com/pedantic-git><img width="117" alt="pedantic-git" src="https://avatars.githubusercontent.com/u/962930?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/Pzoco><img width="117" alt="Pzoco" src="https://avatars.githubusercontent.com/u/3101348?v=4&s=117"></a></td><td><a href=https://github.com/ppadmavilasom><img width="117" alt="ppadmavilasom" src="https://avatars.githubusercontent.com/u/11167452?v=4&s=117"></a></td><td><a href=https://github.com/phillipalexander><img width="117" alt="phillipalexander" src="https://avatars.githubusercontent.com/u/1577682?v=4&s=117"></a></td><td><a href=https://github.com/pmusaraj><img width="117" alt="pmusaraj" src="https://avatars.githubusercontent.com/u/368961?v=4&s=117"></a></td><td><a href=https://github.com/pedrofs><img width="117" alt="pedrofs" src="https://avatars.githubusercontent.com/u/56484?v=4&s=117"></a></td><td><a href=https://github.com/plneto><img width="117" alt="plneto" src="https://avatars.githubusercontent.com/u/5697434?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/patricklindsay><img width="117" alt="patricklindsay" src="https://avatars.githubusercontent.com/u/7923681?v=4&s=117"></a></td><td><a href=https://github.com/tcgj><img width="117" alt="tcgj" src="https://avatars.githubusercontent.com/u/7994529?v=4&s=117"></a></td><td><a href=https://github.com/Tashows><img width="117" alt="Tashows" src="https://avatars.githubusercontent.com/u/16656928?v=4&s=117"></a></td><td><a href=https://github.com/taj><img width="117" alt="taj" src="https://avatars.githubusercontent.com/u/16062635?v=4&s=117"></a></td><td><a href=https://github.com/strayer><img width="117" alt="strayer" src="https://avatars.githubusercontent.com/u/310624?v=4&s=117"></a></td><td><a href=https://github.com/sjauld><img width="117" alt="sjauld" src="https://avatars.githubusercontent.com/u/8232503?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/steverob><img width="117" alt="steverob" src="https://avatars.githubusercontent.com/u/1220480?v=4&s=117"></a></td><td><a href=https://github.com/amaitu><img width="117" alt="amaitu" src="https://avatars.githubusercontent.com/u/15688439?v=4&s=117"></a></td><td><a href=https://github.com/quigebo><img width="117" alt="quigebo" src="https://avatars.githubusercontent.com/u/741?v=4&s=117"></a></td><td><a href=https://github.com/waptik><img width="117" alt="waptik" src="https://avatars.githubusercontent.com/u/1687551?v=4&s=117"></a></td><td><a href=https://github.com/SpazzMarticus><img width="117" alt="SpazzMarticus" src="https://avatars.githubusercontent.com/u/5716457?v=4&s=117"></a></td><td><a href=https://github.com/szh><img width="117" alt="szh" src="https://avatars.githubusercontent.com/u/546965?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/sergei-zelinsky><img width="117" alt="sergei-zelinsky" src="https://avatars.githubusercontent.com/u/19428086?v=4&s=117"></a></td><td><a href=https://github.com/sebasegovia01><img width="117" alt="sebasegovia01" src="https://avatars.githubusercontent.com/u/35777287?v=4&s=117"></a></td><td><a href=https://github.com/sdebacker><img width="117" alt="sdebacker" src="https://avatars.githubusercontent.com/u/134503?v=4&s=117"></a></td><td><a href=https://github.com/Rattone><img width="117" alt="Rattone" src="https://avatars.githubusercontent.com/u/7362607?v=4&s=117"></a></td><td><a href=https://github.com/samuelcolburn><img width="117" alt="samuelcolburn" src="https://avatars.githubusercontent.com/u/9741902?v=4&s=117"></a></td><td><a href=https://github.com/fortunto2><img width="117" alt="fortunto2" src="https://avatars.githubusercontent.com/u/1236751?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/GNURub><img width="117" alt="GNURub" src="https://avatars.githubusercontent.com/u/1318648?v=4&s=117"></a></td><td><a href=https://github.com/Mitchell8210><img width="117" alt="Mitchell8210" src="https://avatars.githubusercontent.com/u/23045264?v=4&s=117"></a></td><td><a href=https://github.com/achmiral><img width="117" alt="achmiral" src="https://avatars.githubusercontent.com/u/10906059?v=4&s=117"></a></td><td><a href=https://github.com/ken-kuro><img width="117" alt="ken-kuro" src="https://avatars.githubusercontent.com/u/47441476?v=4&s=117"></a></td><td><a href=https://github.com/milannakum><img width="117" alt="milannakum" src="https://avatars.githubusercontent.com/u/11374368?v=4&s=117"></a></td><td><a href=https://github.com/mkopinsky><img width="117" alt="mkopinsky" src="https://avatars.githubusercontent.com/u/591435?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/mhulet><img width="117" alt="mhulet" src="https://avatars.githubusercontent.com/u/293355?v=4&s=117"></a></td><td><a href=https://github.com/hrsh><img width="117" alt="hrsh" src="https://avatars.githubusercontent.com/u/1929359?v=4&s=117"></a></td><td><a href=https://github.com/mauricioribeiro><img width="117" alt="mauricioribeiro" src="https://avatars.githubusercontent.com/u/2589856?v=4&s=117"></a></td><td><a href=https://github.com/matthewhartstonge><img width="117" alt="matthewhartstonge" src="https://avatars.githubusercontent.com/u/6119549?v=4&s=117"></a></td><td><a href=https://github.com/mjesuele><img width="117" alt="mjesuele" src="https://avatars.githubusercontent.com/u/871117?v=4&s=117"></a></td><td><a href=https://github.com/mattfik><img width="117" alt="mattfik" src="https://avatars.githubusercontent.com/u/1638028?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/mateuscruz><img width="117" alt="mateuscruz" src="https://avatars.githubusercontent.com/u/8962842?v=4&s=117"></a></td><td><a href=https://github.com/masum-ulu><img width="117" alt="masum-ulu" src="https://avatars.githubusercontent.com/u/49063256?v=4&s=117"></a></td><td><a href=https://github.com/masaok><img width="117" alt="masaok" src="https://avatars.githubusercontent.com/u/1320083?v=4&s=117"></a></td><td><a href=https://github.com/martin-brennan><img width="117" alt="martin-brennan" src="https://avatars.githubusercontent.com/u/920448?v=4&s=117"></a></td><td><a href=https://github.com/marcusforsberg><img width="117" alt="marcusforsberg" src="https://avatars.githubusercontent.com/u/1009069?v=4&s=117"></a></td><td><a href=https://github.com/marcosthejew><img width="117" alt="marcosthejew" src="https://avatars.githubusercontent.com/u/1500967?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/mperrando><img width="117" alt="mperrando" src="https://avatars.githubusercontent.com/u/525572?v=4&s=117"></a></td><td><a href=https://github.com/pascalwengerter><img width="117" alt="pascalwengerter" src="https://avatars.githubusercontent.com/u/16822008?v=4&s=117"></a></td><td><a href=https://github.com/ParsaArvanehPA><img width="117" alt="ParsaArvanehPA" src="https://avatars.githubusercontent.com/u/62149413?v=4&s=117"></a></td><td><a href=https://github.com/cryptic022><img width="117" alt="cryptic022" src="https://avatars.githubusercontent.com/u/18145703?v=4&s=117"></a></td><td><a href=https://github.com/Ozodbek1405><img width="117" alt="Ozodbek1405" src="https://avatars.githubusercontent.com/u/86141593?v=4&s=117"></a></td><td><a href=https://github.com/leftdevel><img width="117" alt="leftdevel" src="https://avatars.githubusercontent.com/u/843337?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/nil1511><img width="117" alt="nil1511" src="https://avatars.githubusercontent.com/u/2058170?v=4&s=117"></a></td><td><a href=https://github.com/coreprocess><img width="117" alt="coreprocess" src="https://avatars.githubusercontent.com/u/1226918?v=4&s=117"></a></td><td><a href=https://github.com/nicojones><img width="117" alt="nicojones" src="https://avatars.githubusercontent.com/u/6078915?v=4&s=117"></a></td><td><a href=https://github.com/trungcva10a6tn><img width="117" alt="trungcva10a6tn" src="https://avatars.githubusercontent.com/u/18293783?v=4&s=117"></a></td><td><a href=https://github.com/naveed-ahmad><img width="117" alt="naveed-ahmad" src="https://avatars.githubusercontent.com/u/701567?v=4&s=117"></a></td><td><a href=https://github.com/nadeemc><img width="117" alt="nadeemc" src="https://avatars.githubusercontent.com/u/3429228?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/pleasespammelater><img width="117" alt="pleasespammelater" src="https://avatars.githubusercontent.com/u/11870394?v=4&s=117"></a></td><td><a href=https://github.com/marton-laszlo-attila><img width="117" alt="marton-laszlo-attila" src="https://avatars.githubusercontent.com/u/73295321?v=4&s=117"></a></td><td><a href=https://github.com/navruzm><img width="117" alt="navruzm" src="https://avatars.githubusercontent.com/u/168341?v=4&s=117"></a></td><td><a href=https://github.com/mogzol><img width="117" alt="mogzol" src="https://avatars.githubusercontent.com/u/11789801?v=4&s=117"></a></td><td><a href=https://github.com/shahimclt><img width="117" alt="shahimclt" src="https://avatars.githubusercontent.com/u/8318002?v=4&s=117"></a></td><td><a href=https://github.com/mnafees><img width="117" alt="mnafees" src="https://avatars.githubusercontent.com/u/1763885?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/boudra><img width="117" alt="boudra" src="https://avatars.githubusercontent.com/u/711886?v=4&s=117"></a></td><td><a href=https://github.com/netdown><img width="117" alt="netdown" src="https://avatars.githubusercontent.com/u/4265403?v=4&s=117"></a></td><td><a href=https://github.com/mosi-kha><img width="117" alt="mosi-kha" src="https://avatars.githubusercontent.com/u/35611016?v=4&s=117"></a></td><td><a href=https://github.com/maddy-jo><img width="117" alt="maddy-jo" src="https://avatars.githubusercontent.com/u/3241493?v=4&s=117"></a></td><td><a href=https://github.com/mdxiaohu><img width="117" alt="mdxiaohu" src="https://avatars.githubusercontent.com/u/42248614?v=4&s=117"></a></td><td><a href=https://github.com/magumbo><img width="117" alt="magumbo" src="https://avatars.githubusercontent.com/u/6683765?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/jx-zyf><img width="117" alt="jx-zyf" src="https://avatars.githubusercontent.com/u/26456842?v=4&s=117"></a></td><td><a href=https://github.com/kode-ninja><img width="117" alt="kode-ninja" src="https://avatars.githubusercontent.com/u/7857611?v=4&s=117"></a></td><td><a href=https://github.com/sontixyou><img width="117" alt="sontixyou" src="https://avatars.githubusercontent.com/u/19817196?v=4&s=117"></a></td><td><a href=https://github.com/jur-ng><img width="117" alt="jur-ng" src="https://avatars.githubusercontent.com/u/111122756?v=4&s=117"></a></td><td><a href=https://github.com/johnmanjiro13><img width="117" alt="johnmanjiro13" src="https://avatars.githubusercontent.com/u/28798279?v=4&s=117"></a></td><td><a href=https://github.com/jyoungblood><img width="117" alt="jyoungblood" src="https://avatars.githubusercontent.com/u/56104?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/green-mike><img width="117" alt="green-mike" src="https://avatars.githubusercontent.com/u/5584225?v=4&s=117"></a></td><td><a href=https://github.com/gaelicwinter><img width="117" alt="gaelicwinter" src="https://avatars.githubusercontent.com/u/6510266?v=4&s=117"></a></td><td><a href=https://github.com/franckl><img width="117" alt="franckl" src="https://avatars.githubusercontent.com/u/3875803?v=4&s=117"></a></td><td><a href=https://github.com/fingul><img width="117" alt="fingul" src="https://avatars.githubusercontent.com/u/894739?v=4&s=117"></a></td><td><a href=https://github.com/elliotsayes><img width="117" alt="elliotsayes" src="https://avatars.githubusercontent.com/u/7699058?v=4&s=117"></a></td><td><a href=https://github.com/dzcpy><img width="117" alt="dzcpy" src="https://avatars.githubusercontent.com/u/203980?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/dkisic><img width="117" alt="dkisic" src="https://avatars.githubusercontent.com/u/32257921?v=4&s=117"></a></td><td><a href=https://github.com/zanzlender><img width="117" alt="zanzlender" src="https://avatars.githubusercontent.com/u/44570474?v=4&s=117"></a></td><td><a href=https://github.com/olitomas><img width="117" alt="olitomas" src="https://avatars.githubusercontent.com/u/6918659?v=4&s=117"></a></td><td><a href=https://github.com/yoann-hellopret><img width="117" alt="yoann-hellopret" src="https://avatars.githubusercontent.com/u/46525558?v=4&s=117"></a></td><td><a href=https://github.com/vedran555><img width="117" alt="vedran555" src="https://avatars.githubusercontent.com/u/38395951?v=4&s=117"></a></td><td><a href=https://github.com/tusharjkhunt><img width="117" alt="tusharjkhunt" src="https://avatars.githubusercontent.com/u/31904234?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/thanhthot><img width="117" alt="thanhthot" src="https://avatars.githubusercontent.com/u/50633205?v=4&s=117"></a></td><td><a href=https://github.com/stduhpf><img width="117" alt="stduhpf" src="https://avatars.githubusercontent.com/u/28208228?v=4&s=117"></a></td><td><a href=https://github.com/slawexxx44><img width="117" alt="slawexxx44" src="https://avatars.githubusercontent.com/u/11180644?v=4&s=117"></a></td><td><a href=https://github.com/rtaieb><img width="117" alt="rtaieb" src="https://avatars.githubusercontent.com/u/35224301?v=4&s=117"></a></td><td><a href=https://github.com/rmoura-92><img width="117" alt="rmoura-92" src="https://avatars.githubusercontent.com/u/419044?v=4&s=117"></a></td><td><a href=https://github.com/rlebosse><img width="117" alt="rlebosse" src="https://avatars.githubusercontent.com/u/2794137?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/rhymes><img width="117" alt="rhymes" src="https://avatars.githubusercontent.com/u/146201?v=4&s=117"></a></td><td><a href=https://github.com/luntta><img width="117" alt="luntta" src="https://avatars.githubusercontent.com/u/14221637?v=4&s=117"></a></td><td><a href=https://github.com/phil714><img width="117" alt="phil714" src="https://avatars.githubusercontent.com/u/7584581?v=4&s=117"></a></td><td><a href=https://github.com/ordago><img width="117" alt="ordago" src="https://avatars.githubusercontent.com/u/6376814?v=4&s=117"></a></td><td><a href=https://github.com/odselsevier><img width="117" alt="odselsevier" src="https://avatars.githubusercontent.com/u/95745934?v=4&s=117"></a></td><td><a href=https://github.com/ninesalt><img width="117" alt="ninesalt" src="https://avatars.githubusercontent.com/u/7952255?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/neuronet77><img width="117" alt="neuronet77" src="https://avatars.githubusercontent.com/u/4220037?v=4&s=117"></a></td><td><a href=https://github.com/craigcbrunner><img width="117" alt="craigcbrunner" src="https://avatars.githubusercontent.com/u/2780521?v=4&s=117"></a></td><td><a href=https://github.com/weston-sankey-mark43><img width="117" alt="weston-sankey-mark43" src="https://avatars.githubusercontent.com/u/97678695?v=4&s=117"></a></td><td><a href=https://github.com/dwnste><img width="117" alt="dwnste" src="https://avatars.githubusercontent.com/u/17119722?v=4&s=117"></a></td><td><a href=https://github.com/nagyv><img width="117" alt="nagyv" src="https://avatars.githubusercontent.com/u/126671?v=4&s=117"></a></td><td><a href=https://github.com/stiig><img width="117" alt="stiig" src="https://avatars.githubusercontent.com/u/8639922?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/valentinoli><img width="117" alt="valentinoli" src="https://avatars.githubusercontent.com/u/23453691?v=4&s=117"></a></td><td><a href=https://github.com/vially><img width="117" alt="vially" src="https://avatars.githubusercontent.com/u/433598?v=4&s=117"></a></td><td><a href=https://github.com/bodryi><img width="117" alt="bodryi" src="https://avatars.githubusercontent.com/u/7326310?v=4&s=117"></a></td><td><a href=https://github.com/tyler-dot-earth><img width="117" alt="tyler-dot-earth" src="https://avatars.githubusercontent.com/u/595711?v=4&s=117"></a></td><td><a href=https://github.com/trivikr><img width="117" alt="trivikr" src="https://avatars.githubusercontent.com/u/16024985?v=4&s=117"></a></td><td><a href=https://github.com/tanadeau><img width="117" alt="tanadeau" src="https://avatars.githubusercontent.com/u/3734457?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/top-master><img width="117" alt="top-master" src="https://avatars.githubusercontent.com/u/31405473?v=4&s=117"></a></td><td><a href=https://github.com/tvaliasek><img width="117" alt="tvaliasek" src="https://avatars.githubusercontent.com/u/8644946?v=4&s=117"></a></td><td><a href=https://github.com/tomekp><img width="117" alt="tomekp" src="https://avatars.githubusercontent.com/u/1856393?v=4&s=117"></a></td><td><a href=https://github.com/tomsaleeba><img width="117" alt="tomsaleeba" src="https://avatars.githubusercontent.com/u/1773838?v=4&s=117"></a></td><td><a href=https://github.com/WIStudent><img width="117" alt="WIStudent" src="https://avatars.githubusercontent.com/u/2707930?v=4&s=117"></a></td><td><a href=https://github.com/tmaier><img width="117" alt="tmaier" src="https://avatars.githubusercontent.com/u/350038?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/Tiarhai><img width="117" alt="Tiarhai" src="https://avatars.githubusercontent.com/u/12871513?v=4&s=117"></a></td><td><a href=https://github.com/twarlop><img width="117" alt="twarlop" src="https://avatars.githubusercontent.com/u/2856082?v=4&s=117"></a></td><td><a href=https://github.com/codehero7386><img width="117" alt="codehero7386" src="https://avatars.githubusercontent.com/u/56253286?v=4&s=117"></a></td><td><a href=https://github.com/christianwengert><img width="117" alt="christianwengert" src="https://avatars.githubusercontent.com/u/12936057?v=4&s=117"></a></td><td><a href=https://github.com/cgoinglove><img width="117" alt="cgoinglove" src="https://avatars.githubusercontent.com/u/86150470?v=4&s=117"></a></td><td><a href=https://github.com/canvasbh><img width="117" alt="canvasbh" src="https://avatars.githubusercontent.com/u/44477734?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/c0b41><img width="117" alt="c0b41" src="https://avatars.githubusercontent.com/u/2834954?v=4&s=117"></a></td><td><a href=https://github.com/avalla><img width="117" alt="avalla" src="https://avatars.githubusercontent.com/u/986614?v=4&s=117"></a></td><td><a href=https://github.com/arggh><img width="117" alt="arggh" src="https://avatars.githubusercontent.com/u/17210302?v=4&s=117"></a></td><td><a href=https://github.com/alfatv><img width="117" alt="alfatv" src="https://avatars.githubusercontent.com/u/62238673?v=4&s=117"></a></td><td><a href=https://github.com/agreene-coursera><img width="117" alt="agreene-coursera" src="https://avatars.githubusercontent.com/u/30501355?v=4&s=117"></a></td><td><a href=https://github.com/aduh95-test-account><img width="117" alt="aduh95-test-account" src="https://avatars.githubusercontent.com/u/93441190?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/sartoshi-foot-dao><img width="117" alt="sartoshi-foot-dao" src="https://avatars.githubusercontent.com/u/99770068?v=4&s=117"></a></td><td><a href=https://github.com/zackbloom><img width="117" alt="zackbloom" src="https://avatars.githubusercontent.com/u/55347?v=4&s=117"></a></td><td><a href=https://github.com/zlawson-ut><img width="117" alt="zlawson-ut" src="https://avatars.githubusercontent.com/u/7375444?v=4&s=117"></a></td><td><a href=https://github.com/zachconner><img width="117" alt="zachconner" src="https://avatars.githubusercontent.com/u/11339326?v=4&s=117"></a></td><td><a href=https://github.com/yafkari><img width="117" alt="yafkari" src="https://avatars.githubusercontent.com/u/41365655?v=4&s=117"></a></td><td><a href=https://github.com/YehudaKremer><img width="117" alt="YehudaKremer" src="https://avatars.githubusercontent.com/u/946652?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/xhocquet><img width="117" alt="xhocquet" src="https://avatars.githubusercontent.com/u/8116516?v=4&s=117"></a></td><td><a href=https://github.com/willycamargo><img width="117" alt="willycamargo" src="https://avatars.githubusercontent.com/u/5041887?v=4&s=117"></a></td><td><a href=https://github.com/onhate><img width="117" alt="onhate" src="https://avatars.githubusercontent.com/u/980905?v=4&s=117"></a></td><td><a href=https://github.com/ardeois><img width="117" alt="ardeois" src="https://avatars.githubusercontent.com/u/1867939?v=4&s=117"></a></td><td><a href=https://github.com/CommanderRoot><img width="117" alt="CommanderRoot" src="https://avatars.githubusercontent.com/u/4395417?v=4&s=117"></a></td><td><a href=https://github.com/czj><img width="117" alt="czj" src="https://avatars.githubusercontent.com/u/14306?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/cbush06><img width="117" alt="cbush06" src="https://avatars.githubusercontent.com/u/15720146?v=4&s=117"></a></td><td><a href=https://github.com/Aarbel><img width="117" alt="Aarbel" src="https://avatars.githubusercontent.com/u/25119847?v=4&s=117"></a></td><td><a href=https://github.com/cfra><img width="117" alt="cfra" src="https://avatars.githubusercontent.com/u/1347051?v=4&s=117"></a></td><td><a href=https://github.com/csprance><img width="117" alt="csprance" src="https://avatars.githubusercontent.com/u/7902617?v=4&s=117"></a></td><td><a href=https://github.com/prattcmp><img width="117" alt="prattcmp" src="https://avatars.githubusercontent.com/u/1497950?v=4&s=117"></a></td><td><a href=https://github.com/subvertallchris><img width="117" alt="subvertallchris" src="https://avatars.githubusercontent.com/u/4097271?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/charlybillaud><img width="117" alt="charlybillaud" src="https://avatars.githubusercontent.com/u/31970410?v=4&s=117"></a></td><td><a href=https://github.com/Cretezy><img width="117" alt="Cretezy" src="https://avatars.githubusercontent.com/u/2672503?v=4&s=117"></a></td><td><a href=https://github.com/chao><img width="117" alt="chao" src="https://avatars.githubusercontent.com/u/55872?v=4&s=117"></a></td><td><a href=https://github.com/cellvinchung><img width="117" alt="cellvinchung" src="https://avatars.githubusercontent.com/u/5347394?v=4&s=117"></a></td><td><a href=https://github.com/cartfisk><img width="117" alt="cartfisk" src="https://avatars.githubusercontent.com/u/8764375?v=4&s=117"></a></td><td><a href=https://github.com/cyu><img width="117" alt="cyu" src="https://avatars.githubusercontent.com/u/2431?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/bryanjswift><img width="117" alt="bryanjswift" src="https://avatars.githubusercontent.com/u/9911?v=4&s=117"></a></td><td><a href=https://github.com/bedgerotto><img width="117" alt="bedgerotto" src="https://avatars.githubusercontent.com/u/4459657?v=4&s=117"></a></td><td><a href=https://github.com/wbaaron><img width="117" alt="wbaaron" src="https://avatars.githubusercontent.com/u/1048988?v=4&s=117"></a></td><td><a href=https://github.com/yoldar><img width="117" alt="yoldar" src="https://avatars.githubusercontent.com/u/1597578?v=4&s=117"></a></td><td><a href=https://github.com/efbautista><img width="117" alt="efbautista" src="https://avatars.githubusercontent.com/u/35430671?v=4&s=117"></a></td><td><a href=https://github.com/emuell><img width="117" alt="emuell" src="https://avatars.githubusercontent.com/u/11521600?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/EdgarSantiago93><img width="117" alt="EdgarSantiago93" src="https://avatars.githubusercontent.com/u/14806877?v=4&s=117"></a></td><td><a href=https://github.com/sweetro><img width="117" alt="sweetro" src="https://avatars.githubusercontent.com/u/6228717?v=4&s=117"></a></td><td><a href=https://github.com/jeetiss><img width="117" alt="jeetiss" src="https://avatars.githubusercontent.com/u/6726016?v=4&s=117"></a></td><td><a href=https://github.com/DennisKofflard><img width="117" alt="DennisKofflard" src="https://avatars.githubusercontent.com/u/8669129?v=4&s=117"></a></td><td><a href=https://github.com/hoangsvit><img width="117" alt="hoangsvit" src="https://avatars.githubusercontent.com/u/11882322?v=4&s=117"></a></td><td><a href=https://github.com/davilima6><img width="117" alt="davilima6" src="https://avatars.githubusercontent.com/u/422130?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/akizor><img width="117" alt="akizor" src="https://avatars.githubusercontent.com/u/1052439?v=4&s=117"></a></td><td><a href=https://github.com/KaminskiDaniell><img width="117" alt="KaminskiDaniell" src="https://avatars.githubusercontent.com/u/27357868?v=4&s=117"></a></td><td><a href=https://github.com/Cantabar><img width="117" alt="Cantabar" src="https://avatars.githubusercontent.com/u/6812207?v=4&s=117"></a></td><td><a href=https://github.com/mrboomer><img width="117" alt="mrboomer" src="https://avatars.githubusercontent.com/u/5942912?v=4&s=117"></a></td><td><a href=https://github.com/danilat><img width="117" alt="danilat" src="https://avatars.githubusercontent.com/u/22763?v=4&s=117"></a></td><td><a href=https://github.com/danschalow><img width="117" alt="danschalow" src="https://avatars.githubusercontent.com/u/3527437?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/danmichaelo><img width="117" alt="danmichaelo" src="https://avatars.githubusercontent.com/u/434495?v=4&s=117"></a></td><td><a href=https://github.com/Cruaier><img width="117" alt="Cruaier" src="https://avatars.githubusercontent.com/u/5204940?v=4&s=117"></a></td><td><a href=https://github.com/sercraig><img width="117" alt="sercraig" src="https://avatars.githubusercontent.com/u/24261518?v=4&s=117"></a></td><td><a href=https://github.com/Quorafind><img width="117" alt="Quorafind" src="https://avatars.githubusercontent.com/u/13215013?v=4&s=117"></a></td><td><a href=https://github.com/amitport><img width="117" alt="amitport" src="https://avatars.githubusercontent.com/u/1131991?v=4&s=117"></a></td><td><a href=https://github.com/tekacs><img width="117" alt="tekacs" src="https://avatars.githubusercontent.com/u/63247?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/Dogfalo><img width="117" alt="Dogfalo" src="https://avatars.githubusercontent.com/u/2775751?v=4&s=117"></a></td><td><a href=https://github.com/alirezahi><img width="117" alt="alirezahi" src="https://avatars.githubusercontent.com/u/16666064?v=4&s=117"></a></td><td><a href=https://github.com/aalepis><img width="117" alt="aalepis" src="https://avatars.githubusercontent.com/u/35684834?v=4&s=117"></a></td><td><a href=https://github.com/alexnj><img width="117" alt="alexnj" src="https://avatars.githubusercontent.com/u/683500?v=4&s=117"></a></td><td><a href=https://github.com/asmt3><img width="117" alt="asmt3" src="https://avatars.githubusercontent.com/u/1777709?v=4&s=117"></a></td><td><a href=https://github.com/ahmadissa><img width="117" alt="ahmadissa" src="https://avatars.githubusercontent.com/u/9936573?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/adritasharma><img width="117" alt="adritasharma" src="https://avatars.githubusercontent.com/u/29271635?v=4&s=117"></a></td><td><a href=https://github.com/Adrrei><img width="117" alt="Adrrei" src="https://avatars.githubusercontent.com/u/22191685?v=4&s=117"></a></td><td><a href=https://github.com/adityapatadia><img width="117" alt="adityapatadia" src="https://avatars.githubusercontent.com/u/1086617?v=4&s=117"></a></td><td><a href=https://github.com/adamvigneault><img width="117" alt="adamvigneault" src="https://avatars.githubusercontent.com/u/18236120?v=4&s=117"></a></td><td><a href=https://github.com/ajh-sr><img width="117" alt="ajh-sr" src="https://avatars.githubusercontent.com/u/71472057?v=4&s=117"></a></td><td><a href=https://github.com/adamdottv><img width="117" alt="adamdottv" src="https://avatars.githubusercontent.com/u/2363879?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/abannach><img width="117" alt="abannach" src="https://avatars.githubusercontent.com/u/43150303?v=4&s=117"></a></td><td><a href=https://github.com/aaron-russell><img width="117" alt="aaron-russell" src="https://avatars.githubusercontent.com/u/4678453?v=4&s=117"></a></td><td><a href=https://github.com/superhawk610><img width="117" alt="superhawk610" src="https://avatars.githubusercontent.com/u/18172185?v=4&s=117"></a></td><td><a href=https://github.com/ajschmidt8><img width="117" alt="ajschmidt8" src="https://avatars.githubusercontent.com/u/7400326?v=4&s=117"></a></td><td><a href=https://github.com/bducharme><img width="117" alt="bducharme" src="https://avatars.githubusercontent.com/u/4173569?v=4&s=117"></a></td><td><a href=https://github.com/azizk><img width="117" alt="azizk" src="https://avatars.githubusercontent.com/u/37282?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/azeemba><img width="117" alt="azeemba" src="https://avatars.githubusercontent.com/u/2160795?v=4&s=117"></a></td><td><a href=https://github.com/ayhankesicioglu><img width="117" alt="ayhankesicioglu" src="https://avatars.githubusercontent.com/u/36304312?v=4&s=117"></a></td><td><a href=https://github.com/avneetmalhotra><img width="117" alt="avneetmalhotra" src="https://avatars.githubusercontent.com/u/10562207?v=4&s=117"></a></td><td><a href=https://github.com/The-Flash><img width="117" alt="The-Flash" src="https://avatars.githubusercontent.com/u/24375820?v=4&s=117"></a></td><td><a href=https://github.com/atsawin><img width="117" alt="atsawin" src="https://avatars.githubusercontent.com/u/666663?v=4&s=117"></a></td><td><a href=https://github.com/ash-jc-allen><img width="117" alt="ash-jc-allen" src="https://avatars.githubusercontent.com/u/39652331?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/apuyou><img width="117" alt="apuyou" src="https://avatars.githubusercontent.com/u/520053?v=4&s=117"></a></td><td><a href=https://github.com/arthurdenner><img width="117" alt="arthurdenner" src="https://avatars.githubusercontent.com/u/13774309?v=4&s=117"></a></td><td><a href=https://github.com/Abourass><img width="117" alt="Abourass" src="https://avatars.githubusercontent.com/u/39917231?v=4&s=117"></a></td><td><a href=https://github.com/tyndria><img width="117" alt="tyndria" src="https://avatars.githubusercontent.com/u/17138916?v=4&s=117"></a></td><td><a href=https://github.com/anthony0030><img width="117" alt="anthony0030" src="https://avatars.githubusercontent.com/u/13033263?v=4&s=117"></a></td><td><a href=https://github.com/andychongyz><img width="117" alt="andychongyz" src="https://avatars.githubusercontent.com/u/12697240?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/andrii-bodnar><img width="117" alt="andrii-bodnar" src="https://avatars.githubusercontent.com/u/29282228?v=4&s=117"></a></td><td><a href=https://github.com/superandrew213><img width="117" alt="superandrew213" src="https://avatars.githubusercontent.com/u/13059204?v=4&s=117"></a></td><td><a href=https://github.com/radarhere><img width="117" alt="radarhere" src="https://avatars.githubusercontent.com/u/3112309?v=4&s=117"></a></td><td><a href=https://github.com/functino><img width="117" alt="functino" src="https://avatars.githubusercontent.com/u/415498?v=4&s=117"></a></td><td><a href=https://github.com/kevin-west-10x><img width="117" alt="kevin-west-10x" src="https://avatars.githubusercontent.com/u/65194914?v=4&s=117"></a></td><td><a href=https://github.com/kergekacsa><img width="117" alt="kergekacsa" src="https://avatars.githubusercontent.com/u/16637320?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/firesharkstudios><img width="117" alt="firesharkstudios" src="https://avatars.githubusercontent.com/u/17069637?v=4&s=117"></a></td><td><a href=https://github.com/kaspermeinema><img width="117" alt="kaspermeinema" src="https://avatars.githubusercontent.com/u/73821331?v=4&s=117"></a></td><td><a href=https://github.com/tykarol><img width="117" alt="tykarol" src="https://avatars.githubusercontent.com/u/9386320?v=4&s=117"></a></td><td><a href=https://github.com/jvelten><img width="117" alt="jvelten" src="https://avatars.githubusercontent.com/u/48118068?v=4&s=117"></a></td><td><a href=https://github.com/mellow-fellow><img width="117" alt="mellow-fellow" src="https://avatars.githubusercontent.com/u/19280122?v=4&s=117"></a></td><td><a href=https://github.com/jmontoyaa><img width="117" alt="jmontoyaa" src="https://avatars.githubusercontent.com/u/158935?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/jcalonso><img width="117" alt="jcalonso" src="https://avatars.githubusercontent.com/u/664474?v=4&s=117"></a></td><td><a href=https://github.com/jbelej><img width="117" alt="jbelej" src="https://avatars.githubusercontent.com/u/2229202?v=4&s=117"></a></td><td><a href=https://github.com/jszobody><img width="117" alt="jszobody" src="https://avatars.githubusercontent.com/u/203749?v=4&s=117"></a></td><td><a href=https://github.com/jorgeepc><img width="117" alt="jorgeepc" src="https://avatars.githubusercontent.com/u/3879892?v=4&s=117"></a></td><td><a href=https://github.com/jondewoo><img width="117" alt="jondewoo" src="https://avatars.githubusercontent.com/u/1108358?v=4&s=117"></a></td><td><a href=https://github.com/jonathanarbely><img width="117" alt="jonathanarbely" src="https://avatars.githubusercontent.com/u/18177203?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/jsanchez034><img width="117" alt="jsanchez034" src="https://avatars.githubusercontent.com/u/761087?v=4&s=117"></a></td><td><a href=https://github.com/Jokcy><img width="117" alt="Jokcy" src="https://avatars.githubusercontent.com/u/2088642?v=4&s=117"></a></td><td><a href=https://github.com/chromacoma><img width="117" alt="chromacoma" src="https://avatars.githubusercontent.com/u/1535623?v=4&s=117"></a></td><td><a href=https://github.com/profsmallpine><img width="117" alt="profsmallpine" src="https://avatars.githubusercontent.com/u/7328006?v=4&s=117"></a></td><td><a href=https://github.com/marc-mabe><img width="117" alt="marc-mabe" src="https://avatars.githubusercontent.com/u/302689?v=4&s=117"></a></td><td><a href=https://github.com/Lucklj521><img width="117" alt="Lucklj521" src="https://avatars.githubusercontent.com/u/93632042?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/lucax88x><img width="117" alt="lucax88x" src="https://avatars.githubusercontent.com/u/6294464?v=4&s=117"></a></td><td><a href=https://github.com/lucaperret><img width="117" alt="lucaperret" src="https://avatars.githubusercontent.com/u/1887122?v=4&s=117"></a></td><td><a href=https://github.com/ombr><img width="117" alt="ombr" src="https://avatars.githubusercontent.com/u/857339?v=4&s=117"></a></td><td><a href=https://github.com/louim><img width="117" alt="louim" src="https://avatars.githubusercontent.com/u/923718?v=4&s=117"></a></td><td><a href=https://github.com/dolphinigle><img width="117" alt="dolphinigle" src="https://avatars.githubusercontent.com/u/7020472?v=4&s=117"></a></td><td><a href=https://github.com/leomelzer><img width="117" alt="leomelzer" src="https://avatars.githubusercontent.com/u/23313?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/leods92><img width="117" alt="leods92" src="https://avatars.githubusercontent.com/u/879395?v=4&s=117"></a></td><td><a href=https://github.com/galli-leo><img width="117" alt="galli-leo" src="https://avatars.githubusercontent.com/u/5339762?v=4&s=117"></a></td><td><a href=https://github.com/dviry><img width="117" alt="dviry" src="https://avatars.githubusercontent.com/u/1230260?v=4&s=117"></a></td><td><a href=https://github.com/larowlan><img width="117" alt="larowlan" src="https://avatars.githubusercontent.com/u/555254?v=4&s=117"></a></td><td><a href=https://github.com/leaanthony><img width="117" alt="leaanthony" src="https://avatars.githubusercontent.com/u/1943904?v=4&s=117"></a></td><td><a href=https://github.com/hoangbits><img width="117" alt="hoangbits" src="https://avatars.githubusercontent.com/u/7990827?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/labohkip81><img width="117" alt="labohkip81" src="https://avatars.githubusercontent.com/u/36964869?v=4&s=117"></a></td><td><a href=https://github.com/kyleparisi><img width="117" alt="kyleparisi" src="https://avatars.githubusercontent.com/u/1286753?v=4&s=117"></a></td><td><a href=https://github.com/elkebab><img width="117" alt="elkebab" src="https://avatars.githubusercontent.com/u/6313468?v=4&s=117"></a></td><td><a href=https://github.com/kidonng><img width="117" alt="kidonng" src="https://avatars.githubusercontent.com/u/44045911?v=4&s=117"></a></td><td><a href=https://github.com/theJoeBiz><img width="117" alt="theJoeBiz" src="https://avatars.githubusercontent.com/u/189589?v=4&s=117"></a></td><td><a href=https://github.com/huydod><img width="117" alt="huydod" src="https://avatars.githubusercontent.com/u/37580530?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/HussainAlkhalifah><img width="117" alt="HussainAlkhalifah" src="https://avatars.githubusercontent.com/u/43642162?v=4&s=117"></a></td><td><a href=https://github.com/HughbertD><img width="117" alt="HughbertD" src="https://avatars.githubusercontent.com/u/1580021?v=4&s=117"></a></td><td><a href=https://github.com/hiromi2424><img width="117" alt="hiromi2424" src="https://avatars.githubusercontent.com/u/191297?v=4&s=117"></a></td><td><a href=https://github.com/giacomocerquone><img width="117" alt="giacomocerquone" src="https://avatars.githubusercontent.com/u/9303791?v=4&s=117"></a></td><td><a href=https://github.com/roenschg><img width="117" alt="roenschg" src="https://avatars.githubusercontent.com/u/9590236?v=4&s=117"></a></td><td><a href=https://github.com/gjungb><img width="117" alt="gjungb" src="https://avatars.githubusercontent.com/u/3391068?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/geoffappleford><img width="117" alt="geoffappleford" src="https://avatars.githubusercontent.com/u/731678?v=4&s=117"></a></td><td><a href=https://github.com/gabiganam><img width="117" alt="gabiganam" src="https://avatars.githubusercontent.com/u/28859646?v=4&s=117"></a></td><td><a href=https://github.com/fuadscodes><img width="117" alt="fuadscodes" src="https://avatars.githubusercontent.com/u/60370584?v=4&s=117"></a></td><td><a href=https://github.com/dtrucs><img width="117" alt="dtrucs" src="https://avatars.githubusercontent.com/u/1926041?v=4&s=117"></a></td><td><a href=https://github.com/ferdiusa><img width="117" alt="ferdiusa" src="https://avatars.githubusercontent.com/u/1997982?v=4&s=117"></a></td><td><a href=https://github.com/fgallinari><img width="117" alt="fgallinari" src="https://avatars.githubusercontent.com/u/6473638?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/Gkleinereva><img width="117" alt="Gkleinereva" src="https://avatars.githubusercontent.com/u/23621633?v=4&s=117"></a></td><td><a href=https://github.com/epexa><img width="117" alt="epexa" src="https://avatars.githubusercontent.com/u/2198826?v=4&s=117"></a></td><td><a href=https://github.com/EnricoSottile><img width="117" alt="EnricoSottile" src="https://avatars.githubusercontent.com/u/10349653?v=4&s=117"></a></td><td><a href=https://github.com/elliotdickison><img width="117" alt="elliotdickison" src="https://avatars.githubusercontent.com/u/2523678?v=4&s=117"></a></td><td><a href=https://github.com/eliOcs><img width="117" alt="eliOcs" src="https://avatars.githubusercontent.com/u/1283954?v=4&s=117"></a></td><td><a href=https://github.com/Jmales><img width="117" alt="Jmales" src="https://avatars.githubusercontent.com/u/22914881?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/jessica-coursera><img width="117" alt="jessica-coursera" src="https://avatars.githubusercontent.com/u/35155465?v=4&s=117"></a></td><td><a href=https://github.com/vith><img width="117" alt="vith" src="https://avatars.githubusercontent.com/u/3265539?v=4&s=117"></a></td><td><a href=https://github.com/janwilts><img width="117" alt="janwilts" src="https://avatars.githubusercontent.com/u/16721581?v=4&s=117"></a></td><td><a href=https://github.com/janklimo><img width="117" alt="janklimo" src="https://avatars.githubusercontent.com/u/7811733?v=4&s=117"></a></td><td><a href=https://github.com/jamestiotio><img width="117" alt="jamestiotio" src="https://avatars.githubusercontent.com/u/18364745?v=4&s=117"></a></td><td><a href=https://github.com/jcjmcclean><img width="117" alt="jcjmcclean" src="https://avatars.githubusercontent.com/u/1822574?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/Jbithell><img width="117" alt="Jbithell" src="https://avatars.githubusercontent.com/u/8408967?v=4&s=117"></a></td><td><a href=https://github.com/JakubHaladej><img width="117" alt="JakubHaladej" src="https://avatars.githubusercontent.com/u/77832677?v=4&s=117"></a></td><td><a href=https://github.com/jakemcallister><img width="117" alt="jakemcallister" src="https://avatars.githubusercontent.com/u/1185699?v=4&s=117"></a></td><td><a href=https://github.com/gaejabong><img width="117" alt="gaejabong" src="https://avatars.githubusercontent.com/u/978944?v=4&s=117"></a></td><td><a href=https://github.com/JacobMGEvans><img width="117" alt="JacobMGEvans" src="https://avatars.githubusercontent.com/u/27247160?v=4&s=117"></a></td><td><a href=https://github.com/mazoruss><img width="117" alt="mazoruss" src="https://avatars.githubusercontent.com/u/17625190?v=4&s=117"></a></td></tr>
|
||||
<tr><td><a href=https://github.com/GreenJimmy><img width="117" alt="GreenJimmy" src="https://avatars.githubusercontent.com/u/39386?v=4&s=117"></a></td><td><a href=https://github.com/intenzive><img width="117" alt="intenzive" src="https://avatars.githubusercontent.com/u/11055931?v=4&s=117"></a></td><td><a href=https://github.com/NaxYo><img width="117" alt="NaxYo" src="https://avatars.githubusercontent.com/u/1963876?v=4&s=117"></a></td><td><a href=https://github.com/ishendyweb><img width="117" alt="ishendyweb" src="https://avatars.githubusercontent.com/u/10582418?v=4&s=117"></a></td><td><a href=https://github.com/IanVS><img width="117" alt="IanVS" src="https://avatars.githubusercontent.com/u/4616705?v=4&s=117"></a></td></tr>
|
||||
<!--/contributors-->
|
||||
</table>
|
||||

|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 8 KiB |
|
Before Width: | Height: | Size: 1,004 B |
|
Before Width: | Height: | Size: 4.4 MiB |
|
Before Width: | Height: | Size: 4.9 MiB |
|
Before Width: | Height: | Size: 1,017 B |
|
Before Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 1,006 B |
|
Before Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 109 KiB |
|
|
@ -1,26 +0,0 @@
|
|||
module.exports = (api) => {
|
||||
const targets = {}
|
||||
if (api.env('test')) {
|
||||
targets.node = 'current'
|
||||
}
|
||||
|
||||
return {
|
||||
presets: [
|
||||
['@babel/preset-env', {
|
||||
include: [
|
||||
'@babel/plugin-proposal-nullish-coalescing-operator',
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
'@babel/plugin-proposal-numeric-separator',
|
||||
],
|
||||
loose: true,
|
||||
targets,
|
||||
useBuiltIns: false, // Don't add polyfills automatically.
|
||||
modules: false,
|
||||
}],
|
||||
],
|
||||
plugins: [
|
||||
['@babel/plugin-transform-react-jsx', { pragma: 'h', pragmaFrag: 'Fragment' }],
|
||||
process.env.NODE_ENV !== 'dev' && 'babel-plugin-inline-package-json',
|
||||
].filter(Boolean),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import fs from 'node:fs/promises'
|
||||
import chalk from 'chalk'
|
||||
|
||||
import esbuild from 'esbuild'
|
||||
|
||||
const UPPY_ROOT = new URL('../', import.meta.url)
|
||||
const PACKAGES_ROOT = new URL('./packages/', UPPY_ROOT)
|
||||
|
||||
function buildBundle (srcFile, bundleFile, { minify = true, standalone = '', plugins, target, format } = {}) {
|
||||
return esbuild.build({
|
||||
bundle: true,
|
||||
sourcemap: true,
|
||||
entryPoints: [srcFile],
|
||||
outfile: bundleFile,
|
||||
platform: 'browser',
|
||||
minify,
|
||||
keepNames: target !== 'es5',
|
||||
plugins,
|
||||
tsconfigRaw: '{}',
|
||||
target,
|
||||
format,
|
||||
}).then(() => {
|
||||
if (minify) {
|
||||
console.info(chalk.green(`✓ Built Minified Bundle [${standalone}]:`), chalk.magenta(bundleFile))
|
||||
} else {
|
||||
console.info(chalk.green(`✓ Built Bundle [${standalone}]:`), chalk.magenta(bundleFile))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
await fs.mkdir(new URL('./uppy/dist', PACKAGES_ROOT), { recursive: true })
|
||||
|
||||
const methods = [
|
||||
buildBundle(
|
||||
'./packages/uppy/src/bundle.ts',
|
||||
'./packages/uppy/dist/uppy.min.mjs',
|
||||
{ standalone: 'Uppy (ESM)', format: 'esm' },
|
||||
),
|
||||
buildBundle(
|
||||
'./packages/uppy/bundle.mjs',
|
||||
'./packages/uppy/dist/uppy.min.js',
|
||||
{ standalone: 'Uppy', format: 'iife' },
|
||||
),
|
||||
]
|
||||
|
||||
// Add BUNDLE-README.MD
|
||||
methods.push(
|
||||
fs.copyFile(
|
||||
new URL('./BUNDLE-README.md', UPPY_ROOT),
|
||||
new URL('./uppy/dist/README.md', PACKAGES_ROOT),
|
||||
),
|
||||
)
|
||||
|
||||
await Promise.all(methods).then(() => {
|
||||
console.info(chalk.yellow('✓ JS bundles 🎉'))
|
||||
}, (err) => {
|
||||
console.error(chalk.red('✗ Error:'), chalk.red(err.message))
|
||||
})
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
const sass = require('sass')
|
||||
const postcss = require('postcss')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const postcssLogical = require('postcss-logical')
|
||||
const postcssDirPseudoClass = require('postcss-dir-pseudo-class')
|
||||
const cssnano = require('cssnano')
|
||||
const { promisify } = require('node:util')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
const resolve = require('resolve')
|
||||
const glob = promisify(require('glob'))
|
||||
|
||||
const renderScss = promisify(sass.render)
|
||||
const { mkdir, writeFile } = fs.promises
|
||||
|
||||
const cwd = process.cwd()
|
||||
let chalk
|
||||
|
||||
function handleErr (err) {
|
||||
console.error(chalk.red('✗ Error:'), chalk.red(err.message))
|
||||
}
|
||||
|
||||
async function compileCSS () {
|
||||
({ default:chalk } = await import('chalk'))
|
||||
const files = await glob('packages/{,@uppy/}*/src/style.scss')
|
||||
|
||||
for (const file of files) {
|
||||
const importedFiles = new Set()
|
||||
const scssResult = await renderScss({
|
||||
file,
|
||||
importer (url, from, done) {
|
||||
resolve(url, {
|
||||
basedir: path.dirname(from),
|
||||
filename: from,
|
||||
extensions: ['.scss'],
|
||||
}, (err, resolved) => {
|
||||
if (err) {
|
||||
done(err)
|
||||
return
|
||||
}
|
||||
|
||||
const realpath = fs.realpathSync(resolved)
|
||||
|
||||
if (importedFiles.has(realpath)) {
|
||||
done({ contents: '' })
|
||||
return
|
||||
}
|
||||
importedFiles.add(realpath)
|
||||
|
||||
done({ file: realpath })
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const plugins = [
|
||||
autoprefixer,
|
||||
postcssLogical(),
|
||||
postcssDirPseudoClass(),
|
||||
]
|
||||
const postcssResult = await postcss(plugins)
|
||||
.process(scssResult.css, { from: file })
|
||||
postcssResult.warnings().forEach((warn) => {
|
||||
console.warn(warn.toString())
|
||||
})
|
||||
|
||||
const outdir = path.join(path.dirname(file), '../dist')
|
||||
// Save the `uppy` package's CSS as `uppy.css`,
|
||||
// the rest as `style.css`.
|
||||
// const outfile = path.join(outdir, outdir.includes(path.normalize('packages/uppy/')) ? 'uppy.css' : 'style.css')
|
||||
let outfile = path.join(outdir, 'style.css')
|
||||
if (outdir.includes(path.normalize('packages/uppy/'))) {
|
||||
outfile = path.join(outdir, 'uppy.css')
|
||||
}
|
||||
await mkdir(outdir, { recursive: true })
|
||||
await writeFile(outfile, postcssResult.css)
|
||||
console.info(
|
||||
chalk.green('✓ Built Uppy CSS:'),
|
||||
chalk.magenta(path.relative(cwd, outfile)),
|
||||
)
|
||||
|
||||
const minifiedResult = await postcss([
|
||||
cssnano({ safe: true }),
|
||||
]).process(postcssResult.css, { from: outfile })
|
||||
minifiedResult.warnings().forEach((warn) => {
|
||||
console.warn(warn.toString())
|
||||
})
|
||||
await writeFile(outfile.replace(/\.css$/, '.min.css'), minifiedResult.css)
|
||||
console.info(
|
||||
chalk.green('✓ Minified Bundle CSS:'),
|
||||
chalk.magenta(path.relative(cwd, outfile).replace(/\.css$/, '.min.css')),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileCSS().then(() => {
|
||||
console.info(chalk.yellow('✓ CSS Bundles 🎉'))
|
||||
}, handleErr)
|
||||
147
bin/build-lib.js
|
|
@ -1,147 +0,0 @@
|
|||
const babel = require('@babel/core')
|
||||
const t = require('@babel/types')
|
||||
const { promisify } = require('node:util')
|
||||
const glob = promisify(require('glob'))
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
const { mkdir, stat, writeFile } = fs.promises
|
||||
|
||||
const PACKAGE_JSON_IMPORT = /^\..*\/package.json$/
|
||||
const SOURCE = 'packages/{*,@uppy/*}/src/**/*.{js,ts}?(x)'
|
||||
// Files not to build (such as tests)
|
||||
const IGNORE = /\.test\.jsx?$|\.test\.tsx?$|__mocks__|svelte|angular|companion\//;
|
||||
// Files that should trigger a rebuild of everything on change
|
||||
const META_FILES = [
|
||||
'babel.config.js',
|
||||
'package.json',
|
||||
'package-lock.json',
|
||||
'yarn.lock',
|
||||
'bin/build-lib.js',
|
||||
]
|
||||
|
||||
function lastModified (file, createParentDir = false) {
|
||||
return stat(file).then((s) => s.mtime, async (err) => {
|
||||
if (err.code === 'ENOENT') {
|
||||
if (createParentDir) {
|
||||
await mkdir(path.dirname(file), { recursive: true })
|
||||
}
|
||||
return 0
|
||||
}
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
const versionCache = new Map()
|
||||
|
||||
async function preparePackage (file) {
|
||||
const packageFolder = file.slice(0, file.indexOf('/src/'))
|
||||
if (versionCache.has(packageFolder)) return
|
||||
|
||||
// eslint-disable-next-line import/no-dynamic-require, global-require
|
||||
const { version } = require(path.join(__dirname, '..', packageFolder, 'package.json'))
|
||||
if (process.env.FRESH) {
|
||||
// in case it hasn't been done before.
|
||||
await mkdir(path.join(packageFolder, 'lib'), { recursive: true })
|
||||
}
|
||||
versionCache.set(packageFolder, version)
|
||||
}
|
||||
|
||||
const nonJSImport = /^\.\.?\/.+\.([jt]sx|ts)$/
|
||||
// eslint-disable-next-line no-shadow
|
||||
function rewriteNonJSImportsToJS (path) {
|
||||
const match = nonJSImport.exec(path.node.source.value)
|
||||
if (match) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
path.node.source.value = `${match[0].slice(0, -match[1].length)}js`
|
||||
}
|
||||
}
|
||||
|
||||
async function buildLib () {
|
||||
const metaMtimes = await Promise.all(META_FILES.map((filename) => lastModified(path.join(__dirname, '..', filename))))
|
||||
const metaMtime = Math.max(...metaMtimes)
|
||||
|
||||
const files = await glob(SOURCE)
|
||||
/* eslint-disable no-continue */
|
||||
for (const file of files) {
|
||||
if (IGNORE.test(file)) {
|
||||
continue
|
||||
}
|
||||
await preparePackage(file)
|
||||
const libFile = file.replace('/src/', '/lib/').replace(/\.[jt]sx?$/, '.js')
|
||||
|
||||
// on a fresh build, rebuild everything.
|
||||
if (!process.env.FRESH) {
|
||||
const [srcMtime, libMtime] = await Promise.all([
|
||||
lastModified(file),
|
||||
lastModified(libFile, true),
|
||||
])
|
||||
// Skip files that haven't changed
|
||||
if (srcMtime < libMtime && metaMtime < libMtime) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const plugins = [{
|
||||
visitor: {
|
||||
// eslint-disable-next-line no-shadow
|
||||
ImportDeclaration (path) {
|
||||
rewriteNonJSImportsToJS(path)
|
||||
if (PACKAGE_JSON_IMPORT.test(path.node.source.value)
|
||||
&& path.node.specifiers.length === 1
|
||||
&& path.node.specifiers[0].type === 'ImportDefaultSpecifier') {
|
||||
// Vendor-in version number from package.json files:
|
||||
const version = versionCache.get(file.slice(0, file.indexOf('/src/')))
|
||||
if (version != null) {
|
||||
const [{ local }] = path.node.specifiers
|
||||
path.replaceWith(
|
||||
t.variableDeclaration('const', [t.variableDeclarator(local,
|
||||
t.objectExpression([
|
||||
t.objectProperty(t.stringLiteral('version'), t.stringLiteral(version)),
|
||||
]))]),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
ExportAllDeclaration: rewriteNonJSImportsToJS,
|
||||
// eslint-disable-next-line no-shadow
|
||||
ExportNamedDeclaration (path) {
|
||||
if (path.node.source != null) {
|
||||
rewriteNonJSImportsToJS(path)
|
||||
}
|
||||
},
|
||||
},
|
||||
}]
|
||||
const isTSX = file.endsWith('.tsx')
|
||||
if (isTSX || file.endsWith('.ts')) {
|
||||
plugins.push(['@babel/plugin-transform-typescript', {
|
||||
disallowAmbiguousJSXLike: true,
|
||||
isTSX,
|
||||
jsxPragma: 'h',
|
||||
jsxPragmaFrag: 'Fragment',
|
||||
}])
|
||||
}
|
||||
|
||||
const { code, map } = await babel.transformFileAsync(file, {
|
||||
sourceMaps: true,
|
||||
plugins,
|
||||
// no comments because https://github.com/transloadit/uppy/pull/4868#issuecomment-1897717779
|
||||
comments: !process.env.DIFF_BUILDER,
|
||||
})
|
||||
const [{ default: chalk }] = await Promise.all([
|
||||
import('chalk'),
|
||||
writeFile(libFile, code),
|
||||
writeFile(`${libFile}.map`, JSON.stringify(map)),
|
||||
])
|
||||
console.log(chalk.green('Compiled lib:'), chalk.magenta(libFile))
|
||||
}
|
||||
/* eslint-enable no-continue */
|
||||
}
|
||||
|
||||
console.log('Using Babel version:', require('@babel/core/package.json').version)
|
||||
|
||||
buildLib().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/sh
|
||||
# Convert a video file to a gif.
|
||||
# `to-gif /path/to/input.mp4 /path/to/output.gif`
|
||||
palette="/tmp/to-gif-palette.png"
|
||||
filters="fps=15"
|
||||
ffmpeg -v warning -i $1 -vf "$filters,palettegen" -y $palette
|
||||
ffmpeg -v warning -i $1 -i $palette -lavfi "$filters [x]; [x][1:v] paletteuse" -y $2
|
||||
|
||||
# resize after
|
||||
# gifsicle --resize-fit-width 1000 -i animation.gif > animation-1000px.gif
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
#!/bin/sh
|
||||
# Convert a video file to a gif.
|
||||
# `to-gif /path/to/input.mp4 /path/to/output.gif`
|
||||
palette="/tmp/to-gif-palette.png"
|
||||
filters="fps=15"
|
||||
ffmpeg -v warning -i $1 -vf "$filters,palettegen" -y $palette
|
||||
ffmpeg -v warning -i $1 -i $palette -lavfi "$filters [x]; [x][1:v] paletteuse" -y $2
|
||||
|
||||
# gifsicle --resize-fit-width 1000 -i animation.gif > animation-1000px.gif
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -o pipefail
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
# set -o xtrace
|
||||
|
||||
# Set magic variables for current file & dir
|
||||
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
|
||||
__base="$(basename ${__file} .sh)"
|
||||
__root="$(cd "$(dirname "${__dir}")" && pwd)"
|
||||
|
||||
width=600
|
||||
speed=0.7
|
||||
input="${__root}/assets/uppy-demo-oct-2018.mov"
|
||||
base="$(basename "${input}")"
|
||||
output="${__root}/assets/${base}.gif"
|
||||
|
||||
ffmpeg \
|
||||
-y \
|
||||
-i "${input}" \
|
||||
-vf fps=10,scale=${width}:-1:flags=lanczos,palettegen "${__root}/assets/${base}-palette.png"
|
||||
|
||||
ffmpeg \
|
||||
-y \
|
||||
-i "${input}" \
|
||||
-i "${__root}/assets/${base}-palette.png" \
|
||||
-filter_complex "setpts=${speed}*PTS,fps=10,scale=${width}:-1:flags=lanczos[x];[x][1:v]paletteuse" \
|
||||
"${output}"
|
||||
|
||||
du -hs "${output}"
|
||||
open -a 'Google Chrome' "${output}"
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import fetchContrib from 'github-contributors-list/lib/contributors.js'
|
||||
import layoutStrategy from 'github-contributors-list/lib/strategies/layout_strategies/json.js'
|
||||
import sortStrategy from 'github-contributors-list/lib/strategies/sort_strategies/sort_desc.js'
|
||||
import filterStrategy from 'github-contributors-list/lib/strategies/filter_strategies/login.js'
|
||||
import { Buffer } from 'node:buffer'
|
||||
import fs from 'node:fs/promises'
|
||||
import process from 'node:process'
|
||||
|
||||
const README_FILE_NAME = new URL('../README.md', import.meta.url)
|
||||
|
||||
const readme = await fs.open(README_FILE_NAME, 'r+')
|
||||
const readmeContent = await readme.readFile()
|
||||
|
||||
// Detect start of contributors section.
|
||||
const START_TAG = Buffer.from('<table id="contributors_table">\n')
|
||||
const START_TAG_POSITION =
|
||||
readmeContent.indexOf(START_TAG) + START_TAG.byteLength
|
||||
|
||||
const args = {
|
||||
owner: 'transloadit',
|
||||
repository: 'uppy',
|
||||
cols: 6,
|
||||
format: 'json',
|
||||
sortBy: 'contributions',
|
||||
sortOrder: 'desc',
|
||||
filter: [],
|
||||
layoutStrategy,
|
||||
sortStrategy,
|
||||
filterStrategy,
|
||||
}
|
||||
const { contributors } = await fetchContrib(args).loadAll(
|
||||
args.owner,
|
||||
args.repository,
|
||||
args.authToken || process.env.GITHUB_API_TOKEN,
|
||||
args.fromDate,
|
||||
)
|
||||
let cursor = START_TAG_POSITION
|
||||
for (const line of contributors) {
|
||||
let row = '<tr>'
|
||||
for (const { html_url, login, avatar_url } of line) {
|
||||
row += `<td><a href=${html_url}><img width="117" alt=${JSON.stringify(login)} src=${JSON.stringify(avatar_url)}></a></td>`
|
||||
}
|
||||
row += '</tr>\n'
|
||||
const { bytesWritten } = await readme.write(row, cursor, 'utf-8')
|
||||
cursor += bytesWritten
|
||||
}
|
||||
|
||||
if (cursor === START_TAG_POSITION) {
|
||||
console.log('Empty response from githubcontrib. GitHub’s rate limit?')
|
||||
await readme.close()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
await readme.truncate(cursor)
|
||||
|
||||
// Write the end of the file.
|
||||
await readme.write(
|
||||
readmeContent,
|
||||
readmeContent.indexOf('<!--/contributors-->'),
|
||||
undefined,
|
||||
cursor,
|
||||
)
|
||||
await readme.close()
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# This script is meant to be run on a dev's machine to update the version on
|
||||
# Yarn used by the monorepo. Its goal is to make sure that every mention of Yarn
|
||||
# version is updated, and it re-installs the plugins to make sure those are
|
||||
# up-to-date as well.
|
||||
|
||||
set -o pipefail
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
|
||||
CURRENT_VERSION=$(corepack yarn --version)
|
||||
LAST_VERSION=$(curl \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/yarnpkg/berry/releases?per_page=1 | \
|
||||
awk '{ if ($1 == "\"tag_name\":") print $2 }' | \
|
||||
sed 's#^"@yarnpkg/cli/##;s#",$##')
|
||||
|
||||
[ "$CURRENT_VERSION" = "$LAST_VERSION" ] && \
|
||||
echo "Already using latest version." && \
|
||||
exit 0
|
||||
|
||||
echo "Upgrading to Yarn $LAST_VERSION (from Yarn $CURRENT_VERSION)..."
|
||||
|
||||
PLUGINS=$(awk '{ if ($1 == "spec:") print $2 }' .yarnrc.yml)
|
||||
|
||||
echo "$PLUGINS" | xargs -n1 -t corepack yarn plugin remove
|
||||
|
||||
cp package.json .yarn/cache/tmp.package.json
|
||||
sed "s#\"yarn\": \"$CURRENT_VERSION\"#\"yarn\": \"$LAST_VERSION\"#;s#\"yarn@$CURRENT_VERSION\"#\"yarn@$LAST_VERSION\"#" .yarn/cache/tmp.package.json > package.json
|
||||
rm .yarn/cache/tmp.package.json
|
||||
|
||||
echo "$PLUGINS" | xargs -n1 -t corepack yarn plugin import
|
||||
corepack yarn
|
||||
|
||||
git add package.json yarn.lock
|
||||
git add .yarn/plugins
|
||||
84
biome.json
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.0.5/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": true,
|
||||
"includes": [
|
||||
"packages/**",
|
||||
"examples/**",
|
||||
"scripts/**",
|
||||
"bin/**",
|
||||
"e2e/**",
|
||||
"private/**",
|
||||
"!packages/**/{dist,lib}/**",
|
||||
"!node_modules",
|
||||
"!.svelte-kit",
|
||||
"!packages/@uppy/components/src/input.css"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"formatWithErrors": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"includes": [
|
||||
"packages/**",
|
||||
"examples/**",
|
||||
"scripts/**",
|
||||
"private/**",
|
||||
"!packages/**/{dist,lib}/**",
|
||||
"!node_modules",
|
||||
"!.svelte-kit",
|
||||
"!packages/@uppy/components/src/input.css",
|
||||
"!**/*.vue",
|
||||
"!**/*.svelte"
|
||||
],
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"useExhaustiveDependencies": "error",
|
||||
"useHookAtTopLevel": "error",
|
||||
"noUnusedFunctionParameters": "off",
|
||||
"noUnusedVariables": {
|
||||
"level": "error",
|
||||
"options": {
|
||||
"ignoreRestSiblings": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": "off"
|
||||
},
|
||||
"a11y": {
|
||||
"noSvgWithoutTitle": "off"
|
||||
},
|
||||
"nursery": {
|
||||
"noNestedComponentDefinitions": "error",
|
||||
"noReactPropAssign": "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "single",
|
||||
"semicolons": "asNeeded"
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"enabled": true,
|
||||
"actions": {
|
||||
"source": {
|
||||
"organizeImports": "on"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
version: '3.9'
|
||||
|
||||
services:
|
||||
uppy:
|
||||
image: transloadit/companion
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
volumes:
|
||||
- ./:/app
|
||||
- /app/node_modules
|
||||
- /mnt/uppy-server-data:/mnt/uppy-server-data
|
||||
ports:
|
||||
- '3020:3020'
|
||||
command: '/app/src/standalone/start-server.js --config nodemon.json'
|
||||
env_file:
|
||||
- .env
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
version: '3.9'
|
||||
|
||||
services:
|
||||
uppy:
|
||||
image: companion
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.test
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
version: '3.9'
|
||||
|
||||
services:
|
||||
uppy:
|
||||
image: transloadit/companion
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
volumes:
|
||||
- /app/node_modules
|
||||
- /mnt/uppy-server-data:/mnt/uppy-server-data
|
||||
ports:
|
||||
- '3020:3020'
|
||||
env_file:
|
||||
- .env
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
# Uppy documentation
|
||||
|
||||
See instructions for linting this documentation and seeing this documentation in
|
||||
the browser in <https://github.com/transloadit/uppy.io>.
|
||||
|
|
@ -1,989 +0,0 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Companion
|
||||
|
||||
Companion is an open source server application which **takes away the complexity
|
||||
of authentication and the cost of downloading files from remote sources**, such
|
||||
as Instagram, Google Drive, and others. Companion is a server-to-server
|
||||
orchestrator that streams files from a source to a destination, and files are
|
||||
never stored in Companion. Companion can run either as a standalone
|
||||
(self-hosted) application, [Transloadit-hosted](#hosted), or plugged in as an
|
||||
Express middleware into an existing application. The Uppy client requests remote
|
||||
files from Companion, which it will download and simultaneously upload to your
|
||||
[Tus server](/docs/tus), [AWS bucket](/docs/aws-s3), or any server that supports
|
||||
[PUT, POST or Multipart uploads](/docs/xhr-upload).
|
||||
|
||||
This means a user uploading a 5GB video from Google Drive from their phone isn’t
|
||||
eating into their data plans and you don’t have to worry about implementing
|
||||
OAuth.
|
||||
|
||||
## When should I use it?
|
||||
|
||||
If you want to let users download files from [Box][], [Dropbox][], [Facebook][],
|
||||
[Google Drive][googledrive], [Google Photos][googlephotos], [Instagram][],
|
||||
[OneDrive][], [Unsplash][], [Import from URL][url], or [Zoom][] — you need
|
||||
Companion.
|
||||
|
||||
Companion supports the same [uploaders](/docs/guides/choosing-uploader) as Uppy:
|
||||
[Tus](/docs/tus), [AWS S3](/docs/aws-s3), and [regular multipart](/docs/tus).
|
||||
But instead of manually setting a plugin, Uppy sends along a header with the
|
||||
uploader and Companion will use the same on the server. This means if you are
|
||||
using [Tus](/docs/tus) for your local uploads, you can send your remote uploads
|
||||
to the same Tus server (and likewise for your AWS S3 bucket).
|
||||
|
||||
:::note
|
||||
|
||||
Companion only deals with _remote_ files, _local_ files are still uploaded from
|
||||
the client with your upload plugin.
|
||||
|
||||
:::
|
||||
|
||||
## Hosted
|
||||
|
||||
Using [Transloadit][] services comes with a hosted version of Companion so you
|
||||
don’t have to worry about hosting your own server. Whether you are on a free or
|
||||
paid Transloadit [plan](https://transloadit.com/pricing/), you can use
|
||||
Companion. It’s not possible to rent a Companion server without a Transloadit
|
||||
plan.
|
||||
|
||||
[**Sign-up for a (free) plan**](https://transloadit.com/pricing/).
|
||||
|
||||
:::tip
|
||||
|
||||
Choosing Transloadit for your file services also comes with credentials for all
|
||||
remote providers. This means you don’t have to waste time going through the
|
||||
approval process of every app. You can still add your own credentials in the
|
||||
Transloadit admin page if you want.
|
||||
|
||||
:::
|
||||
|
||||
:::info
|
||||
|
||||
Downloading and uploading files through Companion doesn’t count towards your
|
||||
[monthly quota](https://transloadit.com/docs/faq/1gb-worth/), it’s a way for
|
||||
files to arrive at Transloadit servers, much like Uppy.
|
||||
|
||||
:::
|
||||
|
||||
To do so each provider plugin must be configured with Transloadit’s Companion
|
||||
URLs:
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
import Dropbox from '@uppy/dropbox';
|
||||
|
||||
uppy.use(Dropbox, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
});
|
||||
```
|
||||
|
||||
You may also hit rate limits, because the OAuth application is shared between
|
||||
everyone using Transloadit.
|
||||
|
||||
To solve that, you can use your own OAuth keys with Transloadit’s hosted
|
||||
Companion servers by using Transloadit Template Credentials. [Create a Template
|
||||
Credential][template-credentials] on the Transloadit site. Select “Companion
|
||||
OAuth” for the service, and enter the key and secret for the provider you want
|
||||
to use. Then you can pass the name of the new credentials to that provider:
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
import Dropbox from '@uppy/dropbox';
|
||||
|
||||
uppy.use(Dropbox, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
companionKeysParams: {
|
||||
key: 'YOUR_TRANSLOADIT_API_KEY',
|
||||
credentialsName: 'my_companion_dropbox_creds',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Installation & use
|
||||
|
||||
Companion is installed from npm. Depending on how you want to run Companion, the
|
||||
install process is slightly different. Companion can be integrated as middleware
|
||||
into your [Express](https://expressjs.com/) app or as a standalone server. Most
|
||||
people probably want to run it as a standalone server, while the middleware
|
||||
could be used to further customise Companion or integrate it into your own HTTP
|
||||
server code.
|
||||
|
||||
:::note
|
||||
|
||||
Since v2, you need to be running `node.js >= v10.20.1` to use Companion. More
|
||||
information in the
|
||||
[migrating to 2.0](/docs/guides/migration-guides/#migrate-from-uppy-1x-to-2x)
|
||||
guide.
|
||||
|
||||
Windows is not a supported platform right now. It may work, and we’re happy to
|
||||
accept improvements in this area, but we can’t provide support.
|
||||
|
||||
:::
|
||||
|
||||
### Standalone mode
|
||||
|
||||
You can use the standalone version if you want to run Companion as it’s own
|
||||
Node.js process. It’s a configured Express server with sessions, logging, and
|
||||
security best practices. First you’ll typically want to install it globally:
|
||||
|
||||
```bash
|
||||
npm install -g @uppy/companion
|
||||
```
|
||||
|
||||
Standalone Companion will always serve HTTP (not HTTPS) and expects a reverse
|
||||
proxy with SSL termination in front of it when running in production. See
|
||||
[`COMPANION_PROTOCOL`](#server) for more information.
|
||||
|
||||
Companion ships with an executable file (`bin/companion`) which is the
|
||||
standalone server. Unlike the middleware version, options are set via
|
||||
environment variables.
|
||||
|
||||
:::info
|
||||
|
||||
Checkout [options](#options) for the available options in JS and environment
|
||||
variable formats.
|
||||
|
||||
:::
|
||||
|
||||
You need at least these three to get started:
|
||||
|
||||
```bash
|
||||
export COMPANION_SECRET="shh!Issa Secret!"
|
||||
export COMPANION_DOMAIN="YOUR SERVER DOMAIN"
|
||||
export COMPANION_DATADIR="PATH/TO/DOWNLOAD/DIRECTORY"
|
||||
```
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
companion
|
||||
```
|
||||
|
||||
You can also pass in the path to your JSON config file, like so:
|
||||
|
||||
```bash
|
||||
companion --config /path/to/companion.json
|
||||
```
|
||||
|
||||
You may also want to run Companion in a process manager like
|
||||
[PM2](https://pm2.keymetrics.io/) to make sure it gets restarted on upon
|
||||
crashing as well as allowing scaling to many instances.
|
||||
|
||||
### Express middleware mode
|
||||
|
||||
First install it into your Node.js project with your favorite package manager:
|
||||
|
||||
```bash
|
||||
npm install @uppy/companion
|
||||
```
|
||||
|
||||
To plug Companion into an existing server, call its `.app` method, passing in an
|
||||
[options](#options) object as a parameter. This returns a server instance that
|
||||
you can mount on a route in your Express app.
|
||||
|
||||
```js
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import session from 'express-session';
|
||||
import companion from '@uppy/companion';
|
||||
|
||||
const app = express();
|
||||
|
||||
// Companion requires body-parser and express-session middleware.
|
||||
// You can add it like this if you use those throughout your app.
|
||||
//
|
||||
// If you are using something else in your app, you can add these
|
||||
// middlewares in the same subpath as Companion instead.
|
||||
app.use(bodyParser.json());
|
||||
app.use(session({ secret: 'some secrety secret' }));
|
||||
|
||||
const companionOptions = {
|
||||
providerOptions: {
|
||||
drive: {
|
||||
key: 'GOOGLE_DRIVE_KEY',
|
||||
secret: 'GOOGLE_DRIVE_SECRET',
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: 'localhost:3020',
|
||||
protocol: 'http',
|
||||
// Default installations normally don't need a path.
|
||||
// However if you specify a `path`, you MUST specify
|
||||
// the same path in `app.use()` below,
|
||||
// e.g. app.use('/companion', companionApp)
|
||||
// path: '/companion',
|
||||
},
|
||||
filePath: '/path/to/folder/',
|
||||
};
|
||||
|
||||
const { app: companionApp } = companion.app(companionOptions);
|
||||
app.use(companionApp);
|
||||
```
|
||||
|
||||
Companion uses WebSockets to communicate progress, errors, and successes to the
|
||||
client. This is what Uppy listens to to update it’s internal state and UI.
|
||||
|
||||
Add the Companion WebSocket server using the `companion.socket` function:
|
||||
|
||||
```js
|
||||
const server = app.listen(PORT);
|
||||
|
||||
companion.socket(server);
|
||||
```
|
||||
|
||||
If WebSockets fail for some reason Uppy and Companion will fallback to HTTP
|
||||
polling.
|
||||
|
||||
### Running many instances
|
||||
|
||||
We recommend running at least two instances in production, so that if the
|
||||
Node.js event loop gets blocked by one or more requests (due to a bug or spike
|
||||
in traffic), it doesn’t also block or slow down all other requests as well (as
|
||||
Node.js is single threaded).
|
||||
|
||||
As an example for scale, one enterprise customer of Transloadit, who self-hosts
|
||||
Companion to power an education service that is used by many universities
|
||||
globally, deploys 7 Companion instances. Their earlier solution ran on 35
|
||||
instances. In our general experience Companion will saturate network interface
|
||||
cards before other resources on commodity virtual servers (`c5d.2xlarge` for
|
||||
instance).
|
||||
|
||||
Your mileage may vary, so we recommend to add observability. You can let
|
||||
Prometheus crawl the `/metrics` endpoint and graph that with Grafana for
|
||||
instance.
|
||||
|
||||
#### Using unique endpoints
|
||||
|
||||
One option is to run many instances with each instance having its own unique
|
||||
endpoint. This could be on separate ports, (sub)domain names, or IPs. With this
|
||||
setup, you can either:
|
||||
|
||||
1. Implement your own logic that will direct each upload to a specific Companion
|
||||
endpoint by setting the `companionUrl` option
|
||||
2. Setting the Companion option `COMPANION_SELF_ENDPOINT`. This option will
|
||||
cause Companion to respond with a `i-am` HTTP header containing the value
|
||||
from `COMPANION_SELF_ENDPOINT`. When Uppy’s sees this header, it will pin all
|
||||
requests for the upload to this endpoint.
|
||||
|
||||
In either case, you would then also typically configure a single Companion
|
||||
instance (one endpoint) to handle all OAuth authentication requests, so that you
|
||||
only need to specify a single OAuth callback URL. See also `oauthDomain` and
|
||||
`validHosts`.
|
||||
|
||||
#### Using a load balancer
|
||||
|
||||
The other option is to set up a load balancer in front of many Companion
|
||||
instances. Then Uppy will only see a single endpoint and send all requests to
|
||||
the associated load balancer, which will then distribute them between Companion
|
||||
instances. The companion instances coordinate their messages and events over
|
||||
Redis so that any instance can serve the client’s requests. Note that sticky
|
||||
sessions are **not** needed with this setup. Here are the requirements for this
|
||||
setup:
|
||||
|
||||
- The instances need to be connected to the same Redis server.
|
||||
- You need to set `COMPANION_SECRET` to the same value on both servers.
|
||||
- if you use the `companionKeysParams` feature (Transloadit), you also need
|
||||
`COMPANION_PREAUTH_SECRET` to be the same on each instance.
|
||||
- All other configuration needs to be the same, except if you’re running many
|
||||
instances on the same machine, then `COMPANION_PORT` should be different for
|
||||
each instance.
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
:::tip
|
||||
|
||||
The headings display the JS and environment variable options (`option`
|
||||
`ENV_OPTION`). When integrating Companion into your own server, you pass the
|
||||
options to `companion.app()`. If you are using the standalone version, you
|
||||
configure Companion using environment variables. Some options only exist as
|
||||
environment variables or only as a JS option.
|
||||
|
||||
:::
|
||||
|
||||
<details>
|
||||
<summary>Default configuration</summary>
|
||||
|
||||
```javascript
|
||||
const options = {
|
||||
server: {
|
||||
protocol: 'http',
|
||||
path: '',
|
||||
},
|
||||
providerOptions: {},
|
||||
s3: {
|
||||
endpoint: 'https://{service}.{region}.amazonaws.com',
|
||||
conditions: [],
|
||||
useAccelerateEndpoint: false,
|
||||
getKey: ({ filename }) => `${crypto.randomUUID()}-${filename}`,
|
||||
expires: 800, // seconds
|
||||
},
|
||||
allowLocalUrls: false,
|
||||
logClientVersion: true,
|
||||
periodicPingUrls: [],
|
||||
streamingUpload: true,
|
||||
clientSocketConnectTimeout: 60000,
|
||||
metrics: true,
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### `filePath` `COMPANION_DATADIR`
|
||||
|
||||
Full path to the directory to which provider files will be downloaded
|
||||
temporarily.
|
||||
|
||||
#### `secret` `COMPANION_SECRET` `COMPANION_SECRET_FILE`
|
||||
|
||||
A secret string which Companion uses to generate authorization tokens. You
|
||||
should generate a long random string for this. For example:
|
||||
|
||||
```js
|
||||
const crypto = require('node:crypto');
|
||||
|
||||
const secret = crypto.randomBytes(64).toString('hex');
|
||||
```
|
||||
|
||||
:::caution
|
||||
|
||||
Omitting the `secret` in the standalone version will generate a secret for you,
|
||||
using the above `crypto` string. But when integrating with Express you must
|
||||
provide it yourself. This is an essential security measure.
|
||||
|
||||
:::
|
||||
|
||||
:::note
|
||||
|
||||
Using a secret file means passing an absolute path to a file with any extension,
|
||||
which has only the secret, nothing else.
|
||||
|
||||
:::
|
||||
|
||||
#### `preAuthSecret` `COMPANION_PREAUTH_SECRET` `COMPANION_PREAUTH_SECRET_FILE`
|
||||
|
||||
If you are using the [Transloadit](/docs/transloadit) `companionKeysParams`
|
||||
feature (Transloadit-hosted Companion using your own custom OAuth credentials),
|
||||
set this variable to a strong randomly generated secret. See also
|
||||
`COMPANION_SECRET` (but do not use the same secret!)
|
||||
|
||||
:::note
|
||||
|
||||
Using a secret file means passing an absolute path to a file with any extension,
|
||||
which has only the secret, nothing else.
|
||||
|
||||
:::
|
||||
|
||||
#### `uploadUrls` `COMPANION_UPLOAD_URLS`
|
||||
|
||||
An allowlist (array) of strings (exact URLs) or regular expressions. Companion
|
||||
will only accept uploads to these URLs. This ensures that your Companion
|
||||
instance is only allowed to upload to your trusted servers and prevents
|
||||
[SSRF](https://en.wikipedia.org/wiki/Server-side_request_forgery) attacks.
|
||||
|
||||
#### `COMPANION_PORT`
|
||||
|
||||
The port on which to start the standalone server, defaults to 3020. This is a
|
||||
standalone-only option.
|
||||
|
||||
#### `COMPANION_COOKIE_DOMAIN`
|
||||
|
||||
Allows you to customize the domain of the cookies created for Express sessions.
|
||||
This is a standalone-only option.
|
||||
|
||||
#### `COMPANION_HIDE_WELCOME`
|
||||
|
||||
Setting this to `true` disables the welcome message shown at `/`. This is a
|
||||
standalone-only option.
|
||||
|
||||
#### `redisUrl` `COMPANION_REDIS_URL`
|
||||
|
||||
URL to running Redis server. This can be used to scale Companion horizontally
|
||||
using many instances. See [How to scale Companion](#how-to-scale-companion).
|
||||
|
||||
#### `COMPANION_REDIS_EXPRESS_SESSION_PREFIX`
|
||||
|
||||
Set a custom prefix for redis keys created by
|
||||
[connect-redis](https://github.com/tj/connect-redis). Defaults to
|
||||
`companion-session:`. Sessions are used for storing authentication state and for
|
||||
allowing thumbnails to be loaded by the browser via Companion and for OAuth2.
|
||||
See also `COMPANION_REDIS_PUBSUB_SCOPE`.
|
||||
|
||||
#### `redisOptions` `COMPANION_REDIS_OPTIONS`
|
||||
|
||||
An object of
|
||||
[options supported by the `ioredis` client](https://github.com/redis/ioredis).
|
||||
See also
|
||||
[`RedisOptions`](https://github.com/redis/ioredis/blob/af832752040e616daf51621681bcb40cab965a9b/lib/redis/RedisOptions.ts#L8).
|
||||
|
||||
#### `redisPubSubScope` `COMPANION_REDIS_PUBSUB_SCOPE`
|
||||
|
||||
Use a scope for the companion events at the Redis server. Setting this option
|
||||
will prefix all events with the name provided and a colon. See also
|
||||
`COMPANION_REDIS_EXPRESS_SESSION_PREFIX`.
|
||||
|
||||
#### `server`
|
||||
|
||||
Configuration options for the underlying server.
|
||||
|
||||
| Key / Environment variable | Value | Description |
|
||||
| ---------------------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `protocol` `COMPANION_PROTOCOL` | `http` or `https` | Used to build a URL to reference the Companion instance itself, which is used for headers and cookies. Companion itself always runs as a HTTP server, so locally you should use `http`. You must to set this to `https` once you enabled SSL/HTTPS for your domain in production by running a reverse https-proxy in front of Companion, or with a built-in HTTPS feature of your hosting service. |
|
||||
| `host` `COMPANION_DOMAIN` | `String` | Your server’s publicly facing hostname (for example `example.com`). |
|
||||
| `oauthDomain` `COMPANION_OAUTH_DOMAIN` | `String` | If you have several instances of Companion with different (and perhaps dynamic) subdomains, you can set a single fixed subdomain and server (such as `sub1.example.com`) to handle your OAuth authentication for you. This would then redirect back to the correct instance with the required credentials on completion. This way you only need to configure a single callback URL for OAuth providers. |
|
||||
| `path` `COMPANION_PATH` | `String` | The server path to where the Companion app is sitting. For instance, if Companion is at `example.com/companion`, then the path would be `/companion`). |
|
||||
| `implicitPath` `COMPANION_IMPLICIT_PATH` | `String` | If the URL’s path in your reverse proxy is different from your Companion path in your express app, then you need to set this path as `implicitPath`. For instance, if your Companion URL is `example.com/mypath/companion`. Where the path `/mypath` is defined in your NGINX server, while `/companion` is set in your express app. Then you need to set the option `implicitPath` to `/mypath`, and set the `path` option to `/companion`. |
|
||||
| `validHosts` `COMPANION_DOMAINS` | `Array` | If you are setting an `oauthDomain`, you need to set a list of valid hosts, so the oauth handler can validate the host of the Uppy instance requesting the authentication. This is essentially a list of valid domains running your Companion instances. The list may also contain regex patterns. e.g `['sub2.example.com', 'sub3.example.com', '(\\w+).example.com']` |
|
||||
|
||||
#### `sendSelfEndpoint` `COMPANION_SELF_ENDPOINT`
|
||||
|
||||
This is essentially the same as the `server.host + server.path` attributes. The
|
||||
major reason for this attribute is that, when set, it adds the value as the
|
||||
`i-am` header of every request response.
|
||||
|
||||
#### `providerOptions`
|
||||
|
||||
Object to enable providers with their keys and secrets. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"drive": {
|
||||
"key": "***",
|
||||
"secret": "***"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When using the standalone version you use the corresponding environment
|
||||
variables or point to a secret file (such as `COMPANION_GOOGLE_SECRET_FILE`).
|
||||
|
||||
:::note
|
||||
|
||||
Secret files need an absolute path to a file with any extension which only has
|
||||
the secret, nothing else.
|
||||
|
||||
:::
|
||||
|
||||
| Service | Key | Environment variables |
|
||||
| ------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Box | `box` | `COMPANION_BOX_KEY`, `COMPANION_BOX_SECRET`, `COMPANION_BOX_SECRET_FILE` |
|
||||
| Dropbox | `dropbox` | `COMPANION_DROPBOX_KEY`, `COMPANION_DROPBOX_SECRET`, `COMPANION_DROPBOX_SECRET_FILE` |
|
||||
| Facebook | `facebook` | `COMPANION_FACEBOOK_KEY`, `COMPANION_FACEBOOK_SECRET`, `COMPANION_FACEBOOK_SECRET_FILE` |
|
||||
| Google Drive | `drive` | `COMPANION_GOOGLE_KEY`, `COMPANION_GOOGLE_SECRET`, `COMPANION_GOOGLE_SECRET_FILE` |
|
||||
| Google Photos | `googlephotos` | `COMPANION_GOOGLE_KEY`, `COMPANION_GOOGLE_SECRET`, `COMPANION_GOOGLE_SECRET_FILE` |
|
||||
| Instagram | `instagram` | `COMPANION_INSTAGRAM_KEY`, `COMPANION_INSTAGRAM_SECRET`, `COMPANION_INSTAGRAM_SECRET_FILE` |
|
||||
| OneDrive | `onedrive` | `COMPANION_ONEDRIVE_KEY`, `COMPANION_ONEDRIVE_SECRET`, `COMPANION_ONEDRIVE_SECRET_FILE`, `COMPANION_ONEDRIVE_DOMAIN_VALIDATION` (Settings this variable to `true` enables a route that can be used to validate your app with OneDrive) |
|
||||
| Zoom | `zoom` | `COMPANION_ZOOM_KEY`, `COMPANION_ZOOM_SECRET`, `COMPANION_ZOOM_SECRET_FILE`, `COMPANION_ZOOM_VERIFICATION_TOKEN` |
|
||||
|
||||
#### `s3`
|
||||
|
||||
Companion comes with signature endpoints for AWS S3. These can be used by the
|
||||
Uppy client to sign requests to upload files directly to S3, without exposing
|
||||
secret S3 keys in the browser. Companion also supports uploading files from
|
||||
providers like Dropbox and Instagram directly into S3.
|
||||
|
||||
##### `s3.key` `COMPANION_AWS_KEY`
|
||||
|
||||
The S3 access key ID.
|
||||
|
||||
##### `s3.secret` `COMPANION_AWS_SECRET` `COMPANION_AWS_SECRET_FILE`
|
||||
|
||||
The S3 secret access key.
|
||||
|
||||
:::note
|
||||
|
||||
Using a secret file means passing an absolute path to a file with any extension,
|
||||
which has only the secret, nothing else.
|
||||
|
||||
:::
|
||||
|
||||
##### `s3.endpoint` `COMPANION_AWS_ENDPOINT`
|
||||
|
||||
Optional URL to a custom S3 (compatible) service. Otherwise uses the default
|
||||
from the AWS SDK.
|
||||
|
||||
##### `s3.bucket` `COMPANION_AWS_BUCKET`
|
||||
|
||||
The name of the bucket to store uploaded files in.
|
||||
|
||||
A `string` or function that returns the name of the bucket as a `string` and
|
||||
takes one argument which is an object with the following properties:
|
||||
|
||||
- `filename`, the original name of the uploaded file;
|
||||
- `metadata` provided by the user for the file (will only be provided during the
|
||||
initial calls for each uploaded files, otherwise it will be `undefined`).
|
||||
- `req`, Express.js `Request` object. Do not use any Companion internals from
|
||||
the req object, as these might change in any minor version of Companion.
|
||||
|
||||
#### `s3.forcePathStyle` `COMPANION_AWS_FORCE_PATH_STYLE`
|
||||
|
||||
This adds support for setting the S3 client’s `forcePathStyle` option. That is
|
||||
necessary to use Uppy/Companion alongside localstack in development
|
||||
environments. **Default**: `false`.
|
||||
|
||||
##### `s3.region` `COMPANION_AWS_REGION`
|
||||
|
||||
The datacenter region where the target bucket is located.
|
||||
|
||||
##### `COMPANION_AWS_PREFIX`
|
||||
|
||||
An optional prefix for all uploaded keys. This is a standalone-only option. The
|
||||
same can be achieved by the `getKey` option when using the express middleware.
|
||||
|
||||
##### `s3.awsClientOptions`
|
||||
|
||||
You can supply any
|
||||
[S3 option supported by the AWS SDK](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property)
|
||||
in the `providerOptions.s3.awsClientOptions` object, _except for_ the below:
|
||||
|
||||
- `accessKeyId`. Instead, use the `providerOptions.s3.key` property. This is to
|
||||
make configuration names consistent between different Companion features.
|
||||
- `secretAccessKey`. Instead, use the `providerOptions.s3.secret` property. This
|
||||
is to make configuration names consistent between different Companion
|
||||
features.
|
||||
|
||||
Be aware that some options may cause wrong behaviour if they conflict with
|
||||
Companion’s assumptions. If you find that a particular option does not work as
|
||||
expected, please
|
||||
[open an issue on the Uppy repository](https://github.com/transloadit/uppy/issues/new)
|
||||
so we can document it here.
|
||||
|
||||
##### `s3.getKey({ filename, metadata, req })`
|
||||
|
||||
Get the key name for a file. The key is the file path to which the file will be
|
||||
uploaded in your bucket. This option should be a function receiving three
|
||||
arguments:
|
||||
|
||||
- `filename`, the original name of the uploaded file;
|
||||
- `metadata`, user-provided metadata for the file.
|
||||
- `req`, Express.js `Request` object. Do not use any Companion internals from
|
||||
the req object, as these might change in any minor version of Companion.
|
||||
|
||||
This function should return a string `key`. The `req` parameter can be used to
|
||||
upload to a user-specific folder in your bucket, for example:
|
||||
|
||||
```js
|
||||
app.use(authenticationMiddleware);
|
||||
app.use(
|
||||
uppy.app({
|
||||
providerOptions: {
|
||||
s3: {
|
||||
getKey: ({ req, filename, metadata }) => `${req.user.id}/${filename}`,
|
||||
/* auth options */
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
The default implementation returns the `filename`, so all files will be uploaded
|
||||
to the root of the bucket as their original file name.
|
||||
|
||||
```js
|
||||
app.use(
|
||||
uppy.app({
|
||||
providerOptions: {
|
||||
s3: {
|
||||
getKey: ({ filename, metadata }) => filename,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
When signing on the client, this function will only be called for multipart
|
||||
uploads.
|
||||
|
||||
#### `COMPANION_AWS_USE_ACCELERATE_ENDPOINT`
|
||||
|
||||
Enable S3
|
||||
[Transfer Acceleration](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html).
|
||||
This is a standalone-only option.
|
||||
|
||||
#### `COMPANION_AWS_EXPIRES`
|
||||
|
||||
Set `X-Amz-Expires` query parameter in the presigned urls (in seconds, default:
|
||||
300\). This is a standalone-only option.
|
||||
|
||||
#### `COMPANION_AWS_ACL`
|
||||
|
||||
Set a
|
||||
[Canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl)
|
||||
for uploaded objects. This is a standalone-only option.
|
||||
|
||||
#### `customProviders`
|
||||
|
||||
This option enables you to add custom providers along with the already supported
|
||||
providers. See [adding custom providers](#how-to-add-custom-providers) for more
|
||||
information.
|
||||
|
||||
#### `logClientVersion`
|
||||
|
||||
A boolean flag to tell Companion whether to log its version upon startup.
|
||||
|
||||
#### `metrics` `COMPANION_HIDE_METRICS`
|
||||
|
||||
A boolean flag to tell Companion whether to provide an endpoint `/metrics` with
|
||||
Prometheus metrics (by default metrics are enabled.)
|
||||
|
||||
#### `streamingUpload` `COMPANION_STREAMING_UPLOAD`
|
||||
|
||||
A boolean flag to tell Companion whether to enable streaming uploads. If
|
||||
enabled, it will lead to _faster uploads_ because companion will start uploading
|
||||
at the same time as downloading using `stream.pipe`. If `false`, files will be
|
||||
fully downloaded first, then uploaded. Defaults to `true`.
|
||||
|
||||
#### `maxFileSize` `COMPANION_MAX_FILE_SIZE`
|
||||
|
||||
If this value is set, companion will limit the maximum file size to process. If
|
||||
unset, it will process files without any size limit (this is the default).
|
||||
|
||||
#### `periodicPingUrls` `COMPANION_PERIODIC_PING_URLS`
|
||||
|
||||
If this value is set, companion will periodically send POST requests to the
|
||||
specified URLs. Useful for keeping track of companion instances as a keep-alive.
|
||||
|
||||
#### `periodicPingInterval` `COMPANION_PERIODIC_PING_INTERVAL`
|
||||
|
||||
Interval for periodic ping requests (in ms).
|
||||
|
||||
#### `periodicPingStaticPayload` `COMPANION_PERIODIC_PING_STATIC_JSON_PAYLOAD`
|
||||
|
||||
A `JSON.stringify`-able JavaScript Object that will be sent as part of the JSON
|
||||
body in the period ping requests.
|
||||
|
||||
#### `allowLocalUrls` `COMPANION_ALLOW_LOCAL_URLS`
|
||||
|
||||
A boolean flag to tell Companion whether to allow requesting local URLs
|
||||
(non-internet IPs).
|
||||
|
||||
:::caution
|
||||
|
||||
Only enable this in development. **Enabling it in production is a security
|
||||
risk.**
|
||||
|
||||
:::
|
||||
|
||||
#### `corsOrigins` (required)
|
||||
|
||||
Allowed CORS Origins. Passed as the `origin` option in
|
||||
[cors](https://github.com/expressjs/cors#configuration-options).
|
||||
|
||||
Note this is used for both CORS’ `Access-Control-Allow-Origin` header, and for
|
||||
the
|
||||
[`targetOrigin`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#targetorigin)
|
||||
for `postMessage` calls in the context of OAuth.
|
||||
|
||||
Setting it to `true` treats any origin as a trusted one, making it easier to
|
||||
impersonate your brand. Setting it to `false` disables cross-origin support, use
|
||||
this if you’re serving Companion and Uppy from the same domain name.
|
||||
|
||||
##### `COMPANION_CLIENT_ORIGINS`
|
||||
|
||||
Stand-alone alternative to the `corsOrigins` option. A comma-separated string of
|
||||
origins, or `'true'` (which will be interpreted as the boolean value `true`), or
|
||||
`'false'` (which will be interpreted as the boolean value `false`).
|
||||
`COMPANION_CLIENT_ORIGINS_REGEX` will be ignored if this option is used.
|
||||
|
||||
##### `COMPANION_CLIENT_ORIGINS_REGEX`
|
||||
|
||||
:::note
|
||||
|
||||
In most cases, you should not be using a regex, and instead provide the list of
|
||||
accepted origins to `COMPANION_CLIENT_ORIGINS`. If you have to use this option,
|
||||
have in mind that this regex will be used to parse unfiltered user input, so
|
||||
make sure you’re validating the entirety of the string.
|
||||
|
||||
:::
|
||||
|
||||
Stand-alone alternative to the `corsOrigins` option. Like
|
||||
`COMPANION_CLIENT_ORIGINS`, but allows a single regex instead.
|
||||
|
||||
#### `chunkSize` `COMPANION_CHUNK_SIZE`
|
||||
|
||||
Controls how big the uploaded chunks are for AWS S3 Multipart and Tus. Smaller
|
||||
values lead to more overhead, but larger values lead to slower retries in case
|
||||
of bad network connections. Passed to tus-js-client
|
||||
[`chunkSize`](https://github.com/tus/tus-js-client/blob/master/docs/api.md#chunksize)
|
||||
as well as
|
||||
[AWS S3 Multipart](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html)
|
||||
`partSize`.
|
||||
|
||||
#### `enableUrlEndpoint` `COMPANION_ENABLE_URL_ENDPOINT`
|
||||
|
||||
Set this to `true` to enable the [URL functionalily](https://uppy.io/docs/url/).
|
||||
Default: `false`.
|
||||
|
||||
### Events
|
||||
|
||||
The object returned by `companion.app()` also has a property `companionEmitter`
|
||||
which is an `EventEmitter` that emits the following events:
|
||||
|
||||
- `upload-start` - When an upload starts, this event is emitted with an object
|
||||
containing the property `token`, which is a unique ID for the upload.
|
||||
- **token** - The event name is the token from `upload-start`. The event has an
|
||||
object with the following properties:
|
||||
- `action` - One of the following strings:
|
||||
- `success` - When the upload succeeds.
|
||||
- `error` - When the upload fails with an error.
|
||||
- `payload` - the error or success payload.
|
||||
|
||||
Example code for using the `EventEmitter` to handle a finished file upload:
|
||||
|
||||
```js
|
||||
const companionApp = companion.app(options);
|
||||
const { companionEmitter: emitter } = companionApp;
|
||||
|
||||
emitter.on('upload-start', ({ token }) => {
|
||||
console.log('Upload started', token);
|
||||
|
||||
function onUploadEvent({ action, payload }) {
|
||||
if (action === 'success') {
|
||||
emitter.off(token, onUploadEvent); // avoid listener leak
|
||||
console.log('Upload finished', token, payload.url);
|
||||
} else if (action === 'error') {
|
||||
emitter.off(token, onUploadEvent); // avoid listener leak
|
||||
console.error('Upload failed', payload);
|
||||
}
|
||||
}
|
||||
emitter.on(token, onUploadEvent);
|
||||
});
|
||||
```
|
||||
|
||||
<!--retext-simplify ignore frequently-->
|
||||
|
||||
## Frequently asked questions
|
||||
|
||||
### Do you have a live example?
|
||||
|
||||
An example server is running at <https://companion.uppy.io>.
|
||||
|
||||
### How does the Authentication and Token mechanism work?
|
||||
|
||||
This section describes how Authentication works between Companion and Providers.
|
||||
While this behaviour is the same for all Providers (Dropbox, Instagram, Google
|
||||
Drive, etc.), we are going to be referring to Dropbox in place of any Provider
|
||||
throughout this section.
|
||||
|
||||
The following steps describe the actions that take place when a user
|
||||
Authenticates and Uploads from Dropbox through Companion:
|
||||
|
||||
- The visitor to a website with Uppy clicks `Connect to Dropbox`.
|
||||
- Uppy sends a request to Companion, which in turn sends an OAuth request to
|
||||
Dropbox (Requires that OAuth credentials from Dropbox have been added to
|
||||
Companion).
|
||||
- Dropbox asks the visitor to log in, and whether the Website should be allowed
|
||||
to access your files
|
||||
- If the visitor agrees, Companion will receive a token from Dropbox, with which
|
||||
we can temporarily download files.
|
||||
- Companion encrypts the token with a secret key and sends the encrypted token
|
||||
to Uppy (client)
|
||||
- Every time the visitor clicks on a folder in Uppy, it asks Companion for the
|
||||
new list of files, with this question, the token (still encrypted by
|
||||
Companion) is sent along.
|
||||
- Companion decrypts the token, requests the list of files from Dropbox and
|
||||
sends it to Uppy.
|
||||
- When a file is selected for upload, Companion receives the token again
|
||||
according to this procedure, decrypts it again, and thereby downloads the file
|
||||
from Dropbox.
|
||||
- As the bytes arrive, Companion uploads the bytes to the final destination
|
||||
(depending on the configuration: Apache, a Tus server, S3 bucket, etc).
|
||||
- Companion reports progress to Uppy, as if it were a local upload.
|
||||
- Completed!
|
||||
|
||||
### How to use provider redirect URIs?
|
||||
|
||||
When generating your provider API keys on their corresponding developer
|
||||
platforms (e.g
|
||||
[Google Developer Console](https://console.developers.google.com/)), you’d need
|
||||
to provide a `redirect URI` for the OAuth authorization process. In general the
|
||||
redirect URI for each provider takes the format:
|
||||
|
||||
`http(s)://$YOUR_COMPANION_HOST_NAME/$PROVIDER_NAME/redirect`
|
||||
|
||||
For example, if your Companion server is hosted on
|
||||
`https://my.companion.server.com`, then the redirect URI you would supply for
|
||||
your OneDrive provider would be:
|
||||
|
||||
`https://my.companion.server.com/onedrive/redirect`
|
||||
|
||||
Please see
|
||||
[Supported Providers](https://uppy.io/docs/companion/#Supported-providers) for a
|
||||
list of all Providers and their corresponding names.
|
||||
|
||||
### How to use Companion with Kubernetes?
|
||||
|
||||
We have a detailed
|
||||
[guide](https://github.com/transloadit/uppy/blob/main/packages/%40uppy/companion/KUBERNETES.md)
|
||||
on running Companion in Kubernetes.
|
||||
|
||||
### How to add custom providers?
|
||||
|
||||
As of now, Companion supports the
|
||||
[providers listed here](https://uppy.io/docs/companion/#Supported-providers) out
|
||||
of the box, but you may also choose to add your own custom providers. You can do
|
||||
this by passing the `customProviders` option when calling the Uppy `app` method.
|
||||
The custom provider is expected to support Oauth 1 or 2 for
|
||||
authentication/authorization.
|
||||
|
||||
```javascript
|
||||
import providerModule from './path/to/provider/module';
|
||||
|
||||
const options = {
|
||||
customProviders: {
|
||||
myprovidername: {
|
||||
config: {
|
||||
authorize_url: 'https://mywebsite.com/authorize',
|
||||
access_url: 'https://mywebsite.com/token',
|
||||
oauth: 2,
|
||||
key: '***',
|
||||
secret: '***',
|
||||
scope: ['read', 'write'],
|
||||
},
|
||||
module: providerModule,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
uppy.app(options);
|
||||
```
|
||||
|
||||
The `customProviders` option should be an object containing each custom
|
||||
provider. Each custom provider would, in turn, be an object with two keys,
|
||||
`config` and `module`. The `config` option would contain Oauth API settings,
|
||||
while the `module` would point to the provider module.
|
||||
|
||||
To work well with Companion, the **module** must be a class with the following
|
||||
methods. Note that the methods must be `async`, return a `Promise` or reject
|
||||
with an `Error`):
|
||||
|
||||
1. `async list ({ token, directory, query })` - Returns a object containing a
|
||||
list of user files (such as a list of all the files in a particular
|
||||
directory). See [example returned list data structure](#list-data). `token` -
|
||||
authorization token (retrieved from oauth process) to send along with your
|
||||
request
|
||||
- `directory` - the id/name of the directory from which data is to be
|
||||
retrieved. This may be ignored if it doesn’t apply to your provider
|
||||
- `query` - expressjs query params object received by the server (in case
|
||||
some data you need in there).
|
||||
2. `async download ({ token, id, query })` - Downloads a particular file from
|
||||
the provider. Returns an object with a single property `{ stream }` - a
|
||||
[`stream.Readable`](https://nodejs.org/api/stream.html#stream_class_stream_readable),
|
||||
which will be read from and uploaded to the destination. To prevent memory
|
||||
leaks, make sure you release your stream if you reject this method with an
|
||||
error.
|
||||
- `token` - authorization token (retrieved from oauth process) to send along
|
||||
with your request.
|
||||
- `id` - ID of the file being downloaded.
|
||||
- `query` - expressjs query params object received by the server (in case
|
||||
some data you need in there).
|
||||
3. `async size ({ token, id, query })` - Returns the byte size of the file that
|
||||
needs to be downloaded as a `Number`. If the size of the object is not known,
|
||||
`null` may be returned.
|
||||
- `token` - authorization token (retrieved from oauth process) to send along
|
||||
with your request.
|
||||
- `id` - ID of the file being downloaded.
|
||||
- `query` - expressjs query params object received by the server (in case
|
||||
some data you need in there).
|
||||
|
||||
The class must also have:
|
||||
|
||||
- A unique `static authProvider` string property - a lowercased value which
|
||||
indicates name of the [`grant`](https://github.com/simov/grant) OAuth2
|
||||
provider to use (e.g `google` for Google). If your provider doesn’t use
|
||||
OAuth2, you can omit this property.
|
||||
- A `static` property `static version = 2`, which is the current version of the
|
||||
Companion Provider API.
|
||||
|
||||
See also
|
||||
[example code with a custom provider](https://github.com/transloadit/uppy/blob/main/examples/custom-provider/server).
|
||||
|
||||
#### list data
|
||||
|
||||
```json
|
||||
{
|
||||
// username or email of the user whose provider account is being accessed
|
||||
"username": "johndoe",
|
||||
// list of files and folders in the directory. An item is considered a folder
|
||||
// if it mainly exists as a collection to contain sub-items
|
||||
"items": [
|
||||
{
|
||||
// boolean value of whether or NOT it's a folder
|
||||
"isFolder": false,
|
||||
// icon image URL
|
||||
"icon": "https://random-api.url.com/fileicon.jpg",
|
||||
// name of the item
|
||||
"name": "myfile.jpg",
|
||||
// the mime type of the item. Only relevant if the item is NOT a folder
|
||||
"mimeType": "image/jpg",
|
||||
// the id (in string) of the item
|
||||
"id": "uniqueitemid",
|
||||
// thumbnail image URL. Only relevant if the item is NOT a folder
|
||||
"thumbnail": "https://random-api.url.com/filethumbnail.jpg",
|
||||
// for folders this is typically the value that will be passed as "directory" in the list(...) method.
|
||||
// For files, this is the value that will be passed as id in the download(...) method.
|
||||
"requestPath": "file-or-folder-requestpath",
|
||||
// datetime string (in ISO 8601 format) of when this item was last modified
|
||||
"modifiedDate": "2020-06-29T19:59:58Z",
|
||||
// the size in bytes of the item. Only relevant if the item is NOT a folder
|
||||
"size": 278940,
|
||||
"custom": {
|
||||
// an object that may contain some more custom fields that you may need to send to the client. Only add this object if you have a need for it.
|
||||
"customData1": "the value",
|
||||
"customData2": "the value"
|
||||
}
|
||||
// more items here
|
||||
}
|
||||
],
|
||||
// if the "items" list is paginated, this is the request path needed to fetch the next page.
|
||||
"nextPagePath": "directory-name?cursor=cursor-to-next-page"
|
||||
}
|
||||
```
|
||||
|
||||
### How to run Companion locally?
|
||||
|
||||
1. To set up Companion for local development, please clone the Uppy repo and
|
||||
install, like so:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/transloadit/uppy
|
||||
cd uppy
|
||||
yarn install
|
||||
```
|
||||
|
||||
2. Configure your environment variables by copying the `env.example.sh` file to
|
||||
`env.sh` and edit it to its correct values.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
$EDITOR .env
|
||||
```
|
||||
|
||||
3. To start the server, run:
|
||||
|
||||
```bash
|
||||
yarn run start:companion
|
||||
```
|
||||
|
||||
This would get the Companion instance running on `http://localhost:3020`. It
|
||||
uses [`node --watch`](https://nodejs.org/api/cli.html#--watch) so it will
|
||||
automatically restart when files are changed.
|
||||
|
||||
[box]: /docs/box
|
||||
[dropbox]: /docs/dropbox
|
||||
[facebook]: /docs/facebook
|
||||
[googledrive]: /docs/google-drive
|
||||
[googlephotos]: /docs/google-photos
|
||||
[instagram]: /docs/instagram
|
||||
[onedrive]: /docs/onedrive
|
||||
[unsplash]: /docs/unsplash
|
||||
[url]: /docs/url
|
||||
[zoom]: /docs/zoom
|
||||
[transloadit]: https://transloadit.com
|
||||
[template-credentials]:
|
||||
https://transloadit.com/docs/#how-to-create-template-credentials
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
---
|
||||
sidebar_position: 12
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Compressor
|
||||
|
||||
The `@uppy/compressor` plugin optimizes images (JPEG, PNG, and any other format
|
||||
supported by the client’s browser) before upload, saving up to 60% in size
|
||||
(roughly 18 MB for 10 images). It uses [Compressor.js][] library under the hood.
|
||||
|
||||
## When should I use it?
|
||||
|
||||
When your users are likely to upload images, potentially on mobile devices, and
|
||||
saving data and faster uploads are important.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/compressor
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/compressor
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Compressor } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Compressor, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js {7} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Compressor from '@uppy/compressor';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, {inline:true, target: '#dashboard')
|
||||
.use(Compressor);
|
||||
```
|
||||
|
||||
No action is needed from the user — Uppy will automatically optimize images,
|
||||
show an [Informer](/docs/informer) message with saved bytes, and then begin the
|
||||
upload as usual.
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
:::tip
|
||||
|
||||
You can also pass any of the [Compressor.js options][] here as well.
|
||||
|
||||
:::
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Compressor'`).
|
||||
|
||||
#### `quality`
|
||||
|
||||
The quality of the output image passed to [Compressor.js][] (`number`, default:
|
||||
`0.6`).
|
||||
|
||||
It must be a number between `0` and `1`. Be careful to use `1` as it may make
|
||||
the size of the output image become larger. In most cases, going with the
|
||||
default value is best.
|
||||
|
||||
:::note
|
||||
|
||||
This option is only available for `image/jpeg` and `image/webp` images.
|
||||
|
||||
:::
|
||||
|
||||
#### `limit`
|
||||
|
||||
Number of images that will be compressed in parallel (`number`, default: `10`).
|
||||
|
||||
You likely don’t need to change this, unless you are experiencing performance
|
||||
issues.
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
// Shown in the Status Bar
|
||||
compressingImages: 'Compressing images...',
|
||||
compressedX: 'Saved %{size} by compressing images',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
#### `compressor:complete`
|
||||
|
||||
The event is emitted when all files are compressed. You can use it for side
|
||||
effects or custom UI notifications.
|
||||
|
||||
[compressor.js]: https://github.com/fengyuanchen/compressorjs
|
||||
[compressor.js options]: https://github.com/fengyuanchen/compressorjs#options
|
||||
181
docs/form.mdx
|
|
@ -1,181 +0,0 @@
|
|||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Form
|
||||
|
||||
The `@uppy/form` plugin integrates with an existing HTML `<form>` element to
|
||||
extract input data from it, and send along with the Uppy upload. It then appends
|
||||
the upload result back to the form via a hidden input.
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you have an existing HTML `<form>` element and you would like to:
|
||||
|
||||
- **Attach the form input values to the files.** This is useful if you want to
|
||||
associate meta data from the form (for example, name, location, id) with the
|
||||
uploaded file, so you can process it on the backend. `@uppy/form` extracts the
|
||||
input values before uploading/processing files and adds them to Uppy meta data
|
||||
state (`uppy.state.meta`), as well as and each file’s meta, and appends to the
|
||||
upload in an object with `[file input name attribute]` -> `[file input value]`
|
||||
key/values.
|
||||
- **Upload the files and put the response (such as the file URLs) into a hidden
|
||||
field** (`<input type="hidden" name="uppyResult">`). Then you can POST and
|
||||
handle the form yourself. The appended result is a stringified version of a
|
||||
result returned from calling `uppy.upload()` or listening to `complete` event.
|
||||
- **Automatically start the file upload on submit or submit the form after file
|
||||
upload.** This is off by default. See [`submitOnSuccess`](#submitOnSuccess)
|
||||
and [`triggerUploadOnSubmit`](#triggerUploadOnSubmit) options respectively for
|
||||
details.
|
||||
|
||||
:::note
|
||||
|
||||
If you are using a UI framework or library like React, Vue or Svelte, you’ll
|
||||
most likely handle form data there as well, and thus won’t need this plugin.
|
||||
Instead, pass meta data to Uppy via [`uppy.setMeta()`](/docs/uppy#setmetadata)
|
||||
and listen to [`uppy.on('complete')`](/docs/uppy#complete) to get the upload
|
||||
results back.
|
||||
|
||||
:::
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/form
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/form
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Form } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Form, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js title="app.js"
|
||||
import Uppy from '@uppy/core';
|
||||
import Form from '@uppy/form';
|
||||
import DragDrop from '@uppy/drag-drop';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/drag-drop/dist/style.min.css';
|
||||
|
||||
new Uppy().use(Form, {
|
||||
target: '#my-form',
|
||||
});
|
||||
```
|
||||
|
||||
```html title="index.html"
|
||||
<form id="my-form" action="/submit" method="get">
|
||||
<label for="name">Enter your name: </label>
|
||||
<input type="text" name="name" required />
|
||||
|
||||
<label for="dob">Date of birth: </label>
|
||||
<input type="date" name="dob" />
|
||||
|
||||
<input type="submit" value="Save" />
|
||||
</form>
|
||||
```
|
||||
|
||||
By default the code above will:
|
||||
|
||||
1. Extract meta data from the form `#my-form`.
|
||||
2. Send it with the Uppy upload.
|
||||
3. Those fields will then be added to Uppy meta data state (`uppy.state.meta`)
|
||||
and each file’s meta, and appended as (meta)data to the upload in an object
|
||||
with `[file input name attribute]` -> `[file input value]` key/values.
|
||||
4. When Uppy completes upload/processing, it will add an
|
||||
`<input type="hidden" name="uppyResult">` with the stringified upload result
|
||||
object back to the form.
|
||||
|
||||
:::note
|
||||
|
||||
You can disable both of these features, see options below.
|
||||
|
||||
:::
|
||||
|
||||
:::tip
|
||||
|
||||
`@uppy/form` can also start Uppy upload automatically once the form is
|
||||
submitted, and even submit the form after the upload is complete. This is off by
|
||||
default. See [`triggerUploadOnSubmit`](#triggerUploadOnSubmit) and
|
||||
[`submitOnSuccess`](#submitOnSuccess) options below for details.
|
||||
|
||||
:::
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Form'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element or CSS selector for the form element (`string` or `Element`,
|
||||
default: `null`).
|
||||
|
||||
This is required for the plugin to work.
|
||||
|
||||
#### `resultName`
|
||||
|
||||
The `name` attribute for the `<input type="hidden">` where the result will be
|
||||
added (`string`, default: `uppyResult`).
|
||||
|
||||
#### `getMetaFromForm`
|
||||
|
||||
Configures whether to extract metadata from the form (`boolean`, default:
|
||||
`true`).
|
||||
|
||||
When set to `true`, the Form plugin will extract all fields from a `<form>`
|
||||
element before upload begins. Those fields will then be added to Uppy meta data
|
||||
state (`uppy.state.meta`) and each file’s meta, and appended as (meta)data to
|
||||
the upload in an object with `[file input name attribute]` ->
|
||||
`[file input value]` key/values.
|
||||
|
||||
#### `addResultToForm`
|
||||
|
||||
Configures whether to add upload/encoding results back to the form in an
|
||||
`<input name="uppyResult" type="hidden">` element (`boolean`, default: `true`).
|
||||
|
||||
#### `triggerUploadOnSubmit`
|
||||
|
||||
Configures whether to start the upload when the form is submitted (`boolean`,
|
||||
default: `false`).
|
||||
|
||||
When a user submits the form (via a submit button, the <kbd>Enter</kbd> key or
|
||||
otherwise), this option will prevent form submission, and instead upload files
|
||||
via Uppy. Then you could:
|
||||
|
||||
- Set `submitOnSuccess: true` if you need the form to _actually_ be submitted
|
||||
once all files have been uploaded.
|
||||
- Listen for `uppy.on('complete')` event to do something else if the file
|
||||
uploads are all you need. For example, if the form is used for file metadata
|
||||
only.
|
||||
|
||||
#### `submitOnSuccess`
|
||||
|
||||
Configures whether to submit the form after Uppy finishes uploading/encoding
|
||||
(`boolean`, default: `false`).
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"label": "Framework integrations",
|
||||
"position": 8
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
---
|
||||
slug: /angular
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Angular
|
||||
|
||||
[Angular][] components for Uppy UI plugins.
|
||||
|
||||
## When should I use it?
|
||||
|
||||
When you are using the Angular framework and you would like to use one of the UI
|
||||
components.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/angular
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/angular
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::note
|
||||
|
||||
You also need to install the UI plugin you want to use. For instance,
|
||||
`@uppy/dashboard`.
|
||||
|
||||
:::
|
||||
|
||||
## Use
|
||||
|
||||
Instead of adding a UI plugin to an Uppy instance with `.use()`, the Uppy
|
||||
instance can be passed into components as a `props` prop.
|
||||
|
||||
The following plugins are available as Angular component wrappers:
|
||||
|
||||
- Import `UppyAngularDashboardModule` used as `<uppy-dashboard>` renders
|
||||
[`@uppy/dashboard`](/docs/dashboard)
|
||||
- Import `UppyAngularDashboardModalModule` used as `<uppy-dashboard-modal>`
|
||||
renders [`@uppy/dashboard`](/docs/dashboard) as a modal
|
||||
- Import `UppyAngularProgressBarModule` used as `<uppy-progress-bar>` renders
|
||||
[`@uppy/progress-bar`](/docs/progress-bar)
|
||||
- Import `UppyAngularStatusBarModule` used as `<uppy-status-bar>` renders
|
||||
[`@uppy/status-bar`](/docs/status-bar)
|
||||
- Import `UppyAngularDragDropModule` used as `<uppy-drag-drop>` renders
|
||||
[`@uppy/drag-drop`](/docs/drag-drop)
|
||||
|
||||
Each component takes a `props` prop that will be passed to the UI Plugin.
|
||||
|
||||
```typescript title="app.module.ts" showLineNumbers
|
||||
import { NgModule } from '@angular/core';
|
||||
import { UppyAngularDashboardModule } from '@uppy/angular';
|
||||
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AppComponent],
|
||||
imports: [BrowserModule, UppyAngularDashboardModule],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
class {}
|
||||
```
|
||||
|
||||
```html title="app.component.html" showLineNumbers
|
||||
<uppy-dashboard [uppy]="uppy"> </uppy-dashboard>
|
||||
```
|
||||
|
||||
You should initialize Uppy as a property of your component.
|
||||
|
||||
```typescript title="app.component.ts" showLineNumbers
|
||||
import { Component } from '@angular/core';
|
||||
import { Uppy } from '@uppy/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
})
|
||||
export class AppComponent {
|
||||
uppy: Uppy = new Uppy({ debug: true, autoProceed: true });
|
||||
}
|
||||
```
|
||||
|
||||
### CSS
|
||||
|
||||
All components have their own styling and should be added to your component
|
||||
decorator. You can find the CSS import statements in the docs of the UI plugin
|
||||
you want to use. For instance, for `@uppy/dashboard`:
|
||||
|
||||
```typescript
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
//...
|
||||
@Component({
|
||||
// ...
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
styleUrls: [
|
||||
'../node_modules/@uppy/core/dist/style.css',
|
||||
'../node_modules/@uppy/dashboard/dist/style.css',
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
[angular]: https://angular.io
|
||||
|
|
@ -1,244 +0,0 @@
|
|||
---
|
||||
slug: /react
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# React
|
||||
|
||||
[React][] components for the Uppy UI plugins and hooks.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/react
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/react
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::note
|
||||
|
||||
You also need to install the UI plugin you want to use. For instance,
|
||||
`@uppy/dashboard`.
|
||||
|
||||
:::
|
||||
|
||||
## Use
|
||||
|
||||
`@uppy/react` exposes component wrappers for `Dashboard`, `DragDrop`, and all
|
||||
other UI elements. The components can be used with either [React][] or
|
||||
API-compatible alternatives such as [Preact][].
|
||||
|
||||
:::caution
|
||||
|
||||
If you find yourself writing many instances of `useState` and `useEffect` to
|
||||
achieve something with Uppy in React, you are most likely breaking React best
|
||||
practices. Consider reading
|
||||
“[You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)”
|
||||
and looking at our examples below.
|
||||
|
||||
:::
|
||||
|
||||
### Components
|
||||
|
||||
The following components are exported from `@uppy/react`:
|
||||
|
||||
- `<Dashboard />` renders [`@uppy/dashboard`](/docs/dashboard)
|
||||
- `<DashboardModal />` renders [`@uppy/dashboard`](/docs/dashboard) as a modal
|
||||
- `<DragDrop />` renders [`@uppy/drag-drop`](/docs/drag-drop)
|
||||
- `<ProgressBar />` renders [`@uppy/progress-bar`](/docs/progress-bar)
|
||||
- `<StatusBar />` renders [`@uppy/status-bar`](/docs/status-bar)
|
||||
|
||||
### Hooks
|
||||
|
||||
#### `useUppyState(uppy, selector)`
|
||||
|
||||
Use this hook when you need to access Uppy’s state reactively. Most of the
|
||||
times, this is needed if you are building a custom UI for Uppy in React.
|
||||
|
||||
```js
|
||||
// IMPORTANT: passing an initializer function to prevent Uppy from being reinstantiated on every render.
|
||||
const [uppy] = useState(() => new Uppy());
|
||||
|
||||
const files = useUppyState(uppy, (state) => state.files);
|
||||
const totalProgress = useUppyState(uppy, (state) => state.totalProgress);
|
||||
// We can also get specific plugin state.
|
||||
// Note that the value on `plugins` depends on the `id` of the plugin.
|
||||
const metaFields = useUppyState(
|
||||
uppy,
|
||||
(state) => state.plugins?.Dashboard?.metaFields,
|
||||
);
|
||||
```
|
||||
|
||||
You can see all the values you can access on the
|
||||
[`State`](https://github.com/transloadit/uppy/blob/c45407d099d87e25cecaf03c5d9ce59c582ca0dc/packages/%40uppy/core/src/Uppy.ts#L155-L181)
|
||||
type. If you are accessing plugin state, you would have to look at the types of
|
||||
the plugin.
|
||||
|
||||
#### `useUppyEvent(uppy, event, callback)`
|
||||
|
||||
Listen to Uppy events in a React component.
|
||||
|
||||
The first item in the array is an array of results from the event. Depending on
|
||||
the event, that can be empty or have up to three values. The second item is a
|
||||
function to clear the results. Values remain in state until the next event (if
|
||||
that ever comes). Depending on your use case, you may want to keep the values in
|
||||
state or clear the state after something else happenend.
|
||||
|
||||
```ts
|
||||
// IMPORTANT: passing an initializer function to prevent Uppy from being reinstantiated on every render.
|
||||
const [uppy] = useState(() => new Uppy());
|
||||
|
||||
const [results, clearResults] = useUppyEvent(uppy, 'transloadit:result');
|
||||
const [stepName, result, assembly] = results; // strongly typed
|
||||
|
||||
useUppyEvent(uppy, 'cancel-all', clearResults);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Example: basic component
|
||||
|
||||
Here we have a basic component which ties Uppy’s state to the component. This
|
||||
means you can render multiple instances. But be aware that as your component
|
||||
unmounts, for instance because the user navigates to a different page, Uppy’s
|
||||
state will be lost and uploads will stop.
|
||||
|
||||
:::note
|
||||
|
||||
If you render multiple instances of Uppy, make sure to give each instance a
|
||||
unique `id`.
|
||||
|
||||
:::
|
||||
|
||||
```js
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Uppy from '@uppy/core';
|
||||
import Webcam from '@uppy/webcam';
|
||||
import { Dashboard } from '@uppy/react';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
import '@uppy/webcam/dist/style.min.css';
|
||||
|
||||
function Component() {
|
||||
// IMPORTANT: passing an initializer function to prevent Uppy from being reinstantiated on every render.
|
||||
const [uppy] = useState(() => new Uppy().use(Webcam));
|
||||
|
||||
return <Dashboard uppy={uppy} />;
|
||||
}
|
||||
```
|
||||
|
||||
### Example: keep Uppy state and uploads while navigating between pages
|
||||
|
||||
When you want Uppy’s state to persist and keep uploads running between pages,
|
||||
you can
|
||||
[lift the state up](https://react.dev/learn/sharing-state-between-components#lifting-state-up-by-example).
|
||||
|
||||
```js
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Uppy from '@uppy/core';
|
||||
import { Dashboard } from '@uppy/react';
|
||||
|
||||
function Page1() {
|
||||
// ...
|
||||
}
|
||||
|
||||
function Page2({ uppy }) {
|
||||
return (
|
||||
<>
|
||||
<p>{totalProgress}</p>
|
||||
<Dashboard id="dashboard" uppy={uppy} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
// keeping the uppy instance alive above the pages the user can switch during uploading
|
||||
const [uppy] = useState(() => new Uppy());
|
||||
|
||||
return (
|
||||
// Add your router here
|
||||
<>
|
||||
<Page1 />
|
||||
<Page2 uppy={uppy} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Example: updating Uppy’s options dynamically based on props
|
||||
|
||||
```js
|
||||
// ...
|
||||
function Component(props) {
|
||||
// IMPORTANT: passing an initializer function to prevent the state from recreating.
|
||||
const [uppy] = useState(() => new Uppy().use(Webcam));
|
||||
|
||||
useEffect(() => {
|
||||
uppy.setOptions({ restrictions: props.restrictions });
|
||||
}, [props.restrictions]);
|
||||
|
||||
useEffect(() => {
|
||||
uppy.getPlugin('Webcam').setOptions({ modes: props.webcamModes });
|
||||
}, [props.webcamModes]);
|
||||
|
||||
return <Dashboard uppy={uppy} />;
|
||||
}
|
||||
```
|
||||
|
||||
### Example: dynamic params and signature for Transloadit
|
||||
|
||||
When you go to production always make sure to set the `signature`. **Not using
|
||||
[Signature Authentication](https://transloadit.com/docs/topics/signature-authentication/)
|
||||
can be a security risk**. Signature Authentication is a security measure that
|
||||
can prevent outsiders from tampering with your Assembly Instructions.
|
||||
|
||||
Generating a signature should be done on the server to avoid leaking secrets. In
|
||||
React, this could get awkward with a `fetch` in a `useEffect` and setting it to
|
||||
`useState`. Instead, it’s easier to use the
|
||||
[`assemblyOptions`](/docs/transloadit#assemblyoptions) option to `fetch` the
|
||||
params.
|
||||
|
||||
```js
|
||||
// ...
|
||||
function createUppy(userId) {
|
||||
return new Uppy({ meta: { userId } }).use(Transloadit, {
|
||||
async assemblyOptions(file) {
|
||||
// You can send meta data along for use in your template.
|
||||
// https://transloadit.com/docs/topics/assembly-instructions/#form-fields-in-instructions
|
||||
const body = JSON.stringify({ userId: file.meta.userId });
|
||||
const res = await fetch('/transloadit-params', { method: 'POST', body });
|
||||
return response.json();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function Component({ userId }) {
|
||||
// IMPORTANT: passing an initializer function to prevent Uppy from being reinstantiated on every render.
|
||||
const [uppy] = useState(() => createUppy(userId));
|
||||
|
||||
useEffect(() => {
|
||||
if (userId) {
|
||||
// Adding to global `meta` will add it to every file.
|
||||
uppy.setOptions({ meta: { userId } });
|
||||
}
|
||||
}, [uppy, userId]);
|
||||
}
|
||||
```
|
||||
|
||||
[react]: https://facebook.github.io/react
|
||||
[preact]: https://preactjs.com/
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
---
|
||||
slug: /svelte
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Svelte
|
||||
|
||||
[Svelte][] components for the Uppy UI plugins.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/svelte
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/svelte
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::note
|
||||
|
||||
You also need to install the UI plugin you want to use. For instance,
|
||||
`@uppy/dashboard`.
|
||||
|
||||
:::
|
||||
|
||||
## Use
|
||||
|
||||
The following plugins are available as Svelte component wrappers:
|
||||
|
||||
- `<Dashboard />` renders [`@uppy/dashboard`](/docs/dashboard)
|
||||
- `<DashboardModal />` renders [`@uppy/dashboard`](/docs/dashboard) as a modal
|
||||
- `<DragDrop />` renders [`@uppy/drag-drop`](/docs/drag-drop)
|
||||
- `<ProgressBar />` renders [`@uppy/progress-bar`](/docs/progress-bar)
|
||||
- `<StatusBar />` renders [`@uppy/status-bar`](/docs/status-bar)
|
||||
|
||||
Instead of adding a UI plugin to an Uppy instance with `.use()`, the Uppy
|
||||
instance can be passed into components as an `uppy` prop. Due to the way Svelte
|
||||
handles reactivity, you can initialize Uppy the same way you would with vanilla
|
||||
JavaScript.
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { Dashboard } from '@uppy/svelte';
|
||||
import Uppy from '@uppy/core';
|
||||
import Webcam from '@uppy/webcam';
|
||||
|
||||
// Don't forget the CSS: core and UI components + plugins you are using
|
||||
import '@uppy/core/dist/style.css';
|
||||
import '@uppy/dashboard/dist/style.css';
|
||||
import '@uppy/webcam/dist/style.css';
|
||||
|
||||
const uppy = new Uppy().use(Webcam);
|
||||
</script>
|
||||
|
||||
<main><Dashboard uppy={uppy} /></main>
|
||||
```
|
||||
|
||||
[svelte]: https://svelte.dev
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
---
|
||||
slug: /vue
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# Vue
|
||||
|
||||
[Vue][] components for the Uppy UI plugins.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/vue
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/vue
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::note
|
||||
|
||||
You also need to install the UI plugin you want to use. For instance,
|
||||
`@uppy/dashboard`.
|
||||
|
||||
:::
|
||||
|
||||
## Use
|
||||
|
||||
The following plugins are available as Vue component wrappers:
|
||||
|
||||
- `<Dashboard />` renders [`@uppy/dashboard`](/docs/dashboard) inline
|
||||
- `<DashboardModal />` renders [`@uppy/dashboard`](/docs/dashboard) as a modal
|
||||
- `<DragDrop />` renders [`@uppy/drag-drop`](/docs/drag-drop)
|
||||
- `<ProgressBar />` renders [`@uppy/progress-bar`](/docs/progress-bar)
|
||||
- `<StatusBar />` renders [`@uppy/status-bar`](/docs/status-bar)
|
||||
|
||||
Instead of adding a UI plugin to an Uppy instance with `.use()`, the Uppy
|
||||
instance can be passed into components as an `uppy` prop. Due to the way Vue
|
||||
handles reactivity, you can initialize Uppy the same way you would with vanilla
|
||||
JavaScript.
|
||||
|
||||
```html
|
||||
<script>
|
||||
import { Dashboard } from '@uppy/vue';
|
||||
import Uppy from '@uppy/core';
|
||||
import Webcam from '@uppy/webcam';
|
||||
|
||||
// Don't forget the CSS: core and UI components + plugins you are using
|
||||
import '@uppy/core/dist/style.css';
|
||||
import '@uppy/dashboard/dist/style.css';
|
||||
import '@uppy/webcam/dist/style.css';
|
||||
|
||||
const uppy = new Uppy().use(Webcam);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dashboard :uppy="uppy" />
|
||||
</template>
|
||||
```
|
||||
|
||||
[vue]: https://vuejs.org
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
---
|
||||
sidebar_position: 11
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Golden Retriever
|
||||
|
||||
The `@uppy/golden-retriever` plugin saves selected files in your browser cache,
|
||||
so that if the browser crashes, or the user accidentally closes the tab, Uppy
|
||||
can restore everything and continue uploading as if nothing happened. You can
|
||||
read more about it
|
||||
[on our blog](https://uppy.io/blog/2017/07/golden-retriever/).
|
||||
|
||||
The Golden Retriever uses three methods of browser data storage:
|
||||
|
||||
- `LocalStorage` to store file metadata and Uppy state only.
|
||||
- `IndexedDB` for small files, usually under 5MiB.
|
||||
- `Service Worker` (_optional_) for _all_ files because, unlike `IndexedDB`,
|
||||
`Service Worker` can keep references to large files. Service Worker storage is
|
||||
_quite_ temporary though, and doesn’t persist across browser crashes or
|
||||
restarts. It works well, however, for accidental refreshes or closed tabs.
|
||||
|
||||
Upon restore, the plugin first restores state from `LocalStorage` and then
|
||||
checks both file storages — `IndexedDB` and `ServiceWorker` — merging the
|
||||
results.
|
||||
|
||||
If restore is unsuccessful for certain files, they will be marked as “ghosts” in
|
||||
the Dashboard UI, and a message + button offering to re-select those files will
|
||||
be displayed.
|
||||
|
||||
Checkout the
|
||||
[storage quotas](https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria#other_web_technologies)
|
||||
on MDN to see how much data can be stored depending on the device and browser.
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want extra insurance for the user-selected files. If you feel like
|
||||
users might spend some time picking files, tweaking descriptions etc, and will
|
||||
not appreciate having to do it over again if something crashes. Another use case
|
||||
might be when you think user might want to pick a few files, navigate away to do
|
||||
something else in your app or otherwise, and then come back to the same
|
||||
selection.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/golden-retriever
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/golden-retriever
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, GoldenRetriever } from "{{UPPY_JS_URL}}"
|
||||
new Uppy().use(GoldenRetriever)
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js {7} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import GoldenRetriever from '@uppy/golden-retriever';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, {inline:true, target: '#dashboard')
|
||||
.use(GoldenRetriever);
|
||||
```
|
||||
|
||||
By default, Golden Retriever will only use the `IndexedDB` storage, which is
|
||||
good enough for files up to 5 MiB. `Service Worker` is optional and requires
|
||||
setup.
|
||||
|
||||
### Enabling Service Worker
|
||||
|
||||
With the Service Worker storage, Golden Retriever will be able to temporary
|
||||
store references to large files.
|
||||
|
||||
1. Bundle your own service worker `sw.js` file with Uppy GoldenRetriever’s
|
||||
service worker.
|
||||
|
||||
:::tip
|
||||
|
||||
For Webpack, check out
|
||||
[serviceworker-webpack-plugin](https://github.com/oliviertassinari/serviceworker-webpack-plugin).
|
||||
|
||||
:::
|
||||
|
||||
```js title="sw.js"
|
||||
import '@uppy/golden-retriever/lib/ServiceWorker';
|
||||
```
|
||||
|
||||
2. Register it in your app’s entry point:
|
||||
|
||||
```js
|
||||
// you app.js entry point
|
||||
uppy.use(GoldenRetriever, { serviceWorker: true });
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker
|
||||
.register('/sw.js') // path to your bundled service worker with GoldenRetriever service worker
|
||||
.then((registration) => {
|
||||
console.log(
|
||||
'ServiceWorker registration successful with scope: ',
|
||||
registration.scope,
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(`Registration failed with ${error}`);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Voilà, that’s it. Happy retrieving!
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'GoldenRetriever'`).
|
||||
|
||||
#### `expires`
|
||||
|
||||
How long to store metadata and files for. Used for `LocalStorage` and
|
||||
`IndexedDB` (`number`, default: `24 * 60 * 60 * 1000` // 24 hours).
|
||||
|
||||
#### `serviceWorker`
|
||||
|
||||
Whether to enable `Service Worker` storage (`boolean`, default: `false`).
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"label": "Guides",
|
||||
"position": 2
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
---
|
||||
sidebar_position: 7
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Supporting IE11
|
||||
|
||||
We officially support recent versions of Chrome, Firefox, Safari and Edge.
|
||||
|
||||
Internet Explorer is not officially supported, as in we don’t run tests for it,
|
||||
but you can be mostly confident it works with the right polyfills. But it does
|
||||
come with a risk of unexpected results in styling or functionality.
|
||||
|
||||
## Polyfills
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install core-js whatwg-fetch abortcontroller-polyfill md-gum-polyfill resize-observer-polyfill
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add core-js whatwg-fetch abortcontroller-polyfill md-gum-polyfill resize-observer-polyfill
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
```js
|
||||
import 'core-js';
|
||||
import 'whatwg-fetch';
|
||||
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch';
|
||||
// Order matters: AbortController needs fetch which needs Promise (provided by core-js).
|
||||
|
||||
import 'md-gum-polyfill';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
window.ResizeObserver ??= ResizeObserver;
|
||||
|
||||
export { default } from '@uppy/core';
|
||||
export * from '@uppy/core';
|
||||
```
|
||||
|
||||
## Legacy CDN bundle
|
||||
|
||||
<UppyCdnExample uppyJsName="uppy.legacy.min.js">
|
||||
{`
|
||||
import { Uppy, DragDrop, Tus } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(DragDrop, { target: '#uppy' })
|
||||
uppy.use(Tus, { endpoint: '//tusd.tusdemo.net/files/' })
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
|
|
@ -1,382 +0,0 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Building plugins
|
||||
|
||||
You can find already a few useful Uppy plugins out there, but there might come a
|
||||
time when you will want to build your own. Plugins can hook into the upload
|
||||
process or render a custom UI, typically to:
|
||||
|
||||
- Render some custom UI element, such as [StatusBar](/docs/status-bar) or
|
||||
[Dashboard](/docs/dashboard).
|
||||
- Do the actual uploading, such as [XHRUpload](/docs/xhr-upload) or
|
||||
[Tus](/docs/tus).
|
||||
- Do work before the upload, like compressing an image or calling external API.
|
||||
- Interact with a third-party service to process uploads correctly, such as
|
||||
[Transloadit](/docs/transloadit) or [AwsS3](/docs/aws-s3).
|
||||
|
||||
See a [full example of a plugin](#example-of-a-custom-plugin) below.
|
||||
|
||||
## Creating A Plugin
|
||||
|
||||
Uppy has two classes to create plugins with. `BasePlugin` for plugins that don’t
|
||||
need a user interface, and `UIPlugin` for ones that do. Each plugin has an `id`
|
||||
and a `type`. `id`s are used to uniquely identify plugins. A `type` can be
|
||||
anything—some plugins use `type`s to decide whether to do something to some
|
||||
other plugin. For example, when targeting plugins at the built-in `Dashboard`
|
||||
plugin, the Dashboard uses the `type` to figure out where to mount different UI
|
||||
elements. `'acquirer'`-type plugins are mounted into the tab bar, while
|
||||
`'progressindicator'`-type plugins are mounted into the progress bar area.
|
||||
|
||||
The plugin constructor receives the Uppy instance in the first parameter, and
|
||||
any options passed to `uppy.use()` in the second parameter.
|
||||
|
||||
```js
|
||||
import BasePlugin from '@uppy/core';
|
||||
|
||||
export default class MyPlugin extends BasePlugin {
|
||||
constructor(uppy, opts) {
|
||||
super(uppy, opts);
|
||||
this.id = opts.id || 'MyPlugin';
|
||||
this.type = 'example';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Methods
|
||||
|
||||
Plugins can define methods to execute certain tasks. The most important method
|
||||
is `install()`, which is called when a plugin is `.use`d.
|
||||
|
||||
All the below methods are optional! Only define the methods you need.
|
||||
|
||||
### `BasePlugin`
|
||||
|
||||
#### `install()`
|
||||
|
||||
Called when the plugin is `.use`d. Do any setup work here, like attaching events
|
||||
or adding [upload hooks](#Upload-Hooks).
|
||||
|
||||
```js
|
||||
export default class MyPlugin extends UIPlugin {
|
||||
// ...
|
||||
install() {
|
||||
this.uppy.on('upload-progress', this.onProgress);
|
||||
this.uppy.addPostProcessor(this.afterUpload);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `uninstall()`
|
||||
|
||||
Called when the plugin is removed, or the Uppy instance is closed. This should
|
||||
undo all the work done in the `install()` method.
|
||||
|
||||
```js
|
||||
export default class MyPlugin extends UIPlugin {
|
||||
// ...
|
||||
uninstall() {
|
||||
this.uppy.off('upload-progress', this.onProgress);
|
||||
this.uppy.removePostProcessor(this.afterUpload);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `afterUpdate()`
|
||||
|
||||
Called after every state update with a debounce, after everything has mounted.
|
||||
|
||||
#### `addTarget()`
|
||||
|
||||
Use this to add your plugin to another plugin’s target. This is what
|
||||
`@uppy/dashboard` uses to add other plugins to its UI.
|
||||
|
||||
### `UIPlugin`
|
||||
|
||||
`UIPlugin` extends the `BasePlugin` class so it will also contain all the above
|
||||
methods.
|
||||
|
||||
#### `mount(target)`
|
||||
|
||||
Mount this plugin to the `target` element. `target` can be a CSS query selector,
|
||||
a DOM element, or another Plugin. If `target` is a Plugin, the source (current)
|
||||
plugin will register with the target plugin, and the latter can decide how and
|
||||
where to render the source plugin.
|
||||
|
||||
This method can be overridden to support for different render engines.
|
||||
|
||||
#### `render()`
|
||||
|
||||
Render this plugin’s UI. Uppy uses [Preact](https://preactjs.com) as its view
|
||||
engine, so `render()` should return a Preact element. `render` is automatically
|
||||
called by Uppy on each state change.
|
||||
|
||||
#### `onMount()`
|
||||
|
||||
Called after Preact has rendered the components of the plugin.
|
||||
|
||||
#### `update(state)`
|
||||
|
||||
Called on each state update. You will rarely need to use this, unless if you
|
||||
want to build a UI plugin using something other than Preact.
|
||||
|
||||
#### `onUnmount()`
|
||||
|
||||
Called after the elements have been removed from the DOM. Can be used to do some
|
||||
clean up or other side-effects.
|
||||
|
||||
## Upload Hooks
|
||||
|
||||
When creating an upload, Uppy runs files through an upload pipeline. The
|
||||
pipeline consists of three parts, each of which can be hooked into:
|
||||
Preprocessing, Uploading, and Postprocessing. Preprocessors can be used to
|
||||
configure uploader plugins, encrypt files, resize images, etc., before uploading
|
||||
them. Uploaders do the actual uploading work, such as creating an XMLHttpRequest
|
||||
object and sending the file. Postprocessors do their work after files have been
|
||||
uploaded completely. This could be anything from waiting for a file to propagate
|
||||
across a CDN, to sending another request to relate some metadata to the file.
|
||||
|
||||
Each hook is a function that receives an array containing the file IDs that are
|
||||
being uploaded, and returns a Promise to signal completion. Hooks are added and
|
||||
removed through `Uppy` methods:
|
||||
[`addPreProcessor`](/docs/uppy#addpreprocessorfn),
|
||||
[`addUploader`](/docs/uppy#adduploaderfn),
|
||||
[`addPostProcessor`](/docs/uppy#addpostprocessorfn), and their
|
||||
[`remove*`](/docs/uppy#removepreprocessorremoveuploaderremovepostprocessorfn)
|
||||
counterparts. Normally, hooks should be added during the plugin `install()`
|
||||
method, and removed during the `uninstall()` method.
|
||||
|
||||
Additionally, upload hooks can fire events to signal progress.
|
||||
|
||||
When adding hooks, make sure to bind the hook `fn` beforehand! Otherwise, it
|
||||
will be impossible to remove. For example:
|
||||
|
||||
```js
|
||||
class MyPlugin extends BasePlugin {
|
||||
constructor(uppy, opts) {
|
||||
super(uppy, opts);
|
||||
this.id = opts.id || 'MyPlugin';
|
||||
this.type = 'example';
|
||||
this.prepareUpload = this.prepareUpload.bind(this); // ← this!
|
||||
}
|
||||
|
||||
prepareUpload(fileIDs) {
|
||||
console.log(this); // `this` refers to the `MyPlugin` instance.
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
install() {
|
||||
this.uppy.addPreProcessor(this.prepareUpload);
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
this.uppy.removePreProcessor(this.prepareUpload);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or you can define the method as a class field:
|
||||
|
||||
```js
|
||||
class MyPlugin extends UIPlugin {
|
||||
constructor(uppy, opts) {
|
||||
super(uppy, opts);
|
||||
this.id = opts.id || 'MyPlugin';
|
||||
this.type = 'example';
|
||||
}
|
||||
|
||||
prepareUpload = (fileIDs) => {
|
||||
// ← this!
|
||||
console.log(this); // `this` refers to the `MyPlugin` instance.
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
install() {
|
||||
this.uppy.addPreProcessor(this.prepareUpload);
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
this.uppy.removePreProcessor(this.prepareUpload);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Progress events
|
||||
|
||||
Progress events can be fired for individual files to show feedback to the user.
|
||||
For upload progress events, only emitting how many bytes are expected and how
|
||||
many have been uploaded is enough. Uppy will handle calculating progress
|
||||
percentages, upload speed, etc.
|
||||
|
||||
Preprocessing and postprocessing progress events are plugin-dependent and can
|
||||
refer to anything, so Uppy doesn’t try to be smart about them. Processing
|
||||
progress events can be of two types: determinate or indeterminate. Some
|
||||
processing does not have meaningful progress beyond “not done” and “done”. For
|
||||
example, sending a request to initialize a server-side resource that will serve
|
||||
as the upload destination. In those situations, indeterminate progress is
|
||||
suitable. Other processing does have meaningful progress. For example,
|
||||
encrypting a large file. In those situations, determinate progress is suitable.
|
||||
|
||||
Here are the relevant events:
|
||||
|
||||
- [`preprocess-progress`](/docs/uppy#preprocess-progress)
|
||||
- [`upload-progress`](/docs/uppy#upload-progress)
|
||||
- [`postprocess-progress`](/docs/uppy#postprocess-progress)
|
||||
|
||||
## JSX
|
||||
|
||||
Since Uppy uses Preact and not React, the default Babel configuration for JSX
|
||||
elements does not work. You have to import the Preact `h` function and tell
|
||||
Babel to use it by adding a `/** @jsx h */` comment at the top of the file.
|
||||
|
||||
See the Preact
|
||||
[Getting Started Guide](https://preactjs.com/guide/getting-started) for more on
|
||||
Babel and JSX.
|
||||
|
||||
<!-- eslint-disable jsdoc/check-tag-names -->
|
||||
|
||||
```jsx
|
||||
/** @jsx h */
|
||||
import { UIPlugin } from '@uppy/core';
|
||||
import { h } from 'preact';
|
||||
|
||||
class NumFiles extends UIPlugin {
|
||||
render() {
|
||||
const numFiles = Object.keys(this.uppy.state.files).length;
|
||||
|
||||
return <div>Number of files: {numFiles}</div>;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Locales
|
||||
|
||||
For any user facing language that you use while writing your Plugin, please
|
||||
provide them as strings in the `defaultLocale` property like so:
|
||||
|
||||
```js
|
||||
this.defaultLocale = {
|
||||
strings: {
|
||||
youCanOnlyUploadFileTypes: 'You can only upload: %{types}',
|
||||
youCanOnlyUploadX: {
|
||||
0: 'You can only upload %{smart_count} file',
|
||||
1: 'You can only upload %{smart_count} files',
|
||||
2: 'You can only upload %{smart_count} files',
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
This allows them to be overridden by Locale Packs, or directly when users pass
|
||||
`locale: { strings: youCanOnlyUploadFileTypes: 'Something else completely about %{types}'} }`.
|
||||
For this to work, it’s also required that you call `this.i18nInit()` in the
|
||||
plugin constructor.
|
||||
|
||||
## Example of a custom plugin
|
||||
|
||||
Below is a full example of a
|
||||
[small plugin](https://github.com/arturi/uppy-plugin-image-compressor) that
|
||||
compresses images before uploading them. You can replace `compressorjs` method
|
||||
with any other work you need to do. This works especially well for async stuff,
|
||||
like calling an external API.
|
||||
|
||||
<!-- eslint-disable consistent-return -->
|
||||
|
||||
```js
|
||||
import { UIPlugin } from '@uppy/core';
|
||||
import Translator from '@uppy/utils/lib/Translator';
|
||||
import Compressor from 'compressorjs/dist/compressor.esm.js';
|
||||
|
||||
class UppyImageCompressor extends UIPlugin {
|
||||
constructor(uppy, opts) {
|
||||
const defaultOptions = {
|
||||
quality: 0.6,
|
||||
};
|
||||
super(uppy, { ...defaultOptions, ...opts });
|
||||
|
||||
this.id = this.opts.id || 'ImageCompressor';
|
||||
this.type = 'modifier';
|
||||
|
||||
this.defaultLocale = {
|
||||
strings: {
|
||||
compressingImages: 'Compressing images...',
|
||||
},
|
||||
};
|
||||
|
||||
// we use those internally in `this.compress`, so they
|
||||
// should not be overridden
|
||||
delete this.opts.success;
|
||||
delete this.opts.error;
|
||||
|
||||
this.i18nInit();
|
||||
}
|
||||
|
||||
compress(blob) {
|
||||
return new Promise(
|
||||
(resolve, reject) =>
|
||||
new Compressor(blob, {
|
||||
...this.opts,
|
||||
success(result) {
|
||||
return resolve(result);
|
||||
},
|
||||
error(err) {
|
||||
return reject(err);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
prepareUpload = (fileIDs) => {
|
||||
const promises = fileIDs.map((fileID) => {
|
||||
const file = this.uppy.getFile(fileID);
|
||||
this.uppy.emit('preprocess-progress', file, {
|
||||
mode: 'indeterminate',
|
||||
message: this.i18n('compressingImages'),
|
||||
});
|
||||
|
||||
if (!file.type.startsWith('image/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.compress(file.data)
|
||||
.then((compressedBlob) => {
|
||||
this.uppy.log(
|
||||
`[Image Compressor] Image ${file.id} size before/after compression: ${file.data.size} / ${compressedBlob.size}`,
|
||||
);
|
||||
this.uppy.setFileState(fileID, { data: compressedBlob });
|
||||
})
|
||||
.catch((err) => {
|
||||
this.uppy.log(
|
||||
`[Image Compressor] Failed to compress ${file.id}:`,
|
||||
'warning',
|
||||
);
|
||||
this.uppy.log(err, 'warning');
|
||||
});
|
||||
});
|
||||
|
||||
const emitPreprocessCompleteForAll = () => {
|
||||
fileIDs.forEach((fileID) => {
|
||||
const file = this.uppy.getFile(fileID);
|
||||
this.uppy.emit('preprocess-complete', file);
|
||||
});
|
||||
};
|
||||
|
||||
// Why emit `preprocess-complete` for all files at once, instead of
|
||||
// above when each is processed?
|
||||
// Because it leads to StatusBar showing a weird “upload 6 files” button,
|
||||
// while waiting for all the files to complete pre-processing.
|
||||
return Promise.all(promises).then(emitPreprocessCompleteForAll);
|
||||
};
|
||||
|
||||
install() {
|
||||
this.uppy.addPreProcessor(this.prepareUpload);
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
this.uppy.removePreProcessor(this.prepareUpload);
|
||||
}
|
||||
}
|
||||
|
||||
export default UppyImageCompressor;
|
||||
```
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Building your own UI with Uppy
|
||||
|
||||
:::note
|
||||
|
||||
This guide is in progress.
|
||||
|
||||
:::
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Choosing the uploader you need
|
||||
|
||||
Versatile, reliable uploading is at the heart of Uppy. It has many configurable
|
||||
plugins to suit your needs. In this guide we will explain the different plugins,
|
||||
their strategies, and when to use them based on use cases.
|
||||
|
||||
## Use cases
|
||||
|
||||
### I want worry-free, plug-and-play uploads with Transloadit services
|
||||
|
||||
Transloadit’s strength is versatility. By doing video, audio, images, documents,
|
||||
and more, you only need one vendor for [all your file processing
|
||||
needs][transloadit-services]. The [`@uppy/transloadit`][] plugin directly
|
||||
uploads to Transloadit so you only have to worry about creating a
|
||||
[template][transloadit-concepts]. It uses
|
||||
[Tus](#i-want-reliable-resumable-uploads) under the hood so you don’t have to
|
||||
sacrifice reliable, resumable uploads for convenience.
|
||||
|
||||
You should use [`@uppy/transloadit`][] if you don’t want to host your own
|
||||
server, (optionally) need file processing, and store it in the service (such as
|
||||
S3 or GCS) of your liking. All with minimal effort.
|
||||
|
||||
### I want reliable, resumable uploads
|
||||
|
||||
[Tus][tus] is a new open protocol for resumable uploads built on HTTP. This
|
||||
means accidentally closing your tab or losing connection let’s you continue, for
|
||||
instance, your 10GB upload instead of starting all over.
|
||||
|
||||
Tus supports any language, any platform, and any network. It requires a client
|
||||
and server integration to work. You can checkout the client and server
|
||||
[implementations][tus-implementations] to find the server in your preferred
|
||||
language. You can store files on the Tus server itself, but you can also use
|
||||
service integrations (such as S3) to store files externally.
|
||||
|
||||
If you want reliable, resumable uploads: use [`@uppy/tus`][] to connect to your
|
||||
Tus server in a few lines of code.
|
||||
|
||||
:::tip
|
||||
|
||||
If you plan to let people upload _a lot_ of files, [`@uppy/tus`][] has
|
||||
exponential backoff built-in. Meaning if your server (or proxy) returns HTTP 429
|
||||
because it’s being overloaded, [`@uppy/tus`][] will find the ideal sweet spot to
|
||||
keep uploading without overloading.
|
||||
|
||||
:::
|
||||
|
||||
### I want to upload to AWS S3 (or S3-compatible storage) directly
|
||||
|
||||
When you prefer a _client-to-storage_ over a _client-to-server-to-storage_ (such
|
||||
as [Transloadit](/docs/transloadit) or [Tus](/docs/tus)) setup. This may in some
|
||||
cases be preferable, for instance, to reduce costs or the complexity of running
|
||||
a server and load balancer with [Tus](/docs/tus).
|
||||
|
||||
Uppy has two plugins to make this happen [`@uppy/aws-s3`][] and
|
||||
[`@uppy/aws-s3-multipart`][], but we are planning to merge the two plugins in
|
||||
the next major. You should use [`@uppy/aws-s3`][] with the new
|
||||
`shouldUseMultipart` option.
|
||||
|
||||
:::info
|
||||
|
||||
You can also save files in S3 with the [`/s3/store`][s3-robot] robot while still
|
||||
using the powers of Transloadit services.
|
||||
|
||||
:::
|
||||
|
||||
### I want to send regular HTTP uploads to my own server
|
||||
|
||||
[`@uppy/xhr-upload`][] handles classic HTML multipart form uploads as well as
|
||||
uploads using the HTTP `PUT` method.
|
||||
|
||||
[s3-robot]: https://transloadit.com/services/file-exporting/s3-store/
|
||||
[transloadit-services]: https://transloadit.com/services/
|
||||
[transloadit-concepts]: https://transloadit.com/docs/getting-started/concepts/
|
||||
[`@uppy/transloadit`]: /docs/transloadit
|
||||
[`@uppy/tus`]: /docs/tus
|
||||
[`@uppy/aws-s3-multipart`]: /docs/aws-s3-multipart
|
||||
[`@uppy/aws-s3`]: /docs/aws-s3-multipart
|
||||
[`@uppy/xhr-upload`]: /docs/xhr-upload
|
||||
[tus]: https://tus.io/
|
||||
[tus-implementations]: https://tus.io/implementations.html
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
# Custom stores
|
||||
|
||||
If your app uses a state management library such as
|
||||
[Redux](https://redux.js.org), it can be useful to have Uppy store its state
|
||||
there instead—that way, you could write custom uploader UI components in the
|
||||
same way as the other components in the application.
|
||||
|
||||
Uppy comes with two state management solutions (stores):
|
||||
|
||||
- `@uppy/store-default`, a basic object-based store.
|
||||
- `@uppy/store-redux`, a store that uses a key in a Redux store.
|
||||
|
||||
You can also use a third-party store:
|
||||
|
||||
- [uppy-store-ngrx](https://github.com/rimlin/uppy-store-ngrx/), keeping Uppy
|
||||
state in a key in an [Ngrx](https://github.com/ngrx/platform) store for use
|
||||
with Angular.
|
||||
|
||||
## Using stores
|
||||
|
||||
To use a store, pass an instance to the
|
||||
[`store` option](/docs/uppy#store-defaultstore) in the Uppy constructor:
|
||||
|
||||
```js
|
||||
import DefaultStore from '@uppy/store-default';
|
||||
|
||||
const uppy = new Uppy({
|
||||
store: new DefaultStore(),
|
||||
});
|
||||
```
|
||||
|
||||
### `DefaultStore`
|
||||
|
||||
Uppy uses the `DefaultStore` by default! You do not need to do anything to use
|
||||
it. It does not take any options.
|
||||
|
||||
### `ReduxStore`
|
||||
|
||||
The `ReduxStore` stores Uppy state on a key in an existing Redux store. The
|
||||
`ReduxStore` dispatches `uppy/STATE_UPDATE` actions to update state. When the
|
||||
state in Redux changes, it notifies Uppy. This way, you get most of the benefits
|
||||
of Redux, including support for the Redux Devtools and time traveling!
|
||||
|
||||
Checkout our
|
||||
[Redux example](https://github.com/transloadit/uppy/tree/main/examples/redux)
|
||||
for a working demo.
|
||||
|
||||
#### `opts.store`
|
||||
|
||||
Pass a Redux store instance, from `Redux.createStore`. This instance should have
|
||||
the Uppy reducer mounted somewhere already.
|
||||
|
||||
#### `opts.id`
|
||||
|
||||
By default, the `ReduxStore` assumes Uppy state is stored on a `state.uppy[id]`
|
||||
key. `id` is randomly generated by the store constructor, but can be specified
|
||||
by passing an `id` option if it should be predictable.
|
||||
|
||||
```js
|
||||
ReduxStore({
|
||||
store,
|
||||
id: 'avatarUpload',
|
||||
});
|
||||
```
|
||||
|
||||
#### `opts.selector`
|
||||
|
||||
If you’d rather not store the Uppy state under the `state.uppy` key at all, use
|
||||
the `selector` option to the `ReduxStore` constructor to tell it where to find
|
||||
state instead:
|
||||
|
||||
```js
|
||||
const uppy = new Uppy({
|
||||
store: ReduxStore({
|
||||
store,
|
||||
id: 'avatarUpload',
|
||||
selector: (state) => state.pages.profile.uppy.avatarUpload,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
Note that when specifying a custom selector, you **must** also specify a custom
|
||||
store ID. The store `id` tells the reducer in which property it should put
|
||||
Uppy’s state. The selector must then take the state from that property. In the
|
||||
example, we set the ID to `avatarUpload` and take the state from the
|
||||
`[reducer mount path].avatarUpload`.
|
||||
|
||||
If your app uses [`reselect`](https://npmjs.com/package/reselect), its selectors
|
||||
work well with this!
|
||||
|
||||
## Implementing Stores
|
||||
|
||||
An Uppy store is an object with three methods.
|
||||
|
||||
- `getState()` - Return the current state object.
|
||||
- `setState(patch)` - Merge the object `patch` into the current state.
|
||||
- `subscribe(listener)` - Call `listener` whenever the state changes. `listener`
|
||||
is a function that should receive three parameters:
|
||||
`(prevState, nextState, patch)`
|
||||
|
||||
The `subscribe()` method should return a function that “unsubscribes”
|
||||
(removes) the `listener`.
|
||||
|
||||
The default store implementation, for example, looks a bit like this:
|
||||
|
||||
```js
|
||||
function createDefaultStore() {
|
||||
let state = {};
|
||||
const listeners = new Set();
|
||||
|
||||
return {
|
||||
getState: () => state,
|
||||
setState: (patch) => {
|
||||
const prevState = state;
|
||||
const nextState = { ...prevState, ...patch };
|
||||
|
||||
state = nextState;
|
||||
|
||||
listeners.forEach((listener) => {
|
||||
listener(prevState, nextState, patch);
|
||||
});
|
||||
},
|
||||
subscribe: (listener) => {
|
||||
listeners.add(listener);
|
||||
return () => listeners.remove(listener);
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
A pattern like this, where users can pass options via a function call if
|
||||
necessary, is recommended.
|
||||
|
||||
See the
|
||||
[@uppy/store-default](https://github.com/transloadit/uppy/tree/main/packages/%40uppy/store-default)
|
||||
package for more inspiration.
|
||||
|
|
@ -1,865 +0,0 @@
|
|||
# Migration guides
|
||||
|
||||
These cover all the major Uppy versions and how to migrate to them.
|
||||
|
||||
## Migrate from Companion 4.x to 5.x
|
||||
|
||||
- End-of-Life versions of Node.js are no longer supported (use latest 18.x LTS,
|
||||
20.x LTS, or 22.x current).
|
||||
- Setting the `corsOrigin` (`COMPANION_CLIENT_ORIGINS`) option is now required.
|
||||
You should define the list of origins you expect your app to be served from,
|
||||
otherwise it can be impersonated from a different origin you don’t control.
|
||||
Set it to `true` if you don’t care about impersonating.
|
||||
- `COMPANION_REDIS_EXPRESS_SESSION_PREFIX` now defaults to `companion-session:`
|
||||
(before `sess:`). To revert keep backwards compatibility, set the environment
|
||||
variable `COMPANION_REDIS_EXPRESS_SESSION_PREFIX=sess:`.
|
||||
- The URL endpoint (used by the `Url`/`Link` plugin) is now turned off by
|
||||
default and must be explicitly enabled with
|
||||
`COMPANION_ENABLE_URL_ENDPOINT=true` or `enableUrlEndpoint: true`.
|
||||
- The option `streamingUpload` / `COMPANION_STREAMING_UPLOAD` now defaults to
|
||||
`true`.
|
||||
- The option `getKey(req, filename, metadata)` has changed signature to
|
||||
`getKey({ filename, metadata, req })`.
|
||||
- The option `bucket(req, metadata)` has changed signature to
|
||||
`bucketOrFn({ req, metadata, filename })`.
|
||||
- Custom provider breaking changes. If you have not implemented a custom
|
||||
provider, you should not be affected.
|
||||
- The static `getExtraConfig` property has been renamed to
|
||||
`getExtraGrantConfig`.
|
||||
- The static `authProvider` property has been renamed to `oauthProvider`.
|
||||
- Endpoint `GET /s3/params` now returns `{ method: "POST" }` instead of
|
||||
`{ method: "post" }`. This will not affect most people.
|
||||
- `access-control-allow-headers` is no longer included in
|
||||
`Access-Control-Expose-Headers`, and `uppy-versions` is no longer an allowed
|
||||
header. We are not aware of any issues this might cause.
|
||||
- Internal refactoring (probably won’t affect you)
|
||||
- `getProtectedGot` parameter `blockLocalIPs` changed to `allowLocalIPs`
|
||||
(inverted boolean).
|
||||
- `getURLMeta` 2nd (boolean) argument inverted.
|
||||
- `getProtectedHttpAgent` parameter `blockLocalIPs` changed to `allowLocalIPs`
|
||||
(inverted boolean).
|
||||
- `downloadURL` 2nd (boolean) argument inverted.
|
||||
- `StreamHttpJsonError` renamed to `HttpError`.
|
||||
- Removed the `oauthOrigin` option, as well as the (undocumented) option
|
||||
`clients`. Use `corsOrigin` instead.
|
||||
|
||||
### `@uppy/companion-client`
|
||||
|
||||
:::info
|
||||
|
||||
Unless you built a custom provider, you don’t use `@uppy/companion-client`
|
||||
directly but through provider plugins such as `@uppy/google-drive`. In which
|
||||
case you don’t have to do anything.
|
||||
|
||||
:::
|
||||
|
||||
- `supportsRefreshToken` now defaults to `false` instead of `true`. If you have
|
||||
implemented a custom provider, this might affect you.
|
||||
- `Socket` class is no longer in use and has been removed. Unless you used this
|
||||
class you don’t need to do anything.
|
||||
- Remove deprecated options `serverUrl` and `serverPattern` (they were merely
|
||||
defined in Typescript, not in use).
|
||||
- `RequestClient` methods `get`, `post`, `delete` no longer accepts a boolean as
|
||||
the third argument. Instead, pass `{ skipPostResponse: true | false }`. This
|
||||
won’t affect you unless you’ve been using `RequestClient`.
|
||||
- When pausing uploads, the WebSocket towards companion will no longer be
|
||||
closed. This allows paused uploads to be cancelled, but once a file has been
|
||||
paused it will still occupy its place in the concurrency queue.
|
||||
|
||||
## Migrate from Uppy 3.x to 4.x
|
||||
|
||||
### TypeScript rewrite
|
||||
|
||||
Almost all plugins have been completely rewritten in TypeScript! This means you
|
||||
may run into type error all over the place, but the types now accurately show
|
||||
Uppy’s state and files.
|
||||
|
||||
There are too many small changes to cover, so you have to upgrade and see where
|
||||
TypeScript complains.
|
||||
|
||||
One important thing to note are the new generics on `@uppy/core`.
|
||||
|
||||
<!-- eslint-disable @typescript-eslint/no-unused-vars -->
|
||||
|
||||
<!-- eslint-disable @typescript-eslint/no-non-null-assertion -->
|
||||
|
||||
```ts
|
||||
import Uppy from '@uppy/core';
|
||||
// xhr-upload is for uploading to your own backend.
|
||||
import XHRUpload from '@uppy/xhr-upload';
|
||||
|
||||
// Your own metadata on files
|
||||
type Meta = { myCustomMetadata: string };
|
||||
// The response from your server
|
||||
type Body = { someThingMyBackendReturns: string };
|
||||
|
||||
const uppy = new Uppy<Meta, Body>().use(XHRUpload, {
|
||||
endpoint: '/upload',
|
||||
});
|
||||
|
||||
const id = uppy.addFile({
|
||||
name: 'example.jpg',
|
||||
data: new Blob(),
|
||||
meta: { myCustomMetadata: 'foo' },
|
||||
});
|
||||
|
||||
// This is now typed
|
||||
const { myCustomMetadata } = uppy.getFile(id).meta;
|
||||
|
||||
await uppy.upload();
|
||||
|
||||
// This is strictly typed too
|
||||
const { someThingMyBackendReturns } = uppy.getFile(id).response.body!;
|
||||
```
|
||||
|
||||
### `@uppy/aws-s3` and `@uppy/aws-s3-multipart`
|
||||
|
||||
- `@uppy/aws-s3` and `@uppy/aws-s3-multipart` have been combined into a single
|
||||
plugin. You should now only use `@uppy/aws-s3` with the new option,
|
||||
[`shouldUseMultipart()`](/docs/aws-s3-multipart/#shouldusemultipartfile), to
|
||||
allow you to switch between regular and multipart uploads. You can read more
|
||||
about this in the
|
||||
[plugin docs](https://uppy.io/docs/aws-s3-multipart/#when-should-i-use-it).
|
||||
- Remove deprecated `prepareUploadParts` option.
|
||||
- Companion’s options (`companionUrl`, `companionHeaders`, and
|
||||
`companionCookieRules`) are renamed to more generic names (`endpoint`,
|
||||
`headers`, and `cookieRules`).
|
||||
|
||||
Using Companion with the `@uppy/aws-s3` plugin only makes sense if you already
|
||||
need Companion for remote providers (such as Google Drive). When using your
|
||||
own backend, you can let Uppy do all the heavy lifting on the client which it
|
||||
would normally do for Companion, so you don’t have to implement that yourself.
|
||||
|
||||
As long as you return the JSON for the expected endpoints (see our
|
||||
[server example](https://github.com/transloadit/uppy/blob/main/examples/aws-nodejs/index.js)),
|
||||
you only need to set `endpoint`.
|
||||
|
||||
If you are using Companion, rename the options. If you have a lot of
|
||||
client-side complexity (`createMultipartUpload`, `signPart`, etc), consider
|
||||
letting Uppy do this for you.
|
||||
|
||||
### `@uppy/core`
|
||||
|
||||
- The `close()` method has been renamed to `destroy()` to more accurately
|
||||
reflect you can not recover from it without creating a new `Uppy` instance.
|
||||
- The `clearUploadedFiles()` method has been renamed to `clear()` as a
|
||||
convenience method to clear all the state. This can be useful when a user
|
||||
navigates away and you want to clear the state on success.
|
||||
- `bytesUploaded`, in `file.progress.bytesUploaded`, is now always a `boolean`,
|
||||
instead of a `boolean` or `number`.
|
||||
|
||||
### `@uppy/xhr-upload`
|
||||
|
||||
Before the plugin had the options `getResponseData`, `getResponseError`,
|
||||
`validateStatus` and `responseUrlFieldName`. These were inflexible and too
|
||||
specific. Now we have hooks similar to `@uppy/tus`:
|
||||
|
||||
- `onBeforeRequest` to manipulate the request before it is sent.
|
||||
- `shouldRetry` to determine if a request should be retried. By default 3
|
||||
retries with exponential backoff. After three attempts it will throw an error,
|
||||
regardless of whether you returned `true`.
|
||||
- `onAfterResponse` called after a successful response but before Uppy resolves
|
||||
the upload.
|
||||
|
||||
Checkout the [docs](/docs/xhr-upload/) for more info.
|
||||
|
||||
### `@uppy/transloadit`
|
||||
|
||||
The options `signature`, `params`, `fields`, and `getAssemblyOptions` have been
|
||||
removed in favor of [`assemblyOptions`](/docs/transloadit/#assemblyoptions),
|
||||
which can be an object or an (async) function returning an object.
|
||||
|
||||
When using `assemblyOptions()` as a function, it is now called only once for all
|
||||
files, instead of per file. Before `@uppy/transloadit` was trying to be too
|
||||
smart, creating multiple assemblies in which each assembly has files with
|
||||
identical `fields`. This was done so you can use `fields` dynamically in your
|
||||
template per file, instead of per assembly.
|
||||
|
||||
Now we sent all metadata of a file inside the tus upload (which
|
||||
`@uppy/transloadit` uses under the hood) and make it accessible in your
|
||||
Transloadit template as `file_user_meta`. You should use `fields` for global
|
||||
values in your template and `file_user_meta` for per file values.
|
||||
|
||||
Another benefit of running `assemblyOptions()` only once, is that when
|
||||
generating a
|
||||
[secret](https://transloadit.com/docs/topics/signature-authentication/) on your
|
||||
server (which you should), a network request is made only once for all files,
|
||||
instead of per file.
|
||||
|
||||
### CDN
|
||||
|
||||
- We no longer build and serve the legacy build, made for IE11, on our CDN.
|
||||
|
||||
### Miscellaneous
|
||||
|
||||
- All uploaders plugins now consistently use
|
||||
[`allowedMetaFields`](/docs/xhr-upload/#allowedmetafields). Before there were
|
||||
inconsistencies between plugins.
|
||||
- All plugin `titles` (what you see in the Dashboard when you open a plugin) are
|
||||
now set from the `locale` option. See the
|
||||
[docs](/docs/locales/#overriding-locale-strings-for-a-specific-plugin) on how
|
||||
to overide a string.
|
||||
|
||||
### `@uppy/angular`
|
||||
|
||||
- Upgrade to Angular 18.x (17.x is still supported too) and to TS 5.4
|
||||
|
||||
### `@uppy/react`
|
||||
|
||||
- Remove deprecated `useUppy`
|
||||
- You can no longer set `inline` on the `Dashboard` component, use `Dashboard`
|
||||
or `DashboardModal` components respectively.
|
||||
|
||||
### `@uppy/svelte`
|
||||
|
||||
- Make Svelte 5 the peer dependency
|
||||
- Remove UMD output
|
||||
|
||||
### `@uppy/vue`
|
||||
|
||||
- Migrate to Composition API with TypeScript & drop Vue 2 support
|
||||
- Drop Vue 2 support
|
||||
|
||||
## Migrate from Robodog to Uppy plugins
|
||||
|
||||
Uppy is flexible and extensible through plugins. But the integration code could
|
||||
sometimes be daunting. This is what brought Robodog to life. An alternative with
|
||||
the same features, but with a more ergonomic and minimal API.
|
||||
|
||||
But, it didn’t come with its own set of new problems:
|
||||
|
||||
- It tries to do the exact same, but it looks like a different product.
|
||||
- It’s confusing for users whether they want to use Robodog or Uppy directly.
|
||||
- Robodog is more ergonomic because it’s limited. When you hit such a limit, you
|
||||
need to refactor everything to Uppy with plugins.
|
||||
|
||||
This has now led us to deprecating Robodog and embrace Uppy for its strong
|
||||
suits; modularity and flexibility. At the same time, we also introduced
|
||||
something to take away some repetitive integration code:
|
||||
[`@uppy/remote-sources`](/docs/remote-sources).
|
||||
|
||||
To mimic the Robodog implementation with all its features, you can use the code
|
||||
snippet below. But chances are Robodog did more than you need so feel free to
|
||||
remove things or go through the [list of plugins](/docs/companion/) and install
|
||||
and use the ones you need.
|
||||
|
||||
You can also checkout how we migrated the Robodog example ourselves in this
|
||||
[commit](https://github.com/transloadit/uppy/commit/089aaed615c77bafaf905e291b6b4e82aaeb2f6f).
|
||||
|
||||
```js
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import RemoteSources from '@uppy/remote-sources';
|
||||
import Webcam from '@uppy/webcam';
|
||||
import ScreenCapture from '@uppy/screen-capture';
|
||||
import GoldenRetriever from '@uppy/golden-retriever';
|
||||
import ImageEditor from '@uppy/image-editor';
|
||||
import Audio from '@uppy/audio';
|
||||
import Transloadit, {
|
||||
COMPANION_URL,
|
||||
COMPANION_ALLOWED_HOSTS,
|
||||
} from '@uppy/transloadit';
|
||||
|
||||
import '@uppy/core/dist/style.css';
|
||||
import '@uppy/dashboard/dist/style.css';
|
||||
import '@uppy/audio/dist/style.css';
|
||||
import '@uppy/screen-capture/dist/style.css';
|
||||
import '@uppy/image-editor/dist/style.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, {
|
||||
inline: true,
|
||||
target: '#app',
|
||||
showProgressDetails: true,
|
||||
proudlyDisplayPoweredByUppy: true,
|
||||
})
|
||||
.use(RemoteSources, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
})
|
||||
.use(Webcam, {
|
||||
showVideoSourceDropdown: true,
|
||||
showRecordingLength: true,
|
||||
})
|
||||
.use(Audio, {
|
||||
showRecordingLength: true,
|
||||
})
|
||||
.use(ScreenCapture)
|
||||
.use(ImageEditor)
|
||||
.use(Transloadit, {
|
||||
service: 'https://api2.transloadit.com',
|
||||
async getAssemblyOptions(file) {
|
||||
// This is where you configure your auth key, auth secret, and template ID
|
||||
// /uppy/docs/transloadit/#getAssemblyOptions-file
|
||||
//
|
||||
// It is important to set the secret in production:
|
||||
// https://transloadit.com/docs/topics/signature-authentication/
|
||||
const response = await fetch('/some-endpoint');
|
||||
return response.json();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Migrate from Uppy 2.x to 3.x
|
||||
|
||||
### Uppy is pure ESM
|
||||
|
||||
Following the footsteps of many packages, we now only ship Uppy core and its
|
||||
plugins as
|
||||
[ECMAScript Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)
|
||||
(ESM). On Uppy 2.x, we were shipping CommonJS.
|
||||
|
||||
If are already using ESM yourself, or are using the CDN builds, nothing changes
|
||||
for you!
|
||||
|
||||
If you are using CommonJS, you might need to add some tooling for everything to
|
||||
work, or you might want to refactor your codebase to ESM – refer to the
|
||||
[Pure ESM package](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c)
|
||||
gist for added information and help on how to do that.
|
||||
|
||||
### Robodog is deprecated
|
||||
|
||||
See the [Robodog migration guide](#Migrate-from-Robodog-to-Uppy-plugins).
|
||||
|
||||
### `@uppy/core`
|
||||
|
||||
#### Remove `AggregateError` polyfill.
|
||||
|
||||
It’s supported by most modern browsers and
|
||||
[can be polyfilled by the user](https://github.com/transloadit/uppy/pull/3532#discussion_r818602636)
|
||||
if needed.
|
||||
|
||||
To migrate: install a `AggregateError` polyfill or use `core-js`.
|
||||
|
||||
#### Remove `reset()` method.
|
||||
|
||||
It’s a duplicate of `cancelAll`, but with a less intention revealing name.
|
||||
|
||||
To migrate: use `cancelAll`.
|
||||
|
||||
#### Remove backwards compatible exports (static properties on `Uppy`)\`
|
||||
|
||||
`Uppy`, `UIPlugin`, `BasePlugin`, and `debugLogger` used to also be accessible
|
||||
on the `Uppy` export. This has now been removed due to the transition to ESM.
|
||||
|
||||
To migrate: import the `Uppy` class by default and/or use named exports for
|
||||
everything else.
|
||||
|
||||
#### `uppy.validateRestrictions()` now returns a `RestrictionError`
|
||||
|
||||
This method used to return `{ result: false, reason: err.message }`, but that
|
||||
felt strange as it tries to mimic an error. Instead it now return a
|
||||
`RestrictionError`, which is extended `Error` class.
|
||||
|
||||
To migrate: check the return value, if it’s defined you have an error, otherwise
|
||||
all went well. Note that the error is `return`’ed, it’s not `throw`’n, so you
|
||||
don’t have to `catch` it.
|
||||
|
||||
### `@uppy/transloadit`
|
||||
|
||||
Remove export of `ALLOWED_COMPANION_PATTERN`, `COMPANION`, and
|
||||
`COMPANION_PATTERN` in favor of `COMPANION_URL` and `COMPANION_ALLOWED_HOSTS`.
|
||||
This is to have more intention revealing names, `COMPANION` sounds like the
|
||||
Companion instance, `COMPANION_URL` makes it more clear that it’s a URL.
|
||||
|
||||
These are properties can now be imported and used for remote sources plugins
|
||||
when using Transloadit:
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
|
||||
// ...
|
||||
uppy.use(Dropbox, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
});
|
||||
```
|
||||
|
||||
### `@uppy/aws-s3-multipart`
|
||||
|
||||
#### Make `headers` inside the return value of [`prepareUploadParts`](/docs/aws-s3-multipart/#prepareUploadParts-file-partData) part-indexed too.
|
||||
|
||||
This is to allow custom headers to be set per part. See this
|
||||
[issue](https://github.com/transloadit/uppy/issues/3881) for details.
|
||||
|
||||
To migrate: make headers part indexed like `presignedUrls`:
|
||||
`{ "headers": { "1": { "Content-MD5": "foo" } }}`.
|
||||
|
||||
#### Remove `client` getter and setter.
|
||||
|
||||
It’s internal usage only.
|
||||
|
||||
To migrate: use exposed options only.
|
||||
|
||||
### `@uppy/tus/`, `@uppy/aws-s3`, `@uppy/xhr-upload`
|
||||
|
||||
Rename `metaFields` option to `allowedMetaFields`. Counter intuitively,
|
||||
`metaFields` is for _filtering_ which `metaFields` to send along with the
|
||||
request, not for adding extra meta fields to a request. As a lot of people were
|
||||
confused by this, and the name overlaps with the
|
||||
[`metaFields` option from Dashboard](/docs/dashboard/#metaFields), we renamed
|
||||
it.
|
||||
|
||||
To migrate: use `allowedMetaFields`.
|
||||
|
||||
### `@uppy/react`
|
||||
|
||||
#### Uppy dependencies have become peer dependencies
|
||||
|
||||
`@uppy/dashboard`, `@uppy/drag-drop`, `@uppy/file-input`, `@uppy/progress-bar`,
|
||||
and `@uppy/status-bar` are now peer dependencies. This means you don’t install
|
||||
all these packages if you only need one.
|
||||
|
||||
To migrate: install only the packages you need. If you use the Dashboard
|
||||
component, you need `@uppy/dashboard`, and so onwards.
|
||||
|
||||
#### Don’t expose `validProps` on the exported components.
|
||||
|
||||
It’s internal usage only.
|
||||
|
||||
To migrate: use exposed options only.
|
||||
|
||||
### `@uppy/svelte`
|
||||
|
||||
`@uppy/dashboard`, `@uppy/drag-drop`, `@uppy/progress-bar`, and
|
||||
`@uppy/status-bar` are now peer dependencies. This means you don’t install all
|
||||
these packages if you only need one.
|
||||
|
||||
To migrate: install only the packages you need. If you use the Dashboard
|
||||
component, you need `@uppy/dashboard`, and so onwards.
|
||||
|
||||
### `@uppy/vue`
|
||||
|
||||
`@uppy/dashboard`, `@uppy/drag-drop`, `@uppy/file-input`, `@uppy/progress-bar`,
|
||||
and `@uppy/status-bar` are now peer dependencies. This means you don’t install
|
||||
all these packages if you only need one.
|
||||
|
||||
To migrate: install only the packages you need. If you use the Dashboard
|
||||
component, you need `@uppy/dashboard`, and so onwards.
|
||||
|
||||
### `@uppy/store-redux`
|
||||
|
||||
Remove backwards compatible exports (static properties on `ReduxStore`).
|
||||
Exports, such as `reducer`, used to also be accessible on the `ReduxStore`
|
||||
export. This has now been removed due to the transition to ESM.
|
||||
|
||||
To migrate: use named imports.
|
||||
|
||||
### `@uppy/thumbnail-generator`
|
||||
|
||||
Remove `rotateImage`, `protect`, and `canvasToBlob` from the plugin prototype.
|
||||
They are internal usage only.
|
||||
|
||||
To migrate: use exposed options only.
|
||||
|
||||
### Known issues
|
||||
|
||||
- [`ERESOLVE could not resolve` on npm install](https://github.com/transloadit/uppy/issues/4057).
|
||||
- [@uppy/svelte reports a broken dependency with the Vite bundler](https://github.com/transloadit/uppy/issues/4069).
|
||||
|
||||
## Migrate from Companion 3.x to 4.x
|
||||
|
||||
### Minimum required Node.js version is v14.20.0
|
||||
|
||||
Aligning with the Node.js
|
||||
[Long Term Support (LTS) schedule](https://nodejs.org/en/about/releases/) and to
|
||||
use modern syntax features.
|
||||
|
||||
### `companion.app()` returns `{ app, emitter }` instead of `app`
|
||||
|
||||
Companion 3.x provides the emitter as `companionEmitter` on `app`. As of 4.x, an
|
||||
object is returned with an `app` property (express middleware) and an `emitter`
|
||||
property (event emitter). This provides more flexibility in the future and
|
||||
follows best practices.
|
||||
|
||||
### Removed `searchProviders` wrapper object inside `providerOptions`
|
||||
|
||||
To use [`@uppy/unsplash`](/docs/unsplash), you had to configure Unsplash in
|
||||
Companion inside `providerOptions.searchProviders`. This is redundant, Unsplash
|
||||
is a provider as well so we removed the wrapper object.
|
||||
|
||||
### Moved the `s3` options out of `providerOptions`
|
||||
|
||||
To use AWS S3 for storage, you configured the `s3` object inside
|
||||
`providerOptions`. But as S3 is not a provider but a destination. To avoid
|
||||
confusion we moved the `s3` settings to the root settings object.
|
||||
|
||||
### Removed compatibility for legacy Custom Provider implementations
|
||||
|
||||
[Custom Provider](/docs/companion/#Adding-custom-providers) implementations must
|
||||
use the Promise API. The callback API is no longer supported.
|
||||
|
||||
### Default to no ACL for AWS S3
|
||||
|
||||
Default to no
|
||||
[ACL](https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html)
|
||||
for S3 uploads. Before the default was `public-read` but AWS now discourages
|
||||
ACLs. The environment variable `COMPANION_AWS_DISABLE_ACL` is also removed,
|
||||
instead Companion only uses `COMPANION_AWS_ACL`.
|
||||
|
||||
### `protocol` sent from Uppy in any `get` request is now required (before it would default to Multipart).
|
||||
|
||||
If you use any official Uppy plugins, then no migration is needed. For custom
|
||||
plugins that talk to Companion, make to send along the `protocol` header with a
|
||||
value of `multipart`, `s3Multipart`, or `tus`.
|
||||
|
||||
### `emitSuccess` and `emitError` are now private methods on the `Uploader` class.
|
||||
|
||||
It’s unlikely you’re using this, but it’s technically a breaking change. In
|
||||
general, don’t depend on implicitly internal methods, use exposed APIs instead.
|
||||
|
||||
### Removed `chunkSize` backwards compatibility for AWS S3 Multipart
|
||||
|
||||
`chunkSize` option will now be used as `partSize` in AWS multipart. Before only
|
||||
valid values would be respected. Invalid values would be ignored. Now any value
|
||||
will be passed on to the AWS SDK, possibly throwing an error on invalid values.
|
||||
|
||||
### Removed backwards compatibility for `/metrics` endpoint
|
||||
|
||||
The `metrics` option is a boolean flag to tell Companion whether to provide an
|
||||
endpoint `/metrics` with Prometheus metrics. Metrics will now always be served
|
||||
under `options.server.path`. Before v4.x, it would always be served under the
|
||||
root.
|
||||
|
||||
For example: if `{ options: { metrics: true, server: { path: '/companion' }}}`,
|
||||
metrics will now be served under `/companion/metrics`. In v3.x, the metrics
|
||||
would be served under `/metrics`.
|
||||
|
||||
## Migrate from Uppy 1.x to 2.x
|
||||
|
||||
### New bundle requires manual polyfilling
|
||||
|
||||
With 2.0, following in the footsteps of Microsoft, we are dropping support for
|
||||
IE11. As a result, we are able to remove all built-in polyfills, and the new
|
||||
bundle size is **25% smaller**! If you want your app to still support older
|
||||
browsers (such as IE11), you may need to add the following polyfills to your
|
||||
bundle:
|
||||
|
||||
- [abortcontroller-polyfill](https://github.com/mo/abortcontroller-polyfill)
|
||||
- [core-js](https://github.com/zloirock/core-js)
|
||||
- [md-gum-polyfill](https://github.com/mozdevs/mediaDevices-getUserMedia-polyfill)
|
||||
- [resize-observer-polyfill](https://github.com/que-etc/resize-observer-polyfill)
|
||||
- [whatwg-fetch](https://github.com/github/fetch)
|
||||
|
||||
If you’re using a bundler, you need import these before Uppy:
|
||||
|
||||
```js
|
||||
import 'core-js';
|
||||
import 'whatwg-fetch';
|
||||
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch';
|
||||
// Order matters here: AbortController needs fetch, which needs Promise (provided by core-js).
|
||||
|
||||
import 'md-gum-polyfill';
|
||||
import ResizeObserver from 'resize-observer-polyfill';
|
||||
|
||||
window.ResizeObserver ??= ResizeObserver;
|
||||
|
||||
export { default } from '@uppy/core';
|
||||
export * from '@uppy/core';
|
||||
```
|
||||
|
||||
If you’re using Uppy from a CDN, we now provide two bundles: one for up-to-date
|
||||
browsers that do not include polyfills and use modern syntax, and one for legacy
|
||||
browsers. When migrating, be mindful about the types of browsers you want to
|
||||
support:
|
||||
|
||||
```html
|
||||
<!-- Modern browsers (recommended) -->
|
||||
<script src="https://releases.transloadit.com/uppy/v3.17.0/uppy.min.js"></script>
|
||||
|
||||
<!-- Legacy browsers (IE11+) -->
|
||||
<script
|
||||
nomodule
|
||||
src="https://releases.transloadit.com/uppy/v3.17.0/uppy.legacy.min.js"
|
||||
></script>
|
||||
<script type="module">
|
||||
import 'https://releases.transloadit.com/uppy/v3.17.0/uppy.min.js';
|
||||
</script>
|
||||
```
|
||||
|
||||
Please note that while you may be able to get 2.0 to work in IE11 this way, we
|
||||
do not officially support it anymore.
|
||||
|
||||
### Use `BasePlugin` or `UIPlugin` instead of `Plugin`
|
||||
|
||||
[`@uppy/core`][core] used to provide a `Plugin` class for creating plugins. This
|
||||
was used for any official plugin, but also for users who want to create their
|
||||
own custom plugin. But, `Plugin` always came bundled with Preact, even if the
|
||||
plugin itself didn’t add any UI elements.
|
||||
|
||||
`Plugin` has been replaced with `BasePlugin` and `UIPlugin`. `BasePlugin` is the
|
||||
minimum you need to create a plugin and `UIPlugin` adds Preact for rendering
|
||||
user interfaces.
|
||||
|
||||
You can import them from [`@uppy/core`][core]:
|
||||
|
||||
```js
|
||||
import { BasePlugin, UIPlugin } from '@uppy/core';
|
||||
```
|
||||
|
||||
**Note:** some bundlers will include `UIPlugin` (and thus Preact) if you import
|
||||
from `@uppy/core`. To make sure this does not happen, you can import `Uppy` and
|
||||
`BasePlugin` directly:
|
||||
|
||||
```js
|
||||
import Uppy from '@uppy/core/lib/Uppy.js';
|
||||
import BasePlugin from '@uppy/core/lib/BasePlugin.js';
|
||||
```
|
||||
|
||||
### Use the latest Preact for your Uppy plugins
|
||||
|
||||
Official plugins have already been upgraded. If you are using any custom
|
||||
plugins, upgrade Preact to the latest version. At the time of writing this is
|
||||
`10.5.13`.
|
||||
|
||||
### Set plugin titles from locales
|
||||
|
||||
Titles for plugins used to be set with the `title` property in the plugin
|
||||
options, but all other strings are set in `locale`. This has now been aligned.
|
||||
You should set your plugin title from the `locale` property.
|
||||
|
||||
Before
|
||||
|
||||
```js
|
||||
import Webcam from '@uppy/webcam';
|
||||
|
||||
uppy.use(Webcam, {
|
||||
title: 'Some title',
|
||||
});
|
||||
```
|
||||
|
||||
After
|
||||
|
||||
```js
|
||||
import Webcam from '@uppy/webcam';
|
||||
|
||||
uppy.use(Webcam, {
|
||||
locale: {
|
||||
strings: {
|
||||
title: 'Some title',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Initialize Uppy with the `new` keyword
|
||||
|
||||
The default export `Uppy` is no longer callable as a function. This means you
|
||||
construct the `Uppy` instance using the `new` keyword.
|
||||
|
||||
```js
|
||||
import Uppy from '@uppy/core';
|
||||
|
||||
const uppy = new Uppy(); // correct.
|
||||
|
||||
const otherUppy = Uppy(); // incorrect, will throw.
|
||||
```
|
||||
|
||||
### Rename `allowMultipleUploads` to `allowMultipleUploadBatches`
|
||||
|
||||
[`allowMultipleUploadBatches`](/docs/uppy/#allowmultipleuploadbatches) means
|
||||
allowing several calls to [`.upload()`](/docs/uppy/#upload), in other words, a
|
||||
user can add more files after already having uploaded some.
|
||||
|
||||
<!--retext-simplify ignore multiple-->
|
||||
|
||||
We have renamed this to be more intention revealing that this is about uploads,
|
||||
and not whether a user can choose multiple files for one upload.
|
||||
|
||||
```js
|
||||
const uppy = new Uppy({
|
||||
allowMultipleUploadBatches: true,
|
||||
});
|
||||
```
|
||||
|
||||
### New default limits for [`@uppy/xhr-upload`][xhr] and [`@uppy/tus`][tus]
|
||||
|
||||
The default limit has been changed from `0` to `5`. Setting this to `0` means no
|
||||
limit on concurrent uploads.
|
||||
|
||||
You can change the limit on the Tus and XHR plugin options.
|
||||
|
||||
```js
|
||||
uppy.use(Tus, {
|
||||
// ...
|
||||
limit: 10,
|
||||
});
|
||||
```
|
||||
|
||||
```js
|
||||
uppy.use(XHRUpload, {
|
||||
// ...
|
||||
limit: 10,
|
||||
});
|
||||
```
|
||||
|
||||
### TypeScript changes
|
||||
|
||||
Uppy used to have loose types by default and strict types as an opt-in. The
|
||||
default export was a function that returned the `Uppy` class, and the types came
|
||||
bundled with the default export (`Uppy.SomeType`).
|
||||
|
||||
```ts
|
||||
import Uppy from '@uppy/core';
|
||||
import Tus from '@uppy/tus';
|
||||
|
||||
const uppy = Uppy<Uppy.StrictTypes>();
|
||||
|
||||
uppy.use(Tus, {
|
||||
invalidOption: null, // this will make the compilation fail!
|
||||
});
|
||||
```
|
||||
|
||||
Uppy is now strictly typed by default and loose types have been removed.
|
||||
|
||||
```ts
|
||||
// ...
|
||||
|
||||
const uppy = new Uppy();
|
||||
|
||||
uppy.use(Tus, {
|
||||
invalidOption: null, // this will make the compilation fail!
|
||||
});
|
||||
```
|
||||
|
||||
Uppy types are now individual exports and should be imported separately.
|
||||
|
||||
<!-- eslint-disable @typescript-eslint/no-unused-vars -->
|
||||
|
||||
```ts
|
||||
import type { PluginOptions, UIPlugin, PluginTarget } from '@uppy/core';
|
||||
```
|
||||
|
||||
#### Event types
|
||||
|
||||
[`@uppy/core`][core] provides an [`.on`](/docs/uppy/#uppy-on-39-event-39-action)
|
||||
method to listen to [events](/docs/uppy/#Events). The types for these events
|
||||
were loose and allowed for invalid events to be passed, such as
|
||||
`uppy.on('upload-errrOOOoooOOOOOrrrr')`.
|
||||
|
||||
<!-- eslint-disable @typescript-eslint/no-unused-vars -->
|
||||
|
||||
```ts
|
||||
// Before:
|
||||
|
||||
type Meta = { myCustomMetadata: string };
|
||||
|
||||
// Invalid event
|
||||
uppy.on<Meta>('upload-errrOOOoooOOOOOrrrr', () => {
|
||||
// ...
|
||||
});
|
||||
|
||||
// After:
|
||||
|
||||
// Normal event signature
|
||||
uppy.on('complete', (result) => {
|
||||
const successResults = result.successful;
|
||||
});
|
||||
|
||||
// Custom signature
|
||||
type Meta = { myCustomMetadata: string };
|
||||
|
||||
// Notice how the custom type has now become the second argument
|
||||
uppy.on<'complete', Meta>('complete', (result) => {
|
||||
// The passed type is now merged into the `meta` types.
|
||||
const meta = result.successful[0].meta.myCustomMetadata;
|
||||
});
|
||||
```
|
||||
|
||||
Plugins that add their own events can merge with existing ones in `@uppy/core`
|
||||
with `declare module '@uppy/core' { ... }`. This is a TypeScript pattern called
|
||||
[module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation).
|
||||
For instance, when using [`@uppy/dashboard`][dashboard]:
|
||||
|
||||
<!-- eslint-disable @typescript-eslint/no-unused-vars -->
|
||||
|
||||
```ts
|
||||
uppy.on('dashboard:file-edit-start', (file) => {
|
||||
const fileName = file.name;
|
||||
});
|
||||
```
|
||||
|
||||
### Changes to pre-signing URLs for [`@uppy/aws-s3-multipart`][aws-s3-multipart]
|
||||
|
||||
See the Uppy 2.0.0 announcement post about the batch
|
||||
[pre-signing URLs change](/blog/2021/08/2.0/#Batch-pre-signing-URLs-for-AWS-S3-Multipart).
|
||||
|
||||
`prepareUploadPart` has been renamed to
|
||||
[`prepareUploadParts`](/docs/aws-s3-multipart/#prepareUploadParts-file-partData)
|
||||
(plural). See the documentation link on how to use this function.
|
||||
|
||||
### Removed the `.run` method from [`@uppy/core`][core]
|
||||
|
||||
The `.run` method on the `Uppy` instance has been removed. This method was
|
||||
already obsolete and only logged a warning. As of this major version, it no
|
||||
longer exists.
|
||||
|
||||
### Removed `resume` and `removeFingerprintOnSuccess` options from [`@uppy/tus`][tus]
|
||||
|
||||
Tus will now by default try to resume uploads if the upload has been started in
|
||||
the past.
|
||||
|
||||
This also means tus will store some data in localStorage for each upload, which
|
||||
will automatically be removed on success. Making `removeFingerprintOnSuccess`
|
||||
obsolete too.
|
||||
|
||||
### That’s it!
|
||||
|
||||
Uppy 1.0 will continue to receive bug fixes for three more months (until
|
||||
<time datetime="2021-12-01">1 December 2021</time>), security fixes for one more
|
||||
year (until <time datetime="2022-09-01">1 September 2022</time>), but no more
|
||||
new features after today. Exceptions are unlikely, but _can_ be made – to
|
||||
accommodate those with commercial support contracts, for example.
|
||||
|
||||
We hope you’ll waste no time in taking Uppy 2.0 out for a walk. When you do,
|
||||
please let us know what you thought of it on
|
||||
[Reddit](https://www.reddit.com/r/javascript/comments/penbr7/uppy_file_uploader_20_smaller_and_faster_modular/),
|
||||
[HN](https://news.ycombinator.com/item?id=28359287), ProductHunt, or
|
||||
[Twitter](https://twitter.com/uppy_io/status/1432399270846603264). We’re howling
|
||||
at the moon to hear from you!
|
||||
|
||||
## Migrate from Companion 1.x to 2.x
|
||||
|
||||
### Prerequisite
|
||||
|
||||
Since v2, you now need to be running `node.js >= v10.20.1` to use Companion.
|
||||
|
||||
### ProviderOptions
|
||||
|
||||
In v2 the `google` and `microsoft` [providerOptions](/docs/companion/#Options)
|
||||
have been changed to `drive` and `onedrive` respectively.
|
||||
|
||||
### OAuth Redirect URIs
|
||||
|
||||
On your Providers’ respective developer platforms, the OAuth redirect URIs that
|
||||
you should supply has now changed from:
|
||||
|
||||
`http(s)://$COMPANION_HOST_NAME/connect/$AUTH_PROVIDER/callback` in v1
|
||||
|
||||
to:
|
||||
|
||||
`http(s)://$COMPANION_HOST_NAME/$PROVIDER_NAME/redirect` in v2
|
||||
|
||||
#### New Redirect URIs
|
||||
|
||||
<div class="table-responsive">
|
||||
|
||||
| Provider | New Redirect URI |
|
||||
| ------------- | ---------------------------------------------------- |
|
||||
| Dropbox | `https://$COMPANION_HOST_NAME/dropbox/redirect` |
|
||||
| Google Drive | `https://$COMPANION_HOST_NAME/drive/redirect` |
|
||||
| Google Photos | `https://$COMPANION_HOST_NAME/googlephotos/redirect` |
|
||||
| OneDrive | `https://$COMPANION_HOST_NAME/onedrive/redirect` |
|
||||
| Box | `https://$YOUR_COMPANION_HOST_NAME/box/redirect` |
|
||||
| Facebook | `https://$COMPANION_HOST_NAME/facebook/redirect` |
|
||||
| Instagram | `https://$COMPANION_HOST_NAME/instagram/redirect` |
|
||||
|
||||
</div>
|
||||
|
||||
<!-- definitions -->
|
||||
|
||||
[core]: /docs/uppy/
|
||||
[xhr]: /docs/xhr-upload/
|
||||
[dashboard]: /docs/dashboard/
|
||||
[aws-s3-multipart]: /docs/aws-s3-multipart/
|
||||
[tus]: /docs/tus/
|
||||
1562
docs/locales.mdx
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"label": "Presets",
|
||||
"position": 9
|
||||
}
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
slug: /remote-sources
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Remote sources
|
||||
|
||||
`@uppy/remote-sources` is a preset plugin (meaning it bundles and sets up other
|
||||
plugins) to add all the available remote sources, such Instagram, Google Drive,
|
||||
Dropbox, and others to Uppy Dashboard in one package.
|
||||
|
||||
:::note
|
||||
|
||||
Remote Sources requires Dashboard and automatically installs all its plugins to
|
||||
it.
|
||||
|
||||
:::
|
||||
|
||||
## When should I use it?
|
||||
|
||||
`@uppy/remote-sources` aims to simplify the setup for adding Companion plugins,
|
||||
when you want to share the configuration between plugins. If you want your users
|
||||
to upload files from any of the remote sources that Uppy offers, this plugin is
|
||||
recommended.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the Remote Sources
|
||||
plugin to work. Companion handles authentication with the remote services (such
|
||||
as Facebook, Dropbox, etc.), downloads the files, and uploads them to the
|
||||
destination. This saves the user bandwidth, especially helpful if they are on a
|
||||
mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/remote-sources
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/remote-sources
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { RemoteSources } from "{{UPPY_JS_URL}}"
|
||||
const RemoteSources = new Uppy().use(RemoteSources)
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js {10} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import RemoteSources from '@uppy/remote-sources';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy();
|
||||
.use(Dashboard);
|
||||
.use(RemoteSources, { companionUrl: 'https://your-companion-url' });
|
||||
```
|
||||
|
||||
### Use with Transloadit
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
import RemoteSources from '@uppy/remote-sources';
|
||||
|
||||
uppy.use(RemoteSources, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
});
|
||||
```
|
||||
|
||||
You may also hit rate limits, because the OAuth application is shared between
|
||||
everyone using Transloadit.
|
||||
|
||||
To solve that, you can use your own OAuth keys with Transloadit’s hosted
|
||||
Companion servers by using Transloadit Template Credentials. [Create a Template
|
||||
Credential][template-credentials] on the Transloadit site. Select “Companion
|
||||
OAuth” for the service, and enter the key and secret for the provider you want
|
||||
to use. Then you can pass the name of the new credentials to that provider:
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
import RemoteSources from '@uppy/remote-sources';
|
||||
|
||||
uppy.use(RemoteSources, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
companionKeysParams: {
|
||||
GoogleDrive: { key: '...', credentialsName: '...' },
|
||||
Dropbox: { key: '...', credentialsName: '...' },
|
||||
// ...etc
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `RemoteSources`).
|
||||
|
||||
#### `sources`
|
||||
|
||||
List of remote sources that will be enabled (`array`, default:
|
||||
`['Box', 'Dropbox', 'Facebook', 'GoogleDrive','Instagram', 'OneDrive', 'Unsplash', 'Url', 'Zoom']`).
|
||||
|
||||
You don’t need to specify them manually or change them, but if you want to alter
|
||||
the order in which they appear in the Dashboard, or disable some sources, this
|
||||
option is for you.
|
||||
|
||||
```js
|
||||
uppy.use(RemoteSources, {
|
||||
companionUrl: 'https://your-companion-url',
|
||||
sources: ['Instagram', 'GoogleDrive', 'Unsplash', 'Url'],
|
||||
});
|
||||
```
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorized URL(s)/URL pattern(s) from which OAuth responses should
|
||||
be accepted (`string | RegExp | Array<string | RegExp>`, Default:
|
||||
`companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` object, or an array of both.
|
||||
|
||||
This is useful when you have your [Companion](/docs/companion) running on
|
||||
several hosts. Otherwise, the default value, which is `companionUrl`, should do
|
||||
fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the [`Request.credentials` value][], which tells the plugin
|
||||
whether to send cookies to [Companion](/docs/companion) (`string`, default: `same-origin`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string`, `Element`, `Function`, or `UIPlugin`, default:
|
||||
[`Dashboard`](/docs/dashboard)).
|
||||
|
||||
[`request.credentials` value]:
|
||||
https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials
|
||||
[template-credentials]:
|
||||
https://transloadit.com/docs/#how-to-create-template-credentials
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
import { QuickStartLinks } from '../src/components/QuickStartLinks/QuickStartLinks.tsx';
|
||||
|
||||
# Quick start
|
||||
|
||||
Uppy is a sleek, modular JavaScript file uploader that integrates seamlessly
|
||||
with any application. It’s fast, has a comprehensible API and lets you worry
|
||||
about more important problems than building a file uploader.
|
||||
|
||||
:::tip
|
||||
|
||||
You can take Uppy for a walk inside StackBlitz with a
|
||||
[minimal drag & drop](https://stackblitz.com/edit/vitejs-vite-yzbujq?file=main.js/g)
|
||||
experience or a
|
||||
[full featured dashboard](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js).
|
||||
|
||||
:::
|
||||
|
||||
<QuickStartLinks
|
||||
items={[
|
||||
{
|
||||
name: 'I want a full featured, extendable UI',
|
||||
description: 'Learn more about the Dashboard',
|
||||
link: '/docs/dashboard',
|
||||
},
|
||||
{
|
||||
name: 'I want a minimal drag & drop UI',
|
||||
description: 'We have a lightweight plugin for that',
|
||||
link: '/docs/drag-drop',
|
||||
},
|
||||
{
|
||||
name: 'Which uploader do I need?',
|
||||
description: 'Choosing the uploader you need',
|
||||
link: '/docs/guides/choosing-uploader',
|
||||
},
|
||||
{
|
||||
name: 'I want to add files from remote sources',
|
||||
description: 'Such as Google Drive, Dropbox, Instagram',
|
||||
link: '/docs/companion',
|
||||
},
|
||||
{
|
||||
name: 'I’d like a project example',
|
||||
description: 'Try out one of our extensive example projects',
|
||||
link: 'https://github.com/transloadit/uppy/tree/main/examples',
|
||||
},
|
||||
{
|
||||
name: 'I have a question',
|
||||
description: 'Our community forum is there to help',
|
||||
link: 'https://community.transloadit.com/',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"label": "Sources",
|
||||
"position": 6
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
---
|
||||
sidebar_position: 3
|
||||
slug: /audio
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Audio
|
||||
|
||||
The `@uppy/audio` plugin lets you record audio using a built-in or external
|
||||
microphone, or any other audio device, on desktop and mobile. The UI shows real
|
||||
time sound wavelength when recording.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want users to record audio on their computer.
|
||||
|
||||
## Install
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/audio
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/audio
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Dashboard, Audio } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Dashboard, { inline: true, target: 'body' })
|
||||
uppy.use(Audio)
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
```js {3,7,11} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Audio from '@uppy/audio';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
import '@uppy/audio/dist/style.min.css';
|
||||
|
||||
new Uppy().use(Dashboard, { inline: true, target: 'body' }).use(Audio);
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'audio'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Configures the title / name shown in the UI, for instance, on Dashboard tabs
|
||||
(`string`, default: `'Audio'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string`, `Element`, `Function`, or `UIPlugin`, default:
|
||||
[`Dashboard`](/docs/dashboard)).
|
||||
|
||||
#### `showAudioSourceDropdown`
|
||||
|
||||
Configures whether to show a dropdown to select audio device (`boolean`,
|
||||
default: `false`).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameAudio: 'Audio',
|
||||
// Used as the label for the button that starts an audio recording.
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
startAudioRecording: 'Begin audio recording',
|
||||
// Used as the label for the button that stops an audio recording.
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
stopAudioRecording: 'Stop audio recording',
|
||||
// Title on the “allow access” screen
|
||||
allowAudioAccessTitle: 'Please allow access to your microphone',
|
||||
// Description on the “allow access” screen
|
||||
allowAudioAccessDescription:
|
||||
'In order to record audio, please allow microphone access for this site.',
|
||||
// Title on the “device not available” screen
|
||||
noAudioTitle: 'Microphone Not Available',
|
||||
// Description on the “device not available” screen
|
||||
noAudioDescription:
|
||||
'In order to record audio, please connect a microphone or another audio input device',
|
||||
// Message about file size will be shown in an Informer bubble
|
||||
recordingStoppedMaxSize:
|
||||
'Recording stopped because the file size is about to exceed the limit',
|
||||
// Used as the label for the counter that shows recording length (`1:25`).
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
recordingLength: 'Recording length %{recording_length}',
|
||||
// Used as the label for the submit checkmark button.
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
submitRecordedFile: 'Submit recorded file',
|
||||
// Used as the label for the discard cross button.
|
||||
// This is not visibly rendered but is picked up by screen readers.
|
||||
discardRecordedFile: 'Discard recorded file',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"label": "Companion plugins",
|
||||
"position": 3,
|
||||
"collapsed": false
|
||||
}
|
||||
|
|
@ -1,226 +0,0 @@
|
|||
---
|
||||
slug: /box
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Box
|
||||
|
||||
The `@uppy/box` plugin lets users import files from their
|
||||
[Box](https://www.box.com/en-nl/home) account.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to let users import files from their
|
||||
[Box](https://www.box.com/en-nl/home) account.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the Box plugin to work.
|
||||
Companion handles authentication with Box, downloads the files, and uploads them
|
||||
to the destination. This saves the user bandwidth, especially helpful if they
|
||||
are on a mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/box
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/box
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Box } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Box, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
Using Box requires setup in both Uppy and Companion.
|
||||
|
||||
### Use in Uppy
|
||||
|
||||
```js {10-13} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Box from '@uppy/box';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: '#dashboard' })
|
||||
.use(Box, { companionUrl: 'https://your-companion.com' });
|
||||
```
|
||||
|
||||
### Use with Transloadit
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
import Box from '@uppy/box';
|
||||
|
||||
uppy.use(Box, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
});
|
||||
```
|
||||
|
||||
You may also hit rate limits, because the OAuth application is shared between
|
||||
everyone using Transloadit.
|
||||
|
||||
To solve that, you can use your own OAuth keys with Transloadit’s hosted
|
||||
Companion servers by using Transloadit Template Credentials. [Create a Template
|
||||
Credential][template-credentials] on the Transloadit site. Select “Companion
|
||||
OAuth” for the service, and enter the key and secret for the provider you want
|
||||
to use. Then you can pass the name of the new credentials to that provider:
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
import Box from '@uppy/box';
|
||||
|
||||
uppy.use(Box, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
companionKeysParams: {
|
||||
key: 'YOUR_TRANSLOADIT_API_KEY',
|
||||
credentialsName: 'my_companion_dropbox_creds',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Use in Companion
|
||||
|
||||
You can create a Box App on the
|
||||
[Box Developers site](https://app.box.com/developers/console).
|
||||
|
||||
Things to note:
|
||||
|
||||
- Choose `Custom App` and select the `User Authentication (OAuth 2.0)` app type.
|
||||
- You must enable full write access, or you will get
|
||||
[403 when downloading files](https://support.box.com/hc/en-us/community/posts/360049195613-403-error-while-file-download-API-Call)
|
||||
|
||||
You’ll be redirected to the app page. This page lists the client ID (app key)
|
||||
and client secret (app secret), which you should use to configure Companion.
|
||||
|
||||
The app page has a `"Redirect URIs"` field. Here, add:
|
||||
|
||||
```
|
||||
https://$YOUR_COMPANION_HOST_NAME/box/redirect
|
||||
```
|
||||
|
||||
If you are using Transloadit hosted Companion:
|
||||
|
||||
```
|
||||
https://api2.transloadit.com/companion/box/redirect
|
||||
```
|
||||
|
||||
You can only use the integration with your own account initially. Make sure to
|
||||
apply for production status on the app page before you publish your app, or your
|
||||
users will not be able to sign in!
|
||||
|
||||
Configure the Box key and secret. With the standalone Companion server, specify
|
||||
environment variables:
|
||||
|
||||
```shell
|
||||
export COMPANION_BOX_KEY="Box API key"
|
||||
export COMPANION_BOX_SECRET="Box API secret"
|
||||
```
|
||||
|
||||
When using the Companion Node.js API, configure these options:
|
||||
|
||||
```js
|
||||
companion.app({
|
||||
providerOptions: {
|
||||
box: {
|
||||
key: 'Box API key',
|
||||
secret: 'Box API secret',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Box'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Title / name shown in the UI, such as Dashboard tabs (`string`, default:
|
||||
`'Box'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string`, `Element`, `Function`, or `UIPlugin`, default:
|
||||
[`Dashboard`](/docs/dashboard)).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorised URL(s) from which OAuth responses should be accepted
|
||||
(`string` or `RegExp` or `Array`, default: `companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is
|
||||
useful when you have your [Companion](/docs/companion) running on several hosts.
|
||||
Otherwise, the default value should do fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameBox: 'Box',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
[template-credentials]:
|
||||
https://transloadit.com/docs/#how-to-create-template-credentials
|
||||
|
|
@ -1,226 +0,0 @@
|
|||
---
|
||||
slug: /dropbox
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Dropbox
|
||||
|
||||
The `@uppy/dropbox` plugin lets users import files from their
|
||||
[Dropbox](https://www.dropbox.com) account.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to let users import files from their
|
||||
[Dropbox](https://www.dropbox.com) account.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the Dropbox plugin to
|
||||
work. Companion handles authentication with Dropbox, downloads the files, and
|
||||
uploads them to the destination. This saves the user bandwidth, especially
|
||||
helpful if they are on a mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/dropbox
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/dropbox
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Dropbox } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Dropbox, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
Using Dropbox requires setup in both Uppy and Companion.
|
||||
|
||||
### Use in Uppy
|
||||
|
||||
```js {10-13} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Dropbox from '@uppy/dropbox';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: '#dashboard' })
|
||||
.use(Dropbox, { companionUrl: 'https://your-companion.com' });
|
||||
```
|
||||
|
||||
### Use with Transloadit
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
import Dropbox from '@uppy/dropbox';
|
||||
|
||||
uppy.use(Dropbox, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
});
|
||||
```
|
||||
|
||||
You may also hit rate limits, because the OAuth application is shared between
|
||||
everyone using Transloadit.
|
||||
|
||||
To solve that, you can use your own OAuth keys with Transloadit’s hosted
|
||||
Companion servers by using Transloadit Template Credentials. [Create a Template
|
||||
Credential][template-credentials] on the Transloadit site. Select “Companion
|
||||
OAuth” for the service, and enter the key and secret for the provider you want
|
||||
to use. Then you can pass the name of the new credentials to that provider:
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
import Dropbox from '@uppy/dropbox';
|
||||
|
||||
uppy.use(Dropbox, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
companionKeysParams: {
|
||||
key: 'YOUR_TRANSLOADIT_API_KEY',
|
||||
credentialsName: 'my_companion_dropbox_creds',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Use in Companion
|
||||
|
||||
You can create a Dropbox App on the
|
||||
[Dropbox Developers site](https://www.dropbox.com/developers/apps/create).
|
||||
|
||||
Things to note:
|
||||
|
||||
- Choose the “Dropbox API”, not the business variant.
|
||||
- Typically you’ll want “Full Dropbox” access, unless you are absolutely certain
|
||||
that you need the other one.
|
||||
|
||||
You’ll be redirected to the app page. This page lists the app key and app
|
||||
secret, which you should use to configure Companion as shown above.
|
||||
|
||||
The app page has a “Redirect URIs” field. Here, add:
|
||||
|
||||
```
|
||||
https://$YOUR_COMPANION_HOST_NAME/dropbox/redirect
|
||||
```
|
||||
|
||||
If you are using Transloadit hosted Companion:
|
||||
|
||||
```
|
||||
https://api2.transloadit.com/companion/dropbox/redirect
|
||||
```
|
||||
|
||||
You can only use the integration with your own account initially. Make sure to
|
||||
apply for production status on the app page before you publish your app, or your
|
||||
users will not be able to sign in!
|
||||
|
||||
Configure the Dropbox key and secret. With the standalone Companion server,
|
||||
specify environment variables:
|
||||
|
||||
```shell
|
||||
export COMPANION_DROPBOX_KEY="Dropbox API key"
|
||||
export COMPANION_DROPBOX_SECRET="Dropbox API secret"
|
||||
```
|
||||
|
||||
When using the Companion Node.js API, configure these options:
|
||||
|
||||
```js
|
||||
companion.app({
|
||||
providerOptions: {
|
||||
dropbox: {
|
||||
key: 'Dropbox API key',
|
||||
secret: 'Dropbox API secret',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Dropbox'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Title / name shown in the UI, such as Dashboard tabs (`string`, default:
|
||||
`'Dropbox'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string`, `Element`, `Function`, or `UIPlugin`, default:
|
||||
[`Dashboard`](/docs/dashboard)).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorised URL(s) from which OAuth responses should be accepted
|
||||
(`string` or `RegExp` or `Array`, default: `companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is
|
||||
useful when you have your [Companion](/docs/companion) running on several hosts.
|
||||
Otherwise, the default value should do fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameDropbox: 'Dropbox',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
[template-credentials]:
|
||||
https://transloadit.com/docs/#how-to-create-template-credentials
|
||||
|
|
@ -1,223 +0,0 @@
|
|||
---
|
||||
slug: /facebook
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
import UppyCdnExample from '/src/components/UppyCdnExample';
|
||||
|
||||
# Facebook
|
||||
|
||||
The `@uppy/facebook` plugin lets users import files from their
|
||||
[Facebook](https://www.facebook.com) account.
|
||||
|
||||
:::tip
|
||||
|
||||
[Try out the live example](/examples) or take it for a spin in
|
||||
[StackBlitz](https://stackblitz.com/edit/vitejs-vite-zaqyaf?file=main.js).
|
||||
|
||||
:::
|
||||
|
||||
## When should I use this?
|
||||
|
||||
When you want to let users import files from their
|
||||
[Facebook](https://www.facebook.com) account.
|
||||
|
||||
A [Companion](/docs/companion) instance is required for the Facebook plugin to
|
||||
work. Companion handles authentication with Facebook, downloads the files, and
|
||||
uploads them to the destination. This saves the user bandwidth, especially
|
||||
helpful if they are on a mobile connection.
|
||||
|
||||
You can self-host Companion or get a hosted version with any
|
||||
[Transloadit plan](https://transloadit.com/pricing/).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="npm" label="NPM" default>
|
||||
|
||||
```shell
|
||||
npm install @uppy/facebook
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="yarn" label="Yarn">
|
||||
|
||||
```shell
|
||||
yarn add @uppy/facebook
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="cdn" label="CDN">
|
||||
<UppyCdnExample>
|
||||
{`
|
||||
import { Uppy, Facebook } from "{{UPPY_JS_URL}}"
|
||||
const uppy = new Uppy()
|
||||
uppy.use(Facebook, {
|
||||
// Options
|
||||
})
|
||||
`}
|
||||
</UppyCdnExample>
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Use
|
||||
|
||||
Using Facebook requires setup in both Uppy and Companion.
|
||||
|
||||
### Use in Uppy
|
||||
|
||||
```js {10-13} showLineNumbers
|
||||
import Uppy from '@uppy/core';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Facebook from '@uppy/facebook';
|
||||
|
||||
import '@uppy/core/dist/style.min.css';
|
||||
import '@uppy/dashboard/dist/style.min.css';
|
||||
|
||||
new Uppy()
|
||||
.use(Dashboard, { inline: true, target: '#dashboard' })
|
||||
.use(Facebook, { companionUrl: 'https://your-companion.com' });
|
||||
```
|
||||
|
||||
### Use with Transloadit
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
import Facebook from '@uppy/facebook';
|
||||
|
||||
uppy.use(Facebook, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
});
|
||||
```
|
||||
|
||||
You may also hit rate limits, because the OAuth application is shared between
|
||||
everyone using Transloadit.
|
||||
|
||||
To solve that, you can use your own OAuth keys with Transloadit’s hosted
|
||||
Companion servers by using Transloadit Template Credentials. [Create a Template
|
||||
Credential][template-credentials] on the Transloadit site. Select “Companion
|
||||
OAuth” for the service, and enter the key and secret for the provider you want
|
||||
to use. Then you can pass the name of the new credentials to that provider:
|
||||
|
||||
```js
|
||||
import { COMPANION_URL, COMPANION_ALLOWED_HOSTS } from '@uppy/transloadit';
|
||||
import Facebook from '@uppy/facebook';
|
||||
|
||||
uppy.use(Facebook, {
|
||||
companionUrl: COMPANION_URL,
|
||||
companionAllowedHosts: COMPANION_ALLOWED_HOSTS,
|
||||
companionKeysParams: {
|
||||
key: 'YOUR_TRANSLOADIT_API_KEY',
|
||||
credentialsName: 'my_companion_dropbox_creds',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Use in Companion
|
||||
|
||||
You can create a Facebook App on the
|
||||
[Facebook Developers site](https://developers.facebook.com/apps).
|
||||
|
||||
The app page has a “Redirect URIs” field. Here, add:
|
||||
|
||||
```
|
||||
https://$YOUR_COMPANION_HOST_NAME/facebook/redirect
|
||||
```
|
||||
|
||||
If you are using Transloadit hosted Companion:
|
||||
|
||||
```
|
||||
https://api2.transloadit.com/companion/facebook/redirect
|
||||
```
|
||||
|
||||
You can only use the integration with your own account initially. Make sure to
|
||||
apply for production status on the app page before you publish your app, or your
|
||||
users will not be able to sign in!
|
||||
|
||||
You need to set up OAuth in your Facebook app for Companion to be able to
|
||||
connect to users’ Facebook accounts. You have to enable “Advanced Access” for
|
||||
the `user_photos` permission. A precondition of that is “Business Verification”
|
||||
which involves setting up a Meta Business Account and submitting documents to
|
||||
prove business ownership.
|
||||
|
||||
Configure the Facebook key and secret. With the standalone Companion server,
|
||||
specify environment variables:
|
||||
|
||||
```shell
|
||||
export COMPANION_FACEBOOK_KEY="Facebook API key"
|
||||
export COMPANION_FACEBOOK_SECRET="Facebook API secret"
|
||||
```
|
||||
|
||||
When using the Companion Node.js API, configure these options:
|
||||
|
||||
```js
|
||||
companion.app({
|
||||
providerOptions: {
|
||||
facebook: {
|
||||
key: 'Facebook API key',
|
||||
secret: 'Facebook API secret',
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Options
|
||||
|
||||
#### `id`
|
||||
|
||||
A unique identifier for this plugin (`string`, default: `'Facebook'`).
|
||||
|
||||
#### `title`
|
||||
|
||||
Title / name shown in the UI, such as Dashboard tabs (`string`, default:
|
||||
`'Facebook'`).
|
||||
|
||||
#### `target`
|
||||
|
||||
DOM element, CSS selector, or plugin to place the drag and drop area into
|
||||
(`string`, `Element`, `Function`, or `UIPlugin`, default:
|
||||
[`Dashboard`](/docs/dashboard)).
|
||||
|
||||
#### `companionUrl`
|
||||
|
||||
URL to a [Companion](/docs/companion) instance (`string`, default: `null`).
|
||||
|
||||
#### `companionHeaders`
|
||||
|
||||
Custom headers that should be sent along to [Companion](/docs/companion) on
|
||||
every request (`Object`, default: `{}`).
|
||||
|
||||
#### `companionAllowedHosts`
|
||||
|
||||
The valid and authorised URL(s) from which OAuth responses should be accepted
|
||||
(`string` or `RegExp` or `Array`, default: `companionUrl`).
|
||||
|
||||
This value can be a `string`, a `RegExp` pattern, or an `Array` of both. This is
|
||||
useful when you have your [Companion](/docs/companion) running on several hosts.
|
||||
Otherwise, the default value should do fine.
|
||||
|
||||
#### `companionCookiesRule`
|
||||
|
||||
This option correlates to the
|
||||
[RequestCredentials value](https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials)
|
||||
(`string`, default: `'same-origin'`).
|
||||
|
||||
This tells the plugin whether to send cookies to [Companion](/docs/companion).
|
||||
|
||||
#### `locale`
|
||||
|
||||
```js
|
||||
export default {
|
||||
strings: {
|
||||
pluginNameFacebook: 'Facebook',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
[template-credentials]:
|
||||
https://transloadit.com/docs/#how-to-create-template-credentials
|
||||