diff --git a/README.md b/README.md index 23016167..fb0218a0 100644 --- a/README.md +++ b/README.md @@ -323,9 +323,10 @@ globally in your command line. **Note:** If adding or modifying colours, spacing, font sizes etc. please try and use an appropriate variable from the silverstripe/admin module if available. -## Advanced setup +## Integration with other modules -* [Advanced setup](docs/en/advanced_setup.md) +* [Multiple languages with tractorcow/silverstripe-fluent](docs/en/advanced_setup.md) +* [Search through silverstripe/fulltextsearch](docs/en/searching-blocks.md) ## Upgrading diff --git a/_config/graphql.yml b/_config/graphql.yml index e91eed8c..3e13f4a2 100644 --- a/_config/graphql.yml +++ b/_config/graphql.yml @@ -26,6 +26,7 @@ SilverStripe\GraphQL\Manager: nestedQueries: Elements: resolver: DNADesign\Elemental\GraphQL\ElementsResolver + paginate: false operations: readOne: resolver: DNADesign\Elemental\GraphQL\ReadOneAreaResolver diff --git a/client/dist/js/bundle.js b/client/dist/js/bundle.js index 4ae2fc13..b49a7a6d 100644 --- a/client/dist/js/bundle.js +++ b/client/dist/js/bundle.js @@ -1 +1 @@ -!function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},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";function r(e){return e&&e.__esModule?e:{default:e}}var o=n("./client/src/boot/registerComponents.js"),a=r(o),i=n("./client/src/boot/registerTransforms.js"),l=r(i);window.document.addEventListener("DOMContentLoaded",function(){(0,a.default)(),(0,l.default)()})},"./client/src/boot/registerComponents.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(2),a=r(o),i=n("./client/src/components/ElementEditor/Element.js"),l=r(i),u=n("./client/src/components/ElementEditor/ElementActions.js"),c=r(u),s=n("./client/src/components/ElementEditor/ElementEditor.js"),d=r(s),f=n("./client/src/components/ElementEditor/ElementList.js"),p=r(f),m=n("./client/src/components/ElementEditor/Toolbar.js"),y=r(m),b=n("./client/src/components/ElementEditor/AddNewButton.js"),v=r(b),h=n("./client/src/components/ElementEditor/Header.js"),g=r(h),E=n("./client/src/components/ElementEditor/Content.js"),_=r(E),O=n("./client/src/components/ElementEditor/Summary.js"),T=r(O),j=n("./client/src/components/ElementEditor/InlineEditForm.js"),I=r(j),w=n("./client/src/components/ElementEditor/AddElementPopover.js"),D=r(w),k=n("./client/src/components/ElementEditor/HoverBar.js"),S=r(k),A=n("./client/src/components/ElementEditor/DragPositionIndicator.js"),P=r(A),C=n("./client/src/components/TextCheckboxGroupField/TextCheckboxGroupField.js"),N=r(C);t.default=function(){a.default.component.registerMany({ElementEditor:d.default,ElementToolbar:y.default,ElementAddNewButton:v.default,ElementList:p.default,Element:l.default,ElementActions:c.default,ElementHeader:g.default,ElementContent:_.default,ElementSummary:T.default,ElementInlineEditForm:I.default,AddElementPopover:D.default,HoverBar:S.default,DragPositionIndicator:P.default,TextCheckboxGroupField:N.default})}},"./client/src/boot/registerTransforms.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=n(2),a=r(o),i=n("./client/src/state/history/readOneBlockQuery.js"),l=r(i),u=n("./client/src/components/HistoricElementView/HistoricElementView.js"),c=r(u),s=n("./client/src/state/history/revertToBlockVersionMutation.js"),d=r(s),f=n("./client/src/state/editor/readBlocksForAreaQuery.js"),p=r(f),m=n("./client/src/state/editor/addElementMutation.js"),y=r(m),b=n("./client/src/components/ElementActions/ArchiveAction.js"),v=r(b),h=n("./client/src/components/ElementActions/PublishAction.js"),g=r(h),E=n("./client/src/components/ElementActions/SaveAction.js"),_=r(E),O=n("./client/src/components/ElementActions/UnpublishAction.js"),T=r(O);t.default=function(){a.default.transform("elemental-fieldgroup",function(e){e.component("FieldGroup.HistoryViewer.VersionDetail",c.default,"HistoricElement")},{after:"field-holders"}),a.default.transform("elements-history",function(e){e.component("HistoryViewer.Form_ItemEditForm",l.default,"ElementHistoryViewer")}),a.default.transform("blocks-history-revert",function(e){e.component("HistoryViewerToolbar.VersionedAdmin.HistoryViewer.Element.HistoryViewerVersionDetail",d.default,"BlockRevertMutation")}),a.default.transform("cms-element-editor",function(e){e.component("ElementList",p.default,"PageElements")}),a.default.transform("cms-element-adder",function(e){e.component("AddElementPopover",y.default,"ElementAddButton")}),a.default.transform("element-actions",function(e){e.component("ElementActions",_.default,"ElementActionsWithSave"),e.component("ElementActions",g.default,"ElementActionsWithPublish"),e.component("ElementActions",T.default,"ElementActionsWithUnpublish"),e.component("ElementActions",v.default,"ElementActionsWithArchive")})}},"./client/src/bundles/bundle.js":function(e,t,n){"use strict";n("./client/src/legacy/ElementEditor/entwine.js"),n("./client/src/boot/index.js")},"./client/src/components/ElementActions/AbstractAction.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var o=Object.assign||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:null,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n=!!e&&e.ID;this.setState({dragTargetElementId:n,dragSpot:!1===t?"bottom":"top"})}},{key:"handleDragEnd",value:function(e,t){var n=this.props;(0,n.actions.handleSortBlock)(e,t,n.areaId).then(function(){var e=window.jQuery(".cms-preview");e.entwine("ss.preview")._loadUrl(e.find("iframe").attr("src"))}),this.setState({dragTargetElementId:null,dragSpot:null})}},{key:"render",value:function(){var e=this.props,t=e.fieldName,n=e.formState,r=e.ToolbarComponent,o=e.ListComponent,a=e.areaId,i=e.elementTypes,l=e.isDraggingOver,u=e.connectDropTarget,c=e.allowedElements,s=this.state,d=s.dragTargetElementId,p=s.dragSpot,m=c.map(function(e){return i.find(function(t){return t.class===e})});return u(f.default.createElement("div",{className:"element-editor"},f.default.createElement(r,{elementTypes:m,areaId:a,onDragOver:this.handleDragOver}),f.default.createElement(o,{allowedElementTypes:m,elementTypes:i,areaId:a,onDragOver:this.handleDragOver,onDragStart:this.handleDragStart,onDragEnd:this.handleDragEnd,dragSpot:p,isDraggingOver:l,dragTargetElementId:d}),f.default.createElement(j.default,{elementTypes:i}),f.default.createElement("input",{name:t,type:"hidden",value:JSON.stringify(n)||"",className:"no-change-track"})))}}]),t}(d.PureComponent);D.propTypes={fieldName:m.default.string,elementTypes:m.default.arrayOf(v.elementTypeType).isRequired,allowedElements:m.default.arrayOf(m.default.string).isRequired,areaId:m.default.number.isRequired,actions:m.default.shape({handleSortBlock:m.default.func})},t.Component=D,t.default=(0,b.compose)(w.default,(0,E.DropTarget)("element",{},function(e,t){return{connectDropTarget:e.dropTarget(),isDraggingOver:t.isOver()}}),(0,h.connect)(u),(0,y.inject)(["ElementToolbar","ElementList"],function(e,t){return{ToolbarComponent:e,ListComponent:t}},function(){return"ElementEditor"}),O.default)(D)},"./client/src/components/ElementEditor/ElementList.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(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 i(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=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:null;return(Array.isArray(t)?t:a().elementTypes).find(function(t){return t.class===e||t.name===e})}},"./client/src/state/editor/loadElementFormStateName.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.loadElementFormStateName=void 0;var r=n(12),o=function(e){return e&&e.__esModule?e:{default:e}}(r);t.loadElementFormStateName=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=o.default.getSection("DNADesign\\Elemental\\Controllers\\ElementalAreaController"),n=t.form.elementForm.formNameTemplate;return e?n.replace("{id}",e):n}},"./client/src/state/editor/loadElementSchemaValue.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.loadElementSchemaValue=void 0;var r=n(12),o=function(e){return e&&e.__esModule?e:{default:e}}(r);t.loadElementSchemaValue=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n=o.default.getSection("DNADesign\\Elemental\\Controllers\\ElementalAreaController"),r=n.form.elementForm[e]||"";return t?r+"/"+t:r}},"./client/src/state/editor/publishBlockMutation.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.config=t.mutation=void 0;var r=Object.assign||function(e){for(var t=1;t0&&void 0!==arguments[0]?arguments[0]:null,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n=!!e&&e.ID;this.setState({dragTargetElementId:n,dragSpot:!1===t?"bottom":"top"})}},{key:"handleDragEnd",value:function(e,t){var n=this.props;(0,n.actions.handleSortBlock)(e,t,n.areaId).then(function(){var e=window.jQuery(".cms-preview");e.entwine("ss.preview")._loadUrl(e.find("iframe").attr("src"))}),this.setState({dragTargetElementId:null,dragSpot:null})}},{key:"render",value:function(){var e=this.props,t=e.fieldName,n=e.formState,r=e.ToolbarComponent,o=e.ListComponent,a=e.areaId,i=e.elementTypes,l=e.isDraggingOver,u=e.connectDropTarget,c=e.allowedElements,s=this.state,d=s.dragTargetElementId,p=s.dragSpot,m=c.map(function(e){return i.find(function(t){return t.class===e})});return u(f.default.createElement("div",{className:"element-editor"},f.default.createElement(r,{elementTypes:m,areaId:a,onDragOver:this.handleDragOver}),f.default.createElement(o,{allowedElementTypes:m,elementTypes:i,areaId:a,onDragOver:this.handleDragOver,onDragStart:this.handleDragStart,onDragEnd:this.handleDragEnd,dragSpot:p,isDraggingOver:l,dragTargetElementId:d}),f.default.createElement(j.default,{elementTypes:i}),f.default.createElement("input",{name:t,type:"hidden",value:JSON.stringify(n)||"",className:"no-change-track"})))}}]),t}(d.PureComponent);D.propTypes={fieldName:m.default.string,elementTypes:m.default.arrayOf(v.elementTypeType).isRequired,allowedElements:m.default.arrayOf(m.default.string).isRequired,areaId:m.default.number.isRequired,actions:m.default.shape({handleSortBlock:m.default.func})},t.Component=D,t.default=(0,b.compose)(w.default,(0,E.DropTarget)("element",{},function(e,t){return{connectDropTarget:e.dropTarget(),isDraggingOver:t.isOver()}}),(0,h.connect)(u),(0,y.inject)(["ElementToolbar","ElementList"],function(e,t){return{ToolbarComponent:e,ListComponent:t}},function(){return"ElementEditor"}),O.default)(D)},"./client/src/components/ElementEditor/ElementList.js":function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function o(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 i(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=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:null;return(Array.isArray(t)?t:a().elementTypes).find(function(t){return t.class===e||t.name===e})}},"./client/src/state/editor/loadElementFormStateName.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.loadElementFormStateName=void 0;var r=n(12),o=function(e){return e&&e.__esModule?e:{default:e}}(r);t.loadElementFormStateName=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=o.default.getSection("DNADesign\\Elemental\\Controllers\\ElementalAreaController"),n=t.form.elementForm.formNameTemplate;return e?n.replace("{id}",e):n}},"./client/src/state/editor/loadElementSchemaValue.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.loadElementSchemaValue=void 0;var r=n(12),o=function(e){return e&&e.__esModule?e:{default:e}}(r);t.loadElementSchemaValue=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n=o.default.getSection("DNADesign\\Elemental\\Controllers\\ElementalAreaController"),r=n.form.elementForm[e]||"";return t?r+"/"+t:r}},"./client/src/state/editor/publishBlockMutation.js":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.config=t.mutation=void 0;var r=Object.assign||function(e){for(var t=1;t { .data .readOneElementalArea .Elements - .edges - .find((elementData) => elementData.node.ID === id); - return newElementData && newElementData.node.Version; + .find((elementData) => elementData.ID === id); + return newElementData && newElementData.Version; }); }; diff --git a/client/src/state/editor/readBlocksForAreaQuery.js b/client/src/state/editor/readBlocksForAreaQuery.js index 3b547f9b..e3319848 100644 --- a/client/src/state/editor/readBlocksForAreaQuery.js +++ b/client/src/state/editor/readBlocksForAreaQuery.js @@ -10,19 +10,12 @@ query ReadBlocksForArea($id:ID!) { Mode: DRAFT }){ Elements { - pageInfo { - totalCount - } - edges { - node { - ID - Title - BlockSchema - IsLiveVersion - IsPublished - Version - } - } + ID + Title + BlockSchema + IsLiveVersion + IsPublished + Version } } } @@ -48,7 +41,7 @@ const config = { let blocks = null; if (readOneElementalArea) { // Remove the GraphQL pagination keys - blocks = readOneElementalArea.Elements.edges.map((element) => element.node); + blocks = readOneElementalArea.Elements; } const errors = error && error.graphQLErrors && diff --git a/client/src/state/editor/sortBlockMutation.js b/client/src/state/editor/sortBlockMutation.js index 9b5addac..c2cb954c 100644 --- a/client/src/state/editor/sortBlockMutation.js +++ b/client/src/state/editor/sortBlockMutation.js @@ -37,12 +37,12 @@ const config = { // Query returns a deeply nested object. Explicit reconstruction via spreads is too verbose. // This is an alternative, relatively efficient way to deep clone const newData = JSON.parse(JSON.stringify(cachedData)); - let { edges } = newData.readOneElementalArea.Elements; + let blocks = newData.readOneElementalArea.Elements; // Find the block we reordered - const movedBlockIndex = edges.findIndex(edge => edge.node.ID === blockId); + const movedBlockIndex = blocks.findIndex(block => block.ID === blockId); // Keep it - const movedBlock = edges[movedBlockIndex]; + const movedBlock = blocks[movedBlockIndex]; // Update the moved block with the new details returned in the GraphQL response Object.entries(updatedElementData).forEach(([key, value]) => { // Skip the type name as this is always returned but should never change @@ -53,22 +53,22 @@ const config = { movedBlock[key] = value; }); // Remove the moved block - edges.splice(movedBlockIndex, 1); + blocks.splice(movedBlockIndex, 1); // If the target is 0, it's added to the start if (!afterBlockId) { - edges.unshift(movedBlock); + blocks.unshift(movedBlock); } else { // Else, find the block we inserted after - const targetBlockIndex = edges.findIndex(edge => edge.node.ID === afterBlockId); + const targetBlockIndex = blocks.findIndex(block => block.ID === afterBlockId); // Add it back after the target - const end = edges.slice(targetBlockIndex + 1); - edges = edges.slice(0, targetBlockIndex + 1); - edges.push(movedBlock); - edges = edges.concat(end); + const end = blocks.slice(targetBlockIndex + 1); + blocks = blocks.slice(0, targetBlockIndex + 1); + blocks.push(movedBlock); + blocks = blocks.concat(end); } // Add it back to the full result - newData.readOneElementalArea.Elements.edges = edges; + newData.readOneElementalArea.Elements = blocks; store.writeQuery({ query: readBlocksQuery, data: newData, variables }); }, }); diff --git a/docs/en/searching-blocks.md b/docs/en/searching-blocks.md new file mode 100644 index 00000000..07fb0c80 --- /dev/null +++ b/docs/en/searching-blocks.md @@ -0,0 +1,32 @@ +# Searching Blocks + +## Overview + +Content created through SilverStripe can be searched in various ways. +A popular option is the [silverstripe/fulltextsearch](https://github.com/silverstripe/silverstripe-fulltextsearch) +module, which integrates with search services such as Solr. + +## Usage + +When you install the fulltextsearch module, it will auto-discover +an index provided by elemental: +`DNADesign\Elemental\Search\ElementalSolrIndex`. + +It works based on a new `getElementsForSearch` method added to your `Page` class +through the `DNADesign\Elemental\Extensions\ElementalPageExtension`. +This method renders out the full content of all elements, +strips out the HTML, and indexes it as one field. + +## Configuration + +In many cases, you'll already have configured your own search index. +Since the `ElementalSolrIndex` is auto-discovered, it duplicates +search indexing effort even when it is unused. + +You can disable it via YAML config in favour of your own index definition: + +```yml +SilverStripe\FullTextSearch\Search\FullTextSearch: + indexes: + - MyCustomIndex +``` \ No newline at end of file diff --git a/src/Forms/EditFormFactory.php b/src/Forms/EditFormFactory.php index 26ab3a68..4dbc9ea3 100644 --- a/src/Forms/EditFormFactory.php +++ b/src/Forms/EditFormFactory.php @@ -3,6 +3,7 @@ namespace DNADesign\Elemental\Forms; use SilverStripe\Control\RequestHandler; +use SilverStripe\Core\Config\Configurable; use SilverStripe\Forms\DefaultFormFactory; use SilverStripe\Forms\FieldList; use SilverStripe\Forms\FormField; @@ -11,6 +12,16 @@ class EditFormFactory extends DefaultFormFactory { + use Configurable; + + /** + * This will be set the number of rows in HTML field + * + * @config + * @var integer + */ + private static $html_field_rows = 7; + /** * @var string */ @@ -39,7 +50,7 @@ protected function getFormFields(RequestHandler $controller = null, $name, $cont /** @var HTMLEditorField|null $editorField */ $editorField = $fields->fieldByName('Root.Main.HTML'); if ($editorField) { - $editorField->setRows(7); + $editorField->setRows($this->config()->get('html_field_rows')); } return $fields; diff --git a/src/Models/BaseElement.php b/src/Models/BaseElement.php index 3ba48f9c..05cbca3c 100644 --- a/src/Models/BaseElement.php +++ b/src/Models/BaseElement.php @@ -682,7 +682,9 @@ public function CMSEditLink($directLink = false) $page = $this->getPage(); if (!$page) { - return null; + $link = null; + $this->extend('updateCMSEditLink', $link); + return $link; } if (!$page instanceof SiteTree && method_exists($page, 'CMSEditLink')) {