diff --git a/.gitignore b/.gitignore index 8d44dfa..52ad76e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ dist/ .DS_Store node_modules npm-debug.log +yarn-error.log diff --git a/gulpfile.babel.js b/gulpfile.babel.js index b5f2c30..cf46b84 100755 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -14,15 +14,12 @@ import BrowserSync from "browser-sync"; import webpack from "webpack"; import webpackConfig from "./webpack.conf"; -import markWithMagic from './markdown.config.js'; - const browserSync = BrowserSync.create(); const hugoBin = "hugo"; const defaultArgs = ["-d", "../dist", "-s", "site", "-v"]; gulp.task("hugo", (cb) => buildSite(cb)); gulp.task("hugo-preview", (cb) => buildSite(cb, ["--buildDrafts", "--buildFuture"])); -gulp.task("docs", (cb) => markWithMagic()) gulp.task("build", ["css", "docs", "js", "fonts", "images", "hugo"]); gulp.task("build-preview", ["css", "docs", "js", "fonts", "images", "hugo-preview"]); diff --git a/markdown.config.js b/markdown.config.js deleted file mode 100644 index 76b4a0a..0000000 --- a/markdown.config.js +++ /dev/null @@ -1,33 +0,0 @@ -const path = require('path') -const markdownMagic = require('markdown-magic') - -const config = {} -const callback = function(updatedContent, outputConfig) { - console.log('all set!') -} - -const docFiles = [ - 'architecture.md', - 'editorial-workflow.md', - 'extending.md', - 'intro.md', - 'quick-start.md', - 'validation.md', - 'widgets.md', - 'contributor-guide.md', - 'customization.md', - 'test-drive.md', - 'custom-authentication.md', -] - -const markdownPaths = docFiles.map((file) => { - return path.join(__dirname, `/site/content/docs/${file}`) -}) - -function markWithMagic(filePaths) { - markdownMagic(filePaths, config, callback) -} - -export default function makeMagic() { - markWithMagic(markdownPaths) -} diff --git a/package.json b/package.json index 1d8e2a7..ee0dfab 100755 --- a/package.json +++ b/package.json @@ -9,8 +9,7 @@ "build": "gulp build", "build-preview": "gulp build-preview", "start": "gulp server", - "lint": "eslint src", - "docs": "node markdown.config.js" + "lint": "eslint src" }, "author": "", "license": "MIT", @@ -49,7 +48,5 @@ "whatwg-fetch": "^1.0.0", "yamljs": "^0.2.8" }, - "devDependencies": { - "markdown-magic": "^0.1.13" - } + "devDependencies": {} } diff --git a/site/content/docs/architecture.md b/site/content/docs/architecture.md index 16adaf5..1c981a5 100755 --- a/site/content/docs/architecture.md +++ b/site/content/docs/architecture.md @@ -2,6 +2,64 @@ title: Architecture position: 9 --- - - +# Technical Architecture + +Netlify CMS is a React application, using Redux for state management with immutable data structures (immutable.js). + +The core abstractions for content editing are `collections`, `entries` and `widgets`. + +Each `collection` represents a collection of entries. This can either be a collection of similar entries with the same structure, or a set of entries where each has its own structure. + +The structure of an entry is defined as a series of fields, each with a `name`, a `label`, and a `widget` . + +The `widget` determines the UI widget that the content editor will use when editing this field of an entry, as well as how the content of the field is presented in the editing preview. + +Entries are loaded and persisted through a `backend` that will typically represent a `git` repository. + +## State shape / reducers +**Auth:** Keeps track of the logged state and the current user. + +**Config:** Holds the environment configuration (backend type, available collections and fields). + +**Collections** List of available collections, their fields and metadata information. + +**Entries:** Entries for each field. + +**EntryDraft:** Reused for each entry that is edited or created. It holds the entry's temporary data until it's persisted on the backend. + +**Medias:** Keeps references to all media files uploaded by the user during the current session. + +## Selectors +Selectors are functions defined within reducers used to compute derived data from the Redux store. The available selectors are: + +**selectEntry:** Selects a single entry, given the collection and a slug. + +**selectEntries:** Selects all entries for a given collection. + +**getAsset:** Selects a single AssetProxy object for the given URI. + +## Value Objects +**AssetProxy:** AssetProxy is a Value Object that holds information regarding an asset file (such as an image, for example), whether it's persisted online or held locally in cache. + +For a file persisted online, the AssetProxy only keeps information about its URI. For local files, the AssetProxy will keep a reference to the actual File object while generating the expected final URIs and on-demand blobs for local preview. + +The AssetProxy object can be used directly inside a media tag (such as ``), as it will always return something that can be used by the media tag to render correctly (either the URI for the online file or a single-use blob). + +## Components structure and Workflows +Components are separated into two main categories: Container components and Presentational components. + +### Entry Editing +For either updating an existing entry or creating a new one, the `EntryEditor` is used and the flow is the same: +* When mounted, the `EntryPage` container component dispatches the `createDraft` action, setting the `entryDraft` state to a blank state (in case of a new entry) or to a copy of the selected entry (in case of an edit). +* The `EntryPage` will also render widgets for each field type in the given entry. +* Widgets are used for editing entry fields. There are different widgets for different field types, and they are always defined in a pair containing a `control` and a `preview` component. The control component is responsible for presenting the user with the appropriate interface for manipulating the current field value, while the preview component is responsible for displaying the value with the appropriate styling. + +#### Widget components implementation +The control component receives three (3) callbacks as props: `onChange`, `onAddAsset`, and `onRemoveAsset`. +* onChange (required): Should be called when the users changes the current value. It will ultimately end up updating the EntryDraft object in the Redux Store, thus updating the preview component. +* onAddAsset & onRemoveAsset (optionals): If the field accepts file uploads for media (images, for example), these callbacks should be invoked with a `AssetProxy` value object. `onAddAsset` will get the current media stored in the Redux state tree while `onRemoveAsset` will remove it. AssetProxy objects are stored in the `Medias` object and referenced in the `EntryDraft` object on the state tree. + +Both control and preview widgets receive a `getAsset` selector via props. Displaying the media (or its URI) for the user should always be done via `getAsset`, as it returns an AssetProxy that can return the correct value for both medias already persisted on the server and cached media not yet uploaded. + +The actual persistence of the content and medias inserted into the control component is delegated to the backend implementation. The backend will be called with the updated values and a list of assetProxy objects for each field of the entry, and should return a promise that can resolve into the persisted entry object and the list of the persisted media URIs. diff --git a/site/content/docs/contributor-guide.md b/site/content/docs/contributor-guide.md index e5055c6..565a782 100644 --- a/site/content/docs/contributor-guide.md +++ b/site/content/docs/contributor-guide.md @@ -2,6 +2,13 @@ title: Contributing position: 10 --- - - +# Welcome, contributors! + +We're hoping that Netlify CMS will do for the [JAMstack](https://www.jamstack.org) what WordPress did for dynamic sites back in the day. We know we can't do that without building a thriving community of contributors and users, and we'd love to have you join us. + +While we work on building this page (and you can help!), here are some links with more information about getting involved: + +* [Project Milestones](https://github.com/netlify/netlify-cms/milestones) +* [Code of Conduct](https://github.com/netlify/netlify-cms/blob/master/CODE_OF_CONDUCT.md) +* [Setup instructions and Contribution Guidelines](https://github.com/netlify/netlify-cms/blob/master/CONTRIBUTING.md) diff --git a/site/content/docs/custom-authentication.md b/site/content/docs/custom-authentication.md index 75717a8..39e4ae8 100644 --- a/site/content/docs/custom-authentication.md +++ b/site/content/docs/custom-authentication.md @@ -2,6 +2,37 @@ title: Custom Authentication position: 8 --- - - +# Custom Authentication + +Netlify CMS is meant to be platform agnostic, so we're always looking to expand the ecosystem and find new ways to use it. Below is a list of currently submitted OAuth providers - feel free to [submit a pull request](https://github.com/netlify/netlify-cms/blob/master/CONTRIBUTING.md) if you'd like to add yours! + +## External OAuth Clients: +| Author | Supported Git hosts | Languages | Link | +|------------|---------------------------|-----------|---------------------------------------------------------------------| +| @vencax | GitHub, GitHub Enterprise | Node.js | [Repo](https://github.com/vencax/netlify-cms-github-oauth-provider) | + +Check each project's readme for instructions on how to configure it. + +## Configuration +CMS configuration properties that affect authentication, including some optional properties that aren't mentioned elsewhere in the docs, are explained below: + +```yaml +backend: + + # REQUIRED CONFIG + name: github + repo: user/repository + + # OPTIONAL CONFIG + # Note: no trailing slashes on URLs + api_root: https://github.some.domain.com/api/v3 + site_domain: static.site.url.com + base_url: https://auth.server.url.com +``` + +* **name:** name of the auth provider, varies by implementation. `github` when using GitHub auth, even with a third party auth client. +* **repo:** repo where content is to be stored. +* **api_root (optional):** the API endpoint. Defaults to `https://api.github.com` when used with the `github` provider. Only necessary in certain cases, e.g., when using with GitHub Enterprise. +* **site_domain (optional):** sets `site_id` query param sent to API endpoint. Defaults to `location.hostname`, minus any port, or `cms.netlify.com` on localhost so that auth "just works" during local development. Sites with custom authentication will often need to set this for local development to work properly. +* **base_url (optional):** OAuth client URL, defaults to `https://api.netlify.com` as a convenience. This is **required** when using an external OAuth server. diff --git a/site/content/docs/customization.md b/site/content/docs/customization.md index b1703e2..6f70658 100644 --- a/site/content/docs/customization.md +++ b/site/content/docs/customization.md @@ -2,6 +2,198 @@ title: Custom Previews position: 5 --- - - +# Customizing the Preview Pane + +The NetlifyCMS exposes a `window.CMS` global object that you can use to register custom widgets, previews and editor plugins. The available customization methods are: + +* **registerPreviewStyle:** Register a custom stylesheet to use on the preview pane. +* **registerPreviewTemplate:** Registers a template for a collection. + +Explore the [NetlifyCMS GitHub example](https://github.com/netlify/netlify-cms/blob/9ced3f16c8bcc3d1a36773b126842d023c589eaf/example/index.html#L90-L91), a working example you can review on GitHub. + +### React Components inline interaction + +NetlifyCMS is a collection of React components and exposes two constructs globally to allow you to create components inline: ‘createClass’ and ‘h’ (alias for React.createElement). + +## `registerPreviewStyle` + +Register a custom stylesheet to use on the preview pane. + +```js +CMS.registerPreviewStyle(file); +``` + +**Params:** + +* **file:** css file path + +**Example:** +```html +// index.html + + +``` +```css +/* example.css */ + +html, +body { + color: #444; + font-size: 14px; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +body { + padding: 20px; +} + + +``` + + +## `registerPreviewTemplate` + +Registers a template for a collection. + +`CMS.registerPreviewTemplate(collection, react_component);` + +**Params:** + +* collection: The name of the collection which this preview component will be used for. +* react_component: A React component that renders the collection data. Four props will be passed to your component during render: + * entry: Immutable collection containing the entry data. + * widgetFor: Returns the appropriate widget preview component for a given field. + * [widgetsFor](#lists-and-objects): Returns an array of objects with widgets and associated field data. For use with list and object type entries. + * getAsset: Returns the correct filePath or in-memory preview for uploaded images. + + **Example:** + + ```html + + + ``` + + ### Lists and Objects + The API for accessing the individual fields of list- and object-type entries is similar to the API + for accessing fields in standard entries, but there are a few key differences. Access to these + nested fields is facilitated through the `widgetsFor` function, which is passed to the preview + template component during render. + + **Note**: as is often the case with the NetlifyCMS API, arrays and objects are created with + Immutable.js. If some of the methods that we use are unfamiliar, such as `getIn`, check out + [their docs](https://facebook.github.io/immutable-js/docs/#/) to get a better understanding. + + **List Example:** + + ```html + + ``` + + **Object Example:** + + ```html + + ``` + + ### Accessing Metadata + Preview Components also receive an additional prop: `fieldsMetaData`. It contains aditional information (besides the plain plain textual value of each field) that can be useful for preview purposes. + + For example, the Relation widget passes the whole selected relation data in `fieldsMetaData`. + + ```js + export default class ArticlePreview extends React.Component { + render() { + const {entry, fieldsMetaData} = this.props; + const author = fieldsMetaData.getIn(['authors', data.author]); + + return

