diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e71c530d..d7f4ab9ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,9 @@ PRs are welcome! Please do open an issue to discuss first if it's a big feature, What we need to do to release Uppy 1.0 - [ ] website: big release blog post +- [ ] chore: hunt down all `@TODO`s and either fix, or remove, or move to github issues/changelog backlog +- [ ] chore: remove dead code/commented blocks +- [ ] chore: rewrite all code based on prettier+standardjs.com - [ ] ~refactoring: Make `uppy-server` module live in main Uppy repo in `./server` as a second stage todo (after Lerna is done and we're happy) (@ife) - [ ] QA: manually test in multiple browsers and mobile devices again (SauceLabs can do Android/iOS too) (@nqst) - [ ] QA: add one integration test that uses a Webpack and React/Redux environment (e.g. via `create-react-app`) (@goto-bus-stop) diff --git a/package.json b/package.json index 9d875ebfd..ea2cae7ed 100644 --- a/package.json +++ b/package.json @@ -134,8 +134,8 @@ "build:lib": "babel --version && babel src --source-maps -d lib", "build": "npm-run-all --parallel build:js build:css --serial build:gzip size", "clean": "rm -rf lib && rm -rf dist", - "lint:fix": "eslint src test website/build-examples.js website/update.js website/themes/uppy/source/js/common.js --fix", - "lint": "eslint src test website/build-examples.js website/update.js website/themes/uppy/source/js/common.js", + "lint:fix": "eslint src test website/scripts website/build-examples.js website/update.js website/themes/uppy/source/js/common.js --fix", + "lint": "eslint src test website/scripts website/build-examples.js website/update.js website/themes/uppy/source/js/common.js", "lint-staged": "lint-staged", "release:major": "env SEMANTIC=major npm run release", "release:minor": "env SEMANTIC=minor npm run release", diff --git a/src/plugins/AwsS3/index.js b/src/plugins/AwsS3/index.js index 8293bd8f5..05dd67c04 100644 --- a/src/plugins/AwsS3/index.js +++ b/src/plugins/AwsS3/index.js @@ -159,7 +159,10 @@ module.exports = class AwsS3 extends Plugin { // If no response, we've hopefully done a PUT request to the file // in the bucket on its full URL. if (!isXml(xhr)) { - return { location: xhr.responseURL } + // Trim the query string because it's going to be a bunch of presign + // parameters for a PUT request—doing a GET request with those will + // always result in an error + return { location: xhr.responseURL.replace(/\?.*$/, '') } } let getValue = () => '' diff --git a/src/plugins/Dashboard/index.js b/src/plugins/Dashboard/index.js index 9434edabb..b959dce69 100644 --- a/src/plugins/Dashboard/index.js +++ b/src/plugins/Dashboard/index.js @@ -593,19 +593,19 @@ module.exports = class Dashboard extends Plugin { uninstall () { if (!this.opts.disableInformer) { - const informer = this.uppy.getPlugin('Informer') + const informer = this.uppy.getPlugin(`${this.id}:Informer`) // Checking if this plugin exists, in case it was removed by uppy-core // before the Dashboard was. if (informer) this.uppy.removePlugin(informer) } if (!this.opts.disableStatusBar) { - const statusBar = this.uppy.getPlugin('StatusBar') + const statusBar = this.uppy.getPlugin(`${this.id}:StatusBar`) if (statusBar) this.uppy.removePlugin(statusBar) } if (!this.opts.disableThumbnailGenerator) { - const thumbnail = this.uppy.getPlugin('ThumbnailGenerator') + const thumbnail = this.uppy.getPlugin(`${this.id}:ThumbnailGenerator`) if (thumbnail) this.uppy.removePlugin(thumbnail) } diff --git a/src/scss/_dashboard.scss b/src/scss/_dashboard.scss index 182600456..0035d660c 100644 --- a/src/scss/_dashboard.scss +++ b/src/scss/_dashboard.scss @@ -254,6 +254,7 @@ .uppy-DashboardTab-name { font-size: 8px; + line-height: 11px; margin-top: 5px; margin-bottom: 0; font-weight: 500; diff --git a/src/server/RequestClient.js b/src/server/RequestClient.js index 013a3de0e..95788b7b2 100644 --- a/src/server/RequestClient.js +++ b/src/server/RequestClient.js @@ -2,6 +2,11 @@ require('whatwg-fetch') +// Remove the trailing slash so we can always safely append /xyz. +function stripSlash (url) { + return url.replace(/\/$/, '') +} + module.exports = class RequestClient { constructor (uppy, opts) { this.uppy = uppy @@ -12,7 +17,7 @@ module.exports = class RequestClient { get hostname () { const { uppyServer } = this.uppy.getState() const host = this.opts.host - return uppyServer && uppyServer[host] ? uppyServer[host] : host + return stripSlash(uppyServer && uppyServer[host] ? uppyServer[host] : host) } get defaultHeaders () { diff --git a/src/server/RequestClient.test.js b/src/server/RequestClient.test.js new file mode 100644 index 000000000..6c07151fd --- /dev/null +++ b/src/server/RequestClient.test.js @@ -0,0 +1,12 @@ +const RequestClient = require('./RequestClient') + +describe('RequestClient', () => { + it('has a hostname without trailing slash', () => { + const mockCore = { getState: () => ({}) } + const a = new RequestClient(mockCore, { host: 'http://server.uppy.io' }) + const b = new RequestClient(mockCore, { host: 'http://server.uppy.io/' }) + + expect(a.hostname).toBe('http://server.uppy.io') + expect(b.hostname).toBe('http://server.uppy.io') + }) +}) diff --git a/src/views/ProviderView/Browser.js b/src/views/ProviderView/Browser.js index f755b0d6b..e7878f8c5 100644 --- a/src/views/ProviderView/Browser.js +++ b/src/views/ProviderView/Browser.js @@ -21,11 +21,12 @@ const Browser = (props) => {
([\s\S]*?)<\/code><\/pre>/igm
+
+function highlight (lang, code) {
+ const startTag = ``
+ const endTag = `
`
+ let parsedCode = ''
+ if (Prism.languages[lang]) {
+ parsedCode = Prism.highlight(code, Prism.languages[lang])
+ } else {
+ parsedCode = code
+ }
+
+ return startTag + parsedCode + endTag
+}
+
+function prismify (data) {
+ data.content = data.content.replace(unhighlightedCodeRx,
+ (_, lang, code) => highlight(lang, entities.decode(code)))
+
+ return data
+}
+
+function code (args, content) {
+ let lang = ''
+ if (args[0].startsWith('lang:')) {
+ lang = args.shift().replace(/^lang:/, '')
+ }
+
+ return highlight(lang, content)
+}
+
+function includeCode (args) {
+ let lang = ''
+ if (args[0].startsWith('lang:')) {
+ lang = args.shift().replace(/^lang:/, '')
+ }
+
+ const file = path.join(hexo.source_dir, hexo.config.code_dir, args.join(' '))
+ return readFile(file, 'utf8').then((code) => highlight(lang, code.trim()))
+}
+
+// Highlight as many things as we possibly can
+hexo.extend.tag.register('code', code, true)
+hexo.extend.tag.register('codeblock', code, true)
+hexo.extend.tag.register('include_code', includeCode, { async: true })
+hexo.extend.tag.register('include-code', includeCode, { async: true })
+
+// Hexo includes its own code block handling by default which may
+// cause the above to miss some things, so do another pass when the page
+// is done rendering to pick up any code blocks we may have missed.
+hexo.extend.filter.register('after_post_render', prismify)
diff --git a/website/src/api-usage-example.ejs b/website/src/api-usage-example.js
similarity index 99%
rename from website/src/api-usage-example.ejs
rename to website/src/api-usage-example.js
index 562b54a16..8a2a1ef7d 100644
--- a/website/src/api-usage-example.ejs
+++ b/website/src/api-usage-example.js
@@ -1,7 +1,7 @@
import Uppy from 'uppy/lib/core'
import Dashboard from 'uppy/lib/plugins/Dashboard'
import Tus from 'uppy/lib/plugins/Tus'
-
+
Uppy({ autoProceed: false })
.use(Dashboard, { trigger: '#select-files' })
.use(Tus, { endpoint: 'https://master.tus.io/files/' })
diff --git a/website/src/docs/architecture.md b/website/src/docs/architecture.md
deleted file mode 100644
index 3deea7e00..000000000
--- a/website/src/docs/architecture.md
+++ /dev/null
@@ -1,149 +0,0 @@
----
-type: api
-order: 10
-title: "Architecture"
-permalink: api/architecture/
-published: false
----
-
-Uppy file uploader consists of a lean [Core](https://github.com/transloadit/uppy/blob/master/src/core/Core.js) module and [Plugins](https://github.com/transloadit/uppy/tree/master/src/plugins) (see simple [Input](https://github.com/transloadit/uppy/blob/master/src/plugins/Formtag.js) as an example) that extend it’s functionality. Like this:
-
-{% include_code lang:js ../api-usage-example.ejs %}
-
-## Core
-
-1. Core module orchestrates Uppy plugins, stores `state` with `files`, and exposes useful methods like `addFile`, `setState`, `upload` to the user and plugins. Plugins are added to Uppy with `.use(Plugin, opts)` API, like so: `.use(DragDrop, {target: 'body'})`.
-
-2. Internally Core then instantiates plugins via `new Plugin(this, opts)`, passing options to them, then places them in `plugins` object, nested by type: `uploader`, `progressindicator`, `acquirer`, etc. Core then iterates over `plugins` and calls `install` on each of them. In it’s `install` method a plugin can set event listeners to react to things happening in Uppy (upload progress, file was removed), or do anything else needed on init.
-
-3. Each time `state` is updated with `setState(statePatch)`, Core runs `updateAll()` method that re-renders all of the view plugins (components) that have been mounted in the DOM somewhere, passing the new state to each of them.
-
-Core is very lean (at least we try to keep it sobe), and acts as a glue that ties together all the plugins, shared data and functionality together. It keeps `state` with `files`, `capabilities`, plus exposes a few methods that are called by plugins to update state — adding files, adding preview thumbnails to images, updating progress for each file and total progress, etc.
-
-*Comment: There is a discussion that these methods could in theory all live in Utils or be standalone modules used by plugins and the user directly, see https://github.com/transloadit/uppy/issues/116#issuecomment-247695921.*
-
-### getState()
-
-Returns current state.
-
-### setState({itemToUpdate: data})
-
-Updates state with new state (Object.assign({}, this.state, newState)) and then runs `updateAll()`.
-
-### updateAll()
-
-Iterates over all `plugins` and runs `update()` on each of them.
-
-### updateMeta(data, fileID)
-
-Given `{ size: 1200 }` adds that metadata to a file.
-
-### addFile(file)
-
-Adds a new file to `state`. This method is used by `acquirer` plugins like Drag & Drop, Webcam and Google Drive,
-or can be called by the user on uppy instance directly: `uppy.addFile(myFile)`.
-
-Normalizes that file too: tries to figure out file type by extension if mime is missing, use noname if name is missing, sets progress: 0 and so on.
-
-### removeFile(fileID)
-
-Removes file from `state.files`.
-
-### addThumbnail(fileID)
-
-Reads image from file’s data object in base64 and resizes that, using canvas. Then `state` is updated with a file that has thumbnail in it. Thumbnails are used for file previews by plugins like Dashboard.
-
-### state.capabilities
-
-Core (or plugins) check and set capabilities, like: `resumable: true` (this is set by Tus Plugin), `touch: false`, `dragdrop: true`, that could possibly be used by all plugins.
-
-### log(msg)
-
-Logs stuff to console only if user specified `debug: true`, silent in production.
-
-### core.on('event', action), core.emit('event')
-
-An event emitter embedded into Core that is passed to Plugins, and can be used directly on the instance. Used by Plugins for communicating with other Plugins and Core.
-
-## Events
-
-- Core listens for `core:upload-progress` event and calculates speed & ETA for all files currently in progress. *Uploader plugins could just call core.updateProgress().*
-- Core checks if browser in offline or online and emits `core:is-offline` or `core:is-online` that other plugins can listen to.
-- Any plugin can emit `informer` event that `Informer` plugin can use to display info bubbles. Currently only used in the Dashboard plugin. Example: `core.emitter.emit('informer', 'Connected!', 'success', 3000)`. *(Should this be a Core method instead of Plugin? Could be replaced by core.inform(info) method that will just update state with info)*
-- Uploader plugins listen to `core:upload` event (can be emitted after a file has been added or when upload button has been pressed), get files via `core.getState().files`, filter those that are not marked as complete and upload them, emitting progress events.
-
-*See discussion about Core and Event Emitter: https://github.com/transloadit/uppy/issues/116#issuecomment-247695921*
-
-## Plugins
-
-Plugins extend the functionality of Core (which itself is very much barebones, does almost nothing). Plugins actually do the work — select files, modify and upload them, and show the UI.
-
-Plugins that have some UI can be mounted anywhere in the dom. With current design you can have a Drag & Drop area in `#dragdrop` and Progressbar in `body`. Plugins can also be mounted into other plugins that support that, like Dashboard.
-
-Each plugin extends `Plugin` class with default methods that can be overridden:
-
-### update()
-
-Gets called when state changes and `updateAll()` is called from Core. Checks if a DOM element (tree) has been created with a reference for it stored in plugin’s `this.el`. If so, creates a new element (tree) `const newEl = this.render(currentState)` for current plugin and then calls `yo.update(this.el, newEl)` to effectively update the existing element to match the new one (morphdom is behind that).
-
-All together now:
-
-``` javascript
-const newEl = this.render(currentState)
-yo.update(this.el, newEl)
-```
-
-### mount(target, plugin)
-
-Called by the plugin itself, if it is a UI plugin that needs to do something in DOM. A `target` is passed as an argument. There are two possible scenarios for mounting:
-
-1\. If `typeof target === 'string'`, then the element (tree) is rendered and gets mounted to the DOM:
-
-``` javascript
-this.el = plugin.render(this.core.state)
-document.querySelector(target).appendChild(this.el)
-```
-
-2\. If `typeof target === 'object'`, then that plugin is found in `plugins` object of Core and `addTarget()` is called on that plugin.
-
-``` javascript
-const targetPlugin = this.core.getPlugin(targetPluginName)
-const selectorTarget = targetPlugin.addTarget(plugin)
-```
-
-### focus()
-
-Called when plugin is in focus, like when that plugin’s tab is selected in the Dashboard.
-
-### install()
-
-Called by Core when it instantiates new plugins. In `install`
-a plugin can extend global state with its own state (like `{ modal: { isHidden: true } }`), or do anything needed on initialization, including `mount()`.
-
-### Plugins are currently of the following types:
-
-### acquirer
-
-- **DragDrop** — simple DragDrop, once file is dropped on a target, it gets added to state.files. “click here to select” too
-- **GoogleDrive** — GoogleDrive UI, uses uppy-server component. Files are downloaded from Google Drive to uppy-server, without having to go through the client, saving bandwidth and space
-- **Formtag** — simple input[type=file] element
-- **Webcam** — allows to take pictures with your webcam, works in most browsers, except Safari. Flash (ugh) is used for fallback
-
-### orchestrator
-
-*Should probably be called UI*
-
-- **Dashboard** — the full-featured interface for interacting with Uppy. Supports drag & dropping files, provides UI for Google Drive, Webcam and any other plugin, shows selected files, shows progress on them.
-
-### progressindicator
-
-- **ProgressBar** — tiny progressbar that can be mounted anywhere.
-
-### uploader
-
-- **Tus** — tus resumable file uploads, see http://tus.io
-- **Multipart** — regular form/multipart/xhr uploads
-
-### modifier
-
-- **Metadata** — allows adding custom metadata fields, data from each field is then added to a file
diff --git a/website/src/docs/contributing.md b/website/src/docs/contributing.md
index de00951d8..04723815d 100644
--- a/website/src/docs/contributing.md
+++ b/website/src/docs/contributing.md
@@ -4,7 +4,7 @@ type: docs
order: 4
---
-## Uppy Development
+## Uppy development
Fork the repository into your own account first. See the [GitHub Help](https://help.github.com/articles/fork-a-repo/) article for instructions.
@@ -16,7 +16,7 @@ cd uppy
npm install
```
-Our website’s examples section is also our playground, please read [Local Previews](#Local-Previews) section to get up and running.
+Our website’s examples section is also our playground, please read the [Local Previews](#Local-Previews) section to get up and running.
## Tests
@@ -24,7 +24,7 @@ Unit tests are using Jest and can be run with:
`npm run test:unit`
-For acceptance (or end to end) tests, we use [Webdriverio](http://webdriver.io). For it to run locally, you need to install selenium standalone server, just follow [the guide](http://webdriver.io/guide.html) to do so. You can also install Selenium Standalone server from NPM:
+For acceptance (or end-to-end) tests, we use [Webdriverio](http://webdriver.io). For it to run locally, you need to install a Selenium standalone server. Just follow [the guide](http://webdriver.io/guide.html) to do so. You can also install a Selenium standalone server from NPM:
```bash
npm install selenium-standalone -g
@@ -35,30 +35,31 @@ And then launch it:
`selenium-standalone start`
-After you’ve installed and launched the selenium standalone server, run:
+After you have installed and launched the selenium standalone server, run:
`npm run test:acceptance:local`
These tests are also run automatically on Travis builds with [SauceLabs](https://saucelabs.com/) cloud service using different OSes.
-## Website Development
+## Website development
-We keep the [uppy.io](http://uppy.io) website in `./website` so it’s easy to keep docs & code in sync as we’re still iterating at high velocity.
+We keep the [uppy.io](http://uppy.io) website in `./website`, so it’s easy to keep docs and code in sync as we are still iterating at high velocity.
The site is built with [Hexo](http://hexo.io/), and Travis automatically deploys this onto GitHub Pages (it overwrites the `gh-pages` branch with Hexo's build at every change to `master`). The content is written in Markdown and located in `./website/src`. Feel free to fork & hack!
-Even though bundled in this repo, the website is regarded as a separate project. So it has its own `package.json` and we aim to keep the surface where the two projects interface as small as possible. `./website/update.js` is called during website builds to inject the Uppy knowledge into the site.
+Even though bundled in this repo, the website is regarded as a separate project. As such, it has its own `package.json` and we aim to keep the surface where the two projects interface as small as possible. `./website/update.js` is called during website builds to inject the Uppy knowledge into the site.
-### Local Previews
+### Local previews
-It's recommended to exclude `./website/public/` from your editor if you want efficient searches.
+It is recommended to exclude `./website/public/` from your editor if you want efficient searches.
To install the required node modules, type:
+
```bash
npm install && cd website && npm install && cd ..
```
-For local previews on http://127.0.0.1:4000 type:
+For local previews on http://127.0.0.1:4000, type:
```bash
npm start
@@ -66,7 +67,7 @@ npm start
This will watch the website, as well as Uppy, as the examples, and rebuild everything and refresh your browser as files change.
-Then, to work on e.g. the XHRUpload example, you'd edit the following files:
+Then, to work on, for instance, the XHRUpload example, you would edit the following files:
```bash
atom src/core/Core.js \
@@ -75,13 +76,13 @@ atom src/core/Core.js \
website/src/examples/xhrupload/app.es6
```
-And open in your webbrowser.
+And open in your web browser.
-## CSS Guidelines
+## CSS guidelines
-The CSS standards followed in this project closely resemble those from [Medium's CSS Guidelines](https://gist.github.com/fat/a47b882eb5f84293c4ed). If it's not mentioned here, follow their 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
+### 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).
@@ -118,7 +119,7 @@ Syntax: [-][-descendentName][--modifierName]
### 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:
+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 */
@@ -138,11 +139,11 @@ This project uses SASS, with some limitations on nesting. One-level deep nestin
}
```
-### Mobile-first Responsive Approach
+### 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
+### Selector, rule ordering
- All selectors are sorted alphabetically and by type.
- HTML elements go above classes and IDs in a file.
diff --git a/website/src/docs/plugins.md b/website/src/docs/plugins.md
index dc4ab54e3..007b91e4e 100644
--- a/website/src/docs/plugins.md
+++ b/website/src/docs/plugins.md
@@ -8,8 +8,8 @@ order: 10
Plugins are what makes Uppy useful: they help select, manipulate and upload files.
- **Acquirers (various ways of picking files):**
- - [Dashboard](/docs/dashboard) — full featured sleek UI with file previews, metadata editing, upload/pause/resume/cancel buttons and more. Includes `StatusBar` and `Informer` plugins by default
- - [DragDrop](/docs/dragdrop) — plain and simple drag and drop area
+ - [Dashboard](/docs/dashboard) — full-featured sleek UI with file previews, metadata editing, upload/pause/resume/cancel buttons and more. Includes `StatusBar` and `Informer` plugins by default
+ - [DragDrop](/docs/dragdrop) — plain and simple drag-and-drop area
- [FileInput](/docs/fileinput) — even more plain and simple, just a button
- [Webcam](/docs/webcam) — upload selfies or audio / video recordings
- [Provider Plugins](/docs/providers) (remote sources that work through [Uppy Server](/docs/uppy-server/))
@@ -27,13 +27,13 @@ Plugins are what makes Uppy useful: they help select, manipulate and upload file
- [Informer](/docs/informer) — show notifications
- **Helpers:**
- [GoldenRetriever](/docs/golden-retriever) — restore files and continue uploading after a page refresh or a browser crash
- - [Form](/docs/form) — collect metadata from `