Allow articles to be ordered within a publication.

master
Pacman Ghost 4 years ago
parent 47c0ae31ad
commit cbb5039321
  1. 28
      alembic/versions/064497c51f2d_added_a_sequence_number_to_articles.py
  2. 14
      asl_articles/articles.py
  3. 1
      asl_articles/models.py
  4. 28
      asl_articles/publications.py
  5. 8
      asl_articles/tests/fixtures/article-order.json
  6. 4
      asl_articles/tests/test_articles.py
  7. 90
      asl_articles/tests/test_publications.py
  8. 8
      web/package-lock.json
  9. 1
      web/package.json
  10. 1
      web/src/App.css
  11. 1
      web/src/ModalForm.css
  12. 4
      web/src/ModalForm.js
  13. 8
      web/src/PublicationSearchResult.css
  14. 53
      web/src/PublicationSearchResult.js
  15. 238
      web/src/PublicationSearchResult2.js

@ -0,0 +1,28 @@
"""Added a sequence number to articles.
Revision ID: 064497c51f2d
Revises: 07ff815f36b7
Create Date: 2020-01-23 06:30:49.893693
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '064497c51f2d'
down_revision = '07ff815f36b7'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('article', sa.Column('article_seqno', sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('article', 'article_seqno')
# ### end Alembic commands ###

@ -5,6 +5,7 @@ import base64
import logging
from flask import request, jsonify, abort
from sqlalchemy.sql.expression import func
from asl_articles import app, db
from asl_articles.models import Article, Author, ArticleAuthor, Scenario, ArticleScenario, ArticleImage
@ -83,6 +84,7 @@ def create_article():
db.session.add( article )
db.session.flush()
new_article_id = article.article_id
_set_seqno( article, article.pub_id )
_save_authors( article, updated )
_save_scenarios( article, updated )
_save_image( article, updated )
@ -98,6 +100,16 @@ def create_article():
extras[ "tags" ] = do_get_tags()
return make_ok_response( updated=updated, extras=extras, warnings=warnings )
def _set_seqno( article, pub_id ):
"""Set an article's seq#."""
if pub_id:
max_seqno = db.session.query( func.max( Article.article_seqno ) ) \
.filter( Article.pub_id == pub_id ) \
.scalar()
article.article_seqno = 1 if max_seqno is None else max_seqno+1
else:
article.article_seqno = None
def _save_authors( article, updated_fields ):
"""Save the article's authors."""
@ -218,6 +230,8 @@ def update_article():
article = Article.query.get( article_id )
if not article:
abort( 404 )
if vals["pub_id"] != article.pub_id:
_set_seqno( article, vals["pub_id"] )
vals[ "time_updated" ] = datetime.datetime.now()
apply_attrs( article, vals )
_save_authors( article, updated )

@ -61,6 +61,7 @@ class Article( db.Model ):
article_title = db.Column( db.String(200), nullable=False )
article_subtitle = db.Column( db.String(200) )
article_snippet = db.Column( db.String(5000) )
article_seqno = db.Column( db.Integer )
article_pageno = db.Column( db.String(20) )
article_url = db.Column( db.String(500) )
article_tags = db.Column( db.String(1000) )