{ entry.getIn(['data', 'title']) }

+ {author &&} +
+ } + } + ``` diff --git a/site/content/docs/editorial-workflow.md b/site/content/docs/editorial-workflow.md index 951f4dc..3a70304 100755 --- a/site/content/docs/editorial-workflow.md +++ b/site/content/docs/editorial-workflow.md @@ -2,6 +2,43 @@ title: Editorial Workflow position: 4 --- - - +# Editorial Workflow + +## Overview + +By default, all entries created or edited in the Netlify CMS are committed directly into the main repository branch. + +Alternatively, you can enable an optional "Editorial Workflow" mode that allows for more control over the content publishing phases. All unpublished entries will be arranged in a board according to their status, and they can be further reviewed and edited before going live. + +![Editorial workflow](https://cloud.githubusercontent.com/assets/33676/19452442/d10d9002-948f-11e6-9463-06955b6c15c8.png) + +From a technical perspective, the workflow translates editor UI actions into common Git commands: + +Actions in Netlify UI ... | Perform these Git actions +--- | --- +Save draft | Commits to a new branch, and opens a pull request +Edit draft | Pushes another commit to the draft branch/pull request +Approve and publish draft | Merges pull request and deletes branch + +## Adding to your site + +To enable the editorial workflow, add this line to your `admin/config.yml` file: + +``` yaml +publish_mode: editorial_workflow +``` + +There are no other configuration options right now. There are always three possible statuses, and new branch names are created according to the pattern `cms/collectionName-entrySlug`. + +## About metadata + +Netlify CMS embraces the idea of Git-as-backend for storing metadata. The first time it runs with the editorial_workflow setup, it creates a new ref called `meta/_netlify_cms`, pointing to an empty, orphan tree. + +Actual data are stored in individual `json` files committed to this tree. + +## Implementation + +Instead of adding logic to `CollectionPage` and `EntryPage`, the Editorial Workflow is implemented as Higher Order Components, adding UI and dispatching additional actions. + +Furthermore, all editorial workflow state is managed in Redux - there's an `actions/editorialWorkflow.js` file and a `reducers/editorialWorkflow.js` file. diff --git a/site/content/docs/examples.md b/site/content/docs/examples.md new file mode 100644 index 0000000..4a5158c --- /dev/null +++ b/site/content/docs/examples.md @@ -0,0 +1,13 @@ +--- +title: Examples +position: 11 +--- + +# Examples + +Do you have a great example? Submit a pull request to this page. + +Name | Tools | Type | Example | More info | +--- | --- | --- | --- | --- +This Developing Journey | middleman | blog | [briandouglas.me](https://briandouglas.me) | [blog post](https://deploy-preview-496--www.netlify.com/blog/2017/04/18/blog-with-middleman-and-the-netlifycms/) +JAMstack Recipes | Hugo, Azure | demo | [jamstack-cms.netlify.com](http://jamstack-cms.netlify.com) | [blog post](http://conductofcode.io/post/managing-content-for-a-jamstack-site-with-netlify-cms/) diff --git a/site/content/docs/extending.md b/site/content/docs/extending.md index 0983e9a..b52ac8e 100755 --- a/site/content/docs/extending.md +++ b/site/content/docs/extending.md @@ -2,6 +2,117 @@ title: Extending Widgets position: 6 --- - - +# Extending With Widgets + +The NetlifyCMS exposes an `window.CMS` global object that you can use to register custom widgets, previews, and editor plugins. The available widget extension methods are: + +* **registerWidget:** lets you register a custom widget. +* **registerEditorComponent:** lets you add a block component to the Markdown editor. + +### Writing React Components inline + +The `registerWidget` requires you to provide a React component. If you have a build process in place for your project, it is possible to integrate with this build process. + +However, although possible, it may be cumbersome or even impractical to add a React build phase. For this reason, NetlifyCMS exposes two constructs globally to allow you to create components inline: ‘createClass’ and ‘h’ (alias for React.createElement). + +## `registerWidget` + +Register a custom widget. + +```js +CMS.registerWidget(name, control, \[preview\]) +``` + +**Params:** + +Param | Type | Description +--- | --- | --- +`name` | string | Widget name, allows this widget to be used via the field `widget` property in config +`control` | React.Component \| string | +[`preview`] | React.Component, optional | Renders the widget preview, receives the following props: +* **field:** The field type that this widget will be used for. +* **control:** A React component that renders the editing interface for this field. Two props will be passed: + * **value:** The current value for this field. + * **onChange:** Callback function to update the field value. +* **preview (optional):** A React component that renders the preview of how the content will look. A `value` prop will be passed to this component. + +**Example:** + +```html + + +``` + +## `registerEditorComponent` + +Register a block level component for the Markdown editor: + + CMS.registerEditorComponent(definition) + +**Params** + +* **definition:** The component definition; must specify: id, label, fields, patterns, fromBlock, toBlock, toPreview + +**Example:** + +```html + + +``` + +**Result:** + +![youtube-widget](/img/youtube-widget.png) + diff --git a/site/content/docs/intro.md b/site/content/docs/intro.md index 9275daa..ac242c7 100755 --- a/site/content/docs/intro.md +++ b/site/content/docs/intro.md @@ -2,6 +2,107 @@ title: Introduction position: 0 --- - - +# Introduction + +Netlify CMS is a Content Management System for static sites, allowing collaborators to create, edit, review, and publish content without writing code or dealing with version control. It brings the ease of WordPress-style editing to the simplicity and speed of static sites. + +At its core, Netlify CMS is an open-source React app that acts as a wrapper for the Git workflow, using the GitHub API. This provides many advantages, including: + +* **Fast, web-based UI:** with rich-text editing, real-time preview, and drag-and-drop media uploads. +* **Platform agnostic:** works with most static site generators. +* **Easy installation:** add two files to your site and hook up the backend by including in your build process or linking to our CDN. +* **Modern authentication:** using GitHub and JSON web tokens. +* **Flexible content types:** specify an unlimited number of content types with custom fields. +* **Fully extensible:** create custom-styled previews, UI widgets, and editor plugins. + +# Core Concepts + +## The Admin Interface + +The admin interface is a single-page app with the entry point stored in a static `/admin` folder you add to the root of your site. You can include it with a simple `index.html` file that loads the necessary CSS and JS files from a CDN: + +``` html + + + + + + Content Manager + + + + + + + + +``` + +The JS is also available via npm and can be integrated into your regular build process. + +### Editorial Workflow + +Netlify CMS has an optional [editorial workflow](/docs/editorial-workflow) that translates common Git commands into familiar language in a simple UI: + +Actions in Netlify UI ... | Perform these Git actions +--- | --- +Save draft | Commits to a new branch, and opens a pull request +Edit draft | Pushes another commit to the draft branch/pull request +Approve and publish draft | Merges pull request and deletes branch + +## Configuration + +All configuration is handled in a single `config.yml` file, also stored in the `/admin` folder. A simple version might look like this: + +``` yaml +backend: + name: github + repo: owner/repo # Path to your Github repository + branch: master # Branch to update (master by default) + +media_folder: "img/uploads" # Folder where user uploaded files should go + +collections: # A list of collections the CMS should be able to edit + - name: "post" # Used in routes, e.g., /admin/collections/:slug/edit + label: "Post" # Used in the UI, e.g., "New Post" + folder: "_posts" # The path to the folder where the documents are stored + create: true # Allow users to create new documents in this collection + fields: # The fields each document in this collection have + - {label: "Title", name: "title", widget: "string", tagname: "h1"} + - {label: "Body", name: "body", widget: "markdown"} + - {label: "Foo", name: "foo", widget: "foo"} + - {label: "Publish Date", name: "date", widget: "datetime"} +``` + +### Backend + +Because Netlify CMS is a wrapper for the GitHub API, the "backend" is a repo stored on GitHub. *(Using a different Git host? File a [feature request](https://github.com/netlify/netlify-cms/issues), or [help us add it](https://github.com/netlify/netlify-cms/blob/master/CONTRIBUTING.md)!)* For authentication, you can connect to GitHub using Netlify’s [Authentication Provider feature](https://www.netlify.com/docs/authentication-providers), or you can roll your own. + +### Collections + +All content managed by Netlify CMS is organized in Collections (groups of files), such as: + +* blog posts +* portfolio samples +* product listings +* podcast episodes + +You point to where the files are stored, and specify the fields that define them. The `body` field typically stores the main text of a file, while any other fields are included at the top of the document in the front matter. They can be required, optional, or hidden, and can have preset defaults. + +### Widgets + +Widgets define the data type and interface for entry fields. Netlify CMS comes with several built-in [widgets](/docs/widgets). + +## Customization + +Netlify CMS exposes a `window.CMS` global object that you can use to register custom widgets, previews, and editor plugins. The available methods are: + +* `registerPreviewStyle`: register a custom stylesheet to match the editor preview pane to your site style. + +* `registerPreviewTemplate`: registers a template to determine how fields are displayed in the preview, customizable for each collection. + +* `registerWidget`: registers a custom widget. + +* `registerEditorComponent`: adds a block component to the Markdown editor. + diff --git a/site/content/docs/quick-start.md b/site/content/docs/quick-start.md index 9b35be4..23edbc8 100755 --- a/site/content/docs/quick-start.md +++ b/site/content/docs/quick-start.md @@ -2,6 +2,237 @@ title: Quick Start position: 2 --- - - +# Quick Start +Netlify CMS is adaptable to a wide variety of projects. The only inflexible requirement is that your site content must be written in markdown, JSON, YAML, or TOML files, stored in a repo on [GitHub](https://github.com/). (If you're partial to another Git hosting service, check out the PRs in progress for [GitLab](https://github.com/netlify/netlify-cms/pull/517) and [Bitbucket](https://github.com/netlify/netlify-cms/pull/525) support.) + +In this guide, we're going to assume you're using a [static site generator](https://www.staticgen.com/), like Jekyll, Hugo, Hexo, or Gatsby. + +## App File Structure +All Netlify CMS files are contained in a static `admin` folder, stored at the root of your published site. Where you store this folder in the source files depends on your static site generator. Here's the the static file location for a few of the most popular static site generators: + +These generators ... | store static files in +--- | --- +Jekyll, GitBook | `/` (project root) +Hugo, Gatsby* | `/static` +Hexo, Middleman | `/source` +Spike | `/views` + +Notes: +- Gatsby treats the `static` folder more strictly and will not render the admin page as the other generators. You will have to make a [page component](https://www.gatsbyjs.org/docs/building-with-components/) containing the necessary scripts of the Netlify CMS app in the admin page. However, the `config.yml` file (described below) can be placed in the `static` folder as with other generators. + +If your generator isn't listed here, you can check its documentation, or as a shortcut, look in your project for a `css` or `images` folder. The contents of folders like that are usually processed as static files, so it's likely you can store your `admin` folder next to those. (When you've found the location, feel free to add it to these docs by [filing a pull request](https://github.com/netlify/netlify-cms/blob/master/CONTRIBUTING.md)!) + +Inside the `admin` folder, you'll create two files: + +``` +admin + ├ index.html + └ config.yml +``` + +The first file, `admin/index.html`, is the entry point for the Netlify CMS admin interface. This means that users can navigate to `yoursite.com/admin` to access it. On the code side, it's a basic HTML starter page that loads the necessary CSS and JavaScript files. In this example, we pull those files from a public CDN: + +``` html + + + + + + Content Manager + + + + + + + + + + +``` + +The second file, `admin/config.yml`, is the heart of your Netlify CMS installation, and a bit more complex. The next section covers the details. + +## Configuration +Configuration will be different for every site, so we'll break it down into parts. All code snippets in this section will be added to your `admin/config.yml` file. + +### Backend +Because we're using GitHub and Netlify for our hosting and authentication, backend configuration is fairly strightforward. You can start your `config.yml` file with these lines: + +``` yaml +backend: + name: git-gateway + branch: master # Branch to update (optional; defaults to master) +``` + +These lines specify your backend protocol and your publication branch. Git Gateway is an open source API that acts as a proxy between authenticated users of your site and your site repo. (We'll get to the details of that in the [Authentication section](#authentication) below.) If you leave out the `branch` declaration, it will default to `master`. + +### Editorial Workflow +By default, saving a post in the CMS interface will push a commit directly to the publication branch specified in `backend`. However, you also have the option to enable the [Editorial Workflow](/docs/editorial-workflow), which adds an interface for drafting, reviewing, and approving posts. To do this, add the following line to your `config.yml`: + +``` yaml +publish_mode: editorial_workflow +``` + +### Media and Public Folders +Netlify CMS allows users to upload images directly within the editor. For this to work, the CMS needs to know where to save them. If you already have an `images` folder in your project, you could use its path, possibly creating an `uploads` sub-folder, for example: + +``` yaml +media_folder: "images/uploads" # Media files will be stored in the repo under images/uploads +``` + +If you're creating a new folder for uploaded media, you'll need to know where your static site generator expects static files. You can refer to the paths outlined above in [App File Structure](#app-file-structure), and put your media folder in the same location where you put the `admin` folder. + +Note that the`media_folder` file path is relative to the project root, so the example above would work for Jekyll, GitBook, or any other generator that stores static files at the project root. However, it would not work for Hugo, Hexo, Middleman or others that store static files in a subfolder. Here's an example that could work for a Hugo site: + +``` yaml +media_folder: "static/images/uploads" # Media files will be stored in the repo under static/images/uploads +public_folder: "/images/uploads" # The src attribute for uploaded media will begin with /images/uploads +``` + +The configuration above adds a new setting, `public_folder`. While `media_folder` specifies where uploaded files will be saved in the repo, `public_folder` indicates where they can be found in the published site. This path is used in image `src` attributes and is relative to the file where it's called. For this reason, we usually start the path at the site root, using the opening `/`. + +*If `public_folder` is not set, Netlify CMS will default to the same value as `media_folder`, adding an opening `/` if one is not included.* + +### Collections +Collections define the structure for the different content types on your static site. Since every site is different, the `collections` settings will be very different from one site to the next. + +Let's say your site has a blog, with the posts stored in `_posts/blog`, and files saved in a date-title format, like `1999-12-31-lets-party.md`. Each post begins with settings in yaml-formatted front matter, like so: + +``` yaml +--- +layout: blog +title: "Let's Party" +date: 1999-12-31 11:59:59 -0800 +thumbnail: "/images/prince.jpg" +rating: 5 +--- + +This is the post body, where I write about our last chance to party before the Y2K bug destroys us all. +``` + +Given this example, our `collections` settings would look like this: + +``` yaml +collections: + - name: "blog" # Used in routes, e.g., /admin/collections/blog + label: "Blog" # Used in the UI + folder: "_posts/blog" # The path to the folder where the documents are stored + create: true # Allow users to create new documents in this collection + slug: "{{year}}-{{month}}-{{day}}-{{slug}}" # Filename template, e.g., YYYY-MM-DD-title.md + fields: # The fields for each document, usually in front matter + - {label: "Layout", name: "layout", widget: "hidden", default: "blog"} + - {label: "Title", name: "title", widget: "string"} + - {label: "Publish Date", name: "date", widget: "datetime"} + - {label: "Featured Image", name: "thumbnail", widget: "image"} + - {label: "Rating (scale of 1-5)", name: "rating", widget: "number"} + - {label: "Body", name: "body", widget: "markdown"} +``` + +Let's break that down: + + + + + + + + + + + + + + + + + + + + + + + + + +
namePost type identifier, used in routes. Must be unique.
labelWhat the post type will be called in the admin UI.
folderWhere files of this type are stored, relative to the repo root.
createSet to true to allow users to create new files in this collection. +
slugTemplate for filenames. {{year}}, {{month}}, and {{day}} will pull from the post's date field or save date. {{slug}} is a url-safe version of the post's title. Default is simply {{slug}}. +
fieldsFields listed here are shown as fields in the content editor, then saved as front matter at the beginning of the document (except for body, which follows the front matter). Each field contains the following properties: +
    +
  • label: Field label in the editor UI.
  • +
  • name: Field name in the document front matter.
  • +
  • widget: Determines UI style and value data type (details below).
  • +
  • default (optional): Sets a default value for the field.
  • +
