Add sort panel (#656)

* Add SortPanel to select a field to sort by and it's sortorder

* Fix bug with ordering the sorted results

* apply state from searchresultcontainer to the sort panel

* Fix issue with sortorder being undefined in certain scenarios

* - Fix error on opening the web part without any settings configured
- Show relevant error message when using an invalid sort field
- Fix styling difference between 'Filter' and 'Sort' buttons

* Add info to readme.md

* Add new property_pane images to reflect latest changes

* Remove black line at the bottom of property_pane3.png

* Merge 'Dev' with current branch
and show Sort button when "SortError" is shown

* apply state from searchresultcontainer to the sort panel

* - Fix error on opening the web part without any settings configured
- Show relevant error message when using an invalid sort field
- Fix styling difference between 'Filter' and 'Sort' buttons

* Add info to readme.md

* Add new property_pane images to reflect latest changes

* Remove black line at the bottom of property_pane3.png

* Add missing localization for SortPanel, Update componentReceiveProps, rename sortOrder to sortDirection, fix typo when setting sort direction
This commit is contained in:
Stijn Brouwers 2018-11-06 07:53:16 +01:00 committed by Mikael Svenson
parent 27977d9235
commit 2df0641eb7
21 changed files with 337 additions and 25 deletions

View File

@ -84,6 +84,7 @@ Result Source Identifier | The GUID of a SharePoint result source. If you specif
Enable Query Rules | Enable the query rules if applies Enable Query Rules | Enable the query rules if applies
Selected properties | The search managed properties to retrieve. You can use these properties then in the code like this (`item.property_name`). Selected properties | The search managed properties to retrieve. You can use these properties then in the code like this (`item.property_name`).
Refiners | The search managed properties to use as refiners. Make sure these are refinable. With SharePoint Online, you have to reuse the default ones to do so (RefinableStringXX etc.). The order is the same as they will appear in the refnement panel. You can also provide your own custom labels using the following format RefinableString01:"You custom filter label",RefinableString02:"You custom filter label",... Refiners | The search managed properties to use as refiners. Make sure these are refinable. With SharePoint Online, you have to reuse the default ones to do so (RefinableStringXX etc.). The order is the same as they will appear in the refnement panel. You can also provide your own custom labels using the following format RefinableString01:"You custom filter label",RefinableString02:"You custom filter label",...
Sortable fields | The search managed properties to use for sorting. Make sure these are sortable. With SharePoint Online, you have to reuse the default ones to do so (RefinableStringXX etc.). The order is the same as they will appear in the sort panel. You can also provide your own custom labels using the following format RefinableString01:"You custom filter label",RefinableString02:"You custom filter label",... If no sortable fields are provided, the 'Sort' button will not be visible.
Number of items to retrieve per page | Quite explicit. The paging behavior is done directly by the search API (See the *SearchDataProvider.ts* file), not by the code on post-render. Number of items to retrieve per page | Quite explicit. The paging behavior is done directly by the search API (See the *SearchDataProvider.ts* file), not by the code on post-render.
#### Styling Options #### #### Styling Options ####

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 73 KiB

View File

@ -0,0 +1,6 @@
enum SortDirection {
Ascending = "ascending",
Descending = "descending"
}
export default SortDirection;

View File

@ -0,0 +1,5 @@
import SortDirection from "./SortDirection";
type UpdateSortOperationCallback = (sortDirection:SortDirection,sortField?:string) => void;
export default UpdateSortOperationCallback;

View File

@ -103,8 +103,8 @@ class SearchService implements ISearchService {
]; ];
if (this._sortList) { if (this._sortList) {
let sortOrders = this._sortList.split(','); let sortDirections = this._sortList.split(',');
sortList = sortOrders.map(sorter => { sortList = sortDirections.map(sorter => {
let sort = sorter.split(':'); let sort = sorter.split(':');
let s: Sort = { Property: sort[0].trim(), Direction: SortDirection.Descending }; let s: Sort = { Property: sort[0].trim(), Direction: SortDirection.Descending };
if (sort.indexOf('[') !== -1) { if (sort.indexOf('[') !== -1) {

View File

@ -10,6 +10,7 @@ export interface ISearchResultsWebPartProps {
maxResultsCount: number; maxResultsCount: number;
selectedProperties: string; selectedProperties: string;
refiners: string; refiners: string;
sortableFields: string;
showPaging: boolean; showPaging: boolean;
showResultsCount: boolean; showResultsCount: boolean;
showBlank: boolean; showBlank: boolean;

View File

@ -67,7 +67,7 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
constructor() { constructor() {
super(); super();
this._parseRefiners = this._parseRefiners.bind(this); this._parseFieldListString = this._parseFieldListString.bind(this);
} }
/** /**
@ -145,6 +145,14 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
value: this.properties.sortList, value: this.properties.sortList,
deferredValidationTime: 300 deferredValidationTime: 300
}), }),
PropertyPaneTextField('sortableFields', {
label: strings.SortableFieldsLabel,
description: strings.SortableFieldsDescription,
multiline: true,
resizable: true,
value: this.properties.sortableFields,
deferredValidationTime: 300,
}),
PropertyPaneToggle('enableQueryRules', { PropertyPaneToggle('enableQueryRules', {
label: strings.EnableQueryRulesLabel, label: strings.EnableQueryRulesLabel,
checked: this.properties.enableQueryRules, checked: this.properties.enableQueryRules,
@ -388,12 +396,13 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
} }
/** /**
* Parses refiners from the property pane value by extracting the refiner managed property and its label in the filter panel. * Parses a list of Fields from the property pane value by extracting the managed property and its label.
* @param rawValue the raw value of the refiner * @param rawValue the raw value of the refiner
*/ */
private _parseRefiners(rawValue: string): { [key: string]: string } { private _parseFieldListString(rawValue: string): { [key: string]: string } {
let refiners = {}; let returnValues = {};
if(!rawValue) { return returnValues; }
// Get each configuration // Get each configuration
let refinerKeyValuePair = rawValue.split(','); let refinerKeyValuePair = rawValue.split(',');
@ -405,18 +414,18 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
switch (refinerValues.length) { switch (refinerValues.length) {
case 1: case 1:
// Take the same name as the refiner managed property // Take the same name as the refiner managed property
refiners[refinerValues[0]] = refinerValues[0]; returnValues[refinerValues[0]] = refinerValues[0];
break; break;
case 2: case 2:
// Trim quotes if present // Trim quotes if present
refiners[refinerValues[0]] = refinerValues[1].replace(/^'(.*)'$/, '$1'); returnValues[refinerValues[0]] = refinerValues[1].replace(/^'(.*)'$/, '$1');
break; break;
} }
}); });
} }
return refiners; return returnValues;
} }
/** /**
@ -574,7 +583,8 @@ export default class SearchResultsWebPart extends BaseClientSideWebPart<ISearchR
sortList: this.properties.sortList, sortList: this.properties.sortList,
enableQueryRules: this.properties.enableQueryRules, enableQueryRules: this.properties.enableQueryRules,
selectedProperties: this.properties.selectedProperties ? this.properties.selectedProperties.replace(/\s|,+$/g, '').split(',') : [], selectedProperties: this.properties.selectedProperties ? this.properties.selectedProperties.replace(/\s|,+$/g, '').split(',') : [],
refiners: this._parseRefiners(this.properties.refiners), refiners: this._parseFieldListString(this.properties.refiners),
sortableFields: this._parseFieldListString(this.properties.sortableFields),
showPaging: this.properties.showPaging, showPaging: this.properties.showPaging,
showResultsCount: this.properties.showResultsCount, showResultsCount: this.properties.showResultsCount,
showBlank: this.properties.showBlank, showBlank: this.properties.showBlank,

View File

@ -114,7 +114,7 @@ export default class FilterPanel extends React.Component<IFilterPanelProps, IFil
return ( return (
<div> <div>
<div className="ms-textAlignRight"> <div className={`${styles.searchWp__buttonBar__button} ms-textAlignRight`}>
<ActionButton <ActionButton
className={`${styles.searchWp__filterResultBtn} ms-fontWeight-semibold`} className={`${styles.searchWp__filterResultBtn} ms-fontWeight-semibold`}
iconProps={{ iconName: 'Filter' }} iconProps={{ iconName: 'Filter' }}
@ -130,7 +130,6 @@ export default class FilterPanel extends React.Component<IFilterPanelProps, IFil
: null : null
} }
<Panel <Panel
className={styles.searchWp__filterPanel}
isOpen={this.state.showPanel} isOpen={this.state.showPanel}
type={PanelType.custom} type={PanelType.custom}
customWidth="450px" customWidth="450px"

View File

@ -56,6 +56,11 @@ interface ISearchResultsContainerProps {
*/ */
refiners: { [key: string]: string }; refiners: { [key: string]: string };
/**
* The managed properties used as sortable fields for the query
*/
sortableFields: { [key: string]: string };
/** /**
* Show the paging control * Show the paging control
*/ */

View File

@ -1,4 +1,5 @@
import { ISearchResults, IRefinementFilter, IRefinementResult } from '../../../../models/ISearchResult'; import { ISearchResults, IRefinementFilter, IRefinementResult } from '../../../../models/ISearchResult';
import SortDirection from '../../../../models/SortDirection';
interface ISearchResultsContainerState { interface ISearchResultsContainerState {
@ -46,6 +47,16 @@ interface ISearchResultsContainerState {
* Keeps the last query in case you change the query in the propery panel * Keeps the last query in case you change the query in the propery panel
*/ */
lastQuery: string; lastQuery: string;
/**
* Keeps the field on which the results need to be sorted (after initial sort)
*/
sortField?: string;
/**
* Keeps the order in which the results need to be sorted (after initial sort)
*/
sortDirection?: SortDirection;
} }
export default ISearchResultsContainerState; export default ISearchResultsContainerState;

View File

@ -13,6 +13,8 @@ import { DisplayMode } from '@microsoft/sp-core-library';
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle"; import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
import SearchResultsTemplate from '../Layouts/SearchResultsTemplate'; import SearchResultsTemplate from '../Layouts/SearchResultsTemplate';
import styles from '../SearchResultsWebPart.module.scss'; import styles from '../SearchResultsWebPart.module.scss';
import { SortPanel } from '../SortPanel';
import SortDirection from '../../../../models/SortDirection';
declare var System: any; declare var System: any;
let FilterPanel = null; let FilterPanel = null;
@ -35,10 +37,11 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
areResultsLoading: false, areResultsLoading: false,
errorMessage: '', errorMessage: '',
hasError: false, hasError: false,
lastQuery: '' lastQuery: '',
}; };
this._onUpdateFilters = this._onUpdateFilters.bind(this); this._onUpdateFilters = this._onUpdateFilters.bind(this);
this._onUpdateSort = this._onUpdateSort.bind(this);
this._onPageUpdate = this._onPageUpdate.bind(this); this._onPageUpdate = this._onPageUpdate.bind(this);
} }
@ -83,8 +86,18 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
renderWebPartTitle = <WebPartTitle title={this.props.webPartTitle} updateProperty={null} displayMode={DisplayMode.Read} />; renderWebPartTitle = <WebPartTitle title={this.props.webPartTitle} updateProperty={null} displayMode={DisplayMode.Read} />;
} }
const sortPanel = <SortPanel
onUpdateSort={this._onUpdateSort}
sortableFieldsConfiguration={this.props.sortableFields}
sortDirection={this.state.sortDirection}
sortField={this.state.sortField} />;
if (hasError) { if (hasError) {
renderWpContent = <MessageBar messageBarType={MessageBarType.error}>{errorMessage}</MessageBar>; if(this.state.errorMessage === strings.SortErrorMessage)
{
renderWpContent = <div><div className={styles.searchWp__buttonBar}>{sortPanel}</div><MessageBar messageBarType={MessageBarType.error}>{errorMessage}</MessageBar></div>;
}else {
renderWpContent = <MessageBar messageBarType={MessageBarType.error}>{errorMessage}</MessageBar>;
}
} else { } else {
const currentQuery = this.props.queryKeywords + this.props.searchService.queryTemplate + this.props.selectedProperties.join(','); const currentQuery = this.props.queryKeywords + this.props.searchService.queryTemplate + this.props.selectedProperties.join(',');
@ -94,7 +107,7 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
onUpdateFilters={this._onUpdateFilters} onUpdateFilters={this._onUpdateFilters}
refinersConfiguration={this.props.refiners} refinersConfiguration={this.props.refiners}
resetSelectedFilters={ this.state.lastQuery !== currentQuery ? true : false} resetSelectedFilters={ this.state.lastQuery !== currentQuery ? true : false}
/> : <span />; /> : <span />;
if (items.RelevantResults.length === 0) { if (items.RelevantResults.length === 0) {
@ -103,7 +116,7 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
renderWpContent = renderWpContent =
<div> <div>
{renderWebPartTitle} {renderWebPartTitle}
{renderFilterPanel} <div className={styles.searchWp__buttonBar}>{sortPanel}{renderFilterPanel}</div>
<div className={styles.searchWp__noresult}>{strings.NoResultMessage}</div> <div className={styles.searchWp__noresult}>{strings.NoResultMessage}</div>
</div>; </div>;
} else { } else {
@ -116,7 +129,7 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
renderWpContent = renderWpContent =
<div> <div>
{renderWebPartTitle} {renderWebPartTitle}
{renderFilterPanel} <div className={styles.searchWp__buttonBar}>{sortPanel}{renderFilterPanel}</div>
{renderOverlay} {renderOverlay}
<SearchResultsTemplate <SearchResultsTemplate
templateService={this.props.templateService} templateService={this.props.templateService}
@ -157,7 +170,7 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
public async componentDidMount() { public async componentDidMount() {
// Don't perform search is there is no keywords // Don't perform search if there are no keywords
if (this.props.queryKeywords) { if (this.props.queryKeywords) {
try { try {
@ -212,6 +225,8 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
// New props are passed to the component when the search query has been changed // New props are passed to the component when the search query has been changed
if (JSON.stringify(this.props.refiners) !== JSON.stringify(nextProps.refiners) if (JSON.stringify(this.props.refiners) !== JSON.stringify(nextProps.refiners)
|| JSON.stringify(this.props.sortableFields) !== JSON.stringify(nextProps.sortableFields)
|| this.props.sortList !== nextProps.sortList
|| this.props.maxResultsCount !== nextProps.maxResultsCount || this.props.maxResultsCount !== nextProps.maxResultsCount
|| this.state.lastQuery !== query || this.state.lastQuery !== query
|| this.props.resultSourceId !== nextProps.resultSourceId || this.props.resultSourceId !== nextProps.resultSourceId
@ -225,12 +240,17 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
this.setState({ this.setState({
selectedFilters: [], selectedFilters: [],
areResultsLoading: true, areResultsLoading: true,
hasError: false,
errorMessage: ""
}); });
this.props.searchService.selectedProperties = nextProps.selectedProperties; this.props.searchService.selectedProperties = nextProps.selectedProperties;
const refinerManagedProperties = Object.keys(nextProps.refiners).join(','); const refinerManagedProperties = Object.keys(nextProps.refiners).join(',');
// Reset sortlist
this.props.searchService.sortList = this.props.sortList;
// We reset the page number and refinement filters // We reset the page number and refinement filters
const searchResults = await this.props.searchService.search(nextProps.queryKeywords, refinerManagedProperties, [], 1); const searchResults = await this.props.searchService.search(nextProps.queryKeywords, refinerManagedProperties, [], 1);
const localizedFilters = await this._getLocalizedFilters(searchResults.RefinementResults); const localizedFilters = await this._getLocalizedFilters(searchResults.RefinementResults);
@ -315,6 +335,48 @@ export default class SearchResultsContainer extends React.Component<ISearchConta
}); });
} }
/**
* Callback function to apply new sort configuration coming from the sort panel child component
* @param newFilters The new filters to apply
*/
private async _onUpdateSort(sortDirection:SortDirection,sortField?:string) {
if(sortField) {
// Get back to the first page when new sorting has been selected
this.setState({
sortField: sortField,
sortDirection: sortDirection,
currentPage: 1,
areResultsLoading: true,
hasError:false,
errorMessage:null
});
const refinerManagedProperties = Object.keys(this.props.refiners).join(',');
this.props.searchService.sortList = `${sortField}:${sortDirection}`;
try
{
const searchResults = await this.props.searchService.search(this.props.queryKeywords, refinerManagedProperties, this.state.selectedFilters, 1);
this.setState({
results: searchResults,
areResultsLoading: false,
});
}
catch(error) {
Logger.write('[SearchContainer._onUpdateSort(sortDirection:SortDirection,sortField?:string)]: Error: ' + error, LogLevel.Error);
const errorMessage = /\"value\":\"[^:]+: SortList\.\"/.test(error.message) ? strings.SortErrorMessage : error.message;
this.setState({
areResultsLoading: false,
results: { RefinementResults: [], RelevantResults: [] },
hasError: true,
errorMessage: errorMessage
});
}
}
}
/** /**
* Callback function update search results according the page number * Callback function update search results according the page number
* @param pageNumber The page mumber to get * @param pageNumber The page mumber to get

View File

@ -10,10 +10,26 @@
text-align: center; text-align: center;
} }
&__buttonBar {
overflow:auto;
&__button {
float:right;
}
}
&__filterResultBtn { &__filterResultBtn {
color: "[theme: themePrimary, default: #005a9e]"; color: "[theme: themePrimary, default: #005a9e]";
} }
&__sortResultBtn {
color: "[theme: themePrimary, default: #005a9e]";
}
&__sortResultBtn {
color: "[theme: themePrimary]";
}
&__selectedFilters { &__selectedFilters {
padding: 10px; padding: 10px;
@ -83,9 +99,6 @@
} }
&__filterPanel { &__filterPanel {
position: relative;
&__body { &__body {
padding-right: 20px; padding-right: 20px;
padding-left: 20px; padding-left: 20px;
@ -124,6 +137,13 @@
} }
} }
} }
&__sortPanel {
&__body {
padding: 20px;
overflow: auto;
}
}
} }
.overlay { .overlay {

View File

@ -0,0 +1,11 @@
import UpdateSortOperationCallback from '../../../../models/UpdateSortOperationCallback';
import SortDirection from '../../../../models/SortDirection';
interface ISortPanelProps {
sortableFieldsConfiguration: { [key: string]: string };
onUpdateSort: UpdateSortOperationCallback;
sortDirection?:SortDirection;
sortField?:string;
}
export default ISortPanelProps;

View File

@ -0,0 +1,10 @@
import { IRefinementFilter } from '../../../../models/ISearchResult';
import SortDirection from '../../../../models/SortDirection';
interface IFilterPanelState {
showPanel?: boolean;
sortField?: string;
sortDirection: SortDirection;
}
export default IFilterPanelState;

View File

@ -0,0 +1,134 @@
import * as React from 'react';
import ISortPanelProps from './ISortPanelProps';
import ISortPanelState from './ISortPanelState';
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { Toggle } from 'office-ui-fabric-react/lib/Toggle';
import * as strings from 'SearchWebPartStrings';
import { Scrollbars } from 'react-custom-scrollbars';
import { ActionButton } from 'office-ui-fabric-react/lib/Button';
import SortDirection from '../../../../models/SortDirection';
import styles from '../SearchResultsWebPart.module.scss';
export default class SortPanel extends React.Component<ISortPanelProps, ISortPanelState> {
public constructor(props) {
super(props);
this.state = {
showPanel: false,
sortDirection:this.props.sortDirection ? this.props.sortDirection :SortDirection.Ascending,
sortField:this.props.sortField ? this.props.sortField : null
};
this._onTogglePanel = this._onTogglePanel.bind(this);
this._onClosePanel = this._onClosePanel.bind(this);
this._getSortableFieldCount = this._getSortableFieldCount.bind(this);
this._setSortDirection = this._setSortDirection.bind(this);
this._getDropdownOptions = this._getDropdownOptions.bind(this);
this._onChangedSelectedField = this._onChangedSelectedField.bind(this);
}
public render(): React.ReactElement<ISortPanelProps> {
if(this._getSortableFieldCount() === 0) return <span />;
const dropdownOptions: IDropdownOption[] = this._getDropdownOptions();
return (
<div>
<div className={`${styles.searchWp__buttonBar__button} ms-textAlignRight`}>
<ActionButton
className={`${styles.searchWp__filterResultBtn} ms-fontWeight-semibold`}
iconProps={{ iconName:'Sort' }}
text={strings.SortResultsButtonLabel}
onClick={this._onTogglePanel}
/>
</div>
<Panel
isOpen={this.state.showPanel}
type={PanelType.custom}
customWidth="450px"
isBlocking={false}
isLightDismiss={true}
onDismiss={this._onClosePanel}
headerText={strings.SortPanelTitle}
closeButtonAriaLabel={strings.PanelCloseButtonAria}
hasCloseButton={true}
onRenderBody={() => {
return <Scrollbars style={{ height: '100%' }}>
<div className={styles.searchWp__sortPanel__body}>
<div>
<Dropdown
placeHolder={strings.SortPanelSortFieldPlaceHolder}
label={strings.SortPanelSortFieldLabel}
ariaLabel={strings.SortPanelSortFieldAria}
onChanged={this._onChangedSelectedField}
selectedKey={this.state.sortField}
options={dropdownOptions}
/>
</div>
<div>
<Toggle
label={strings.SortPanelSortDirectionLabel}
onText={strings.SortDirectionAscendingLabel}
offText={strings.SortDirectionDescendingLabel}
onChanged={(checked: boolean) => {
this._setSortDirection(checked);
}}
checked={this.state.sortDirection === SortDirection.Ascending || typeof(this.state.sortDirection) === 'undefined'}
/>
</div>
</div>
</Scrollbars>;
}}
>
</Panel>
</div>
);
}
private _getSortableFieldCount() {
if(!this.props.sortableFieldsConfiguration) return 0;
return Object.keys(this.props.sortableFieldsConfiguration).filter(value => {
return value;
}).length;
}
private _setSortDirection(checked:boolean) {
const sortDirection = checked ? SortDirection.Ascending : SortDirection.Descending;
this.setState({
sortDirection: sortDirection,
});
this.props.onUpdateSort(sortDirection,this.state.sortField);
}
private _getDropdownOptions():IDropdownOption[] {
let dropdownOptions:IDropdownOption[] = [];
const sortableFields = Object.keys(this.props.sortableFieldsConfiguration);
sortableFields.forEach((fieldKey) => {
//Strip " from start and end of the display name if present
const fieldDisplayName = this.props.sortableFieldsConfiguration[fieldKey].replace(/^\"+|\"+$/g, '');
dropdownOptions.push({ key: fieldKey, text: fieldDisplayName});
});
return dropdownOptions;
}
private _onChangedSelectedField(option: IDropdownOption, index?: number):void {
const sortField = option.key.toString();
this.setState({
sortField: sortField,
});
this.props.onUpdateSort(this.state.sortDirection,sortField);
}
private _onClosePanel() {
this.setState({ showPanel: false });
}
private _onTogglePanel() {
this.setState({ showPanel: !this.state.showPanel });
}
}

View File

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

View File

@ -9,8 +9,11 @@ define([], function() {
"MaxResultsCount": "Number of items to retrieve per page", "MaxResultsCount": "Number of items to retrieve per page",
"NoResultMessage": "There are no results to show", "NoResultMessage": "There are no results to show",
"RefinersFieldLabel": "Refiners", "RefinersFieldLabel": "Refiners",
"SortableFieldsLabel": "Sortable fields",
"FilterPanelTitle": "Available filters", "FilterPanelTitle": "Available filters",
"SortPanelTitle":"Sort",
"FilterResultsButtonLabel": "Filters", "FilterResultsButtonLabel": "Filters",
"SortResultsButtonLabel":"Sort",
"SelectedFiltersLabel": "Selected filters:", "SelectedFiltersLabel": "Selected filters:",
"ApplyAllFiltersLabel": "Apply all filters", "ApplyAllFiltersLabel": "Apply all filters",
"RemoveAllFiltersLabel": "Remove all filters", "RemoveAllFiltersLabel": "Remove all filters",
@ -26,13 +29,14 @@ define([], function() {
"PlaceHolderIconText": "Search Results Web Part with Refinements", "PlaceHolderIconText": "Search Results Web Part with Refinements",
"PlaceHolderDescription": "This component displays search results with paging and customizable refinement panel", "PlaceHolderDescription": "This component displays search results with paging and customizable refinement panel",
"ResultSourceIdLabel": "Result Source Identifier", "ResultSourceIdLabel": "Result Source Identifier",
"SortList": "Sort order", "SortList": "Initial sort order",
"SortListDescription": "Specify sort order in a comma separated list on the format <Managed Property Name>:ascending/descending (default:Created:descending,Size:ascending).", "SortListDescription": "Specify initial sort order in a comma separated list on the format <Managed Property Name>:ascending/descending (default:Created:descending,Size:ascending).",
"InvalidResultSourceIdMessage": "Invalid identifier", "InvalidResultSourceIdMessage": "Invalid identifier",
"UseSearchBoxQueryLabel": "Use a dynamic data source as search query", "UseSearchBoxQueryLabel": "Use a dynamic data source as search query",
"EnableQueryRulesLabel": "Enable query rules", "EnableQueryRulesLabel": "Enable query rules",
"StylingSettingsGroupName": "Styling options", "StylingSettingsGroupName": "Styling options",
"RefinersFieldDescription": "Specifies managed properties used as refiners (ordered comma-separated list). You can specify the label by using the following format <Managed Property Name>:\"My friendly name\".", "RefinersFieldDescription": "Specifies managed properties used as refiners (ordered comma-separated list). You can specify the label by using the following format <Managed Property Name>:\"My friendly name\".",
"SortableFieldsDescription": "Specifies sortable properties used by the sort panel (ordered comma-separated list). You can specify the label by using the following format <Managed Property Name>:\"My friendly name\".",
"SelectedPropertiesFieldDescription": "Speficies the properties to retrieve from the search results.", "SelectedPropertiesFieldDescription": "Speficies the properties to retrieve from the search results.",
"SearchQueryKeywordsFieldDescription": "Use pre-defined search query keywords to retrieve a static set of results.", "SearchQueryKeywordsFieldDescription": "Use pre-defined search query keywords to retrieve a static set of results.",
"CountMessageLong": "<b>{0}</b> results for '<em>{1}</em>'", "CountMessageLong": "<b>{0}</b> results for '<em>{1}</em>'",
@ -53,6 +57,14 @@ define([], function() {
"HandlebarsHelpersDescription": "Enable functions from moment and handlebars helpers. See https://github.com/SharePoint/sp-dev-fx-webparts/blob/master/samples/react-search-refiners/README.md#available-tokens for more information.", "HandlebarsHelpersDescription": "Enable functions from moment and handlebars helpers. See https://github.com/SharePoint/sp-dev-fx-webparts/blob/master/samples/react-search-refiners/README.md#available-tokens for more information.",
"DynamicDataSourceLabel": "Available data sources", "DynamicDataSourceLabel": "Available data sources",
"DynamicDataSourcePropertyLabel": "Available properties", "DynamicDataSourcePropertyLabel": "Available properties",
"PromotedResultsLabel": "Promoted result(s)" "PromotedResultsLabel": "Promoted result(s)",
"SortDirectionAscendingLabel":"Ascending",
"SortDirectionDescendingLabel":"Descending",
"SortErrorMessage":"Invalid search property (Check if the managed property is sortable).",
"SortPanelSortFieldLabel":"Sort on field",
"SortPanelSortFieldAria":"Select a field",
"SortPanelSortFieldPlaceHolder":"Select a field",
"SortPanelSortDirectionLabel":"Sort Direction",
"PanelCloseButtonAria":"Close",
} }
}); });

View File

@ -9,8 +9,11 @@ define([], function() {
"MaxResultsCount": "Nombre de résulats à récupérer par page", "MaxResultsCount": "Nombre de résulats à récupérer par page",
"NoResultMessage": "Il n'y a aucun résultat à afficher.", "NoResultMessage": "Il n'y a aucun résultat à afficher.",
"RefinersFieldLabel": "Filtres", "RefinersFieldLabel": "Filtres",
"SortableFieldsLabel": "Triables",
"FilterPanelTitle": "Filtres disponibles", "FilterPanelTitle": "Filtres disponibles",
"SortPanelTitle":"Trier",
"FilterResultsButtonLabel": "Filtrer", "FilterResultsButtonLabel": "Filtrer",
"SortResultsButtonLabel":"Trier",
"SelectedFiltersLabel": "Filtre(s) appliqué(s):", "SelectedFiltersLabel": "Filtre(s) appliqué(s):",
"ApplyAllFiltersLabel": "Appliquer tous les filters", "ApplyAllFiltersLabel": "Appliquer tous les filters",
"RemoveAllFiltersLabel": "Supprimer tous les filtres", "RemoveAllFiltersLabel": "Supprimer tous les filtres",
@ -33,6 +36,7 @@ define([], function() {
"EnableQueryRulesLabel": "Activer les règles de requête", "EnableQueryRulesLabel": "Activer les règles de requête",
"StylingSettingsGroupName": "Options d'affichage", "StylingSettingsGroupName": "Options d'affichage",
"RefinersFieldDescription": "Propriétés gerées à utiliser comme filtres (liste ordonnée séparée par une virgule). Vous pouvez spécifier un label personnalisé en utilisant le format suivant <Nom de la propriété gérée>:\"Nom convivial\".", "RefinersFieldDescription": "Propriétés gerées à utiliser comme filtres (liste ordonnée séparée par une virgule). Vous pouvez spécifier un label personnalisé en utilisant le format suivant <Nom de la propriété gérée>:\"Nom convivial\".",
"SortableFieldsDescription": "Propriétés gerées à utiliser comme triables (liste ordonnée séparée par une virgule). Vous pouvez spécifier un label personnalisé en utilisant le format suivant <Nom de la propriété gérée>:\"Nom convivial\".",
"SelectedPropertiesFieldDescription": "Propriétés à récupérer des résulats de recherche.", "SelectedPropertiesFieldDescription": "Propriétés à récupérer des résulats de recherche.",
"SearchQueryKeywordsFieldDescription": "Utilisez une requête de recherche prédéfinie pour obtenir un ensemble de résultats statique.", "SearchQueryKeywordsFieldDescription": "Utilisez une requête de recherche prédéfinie pour obtenir un ensemble de résultats statique.",
"CountMessageLong": "<b>{0}</b> résultats pour '<em>{1}</em>'", "CountMessageLong": "<b>{0}</b> résultats pour '<em>{1}</em>'",
@ -53,6 +57,14 @@ define([], function() {
"HandlebarsHelpersDescription": "Activer les fonctions de moment et handlebars helpers. Voir https://github.com/SharePoint/sp-dev-fx-webparts/blob/master/samples/react-search-refiners/README.md#available-tokens pour plus d'informations.", "HandlebarsHelpersDescription": "Activer les fonctions de moment et handlebars helpers. Voir https://github.com/SharePoint/sp-dev-fx-webparts/blob/master/samples/react-search-refiners/README.md#available-tokens pour plus d'informations.",
"DynamicDataSourceLabel": "Source de données disponibles", "DynamicDataSourceLabel": "Source de données disponibles",
"DynamicDataSourcePropertyLabel": "Propriétés disponibles", "DynamicDataSourcePropertyLabel": "Propriétés disponibles",
"PromotedResultsLabel": "Résultat(s) promu(s)" "PromotedResultsLabel": "Résultat(s) promu(s)",
"SortDirectionAscendingLabel":"Ascendant",
"SortDirectionDescendingLabel":"Descendant",
"SortErrorMessage":"Propriété de recherche non valide (Vérifiez si la propriété managée est triable).",
"SortPanelSortFieldLabel":"Trier sur le champ",
"SortPanelSortFieldAria":"Sélectionner un champ",
"SortPanelSortFieldPlaceHolder":"Sélectionner un champ",
"SortPanelSortDirectionLabel":"Direction de tri",
"PanelCloseButtonAria":"Proche",
} }
}); });

View File

@ -10,9 +10,13 @@ declare interface ISearchWebPartStrings {
MaxResultsCount: string; MaxResultsCount: string;
NoResultMessage: string; NoResultMessage: string;
RefinersFieldLabel: string; RefinersFieldLabel: string;
SortableFieldsLabel: string;
RefinersFieldDescription: string; RefinersFieldDescription: string;
SortableFieldsDescription: string;
FilterPanelTitle: string; FilterPanelTitle: string;
SortPanelTitle: string;
FilterResultsButtonLabel: string; FilterResultsButtonLabel: string;
SortResultsButtonLabel:string;
SelectedFiltersLabel: string; SelectedFiltersLabel: string;
ApplyAllFiltersLabel: string; ApplyAllFiltersLabel: string;
RemoveAllFiltersLabel: string; RemoveAllFiltersLabel: string;
@ -53,6 +57,14 @@ declare interface ISearchWebPartStrings {
DynamicDataSourceLabel: string; DynamicDataSourceLabel: string;
DynamicDataSourcePropertyLabel: string; DynamicDataSourcePropertyLabel: string;
PromotedResultsLabel: string; PromotedResultsLabel: string;
SortDirectionAscendingLabel:string;
SortDirectionDescendingLabel:string;
SortErrorMessage:string;
SortPanelSortFieldLabel:string;
SortPanelSortFieldAria:string;
SortPanelSortFieldPlaceHolder:string;
SortPanelSortDirectionLabel:string;
PanelCloseButtonAria:string;
} }
declare module 'SearchWebPartStrings' { declare module 'SearchWebPartStrings' {