mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-11-04 22:38:43 +00:00
Merge pull request #5129 from naturalcrit/refactor-metadataEditor
Refactor metadataEditor as functional
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
import '../uiEditor.less';
|
import '../uiEditor.less';
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import createReactClass from 'create-react-class';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import request from '../../utils/request-middleware.js';
|
import request from '../../utils/request-middleware.js';
|
||||||
import Combobox from '@components/combobox.jsx';
|
import Combobox from '@components/combobox.jsx';
|
||||||
@@ -18,48 +17,35 @@ const callIfExists = (val, fn, ...args)=>{
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const MetadataEditor = createReactClass({
|
const MetadataEditor = ({
|
||||||
displayName : 'MetadataEditor',
|
metadata = {
|
||||||
getDefaultProps : function() {
|
editId : null,
|
||||||
return {
|
shareId : null,
|
||||||
metadata : {
|
title : '',
|
||||||
editId : null,
|
description : '',
|
||||||
shareId : null,
|
thumbnail : '',
|
||||||
title : '',
|
tags : [],
|
||||||
description : '',
|
published : false,
|
||||||
thumbnail : '',
|
authors : [],
|
||||||
tags : [],
|
renderer : 'legacy',
|
||||||
published : false,
|
theme : '5ePHB',
|
||||||
authors : [],
|
lang : 'en',
|
||||||
renderer : 'legacy',
|
|
||||||
theme : '5ePHB',
|
|
||||||
lang : 'en'
|
|
||||||
},
|
|
||||||
|
|
||||||
onChange : ()=>{},
|
|
||||||
reportError : ()=>{}
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState : function(){
|
onChange = ()=>{},
|
||||||
return {
|
reportError = ()=>{},
|
||||||
isOwner : global.account?.username && global.account?.username === this.props.metadata?.authors[0],
|
...props
|
||||||
showThumbnail : true
|
})=>{
|
||||||
};
|
const [showThumbnail, setShowThumbnail] = useState(true);
|
||||||
},
|
|
||||||
|
|
||||||
toggleThumbnailDisplay : function(){
|
const toggleThumbnailDisplay = ()=>{
|
||||||
this.setState({
|
setShowThumbnail(!showThumbnail);
|
||||||
showThumbnail : !this.state.showThumbnail
|
};
|
||||||
});
|
const renderThumbnail = ()=>{
|
||||||
},
|
if(!showThumbnail) return;
|
||||||
|
return <img className='thumbnail-preview' src={metadata.thumbnail || homebreweryThumbnail}></img>;
|
||||||
renderThumbnail : function(){
|
};
|
||||||
if(!this.state.showThumbnail) return;
|
const handleFieldChange = (name, e)=>{
|
||||||
return <img className='thumbnail-preview' src={this.props.metadata.thumbnail || homebreweryThumbnail}></img>;
|
|
||||||
},
|
|
||||||
|
|
||||||
handleFieldChange : function(name, e){
|
|
||||||
// load validation rules, and check input value against them
|
// load validation rules, and check input value against them
|
||||||
const inputRules = validations[name] ?? [];
|
const inputRules = validations[name] ?? [];
|
||||||
const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean);
|
const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean);
|
||||||
@@ -72,8 +58,8 @@ const MetadataEditor = createReactClass({
|
|||||||
// if no validation rules, save to props
|
// if no validation rules, save to props
|
||||||
if(validationErr.length === 0){
|
if(validationErr.length === 0){
|
||||||
callIfExists(e.target, 'setCustomValidity', '');
|
callIfExists(e.target, 'setCustomValidity', '');
|
||||||
this.props.onChange({
|
onChange({
|
||||||
...this.props.metadata,
|
...metadata,
|
||||||
[name] : e.target.value
|
[name] : e.target.value
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
@@ -86,45 +72,39 @@ const MetadataEditor = createReactClass({
|
|||||||
debouncedReportValidity(e.target, errMessage);
|
debouncedReportValidity(e.target, errMessage);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
const handleRenderer = (renderer, e)=>{
|
||||||
handleRenderer : function(renderer, e){
|
|
||||||
if(e.target.checked){
|
if(e.target.checked){
|
||||||
this.props.metadata.renderer = renderer;
|
metadata.renderer = renderer;
|
||||||
if(renderer == 'legacy')
|
if(renderer == 'legacy')
|
||||||
this.props.metadata.theme = '5ePHB';
|
metadata.theme = '5ePHB';
|
||||||
}
|
}
|
||||||
this.props.onChange(this.props.metadata, 'renderer');
|
onChange(metadata, 'renderer');
|
||||||
},
|
};
|
||||||
|
const handlePublish = (val)=>{
|
||||||
handlePublish : function(val){
|
onChange({
|
||||||
this.props.onChange({
|
...metadata,
|
||||||
...this.props.metadata,
|
|
||||||
published : val
|
published : val
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
|
const handleTheme = (theme)=>{
|
||||||
|
metadata.renderer = theme.renderer;
|
||||||
|
metadata.theme = theme.path;
|
||||||
|
|
||||||
handleTheme : function(theme){
|
onChange(metadata, 'theme');
|
||||||
this.props.metadata.renderer = theme.renderer;
|
};
|
||||||
this.props.metadata.theme = theme.path;
|
const handleThemeWritein = (e)=>{
|
||||||
|
|
||||||
this.props.onChange(this.props.metadata, 'theme');
|
|
||||||
},
|
|
||||||
|
|
||||||
handleThemeWritein : function(e) {
|
|
||||||
const shareId = e.target.value.split('/').pop(); //Extract just the ID if a URL was pasted in
|
const shareId = e.target.value.split('/').pop(); //Extract just the ID if a URL was pasted in
|
||||||
this.props.metadata.theme = shareId;
|
metadata.theme = shareId;
|
||||||
|
|
||||||
this.props.onChange(this.props.metadata, 'theme');
|
onChange(metadata, 'theme');
|
||||||
},
|
};
|
||||||
|
const handleLanguage = (languageCode)=>{
|
||||||
handleLanguage : function(languageCode){
|
metadata.lang = languageCode;
|
||||||
this.props.metadata.lang = languageCode;
|
onChange(metadata);
|
||||||
this.props.onChange(this.props.metadata);
|
};
|
||||||
},
|
const handleDelete = ()=>{
|
||||||
|
if(metadata.authors && metadata.authors.length <= 1){
|
||||||
handleDelete : function(){
|
|
||||||
if(this.props.metadata.authors && this.props.metadata.authors.length <= 1){
|
|
||||||
if(!confirm('Are you sure you want to delete this brew? Because you are the only owner of this brew, the document will be deleted permanently.')) return;
|
if(!confirm('Are you sure you want to delete this brew? Because you are the only owner of this brew, the document will be deleted permanently.')) return;
|
||||||
if(!confirm('Are you REALLY sure? You will not be able to recover the document.')) return;
|
if(!confirm('Are you REALLY sure? You will not be able to recover the document.')) return;
|
||||||
} else {
|
} else {
|
||||||
@@ -132,54 +112,51 @@ const MetadataEditor = createReactClass({
|
|||||||
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
|
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.delete(`/api/${this.props.metadata.googleId ?? ''}${this.props.metadata.editId}`)
|
request.delete(`/api/${metadata.googleId ?? ''}${metadata.editId}`)
|
||||||
.send()
|
.send()
|
||||||
.end((err, res)=>{
|
.end((err, res)=>{
|
||||||
if(err) {
|
if(err) {
|
||||||
this.props.reportError(err);
|
reportError(err);
|
||||||
} else {
|
} else {
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
|
const handleDeleteAuthor = (author)=>{
|
||||||
handleDeleteAuthor : function(author){
|
|
||||||
if(!confirm('Are you sure you want to remove this author? They will lose all edit access to this brew, and it will dissapear from their userpage.')) return;
|
if(!confirm('Are you sure you want to remove this author? They will lose all edit access to this brew, and it will dissapear from their userpage.')) return;
|
||||||
if(!this.props.metadata.authors.includes(author)) return;
|
if(!metadata.authors.includes(author)) return;
|
||||||
this.props.onChange({
|
onChange({
|
||||||
...this.props.metadata,
|
...metadata,
|
||||||
authors : this.props.metadata.authors.filter((a)=>a !== author)
|
authors : metadata.authors.filter((a)=>a !== author)
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
|
const renderPublish = ()=>{
|
||||||
renderPublish : function(){
|
if(metadata.published){
|
||||||
if(this.props.metadata.published){
|
return <button id='publish-button' className='unpublish' onClick={()=>handlePublish(false)}>
|
||||||
return <button id='publish-button' className='unpublish' onClick={()=>this.handlePublish(false)}>
|
|
||||||
<i className='fas fa-ban' aria-hidden='true' /> unpublish
|
<i className='fas fa-ban' aria-hidden='true' /> unpublish
|
||||||
</button>;
|
</button>;
|
||||||
} else {
|
} else {
|
||||||
return <button id='publish-button' className='publish' onClick={()=>this.handlePublish(true)}>
|
return <button id='publish-button' className='publish' onClick={()=>handlePublish(true)}>
|
||||||
<i className='fas fa-globe' aria-hidden='true' /> publish
|
<i className='fas fa-globe' aria-hidden='true' /> publish
|
||||||
</button>;
|
</button>;
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
const renderDelete = ()=>{
|
||||||
renderDelete : function(){
|
if(!metadata.editId) return;
|
||||||
if(!this.props.metadata.editId) return;
|
|
||||||
|
|
||||||
return <div className='field delete'>
|
return <div className='field delete'>
|
||||||
<label htmlFor='delete-button'>delete</label>
|
<label htmlFor='delete-button'>delete</label>
|
||||||
<div className='value'>
|
<div className='value'>
|
||||||
<button id='delete-button' onClick={this.handleDelete}>
|
<button id='delete-button' onClick={handleDelete}>
|
||||||
<i className='fas fa-trash-alt' /> delete brew
|
<i className='fas fa-trash-alt' /> delete brew
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
},
|
};
|
||||||
|
const renderAuthors = ()=>{
|
||||||
renderAuthors : function(){
|
const authors = metadata.authors;
|
||||||
const authors = this.props.metadata.authors;
|
const isOwner = global.account?.username && global.account?.username === authors[0];
|
||||||
if(!this.state.isOwner || authors.length < 2) return (
|
if(!isOwner || authors.length < 2) return (
|
||||||
<div className='field authors'>
|
<div className='field authors'>
|
||||||
<label>authors</label>
|
<label>authors</label>
|
||||||
<div className='value'>
|
<div className='value'>
|
||||||
@@ -214,7 +191,7 @@ const MetadataEditor = createReactClass({
|
|||||||
{author}
|
{author}
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
onClick={()=>this.handleDeleteAuthor(author)}
|
onClick={()=>handleDeleteAuthor(author)}
|
||||||
className='delete'
|
className='delete'
|
||||||
title={`Remove ${author} as an author`}
|
title={`Remove ${author} as an author`}
|
||||||
>
|
>
|
||||||
@@ -225,14 +202,13 @@ const MetadataEditor = createReactClass({
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
};
|
||||||
|
const renderThemeDropdown = ()=>{
|
||||||
renderThemeDropdown : function(){
|
const mergedThemes = _.merge(Themes, props.userThemes);
|
||||||
const mergedThemes = _.merge(Themes, this.props.userThemes);
|
|
||||||
|
|
||||||
const listThemes = (renderer)=>{
|
const listThemes = (renderer)=>{
|
||||||
return _.map(_.values(mergedThemes[renderer]), (theme)=>{
|
return _.map(_.values(mergedThemes[renderer]), (theme)=>{
|
||||||
if(theme.path == this.props.metadata.shareId) return;
|
if(theme.path == metadata.shareId) return;
|
||||||
const preview = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownPreview.png`;
|
const preview = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownPreview.png`;
|
||||||
const texture = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`;
|
const texture = theme.thumbnail || `/themes/${theme.renderer}/${theme.path}/dropdownTexture.png`;
|
||||||
return <div className='item' key={`${renderer}_${theme.name}`} value={`${theme.author ?? renderer} : ${theme.name}`} data={theme} title={''}>
|
return <div className='item' key={`${renderer}_${theme.name}`} value={`${theme.author ?? renderer} : ${theme.name}`} data={theme} title={''}>
|
||||||
@@ -248,8 +224,8 @@ const MetadataEditor = createReactClass({
|
|||||||
}).filter(Boolean);
|
}).filter(Boolean);
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentRenderer = this.props.metadata.renderer;
|
const currentRenderer = metadata.renderer;
|
||||||
const currentThemeDisplay = this.props.themeBundle?.name ? `${this.props.themeBundle.author ?? currentRenderer} : ${this.props.themeBundle.name}` : 'No Theme Selected';
|
const currentThemeDisplay = props.themeBundle?.name ? `${props.themeBundle.author ?? currentRenderer} : ${props.themeBundle.name}` : 'No Theme Selected';
|
||||||
let dropdown;
|
let dropdown;
|
||||||
|
|
||||||
if(currentRenderer == 'legacy') {
|
if(currentRenderer == 'legacy') {
|
||||||
@@ -265,11 +241,11 @@ const MetadataEditor = createReactClass({
|
|||||||
className='themes-dropdown'
|
className='themes-dropdown'
|
||||||
default={currentThemeDisplay}
|
default={currentThemeDisplay}
|
||||||
placeholder='Select from below, or enter the Share URL or ID of a brew with the meta:theme tag'
|
placeholder='Select from below, or enter the Share URL or ID of a brew with the meta:theme tag'
|
||||||
onSelect={(value)=>this.handleTheme(value)}
|
onSelect={(value)=>handleTheme(value)}
|
||||||
onEntry={(e)=>{
|
onEntry={(e)=>{
|
||||||
e.target.setCustomValidity(''); //Clear the validation popup while typing
|
e.target.setCustomValidity(''); //Clear the validation popup while typing
|
||||||
if(this.handleFieldChange('theme', e))
|
if(handleFieldChange('theme', e))
|
||||||
this.handleThemeWritein(e);
|
handleThemeWritein(e);
|
||||||
}}
|
}}
|
||||||
options={listThemes(currentRenderer)}
|
options={listThemes(currentRenderer)}
|
||||||
autoSuggest={{
|
autoSuggest={{
|
||||||
@@ -285,9 +261,8 @@ const MetadataEditor = createReactClass({
|
|||||||
<label htmlFor='combobox-themes'>theme</label>
|
<label htmlFor='combobox-themes'>theme</label>
|
||||||
{dropdown}
|
{dropdown}
|
||||||
</div>;
|
</div>;
|
||||||
},
|
};
|
||||||
|
const renderLanguageDropdown = ()=>{
|
||||||
renderLanguageDropdown : function(){
|
|
||||||
const langCodes = ['en', 'de', 'de-ch', 'fr', 'ja', 'es', 'it', 'sv', 'ru', 'zh-Hans', 'zh-Hant'];
|
const langCodes = ['en', 'de', 'de-ch', 'fr', 'ja', 'es', 'it', 'sv', 'ru', 'zh-Hans', 'zh-Hant'];
|
||||||
const listLanguages = ()=>{
|
const listLanguages = ()=>{
|
||||||
return _.map(langCodes.sort(), (code, index)=>{
|
return _.map(langCodes.sort(), (code, index)=>{
|
||||||
@@ -306,12 +281,12 @@ const MetadataEditor = createReactClass({
|
|||||||
<Combobox trigger='click'
|
<Combobox trigger='click'
|
||||||
id='combobox-language'
|
id='combobox-language'
|
||||||
className='language-dropdown'
|
className='language-dropdown'
|
||||||
default={this.props.metadata.lang || ''}
|
default={metadata.lang || ''}
|
||||||
placeholder='en'
|
placeholder='en'
|
||||||
onSelect={(value)=>this.handleLanguage(value)}
|
onSelect={(value)=>handleLanguage(value)}
|
||||||
onEntry={(e)=>{
|
onEntry={(e)=>{
|
||||||
e.target.setCustomValidity(''); //Clear the validation popup while typing
|
e.target.setCustomValidity(''); //Clear the validation popup while typing
|
||||||
this.handleFieldChange('lang', e);
|
handleFieldChange('lang', e);
|
||||||
}}
|
}}
|
||||||
options={listLanguages()}
|
options={listLanguages()}
|
||||||
autoSuggest={{
|
autoSuggest={{
|
||||||
@@ -323,9 +298,8 @@ const MetadataEditor = createReactClass({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>;
|
</div>;
|
||||||
},
|
};
|
||||||
|
const renderRenderOptions = ()=>{
|
||||||
renderRenderOptions : function(){
|
|
||||||
return <div className='field renderers'>
|
return <div className='field renderers'>
|
||||||
<label>Renderer</label>
|
<label>Renderer</label>
|
||||||
<div className='value'>
|
<div className='value'>
|
||||||
@@ -334,8 +308,8 @@ const MetadataEditor = createReactClass({
|
|||||||
type='radio'
|
type='radio'
|
||||||
value = 'legacy'
|
value = 'legacy'
|
||||||
name = 'renderer'
|
name = 'renderer'
|
||||||
checked={this.props.metadata.renderer === 'legacy'}
|
checked={metadata.renderer === 'legacy'}
|
||||||
onChange={(e)=>this.handleRenderer('legacy', e)} />
|
onChange={(e)=>handleRenderer('legacy', e)} />
|
||||||
Legacy
|
Legacy
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -344,73 +318,86 @@ const MetadataEditor = createReactClass({
|
|||||||
type='radio'
|
type='radio'
|
||||||
value = 'V3'
|
value = 'V3'
|
||||||
name = 'renderer'
|
name = 'renderer'
|
||||||
checked={this.props.metadata.renderer === 'V3'}
|
checked={metadata.renderer === 'V3'}
|
||||||
onChange={(e)=>this.handleRenderer('V3', e)} />
|
onChange={(e)=>handleRenderer('V3', e)} />
|
||||||
V3
|
V3
|
||||||
</label>
|
</label>
|
||||||
<small><a href='/legacy' target='_blank' rel='noopener noreferrer'>Click here to see the demo page for the old Legacy renderer!</a></small>
|
<small><a href='/legacy' target='_blank' rel='noopener noreferrer'>Click here to see the demo page for the old Legacy renderer!</a></small>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
},
|
};
|
||||||
|
|
||||||
render : function(){
|
return (
|
||||||
return <div className='metadataEditor uiEditor'>
|
<div className='metadataEditor uiEditor'>
|
||||||
<h1>Properties Editor</h1>
|
<h1>Properties Editor</h1>
|
||||||
|
|
||||||
<div className='field title'>
|
<div className='field title'>
|
||||||
<label htmlFor='title_field'>title</label>
|
<label htmlFor='title_field'>title</label>
|
||||||
<input type='text' id='title_field' className='value'
|
<input
|
||||||
defaultValue={this.props.metadata.title}
|
type='text'
|
||||||
onChange={(e)=>this.handleFieldChange('title', e)} />
|
id='title_field'
|
||||||
|
className='value'
|
||||||
|
defaultValue={metadata.title}
|
||||||
|
onChange={(e)=>handleFieldChange('title', e)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<fieldset className='field-group'>
|
<fieldset className='field-group'>
|
||||||
<div className='field-column'>
|
<div className='field-column'>
|
||||||
<div className='field description'>
|
<div className='field description'>
|
||||||
<label htmlFor='description_field'>description</label>
|
<label htmlFor='description_field'>description</label>
|
||||||
<textarea id='description_field' defaultValue={this.props.metadata.description} className='value'
|
<textarea
|
||||||
onChange={(e)=>this.handleFieldChange('description', e)} />
|
id='description_field'
|
||||||
|
defaultValue={metadata.description}
|
||||||
|
className='value'
|
||||||
|
onChange={(e)=>handleFieldChange('description', e)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='field thumbnail'>
|
<div className='field thumbnail'>
|
||||||
<label htmlFor='thumbnail_field'>thumbnail</label>
|
<label htmlFor='thumbnail_field'>thumbnail</label>
|
||||||
<input type='text'
|
<input
|
||||||
|
type='text'
|
||||||
id='thumbnail_field'
|
id='thumbnail_field'
|
||||||
defaultValue={this.props.metadata.thumbnail}
|
defaultValue={metadata.thumbnail}
|
||||||
placeholder='https://my.thumbnail.url'
|
placeholder='https://my.thumbnail.url'
|
||||||
className='value'
|
className='value'
|
||||||
onChange={(e)=>this.handleFieldChange('thumbnail', e)} />
|
onChange={(e)=>handleFieldChange('thumbnail', e)}
|
||||||
<button className='display' onClick={this.toggleThumbnailDisplay}
|
/>
|
||||||
aria-label={`${this.state.showThumbnail ? 'hide thumbnail' : 'show thumbnail'}`}>
|
<button
|
||||||
<i className={`fas fa-caret-${this.state.showThumbnail ? 'right' : 'left'}`} />
|
className='display'
|
||||||
|
onClick={toggleThumbnailDisplay}
|
||||||
|
aria-label={`${showThumbnail ? 'hide thumbnail' : 'show thumbnail'}`}>
|
||||||
|
<i className={`fas fa-caret-${showThumbnail ? 'right' : 'left'}`} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.renderThumbnail()}
|
{renderThumbnail()}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div className='field tags'>
|
<div className='field tags'>
|
||||||
<label htmlFor='combobox-tags'>Tags</label>
|
<label htmlFor='combobox-tags'>Tags</label>
|
||||||
<div className='value' >
|
<div className='value'>
|
||||||
<TagInput
|
<TagInput
|
||||||
id='combobox-tags'
|
id='combobox-tags'
|
||||||
label='tags'
|
label='tags'
|
||||||
valuePatterns={/^\s*(?:(?:group|meta|system|type)\s*:\s*)?[A-Za-z0-9][A-Za-z0-9 \/\\.&_\-]{0,40}\s*$/}
|
valuePatterns={/^\s*(?:(?:group|meta|system|type)\s*:\s*)?[A-Za-z0-9][A-Za-z0-9 \/\\.&_\-]{0,40}\s*$/}
|
||||||
placeholder='add tag' unique={true}
|
placeholder='add tag'
|
||||||
values={this.props.metadata.tags}
|
unique={true}
|
||||||
onChange={(e)=>this.handleFieldChange('tags', e)}
|
values={metadata.tags}
|
||||||
|
onChange={(e)=>handleFieldChange('tags', e)}
|
||||||
tooltip='You may start tags with "type", "system", "group" or "meta" followed by a colon ":", these will be colored in your userpage.'
|
tooltip='You may start tags with "type", "system", "group" or "meta" followed by a colon ":", these will be colored in your userpage.'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{this.renderLanguageDropdown()}
|
{renderLanguageDropdown()}
|
||||||
|
|
||||||
{this.renderThemeDropdown()}
|
{renderThemeDropdown()}
|
||||||
|
|
||||||
{this.renderRenderOptions()}
|
{renderRenderOptions()}
|
||||||
|
|
||||||
<h2>Authors</h2>
|
<h2>Authors</h2>
|
||||||
|
|
||||||
{this.renderAuthors()}
|
{renderAuthors()}
|
||||||
|
|
||||||
<div className='field invitedAuthors'>
|
<div className='field invitedAuthors'>
|
||||||
<label htmlFor='combobox-invited-authors'>Invited authors</label>
|
<label htmlFor='combobox-invited-authors'>Invited authors</label>
|
||||||
@@ -419,12 +406,13 @@ const MetadataEditor = createReactClass({
|
|||||||
id='combobox-invited-authors'
|
id='combobox-invited-authors'
|
||||||
label='invited authors'
|
label='invited authors'
|
||||||
valuePatterns={/.+/}
|
valuePatterns={/.+/}
|
||||||
validators={[(v)=>!this.props.metadata.authors?.includes(v)]}
|
validators={[(v)=>!metadata.authors?.includes(v)]}
|
||||||
placeholder='invite author' unique={true}
|
placeholder='invite author'
|
||||||
|
unique={true}
|
||||||
tooltip={`Invited author usernames are case sensitive.
|
tooltip={`Invited author usernames are case sensitive.
|
||||||
After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.`}
|
After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.`}
|
||||||
values={this.props.metadata.invitedAuthors}
|
values={metadata.invitedAuthors}
|
||||||
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}
|
onChange={(e)=>handleFieldChange('invitedAuthors', e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -434,15 +422,18 @@ const MetadataEditor = createReactClass({
|
|||||||
<div className='field publish'>
|
<div className='field publish'>
|
||||||
<label htmlFor='publish-button'>publish</label>
|
<label htmlFor='publish-button'>publish</label>
|
||||||
<div className='value'>
|
<div className='value'>
|
||||||
{this.renderPublish()}
|
{renderPublish()}
|
||||||
<small>Published brews are searchable in <a href='/vault'>the Vault</a> and visible on your user page. Unpublished brews are not indexed in the Vault or visible on your user page, but can still be shared and indexed by search engines. You can unpublish a brew any time.</small>
|
<small>
|
||||||
|
Published brews are searchable in <a href='/vault'>the Vault</a> and visible on your user page. Unpublished brews are not
|
||||||
|
indexed in the Vault or visible on your user page, but can still be shared and indexed by search engines. You can unpublish a
|
||||||
|
brew any time.
|
||||||
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{this.renderDelete()}
|
{renderDelete()}
|
||||||
|
</div>
|
||||||
</div>;
|
);
|
||||||
}
|
};
|
||||||
});
|
|
||||||
|
|
||||||
export default MetadataEditor;
|
export default MetadataEditor;
|
||||||
|
|||||||
Reference in New Issue
Block a user