+
+ +As described above, the `widget` property specifies a built-in or custom UI widget for a given field. When a content editor enters a value into a widget, that value will be saved in the document front matter as the value for the `name` specified for that field. A full listing of available widgets can be found in the [Widgets doc](/docs/widgets). + +Based on this example, you can go through the post types in your site and add the appropriate settings to your `config.yml` file. Each post type should be listed as a separate node under the `collections` field. + +### Filter +The entries for any collection can be filtered based on the value of a single field. The example collection below would only show post entries with the value "en" in the language field. + +``` yaml +collections: + - name: "posts" + label: "Post" + folder: "_posts" + filter: + field: language + value: en + fields: + - {label: "Language", name: "language"} +``` + +## Authentication + +Now that you have your Netlify CMS files in place and configured, all that's left is to enable authentication. There are [many ways to do this](/docs/custom-authentication) (with or without deploying to Netlify), but this example uses Netlify because it's one of the quickest ways to get started. + +### Setup on Netlify +Netlify offers a built-in authentication service called Identity. In order to use it, you'll need to connect your site repo with Netlify. Netlify has published a general [Step-by-Step Guide](https://www.netlify.com/blog/2016/10/27/a-step-by-step-guide-deploying-a-static-site-or-single-page-app/) for this, along with detailed guides for many popular static site generators, including [Jekyll](https://www.netlify.com/blog/2015/10/28/a-step-by-step-guide-jekyll-3.0-on-netlify/), [Hugo](https://www.netlify.com/blog/2016/09/21/a-step-by-step-guide-victor-hugo-on-netlify/), [Hexo](https://www.netlify.com/blog/2015/10/26/a-step-by-step-guide-hexo-on-netlify/), [Middleman](https://www.netlify.com/blog/2015/10/01/a-step-by-step-guide-middleman-on-netlify/), [Gatsby](https://www.netlify.com/blog/2016/02/24/a-step-by-step-guide-gatsby-on-netlify/) and more. + +### Enable Identity and Git Gateway +Netlify's Identity and Git Gateway services allow you to manage CMS admin users for your site without requiring them to have GitHub accounts or commit access on your repo. From your site dashboard on Netlify: + + 1. Go to **Settings > Identity**, and select **Enable Identity service**. + 2. Under **Registration preferences**, select **Open** or **Invite only**. In most cases, you'll want only invited users to access your CMS, but if you're just experimenting, you can leave it open for convenience. + 3. If you'd like to allow one-click login with services like Google and GitHub, check the boxes next to the services you'd like to use, under **External providers**. + 4. Scroll down to **Services > Git Gateway**, and click **Enable Git Gateway**. This will authenticate with GitHub and generate a GitHub API access token. In this case, we're leaving the **Roles** field blank, which means any logged in user may access the CMS. For information on changing this, check the [Netlify Identity documentation](https://www.netlify.com/docs/identity/). + +### Add the Netlify Identity Widget +With the backend set to handle authentication, now you need a frontend interface to connect to it. The open source Netlify Identity Widget is a drop-in widget made for just this purpose. To include the widget in your site, you'll need to add the following script tag in two places: + +```html + +``` + +You'll need to add this to the `` of your CMS index page at `/admin/index.html`, as well as the `` of your site's main index page. Depending on how your site generator is set up, this may mean you need to add it to the default template, or to a "partial" or "include" template. If you can find where the site stylesheet is linked, that's probably the right place. Alternatively, you can include the script in your site using Netlify's [Script Injection](https://www.netlify.com/docs/inject-analytics-snippets/) feature. + +When a user logs in with the Netlify Identity widget, they will be directed to the site homepage with an access token. In order to complete the login and get back to the CMS, we'll need to redirect the user back to the `/admin` path. To do this, add the following script before the closing `body` tag of your site's main index page: + +```html + +``` +Note: This example script requires modern JavaScript and will not work on IE11. For legacy browser support, use function expressions (`function () {}`) in place of the arrow functions (`() => {}`), or use a transpiler like [Babel](https://babeljs.io/). + +## Accessing the CMS + +Your site CMS is now fully configured and ready for login! + +If you set your registration preference to "Invite only," you'll need to invite yourself (and anyone else you choose) as a site user. To do this, select the **Identity** tab from your site dashboard, and then select the **Invite users** button. Invited users will receive an email invitation with a confirmation link. Clicking the link will take you to your site with a login prompt. + +If you left your site registration open, or for return visits after comfirming an email invitation, you can access your site's CMS at `yoursite.com/admin`. + +Happy posting! diff --git a/site/content/docs/test-drive.md b/site/content/docs/test-drive.md index 44822ff..5b8e14a 100644 --- a/site/content/docs/test-drive.md +++ b/site/content/docs/test-drive.md @@ -2,6 +2,32 @@ title: Test Drive position: 1 --- - - +# Take a test drive + +Netlify CMS can run in any frontend web environment, but the quickest way to try it out is by running it on a pre-configured starter site with Netlify. Our example here is the [Kaldi coffee company template](https://github.com/netlify-templates/one-click-hugo-cms). Use the button below to build and deploy your own copy of the repository: + +[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/netlify-templates/one-click-hugo-cms&stack=cms) + +After clicking that button, you’ll authenticate with GitHub and choose a repository name. Netlify will then automatically create a repository in your GitHub account with a copy of the files from the template. Next, it will build and deploy the new site on Netlify, bringing you to the site dashboard when the build is complete. Next, you’ll need to set up Netlify's [Identity](https://www.netlify.com/docs/identity) service to authorize users to log in to the CMS. + +## Adding users + +1. From the Netlify site dashboard, select the **Identity** tab, and you'll find that there are no users yet. By default, this site is set to accept users by invitation only, and even the site owner needs to be invited. +2. Select **Invite users**, enter your email address, and select Send. +3. Check your email for the invitation. It will be sent from `no-reply@netlify.com`. + + ![Sample email subject line: You've been invited to join radiologist-amanda-53841.netlify.com](/img/email-subject.png?raw=true) + +4. Click the link to accept the invite, and you’ll be directed to your new site, with a prompt to create a password. + + !["Complete your signup" modal on the Kaldi coffee site](/img/create-password.png?raw=true) + +5. Enter a password, sign in, and you’ll be directed straight to the CMS! + +Try adding and editing posts, or changing the content of the Products page. When you save, the changes will be pushed immediately to GitHub, triggering a build on Netlify, and updating the content on your site. + +## More paths to explore +- If you’d like to learn more about how it all works, check out the [Intro](/docs/intro). +- To see how to integrate Netlify CMS into an existing project, go to the [Quick Start](/docs/quick-start). +- If you’d like to change how users log in to your site, read up on [Netlify Identity service](https://www.netlify.com/docs/identity). diff --git a/site/content/docs/validation.md b/site/content/docs/validation.md index a663a7c..0615e91 100755 --- a/site/content/docs/validation.md +++ b/site/content/docs/validation.md @@ -2,6 +2,58 @@ title: Validation position: 7 --- - - +# Collection Field Validation + +## Available validations to use on `config.yml`: + +- Presence: By default all widgets are required, unless specified in the config. Example: +`- {label: "Subtitle", name: "subtitle", widget: "string", required: false}` + +- Pattern: Field configuration can specify a regex pattern with the appropriate error message. Example: +`- {label: "Title", name: "title", widget: "string", pattern: ['.{10,}', "Should have more than 10 characters"] }` + + +## Advanced Guide (For widget authors) + +The widget control can optionally implement an `isValid` method to perform custom validations, in addition to presence and pattern. The `isValid` method will be automatically called, and it can return either a boolean value, an object with an error message or a promise. Examples: + +**Boolean** +No errors: + +```javascript + isValid = () => { + // Do internal validation + return true; + }; +``` + +Existing error: + +```javascript + isValid = () => { + // Do internal validation + return false; + }; +``` + +**Object with `error` (useful for returning custom error messages)** +Existing error: + +```javascript + isValid = () => { + // Do internal validation + return { error: 'Your error message.' }; + }; +``` + +**Promise** +You can also return a promise from `isValid`. While the promise is pending, the widget will be marked as "in error". When the promise resolves, the error is automatically cleared. + +```javascript + isValid = () => { + return this.existingPromise; + }; +``` + +Note: Do not create a promise inside `isValid` - `isValid` is called right before trying to persist. This means that even if a previous promise was already resolved, when the user hits 'save', `isValid` will be called again. If it returns a new promise, it will be immediately marked as "in error" until the new promise resolves. diff --git a/site/content/docs/widgets.md b/site/content/docs/widgets.md index 53ba11e..a338a64 100644 --- a/site/content/docs/widgets.md +++ b/site/content/docs/widgets.md @@ -2,6 +2,105 @@ title: Widgets position: 3 --- - - +# Configuring your site + +## Widgets + +Widgets define the data type and interface for entry fields. Netlify CMS comes with several built-in widgets, including: + +| Name | UI | Data Type | +| -------- | ---------------------------------- | ---------------------------------------------------| +| `string` | text input | string | +| `boolean` | toggle switch | boolean | +| `text` | textarea input | string (multiline) | +| `number` | number input | number | +| `markdown` | rich text editor | string (markdown) | +| `datetime` | date picker | string (ISO date) | +| `select` | select input (dropdown) | string | +| `image` | file picker w/ drag-and-drop | image file | +| `file` | file picker w/ drag-and-drop | file | +| `hidden` | none | string | +| `object` | group of other widgets | Immutable Map containing field values | +| `list` | repeatable group of other widgets | Immutable List of objects containing field values | +| `relation` | text input w/ suggestions dropdown | value of `valueField` in related entry (see below) | + +We’re always adding new widgets, and you can also [create your own](/docs/extending)! + +### List Widget + +The list widget allows you to map a user-provided string with a comma delimiter into a list. Consider the following example that also demonstrates how to set default values: + +```yaml +collections: + - name: posts + label: Post + folder: "_posts" + slug: "{{year}}-{{month}}-{{day}}-{{slug}}" + create: true + fields: + - {label: Title, name: title, widget: string, tagname: h1} + - {label: Body, name: body, widget: markdown} + - {label: Categories, name: categories, widget: list} + - {label: Tags, name: tags, widget: list, default: ['term_1', 'term_2']} +``` + +Lists of objects are supported as well and require a nested field list. + +```yaml +collections: + - name: posts + label: Post + folder: "_posts" + slug: "{{year}}-{{month}}-{{day}}-{{slug}}" + create: true + fields: + - {label: Title, name: title, widget: string, tagname: h1} + - {label: Body, name: body, widget: markdown} + - name: authors + label: Authors + widget: list + fields: + - {label: Name, name: name, widget: string} + - {label: Description, name: description, widget: markdown} +``` + +### Relation Widget + +The relation widget allows you to reference an existing entry from within the entry you're editing. It provides a search input with a list of entries from the collection you're referencing, and the list automatically updates with matched entries based on what you've typed. + +The following field configuration properties are specific to fields using the relation widget: + +Property | Accepted Values | Description +--- | --- | --- +`collection` | string | name of the collection being referenced +`searchFields` | list | one or more names of fields in the referenced colleciton to search for the typed value +`valueField` | string | name a field from the referenced collection whose value will be stored for the relation +`name` | text input | string + +Let's say we have a "posts" collection and an "authors" collection, and we want to select an author for each post - our config might look something like this: + +```yaml +collections: + - name: authors + label: Authors + folder: "authors" + create: true + fields: + - {name: name, label: Name} + - {name: twitterHandle, label: "Twitter Handle"} + - {name: bio, label: Bio, widget: text} + - name: posts + label: Posts + folder: "posts" + create: true + fields: + - {name: title, label: Title} + - {name: body, label: Body, widget: markdown} + - name: author + label: Author + widget: relation + collection: authors + searchFields: [name, twitterHandle] + valueField: name +``` diff --git a/site/layouts/_default/list.html b/site/layouts/_default/list.html index 7a17126..3998911 100644 --- a/site/layouts/_default/list.html +++ b/site/layouts/_default/list.html @@ -22,7 +22,7 @@ {{ if eq .Params.position 0 }}

