├── Procfile ├── public ├── img │ ├── .DS_Store │ ├── users.png │ ├── badge │ │ ├── funding.png │ │ ├── testing.png │ │ ├── resources.png │ │ ├── software.png │ │ ├── methodology.png │ │ ├── supervision.png │ │ ├── data_curation.png │ │ ├── investigation.png │ │ ├── writing_review.png │ │ ├── conceptualization.png │ │ ├── formal_analysis.png │ │ ├── writing_initial.png │ │ ├── data_visualization.png │ │ └── project_administration.png │ └── Badges-ProposedWorkflow.jpg ├── code.pug ├── widgets │ ├── demos │ │ ├── orcid-widget-with-name-shown.html │ │ ├── orcid-widget-with-doi-shown.html │ │ └── doi-widget.html │ ├── paper-badger-widget.js │ └── widget.js ├── index.pug └── css │ ├── main.css │ └── vendor │ ├── grids-responsive-min.css │ └── pure-min.css ├── .travis.yml ├── bin ├── badgekit_to_badgr │ ├── badgekit.db │ ├── badges_only.local.sqlite3 │ ├── default.db.env │ ├── README.md │ └── migrateBKtoBS.js └── load_badges.sql ├── docs ├── Badges--Proposed-Workflow_1_.png ├── orcid-setup.md ├── high-level-architecture.md ├── paper-badger-widget.md └── widget.md ├── scripts └── run-tests.sh ├── .gitignore ├── src ├── environments.js ├── badges │ ├── client │ │ └── index.js │ └── service │ │ └── index.js ├── index.js ├── routes │ ├── claims │ │ └── index.js │ ├── badges │ │ └── index.js │ ├── users │ │ └── index.js │ └── papers │ │ └── index.js ├── models.js ├── helpers.js └── app.js ├── .editorconfig ├── app.json ├── templates ├── pages │ ├── 404.jsx │ ├── denied.jsx │ ├── about.jsx │ ├── view.jsx │ ├── paper.jsx │ ├── home.jsx │ └── issue.jsx ├── components │ ├── badge.jsx │ ├── footer.jsx │ ├── header.jsx │ ├── badgeInstance.jsx │ ├── badgelist.jsx │ ├── badgeInstanceList.jsx │ └── page.jsx └── client.jsx ├── docker-compose.yml ├── CODE_OF_CONDUCT.md ├── default.env ├── webpack.config.js ├── test ├── unit │ └── helpers.js └── integration │ └── app.js ├── package.json ├── .eslintrc.yaml ├── CONTRIBUTING.md ├── README.md └── LICENSE /Procfile: -------------------------------------------------------------------------------- 1 | web: npm run start:production -------------------------------------------------------------------------------- /public/img/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/.DS_Store -------------------------------------------------------------------------------- /public/img/users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/users.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "4" 5 | script: ./scripts/run-tests.sh 6 | -------------------------------------------------------------------------------- /public/img/badge/funding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/funding.png -------------------------------------------------------------------------------- /public/img/badge/testing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/testing.png -------------------------------------------------------------------------------- /public/img/badge/resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/resources.png -------------------------------------------------------------------------------- /public/img/badge/software.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/software.png -------------------------------------------------------------------------------- /bin/badgekit_to_badgr/badgekit.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/bin/badgekit_to_badgr/badgekit.db -------------------------------------------------------------------------------- /public/img/badge/methodology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/methodology.png -------------------------------------------------------------------------------- /public/img/badge/supervision.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/supervision.png -------------------------------------------------------------------------------- /public/img/badge/data_curation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/data_curation.png -------------------------------------------------------------------------------- /public/img/badge/investigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/investigation.png -------------------------------------------------------------------------------- /public/img/badge/writing_review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/writing_review.png -------------------------------------------------------------------------------- /docs/Badges--Proposed-Workflow_1_.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/docs/Badges--Proposed-Workflow_1_.png -------------------------------------------------------------------------------- /public/img/Badges-ProposedWorkflow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/Badges-ProposedWorkflow.jpg -------------------------------------------------------------------------------- /public/img/badge/conceptualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/conceptualization.png -------------------------------------------------------------------------------- /public/img/badge/formal_analysis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/formal_analysis.png -------------------------------------------------------------------------------- /public/img/badge/writing_initial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/writing_initial.png -------------------------------------------------------------------------------- /public/img/badge/data_visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/data_visualization.png -------------------------------------------------------------------------------- /public/img/badge/project_administration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/public/img/badge/project_administration.png -------------------------------------------------------------------------------- /bin/badgekit_to_badgr/badges_only.local.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozillascience/PaperBadger/HEAD/bin/badgekit_to_badgr/badges_only.local.sqlite3 -------------------------------------------------------------------------------- /scripts/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ev 3 | if [ "${TRAVIS_PULL_REQUEST}" = "false" ] 4 | then 5 | npm test 6 | else 7 | npm run test:unit 8 | fi 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | env.docker 3 | .db.env 4 | bin/badgekit_to_badgr/local.sqlite3 5 | node_modules/ 6 | npm-debug.log 7 | npm-debug.log.* 8 | public/js/ 9 | 10 | .DS_Store 11 | .idea 12 | -------------------------------------------------------------------------------- /src/environments.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (function () { 4 | var Habitat = require('habitat'); 5 | Habitat.load('.env'); 6 | Habitat.load('default.env'); 7 | return new Habitat(); 8 | })(); 9 | -------------------------------------------------------------------------------- /bin/badgekit_to_badgr/default.db.env: -------------------------------------------------------------------------------- 1 | # Copy this file to .db.env or just set these environment variables 2 | 3 | # Badgr 4 | export BADGR_ENDPOINT=http://localhost:8000/ 5 | export BADGR_USER=############# 6 | export BADGR_PASSWORD=############# 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig : http://EditorConfig.org - Copied from node_modules/mofo-style 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PaperBadger", 3 | "description": "Exploring the use of digital badges for crediting contributors to scholarly papers for their work", 4 | "repository": "https://github.com/mozillascience/PaperBadger", 5 | "logo": "", 6 | "keywords": ["badges", "science", "node"] 7 | } 8 | -------------------------------------------------------------------------------- /src/badges/client/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ApiClient = require('badgekit-api-client'); 4 | 5 | module.exports = function (config) { 6 | var auth = { 7 | key: config.get('BADGES_KEY'), 8 | secret: config.get('BADGES_SECRET') 9 | }; 10 | 11 | return new ApiClient(config.get('BADGES_ENDPOINT'), auth); 12 | }; 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var app = require('./app'); 4 | var env = require('./environments'); 5 | var client = require('./badges/client')(env); 6 | var service = require('./badges/service'); 7 | 8 | function init() { 9 | service.init(client, env); 10 | app.listen(env.get('PORT'), function () { 11 | console.log('PaperBadger started on port: ', env.get('PORT')); 12 | }); 13 | } 14 | 15 | init(); 16 | -------------------------------------------------------------------------------- /templates/pages/404.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | Page = require('../components/page.jsx'); 3 | 4 | var fourOhFour = React.createClass({ 5 | componentDidMount: function() { 6 | document.title = "PaperBadger: Page Not Found"; 7 | }, 8 | render: function() { 9 | return ( 10 | 11 |
Page not found
12 |
13 | ); 14 | } 15 | }); 16 | 17 | module.exports = fourOhFour; 18 | -------------------------------------------------------------------------------- /src/routes/claims/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'); 4 | var Claim = mongoose.model('Claim'); 5 | 6 | module.exports = { 7 | getClaim: function getClaim(request, response) { 8 | if (!request.params.slug) { 9 | response.status(400).end(); 10 | return; 11 | } 12 | var query = Claim.where({ slug: request.params.slug }); 13 | query.findOne(function(err, claim) { 14 | response.json(claim); 15 | }); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /templates/components/badge.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | 4 | var Badge = React.createClass({ 5 | render: function() { 6 | var badge = this.props.badge, 7 | slug = "/v/#/badges/" + badge.slug; 8 | return ( 9 |
10 | 11 | 12 | {badge.name} 13 | 14 |
15 | ); 16 | } 17 | }); 18 | 19 | module.exports = Badge; -------------------------------------------------------------------------------- /templates/pages/denied.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | Page = require('../components/page.jsx'); 3 | 4 | var deniedAccess = React.createClass({ 5 | componentDidMount: function() { 6 | document.title = "PaperBadger: User denied access"; 7 | }, 8 | render: function() { 9 | return ( 10 | 11 |
You must sign in to ORCID to be able to issue a badge. Try again.
12 |
13 | ); 14 | } 15 | }); 16 | 17 | module.exports = deniedAccess; 18 | -------------------------------------------------------------------------------- /public/code.pug: -------------------------------------------------------------------------------- 1 | doctype html5 2 | html. 3 | 4 | 5 | Contributorship Badges 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |     #{data}
14 |   
15 | 16 | -------------------------------------------------------------------------------- /public/widgets/demos/orcid-widget-with-name-shown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Paper view snippet example | Paper Badger 6 | 7 | 8 | 9 |
10 | 11 | 24 | 25 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | 5 | paperbadger: 6 | image: node:4 7 | command: bash -c 'cd /src && (if [ ! -d "node_modules" ]; then npm install && chmod -R uo+w node_modules; fi) && npm start' 8 | ports: 9 | - 5000:5000 10 | volumes: 11 | - .:/src 12 | environment: 13 | - MONGOLAB_URI=mongodb://mongo:27017/test 14 | - REDISCLOUD_URL=redis://redis:6379/0 15 | 16 | mongo: 17 | image: mongo:2 18 | ports: 19 | - 27017:27017 20 | # volumes: # to persist database between runs 21 | # - /data/db 22 | 23 | redis: 24 | image: redis 25 | ports: 26 | - 6379:6379 27 | -------------------------------------------------------------------------------- /public/widgets/demos/orcid-widget-with-doi-shown.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Paper view snippet example | Paper Badger 6 | 7 | 8 | 9 |
10 | 11 | 25 | 26 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /default.env: -------------------------------------------------------------------------------- 1 | # Copy this file to .env or just set these environment variables 2 | 3 | # default port is 5000 4 | export PORT=5000 5 | export SESSION_SECRET=USE_SOMETHING_GOOD_LIKE_puUJjfE6QtUnYryb 6 | 7 | # Badges 8 | export BADGES_ENDPOINT=http://badgekit-api-test-sciencelab.herokuapp.com/ 9 | export BADGES_KEY=master 10 | export BADGES_SECRET=############# 11 | export BADGES_SYSTEM=badgekit 12 | 13 | # ORCID Auth 14 | export ORCID_AUTH_CLIENT_ID=############# 15 | export ORCID_AUTH_CLIENT_SECRET=############# 16 | export ORCID_AUTH_SITE=https://orcid.org 17 | export ORCID_AUTH_TOKEN_PATH=https://orcid.org/oauth/token 18 | export ORCID_REDIRECT_URI=http://localhost:5000/orcid_auth_callback 19 | 20 | # MongoDB 21 | export MONGOLAB_URI=mongodb://127.0.0.1:27017/test 22 | 23 | # Email 24 | export AWS_ACCESS_KEY= 25 | export AWS_SECRET_KEY= 26 | -------------------------------------------------------------------------------- /src/models.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mongoose = require('mongoose'), 4 | Schema = mongoose.Schema; 5 | 6 | /* Claim Schema -- stores the unique claim codes for each email / doi combo to claim their badges*/ 7 | 8 | var claimSchema = new Schema({ 9 | slug: String, 10 | doi: String, 11 | status: String 12 | }); 13 | 14 | mongoose.model('Claim', claimSchema); 15 | 16 | /* User Schema -- to store Publisher ORCIDS. */ 17 | /* Only publishers can create papers that will issue claim codes. */ 18 | 19 | var userSchema = new Schema({ 20 | name: String, 21 | orcid: String, 22 | role: String 23 | }); 24 | 25 | userSchema.pre('save', function (next) { 26 | if (this.isNew) { 27 | this.createdAt = Date.now(); 28 | } 29 | this.updatedAt = Date.now(); 30 | next(); 31 | }); 32 | 33 | mongoose.model('User', userSchema); 34 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var webpack = require('webpack'); 5 | 6 | var IMPORT_ES5_SHIM = 'imports?shim=es5-shim/es5-shim&' + 7 | 'sham=es5-shim/es5-sham'; 8 | 9 | module.exports = { 10 | entry: './templates/client.jsx', 11 | 12 | output: { 13 | filename: '[name].js', 14 | chunkFilename: '[id].chunk.js', 15 | path: path.join('public', 'js'), 16 | publicPath: '/js/' 17 | }, 18 | externals: { 19 | App: true 20 | }, 21 | module: { 22 | loaders: [{ 23 | test: /\.jsx$/, 24 | loaders: ['babel', 'jsx-loader'] 25 | }, { 26 | test: require.resolve('react'), 27 | loader: IMPORT_ES5_SHIM 28 | }] 29 | }, 30 | plugins: [ 31 | new webpack.ProvidePlugin({ 32 | 'fetch': 'imports?this=>global!exports?global.fetch!whatwg-fetch' 33 | }) 34 | ] 35 | }; 36 | -------------------------------------------------------------------------------- /public/widgets/demos/doi-widget.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Paper view snippet example | Paper Badger 6 | 7 | 8 | 9 | 10 |
11 |
This text is only shown when the API returns badges.
12 | 13 | 27 | 28 | -------------------------------------------------------------------------------- /templates/components/footer.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | 4 | var Footer = React.createClass({ 5 | render: function() { 6 | return ( 7 |
8 |

This work is a collaboration with publishers BioMed Central (BMC), Ubiquity Press (UP) and the Public Library of Science (PLoS); the biomedical research foundation, The Wellcome Trust; the software and technology firm Digital Science; the registry of unique researcher identifiers, ORCID; and the Mozilla Science Lab.

9 |
10 | ); 11 | } 12 | }); 13 | 14 | module.exports = Footer; -------------------------------------------------------------------------------- /templates/components/header.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | Link = require('react-router').Link; 3 | 4 | 5 | 6 | var Header = React.createClass({ 7 | render: function() { 8 | return ( 9 |
10 |
11 | Paper Badger 12 | 13 | 18 |
19 |
20 | ); 21 | } 22 | }); 23 | 24 | module.exports = Header; -------------------------------------------------------------------------------- /templates/components/badgeInstance.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | 4 | var BadgeInstance = React.createClass({ 5 | render: function() { 6 | var badgeInstance = this.props.badge, 7 | doiRe = /(10\.\d{3}\d+)\/(.*)\b/, 8 | m = doiRe.exec(badgeInstance.evidenceUrl), 9 | paperUrl = '/v/#/papers/' + m[1] + '/' + encodeURIComponent(m[2]) + '/badges'; 10 | 11 | return ( 12 |
13 |
14 | 15 |
16 |

{ badgeInstance.badge.name }

17 | { badgeInstance.orcid }
18 | { badgeInstance.evidenceUrl } 19 |
20 | ); 21 | } 22 | }); 23 | 24 | module.exports = BadgeInstance; -------------------------------------------------------------------------------- /docs/orcid-setup.md: -------------------------------------------------------------------------------- 1 | # ORCID auth instructions 2 | 3 | #### Steps to obtain ORCID AUTH SECRET VARIABLES 4 | Go to `https://orcid.org/developer-tools` and create new Application. While creating new application in Redirect URI field you have to fill `http://localhost:5000/orcid_auth_callback`. It will generate Client ID and Client Secret, using those values set your environment variable in default.env 5 | 6 | #### Steps to obtain ORCID 7 | Run `npm start` and then open up `http://localhost:5000/request-orcid-user-auth`, this will redirect orcid website where you have to sign in, after that you would be redirected back. 8 | 9 | Then open up `http://localhost:5000/user`, this will return a json object with your orcid. 10 | 11 | #### Adding user as a publisher 12 | 13 | Run `npm start`, and open up mongo shell in your terminal. It would connect you with the test database. Then follow these steps. 14 | 15 | ```js 16 | db.users.insert({"name":"Your name", "orcid":"Your ORCID", "role":"publisher"}) 17 | ``` 18 | -------------------------------------------------------------------------------- /templates/components/badgelist.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | Badge = require('./badge.jsx'); 3 | 4 | var BadgeList = React.createClass({ 5 | loadUsersFromServer: function() { 6 | fetch('/badges') 7 | .then((response) => { 8 | if (response.status >= 400) { 9 | throw new Error("Bad response from server"); 10 | } 11 | return response.json(); 12 | }) 13 | .then((badges) => { 14 | this.setState({data: badges}); 15 | }); 16 | }, 17 | getInitialState: function() { 18 | return {data: []}; 19 | }, 20 | componentDidMount: function() { 21 | this.loadUsersFromServer(); 22 | }, 23 | render: function() { 24 | var badgeNodes = this.state.data.map(function(badge, index) { 25 | return ( 26 | 27 | 28 | ); 29 | }); 30 | return ( 31 |
32 | {badgeNodes} 33 |
34 | ); 35 | } 36 | }) 37 | 38 | module.exports = BadgeList; -------------------------------------------------------------------------------- /public/index.pug: -------------------------------------------------------------------------------- 1 | doctype html5 2 | html. 3 | 4 | 5 | Contributorship Badges 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /templates/client.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | Router = require('react-router'), 3 | NotFoundRoute = Router.NotFoundRoute, 4 | Route = Router.Route; 5 | 6 | var routes = ( 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | 19 | fetch('/user', { 20 | credentials: 'same-origin' 21 | }) 22 | .then((response) => { 23 | if (response.status >= 400) { 24 | throw new Error("Bad response from server"); 25 | } 26 | return response.json(); 27 | }) 28 | .then((user) => { 29 | Router.run(routes, Router.HistoryLocation, function (Handler) { 30 | React.render(, document.body); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /templates/components/badgeInstanceList.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | BadgeInstance = require('./badgeInstance.jsx'); 3 | 4 | var BadgeInstanceList = React.createClass({ 5 | loadBadgesFromServer: function() { 6 | fetch(this.state.url) 7 | .then((response) => { 8 | if (response.status >= 400) { 9 | throw new Error("Bad response from server"); 10 | } 11 | return response.json(); 12 | }) 13 | .then((badges) => { 14 | this.setState({data: badges}); 15 | }); 16 | }, 17 | componentWillReceiveProps: function(nextProps) { 18 | this.setState({ url: nextProps.url, data:[] }, this.loadBadgesFromServer); 19 | }, 20 | getInitialState: function() { 21 | return {data: [], url:this.props.url}; 22 | }, 23 | componentDidMount: function() { 24 | this.loadBadgesFromServer(); 25 | }, 26 | render: function() { 27 | var badgeNodes = this.state.data.map(function(badge, index) { 28 | return ( 29 | 30 | 31 | ); 32 | }); 33 | return ( 34 |
35 | {badgeNodes} 36 |
37 | ); 38 | } 39 | }) 40 | 41 | module.exports = BadgeInstanceList; -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // should this be configuration? 4 | var orcidRe = /(\d{4}-\d{4}-\d{4}-\d{3}[\dX])@orcid\.org/; 5 | var path = require('path'); 6 | 7 | function emailFromORCID(orcid) { 8 | return orcid + '@orcid.org'; 9 | } 10 | 11 | function ORCIDFromEmail(email) { 12 | var m = orcidRe.exec(email); 13 | if (m !== null) { 14 | return m[1]; 15 | } 16 | } 17 | 18 | function modEntry(entry) { 19 | var clone = {}; 20 | Object.keys(entry).forEach(function (key) { 21 | if (key === 'email') { 22 | clone.orcid = ORCIDFromEmail(entry.email); 23 | } else { 24 | clone[key] = entry[key]; 25 | } 26 | }); 27 | return clone; 28 | } 29 | 30 | function urlFromDOI(doi1, doi2) { 31 | return 'http://dx.doi.org/' + path.join(doi1, decodeURIComponent(doi2)); 32 | } 33 | 34 | function DOIFromURL(url) { 35 | // pathname should be '10.1371/journal.pbio.1002126' from 'http://dx.doi.org/10.1371/journal.pbio.1002126' 36 | var doiRe = /(10\.\d{3}\d+\/.*)\b/; 37 | var m = doiRe.exec(url); 38 | return m[1]; 39 | } 40 | 41 | module.exports = { 42 | emailFromORCID: emailFromORCID, 43 | ORCIDFromEmail: ORCIDFromEmail, 44 | modEntry: modEntry, 45 | urlFromDOI: urlFromDOI, 46 | DOIFromURL: DOIFromURL 47 | }; 48 | -------------------------------------------------------------------------------- /src/routes/badges/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var returnBadges, badgerService; 4 | 5 | function getAll(request, response) { 6 | returnBadges(badgerService.getAllBadges(), request, response); 7 | } 8 | 9 | function getAllCount(request, response) { 10 | badgerService.getAllBadges()(function (error, badges) { 11 | if (error !== null || !badges) { 12 | response.json(0); 13 | } else { 14 | response.json(badges.length); 15 | } 16 | }); 17 | } 18 | 19 | function getAllByType(request, response) { 20 | returnBadges(badgerService.getBadges(null, request.params.badge), request, response); 21 | } 22 | 23 | function getAllByTypeCount(request, response) { 24 | if (!request.params.badge) { 25 | response.status(400).end(); 26 | return; 27 | } 28 | 29 | badgerService.getBadges(null, request.params.badge)(function (error, badges) { 30 | if (error !== null || !badges) { 31 | response.json(0); 32 | } else { 33 | response.json(badges.length); 34 | } 35 | }); 36 | } 37 | 38 | module.exports = function (rb, bs) { 39 | returnBadges = rb; 40 | badgerService = bs; 41 | return { 42 | getAll: getAll, 43 | getAllCount: getAllCount, 44 | getAllByType: getAllByType, 45 | getAllByTypeCount: getAllByTypeCount 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /templates/pages/about.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | Page = require('../components/page.jsx'); 3 | 4 | var About = React.createClass({ 5 | componentDidMount: function() { 6 | document.title = "About Contributorship Badges"; 7 | }, 8 | render: function() { 9 | return ( 10 | 11 |

About Contributorship Badges

12 |

Exploring the use of digital badges for crediting contributors to scholarly papers for their work

13 |

As the research environment becomes more digital, we want to test how we can use this medium to help bring transparency and credit for individuals in the publication process.

14 |

This work is a collaboration with publishers BioMed Central (BMC), Ubiquity Press (UP) and the Public Library of Science (PLoS); the biomedical research foundation, The Wellcome Trust; the software and technology firm Digital Science; the registry of unique researcher identifiers, ORCID; and the Mozilla Science Lab.

15 |
16 | ); 17 | } 18 | }); 19 | 20 | module.exports = About; 21 | -------------------------------------------------------------------------------- /templates/components/page.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | Link = require('react-router').Link, 3 | Header = require('./header.jsx'), 4 | Footer = require('./footer.jsx'); 5 | 6 | 7 | 8 | var Page = React.createClass({ 9 | render: function() { 10 | var splash; 11 | 12 | if (this.props.splash){ 13 | splash = 14 |
15 |
16 |
17 | Contributor Badges 18 |
19 |
20 |

Exploring the use of digital badges for crediting contributors to scholarly papers for their work

21 |
22 |

23 | Learn More 24 |

25 |
26 |
27 | } 28 | 29 | return ( 30 |
31 |
32 | { splash } 33 |
34 |
35 | { this.props.children } 36 |
37 |
38 |
39 |
40 | ); 41 | } 42 | }); 43 | 44 | module.exports = Page; -------------------------------------------------------------------------------- /docs/high-level-architecture.md: -------------------------------------------------------------------------------- 1 | # High level Architecture 2 | 3 | 4 | Paper Badger is both an API and a UI, and it also integrates and uses data from different sources. 5 | The following image contains the main building blocks of the system: 6 | 7 | ![Badges--Proposed-Workflow_1_.png](./Badges--Proposed-Workflow_1_.png?raw=true) 8 | 9 | It is recommended to keep an eye on the [Roadmap #17](https://github.com/mozillascience/PaperBadger/issues/17) to see changes that may affect this document in the near future. 10 | 11 | ## Main building blocks 12 | 13 | The main building blocks of the system are as follows. 14 | 15 | Integration with : 16 | - ORCID 17 | - A badges Server (currently badgekit-api; being migrated to badgr-server, see [#159](https://github.com/mozillascience/PaperBadger/issues/159)) 18 | 19 | ## Flows 20 | Flows for the system are as stated in issue [#1](https://github.com/mozillascience/PaperBadger/issues/1) 21 | 22 | ## Journal submission 23 | This is a feature that is currently done through a form, but needs to be automated. 24 | Progress can be followed in [#160](https://github.com/mozillascience/PaperBadger/issues/160) 25 | 26 | # The Stack 27 | 28 | Paper Badger is written in nodejs, using expressjs, MongoDB, Redis, and reactjs for the front end. 29 | 30 | The data for Badges comes from API calls to badgekit-api, through badgekit-api-client. 31 | 32 | ## ORCID integration 33 | [Setup details](orcid-setup.md) to integrate with ORCID. 34 | 35 | ## Badges integration 36 | A badges server has to be available so that Paper Badger can push and pull data from. The file [default.env](../default.env) 37 | contains the endpoint for the currently available _badgekit-api_ server. 38 | 39 | 40 | -------------------------------------------------------------------------------- /docs/paper-badger-widget.md: -------------------------------------------------------------------------------- 1 | # paper-badger-widget.js Documentation 2 | 1. To use the widget on your own site, include a `
` with your custom class in your view file, for example: 3 | `
` 4 | 5 | 2. Above the closing `` tag, add 6 | 7 | ```html 8 | 14 | ``` 15 | 16 | 3. In your scripts, include your custom values: 17 | * the class name where your widget will appear for the `container-class` key and 18 | * the doi for the paper you are interested in as the `article-doi` key 19 | 20 | ```html 21 | 22 | 23 | 24 | 25 | Paper view snippet example | Paper Badger 26 | 27 | 28 | 29 |
30 | 31 | 37 | 41 | 42 | 43 | ``` -------------------------------------------------------------------------------- /test/unit/helpers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var helpers = require('../../src/helpers.js'); 4 | var assert = require('assert'); 5 | 6 | describe('helpers', function () { 7 | var orcid = '0000-0000-0000-000X'; 8 | var entry; 9 | 10 | beforeEach(function () { 11 | entry = { 12 | email: orcid + '@orcid.org' 13 | }; 14 | }); 15 | 16 | it('emailFromORCID creates an email address', function () { 17 | assert.equal(helpers.emailFromORCID('0000-0003-4959-3049'), '0000-0003-4959-3049@orcid.org'); 18 | }); 19 | 20 | it('ORCIDFromEmail strips email leaving ORCID', function () { 21 | assert.equal(helpers.ORCIDFromEmail('0000-0003-4959-3049@orcid.org'), '0000-0003-4959-3049'); 22 | }); 23 | 24 | it('ORCIDFromEmail strips email leaving ORCID when ORCID ends in X', function () { 25 | assert.equal(helpers.ORCIDFromEmail('0000-0002-3881-294X@orcid.org'), '0000-0002-3881-294X'); 26 | }); 27 | 28 | it('modEntry removes email and adds ORCID ID', function () { 29 | assert.deepEqual(helpers.modEntry(entry), { 30 | orcid: orcid 31 | }); 32 | }); 33 | 34 | it('modEntry deletes only email from entry', function () { 35 | entry.science = 'lab'; 36 | assert.deepEqual(helpers.modEntry(entry), { 37 | orcid: orcid, 38 | science: 'lab' 39 | }); 40 | }); 41 | 42 | it('urlFromDOI returns a doi.org URI given a DOI', function () { 43 | assert.equal(helpers.urlFromDOI('10.1186', '2047-217X-2-10'), 'http://dx.doi.org/10.1186/2047-217X-2-10'); 44 | }); 45 | 46 | it('DOIFromURL returns a DOI given a URI', function () { 47 | assert.equal(helpers.DOIFromURL('http://dx.doi.org/10.1186/2047-217X-2-10'), '10.1186/2047-217X-2-10'); 48 | assert.equal(helpers.DOIFromURL('http://dx.doi.org/10.1186/2047-217X-2-10/'), '10.1186/2047-217X-2-10'); 49 | assert.equal(helpers.DOIFromURL('https://dx.doi.org/10.1186/2047-217X-2-10'), '10.1186/2047-217X-2-10'); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /templates/pages/view.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | BadgeInstanceList = require('../components/badgeInstanceList.jsx'), 3 | Page = require('../components/page.jsx'), 4 | orcidRe = /\/users\/(\d{4}-\d{4}-\d{4}-\d{3}[\dX])/, 5 | doiRe = /\/papers\/(10\.\d{3}\d+)\/([^\/]*)\//, 6 | badgeRe = /\/badges\/([a-z_]*)\b/; 7 | 8 | var viewBadges = React.createClass({ 9 | componentDidMount: function() { 10 | document.title = "Contributorship Badges"; 11 | window.onhashchange = this.updateUrl; 12 | this.updateUrl(); 13 | }, 14 | updateUrl: function(){ 15 | var url = (window.location.href.split('#')[1] || ''), 16 | orcid = orcidRe.exec(url), 17 | doi = doiRe.exec(url), 18 | badge = badgeRe.exec(url); 19 | 20 | orcid = orcid && orcid[1]; 21 | doi = doi && [doi[1], decodeURIComponent(doi[2])]; 22 | badge = badge && badge[1]; 23 | 24 | this.setState({ url: url, 25 | orcid: orcid, 26 | doi: doi, 27 | badge: badge }); 28 | }, 29 | getInitialState: function() { 30 | return { }; 31 | }, 32 | render: function() { 33 | var orcid, badge, doi; 34 | if(this.state.orcid) { 35 | orcid = (

Badges for user {this.state.orcid}

); 36 | } 37 | if(this.state.doi){ 38 | doi = (

Badges for paper {this.state.doi.join('/') }

); 39 | } 40 | if(this.state.badge){ 41 | badge = (

All {this.state.badge} badges

); 42 | } 43 | return ( 44 | 45 |

View JSON: {this.state.url}

46 | { orcid } 47 | { doi } 48 | { badge } 49 | 50 | 51 |
52 | ); 53 | } 54 | }); 55 | 56 | module.exports = viewBadges; 57 | -------------------------------------------------------------------------------- /bin/badgekit_to_badgr/README.md: -------------------------------------------------------------------------------- 1 | # Data Migration from badgekit-server to badgr-server 2 | 3 | This section has its on environment file, and its own module dependencies (stated below) to avoid polluting the main app. 4 | 5 | ## How to generate the source database 6 | Even though the _badgekit.db_ file is available directly in this folder, it was generated with: https://github.com/dumblob/mysql2sqlite and can be generated again as: 7 | 8 | ``` 9 | >./mysql2sqlite May_31_2016_362499FADFED219BBD3A417FC213E484.sql | sqlite3 badgekit.db 10 | ``` 11 | 12 | _May_31_2016_362499FADFED219BBD3A417FC213E484.sql_ is a dump of the badgekit 13 | server used in PaperBadger. 14 | 15 | ### Investigating some of the data in the original DB 16 | 17 | A csv file with data from badge instances can be generated from the badgekit.db 18 | with: 19 | 20 | ``` 21 | sqlite> .header on 22 | sqlite> .mode csv 23 | sqlite> .once badgeinstances.csv 24 | sqlite> select b.slug, a.* from badgeinstances a, badges b where a.badgeId=b.id; 25 | sqlite> .exit 26 | ``` 27 | 28 | This is just to figure which parts of the info go into each table. The automated 29 | migration created from this info can be run with `node migrateBKtoBS.js`. 30 | 31 | ## How to generate the destination database 32 | _badges_only.local.sqlite3_ was created manually by inserting only badges 33 | information directly through the badgr-server interface (note: this can be 34 | automated). 35 | 36 | ## Running the script 37 | Note that the script at _migrateBKtoBS.js_ only migrates data for badge instances. 38 | 39 | A few packages are needed to run this script: 40 | 41 | `npm install sqlite3 fs-extra` 42 | 43 | Then simply: 44 | 45 | `node migrateBKtoBS.js` 46 | 47 | At the moment of writing, 294 instances will be migrated. 48 | 49 | ### Location of the badgr-server 50 | The URL of the badgr-server is necessary for a number of fields in the instances 51 | (such as where images are going to be available from). There is a _default.db.env_ 52 | file that can be customised locally as _.db.env_. Environment variables can also be used. 53 | -------------------------------------------------------------------------------- /docs/widget.md: -------------------------------------------------------------------------------- 1 | # widget.js Documentation 2 | This shows what the Paper Badger Widget can do and how to use it. 3 | 4 | ## Demos 5 | The demos can be found in [public/widgets/demos](../public/widgets/demos). 6 | 7 | ## Usage 8 | 1. To use the widget on your own site, include a `
` with your custom class in your view file, for example: 9 | `
` 10 | 11 | 2. Above the closing `` tag, add 12 | ```html 13 | 26 | ``` 27 | 28 | 3. In the load method, include your custom values: 29 | * the element class where your widget will appear for the `containerClass` key and 30 | * either the doi for the paper you are interested in as the `DOI` key or the ORCID for the author you are interesed in as the `ORCID` key 31 | 32 | ## Required configuration options 33 | * the `containerClass` key: contains the class of the element which will contain the Paper Badger widget 34 | * either the `DOI` or the `ORCID` key: defines which information the widget will display 35 | 36 | ## Optional configuration options 37 | * the `ORCIDWidgetType` key (only in combination with the `ORCID` key): set the key to the value `default` to show names below the badges, set it to `DOI` to show DOIs 38 | * the `loaderText` key: changes the default text shown while the Paper Badger widget is loading 39 | * the `removeClass` key: the widget will remove this class from all elements once it has loaded, if this is a non-empty string 40 | * the `clickCallback` key: a method that gets called when a link under a badge gets clicked. The method is called with an object containing either the DOI or ORCID of the link and the badge taxonomy: 41 | 42 | ``` 43 | { 44 | doi: '10.1186/2047-217X-3-18', // either DOI 45 | orcid: '0000-0001-5207-5061', // or ORCID 46 | taxonomy: 'data_curation' 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /src/routes/users/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var returnBadges, badgerService; 4 | var mongoose = require('mongoose'); 5 | var User = mongoose.model('User'); 6 | 7 | function getBadgeByUser(request, response) { 8 | if (!request.params.orcid) { 9 | response.status(400).end(); 10 | return; 11 | } 12 | return badgerService.getBadges(request.params.orcid); 13 | } 14 | 15 | function getBadges(request, response) { 16 | returnBadges(getBadgeByUser(request, response), request, response); 17 | } 18 | 19 | function getBadgeCount(request, response) { 20 | getBadgeByUser(request, response)(function (error, badges) { 21 | if (error !== null || !badges) { 22 | response.json(0); 23 | } else { 24 | response.json(badges.length); 25 | } 26 | }); 27 | } 28 | 29 | function getBadgesByType(request, response){ 30 | if (!request.params.orcid || !request.params.badge) { 31 | response.status(400).end(); 32 | return; 33 | } 34 | return badgerService.getBadges(request.params.orcid, request.params.badge); 35 | } 36 | 37 | function getBadgesByBadge(request, response) { 38 | returnBadges(getBadgesByType(request, response), request, response); 39 | } 40 | 41 | function getBadgesByBadgeCount(request, response) { 42 | getBadgesByType(request, response)(function (error, badges) { 43 | if (error !== null || !badges) { 44 | response.json(0); 45 | } else { 46 | response.json(badges.length); 47 | } 48 | }); 49 | } 50 | 51 | function getUser(request, response) { 52 | var orcid; 53 | response.header('Cache-Control', 'private, no-cache, no-store, must-revalidate'); 54 | if (request.session.orcid_token && request.session.orcid_token.token) { 55 | orcid = request.session.orcid_token.token.orcid; 56 | } 57 | 58 | var query = User.where({ orcid:orcid }); 59 | query.findOne(function(err, user) { 60 | if (user) { 61 | response.json(user); 62 | } else { 63 | response.json( orcid ? { 64 | name: request.session.orcid_token.name, 65 | orcid: orcid } : null ); 66 | } 67 | }); 68 | } 69 | 70 | module.exports = function (rb, bs) { 71 | returnBadges = rb; 72 | badgerService = bs; 73 | return { 74 | getBadges: getBadges, 75 | getBadgeCount: getBadgeCount, 76 | getBadgesByBadge: getBadgesByBadge, 77 | getBadgesByBadgeCount: getBadgesByBadgeCount, 78 | getUser: getUser 79 | }; 80 | }; 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PaperBadger", 3 | "version": "0.1.1", 4 | "description": "Exploring the use of digital badges for crediting contributors to scholarly papers for their work", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "npm-run-all --parallel server watch:js", 8 | "build:js": "webpack --config webpack.config.js --progress --profile --colors", 9 | "watch:js": "npm run build:js -- --watch", 10 | "clean": "rm -r public/js/*", 11 | "server": "node src/index.js", 12 | "eslint": "eslint --config .eslintrc.yaml src webpack.config.js public/widgets/widget.js", 13 | "eslint:test": "eslint --config .eslintrc.yaml test", 14 | "lint": "npm run eslint && npm run eslint:test", 15 | "build:production": "npm run build:js -- --optimize-minimize --optimize-dedupe", 16 | "start:production": "npm-run-all build:production server", 17 | "test:integration": "mocha -t 4000 test/integration", 18 | "test:unit": "mocha test/unit", 19 | "test": "mocha -t 4000 test/*" 20 | }, 21 | "dependencies": { 22 | "babel-loader": "^5.0.0", 23 | "badgekit-api-client": "https://github.com/mozilla/badgekit-api-client/tarball/v0.2.4", 24 | "body-parser": "^1.13.2", 25 | "connect-redis": "^3.0.1", 26 | "es5-shim": "^4.1.1", 27 | "exports-loader": "^0.6.2", 28 | "express": "~4.13.x", 29 | "express-session": "~1.11.2", 30 | "habitat": "^3.1.2", 31 | "imports-loader": "^0.6.4", 32 | "pug": "^2.0.0-alpha8", 33 | "jsx-loader": "^0.13.2", 34 | "mofo-style": "^2.3.0", 35 | "mongoose": "^4.1.4", 36 | "nodemailer": "^1.4.0", 37 | "nodemailer-ses-transport": "^1.3.0", 38 | "npm-run-all": "^1.2.4", 39 | "react": "^0.13.2", 40 | "react-checkbox-group": "^0.1.9", 41 | "react-router": "^0.13.3", 42 | "shortid": "^2.2.2", 43 | "simple-oauth2": "0.5.1", 44 | "webpack": "^1.8.11", 45 | "whatwg-fetch": "^0.9.0", 46 | "validator": "^5.2.0" 47 | }, 48 | "devDependencies": { 49 | "eslint": "^2.3.0", 50 | "mocha": "^2.2.5", 51 | "nock": "^2.3.0", 52 | "supertest": "^1.0.1" 53 | }, 54 | "engines": { 55 | "node": "4.4.5" 56 | }, 57 | "repository": { 58 | "type": "git", 59 | "url": "https://github.com/mozillascience/PaperBadger" 60 | }, 61 | "keywords": [ 62 | "badges", 63 | "science", 64 | "node" 65 | ], 66 | "license": "MPL-2.0" 67 | } 68 | -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | # This is a modified set of rules based on the baseline defined in mofo-style v 2.3.0. 2 | # This project was started with a much older version of mofo-style, using jshint, and this is an 3 | # effort to tranistion the project to use the latest version, which is intended for usage in any ES6-based project. 4 | 5 | # The main changes in the file are as follows: 6 | # Eliminated rules: 7 | # - valid-jsdoc 8 | # - newline-after-var 9 | # - prefer-arrow-callback 10 | # Modified rules: 11 | # - arrowFunctions: false // instead of true 12 | # - quotes: [2, "single"] // instead of "backtick" 13 | # - no-use-before-define: [2, "nofunc"] // instead of defaulting to none 14 | # 15 | # The specific environment(s) needed are configured in this file directly. 16 | 17 | --- 18 | rules: # http://eslint.org/docs/rules 19 | 20 | # Possible Errors 21 | 22 | 23 | # Best Practices 24 | 25 | curly: [2] 26 | dot-notation: [2] 27 | eqeqeq: [2] 28 | guard-for-in: [2] 29 | no-div-regex: [2] 30 | no-eval: [2] 31 | no-extend-native: [2] 32 | no-floating-decimal: [2] 33 | no-implied-eval: [2] 34 | no-labels: [2] 35 | no-lone-blocks: [2] 36 | no-loop-func: [2] 37 | no-multi-spaces: [2] 38 | no-native-reassign: [2] 39 | no-new-wrappers: [2] 40 | no-new: [2] 41 | no-redeclare: [2] 42 | no-return-assign: [2] 43 | no-self-compare: [2] 44 | radix: [2] 45 | wrap-iife: [2, "inside"] 46 | yoda: [2, "never"] 47 | 48 | # Strict Mode 49 | 50 | strict: [2, "global"] 51 | 52 | # Variables 53 | 54 | no-shadow: [2] 55 | no-use-before-define: [2, "nofunc"] 56 | 57 | # Stylistic Issues 58 | 59 | block-spacing: [2, "always"] 60 | brace-style: [2, "1tbs", allowSingleLine: true] 61 | camelcase: [2, properties: "always"] 62 | comma-style: [2, "last"] 63 | consistent-this: [2, "self"] 64 | eol-last: [2] 65 | indent: [2, 2, {"VariableDeclarator": { "var": 2, "let": 2, "const": 3}}] 66 | linebreak-style: [2, "unix"] 67 | no-lonely-if: [2] 68 | no-trailing-spaces: [2] 69 | no-unneeded-ternary: [2] 70 | quotes: [2, "single"] 71 | semi: [2, "always"] 72 | spaced-comment: [2, "always"] 73 | wrap-regex: [2] 74 | 75 | # ES6 76 | 77 | # Overrides to `eslint:recommended` rule set 78 | 79 | no-console: [0] 80 | 81 | 82 | extends: "eslint:recommended" 83 | 84 | ecmaFeatures: 85 | arrowFunctions: false 86 | blockBindings: true 87 | defaultParams: true 88 | destructuring: true 89 | forOf: true 90 | spread: true 91 | templateStrings: true 92 | 93 | # Environments 94 | env: 95 | browser: true 96 | node: true 97 | mocha: true 98 | -------------------------------------------------------------------------------- /src/badges/service/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var helpers = require(path.join(process.cwd(), 'src', 'helpers')); 5 | 6 | // We really want a singleton here 7 | var instance, client, system; 8 | 9 | function BadgeService() {} 10 | 11 | BadgeService.prototype.createBadge = function (orcid, badge, dois, name) { 12 | return function (callback) { 13 | var evidence = helpers.urlFromDOI(dois._1, dois._2); 14 | var context = { 15 | system: system, 16 | badge: badge, 17 | instance: { 18 | email: helpers.emailFromORCID(orcid), 19 | evidenceUrl: evidence, 20 | authorName: name 21 | } 22 | }; 23 | 24 | client.createBadgeInstance(context, function (err, badgeResult) { 25 | if (err) { 26 | console.error(err); 27 | return callback(err); 28 | } 29 | badgeResult = helpers.modEntry(badgeResult); 30 | callback(null, badgeResult); 31 | }); 32 | }; 33 | }; 34 | 35 | BadgeService.prototype.getBadges = function (orcid, badge, dois) { 36 | return function (callback) { 37 | var evidenceUrl = dois ? helpers.urlFromDOI(dois._1, dois._2) : null; 38 | 39 | var clientCallback = function (err, badges) { 40 | var filtered; 41 | if (err) { 42 | console.error(err); 43 | return callback(err); 44 | } 45 | 46 | // filter for the badge 47 | if (badges) { 48 | filtered = badges.filter(function (entry) { 49 | var goodBadge = (!badge || entry.badge.slug === badge); 50 | var goodDoi = (!dois || entry.evidenceUrl === evidenceUrl); 51 | return goodBadge && goodDoi; 52 | }); 53 | 54 | filtered = filtered.map(helpers.modEntry); 55 | } 56 | 57 | if (filtered && filtered.length === 0) { 58 | callback('client return empty result'); 59 | } else { 60 | callback(null, filtered); 61 | } 62 | }; 63 | 64 | var context = { 65 | system: system 66 | }; 67 | var options = {}; 68 | if (orcid) { 69 | options.email = helpers.emailFromORCID(orcid); 70 | } else { 71 | context.badge = badge || '*'; 72 | } 73 | if (evidenceUrl) { 74 | options.paginate = { 75 | evidenceUrl: evidenceUrl 76 | }; 77 | } 78 | client.getBadgeInstances(context, options, clientCallback); 79 | }; 80 | }; 81 | 82 | BadgeService.prototype.getAllBadges = function () { 83 | return function (callback) { 84 | client.getAllBadges({ 85 | system: system 86 | }, function (err, badges) { 87 | if (err) { 88 | console.error(err); 89 | callback(err); 90 | } else { 91 | callback(null, badges); 92 | } 93 | }); 94 | }; 95 | }; 96 | 97 | module.exports = { 98 | init: function (apiClient, config) { 99 | client = apiClient; 100 | system = config.get('BADGES_SYSTEM'); 101 | }, 102 | 103 | getInstance: function () { 104 | if (!instance) { 105 | instance = new BadgeService(); 106 | } 107 | return instance; 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Reporting issues 4 | 5 | - **Search for existing issues.** Please check to see if someone else has reported the same issue. 6 | - **Share as much information as possible.** Include operating system and version, browser and version. Also, include steps to reproduce the bug. 7 | 8 | ## Project Setup 9 | 10 | Refer to the [README](README.md). 11 | 12 | ## Code Style 13 | 14 | ### Editor 15 | Please use an editor with support for [ESLint](http://eslint.org/) and [EditorConfig](http://editorconfig.org/). Configuration 16 | files for both tools are provided in the root directory of the project. 17 | 18 | ### JavaScript 19 | 20 | See [Mozilla Foundation JavaScript Style Guide](https://www.npmjs.com/package/mofo-style) 21 | 22 | This project is currently _in transition_ to fully support the latest 23 | [mofo-style](https://www.npmjs.com/package/mofo-style) version. At the moment it uses a modified version of .eslintrc.yaml, provided 24 | in the root directory, instead of using the file inside ./node-modules/mofo-style/.eslintrc.yaml, in order to make the transition 25 | easier and smoother. 26 | 27 | **TL;DR** Run `npm run lint` before pushing a commit. It will validate your JS. 28 | 29 | #### Variable Naming 30 | 31 | - `lowerCamelCase` General variables 32 | - `UpperCamelCase` Constructor functions 33 | - Use semantic and descriptive variables names (e.g. `colors` *not* `clrs` or `c`). Avoid abbreviations except in cases of industry wide usage (e.g. `AJAX` and `JSON`). 34 | 35 | ### HTML 36 | 37 | - 2 space indentation 38 | - Class names use hypenated case (e.g. `my-class-name`) 39 | 40 | ### LESS / CSS 41 | 42 | - 2 space indentation 43 | - Always a space after a property's colon (e.g. `display: block;` *not* `display:block;`) 44 | - End all lines with a semi-colon 45 | - For multiple, comma-separated selectors, place each selector on it's own line 46 | 47 | ## Testing 48 | 49 | Any patch should be manually tested in as many of our supported browsers as possible. Obviously, access to all devices is rare, so just aim for the best coverage possible. At a minimum please test in all available desktop browsers. 50 | 51 | You can run all automated tests with `mocha test/*` or `npm test`. If _mocha_ is not installed globally, please use `./node_modules/mocha/bin/mocha test/*`. 52 | 53 | _Unit_ and _Integration_ tests can also be run separately with `npm run test:unit` and `npm run test:integration` respectively. 54 | 55 | ## Pull requests 56 | 57 | - Try not to pollute your pull request with unintended changes – keep them simple and small. If possible, squash your commits. 58 | - Try to share which browsers and devices your code has been tested in before submitting a pull request. 59 | - If your PR resolves an issue, include **closes #ISSUE_NUMBER** in your commit message (or a [synonym](https://help.github.com/articles/closing-issues-via-commit-messages)). 60 | - Review 61 | - If your PR is ready for review, another contributor will be assigned to review your PR within 1 business day 62 | - The reviewer will comment on the PR with a final r+ or r-, along with inline comments on the code (if any) 63 | - r-: address the comments left by the reviewer. Once you're ready to continue the review, ping the reviewer in a comment. 64 | - r+: You code will be merged to `master` 65 | -------------------------------------------------------------------------------- /templates/pages/paper.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react/addons'), 2 | Router = require('react-router'), 3 | Navigation = require('react-router').Navigation, 4 | path = require('path'), 5 | validator = require('validator'), 6 | Page = require('../components/page.jsx'); 7 | 8 | var Issue = React.createClass({ 9 | mixins: [React.addons.LinkedStateMixin, Navigation], 10 | componentWillMount: function() { 11 | document.title = "Submit a Paper | Contributorship Badges"; 12 | }, 13 | componentDidMount: function() { 14 | if(!this.props.user){ 15 | //redirect if user isn't logged in 16 | window.location.href="/request-orcid-user-auth"; 17 | } 18 | 19 | if(this.props.user.role != 'publisher'){ 20 | //redirect home is user isn't a publisher 21 | this.replaceWith('home'); 22 | } 23 | }, 24 | validateEmail: function(emailId) { 25 | return validator.isEmail(emailId); 26 | }, 27 | validateDOI: function(doi) { 28 | var doiRe = /(10\.\d{3}\d+)\/(.*)\b/; 29 | return doiRe.test(doi); 30 | }, 31 | handleSubmit: function(e) { 32 | e.preventDefault(); 33 | var doi = this.state.doi; 34 | var emails = this.state.data.split('\n'); 35 | 36 | if(!this.validateDOI(doi)) { 37 | this.setState({'doiError': true, 'submitted': false}); 38 | return; 39 | } 40 | 41 | for(var i=0;i { 62 | if (response.status >= 400) { 63 | throw new Error("Bad response from server"); 64 | } 65 | return response.json(); 66 | }) 67 | .then((data) => { 68 | this.setState({data: '', doi: '', 'submitted': true, 'emailError': false, 'doiError': false}); 69 | }); 70 | return; 71 | }, 72 | getInitialState: function() { 73 | return {data: '', doi: '', 'submitted': false, 'emailError': false, 'doiError': false}; 74 | }, 75 | render: function() { 76 | return ( 77 | 78 |

Submit a Paper

79 |

This is a simple prototype demonstrating using a form to submit papers to Paper Badger, our contributorship badges prototype. In future versions we will integrate with publisher submission pipelines.

80 | 81 | 82 | 83 |
84 |
85 |
86 | 87 | 88 |
89 | 90 |
91 | 92 | 93 |
94 | 95 |
96 | 97 | Add each email on a separate line 98 |
99 | 100 |
101 | 102 |
103 |
104 |
105 |
106 | 107 | ); 108 | } 109 | }); 110 | 111 | module.exports = Issue; -------------------------------------------------------------------------------- /bin/badgekit_to_badgr/migrateBKtoBS.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var sqlite3 = require('sqlite3'); 4 | var fs = require('fs-extra'); 5 | 6 | var env = (function () { 7 | var Habitat = require('habitat'); 8 | Habitat.load('.db.env'); 9 | Habitat.load('default.db.env'); 10 | return new Habitat(); 11 | })(); 12 | 13 | // Make a Sync copy of the DB with badges to get started with 14 | fs.copySync('badges_only.local.sqlite3', 'local.sqlite3'); 15 | 16 | // Destination DB for badge instances 17 | var badgesDB = new sqlite3.Database('local.sqlite3', function(err){ 18 | if (err) { 19 | throw err; 20 | } 21 | }); 22 | 23 | // Source DB for badge instances; exported from badgekit-server 24 | var bkDB = new sqlite3.Database('badgekit.db', function(err){ 25 | if (err) { 26 | throw err; 27 | } 28 | }); 29 | 30 | function lookUpBadgeData(badgesList, dataItem) { 31 | var badgeData = {}; 32 | badgesList.forEach(function(item){ 33 | // A few of the slugs are different in the two databases, in some cases the difference is just an underscore, but 34 | // in other cases (like the three below) the full name is different. The ones without difference are taken care of 35 | // in the first if statement. 36 | if (item.slug.replace(/-/g, '') === dataItem.slug.replace(/_/g, '')) { 37 | badgeData = { 'image': item.image, 'id': item.id }; 38 | } 39 | 40 | if (item.slug === 'funding-acquisition' && dataItem.slug === 'funding' ) { 41 | badgeData = { 'image': item.image, 'id': item.id }; 42 | } 43 | 44 | if (item.slug === 'writing-original-draft' && dataItem.slug === 'writing_initial' ) { 45 | badgeData = { 'image': item.image, 'id': item.id }; 46 | } 47 | 48 | if (item.slug === 'writing-review-editing' && dataItem.slug === 'writing_review' ) { 49 | badgeData = { 'image': item.image, 'id': item.id }; 50 | } 51 | }); 52 | 53 | return badgeData; 54 | } 55 | 56 | function insertToInstance(badgesIdSlug, data) { 57 | console.info(data); 58 | var badgeData = lookUpBadgeData(badgesIdSlug, data); 59 | 60 | var serverUrl = env.get('BADGR_ENDPOINT') + 'public/'; 61 | // using data.newSlug instead of generating a new UID (it is a UID in the original DB). 62 | var jsonAssertion = { 63 | 'issuedOn': data.issuedOn, 64 | 'uid': data.newSlug, 65 | 'verify': { 66 | 'url': serverUrl + 'assertions/' + data.newSlug, 67 | 'type': 'hosted' 68 | }, 69 | 'image': serverUrl + 'assertions/' + data.newSlug + '/image', 70 | 'recipient': { // Not real credentials 71 | 'type': 'email', 72 | 'salt': '4a5b2567-83e7-43b4-bf42-c2530377c953', 73 | 'hashed': true, 74 | 'identity': 'sha256$98b1d080e491f9df4504ff5fc11b16e73de75043b4eada358e22f58206fd10a9' 75 | }, 76 | 'evidence': data.evidenceUrl, 77 | 'type': 'Assertion', 78 | '@context': 'https://w3id.org/openbadges/v1', 79 | 'badge': serverUrl + 'badges/' + data.slug, 80 | 'id': serverUrl + 'assertions/' + data.newSlug 81 | }; 82 | 83 | var recipient = { 84 | 'id': data.id, 85 | 'created_at': data.issuedOn, 86 | 'json': jsonAssertion, 87 | 'slug': data.newSlug, 88 | 'image': badgeData.image, 89 | 'revoked': false, 90 | 'revocation_reason': '', 91 | 'created_by_id': 1, // Hardcoded from b@d.gr user 92 | 'issuer_id': 2, // Hardcoded from b@d.gr user's first issuer 93 | 'identifier': 'get_full_url', 94 | 'recipient_identifier': data.email, 95 | 'badgeclass_id': badgeData.id 96 | }; 97 | 98 | badgesDB.run('INSERT INTO issuer_badgeinstance VALUES(:id, :created_at, :json, :slug, :image,' + 99 | ':revoked, :revocation_reason, :created_by_id, :issuer_id, :identifier, :recipient_identifier,' + 100 | ':badgeclass_id)', { 101 | ':id': recipient.id, 102 | ':created_at': new Date(recipient.created_at), 103 | ':json': JSON.stringify(recipient.json), 104 | ':slug': recipient.slug, 105 | ':image': recipient.image, 106 | ':revoked': recipient.revoked, 107 | ':revocation_reason': recipient.revocation_reason, 108 | ':created_by_id': recipient.created_by_id, 109 | ':issuer_id': recipient.issuer_id, 110 | ':identifier': recipient.identifier, 111 | ':recipient_identifier': recipient.recipient_identifier, 112 | ':badgeclass_id': recipient.badgeclass_id 113 | }, function(e){ 114 | if (e) { 115 | console.log(e); 116 | console.log('Failed id: ' , recipient.id); // Let other queries run anyway 117 | } 118 | }); 119 | 120 | } 121 | 122 | badgesDB.serialize(function(){ 123 | badgesDB.all('SELECT slug, id, image FROM issuer_badgeclass', function(err, badgesIdSlug) { 124 | 125 | bkDB.serialize(function(){ 126 | bkDB.all('select a.id, b.slug, a.slug as newSlug, a.email, a.issuedOn, a.evidenceUrl from badgeinstances a, ' + 127 | 'badges b where a.badgeId=b.id', function(er, data){ 128 | 129 | if (er) { 130 | throw er; 131 | } 132 | 133 | data.forEach(function(dataItem){ 134 | insertToInstance(badgesIdSlug, dataItem); 135 | }); 136 | 137 | // Close both DBs 138 | badgesDB.close(); 139 | bkDB.close(); 140 | }); 141 | }); 142 | 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /templates/pages/home.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | Link = require('react-router').Link, 3 | Page = require('../components/page.jsx'), 4 | BadgeList = require('../components/badgelist.jsx'); 5 | 6 | var Home = React.createClass({ 7 | componentDidMount: function() { 8 | document.title = "Contributorship Badges"; 9 | }, 10 | handleClick: function(i){ 11 | console.log(i); 12 | }, 13 | render: function() { 14 | return ( 15 | 16 |

