├── .gitignore ├── assets ├── CollectionTable.js ├── Columns │ ├── Author.js │ ├── Checkbox.js │ ├── Content.js │ ├── Date.js │ ├── Legacy.js │ └── Response.js ├── Editor.js ├── ListTable.js ├── QuickEdit.js ├── Row.js ├── RowActions.js ├── Tables │ ├── Comments.js │ └── Posts.js ├── TopNav.js ├── lib │ └── loadColumnData.js └── main.js ├── inc ├── class-tablecontroller.php └── class-tablehelper.php ├── license.txt ├── package.json ├── plugin.php ├── readme.md └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | ### macOS ### 2 | *.DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | ### Node ### 7 | # Log 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Dependency directories 14 | node_modules/ 15 | jspm_packages/ -------------------------------------------------------------------------------- /assets/CollectionTable.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import ColumnLegacy from './Columns/Legacy'; 4 | import ListTable from './ListTable'; 5 | import loadColumnData from './lib/loadColumnData'; 6 | 7 | export default class CollectionTable extends React.Component { 8 | constructor( props ) { 9 | super( props ); 10 | 11 | this.state ={ 12 | items: [], 13 | columnData: {}, 14 | editing: null, 15 | loading: true, 16 | page: 1, 17 | replying: null, 18 | total: 0, 19 | totalPages: 1, 20 | }; 21 | // Attach hooks. 22 | this.onCollectionUpdate = () => this._onCollectionUpdate(); 23 | 24 | this.connectCollection( this.props.collection ); 25 | } 26 | 27 | connectCollection( collection ) { 28 | collection.on( 'add', this.onCollectionUpdate ); 29 | collection.on( 'remove', this.onCollectionUpdate ); 30 | collection.on( 'change', this.onCollectionUpdate ); 31 | collection.on( 'reset', this.onCollectionUpdate ); 32 | } 33 | 34 | disconnectCollection( collection ) { 35 | collection.off( 'add', this.onCollectionUpdate ); 36 | collection.off( 'remove', this.onCollectionUpdate ); 37 | collection.off( 'change', this.onCollectionUpdate ); 38 | collection.off( 'reset', this.onCollectionUpdate ); 39 | } 40 | 41 | componentWillReceiveProps( nextProps ) { 42 | if ( nextProps.collection !== this.props.collection ) { 43 | this.disconnectCollection( this.props.collection ); 44 | this.connectCollection( this.props.collection ); 45 | } 46 | } 47 | 48 | _onCollectionUpdate() { 49 | this.setState({ 50 | items: this.props.collection.map( item => item.toJSON() ), 51 | loading: false, 52 | page: this.props.collection.state.currentPage, 53 | total: this.props.collection.state.totalObjects, 54 | totalPages: this.props.collection.state.totalPages, 55 | }); 56 | 57 | loadColumnData( this.props.id, this.props.collection, this.getLegacyColumns() ) 58 | .then( columnData => this.setState({ columnData }) ); 59 | } 60 | 61 | onJump( page ) { 62 | console.log( 'jump', page ); 63 | 64 | // Set to loading... 65 | this.setState({ loading: true, page }); 66 | 67 | // ...and actually load 68 | this.props.collection.more({ 69 | reset: true, 70 | data: { 71 | page: page, 72 | }, 73 | }); 74 | } 75 | 76 | onDelete( id ) { 77 | console.log( 'delete', id ); 78 | } 79 | 80 | onUpdate( id, data ) { 81 | const obj = this.props.collection.get( id ); 82 | const options = { 83 | patch: true, 84 | }; 85 | obj.save( data, options ); 86 | } 87 | 88 | getColumns() { 89 | const componentable = this.props.columnComponents; 90 | const specified = this.props.columns; 91 | 92 | const columnList = Object.entries( specified ).map( ( [ key, value ] ) => { 93 | const component = key in componentable ? componentable[ key ].component : ColumnLegacy; 94 | const data = { 95 | label: value.label, 96 | className: `column-${key}`, 97 | header: component.getHeader( value ), 98 | component, 99 | }; 100 | 101 | return { [key]: data }; 102 | }); 103 | 104 | // Reassemble into an object. 105 | return columnList.reduce( ( obj, item ) => Object.assign( {}, obj, item ), {} ); 106 | } 107 | 108 | getLegacyColumns() { 109 | const componentable = this.props.columnComponents; 110 | const specified = this.props.columns; 111 | 112 | return Object.keys( specified ).filter( key => ! ( key in componentable ) ); 113 | } 114 | 115 | render() { 116 | const { row } = this.props; 117 | 118 | const { items, loading, page, posts, total, totalPages } = this.state; 119 | 120 | const columns = this.getColumns(); 121 | 122 | return this.setState({ editing: id }) } 134 | onDelete={ id => this.onDelete( id ) } 135 | onJump={ page => this.onJump( page ) } 136 | onReply={ id => this.setState({ replying: id }) } 137 | onUpdate={ (id, data) => this.onUpdate( id, data ) } 138 | /> 139 | } 140 | } 141 | 142 | CollectionTable.propTypes = { 143 | collection: React.PropTypes.object.isRequired, 144 | columns: React.PropTypes.object.isRequired, 145 | columnComponents: React.PropTypes.object, 146 | id: React.PropTypes.string.isRequired, 147 | row: React.PropTypes.func, 148 | }; 149 | 150 | CollectionTable.defaultProps = { 151 | columnComponents: {}, 152 | row: props => , 153 | }; 154 | -------------------------------------------------------------------------------- /assets/Columns/Author.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class Author extends React.Component { 4 | render() { 5 | const { item } = this.props; 6 | return 7 | 8 | 9 | { item.author_name || 'Anonymous' } 10 | 11 |
12 | { item.author_email ? 13 | { item.author_email }
14 | : null } 15 | { item.author_ip } 16 | ; 17 | } 18 | } 19 | Author.getHeader = ({ label }) => { label }; 20 | -------------------------------------------------------------------------------- /assets/Columns/Checkbox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const checkboxId = item => `cb-select-${item.id}`; 4 | 5 | export default class Checkbox extends React.Component { 6 | render() { 7 | const { item } = this.props; 8 | const id = checkboxId( item ); 9 | 10 | return 11 | 15 | 19 | ; 20 | } 21 | } 22 | Checkbox.getHeader = () => ; 23 | -------------------------------------------------------------------------------- /assets/Columns/Content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import RowActions from '../RowActions'; 4 | 5 | export default class Content extends React.Component { 6 | render() { 7 | const { item, onDelete, onUpdate } = this.props; 8 | 9 | return 10 |
11 | 12 | 13 | ; 14 | } 15 | } 16 | Content.getHeader = ({ label }) => { label }; 17 | -------------------------------------------------------------------------------- /assets/Columns/Date.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { gmdate } from 'phpdate-js'; 3 | 4 | export default class Date extends React.Component { 5 | render() { 6 | const { item } = this.props; 7 | return 8 | { gmdate( 'Y/m/d g:i a', item.date_gmt ) } 9 | ; 10 | } 11 | } 12 | Date.getHeader = ({ label }) => { label }; 13 | -------------------------------------------------------------------------------- /assets/Columns/Legacy.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import parser from 'react-html-parser'; 3 | 4 | export default class Legacy extends React.Component { 5 | render() { 6 | const { column, columnData, columnKey } = this.props; 7 | 8 | if ( columnKey in columnData ) { 9 | // Custom column available! 10 | return parser( columnData[ columnKey ] )[0]; 11 | } 12 | 13 | return 14 | 15 | 16 | } 17 | } 18 | Legacy.getHeader = ({ id, label }) => { 19 | return { parser( label )[0] }; 20 | }; 21 | -------------------------------------------------------------------------------- /assets/Columns/Response.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class Response extends React.Component { 4 | render() { 5 | const { item, posts } = this.props; 6 | const post = item.post && item.post in posts ? posts[ item.post ] : null; 7 | 8 | return 9 | { post ? 10 |
11 | 12 | { post.title.rendered } 13 | 14 | 15 | View Post 16 | 17 | {/* 18 | 19 | 20 | 21 | Unknown number of comments 22 | 23 | 24 | */} 25 |
26 | : null } 27 | ; 28 | } 29 | } 30 | Response.getHeader = ({ label }) => { label }; 31 | -------------------------------------------------------------------------------- /assets/Editor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default class Editor extends React.Component { 4 | render() { 5 | const { id, value, onChange } = this.props; 6 | 7 | return
8 |
9 |