diff --git a/.env-template b/.env-template new file mode 100644 index 00000000..8c4fe11c --- /dev/null +++ b/.env-template @@ -0,0 +1,3 @@ +SAUCE_USERNAME=ascribe +SAUCE_ACCESS_KEY= +SAUCE_DEFAULT_URL= diff --git a/.eslintrc b/.eslintrc index d41c0a2a..5751f3ad 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,7 +2,7 @@ "parser": "babel-eslint", "env": { "browser": true, - "es6": true + "es6": true, }, "rules": { "new-cap": [2, {newIsCap: true, capIsNew: false}], diff --git a/.gitignore b/.gitignore index 17b63e95..0b4d85c7 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ gemini-report/* node_modules/* .DS_Store +.env diff --git a/README.md b/README.md index 015c3fe9..1b918e5d 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ We use [ESLint](https://github.com/eslint/eslint) with our own [custom ruleset]( SCSS Code Conventions ===================== + Install [lint-scss](https://github.com/brigade/scss-lint), check the [editor integration docs](https://github.com/brigade/scss-lint#editor-integration) to integrate the lint in your editor. Some interesting links: @@ -66,15 +67,17 @@ Some interesting links: Branch names ============ -Since we moved to Github, we cannot create branch names automatically with JIRA anymore. -To not lose context, but still be able to switch branches quickly using a ticket's number, we're recommending the following rules when naming our branches in onion. +To allow Github and JIRA to track branches while still allowing us to switch branches quickly using a ticket's number (and keep our peace of mind), we have the following rules for naming branches: ``` +// For issues logged in Github: +AG--brief-and-sane-description-of-the-ticket + +// For issues logged in JIRA: AD--brief-and-sane-description-of-the-ticket ``` -where `brief-and-sane-description-of-the-ticket` does not need to equal to the ticket's title. -This allows JIRA to still track branches and pull-requests while allowing us to keep our peace of mind. +where `brief-and-sane-description-of-the-ticket` does not need to equal to the issue or ticket's title. Example diff --git a/js/actions/edition_list_actions.js b/js/actions/edition_list_actions.js index a52e32b9..9474a418 100644 --- a/js/actions/edition_list_actions.js +++ b/js/actions/edition_list_actions.js @@ -39,7 +39,7 @@ class EditionListActions { return Q.Promise((resolve, reject) => { EditionListFetcher - .fetch({ pieceId, page, itemsToFetch, orderBy, orderAsc, filterBy }) + .fetch({ pieceId, page, orderBy, orderAsc, filterBy, pageSize: itemsToFetch }) .then((res) => { if (res && !res.editions) { throw new Error('Piece has no editions to fetch.'); diff --git a/js/components/ascribe_detail/further_details_fileuploader.js b/js/components/ascribe_detail/further_details_fileuploader.js index 8546e43f..61e0724f 100644 --- a/js/components/ascribe_detail/further_details_fileuploader.js +++ b/js/components/ascribe_detail/further_details_fileuploader.js @@ -8,6 +8,7 @@ import ReactS3FineUploader from './../ascribe_uploader/react_s3_fine_uploader'; import ApiUrls from '../../constants/api_urls'; import AppConstants from '../../constants/application_constants'; +import { validationTypes } from '../../constants/uploader_constants'; import { getCookie } from '../../utils/fetch_api_utils'; import { getLangText } from '../../utils/lang_utils'; @@ -15,23 +16,26 @@ import { getLangText } from '../../utils/lang_utils'; let FurtherDetailsFileuploader = React.createClass({ propTypes: { + pieceId: React.PropTypes.number.isRequired, + + areAssetsDownloadable: React.PropTypes.bool, + editable: React.PropTypes.bool, + isReadyForFormSubmission: React.PropTypes.func, label: React.PropTypes.string, - pieceId: React.PropTypes.number, + multiple: React.PropTypes.bool, otherData: React.PropTypes.arrayOf(React.PropTypes.object), + onValidationFailed: React.PropTypes.func, setIsUploadReady: React.PropTypes.func, submitFile: React.PropTypes.func, - onValidationFailed: React.PropTypes.func, - isReadyForFormSubmission: React.PropTypes.func, - editable: React.PropTypes.bool, - multiple: React.PropTypes.bool, - areAssetsDownloadable: React.PropTypes.bool + validation: ReactS3FineUploader.propTypes.validation }, getDefaultProps() { return { areAssetsDownloadable: true, label: getLangText('Additional files'), - multiple: false + multiple: false, + validation: validationTypes.additionalData }; }, @@ -61,7 +65,7 @@ let FurtherDetailsFileuploader = React.createClass({ url: ApiUrls.blob_otherdatas, pieceId: this.props.pieceId }} - validation={AppConstants.fineUploader.validation.additionalData} + validation={this.props.validation} submitFile={this.props.submitFile} onValidationFailed={this.props.onValidationFailed} setIsUploadReady={this.props.setIsUploadReady} diff --git a/js/components/ascribe_detail/media_container.js b/js/components/ascribe_detail/media_container.js index cb86e558..00ca9164 100644 --- a/js/components/ascribe_detail/media_container.js +++ b/js/components/ascribe_detail/media_container.js @@ -65,7 +65,16 @@ let MediaContainer = React.createClass({ // We also force uniqueness of usernames, so this check is safe to dtermine if the // content was registered by the current user. const didUserRegisterContent = currentUser && (currentUser.username === content.user_registered); - const fileExtension = extractFileExtensionFromString(content.digital_work.url); + + // We want to show the file's extension as a label of the download button. + // We can however not only use `extractFileExtensionFromString` on the url for that + // as files might be saved on S3 without a file extension which leads + // `extractFileExtensionFromString` to extract everything starting from the top level + // domain: e.g. '.net/live/'. + // Therefore, we extract the file's name (last part of url, separated with a slash) + // and try to extract the file extension from there. + const fileName = content.digital_work.url.split('/').pop(); + const fileExtension = extractFileExtensionFromString(fileName); let thumbnail = content.thumbnail.thumbnail_sizes && content.thumbnail.thumbnail_sizes['600x600'] ? content.thumbnail.thumbnail_sizes['600x600'] : content.thumbnail.url_safe; @@ -123,7 +132,11 @@ let MediaContainer = React.createClass({ className="ascribe-margin-1px" href={content.digital_work.url} target="_blank"> - {getLangText('Download')} .{fileExtension} + {/* + If it turns out that `fileExtension` is an empty string, we're just + using the label 'file'. + */} + {getLangText('Download')} .{fileExtension || 'file'} {embed} diff --git a/js/components/ascribe_forms/form_create_contract.js b/js/components/ascribe_forms/form_create_contract.js index fcd874da..cbdd12c3 100644 --- a/js/components/ascribe_forms/form_create_contract.js +++ b/js/components/ascribe_forms/form_create_contract.js @@ -2,19 +2,20 @@ import React from 'react'; -import Form from '../ascribe_forms/form'; -import Property from '../ascribe_forms/property'; +import ContractListActions from '../../actions/contract_list_actions'; import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationActions from '../../actions/global_notification_actions'; -import ContractListActions from '../../actions/contract_list_actions'; - -import AppConstants from '../../constants/application_constants'; -import ApiUrls from '../../constants/api_urls'; - import InputFineUploader from './input_fineuploader'; +import Form from '../ascribe_forms/form'; +import Property from '../ascribe_forms/property'; + +import ApiUrls from '../../constants/api_urls'; +import AppConstants from '../../constants/application_constants'; +import { validationTypes } from '../../constants/uploader_constants'; + import { getLangText } from '../../utils/lang_utils'; import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils'; @@ -78,8 +79,8 @@ let CreateContractForm = React.createClass({ url: ApiUrls.blob_contracts }} validation={{ - itemLimit: AppConstants.fineUploader.validation.additionalData.itemLimit, - sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit, + itemLimit: validationTypes.additionalData.itemLimit, + sizeLimit: validationTypes.additionalData.sizeLimit, allowedExtensions: ['pdf'] }} areAssetsDownloadable={true} diff --git a/js/components/ascribe_forms/form_register_piece.js b/js/components/ascribe_forms/form_register_piece.js index 10fb54d3..93f0ddf4 100644 --- a/js/components/ascribe_forms/form_register_piece.js +++ b/js/components/ascribe_forms/form_register_piece.js @@ -8,12 +8,16 @@ import UserActions from '../../actions/user_actions'; import Form from './form'; import Property from './property'; import InputFineUploader from './input_fineuploader'; -import UploadButton from '../ascribe_uploader/ascribe_upload_button/upload_button'; + import FormSubmitButton from '../ascribe_buttons/form_submit_button'; +import UploadButton from '../ascribe_uploader/ascribe_upload_button/upload_button'; + +import AscribeSpinner from '../ascribe_spinner'; + import ApiUrls from '../../constants/api_urls'; import AppConstants from '../../constants/application_constants'; -import AscribeSpinner from '../ascribe_spinner'; +import { validationParts, validationTypes } from '../../constants/uploader_constants'; import { getLangText } from '../../utils/lang_utils'; import { mergeOptions } from '../../utils/general_utils'; @@ -180,7 +184,7 @@ let RegisterPieceForm = React.createClass({ createBlobRoutine={{ url: ApiUrls.blob_digitalworks }} - validation={AppConstants.fineUploader.validation.registerWork} + validation={validationTypes.registerWork} setIsUploadReady={this.setIsUploadReady('digitalWorkKeyReady')} isReadyForFormSubmission={formSubmissionValidation.atLeastOneUploadedFile} isFineUploaderActive={isFineUploaderActive} @@ -206,9 +210,9 @@ let RegisterPieceForm = React.createClass({ fileClass: 'thumbnail' }} validation={{ - itemLimit: AppConstants.fineUploader.validation.workThumbnail.itemLimit, - sizeLimit: AppConstants.fineUploader.validation.workThumbnail.sizeLimit, - allowedExtensions: ['png', 'jpg', 'jpeg', 'gif'] + itemLimit: validationTypes.workThumbnail.itemLimit, + sizeLimit: validationTypes.workThumbnail.sizeLimit, + allowedExtensions: validationParts.allowedExtensions.images }} setIsUploadReady={this.setIsUploadReady('thumbnailKeyReady')} fileClassToUpload={{ diff --git a/js/components/ascribe_forms/input_fineuploader.js b/js/components/ascribe_forms/input_fineuploader.js index fa9c72b6..80fa1891 100644 --- a/js/components/ascribe_forms/input_fineuploader.js +++ b/js/components/ascribe_forms/input_fineuploader.js @@ -28,11 +28,7 @@ const InputFineUploader = React.createClass({ createBlobRoutine: shape({ url: string }), - validation: shape({ - itemLimit: number, - sizeLimit: string, - allowedExtensions: arrayOf(string) - }), + validation: ReactS3FineUploader.propTypes.validation, // isFineUploaderActive is used to lock react fine uploader in case // a user is actually not logged in already to prevent him from droping files diff --git a/js/components/ascribe_settings/contract_settings_update_button.js b/js/components/ascribe_settings/contract_settings_update_button.js index f3e92c69..dc652b9b 100644 --- a/js/components/ascribe_settings/contract_settings_update_button.js +++ b/js/components/ascribe_settings/contract_settings_update_button.js @@ -2,17 +2,18 @@ import React from 'react'; -import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader'; -import UploadButton from '../ascribe_uploader/ascribe_upload_button/upload_button'; - -import AppConstants from '../../constants/application_constants'; -import ApiUrls from '../../constants/api_urls'; - import ContractListActions from '../../actions/contract_list_actions'; import GlobalNotificationModel from '../../models/global_notification_model'; import GlobalNotificationActions from '../../actions/global_notification_actions'; +import ReactS3FineUploader from '../ascribe_uploader/react_s3_fine_uploader'; +import UploadButton from '../ascribe_uploader/ascribe_upload_button/upload_button'; + +import ApiUrls from '../../constants/api_urls'; +import AppConstants from '../../constants/application_constants'; +import { validationTypes } from '../../constants/uploader_constants'; + import { formSubmissionValidation } from '../ascribe_uploader/react_s3_fine_uploader_utils'; import { getCookie } from '../../utils/fetch_api_utils'; import { getLangText } from '../../utils/lang_utils'; @@ -68,8 +69,8 @@ let ContractSettingsUpdateButton = React.createClass({ url: ApiUrls.blob_contracts }} validation={{ - itemLimit: AppConstants.fineUploader.validation.registerWork.itemLimit, - sizeLimit: AppConstants.fineUploader.validation.additionalData.sizeLimit, + itemLimit: validationTypes.registerWork.itemLimit, + sizeLimit: validationTypes.additionalData.sizeLimit, allowedExtensions: ['pdf'] }} setIsUploadReady={() =>{/* So that ReactS3FineUploader is not complaining */}} diff --git a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview.js b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview.js index ca1be2d2..0f95427d 100644 --- a/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview.js +++ b/js/components/ascribe_uploader/ascribe_file_drag_and_drop/file_drag_and_drop_preview.js @@ -23,6 +23,7 @@ const FileDragAndDropPreview = React.createClass({ s3Url: string, s3UrlSafe: string }).isRequired, + handleDeleteFile: func, handleCancelFile: func, handlePauseFile: func, @@ -33,9 +34,9 @@ const FileDragAndDropPreview = React.createClass({ }, toggleUploadProcess() { - if(this.props.file.status === 'uploading') { + if (this.props.file.status === 'uploading') { this.props.handlePauseFile(this.props.file.id); - } else if(this.props.file.status === 'paused') { + } else if (this.props.file.status === 'paused') { this.props.handleResumeFile(this.props.file.id); } }, @@ -54,13 +55,13 @@ const FileDragAndDropPreview = React.createClass({ (file.status === 'upload successful' || file.status === 'online') && file.s3UrlSafe) { handleDeleteFile(file.id); - } else if(handleCancelFile) { + } else if (handleCancelFile) { handleCancelFile(file.id); } }, handleDownloadFile() { - if(this.props.file.s3Url) { + if (this.props.file.s3Url) { // This simply opens a new browser tab with the url provided open(this.props.file.s3Url); } @@ -69,7 +70,7 @@ const FileDragAndDropPreview = React.createClass({ getFileName() { const { numberOfDisplayedFiles, file } = this.props; - if(numberOfDisplayedFiles === 1) { + if (numberOfDisplayedFiles === 1) { return ( {truncateTextAtCharIndex(file.name, 30, '(...).' + extractFileExtensionFromString(file.name))} @@ -81,7 +82,7 @@ const FileDragAndDropPreview = React.createClass({ }, getRemoveButton() { - if(this.props.areAssetsEditable) { + if (this.props.areAssetsEditable) { return (