@ -8,6 +8,7 @@ from flask import request, jsonify, abort
from asl_articles import app, db
from asl_articles.models import Publication, PublicationImage, Article
from asl_articles.articles import get_article_vals
from asl_articles.tags import do_get_tags
from asl_articles import search
from asl_articles.utils import get_request_args, clean_request_args, clean_tags, encode_tags, decode_tags, \
@ -135,6 +136,7 @@ def update_publication():
)
warnings = []
updated = clean_request_args( vals, _FIELD_NAMES, warnings, _logger )
article_order = request.json.get( "article_order" )
# NOTE: Tags are stored in the database using \n as a separator, so we need to encode *after* cleaning them.
cleaned_tags = clean_tags( vals.get("pub_tags"), warnings )
@ -149,6 +151,21 @@ def update_publication():
vals[ "time_updated" ] = datetime.datetime.now()
apply_attrs( pub, vals )
_save_image( pub, updated )
if article_order:
query = Article.query.filter( Article.pub_id == pub_id )
articles = { int(a.article_id): a for a in query }
for n,article_id in enumerate(article_order):
if article_id not in articles:
_logger.warning( "Can't set seq# for article %d, not in publication %d: %s",
article_id, pub_id, article_order
)
continue
articles[ article_id ].article_seqno = n
del articles[ article_id ]
if articles:
_logger.warning( "seq# was not set for some articles in publication %d: %s",
pub_id, ", ".join(str(k) for k in articles)
)
db.session.commit()
search.add_or_update_publication( None, pub )
@ -189,3 +206,14 @@ def delete_publication( pub_id ):
extras[ "publications" ] = do_get_publications()
extras[ "tags" ] = do_get_tags()
return make_ok_response( extras=extras )
# ---------------------------------------------------------------------
@app.route( "/publication/<pub_id>/articles" )
def get_publication_articles( pub_id ):
"""Get the articles for a publication."""
pub = Publication.query.get( pub_id )
if not pub:
abort( 404 )
articles = sorted( pub.articles, key=lambda a: 999 if a.article_seqno is None else a.article_seqno )
return jsonify( [ get_article_vals(a) for a in articles ] )

@ -0,0 +1,8 @@
{
"publication": [
{ "pub_id": 10, "pub_name": "Publication A" },
{ "pub_id": 20, "pub_name": "Publication B" }
]
}

@ -373,6 +373,9 @@ def create_article( vals, toast_type="info" ):
if key in ["authors","scenarios","tags"]:
select = ReactSelect( find_child( ".row.{} .react-select".format(key), dlg ) )
select.update_multiselect_values( *val )
elif key == "publication":
select = ReactSelect( find_child( ".row.publication .react-select", dlg ) )
select.select_by_name( val )
else:
sel = ".row.{} {}".format( key , "textarea" if key == "snippet" else "input" )
set_elem_text( find_child( sel, dlg ), val )
@ -419,7 +422,6 @@ def edit_article( sr, vals, toast_type="info", expected_error=None ): #pylint: d
sel = "input.pageno"
else:
sel = ".row.{} input".format( key )
print( "@@@",sel)
set_elem_text( find_child( sel, dlg ), val )
set_toast_marker( toast_type )
find_child( "button.ok", dlg ).click()

