diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.jsx b/client/homebrew/editor/metadataEditor/metadataEditor.jsx
index f3b5419e5..9f8094efd 100644
--- a/client/homebrew/editor/metadataEditor/metadataEditor.jsx
+++ b/client/homebrew/editor/metadataEditor/metadataEditor.jsx
@@ -5,6 +5,7 @@ const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const request = require('superagent');
+const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx');
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder'];
@@ -27,38 +28,6 @@ const MetadataEditor = createClass({
onChange : ()=>{}
};
},
- getInitialState : function() {
- return {
- tagContext : !!this.props.metadata.tags ? this.props.metadata.tags.map((tag)=>({
- tag,
- editing : false
- })) : [],
- temporaryTag : '',
- updateTag : ''
- };
- },
-
- componentWillUpdate : function() {
- const tags = this.props.metadata.tags;
- if(!!tags) {
- const contextTags = Object.values(this.state.tagContext).map((context)=>context.tag);
- let updateContext = tags.length !== contextTags.length;
- for (let i = 0; i < tags.length && updateContext === false; i++){
- const tag = tags[i];
- if(!contextTags.includes(tag))
- updateContext = true;
- }
- if(updateContext) {
- this.setState((prevState)=>({
- ...prevState,
- tagContext : this.props.metadata.tags.map((tag)=>({
- tag,
- editing : false
- }))
- }));
- }
- }
- },
getInitialState : function(){
return {
@@ -78,9 +47,10 @@ const MetadataEditor = createClass({
},
handleFieldChange : function(name, e){
- this.props.onChange(_.merge({}, this.props.metadata, {
+ this.props.onChange({
+ ...this.props.metadata,
[name] : e.target.value
- }));
+ });
},
handleSystem : function(system, e){
if(e.target.checked){
@@ -97,9 +67,10 @@ const MetadataEditor = createClass({
this.props.onChange(this.props.metadata);
},
handlePublish : function(val){
- this.props.onChange(_.merge({}, this.props.metadata, {
+ this.props.onChange({
+ ...this.props.metadata,
published : val
- }));
+ });
},
handleDelete : function(){
@@ -201,68 +172,6 @@ const MetadataEditor = createClass({
;
},
- addTag : function(tag){
- this.props.metadata.tags = _.uniq([...this.props.metadata.tags, ...(tag.split(',').map((t)=>t.trim()))]);
- this.props.onChange(this.props.metadata);
- },
- removeTag : function(tag){
- this.props.metadata.tags = this.props.metadata.tags.filter((t)=>t !== tag);
- this.props.onChange(this.props.metadata);
- },
- updateTag : function(tag, index){
- this.props.metadata.tags[index] = tag;
- this.props.onChange(this.props.metadata);
- },
- editTag : function(index){
- const tagContext = this.state.tagContext.map((context, i)=>{
- context.editing = index === i;
- return context;
- });
- this.setState({ tagContext, updateTag: this.props.metadata.tags[index] });
- },
- handleTagInputKeyDown : function(event, index){
- if(event.key === 'Enter') {
- const tagPattern = /^(?:(?:group|meta|system|type):)?[A-Za-z0-9][A-Za-z0-9 /.:\-]+$/;
- if(!!event.target.value.match(tagPattern)) {
- if(!!index) {
- this.updateTag(event.target.value, index);
- const tagContext = this.state.tagContext;
- tagContext[index].editing = false;
- this.setState({ tagContext, updateTag: '' });
- } else {
- this.addTag(event.target.value);
- this.setState({ temporaryTag: '' });
- }
- } else {
- console.log('does not match');
- }
- }
- },
-
- renderTags : function(){
- const tagElements = Object.values(this.state.tagContext).map((context, i)=>context.editing
- ? { this.handleTagInputKeyDown(e, i); }}
- onChange={(e)=>{ this.setState({ updateTag: e.target.value }); }}/>
- :
{ this.editTag(i); }}>{context.tag}
- this.removeTag(context.tag)}/>
-
- );
-
- return
-
-
- {tagElements}
- { this.handleTagInputKeyDown(e); }}
- onChange={(e)=>{ this.setState({ temporaryTag: e.target.value }); }}/>
-
-
;
- },
-
render : function(){
return
@@ -289,7 +198,10 @@ const MetadataEditor = createClass({
{this.renderThumbnail()}
- {this.renderTags()}
+
this.handleFieldChange('tags', e)}/>
{this.renderAuthors()}
diff --git a/client/homebrew/editor/metadataEditor/metadataEditor.less b/client/homebrew/editor/metadataEditor/metadataEditor.less
index 31de998ce..d4fe62397 100644
--- a/client/homebrew/editor/metadataEditor/metadataEditor.less
+++ b/client/homebrew/editor/metadataEditor/metadataEditor.less
@@ -1,3 +1,4 @@
+@import 'naturalcrit/styles/colors.less';
.metadataEditor{
position : absolute;
@@ -109,30 +110,74 @@
line-height : 1.5em;
}
- .tags.field .list {
- display : flex;
- flex-wrap : wrap;
+ .field .list {
+ display: flex;
+ flex-wrap: wrap;
- >* {
- flex : 0 0 auto;
+ > * {
+ flex: 0 0 auto;
}
- .badge {
- background-color : #444;
- border-radius : .5em;
- color : white;
- font-size : .9em;
- margin : 2px;
- padding : .3em;
+ #groupedIcon {
+ #backgroundColors;
+ display: inline-block;
+ border-radius: 0 0.5em 0.5em 0;
+ height: ~"calc(100% + 0.6em)";
+ position: relative;
+ top: -0.3em;
+ right: -0.3em;
+ cursor: pointer;
+ min-width: 20px;
+ text-align: center;
+ color: white;
- i.fa-times {
- cursor: pointer;
+ i {
+ position: relative;
+ top: 50%;
+ transform: translateY(-50%);
}
}
- .value {
- width : 7.5vw;
- min-width : 75px;
+ .badge {
+ background-color: #dddddd;
+ border-radius: .5em;
+ font-size: .9em;
+ margin: 2px;
+ padding: .3em;
+
+ .icon {
+ #groupedIcon
+ }
+ }
+
+ .input-group {
+ height: ~"calc(.9em + 4px + .6em)";
+
+ input {
+ border-radius: .5em 0 0 .5em;
+ }
+
+ input:last-child {
+ border-radius: .5em;
+ }
+
+ .value {
+ width: 7.5vw;
+ min-width: 75px;
+ height: 100%;
+ }
+
+ .invalid:focus {
+ background-color: pink;
+ }
+
+ .icon {
+ #groupedIcon;
+ height: 97%;
+ font-size: .8em;
+ right: 1px;
+ top: -.54em;
+ }
}
}
}
diff --git a/client/homebrew/editor/stringArrayEditor/stringArrayEditor.jsx b/client/homebrew/editor/stringArrayEditor/stringArrayEditor.jsx
new file mode 100644
index 000000000..9c29f2e4e
--- /dev/null
+++ b/client/homebrew/editor/stringArrayEditor/stringArrayEditor.jsx
@@ -0,0 +1,133 @@
+const React = require('react');
+const createClass = require('create-react-class');
+const _ = require('lodash');
+
+const StringArrayEditor = createClass({
+ displayName : 'StringArrayEditor',
+ getDefaultProps : function() {
+ return {
+ label : '',
+ values : [],
+ valuePatterns : null,
+ placeholder : '',
+ unique : false,
+ cannotEdit : [],
+ onChange : ()=>{}
+ };
+ },
+
+ getInitialState : function() {
+ return {
+ valueContext : !!this.props.values ? this.props.values.map((value)=>({
+ value,
+ editing : false
+ })) : [],
+ temporaryValue : '',
+ updateValue : ''
+ };
+ },
+
+ componentDidUpdate : function(prevProps) {
+ if(!_.eq(this.props, prevProps)) {
+ this.setState({
+ valueContext : !!this.props.values ? this.props.values.map((newValue)=>({
+ value : newValue,
+ editing : this.state.valueContext.find(({ value })=>value === newValue)?.editing || false
+ })) : []
+ });
+ }
+ },
+
+ handleChange : function(value) {
+ this.props.onChange({
+ target : {
+ value
+ }
+ });
+ },
+
+ addValue : function(value){
+ this.handleChange(_.uniq([...this.props.values, value]));
+ this.setState({
+ temporaryValue : ''
+ });
+ },
+
+ removeValue : function(index){
+ this.handleChange(this.props.values.filter((_, i)=>i !== index));
+ },
+
+ updateValue : function(value, index){
+ const valueContext = this.state.valueContext;
+ valueContext[index].value = value;
+ valueContext[index].editing = false;
+ this.handleChange(valueContext.map((context)=>context.value));
+ this.setState({ valueContext, updateValue: '' });
+ },
+
+ editValue : function(index){
+ if(!!this.props.cannotEdit && this.props.cannotEdit.includes(this.props.values[index])) {
+ return;
+ }
+ const valueContext = this.state.valueContext.map((context, i)=>{
+ context.editing = index === i;
+ return context;
+ });
+ this.setState({ valueContext, updateValue: this.props.values[index] });
+ },
+
+ valueIsValid : function(value) {
+ const matchesPatterns = !this.props.valuePatterns || this.props.valuePatterns.some((pattern)=>!!(value || '').match(pattern));
+ const uniqueIfSet = !this.props.unique || !this.props.values.includes(value);
+ return matchesPatterns && uniqueIfSet;
+ },
+
+ handleValueInputKeyDown : function(event, index) {
+ if(event.key === 'Enter') {
+ if(this.valueIsValid(event.target.value)) {
+ if(index !== undefined) {
+ this.updateValue(event.target.value, index);
+ } else {
+ this.addValue(event.target.value);
+ }
+ }
+ } else if(event.key === 'Escape') {
+ const valueContext = this.state.valueContext;
+ valueContext[index].editing = false;
+ this.setState({ valueContext, updateValue: '' });
+ }
+ },
+
+ render : function() {
+ const valueElements = Object.values(this.state.valueContext).map((context, i)=>context.editing
+ ?
+
+
this.handleValueInputKeyDown(e, i)}
+ onChange={(e)=>this.setState({ updateValue: e.target.value })}/>
+ {this.valueIsValid(this.state.updateValue) ?
{ e.stopPropagation(); this.updateValue(this.state.updateValue, i); }}/>
: null}
+
+
+ : this.editValue(i)}>{context.value}
+ {!!this.props.cannotEdit && this.props.cannotEdit.includes(context.value) ? null :
{ e.stopPropagation(); this.removeValue(i); }}/>
}
+
+ );
+
+ return
+
+
+ {valueElements}
+
+
this.handleValueInputKeyDown(e)}
+ onChange={(e)=>this.setState({ temporaryValue: e.target.value })}/>
+ {this.valueIsValid(this.state.temporaryValue) ?
{ e.stopPropagation(); this.addValue(this.state.temporaryValue); }}/>
: null}
+
+
+
;
+ }
+});
+
+module.exports = StringArrayEditor;
\ No newline at end of file
diff --git a/client/homebrew/navbar/navbar.less b/client/homebrew/navbar/navbar.less
index 95cc04c7a..39fbfaf5c 100644
--- a/client/homebrew/navbar/navbar.less
+++ b/client/homebrew/navbar/navbar.less
@@ -91,7 +91,7 @@
&:nth-of-type(2){ background-color: darken(@purple, 30%); }
}
.item{
- #backgroundColors;
+ #backgroundColorsHover;
.animate(background-color);
position : relative;
display : block;
diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx
index f8ac70a5d..d28968df7 100644
--- a/client/homebrew/pages/editPage/editPage.jsx
+++ b/client/homebrew/pages/editPage/editPage.jsx
@@ -114,7 +114,7 @@ const EditPage = createClass({
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
this.setState((prevState)=>({
- brew : _.merge({}, prevState.brew, { text: text }),
+ brew : { ...prevState.brew, text: text },
isPending : true,
htmlErrors : htmlErrors
}), ()=>this.trySave());
@@ -122,14 +122,17 @@ const EditPage = createClass({
handleStyleChange : function(style){
this.setState((prevState)=>({
- brew : _.merge({}, prevState.brew, { style: style }),
+ brew : { ...prevState.brew, style: style },
isPending : true
}), ()=>this.trySave());
},
handleMetaChange : function(metadata){
this.setState((prevState)=>({
- brew : _.merge({}, prevState.brew, metadata),
+ brew : {
+ ...prevState.brew,
+ ...metadata
+ },
isPending : true,
}), ()=>this.trySave());
@@ -213,11 +216,11 @@ const EditPage = createClass({
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`);
this.setState((prevState)=>({
- brew : _.merge({}, prevState.brew, {
+ brew : { ...prevState.brew,
googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null,
editId : this.savedBrew.editId,
shareId : this.savedBrew.shareId
- }),
+ },
isPending : false,
isSaving : false,
}));
diff --git a/client/homebrew/pages/homePage/homePage.jsx b/client/homebrew/pages/homePage/homePage.jsx
index 6947eb031..c70894093 100644
--- a/client/homebrew/pages/homePage/homePage.jsx
+++ b/client/homebrew/pages/homePage/homePage.jsx
@@ -52,7 +52,7 @@ const HomePage = createClass({
},
handleTextChange : function(text){
this.setState((prevState)=>({
- brew : _.merge({}, prevState.brew, { text: text })
+ brew : { ...prevState.brew, text: text }
}));
},
renderNavbar : function(){
diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx
index ae57039b5..c47e38dc2 100644
--- a/client/homebrew/pages/newPage/newPage.jsx
+++ b/client/homebrew/pages/newPage/newPage.jsx
@@ -112,7 +112,7 @@ const NewPage = createClass({
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
this.setState((prevState)=>({
- brew : _.merge({}, prevState.brew, { text: text }),
+ brew : { ...prevState.brew, text: text },
htmlErrors : htmlErrors
}));
localStorage.setItem(BREWKEY, text);
@@ -120,14 +120,14 @@ const NewPage = createClass({
handleStyleChange : function(style){
this.setState((prevState)=>({
- brew : _.merge({}, prevState.brew, { style: style }),
+ brew : { ...prevState.brew, style: style },
}));
localStorage.setItem(STYLEKEY, style);
},
handleMetaChange : function(metadata){
this.setState((prevState)=>({
- brew : _.merge({}, prevState.brew, metadata),
+ brew : { ...prevState.brew, ...metadata },
}));
localStorage.setItem(METAKEY, JSON.stringify({
// 'title' : this.state.brew.title,
diff --git a/shared/naturalcrit/nav/nav.less b/shared/naturalcrit/nav/nav.less
index f84d11733..43eaa0819 100644
--- a/shared/naturalcrit/nav/nav.less
+++ b/shared/naturalcrit/nav/nav.less
@@ -50,7 +50,7 @@ nav{
}
}
.navItem{
- #backgroundColors;
+ #backgroundColorsHover;
.animate(background-color);
padding : 8px 12px;
cursor : pointer;
diff --git a/shared/naturalcrit/styles/colors.less b/shared/naturalcrit/styles/colors.less
index 340fb38a7..30a7610a2 100644
--- a/shared/naturalcrit/styles/colors.less
+++ b/shared/naturalcrit/styles/colors.less
@@ -23,6 +23,29 @@
@grey : #7F8C8D;
#backgroundColors {
+ &.tealLight{ background-color : @tealLight };
+ &.teal{ background-color : @teal };
+ &.greenLight{ background-color : @greenLight };
+ &.green{ background-color : @green };
+ &.blueLight{ background-color : @blueLight };
+ &.blue{ background-color : @blue };
+ &.purpleLight{ background-color : @purpleLight };
+ &.purple{ background-color : @purple };
+ &.steelLight{ background-color : @steelLight };
+ &.steel{ background-color : @steel };
+ &.yellowLight{ background-color : @yellowLight };
+ &.yellow{ background-color : @yellow };
+ &.orangeLight{ background-color : @orangeLight };
+ &.orange{ background-color : @orange };
+ &.redLight{ background-color : @redLight };
+ &.red{ background-color : @red };
+ &.silverLight{ background-color : @silverLight };
+ &.silver{ background-color : @silver };
+ &.greyLight{ background-color : @greyLight };
+ &.grey{ background-color : @grey };
+}
+
+#backgroundColorsHover {
&.tealLight:hover{ background-color : @tealLight };
&.teal:hover{ background-color : @teal };
&.greenLight:hover{ background-color : @greenLight };