diff --git a/src/actions/index.js b/src/actions/index.js
index 590f56d..1264aa2 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -81,8 +81,21 @@ export function fetchDomain () {
.catch(handleError('tags'))
}
- return Promise.all([eventPromise, catPromise, narPromise,
- sitesPromise, tagsPromise])
+ let sourcesPromise = Promise.resolve([])
+ if (process.env.features.USE_SOURCES) {
+ sourcesPromise = fetch(SOURCES_URL)
+ .then(response => response.json())
+ .catch(handleError('sources'))
+ }
+
+ return Promise.all([
+ eventPromise,
+ catPromise,
+ narPromise,
+ sitesPromise,
+ tagsPromise,
+ sourcesPromise
+ ])
.then(response => {
dispatch(toggleFetchingDomain())
const result = {
@@ -91,6 +104,7 @@ export function fetchDomain () {
narratives: response[2],
sites: response[3],
tags: response[4],
+ sources: response[5],
notifications
}
return result
@@ -114,30 +128,19 @@ export const UPDATE_DOMAIN = 'UPDATE_DOMAIN'
export function updateDomain(domain) {
return {
type: UPDATE_DOMAIN,
- domain: {
- events: domain.events,
- categories: domain.categories,
- tags: domain.tags,
- sites: domain.sites,
- narratives: domain.narratives,
- notifications: domain.notifications
- }
+ domain
}
}
-export function fetchSelected(selected) {
- if (!selected || !selected.length || selected.length === 0) {
- return updateSelected([])
- }
+export function fetchSource(source) {
return dispatch => {
- dispatch(updateSelected(selected))
if (!SOURCES_URL) {
dispatch(fetchSourceError('No source extension specified.'))
} else {
dispatch(toggleFetchingSources())
- fetch(SOURCES_URL)
+ fetch(`${SOURCES_URL}`)
.then(response => {
if (!response.ok) {
throw new Error('No sources are available at the URL specified in the config specified.')
diff --git a/src/components/Card.jsx b/src/components/Card.jsx
index 520b7b9..b85a144 100644
--- a/src/components/Card.jsx
+++ b/src/components/Card.jsx
@@ -86,17 +86,17 @@ class Card extends React.Component {
)
}
- renderSource() {
- return (
+ renderSources() {
+ return this.props.event.sources.map(source => (
- )
+ ))
}
// NB: should be internaionalized.
@@ -145,7 +145,7 @@ class Card extends React.Component {
return (
{this.renderTags()}
- {this.renderSource()}
+ {this.renderSources()}
{this.renderNarrative()}
)
diff --git a/src/components/CardStack.jsx b/src/components/CardStack.jsx
index 98185be..bb47d71 100644
--- a/src/components/CardStack.jsx
+++ b/src/components/CardStack.jsx
@@ -89,7 +89,7 @@ class CardStack extends React.Component {
function mapStateToProps(state) {
return {
- selected: state.app.selected,
+ selected: selectors.selectSelected(state),
sourceError: state.app.errors.source,
language: state.app.language,
isCardstack: state.app.flags.isCardstack,
diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx
index 744b990..479be0a 100644
--- a/src/components/Dashboard.jsx
+++ b/src/components/Dashboard.jsx
@@ -32,7 +32,7 @@ class Dashboard extends React.Component {
componentDidMount() {
if (!this.props.app.isMobile) {
this.props.actions.fetchDomain()
- .then((domain) => this.props.actions.updateDomain(domain));
+ .then(domain => this.props.actions.updateDomain(domain));
}
}
@@ -51,7 +51,7 @@ class Dashboard extends React.Component {
let eventsToSelect = selected.map(event => this.getEventById(event.id));
eventsToSelect = eventsToSelect.sort((a, b) => parseDate(a.timestamp) - parseDate(b.timestamp))
- this.props.actions.fetchSelected(eventsToSelect)
+ this.props.actions.updateSelected(eventsToSelect)
}
}
diff --git a/src/components/presentational/CardSource.js b/src/components/presentational/CardSource.js
index 95fdb06..fe67652 100644
--- a/src/components/presentational/CardSource.js
+++ b/src/components/presentational/CardSource.js
@@ -3,23 +3,27 @@ import Spinner from './Spinner'
import copy from '../../js/data/copy.json'
+function renderSource(source) {
+ return source.error ? (
+
+
{source_lang}:
{renderContent()}
diff --git a/src/reducers/schema/eventSchema.js b/src/reducers/schema/eventSchema.js
index 9dd4f90..b497841 100644
--- a/src/reducers/schema/eventSchema.js
+++ b/src/reducers/schema/eventSchema.js
@@ -12,7 +12,7 @@ const eventSchema = Joi.object().keys({
type: Joi.string().allow(''),
category: Joi.string().required(),
narrative: Joi.string().allow(''),
- source: Joi.string().allow(''),
+ sources: Joi.array(),
tags: Joi.string().allow(''),
comments: Joi.string().allow(''),
timestamp: Joi.string().required(),
diff --git a/src/reducers/schema/sourceSchema.js b/src/reducers/schema/sourceSchema.js
new file mode 100644
index 0000000..3dc17dd
--- /dev/null
+++ b/src/reducers/schema/sourceSchema.js
@@ -0,0 +1,17 @@
+import Joi from 'joi';
+
+const sourceSchema = Joi.object().keys({
+ id: Joi.string().required(),
+ path: Joi.string().required(),
+ type: Joi.string().allow(''),
+ affil_1: Joi.string().allow(''),
+ affil_2: Joi.string().allow(''),
+ url: Joi.string().allow(''),
+ title: Joi.string().allow(''),
+ parent: Joi.string().allow(''),
+ author: Joi.string().allow(''),
+ date: Joi.string().allow(''),
+ notes: Joi.string().allow('')
+});
+
+export default sourceSchema;
diff --git a/src/reducers/utils/validators.js b/src/reducers/utils/validators.js
index ecc8f87..42bf3d3 100644
--- a/src/reducers/utils/validators.js
+++ b/src/reducers/utils/validators.js
@@ -1,9 +1,10 @@
import Joi from 'joi';
-import eventSchema from '../schema/eventSchema.js';
-import categorySchema from '../schema/categorySchema.js';
-import siteSchema from '../schema/siteSchema.js';
-import narrativeSchema from '../schema/narrativeSchema.js';
+import eventSchema from '../schema/eventSchema';
+import categorySchema from '../schema/categorySchema';
+import siteSchema from '../schema/siteSchema';
+import narrativeSchema from '../schema/narrativeSchema';
+import sourceSchema from '../schema/sourceSchema'
import { capitalize } from './helpers.js';
@@ -59,6 +60,7 @@ export function validateDomain (domain) {
categories: [],
sites: [],
narratives: [],
+ sources: {},
notifications: domain.notifications,
tags: {}
}
@@ -68,33 +70,50 @@ export function validateDomain (domain) {
categories: [],
sites: [],
narratives: [],
+ sources: [],
}
- function validateItem(item, domainClass, schema) {
+ function validateArrayItem(item, domainKey, schema) {
const result = Joi.validate(item, schema);
if (result.error !== null) {
const id = item.id || '-';
- const domainStr = capitalize(domainClass);
+ const domainStr = capitalize(domainKey);
const error = makeError(domainStr, id, result.error.message);
- discardedDomain[domainClass].push(Object.assign(item, { error }));
+ discardedDomain[domainKey].push(Object.assign(item, { error }));
} else {
- sanitizedDomain[domainClass].push(item);
+ sanitizedDomain[domainKey].push(item);
}
}
- domain.events.forEach(event => {
- validateItem(event, 'events', eventSchema);
- });
- domain.categories.forEach(category => {
- validateItem(category, 'categories', categorySchema);
- });
- domain.sites.forEach(site => {
- validateItem(site, 'sites', siteSchema);
- });
- domain.narratives.forEach(narrative => {
- validateItem(narrative, 'narratives', narrativeSchema);
- });
+ function validateArray(items, domainKey, schema) {
+ items.forEach(item => {
+ validateArrayItem(item, domainKey, schema)
+ })
+ }
+
+ function validateObject(obj, domainKey, itemSchema) {
+ Object.keys(obj).forEach(key => {
+ const vl = obj[key]
+ const result = Joi.validate(vl, itemSchema)
+ if (result.error !== null) {
+ const id = vl.id || '-'
+ const domainStr = capitalize(domainKey)
+ discardedDomain[domainKey].push({
+ ...vl,
+ error: makeError(domainStr, id, result.error.message)
+ })
+ } else {
+ sanitizedDomain[domainKey][key] = vl
+ }
+ })
+ }
+
+ validateArray(domain.events, 'events', eventSchema);
+ validateArray(domain.categories, 'categories', categorySchema);
+ validateArray(domain.sites, 'sites', siteSchema);
+ validateArray(domain.narratives, 'narratives', narrativeSchema);
+ validateObject(domain.sources, 'sources', sourceSchema);
// Message the number of failed items in domain
diff --git a/src/scss/cardstack.scss b/src/scss/cardstack.scss
index 70393db..1879969 100644
--- a/src/scss/cardstack.scss
+++ b/src/scss/cardstack.scss
@@ -1,6 +1,8 @@
@import 'burger';
@import 'card';
+$card-width: 500px;
+
.card-stack {
position: absolute;
top: 10px;
@@ -20,7 +22,7 @@
.card-stack-header {
min-height: 38px;
line-height: 38px;
- width: 360px;
+ width: $card-width;
box-sizing: border-box;
padding: 0 5px;
background: $black;
@@ -61,7 +63,7 @@
}
.card-stack-content {
- width: 360px;
+ width: $card-width;
ul {
padding: 0;
diff --git a/src/selectors/index.js b/src/selectors/index.js
index 76f8f40..32a4e49 100644
--- a/src/selectors/index.js
+++ b/src/selectors/index.js
@@ -1,16 +1,19 @@
-import {
- createSelector
-} from 'reselect'
+import { createSelector} from 'reselect'
// Input selectors
export const getEvents = state => state.domain.events;
export const getLocations = state => state.domain.locations;
export const getCategories = state => state.domain.categories;
export const getNarratives = state => state.domain.narratives;
+export const getSelected = state => state.app.selected;
export const getSites = (state) => {
if (process.env.features.USE_SITES) return state.domain.sites;
return [];
}
+export const getSources = state => {
+ if (process.env.features.USE_SOURCES) return state.domain.sources;
+ return [];
+}
export const getNotifications = state => state.domain.notifications;
export const getTagTree = state => state.domain.tags;
export const getTagsFilter = state => state.app.filters.tags;
@@ -152,6 +155,27 @@ export const selectLocations = createSelector(
}
);
+/**
+ * Of all the sources, select those that are relevant to the selected events.
+ */
+export const selectSelected = createSelector(
+ [getSelected, getSources],
+ (selected, sources) => {
+ if (selected.length === 0) {
+ return []
+ }
+ const srcs = selected
+ .map(e => e.sources)
+ .map(_sources =>
+ _sources.map(id => sources[id])
+ )
+
+ return selected.map((s, idx) => ({
+ ...s,
+ sources: srcs[idx]
+ }))
+ }
+)
/*
* Select categories, return them as a list
diff --git a/src/store/initial.js b/src/store/initial.js
index dbe0dd7..03d8fef 100644
--- a/src/store/initial.js
+++ b/src/store/initial.js
@@ -106,7 +106,7 @@ const initial = {
ui: {
style: {
categories: {
- default: 'red',
+ default: 'yellow',
// Add here other categories to differentiate by color, like:
alpha: '#00ff00',
beta: '#ff0000',
@@ -115,17 +115,11 @@ const initial = {
narratives: {
default: {
- style: 'dotted', // ['dotted', 'solid']
- opacity: 0.9, // range between 0 and 1
- stroke: 'red', // Any hex or rgb code
+ style: 'solid', // ['dotted', 'solid']
+ opacity: 0.5, // range between 0 and 1
+ stroke: 'transparent', // Any hex or rgb code
strokeWidth: 2
},
- narrative_1: {
- style: 'solid', // ['dotted', 'solid']
- opacity: 0.4, // range between 0 and 1
- stroke: '#f18f01', // Any hex or rgb code
- strokeWidth: 2
- }
}
},
dom: {
diff --git a/webpack.config.js b/webpack.config.js
index 4706f03..768e021 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -64,7 +64,8 @@ const config = {
'features': {
'USE_TAGS': JSON.stringify(userConfig.features.USE_TAGS),
'USE_SEARCH': JSON.stringify(userConfig.features.USE_SEARCH),
- 'USE_SITES': JSON.stringify(userConfig.features.USE_SITES)
+ 'USE_SITES': JSON.stringify(userConfig.features.USE_SITES),
+ 'USE_SOURCES': JSON.stringify(userConfig.features.USE_SOURCES)
}
}
}),