@ -5,11 +5,13 @@ import urllib.request
import urllib.error
import json
import base64
from collections import defaultdict
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import StaleElementReferenceException
from asl_articles.search import SEARCH_ALL, SEARCH_ALL_PUBLICATIONS, SEARCH_ALL_ARTICLES
from asl_articles.tests.test_articles import create_article, edit_article
from asl_articles.tests.utils import init_tests, load_fixtures, select_main_menu_option, select_sr_menu_option, \
do_search, get_search_results, get_search_result_names, check_search_result, \
wait_for, wait_for_elem, wait_for_not_elem, find_child, find_children, find_search_result, set_elem_text, \
@ -402,6 +404,94 @@ def test_timestamps( webdriver, flask_app, dbconn ):
# ---------------------------------------------------------------------
def test_article_order( webdriver, flask_app, dbconn ):
"""Test ordering of articles."""
# initialize
init_tests( webdriver, flask_app, dbconn, fixtures="article-order.json" )
def check_article_order( expected ):
# check the article order in the database
articles = defaultdict( list )
query = dbconn.execute(
"SELECT pub_name, article_title, article_seqno"
" FROM article LEFT JOIN publication"
" ON article.pub_id = publication.pub_id"
" ORDER BY article.pub_id, article_seqno"
)
for row in query:
articles[ row[0] ].append( ( row[1], row[2] ) )
assert articles == expected
# check the article order in the UI
results = do_search( SEARCH_ALL )
for pub_name in expected:
if not pub_name:
continue
sr = find_search_result( pub_name, results )
select_sr_menu_option( sr, "edit" )
dlg = wait_for_elem( 2, "#publication-form" )
articles = [ a.text for a in find_children( ".articles li.draggable", dlg ) ]
find_child( ".cancel", dlg ).click()
assert articles == [ a[0] for a in expected[pub_name] ]
# create some articles (to check the seq# is being assigned correctly)
create_article( { "title": "Article 1", "publication": "Publication A" } )
check_article_order( {
"Publication A": [ ("Article 1",1) ]
} )
create_article( { "title": "Article 2", "publication": "Publication A" } )
check_article_order( {
"Publication A": [ ("Article 1",1), ("Article 2",2) ]
} )
create_article( { "title": "Article 3", "publication": "Publication A" } )
check_article_order( {
"Publication A": [ ("Article 1",1), ("Article 2",2), ("Article 3",3) ]
} )
# create some articles (to check the seq# is being assigned correctly)
create_article( { "title": "Article 5", "publication": "Publication B" } )
check_article_order( {
"Publication A": [ ("Article 1",1), ("Article 2",2), ("Article 3",3) ],
"Publication B": [ ("Article 5",1) ]
} )
create_article( { "title": "Article 6", "publication": "Publication B" } )
check_article_order( {
"Publication A": [ ("Article 1",1), ("Article 2",2), ("Article 3",3) ],
"Publication B": [ ("Article 5",1), ("Article 6",2) ]
} )
# NOTE: It would be nice to test re-ordering articles via drag-and-drop,
# but Selenium just ain't co-operating... :-/
# move an article to another publication
sr = find_search_result( "Article 1" )
edit_article( sr, { "publication": "Publication B" } )
check_article_order( {
"Publication A": [ ("Article 2",2), ("Article 3",3) ],
"Publication B": [ ("Article 5",1), ("Article 6",2), ("Article 1",3) ]
} )
# remove the article from the publication
sr = find_search_result( "Article 1" )
edit_article( sr, { "publication": "(none)" } )
check_article_order( {
"Publication A": [ ("Article 2",2), ("Article 3",3) ],
"Publication B": [ ("Article 5",1), ("Article 6",2) ],
None: [ ("Article 1", None) ]
} )
# add the article to another publication
sr = find_search_result( "Article 1" )
edit_article( sr, { "publication": "Publication A" } )
check_article_order( {
"Publication A": [ ("Article 2",2), ("Article 3",3), ("Article 1",4) ],
"Publication B": [ ("Article 5",1), ("Article 6",2) ],
} )
# ---------------------------------------------------------------------
def create_publication( vals, toast_type="info" ):
"""Create a new publication."""

@ -10936,6 +10936,14 @@
"scheduler": "^0.17.0"
}
},
"react-drag-listview": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/react-drag-listview/-/react-drag-listview-0.1.6.tgz",
"integrity": "sha512-0nSWkR1bMLKgLZIYY2YVURYapppzy46FNSs9uAcCxceo2lnajngzLQ3tBgWaTjKTlWMXD0MAcDUWFDYdqMPYUg==",
"requires": {
"prop-types": "^15.5.8"
}
},
"react-draggable": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.1.0.tgz",

@ -10,6 +10,7 @@
"lodash.clonedeep": "^4.5.0",
"react": "^16.11.0",
"react-dom": "^16.11.0",
"react-drag-listview": "^0.1.6",
"react-draggable": "^4.1.0",
"react-scripts": "3.2.0",
"react-select": "^3.0.8",

