diff --git a/src/components/Notification.jsx b/src/components/Notification.jsx
index e718b9d..8af75e9 100644
--- a/src/components/Notification.jsx
+++ b/src/components/Notification.jsx
@@ -31,12 +31,12 @@ export default class Notification extends React.Component{
if (this.props.isNotification) {
return (
- {this.props.notifications.map(not => (
+ {this.props.notifications.map(notification => (
this.toggleDetails() }>
-
{`${not.message}`}
+
{`${notification.message}`}
- {(not.items !== null) ? this.renderItems(not.items) : ''}
+ {(notification.items !== null) ? this.renderItems(notification.items) : ''}
))}
diff --git a/src/reducers/utils/validators.js b/src/reducers/utils/validators.js
index b6c38b9..8c07a32 100644
--- a/src/reducers/utils/validators.js
+++ b/src/reducers/utils/validators.js
@@ -18,6 +18,34 @@ function makeError(type, id, message) {
}
}
+
+const isLeaf = node => (Object.keys(node.children).length === 0);
+const isDuplicate = (node, set) => { return (set.has(node.key)); };
+
+
+/*
+* Traverse a tag tree and check its duplicates
+*/
+function traverseNodeAndCheckIt(node, parent, set, duplicates) {
+ // If it's a leaf, check that it's not duplicate
+ if (isLeaf(node)) {
+ if (isDuplicate(node, set)) {
+ duplicates.push({
+ id: node.key,
+ error: makeError('Tags', node.key, 'tag was found more than once in hierarchy. Ignoring duplicate.')
+ });
+ delete parent.children[node.key];
+ } else {
+ set.add(node.key);
+ }
+ } else {
+ // If it's not a leaf, simply keep going
+ Object.values(node.children).forEach((childNode) => {
+ traverseNodeAndCheckIt(childNode, node, set, duplicates);
+ });
+ }
+}
+
/*
* Validate domain schema
*/
@@ -27,7 +55,7 @@ export function validate(domain) {
categories: [],
sites: [],
notifications: domain.notifications,
- tags: domain.tags
+ tags: {}
}
const discardedDomain = {
@@ -59,7 +87,7 @@ export function validate(domain) {
validateItem(site, 'sites', siteSchema);
});
- // Message the number of failed items
+ // Message the number of failed items in domain
Object.keys(discardedDomain).forEach(disc => {
const len = discardedDomain[disc].length;
if (len) {
@@ -69,7 +97,22 @@ export function validate(domain) {
type: 'error'
});
}
- })
+ });
+
+ // Validate uniqueness of tags
+ const tagSet = new Set([]);
+ const duplicateTags = [];
+ traverseNodeAndCheckIt(domain.tags, {}, tagSet, duplicateTags);
+
+ // Duplicated tags
+ if (duplicateTags.length > 0) {
+ sanitizedDomain.notifications.push({
+ message: `Tags are required to be unique. Ignoring duplicates for now.`,
+ items: duplicateTags,
+ type: 'error'
+ });
+ }
+ sanitizedDomain.tags = domain.tags;
return sanitizedDomain;
}
diff --git a/src/scss/notification.scss b/src/scss/notification.scss
index 4b25d46..49a8280 100644
--- a/src/scss/notification.scss
+++ b/src/scss/notification.scss
@@ -57,15 +57,23 @@
overflow: hidden;
display: flex;
flex-direction: column;
+ border-radius: 3px;
+ margin-top: 10px;
+ padding: 10px;
+ background: $darkgrey;
+ color: $offwhite;
+ font-family: monospace;
&.true {
height: auto;
- transition: height 0.4s;
+ transition: height 0.4s, margin 0.4s;
}
&.false {
height: 0;
- transition: height 0.4s;
+ padding: 0;
+ margin: 0;
+ transition: height 0.4s, margin 0.4s;
}
}
}