From 3e8a6e5091908446f161d8ec021d604602b3a028 Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Tue, 24 Jul 2018 23:35:59 -0400 Subject: [PATCH] migrate relation widget --- packages/netlify-cms-core/package.json | 2 - .../scripts/load-extensions.js | 4 +- .../Editor/EditorControlPane/EditorControl.js | 20 ++++- .../Editor/EditorControlPane/Widget.js | 15 ++++ .../Relation/ReactAutosuggest.css | 35 -------- .../EditorWidgets/Relation/Relation.css | 1 - .../EditorWidgets/Relation/RelationPreview.js | 10 --- .../src/components/EditorWidgets/index.js | 3 - .../netlify-cms-ui-default/src/Dropdown.js | 27 +------ packages/netlify-cms-ui-default/src/styles.js | 29 +++++++ .../netlify-cms-widget-relation/package.json | 35 ++++++++ .../src}/RelationControl.js | 79 +++++++++++++------ .../src/RelationPreview.js | 13 +++ .../netlify-cms-widget-relation/src/index.js | 2 + .../webpack.config.js | 3 + 15 files changed, 174 insertions(+), 104 deletions(-) delete mode 100644 packages/netlify-cms-core/src/components/EditorWidgets/Relation/ReactAutosuggest.css delete mode 100644 packages/netlify-cms-core/src/components/EditorWidgets/Relation/Relation.css delete mode 100644 packages/netlify-cms-core/src/components/EditorWidgets/Relation/RelationPreview.js create mode 100644 packages/netlify-cms-widget-relation/package.json rename packages/{netlify-cms-core/src/components/EditorWidgets/Relation => netlify-cms-widget-relation/src}/RelationControl.js (75%) create mode 100644 packages/netlify-cms-widget-relation/src/RelationPreview.js create mode 100644 packages/netlify-cms-widget-relation/src/index.js create mode 100644 packages/netlify-cms-widget-relation/webpack.config.js diff --git a/packages/netlify-cms-core/package.json b/packages/netlify-cms-core/package.json index 40261eb2..bfafbc7f 100644 --- a/packages/netlify-cms-core/package.json +++ b/packages/netlify-cms-core/package.json @@ -41,7 +41,6 @@ "prop-types": "^15.5.10", "react": "^16.4.1", "react-aria-menubutton": "^5.1.0", - "react-autosuggest": "^9.3.2", "react-dnd": "^2.5.4", "react-dnd-html5-backend": "^2.5.4", "react-dom": "^16.0.0", @@ -70,7 +69,6 @@ "toml-j0.4": "^1.1.1", "tomlify-j0.4": "^3.0.0-alpha.0", "url": "^0.11.0", - "uuid": "^3.1.0", "what-input": "^5.0.3" }, "devDependencies": { diff --git a/packages/netlify-cms-core/scripts/load-extensions.js b/packages/netlify-cms-core/scripts/load-extensions.js index 553e6bc0..8a759dd8 100644 --- a/packages/netlify-cms-core/scripts/load-extensions.js +++ b/packages/netlify-cms-core/scripts/load-extensions.js @@ -12,7 +12,7 @@ import { ListControl, ListPreview } from 'netlify-cms-widget-list'; import { ObjectControl, ObjectPreview } from 'netlify-cms-widget-object'; import { MarkdownControl, MarkdownPreview } from 'netlify-cms-widget-markdown'; import { NumberControl, NumberPreview } from 'netlify-cms-widget-number'; -// import { RelationControl, RelationPreview } from 'netlify-cms-widget-relation'; +import { RelationControl, RelationPreview } from 'netlify-cms-widget-relation'; import { StringControl, StringPreview } from 'netlify-cms-widget-string'; import { SelectControl, SelectPreview } from 'netlify-cms-widget-select'; import { TextControl, TextPreview } from 'netlify-cms-widget-text'; @@ -31,7 +31,7 @@ registerWidget('list', ListControl, ListPreview); registerWidget('markdown', MarkdownControl, MarkdownPreview); registerWidget('number', NumberControl, NumberPreview); registerWidget('object', ObjectControl, ObjectPreview); -// registerWidget('relation', RelationControl, RelationPreview); +registerWidget('relation', RelationControl, RelationPreview); registerWidget('string', StringControl, StringPreview); registerWidget('text', TextControl, TextPreview); registerWidget('select', SelectControl, SelectPreview); diff --git a/packages/netlify-cms-core/src/components/Editor/EditorControlPane/EditorControl.js b/packages/netlify-cms-core/src/components/Editor/EditorControlPane/EditorControl.js index 9eade098..af14bd64 100644 --- a/packages/netlify-cms-core/src/components/Editor/EditorControlPane/EditorControl.js +++ b/packages/netlify-cms-core/src/components/Editor/EditorControlPane/EditorControl.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux'; import { colors, colorsRaw, transitions, lengths, borders } from 'netlify-cms-ui-default'; import { resolveWidget, getEditorComponents } from 'Lib/registry'; import { addAsset } from 'Actions/media'; +import { query, clearSearch } from 'Actions/search'; import { openMediaLibrary, removeInsertedMedia } from 'Actions/mediaLibrary'; import { getAsset } from 'Reducers'; import Widget from './Widget'; @@ -123,6 +124,10 @@ class EditorControl extends React.Component { removeInsertedMedia, onValidate, processControlRef, + query, + queryHits, + isFetching, + clearSearch, } = this.props; const widgetName = field.get('widget'); const widget = resolveWidget(widgetName); @@ -180,6 +185,10 @@ class EditorControl extends React.Component { getEditorComponents={getEditorComponents} ref={processControlRef && partial(processControlRef, fieldName)} editorControl={ConnectedEditorControl} + query={query} + queryHits={queryHits} + clearSearch={clearSearch} + isFetching={isFetching} /> ); @@ -189,14 +198,23 @@ class EditorControl extends React.Component { const mapStateToProps = (state, ownProps) => ({ mediaPaths: state.mediaLibrary.get('controlMedia'), boundGetAsset: getAsset.bind(null, state), + isFetching: state.search.get('isFetching'), + queryHits: state.search.get('queryHits'), }); const mapDispatchToProps = { openMediaLibrary, removeInsertedMedia, addAsset, + query, + clearSearch, }; -const ConnectedEditorControl = connect(mapStateToProps, mapDispatchToProps)(EditorControl); +const ConnectedEditorControl = connect( + mapStateToProps, + mapDispatchToProps, + null, + { withRef: true }, +)(EditorControl); export default ConnectedEditorControl; diff --git a/packages/netlify-cms-core/src/components/Editor/EditorControlPane/Widget.js b/packages/netlify-cms-core/src/components/Editor/EditorControlPane/Widget.js index 5e9538de..7bfca195 100644 --- a/packages/netlify-cms-core/src/components/Editor/EditorControlPane/Widget.js +++ b/packages/netlify-cms-core/src/components/Editor/EditorControlPane/Widget.js @@ -41,6 +41,13 @@ export default class Widget extends Component { getAsset: PropTypes.func.isRequired, resolveWidget: PropTypes.func.isRequired, getEditorComponents: PropTypes.func.isRequired, + isFetching: PropTypes.node, + query: PropTypes.func.isRequired, + clearSearch: PropTypes.func.isRequired, + queryHits: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object, + ]), }; shouldComponentUpdate(nextProps) { @@ -194,6 +201,10 @@ export default class Widget extends Component { uniqueFieldId, resolveWidget, getEditorComponents, + query, + queryHits, + clearSearch, + isFetching, } = this.props; return React.createElement(controlComponent, { field, @@ -219,6 +230,10 @@ export default class Widget extends Component { editorControl, resolveWidget, getEditorComponents, + query, + queryHits, + clearSearch, + isFetching, }); } } diff --git a/packages/netlify-cms-core/src/components/EditorWidgets/Relation/ReactAutosuggest.css b/packages/netlify-cms-core/src/components/EditorWidgets/Relation/ReactAutosuggest.css deleted file mode 100644 index 9bed23f7..00000000 --- a/packages/netlify-cms-core/src/components/EditorWidgets/Relation/ReactAutosuggest.css +++ /dev/null @@ -1,35 +0,0 @@ -.react-autosuggest__container { - position: relative; -} - -.react-autosuggest__suggestions-container { - display: none; -} - -.react-autosuggest__container--open .react-autosuggest__suggestions-container { - @apply(--dropdownList); - position: absolute; - display: block; - top: 51px; - width: 100%; - z-index: 2; -} - -.react-autosuggest__suggestion { - @apply(--dropdownItem); -} - -.react-autosuggest__suggestions-list { - margin: 0; - padding: 0; - list-style-type: none; -} - -.react-autosuggest__suggestion { - cursor: pointer; - padding: 10px 20px; -} - -.react-autosuggest__suggestion--focused { - background-color: #ddd; -} diff --git a/packages/netlify-cms-core/src/components/EditorWidgets/Relation/Relation.css b/packages/netlify-cms-core/src/components/EditorWidgets/Relation/Relation.css deleted file mode 100644 index 2556f673..00000000 --- a/packages/netlify-cms-core/src/components/EditorWidgets/Relation/Relation.css +++ /dev/null @@ -1 +0,0 @@ -@import "./ReactAutosuggest.css"; diff --git a/packages/netlify-cms-core/src/components/EditorWidgets/Relation/RelationPreview.js b/packages/netlify-cms-core/src/components/EditorWidgets/Relation/RelationPreview.js deleted file mode 100644 index 7ee2fcac..00000000 --- a/packages/netlify-cms-core/src/components/EditorWidgets/Relation/RelationPreview.js +++ /dev/null @@ -1,10 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; - -export default function RelationPreview({ value }) { - return
{ value }
; -} - -RelationPreview.propTypes = { - value: PropTypes.node, -}; diff --git a/packages/netlify-cms-core/src/components/EditorWidgets/index.js b/packages/netlify-cms-core/src/components/EditorWidgets/index.js index 1a95caf1..d453e2a3 100644 --- a/packages/netlify-cms-core/src/components/EditorWidgets/index.js +++ b/packages/netlify-cms-core/src/components/EditorWidgets/index.js @@ -1,8 +1,5 @@ import { registerWidget } from 'Lib/registry'; import UnknownControl from './Unknown/UnknownControl'; import UnknownPreview from './Unknown/UnknownPreview'; -import RelationControl from './Relation/RelationControl'; -import RelationPreview from './Relation/RelationPreview'; -registerWidget('relation', RelationControl, RelationPreview); registerWidget('unknown', UnknownControl, UnknownPreview); diff --git a/packages/netlify-cms-ui-default/src/Dropdown.js b/packages/netlify-cms-ui-default/src/Dropdown.js index edef97e3..c1c95fe3 100644 --- a/packages/netlify-cms-ui-default/src/Dropdown.js +++ b/packages/netlify-cms-ui-default/src/Dropdown.js @@ -30,10 +30,7 @@ const StyledDropdownButton = styled(DropdownButton)` ` const DropdownList = styled.ul` - ${shadows.dropDeep}; - background-color: ${colorsRaw.white}; - border-radius: ${lengths.borderRadius}; - overflow: hidden; + ${components.dropdownList}; margin: 0; position: absolute; top: 0; @@ -50,27 +47,7 @@ const DropdownList = styled.ul` ` const StyledMenuItem = styled(MenuItem)` - ${buttons.button}; - background-color: transparent; - border-radius: 0; - color: ${colorsRaw.gray}; - font-weight: 500; - border-bottom: 1px solid #eaebf1; - padding: 10px 14px; - display: flex; - justify-content: space-between; - align-items: center; - - &:last-of-type { - border-bottom: 0; - } - - &:hover, - &:active, - &:focus { - color: ${colors.active}; - background-color: ${colors.activeBackground}; - } + ${components.dropdownItem}; ` const MenuItemIconContainer = styled.div` diff --git a/packages/netlify-cms-ui-default/src/styles.js b/packages/netlify-cms-ui-default/src/styles.js index 563c7086..ef947670 100644 --- a/packages/netlify-cms-ui-default/src/styles.js +++ b/packages/netlify-cms-ui-default/src/styles.js @@ -256,6 +256,35 @@ const components = { objectWidgetTopBarContainer: css` padding: 0 14px 14px; `, + dropdownList: css` + ${shadows.dropDeep}; + background-color: ${colorsRaw.white}; + border-radius: ${lengths.borderRadius}; + overflow: hidden; + `, + dropdownItem: css` + ${buttons.button}; + background-color: transparent; + border-radius: 0; + color: ${colorsRaw.gray}; + font-weight: 500; + border-bottom: 1px solid #eaebf1; + padding: 10px 14px; + display: flex; + justify-content: space-between; + align-items: center; + + &:last-of-type { + border-bottom: 0; + } + + &:hover, + &:active, + &:focus { + color: ${colors.active}; + background-color: ${colors.activeBackground}; + } + `, } injectGlobal` diff --git a/packages/netlify-cms-widget-relation/package.json b/packages/netlify-cms-widget-relation/package.json new file mode 100644 index 00000000..ce6cf262 --- /dev/null +++ b/packages/netlify-cms-widget-relation/package.json @@ -0,0 +1,35 @@ +{ + "name": "netlify-cms-widget-relation", + "description": "Widget for linking related entries in Netlify CMS.", + "version": "2.0.0-alpha.0", + "main": "dist/netlify-cms-widget-relation.js", + "license": "MIT", + "keywords": [ + "netlify", + "netlify-cms", + "widget", + "relation", + "link" + ], + "sideEffects": false, + "scripts": { + "watch": "webpack -w", + "build": "webpack" + }, + "dependencies": { + "react-autosuggest": "^9.3.2" + }, + "devDependencies": { + "webpack": "^4.16.1", + "webpack-cli": "^3.1.0" + }, + "peerDependencies": { + "immutable": "^3.7.6", + "lodash": "^4.17.10", + "netlify-cms-ui-default": "^2.0.0-alpha.0", + "prop-types": "^15.5.10", + "react": "^16.4.1", + "react-emotion": "^9.2.5", + "uuid": "^3.1.0" + } +} diff --git a/packages/netlify-cms-core/src/components/EditorWidgets/Relation/RelationControl.js b/packages/netlify-cms-widget-relation/src/RelationControl.js similarity index 75% rename from packages/netlify-cms-core/src/components/EditorWidgets/Relation/RelationControl.js rename to packages/netlify-cms-widget-relation/src/RelationControl.js index 23fb7c01..86307661 100644 --- a/packages/netlify-cms-core/src/components/EditorWidgets/Relation/RelationControl.js +++ b/packages/netlify-cms-widget-relation/src/RelationControl.js @@ -1,18 +1,61 @@ +import React from 'react'; import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import { css } from 'react-emotion'; import Autosuggest from 'react-autosuggest'; import uuid from 'uuid/v4'; import { List, Map } from 'immutable'; -import { connect } from 'react-redux'; import { debounce } from 'lodash'; -import { query, clearSearch } from 'Actions/search'; -import { Loader } from 'netlify-cms-ui-default'; +import { Loader, components } from 'netlify-cms-ui-default'; + +/** + * Create a classname for use as a descendant selector. This is generally + * discouraged in Emotion because composition will break the resulting + * classnames, but we won't be using composition here. + */ +const styles = { + suggestionsContainer: css` + display: none; + `, +}; + +/** + * react-autosuggest theme spec: + * https://github.com/moroshko/react-autosuggest#theme-optional + */ +const theme = { + container: css` + position: relative; + `, + containerOpen: css` + ${styles.suggestionsContainer} { + ${components.dropdownList} + position: absolute; + display: block; + top: 51px; + width: 100%; + z-index: 2; + } + `, + suggestion: css` + ${components.drodpownItem}; + cursor: pointer; + padding: 10px 20px; + `, + suggestionsList: css` + margin: 0; + padding: 0; + list-style-type: none; + `, + suggestionHighlighted: css` + background-color: #ddd; + `, +}; function escapeRegexCharacters(str) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } -class RelationControl extends Component { +export default class RelationControl extends React.Component { static propTypes = { onChange: PropTypes.func.isRequired, forID: PropTypes.string.isRequired, @@ -50,12 +93,15 @@ class RelationControl extends Component { } componentWillReceiveProps(nextProps) { + console.log(`receiving`); + console.log(nextProps.queryHits && nextProps.queryHits.get(this.controlID)); if (this.didInitialSearch) return; if (nextProps.queryHits !== this.props.queryHits && nextProps.queryHits.get && nextProps.queryHits.get(this.controlID)) { this.didInitialSearch = true; const suggestion = nextProps.queryHits.get(this.controlID); if (suggestion && suggestion.length === 1) { const val = this.getSuggestionValue(suggestion[0]); + console.log(`accepting ${val}`); this.props.onChange(val, { [nextProps.field.get('collection')]: { [val]: suggestion[0].data } }); } } @@ -75,6 +121,7 @@ class RelationControl extends Component { const { field } = this.props; const collection = field.get('collection'); const searchFields = field.get('searchFields').toJS(); + console.log(`querying ${value}`); this.props.query(this.controlID, collection, searchFields, value); }, 500); @@ -135,28 +182,10 @@ class RelationControl extends Component { renderSuggestion={this.renderSuggestion} inputProps={inputProps} focusInputOnSuggestionClick={false} + theme={theme} /> ); } -} - -function mapStateToProps(state, ownProps) { - const { className } = ownProps; - const isFetching = state.search.get('isFetching'); - const queryHits = state.search.get('queryHits'); - return { isFetching, queryHits, className }; -} - -export default connect( - mapStateToProps, - { - query, - clearSearch, - }, - null, - { - withRef: true, - } -)(RelationControl); +}; diff --git a/packages/netlify-cms-widget-relation/src/RelationPreview.js b/packages/netlify-cms-widget-relation/src/RelationPreview.js new file mode 100644 index 00000000..916cec6b --- /dev/null +++ b/packages/netlify-cms-widget-relation/src/RelationPreview.js @@ -0,0 +1,13 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { WidgetPreviewContainer } from 'netlify-cms-ui-default'; + +const RelationPreview = ({ value }) => ( + { value } +) + +RelationPreview.propTypes = { + value: PropTypes.node, +}; + +export default RelationPreview; diff --git a/packages/netlify-cms-widget-relation/src/index.js b/packages/netlify-cms-widget-relation/src/index.js new file mode 100644 index 00000000..2bdf5132 --- /dev/null +++ b/packages/netlify-cms-widget-relation/src/index.js @@ -0,0 +1,2 @@ +export RelationControl from './RelationControl'; +export RelationPreview from './RelationPreview'; diff --git a/packages/netlify-cms-widget-relation/webpack.config.js b/packages/netlify-cms-widget-relation/webpack.config.js new file mode 100644 index 00000000..42edd361 --- /dev/null +++ b/packages/netlify-cms-widget-relation/webpack.config.js @@ -0,0 +1,3 @@ +const { getConfig } = require('../../scripts/webpack.js'); + +module.exports = getConfig();