Compare commits
No commits in common. "3.2.0" and "2" have entirely different histories.
|
@ -1 +0,0 @@
|
|||
<!-- Blank templates are for use by maintainers only! If you aren't a maintainer, please go back and pick one of the issue templates. -->
|
|
@ -1,72 +0,0 @@
|
|||
name: 🪳 Bug Report
|
||||
description: Tell us if something isn't working the way it's supposed to
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
We strongly encourage you to [submit a pull request](https://docs.silverstripe.org/en/contributing/code/) which fixes the issue.
|
||||
Bug reports which are accompanied with a pull request are a lot more likely to be resolved quickly.
|
||||
- type: input
|
||||
id: affected-versions
|
||||
attributes:
|
||||
label: Module version(s) affected
|
||||
description: |
|
||||
What version of _this module_ have you reproduced this bug on?
|
||||
Run `composer info` to see the specific version of each module installed in your project.
|
||||
If you don't have access to that, check inside the help menu in the bottom left of the CMS.
|
||||
placeholder: x.y.z
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of the problem
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: how-to-reproduce
|
||||
attributes:
|
||||
label: How to reproduce
|
||||
description: |
|
||||
⚠️ This is the most important part of the report ⚠️
|
||||
Without a way to easily reproduce your issue, there is little chance we will be able to help you and work on a fix.
|
||||
- Please, take the time to show us some code and/or configuration that is needed for others to reproduce the problem easily.
|
||||
- If the bug is too complex to reproduce with some short code samples, please reproduce it in a public repository and provide a link to the repository along with steps for setting up and reproducing the bug using that repository.
|
||||
- If part of the bug includes an error or exception, please provide a full stack trace.
|
||||
- If any user interaction is required to reproduce the bug, please add an ordered list of steps that are required to reproduce it.
|
||||
- Be as clear as you can, but don't miss any steps out. Simply saying "create a page" is less useful than guiding us through the steps you're taking to create a page, for example.
|
||||
placeholder: |
|
||||
|
||||
#### Code sample
|
||||
```php
|
||||
|
||||
```
|
||||
|
||||
#### Reproduction steps
|
||||
1.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: possible-solution
|
||||
attributes:
|
||||
label: Possible Solution
|
||||
description: |
|
||||
*Optional: only if you have suggestions on a fix/reason for the bug*
|
||||
Please consider [submitting a pull request](https://docs.silverstripe.org/en/contributing/code/) with your solution! It helps get faster feedback and greatly increases the chance of the bug being fixed.
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: "*Optional: any other context about the problem: log messages, screenshots, etc.*"
|
||||
- type: checkboxes
|
||||
id: validations
|
||||
attributes:
|
||||
label: Validations
|
||||
description: "Before submitting the issue, please make sure you do the following:"
|
||||
options:
|
||||
- label: Check that there isn't already an issue that reports the same bug
|
||||
required: true
|
||||
- label: Double check that your reproduction steps work in a fresh installation of [`silverstripe/installer`](https://github.com/silverstripe/silverstripe-installer) (with any code examples you've provided)
|
||||
required: true
|
|
@ -1,35 +0,0 @@
|
|||
name: 🚀 Feature Request
|
||||
description: Submit a feature request (but only if you're planning on implementing it)
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please only submit feature requests if you plan on implementing the feature yourself.
|
||||
See the [contributing code documentation](https://docs.silverstripe.org/en/contributing/code/#make-or-find-a-github-issue) for more guidelines about submitting feature requests.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of the new feature, and why it belongs in core
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: more-info
|
||||
attributes:
|
||||
label: Additional context or points of discussion
|
||||
description: |
|
||||
*Optional: Any additional context, points of discussion, etc that might help validate and refine your idea*
|
||||
- type: checkboxes
|
||||
id: validations
|
||||
attributes:
|
||||
label: Validations
|
||||
description: "Before submitting the issue, please confirm the following:"
|
||||
options:
|
||||
- label: You intend to implement the feature yourself
|
||||
required: true
|
||||
- label: You have read the [contributing guide](https://docs.silverstripe.org/en/contributing/code/)
|
||||
required: true
|
||||
- label: You strongly believe this feature should be in core, rather than being its own community module
|
||||
required: true
|
||||
- label: You have checked for existing issues or pull requests related to this feature (and didn't find any)
|
||||
required: true
|
|
@ -1,8 +0,0 @@
|
|||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Security Vulnerability
|
||||
url: https://docs.silverstripe.org/en/contributing/issues_and_bugs/#reporting-security-issues
|
||||
about: ⚠️ We do not use GitHub issues to track security vulnerability reports. Click "open" on the right to see how to report security vulnerabilities.
|
||||
- name: Support Question
|
||||
url: https://www.silverstripe.org/community/
|
||||
about: We use GitHub issues only to discuss bugs and new features. For support questions, please use one of the support options available in our community channels.
|
|
@ -1,39 +0,0 @@
|
|||
<!--
|
||||
Thanks for contributing, you're awesome! ⭐
|
||||
|
||||
Please read https://docs.silverstripe.org/en/contributing/code/ if you haven't contributed to this project recently.
|
||||
-->
|
||||
## Description
|
||||
<!--
|
||||
Please describe expected and observed behaviour, and what you're fixing.
|
||||
For visual fixes, please include tested browsers and screenshots.
|
||||
-->
|
||||
|
||||
## Manual testing steps
|
||||
<!--
|
||||
Include any manual testing steps here which a reviewer can perform to validate your pull request works correctly.
|
||||
Note that this DOES NOT replace unit or end-to-end tests.
|
||||
-->
|
||||
|
||||
## Issues
|
||||
<!--
|
||||
List all issues here that this pull request fixes/resolves.
|
||||
If there is no issue already, create a new one! You must link your pull request to at least one issue.
|
||||
-->
|
||||
- #
|
||||
|
||||
## Pull request checklist
|
||||
<!--
|
||||
PLEASE check each of these to ensure you have done everything you need to do!
|
||||
If there's something in this list you need help with, please ask so that we can assist you.
|
||||
-->
|
||||
- [ ] The target branch is correct
|
||||
- See [picking the right version](https://docs.silverstripe.org/en/contributing/code/#picking-the-right-version)
|
||||
- [ ] All commits are relevant to the purpose of the PR (e.g. no debug statements, unrelated refactoring, or arbitrary linting)
|
||||
- Small amounts of additional linting are usually okay, but if it makes it hard to concentrate on the relevant changes, ask for the unrelated changes to be reverted, and submitted as a separate PR.
|
||||
- [ ] The commit messages follow our [commit message guidelines](https://docs.silverstripe.org/en/contributing/code/#commit-messages)
|
||||
- [ ] The PR follows our [contribution guidelines](https://docs.silverstripe.org/en/contributing/code/)
|
||||
- [ ] Code changes follow our [coding conventions](https://docs.silverstripe.org/en/contributing/coding_conventions/)
|
||||
- [ ] This change is covered with tests (or tests aren't necessary for this change)
|
||||
- [ ] Any relevant User Help/Developer documentation is updated; for impactful changes, information is added to the changelog for the intended release
|
||||
- [ ] CI is green
|
|
@ -1,10 +1,10 @@
|
|||
name: Keepalive
|
||||
|
||||
on:
|
||||
# At 9:45 PM UTC, on day 26 of the month
|
||||
schedule:
|
||||
- cron: '45 21 26 * *'
|
||||
workflow_dispatch:
|
||||
# The 4th of every month at 10:50am UTC
|
||||
schedule:
|
||||
- cron: '50 10 4 * *'
|
||||
|
||||
jobs:
|
||||
keepalive:
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
name: Merge-up
|
||||
|
||||
on:
|
||||
# At 12:00 PM UTC, only on Wednesday
|
||||
schedule:
|
||||
- cron: '0 12 * * 3'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
merge-up:
|
||||
name: Merge-up
|
||||
# Only run cron on the silverstripe account
|
||||
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Merge-up
|
||||
uses: silverstripe/gha-merge-up@v1
|
|
@ -4,7 +4,7 @@ on:
|
|||
workflow_dispatch:
|
||||
# Run on a schedule of once per quarter
|
||||
schedule:
|
||||
- cron: '45 21 1 */3 *'
|
||||
- cron: '0 0 1 */3 *'
|
||||
|
||||
jobs:
|
||||
update-js:
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
mappings:
|
||||
StringTagField: SilverStripe\TagField\StringTagField
|
||||
TagField: SilverStripe\TagField\TagField
|
||||
TagField_Readonly: SilverStripe\TagField\TagField\Readonly
|
18
README.md
18
README.md
|
@ -5,12 +5,6 @@
|
|||
|
||||
Custom tag input field, for SilverStripe.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
composer require silverstripe/tagfield
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
![Screenshot](docs/en/screenshot.png)
|
||||
|
@ -22,6 +16,16 @@ Note: The field is optimised for usage in the Silverstripe CMS UI.
|
|||
The form field class itself can be used outside of the CMS,
|
||||
but you'll need to build your own frontend to interpret the raw field data (`data-schema` attribute).
|
||||
|
||||
## Requirements
|
||||
|
||||
* Silverstripe 4.0
|
||||
|
||||
## Installing
|
||||
|
||||
```sh
|
||||
$ composer require silverstripe/tagfield
|
||||
```
|
||||
|
||||
## Using
|
||||
|
||||
### Relational Tags
|
||||
|
@ -121,7 +125,7 @@ $field = StringTagField::create(
|
|||
'Tags',
|
||||
'Tags',
|
||||
['one', 'two'],
|
||||
explode(',', $this->Tags ?: '')
|
||||
explode(',', $this->Tags)
|
||||
);
|
||||
|
||||
$field->setShouldLazyLoad(true); // tags should be lazy loaded
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react"
|
||||
]
|
||||
}
|
|
@ -1 +1 @@
|
|||
!function(){"use strict";var e={274:function(e,t,n){var o,a=(o=n(521))&&o.__esModule?o:{default:o};window.document.addEventListener("DOMContentLoaded",(()=>{(0,a.default)()}))},521:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var o=i(n(648)),a=i(n(157));function i(e){return e&&e.__esModule?e:{default:e}}t.default=()=>{o.default.component.registerMany({TagField:a.default})}},157:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.Component=void 0;var o=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=v(t);if(n&&n.has(e))return n.get(e);var o={__proto__:null},a=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if("default"!==i&&Object.prototype.hasOwnProperty.call(e,i)){var s=a?Object.getOwnPropertyDescriptor(e,i):null;s&&(s.get||s.set)?Object.defineProperty(o,i,s):o[i]=e[i]}return o.default=e,n&&n.set(e,o),o}(n(363)),a=g(n(967)),i=g(n(493)),s=g(n(198)),r=g(n(453)),l=g(n(78)),u=g(n(754)),c=g(n(875)),f=g(n(42)),d=g(n(872)),p=g(n(496)),h=g(n(86));function g(e){return e&&e.__esModule?e:{default:e}}function v(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(v=function(e){return e?n:t})(e)}function m(){return m=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(e[o]=n[o])}return e},m.apply(this,arguments)}class y extends o.Component{constructor(e){super(e),this.selectComponentRef=o.default.createRef(),this.state={initalState:e.value?e.value:[],hasChanges:!1},this.isControlled()||(this.state={...this.state,value:e.value}),this.handleChange=this.handleChange.bind(this),this.handleOnBlur=this.handleOnBlur.bind(this),this.isValidNewOption=this.isValidNewOption.bind(this),this.getOptions=this.getOptions.bind(this),this.fetchOptions=(0,p.default)(this.fetchOptions,500)}componentDidUpdate(e,t){if(t.hasChanges!==this.state.hasChanges){const e=this.selectComponentRef.current.inputRef,t=new Event("change",{bubbles:!0});e.dispatchEvent(t)}}getOptions(e){const{lazyLoad:t,options:n}=this.props;return t?e?this.fetchOptions(e):Promise.resolve([]):Promise.resolve(n)}handleChange(e){this.setState({hasChanges:!1}),JSON.stringify(this.state.initalState)!==JSON.stringify(e)&&this.setState({hasChanges:!0}),this.isControlled()?this.props.onChange(e):this.setState({value:e})}isControlled(){return"function"==typeof this.props.onChange}handleOnBlur(){}fetchOptions(e){const{optionUrl:t,labelKey:n,valueKey:o}=this.props,a=d.default.parse(t,!0);return a.query.term=e,(0,c.default)(d.default.format(a),{credentials:"same-origin"}).then((e=>e.json())).then((e=>e.items.map((e=>({[n]:e.Title,[o]:e.Value,Selected:e.Selected})))))}isValidNewOption(e,t,n){const{valueKey:o}=this.props;if(!e)return!1;if(Array.isArray(t)){if(this.valueInOptions(e,t,o))return!1}else if(e===t[o])return!1;return!this.valueInOptions(e,n,o)}valueInOptions(e,t,n){for(const o of t)if(e===o[n])return!0;return!1}render(){const{lazyLoad:e,options:t,creatable:n,multi:a,disabled:i,labelKey:s,valueKey:r,SelectComponent:c,AsyncCreatableSelectComponent:f,AsyncSelectComponent:d,CreatableSelectComponent:p,...h}=this.props,g=e?{loadOptions:this.getOptions}:{options:t};let v=c;if(e&&n?v=f:e?v=d:n&&(v=p),this.isControlled()||(h.value=this.state.value),!a&&h.value&&Object.keys(h.value).length>0){const e=h.value[Object.keys(h.value)[0]];"object"==typeof e&&(h.value=e)}const y=this.state.hasChanges?"":"no-change-track";return o.default.createElement(l.default,null,o.default.createElement(v,m({},h,{isMulti:a,isDisabled:i,cacheOptions:!0,onChange:this.handleChange,onBlur:this.handleOnBlur},g,{getOptionLabel:e=>e[s],getOptionValue:e=>e[r],noOptionsMessage:e=>{let{inputValue:t}=e;return t?u.default._t("TagField.NO_OPTIONS","No options"):u.default._t("TagField.TYPE_TO_SEARCH","Type to search")},isValidNewOption:this.isValidNewOption,getNewOptionData:(e,t)=>({[s]:t,[r]:e}),classNamePrefix:"ss-tag-field",className:y,ref:this.selectComponentRef})))}}t.Component=y,y.propTypes={name:h.default.string.isRequired,labelKey:h.default.string.isRequired,valueKey:h.default.string.isRequired,lazyLoad:h.default.bool,creatable:h.default.bool,multi:h.default.bool,disabled:h.default.bool,options:h.default.arrayOf(h.default.object),optionUrl:h.default.string,value:h.default.any,onChange:h.default.func,onBlur:h.default.func,SelectComponent:h.default.oneOfType([h.default.object,h.default.func]),AsyncCreatableSelectComponent:h.default.oneOfType([h.default.object,h.default.func]),AsyncSelectComponent:h.default.oneOfType([h.default.object,h.default.func]),CreatableSelectComponent:h.default.oneOfType([h.default.object,h.default.func])},y.defaultProps={labelKey:"Title",valueKey:"Value",disabled:!1,lazyLoad:!1,creatable:!1,multi:!1,SelectComponent:a.default,AsyncCreatableSelectComponent:s.default,AsyncSelectComponent:i.default,CreatableSelectComponent:r.default};t.default=(0,f.default)(y)},265:function(e,t,n){var o,a=(o=n(363))&&o.__esModule?o:{default:o},i=n(691),s=n(648);function r(){return r=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(e[o]=n[o])}return e},r.apply(this,arguments)}window.jQuery.entwine("ss",(e=>{e(".js-injector-boot .ss-tag-field.entwine").entwine({ReactRoot:null,onmatch(){const e=this.closest(".cms-content").attr("id"),t=e?{context:e}:{},n=(0,s.loadComponent)("TagField",t),o={...this.data("schema"),onBlur:()=>{this.parents(".cms-edit-form:first").trigger("change")}};let l=this.getReactRoot();l||(l=(0,i.createRoot)(this[0]),this.setReactRoot(l)),l.render(a.default.createElement(n,r({noHolder:!0},o)))},onunmatch(){const e=this.getReactRoot();e&&(e.unmount(),this.setReactRoot(null))}}),e(".cms-edit-form").entwine({getChangeTrackerOptions(){const t=void 0===this.entwineData("ChangeTrackerOptions");let n=this._super();return t&&(n=e.extend({},n),n.ignoreFieldSelector+=", .ss-tag-field .no-change-track :input",this.setChangeTrackerOptions(n)),n}})}))},496:function(e){e.exports=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},o=void 0,a=void 0,i=void 0,s=[];return function(){var l=function(e){return"function"==typeof e?e():e}(t),u=(new Date).getTime(),c=!o||u-o>l;o=u;for(var f=arguments.length,d=Array(f),p=0;p<f;p++)d[p]=arguments[p];if(c&&n.leading)return n.accumulate?Promise.resolve(e.call(this,[d])).then((function(e){return e[0]})):Promise.resolve(e.call.apply(e,[this].concat(d)));if(a?clearTimeout(i):a=function(){var e={};return e.promise=new Promise((function(t,n){e.resolve=t,e.reject=n})),e}(),s.push(d),i=setTimeout(r.bind(this),l),n.accumulate){var h=s.length-1;return a.promise.then((function(e){return e[h]}))}return a.promise};function r(){var t=a;clearTimeout(i),Promise.resolve(n.accumulate?e.call(this,s):e.apply(this,s[s.length-1])).then(t.resolve,t.reject),s=[],a=null}}},78:function(e){e.exports=EmotionCssCacheProvider},42:function(e){e.exports=FieldHolder},648:function(e){e.exports=Injector},875:function(e){e.exports=IsomorphicFetch},872:function(e){e.exports=NodeUrl},86:function(e){e.exports=PropTypes},363:function(e){e.exports=React},691:function(e){e.exports=ReactDomClient},967:function(e){e.exports=ReactSelect},493:function(e){e.exports=ReactSelectAsync},198:function(e){e.exports=ReactSelectAsyncCreatable},453:function(e){e.exports=ReactSelectCreatable},754:function(e){e.exports=i18n}},t={};function n(o){var a=t[o];if(void 0!==a)return a.exports;var i=t[o]={exports:{}};return e[o](i,i.exports,n),i.exports}n(265),n(274)}();
|
||||
!function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s="./client/src/bundles/bundle.js")}({"./client/src/boot/index.js":function(e,t,n){"use strict";var o=n("./client/src/boot/registerComponents.js"),r=function(e){return e&&e.__esModule?e:{default:e}}(o);window.document.addEventListener("DOMContentLoaded",function(){(0,r.default)()})},"./client/src/boot/registerComponents.js":function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(0),s=o(r),i=n("./client/src/components/TagField.js"),a=o(i);t.default=function(){s.default.component.registerMany({TagField:a.default})}},"./client/src/bundles/bundle.js":function(e,t,n){"use strict";n("./client/src/legacy/entwine/TagField.js"),n("./client/src/boot/index.js")},"./client/src/components/TagField.js":function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n={};for(var o in e)t.indexOf(o)>=0||Object.prototype.hasOwnProperty.call(e,o)&&(n[o]=e[o]);return n}function s(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function u(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0}),t.Component=void 0;var l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},h=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(e[o]=n[o])}return e},c=function(){function e(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}return function(t,n,o){return n&&e(t.prototype,n),o&&e(t,o),t}}(),f=n(1),p=o(f),d=n(5),m=o(d),v=n(3),y=o(v),b=n(2),g=o(b),j=n("./node_modules/url/url.js"),O=o(j),w=n("./node_modules/debounce-promise/dist/index.js"),C=o(w),x=n("./node_modules/prop-types/index.js"),_=o(x),S=function(e){function t(e){i(this,t);var n=a(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e));return n.selectComponentRef=null,n.setSelectComponentRef=function(e){n.selectComponentRef=e},n.state={initalState:e.value?e.value:[],hasChanges:!1},n.isControlled()||(n.state=h({},n.state,{value:e.value})),n.handleChange=n.handleChange.bind(n),n.handleOnBlur=n.handleOnBlur.bind(n),n.getOptions=n.getOptions.bind(n),n.fetchOptions=(0,C.default)(n.fetchOptions,500),n}return u(t,e),c(t,[{key:"componentDidUpdate",value:function(e,t){if(t.hasChanges!==this.state.hasChanges){var n=this.selectComponentRef.select.wrapper,o=new Event("change",{bubbles:!0});n.dispatchEvent(o)}}},{key:"getOptions",value:function(e){var t=this.props,n=t.lazyLoad,o=t.options;return n?e?this.fetchOptions(e):Promise.resolve({options:[]}):Promise.resolve({options:o})}},{key:"handleChange",value:function(e){if(this.setState({hasChanges:!1}),JSON.stringify(this.state.initalState)!==JSON.stringify(e)&&this.setState({hasChanges:!0}),this.isControlled())return void this.props.onChange(e);this.setState({value:e})}},{key:"isControlled",value:function(){return"function"==typeof this.props.onChange}},{key:"handleOnBlur",value:function(){}},{key:"fetchOptions",value:function(e){var t=this.props,n=t.optionUrl,o=t.labelKey,r=t.valueKey,i=O.default.parse(n,!0);return i.query.term=e,(0,y.default)(O.default.format(i),{credentials:"same-origin"}).then(function(e){return e.json()}).then(function(e){return{options:e.items.map(function(e){var t;return t={},s(t,o,e.Title),s(t,r,e.Value),s(t,"Selected",e.Selected),t})}})}},{key:"render",value:function(){var e=this.props,t=e.lazyLoad,n=e.options,o=e.creatable,s=r(e,["lazyLoad","options","creatable"]),i=t?{loadOptions:this.getOptions}:{options:n},a=m.default;if(t&&o?a=m.default.AsyncCreatable:t?a=m.default.Async:o&&(a=m.default.Creatable),this.isControlled()||(s.value=this.state.value),!s.multi&&s.value&&Object.keys(s.value).length>0){var u=s.value[Object.keys(s.value)[0]];"object"===(void 0===u?"undefined":l(u))&&(s.value=u)}var c=this.state.hasChanges?"":"no-change-track";return p.default.createElement(a,h({},s,{onChange:this.handleChange,onBlur:this.handleOnBlur},i,{className:c,ref:this.setSelectComponentRef}))}}]),t}(f.Component);S.propTypes={name:_.default.string.isRequired,labelKey:_.default.string.isRequired,valueKey:_.default.string.isRequired,lazyLoad:_.default.bool,creatable:_.default.bool,multi:_.default.bool,disabled:_.default.bool,options:_.default.arrayOf(_.default.object),optionUrl:_.default.string,value:_.default.any,onChange:_.default.func,onBlur:_.default.func},S.defaultProps={labelKey:"Title",valueKey:"Value",disabled:!1,lazyLoad:!1,creatable:!1,multi:!1},t.Component=S,t.default=(0,g.default)(S)},"./client/src/legacy/entwine/TagField.js":function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{default:e}}var r=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(e[o]=n[o])}return e},s=n(1),i=o(s),a=n(4),u=o(a),l=n(0);window.jQuery.entwine("ss",function(e){e(".js-injector-boot .ss-tag-field.entwine").entwine({onmatch:function(){var e=this,t=this.closest(".cms-content").attr("id"),n=t?{context:t}:{},o=(0,l.loadComponent)("TagField",n),s=r({},this.data("schema"),{onBlur:function(){e.parents(".cms-edit-form:first").trigger("change")}});u.default.render(i.default.createElement(o,r({noHolder:!0},s)),this[0])},onunmatch:function(){u.default.unmountComponentAtNode(this[0])}}),e(".cms-edit-form").entwine({getChangeTrackerOptions:function(){var t=void 0===this.entwineData("ChangeTrackerOptions"),n=this._super();return t&&(n=e.extend({},n),n.ignoreFieldSelector+=", .ss-tag-field .no-change-track :input",this.setChangeTrackerOptions(n)),n}})})},"./node_modules/debounce-promise/dist/index.js":function(e,t,n){"use strict";function o(e){return"function"==typeof e?e():e}function r(){var e={};return e.promise=new Promise(function(t,n){e.resolve=t,e.reject=n}),e}e.exports=function(e){function t(){var t=a;clearTimeout(u),Promise.resolve(s.accumulate?e.call(this,l):e.apply(this,l[l.length-1])).then(t.resolve,t.reject),l=[],a=null}var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},i=void 0,a=void 0,u=void 0,l=[];return function(){var h=o(n),c=(new Date).getTime(),f=!i||c-i>h;i=c;for(var p=arguments.length,d=Array(p),m=0;m<p;m++)d[m]=arguments[m];if(f&&s.leading)return s.accumulate?Promise.resolve(e.call(this,[d])).then(function(e){return e[0]}):Promise.resolve(e.call.apply(e,[this].concat(d)));if(a?clearTimeout(u):a=r(),l.push(d),u=setTimeout(t.bind(this),h),s.accumulate){var v=l.length-1;return a.promise.then(function(e){return e[v]})}return a.promise}}},"./node_modules/node-libs-browser/node_modules/punycode/punycode.js":function(e,t,n){(function(e,o){var r;!function(o){function s(e){throw new RangeError(R[e])}function i(e,t){for(var n=e.length,o=[];n--;)o[n]=t(e[n]);return o}function a(e,t){var n=e.split("@"),o="";return n.length>1&&(o=n[0]+"@",e=n[1]),e=e.replace(k,"."),o+i(e.split("."),t).join(".")}function u(e){for(var t,n,o=[],r=0,s=e.length;r<s;)t=e.charCodeAt(r++),t>=55296&&t<=56319&&r<s?(n=e.charCodeAt(r++),56320==(64512&n)?o.push(((1023&t)<<10)+(1023&n)+65536):(o.push(t),r--)):o.push(t);return o}function l(e){return i(e,function(e){var t="";return e>65535&&(e-=65536,t+=I(e>>>10&1023|55296),e=56320|1023&e),t+=I(e)}).join("")}function h(e){return e-48<10?e-22:e-65<26?e-65:e-97<26?e-97:g}function c(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function f(e,t,n){var o=0;for(e=n?A(e/C):e>>1,e+=A(e/t);e>q*O>>1;o+=g)e=A(e/q);return A(o+(q+1)*e/(e+w))}function p(e){var t,n,o,r,i,a,u,c,p,d,m=[],v=e.length,y=0,w=_,C=x;for(n=e.lastIndexOf(S),n<0&&(n=0),o=0;o<n;++o)e.charCodeAt(o)>=128&&s("not-basic"),m.push(e.charCodeAt(o));for(r=n>0?n+1:0;r<v;){for(i=y,a=1,u=g;r>=v&&s("invalid-input"),c=h(e.charCodeAt(r++)),(c>=g||c>A((b-y)/a))&&s("overflow"),y+=c*a,p=u<=C?j:u>=C+O?O:u-C,!(c<p);u+=g)d=g-p,a>A(b/d)&&s("overflow"),a*=d;t=m.length+1,C=f(y-i,t,0==i),A(y/t)>b-w&&s("overflow"),w+=A(y/t),y%=t,m.splice(y++,0,w)}return l(m)}function d(e){var t,n,o,r,i,a,l,h,p,d,m,v,y,w,C,T=[];for(e=u(e),v=e.length,t=_,n=0,i=x,a=0;a<v;++a)(m=e[a])<128&&T.push(I(m));for(o=r=T.length,r&&T.push(S);o<v;){for(l=b,a=0;a<v;++a)(m=e[a])>=t&&m<l&&(l=m);for(y=o+1,l-t>A((b-n)/y)&&s("overflow"),n+=(l-t)*y,t=l,a=0;a<v;++a)if(m=e[a],m<t&&++n>b&&s("overflow"),m==t){for(h=n,p=g;d=p<=i?j:p>=i+O?O:p-i,!(h<d);p+=g)C=h-d,w=g-d,T.push(I(c(d+C%w,0))),h=A(C/w);T.push(I(c(h,0))),i=f(n,y,o==r),n=0,++o}++n,++t}return T.join("")}function m(e){return a(e,function(e){return T.test(e)?p(e.slice(4).toLowerCase()):e})}function v(e){return a(e,function(e){return P.test(e)?"xn--"+d(e):e})}var y,b=("object"==typeof t&&t&&t.nodeType,"object"==typeof e&&e&&e.nodeType,2147483647),g=36,j=1,O=26,w=38,C=700,x=72,_=128,S="-",T=/^xn--/,P=/[^\x20-\x7E]/,k=/[\x2E\u3002\uFF0E\uFF61]/g,R={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},q=g-j,A=Math.floor,I=String.fromCharCode;y={version:"1.4.1",ucs2:{decode:u,encode:l},decode:p,encode:d,toASCII:v,toUnicode:m},void 0!==(r=function(){return y}.call(t,n,t,e))&&(e.exports=r)}()}).call(t,n("./node_modules/webpack/buildin/module.js")(e),n("./node_modules/webpack/buildin/global.js"))},"./node_modules/prop-types/factoryWithThrowingShims.js":function(e,t,n){"use strict";function o(){}function r(){}var s=n("./node_modules/prop-types/lib/ReactPropTypesSecret.js");r.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,r,i){if(i!==s){var a=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw a.name="Invariant Violation",a}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:r,resetWarningCache:o};return n.PropTypes=n,n}},"./node_modules/prop-types/index.js":function(e,t,n){e.exports=n("./node_modules/prop-types/factoryWithThrowingShims.js")()},"./node_modules/prop-types/lib/ReactPropTypesSecret.js":function(e,t,n){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},"./node_modules/querystring-es3/decode.js":function(e,t,n){"use strict";function o(e,t){return Object.prototype.hasOwnProperty.call(e,t)}e.exports=function(e,t,n,s){t=t||"&",n=n||"=";var i={};if("string"!=typeof e||0===e.length)return i;var a=/\+/g;e=e.split(t);var u=1e3;s&&"number"==typeof s.maxKeys&&(u=s.maxKeys);var l=e.length;u>0&&l>u&&(l=u);for(var h=0;h<l;++h){var c,f,p,d,m=e[h].replace(a,"%20"),v=m.indexOf(n);v>=0?(c=m.substr(0,v),f=m.substr(v+1)):(c=m,f=""),p=decodeURIComponent(c),d=decodeURIComponent(f),o(i,p)?r(i[p])?i[p].push(d):i[p]=[i[p],d]:i[p]=d}return i};var r=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)}},"./node_modules/querystring-es3/encode.js":function(e,t,n){"use strict";function o(e,t){if(e.map)return e.map(t);for(var n=[],o=0;o<e.length;o++)n.push(t(e[o],o));return n}var r=function(e){switch(typeof e){case"string":return e;case"boolean":return e?"true":"false";case"number":return isFinite(e)?e:"";default:return""}};e.exports=function(e,t,n,a){return t=t||"&",n=n||"=",null===e&&(e=void 0),"object"==typeof e?o(i(e),function(i){var a=encodeURIComponent(r(i))+n;return s(e[i])?o(e[i],function(e){return a+encodeURIComponent(r(e))}).join(t):a+encodeURIComponent(r(e[i]))}).join(t):a?encodeURIComponent(r(a))+n+encodeURIComponent(r(e)):""};var s=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)},i=Object.keys||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.push(n);return t}},"./node_modules/querystring-es3/index.js":function(e,t,n){"use strict";t.decode=t.parse=n("./node_modules/querystring-es3/decode.js"),t.encode=t.stringify=n("./node_modules/querystring-es3/encode.js")},"./node_modules/url/url.js":function(e,t,n){"use strict";function o(){this.protocol=null,this.slashes=null,this.auth=null,this.host=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.query=null,this.pathname=null,this.path=null,this.href=null}function r(e,t,n){if(e&&l.isObject(e)&&e instanceof o)return e;var r=new o;return r.parse(e,t,n),r}function s(e){return l.isString(e)&&(e=r(e)),e instanceof o?e.format():o.prototype.format.call(e)}function i(e,t){return r(e,!1,!0).resolve(t)}function a(e,t){return e?r(e,!1,!0).resolveObject(t):t}var u=n("./node_modules/node-libs-browser/node_modules/punycode/punycode.js"),l=n("./node_modules/url/util.js");t.parse=r,t.resolve=i,t.resolveObject=a,t.format=s,t.Url=o;var h=/^([a-z0-9.+-]+:)/i,c=/:[0-9]*$/,f=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,p=["<",">",'"',"`"," ","\r","\n","\t"],d=["{","}","|","\\","^","`"].concat(p),m=["'"].concat(d),v=["%","/","?",";","#"].concat(m),y=["/","?","#"],b=/^[+a-z0-9A-Z_-]{0,63}$/,g=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,j={javascript:!0,"javascript:":!0},O={javascript:!0,"javascript:":!0},w={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},C=n("./node_modules/querystring-es3/index.js");o.prototype.parse=function(e,t,n){if(!l.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+typeof e);var o=e.indexOf("?"),r=-1!==o&&o<e.indexOf("#")?"?":"#",s=e.split(r),i=/\\/g;s[0]=s[0].replace(i,"/"),e=s.join(r);var a=e;if(a=a.trim(),!n&&1===e.split("#").length){var c=f.exec(a);if(c)return this.path=a,this.href=a,this.pathname=c[1],c[2]?(this.search=c[2],this.query=t?C.parse(this.search.substr(1)):this.search.substr(1)):t&&(this.search="",this.query={}),this}var p=h.exec(a);if(p){p=p[0];var d=p.toLowerCase();this.protocol=d,a=a.substr(p.length)}if(n||p||a.match(/^\/\/[^@\/]+@[^@\/]+/)){var x="//"===a.substr(0,2);!x||p&&O[p]||(a=a.substr(2),this.slashes=!0)}if(!O[p]&&(x||p&&!w[p])){for(var _=-1,S=0;S<y.length;S++){var T=a.indexOf(y[S]);-1!==T&&(-1===_||T<_)&&(_=T)}var P,k;k=-1===_?a.lastIndexOf("@"):a.lastIndexOf("@",_),-1!==k&&(P=a.slice(0,k),a=a.slice(k+1),this.auth=decodeURIComponent(P)),_=-1;for(var S=0;S<v.length;S++){var T=a.indexOf(v[S]);-1!==T&&(-1===_||T<_)&&(_=T)}-1===_&&(_=a.length),this.host=a.slice(0,_),a=a.slice(_),this.parseHost(),this.hostname=this.hostname||"";var R="["===this.hostname[0]&&"]"===this.hostname[this.hostname.length-1];if(!R)for(var q=this.hostname.split(/\./),S=0,A=q.length;S<A;S++){var I=q[S];if(I&&!I.match(b)){for(var U="",E=0,F=I.length;E<F;E++)I.charCodeAt(E)>127?U+="x":U+=I[E];if(!U.match(b)){var L=q.slice(0,S),N=q.slice(S+1),M=I.match(g);M&&(L.push(M[1]),N.unshift(M[2])),N.length&&(a="/"+N.join(".")+a),this.hostname=L.join(".");break}}}this.hostname.length>255?this.hostname="":this.hostname=this.hostname.toLowerCase(),R||(this.hostname=u.toASCII(this.hostname));var z=this.port?":"+this.port:"",B=this.hostname||"";this.host=B+z,this.href+=this.host,R&&(this.hostname=this.hostname.substr(1,this.hostname.length-2),"/"!==a[0]&&(a="/"+a))}if(!j[d])for(var S=0,A=m.length;S<A;S++){var K=m[S];if(-1!==a.indexOf(K)){var D=encodeURIComponent(K);D===K&&(D=escape(K)),a=a.split(K).join(D)}}var H=a.indexOf("#");-1!==H&&(this.hash=a.substr(H),a=a.slice(0,H));var W=a.indexOf("?");if(-1!==W?(this.search=a.substr(W),this.query=a.substr(W+1),t&&(this.query=C.parse(this.query)),a=a.slice(0,W)):t&&(this.search="",this.query={}),a&&(this.pathname=a),w[d]&&this.hostname&&!this.pathname&&(this.pathname="/"),this.pathname||this.search){var z=this.pathname||"",$=this.search||"";this.path=z+$}return this.href=this.format(),this},o.prototype.format=function(){var e=this.auth||"";e&&(e=encodeURIComponent(e),e=e.replace(/%3A/i,":"),e+="@");var t=this.protocol||"",n=this.pathname||"",o=this.hash||"",r=!1,s="";this.host?r=e+this.host:this.hostname&&(r=e+(-1===this.hostname.indexOf(":")?this.hostname:"["+this.hostname+"]"),this.port&&(r+=":"+this.port)),this.query&&l.isObject(this.query)&&Object.keys(this.query).length&&(s=C.stringify(this.query));var i=this.search||s&&"?"+s||"";return t&&":"!==t.substr(-1)&&(t+=":"),this.slashes||(!t||w[t])&&!1!==r?(r="//"+(r||""),n&&"/"!==n.charAt(0)&&(n="/"+n)):r||(r=""),o&&"#"!==o.charAt(0)&&(o="#"+o),i&&"?"!==i.charAt(0)&&(i="?"+i),n=n.replace(/[?#]/g,function(e){return encodeURIComponent(e)}),i=i.replace("#","%23"),t+r+n+i+o},o.prototype.resolve=function(e){return this.resolveObject(r(e,!1,!0)).format()},o.prototype.resolveObject=function(e){if(l.isString(e)){var t=new o;t.parse(e,!1,!0),e=t}for(var n=new o,r=Object.keys(this),s=0;s<r.length;s++){var i=r[s];n[i]=this[i]}if(n.hash=e.hash,""===e.href)return n.href=n.format(),n;if(e.slashes&&!e.protocol){for(var a=Object.keys(e),u=0;u<a.length;u++){var h=a[u];"protocol"!==h&&(n[h]=e[h])}return w[n.protocol]&&n.hostname&&!n.pathname&&(n.path=n.pathname="/"),n.href=n.format(),n}if(e.protocol&&e.protocol!==n.protocol){if(!w[e.protocol]){for(var c=Object.keys(e),f=0;f<c.length;f++){var p=c[f];n[p]=e[p]}return n.href=n.format(),n}if(n.protocol=e.protocol,e.host||O[e.protocol])n.pathname=e.pathname;else{for(var d=(e.pathname||"").split("/");d.length&&!(e.host=d.shift()););e.host||(e.host=""),e.hostname||(e.hostname=""),""!==d[0]&&d.unshift(""),d.length<2&&d.unshift(""),n.pathname=d.join("/")}if(n.search=e.search,n.query=e.query,n.host=e.host||"",n.auth=e.auth,n.hostname=e.hostname||e.host,n.port=e.port,n.pathname||n.search){var m=n.pathname||"",v=n.search||"";n.path=m+v}return n.slashes=n.slashes||e.slashes,n.href=n.format(),n}var y=n.pathname&&"/"===n.pathname.charAt(0),b=e.host||e.pathname&&"/"===e.pathname.charAt(0),g=b||y||n.host&&e.pathname,j=g,C=n.pathname&&n.pathname.split("/")||[],d=e.pathname&&e.pathname.split("/")||[],x=n.protocol&&!w[n.protocol];if(x&&(n.hostname="",n.port=null,n.host&&(""===C[0]?C[0]=n.host:C.unshift(n.host)),n.host="",e.protocol&&(e.hostname=null,e.port=null,e.host&&(""===d[0]?d[0]=e.host:d.unshift(e.host)),e.host=null),g=g&&(""===d[0]||""===C[0])),b)n.host=e.host||""===e.host?e.host:n.host,n.hostname=e.hostname||""===e.hostname?e.hostname:n.hostname,n.search=e.search,n.query=e.query,C=d;else if(d.length)C||(C=[]),C.pop(),C=C.concat(d),n.search=e.search,n.query=e.query;else if(!l.isNullOrUndefined(e.search)){if(x){n.hostname=n.host=C.shift();var _=!!(n.host&&n.host.indexOf("@")>0)&&n.host.split("@");_&&(n.auth=_.shift(),n.host=n.hostname=_.shift())}return n.search=e.search,n.query=e.query,l.isNull(n.pathname)&&l.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")),n.href=n.format(),n}if(!C.length)return n.pathname=null,n.search?n.path="/"+n.search:n.path=null,n.href=n.format(),n;for(var S=C.slice(-1)[0],T=(n.host||e.host||C.length>1)&&("."===S||".."===S)||""===S,P=0,k=C.length;k>=0;k--)S=C[k],"."===S?C.splice(k,1):".."===S?(C.splice(k,1),P++):P&&(C.splice(k,1),P--);if(!g&&!j)for(;P--;P)C.unshift("..");!g||""===C[0]||C[0]&&"/"===C[0].charAt(0)||C.unshift(""),T&&"/"!==C.join("/").substr(-1)&&C.push("");var R=""===C[0]||C[0]&&"/"===C[0].charAt(0);if(x){n.hostname=n.host=R?"":C.length?C.shift():"";var _=!!(n.host&&n.host.indexOf("@")>0)&&n.host.split("@");_&&(n.auth=_.shift(),n.host=n.hostname=_.shift())}return g=g||n.host&&C.length,g&&!R&&C.unshift(""),C.length?n.pathname=C.join("/"):(n.pathname=null,n.path=null),l.isNull(n.pathname)&&l.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")),n.auth=e.auth||n.auth,n.slashes=n.slashes||e.slashes,n.href=n.format(),n},o.prototype.parseHost=function(){var e=this.host,t=c.exec(e);t&&(t=t[0],":"!==t&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)}},"./node_modules/url/util.js":function(e,t,n){"use strict";e.exports={isString:function(e){return"string"==typeof e},isObject:function(e){return"object"==typeof e&&null!==e},isNull:function(e){return null===e},isNullOrUndefined:function(e){return null==e}}},"./node_modules/webpack/buildin/global.js":function(e,t){var n;n=function(){return this}();try{n=n||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(n=window)}e.exports=n},"./node_modules/webpack/buildin/module.js":function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},0:function(e,t){e.exports=Injector},1:function(e,t){e.exports=React},2:function(e,t){e.exports=FieldHolder},3:function(e,t){e.exports=IsomorphicFetch},4:function(e,t){e.exports=ReactDom},5:function(e,t){e.exports=ReactSelect}});
|
|
@ -1 +1 @@
|
|||
.ss-tag-field__multi-value{margin-top:3px}.ss-tag-field__control{border-color:#ced5e1;box-shadow:none}.ss-tag-field__control--is-focused{border-color:#29abe2;box-shadow:none}.ss-tag-field__option+.ss-tag-field__option{border-top:1px solid #dbe0e9}.ss-tag-field__option-button{border:1px solid #dbe0e9;border-radius:.23rem;background:#fff;width:auto !important;max-width:25%;margin:-4px -5px -4px 5px;padding:4px 5px 4px 4px;cursor:pointer}.ss-tag-field__option-button:hover{background:#ced5e1}.ss-tag-field__option-button .font-icon-right-open-big{margin:2px 0 0 -1px;width:24px}.ss-tag-field__option-count-icon{padding:0 .6154rem;line-height:.8}.ss-tag-field__option-context{color:#566b8d;font-size:.923rem}.ss-tag-field__option--is-focused{background-color:#f5f5f5}.ss-tag-field__option--is-selected{background:#0071c4;color:#fff}.ss-tag-field__option--is-selected .ss-tag-field__option-button{border-color:#29abe2;background:none;color:#fff}.ss-tag-field__option--is-selected .ss-tag-field__option-button:hover{background:rgba(0,0,0,.2)}.ss-tag-field__option-title--highlighted{font-weight:bold}.ss-tag-field__indicator{cursor:pointer}.ss-tag-field__clear-indicator:hover,.ss-tag-field__clear-indicator:focus{color:#d40404}.ss-tag-field__dropdown-indicator:hover,.ss-tag-field__dropdown-indicator:focus{color:#303b4d}.ss-tag-field__multi-value{color:#43536d;background-color:#fff;border:1px solid #66afe9;border-radius:.23rem}.ss-tag-field__multi-value__remove{font-size:1.231rem;padding:0 5px 2px;border-left:1px solid #66afe9;border-radius:0}.ss-tag-field__multi-value__remove:focus,.ss-tag-field__multi-value__remove:hover{background-color:rgba(0,113,230,.08);color:#0071e6}.ss-tag-field__menu{z-index:10}
|
||||
.ss-tag-field .Select--multi .Select-value{margin-top:3px}
|
|
@ -1,2 +1,2 @@
|
|||
import 'legacy/entwine/TagField';
|
||||
import 'boot';
|
||||
require('legacy/entwine/TagField');
|
||||
require('boot');
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import React, { Component } from 'react';
|
||||
import Select from 'react-select';
|
||||
import AsyncSelect from 'react-select/async';
|
||||
import AsyncCreatableSelect from 'react-select/async-creatable';
|
||||
import CreatableSelect from 'react-select/creatable';
|
||||
import EmotionCssCacheProvider from 'containers/EmotionCssCacheProvider/EmotionCssCacheProvider';
|
||||
import i18n from 'i18n';
|
||||
import fetch from 'isomorphic-fetch';
|
||||
import fieldHolder from 'components/FieldHolder/FieldHolder';
|
||||
import url from 'url';
|
||||
|
@ -15,7 +10,11 @@ class TagField extends Component {
|
|||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.selectComponentRef = React.createRef();
|
||||
this.selectComponentRef = null;
|
||||
|
||||
this.setSelectComponentRef = ref => {
|
||||
this.selectComponentRef = ref;
|
||||
};
|
||||
|
||||
this.state = {
|
||||
initalState: props.value ? props.value : [],
|
||||
|
@ -31,14 +30,13 @@ class TagField extends Component {
|
|||
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
this.handleOnBlur = this.handleOnBlur.bind(this);
|
||||
this.isValidNewOption = this.isValidNewOption.bind(this);
|
||||
this.getOptions = this.getOptions.bind(this);
|
||||
this.fetchOptions = debounce(this.fetchOptions, 500);
|
||||
}
|
||||
|
||||
componentDidUpdate(previousProps, previousState) {
|
||||
if (previousState.hasChanges !== this.state.hasChanges) {
|
||||
const element = this.selectComponentRef.current.inputRef;
|
||||
const element = this.selectComponentRef.select.wrapper;
|
||||
const event = new Event('change', { bubbles: true });
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
|
@ -55,12 +53,13 @@ class TagField extends Component {
|
|||
const { lazyLoad, options } = this.props;
|
||||
|
||||
if (!lazyLoad) {
|
||||
return Promise.resolve(options);
|
||||
return Promise.resolve({ options });
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
return Promise.resolve([]);
|
||||
return Promise.resolve({ options: [] });
|
||||
}
|
||||
|
||||
return this.fetchOptions(input);
|
||||
}
|
||||
|
||||
|
@ -122,87 +121,30 @@ class TagField extends Component {
|
|||
|
||||
return fetch(url.format(fetchURL), { credentials: 'same-origin' })
|
||||
.then((response) => response.json())
|
||||
.then((json) => json.items.map(
|
||||
(item) => ({
|
||||
.then((json) => ({
|
||||
options: json.items.map((item) => ({
|
||||
[labelKey]: item.Title,
|
||||
[valueKey]: item.Value,
|
||||
Selected: item.Selected,
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a new option can be created based on a given input
|
||||
* @param {string} inputValue
|
||||
* @param {array|object} value
|
||||
* @param {array} currentOptions
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isValidNewOption(inputValue, value, currentOptions) {
|
||||
const { valueKey } = this.props;
|
||||
|
||||
// Don't allow empty options
|
||||
if (!inputValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't repeat the currently selected option
|
||||
if (Array.isArray(value)) {
|
||||
if (this.valueInOptions(inputValue, value, valueKey)) {
|
||||
return false;
|
||||
}
|
||||
} else if (inputValue === value[valueKey]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't repeat any existing option
|
||||
return !this.valueInOptions(inputValue, currentOptions, valueKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is in an array of options already
|
||||
* @param {string} value
|
||||
* @param {array} options
|
||||
* @param {string} valueKey
|
||||
* @returns {boolean}
|
||||
*/
|
||||
valueInOptions(value, options, valueKey) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const item of options) {
|
||||
if (value === item[valueKey]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
lazyLoad,
|
||||
options,
|
||||
creatable,
|
||||
multi,
|
||||
disabled,
|
||||
labelKey,
|
||||
valueKey,
|
||||
SelectComponent,
|
||||
AsyncCreatableSelectComponent,
|
||||
AsyncSelectComponent,
|
||||
CreatableSelectComponent,
|
||||
...passThroughAttributes
|
||||
} = this.props;
|
||||
const { lazyLoad, options, creatable, ...passThroughAttributes } =
|
||||
this.props;
|
||||
|
||||
const optionAttributes = lazyLoad
|
||||
? { loadOptions: this.getOptions }
|
||||
: { options };
|
||||
|
||||
let DynamicSelect = SelectComponent;
|
||||
let SelectComponent = Select;
|
||||
if (lazyLoad && creatable) {
|
||||
DynamicSelect = AsyncCreatableSelectComponent;
|
||||
SelectComponent = Select.AsyncCreatable;
|
||||
} else if (lazyLoad) {
|
||||
DynamicSelect = AsyncSelectComponent;
|
||||
SelectComponent = Select.Async;
|
||||
} else if (creatable) {
|
||||
DynamicSelect = CreatableSelectComponent;
|
||||
SelectComponent = Select.Creatable;
|
||||
}
|
||||
|
||||
// Update the value to passthrough with the kept state provided this component is not
|
||||
|
@ -212,7 +154,7 @@ class TagField extends Component {
|
|||
}
|
||||
|
||||
// if this is a single select then we just need the first value
|
||||
if (!multi && passThroughAttributes.value) {
|
||||
if (!passThroughAttributes.multi && passThroughAttributes.value) {
|
||||
if (Object.keys(passThroughAttributes.value).length > 0) {
|
||||
const value =
|
||||
passThroughAttributes.value[
|
||||
|
@ -228,25 +170,14 @@ class TagField extends Component {
|
|||
const changedClassName = this.state.hasChanges ? '' : 'no-change-track';
|
||||
|
||||
return (
|
||||
<EmotionCssCacheProvider>
|
||||
<DynamicSelect
|
||||
{...passThroughAttributes}
|
||||
isMulti={multi}
|
||||
isDisabled={disabled}
|
||||
cacheOptions
|
||||
onChange={this.handleChange}
|
||||
onBlur={this.handleOnBlur}
|
||||
{...optionAttributes}
|
||||
getOptionLabel={(option) => option[labelKey]}
|
||||
getOptionValue={(option) => option[valueKey]}
|
||||
noOptionsMessage={({ inputValue }) => (inputValue ? i18n._t('TagField.NO_OPTIONS', 'No options') : i18n._t('TagField.TYPE_TO_SEARCH', 'Type to search'))}
|
||||
isValidNewOption={this.isValidNewOption}
|
||||
getNewOptionData={(inputValue, label) => ({ [labelKey]: label, [valueKey]: inputValue })}
|
||||
classNamePrefix="ss-tag-field"
|
||||
className={changedClassName}
|
||||
ref={this.selectComponentRef}
|
||||
/>
|
||||
</EmotionCssCacheProvider>
|
||||
<SelectComponent
|
||||
{...passThroughAttributes}
|
||||
onChange={this.handleChange}
|
||||
onBlur={this.handleOnBlur}
|
||||
{...optionAttributes}
|
||||
className={changedClassName}
|
||||
ref={this.setSelectComponentRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -264,10 +195,6 @@ TagField.propTypes = {
|
|||
value: PropTypes.any,
|
||||
onChange: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
SelectComponent: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
|
||||
AsyncCreatableSelectComponent: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
|
||||
AsyncSelectComponent: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
|
||||
CreatableSelectComponent: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
|
||||
};
|
||||
|
||||
TagField.defaultProps = {
|
||||
|
@ -277,10 +204,6 @@ TagField.defaultProps = {
|
|||
lazyLoad: false,
|
||||
creatable: false,
|
||||
multi: false,
|
||||
SelectComponent: Select,
|
||||
AsyncCreatableSelectComponent: AsyncCreatableSelect,
|
||||
AsyncSelectComponent: AsyncSelect,
|
||||
CreatableSelectComponent: CreatableSelect,
|
||||
};
|
||||
|
||||
export { TagField as Component };
|
||||
|
|
|
@ -1,114 +1,3 @@
|
|||
.ss-tag-field__multi-value {
|
||||
.ss-tag-field .Select--multi .Select-value {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
/* Styles below here are duplicates of the treedropdownfield styles in silverstripe/admin, but with the appropriate classnames for tagfield. */
|
||||
.ss-tag-field__control {
|
||||
border-color: $gray-200;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ss-tag-field__control--is-focused {
|
||||
border-color: $brand-primary;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ss-tag-field__option+.ss-tag-field__option {
|
||||
border-top: 1px solid $border-color-light;
|
||||
}
|
||||
|
||||
.ss-tag-field__option-button {
|
||||
border: 1px solid $border-color-light;
|
||||
border-radius: $border-radius;
|
||||
background: $white;
|
||||
// needed to override the width rule in .fill-width
|
||||
width: auto !important; // sass-lint:disable-line no-important
|
||||
max-width: 25%;
|
||||
margin: -4px -5px -4px 5px;
|
||||
padding: 4px 5px 4px 4px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: $gray-200;
|
||||
}
|
||||
|
||||
.font-icon-right-open-big {
|
||||
margin: 2px 0 0 -1px;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.ss-tag-field__option-count-icon {
|
||||
padding: 0 calc($spacer / 2);
|
||||
line-height: 0.8;
|
||||
}
|
||||
|
||||
.ss-tag-field__option-context {
|
||||
color: $gray-600;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
.ss-tag-field__option--is-focused {
|
||||
background-color: $list-group-hover-bg;
|
||||
}
|
||||
|
||||
.ss-tag-field__option--is-selected {
|
||||
background: $link-color;
|
||||
color: $white;
|
||||
|
||||
.ss-tag-field__option-button {
|
||||
border-color: $brand-primary;
|
||||
background: none;
|
||||
color: $white;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ss-tag-field__option-title--highlighted {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ss-tag-field__indicator {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ss-tag-field__clear-indicator {
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $brand-danger;
|
||||
}
|
||||
}
|
||||
|
||||
.ss-tag-field__dropdown-indicator {
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $body-color-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.ss-tag-field__multi-value {
|
||||
color: $body-color;
|
||||
background-color: $white;
|
||||
border: 1px solid $input-focus-border-color;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.ss-tag-field__multi-value__remove {
|
||||
font-size: $font-size-lg;
|
||||
padding: 0 5px 2px;
|
||||
border-left: 1px solid $input-focus-border-color;
|
||||
border-radius: 0;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
background-color: rgba(0, 113, 230, .08);
|
||||
color: #0071e6;
|
||||
}
|
||||
}
|
||||
|
||||
.ss-tag-field__menu {
|
||||
z-index: 10;
|
||||
}
|
||||
|
|
|
@ -1,109 +1,36 @@
|
|||
import React from 'react';
|
||||
import TagField from 'components/TagField';
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { Component as TagField } from '../TagField';
|
||||
|
||||
const options = [
|
||||
{ Title: 'One', Value: 1 },
|
||||
{ Title: 'Two', Value: 2 },
|
||||
{ Title: 'Three', Value: 3 },
|
||||
{ Title: 'Four', Value: 4 },
|
||||
{ Title: 'Five', Value: 5 },
|
||||
];
|
||||
|
||||
export default {
|
||||
title: 'TagField/TagField',
|
||||
component: TagField,
|
||||
decorators: [],
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: 'The Tag Field component.'
|
||||
},
|
||||
canvas: {
|
||||
sourceState: 'shown',
|
||||
},
|
||||
}
|
||||
},
|
||||
argTypes: {
|
||||
name: {
|
||||
control: 'text',
|
||||
table: {
|
||||
type: { summary: 'string' },
|
||||
defaultValue: { summary: null },
|
||||
},
|
||||
},
|
||||
options: {
|
||||
description: 'List of tags options',
|
||||
control: 'select',
|
||||
table: {
|
||||
type: { summary: 'string' },
|
||||
},
|
||||
},
|
||||
labelKey: {
|
||||
control: 'text',
|
||||
table: {
|
||||
type: { summary: 'string' },
|
||||
defaultValue: { summary: 'Title' },
|
||||
},
|
||||
},
|
||||
valueKey: {
|
||||
control: 'text',
|
||||
table: {
|
||||
type: { summary: 'string' },
|
||||
defaultValue: { summary: 'Value' },
|
||||
},
|
||||
},
|
||||
lazyLoad: {
|
||||
control: 'boolean',
|
||||
table: {
|
||||
type: { summary: 'boolean' },
|
||||
defaultValue: { summary: false },
|
||||
},
|
||||
},
|
||||
creatable: {
|
||||
control: 'boolean',
|
||||
table: {
|
||||
type: { summary: 'boolean' },
|
||||
defaultValue: { summary: false },
|
||||
},
|
||||
},
|
||||
multi: {
|
||||
control: 'boolean',
|
||||
table: {
|
||||
type: { summary: 'boolean' },
|
||||
defaultValue: { summary: false },
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
control: 'boolean',
|
||||
table: {
|
||||
type: { summary: 'boolean' },
|
||||
defaultValue: { summary: false },
|
||||
},
|
||||
},
|
||||
optionUrl: {
|
||||
control: 'text',
|
||||
table: {
|
||||
type: { summary: 'string' },
|
||||
defaultValue: { summary: null },
|
||||
},
|
||||
},
|
||||
},
|
||||
args: {
|
||||
name: 'Test',
|
||||
options
|
||||
}
|
||||
};
|
||||
|
||||
export const SimpleExample = (args) => (
|
||||
<TagField
|
||||
{...args}
|
||||
/>
|
||||
);
|
||||
|
||||
export const MultipleSelection = (args) => (
|
||||
<TagField
|
||||
{...args}
|
||||
multi
|
||||
/>
|
||||
);
|
||||
storiesOf('TagField/TagField', module)
|
||||
.addDecorator(storyFn => (
|
||||
<div style={{ width: '250px' }} className="ss-tag-field">
|
||||
{storyFn()}
|
||||
</div>
|
||||
))
|
||||
.add('Simple Example', () => (
|
||||
<TagField
|
||||
name="test"
|
||||
options={[
|
||||
{ Title: 'One', Value: 1 },
|
||||
{ Title: 'Two', Value: 2 },
|
||||
{ Title: 'Three', Value: 3 },
|
||||
{ Title: 'Four', Value: 4 },
|
||||
{ Title: 'Five', Value: 5 },
|
||||
]}
|
||||
/>
|
||||
))
|
||||
.add('Multiple Selection', () => (
|
||||
<TagField
|
||||
name="test"
|
||||
multi
|
||||
options={[
|
||||
{ Title: 'One', Value: 1 },
|
||||
{ Title: 'Two', Value: 2 },
|
||||
{ Title: 'Three', Value: 3 },
|
||||
{ Title: 'Four', Value: 4 },
|
||||
{ Title: 'Five', Value: 5 },
|
||||
]}
|
||||
/>
|
||||
))
|
||||
;
|
||||
|
|
|
@ -1,64 +1,85 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
/* global jest, test, describe, beforeEach, it, expect, setTimeout, document */
|
||||
/* global jest, describe, beforeEach, it, expect, setTimeout, document */
|
||||
|
||||
jest.mock('isomorphic-fetch');
|
||||
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import Enzyme, { shallow } from 'enzyme';
|
||||
import Adapter from 'enzyme-adapter-react-15.4';
|
||||
import { Component as TagField } from '../TagField';
|
||||
import Select from 'react-select';
|
||||
import fetch from 'isomorphic-fetch';
|
||||
|
||||
function makeProps(obj = {}) {
|
||||
return {
|
||||
name: 'Test',
|
||||
labelKey: 'label',
|
||||
valueKey: 'value',
|
||||
lazyLoad: false,
|
||||
creatable: false,
|
||||
multi: true,
|
||||
SelectComponent: () => <div className="test-dynamic test-select" />,
|
||||
CreatableSelectComponent: () => <div className="test-dynamic test-creatable-select" />,
|
||||
AsyncSelectComponent: () => <div className="test-dynamic test-async-select" />,
|
||||
AsyncCreatableSelectComponent: () => <div className="test-dynamic test-async-creatable-select" />,
|
||||
...obj,
|
||||
};
|
||||
}
|
||||
Enzyme.configure({ adapter: new Adapter() });
|
||||
|
||||
test('TagField should render a Select component by default', () => {
|
||||
const { container } = render(
|
||||
<TagField {...makeProps()}/>
|
||||
);
|
||||
expect(container.querySelectorAll('.test-dynamic')).toHaveLength(1);
|
||||
expect(container.querySelector('.test-select')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('TagField should render a CreatableSelect with creatable option', () => {
|
||||
const { container } = render(
|
||||
<TagField {...makeProps({
|
||||
creatable: true
|
||||
})}
|
||||
/>
|
||||
);
|
||||
expect(container.querySelectorAll('.test-dynamic')).toHaveLength(1);
|
||||
expect(container.querySelector('.test-creatable-select')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('Tagfiled should render an AsyncSelect with lazy load option', () => {
|
||||
const { container } = render(
|
||||
<TagField {...makeProps({
|
||||
lazyLoad: true
|
||||
})}
|
||||
/>
|
||||
);
|
||||
expect(container.querySelectorAll('.test-dynamic')).toHaveLength(1);
|
||||
expect(container.querySelector('.test-async-select')).not.toBeNull();
|
||||
});
|
||||
|
||||
test('Tagfiled should render a AsyncCreatableSelect with lazy load and creatable options', () => {
|
||||
const { container } = render(
|
||||
<TagField {...makeProps({
|
||||
lazyLoad: true,
|
||||
creatable: true
|
||||
})}
|
||||
/>
|
||||
);
|
||||
expect(container.querySelectorAll('.test-dynamic')).toHaveLength(1);
|
||||
expect(container.querySelector('.test-async-creatable-select')).not.toBeNull();
|
||||
describe('TagField', () => {
|
||||
let props;
|
||||
|
||||
beforeEach(() => {
|
||||
props = {
|
||||
name: 'Test',
|
||||
labelKey: 'label',
|
||||
valueKey: 'value',
|
||||
lazyLoad: false,
|
||||
creatable: false,
|
||||
multi: true,
|
||||
};
|
||||
});
|
||||
|
||||
describe('should render a Select component with type', () => {
|
||||
it('Select', () => {
|
||||
const wrapper = shallow(
|
||||
<TagField {...props} />
|
||||
);
|
||||
expect(wrapper.find(Select).length).toBe(1);
|
||||
});
|
||||
it('Select.Creatable with creatable option', () => {
|
||||
props.creatable = true;
|
||||
const wrapper = shallow(
|
||||
<TagField {...props} />
|
||||
);
|
||||
expect(wrapper.find(Select.Creatable).length).toBe(1);
|
||||
});
|
||||
it('Select.Async with lazyLoad option', () => {
|
||||
props.lazyLoad = true;
|
||||
const wrapper = shallow(
|
||||
<TagField {...props} />
|
||||
);
|
||||
expect(wrapper.find(Select.Async).length).toBe(1);
|
||||
});
|
||||
it('Select.AsyncCreatable with both creatable and lazyLoad options', () => {
|
||||
props.creatable = true;
|
||||
props.lazyLoad = true;
|
||||
const wrapper = shallow(
|
||||
<TagField {...props} />
|
||||
);
|
||||
expect(wrapper.find(Select.AsyncCreatable).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with lazyLoad on and given a URL', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
props.lazyLoad = true;
|
||||
props.optionUrl = 'localhost/some-fetch-url';
|
||||
|
||||
wrapper = shallow(
|
||||
<TagField {...props} />
|
||||
);
|
||||
|
||||
fetch.mockImplementation(() => Promise.resolve({
|
||||
json: () => ({}),
|
||||
}));
|
||||
});
|
||||
|
||||
it('should fetch the URL for results', done => {
|
||||
wrapper.instance().getOptions('a');
|
||||
|
||||
setTimeout(() => {
|
||||
expect(fetch).toBeCalledWith('localhost/some-fetch-url?term=a', expect.anything());
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
/* global window */
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { loadComponent } from 'lib/Injector';
|
||||
|
||||
window.jQuery.entwine('ss', ($) => {
|
||||
$('.js-injector-boot .ss-tag-field.entwine').entwine({
|
||||
ReactRoot: null,
|
||||
|
||||
onmatch() {
|
||||
const cmsContent = this.closest('.cms-content').attr('id');
|
||||
const context = (cmsContent)
|
||||
|
@ -20,25 +18,17 @@ window.jQuery.entwine('ss', ($) => {
|
|||
}
|
||||
};
|
||||
|
||||
let root = this.getReactRoot();
|
||||
if (!root) {
|
||||
root = createRoot(this[0]);
|
||||
this.setReactRoot(root);
|
||||
}
|
||||
root.render(
|
||||
ReactDOM.render(
|
||||
<TagField
|
||||
noHolder
|
||||
{...dataSchema}
|
||||
/>
|
||||
/>,
|
||||
this[0]
|
||||
);
|
||||
},
|
||||
|
||||
onunmatch() {
|
||||
const root = this.getReactRoot();
|
||||
if (root) {
|
||||
root.unmount();
|
||||
this.setReactRoot(null);
|
||||
}
|
||||
ReactDOM.unmountComponentAtNode(this[0]);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -19,15 +19,13 @@
|
|||
"issues": "http://github.com/silverstripe/silverstripe-tagfield/issues"
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"silverstripe/framework": "^5",
|
||||
"silverstripe/versioned": "^2"
|
||||
"php": "^7.4 || ^8.0",
|
||||
"silverstripe/framework": "^4.10",
|
||||
"silverstripe/versioned": "^1.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"squizlabs/php_codesniffer": "^3",
|
||||
"silverstripe/standards": "^1",
|
||||
"phpstan/extension-installer": "^1.3"
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"squizlabs/php_codesniffer": "^3.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@ -42,4 +40,4 @@
|
|||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2016, Silverstripe Limited
|
||||
Copyright (c) 2016, SilverStripe Limited
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
69
package.json
69
package.json
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"name": "silverstripe-tagfield",
|
||||
"version": "2.2.0",
|
||||
"description": "Tag field for SilverStripe",
|
||||
"main": "./client/src/boot/index.js",
|
||||
"license": "BSD-3-Clause",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -12,10 +14,10 @@
|
|||
},
|
||||
"author": "SilverStripe Ltd.",
|
||||
"engines": {
|
||||
"node": "^18.x"
|
||||
"node": ">=6.x"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "yarn && yarn lint && yarn test && rm -rf client/dist/* && NODE_ENV=production webpack --mode production --bail --progress",
|
||||
"build": "yarn && yarn lint && yarn test && NODE_ENV=production webpack -p --bail --progress",
|
||||
"dev": "NODE_ENV=development webpack --progress",
|
||||
"watch": "NODE_ENV=development webpack --watch --progress",
|
||||
"css": "WEBPACK_CHILD=css npm run build",
|
||||
|
@ -27,36 +29,7 @@
|
|||
"lint-js-fix": "eslint client/src --fix",
|
||||
"lint-sass": "sass-lint client/src"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.3.2",
|
||||
"debounce-promise": "^3.1.2",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-select": "^5.5.8",
|
||||
"url": "^0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@silverstripe/eslint-config": "^1.1.0",
|
||||
"@silverstripe/webpack-config": "^2.0.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"babel-jest": "^29.2.2",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"html-loader": "^4.2.0",
|
||||
"jest-cli": "^29.2.2",
|
||||
"jest-environment-jsdom": "^29.3.1",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^5.0.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"colors": "1.4.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"jest": {
|
||||
"testEnvironment": "jsdom",
|
||||
"roots": [
|
||||
"client/src"
|
||||
],
|
||||
|
@ -73,5 +46,39 @@
|
|||
"transform": {
|
||||
".*": "babel-jest"
|
||||
}
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"env",
|
||||
"react"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-object-rest-spread"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@silverstripe/eslint-config": "^0.0.6",
|
||||
"@silverstripe/webpack-config": "^0.10.0",
|
||||
"@storybook/react": "^3.2.19",
|
||||
"babel-jest": "^23.6.0",
|
||||
"copy-webpack-plugin": "^4.2.0",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-15.4": "^1.0.5",
|
||||
"html-loader": "^0.5.1",
|
||||
"jest-cli": "^23.6.0",
|
||||
"react-addons-test-utils": "15.3.1",
|
||||
"webpack": "^2"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "6.7.4",
|
||||
"classnames": "^2.2.5",
|
||||
"debounce-promise": "^3.1.0",
|
||||
"isomorphic-fetch": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
|
||||
"jquery": "^3.5.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "15.3.1",
|
||||
"react-dom": "15.3.1",
|
||||
"react-select": "^1.2.1",
|
||||
"url": "^0.11.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
parameters:
|
||||
paths:
|
||||
- src
|
|
@ -6,6 +6,7 @@ use Iterator;
|
|||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Control\HTTPRequest;
|
||||
use SilverStripe\Control\HTTPResponse;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Forms\DropdownField;
|
||||
use SilverStripe\Forms\Validator;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
|
@ -33,6 +34,13 @@ class StringTagField extends DropdownField
|
|||
'suggest',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var bool Triggers a write call within the saveInto function if enabled
|
||||
*
|
||||
* @deprecated 3.0.0 Will be removed without equivalent functionality to replace it
|
||||
*/
|
||||
private static $immediate_write_enabled = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
|
@ -58,6 +66,7 @@ class StringTagField extends DropdownField
|
|||
*/
|
||||
protected $isMultiple = true;
|
||||
|
||||
/** @skipUpgrade */
|
||||
protected $schemaComponent = 'TagField';
|
||||
|
||||
/**
|
||||
|
@ -219,7 +228,7 @@ class StringTagField extends DropdownField
|
|||
}
|
||||
|
||||
/**
|
||||
* @return ArrayList<ArrayData>
|
||||
* @return ArrayList
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
|
@ -272,6 +281,13 @@ class StringTagField extends DropdownField
|
|||
$name = $this->getName();
|
||||
|
||||
$record->$name = $this->dataValue();
|
||||
|
||||
$immediate_write_enabled = Deprecation::withNoReplacement(function () {
|
||||
return self::config()->get('immediate_write_enabled');
|
||||
});
|
||||
if ($immediate_write_enabled) {
|
||||
$record->write();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -356,6 +372,7 @@ class StringTagField extends DropdownField
|
|||
{
|
||||
$items = [];
|
||||
foreach ($this->getOptions() as $i => $tag) {
|
||||
/** @var ArrayData $tag */
|
||||
$tagValue = $tag->Value;
|
||||
// Map into a distinct list (prevent duplicates)
|
||||
if (stripos($tagValue ?? '', $term ?? '') !== false && !array_key_exists($tagValue, $items ?? [])) {
|
||||
|
@ -365,7 +382,7 @@ class StringTagField extends DropdownField
|
|||
];
|
||||
}
|
||||
}
|
||||
|
||||
// @todo do we actually need lazy loading limits for StringTagField?
|
||||
return array_slice(array_values($items ?? []), 0, $this->getLazyLoadItemLimit());
|
||||
}
|
||||
|
||||
|
@ -378,7 +395,7 @@ class StringTagField extends DropdownField
|
|||
*/
|
||||
public function validate($validator)
|
||||
{
|
||||
return $this->extendValidationResult(true, $validator);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -63,6 +63,7 @@ class TagField extends MultiSelectField
|
|||
*/
|
||||
protected $isMultiple = true;
|
||||
|
||||
/** @skipUpgrade */
|
||||
protected $schemaComponent = 'TagField';
|
||||
|
||||
/**
|
||||
|
@ -257,7 +258,7 @@ class TagField extends MultiSelectField
|
|||
}
|
||||
|
||||
/**
|
||||
* @return ArrayList<ArrayData>
|
||||
* @return ArrayList
|
||||
*/
|
||||
protected function getOptions($onlySelected = false)
|
||||
{
|
||||
|
@ -496,7 +497,7 @@ class TagField extends MultiSelectField
|
|||
}
|
||||
|
||||
// Create new instance if not yet saved
|
||||
if ($this->getCanCreate() && $value) {
|
||||
if ($this->getCanCreate()) {
|
||||
$dataClass = $source->dataClass();
|
||||
$record = Injector::inst()->create($dataClass);
|
||||
$record->{$titleField} = $value;
|
||||
|
@ -572,7 +573,7 @@ class TagField extends MultiSelectField
|
|||
*/
|
||||
public function validate($validator)
|
||||
{
|
||||
return $this->extendValidationResult(true, $validator);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -582,6 +583,7 @@ class TagField extends MultiSelectField
|
|||
*/
|
||||
public function performReadonlyTransformation()
|
||||
{
|
||||
/** @var ReadonlyTagField $copy */
|
||||
$copy = $this->castedCopy(ReadonlyTagField::class);
|
||||
$copy->setSourceList($this->getSourceList());
|
||||
$copy->setTitleField($this->getTitleField());
|
||||
|
|
|
@ -8,6 +8,7 @@ use SilverStripe\Forms\FieldList;
|
|||
use SilverStripe\Forms\Form;
|
||||
use SilverStripe\TagField\StringTagField;
|
||||
use SilverStripe\TagField\Tests\Stub\StringTagFieldTestBlogPost;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
|
||||
class StringTagFieldTest extends SapphireTest
|
||||
{
|
||||
|
@ -61,6 +62,44 @@ class StringTagFieldTest extends SapphireTest
|
|||
$this->assertEquals('Tag1,Tag2', $record->Tags);
|
||||
}
|
||||
|
||||
public function testImmediateWriteEnabled()
|
||||
{
|
||||
if (Deprecation::isEnabled()) {
|
||||
$this->markTestSkipped('Test calls deprecated code');
|
||||
}
|
||||
$record = $this->getNewStringTagFieldTestBlogPost('BlogPost1');
|
||||
$record->write();
|
||||
|
||||
StringTagField::config()->set('immediate_write_enabled', true);
|
||||
|
||||
$field = new StringTagField('Tags');
|
||||
$field->setValue(['Tag1', 'Tag2']);
|
||||
$field->saveInto($record);
|
||||
|
||||
$this->assertEquals('Tag1,Tag2', StringTagFieldTestBlogPost::get()->byID($record->ID)->Tags);
|
||||
}
|
||||
|
||||
public function testImmediateWriteDisabled()
|
||||
{
|
||||
if (Deprecation::isEnabled()) {
|
||||
$this->markTestSkipped('Test calls deprecated code');
|
||||
}
|
||||
$record = $this->getNewStringTagFieldTestBlogPost('BlogPost1');
|
||||
$record->write();
|
||||
|
||||
StringTagField::config()->set('immediate_write_enabled', false);
|
||||
|
||||
$field = new StringTagField('Tags');
|
||||
$field->setValue(['Tag1', 'Tag2']);
|
||||
$field->saveInto($record);
|
||||
|
||||
$this->assertNull(StringTagFieldTestBlogPost::get()->byID($record->ID)->Tags);
|
||||
|
||||
$record->write();
|
||||
|
||||
$this->assertEquals('Tag1,Tag2', StringTagFieldTestBlogPost::get()->byID($record->ID)->Tags);
|
||||
}
|
||||
|
||||
public function testItSuggestsTags()
|
||||
{
|
||||
$field = new StringTagField('SomeField', 'Some field', ['Tag1', 'Tag2'], []);
|
||||
|
|
|
@ -1,31 +1,60 @@
|
|||
const Path = require('path');
|
||||
const { JavascriptWebpackConfig, CssWebpackConfig } = require('@silverstripe/webpack-config');
|
||||
const webpackConfig = require('@silverstripe/webpack-config');
|
||||
const {
|
||||
resolveJS,
|
||||
externalJS,
|
||||
moduleJS,
|
||||
pluginJS,
|
||||
moduleCSS,
|
||||
pluginCSS,
|
||||
} = webpackConfig;
|
||||
|
||||
const ENV = process.env.NODE_ENV;
|
||||
const PATHS = {
|
||||
MODULES: 'node_modules',
|
||||
FILES_PATH: '../',
|
||||
ROOT: Path.resolve(),
|
||||
SRC: Path.resolve('client/src'),
|
||||
DIST: Path.resolve('client/dist'),
|
||||
};
|
||||
|
||||
const externals = externalJS(ENV, PATHS);
|
||||
delete externals.reactstrap;
|
||||
|
||||
const config = [
|
||||
// Main JS bundle
|
||||
new JavascriptWebpackConfig('js', PATHS, 'silverstripe/tagfield')
|
||||
.setEntry({
|
||||
{
|
||||
name: 'js',
|
||||
entry: {
|
||||
bundle: `${PATHS.SRC}/bundles/bundle.js`,
|
||||
})
|
||||
.getConfig(),
|
||||
// sass to css
|
||||
new CssWebpackConfig('css', PATHS)
|
||||
.setEntry({
|
||||
},
|
||||
output: {
|
||||
path: PATHS.DIST,
|
||||
filename: 'js/[name].js',
|
||||
},
|
||||
devtool: (ENV !== 'production') ? 'source-map' : '',
|
||||
resolve: resolveJS(ENV, PATHS),
|
||||
externals,
|
||||
module: moduleJS(ENV, PATHS),
|
||||
plugins: pluginJS(ENV, PATHS),
|
||||
},
|
||||
{
|
||||
name: 'css',
|
||||
entry: {
|
||||
bundle: `${PATHS.SRC}/styles/bundle.scss`,
|
||||
})
|
||||
.getConfig(),
|
||||
},
|
||||
output: {
|
||||
path: PATHS.DIST,
|
||||
filename: 'styles/[name].css',
|
||||
},
|
||||
devtool: (ENV !== 'production') ? 'source-map' : '',
|
||||
module: moduleCSS(ENV, PATHS),
|
||||
plugins: pluginCSS(ENV, PATHS),
|
||||
},
|
||||
];
|
||||
|
||||
// Use WEBPACK_CHILD=js or WEBPACK_CHILD=css env var to run a single config
|
||||
module.exports = (process.env.WEBPACK_CHILD)
|
||||
? config.find((entry) => entry.name === process.env.WEBPACK_CHILD)
|
||||
: config;
|
||||
: module.exports = config;
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue