diff --git a/_config.php b/_config.php index 9e519d4a..71c7914c 100644 --- a/_config.php +++ b/_config.php @@ -5,5 +5,4 @@ // Avoid creating global variables call_user_func(function () { - }); diff --git a/_config/config.yml b/_config/config.yml index 553ede50..f1d647c3 100644 --- a/_config/config.yml +++ b/_config/config.yml @@ -4,20 +4,12 @@ Name: linkfield SilverStripe\Admin\LeftAndMain: extensions: - - SilverStripe\LinkField\Extensions\LeftAndMain + - SilverStripe\LinkField\Extensions\LeftAndMainExtension SilverStripe\Admin\ModalController: extensions: - SilverStripe\LinkField\Extensions\ModalController -SilverStripe\Forms\TreeDropdownField: - extensions: - - SilverStripe\LinkField\Extensions\AjaxField - -SilverStripe\CMS\Forms\AnchorSelectorField: - extensions: - - SilverStripe\LinkField\Extensions\AjaxField - SilverStripe\LinkField\Form\FormFactory: extensions: - SilverStripe\LinkField\Extensions\FormFactoryExtension diff --git a/client/dist/js/bundle.js b/client/dist/js/bundle.js index 8726ffc2..26bd91ba 100644 --- a/client/dist/js/bundle.js +++ b/client/dist/js/bundle.js @@ -1 +1,996 @@ -!function(){"use strict";var e={274:function(e,t,n){i(n(510));var r=i(n(180)),a=i(n(521)),o=i(n(154));function i(e){return e&&e.__esModule?e:{default:e}}document.addEventListener("DOMContentLoaded",(()=>{(0,a.default)(),(0,o.default)(),(0,r.default)()}))},521:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=u(n(648)),a=u(n(809)),o=u(n(852)),i=u(n(117)),l=u(n(606));function u(e){return e&&e.__esModule?e:{default:e}}var d=()=>{r.default.component.registerMany({LinkPicker:a.default,LinkField:o.default,"LinkModal.FormBuilderModal":i.default,"LinkModal.InsertMediaModal":l.default})};t.default=d},154:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=i(n(648)),a=i(n(689)),o=i(n(287));function i(e){return e&&e.__esModule?e:{default:e}}var l=()=>{r.default.query.register("readLinkTypes",a.default),r.default.query.register("readLinkDescription",o.default)};t.default=l},180:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r;(r=n(648))&&r.__esModule,n(827);var a=()=>{};t.default=a},852:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,a=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=u(t);if(n&&n.has(e))return n.get(e);var r={},a=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if("default"!==o&&Object.prototype.hasOwnProperty.call(e,o)){var i=a?Object.getOwnPropertyDescriptor(e,o):null;i&&(i.get||i.set)?Object.defineProperty(r,o,i):r[o]=e[o]}r.default=e,n&&n.set(e,r);return r}(n(363)),o=n(827),i=n(648),l=(r=n(42))&&r.__esModule?r:{default:r};function u(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(u=function(e){return e?n:t})(e)}function d(){return d=Object.assign?Object.assign.bind():function(e){for(var t=1;tt=>{let{data:n,value:r,...o}=t,i=r||n;return"string"==typeof i&&(i=JSON.parse(i)),a.default.createElement(e,d({dataStr:JSON.stringify(i)},o,{data:i}))}),(0,i.injectGraphql)("readLinkDescription"),l.default)((e=>{let{id:t,loading:n,Loading:r,data:o,LinkPicker:l,onChange:u,types:d,linkDescription:s,...f}=e;if(n)return a.default.createElement(r,null);const[c,p]=(0,a.useState)(!1),[y,v]=(0,a.useState)(""),{typeKey:g}=o,m=d[g],k=y?d[y]:m;let _=o?o.Title:"";_||(_=o?o.TitleRelField:"");const O={title:_,link:m?{type:m,title:_,description:s}:void 0,onEdit:()=>{p(!0)},onClear:e=>{"function"==typeof u&&u(e,{id:t,value:{}})},onSelect:e=>{v(e),p(!0)},types:Object.values(d)},h={type:k,editing:c,onSubmit:(e,n,r)=>{const{SecurityID:a,action_insert:o,...i}=e;return"function"==typeof u&&u(event,{id:t,value:i}),p(!1),v(""),Promise.resolve()},onClosed:()=>{p(!1)},data:o},b=k?k.handlerName:"FormBuilderModal",j=(0,i.loadComponent)(`LinkModal.${b}`);return a.default.createElement(a.Fragment,null,a.default.createElement(l,O),a.default.createElement(j,h))}));t.default=s},606:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;i(n(754));var r=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=o(t);if(n&&n.has(e))return n.get(e);var r={},a=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var i in e)if("default"!==i&&Object.prototype.hasOwnProperty.call(e,i)){var l=a?Object.getOwnPropertyDescriptor(e,i):null;l&&(l.get||l.set)?Object.defineProperty(r,i,l):r[i]=e[i]}r.default=e,n&&n.set(e,r);return r}(n(363)),a=i(n(475));function o(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(o=function(e){return e?n:t})(e)}function i(e){return e&&e.__esModule?e:{default:e}}function l(){return l=Object.assign?Object.assign.bind():function(e){for(var t=1;te({type:"INIT_FORM_SCHEMA_STACK",payload:{formSchema:{type:"insert-link",nextType:"admin"}}}),reset:()=>e({type:"RESET"})}}}))((e=>{let{type:t,editing:n,data:o,actions:i,onSubmit:u,...d}=e;if(!t)return!1;(0,r.useEffect)((()=>{n?i.initModal():i.reset()}),[n]);const s=o?{ID:o.FileID,Description:o.Title,TargetBlank:!!o.OpenInNew}:{};return r.default.createElement(a.default,l({isOpen:n,type:"insert-link",title:!1,bodyClassName:"modal__dialog",className:"insert-link__dialog-wrapper--internal",fileAttributes:s,onInsert:e=>{let{ID:n,Description:r,TargetBlank:a}=e;return u({FileID:n,Title:r,OpenInNew:a,typeKey:t.key},"",(()=>{}))}},d))}));t.default=u},117:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;u(n(754));var r=u(n(363)),a=(u(n(86)),u(n(912))),o=u(n(872)),i=u(n(902)),l=u(n(510));function u(e){return e&&e.__esModule?e:{default:e}}function d(){return d=Object.assign?Object.assign.bind():function(e){for(var t=1;t{const{schemaUrl:n}=l.default.getSection("SilverStripe\\Admin\\LeftAndMain").form.DynamicLink,r=o.default.parse(n),a=i.default.parse(r.query);return a.key=e,t&&(a.data=JSON.stringify(t)),o.default.format({...r,search:i.default.stringify(a)})};var f=e=>{let{type:t,editing:n,data:o,...i}=e;return!!t&&r.default.createElement(a.default,d({title:t.title,isOpen:n,schemaUrl:s(t.key,o),identifier:"Link.EditingLinkInfo"},i))};t.default=f},809:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=t.Component=void 0;u(n(754));var r=u(n(363)),a=(n(648),u(n(86))),o=(n(127),u(n(820))),i=u(n(97)),l=u(n(734));u(n(686));function u(e){return e&&e.__esModule?e:{default:e}}function d(){return d=Object.assign?Object.assign.bind():function(e){for(var t=1;t{let{types:t,onSelect:n,link:a,onEdit:u,onClear:s}=e;return r.default.createElement("div",{className:(0,o.default)("link-picker","form-control",{"link-picker--selected":a})},void 0===a&&r.default.createElement(i.default,{types:t,onSelect:n}),a&&r.default.createElement(l.default,d({},a,{onClear:s,onClick:()=>a&&u&&u(a)})))};t.Component=s,s.propTypes={...i.default.propTypes,link:a.default.shape(l.default.propTypes),onEdit:a.default.func,onClear:a.default.func};var f=s;t.default=f},97:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=d(n(754)),a=function(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=u(t);if(n&&n.has(e))return n.get(e);var r={},a=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if("default"!==o&&Object.prototype.hasOwnProperty.call(e,o)){var i=a?Object.getOwnPropertyDescriptor(e,o):null;i&&(i.get||i.set)?Object.defineProperty(r,o,i):r[o]=e[o]}r.default=e,n&&n.set(e,r);return r}(n(363)),o=(n(648),d(n(86))),i=n(127),l=(d(n(820)),d(n(686)));function u(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(u=function(e){return e?n:t})(e)}function d(e){return e&&e.__esModule?e:{default:e}}const s=e=>{let{types:t,onSelect:n}=e;const[o,l]=(0,a.useState)(!1);return a.default.createElement(i.Dropdown,{isOpen:o,toggle:()=>l((e=>!e)),className:"link-picker__menu"},a.default.createElement(i.DropdownToggle,{className:"link-picker__menu-toggle font-icon-link",caret:!0},r.default._t("Link.ADD_LINK","Add Link")),a.default.createElement(i.DropdownMenu,null,t.map((e=>{let{key:t,title:r}=e;return a.default.createElement(i.DropdownItem,{key:t,onClick:()=>n(t)},r)}))))};s.propTypes={types:o.default.arrayOf(l.default).isRequired,onSelect:o.default.func.isRequired};var f=s;t.default=f},734:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=u(n(754)),a=u(n(363)),o=u(n(86)),i=u(n(686)),l=n(127);function u(e){return e&&e.__esModule?e:{default:e}}const d=e=>t=>{t.nativeEvent.stopImmediatePropagation(),t.preventDefault(),t.nativeEvent.preventDefault(),t.stopPropagation(),e&&e()},s=e=>{let{title:t,type:n,description:o,onClear:i,onClick:u}=e;return a.default.createElement("div",{className:"link-picker__link"},a.default.createElement(l.Button,{className:"link-picker__button font-icon-link",color:"secondary",onClick:d(u)},a.default.createElement("div",{className:"link-picker__link-detail"},a.default.createElement("div",{className:"link-picker__title"},t),a.default.createElement("small",{className:"link-picker__type"},n.title,": ",a.default.createElement("span",{className:"link-picker__url"},o)))),a.default.createElement(l.Button,{className:"link-picker__clear",color:"link",onClick:d(i)},r.default._t("Link.CLEAR","Clear")))};s.propTypes={title:o.default.string.isRequired,type:i.default,description:o.default.string,onClear:o.default.func,onClick:o.default.func};var f=s;t.default=f},115:function(e,t,n){var r=l(n(311)),a=l(n(363)),o=l(n(691)),i=n(648);function l(e){return e&&e.__esModule?e:{default:e}}function u(){return u=Object.assign?Object.assign.bind():function(e){for(var t=1;t{e(".js-injector-boot .entwine-jsonfield").entwine({Component:null,Root:null,onmatch(){const e=this.closest(".cms-content").attr("id"),t=e?{context:e}:{},n=this.data("schema-component"),r=(0,i.loadComponent)(n,t);this.setComponent(r),this.setRoot(o.default.createRoot(this[0])),this._super(),this.refresh()},refresh(){const e=this.getProps(),t=this.getComponent();this.getRoot().render(a.default.createElement(t,u({},e,{noHolder:!0})))},handleChange(t,n){let{id:r,value:a}=n;const o=e(this).data("field-id");e("#"+o).val(JSON.stringify(a)).trigger("change"),this.refresh()},getProps(){const t=e(this).data("field-id"),n=e("#"+t).val();return{id:t,value:n?JSON.parse(n):void 0,onChange:this.handleChange.bind(this)}},onunmatch(){this.getRoot().unmount()}})}))},287:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=n(648);const a={props(e){const{data:{error:t,readLinkDescription:n,loading:r}}=e,a=t&&t.graphQLErrors&&t.graphQLErrors.map((e=>e.message));return{loading:r,linkDescription:n?n.description:"",graphQLErrors:a}}},{READ:o}=r.graphqlTemplates;var i={apolloConfig:a,templateName:o,pluralName:"LinkDescription",pagination:!1,params:{dataStr:"String!"},args:{root:{dataStr:"dataStr"}},fields:["description"]};t.default=i},689:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=n(648);const a={props(e){const{data:{error:t,readLinkTypes:n,loading:r}}=e,a=t&&t.graphQLErrors&&t.graphQLErrors.map((e=>e.message));return{loading:r,types:n?n.reduce(((e,t)=>({...e,[t.key]:t})),{}):{},graphQLErrors:a}}},{READ:o}=r.graphqlTemplates;var i={apolloConfig:a,templateName:o,pluralName:"LinkTypes",pagination:!1,params:{keys:"[ID]"},args:{root:{keys:"keys"}},fields:["key","title","handlerName"]};t.default=i},686:function(e,t,n){Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r,a=(r=n(86))&&r.__esModule?r:{default:r};var o=a.default.shape({key:a.default.string.isRequired,title:a.default.string.isRequired});t.default=o},510:function(e){e.exports=Config},42:function(e){e.exports=FieldHolder},912:function(e){e.exports=FormBuilderModal},648:function(e){e.exports=Injector},475:function(e){e.exports=InsertMediaModal},872:function(e){e.exports=NodeUrl},86:function(e){e.exports=PropTypes},363:function(e){e.exports=React},691:function(e){e.exports=ReactDomClient},624:function(e){e.exports=ReactRedux},127:function(e){e.exports=Reactstrap},827:function(e){e.exports=Redux},820:function(e){e.exports=classnames},754:function(e){e.exports=i18n},311:function(e){e.exports=jQuery},902:function(e){e.exports=qs}},t={};function n(r){var a=t[r];if(void 0!==a)return a.exports;var o=t[r]={exports:{}};return e[r](o,o.exports,n),o.exports}n(274),n(115)}(); \ No newline at end of file +/******/ (function() { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({ + +/***/ "./client/src/boot/index.js": +/*!**********************************!*\ + !*** ./client/src/boot/index.js ***! + \**********************************/ +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + + + +var _Config = _interopRequireDefault(__webpack_require__(/*! lib/Config */ "lib/Config")); +var _registerReducers = _interopRequireDefault(__webpack_require__(/*! ./registerReducers */ "./client/src/boot/registerReducers.js")); +var _registerComponents = _interopRequireDefault(__webpack_require__(/*! ./registerComponents */ "./client/src/boot/registerComponents.js")); +var _registerQueries = _interopRequireDefault(__webpack_require__(/*! ./registerQueries */ "./client/src/boot/registerQueries.js")); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +document.addEventListener('DOMContentLoaded', () => { + (0, _registerComponents.default)(); + (0, _registerQueries.default)(); + (0, _registerReducers.default)(); +}); + +/***/ }), + +/***/ "./client/src/boot/registerComponents.js": +/*!***********************************************!*\ + !*** ./client/src/boot/registerComponents.js ***! + \***********************************************/ +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _Injector = _interopRequireDefault(__webpack_require__(/*! lib/Injector */ "lib/Injector")); +var _LinkPicker = _interopRequireDefault(__webpack_require__(/*! components/LinkPicker/LinkPicker */ "./client/src/components/LinkPicker/LinkPicker.js")); +var _LinkField = _interopRequireDefault(__webpack_require__(/*! components/LinkField/LinkField */ "./client/src/components/LinkField/LinkField.js")); +var _LinkModal = _interopRequireDefault(__webpack_require__(/*! components/LinkModal/LinkModal */ "./client/src/components/LinkModal/LinkModal.js")); +var _FileLinkModal = _interopRequireDefault(__webpack_require__(/*! components/LinkModal/FileLinkModal */ "./client/src/components/LinkModal/FileLinkModal.js")); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +const registerComponents = () => { + _Injector.default.component.registerMany({ + LinkPicker: _LinkPicker.default, + LinkField: _LinkField.default, + 'LinkModal.FormBuilderModal': _LinkModal.default, + 'LinkModal.InsertMediaModal': _FileLinkModal.default + }); +}; +var _default = registerComponents; +exports["default"] = _default; + +/***/ }), + +/***/ "./client/src/boot/registerQueries.js": +/*!********************************************!*\ + !*** ./client/src/boot/registerQueries.js ***! + \********************************************/ +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _Injector = _interopRequireDefault(__webpack_require__(/*! lib/Injector */ "lib/Injector")); +var _readLinkTypes = _interopRequireDefault(__webpack_require__(/*! state/linkTypes/readLinkTypes */ "./client/src/state/linkTypes/readLinkTypes.js")); +var _readLinkDescription = _interopRequireDefault(__webpack_require__(/*! state/linkDescription/readLinkDescription */ "./client/src/state/linkDescription/readLinkDescription.js")); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +const registerQueries = () => { + _Injector.default.query.register('readLinkTypes', _readLinkTypes.default); + _Injector.default.query.register('readLinkDescription', _readLinkDescription.default); +}; +var _default = registerQueries; +exports["default"] = _default; + +/***/ }), + +/***/ "./client/src/boot/registerReducers.js": +/*!*********************************************!*\ + !*** ./client/src/boot/registerReducers.js ***! + \*********************************************/ +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _Injector = _interopRequireDefault(__webpack_require__(/*! lib/Injector */ "lib/Injector")); +var _redux = __webpack_require__(/*! redux */ "redux"); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +const registerReducers = () => {}; +var _default = registerReducers; +exports["default"] = _default; + +/***/ }), + +/***/ "./client/src/components/LinkField/LinkField.js": +/*!******************************************************!*\ + !*** ./client/src/components/LinkField/LinkField.js ***! + \******************************************************/ +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _react = _interopRequireWildcard(__webpack_require__(/*! react */ "react")); +var _redux = __webpack_require__(/*! redux */ "redux"); +var _reactRedux = __webpack_require__(/*! react-redux */ "react-redux"); +var _Injector = __webpack_require__(/*! lib/Injector */ "lib/Injector"); +var _FieldHolder = _interopRequireDefault(__webpack_require__(/*! components/FieldHolder/FieldHolder */ "components/FieldHolder/FieldHolder")); +var toastsActions = _interopRequireWildcard(__webpack_require__(/*! state/toasts/ToastsActions */ "state/toasts/ToastsActions")); +var _Backend = _interopRequireDefault(__webpack_require__(/*! lib/Backend */ "lib/Backend")); +var _Config = _interopRequireDefault(__webpack_require__(/*! lib/Config */ "lib/Config")); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } +function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } +function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } +const LinkField = _ref => { + let { + loading, + Loading, + data, + LinkPicker, + onChange, + types, + linkDescription, + submitForm, + actions, + ...props + } = _ref; + if (loading) { + return _react.default.createElement(Loading, null); + } + const [editing, setEditing] = (0, _react.useState)(false); + const [newTypeKey, setNewTypeKey] = (0, _react.useState)(''); + const [linkID, setLinkID] = (0, _react.useState)(data.ID || 0); + const onClear = event => { + const section = _Config.default.getSection('SilverStripe\\Admin\\LeftAndMain'); + const endpoint = `${section.form.DynamicLink.deleteUrl}/${linkID}`; + _Backend.default.delete(endpoint).then(() => { + actions.toasts.success('Deleted link'); + }).catch(() => { + actions.toasts.error('Failed to delete link'); + }); + if (typeof onChange === 'function') { + onChange(event, {}); + } + }; + const { + typeKey + } = data; + const type = types[typeKey]; + const modalType = newTypeKey ? types[newTypeKey] : type; + let title = data ? data.Title : ''; + if (!title) { + title = data ? data.TitleRelField : ''; + } + const pickerProps = { + title, + link: type ? { + type, + title, + description: linkDescription + } : undefined, + onEdit: () => { + setEditing(true); + }, + onClear, + onSelect: key => { + setNewTypeKey(key); + setEditing(true); + }, + types: Object.values(types) + }; + const onModalSubmit = async (modalData, action, submitFn) => { + const { + SecurityID, + action_submit: actionSubmit, + ...data + } = modalData; + const formSchema = await submitFn(); + console.log('formSchema', formSchema); + let id = 0; + if (typeof formSchema !== 'undefined') { + const match = formSchema.id.match(/\/saveForm\/([0-9]+)$/); + if (match) { + id = parseInt(match[1]); + } + } + if (typeof onChange === 'function') { + data.ID = id; + onChange(null, data); + } + setEditing(false); + setNewTypeKey(''); + setLinkID(id); + actions.toasts.success('Saved link'); + return Promise.resolve(); + }; + const modalProps = { + type: modalType, + editing, + onSubmit: onModalSubmit, + onClosed: () => { + setEditing(false); + }, + data + }; + const handlerName = modalType ? modalType.handlerName : 'FormBuilderModal'; + const LinkModal = (0, _Injector.loadComponent)(`LinkModal.${handlerName}`); + return _react.default.createElement(_react.Fragment, null, _react.default.createElement(LinkPicker, pickerProps), _react.default.createElement(LinkModal, modalProps)); +}; +const stringifyData = Component => _ref2 => { + let { + data, + value, + ...props + } = _ref2; + let dataValue = value || data; + if (typeof dataValue === 'string') { + dataValue = JSON.parse(dataValue); + } + return _react.default.createElement(Component, _extends({ + dataStr: JSON.stringify(dataValue) + }, props, { + data: dataValue + })); +}; +const mapDispatchToProps = dispatch => { + return { + actions: { + toasts: (0, _redux.bindActionCreators)(toastsActions, dispatch) + } + }; +}; +var _default = (0, _redux.compose)((0, _Injector.inject)(['LinkPicker', 'Loading']), (0, _Injector.injectGraphql)('readLinkTypes'), stringifyData, (0, _Injector.injectGraphql)('readLinkDescription'), _FieldHolder.default, (0, _reactRedux.connect)(null, mapDispatchToProps))(LinkField); +exports["default"] = _default; + +/***/ }), + +/***/ "./client/src/components/LinkModal/FileLinkModal.js": +/*!**********************************************************!*\ + !*** ./client/src/components/LinkModal/FileLinkModal.js ***! + \**********************************************************/ +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _i18n = _interopRequireDefault(__webpack_require__(/*! i18n */ "i18n")); +var _react = _interopRequireWildcard(__webpack_require__(/*! react */ "react")); +var _InsertMediaModal = _interopRequireDefault(__webpack_require__(/*! containers/InsertMediaModal/InsertMediaModal */ "containers/InsertMediaModal/InsertMediaModal")); +var _reactRedux = __webpack_require__(/*! react-redux */ "react-redux"); +function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } +function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } +const FileLinkModal = _ref => { + let { + type, + editing, + data, + actions, + onSubmit, + ...props + } = _ref; + if (!type) { + return false; + } + (0, _react.useEffect)(() => { + if (editing) { + actions.initModal(); + } else { + actions.reset(); + } + }, [editing]); + const attrs = data ? { + ID: data.FileID, + Description: data.Title, + TargetBlank: data.OpenInNew ? true : false + } : {}; + const onInsert = _ref2 => { + let { + ID, + Description, + TargetBlank + } = _ref2; + return onSubmit({ + FileID: ID, + Title: Description, + OpenInNew: TargetBlank, + typeKey: type.key + }, '', () => {}); + }; + return _react.default.createElement(_InsertMediaModal.default, _extends({ + isOpen: editing, + type: "insert-link", + title: false, + bodyClassName: "modal__dialog", + className: "insert-link__dialog-wrapper--internal", + fileAttributes: attrs, + onInsert: onInsert + }, props)); +}; +function mapStateToProps() { + return {}; +} +function mapDispatchToProps(dispatch) { + return { + actions: { + initModal: () => dispatch({ + type: 'INIT_FORM_SCHEMA_STACK', + payload: { + formSchema: { + type: 'insert-link', + nextType: 'admin' + } + } + }), + reset: () => dispatch({ + type: 'RESET' + }) + } + }; +} +var _default = (0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps)(FileLinkModal); +exports["default"] = _default; + +/***/ }), + +/***/ "./client/src/components/LinkModal/LinkModal.js": +/*!******************************************************!*\ + !*** ./client/src/components/LinkModal/LinkModal.js ***! + \******************************************************/ +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _i18n = _interopRequireDefault(__webpack_require__(/*! i18n */ "i18n")); +var _react = _interopRequireDefault(__webpack_require__(/*! react */ "react")); +var _propTypes = _interopRequireDefault(__webpack_require__(/*! prop-types */ "prop-types")); +var _FormBuilderModal = _interopRequireDefault(__webpack_require__(/*! components/FormBuilderModal/FormBuilderModal */ "components/FormBuilderModal/FormBuilderModal")); +var _url = _interopRequireDefault(__webpack_require__(/*! url */ "url")); +var _qs = _interopRequireDefault(__webpack_require__(/*! qs */ "qs")); +var _Config = _interopRequireDefault(__webpack_require__(/*! lib/Config */ "lib/Config")); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } +const leftAndMain = 'SilverStripe\\Admin\\LeftAndMain'; +const buildSchemaUrl = (key, data) => { + const { + schemaUrl + } = _Config.default.getSection(leftAndMain).form.DynamicLink; + const parsedURL = _url.default.parse(schemaUrl); + const parsedQs = _qs.default.parse(parsedURL.query); + parsedQs.key = key; + if (data) { + for (const prop of ['href', 'path', 'pathname']) { + const id = typeof data.ID !== 'undefined' ? data.ID : '0'; + parsedURL[prop] += `/${id}`; + } + } + return _url.default.format({ + ...parsedURL, + search: _qs.default.stringify(parsedQs) + }); +}; +const LinkModal = _ref => { + let { + type, + editing, + data, + ...props + } = _ref; + if (!type) { + return false; + } + return _react.default.createElement(_FormBuilderModal.default, _extends({ + title: type.title, + isOpen: editing, + schemaUrl: buildSchemaUrl(type.key, data), + identifier: "Link.EditingLinkInfo" + }, props)); +}; +var _default = LinkModal; +exports["default"] = _default; + +/***/ }), + +/***/ "./client/src/components/LinkPicker/LinkPicker.js": +/*!********************************************************!*\ + !*** ./client/src/components/LinkPicker/LinkPicker.js ***! + \********************************************************/ +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = exports.Component = void 0; +var _i18n = _interopRequireDefault(__webpack_require__(/*! i18n */ "i18n")); +var _react = _interopRequireDefault(__webpack_require__(/*! react */ "react")); +var _Injector = __webpack_require__(/*! lib/Injector */ "lib/Injector"); +var _propTypes = _interopRequireDefault(__webpack_require__(/*! prop-types */ "prop-types")); +var _reactstrap = __webpack_require__(/*! reactstrap */ "reactstrap"); +var _classnames = _interopRequireDefault(__webpack_require__(/*! classnames */ "classnames")); +var _LinkPickerMenu = _interopRequireDefault(__webpack_require__(/*! ./LinkPickerMenu */ "./client/src/components/LinkPicker/LinkPickerMenu.js")); +var _LinkPickerTitle = _interopRequireDefault(__webpack_require__(/*! ./LinkPickerTitle */ "./client/src/components/LinkPicker/LinkPickerTitle.js")); +var _LinkType = _interopRequireDefault(__webpack_require__(/*! types/LinkType */ "./client/src/types/LinkType.js")); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } +const LinkPicker = _ref => { + let { + types, + onSelect, + link, + onEdit, + onClear + } = _ref; + return _react.default.createElement("div", { + className: (0, _classnames.default)('link-picker', 'form-control', { + 'link-picker--selected': link + }) + }, link === undefined && _react.default.createElement(_LinkPickerMenu.default, { + types: types, + onSelect: onSelect + }), link && _react.default.createElement(_LinkPickerTitle.default, _extends({}, link, { + onClear: onClear, + onClick: () => link && onEdit && onEdit(link) + }))); +}; +exports.Component = LinkPicker; +LinkPicker.propTypes = { + ..._LinkPickerMenu.default.propTypes, + link: _propTypes.default.shape(_LinkPickerTitle.default.propTypes), + onEdit: _propTypes.default.func, + onClear: _propTypes.default.func +}; +var _default = LinkPicker; +exports["default"] = _default; + +/***/ }), + +/***/ "./client/src/components/LinkPicker/LinkPickerMenu.js": +/*!************************************************************!*\ + !*** ./client/src/components/LinkPicker/LinkPickerMenu.js ***! + \************************************************************/ +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _i18n = _interopRequireDefault(__webpack_require__(/*! i18n */ "i18n")); +var _react = _interopRequireWildcard(__webpack_require__(/*! react */ "react")); +var _Injector = __webpack_require__(/*! lib/Injector */ "lib/Injector"); +var _propTypes = _interopRequireDefault(__webpack_require__(/*! prop-types */ "prop-types")); +var _reactstrap = __webpack_require__(/*! reactstrap */ "reactstrap"); +var _classnames = _interopRequireDefault(__webpack_require__(/*! classnames */ "classnames")); +var _LinkType = _interopRequireDefault(__webpack_require__(/*! types/LinkType */ "./client/src/types/LinkType.js")); +function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } +function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +const LinkPickerMenu = _ref => { + let { + types, + onSelect + } = _ref; + const [isOpen, setIsOpen] = (0, _react.useState)(false); + const toggle = () => setIsOpen(prevState => !prevState); + return _react.default.createElement(_reactstrap.Dropdown, { + isOpen: isOpen, + toggle: toggle, + className: "link-picker__menu" + }, _react.default.createElement(_reactstrap.DropdownToggle, { + className: "link-picker__menu-toggle font-icon-link", + caret: true + }, _i18n.default._t('Link.ADD_LINK', 'Add Link')), _react.default.createElement(_reactstrap.DropdownMenu, null, types.map(_ref2 => { + let { + key, + title + } = _ref2; + return _react.default.createElement(_reactstrap.DropdownItem, { + key: key, + onClick: () => onSelect(key) + }, title); + }))); +}; +LinkPickerMenu.propTypes = { + types: _propTypes.default.arrayOf(_LinkType.default).isRequired, + onSelect: _propTypes.default.func.isRequired +}; +var _default = LinkPickerMenu; +exports["default"] = _default; + +/***/ }), + +/***/ "./client/src/components/LinkPicker/LinkPickerTitle.js": +/*!*************************************************************!*\ + !*** ./client/src/components/LinkPicker/LinkPickerTitle.js ***! + \*************************************************************/ +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _i18n = _interopRequireDefault(__webpack_require__(/*! i18n */ "i18n")); +var _react = _interopRequireDefault(__webpack_require__(/*! react */ "react")); +var _propTypes = _interopRequireDefault(__webpack_require__(/*! prop-types */ "prop-types")); +var _LinkType = _interopRequireDefault(__webpack_require__(/*! types/LinkType */ "./client/src/types/LinkType.js")); +var _reactstrap = __webpack_require__(/*! reactstrap */ "reactstrap"); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +const stopPropagation = fn => e => { + e.nativeEvent.stopImmediatePropagation(); + e.preventDefault(); + e.nativeEvent.preventDefault(); + e.stopPropagation(); + fn && fn(); +}; +const LinkPickerTitle = _ref => { + let { + title, + type, + description, + onClear, + onClick + } = _ref; + return _react.default.createElement("div", { + className: "link-picker__link" + }, _react.default.createElement(_reactstrap.Button, { + className: "link-picker__button font-icon-link", + color: "secondary", + onClick: stopPropagation(onClick) + }, _react.default.createElement("div", { + className: "link-picker__link-detail" + }, _react.default.createElement("div", { + className: "link-picker__title" + }, title), _react.default.createElement("small", { + className: "link-picker__type" + }, type.title, ":\xA0", _react.default.createElement("span", { + className: "link-picker__url" + }, description)))), _react.default.createElement(_reactstrap.Button, { + className: "link-picker__clear", + color: "link", + onClick: stopPropagation(onClear) + }, _i18n.default._t('Link.CLEAR', 'Clear'))); +}; +LinkPickerTitle.propTypes = { + title: _propTypes.default.string.isRequired, + type: _LinkType.default, + description: _propTypes.default.string, + onClear: _propTypes.default.func, + onClick: _propTypes.default.func +}; +var _default = LinkPickerTitle; +exports["default"] = _default; + +/***/ }), + +/***/ "./client/src/entwine/JsonField.js": +/*!*****************************************!*\ + !*** ./client/src/entwine/JsonField.js ***! + \*****************************************/ +/***/ (function(__unused_webpack_module, __unused_webpack_exports, __webpack_require__) { + + + +var _jquery = _interopRequireDefault(__webpack_require__(/*! jquery */ "jquery")); +var _react = _interopRequireDefault(__webpack_require__(/*! react */ "react")); +var _client = _interopRequireDefault(__webpack_require__(/*! react-dom/client */ "react-dom/client")); +var _Injector = __webpack_require__(/*! lib/Injector */ "lib/Injector"); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } +_jquery.default.entwine('ss', $ => { + $('.js-injector-boot .entwine-jsonfield').entwine({ + Component: null, + Root: null, + onmatch() { + const cmsContent = this.closest('.cms-content').attr('id'); + const context = cmsContent ? { + context: cmsContent + } : {}; + const schemaComponent = this.data('schema-component'); + const ReactField = (0, _Injector.loadComponent)(schemaComponent, context); + this.setComponent(ReactField); + this.setRoot(_client.default.createRoot(this[0])); + this._super(); + this.refresh(); + }, + refresh() { + const props = this.getProps(); + const ReactField = this.getComponent(); + const Root = this.getRoot(); + Root.render(_react.default.createElement(ReactField, _extends({}, props, { + noHolder: true + }))); + }, + handleChange(event, value) { + const fieldID = $(this).data('field-id'); + $('#' + fieldID).val(JSON.stringify(value)).trigger('change'); + this.refresh(); + }, + getProps() { + const fieldID = $(this).data('field-id'); + const dataStr = $('#' + fieldID).val(); + const value = dataStr ? JSON.parse(dataStr) : undefined; + return { + value, + onChange: this.handleChange.bind(this) + }; + }, + onunmatch() { + const Root = this.getRoot(); + Root.unmount(); + } + }); +}); + +/***/ }), + +/***/ "./client/src/state/linkDescription/readLinkDescription.js": +/*!*****************************************************************!*\ + !*** ./client/src/state/linkDescription/readLinkDescription.js ***! + \*****************************************************************/ +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _Injector = __webpack_require__(/*! lib/Injector */ "lib/Injector"); +const apolloConfig = { + props(props) { + const { + data: { + error, + readLinkDescription, + loading: networkLoading + } + } = props; + const errors = error && error.graphQLErrors && error.graphQLErrors.map(graphQLError => graphQLError.message); + const linkDescription = readLinkDescription ? readLinkDescription.description : ''; + return { + loading: networkLoading, + linkDescription, + graphQLErrors: errors + }; + } +}; +const { + READ +} = _Injector.graphqlTemplates; +const query = { + apolloConfig, + templateName: READ, + pluralName: 'LinkDescription', + pagination: false, + params: { + dataStr: 'String!' + }, + args: { + root: { + dataStr: 'dataStr' + } + }, + fields: ['description'] +}; +var _default = query; +exports["default"] = _default; + +/***/ }), + +/***/ "./client/src/state/linkTypes/readLinkTypes.js": +/*!*****************************************************!*\ + !*** ./client/src/state/linkTypes/readLinkTypes.js ***! + \*****************************************************/ +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _Injector = __webpack_require__(/*! lib/Injector */ "lib/Injector"); +const apolloConfig = { + props(props) { + const { + data: { + error, + readLinkTypes, + loading: networkLoading + } + } = props; + const errors = error && error.graphQLErrors && error.graphQLErrors.map(graphQLError => graphQLError.message); + const types = readLinkTypes ? readLinkTypes.reduce((accumulator, type) => ({ + ...accumulator, + [type.key]: type + }), {}) : {}; + return { + loading: networkLoading, + types, + graphQLErrors: errors + }; + } +}; +const { + READ +} = _Injector.graphqlTemplates; +const query = { + apolloConfig, + templateName: READ, + pluralName: 'LinkTypes', + pagination: false, + params: { + keys: '[ID]' + }, + args: { + root: { + keys: 'keys' + } + }, + fields: ['key', 'title', 'handlerName'] +}; +var _default = query; +exports["default"] = _default; + +/***/ }), + +/***/ "./client/src/types/LinkType.js": +/*!**************************************!*\ + !*** ./client/src/types/LinkType.js ***! + \**************************************/ +/***/ (function(__unused_webpack_module, exports, __webpack_require__) { + + + +Object.defineProperty(exports, "__esModule", ({ + value: true +})); +exports["default"] = void 0; +var _propTypes = _interopRequireDefault(__webpack_require__(/*! prop-types */ "prop-types")); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +const LinkType = _propTypes.default.shape({ + key: _propTypes.default.string.isRequired, + title: _propTypes.default.string.isRequired +}); +var _default = LinkType; +exports["default"] = _default; + +/***/ }), + +/***/ "lib/Backend": +/*!**************************!*\ + !*** external "Backend" ***! + \**************************/ +/***/ (function(module) { + +module.exports = Backend; + +/***/ }), + +/***/ "lib/Config": +/*!*************************!*\ + !*** external "Config" ***! + \*************************/ +/***/ (function(module) { + +module.exports = Config; + +/***/ }), + +/***/ "components/FieldHolder/FieldHolder": +/*!******************************!*\ + !*** external "FieldHolder" ***! + \******************************/ +/***/ (function(module) { + +module.exports = FieldHolder; + +/***/ }), + +/***/ "components/FormBuilderModal/FormBuilderModal": +/*!***********************************!*\ + !*** external "FormBuilderModal" ***! + \***********************************/ +/***/ (function(module) { + +module.exports = FormBuilderModal; + +/***/ }), + +/***/ "lib/Injector": +/*!***************************!*\ + !*** external "Injector" ***! + \***************************/ +/***/ (function(module) { + +module.exports = Injector; + +/***/ }), + +/***/ "containers/InsertMediaModal/InsertMediaModal": +/*!***********************************!*\ + !*** external "InsertMediaModal" ***! + \***********************************/ +/***/ (function(module) { + +module.exports = InsertMediaModal; + +/***/ }), + +/***/ "url": +/*!**************************!*\ + !*** external "NodeUrl" ***! + \**************************/ +/***/ (function(module) { + +module.exports = NodeUrl; + +/***/ }), + +/***/ "prop-types": +/*!****************************!*\ + !*** external "PropTypes" ***! + \****************************/ +/***/ (function(module) { + +module.exports = PropTypes; + +/***/ }), + +/***/ "react": +/*!************************!*\ + !*** external "React" ***! + \************************/ +/***/ (function(module) { + +module.exports = React; + +/***/ }), + +/***/ "react-dom/client": +/*!*********************************!*\ + !*** external "ReactDomClient" ***! + \*********************************/ +/***/ (function(module) { + +module.exports = ReactDomClient; + +/***/ }), + +/***/ "react-redux": +/*!*****************************!*\ + !*** external "ReactRedux" ***! + \*****************************/ +/***/ (function(module) { + +module.exports = ReactRedux; + +/***/ }), + +/***/ "reactstrap": +/*!*****************************!*\ + !*** external "Reactstrap" ***! + \*****************************/ +/***/ (function(module) { + +module.exports = Reactstrap; + +/***/ }), + +/***/ "redux": +/*!************************!*\ + !*** external "Redux" ***! + \************************/ +/***/ (function(module) { + +module.exports = Redux; + +/***/ }), + +/***/ "state/toasts/ToastsActions": +/*!********************************!*\ + !*** external "ToastsActions" ***! + \********************************/ +/***/ (function(module) { + +module.exports = ToastsActions; + +/***/ }), + +/***/ "classnames": +/*!*****************************!*\ + !*** external "classnames" ***! + \*****************************/ +/***/ (function(module) { + +module.exports = classnames; + +/***/ }), + +/***/ "i18n": +/*!***********************!*\ + !*** external "i18n" ***! + \***********************/ +/***/ (function(module) { + +module.exports = i18n; + +/***/ }), + +/***/ "jquery": +/*!*************************!*\ + !*** external "jQuery" ***! + \*************************/ +/***/ (function(module) { + +module.exports = jQuery; + +/***/ }), + +/***/ "qs": +/*!*********************!*\ + !*** external "qs" ***! + \*********************/ +/***/ (function(module) { + +module.exports = qs; + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; +// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk. +!function() { +/*!**************************************!*\ + !*** ./client/src/bundles/bundle.js ***! + \**************************************/ + + +__webpack_require__(/*! boot */ "./client/src/boot/index.js"); +__webpack_require__(/*! entwine/JsonField */ "./client/src/entwine/JsonField.js"); +}(); +/******/ })() +; +//# sourceMappingURL=bundle.js.map \ No newline at end of file diff --git a/client/dist/styles/bundle.css b/client/dist/styles/bundle.css index 5c415c8c..9b3eaaf0 100644 --- a/client/dist/styles/bundle.css +++ b/client/dist/styles/bundle.css @@ -1 +1,68 @@ -.link-picker{display:flex;height:auto;min-height:54px;background:#fff;width:100%;align-items:stretch;cursor:pointer;padding:0;box-shadow:none}.link-picker.font-icon-link::before{margin:.76925rem}.link-picker__menu{flex-grow:1}.link-picker__menu-toggle{width:100%;height:100%;text-align:left}.link-picker__menu-toggle::before{padding:.76925rem}.link-picker__link{display:flex;align-items:center;width:100%;text-align:left;border:none;margin-right:0;justify-content:space-between}.link-picker__link:hover,.link-picker__link:focus{background:#eef0f4;text-decoration:none;color:inherit}.link-picker__button{display:flex;align-items:center;flex-grow:1;height:100%;text-align:left;border:none;margin-right:0}.link-picker__button::before{font-size:1.231rem;padding:.76925rem;margin-right:6px;flex-grow:0}.link-picker__link-detail{flex-grow:1}.link-picker__clear{flex-grow:0}.link-picker__url{color:#0071c4} +/*!*****************************************************************************************************************************************************************************************************************************************************************************************************************************!*\ + !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[0].use[2]!./node_modules/resolve-url-loader/index.js??ruleSet[1].rules[0].use[3]!./node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[0].use[4]!./client/src/styles/bundle.scss ***! + \*****************************************************************************************************************************************************************************************************************************************************************************************************************************/ +.link-picker { + display: flex; + height: auto; + min-height: 54px; + background: white; + width: 100%; + align-items: stretch; + cursor: pointer; + padding: 0; + box-shadow: none; +} +.link-picker.font-icon-link::before { + margin: 0.76925rem; +} +.link-picker__menu { + flex-grow: 1; +} +.link-picker__menu-toggle { + width: 100%; + height: 100%; + text-align: left; +} +.link-picker__menu-toggle::before { + padding: 0.76925rem; +} +.link-picker__link { + display: flex; + align-items: center; + width: 100%; + text-align: left; + border: none; + margin-right: 0; + justify-content: space-between; +} +.link-picker__link:hover, .link-picker__link:focus { + background: #eef0f4; + text-decoration: none; + color: inherit; +} +.link-picker__button { + display: flex; + align-items: center; + flex-grow: 1; + height: 100%; + text-align: left; + border: none; + margin-right: 0; +} +.link-picker__button::before { + font-size: 1.231rem; + padding: 0.76925rem; + margin-right: 6px; + flex-grow: 0; +} +.link-picker__link-detail { + flex-grow: 1; +} +.link-picker__clear { + flex-grow: 0; +} +.link-picker__url { + color: #0071c4; +} + +/*# sourceMappingURL=bundle.css.map*/ \ No newline at end of file diff --git a/client/src/components/LinkField/LinkField.js b/client/src/components/LinkField/LinkField.js index 5571450f..83d3bbb1 100644 --- a/client/src/components/LinkField/LinkField.js +++ b/client/src/components/LinkField/LinkField.js @@ -1,22 +1,37 @@ import React, { Fragment, useState } from 'react'; -import { compose } from 'redux'; +import { bindActionCreators, compose } from 'redux'; +import { connect } from 'react-redux'; import { inject, injectGraphql, loadComponent } from 'lib/Injector'; import fieldHolder from 'components/FieldHolder/FieldHolder'; +import * as toastsActions from 'state/toasts/ToastsActions'; +import backend from 'lib/Backend'; +import Config from 'lib/Config'; -const LinkField = ({ id, loading, Loading, data, LinkPicker, onChange, types, linkDescription, ...props }) => { +const LinkField = ({ loading, Loading, data, LinkPicker, onChange, types, linkDescription, submitForm, actions, ...props }) => { if (loading) { return ; } const [editing, setEditing] = useState(false); const [newTypeKey, setNewTypeKey] = useState(''); + const [linkID, setLinkID] = useState(data.ID || 0); const onClear = (event) => { - if (typeof onChange !== 'function') { - return; - } - onChange(event, { id, value: {} }); + // deleteUrl is set via LeftAndMainExtension.php + const section = Config.getSection('SilverStripe\\Admin\\LeftAndMain'); + const endpoint = `${section.form.DynamicLink.deleteUrl}/${linkID}`; + backend.delete(endpoint) + .then(() => { + actions.toasts.success('Deleted link'); + }) + .catch(() => { + actions.toasts.error('Failed to delete link'); + }); + + if (typeof onChange === 'function') { + onChange(event, {}); + } }; const { typeKey } = data; @@ -24,12 +39,11 @@ const LinkField = ({ id, loading, Loading, data, LinkPicker, onChange, types, li const modalType = newTypeKey ? types[newTypeKey] : type; let title = data ? data.Title : ''; - if (!title) { title = data ? data.TitleRelField : ''; } - const linkProps = { + const pickerProps = { title, link: type ? { type, title, description: linkDescription } : undefined, onEdit: () => { setEditing(true); }, @@ -41,15 +55,32 @@ const LinkField = ({ id, loading, Loading, data, LinkPicker, onChange, types, li types: Object.values(types) }; - const onModalSubmit = (modalData, action, submitFn) => { - const { SecurityID, action_insert: actionInsert, ...value } = modalData; + const onModalSubmit = async (modalData, action, submitFn) => { + const { SecurityID, action_submit: actionSubmit, ...data } = modalData; + + const formSchema = await submitFn(); + console.log('formSchema', formSchema); + + let id = 0; + + // formSchema will be undefined for FileLink which uses asset-admin graphql when using InsertMediaModal rather than FormBuilderModal + if (typeof formSchema !== 'undefined') { + const match = formSchema.id.match(/\/saveForm\/([0-9]+)$/); + if (match) { + id = parseInt(match[1]); + } + } if (typeof onChange === 'function') { - onChange(event, { id, value }); + data.ID = id; + onChange(null, data); } setEditing(false); setNewTypeKey(''); + setLinkID(id); + + actions.toasts.success('Saved link'); return Promise.resolve(); }; @@ -68,7 +99,7 @@ const LinkField = ({ id, loading, Loading, data, LinkPicker, onChange, types, li const LinkModal = loadComponent(`LinkModal.${handlerName}`); return - + ; }; @@ -81,10 +112,19 @@ const stringifyData = (Component) => (({ data, value, ...props }) => { return ; }); +const mapDispatchToProps = (dispatch) => { + return { + actions: { + toasts: bindActionCreators(toastsActions, dispatch), + }, + }; +} + export default compose( inject(['LinkPicker', 'Loading']), injectGraphql('readLinkTypes'), stringifyData, injectGraphql('readLinkDescription'), - fieldHolder + fieldHolder, + connect(null, mapDispatchToProps) )(LinkField); diff --git a/client/src/components/LinkModal/LinkModal.js b/client/src/components/LinkModal/LinkModal.js index 7e5ce86d..ecd4bb04 100644 --- a/client/src/components/LinkModal/LinkModal.js +++ b/client/src/components/LinkModal/LinkModal.js @@ -17,7 +17,10 @@ const buildSchemaUrl = (key, data) => { const parsedQs = qs.parse(parsedURL.query); parsedQs.key = key; if (data) { - parsedQs.data = JSON.stringify(data); + for (const prop of ['href', 'path', 'pathname']) { + const id = typeof data.ID !== 'undefined' ? data.ID : '0'; + parsedURL[prop] += `/${id}`; + } } return url.format({ ...parsedURL, search: qs.stringify(parsedQs)}); } diff --git a/client/src/entwine/JsonField.js b/client/src/entwine/JsonField.js index ee8f22c4..6d924f17 100644 --- a/client/src/entwine/JsonField.js +++ b/client/src/entwine/JsonField.js @@ -33,7 +33,7 @@ jQuery.entwine('ss', ($) => { Root.render(); }, - handleChange(event, {id, value}) { + handleChange(event, value) { const fieldID = $(this).data('field-id'); $('#' + fieldID).val(JSON.stringify(value)).trigger('change'); this.refresh(); @@ -50,7 +50,6 @@ jQuery.entwine('ss', ($) => { const value = dataStr ? JSON.parse(dataStr) : undefined; return { - id: fieldID, value, onChange: this.handleChange.bind(this) }; diff --git a/src/Controllers/LinkFieldController.php b/src/Controllers/LinkFieldController.php new file mode 100644 index 00000000..12c30ad6 --- /dev/null +++ b/src/Controllers/LinkFieldController.php @@ -0,0 +1,352 @@ + 'apiSaveForm', + 'DELETE deleteLink/$ItemID' => 'apiDeleteLink', + + 'GET saveForm/$ItemID/field/$FieldName/$FieldAction/$FieldArg' => 'handleLinkFormFieldRequest', + ]; + + private static $allowed_actions = [ + // 'linkForm' needs to defined for the benefit of LeftAndMain::schema() even though + // 'getLinkForm()' is the method that's ultimately called + // it's pretty unintuitive and could be made better, though it works for now + 'linkForm', + 'apiSaveForm', + 'apiDeleteLink', + + 'getLinkForm', // remove this? + + 'handleLinkFormFieldRequest', + ]; + + /** + * Handle a linkform field request e.g. TreeDropdownField in SiteTreeLink + * requests will look like this for a Link with an ID of 23 + * /admin/linkfield/saveForm/23/field/PageID/tree + */ + public function handleLinkFormFieldRequest(): HTTPResponse + { + $request = $this->getRequest(); + $fieldAction = $request->param('FieldAction'); + if (!$request->getVar('key')) { + // add in an arbitary key so that getLinkForm() doesn't complain about key missing + // this isn't actually used for the HTTPResponse + $key = array_keys((new Registry)->list())[0]; + $request->offsetSet('key', $key); + } + + $form = $this->getLinkForm(); + $formRequestHandler = $form->getRequestHandler(); + // note the other param in this request $FieldName is important as it's used in + // FormRequestHandler::handleField() to work out which field to extract from the form + $field = $formRequestHandler->handleField($request); + + // Special logic for AnchorSelectorField + if (is_a($field, AnchorSelectorField::class)) { + // AnchorSelectorField::anchors() expects a $request with a $PageID + $pageID = $request->param('FieldArg'); + $routeParams = $request->routeParams(); + $routeParams['PageID'] = $pageID; + $request->setRouteParams($routeParams); + // AnchorSelectorField::anchors() uses $this->getRequest() rather than $request arg + // so we need to ensure the field is the correct request object + $field->setRequest($request); + } + + // e.g. TreeDropdownField::tree($request); + $ret = $field->$fieldAction($request); + if (!is_a($ret, HTTPResponse::class)) { + $response = $this->getResponse(); + if (is_array($ret) || is_object($ret)) { + $ret = json_encode($ret); + } + $response->setBody($ret); + $response->addHeader('Content-type', 'application/json'); + $ret = $response; + } + return $ret; + } + + /** + * + */ + public function init() + { + parent::init(); + // TODO have temporarily disabled for easier dev + SecurityToken::disable(); + // Check security token + if (!SecurityToken::inst()->checkRequest($this->getRequest())) { + $this->jsonError(400, 'Invalid CSRF token'); + } + // Add full X-Formschema-Request header to request to ensure entire FormSchema is returned + // This means we don't need to worry about adding them in javascript + // This makes dev life easier and we should update this to be the default behaviour + $request = $this->getRequest(); + $header = $request->getHeader(LeftAndMain::SCHEMA_HEADER) ?? ''; + if ($header === '' || $header === 'auto') { + // this will overwrite any existing header + $request->addHeader(LeftAndMain::SCHEMA_HEADER, 'auto,schema,state,errors'); + } + } + + public function getClientConfig() + { + $clientConfig = parent::getClientConfig(); + $clientConfig['form']['DynamicLink'] = [ // << todo: rename to linkForm or something + // will probably need to update something in client + 'schemaUrl' => $this->Link('schema/linkForm'), + 'saveUrl' => $this->Link('saveForm'), + 'saveMethod' => 'post', + 'payloadFormat' => 'json', + 'formNameTemplate' => sprintf(self::FORM_NAME_TEMPLATE, '{id}'), + ]; + + // Configuration that is available per element type + $clientConfig['elementTypes'] = ElementTypeRegistry::generate()->getDefinitions(); + + return $clientConfig; + } + + /** + * This method is called from LeftAndMain::schema() + * /admin/linkfield/schema/linkForm/ + * + * Adapted from ElementalAreaController::getElementForm() + * + * @return Form + */ + public function getLinkForm(): Form + { + $p = $this->getRequest()->params(); + $x=1; + // this is allowed to be not be present? (when creating a new link?) + + // saving an existing form + $id = (int) $this->getRequest()->param('ItemID'); + + if ($id) { + // ORM will automatically get the correct subclass e.g. EmailLink + $link = Link::get()->byID($id); + if (!$link) { + $this->jsonError(400, 'Invalid link id'); + } + } else { + $key = $this->keyFromRequest(); + $link = (new Registry)->byKey($key); + if (!$link) { + $this->jsonError(400, 'Invalid key'); + } + } + return $this->createLinkForm($link); + } + + private function createLinkForm(Link $link): Form + { + $id = $link->ID; + + // $formFactory = FormFactory::create(); // << extends LinkFormFactory + $formFactory = new DefaultFormFactory; // << feed it a DataObject with $context['Record'] = $obj + + /** @var Form $form */ + $form = $formFactory->getForm( + $this, + sprintf(self::FORM_NAME_TEMPLATE, $id), + [ + // This is from when we were using LinkFormFactory + // 'LinkType' => $obj, + // 'LinkTypeKey' => $linkTypeKey, + + // This is for DefaultFormFactory + 'Record' => $link, + + // the following is here for (silverstripe/admin) LinkFormFactory::getRequiredContext() + // 'RequireLinkText' => false + ] + ); + + // LinkFormFactory has this styling + $form->addExtraClass('form--no-dividers'); + + // This is required so that when the form is submitted it goes to the right place + // this really should be solved by LeftAndMainExtension::updateClientConfig() + // where it sets 'saveUrl' ... :shrug: ... not sure why it's not + // shouldn't have to set here + $urlSegment = $this->config()->get('url_segment'); + $form->setFormAction("admin/$urlSegment/saveForm/$id"); + + // todo: rename typeKey to linkKey, or just key + $typeKey = (new Registry)->keyByClassName($link->ClassName); + $form->Fields()->push(HiddenField::create('typeKey')->setValue($typeKey)); + + // Adding button "manually" - need this otherwise there's no submit button in the modal + $title = $id ? 'Update link' : 'Create link'; // todo: _t() + + // Copied from LinkFormFactory::getFormActions() + $actions = FieldList::create([ + FormAction::create('submit', $title) + ->setSchemaData(['data' => ['buttonStyle' => 'primary']]), + ]); + $form->setActions($actions); + + if (!$link->canEdit()) { + $form->makeReadonly(); + } + + return $form; + } + + /** + * TODO: replace POST typeKey with simply key - will require updating JS + */ + private function keyFromRequest() + { + $request = $this->getRequest(); + $key = $request->postVar('typeKey') ?: $request->getVar('key'); + if (!$key) { + $this->jsonError(400, 'Need to pass in typeKey via POST body or key via querystring'); + } + return $key; + } + + /** + * Code adapted from https://github.com/silverstripe/silverstripe-elemental/pull/1113/files#diff-942115e4b8f6030e4d72ebc2b3a0772ec65e5dfcd08fbd0e677c70d1231daf28R189 + * + * Save an inline edit form for a Link + */ + public function apiSaveForm(HTTPRequest $request): HTTPResponse + { + // Check data was posted + $data = $request->postVars(); + if (empty($data)) { + $this->jsonError(400, 'Empty data'); + } + // Ensure that url param is used as the source of truth for ID. ID shouldn't be in the + // POST request, though just remove it in case someone tries to add it in + if (isset($data['ID'])) { + unset($data['ID']); + } + + /** @var Link $link */ + $id = (int) $this->getRequest()->param('ItemID'); + if ($id) { + // Editing an existing Link + $link = Link::get()->byID($id); + } else { + // Createing a new Link + $key = $this->keyFromRequest(); + /** @var Link $className */ + $className = (new Registry)->list()[$key] ?? ''; + if (!$className) { + $this->jsonError(400, 'Invalid key'); + } + $link = $className::create(); + } + + // Ensure the DataObject can be edited by the current user + if (!$link || !$link->canEdit()) { + $this->jsonError(403, 'Unauthorized'); + } + + // Get form and populate it with POSTed data + $form = $this->createLinkForm($link); + $form->loadDataFrom($data); + + // Validate the Form + $validationResult = $form->validationResult(); + + // Update DataObject from from data + $form->saveInto($link); + + // Special logic for FileLink + if (is_a($link, FileLink::class)) { + // FileField value will come in as $postVars['File']['Files'][0]; + // $form->saveInto($link); doesn't seem to handle this + $link->FileID = $data['File']['Files'][0] ?? 0; + } + + // Validate the DataObject and combine the two validation results together + $validationResult->combineAnd($link->validate()); + + // Write to the DataObject if things are valid and it's appropriate to do so + if ($validationResult->isValid()) { + if ($link->isChanged()) { + $link->write(); + } + // Create a new Form so that it has the correct ID for the DataObject when creating + // a new DataObject, as well as anything else on the DataObject that may have been + // updated in an extension hook. We do this so that the FormSchema state is correct + // before returning it in the response + $form = $this->createLinkForm($link); + } + + // Create and send FormSchema JSON response + $schemaID = $form->FormAction(); + $response = $this->getSchemaResponse($schemaID, $form, $validationResult); + + // returning a 400 means that FormBuilder.js::handleSubmit() submitFn() + // that will end up in the catch() .. throw error block and the error + // will just end up in the javascript console + // $response->setStatusCode(400); + // return a 200 for now just to get things to work even though it's + // clearly the wrong code if !$validationResult->isValid(). + // Will require a PR to admin to fix this + $response->setStatusCode(200); + return $response; + } + + /** + * I tried to use GraphQL but could work out apollo so make this instead + * this query did work in graphql ide + * mutation { deleteLinks(ids: [44]) } + */ + public function apiDeleteLink(): HTTPResponse + { + $id = (int) $this->getRequest()->param('ItemID'); + if (!$id) { + $this->jsonError(400, 'Missing ID'); + } + $link = Link::get()->byID($id); + if (!$link) { + $this->jsonError(400, 'Invalid ID'); + } + if (!$link->canDelete()) { + $this->jsonError(403, 'Not authorized to delete link'); + } + // This will also delete any published version immediately too + $link->delete(); + $response = $this->getResponse(); + $response->setBody(json_encode(['success' => true])); + return $response; + } +} diff --git a/src/Extensions/AjaxField.php b/src/Extensions/AjaxField.php deleted file mode 100644 index 7764c65c..00000000 --- a/src/Extensions/AjaxField.php +++ /dev/null @@ -1,33 +0,0 @@ -getOwner(); - $formName = $owner->getForm()->getName(); - - if ($formName !== 'Modals/DynamicLink') { - return; - } - - $request = $owner->getForm()->getController()->getRequest(); - $key = $request->getVar('key'); - - $link .= strpos($link, '?') === false ? '?' : '&'; - $link .= "key={$key}"; - } -} diff --git a/src/Extensions/LeftAndMain.php b/src/Extensions/LeftAndMain.php deleted file mode 100644 index d1173a3d..00000000 --- a/src/Extensions/LeftAndMain.php +++ /dev/null @@ -1,25 +0,0 @@ -init(); - } - - public function updateClientConfig(&$clientConfig) - { - $clientConfig['form']['DynamicLink'] = [ - 'schemaUrl' => $this->getOwner()->Link('methodSchema/Modals/DynamicLink'), - ]; - } -} diff --git a/src/Extensions/LeftAndMainExtension.php b/src/Extensions/LeftAndMainExtension.php new file mode 100644 index 00000000..a7f0c496 --- /dev/null +++ b/src/Extensions/LeftAndMainExtension.php @@ -0,0 +1,31 @@ +init(); + } + + public function updateClientConfig(&$clientConfig) + { + $clientConfig['form']['DynamicLink'] = [ + 'schemaUrl' => $this->getOwner()->Link('linkfield/schema/linkForm'), + 'saveUrl' => $this->getOwner()->Link('linkfield/saveForm'), + 'deleteUrl' => $this->getOwner()->Link('linkfield/deleteLink'), + 'saveMethod' => 'post', + 'payloadFormat' => 'json', // not true? + 'formNameTemplate' => sprintf(LinkFieldController::FORM_NAME_TEMPLATE, '{id}'), + ]; + } +} diff --git a/src/Form/FormFactory.php b/src/Form/FormFactory.php index e9f57f5c..81e58b05 100644 --- a/src/Form/FormFactory.php +++ b/src/Form/FormFactory.php @@ -9,6 +9,8 @@ use SilverStripe\ORM\DataObject; /** + * TODO problably just rid of this class and add logic directly in LinkFieldController::getLinkForm() + * * Create Form schema for the LinkField based on a key provided by the request. */ class FormFactory extends LinkFormFactory @@ -18,7 +20,7 @@ protected function getFormFields($controller, $name, $context) /** @var Type $type */ $type = $context['LinkType']; - if (!$type instanceof Type) { + if (!is_a($type, Type::class)) { throw new LogicException(sprintf('%s: LinkType must be provided and must be an instance of Type', static::class)); } diff --git a/src/Form/JsonField.php b/src/Form/JsonField.php index 4f7ad0b6..6514b72a 100644 --- a/src/Form/JsonField.php +++ b/src/Form/JsonField.php @@ -2,11 +2,13 @@ namespace SilverStripe\LinkField\Form; +use Exception; use InvalidArgumentException; use SilverStripe\Forms\FormField; use SilverStripe\LinkField\JsonData; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObjectInterface; +use SilverStripe\LinkField\Models\Link; /** * Field designed to edit complex data passed as a JSON string. Other FormFields can be built on top of this one. @@ -28,7 +30,7 @@ public function setValue($value, $data = null) } /** - * @param DataObject|DataObjectInterface $record + * @param DataObject|DataObjectInterface $record - A DataObject such as a Page * @return $this */ public function saveInto(DataObjectInterface $record) @@ -40,37 +42,14 @@ public function saveInto(DataObjectInterface $record) return $this; } - $dataValue = $this->dataValue(); - $value = is_string($dataValue) ? $this->parseString($this->dataValue()) : $dataValue; - - if ($class = DataObject::getSchema()->hasOneComponent(get_class($record), $fieldname)) { - /** @var JsonData|DataObject $jsonDataObject */ - - $jsonDataObjectID = $record->{"{$fieldname}ID"}; - - if ($jsonDataObjectID && $jsonDataObject = $record->$fieldname) { - if ($value) { - $jsonDataObject = $jsonDataObject->setData($value); - $this->extend('onBeforeLinkEdit', $jsonDataObject, $record); - $jsonDataObject->write(); - $this->extend('onAfterLinkEdit', $jsonDataObject, $record); - } else { - $this->extend('onBeforeLinkDelete', $jsonDataObject, $record); - $jsonDataObject->delete(); - $record->{"{$fieldname}ID"} = 0; - $this->extend('onAfterLinkDelete', $jsonDataObject, $record); - } - } elseif ($value) { - $jsonDataObject = new $class(); - $jsonDataObject = $jsonDataObject->setData($value); - $this->extend('onBeforeLinkCreate', $jsonDataObject, $record); - $jsonDataObject->write(); - $record->{"{$fieldname}ID"} = $jsonDataObject->ID; - $this->extend('onAfterLinkCreate', $jsonDataObject, $record); - } - } elseif ((DataObject::getSchema()->databaseField(get_class($record), $fieldname))) { - $record->{$fieldname} = $value; - } + // This code will all change in a later PR once we refactor out the JSON data format + // This method used to update the Link DataObject which was a relation + // Update the Pages has_one relationship ID using the existing JSON data format + $data = $this->parseString($this->dataValue()); + // Not 'ID' key is perfectly valid e.g. removing an existing link + $linkID = $data['ID'] ?? 0; + $dbColumn = $fieldname . 'ID'; + $record->$dbColumn = $linkID; return $this; } diff --git a/src/Models/FileLink.php b/src/Models/FileLink.php index 0d40b933..bc8248ad 100644 --- a/src/Models/FileLink.php +++ b/src/Models/FileLink.php @@ -33,7 +33,20 @@ public function generateLinkDescription(array $data): string public function LinkTypeHandlerName(): string { - return 'InsertMediaModal'; + // 3.x + // Can't really use this as there's no FormSchema/FormBuilder, so doesn't work with + // the new way of doing things + // return 'InsertMediaModal'; + // + // Default for Link - this is currently a bit busted it requires the user + // to NOT click the 'Insert file" because this will submit the Link Form seemingly without + // the file that was just selected (presumably the JS in that component expects so callback + // to have been passed, normally used in WYSIWYG context?) + // Instead they need to click the "back" arrow before clicking + // "Insert" and then finally "Create link". You also cannot upload a new file as you'll + // end up with cannot handle sub-URLS on class LinkFieldController + // /admin/linkfield/saveForm/59/field/File/upload + return 'FormBuilderModal'; } public function getURL(): string