Explore Badges

17 | 22 |

Badges

23 | 24 |

API Endpoints

25 | 126 |
127 | ); 128 | } 129 | }); 130 | 131 | module.exports = Home; 132 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Custom rule due to the ORCID API using properties not in camelCase 4 | /* eslint camelcase: [2, {properties: "never"}] */ 5 | 6 | var bodyParser = require('body-parser'); 7 | var express = require('express'); 8 | var path = require('path'); 9 | 10 | var env = require('./environments'); 11 | var badgerService = require('./badges/service').getInstance(); 12 | 13 | var mongoose = require('mongoose'); 14 | var mongoUri = env.get('MONGOLAB_URI'); 15 | 16 | var nodemailer = require('nodemailer'); 17 | var ses = require('nodemailer-ses-transport'); 18 | 19 | // create reusable transporter object using Amazon SES transport 20 | var transporter = nodemailer.createTransport(ses({ 21 | AWSAccessKeyID: env.get('AWS_ACCESS_KEY'), 22 | AWSSecretKey: env.get('AWS_SECRET_KEY') 23 | })); 24 | 25 | // We are simply defining the app in this module. 26 | // Anything below here will configure the app reference. 27 | var app = express(); 28 | module.exports = app; 29 | app.enable('trust proxy'); 30 | 31 | app.set('view engine', 'pug'); 32 | app.use(express.static(path.join(__dirname, '..', '/public'))); 33 | app.use(bodyParser.json()); // to support JSON-encoded bodies 34 | app.use(bodyParser.urlencoded({ // to support URL-encoded bodies 35 | extended: true 36 | })); 37 | 38 | mongoose.connect(mongoUri); 39 | var db = mongoose.connection; 40 | db.on('error', console.error.bind(console, 'connection error:')); 41 | db.once('open', function callback() { 42 | console.log('connection!'); 43 | }); 44 | 45 | require('./models.js'); 46 | 47 | function returnBadges(getBadges, request, response) { 48 | getBadges(function (error, badges) { 49 | if (error !== null) { 50 | console.log('Get error from return Badges ' + error); 51 | return response.send(error); 52 | } 53 | if (request.query.pretty) { 54 | response.render(path.join(__dirname, '..', '/public/code.pug'), { 55 | data: JSON.stringify(badges, null, 2) 56 | }); 57 | } else { 58 | response.json(badges); 59 | } 60 | }); 61 | } 62 | 63 | // Set the client credentials and the OAuth2 server 64 | var credentials = { 65 | clientID: env.get('ORCID_AUTH_CLIENT_ID'), 66 | clientSecret: env.get('ORCID_AUTH_CLIENT_SECRET'), 67 | site: env.get('ORCID_AUTH_SITE'), 68 | tokenPath: env.get('ORCID_AUTH_TOKEN_PATH') 69 | }; 70 | 71 | // Initialize the OAuth2 Library for ORCID 72 | var oauth2 = require('simple-oauth2')(credentials); 73 | 74 | // TODO: review proper session use 75 | var session = require('express-session'); 76 | var RedisStore = require('connect-redis')(session); 77 | 78 | var sessionConfig = { 79 | secret: env.get('SESSION_SECRET'), 80 | store: new RedisStore({ 81 | url: env.get('REDISCLOUD_URL') || 'redis://127.0.0.1:6379/0' 82 | }), 83 | name: 'sid', // Generic - don't leak information 84 | proxy: true, // Trust the reverse proxy for HTTPS/SSL 85 | cookie: { 86 | httpOnly: true, // Reduce XSS attack vector 87 | secure: true // Cookies via SSL 88 | }, 89 | resave: true, 90 | saveUninitialized: true 91 | }; 92 | 93 | if (env.get('APP') !== 'production') { 94 | // Allow non-SSL cookies when not in production 95 | sessionConfig.proxy = false; 96 | sessionConfig.cookie.secure = false; 97 | } 98 | 99 | app.use(session(sessionConfig)); 100 | 101 | // Build ORCID authorization oauth2 URI 102 | var authorizationUri = oauth2.authCode.authorizeURL({ 103 | redirect_uri: env.get('ORCID_REDIRECT_URI'), 104 | scope: '/authenticate', 105 | state: 'none' 106 | }); 107 | 108 | app.use(function (req, res, next) { 109 | res.header('Access-Control-Allow-Origin', '*'); 110 | res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); 111 | next(); 112 | }); 113 | 114 | // Redirect example using Express (see http://expressjs.com/api.html#res.redirect) 115 | app.get('/request-orcid-user-auth', function (request, response) { 116 | // Prepare the context 117 | response.redirect(authorizationUri); 118 | }); 119 | 120 | // Get the ORCID access token object (the authorization code from previous step) 121 | app.get('/orcid_auth_callback', function (request, response) { 122 | var code = request.query.code; 123 | oauth2.authCode.getToken({ 124 | code: code, 125 | redirect_uri: env.get('ORCID_REDIRECT_URI') 126 | }, function (error, result) { 127 | if (error) { 128 | // check for access_denied param 129 | if (request.query.error === 'access_denied') { 130 | // User denied access 131 | response.redirect('/denied'); 132 | } else { 133 | // Token Page 134 | request.session.orcid_token_error = oauth2.accessToken.create(result); 135 | response.redirect('/orcid_token_error'); 136 | } 137 | } else { 138 | // Token Page 139 | request.session.orcid_token = oauth2.accessToken.create(result); 140 | response.redirect(request.session.redirect || '/issue/'); 141 | } 142 | }); 143 | }); 144 | 145 | // Routes for badges 146 | var badges = require('./routes/badges')(returnBadges, badgerService); 147 | app.get('/badges', badges.getAll); 148 | app.get('/badges/count', badges.getAllCount); 149 | app.get('/badges/:badge', badges.getAllByType); 150 | app.get('/badges/:badge/count', badges.getAllByTypeCount); 151 | 152 | // Routes for user 153 | var users = require('./routes/users')(returnBadges, badgerService); 154 | app.get('/users/:orcid/badges', users.getBadges); 155 | app.get('/users/:orcid/badges/count', users.getBadgeCount); 156 | app.get('/users/:orcid/badges/:badge', users.getBadgesByBadge); 157 | app.get('/users/:orcid/badges/:badge/count', users.getBadgesByBadgeCount); 158 | app.get('/user', users.getUser); 159 | 160 | // Routes for papers 161 | var papers = require('./routes/papers')(returnBadges, badgerService, transporter); 162 | app.get('/papers/:doi1/:doi2/badges', papers.getBadges); 163 | app.get('/papers/:doi1/:doi2/badges/count', papers.getBadgeCount); 164 | app.get('/papers/:doi1/:doi2/badges/:badge', papers.getBadgesByBadge); 165 | app.get('/papers/:doi1/:doi2/badges/:badge/count', papers.getBadgesByBadgeCount); 166 | app.get('/papers/:doi1/:doi2/users/:orcid/badges', papers.getUserBadges); 167 | app.get('/papers/:doi1/:doi2/users/:orcid/badges/count', papers.getUserBadgeCount); 168 | app.get('/papers/:doi1/:doi2/users/:orcid/badges/:badge', papers.getUserBadgesByBadge); 169 | app.get('/papers/:doi1/:doi2/users/:orcid/badges/:badge/count', papers.getUserBadgesByBadgeCount); 170 | app.post('/papers/:doi1/:doi2', papers.createPaper); 171 | app.post('/papers/:doi1/:doi2/users/:orcid/badges/:badge?', papers.createBadges); 172 | 173 | // Routes for issue badge claims 174 | var claims = require('./routes/claims'); 175 | app.get('/claims/:slug', claims.getClaim); 176 | 177 | app.get('*', function (request, response) { 178 | request.session.redirect = request.originalUrl; 179 | var orcid; 180 | if (request.session.orcid_token && request.session.orcid_token.token) { 181 | orcid = request.session.orcid_token.token.orcid; 182 | } 183 | response.render(path.join(__dirname, '..', '/public/index.pug'), { 184 | orcid: orcid 185 | }); 186 | }); 187 | -------------------------------------------------------------------------------- /public/css/main.css: -------------------------------------------------------------------------------- 1 | 2 | .badge img{ 3 | width:100%; 4 | } 5 | 6 | * { 7 | -webkit-box-sizing: border-box; 8 | -moz-box-sizing: border-box; 9 | box-sizing: border-box; 10 | } 11 | 12 | /* 13 | * -- BASE STYLES -- 14 | * Most of these are inherited from Base, but I want to change a few. 15 | */ 16 | body { 17 | line-height: 1.7em; 18 | color: #7f8c8d; 19 | font-size: 13px; 20 | height:100%; 21 | } 22 | 23 | h1, 24 | h2, 25 | h3, 26 | h4, 27 | h5, 28 | h6, 29 | label { 30 | color: #34495e; 31 | } 32 | 33 | .pure-img-responsive { 34 | max-width: 100%; 35 | height: auto; 36 | } 37 | 38 | /* 39 | * -- LAYOUT STYLES -- 40 | * These are some useful classes which I will need 41 | */ 42 | .l-box { 43 | padding: 1em; 44 | } 45 | 46 | .l-box-lrg { 47 | padding: 2em; 48 | border-bottom: 1px solid rgba(0,0,0,0.1); 49 | } 50 | 51 | .is-center { 52 | text-align: center; 53 | } 54 | 55 | 56 | 57 | /* 58 | * -- PURE FORM STYLES -- 59 | * Style the form inputs and labels 60 | */ 61 | .pure-form label { 62 | margin: 1em 0 0; 63 | font-weight: bold; 64 | font-size: 100%; 65 | } 66 | 67 | .pure-form input[type] { 68 | border: 2px solid #ddd; 69 | box-shadow: none; 70 | font-size: 100%; 71 | margin-bottom: 1em; 72 | } 73 | 74 | .pure-form input[type=text] { 75 | width: 20em; 76 | } 77 | 78 | .pure-form textarea { 79 | width: 20em; 80 | } 81 | 82 | /* 83 | * -- PURE BUTTON STYLES -- 84 | * I want my pure-button elements to look a little different 85 | */ 86 | .pure-button { 87 | background-color: #1f8dd6; 88 | color: white; 89 | padding: 0.5em 2em; 90 | border-radius: 5px; 91 | } 92 | 93 | a.pure-button-primary { 94 | background: white; 95 | color: #1f8dd6; 96 | border-radius: 5px; 97 | font-size: 120%; 98 | } 99 | 100 | 101 | 102 | /* 103 | * -- MENU STYLES -- 104 | * I want to customize how my .pure-menu looks at the top of the page 105 | */ 106 | 107 | .home-menu { 108 | padding: 0.5em; 109 | text-align: center; 110 | box-shadow: 0 1px 1px rgba(0,0,0, 0.10); 111 | } 112 | .home-menu { 113 | background: #2d3e50; 114 | } 115 | .pure-menu.pure-menu-fixed { 116 | /* Fixed menus normally have a border at the bottom. */ 117 | border-bottom: none; 118 | /* I need a higher z-index here because of the scroll-over effect. */ 119 | z-index: 4; 120 | } 121 | 122 | .home-menu .pure-menu-heading { 123 | color: white; 124 | font-weight: 400; 125 | font-size: 120%; 126 | float:left; 127 | } 128 | 129 | .home-menu .pure-menu-selected .pure-menu-link { 130 | color: white; 131 | } 132 | 133 | .home-menu a { 134 | color: #6FBEF3; 135 | } 136 | .home-menu li a:hover, 137 | .home-menu li a:focus { 138 | background: none; 139 | border: none; 140 | color: #AECFE5; 141 | } 142 | 143 | /* 144 | * -- SPLASH STYLES -- 145 | * This is the blue top section that appears on the page. 146 | */ 147 | 148 | .splash-container { 149 | background: #1f8dd6; 150 | z-index: 1; 151 | overflow: hidden; 152 | /* The following styles are required for the "scroll-over" effect */ 153 | width: 100%; 154 | height: 88%; 155 | top: 0; 156 | left: 0; 157 | position: fixed !important; 158 | } 159 | 160 | .splash { 161 | /* absolute center .splash within .splash-container */ 162 | width: 80%; 163 | height: 50%; 164 | margin: auto; 165 | position: absolute; 166 | top: 100px; left: 0; bottom: 0; right: 0; 167 | text-align: center; 168 | text-transform: uppercase; 169 | } 170 | 171 | /* This is the main heading that appears on the blue section */ 172 | .splash-head { 173 | font-size: 20px; 174 | font-weight: bold; 175 | color: white; 176 | border: 3px solid white; 177 | padding: 1em 1.6em; 178 | font-weight: 100; 179 | border-radius: 5px; 180 | line-height: 1em; 181 | } 182 | 183 | /* This is the subheading that appears on the blue section */ 184 | .splash-subhead { 185 | color: white; 186 | letter-spacing: 0.05em; 187 | opacity: 0.8; 188 | } 189 | 190 | /* 191 | * -- CONTENT STYLES -- 192 | * This represents the content area (everything below the blue section) 193 | */ 194 | .content-wrapper { 195 | /* These styles are required for the "scroll-over" effect */ 196 | position: absolute; 197 | top: 50px; 198 | width: 100%; 199 | z-index: 2; 200 | background: white; 201 | display: flex; 202 | min-height: 100vh; 203 | flex-direction: column; 204 | } 205 | 206 | .home .content-wrapper { 207 | top: 87%; 208 | } 209 | 210 | /* This is the class used for the main content headers (

) */ 211 | .content-head { 212 | font-weight: 400; 213 | text-transform: uppercase; 214 | letter-spacing: 0.1em; 215 | margin: 2em 0 1em; 216 | } 217 | 218 | /* This is a modifier class used when the content-head is inside a ribbon */ 219 | .content-head-ribbon { 220 | color: white; 221 | } 222 | 223 | /* This is the class used for the content sub-headers (

) */ 224 | .content-subhead { 225 | color: #1f8dd6; 226 | } 227 | .content-subhead i { 228 | margin-right: 7px; 229 | } 230 | 231 | /* This is the class used for the dark-background areas. */ 232 | .ribbon { 233 | background: #2d3e50; 234 | color: #aaa; 235 | } 236 | 237 | /* This is the class used for the footer */ 238 | .footer { 239 | background: #111; 240 | flex:none; 241 | } 242 | 243 | .footer a:link { 244 | color:white; 245 | } 246 | .footer a:visited { 247 | color:#CCC; 248 | } 249 | 250 | /* 251 | * -- TABLET (AND UP) MEDIA QUERIES -- 252 | * On tablets and other medium-sized devices, we want to customize some 253 | * of the mobile styles. 254 | */ 255 | @media (min-width: 48em) { 256 | 257 | /* We increase the body font size */ 258 | body { 259 | font-size: 16px; 260 | } 261 | /* We want to give the content area some more padding */ 262 | .content { 263 | padding: 1em; 264 | flex:1; 265 | } 266 | 267 | /* We can align the menu header to the left, but float the 268 | menu items to the right. */ 269 | .home-menu { 270 | text-align: left; 271 | } 272 | .home-menu ul { 273 | float: right; 274 | } 275 | 276 | /* We increase the height of the splash-container */ 277 | /* .splash-container { 278 | height: 500px; 279 | }*/ 280 | 281 | /* We decrease the width of the .splash, since we have more width 282 | to work with */ 283 | .splash { 284 | width: 50%; 285 | height: 50%; 286 | } 287 | 288 | .splash-head { 289 | font-size: 250%; 290 | } 291 | 292 | 293 | /* We remove the border-separator assigned to .l-box-lrg */ 294 | .l-box-lrg { 295 | border: none; 296 | } 297 | 298 | } 299 | 300 | /* 301 | * -- DESKTOP (AND UP) MEDIA QUERIES -- 302 | * On desktops and other large devices, we want to over-ride some 303 | * of the mobile and tablet styles. 304 | */ 305 | @media (min-width: 78em) { 306 | /* We increase the header font size even more */ 307 | .splash-head { 308 | font-size: 300%; 309 | } 310 | } -------------------------------------------------------------------------------- /test/integration/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var request = require('supertest'); 4 | var assert = require('assert'); 5 | 6 | var testEnv = require('../../src/environments'); 7 | 8 | var badgeClient = require('../../src/badges/client')(testEnv); 9 | var badgeService = require('../../src/badges/service'); 10 | badgeService.init(badgeClient, testEnv); 11 | 12 | var app = require('../../src/app.js'); 13 | 14 | describe('Integration test against the real Badge server', function () { 15 | before(function () { 16 | assert.ok(testEnv.get('BADGES_ENDPOINT'), 'should set up BADGES_ENDPOINT in your test environment'); 17 | assert.ok(testEnv.get('BADGES_KEY'), 'should set up BADGES_KEY in your test environment'); 18 | assert.ok(testEnv.get('BADGES_SECRET'), 'should set up BADGES_SECRET in your test environment'); 19 | assert.ok(testEnv.get('BADGES_SYSTEM'), 'should set up BADGES_SYSTEM in your test environment'); 20 | }); 21 | 22 | it('get all the badges', function (done) { 23 | request(app) 24 | .get('/badges') 25 | .expect('Content-Type', /json/) 26 | .expect(function (res) { 27 | assert.ok(res.body[0].slug, 'not find badge slug in json'); 28 | }) 29 | .expect(200, done); 30 | }); 31 | 32 | it('render JSON data in pug with the pretty param', function (done) { 33 | request(app) 34 | .get('/badges?pretty=true') 35 | .expect('Content-Type', /html/) 36 | .expect(200, done); 37 | }); 38 | 39 | it('get a count of all the badges', function (done) { 40 | request(app) 41 | .get('/badges/count') 42 | .expect(function (res) { 43 | assert.ok(res.body > 0, 'no badges found'); 44 | }) 45 | .expect(200, done); 46 | }); 47 | 48 | it('get all badge instances of a certain badge', function (done) { 49 | request(app) 50 | .get('/badges/formal_analysis') 51 | .expect('Content-Type', /json/) 52 | .expect(function (res) { 53 | assert.ok(res.body[0].slug, 'not find badge slug in json'); 54 | assert.equal(res.body[0].badge.name, 'Formal analysis'); 55 | // assert.equal(res.body[0].email, null); ?? bug?? 56 | }) 57 | .expect(200, done); 58 | }); 59 | 60 | it('get a count of all badge instances of a certain badge', function (done) { 61 | request(app) 62 | .get('/badges/formal_analysis/count') 63 | .expect(function (res) { 64 | assert.ok(res.body > 0, 'no badges found'); 65 | }) 66 | .expect(200, done); 67 | }); 68 | 69 | it('get all badge instances earned by a user', function (done) { 70 | request(app) 71 | .get('/users/0000-0003-4959-3049/badges') 72 | .expect('Content-Type', /json/) 73 | .expect(function (res) { 74 | assert.ok(res.body[0].slug, 'not find one badge slug in json'); 75 | assert.equal(res.body[0].orcid, '0000-0003-4959-3049'); 76 | }) 77 | .expect(200, done); 78 | }); 79 | 80 | it('get a count of all badge instances earned by a user', function (done) { 81 | request(app) 82 | .get('/users/0000-0003-4959-3049/badges/count') 83 | .expect(function (res) { 84 | assert.ok(parseInt(res.body, 10) > 0, 'no badges found'); 85 | }) 86 | .expect(200, done); 87 | }); 88 | 89 | it('get all badge instances of a certain badge earned by a user', function (done) { 90 | request(app) 91 | .get('/users/0000-0003-4959-3049/badges/writing_review') 92 | .expect(function (res) { 93 | assert.ok(res.body[0].slug, 'not find one badge slug in json'); 94 | assert.equal(res.body[0].badge.name, 'Writing - review & editing'); 95 | assert.equal(res.body[0].orcid, '0000-0003-4959-3049'); 96 | }) 97 | .expect(200, done); 98 | }); 99 | 100 | it('get a count of all badge instances of a certain badge earned by a user', function (done) { 101 | request(app) 102 | .get('/users/0000-0003-4959-3049/badges/writing_review/count') 103 | .expect(function (res) { 104 | assert.ok(res.body > 0, 'no badges found'); 105 | }) 106 | .expect(200, done); 107 | }); 108 | 109 | it('get all badges of a certain paper', function (done) { 110 | request(app) 111 | .get('/papers/10.1186/2047-217X-2-10/badges/') 112 | .expect(function (res) { 113 | assert.ok(res.body[0].slug, 'not find one badge slug in json'); 114 | assert.equal(res.body[0].badge.name, 'Data curation'); 115 | assert.equal(res.body[0].evidenceUrl, 'http://dx.doi.org/10.1186/2047-217X-2-10'); 116 | }) 117 | .expect(200, done); 118 | }); 119 | 120 | it('get the number of badges of a certain paper', function (done) { 121 | request(app) 122 | .get('/papers/10.1186/2047-217X-2-10/badges/count') 123 | .expect(function (res) { 124 | assert.ok(parseInt(res.body, 10) > 0, 'no badges found'); 125 | }) 126 | .expect(200, done); 127 | }); 128 | 129 | it('get all badge instances of a certain badge for a paper', function (done) { 130 | request(app) 131 | .get('/papers/10.1186/2047-217X-2-10/badges/investigation') 132 | .expect(function (res) { 133 | assert.ok(res.body[0].slug, 'not find one badge slug in json'); 134 | assert.equal(res.body[0].badge.name, 'Investigation'); 135 | assert.equal(res.body[0].evidenceUrl, 'http://dx.doi.org/10.1186/2047-217X-2-10'); 136 | }) 137 | .expect(200, done); 138 | }); 139 | 140 | it('get the badge count for instances of a certain badge for a paper', function (done) { 141 | request(app) 142 | .get('/papers/10.1186/2047-217X-2-10/badges/investigation/count') 143 | .expect(function (res) { 144 | assert.ok(res.body > 0, 'no badges found'); 145 | }) 146 | .expect(200, done); 147 | }); 148 | 149 | it('get all badge instances earned by a user for a paper.', function (done) { 150 | request(app) 151 | .get('/papers/10.1186/2047-217X-2-10/users/0000-0002-3881-294X/badges') 152 | .expect(function (res) { 153 | assert.ok(res.body[0].slug, 'not find one badge slug in json'); 154 | assert.equal(res.body[0].orcid, '0000-0002-3881-294X'); 155 | assert.equal(res.body[0].evidenceUrl, 'http://dx.doi.org/10.1186/2047-217X-2-10'); 156 | }) 157 | .expect(200, done); 158 | }); 159 | 160 | it('get a count of all badge instances earned by a user for a paper.', function (done) { 161 | request(app) 162 | .get('/papers/10.1186/2047-217X-2-10/users/0000-0002-3881-294X/badges/count') 163 | .expect(function (res) { 164 | assert.ok(res.body > 0, 'no badges found'); 165 | }) 166 | .expect(200, done); 167 | }); 168 | 169 | it('get all badge instances of a certain badge earned by a user for a paper.', function (done) { 170 | request(app) 171 | .get('/papers/10.1186/2047-217X-2-10/users/0000-0002-3881-294X/badges/investigation') 172 | .expect(function (res) { 173 | assert.ok(res.body[0].slug, 'not find one badge slug in json'); 174 | assert.equal(res.body[0].badge.name, 'Investigation'); 175 | assert.equal(res.body[0].orcid, '0000-0002-3881-294X'); 176 | assert.equal(res.body[0].evidenceUrl, 'http://dx.doi.org/10.1186/2047-217X-2-10'); 177 | }) 178 | .expect(200, done); 179 | }); 180 | 181 | it('get a count of all badge instances of a certain badge earned by a user for a paper.', function (done) { 182 | request(app) 183 | .get('/papers/10.1186/2047-217X-2-10/users/0000-0002-3881-294X/badges/investigation/count') 184 | .expect(function (res) { 185 | assert.ok(res.body <= 1, 'no badges found'); 186 | }) 187 | .expect(200, done); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /public/widgets/paper-badger-widget.js: -------------------------------------------------------------------------------- 1 | var clickEvent={ 2 | runAnalytics : [], 3 | assignCallback : function(func) 4 | { 5 | this.runAnalytics=func; 6 | } 7 | }; 8 | 9 | function callAjax(url, callbackFunction) { 10 | 11 | var xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); 12 | 13 | xmlhttp.onreadystatechange = function() { 14 | if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { 15 | callbackFunction(xmlhttp.responseText); 16 | } 17 | } 18 | 19 | xmlhttp.open("GET", url, true); 20 | xmlhttp.send(); 21 | } 22 | 23 | function visitArray(arr, visitor) { 24 | var result = []; 25 | 26 | for (var i = 0; i < arr.length; i++) { 27 | result[i] = visitor(arr[i]); 28 | } 29 | 30 | return result; 31 | } 32 | 33 | function renderBadge(key, badgeData) 34 | { 35 | var outString="

"+key+"

"; 36 | var all=badgeData.split(";"); 37 | outString+=""; 38 | outString+="

"+all[1]+"

"; 39 | 40 | return outString; 41 | } 42 | 43 | function createBadgeJSON(badgeData) 44 | { 45 | var all=badgeData.split(";"); 46 | var all1=all[0].split("@"); 47 | var imageUrl="\"imageUrl\":\""+all1[1]+"\""; 48 | var taxon="\"taxonomyClass\":\""+all1[0].split("_").join(" ")+"\""; 49 | var all2="{"+imageUrl+","+taxon+",\"authorList\":"+"[\""+all[1].split(",").join("\",\"")+"\"]}"; 50 | 51 | return all2; 52 | } 53 | 54 | function insertCSS() 55 | { 56 | return ".paper-badge {float: left; width: 10em; height: 20em; overflow: hidden; border-top: 1px solid #ccc; height 15em; padding: 2%; margin-right: 1%; margin-top: 2%}.badge-span {width: 15em; display: inline-block; font-size: 88%; line-height: 1.2; color: #333; padding: 0.4em; cursor: hand; cursor: pointer}.paper-badge img {margin-left: 10%; margin-bottom: 1%}.badges-active {color: #fff !important; background: #7ab441}.paper-badges-hidden {display: none}"; 57 | } 58 | 59 | function showLine(event) 60 | { 61 | var bindBadge=this.getAttribute("data-bind-badge"); 62 | 63 | if(event.type=="click") {clickEvent.runAnalytics({"orcid" : bindBadge, "taxonomyClass" : this.getAttribute("data-bind-taxonomy")});} 64 | 65 | //var boundElem=$(".badge span[data-bind-badge="+bindBadge+"]"); 66 | //boundElem.attr("title", "badge count: "+boundElem.length+"; ORCID: "+bindBadge.split("\_").join(" ")); 67 | var allElems=document.getElementsByClassName("badge-span"); 68 | visitArray(allElems, function(item){if(item.getAttribute("data-bind-badge") == bindBadge){item.className = "badge-span badges-active"; item.setAttribute("title", "ORCID: "+bindBadge.split("\_").join(" "));}else item.className = "badge-span";}); 69 | } 70 | 71 | function nextAction() 72 | { 73 | var bindBadge=this.getAttribute("data-bind-badge"); 74 | window.open("http://orcid.org/"+bindBadge, "orcid"); 75 | } 76 | 77 | function splitArray(arr, lookGroup, taxonomyClass) 78 | { 79 | var ttt=""; 80 | 81 | for(var i=0, arrLen=arr.length; i"+noIn.split("_").join(" ")+""; 85 | } 86 | 87 | return ttt; 88 | } 89 | 90 | function getEndpoint(confIn, count) 91 | { 92 | var orcid=confIn["orcid"]; 93 | var doi=confIn["article-doi"]; 94 | var countPoint=(count) ? "/count" : ""; 95 | 96 | var endPoint=[]; 97 | if(doi) {endPoint.push("papers/"+doi);} 98 | if(orcid) {endPoint.push("users/"+orcid);} 99 | return "https://badges.mozillascience.org/"+endPoint.join("/")+"/badges"+countPoint; 100 | } 101 | 102 | function showBadgeFurniture(confIn) 103 | { 104 | var furnitureClass=(confIn["furniture-class"]) ? confIn["furniture-class"] : "paper-badges-hidden"; 105 | var endPoint=getEndpoint(confIn, 1); 106 | 107 | callAjax(endPoint, function(dataItem){ 108 | var badgesCount=(dataItem) ? dataItem : 0; 109 | 110 | if(badgesCount && dataItem/dataItem) 111 | { 112 | visitArray(document.querySelectorAll("."+furnitureClass), function(elem){ 113 | var list=elem.classList; 114 | 115 | if(list!==undefined && list.contains(furnitureClass)) 116 | { 117 | list.remove(furnitureClass); 118 | } 119 | else 120 | { 121 | var className=elem.className; 122 | var indStart=className.indexOf(furnitureClass); 123 | var indEnd=className.indexOf(" ", indStart); 124 | 125 | indEnd=(indEnd>=0) ? indEnd : className.length; 126 | elem.className=className.substring(0, indStart)+" "+className.substring(indEnd); 127 | } 128 | }); 129 | } 130 | }); 131 | } 132 | 133 | function showBadges(confIn, callback){ 134 | 135 | var k=""; 136 | 137 | var containerClass=(confIn["container-class"]) ? confIn["container-class"] : "badge-container"; 138 | var endPoint=getEndpoint(confIn); 139 | 140 | if(callback) 141 | { 142 | clickEvent.assignCallback(callback); 143 | } 144 | 145 | callAjax(endPoint, function( entryData ) { 146 | 147 | var outCount=0; 148 | var pos=0, end=0; 149 | var excludeChars="\" :\{\}\n\r"; 150 | 151 | var mode=0; 152 | var modeString=find=(!mode) ? "orcid" : "evidenceUrl"; 153 | var look=new Object(), lookGroupOrcidFromBadge=new Object(), lookGroup=new Object(), lookGroupNo=new Object(), lookTemporaryValue=new Array(2); 154 | 155 | look[modeString]=0; 156 | look["authorName"]=1; 157 | look["name"]=2; 158 | var lastTempVal=look["imageUrl"]=3; 159 | 160 | var group=1; 161 | var arrayNumber=1; 162 | 163 | while((pos=entryData.indexOf(find, pos))>0) 164 | { 165 | end=entryData.indexOf("\,", pos); 166 | 167 | var str=entryData.substring(pos+find.length, end); 168 | 169 | var i=0, k=0, ind=0; 170 | 171 | for(; ind>=0; ){ind=excludeChars.indexOf(str.charAt(i)); if(ind>=0) i++;} 172 | for(ind=0, j=str.length-1; ind>=0;){ind=excludeChars.indexOf(str.charAt(j)); if(ind>=0) j--;} 173 | 174 | arrayNumber=look[find]; 175 | 176 | find=(find == modeString) ? "authorName" : (find == "authorName") ? "name" : (find == "name") ? "imageUrl" : modeString; 177 | 178 | lookTemporaryValue[arrayNumber]=str.substring(i, j+1); 179 | 180 | if(arrayNumber == lastTempVal) { 181 | 182 | if(lookTemporaryValue[lastTempVal] != "null") 183 | { 184 | var key=lookTemporaryValue[2].split(" ").join(""); 185 | var lookTempVal=lookTemporaryValue[0]; 186 | 187 | if(lookGroupOrcidFromBadge[key]) 188 | { 189 | if(new String(lookGroupOrcidFromBadge[key]).indexOf(lookTempVal)<0) 190 | lookGroupOrcidFromBadge[key]+=","+lookTempVal; 191 | } 192 | else 193 | { 194 | lookGroupOrcidFromBadge[key]=lookTemporaryValue[2].split(" ").join("_")+"@"+lookTemporaryValue[3]+";"+lookTemporaryValue[0]; 195 | } 196 | lookGroup[lookTempVal]=lookTemporaryValue[1].split(" ").join("_"); 197 | } 198 | } 199 | } 200 | 201 | var allJson=""; 202 | var JSONarray=new Array(), iArr=0; 203 | 204 | for (var k in lookGroupOrcidFromBadge) { 205 | // use hasOwnProperty to filter out keys from the Object.prototype 206 | if (lookGroupOrcidFromBadge.hasOwnProperty(k)) {JSONarray[iArr++]=createBadgeJSON(lookGroupOrcidFromBadge[k]);} 207 | } 208 | 209 | var containerClassQuery=document.querySelector("."+containerClass); 210 | var parsedJSON=JSON.parse("["+JSONarray.join(",")+"]"); 211 | 212 | if(containerClassQuery) 213 | { 214 | containerClassQuery.appendChild(document.createElement("style")).innerHTML=insertCSS(); 215 | 216 | for(var i=0; i"; 221 | returnString+=splitArray(parsedJSON[i]["authorList"], lookGroup, parsedJSON[i]["taxonomyClass"]); 222 | 223 | var newNode=document.createElement("div"); 224 | newNode.setAttribute("class", "paper-badge"); 225 | 226 | containerClassQuery.appendChild(newNode).innerHTML=returnString; 227 | } 228 | } 229 | 230 | visitArray(document.getElementsByClassName("badge-span"), function(elem){ 231 | if(elem){ 232 | elem.addEventListener("mouseover", showLine); 233 | elem.addEventListener("click", showLine); 234 | elem.addEventListener("click", nextAction); 235 | } 236 | }); 237 | }); 238 | } 239 | -------------------------------------------------------------------------------- /src/routes/papers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var returnBadges, badgerService, transporter; 4 | var path = require('path'); 5 | var helpers = require(path.join(process.cwd(), 'src', 'helpers')); 6 | var mongoose = require('mongoose'); 7 | var Claim = mongoose.model('Claim'); 8 | var User = mongoose.model('User'); 9 | var shortid = require('shortid'); 10 | 11 | function getBadgesByDoi(request, response) { 12 | if (!request.params.doi1 || !request.params.doi2) { 13 | response.status(400).end(); 14 | return; 15 | } 16 | 17 | return badgerService.getBadges(null, null, { 18 | '_1': request.params.doi1, 19 | '_2': request.params.doi2 20 | }); 21 | } 22 | 23 | function getBadges(request, response) { 24 | returnBadges(getBadgesByDoi(request, response), request, response); 25 | } 26 | 27 | function getBadgeCount(request, response) { 28 | getBadgesByDoi(request, response)(function (error, badges) { 29 | if (error !== null || !badges) { 30 | response.json(0); 31 | } else { 32 | response.json(badges.length); 33 | } 34 | }); 35 | } 36 | 37 | 38 | function getBadgesByType(request, response) { 39 | if (!request.params.doi1 || !request.params.doi2 || !request.params.badge) { 40 | response.status(400).end(); 41 | return; 42 | } 43 | 44 | return badgerService.getBadges(null, request.params.badge, { 45 | '_1': request.params.doi1, 46 | '_2': request.params.doi2 47 | }); 48 | } 49 | 50 | function getBadgesByBadge(request, response) { 51 | returnBadges(getBadgesByType(request, response), request, response); 52 | } 53 | 54 | function getBadgesByBadgeCount(request, response) { 55 | getBadgesByType(request, response)(function (error, badges) { 56 | if (error !== null || !badges) { 57 | response.json(0); 58 | } else { 59 | response.json(badges.length); 60 | } 61 | }); 62 | } 63 | 64 | function getBadgesByUser(request, response) { 65 | if (!request.params.doi1 || !request.params.doi2 || !request.params.orcid) { 66 | response.status(400).end(); 67 | return; 68 | } 69 | 70 | return badgerService.getBadges(request.params.orcid, null, { 71 | '_1': request.params.doi1, 72 | '_2': request.params.doi2 73 | }); 74 | } 75 | 76 | function getUserBadges(request, response) { 77 | returnBadges(getBadgesByUser(request, response), request, response); 78 | } 79 | 80 | function getUserBadgeCount(request, response) { 81 | getBadgesByUser(request, response)(function (error, badges) { 82 | if (error !== null || !badges) { 83 | response.json(0); 84 | } else { 85 | response.json(badges.length); 86 | } 87 | }); 88 | } 89 | 90 | function getUserBadgesByType(request, response) { 91 | if (!request.params.doi1 || !request.params.doi2 || !request.params.orcid || !request.params.badge) { 92 | response.status(400).end(); 93 | return; 94 | } 95 | 96 | return badgerService.getBadges(request.params.orcid, request.params.badge, { 97 | '_1': request.params.doi1, 98 | '_2': request.params.doi2 99 | }); 100 | } 101 | 102 | function getUserBadgesByBadge(request, response) { 103 | returnBadges(getUserBadgesByType(request, response), request, response); 104 | } 105 | 106 | function getUserBadgesByBadgeCount(request, response) { 107 | getUserBadgesByType(request, response)(function (error, badges) { 108 | if (error !== null || !badges) { 109 | response.json(0); 110 | } else { 111 | response.json(badges.length); 112 | } 113 | }); 114 | } 115 | 116 | function createPaper(request, response) { 117 | var orcid; 118 | if (request.session.orcid_token && request.session.orcid_token.token) { 119 | orcid = request.session.orcid_token.token.orcid; 120 | } 121 | var query = User.where({orcid: orcid}); 122 | query.findOne(function (err, user) { 123 | if (!user || (user.role !== 'publisher')) { 124 | response.status(403).end(); 125 | return; 126 | } 127 | }); 128 | 129 | var doiUrl = helpers.urlFromDOI(request.params.doi1, request.params.doi2); 130 | var emails = request.body.emails; 131 | var mailFinal = []; 132 | emails.map(function (email) { 133 | // Generate a claim code, store in mongo, email each user their unique claim code 134 | var claim = new Claim({ 135 | slug: shortid.generate(), 136 | doi: doiUrl, 137 | status: 'new' 138 | }); 139 | claim.save(); 140 | 141 | 142 | var html = '

You recently authored this academic paper: '; 143 | html += doiUrl + '.

'; 144 | html += '

Now, you can claim '; 145 | html += 'Contributor Badges based on your contributions.

'; 146 | html += '

Your claim code: ' + claim.slug + '

'; 148 | html += '

You can go here to claim your badges: https://badges.mozillascience.org/issue/'; 151 | html += claim.slug + '

'; 152 | 153 | var text = 'You recently authored this academic paper: '; 154 | text += doiUrl + '. Now, you can claim Contributor Badges based on your contributions. Your claim code: '; 155 | text += claim.slug + '. You can go here to claim your badges: https://badges.mozillascience.org/issue/'; 156 | text += claim.slug; 157 | 158 | // setup e-mail data with unicode symbols 159 | var mailOptions = { 160 | from: 'noreply@mozillascience.org', 161 | to: email, // list of receivers 162 | subject: 'Claim badges for your scholarly contributions from Mozilla Science Paper Badger', // Subject line 163 | text: text, // plaintext body 164 | html: html 165 | }; 166 | 167 | // send mail with defined transport object 168 | transporter.sendMail(mailOptions, function (error, info) { 169 | if (error) { 170 | // return console.log(error); 171 | response.send(error); 172 | return console.log(error); 173 | } 174 | mailFinal.push(info); 175 | if (mailFinal.length === emails.length) { 176 | response.json(mailFinal); 177 | } 178 | }); 179 | }); 180 | } 181 | 182 | function createBadges(request, response) { 183 | var orcid; 184 | if (request.session.orcid_token && request.session.orcid_token.token) { 185 | orcid = request.session.orcid_token.token.orcid; 186 | } 187 | if (orcid !== request.params.orcid) { 188 | response.status(403).end(); 189 | return; 190 | } 191 | if (!request.params.doi1 || !request.params.doi2 || !request.params.orcid) { 192 | response.status(400).end(); 193 | return; 194 | } 195 | var name = request.session.orcid_token.token.name; 196 | var badges = request.body.badges || [request.param.badge]; 197 | 198 | // Delete the claim code once it's been used 199 | var slug = request.body.claim; 200 | Claim.find({slug: slug}).remove().exec(); 201 | 202 | var badgeFinal = []; 203 | badges.map(function (badge) { 204 | var getTheBadges = badgerService.createBadge(request.params.orcid, badge, { 205 | '_1': request.params.doi1, 206 | '_2': request.params.doi2 207 | }, name); 208 | getTheBadges(function (error, aBadge) { 209 | if (error !== null) { 210 | console.log('Get error from return Badges ' + error); 211 | response.send(error); 212 | } else { 213 | badgeFinal.push(aBadge); 214 | if (badgeFinal.length === badges.length) { 215 | response.json(badgeFinal); 216 | } 217 | } 218 | }); 219 | }); 220 | } 221 | 222 | 223 | module.exports = function (rb, bs, tr) { 224 | returnBadges = rb; 225 | badgerService = bs; 226 | transporter = tr; 227 | 228 | return { 229 | // GET 230 | getBadges: getBadges, 231 | getBadgeCount: getBadgeCount, 232 | getBadgesByBadge: getBadgesByBadge, 233 | getBadgesByBadgeCount: getBadgesByBadgeCount, 234 | getUserBadges: getUserBadges, 235 | getUserBadgeCount: getUserBadgeCount, 236 | getUserBadgesByBadge: getUserBadgesByBadge, 237 | getUserBadgesByBadgeCount: getUserBadgesByBadgeCount, 238 | 239 | // POST 240 | createPaper: createPaper, 241 | createBadges: createBadges 242 | }; 243 | }; 244 | -------------------------------------------------------------------------------- /templates/pages/issue.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react'), 2 | Router = require('react-router'), 3 | Url = require('url'), 4 | path = require('path'), 5 | CheckboxGroup = require('react-checkbox-group'), 6 | Page = require('../components/page.jsx'); 7 | 8 | var Issue = React.createClass({ 9 | mixins: [ Router.State ], 10 | loadClaimFromServer: function(slug) { 11 | fetch('/claims/' + slug) 12 | .then((response) => { 13 | if (response.status >= 400) { 14 | throw new Error("Bad response from server"); 15 | } 16 | return response.json(); 17 | }) 18 | .then((claim) => { 19 | this.setState({claim: claim}); 20 | }); 21 | }, 22 | componentWillMount: function() { 23 | document.title = "Contributorship Badges"; 24 | if(!this.props.user){ 25 | //redirect if user isn't logged in 26 | window.location.href="/request-orcid-user-auth"; 27 | } 28 | this.loadClaimFromServer(this.getParams().slug); 29 | }, 30 | getInitialState: function() { 31 | return {data: {}, claim: {}}; 32 | }, 33 | claimSubmit: function(e){ 34 | e.preventDefault(); 35 | var claim = this.refs.claim.getDOMNode().value.trim(); 36 | this.loadClaimFromServer(claim); 37 | }, 38 | handleSubmit: function(e) { 39 | e.preventDefault(); 40 | var orcid = this.refs.orcid.getDOMNode().value.trim(); 41 | var doi = this.refs.doi.getDOMNode().value.trim(); 42 | var badges = this.refs.badges.getCheckedValues(); 43 | var claim = this.state.claim.slug; 44 | 45 | var doiRe = /(10\.\d{3}\d+)\/(.*)\b/; 46 | var m = doiRe.exec(doi); 47 | var url = path.join('/papers', m[1],encodeURIComponent(m[2]), 'users', orcid, 'badges'); 48 | 49 | fetch(url, { 50 | method: 'post', 51 | headers: { 52 | 'Accept': 'application/json', 53 | 'Content-Type': 'application/json' 54 | }, 55 | credentials: 'same-origin', 56 | body: JSON.stringify({badges: badges, claim: claim}) 57 | }) 58 | .then((response) => { 59 | if (response.status >= 400) { 60 | throw new Error("Bad response from server"); 61 | } 62 | return response.json(); 63 | }) 64 | .then((data) => { 65 | window.location = '/v/#' + url; 66 | }); 67 | return; 68 | }, 69 | render: function() { 70 | var claim = this.state.claim; 71 | 72 | if(claim.doi){ 73 | return ( 74 | 75 |

Issue Badges

76 |
77 |
78 |
79 | 80 | 81 |
82 | 83 |
84 | 85 | 86 |
87 | 88 |
89 | 90 | Select the roles you contributed to this paper 91 |
92 |
93 | 94 | 97 |
Ideas; formulation or evolution of overarching research goals and aims.
98 | 101 |
Management activities to annotate (produce metadata), scrub data and maintain research data (including software code, where it is necessary for interpreting the data itself) for initial use and later re-use.
102 | 105 |
Application of statistical, mathematical, computational, or other formal techniques to analyse or synthesize study data.
106 | 107 |
Acquisition of the financial support for the project leading to this publication.
108 | 109 |
Conducting a research and investigation process, specifically performing the experiments, or data/evidence collection.
110 | 111 |
Development or design of methodology; creation of models.
112 | 113 |
Management and coordination responsibility for the research activity planning and execution.
114 | 115 |
Provision of study materials, reagents, materials, patients, laboratory samples, animals, instrumentation, computing resources, or other analysis tools.
116 | 117 |
Programming, software development; designing computer programs; implementation of the computer code and supporting algorithms; testing of existing code components.
118 | 119 |
Oversight and leadership responsibility for the research activity planning and execution, including mentorship external to the core team.
120 | 121 |
Verification, whether as a part of the activity or separate, of the overall replication/reproducibility of results/experiments and other research outputs.
122 | 123 |
Preparation, creation and/or presentation of the published work, specifically visualization/data presentation.
124 | 125 |
Preparation, creation and/or presentation of the published work, specifically writing the initial draft (including substantive translation).
126 | 127 |
Preparation, creation and/or presentation of the published work by those from the original research group, specifically critical review, commentary or revision – including pre- or post-publication stages.
128 |
129 |
130 | 131 |
132 | 133 |
134 |
135 |
136 |
137 | 138 | ); 139 | } else { 140 | return ( 141 | 142 |

Issue Badges

143 |

Check your email for a valid claim code to issue badges for your contributions.

144 |
145 |
146 |
147 | 148 | 149 |
150 | 151 |
152 | 153 |
154 |
155 |
156 |
157 | ); 158 | } 159 | } 160 | }); 161 | 162 | module.exports = Issue; 163 | -------------------------------------------------------------------------------- /public/css/vendor/grids-responsive-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | @media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-2,.pure-u-sm-1-3,.pure-u-sm-2-3,.pure-u-sm-1-4,.pure-u-sm-3-4,.pure-u-sm-1-5,.pure-u-sm-2-5,.pure-u-sm-3-5,.pure-u-sm-4-5,.pure-u-sm-5-5,.pure-u-sm-1-6,.pure-u-sm-5-6,.pure-u-sm-1-8,.pure-u-sm-3-8,.pure-u-sm-5-8,.pure-u-sm-7-8,.pure-u-sm-1-12,.pure-u-sm-5-12,.pure-u-sm-7-12,.pure-u-sm-11-12,.pure-u-sm-1-24,.pure-u-sm-2-24,.pure-u-sm-3-24,.pure-u-sm-4-24,.pure-u-sm-5-24,.pure-u-sm-6-24,.pure-u-sm-7-24,.pure-u-sm-8-24,.pure-u-sm-9-24,.pure-u-sm-10-24,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%;*width:4.1357%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%;*width:8.3023%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%;*width:12.469%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%;*width:16.6357%}.pure-u-sm-1-5{width:20%;*width:19.969%}.pure-u-sm-5-24{width:20.8333%;*width:20.8023%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%;*width:24.969%}.pure-u-sm-7-24{width:29.1667%;*width:29.1357%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%;*width:33.3023%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%;*width:37.469%}.pure-u-sm-2-5{width:40%;*width:39.969%}.pure-u-sm-5-12,.pure-u-sm-10-24{width:41.6667%;*width:41.6357%}.pure-u-sm-11-24{width:45.8333%;*width:45.8023%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%;*width:49.969%}.pure-u-sm-13-24{width:54.1667%;*width:54.1357%}.pure-u-sm-7-12,.pure-u-sm-14-24{width:58.3333%;*width:58.3023%}.pure-u-sm-3-5{width:60%;*width:59.969%}.pure-u-sm-5-8,.pure-u-sm-15-24{width:62.5%;*width:62.469%}.pure-u-sm-2-3,.pure-u-sm-16-24{width:66.6667%;*width:66.6357%}.pure-u-sm-17-24{width:70.8333%;*width:70.8023%}.pure-u-sm-3-4,.pure-u-sm-18-24{width:75%;*width:74.969%}.pure-u-sm-19-24{width:79.1667%;*width:79.1357%}.pure-u-sm-4-5{width:80%;*width:79.969%}.pure-u-sm-5-6,.pure-u-sm-20-24{width:83.3333%;*width:83.3023%}.pure-u-sm-7-8,.pure-u-sm-21-24{width:87.5%;*width:87.469%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%;*width:91.6357%}.pure-u-sm-23-24{width:95.8333%;*width:95.8023%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-5-5,.pure-u-sm-24-24{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-2,.pure-u-md-1-3,.pure-u-md-2-3,.pure-u-md-1-4,.pure-u-md-3-4,.pure-u-md-1-5,.pure-u-md-2-5,.pure-u-md-3-5,.pure-u-md-4-5,.pure-u-md-5-5,.pure-u-md-1-6,.pure-u-md-5-6,.pure-u-md-1-8,.pure-u-md-3-8,.pure-u-md-5-8,.pure-u-md-7-8,.pure-u-md-1-12,.pure-u-md-5-12,.pure-u-md-7-12,.pure-u-md-11-12,.pure-u-md-1-24,.pure-u-md-2-24,.pure-u-md-3-24,.pure-u-md-4-24,.pure-u-md-5-24,.pure-u-md-6-24,.pure-u-md-7-24,.pure-u-md-8-24,.pure-u-md-9-24,.pure-u-md-10-24,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-md-1-24{width:4.1667%;*width:4.1357%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%;*width:8.3023%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%;*width:12.469%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%;*width:16.6357%}.pure-u-md-1-5{width:20%;*width:19.969%}.pure-u-md-5-24{width:20.8333%;*width:20.8023%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%;*width:24.969%}.pure-u-md-7-24{width:29.1667%;*width:29.1357%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%;*width:33.3023%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%;*width:37.469%}.pure-u-md-2-5{width:40%;*width:39.969%}.pure-u-md-5-12,.pure-u-md-10-24{width:41.6667%;*width:41.6357%}.pure-u-md-11-24{width:45.8333%;*width:45.8023%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%;*width:49.969%}.pure-u-md-13-24{width:54.1667%;*width:54.1357%}.pure-u-md-7-12,.pure-u-md-14-24{width:58.3333%;*width:58.3023%}.pure-u-md-3-5{width:60%;*width:59.969%}.pure-u-md-5-8,.pure-u-md-15-24{width:62.5%;*width:62.469%}.pure-u-md-2-3,.pure-u-md-16-24{width:66.6667%;*width:66.6357%}.pure-u-md-17-24{width:70.8333%;*width:70.8023%}.pure-u-md-3-4,.pure-u-md-18-24{width:75%;*width:74.969%}.pure-u-md-19-24{width:79.1667%;*width:79.1357%}.pure-u-md-4-5{width:80%;*width:79.969%}.pure-u-md-5-6,.pure-u-md-20-24{width:83.3333%;*width:83.3023%}.pure-u-md-7-8,.pure-u-md-21-24{width:87.5%;*width:87.469%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%;*width:91.6357%}.pure-u-md-23-24{width:95.8333%;*width:95.8023%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-5-5,.pure-u-md-24-24{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-2,.pure-u-lg-1-3,.pure-u-lg-2-3,.pure-u-lg-1-4,.pure-u-lg-3-4,.pure-u-lg-1-5,.pure-u-lg-2-5,.pure-u-lg-3-5,.pure-u-lg-4-5,.pure-u-lg-5-5,.pure-u-lg-1-6,.pure-u-lg-5-6,.pure-u-lg-1-8,.pure-u-lg-3-8,.pure-u-lg-5-8,.pure-u-lg-7-8,.pure-u-lg-1-12,.pure-u-lg-5-12,.pure-u-lg-7-12,.pure-u-lg-11-12,.pure-u-lg-1-24,.pure-u-lg-2-24,.pure-u-lg-3-24,.pure-u-lg-4-24,.pure-u-lg-5-24,.pure-u-lg-6-24,.pure-u-lg-7-24,.pure-u-lg-8-24,.pure-u-lg-9-24,.pure-u-lg-10-24,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-lg-1-24{width:4.1667%;*width:4.1357%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%;*width:8.3023%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%;*width:12.469%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%;*width:16.6357%}.pure-u-lg-1-5{width:20%;*width:19.969%}.pure-u-lg-5-24{width:20.8333%;*width:20.8023%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%;*width:24.969%}.pure-u-lg-7-24{width:29.1667%;*width:29.1357%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%;*width:33.3023%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%;*width:37.469%}.pure-u-lg-2-5{width:40%;*width:39.969%}.pure-u-lg-5-12,.pure-u-lg-10-24{width:41.6667%;*width:41.6357%}.pure-u-lg-11-24{width:45.8333%;*width:45.8023%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%;*width:49.969%}.pure-u-lg-13-24{width:54.1667%;*width:54.1357%}.pure-u-lg-7-12,.pure-u-lg-14-24{width:58.3333%;*width:58.3023%}.pure-u-lg-3-5{width:60%;*width:59.969%}.pure-u-lg-5-8,.pure-u-lg-15-24{width:62.5%;*width:62.469%}.pure-u-lg-2-3,.pure-u-lg-16-24{width:66.6667%;*width:66.6357%}.pure-u-lg-17-24{width:70.8333%;*width:70.8023%}.pure-u-lg-3-4,.pure-u-lg-18-24{width:75%;*width:74.969%}.pure-u-lg-19-24{width:79.1667%;*width:79.1357%}.pure-u-lg-4-5{width:80%;*width:79.969%}.pure-u-lg-5-6,.pure-u-lg-20-24{width:83.3333%;*width:83.3023%}.pure-u-lg-7-8,.pure-u-lg-21-24{width:87.5%;*width:87.469%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%;*width:91.6357%}.pure-u-lg-23-24{width:95.8333%;*width:95.8023%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-5-5,.pure-u-lg-24-24{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-2,.pure-u-xl-1-3,.pure-u-xl-2-3,.pure-u-xl-1-4,.pure-u-xl-3-4,.pure-u-xl-1-5,.pure-u-xl-2-5,.pure-u-xl-3-5,.pure-u-xl-4-5,.pure-u-xl-5-5,.pure-u-xl-1-6,.pure-u-xl-5-6,.pure-u-xl-1-8,.pure-u-xl-3-8,.pure-u-xl-5-8,.pure-u-xl-7-8,.pure-u-xl-1-12,.pure-u-xl-5-12,.pure-u-xl-7-12,.pure-u-xl-11-12,.pure-u-xl-1-24,.pure-u-xl-2-24,.pure-u-xl-3-24,.pure-u-xl-4-24,.pure-u-xl-5-24,.pure-u-xl-6-24,.pure-u-xl-7-24,.pure-u-xl-8-24,.pure-u-xl-9-24,.pure-u-xl-10-24,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-xl-1-24{width:4.1667%;*width:4.1357%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%;*width:8.3023%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%;*width:12.469%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%;*width:16.6357%}.pure-u-xl-1-5{width:20%;*width:19.969%}.pure-u-xl-5-24{width:20.8333%;*width:20.8023%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%;*width:24.969%}.pure-u-xl-7-24{width:29.1667%;*width:29.1357%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%;*width:33.3023%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%;*width:37.469%}.pure-u-xl-2-5{width:40%;*width:39.969%}.pure-u-xl-5-12,.pure-u-xl-10-24{width:41.6667%;*width:41.6357%}.pure-u-xl-11-24{width:45.8333%;*width:45.8023%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%;*width:49.969%}.pure-u-xl-13-24{width:54.1667%;*width:54.1357%}.pure-u-xl-7-12,.pure-u-xl-14-24{width:58.3333%;*width:58.3023%}.pure-u-xl-3-5{width:60%;*width:59.969%}.pure-u-xl-5-8,.pure-u-xl-15-24{width:62.5%;*width:62.469%}.pure-u-xl-2-3,.pure-u-xl-16-24{width:66.6667%;*width:66.6357%}.pure-u-xl-17-24{width:70.8333%;*width:70.8023%}.pure-u-xl-3-4,.pure-u-xl-18-24{width:75%;*width:74.969%}.pure-u-xl-19-24{width:79.1667%;*width:79.1357%}.pure-u-xl-4-5{width:80%;*width:79.969%}.pure-u-xl-5-6,.pure-u-xl-20-24{width:83.3333%;*width:83.3023%}.pure-u-xl-7-8,.pure-u-xl-21-24{width:87.5%;*width:87.469%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%;*width:91.6357%}.pure-u-xl-23-24{width:95.8333%;*width:95.8023%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-5-5,.pure-u-xl-24-24{width:100%}} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PaperBadger [![Build Status](https://travis-ci.org/mozillascience/PaperBadger.svg)](https://travis-ci.org/mozillascience/PaperBadger) 2 | 3 | [![Join the chat at https://gitter.im/mozillascience/PaperBadger](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mozillascience/PaperBadger?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 4 | 5 | Issuing badges to credit authors for their work on academic papers 6 | 7 | As the research environment becomes more digital, we want to test how we can use this medium to help bring transparency and credit for individuals in the publication process. 8 | 9 | ## Using Paper Badger 10 | 11 | You can display contributorship badges for science on your own site! Researchers earn badges for their specific contributions to an academic paper. A researcher who worked on investigation earns a prestigious investigation badge for that paper. 12 | 13 | The PaperBadger widget enables *anyone*, from publishers to individual researchers, to easily display badges on a website by including just a few lines of script with the relevant doi (digital object identifier) and a designated `
` in your view file. Authors can add the script to their own sites to display badges earned, while publishers can use the script to display all badges associated with a paper: 14 | 15 | You can either use paper-badger-widget.js ([documentation](docs/paper-badger-widget.md)), which supports old browsers or widget.js ([documentation](docs/widget.md)), which is in development and currently only supports evergreen browsers and IE9+. 16 | 17 | ### Current Users 18 | 19 | Two journals, [GigaScience (BioMed Central)](http://gigascience.biomedcentral.com/) and [Journal of Open Research Software (Ubiquity Press)](http://openresearchsoftware.metajnl.com/) have added the Paper Badger widget to their papers. 20 | 21 | JORS example: [A Web-based modeling tool for the SEMAT Essence theory of software engineering](http://openresearchsoftware.metajnl.com/articles/10.5334/jors.ad/metrics/#author-contributions) 22 | 23 | ![Live Example](./public/img/users.png) 24 | 25 | ## Contributing 26 | 27 | [Project Roadmap: #17](https://github.com/mozillascience/paperbadger/issues/17) 28 | 29 | Want to help? We love new contributors! Please review our [contributing guidelines](CONTRIBUTING.md) and take a look at some [good first bugs](https://github.com/mozillascience/PaperBadger/labels/good%20first%20bug). 30 | 31 | ### Getting Started 32 | 33 | Are you ready to contribute to Paper Badger? This section will help you set up your own development version of the Contributorship Badges prototype. 34 | 35 | Clone (or Fork) PaperBadger and enter the directory: `git clone https://github.com/mozillascience/PaperBadger && cd PaperBadger` 36 | 37 | For an overview of the [architecture](docs/high-level-architecture.md) of the system and other details, visit the [docs](docs/) section. 38 | 39 | #### Environment variables 40 | If you would like to override the default configuration, create an `.env` file in your favourite text editor and use _default.env_ as a template (do not delete or modify _default.env_). 41 | 42 | If you would like to develop against the hosted custom badgekit-api we have running specifically for PaperBadger testing, your environment values should look this: 43 | 44 | # default port is 5000 45 | export PORT=5000 46 | export SESSION_SECRET=USE_SOMETHING_GOOD_LIKE_puUJjfE6QtUnYryb 47 | 48 | # Badges 49 | export BADGES_ENDPOINT=http://badgekit-api-test-sciencelab.herokuapp.com/ 50 | export BADGES_KEY=master 51 | export BADGES_SECRET=############# 52 | export BADGES_SYSTEM=badgekit 53 | 54 | # ORCID Auth 55 | export ORCID_AUTH_CLIENT_ID=############# 56 | export ORCID_AUTH_CLIENT_SECRET=############# 57 | export ORCID_AUTH_SITE=############# 58 | export ORCID_AUTH_TOKEN_PATH=############# 59 | export ORCID_REDIRECT_URI=############# 60 | 61 | Ask [@acabunoc](http://github.com/acabunoc) for ones marked `###########`. Our custom BadgeKit API code can be found [here](https://github.com/acabunoc/badgekit-api). 62 | Feel free to change `PORT` to any available port. 63 | 64 | #### Run using Docker 65 | 66 | You can use Docker to bring up a quick instance of the app to develop against. This way you don't need to have node, MongoDB, and redis installed on your host. 67 | 68 | * Make sure you have [Docker](https://www.docker.com/) (>=1.10) and docker-compose (>=1.6) installed. 69 | * Setup your environment variables as explained in the previous section 70 | * start the service with `docker-compose up` 71 | * visit the running service 72 | * If on Linux host: http://localhost:5000 73 | * If not Linux: http://(docker_host_ip):5000 (You can find your docker IP with `docker-machine ip default`) 74 | 75 | This setup will create 3 containers: 76 | - paperbadger_paperbadger_1 contains the PaperBadger code, mapped to the _/src_ volume 77 | - paperbadger_mongo_1 is the MongoDB container with a DB called _test_ 78 | - paperbadger_redis_1 is the redis server 79 | 80 | You can connect to the main container by using `docker exec -it paperbadger_paperbadger_1 /bin/bash`. _ctrl+c_ will stop the three containers. 81 | 82 | #### Run locally 83 | 84 | * Make sure you have at least node version 4.4.5 installed. Installing node through [nvm](https://github.com/creationix/nvm) is recommended, so you don't need to use _sudo_ in the next step. 85 | * Install PaperBadger's Node dependencies: `npm install` 86 | * Make sure [MongoDB](https://www.mongodb.org/) and [redis-server](http://redis.io/download) are running and locally accessible. You can install these from their official website or use your favorite package manager. 87 | * Setup your environment variables as explained in a previous section 88 | * Run `npm start`, and open up `http://localhost:5000/` in your favourite web browser! 89 | 90 | 91 | ### API Endpoints 92 | 93 | * GET [/badges](http://badges.mozillascience.org/badges) 94 | * Get all badges we issue 95 | * GET [/badges/count](http://badges.mozillascience.org/badges/count) 96 | * Get a count of all badges we issue 97 | * GET /badges/:badge 98 | * Get all badge instances of a certain badge 99 | * e.g. [/badges/formal_analysis](http://badges.mozillascience.org/badges/formal_analysis) 100 | * GET /badges/:badge/count 101 | * Get a count of all badge instances of a certain badge 102 | * e.g. [/badges/formal_analysis/count](http://badges.mozillascience.org/badges/formal_analysis/count) 103 | * GET /users/:orcid/badges 104 | * Get all badge instances earned by a user 105 | * e.g. [/users/0000-0001-5979-8713/badges](http://badges.mozillascience.org/users/0000-0001-5979-8713/badges) 106 | * GET /users/:orcid/badges/count 107 | * Get a count of all badge instances earned by a user 108 | * e.g. [/users/0000-0001-5979-8713/badges/count](http://badges.mozillascience.org/users/0000-0001-5979-8713/badges/count) 109 | * GET /users/:orcid/badges/:badge 110 | * Get all badge instances of a certain badge earned by a user 111 | * e.g. [/users/0000-0001-5979-8713/badges/data_curation](http://badges.mozillascience.org/users/0000-0001-5979-8713/badges/data_curation) 112 | * GET /users/:orcid/badges/:badge/count 113 | * Get a count of all badge instances of a certain badge earned by a user 114 | * e.g. [/users/0000-0001-5979-8713/badges/data_curation/count](http://badges.mozillascience.org/users/0000-0001-5979-8713/badges/data_curation/count) 115 | * GET /papers/:doi1/:doi2/badges 116 | * Get all badge instances for a paper. 117 | * e.g. [/papers/10.1186/2047-217X-3-18/badges](http://badges.mozillascience.org/papers/10.1186/2047-217X-3-18/badges) 118 | * GET /papers/:doi1/:doi2/badges/count 119 | * Get a count of all badge instances for a paper. 120 | * e.g. [/papers/10.1186/2047-217X-3-18/badges/count](http://badges.mozillascience.org/papers/10.1186/2047-217X-3-18/badges/count) 121 | * GET /papers/:doi1/:doi2/badges/:badge 122 | * Get all badge instances of a certain badge for a paper. 123 | * e.g. [/papers/10.1186/2047-217X-3-18/badges/investigation](http://badges.mozillascience.org/papers/10.1186/2047-217X-3-18/badges/investigation) 124 | * GET /papers/:doi1/:doi2/badges/:badge/count 125 | * Get a count of all badge instances of a certain badge for a paper. 126 | * e.g. [/papers/10.1186/2047-217X-3-18/badges/investigation/count](http://badges.mozillascience.org/papers/10.1186/2047-217X-3-18/badges/investigation/count) 127 | * GET /papers/:doi1/:doi2/users/:orcid/badges 128 | * Get all badge instances earned by a user for a paper. 129 | * e.g. [/papers/10.1186/2047-217X-3-18/users/0000-0001-5979-8713/badges](http://badges.mozillascience.org/papers/10.1186/2047-217X-3-18/users/0000-0001-5979-8713/badges) 130 | * GET /papers/:doi1/:doi2/users/:orcid/badges/count 131 | * Get a count of all badge instances earned by a user for a paper. 132 | * e.g. [/papers/10.1186/2047-217X-3-18/users/0000-0001-5979-8713/badges/count](http://badges.mozillascience.org/papers/10.1186/2047-217X-3-18/users/0000-0001-5979-8713/badges/count) 133 | * GET /papers/:doi1/:doi2/users/:orcid/badges/:badge 134 | * Get all badge instances of a certain badge earned by a user for a paper. 135 | * e.g. [/papers/10.1186/2047-217X-3-18/users/0000-0001-5979-8713/badges/data_curation](http://badges.mozillascience.org/papers/10.1186/2047-217X-3-18/users/0000-0001-5979-8713/badges/data_curation) 136 | * GET /papers/:doi1/:doi2/users/:orcid/badges/:badge/count 137 | * Get a count of all badge instances of a certain badge earned by a user for a paper. 138 | * e.g. [/papers/10.1186/2047-217X-3-18/users/0000-0001-5979-8713/badges/data_curation/count](http://badges.mozillascience.org/papers/10.1186/2047-217X-3-18/users/0000-0001-5979-8713/badges/data_curation/count) 139 | * POST /papers/:doi1/:doi2/users/:orcid/badges/:badge 140 | * Issue a badge 141 | 142 | *** 143 | 144 | This work is a collaboration with publishers [BioMed Central](http://www.biomedcentral.com/) (BMC), [Ubiquity Press](http://www.ubiquitypress.com/) (UP) and the [Public Library of Science](http://www.plos.org/) (PLoS); the biomedical research foundation, [The Wellcome Trust](http://www.wellcome.ac.uk/); the software and technology firm [Digital Science](http://www.digital-science.com/); the registry of unique researcher identifiers, [ORCID](http://orcid.org/); and the [Mozilla Science Lab](http://mozillascience.org/). 145 | -------------------------------------------------------------------------------- /public/widgets/widget.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // eslint `exported` takes no effect because `node` is used as as environment. A custom rule for vars is defined here: 4 | /* eslint no-unused-vars: [2, { "varsIgnorePattern": "PaperBadgerWidget" }] */ 5 | /* global ActiveXObject: false */ 6 | /** 7 | * PaperBadger widget 8 | * Displays all badges for a DOI or ORCID. 9 | * 10 | * @param {Object} settings 11 | * @param {string=} settings.DOI 12 | * @param {string=} settings.ORCID 13 | * @param {string} settings.containerClass 14 | * @param {string=} settings.ORCIDWidgetType 15 | * @param {string=} settings.loaderText 16 | * @param {string=} settings.removeClass 17 | * @param {Function=} settings.clickCallback 18 | * @constructor 19 | */ 20 | var PaperBadgerWidget = function (settings) { 21 | /** 22 | * Widget container element 23 | * @type {Element} 24 | */ 25 | var containerElement; 26 | 27 | /** 28 | * Determines whether the ORCID widget shows the 29 | * author's name or DOIs 30 | * @type {string} 31 | */ 32 | var ORCIDWidgetType = 'default'; 33 | 34 | /** 35 | * String shown in the loading animation 36 | * @type {string} 37 | */ 38 | var loaderText = 'loading widget'; 39 | 40 | /** 41 | * Element for the loading animation 42 | * @type {Element} 43 | */ 44 | var loaderElement; 45 | 46 | /** 47 | * Contains the interval id for the animation loop 48 | * @type {Number} 49 | */ 50 | var loaderInterval; 51 | 52 | /** 53 | * Checks the settings given to the function and starts 54 | * the widget creation. 55 | */ 56 | var construct = function () { 57 | // fix for #135, all but the first forward slash of a DOI 58 | // should be encoded as %2F 59 | if (settings.hasOwnProperty('DOI')) { 60 | var tmp = settings.DOI.split('/'); 61 | settings.DOI = tmp[0] + '/' + tmp.slice(1).join('%2F'); 62 | } 63 | 64 | containerElement = document.getElementsByClassName(settings.containerClass)[0]; 65 | 66 | // Changes the ORCID widget type to DOI if the user requests it. 67 | if (settings.hasOwnProperty('ORCIDWidgetType') && settings.ORCIDWidgetType === 'DOI') { 68 | ORCIDWidgetType = 'DOI'; 69 | } 70 | 71 | if (settings.hasOwnProperty('loaderText')) { 72 | loaderText = settings.loaderText; 73 | } 74 | 75 | // we need a container element and either a DOI or an ORCID 76 | // proceed if these are available, throw an error if not 77 | if (containerElement && (settings.hasOwnProperty('DOI') || settings.hasOwnProperty('ORCID'))) { 78 | insertCSS(); 79 | startLoader(); 80 | loadWidgetContent(); 81 | } else { 82 | throw 'The settings object is incomplete. You need to supply an element id and either a DOI or an ORCID.'; 83 | } 84 | }; 85 | 86 | /** 87 | * Injects a style tag into the header containing the necessary 88 | * styles for the widget. 89 | */ 90 | function insertCSS() { 91 | var css = '.paper-badge {float: left; width: 10em; height: 20em; overflow: hidden; ' + 92 | 'border-top: 1px solid #ccc; height 15em; padding: 2%; margin-right: 1%; margin-top: 2%}' + 93 | '.paper-badge a {width: 100%; display: inline-block; font-size: 88%; line-height: 1.2; ' + 94 | 'color: #333; padding: 0.4em; cursor: pointer; text-decoration: none} .paper-badge ' + 95 | 'a.active {color: #fff; background: #7ab441; text-decoration: none} .paper-badge img ' + 96 | '{max-width: 8em; margin-left: 10%; margin-bottom: 1%} .paper-badges-hidden{display: none}'; 97 | 98 | var head = document.head || document.getElementsByTagName('head')[0]; 99 | var style = document.createElement('style'); 100 | style.type = 'text/css'; 101 | if (style.styleSheet) { 102 | style.styleSheet.cssText = css; 103 | } else { 104 | style.appendChild(document.createTextNode(css)); 105 | } 106 | 107 | head.appendChild(style); 108 | } 109 | 110 | /** 111 | * Inserts the loader element into the container element 112 | * and starts an animation loop. 113 | */ 114 | function startLoader() { 115 | loaderElement = document.createElement('div'); 116 | loaderElement.className = 'paperbadger-loader'; 117 | loaderElement.setAttribute('data-step', '4'); 118 | containerElement.appendChild(loaderElement); 119 | loaderMethod(); 120 | 121 | loaderInterval = setInterval(loaderMethod, 750); 122 | } 123 | 124 | /** 125 | * Animation loop for the loader text. Adds / removes 126 | * dots from the loader text. 127 | */ 128 | function loaderMethod() { 129 | var step = loaderElement.getAttribute('data-step'); 130 | var text = loaderText; 131 | 132 | if (step === '4') { 133 | step = 1; 134 | } else if (step === '1') { 135 | step = 2; 136 | text += '.'; 137 | } else if (step === '2') { 138 | step = 3; 139 | text += '..'; 140 | } else if (step === '3') { 141 | step = 4; 142 | text += '...'; 143 | } 144 | 145 | loaderElement.innerHTML = text; 146 | loaderElement.setAttribute('data-step', step); 147 | } 148 | 149 | /** 150 | * Stops the loader animation loop. 151 | */ 152 | var stopLoader = function () { 153 | clearInterval(loaderInterval); 154 | }; 155 | 156 | /** 157 | * Loads the widget content from the Badges API. 158 | */ 159 | function loadWidgetContent() { 160 | var url = getEndpoint(); 161 | var xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); 162 | 163 | xmlhttp.onreadystatechange = function () { 164 | if (xmlhttp.readyState === 4 && xmlhttp.status === 200) { 165 | stopLoader(); 166 | processAjaxResponse(xmlhttp.responseText); 167 | } 168 | }; 169 | 170 | xmlhttp.open('GET', url, true); 171 | xmlhttp.send(); 172 | } 173 | 174 | /** 175 | * Generates the endpoint url depending on the 176 | * widget settings. 177 | * 178 | * @returns {string} 179 | */ 180 | function getEndpoint() { 181 | var url = 'https://badges.mozillascience.org/'; 182 | 183 | if (settings.DOI) { 184 | url += 'papers/' + settings.DOI; 185 | } else if (settings.ORCID) { 186 | url += 'users/' + settings.ORCID; 187 | } else { 188 | throw 'You need to supply either a DOI or an ORCID.'; 189 | } 190 | 191 | url += '/badges'; 192 | 193 | return url; 194 | } 195 | 196 | /** 197 | * Processes the data from the Badges API so it 198 | * can be rendered in the next step. 199 | * 200 | * @param {string} json 201 | */ 202 | function processAjaxResponse(json) { 203 | var badgesJson = JSON.parse(json); 204 | var badges = {}; 205 | var isDOI = (settings.hasOwnProperty('DOI') && settings.DOI.length); 206 | 207 | for (var i = 0; i < badgesJson.length; i++) { 208 | var badgeId = badgesJson[i].badge.slug; 209 | if (!badges.hasOwnProperty(badgeId)) { 210 | badges[badgeId] = { 211 | name: badgesJson[i].badge.name, 212 | consumerDescription: badgesJson[i].badge.consumerDescription, 213 | imageUrl: badgesJson[i].badge.imageUrl, 214 | links: [] 215 | }; 216 | } 217 | 218 | // depending whether we have a DOI widget, an ORCID default widget 219 | // ( = displays names) or an ORCID display a list 220 | // of either authors or articles for a badge 221 | if (isDOI || ORCIDWidgetType === 'default') { 222 | badges[badgeId].links.push({ 223 | id: badgesJson[i].orcid, 224 | type: 'ORCID', 225 | taxonomy: badgeId, 226 | text: badgesJson[i].authorName, 227 | url: 'https://orcid.org/' + badgesJson[i].orcid 228 | }); 229 | } else { 230 | var localDOI = badgesJson[i].evidenceUrl.split('doi.org/')[1]; 231 | 232 | badges[badgeId].links.push({ 233 | id: localDOI, 234 | type: 'DOI', 235 | taxonomy: badgeId, 236 | text: localDOI, 237 | url: badgesJson[i].evidenceUrl.replace('http:', 'https:') 238 | }); 239 | } 240 | } 241 | 242 | renderBadges(badges); 243 | } 244 | 245 | /** 246 | * Generates and displays the HTML code for the received 247 | * badge data. 248 | * 249 | * @param {Object} badges 250 | */ 251 | function renderBadges(badges) { 252 | var i = 0; 253 | var html = ''; 254 | var numBadges = 0; 255 | 256 | for (var key in badges) { 257 | if (badges.hasOwnProperty(key)) { 258 | numBadges++; 259 | 260 | html += '
'; 261 | html += '' + badges[key].name + ''; 263 | 264 | for (i = 0; i < badges[key].links.length; i++) { 265 | html += '' + badges[key].links[i].text + ''; 269 | } 270 | 271 | html += '
'; 272 | } 273 | } 274 | 275 | containerElement.innerHTML = html; 276 | 277 | // if there were any badges and if the user supplied 278 | // removeClass in the settings, remove the class from 279 | // all elements on the page 280 | if (numBadges && settings.hasOwnProperty('removeClass') && settings.removeClass.length) { 281 | var elements = document.getElementsByClassName(settings.removeClass); 282 | 283 | for (i = 0; i < elements.length; i++) { 284 | var classes = elements[i].className.split(' '); 285 | for (var j = 0; j < classes.length; j++) { 286 | if (classes[j] === settings.removeClass) { 287 | classes.splice(j, 1); 288 | } 289 | } 290 | elements[i].className = classes.join(' '); 291 | } 292 | } 293 | 294 | // add mouseover and clickCallbacks for all links 295 | var links = document.getElementsByClassName('paper-badger-link'); 296 | for (i = 0; i < links.length - 1; i++) { 297 | links[i].addEventListener('mouseover', mouseOverMethod); 298 | links[i].addEventListener('click', clickCallbackMethod); 299 | } 300 | } 301 | 302 | /** 303 | * Marks all occurrences of an author on mouse over 304 | * by adding a .active CSS class to the link element. 305 | */ 306 | function mouseOverMethod() { 307 | var id = this.getAttribute('data-id'); 308 | var links = document.getElementsByClassName('paper-badger-link'); 309 | 310 | for (var i = 0; i < links.length; i++) { 311 | var j = 0; 312 | var classes = links[i].className.split(' '); 313 | 314 | if (links[i].getAttribute('data-id') === id) { 315 | var found = false; 316 | for (j = 0; j < classes.length - 1; j++) { 317 | if (classes[j] === 'active') { 318 | found = true; 319 | break; 320 | } 321 | } 322 | 323 | if (!found) { 324 | classes.push('active'); 325 | } 326 | } else { 327 | for (j = classes.length; j > 0; j--) { 328 | if (classes[j] === 'active') { 329 | classes.splice(j, 1); 330 | } 331 | } 332 | } 333 | 334 | links[i].className = classes.join(' '); 335 | } 336 | } 337 | 338 | /** 339 | * Calls the clickCallback method if the user supplied one 340 | */ 341 | function clickCallbackMethod() { 342 | if (settings.hasOwnProperty('clickCallback') && settings.clickCallback) { 343 | var data = { 344 | taxonomy: this.getAttribute('data-taxonomy') 345 | }; 346 | 347 | if (this.getAttribute('data-type') === 'DOI') { 348 | data.doi = this.getAttribute('data-id'); 349 | } else if (this.getAttribute('data-type') === 'ORCID') { 350 | data.orcid = this.getAttribute('data-id'); 351 | } 352 | 353 | settings.clickCallback(data); 354 | } 355 | } 356 | 357 | construct(); 358 | }; 359 | -------------------------------------------------------------------------------- /bin/load_badges.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- Insert Contributorship Badges data into `images` table 3 | -- see: http://dictionary.casrai.org/Contributor_Roles 4 | -- desc images; 5 | -- +----------+--------------+------+-----+---------+----------------+ 6 | -- | Field | Type | Null | Key | Default | Extra | 7 | -- +----------+--------------+------+-----+---------+----------------+ 8 | -- | id | int(11) | NO | PRI | NULL | auto_increment | 9 | -- | slug | varchar(255) | NO | UNI | NULL | | 10 | -- | url | varchar(255) | YES | | NULL | | 11 | -- | mimetype | varchar(255) | YES | | NULL | | 12 | -- | data | longblob | YES | | NULL | | 13 | -- +----------+--------------+------+-----+---------+----------------+ 14 | -- 15 | 16 | LOCK TABLES `images` WRITE; 17 | /*!40000 ALTER TABLE `images` DISABLE KEYS */; 18 | INSERT INTO `images` VALUES 19 | (NULL,'conceptualization-img','https://s3.amazonaws.com/mozillascience/badges/conceptualization.png',NULL,NULL), 20 | (NULL,'data_curation-img','https://s3.amazonaws.com/mozillascience/badges/data_curation.png',NULL,NULL), 21 | (NULL,'formal_analysis-img','https://s3.amazonaws.com/mozillascience/badges/formal_analysis.png',NULL,NULL), 22 | (NULL,'funding-img','https://s3.amazonaws.com/mozillascience/badges/funding.png',NULL,NULL), 23 | (NULL,'investigation-img','https://s3.amazonaws.com/mozillascience/badges/investigation.png',NULL,NULL), 24 | (NULL,'methodology-img','https://s3.amazonaws.com/mozillascience/badges/methodology.png',NULL,NULL), 25 | (NULL,'project_administration-img','https://s3.amazonaws.com/mozillascience/badges/project_administration.png',NULL,NULL), 26 | (NULL,'resources-img','https://s3.amazonaws.com/mozillascience/badges/resources.png',NULL,NULL), 27 | (NULL,'software-img','https://s3.amazonaws.com/mozillascience/badges/software.png',NULL,NULL), 28 | (NULL,'supervision-img','https://s3.amazonaws.com/mozillascience/badges/supervision.png',NULL,NULL), 29 | (NULL,'validation-img','https://s3.amazonaws.com/mozillascience/badges/testing.png',NULL,NULL), 30 | (NULL,'data_visualization-img','https://s3.amazonaws.com/mozillascience/badges/data_visualization.png',NULL,NULL), 31 | (NULL,'writing_initial-img','https://s3.amazonaws.com/mozillascience/badges/writing_initial.png',NULL,NULL), 32 | (NULL,'writing_review-img','https://s3.amazonaws.com/mozillascience/badges/writing_review.png',NULL,NULL); 33 | /*!40000 ALTER TABLE `images` ENABLE KEYS */; 34 | UNLOCK TABLES; 35 | 36 | -- 37 | -- Insert Contributorship Badges data into `badges` table 38 | -- see: http://dictionary.casrai.org/Contributor_Roles 39 | -- desc badges; 40 | -- +---------------------+--------------------------------------------+------+-----+-------------------+----------------+ 41 | -- | Field | Type | Null | Key | Default | Extra | 42 | -- +---------------------+--------------------------------------------+------+-----+-------------------+----------------+ 43 | -- | id | int(11) | NO | PRI | NULL | auto_increment | 44 | -- | slug | varchar(255) | NO | UNI | NULL | | 45 | -- | name | varchar(255) | NO | | NULL | | 46 | -- | strapline | varchar(140) | YES | | NULL | | 47 | -- | earnerDescription | text | NO | | NULL | | 48 | -- | consumerDescription | text | NO | | NULL | | 49 | -- | issuerUrl | varchar(255) | YES | | NULL | | 50 | -- | rubricUrl | varchar(255) | YES | | NULL | | 51 | -- | criteriaUrl | varchar(255) | NO | | NULL | | 52 | -- | timeValue | int(11) | YES | | NULL | | 53 | -- | timeUnits | enum('minutes','hours','days','weeks') | YES | | NULL | | 54 | -- | limit | int(11) | YES | | NULL | | 55 | -- | unique | tinyint(1) | NO | | 0 | | 56 | -- | archived | tinyint(1) | NO | | 0 | | 57 | -- | created | timestamp | NO | | CURRENT_TIMESTAMP | | 58 | -- | imageId | int(11) | NO | | NULL | | 59 | -- | programId | int(11) | YES | | NULL | | 60 | -- | issuerId | int(11) | YES | | NULL | | 61 | -- | systemId | int(11) | YES | | NULL | | 62 | -- | type | varchar(255) | NO | | NULL | | 63 | -- | evidenceType | enum('URL','Text','Photo','Video','Sound') | YES | | NULL | | 64 | -- +---------------------+--------------------------------------------+------+-----+-------------------+----------------+ 65 | -- 66 | 67 | LOCK TABLES `badges` WRITE; 68 | /*!40000 ALTER TABLE `badges` DISABLE KEYS */; 69 | INSERT INTO `badges` VALUES 70 | (NULL,'conceptualization','Conceptualization',NULL,'Ideas; formulation or evolution of overarching research goals and aims.','Ideas; formulation or evolution of overarching research goals and aims.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 71 | (NULL,'data_curation','Data curation',NULL,'Management activities to annotate (produce metadata), scrub data and maintain research data (including software code, where it is necessary for interpreting the data itself) for initial use and later re-use.','Management activities to annotate (produce metadata), scrub data and maintain research data (including software code, where it is necessary for interpreting the data itself) for initial use and later re-use.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 72 | (NULL,'formal_analysis','Formal analysis',NULL,'Application of statistical, mathematical, computational, or other formal techniques to analyse or synthesize study data.','Application of statistical, mathematical, computational, or other formal techniques to analyse or synthesize study data.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 73 | (NULL,'funding','Funding acquisition',NULL,'Acquisition of the financial support for the project leading to this publication.','Acquisition of the financial support for the project leading to this publication.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 74 | (NULL,'investigation','Investigation',NULL,'Conducting a research and investigation process, specifically performing the experiments, or data/evidence collection.','Conducting a research and investigation process, specifically performing the experiments, or data/evidence collection.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 75 | (NULL,'methodology','Methodology',NULL,'Development or design of methodology; creation of models.','Development or design of methodology; creation of models.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 76 | (NULL,'project_administration','Project administration',NULL,'Management and coordination responsibility for the research activity planning and execution.','Management and coordination responsibility for the research activity planning and execution.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 77 | (NULL,'resources','Resources',NULL,'Provision of study materials, reagents, materials, patients, laboratory samples, animals, instrumentation, computing resources, or other analysis tools.','Provision of study materials, reagents, materials, patients, laboratory samples, animals, instrumentation, computing resources, or other analysis tools.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 78 | (NULL,'software','Software',NULL,'Programming, software development; designing computer programs; implementation of the computer code and supporting algorithms; testing of existing code components.','Programming, software development; designing computer programs; implementation of the computer code and supporting algorithms; testing of existing code components.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 79 | (NULL,'supervision','Supervision',NULL,'Oversight and leadership responsibility for the research activity planning and execution, including mentorship external to the core team.','Oversight and leadership responsibility for the research activity planning and execution, including mentorship external to the core team.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 80 | (NULL,'validation','Validation',NULL,'Verification, whether as a part of the activity or separate, of the overall replication/reproducibility of results/experiments and other research outputs.','Verification, whether as a part of the activity or separate, of the overall replication/reproducibility of results/experiments and other research outputs.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 81 | (NULL,'data_visualization','Data visualization',NULL,'Preparation, creation and/or presentation of the published work, specifically visualization/data presentation.','Preparation, creation and/or presentation of the published work, specifically visualization/data presentation.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 82 | (NULL,'writing_initial','Writing - original draft',NULL,'Preparation, creation and/or presentation of the published work, specifically writing the initial draft (including substantive translation).','Preparation, creation and/or presentation of the published work, specifically writing the initial draft (including substantive translation).',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL), 83 | (NULL,'writing_review','Writing - review & editing',NULL,'Preparation, creation and/or presentation of the published work by those from the original research group, specifically critical review, commentary or revision – including pre- or post-publication stages.','Preparation, creation and/or presentation of the published work by those from the original research group, specifically critical review, commentary or revision – including pre- or post-publication stages.',NULL,NULL,'http://dictionary.casrai.org/Contributor_Roles',NULL,NULL,NULL,0,0,NULL,1,NULL,NULL,1,'contributor',NULL); 84 | /*!40000 ALTER TABLE `badges` ENABLE KEYS */; 85 | UNLOCK TABLES; 86 | 87 | -- 88 | -- Insert correct imageId in badges table (id autoincremented on insert) 89 | -- 90 | 91 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'conceptualization-img') WHERE slug = 'conceptualization'; 92 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'data_curation-img') WHERE slug = 'data_curation'; 93 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'formal_analysis-img') WHERE slug = 'formal_analysis'; 94 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'funding-img') WHERE slug = 'funding'; 95 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'investigation-img') WHERE slug = 'investigation'; 96 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'methodology-img') WHERE slug = 'methodology'; 97 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'project_administration-img') WHERE slug = 'project_administration'; 98 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'resources-img') WHERE slug = 'resources'; 99 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'software-img') WHERE slug = 'software'; 100 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'supervision-img') WHERE slug = 'supervision'; 101 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'validation-img') WHERE slug = 'validation'; 102 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'data_visualization-img') WHERE slug = 'data_visualization'; 103 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'writing_initial-img') WHERE slug = 'writing_initial'; 104 | UPDATE `badges` SET imageId = (SELECT id FROM images where slug = 'writing_review-img') WHERE slug = 'writing_review'; 105 | 106 | -- 107 | -- Create system badgekit for MozillaScience. Assign all badges to this system. 108 | -- 109 | 110 | INSERT INTO systems (slug, name, url) VALUES ('badgekit', 'MozillaScience', 'http://mozillascience.org/'); 111 | UPDATE `badges` SET systemId = (SELECT id FROM systems where slug = 'badgekit'); 112 | -------------------------------------------------------------------------------- /public/css/vendor/pure-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v^3.0 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | --------------------------------------------------------------------------------