[react-search-refiners] 2.3.0.0 - Added result types feature + bug fixes (#727)

* * Migrated to SPFx 1.7.0
* Fixed sort feature
* Added a sample TypeScript function to demonstrate NLP processing for the search query
* Miscelleanous improvements

* * Fixed wrong ids and dependencies

* * Updated README

* * Replaced JSOM taxonomy methods by the @pnp/sp-taxonomy counterparts + refactored refiners translation logic
* Updated the filter panel to close on click out
* Updgraded to @pnp 1.2.6
* Added a event listeners for hash change when the search box in bound to the 'URL fragment' SPFx builtin data source property so you can now build predefined filters with '#'.
* Fix suggestions panel position to be absolute

* * Quick fix on the search box

* * Added a default query option (related to https://github.com/SharePoint/sp-dev-fx-webparts/issues/556)

* * Added the ability to search by clicking on the search box icon

* * Replaced 'refiners' property by a property collection.

* * Replaced the 'sortList' WP property by a collection data control from PnP.

* * Replaced 'sortableFields' by a collection data pnp control.

* * Replaced the code editor control by the PnP one
* Set fix width on previews

* * Added result type interface

* * Added the result types feature
* Removed 'on-el-resize'. Too much trouble, not really needed. Now the preview width can be set manually.

* * Miscelaneous fixes

* * Upddated documentation + instructions

* * Upgraded to 1.13.0 for @pnp controls

* * [react-search-refiners] Version 2.3.0.0

* * Fixes and improvements as pointed out by @wobba.

* * Added missing file

* * Updated README with result types use
* Updated the code to load the property pane code field async and reduce mainbundle size

* * Updated README
This commit is contained in:
Franck Cornu 2018-12-18 02:32:34 -05:00 committed by Mikael Svenson
parent 7966809468
commit 0b63c8a8c4
33 changed files with 762 additions and 12211 deletions

View File

@ -62,6 +62,7 @@ Version|Date|Comments
2.1.2.0 | Nov 9, 2018 | <ul><li>Bug fix for IE11.</li><li>Added date query variables.</li><li>Added support for both result source id and query template.</li><li>Added `getUniqueCount` helper function.</li></ul>
2.2.0.0 | Nov 11, 2018 | <ul><li>Upgraded to SPFx 1.7.0</li><li>Added a TypeScript Azure Function to demonstrate NLP processing on search query</li><li>Removed extension data source. Now we use the default SPFx 'Page Environment' data source.</li></ul>
2.2.0.1 | Dec 3, 2018 | <ul><li>Remove switch for handlebar helpers, and instead load helpers if used in the template.</li></ul>
2.3.0.0 | Dec 13, 2018 | <ul><li>Upgraded to @pnp/controls 1.13.0</li><li>Added a result types features</li><li>Fix bug regarding dynamic data source connection</li></ul>
## Important notice on upgrading the solution from pre v2.2.0.0
**Due to code restucturing we have hit an edge case which impacts upgrades from previous versions. To solve the issue go to `https://<tenant>.sharepoint.com/sites/<appcatalog>/Lists/ComponentManifests` and remove the entries for SearchBox and Search Results, and then upload the .sppkg for the new release.**
@ -172,6 +173,7 @@ Show blank if no result | Shows nothing if there is no result
Show result count | Shows the result count and entered keywords
Show paging | Indicates whether or not the component should show the paging control at the bottom.
Result Layouts options | Choose the template to use to display search results. Some layouts are defined by default (List oand Tiles) but you can create your own either by clinkg on the **"Custom"** tile, or **"Edit template"** from an existing chosen template. In custom mode, you can set an external template. It has to be in the same SharePoint tenant. Behind the scenes, the Office UI Fabric core CSS components are used in a isolated way.
Result types | Allows you to set a custom template at item level according to a specific condition (ex: FileType equals 'pdf').
Handlebars Helpers | Load [handlebar helpers](https://github.com/helpers/handlebars-helpers) to use in your template. Disable this option will make Web Part loading faster if you don't need them.
---
@ -207,6 +209,38 @@ Setting | Description
Also the [Handlebars helpers](https://github.com/helpers/handlebars-helpers) (188 helpers) are also available. You can also define your own in the *BaseTemplateService.ts* file. See [helper-moment](https://github.com/helpers/helper-moment) for date samples using moment.
##### Use result types
The result types feature is a convenient way to split your templates according to results characteristics instead of making a huge central template with multiple conditions. They can be defined in 'inline' mode or using an external file. You can use the sorting option to determine to order of evaluation for each condition.
<p align="center">
<img width="5700px" src="./images/result_types.png"/>
</p>
The following operators are supported:
- Equals
- Contains
- StartsWith
- Greater Or Equal
- Less Or Equal
- Less than
- Greater than
- Is not null
To use it in your main template, just follow this pattern. This block is not mandatory.
```
{{#> resultTypes}}
{{!-- The block below will be used as default item template if no result types matched --}}
<div class="template_result">
<!-- Your default template markup -->
</div>
{{/resultTypes}}
```
Handlebars [partials](https://handlebarsjs.com/partials.html) are used behind the scenes and conditions are built dynamically using a recursive if/else structure.
#### Query variables
The following out of the box query variables are supported/tested:
@ -264,11 +298,10 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
- Build an user friendly search experience on the top of the SharePoint search REST API with paging, refiners and query suggestions using the *@pnp* JavaScript library.
- Use [Handlebars](https://handlebarsjs.com/) to create templates for search results according to your requirements like the good old display templates.
- Using the SPFx [dynamic data feature](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/dynamic-data) to connect Web Parts and/or Extensions.
- Using the SPFx [dynamic data feature](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/dynamic-data) to connect Web Parts.
- Using SharePoint taxonomy using JSOM in SPFx (filter translations)
- Integrate the [@pnp/spfx-property-controls](https://github.com/SharePoint/sp-dev-fx-property-controls) in your solution (*PlaceHolder* control).
- Integrate multiple Office UI Fabric components (DocumentCard, Panel, GroupedList, ...) to fit with the native Office 365 theme.
- Use the React container component approach inspiring by the [react-todo-basic sample](https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples/react-todo-basic).
- Use [on-el-resize](https://www.npmjs.com/package/on-el-resize) by [Andrew Koltyakov](https://github.com/koltyakov) to resize iframes dynamically
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-search-refiners" />

View File

@ -3002,12 +3002,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -3022,17 +3024,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -3149,7 +3154,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -3161,6 +3167,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -3175,6 +3182,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -3182,12 +3190,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -3206,6 +3216,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -3286,7 +3297,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -3298,6 +3310,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -3419,6 +3432,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",

View File

@ -4,7 +4,7 @@
"LUIS_SubscriptionKey": "0065cf294a8244409cf40f1400ed74ab",
"LUIS_AzureRegion": "eastus",
"LUIS_AppConfigFolderPath": ".\\config",
"LUIS_MappingsFile": ".\\config\\luismappings.dev.json",
"LUIS_MappingsFile": ".\\config\\luismappings.json",
"Bing_SpellCheckApiKey": "c5895173d71242b6b1c20cdb28362549",
"TextAnalytics_SubscriptionKey": "5332d75141cb4b2c90365299e5262318",
"TextAnalytics_AzureRegion": "canadacentral",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1,3 @@
{
"lockfileVersion": 1
}

View File

@ -27,7 +27,7 @@
"name": "Hosted workbench",
"type": "chrome",
"request": "launch",
"url": "https://collaborationcorner.sharepoint.com/teams/SharePointAADTokenProvider/_layouts/workbench.aspx",
"url": "https://collaborationcorner.sharepoint.com/teams/demosearch/SitePages/Search-page.aspx?Mode=Edit",
"webRoot": "${workspaceRoot}",
"sourceMaps": true,
"sourceMapPathOverrides": {

View File

@ -3,7 +3,7 @@
"solution": {
"name": "PnP - Search Web Parts",
"id": "890affef-33e0-4d72-bd72-36399e02143b",
"version": "2.2.0.1",
"version": "2.3.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": false,
"isDomainIsolated": false

View File

@ -1,6 +1,6 @@
{
"name": "pnp-react-search-refiners",
"version": "2.2.0",
"version": "2.3.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -655,9 +655,9 @@
}
},
"@microsoft/load-themed-styles": {
"version": "1.8.39",
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.8.39.tgz",
"integrity": "sha512-p1NEyjP+QEStszRKMkxJf/aedjyQ2FK0deCvvLRHOmG1B+pTtby78l8y4ka98a3jNkSm6mEhAmWrOrmMbXqDMQ=="
"version": "1.8.40",
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.8.40.tgz",
"integrity": "sha512-wuBDF+ewLi9RcpT6FOxdW2LpKKuKaZFqoGaVeE9N7N05Dye63O5oRQcut+UsczhMDEl3e96oAunfN5VpkyZxtw=="
},
"@microsoft/loader-cased-file": {
"version": "1.7.0",
@ -1622,9 +1622,9 @@
"dev": true
},
"@pnp/common": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@pnp/common/-/common-1.2.6.tgz",
"integrity": "sha512-Gp1ddyaXDQa1aWqkbXFv4zMZ4l8CYz5/dZnMy7fyUtT1hD2FPzofm1dqLpVbxfKOi3C3pKzCJCWUy9BuvUBmdQ==",
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@pnp/common/-/common-1.2.7.tgz",
"integrity": "sha512-4tja4zjAHhNZwhkcAhG8MPInFHIlspb7jz/Sk/wCbNCrobNSBGt9hoSvMBAOxYIhUinflCOWmGhk3Xw6vk8EXg==",
"requires": {
"adal-angular": "1.0.17",
"tslib": "1.9.3"
@ -1643,9 +1643,9 @@
}
},
"@pnp/logging": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@pnp/logging/-/logging-1.2.6.tgz",
"integrity": "sha512-HoUQhZtBbt/lhZWiCPZvnP7SihDPs+vz6hEhgG6U6+xdhGFdGsEtH/ZJxz70jgUcm//7wvo/gdpfjbrbDcwr5Q==",
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@pnp/logging/-/logging-1.2.7.tgz",
"integrity": "sha512-3OlO5o7dfP7q0bAfeZ6sHn3BBYfPM3rpw0w+aZGju84MjM7YcdLS2U5JlXMjb0eKwyA0kZOZeJNLt0l0vuGKrA==",
"requires": {
"tslib": "1.9.3"
},
@ -1658,9 +1658,9 @@
}
},
"@pnp/odata": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@pnp/odata/-/odata-1.2.6.tgz",
"integrity": "sha512-Oezy073IBpO3xatYHJ8dvRqXJ+mEcOQcb9vB3458gXehe2s5MHN6FYHdF71AUjU0F+wU4y8cTmepYjUGffKmKA==",
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@pnp/odata/-/odata-1.2.7.tgz",
"integrity": "sha512-7hgSZcC0mZd+6YaFWxT6l3QPu3HS2DknIECx/64YHEqF+0qXX6Tcg9Rtn7lsVt6kG3VXSnGnN0A5pCG6VaT5ag==",
"requires": {
"tslib": "1.9.3"
},
@ -1691,9 +1691,9 @@
}
},
"@pnp/sp": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@pnp/sp/-/sp-1.2.6.tgz",
"integrity": "sha512-A5Qxkn6tohQRLZM0z6AgUGuNS4ppxVayvlGWB4E4UcAdTp+IMYUftskD77Iu4bogLmiNc0Iqjdsxp27vG3JxVg==",
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@pnp/sp/-/sp-1.2.7.tgz",
"integrity": "sha512-shhqPNYBRV7jEO1TLtRzGk/Czd43WFJTqH19Gll2dt9MmTUzLj7Pyc9ioEsSXp6GdjtQ0Qz9e2hVX9UZboLxJQ==",
"requires": {
"tslib": "1.9.3"
},
@ -1706,9 +1706,9 @@
}
},
"@pnp/sp-clientsvc": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@pnp/sp-clientsvc/-/sp-clientsvc-1.2.6.tgz",
"integrity": "sha512-bb7vjUrL1zH0GoZfBwu3DBaIOvtJ7cbHcyunxG8m0yO4oVsLr2V40ByzWQCqvoFwAyApxkyzUzoK3gCZ1rXPYg==",
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@pnp/sp-clientsvc/-/sp-clientsvc-1.2.7.tgz",
"integrity": "sha512-YiwCXHXojUPa+rP0+stoAGnFSPhJGU4WzH3k0K5nQsKtf/PONW/8v4hU9TsuOm5relk8VNquXlUPq33xiZmufQ==",
"requires": {
"tslib": "1.9.3"
},
@ -1721,9 +1721,9 @@
}
},
"@pnp/sp-taxonomy": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@pnp/sp-taxonomy/-/sp-taxonomy-1.2.6.tgz",
"integrity": "sha512-0chCzocUgsDcZb+fKmzxjXYygxbBwGyDHF5MAmatixvrFPBnMOTkjR+ihVveXFSaBzr9QTVIL9poBohJfElPjA==",
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@pnp/sp-taxonomy/-/sp-taxonomy-1.2.7.tgz",
"integrity": "sha512-74wDz2toFwK3mGg4W4XYbmV9BvOaDrfDpAsCP9DaiGU7AKtsIrlTcsO4WJpN8IFBIJPnCGSFsTRmNTefg+4iHw==",
"requires": {
"tslib": "1.9.3"
},
@ -1736,39 +1736,68 @@
}
},
"@pnp/spfx-controls-react": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@pnp/spfx-controls-react/-/spfx-controls-react-1.10.0.tgz",
"integrity": "sha512-5HWuKRUoQwoQSjRroY7YGj3uNoOvRrB8HFP3seDoD+snVTE3lfU3/8Eye+bLxz/3HptZGDSX+0LjSLRpmLbNdw==",
"version": "1.11.0-beta.ca4bd7e",
"resolved": "https://registry.npmjs.org/@pnp/spfx-controls-react/-/spfx-controls-react-1.11.0-beta.ca4bd7e.tgz",
"integrity": "sha512-HLDJ0TBot28rsUvYSwzO4Hy4KARPypPIBMg4RI/ZG5fGzrBO9yb0aYiLWuLiylPHQnzsiOaD3B3n+/GjZ6pvEw==",
"requires": {
"@pnp/common": "^1.0.1",
"@pnp/logging": "^1.0.1",
"@pnp/odata": "^1.0.1",
"@pnp/sp": "^1.0.1",
"@pnp/common": "1.0.1",
"@pnp/logging": "1.0.1",
"@pnp/odata": "1.0.1",
"@pnp/sp": "1.0.1",
"@pnp/telemetry-js": "1.0.0",
"lodash": "^4.17.4",
"office-ui-fabric-react": "~5.120.0"
"@types/chart.js": "2.7.40",
"chart.js": "2.7.3",
"lodash": "4.17.4",
"office-ui-fabric-react": "5.131.0"
},
"dependencies": {
"@pnp/common": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@pnp/common/-/common-1.0.1.tgz",
"integrity": "sha1-T+cuONHexjlQSvxxQclSEh5YqOk=",
"requires": {
"tslib": "1.8.1"
}
},
"@pnp/logging": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@pnp/logging/-/logging-1.0.1.tgz",
"integrity": "sha1-Nl1/dmiW943xIMgd9D3dlrCgojY=",
"requires": {
"tslib": "1.8.1"
}
},
"@pnp/odata": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@pnp/odata/-/odata-1.0.1.tgz",
"integrity": "sha1-yE5s/MV2VdZj2IEFlgGT8yiOwAI=",
"requires": {
"tslib": "1.8.1"
}
},
"@pnp/sp": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@pnp/sp/-/sp-1.0.1.tgz",
"integrity": "sha1-5XXJVqZWk9KRkI4yEdzWbc5KFWM=",
"requires": {
"tslib": "1.8.1"
}
},
"lodash": {
"version": "4.17.4",
"resolved": "http://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
}
}
},
"@pnp/spfx-property-controls": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/@pnp/spfx-property-controls/-/spfx-property-controls-1.12.0.tgz",
"integrity": "sha512-p/I50gZ1Iamy9xFLN9z4pNYicRFpNU8bPDDTBJDE7Ebauzh78/wjtTs5DTGi9/mBdwiEPhS2IreFl5+guahY6g==",
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/@pnp/spfx-property-controls/-/spfx-property-controls-1.13.1.tgz",
"integrity": "sha512-CVlKtAGrwig+cNUr9EQ3dRkwyorqlpuDKOAf7XsnMRaHEcMsa2qenlK9UzpOI5w9bsI14mF69nlztKqpT1NRxA==",
"requires": {
"@pnp/telemetry-js": "1.0.0",
"office-ui-fabric-react": "5.131.0",
"react-ace": "5.8.0"
},
"dependencies": {
"react-ace": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/react-ace/-/react-ace-5.8.0.tgz",
"integrity": "sha1-hy2e6LZkMA7Vq57axiNLvpCDaDY=",
"requires": {
"brace": "^0.11.0",
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.1.1",
"prop-types": "^15.5.8"
}
}
}
},
"@pnp/telemetry-js": {
@ -1894,6 +1923,11 @@
"integrity": "sha1-ox10JBprHtu5c8822XooloNKUfk=",
"dev": true
},
"@types/chart.js": {
"version": "2.7.40",
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.7.40.tgz",
"integrity": "sha512-yC8Ff5vsHFTClGCWXoAmNCh33cNYfP2/yFANBLjLiso4jTKsLfQ0KQuBEuKxOWTRoOSLyT6v+ZYcvz0uonvvsA=="
},
"@types/core-js": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@types/core-js/-/core-js-2.5.0.tgz",
@ -2245,9 +2279,9 @@
}
},
"@uifabric/styling": {
"version": "5.36.1",
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-5.36.1.tgz",
"integrity": "sha512-VYjspshM5V71+RD4NtGvycdomTx5Nm5Ci8J4gv4TQK+Y5RZCBazZamSPY8+iO6+ueO6lcXoTkV9X/vnZahVsow==",
"version": "5.37.0",
"resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-5.37.0.tgz",
"integrity": "sha512-3hC0itW/hWSD5J4uANzUKk8XVGWUNkU+VLjEjWsQ6i5lvwFGaanR6Qy0bTkZdFGqFWMXe91CkBHV7HnvEx7tCA==",
"requires": {
"@microsoft/load-themed-styles": "^1.7.13",
"@uifabric/merge-styles": ">=5.17.1 <6.0.0",
@ -4046,15 +4080,15 @@
}
},
"caniuse-db": {
"version": "1.0.30000910",
"resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000910.tgz",
"integrity": "sha512-eysv5eAsXCBnfnhTZsKBtCZKdgeFaRqOlTN74kCfzdHdz0In3E5Aop7PyqPI757DsdjVwJOWrFHIrTPYzmll6g==",
"version": "1.0.30000912",
"resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000912.tgz",
"integrity": "sha512-uiepPdHcJ06Na9t15L5l+pp3NWQU4IETbmleghD6tqCqbIYqhHSu7nVfbK2gqPjfy+9jl/wHF1UQlyTszh9tJQ==",
"dev": true
},
"caniuse-lite": {
"version": "1.0.30000910",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000910.tgz",
"integrity": "sha512-u/nxtHGAzCGZzIxt3dA/tpSPOcirBZFWKwz1EPz4aaupnBI2XR0Rbr74g0zc6Hzy41OEM4uMoZ38k56TpYAWjQ==",
"version": "1.0.30000912",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000912.tgz",
"integrity": "sha512-M3zAtV36U+xw5mMROlTXpAHClmPAor6GPKAMD5Yi7glCB5sbMPFtnQ3rGpk4XqPdUrrTIaVYSJZxREZWNy8QJg==",
"dev": true
},
"capture-exit": {
@ -4113,6 +4147,39 @@
"supports-color": "^2.0.0"
}
},
"chart.js": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.3.tgz",
"integrity": "sha512-3+7k/DbR92m6BsMUYP6M0dMsMVZpMnwkUyNSAbqolHKsbIzH2Q4LWVEHHYq7v0fmEV8whXE0DrjANulw9j2K5g==",
"requires": {
"chartjs-color": "^2.1.0",
"moment": "^2.10.2"
}
},
"chartjs-color": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.2.0.tgz",
"integrity": "sha1-hKL7dVeH7YXDndbdjHsdiEKbrq4=",
"requires": {
"chartjs-color-string": "^0.5.0",
"color-convert": "^0.5.3"
},
"dependencies": {
"color-convert": {
"version": "0.5.3",
"resolved": "http://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
"integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0="
}
}
},
"chartjs-color-string": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz",
"integrity": "sha512-amWNvCOXlOUYxZVDSa0YOab5K/lmEhbFNKI55PWc4mlv28BDzA7zaoQTGxSBgJMHIW+hGX8YUrvw/FH4LyhwSQ==",
"requires": {
"color-name": "^1.0.0"
}
},
"check-types": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/check-types/-/check-types-7.4.0.tgz",
@ -4372,8 +4439,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"color-string": {
"version": "0.3.0",
@ -5341,11 +5407,6 @@
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
"dev": true
},
"diff-match-patch": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.4.tgz",
"integrity": "sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg=="
},
"diffie-hellman": {
"version": "5.0.3",
"resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
@ -5494,9 +5555,9 @@
"dev": true
},
"electron-to-chromium": {
"version": "1.3.84",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.84.tgz",
"integrity": "sha512-IYhbzJYOopiTaNWMBp7RjbecUBsbnbDneOP86f3qvS0G0xfzwNSvMJpTrvi5/Y1gU7tg2NAgeg8a8rCYvW9Whw==",
"version": "1.3.85",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.85.tgz",
"integrity": "sha512-kWSDVVF9t3mft2OHVZy4K85X2beP6c6mFm3teFS/mLSDJpQwuFIWHrULCX+w6H1E55ZYmFRlT+ATAFRwhrYzsw==",
"dev": true
},
"elliptic": {
@ -6780,12 +6841,6 @@
"integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=",
"dev": true
},
"flatmap-stream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/flatmap-stream/-/flatmap-stream-0.1.2.tgz",
"integrity": "sha512-ucyr6WkLXjyMuHPtOUq4l+nSAxgWi7v4QO508eQ9resnGj+lSup26oIsUI5aH8k4Qfpjsxa8dDf9UCKkS2KHzQ==",
"dev": true
},
"flatten": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz",
@ -7948,13 +8003,12 @@
"dev": true
},
"event-stream": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz",
"integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==",
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.5.tgz",
"integrity": "sha512-vyibDcu5JL20Me1fP734QBH/kenBGLZap2n0+XXM7mvuUPzJ20Ydqj1aKcIeMdri1p+PU+4yAKugjN8KCVst+g==",
"dev": true,
"requires": {
"duplexer": "^0.1.1",
"flatmap-stream": "^0.1.0",
"from": "^0.1.7",
"map-stream": "0.0.7",
"pause-stream": "^0.0.11",
@ -12142,12 +12196,23 @@
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
},
"dependencies": {
"string_decoder": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz",
"integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
@ -12573,24 +12638,19 @@
"integrity": "sha512-gcBs5HHr7tjkvk/+Ls10ttb3jEllRn7SvJitX/kx/gQq8BiFMSMKr1w+oNqXlh4EgkBHWUlJVPrYUu1KW/jVaQ=="
},
"office-ui-fabric-react": {
"version": "5.120.0",
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-5.120.0.tgz",
"integrity": "sha512-WUyEExfSROSn5XIQudmVpo8cXV2h7RVvGDZTFIbWAEwh2gyxQjHegzoGDLeoTWy0ulD6+RM3vonrQNE5c629AQ==",
"version": "5.131.0",
"resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-5.131.0.tgz",
"integrity": "sha512-QOYu1uf92qhTTIlBAj8teKvRpCmpliRZjynYtgeeUbDm4C4GtXdb/O1rPNFsfT0PNtPC8dCNeQ7/CXjQenUkyw==",
"requires": {
"@microsoft/load-themed-styles": "^1.7.13",
"@uifabric/icons": ">=5.8.0 <6.0.0",
"@uifabric/merge-styles": ">=5.17.1 <6.0.0",
"@uifabric/styling": ">=5.32.0 <6.0.0",
"@uifabric/utilities": ">=5.34.1 <6.0.0",
"@uifabric/styling": ">=5.36.0 <6.0.0",
"@uifabric/utilities": ">=5.34.2 <6.0.0",
"prop-types": "^15.5.10",
"tslib": "^1.7.1"
}
},
"on-el-resize": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/on-el-resize/-/on-el-resize-0.0.4.tgz",
"integrity": "sha512-tuSR1HTkNtcr19G9utM1bHFSMPAn+1VfYalVPXrN03jgNXFAUfr0A+4Q0irReQVdlDAGccvCmRk3BWQUyfdKMg=="
},
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@ -14943,12 +15003,11 @@
}
},
"react-ace": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/react-ace/-/react-ace-6.1.4.tgz",
"integrity": "sha512-a8/lAsy2bfi7Ho+3Kaj8hBPR+PEiCTG9xFG9LIjCJrv5WQFYFpeFTiPWA96M3t+LgIDFFltwfVTwD2pmdAVOxQ==",
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/react-ace/-/react-ace-5.8.0.tgz",
"integrity": "sha1-hy2e6LZkMA7Vq57axiNLvpCDaDY=",
"requires": {
"brace": "^0.11.0",
"diff-match-patch": "^1.0.0",
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.1.1",
"prop-types": "^15.5.8"
@ -15725,6 +15784,24 @@
"tough-cookie": "~2.4.3",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
},
"dependencies": {
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"dev": true
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"dev": true,
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
}
}
}
},
"request-progress": {
@ -17364,9 +17441,9 @@
"dev": true
},
"tapable": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz",
"integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=",
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz",
"integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==",
"dev": true
},
"tar": {
@ -17767,21 +17844,13 @@
}
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"dev": true,
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
},
"dependencies": {
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"dev": true
}
"psl": "^1.1.28",
"punycode": "^2.1.1"
}
},
"tr46": {
@ -19164,9 +19233,9 @@
"dev": true
},
"yallist": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz",
"integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
"integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
"dev": true
},
"yargs": {

View File

@ -1,6 +1,6 @@
{
"name": "pnp-react-search-refiners",
"version": "2.2.0",
"version": "2.3.0",
"private": true,
"engines": {
"node": ">=0.10.0"
@ -19,15 +19,15 @@
"@microsoft/sp-lodash-subset": "1.7.0",
"@microsoft/sp-office-ui-fabric-core": "1.7.0",
"@microsoft/sp-webpart-base": "1.7.0",
"@pnp/common": "1.2.6",
"@pnp/logging": "1.2.6",
"@pnp/odata": "1.2.6",
"@pnp/common": "1.2.7",
"@pnp/logging": "1.2.7",
"@pnp/odata": "1.2.7",
"@pnp/polyfill-ie11": "1.0.0",
"@pnp/sp": "1.2.6",
"@pnp/sp-taxonomy": "1.2.6",
"@pnp/sp-clientsvc": "1.2.6",
"@pnp/spfx-controls-react": "1.10.0",
"@pnp/spfx-property-controls": "1.12.0",
"@pnp/sp": "1.2.7",
"@pnp/sp-clientsvc": "1.2.7",
"@pnp/sp-taxonomy": "1.2.7",
"@pnp/spfx-controls-react": "1.11.0-beta.ca4bd7e",
"@pnp/spfx-property-controls": "1.13.1",
"@types/es6-promise": "0.0.33",
"@types/fabric": "^1.5.43",
"@types/handlebars": "^4.0.39",
@ -41,10 +41,9 @@
"handlebars-helpers": "^0.8.4",
"immutability-helper": "2.4.0",
"lodash": "4.17.11",
"office-ui-fabric-react": "5.120.0",
"on-el-resize": "0.0.4",
"office-ui-fabric-react": "5.131.0",
"react": "16.3.2",
"react-ace": "6.1.4",
"react-ace": "5.8.0",
"react-custom-scrollbars": "4.2.1",
"react-dom": "16.3.2",
"react-js-pagination": "3.0.0",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
export interface ISearchResultType {
property: string;
operator: ResultTypeOperator;
value: string;
inlineTemplateContent: string;
externalTemplateUrl: string;
}
export enum ResultTypeOperator {
Equal = 'eq',
LessThan = 'lt',
GreaterThan = 'gt',
LessOrEqual = 'lte',
GreaterOrEqual = 'gte',
Contains = 'contains',
StartsWith = 'startsWith',
NotNull = 'if',
}

View File

@ -1,5 +1,5 @@
.previewContainer {
&.videoPreview {
display: flex;
flex-direction: column;

View File

@ -10,7 +10,7 @@ import { Text } from '@microsoft/sp-core-library';
import { Logger } from '@pnp/logging';
import templateStyles from './BaseTemplateService.module.scss';
import { DomHelper } from '../../helpers/DomHelper';
declare var System: any;
import { ISearchResultType, ResultTypeOperator } from '../../models/ISearchResultType';
abstract class BaseTemplateService {
private _helper = null;
@ -78,6 +78,11 @@ abstract class BaseTemplateService {
display: flex;
display: -ms-flexbox;
}
/* Width for the documents and videos preview */
.videoPreview, .iframePreview {
width: 400px;
}
.template_icon {
height: 32px;
@ -133,39 +138,41 @@ abstract class BaseTemplateService {
<ul class="ms-List template_defaultList">
{{#each items as |item|}}
<li class="template_listItem" tabindex="0">
<div class="template_result">
<img class="template_icon" src="{{iconSrc}}"/>
<div class="template_contentContainer">
<span class=""><a href="{{getUrl item}}">{{Title}}</a></span>
<span class="">{{getSummary HitHighlightedSummary}}</span>
<span class=""><span>{{getDate Created "LL"}}</span></span>
</div>
</div>
<div class="template_previewContainer ms-hiddenSm">
{{#eq item.contentclass compare='STS_ListItem_851'}}
<div class="video-container">
<div class="img-container">
<img id="preview_{{@index}}" class="img-preview video-preview-item" src="{{PictureThumbnailURL}}" data-url="{{DefaultEncodingURL}}" data-fileext="{{FileType}}"/>
<div class="hover">
<div class="${templateStyles.hoverIcon}"><i class="ms-Icon ms-Icon--ImageSearch" aria-hidden="true"></i></div>
</div>
{{#> resultTypes}}
{{!-- The block below will be used as default item template if no result types matched --}}
<div class="template_result">
<img class="template_icon" src="{{iconSrc}}"/>
<div class="template_contentContainer">
<span class=""><a href="{{getUrl item}}">{{Title}}</a></span>
<span class="">{{getSummary HitHighlightedSummary}}</span>
<span class=""><span>{{getDate Created "LL"}}</span></span>
</div>
</div>
{{/eq}}
{{#eq item.contentclass compare='STS_ListItem_DocumentLibrary'}}
{{#if ServerRedirectedPreviewURL}}
<div class="doc-container">
</div>
<div class="template_previewContainer ms-hiddenSm">
{{#eq item.contentclass 'STS_ListItem_851'}}
<div class="video-container">
<div class="img-container">
<img id="preview_{{@index}}" class="img-preview document-preview-item" src="{{ServerRedirectedPreviewURL}}" data-url="{{ServerRedirectedEmbedURL}}"/>
<img id="preview_{{@index}}" class="img-preview video-preview-item" src="{{PictureThumbnailURL}}" data-url="{{DefaultEncodingURL}}" data-fileext="{{FileType}}"/>
<div class="hover">
<div class="${templateStyles.hoverIcon}"><i class="ms-Icon ms-Icon--ImageSearch" aria-hidden="true"></i></div>
</div>
</div>
</div>
{{/if}}
{{/eq}}
</div>
{{/eq}}
{{#eq item.contentclass 'STS_ListItem_DocumentLibrary'}}
{{#if ServerRedirectedPreviewURL}}
<div class="doc-container">
<div class="img-container">
<img id="preview_{{@index}}" class="img-preview document-preview-item" src="{{ServerRedirectedPreviewURL}}" data-url="{{ServerRedirectedEmbedURL}}"/>
<div class="hover">
<div class="${templateStyles.hoverIcon}"><i class="ms-Icon ms-Icon--ImageSearch" aria-hidden="true"></i></div>
</div>
</div>
</div>
{{/if}}
{{/eq}}
</div>
{{/resultTypes}}
</li>
{{/each}}
</ul>
@ -190,19 +197,22 @@ abstract class BaseTemplateService {
<div class="ms-Grid-row">
{{#each items as |item|}}
<div class="ms-Grid-col ms-sm12 ms-md6 ms-lg4">
<div class="singleCard">
<div class="previewImg" style="background-image: url('{{getPreviewSrc item}}')">
<img class="cardFileIcon" src="{{iconSrc}}"/>
</div>
<li class="ms-ListItem ms-ListItem--document" tabindex="0">
<div class="cardInfo">
<span class="ms-ListItem-primaryText"><a href="{{getUrl item}}">{{Title}}</a></span>
<span class="ms-ListItem-secondaryText">{{getSummary HitHighlightedSummary}}</span>
<span class="ms-ListItem-tertiaryText">{{getDate Created "LL"}}</span>
<div class="ms-ListItem-selectionTarget"></div>
{{#> resultTypes}}
{{!-- The block below will be used as default item template if no result types matched --}}
<div class="singleCard">
<div class="previewImg" style="background-image: url('{{getPreviewSrc item}}')">
<img class="cardFileIcon" src="{{iconSrc}}"/>
</div>
</li>
</div>
<li class="ms-ListItem ms-ListItem--document" tabindex="0">
<div class="cardInfo">
<span class="ms-ListItem-primaryText"><a href="{{getUrl item}}">{{Title}}</a></span>
<span class="ms-ListItem-secondaryText">{{getSummary HitHighlightedSummary}}</span>
<span class="ms-ListItem-tertiaryText">{{getDate Created "LL"}}</span>
<div class="ms-ListItem-selectionTarget"></div>
</div>
</li>
</div>
{{/resultTypes}}
</div>
{{/each}}
</div>
@ -217,7 +227,7 @@ abstract class BaseTemplateService {
* @returns the template HTML markup
*/
public static getBlankDefaultTemplate(): string {
return `
return html`
<style>
/* Insert your CSS here */
</style>
@ -225,14 +235,73 @@ abstract class BaseTemplateService {
<div class="template_root">
<ul class="ms-List">
{{#each items as |item|}}
<li class="ms-ListItem ms-ListItem--image" tabindex="0">
<span class="ms-ListItem-primaryText"><a href="{{getUrl item}}">{{Title}}</a></span>
</li>
{{#> resultTypes}}
{{!-- The block below will be used as default item template if no result types matched --}}
<li class="ms-ListItem ms-ListItem--image" tabindex="0">
<span class="ms-ListItem-primaryText"><a href="{{getUrl item}}">{{Title}}</a></span>
</li>
{{/resultTypes}}
{{/each}}
</ul>
</div>
`;
}
/**
* Gets the default Handlebars result type list item
* @returns the template HTML markup
*/
public static getDefaultResultTypeListItem(): string {
return html`
<style>
/* Insert your CSS here */
</style>
<div class="template_result">
<img class="template_icon" src="{{iconSrc}}"/>
<div class="template_contentContainer">
<span class=""><a href="{{getUrl item}}">{{Title}}</a></span>
<span class="">{{getSummary HitHighlightedSummary}}</span>
</div>
</div>
`;
}
/**
* Gets the default Handlebars result type tile item
* @returns the template HTML markup
*/
public static getDefaultResultTypeTileItem(): string {
return html`
<style>
/* Insert your CSS here */
</style>
<div class="singleCard">
<div class="previewImg" style="background-image: url('{{getPreviewSrc item}}')">
<img class="cardFileIcon" src="{{iconSrc}}"/>
</div>
<li class="ms-ListItem ms-ListItem--document" tabindex="0">
<div class="cardInfo">
<span class="ms-ListItem-primaryText"><a href="{{getUrl item}}">{{Title}}</a></span>
<span class="ms-ListItem-secondaryText">{{getSummary HitHighlightedSummary}}</span>
</div>
</li>
</div>
`;
}
/**
* Gets the default Handlebars result type custom item
* @returns the template HTML markup
*/
public static getDefaultResultTypeCustomItem(): string {
return html`
<li class="ms-ListItem ms-ListItem--image" tabindex="0">
<span class="ms-ListItem-primaryText"><a href="{{getUrl item}}">{{Title}}</a></span>
</li>
`;
}
/**
* Registers useful helpers for search results templates
@ -487,13 +556,79 @@ abstract class BaseTemplateService {
let template = Handlebars.compile(templateContent);
let result = template(templateContext);
if (result.indexOf("-preview-item") != -1) {
if (result.indexOf("-preview-item") !== -1) {
await this._loadVideoLibrary();
}
return result;
}
/**
* Builds and registers the result types as Handlebars partials
* Based on https://github.com/helpers/handlebars-helpers/ operators
* @param resultTypes the configured result types from the property pane
*/
public async registerResultTypes(resultTypes: ISearchResultType[]) : Promise<void> {
if (resultTypes.length > 0) {
let content = await this._buildCondition(resultTypes, resultTypes[0], 0);
let template = Handlebars.compile(content);
Handlebars.registerPartial('resultTypes', template);
} else {
Handlebars.registerPartial('resultTypes', '{{> @partial-block }}');
}
}
/**
* Builds the Handlebars nested conditions recursively to reflect the result types configuration
* @param resultTypes the configured result types from the property pane
* @param currentResultType the current processed result type
* @param currentIdx current index
*/
private async _buildCondition(resultTypes: ISearchResultType[], currentResultType: ISearchResultType, currentIdx: number): Promise<string> {
let conditionBlockContent;
let templateContent = currentResultType.inlineTemplateContent;
if (currentResultType.externalTemplateUrl) {
templateContent = await this.getFileContent(currentResultType.externalTemplateUrl);
}
let handlebarsToken = currentResultType.value.match(/^\{\{(.*)\}\}$/);
let operator = currentResultType.operator;
let param1 = currentResultType.property;
// Use a token or a string value
let param2 = handlebarsToken ? handlebarsToken[1] : `"${currentResultType.value}"`;
// Operator: "Starts With"
if (currentResultType.operator === ResultTypeOperator.StartsWith) {
param1 = `"${currentResultType.value}"`;
param2 = `${currentResultType.property}`;
}
// Operator: "Not null"
if (currentResultType.operator === ResultTypeOperator.NotNull) {
param2 = null;
}
const baseCondition = `{{#${operator} ${param1} ${param2 || ""}}}
${templateContent}`;
if (currentIdx === resultTypes.length - 1) {
// Renders inner content set in the 'resultTypes' partial
conditionBlockContent = "{{> @partial-block }}";
} else {
conditionBlockContent = await this._buildCondition(resultTypes, resultTypes[currentIdx+1], currentIdx+1);
}
return `${baseCondition}
{{else}}
${conditionBlockContent}
{{/${operator}}}`;
}
/**
* Verifies if the template fiel path is correct
* @param filePath the file path string
@ -628,7 +763,7 @@ abstract class BaseTemplateService {
const closeBtnId = `${playerId}_closeBtn`;
const innerPreviewHtml = `
<video id="${playerId}" class="video-js vjs-big-play-centered">
<video id="${playerId}" class="videoPreview video-js vjs-big-play-centered">
<source src="${url}" type="video/${fileExtension}">
</video>
`;
@ -644,13 +779,12 @@ abstract class BaseTemplateService {
controls: true,
autoplay: false,
preload: "metadata",
fluid: true,
fluid: false,
poster: thumbnailSrc ? thumbnailSrc : null
});
document.getElementById(closeBtnId).addEventListener("click", ((ev) => {
document.getElementById(closeBtnId).addEventListener("click", (() => {
thumbnailElt.parentElement.style.display = '';
if (!videoPlayer.paused()) {
videoPlayer.pause();
}

View File

@ -1,6 +0,0 @@
import { IPropertyPaneCustomFieldProps } from '@microsoft/sp-webpart-base';
import { IPropertyPaneTextDialogProps } from './IPropertyPaneTextDialogProps';
export interface IPropertyPaneTextDialogInternalProps extends IPropertyPaneTextDialogProps, IPropertyPaneCustomFieldProps {
}

View File

@ -1,8 +0,0 @@
import { ITextDialogStrings } from './components/TextDialog/ITextDialogStrings';
export interface IPropertyPaneTextDialogProps {
dialogTextFieldValue?: string;
onPropertyChange: (propertyPath: string, text: string) => void;
disabled?: boolean;
strings: ITextDialogStrings;
}

View File

@ -1,73 +0,0 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import { IPropertyPaneField, PropertyPaneFieldType } from '@microsoft/sp-webpart-base';
import { IPropertyPaneTextDialogProps } from './IPropertyPaneTextDialogProps';
import { IPropertyPaneTextDialogInternalProps } from './IPropertyPaneTextDialogInternalProps';
import { ITextDialogProps } from './components/TextDialog/ITextDialogProps';
import { TextDialog } from './components/TextDialog/TextDialog';
export class PropertyPaneTextDialog implements IPropertyPaneField<IPropertyPaneTextDialogProps> {
public type: PropertyPaneFieldType = PropertyPaneFieldType.Custom;
public targetProperty: string;
public properties: IPropertyPaneTextDialogInternalProps;
private elem: HTMLElement;
/*****************************************************************************************
* Property pane's contructor
* @param targetProperty
* @param properties
*****************************************************************************************/
constructor(targetProperty: string, properties: IPropertyPaneTextDialogProps) {
this.targetProperty = targetProperty;
this.properties = {
dialogTextFieldValue: properties.dialogTextFieldValue,
onPropertyChange: properties.onPropertyChange,
disabled: properties.disabled,
strings: properties.strings,
onRender: this.onRender.bind(this),
key: targetProperty
};
}
/*****************************************************************************************
* Renders the QueryFilterPanel property pane
*****************************************************************************************/
public render(): void {
if (!this.elem) {
return;
}
this.onRender(this.elem);
}
/*****************************************************************************************
* Renders the QueryFilterPanel property pane
*****************************************************************************************/
private async onRender(elem: HTMLElement): Promise<void> {
if (!this.elem) {
this.elem = elem;
}
const textDialog: React.ReactElement<ITextDialogProps> = React.createElement(TextDialog, {
dialogTextFieldValue: this.properties.dialogTextFieldValue,
onChanged: this.onChanged.bind(this),
disabled: this.properties.disabled,
strings: this.properties.strings,
// required to allow the component to be re-rendered by calling this.render() externally
stateKey: new Date().toString()
});
ReactDom.render(textDialog, elem);
}
/*****************************************************************************************
* Call the property pane's onPropertyChange when the TextDialog changes
*****************************************************************************************/
private onChanged(text: string): void {
this.properties.onPropertyChange(this.targetProperty, text);
}
}

View File

@ -1,4 +1,5 @@
import { ITextDialogStrings } from "./ITextDialogStrings";
import { PropertyFieldCodeEditorLanguages } from "@pnp/spfx-property-controls/lib/PropertyFieldCodeEditor";
export interface ITextDialogProps {
dialogTextFieldValue?: string;
@ -6,4 +7,5 @@ export interface ITextDialogProps {
disabled?: boolean;
strings: ITextDialogStrings;
stateKey?: string;
language?: PropertyFieldCodeEditorLanguages;
}

View File

@ -1,7 +1,6 @@
import * as React from 'react';
import { Dialog, DialogType, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
import { Button, ButtonType } from 'office-ui-fabric-react/lib/Button';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { ITextDialogProps } from './ITextDialogProps';
import { ITextDialogState } from './ITextDialogState';
import AceEditor from 'react-ace';
@ -12,8 +11,9 @@ import 'brace';
import 'brace/mode/html';
import 'brace/theme/monokai';
import 'brace/ext/language_tools';
import { Link } from 'office-ui-fabric-react';
export class TextDialog extends React.Component<ITextDialogProps, ITextDialogState> {
export default class TextDialog extends React.Component<ITextDialogProps, ITextDialogState> {
/*************************************************************************************
* Component's constructor
@ -77,14 +77,12 @@ export class TextDialog extends React.Component<ITextDialogProps, ITextDialogSta
*************************************************************************************/
public render() {
return (
<div>
<Label>{ this.props.strings.dialogButtonLabel }</Label>
<Button label={ this.props.strings.dialogButtonLabel }
<div>
<Link label={ this.props.strings.dialogButtonLabel }
onClick={ this.showDialog.bind(this) }
disabled={ this.props.disabled }>
{ this.props.strings.dialogButtonText }
</Button>
</Link>
<Dialog type={ DialogType.normal }
isOpen={ this.state.showDialog }
@ -100,7 +98,7 @@ export class TextDialog extends React.Component<ITextDialogProps, ITextDialogSta
<AceEditor
width="600px"
mode="html"
mode={ this.props.language ? this.props.language: 'html' }
theme="monokai"
enableLiveAutocompletion={ true }
showPrintMargin={ false }

View File

@ -0,0 +1 @@
export { default as TextDialog } from './TextDialog';

View File

@ -3,6 +3,7 @@ import { DynamicProperty } from '@microsoft/sp-component-base';
import IRefinerConfiguration from '../../models/IRefinerConfiguration';
import { ISortFieldConfiguration } from '../../models/ISortFieldConfiguration';
import ISortableFieldConfiguration from '../../models/ISortableFieldConfiguration';
import { ISearchResultType } from '../../models/ISearchResultType';
export interface ISearchResultsWebPartProps {
queryKeywords: DynamicProperty<string>;
@ -23,4 +24,8 @@ export interface ISearchResultsWebPartProps {
externalTemplateUrl: string;
inlineTemplateText: string;
webPartTitle: string;
resultTypes: ISearchResultType[];
sourceId: string;
propertyId: string;
propertyPath: string;
}

View File

@ -52,7 +52,8 @@
"showBlank": true,
"showResultsCount": true,
"webPartTitle": "",
"useDefaultSearchQuery": false
"useDefaultSearchQuery": false,
"resultTypes": []
}
}
]

View File

@ -16,6 +16,7 @@ import {
IPropertyPaneChoiceGroupOption,
PropertyPaneChoiceGroup,
PropertyPaneCheckbox,
PropertyPaneHorizontalRule,
} from '@microsoft/sp-webpart-base';
import * as strings from 'SearchResultsWebPartStrings';
import SearchResultsContainer from './components/SearchResultsContainer/SearchResultsContainer';
@ -25,7 +26,7 @@ import ISearchService from '../../services/SearchService/ISearchService';
import ITaxonomyService from '../../services/TaxonomyService/ITaxonomyService';
import ResultsLayoutOption from '../../models/ResultsLayoutOption';
import TemplateService from '../../services/TemplateService/TemplateService';
import { update, isEmpty } from '@microsoft/sp-lodash-subset';
import { isEmpty } from '@microsoft/sp-lodash-subset';
import MockSearchService from '../../services/SearchService/MockSearchService';
import MockTemplateService from '../../services/TemplateService/MockTemplateService';
import SearchService from '../../services/SearchService/SearchService';
@ -37,6 +38,7 @@ import { PropertyFieldCollectionData, CustomCollectionFieldType } from '@pnp/spf
import { SPHttpClientResponse, SPHttpClient } from '@microsoft/sp-http';
import { SortDirection, Sort } from '@pnp/sp';
import { ISortFieldConfiguration, ISortFieldDirection } from '../../models/ISortFieldConfiguration';
import { ResultTypeOperator } from '../../models/ISearchResultType';
const LOG_SOURCE: string = '[SearchResultsWebPart_{0}]';
@ -45,14 +47,20 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
private _searchService: ISearchService;
private _taxonomyService: ITaxonomyService;
private _templateService: BaseTemplateService;
private _useResultSource: boolean;
private _propertyPage = null;
private _textDialogComponent = null;
private _propertyFieldCodeEditor = null;
private _propertyFieldCodeEditorLanguages = null;
/**
* The template to display at render time
*/
private _templateContentToDisplay: string;
public constructor() {
super();
this._templateContentToDisplay = '';
}
public async render(): Promise<void> {
// Configure the provider before the query according to our needs
this._searchService.resultsCount = this.properties.maxResultsCount;
@ -81,24 +89,35 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
super.renderCompleted();
let queryKeywords;
let renderElement = null;
let dataSourceValue;
// Get value from data source
const dataSourceValue = this.properties.queryKeywords.tryGetValue();
let source = this.properties.queryKeywords.tryGetSource();
if (typeof(dataSourceValue) !== 'string') {
this.properties.queryKeywords.setValue('');
// Try to get the source if a source id is present
if (!source && this.properties.sourceId) {
source = this.context.dynamicDataProvider.tryGetSource(this.properties.sourceId);
if (source && this.properties.propertyId) {
dataSourceValue = source.getPropertyValue(this.properties.propertyId)[this.properties.propertyPath];
}
} else {
dataSourceValue = this.properties.queryKeywords.tryGetValue();
}
if (typeof(dataSourceValue) !== 'string') {
dataSourceValue = '';
this.context.propertyPane.refresh();
}
if (!dataSourceValue) {
queryKeywords = this.properties.defaultSearchQuery;
} else {
queryKeywords = dataSourceValue;
}
const isValueConnected = !!this.properties.queryKeywords.tryGetSource();
const isValueConnected = !!source;
const searchContainer: React.ReactElement<ISearchResultsContainerProps> = React.createElement(
SearchResultsContainer,
@ -120,7 +139,8 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
templateService: this._templateService,
templateContent: this._templateContentToDisplay,
webPartTitle: this.properties.webPartTitle,
context: this.context
context: this.context,
resultTypes: this.properties.resultTypes
} as ISearchResultsContainerProps
);
@ -166,8 +186,11 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
this._templateService = new TemplateService(this.context.spHttpClient, this.context.pageContext.cultureInfo.currentUICultureName);
}
// Configure search query settings
this._useResultSource = false;
if (this.properties.sourceId) {
// Needed to retrieve manually the value for the dynamic property at render time. See the associated SPFx bug
// https://github.com/SharePoint/sp-dev-docs/issues/2985
this.context.dynamicDataProvider.registerSourceChanged(this.properties.sourceId, this.render);
}
// Set the default search results layout
this.properties.selectedLayout = this.properties.selectedLayout ? this.properties.selectedLayout : ResultsLayoutOption.List;
@ -238,6 +261,7 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
this.properties.sortableFields = Array.isArray(this.properties.sortableFields) ? this.properties.sortableFields : [];
this.properties.selectedProperties = this.properties.selectedProperties ? this.properties.selectedProperties : "Title,Path,Created,Filename,SiteLogo,PreviewUrl,PictureThumbnailURL,ServerRedirectedPreviewURL,ServerRedirectedURL,HitHighlightedSummary,FileType,contentclass,ServerRedirectedEmbedURL,DefaultEncodingURL";
this.properties.maxResultsCount = this.properties.maxResultsCount ? this.properties.maxResultsCount : 10;
this.properties.resultTypes = Array.isArray(this.properties.resultTypes) ? this.properties.resultTypes : [];
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
@ -285,10 +309,21 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
}
protected async loadPropertyPaneResources(): Promise<void> {
this._propertyPage = await import(
// Code editor component for result types
this._textDialogComponent = await import(
/* webpackChunkName: 'search-property-pane' */
'../controls/PropertyPaneTextDialog/PropertyPaneTextDialog'
'../controls/TextDialog'
);
// tslint:disable-next-line:no-shadowed-variable
const { PropertyFieldCodeEditor, PropertyFieldCodeEditorLanguages } = await import (
/* webpackChunkName: 'search-property-pane' */
'@pnp/spfx-property-controls/lib/PropertyFieldCodeEditor'
);
this._propertyFieldCodeEditor = PropertyFieldCodeEditor;
this._propertyFieldCodeEditorLanguages = PropertyFieldCodeEditorLanguages;
}
protected async onPropertyPaneFieldChanged(propertyPath: string) {
@ -300,6 +335,8 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
if (propertyPath === 'selectedLayout') {
// Refresh setting the right template for the property pane
await this._getTemplateContent();
this.context.propertyPane.refresh();
}
// Detect if the layout has been changed to custom...
@ -316,6 +353,32 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
}
}
protected async onPropertyPaneConfigurationStart() {
await this.loadPropertyPaneResources();
}
protected onBeforeSerialize() {
this._saveDataSourceInfo();
super.onBeforeSerialize();
}
/**
* Save the useful information for the connected data source.
* They will be used to get the value of the dynamic property if this one fails.
*/
private _saveDataSourceInfo() {
if (this.properties.queryKeywords.reference) {
this.properties.sourceId = this.properties.queryKeywords["_reference"]._sourceId;
this.properties.propertyId = this.properties.queryKeywords["_reference"]._property;
this.properties.propertyPath = this.properties.queryKeywords["_reference"]._propertyPath;
} else {
this.properties.sourceId = null;
this.properties.propertyId = null;
this.properties.propertyPath = null;
}
}
/**
* Opens the Web Part property pane
*/
@ -343,13 +406,8 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
private validateSourceId(value: string): string {
if (value.length > 0) {
if (!/^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$/.test(value)) {
this._useResultSource = false;
return strings.InvalidResultSourceIdMessage;
} else {
this._useResultSource = true;
}
} else {
this._useResultSource = false;
}
return '';
@ -386,35 +444,12 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
break;
}
// Register result types inside the template
this._templateService.registerResultTypes(this.properties.resultTypes);
this._templateContentToDisplay = templateContent;
}
/**
* Custom handler when a custom property pane field is updated
* @param propertyPath the name of the updated property
* @param newValue the new value for this property
*/
private async _onCustomPropertyPaneChange(propertyPath: string, newValue: any): Promise<void> {
// Stores the new value in web part properties
update(this.properties, propertyPath, (): any => { return newValue; });
// Call the default SPFx handler
this.onPropertyPaneFieldChanged(propertyPath);
// Refresh setting the right template for the property pane
await this._getTemplateContent();
// Refreshes the web part manually because custom fields don't update since sp-webpart-base@1.1.1
// https://github.com/SharePoint/sp-dev-docs/issues/594
if (!this.disableReactivePropertyChanges) {
// The render has to be completed before the property pane to refresh to set up the correct property value
// so the property pane field will use the correct value for future edit
this.render();
this.context.propertyPane.refresh();
}
}
/**
* Custom handler when the external template file URL
* @param value the template file URL value
@ -469,7 +504,13 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
let itemProp;
if (pageProp.indexOf(".Label") !== -1 || pageProp.indexOf(".TermID") !== -1) {
let term = pageProp.split(".");
itemProp = item[term[0]][0][term[1]];
// Handle multi or single values
if (item[term[0]].length > 0) {
itemProp = item[term[0]].map(e => { return e[term[1]]; }).join(',');
} else {
itemProp = item[term[0]][term[1]];
}
} else {
itemProp = item[pageProp];
}
@ -508,8 +549,7 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
multiline: true,
resizable: true,
placeholder: strings.SearchQueryPlaceHolderText,
deferredValidationTime: 300,
disabled: this._useResultSource,
deferredValidationTime: 300
}),
PropertyPaneTextField('resultSourceId', {
label: strings.ResultSourceIdLabel,
@ -518,9 +558,9 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
deferredValidationTime: 300
}),
PropertyFieldCollectionData('sortList', {
manageBtnLabel: strings.ConfigureBtnLabel,
manageBtnLabel: strings.Sort.EditSortLabel,
key: 'sortList',
panelHeader: strings.Sort.SortPropertyPaneFieldLabel,
panelHeader: strings.Sort.EditSortLabel,
panelDescription: strings.Sort.SortListDescription,
label: strings.Sort.SortPropertyPaneFieldLabel,
value: this.properties.sortList,
@ -551,9 +591,9 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
]
}),
PropertyFieldCollectionData('sortableFields', {
manageBtnLabel: strings.ConfigureBtnLabel,
manageBtnLabel: strings.Sort.EditSortableFieldsLabel,
key: 'sortableFields',
panelHeader: strings.Sort.SortableFieldsPropertyPaneField,
panelHeader: strings.Sort.EditSortableFieldsLabel,
panelDescription: strings.Sort.SortableFieldsDescription,
label: strings.Sort.SortableFieldsPropertyPaneField,
value: this.properties.sortableFields,
@ -585,9 +625,9 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
deferredValidationTime: 300
}),
PropertyFieldCollectionData('refiners', {
manageBtnLabel: strings.ConfigureBtnLabel,
manageBtnLabel: strings.Refiners.EditRefinersLabel,
key: 'refiners',
panelHeader: strings.Refiners.RefinersFieldLabel,
panelHeader: strings.Refiners.EditRefinersLabel,
panelDescription: strings.Refiners.RefinersFieldDescription,
label: strings.Refiners.RefinersFieldLabel,
value: this.properties.refiners,
@ -723,6 +763,22 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
const canEditTemplate = this.properties.externalTemplateUrl && this.properties.selectedLayout === ResultsLayoutOption.Custom ? false : true;
let dialogTextFieldValue;
switch (this.properties.selectedLayout) {
case ResultsLayoutOption.List:
dialogTextFieldValue = BaseTemplateService.getDefaultResultTypeListItem();
break;
case ResultsLayoutOption.Tiles:
dialogTextFieldValue = BaseTemplateService.getDefaultResultTypeTileItem();
break;
default:
dialogTextFieldValue = BaseTemplateService.getDefaultResultTypeCustomItem();
break;
}
// Sets up styling fields
let stylingFields: IPropertyPaneField<any>[] = [
PropertyPaneTextField('webPartTitle', {
@ -740,27 +796,120 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
label: strings.ShowPagingLabel,
checked: this.properties.showPaging,
}),
PropertyPaneHorizontalRule(),
PropertyPaneChoiceGroup('selectedLayout', {
label: 'Results layout',
options: layoutOptions
}),
new this._propertyPage.PropertyPaneTextDialog('inlineTemplateText', {
dialogTextFieldValue: this._templateContentToDisplay,
onPropertyChange: this._onCustomPropertyPaneChange.bind(this),
this._propertyFieldCodeEditor('inlineTemplateText', {
label: strings.DialogButtonLabel,
panelTitle: strings.DialogTitle,
initialValue: this._templateContentToDisplay,
deferredValidationTime: 500,
onPropertyChange: this.onPropertyPaneFieldChanged,
properties: this.properties,
disabled: !canEditTemplate,
strings: {
cancelButtonText: strings.CancelButtonText,
dialogButtonLabel: strings.DialogButtonLabel,
dialogButtonText: strings.DialogButtonText,
dialogTitle: strings.DialogTitle,
saveButtonText: strings.SaveButtonText
}
key: 'inlineTemplateTextCodeEditor',
language: this._propertyFieldCodeEditorLanguages.Handlebars
}),
PropertyFieldCollectionData('resultTypes', {
manageBtnLabel: strings.ResultTypes.EditResultTypesLabel,
key: 'resultTypes',
panelHeader: strings.ResultTypes.EditResultTypesLabel,
panelDescription: strings.ResultTypes.ResultTypesDescription,
enableSorting: true,
label: strings.ResultTypes.ResultTypeslabel,
value: this.properties.resultTypes,
fields: [
{
id: 'property',
title: strings.ResultTypes.ConditionPropertyLabel,
type: CustomCollectionFieldType.string,
required: true,
},
{
id: 'operator',
title: strings.ResultTypes.CondtionOperatorValue,
type: CustomCollectionFieldType.dropdown,
defaultValue: ResultTypeOperator.Equal,
required: true,
options: [
{
key: ResultTypeOperator.Equal,
text: strings.ResultTypes.EqualOperator
},
{
key: ResultTypeOperator.Contains,
text: strings.ResultTypes.ContainsOperator
},
{
key: ResultTypeOperator.StartsWith,
text: strings.ResultTypes.StartsWithOperator
},
{
key: ResultTypeOperator.NotNull,
text: strings.ResultTypes.NotNullOperator
},
{
key: ResultTypeOperator.GreaterOrEqual,
text: strings.ResultTypes.GreaterOrEqualOperator
},
{
key: ResultTypeOperator.GreaterThan,
text: strings.ResultTypes.GreaterThanOperator
},
{
key: ResultTypeOperator.LessOrEqual,
text: strings.ResultTypes.LessOrEqualOperator
},
{
key: ResultTypeOperator.LessThan,
text: strings.ResultTypes.LessThanOperator
}
]
},
{
id: 'value',
title: strings.ResultTypes.ConditionValueLabel,
type: CustomCollectionFieldType.string,
required: false,
},
{
id: "inlineTemplateContent",
title: "Inline template",
type: CustomCollectionFieldType.custom,
onCustomRender: (field, value, onUpdate) => {
return (
React.createElement("div", null,
React.createElement(this._textDialogComponent.TextDialog, {
language: this._propertyFieldCodeEditorLanguages.Handlebars,
dialogTextFieldValue: value ? value : dialogTextFieldValue,
onChanged: (fieldValue) => onUpdate(field.id, fieldValue),
strings: {
cancelButtonText: strings.CancelButtonText,
dialogButtonText: strings.DialogButtonText,
dialogTitle: strings.DialogTitle,
saveButtonText: strings.SaveButtonText
}
})
)
);
}
},
{
id: 'externalTemplateUrl',
title: strings.ResultTypes.ExternalUrlLabel,
type: CustomCollectionFieldType.url,
onGetErrorMessage: this._onTemplateUrlChange.bind(this),
placeholder: 'https://mysite/Documents/external.html'
},
]
})
];
// Only show the template external URL for 'Custom' option
if (this.properties.selectedLayout === ResultsLayoutOption.Custom) {
stylingFields.push(PropertyPaneTextField('externalTemplateUrl', {
stylingFields.splice(6, 0, PropertyPaneTextField('externalTemplateUrl', {
label: strings.TemplateUrlFieldLabel,
placeholder: strings.TemplateUrlPlaceholder,
deferredValidationTime: 500,

View File

@ -2,23 +2,15 @@ import * as React from 'react';
import ISearchResultsTemplateProps from './ISearchResultsTemplateProps';
import ISearchResultsTemplateState from './ISearchResultsTemplateState';
import './SearchResultsTemplate.scss';
import { Resize } from 'on-el-resize';
import { DomHelper } from '../../../../helpers/DomHelper';
export default class SearchResultsTemplate extends React.Component<ISearchResultsTemplateProps, ISearchResultsTemplateState> {
private parentRef: HTMLElement;
private resize: Resize;
constructor(props: ISearchResultsTemplateProps) {
super(props);
this.resize = new Resize();
this.state = {
processedTemplate: null
};
this.onComponentResize = this.onComponentResize.bind(this);
}
public render() {
@ -27,28 +19,16 @@ export default class SearchResultsTemplate extends React.Component<ISearchResult
objectNode.style.display = "none";
}
return <div ref={el => this.parentRef = el}>
<div dangerouslySetInnerHTML={{ __html: this.state.processedTemplate }}></div>
</div>;
}
public componentWillUnmount() {
try {
this.resize.removeResizeListener(this.parentRef, this.onComponentResize);
} catch (error) {}
return <div dangerouslySetInnerHTML={{ __html: this.state.processedTemplate }}></div>;
}
public componentDidMount() {
this._updateTemplate(this.props);
try {
this.resize.addResizeListener(this.parentRef, this.onComponentResize);
} catch (error) {}
}
public componentDidUpdate() {
// Post render operations (previews on elements, etc.)
this.props.templateService.initPreviewElements();
this.onComponentResize();
this.props.templateService.initPreviewElements();
}
public componentWillReceiveProps(nextProps: ISearchResultsTemplateProps) {
@ -66,14 +46,4 @@ export default class SearchResultsTemplate extends React.Component<ISearchResult
processedTemplate: template
});
}
private onComponentResize() {
// Resize iframes accordingly
const nodes = document.querySelectorAll(".iframePreview, .video-js");
DomHelper.forEach(nodes, (index, elt) => {
elt.style.width = Math.floor(this.parentRef.offsetWidth / 2) + 'px';
});
}
}

View File

@ -6,6 +6,7 @@ import { WebPartContext } from '@microsoft/sp-webpart-base';
import IRefinerConfiguration from '../../../../models/IRefinerConfiguration';
import { Sort } from '@pnp/sp';
import ISortableFieldConfiguration from '../../../../models/ISortableFieldConfiguration';
import { ISearchResultType } from '../../../../models/ISearchResultType';
interface ISearchResultsContainerProps {
@ -98,6 +99,9 @@ interface ISearchResultsContainerProps {
* The web part context
*/
context: WebPartContext;
/** The configured result types */
resultTypes: ISearchResultType[];
}
export default ISearchResultsContainerProps;

View File

@ -24,6 +24,8 @@ let FilterPanel = null;
export default class SearchResultsContainer extends React.Component<ISearchContainerProps, ISearchContainerState> {
private _searchWpRef: HTMLElement;
public constructor(props) {
super(props);
@ -144,7 +146,7 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
webUrl: this.props.context.pageContext.web.serverRelativeUrl,
maxResultsCount: this.props.maxResultsCount,
actualResultsCount: items.RelevantResults.length,
strings: strings,
strings: strings
}
}
/>
@ -162,6 +164,7 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
return (
<div className={styles.searchWp}>
<div tabIndex={-1} ref={ (ref) => { this._searchWpRef = ref; }}></div>
{renderWpContent}
</div>
);
@ -391,6 +394,8 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
areResultsLoading: true,
});
this._searchWpRef.focus();
const refinerManagedProperties = this.props.refiners.map(e => { return e.refinerName ;}).join(',');
const searchResults = await this.props.searchService.search(this.props.queryKeywords, refinerManagedProperties, this.state.selectedFilters, pageNumber);

View File

@ -60,18 +60,37 @@ define([], function() {
"SortPanelSortFieldPlaceHolder":"Select a field",
"SortPanelSortDirectionLabel":"Sort Direction",
"SortableFieldManagedPropertyField": "Sort managed property",
"SortableFieldDisplayValueField": "Field name to display"
"SortableFieldDisplayValueField": "Field name to display",
"EditSortableFieldsLabel": "Edit sortable fields",
"EditSortLabel": "Edit sort order"
},
"Refiners": {
"RefinersFieldLabel": "Refiners",
"RefinerManagedPropertyField": "Filter managed property",
"RefinerDisplayValueField": "Filter name to display",
"RefinersFieldDescription": "Specifies managed properties used as refiners. If there are no values for a filter property, it won't appear in the panel.",
"EditRefinersLabel": "Edit refiners"
},
"TermNotFound": "(Term with ID '{0}' not found)",
"UseDefaultSearchQueryKeywordsFieldLabel": "Use a default search query",
"DefaultSearchQueryKeywordsFieldLabel": "Default search query",
"DefaultSearchQueryKeywordsFieldDescription": "This query will be used when the data source value is still empty.",
"ConfigureBtnLabel": "Configure"
"ResultTypes": {
"ResultTypeslabel": "Result Types",
"ResultTypesDescription": "Add here the display templates to use for result items according to one ore more conditions. Conditions are evaluated in the configured order and external template takes precedence over inline templates. Make sure the managed properties you use are present in the 'Selected Properties' of this Web Part.",
"EditResultTypesLabel": "Edit Result Types",
"ConditionPropertyLabel": "Managed Property",
"ConditionValueLabel": "Condition Value",
"CondtionOperatorValue": "Operator",
"ExternalUrlLabel": "External Template Url",
"EqualOperator": "Equals",
"ContainsOperator": "Contains",
"StartsWithOperator": "Starts with",
"NotNullOperator": "Is not null",
"GreaterOrEqualOperator": "Greater or equal",
"GreaterThanOperator": "Greater than",
"LessOrEqualOperator": "Less or equal",
"LessThanOperator": "Less than"
}
}
});

View File

@ -60,18 +60,37 @@ define([], function() {
"SortPanelSortFieldPlaceHolder":"Sélectionner un champ",
"SortPanelSortDirectionLabel":"Direction de tri",
"SortableFieldManagedPropertyField": "Propriété gérée de tri",
"SortableFieldDisplayValueField": "Intitulé du champ à afficher"
"SortableFieldDisplayValueField": "Intitulé du champ à afficher",
"EditSortableFieldsLabel": "Éditer les champs de tri",
"EditSortLabel": "Éditer l'ordre de tri"
},
"Refiners": {
"RefinersFieldLabel": "Filtres",
"RefinersFieldDescription": "Configurez ici les propriétés gerées à utiliser comme filtres. Si il n'existe pas de valeurs pour le filtre spécifié, il n'apparaîtra pas dans le panneau.",
"RefinerManagedPropertyField": "Propriété gérée de filtre",
"RefinerDisplayValueField": "Intitulé du filtre à afficher",
"EditRefinersLabel": "Éditer les filtres"
},
"TermNotFound": "(Terme avec l'ID '{0}' non trouvé)",
"UseDefaultSearchQueryKeywordsFieldLabel": "Utiliser une requête initiale",
"DefaultSearchQueryKeywordsFieldLabel": "Requête de recherche par défaut",
"DefaultSearchQueryKeywordsFieldDescription": "Cette requête sera utilisée par défault dans le cas où la valeur de la source de données est encore vide.",
"ConfigureBtnLabel": "Configurer"
"ResultTypes": {
"ResultTypeslabel": "Types de résultats",
"ResultTypesDescription": "Ajoutez ici les modèles d'affichage à utiliser pour les éléments correspondant à une ou plusieurs conditions. Les conditions sont évaluées dans l'ordre et les modèles externes ont priorité sur ceux définis à même le composant. Vérifiez également que les propriétés gérées de recherche utilisées comme conditions sont présentes dans le paramètre 'Propriétés à récupérer' de ce Web Part.",
"EditResultTypesLabel": "Éditer les types de résultats",
"ConditionPropertyLabel": "Propriété gérée",
"ConditionValueLabel": "Valeur de la condition",
"CondtionOperatorValue": "Opérateur",
"ExternalUrlLabel": "URL du template externe",
"EqualOperator": "Égal",
"ContainsOperator": "Contient",
"StartsWithOperator": "Commence par",
"NotNullOperator": "N'est pas nul",
"GreaterOrEqualOperator": "Supérieur ou égal",
"GreaterThanOperator": "Supérieur à",
"LessOrEqualOperator": "Inférieur ou égal",
"LessThanOperator": "Inférieur"
}
}
});

View File

@ -48,7 +48,6 @@ declare interface ISearchResultsWebPartStrings {
HandlebarsHelpersDescription: string;
PromotedResultsLabel: string;
PanelCloseButtonAria:string;
ConfigureBtnLabel: string;
Sort: {
SortPropertyPaneFieldLabel
SortListDescription: string;
@ -63,12 +62,33 @@ declare interface ISearchResultsWebPartStrings {
SortableFieldsDescription: string;
SortableFieldManagedPropertyField: string;
SortableFieldDisplayValueField: string;
EditSortableFieldsLabel: string;
EditSortLabel: string;
},
Refiners: {
RefinersFieldLabel: string;
RefinersFieldDescription: string;
RefinerManagedPropertyField: string;
RefinerDisplayValueField: string;
EditRefinersLabel: string;
EditSortLabel: string;
},
ResultTypes: {
ResultTypeslabel: string;
ResultTypesDescription: string;
EditResultTypesLabel: string;
ConditionPropertyLabel: string;
ConditionValueLabel: string;
CondtionOperatorValue: string;
ExternalUrlLabel: string;
EqualOperator: string;
ContainsOperator: string;
StartsWithOperator: string;
NotNullOperator: string;
GreaterOrEqualOperator: string;
GreaterThanOperator: string;
LessOrEqualOperator: string;
LessThanOperator: string;
},
TermNotFound: string;
UseDefaultSearchQueryKeywordsFieldLabel: string;