Skip to content

Commit

Permalink
Merge branch 'master' into append-hms-slug
Browse files Browse the repository at this point in the history
  • Loading branch information
S. Suzuki authored Apr 5, 2018
2 parents f06c629 + b97ce89 commit 58a7bd1
Show file tree
Hide file tree
Showing 40 changed files with 309 additions and 159 deletions.
9 changes: 9 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,15 @@
"contributions": [
"code"
]
},
{
"login": "jeremybise",
"name": "Jeremy Bise",
"avatar_url": "https://avatars0.githubusercontent.com/u/2193?v=4",
"profile": "http://thosegeeks.com",
"contributions": [
"doc"
]
}
]
}
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,38 @@
Click to see more.
</summary>

* Fix broken new media uploads for Git Gateway ([@tech4him1](https://github.com/tech4him1) in [#1221](https://github.com/netlify/netlify-cms/pull/1221))
* Enable editorial workflow for test backend ([@erquhart](https://github.com/erquhart) in [#1225](https://github.com/netlify/netlify-cms/pull/1225))

</details>

## 1.4.0 (March 29, 2018)
Filename creation can now be customized to exclude Unicode! Also, check out the new Beta Features! 💥

### Features
* Add option to strip Unicode from entry filenames ([@tech4him1](https://github.com/tech4him1) in [#1135](https://github.com/netlify/netlify-cms/pull/1135))

### Improvements
* Hide "create new" button for single files ([@tech4him1](https://github.com/tech4him1) in [#1200](https://github.com/netlify/netlify-cms/pull/1200))
* Filter editorial workflow entries by PR base branch ([@erquhart](https://github.com/erquhart) in [#1155](https://github.com/netlify/netlify-cms/pull/1155))

### Bug Fixes
* Allow list widget "add" button to be disabled ([@gazebosx3](https://github.com/gazebosx3) in [#1102](https://github.com/netlify/netlify-cms/pull/1102))
* Fix broken thumbnail when uploading an image to a private repository ([@Quicksaver](https://github.com/Quicksaver) in [#994](https://github.com/netlify/netlify-cms/pull/994))
* Get default value from each widget rather than setting all to null ([@MichaelRomani](https://github.com/MichaelRomani) in [#1126](https://github.com/netlify/netlify-cms/pull/1126))
* Fix editor validation notifications for editorial workflow ([@erquhart](https://github.com/erquhart) in [#1204](https://github.com/netlify/netlify-cms/pull/1204))
* Prevent Git Gateway users with invalid tokens from logging in ([@tech4him1](https://github.com/tech4him1) in [#1209](https://github.com/netlify/netlify-cms/pull/1209))
* Fix relation list preview ([@Quicksaver](https://github.com/Quicksaver) in [#1199](https://github.com/netlify/netlify-cms/pull/1199))
* Fix missing config file handling ([@talves](https://github.com/talves) in [#1182](https://github.com/netlify/netlify-cms/pull/1182))
* Fix initially blank date fields ([@tech4him1](https://github.com/tech4him1) in [#1210](https://github.com/netlify/netlify-cms/pull/1210))

### Beta Features
* Accept CSS strings in `registerPreviewStyle` ([@erquhart](https://github.com/erquhart) in [#1162](https://github.com/netlify/netlify-cms/pull/1162))
* Change manual init API to use the same bundle as auto init ([@talves](https://github.com/talves) and @erquhart in [#1173](https://github.com/netlify/netlify-cms/pull/1173))

### 4 tha devz
* Ship source code to npm ([@tech4him1](https://github.com/tech4him1) in [#1095](https://github.com/netlify/netlify-cms/pull/1095))

## 1.3.5 (March 6, 2018)
Fixes styling issues

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
| [<img src="https://avatars3.githubusercontent.com/u/802086?v=4" width="100px;"/><br /><sub><b>Luís Miguel</b></sub>](https://github.com/Quicksaver)<br />[🐛](https://github.com/netlify/netlify-cms/issues?q=author%3AQuicksaver "Bug reports") [💻](https://github.com/netlify/netlify-cms/commits?author=Quicksaver "Code") | [<img src="https://avatars2.githubusercontent.com/u/357379?v=4" width="100px;"/><br /><sub><b>Chris Swithinbank</b></sub>](http://chrisswithinbank.net/)<br />[📖](https://github.com/netlify/netlify-cms/commits?author=delucis "Documentation") | [<img src="https://avatars3.githubusercontent.com/u/1262221?v=4" width="100px;"/><br /><sub><b>remmah</b></sub>](https://github.com/remmah)<br />[📖](https://github.com/netlify/netlify-cms/commits?author=remmah "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/29388?v=4" width="100px;"/><br /><sub><b>Sumeet Jain</b></sub>](http://sumeetjain.com)<br />[📖](https://github.com/netlify/netlify-cms/commits?author=sumeetjain "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/26816046?v=4" width="100px;"/><br /><sub><b>Sagar Khatri</b></sub>](https://github.com/ragasirtahk)<br />[📖](https://github.com/netlify/netlify-cms/commits?author=ragasirtahk "Documentation") [💡](#example-ragasirtahk "Examples") | [<img src="https://avatars0.githubusercontent.com/u/8182932?v=4" width="100px;"/><br /><sub><b>Kevin Doocey</b></sub>](https://www.dooceykev.in)<br />[💻](https://github.com/netlify/netlify-cms/commits?author=Doocey "Code") | [<img src="https://avatars0.githubusercontent.com/u/31023010?v=4" width="100px;"/><br /><sub><b>Swieckowski</b></sub>](https://www.linkedin.com/in/arthur-swieckowski/)<br />[💻](https://github.com/netlify/netlify-cms/commits?author=Swieckowski "Code") [📖](https://github.com/netlify/netlify-cms/commits?author=Swieckowski "Documentation") [⚠️](https://github.com/netlify/netlify-cms/commits?author=Swieckowski "Tests") |
| [<img src="https://avatars2.githubusercontent.com/u/283419?v=4" width="100px;"/><br /><sub><b>Tim Carry</b></sub>](http://www.pixelastic.com/)<br />[💻](https://github.com/netlify/netlify-cms/commits?author=pixelastic "Code") [🎨](#design-pixelastic "Design") [📖](https://github.com/netlify/netlify-cms/commits?author=pixelastic "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/30510616?v=4" width="100px;"/><br /><sub><b>Sol Park</b></sub>](https://github.com/solpark)<br />[💻](https://github.com/netlify/netlify-cms/commits?author=solpark "Code") | [<img src="https://avatars0.githubusercontent.com/u/29218846?v=4" width="100px;"/><br /><sub><b>Michael Romani</b></sub>](https://github.com/michaelromani)<br />[💻](https://github.com/netlify/netlify-cms/commits?author=michaelromani "Code") | [<img src="https://avatars1.githubusercontent.com/u/15175868?v=4" width="100px;"/><br /><sub><b>Xifeng Jin</b></sub>](http://linkedin/in/xifengjin88)<br />[🐛](https://github.com/netlify/netlify-cms/issues?q=author%3Axifengjin88 "Bug reports") [💻](https://github.com/netlify/netlify-cms/commits?author=xifengjin88 "Code") | [<img src="https://avatars1.githubusercontent.com/u/372831?v=4" width="100px;"/><br /><sub><b>Pedro Duarte</b></sub>](http://pedroduarte.me)<br />[🐛](https://github.com/netlify/netlify-cms/issues?q=author%3Apeduarte "Bug reports") [💻](https://github.com/netlify/netlify-cms/commits?author=peduarte "Code") [📖](https://github.com/netlify/netlify-cms/commits?author=peduarte "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/6064830?v=4" width="100px;"/><br /><sub><b>Antonio Argote</b></sub>](http://antonioargote.com)<br />[🎨](#design-Strangehill "Design") | [<img src="https://avatars3.githubusercontent.com/u/1479451?v=4" width="100px;"/><br /><sub><b>Kristaps Taube</b></sub>](https://www.ktaube.com)<br />[💻](https://github.com/netlify/netlify-cms/commits?author=ktaube "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/26639499?v=4" width="100px;"/><br /><sub><b>David Ko</b></sub>](https://github.com/daveyko)<br />[💻](https://github.com/netlify/netlify-cms/commits?author=daveyko "Code") | [<img src="https://avatars3.githubusercontent.com/u/440562?v=4" width="100px;"/><br /><sub><b>Iñaki García</b></sub>](http://www.txorua.com)<br />[🎨](#design-igarbla "Design") | [<img src="https://avatars3.githubusercontent.com/u/27162255?v=4" width="100px;"/><br /><sub><b>Sam</b></sub>](https://github.com/gazebosx3)<br />[💻](https://github.com/netlify/netlify-cms/commits?author=gazebosx3 "Code") | [<img src="https://avatars1.githubusercontent.com/u/174777?v=4" width="100px;"/><br /><sub><b>Josh Dzielak</b></sub>](https://dzello.com)<br />[📖](https://github.com/netlify/netlify-cms/commits?author=dzello "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/13282103?v=4" width="100px;"/><br /><sub><b>terrierscript</b></sub>](https://terrierscript.com)<br />[💻](https://github.com/netlify/netlify-cms/commits?author=terrierscript "Code") |
| [<img src="https://avatars3.githubusercontent.com/u/26639499?v=4" width="100px;"/><br /><sub><b>David Ko</b></sub>](https://github.com/daveyko)<br />[💻](https://github.com/netlify/netlify-cms/commits?author=daveyko "Code") | [<img src="https://avatars3.githubusercontent.com/u/440562?v=4" width="100px;"/><br /><sub><b>Iñaki García</b></sub>](http://www.txorua.com)<br />[🎨](#design-igarbla "Design") | [<img src="https://avatars3.githubusercontent.com/u/27162255?v=4" width="100px;"/><br /><sub><b>Sam</b></sub>](https://github.com/gazebosx3)<br />[💻](https://github.com/netlify/netlify-cms/commits?author=gazebosx3 "Code") | [<img src="https://avatars1.githubusercontent.com/u/174777?v=4" width="100px;"/><br /><sub><b>Josh Dzielak</b></sub>](https://dzello.com)<br />[📖](https://github.com/netlify/netlify-cms/commits?author=dzello "Documentation") | [<img src="https://avatars0.githubusercontent.com/u/2193?v=4" width="100px;"/><br /><sub><b>Jeremy Bise</b></sub>](http://thosegeeks.com)<br />[📖](https://github.com/netlify/netlify-cms/commits?author=jeremybise "Documentation") |
<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
2 changes: 2 additions & 0 deletions example/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ backend:
display_url: https://example.com
media_folder: "assets/uploads"

publish_mode: editorial_workflow # optional, enables publishing workflow

collections: # A list of collections the CMS should be able to edit
- name: "posts" # Used in routes, ie.: /admin/collections/:slug/edit
label: "Posts" # Used in the UI
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "netlify-cms",
"version": "1.3.5",
"version": "1.4.0",
"description": "Netlify CMS lets content editors work on structured content stored in git",
"main": "dist/cms.js",
"scripts": {
Expand Down Expand Up @@ -31,8 +31,8 @@
]
},
"files": [
"dist/",
"README.md"
"src/",
"dist/"
],
"pre-commit": "lint:staged",
"jest": {
Expand Down
26 changes: 17 additions & 9 deletions src/actions/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,21 @@ function parseConfig(data) {
return config;
}

async function getConfig(file, isPreloaded) {
const response = await fetch(file, { credentials: 'same-origin' });
if (response.status !== 200) {
if (isPreloaded) return parseConfig('');
throw new Error(`Failed to load config.yml (${ response.status })`);
}
const contentType = response.headers.get('Content-Type') || 'Not-Found';
const isYaml = contentType.indexOf('yaml') !== -1;
if (!isYaml) {
console.log(`Response for ${ file } was not yaml. (Content-Type: ${ contentType })`);
if (isPreloaded) return parseConfig('');
}
return parseConfig(await response.text());
}

export function configLoaded(config) {
return {
type: CONFIG_SUCCESS,
Expand Down Expand Up @@ -115,19 +130,12 @@ export function loadConfig() {

try {
const preloadedConfig = getState().config;
const response = await fetch('config.yml', { credentials: 'same-origin' })
const requestSuccess = response.status === 200;

if (!preloadedConfig && !requestSuccess) {
throw new Error(`Failed to load config.yml (${ response.status })`);
}

const loadedConfig = parseConfig(requestSuccess ? await response.text() : '');
const loadedConfig = await getConfig('config.yml', preloadedConfig && preloadedConfig.size > 1);

/**
* Merge any existing configuration so the result can be validated.
*/
const mergedConfig = mergePreloadedConfig(preloadedConfig, loadedConfig)
const mergedConfig = mergePreloadedConfig(preloadedConfig, loadedConfig);
const config = flow(validateConfig, applyDefaults)(mergedConfig);

dispatch(configDidLoad(config));
Expand Down
16 changes: 16 additions & 0 deletions src/backends/git-gateway/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ export default class API extends GithubAPI {
this.repoURL = "";
}

hasWriteAccess() {
return this.getBranch()
.then(() => true)
.catch(error => {
if (error.status === 401) {
if (error.message === "Bad credentials") {
throw new Error("Git Gateway Error: Please ask your site administrator to reissue the Git Gateway token.");
} else {
return false;
}
} else {
console.error("Problem fetching repo data from GitHub");
throw error;
}
});
}

getRequestHeaders(headers = {}) {
return this.tokenPromise()
Expand Down
11 changes: 10 additions & 1 deletion src/backends/git-gateway/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,16 @@ export default class GitGateway extends GitHubBackend {
} else {
throw new Error("You don't have sufficient permissions to access Netlify CMS");
}
});
})
.then(userData =>
this.api.hasWriteAccess().then(canWrite => {
if (canWrite) {
return userData;
} else {
throw new Error("You don't have sufficient permissions to access Netlify CMS");
}
})
);
}

logout() {
Expand Down
6 changes: 1 addition & 5 deletions src/backends/github/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ export default class API {
});
}

isPrivateRepo() {
return this.request(this.repoURL)
.then(repo => repo.private);
}

requestHeaders(headers = {}) {
const baseHeader = {
"Content-Type": "application/json",
Expand Down Expand Up @@ -225,6 +220,7 @@ export default class API {
params: {
head: branchName,
state: 'open',
base: this.branch,
},
})
.then(prs => prs.some(pr => pr.head.ref === branchName));
Expand Down
44 changes: 4 additions & 40 deletions src/backends/github/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,49 +106,13 @@ export default class GitHub {
return this.api.persistFiles(entry, mediaFiles, options);
}

/**
* Pulls repo info from a `repos` response url property.
*
* Turns this:
* '<api_root>/repo/<username>/<repo>/...'
*
* Into this:
* '<username>/<repo>'
*/
getRepoFromResponseUrl(url) {
return url

// -> '/repo/<username>/<repo>/...'
.slice(this.api_root.length)

// -> [ '', 'repo', '<username>', '<repo>', ... ]
.split('/')

// -> [ '<username>', '<repo>' ]
.slice(2, 4)

// -> '<username>/<repo>'
.join('/');
}

async persistMedia(mediaFile, options = {}) {
try {
const response = await this.api.persistFiles(null, [mediaFile], options);
const repo = this.repo || this.getRepoFromResponseUrl(response.url);
const { value, size, path, fileObj } = mediaFile;
let url = `https://raw.githubusercontent.com/${repo}/${this.branch}${path}`;

// Assets uploaded to private repos will need valid access tokens.
const isPrivateRepo = await this.api.isPrivateRepo();
if (isPrivateRepo) {
const files = await this.api.listFiles(this.config.get('media_folder'));
const file = files.find(f => (f.sha === mediaFile.sha));
if (file) {
url = file.download_url;
}
}

return { id: mediaFile.sha, name: value, size: fileObj.size, url, path: trimStart(path, '/') };

const { sha, value, size, path, fileObj } = mediaFile;
const url = URL.createObjectURL(fileObj);
return { id: sha, name: value, size: fileObj.size, url, path: trimStart(path, '/') };
}
catch(error) {
console.error(error);
Expand Down
82 changes: 77 additions & 5 deletions src/backends/test-repo/implementation.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { remove, attempt, isError } from 'lodash';
import uuid from 'uuid/v4';
import { EDITORIAL_WORKFLOW, status } from 'Constants/publishModes';
import { EditorialWorkflowError } from 'ValueObjects/errors';
import AuthenticationPage from './AuthenticationPage';

window.repoFiles = window.repoFiles || {};
window.repoFilesUnpublished = window.repoFilesUnpublished || [];

function getFile(path) {
const segments = path.split('/');
Expand Down Expand Up @@ -78,20 +81,89 @@ export default class TestRepo {
});
}

persistEntry(entry, mediaFiles = [], options) {
unpublishedEntries() {
return Promise.resolve(window.repoFilesUnpublished);
}

unpublishedEntry(collection, slug) {
const entry = window.repoFilesUnpublished.find(e => (
e.metaData.collection === collection.get('name') && e.slug === slug
));
if (!entry) {
return Promise.reject(new EditorialWorkflowError('content is not under editorial workflow', true));
}
return Promise.resolve(entry);
}

deleteUnpublishedEntry(collection, slug) {
const unpubStore = window.repoFilesUnpublished;
const existingEntryIndex = unpubStore.findIndex(e => (
e.metaData.collection === collection && e.slug === slug
));
unpubStore.splice(existingEntryIndex, 1);
return Promise.resolve()
}

persistEntry({ path, raw, slug }, mediaFiles = [], options = {}) {
if (options.mode === EDITORIAL_WORKFLOW) {
const unpubStore = window.repoFilesUnpublished;
const existingEntryIndex = unpubStore.findIndex(e => e.file.path === path);
if (existingEntryIndex >= 0) {
const unpubEntry = { ...unpubStore[existingEntryIndex], data: raw };
unpubEntry.title = options.parsedData && options.parsedData.title;
unpubEntry.description = options.parsedData && options.parsedData.description;
unpubStore.splice(existingEntryIndex, 1, unpubEntry);
} else {
const unpubEntry = {
data: raw,
file: {
path,
},
metaData: {
collection: options.collectionName,
status: status.first(),
title: options.parsedData && options.parsedData.title,
description: options.parsedData && options.parsedData.description,
},
slug,
};
unpubStore.push(unpubEntry);
}
return Promise.resolve();
}

const newEntry = options.newEntry || false;
const folder = entry.path.substring(0, entry.path.lastIndexOf('/'));
const fileName = entry.path.substring(entry.path.lastIndexOf('/') + 1);
const folder = path.substring(0, path.lastIndexOf('/'));
const fileName = path.substring(path.lastIndexOf('/') + 1);
window.repoFiles[folder] = window.repoFiles[folder] || {};
window.repoFiles[folder][fileName] = window.repoFiles[folder][fileName] || {};
if (newEntry) {
window.repoFiles[folder][fileName] = { content: entry.raw };
window.repoFiles[folder][fileName] = { content: raw };
} else {
window.repoFiles[folder][fileName].content = entry.raw;
window.repoFiles[folder][fileName].content = raw;
}
return Promise.resolve();
}

updateUnpublishedEntryStatus(collection, slug, newStatus) {
const unpubStore = window.repoFilesUnpublished;
const entryIndex = unpubStore.findIndex(e => (
e.metaData.collection === collection && e.slug === slug
));
unpubStore[entryIndex].metaData.status = newStatus;
return Promise.resolve();
}

publishUnpublishedEntry(collection, slug) {
const unpubStore = window.repoFilesUnpublished;
const unpubEntryIndex = unpubStore.findIndex(e => (
e.metaData.collection === collection && e.slug === slug
));
const unpubEntry = unpubStore[unpubEntryIndex];
const entry = { raw: unpubEntry.data, slug: unpubEntry.slug, path: unpubEntry.file.path };
unpubStore.splice(unpubEntryIndex, 1);
return this.persistEntry(entry);
}
getMedia() {
return Promise.resolve(this.assets);
}
Expand Down
Loading

0 comments on commit 58a7bd1

Please sign in to comment.