@ -15,6 +15,7 @@
[data-reach-menu-item][data-selected] { background: #90caf9 ; color: black ; }
.MuiDialogTitle-root { padding: 10px 16px 6px 16px !important ; }
.MuiDialogContent-root>div { margin-bottom: 1em ; }
.MuiDialogActions-root { background: #f0ffff ; }
.MuiDialogActions-root .MuiButton-text { padding: 6px 8px 2px 8px ; }
.MuiDialogActions-root button:hover { background: #40a0c0 ; color: white ; }

@ -3,7 +3,6 @@
.modal-form .row { display: flex ; align-items: center ; margin-bottom: 0.25em ; }
.modal-form .row label { margin-top: 3px ; }
.modal-form .row label.select { margin-top: 10px ; }
.modal-form .row input , .row textarea , .row .react-select { flex-grow: 1 ; }
.modal-form .row.image { display: block ; }

@ -23,7 +23,9 @@ export default class ModalForm extends React.Component
}
return ( <Dialog id={this.props.formId} className="modal-form" open={true} onClose={this.onClose.bind(this)} disableBackdropClick>
<DialogTitle style={{background:this.props.titleColor}}> {this.props.title} </DialogTitle>
<DialogContent dividers> {this.props.content} </DialogContent>
<DialogContent dividers>
{ typeof this.props.content === "function" ? this.props.content() : this.props.content }
</DialogContent>
<DialogActions> {buttons} </DialogActions>
</Dialog> ) ;
}

@ -4,3 +4,11 @@
#publication-form .row.edition input { flex-grow: 0 ; width: 3em ; }
#publication-form .row.description { flex-direction: column ; align-items: initial ; margin-top: -0.5em ; }
#publication-form .row.description textarea { min-height: 6em ; }
#publication-form .articles { flex-direction: column ; align-items: initial ; }
#publication-form .articles { font-size: 90% ; padding: 0 0.5em 0.5em 0.5em ; border: 1px solid #c5c5c5 ; }
#publication-form .articles legend { margin-left: 1em ; padding: 0 5px ; font-size: 100% ; }
#publication-form .articles li.draggable { list-style-type: none ; margin: 0 0 2px -16px ; border: 1px solid #91cdf5 ; padding: 2px 5px ; background: #d3edfc ; cursor: pointer ; }
#publication-form .articles .pageno { font-size: 80% ; font-style: italic ; color: #666 ; }
.dragLine { border-bottom: 2px solid #1080d0 !important ; }

@ -63,7 +63,7 @@ export class PublicationSearchResult extends React.Component
}
static onNewPublication( notify ) {
PublicationSearchResult2._doEditPublication( {}, (newVals,refs) => {
PublicationSearchResult2._doEditPublication( {}, null, (newVals,refs) => {
axios.post( gAppRef.makeFlaskUrl( "/publication/create", {list:1} ), newVals )
.then( resp => {
// update the caches
@ -86,28 +86,37 @@ export class PublicationSearchResult extends React.Component
}
onEditPublication() {
PublicationSearchResult2._doEditPublication( this.props.data, (newVals,refs) => {
// send the updated details to the server
newVals.pub_id = this.props.data.pub_id ;
axios.post( gAppRef.makeFlaskUrl( "/publication/update", {list:1} ), newVals )
.then( resp => {
// update the caches
gAppRef.caches.publications = resp.data.publications ;
gAppRef.caches.tags = resp.data.tags ;
// update the UI with the new details
applyUpdatedVals( this.props.data, newVals, resp.data.updated, refs ) ;
removeSpecialFields( this.props.data ) ;
this.forceUpdate() ;
if ( resp.data.warnings )
gAppRef.showWarnings( "The publication was updated OK.", resp.data.warnings ) ;
else
gAppRef.showInfoToast( <div> The publication was updated OK. </div> ) ;
gAppRef.closeModalForm() ;
} )
.catch( err => {
gAppRef.showErrorMsg( <div> Couldn't update the publication: <div className="monospace"> {err.toString()} </div> </div> ) ;
// get the articles for this publication
axios.get( gAppRef.makeFlaskUrl( "/publication/" + this.props.data.pub_id + "/articles" ) )
.then( resp => {
let articles = resp.data ; // nb: _doEditPublication() might modify this list
PublicationSearchResult2._doEditPublication( this.props.data, articles, (newVals,refs) => {
// send the updated details to the server
newVals.pub_id = this.props.data.pub_id ;
newVals.article_order = articles.map( a => a.article_id ) ;
axios.post( gAppRef.makeFlaskUrl( "/publication/update", {list:1} ), newVals )
.then( resp => {
// update the caches
gAppRef.caches.publications = resp.data.publications ;
gAppRef.caches.tags = resp.data.tags ;
// update the UI with the new details
applyUpdatedVals( this.props.data, newVals, resp.data.updated, refs ) ;
removeSpecialFields( this.props.data ) ;
this.forceUpdate() ;
if ( resp.data.warnings )
gAppRef.showWarnings( "The publication was updated OK.", resp.data.warnings ) ;
else
gAppRef.showInfoToast( <div> The publication was updated OK. </div> ) ;
gAppRef.closeModalForm() ;
} )
.catch( err => {
gAppRef.showErrorMsg( <div> Couldn't update the publication: <div className="monospace"> {err.toString()} </div> </div> ) ;
} ) ;
} ) ;
} );
} )
.catch( err => {
gAppRef.showErrorMsg( <div> Couldn't load the articles: <div className="monospace"> {err.toString()} </div> </div> ) ;
} ) ;
}
onDeletePublication() {

@ -1,6 +1,7 @@
import React from "react" ;
import Select from "react-select" ;
import CreatableSelect from "react-select/creatable" ;
import ReactDragListView from "react-drag-listview/lib/index.js" ;
import { gAppRef } from "./index.js" ;
import { ImageFileUploader } from "./FileUploader.js" ;
import { sortSelectableOptions, unloadCreatableSelect } from "./utils.js" ;
@ -10,111 +11,148 @@ import { sortSelectableOptions, unloadCreatableSelect } from "./utils.js" ;
export class PublicationSearchResult2
{
static _doEditPublication( vals, notify ) {
static _doEditPublication( vals, articles, notify ) {
// initialize
let refs = {} ;
// initialize the image
let imageFilename=null, imageData=null ;
let imageRef=null, uploadImageRef=null, removeImageRef=null ;
let imageUrl = gAppRef.makeFlaskUrl( "/images/publication/" + vals.pub_id ) ;
imageUrl += "?foo=" + Math.random() ; // FUDGE! To bypass the cache :-/
let onMissingImage = (evt) => {
imageRef.src = "/images/placeholder.png" ;
removeImageRef.style.display = "none" ;
} ;
let onUploadImage = (evt) => {
if ( evt === null && !gAppRef.isFakeUploads() ) {
// nb: the publication image was clicked - trigger an upload request
uploadImageRef.click() ;
return ;
function doRender() {
// initialize the image
let imageRef=null, uploadImageRef=null, removeImageRef=null ;
let imageUrl = gAppRef.makeFlaskUrl( "/images/publication/" + vals.pub_id ) ;
imageUrl += "?foo=" + Math.random() ; // FUDGE! To bypass the cache :-/
let onMissingImage = (evt) => {
imageRef.src = "/images/placeholder.png" ;
removeImageRef.style.display = "none" ;
} ;
let onUploadImage = (evt) => {
if ( evt === null && !gAppRef.isFakeUploads() ) {
// nb: the publication image was clicked - trigger an upload request
uploadImageRef.click() ;
return ;
}
let fileUploader = new ImageFileUploader() ;
fileUploader.getFile( evt, imageRef, removeImageRef, (fname,data) => {
imageFilename = fname ;
imageData = data ;
} ) ;
} ;
let onRemoveImage = () => {
imageData = "{remove}" ;
imageRef.src = "/images/placeholder.png" ;
removeImageRef.style.display = "none" ;
} ;
// initialize the publishers
let publishers = [ { value: null, label: <i>(none)</i> } ] ;
let currPubl = 0 ;
for ( let p of Object.entries(gAppRef.caches.publishers) ) {
publishers.push( {
value: p[1].publ_id,
label: <span dangerouslySetInnerHTML={{__html: p[1].publ_name}} />
} ) ;
if ( p[1].publ_id === vals.publ_id )
currPubl = publishers.length - 1 ;
}
let fileUploader = new ImageFileUploader() ;
fileUploader.getFile( evt, imageRef, removeImageRef, (fname,data) => {
imageFilename = fname ;
imageData = data ;
} ) ;
} ;
let onRemoveImage = () => {
imageData = "{remove}" ;
imageRef.src = "/images/placeholder.png" ;
removeImageRef.style.display = "none" ;
} ;
sortSelectableOptions( publishers ) ;
// initialize the publishers
let publishers = [ { value: null, label: <i>(none)</i> } ] ;
let currPubl = 0 ;
for ( let p of Object.entries(gAppRef.caches.publishers) ) {
publishers.push( {
value: p[1].publ_id,
label: <span dangerouslySetInnerHTML={{__html: p[1].publ_name}} />
} ) ;
if ( p[1].publ_id === vals.publ_id )
currPubl = publishers.length - 1 ;
}
sortSelectableOptions( publishers ) ;
// initialize the publications
// NOTE: As a convenience, we provide a droplist of known publication names (without edition #'s),
// to make it easier to add a new edition of an existing publication.
let publications = {} ;
for ( let p of Object.entries(gAppRef.caches.publications) )
publications[ p[1].pub_name ] = p[1] ;
let publications2 = [] ;
for ( let pub_name in publications ) {
const pub = publications[ pub_name ] ;
publications2.push( { value: pub.pub_id, label: pub.pub_name } ) ;
}
sortSelectableOptions( publications2 ) ;
let currPub = null ;
for ( let pub of publications2 ) {
if ( pub.label === vals.pub_name ) {
currPub = pub ;
break ;
// initialize the publications
// NOTE: As a convenience, we provide a droplist of known publication names (without edition #'s),
// to make it easier to add a new edition of an existing publication.
let publications = {} ;
for ( let p of Object.entries(gAppRef.caches.publications) )
publications[ p[1].pub_name ] = p[1] ;
let publications2 = [] ;
for ( let pub_name in publications ) {
const pub = publications[ pub_name ] ;
publications2.push( { value: pub.pub_id, label: pub.pub_name } ) ;
}
sortSelectableOptions( publications2 ) ;
let currPub = null ;
for ( let pub of publications2 ) {
if ( pub.label === vals.pub_name ) {
currPub = pub ;
break ;
}
}
}
// initialize the tags
const tags = gAppRef.makeTagLists( vals.pub_tags ) ;
// prepare the form content
/* eslint-disable jsx-a11y/img-redundant-alt */
const content = <div>
<div className="image-container">
<div className="row image">
<img src={imageUrl} className="image" onError={onMissingImage} onClick={() => onUploadImage(null)} ref={r => imageRef=r} alt="Click to upload an image for this publication." />
<img src="/images/delete.png" className="remove-image" onClick={onRemoveImage} ref={r => removeImageRef=r} alt="Remove the publication's image." />
<input type="file" accept="image/*" onChange={onUploadImage} style={{display:"none"}} ref={r => uploadImageRef=r} />
// initialize the tags
const tags = gAppRef.makeTagLists( vals.pub_tags ) ;
// initialize the articles
function make_article_display_name( article ) {
let pageno = null ;
if ( article.article_pageno ) {
pageno = ( <span className="pageno">
{ article.article_pageno.substr( 0, 1 ) === "p" || "p" }
{article.article_pageno}
</span> ) ;
}
return <span> {article.article_title} { pageno && <span className="pageno"> ({pageno}) </span> } </span> ;
}
const dragProps = {
onDragEnd( fromIndex, toIndex ) {
const item = articles.splice( fromIndex, 1 )[0] ;
articles.splice( toIndex, 0, item ) ;
gAppRef._modalFormRef.current.forceUpdate() ;
},
nodeSelector: "li",
lineClassName: "dragLine",
} ;
// prepare the form content
/* eslint-disable jsx-a11y/img-redundant-alt */
const content = <div>
<div className="image-container">
<div className="row image">
<img src={imageUrl} className="image" onError={onMissingImage} onClick={() => onUploadImage(null)} ref={r => imageRef=r} alt="Click to upload an image for this publication." />
<img src="/images/delete.png" className="remove-image" onClick={onRemoveImage} ref={r => removeImageRef=r} alt="Remove the publication's image." />
<input type="file" accept="image/*" onChange={onUploadImage} style={{display:"none"}} ref={r => uploadImageRef=r} />
</div>
</div>
<div className="row name"> <label className="select top"> Name: </label>
<CreatableSelect className="react-select" classNamePrefix="react-select" options={publications2} autoFocus
defaultValue = {currPub}
ref = { r => refs.pub_name=r }
/>
</div>
<div className="row edition"> <label className="top"> Edition: </label>
<input type="text" defaultValue={vals.pub_edition} ref={r => refs.pub_edition=r} />
</div>
<div className="row publisher"> <label className="select top"> Publisher: </label>
<Select className="react-select" classNamePrefix="react-select" options={publishers} isSearchable={true}
defaultValue = { publishers[ currPubl ] }
ref = { r => refs.publ_id=r }
/>
</div>
</div>
<div className="row name"> <label className="select top"> Name: </label>
<CreatableSelect className="react-select" classNamePrefix="react-select" options={publications2} autoFocus
defaultValue = {currPub}
ref = { r => refs.pub_name=r }
/>
</div>
<div className="row edition"> <label className="top"> Edition: </label>
<input type="text" defaultValue={vals.pub_edition} ref={r => refs.pub_edition=r} />
</div>
<div className="row publisher"> <label className="select top"> Publisher: </label>
<Select className="react-select" classNamePrefix="react-select" options={publishers} isSearchable={true}
defaultValue = { publishers[ currPubl ] }
ref = { r => refs.publ_id=r }
/>
</div>
<div className="row description"> <label> Description: </label>
<textarea defaultValue={vals.pub_description} ref={r => refs.pub_description=r} />
</div>
<div className="row tags"> <label className="select"> Tags: </label>
<CreatableSelect className="react-select" classNamePrefix="react-select" options={tags[1]} isMulti
defaultValue = {tags[0]}
ref = { r => refs.pub_tags=r }
/>
</div>
<div className="row url"> <label> Web: </label>
<input type="text" defaultValue={vals.pub_url} ref={r => refs.pub_url=r} />
</div>
</div> ;
<div className="row description"> <label> Description: </label>
<textarea defaultValue={vals.pub_description} ref={r => refs.pub_description=r} />
</div>
<div className="row tags"> <label className="select"> Tags: </label>
<CreatableSelect className="react-select" classNamePrefix="react-select" options={tags[1]} isMulti
defaultValue = {tags[0]}
ref = { r => refs.pub_tags=r }
/>
</div>
<div className="row url"> <label> Web: </label>
<input type="text" defaultValue={vals.pub_url} ref={r => refs.pub_url=r} />
</div>
{ articles && articles.length > 0 &&
<fieldset className="articles"> <legend> Articles </legend>
<ReactDragListView {...dragProps}> <ul>
{ articles.map( a => (
<li key={a.article_id} className="draggable"> {make_article_display_name(a)} </li>
) ) }
</ul> </ReactDragListView>
</fieldset>
}
</div> ;
return content ;
}
// prepare the form buttons
const buttons = {
@ -149,7 +187,11 @@ export class PublicationSearchResult2
// show the form
const isNew = Object.keys( vals ).length === 0 ;
gAppRef.showModalForm( "publication-form", isNew?"New publication":"Edit publication", "#e5f700", content, buttons ) ;
gAppRef.showModalForm( "publication-form",
isNew ? "New publication" : "Edit publication", "#e5f700",
doRender,
buttons
) ;
}
}

Loading…
Cancel
Save