- + - + + - + diff --git a/yarn.lock b/yarn.lock index e9580e0..3e0e383 100644 --- a/yarn.lock +++ b/yarn.lock @@ -68,12 +68,6 @@ ansi-escapes@^1.1.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" -ansi-red@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-red/-/ansi-red-0.1.1.tgz#8c638f9d1080800a353c9c28c8a81ca4705d946c" - dependencies: - ansi-wrap "0.1.0" - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -82,10 +76,6 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ansi-wrap@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" - any-promise@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-0.1.0.tgz#830b680aa7e56f33451d4b049f3bd8044498ee27" @@ -118,13 +108,6 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" -argparse@~0.1.15: - version "0.1.16" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-0.1.16.tgz#cfd01e0fbba3d6caed049fbd758d40f65196f57c" - dependencies: - underscore "~1.7.0" - underscore.string "~2.4.0" - arr-diff@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" @@ -161,10 +144,6 @@ arrify@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" -asap@~2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" - asn1@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" @@ -207,10 +186,6 @@ asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" -autolinker@~0.15.0: - version "0.15.3" - resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.15.3.tgz#342417d8f2f3461b14cf09088d5edf8791dc9832" - autoprefixer@^6.0.2, autoprefixer@^6.3.1, autoprefixer@^6.3.7: version "6.7.6" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.6.tgz#00f05656c7ef73de9d2fd9b4668f6ef6905a855a" @@ -1066,10 +1041,6 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" -coffee-script@^1.12.4: - version "1.12.4" - resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.4.tgz#fe1bced97fe1fb3927b998f2b45616e0658be1ff" - color-convert@^0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd" @@ -1153,7 +1124,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@^1.4.6, concat-stream@^1.4.7, concat-stream@^1.5.2: +concat-stream@^1.4.6: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" dependencies: @@ -1402,10 +1373,6 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" -deepmerge@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-1.3.2.tgz#1663691629d4dbfe364fa12a2a4f0aa86aa3a050" - defaults@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" @@ -1464,10 +1431,6 @@ dev-ip@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dev-ip/-/dev-ip-1.0.1.tgz#a76a3ed1855be7a012bb8ac16cb80f3c00dc28f0" -diacritics-map@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/diacritics-map/-/diacritics-map-0.1.0.tgz#6dfc0ff9d01000a2edf2865371cac316e94977af" - doctrine@1.3.x, doctrine@^1.2.2: version "1.3.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.3.0.tgz#13e75682b55518424276f7c173783456ef913d26" @@ -1850,12 +1813,6 @@ express@2.5.x: mkdirp "0.3.0" qs "0.4.x" -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - dependencies: - is-extendable "^0.1.0" - extend@^3.0.0, extend@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" @@ -1954,12 +1911,6 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" -find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" - dependencies: - locate-path "^2.0.0" - findup-sync@^0.4.2: version "0.4.3" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" @@ -2002,7 +1953,7 @@ flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" -for-in@^1.0.1, for-in@^1.0.2: +for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -2036,7 +1987,7 @@ fs-exists-sync@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" -fs-extra@1.0.0, fs-extra@^1.0.0: +fs-extra@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" dependencies: @@ -2230,16 +2181,6 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - globule@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/globule/-/globule-0.1.0.tgz#d9c8edde1da79d125a151b79533b978676346ae5" @@ -2272,16 +2213,6 @@ graceful-fs@~1.2.0: version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" -gray-matter@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-2.1.1.tgz#3042d9adec2a1ded6a7707a9ed2380f8a17a430e" - dependencies: - ansi-red "^0.1.1" - coffee-script "^1.12.4" - extend-shallow "^2.0.1" - js-yaml "^3.8.1" - toml "^2.3.2" - gulp-babel@^6.1.2: version "6.1.2" resolved "https://registry.yarnpkg.com/gulp-babel/-/gulp-babel-6.1.2.tgz#7c0176e4ba3f244c60588a0c4b320a45d1adefce" @@ -2445,14 +2376,6 @@ html-comment-regex@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" -http-basic@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-2.5.1.tgz#8ce447bdb5b6c577f8a63e3fa78056ec4bb4dbfb" - dependencies: - caseless "~0.11.0" - concat-stream "^1.4.6" - http-response-object "^1.0.0" - http-errors@~1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750" @@ -2468,10 +2391,6 @@ http-proxy@1.15.2: eventemitter3 "1.x.x" requires-port "1.x.x" -http-response-object@^1.0.0, http-response-object@^1.0.1, http-response-object@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-1.1.0.tgz#a7c4e75aae82f3bb4904e4f43f615673b4d518c3" - http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -2619,7 +2538,7 @@ is-equal-shallow@^0.1.3: dependencies: is-primitive "^2.0.0" -is-extendable@^0.1.0, is-extendable@^0.1.1: +is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -2649,10 +2568,6 @@ is-glob@^2.0.0, is-glob@^2.0.1: dependencies: is-extglob "^1.0.0" -is-local-path@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-local-path/-/is-local-path-0.1.6.tgz#815d144b14d569cecbead4d5693097f00a9bf6c5" - is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4: version "2.16.0" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" @@ -2759,7 +2674,7 @@ isnumeric@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/isnumeric/-/isnumeric-0.2.0.tgz#a2347ba360de19e33d0ffd590fddf7755cbf2e64" -isobject@^2.0.0, isobject@^2.1.0: +isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" dependencies: @@ -2783,7 +2698,7 @@ js-tokens@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" -js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@^3.8.1: +js-yaml@^3.4.3, js-yaml@^3.5.1: version "3.8.2" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.2.tgz#02d3e2c0f6beab20248d412c352203827d786721" dependencies: @@ -2934,12 +2849,6 @@ lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" -lazy-cache@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" - dependencies: - set-getter "^0.1.0" - lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -2971,15 +2880,6 @@ limiter@^1.0.5: version "1.1.0" resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.0.tgz#6e2bd12ca3fcdaa11f224e2e53c896df3f08d913" -list-item@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/list-item/-/list-item-1.1.1.tgz#0c65d00e287cb663ccb3cb3849a77e89ec268a56" - dependencies: - expand-range "^1.8.1" - extend-shallow "^2.0.1" - is-number "^2.1.0" - repeat-string "^1.5.2" - load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -3016,13 +2916,6 @@ localtunnel@1.8.2: request "2.78.0" yargs "3.29.0" -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" - dependencies: - p-locate "^2.0.0" - path-exists "^3.0.0" - lodash._basecopy@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" @@ -3250,40 +3143,6 @@ map-cache@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" -markdown-link@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/markdown-link/-/markdown-link-0.1.1.tgz#32c5c65199a6457316322d1e4229d13407c8c7cf" - -markdown-magic@^0.1.13: - version "0.1.13" - resolved "https://registry.yarnpkg.com/markdown-magic/-/markdown-magic-0.1.13.tgz#67ae03032f80cf59f773b01d3e5a330f2459ab94" - dependencies: - commander "^2.9.0" - deepmerge "^1.3.0" - find-up "^2.1.0" - fs-extra "^1.0.0" - globby "^6.1.0" - is-local-path "^0.1.6" - markdown-toc "^1.0.2" - sync-request "^3.0.1" - -markdown-toc@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/markdown-toc/-/markdown-toc-1.1.0.tgz#18e47237d89549e9447121e69e2ca853ca2d752a" - dependencies: - concat-stream "^1.5.2" - diacritics-map "^0.1.0" - gray-matter "^2.1.0" - lazy-cache "^2.0.2" - list-item "^1.1.1" - markdown-link "^0.1.1" - minimist "^1.2.0" - mixin-deep "^1.1.3" - object.pick "^1.2.0" - remarkable "^1.7.1" - repeat-string "^1.6.1" - strip-color "^0.1.0" - math-expression-evaluator@^1.2.14: version "1.2.16" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.16.tgz#b357fa1ca9faefb8e48d10c14ef2bcb2d9f0a7c9" @@ -3366,13 +3225,6 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" -mixin-deep@^1.1.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.2.0.tgz#d02b8c6f8b6d4b8f5982d3fd009c4919851c3fe2" - dependencies: - for-in "^1.0.2" - is-extendable "^0.1.1" - mkdirp@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" @@ -3555,12 +3407,6 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" -object.pick@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.2.0.tgz#b5392bee9782da6d9fb7d6afaf539779f1234c2b" - dependencies: - isobject "^2.1.0" - on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -3650,16 +3496,6 @@ os-tmpdir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" -p-limit@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" - -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" - dependencies: - p-limit "^1.1.0" - pako@~0.2.0: version "0.2.9" resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" @@ -3723,10 +3559,6 @@ path-exists@^2.0.0: dependencies: pinkie-promise "^2.0.0" -path-exists@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -4381,12 +4213,6 @@ promise-each@^2.2.0: dependencies: any-promise "^0.1.0" -promise@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" - dependencies: - asap "~2.0.3" - proper-lockfile@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-1.2.0.tgz#ceff5dd89d3e5f10fb75e1e8e76bc75801a59c34" @@ -4427,7 +4253,7 @@ qs@6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625" -"qs@>= 0.4.0", qs@^6.1.0, qs@~6.3.0: +"qs@>= 0.4.0", qs@~6.3.0: version "6.3.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" @@ -4619,18 +4445,11 @@ regjsparser@^0.1.4: dependencies: jsesc "~0.5.0" -remarkable@^1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.1.tgz#aaca4972100b66a642a63a1021ca4bac1be3bff6" - dependencies: - argparse "~0.1.15" - autolinker "~0.15.0" - repeat-element@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" -repeat-string@^1.5.2, repeat-string@^1.6.1: +repeat-string@^1.5.2: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" @@ -4887,12 +4706,6 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" -set-getter@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" - dependencies: - to-object-path "^0.3.0" - set-immediate-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" @@ -5150,10 +4963,6 @@ strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" -strip-color@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/strip-color/-/strip-color-0.1.0.tgz#106f65d3d3e6a2d9401cac0eb0ce8b8a702b4f7b" - strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -5180,14 +4989,6 @@ svgo@^0.7.0: sax "~1.2.1" whet.extend "~0.9.9" -sync-request@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-3.0.1.tgz#caa1235aaf889ba501076a1834c436830a82fb73" - dependencies: - concat-stream "^1.4.7" - http-response-object "^1.0.1" - then-request "^2.0.1" - systemjs-builder@0.16.3: version "0.16.3" resolved "https://registry.yarnpkg.com/systemjs-builder/-/systemjs-builder-0.16.3.tgz#7dc830f816cf2a86d03019d057ca1bd096659c18" @@ -5284,17 +5085,6 @@ tfunk@^3.0.1: chalk "^1.1.1" object-path "^0.9.0" -then-request@^2.0.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/then-request/-/then-request-2.2.0.tgz#6678b32fa0ca218fe569981bbd8871b594060d81" - dependencies: - caseless "~0.11.0" - concat-stream "^1.4.7" - http-basic "^2.5.1" - http-response-object "^1.1.0" - promise "^7.1.1" - qs "^6.1.0" - through2@^0.6.1: version "0.6.5" resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" @@ -5345,16 +5135,6 @@ to-fast-properties@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.2.tgz#f3f5c0c3ba7299a7ef99427e44633257ade43320" -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - dependencies: - kind-of "^3.0.2" - -toml@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/toml/-/toml-2.3.2.tgz#5eded5ca42887924949fd06eb0e955656001e834" - tough-cookie@~2.3.0: version "2.3.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" @@ -5436,11 +5216,7 @@ unc-path-regex@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" -underscore.string@~2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-2.4.0.tgz#8cdd8fbac4e2d2ea1e7e2e8097c42f442280f85b" - -underscore@1.7.x, underscore@~1.7.0: +underscore@1.7.x: version "1.7.0" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.7.0.tgz#6bbaf0877500d36be34ecaa584e0db9fef035209"