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();