diff --git a/source/actions/getPublicationFromDoi.js b/source/actions/getPublicationFromDoi.js index bfc551e..1024e33 100644 --- a/source/actions/getPublicationFromDoi.js +++ b/source/actions/getPublicationFromDoi.js @@ -21,12 +21,13 @@ export function fetchPublicationFromDoi(doi) { }; } -export function resolvePublicationFromDoi(doi, crossrefMessage, bibtexRequestId) { +export function resolvePublicationFromDoi(doi, crossrefMessage, bibtexRequestId, timestamp) { return { type: RESOLVE_PUBLICATION_FROM_DOI, doi, crossrefMessage, bibtexRequestId, + timestamp, }; } diff --git a/source/actions/manageMenu.js b/source/actions/manageMenu.js index 24b705f..1e4a3dd 100644 --- a/source/actions/manageMenu.js +++ b/source/actions/manageMenu.js @@ -222,12 +222,10 @@ export function bibtexToPublications(bibtex) { ++position; switch (status) { case 'root': - if (character === '%') { - status = 'comment'; - } else if (character === '@') { + if (character === '@') { status = 'type'; } else if (/\S/.test(character)) { - throwError(character); + status = 'comment'; } break; case 'comment': diff --git a/source/actions/manageScholarPage.js b/source/actions/manageScholarPage.js index dc4a43d..2302b75 100644 --- a/source/actions/manageScholarPage.js +++ b/source/actions/manageScholarPage.js @@ -201,7 +201,7 @@ export function resolveHtml(url, text) { let metadata = ''; for (let child of metadataCandidates[0].children) { if (child.type === 'text') { - metadata += child.data; + metadata += child.data.replace(/ /g, ' '); } else if ( child.type === 'tag' && child.name === 'a' diff --git a/source/actors/manageCrossref.js b/source/actors/manageCrossref.js index cb533b3..b12bd18 100644 --- a/source/actors/manageCrossref.js +++ b/source/actors/manageCrossref.js @@ -117,7 +117,8 @@ export default function manageCrossref(store) { store.dispatch(resolvePublicationFromDoi( doi, json.message, - Array.from(bytes).map(byte => byte.toString(16)).join('') + Array.from(bytes).map(byte => byte.toString(16)).join(''), + new Date().getTime() )); }) .catch(error => { diff --git a/source/components/ProgressBar.js b/source/components/ProgressBar.js index dfe1143..0af4db1 100644 --- a/source/components/ProgressBar.js +++ b/source/components/ProgressBar.js @@ -1,5 +1,4 @@ import React from 'react' -import Radium from 'radium' import PropTypes from 'prop-types' class ProgressBar extends React.Component { @@ -45,4 +44,4 @@ class ProgressBar extends React.Component { } } -export default Radium(ProgressBar); +export default ProgressBar; diff --git a/source/containers/AddDoi.js b/source/containers/AddDoi.js index dceb3cb..f1ba343 100644 --- a/source/containers/AddDoi.js +++ b/source/containers/AddDoi.js @@ -76,7 +76,7 @@ class AddDoi extends React.Component { }, }} onClick={() => { - this.props.dispatch(publicationFromDoi(this.state.value.match(doiPattern)[1])); + this.props.dispatch(publicationFromDoi(this.state.value.match(doiPattern)[1], new Date().getTime())); this.setState({valid: false, value: ''}); }} > diff --git a/source/containers/Graph.js b/source/containers/Graph.js index be540be..3dbca1a 100644 --- a/source/containers/Graph.js +++ b/source/containers/Graph.js @@ -1,5 +1,4 @@ import React from 'react' -import Radium from 'radium' import {connect} from 'react-redux' import PropTypes from 'prop-types' import { @@ -41,6 +40,10 @@ class Graph extends React.Component { this.svg = null; this.nodes = []; this.edges = []; + this.d3Node = null; + this.d3Edge = null; + this.zoom = null; + this.reheatSimulation = false; this.simulation = d3.forceSimulation(this.nodes) .stop() .force('link', d3.forceLink(this.edges).distance(80).strength(1).id(node => node.doi)) @@ -68,10 +71,6 @@ class Graph extends React.Component { } }) ; - this.d3Node = null; - this.d3Edge = null; - this.zoom = null; - this.reheatSimulation = false; } componentWillReceiveProps(nextProps) { @@ -388,6 +387,7 @@ class Graph extends React.Component { }) ; this.svg.call(this.zoom); + this.zoom.scaleTo(this.svg, 2 ** (this.props.zoom / 20)); this.d3Node = this.svg.selectAll('.node'); this.d3Edge = this.svg.selectAll('.edge'); this.simulation.alpha(0); @@ -466,4 +466,4 @@ export default connect( colors: state.colors, }; } -)(Radium(Graph)); +)(Graph); diff --git a/source/containers/GraphWithToolbar.js b/source/containers/GraphWithToolbar.js index 893c836..5462d54 100644 --- a/source/containers/GraphWithToolbar.js +++ b/source/containers/GraphWithToolbar.js @@ -86,7 +86,6 @@ class GraphWithToolbar extends React.Component { xOffset={0} yOffset={0} /> -
state, - }) + }), + (state = {}, action) => { + if (process.env.ORIGAMI_ENV === 'development') { + console.log(action.type, action, state); + } + return state; + } )(state, action); } diff --git a/source/reducers/publications.js b/source/reducers/publications.js index 127c633..bcf1592 100644 --- a/source/reducers/publications.js +++ b/source/reducers/publications.js @@ -24,6 +24,7 @@ import { PUBLICATION_STATUS_IN_COLLECTION, PAGE_TYPE_INITIALIZE, } from '../constants/enums' +import {isOlderThan} from '../actions/managePublication' export default function publications(state = new Map(), action, appState) { switch (action.type) { @@ -98,9 +99,24 @@ export default function publications(state = new Map(), action, appState) { newState.set(action.doi, { ...state.get(action.doi), title: action.crossrefMessage.title[0] == null ? '' : action.crossrefMessage.title[0], - authors: action.crossrefMessage.author.map(author => `${author.given} ${author.family}`), + authors: action.crossrefMessage.author.filter( + author => author.given != null || author.family != null + ).map( + author => { + if (author.given == null) { + return author.family; + } + if (author.family == null) { + return author.given; + } + return `${author.given} ${author.family}`; + } + ), journal: action.crossrefMessage.publisher, - date: action.crossrefMessage.created['date-parts'][0], + date: (isOlderThan(action.crossrefMessage.created['date-parts'][0], action.crossrefMessage.issued['date-parts'][0]) ? + action.crossrefMessage.created['date-parts'][0] + : action.crossrefMessage.issued['date-parts'][0] + ), status: PUBLICATION_STATUS_IN_COLLECTION, updated: action.timestamp, validating: false, @@ -144,17 +160,18 @@ export default function publications(state = new Map(), action, appState) { if (!state.has(action.doi) || state.get(action.doi).status !== PUBLICATION_STATUS_IN_COLLECTION) { return state; } + const doisToRemove = new Set([ + ...state.get(action.doi).citers.filter(citer => state.get(citer).status === PUBLICATION_STATUS_DEFAULT), + action.doi, + ]); const newState = new Map(state); newState.set(action.doi, { ...state.get(action.doi), status: PUBLICATION_STATUS_DEFAULT, bibtex: null, locked: false, + citers: [], }); - const doisToRemove = new Set([ - ...state.get(action.doi).citers.filter(citer => state.get(citer).status === PUBLICATION_STATUS_DEFAULT), - action.doi, - ]); for (const publication of newState.values()) { if (publication.status === PUBLICATION_STATUS_IN_COLLECTION) { for (const citer of publication.citers) { @@ -246,17 +263,47 @@ export default function publications(state = new Map(), action, appState) { newState.set(doi, { ...newState.get(doi), title: action.crossrefMessage.title[0], - authors: action.crossrefMessage.author.map(author => `${author.given ? `${author.given} ` : ''}${author.family}`), + authors: action.crossrefMessage.author.filter( + author => author.given != null || author.family != null + ).map( + author => { + if (author.given == null) { + return author.family; + } + if (author.family == null) { + return author.given; + } + return `${author.given} ${author.family}`; + } + ), journal: action.crossrefMessage.publisher, - date: action.crossrefMessage.created['date-parts'][0], + date: (isOlderThan(action.crossrefMessage.created['date-parts'][0], action.crossrefMessage.issued['date-parts'][0]) ? + action.crossrefMessage.created['date-parts'][0] + : action.crossrefMessage.issued['date-parts'][0] + ), }); } else { newState.set(doi, { status: PUBLICATION_STATUS_DEFAULT, title: action.crossrefMessage.title[0], - authors: action.crossrefMessage.author.map(author => `${author.given ? `${author.given} ` : ''}${author.family}`), + authors: action.crossrefMessage.author.filter( + author => author.given != null || author.family != null + ).map( + author => { + if (author.given == null) { + return author.family; + } + if (author.family == null) { + return author.given; + } + return `${author.given} ${author.family}`; + } + ), journal: action.crossrefMessage.publisher, - date: action.crossrefMessage.created['date-parts'][0], + date: (isOlderThan(action.crossrefMessage.created['date-parts'][0], action.crossrefMessage.issued['date-parts'][0]) ? + action.crossrefMessage.created['date-parts'][0] + : action.crossrefMessage.issued['date-parts'][0] + ), citers: [], updated: null, selected: false, @@ -289,18 +336,48 @@ export default function publications(state = new Map(), action, appState) { ...newState.get(doi), status: PUBLICATION_STATUS_IN_COLLECTION, title: action.crossrefMessage.title[0], - authors: action.crossrefMessage.author.map(author => `${author.given ? `${author.given} ` : ''}${author.family}`), + authors: action.crossrefMessage.author.filter( + author => author.given != null || author.family != null + ).map( + author => { + if (author.given == null) { + return author.family; + } + if (author.family == null) { + return author.given; + } + return `${author.given} ${author.family}`; + } + ), journal: action.crossrefMessage.publisher, - date: action.crossrefMessage.created['date-parts'][0], + date: (isOlderThan(action.crossrefMessage.created['date-parts'][0], action.crossrefMessage.issued['date-parts'][0]) ? + action.crossrefMessage.created['date-parts'][0] + : action.crossrefMessage.issued['date-parts'][0] + ), updated: action.timestamp, }); } else { newState.set(doi, { status: PUBLICATION_STATUS_IN_COLLECTION, title: action.crossrefMessage.title[0], - authors: action.crossrefMessage.author.map(author => `${author.given ? `${author.given} ` : ''}${author.family}`), + authors: action.crossrefMessage.author.filter( + author => author.given != null || author.family != null + ).map( + author => { + if (author.given == null) { + return author.family; + } + if (author.family == null) { + return author.given; + } + return `${author.given} ${author.family}`; + } + ), journal: action.crossrefMessage.publisher, - date: action.crossrefMessage.created['date-parts'][0], + date: (isOlderThan(action.crossrefMessage.created['date-parts'][0], action.crossrefMessage.issued['date-parts'][0]) ? + action.crossrefMessage.created['date-parts'][0] + : action.crossrefMessage.issued['date-parts'][0] + ), citers: [], updated: action.timestamp, selected: false, diff --git a/source/reducers/scholar.js b/source/reducers/scholar.js index 7682200..5140058 100644 --- a/source/reducers/scholar.js +++ b/source/reducers/scholar.js @@ -3,6 +3,7 @@ import { REMOVE_PUBLICATION, UPDATE_PUBLICATION, UPDATE_ALL_PUBLICATIONS, + PUBLICATION_FROM_DOI, RESOLVE_PUBLICATION_FROM_DOI, FETCH_SCHOLAR_PAGE, RESOLVE_SCHOLAR_INITIAL_PAGE, @@ -58,7 +59,7 @@ export default function scholar( url: `https://scholar.google.com/scholar?hl=en&q=${encodeURIComponent(action.doi)}`, }], }; - case RESOLVE_PUBLICATION_FROM_IMPORTED_METADATA: + case RESOLVE_PUBLICATION_FROM_IMPORTED_METADATA: { const doi = action.crossrefMessage.DOI.toLowerCase(); if (appState.publications.has(doi) && appState.publications.get(doi).status !== PUBLICATION_STATUS_DEFAULT) { return state; @@ -71,6 +72,7 @@ export default function scholar( url: `https://scholar.google.com/scholar?hl=en&q=${encodeURIComponent(doi)}`, }], }; + } case REMOVE_PUBLICATION: return { ...state, @@ -120,6 +122,20 @@ export default function scholar( }), ], } + case PUBLICATION_FROM_DOI: { + const doi = action.doi.toLowerCase(); + if (!appState.publications.has(doi) || appState.publications.get(doi).status !== PUBLICATION_STATUS_DEFAULT) { + return state; + } + return { + ...state, + pages: [...state.pages, { + type: PAGE_TYPE_INITIALIZE, + doi, + url: `https://scholar.google.com/scholar?hl=en&q=${encodeURIComponent(doi)}`, + }], + }; + } case RESOLVE_PUBLICATION_FROM_DOI: if (!appState.publications.has(action.doi) || appState.publications.get(action.doi).status !== PUBLICATION_STATUS_UNVALIDATED) { return state; diff --git a/source/reducers/warnings.js b/source/reducers/warnings.js index 82cf983..60ed91c 100644 --- a/source/reducers/warnings.js +++ b/source/reducers/warnings.js @@ -1,5 +1,5 @@ import { - RESOLVE_PUBLICATION_FROM_DOI, + PUBLICATION_FROM_DOI, REJECT_PUBLICATION_FROM_DOI, REJECT_SCHOLAR_CITERS_PAGE, REJECT_SCHOLAR_CITER_PARSING, @@ -11,11 +11,17 @@ import { REMOVE_WARNING, REMOVE_ALL_WARNINGS, } from '../constants/actionTypes' +import { + PUBLICATION_STATUS_UNVALIDATED, + PUBLICATION_STATUS_DEFAULT, + PUBLICATION_STATUS_IN_COLLECTION, +} from '../constants/enums' export default function warnings(state = {list: [], hash: 0}, action, appState) { switch (action.type) { - case RESOLVE_PUBLICATION_FROM_DOI: - if (!appState.publications.has(action.doi) || !appState.publications.get(action.doi).isInCollection) { + case PUBLICATION_FROM_DOI: { + const doi = action.doi.toLowerCase(); + if (!appState.publications.has(doi) || appState.publications.get(doi).status === PUBLICATION_STATUS_DEFAULT) { return state; } return { @@ -23,13 +29,14 @@ export default function warnings(state = {list: [], hash: 0}, action, appState) list: [ { title: 'The publication was not added to the collection', - subtitle: `${action.doi} is already in the collection`, + subtitle: `${doi} is already in the collection`, level: 'warning', }, ...state.list, ], hash: state.hash + 1, }; + } case REJECT_PUBLICATION_FROM_DOI: return { ...state, diff --git a/webpack.common.js b/webpack.common.js index db04e43..a856c1b 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -7,13 +7,13 @@ const recursive = require(`${__dirname}/recursive`); module.exports = { /// package uses electron-packager to convert the output of webpack to actual apps. - package: (all, callback) => { + package: (production, callback) => { try { fs.mkdirSync(`${__dirname}/build/origami`); } catch (error) {} - for (const sourceFileToCopy of ['main.js', 'package.json']) { - fs.copyFileSync(`${__dirname}/source/${sourceFileToCopy}`, `${__dirname}/build/origami/${sourceFileToCopy}`); - } + fs.copyFileSync(`${__dirname}/source/package.json`, `${__dirname}/build/origami/package.json`); + fs.writeFileSync(`${__dirname}/build/origami/main.js`, `process.env.ORIGAMI_ENV = '${production ? 'production' : 'development'}';\n`); + fs.appendFileSync(`${__dirname}/build/origami/main.js`, fs.readFileSync(`${__dirname}/source/main.js`)); fs.copyFileSync(`${__dirname}/themes/default.json`, `${__dirname}/build/origami/colors.json`); try { fs.mkdirSync(`${__dirname}/build/origami/fonts`); @@ -24,7 +24,7 @@ module.exports = { child_process.execSync('npm install', {cwd: `${__dirname}/build/origami`}, {stdio: 'inherit'}); packager({ dir: `${__dirname}/build/origami`, - all: all, + all: production, out: `${__dirname}/build`, overwrite: true, icon: `${__dirname}/icons/origami`, @@ -59,7 +59,7 @@ module.exports = { exclude: /node_modules/, query: { presets: [ - ['env', {'targets': {'electron': '1.7.6', 'browsers': 'last 2 versions'}}], + ['env', {'targets': {'electron': '1.7.6'}}], 'react', ], plugins: [ diff --git a/webpack.prod.js b/webpack.prod.js index d7fc819..88cc469 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -15,9 +15,7 @@ module.exports = merge(common.configuration, { }); }, new webpack.DefinePlugin({ - 'process.env': { - 'NODE_ENV': JSON.stringify('production'), - }, + 'process.env.NODE_ENV': JSON.stringify('production'), }), new HtmlWebpackPlugin({ template: './source/index.ejs',