diff --git a/docs/assets/extensibility/web_components/breadcrumb_component.png b/docs/assets/extensibility/web_components/breadcrumb_component.png new file mode 100644 index 000000000..f1dd1b828 Binary files /dev/null and b/docs/assets/extensibility/web_components/breadcrumb_component.png differ diff --git a/docs/scenarios/assets/create-your-first-custom-template/custom_search-results-example.png b/docs/create-custom-layouts/assets/create-your-first-custom-template/custom_search-results-example.png similarity index 100% rename from docs/scenarios/assets/create-your-first-custom-template/custom_search-results-example.png rename to docs/create-custom-layouts/assets/create-your-first-custom-template/custom_search-results-example.png diff --git a/docs/scenarios/assets/create-your-first-custom-template/insert_external-url-for-custom-template.png b/docs/create-custom-layouts/assets/create-your-first-custom-template/insert_external-url-for-custom-template.png similarity index 100% rename from docs/scenarios/assets/create-your-first-custom-template/insert_external-url-for-custom-template.png rename to docs/create-custom-layouts/assets/create-your-first-custom-template/insert_external-url-for-custom-template.png diff --git a/docs/scenarios/assets/create-your-first-custom-template/myfirsttemplate_file-screenshot.png b/docs/create-custom-layouts/assets/create-your-first-custom-template/myfirsttemplate_file-screenshot.png similarity index 100% rename from docs/scenarios/assets/create-your-first-custom-template/myfirsttemplate_file-screenshot.png rename to docs/create-custom-layouts/assets/create-your-first-custom-template/myfirsttemplate_file-screenshot.png diff --git a/docs/scenarios/assets/edit-custom-templates-in-sharepoint/open_htmlfile-in-text-editor.png b/docs/create-custom-layouts/assets/edit-custom-templates-in-sharepoint/open_htmlfile-in-text-editor.png similarity index 100% rename from docs/scenarios/assets/edit-custom-templates-in-sharepoint/open_htmlfile-in-text-editor.png rename to docs/create-custom-layouts/assets/edit-custom-templates-in-sharepoint/open_htmlfile-in-text-editor.png diff --git a/docs/scenarios/assets/edit-custom-templates-in-sharepoint/save_the-changed-file.png b/docs/create-custom-layouts/assets/edit-custom-templates-in-sharepoint/save_the-changed-file.png similarity index 100% rename from docs/scenarios/assets/edit-custom-templates-in-sharepoint/save_the-changed-file.png rename to docs/create-custom-layouts/assets/edit-custom-templates-in-sharepoint/save_the-changed-file.png diff --git a/docs/scenarios/assets/edit-templates-using-vscode-and-onedrive/open-template-folder-onedrive.png b/docs/create-custom-layouts/assets/edit-templates-using-vscode-and-onedrive/open-template-folder-onedrive.png similarity index 100% rename from docs/scenarios/assets/edit-templates-using-vscode-and-onedrive/open-template-folder-onedrive.png rename to docs/create-custom-layouts/assets/edit-templates-using-vscode-and-onedrive/open-template-folder-onedrive.png diff --git a/docs/scenarios/assets/edit-templates-using-vscode-and-onedrive/refresh-search-page-and-see-result-of-changes.png b/docs/create-custom-layouts/assets/edit-templates-using-vscode-and-onedrive/refresh-search-page-and-see-result-of-changes.png similarity index 100% rename from docs/scenarios/assets/edit-templates-using-vscode-and-onedrive/refresh-search-page-and-see-result-of-changes.png rename to docs/create-custom-layouts/assets/edit-templates-using-vscode-and-onedrive/refresh-search-page-and-see-result-of-changes.png diff --git a/docs/scenarios/assets/edit-templates-using-vscode-and-onedrive/sync-or-addshortcut-to-onedrive.png b/docs/create-custom-layouts/assets/edit-templates-using-vscode-and-onedrive/sync-or-addshortcut-to-onedrive.png similarity index 100% rename from docs/scenarios/assets/edit-templates-using-vscode-and-onedrive/sync-or-addshortcut-to-onedrive.png rename to docs/create-custom-layouts/assets/edit-templates-using-vscode-and-onedrive/sync-or-addshortcut-to-onedrive.png diff --git a/docs/scenarios/assets/store-custom-templates-in-sharepoint/central-repository-multiple-sites.png b/docs/create-custom-layouts/assets/store-custom-templates-in-sharepoint/central-repository-multiple-sites.png similarity index 100% rename from docs/scenarios/assets/store-custom-templates-in-sharepoint/central-repository-multiple-sites.png rename to docs/create-custom-layouts/assets/store-custom-templates-in-sharepoint/central-repository-multiple-sites.png diff --git a/docs/scenarios/assets/store-custom-templates-in-sharepoint/custom-template-external-url-edit.png b/docs/create-custom-layouts/assets/store-custom-templates-in-sharepoint/custom-template-external-url-edit.png similarity index 100% rename from docs/scenarios/assets/store-custom-templates-in-sharepoint/custom-template-external-url-edit.png rename to docs/create-custom-layouts/assets/store-custom-templates-in-sharepoint/custom-template-external-url-edit.png diff --git a/docs/scenarios/assets/store-custom-templates-in-sharepoint/sharepoint-resources-lib.png b/docs/create-custom-layouts/assets/store-custom-templates-in-sharepoint/sharepoint-resources-lib.png similarity index 100% rename from docs/scenarios/assets/store-custom-templates-in-sharepoint/sharepoint-resources-lib.png rename to docs/create-custom-layouts/assets/store-custom-templates-in-sharepoint/sharepoint-resources-lib.png diff --git a/docs/scenarios/create-your-first-custom-template.md b/docs/create-custom-layouts/create-your-first-custom-template.md similarity index 100% rename from docs/scenarios/create-your-first-custom-template.md rename to docs/create-custom-layouts/create-your-first-custom-template.md diff --git a/docs/scenarios/edit-custom-templates-in-sharepoint.md b/docs/create-custom-layouts/edit-custom-templates-in-sharepoint.md similarity index 100% rename from docs/scenarios/edit-custom-templates-in-sharepoint.md rename to docs/create-custom-layouts/edit-custom-templates-in-sharepoint.md diff --git a/docs/scenarios/edit-templates-using-vscode-and-onedrive.md b/docs/create-custom-layouts/edit-templates-using-vscode-and-onedrive.md similarity index 100% rename from docs/scenarios/edit-templates-using-vscode-and-onedrive.md rename to docs/create-custom-layouts/edit-templates-using-vscode-and-onedrive.md diff --git a/docs/scenarios/howto-store-custom-templates-in-sharepoint.md b/docs/create-custom-layouts/howto-store-custom-templates-in-sharepoint.md similarity index 100% rename from docs/scenarios/howto-store-custom-templates-in-sharepoint.md rename to docs/create-custom-layouts/howto-store-custom-templates-in-sharepoint.md diff --git a/docs/create-custom-layouts/index.md b/docs/create-custom-layouts/index.md new file mode 100644 index 000000000..9b029585a --- /dev/null +++ b/docs/create-custom-layouts/index.md @@ -0,0 +1,24 @@ + + + +If you are looking for inspiration, you can find a selection of custom layouts in the +[Custom layouts repository](https://github.com/microsoft-search/pnp-modern-search-layouts/blob/main/README.md) + +If you have a custom layout you want to share, please submit a PR to the repository. + + +## [Scenario Create your first custom template](create-your-first-custom-template.md) + +Create your first custom template + +## [Store custom templates in SharePoint](howto-store-custom-templates-in-sharepoint.md) + +Storing custom templates as files in a SharePoint site, is great when you want to use them across sites and want some control. + +## [Edit custom templates in SharePoint](edit-custom-templates-in-sharepoint.md) + +Storing custom templates as files in a SharePoint site, is great when you want to use them across sites and want some control. + +## [Edit custom templates locally in Visual Studio Code](edit-templates-using-vscode-and-onedrive.md) + +When you have your templates in SharePoint, it is easy to setup a way to edit locally on your computer and still get the result in SharePoint almost instantly. diff --git a/docs/extensibility/index.md b/docs/extensibility/index.md index 8a00c369e..474c9ceac 100644 --- a/docs/extensibility/index.md +++ b/docs/extensibility/index.md @@ -54,7 +54,7 @@ From here, you can add the manifest IDs of your libraries and decide to enable o [!["Extensibility manifests registration"](../assets/extensibility/extensibility_configuration_manifest.png){: .center}](../assets/extensibility/extensibility_configuration_manifest.png) -> Multiple librairies can be registred for a single Web Part instance allowing you to split your extensions into multiple projects (in the end, they will be all concatenated). For instance, this could be convenient when extensions come from different IT providers. +> Multiple librairies can be registered for a single Web Part instance allowing you to split your extensions into multiple projects (in the end, they will be all concatenated). For instance, this could be convenient when extensions come from different IT providers. ### Create an extensibility library @@ -179,4 +179,4 @@ export class MyCustomLibraryComponent implements IExtensibilityLibrary { } ``` -In order to run the above sample code, you will need to import in your library the following npm packages: `@microsoft/sp-component-base`, `@microsoft/sp-core-library`, and `@microsoft/sp-webpart-base`. \ No newline at end of file +In order to run the above sample code, you will need to import in your library the following npm packages: `@microsoft/sp-component-base`, `@microsoft/sp-core-library`, and `@microsoft/sp-webpart-base`. diff --git a/docs/extensibility/templating.md b/docs/extensibility/templating.md index 5359ca600..5ea71fe55 100644 --- a/docs/extensibility/templating.md +++ b/docs/extensibility/templating.md @@ -237,7 +237,7 @@ If no placeholder is present in the template, a default one will be loaded. > The Microsoft Graph Toolkit is a collection of reusable, framework-agnostic web components and helpers for accessing and working with Microsoft Graph. The components are fully functional right of out of the box, with built in providers that authenticate with and fetch data from Microsoft Graph. -In the solution, you can use Graph Tookit components whitout the need to re-authenticate against Microsoft Graph because the Web Parts already use the [SharePoint provider](https://docs.microsoft.com/en-us/graph/toolkit/providers/sharepoint). +In the solution, you can use Graph Tookit components without the need to re-authenticate against Microsoft Graph because the Web Parts already use the [SharePoint provider](https://docs.microsoft.com/en-us/graph/toolkit/providers/sharepoint). Refer to the official documentation to see [all available components](https://docs.microsoft.com/en-us/graph/toolkit/components/login). For instance, we use the Microsoft Graph Toolkit for the [people layout](../usage/search-results/layouts/people.md) via ``. diff --git a/docs/extensibility/web_components_list.md b/docs/extensibility/web_components_list.md index c831c6e19..9b502d815 100644 --- a/docs/extensibility/web_components_list.md +++ b/docs/extensibility/web_components_list.md @@ -10,6 +10,7 @@ Here are the list of all **reusable** web components you can use to customize yo - [<pnp-collapsible>](#ltpnp-collapsiblegt) - [<pnp-persona>](#ltpnp-personagt) - [<pnp-img>](#ltpnp-imggt) +- [<pnp-breadcrumb>](#ltpnp-breadcrumbgt) > All other web components you will see in builtin layout templates are considered **internal** and are not supported for custom use. @@ -216,4 +217,51 @@ Here are the list of all **reusable** web components you can use to customize yo | Parameter | Description | | --------- | ----------- | |**errorImage**|URL to the fallback image -|**hideOnError**|Hide image on error \ No newline at end of file +|**hideOnError**|Hide image on error + +## `` +- **Description**: Render a breadcrumb path of a SharePoint entity (file, item, folder, document library etc.). + +!["Breadcrumb component"](../assets/extensibility/web_components/breadcrumb_component.png){: .center} + +- **Usage**: + +Get started with: +```html + +``` +Use all properties: +```html + +``` +|Parameter|Description| +|--|--| +|data-path|Used for creating the breadcrumb path. Component is designed to receive `OriginalPath` or `Path` property. Property is required for rendering the breadcrumb path. `String`| +|data-site-url|Used for creating the breadcrumb path. Component is designed to receive `SPSiteURL` property. Property is required for rendering the breadcrumb path. `String`| +|data-web-url|Used for creating the breadcrumb path. Component is designed to receive `SPWebUrl` property. Property is required for rendering the breadcrumb path. `String`| +|data-entity-title|Used for creating the breadcrumb path. Component is designed to receive `Title` property. Property is required for rendering the breadcrumb path. `String`| +|data-entity-file-type|Used for creating the breadcrumb path. Component is designed to receive `FileType` property. Property is required for rendering the breadcrumb path. `String`| +|data-include-site-name|If the site name should be included in the breadcrumb items. Optional, default value `true`. `Boolean`| +|data-include-entity-name|If the entity name should be included in the breadcrumb items. If the value is set to `false`, not only is the entity name excluded from the breadcrumb path, but also the last item in the breadcrumb path is not highlighted in bold. Optional, default value `true`. `Boolean`| +|data-breadcrumb-items-as-links|If the breadcrumb items should be clickable links to the path they represent. Optional, default value `true`. `Boolean`| +|data-max-displayed-items|The maximum number of breadcrumb items to display before coalescing. If not specified, all breadcrumbs will be rendered. Optional, default value `3`. `Int`| +|data-overflow-index| Index where overflow items will be collapsed. Optional, default value `0`. `Int`| +|data-font-size|Font size of breadcrumb items. Optional, default value `12`. `Int`| \ No newline at end of file diff --git a/docs/scenarios/Setup-Results-web-part-to-show-birthdays.md b/docs/scenarios/Setup-Results-web-part-to-show-birthdays.md index fc3084d1b..2e425b112 100644 --- a/docs/scenarios/Setup-Results-web-part-to-show-birthdays.md +++ b/docs/scenarios/Setup-Results-web-part-to-show-birthdays.md @@ -1,19 +1,15 @@ -A common requirement for intranets is to show birthdays of employees. -And as the SharePoint User Profile Application has a property for birthdays, it is a natural choice to use search to show birthdays of employees. +A common requirement for intranets is to show birthdays of employees and as the SharePoint User Profile Application has a property for birthdays, it is a natural choice to use search to show birthdays of employees. In addition, at the time of writing (2023), the Graph API does not have a property for birthdays, so we can't use the Graph API to get the information. -(as of writing this (2023), the Graph API does not have a property for birthdays, so we can't use the Graph API to get the information) +The tricky part is that in the User profile application the birthday value is store is a rather unusual datatype: "date no year" - -The tricky part is that in the User profile application the birthday value is store is a rather usual datatype "date no year" - -![Birthday in the User Provisioning Service](../assets/../scenarios/assets/Setup-Results-web-part-to-show-birthdays/BirthdayUPA.png) +![Birthday in the User Provisioning Service](../scenarios/assets/Setup-Results-web-part-to-show-birthdays/BirthdayUPA.png) In my tenant the SPS-Birthday property was mapped to RefinableDate00 and the actual value in the property is 2000-[the date]: -![Managed Property value](../assets/Setup-Results-web-part-to-show-birthdays/refinabledate00.png) +![Managed Property value](../scenarios/assets/Setup-Results-web-part-to-show-birthdays/RefinableDate00.png) (use the magnificent [SP Editor tool](https://chrome.google.com/webstore/detail/sp-editor/ecblfcmjnbbgaojblcpmjoamegpbodhd) or [SP Search Query Tool](https://github.com/pnp/PnP-Tools/blob/master/Solutions/SharePoint.Search.QueryTool/README.md) to inspect the managed properties) diff --git a/docs/scenarios/index.md b/docs/scenarios/index.md index 6650f0544..d948ed2d5 100644 --- a/docs/scenarios/index.md +++ b/docs/scenarios/index.md @@ -18,21 +18,6 @@ Most search solutions require some filters (aka refiners) to allow the user to f Search verticals can be used to selectively search specific content per vertical. Using the SharePoint provider you can use result sources to limit the content returned, or you can add the required KQL in the web part itself. This sample shows how to set up multiple search verticals on different pages. -## [Scenario Create your first custom template](create-your-first-custom-template.md) - -Create your first custom template - -## [Store custom templates in SharePoint](howto-store-custom-templates-in-sharepoint.md) - -Storing custom templates as files in a SharePoint site, is great when you want to use them across sites and want some control. - -## [Edit custom templates in SharePoint](edit-custom-templates-in-sharepoint.md) - -Storing custom templates as files in a SharePoint site, is great when you want to use them across sites and want some control. - -## [Edit custom templates locally in Visual Studio Code](edit-templates-using-vscode-and-onedrive.md) - -When you have your templates in SharePoint, it is easy to setup a way to edit locally on your computer and still get the result in SharePoint almost instantly. ## [Create a search page with verticals (within the same page)](Create-a-search-page-with-verticals-within-the-same-page.md) @@ -48,17 +33,14 @@ With the Modern Search Web Parts you can create a simple and useful Department W ## [Use query rules for promoted links](Use-query-rules-for-promoted-links.md) With the Modern Search Web Parts you can show promoted links for important results. They will be configured with query rules in the SharePoint Search Admin Center. Promoted results will show users more informations and direct links about specific, predefined, terms they searching for. +## [Use query string from url for dynamic results](use-query-string-in-url.md) This scenario describes how to use query string as value in the URL from the current page. You can use URL query string parameters to build dynamic search pages. Use a library with metadata that you can use the query string parameter in the URL. - - ## [Setup Results web part to show birthdays](Setup-Results-web-part-to-show-birthdays.md) A common request in any intranet is to show birthdays of employees. This scenario describes how to use stock SharePoint search to show birthdays of employees in the search results. - ## [Setup Results web part to show work anniversaries](Setup-Results-web-part-to-show-work-anniversaries.md) - Showing the work anniversaries of employees is a common request in any intranet. This scenario describes one way to achive this using a sleight of hand trick/cheating as the search index does not contains the information we need. --- diff --git a/docs/usage/search-results/layouts/custom.md b/docs/usage/search-results/layouts/custom.md index d1022b47c..426cbfd90 100644 --- a/docs/usage/search-results/layouts/custom.md +++ b/docs/usage/search-results/layouts/custom.md @@ -1,6 +1,6 @@ The 'Custom' layout is a minimal layout to start with if you want to create your own customized UI from scratch. -You can also start from an existing layout by first selecting it, and then click `{}` next to the *Edit results tempalte* field. This will copy the selected template and allow you to make modifications as needed. +You can also start from an existing layout by first selecting it, and then click `{}` next to the *Edit results template* field. This will copy the selected template and allow you to make modifications as needed. !["Custom layout"](../../../assets/webparts/search-results/layouts/custom_layout.png){: .center} diff --git a/mkdocs.yml b/mkdocs.yml index f341171ba..86535101a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,6 +6,7 @@ nav: - Introduction: index.md - Installation: installation.md - Scenario tutorials: scenarios/index.md + - Create custom layouts: create-custom-layouts/index.md - How to contribute?: how-to-contribute.md - Building documentation: build-the-doc.md - Search Results: @@ -33,8 +34,7 @@ nav: - Custom event handlers for adaptive cards actions: extensibility/adaptivecards_customizations.md - Custom data sources: extensibility/custom_data_sources.md - Custom query modifier: extensibility/custom_query_modifications.md - - Version 3: - - Documentation: v3/index.md + theme: name: 'material' features: diff --git a/search-extensibility/package-lock.json b/search-extensibility/package-lock.json index d49a81398..66cf130e0 100644 --- a/search-extensibility/package-lock.json +++ b/search-extensibility/package-lock.json @@ -15880,9 +15880,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "dev": true, "funding": [ { @@ -43405,9 +43405,9 @@ } }, "follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "dev": true }, "for-in": { diff --git a/search-parts/package-lock.json b/search-parts/package-lock.json index b4902612f..b4a638861 100644 --- a/search-parts/package-lock.json +++ b/search-parts/package-lock.json @@ -12,6 +12,7 @@ "@fluentui/react": "8.106.4", "@fluentui/react-file-type-icons": "8.10.4", "@fluentui/react-theme-provider": "0.19.16", + "@iconify/react": "^4.1.1", "@microsoft/mgt-react": "^3.1.3", "@microsoft/mgt-sharepoint-provider": "^3.1.3", "@microsoft/sp-adaptive-card-extension-base": "1.18.2", @@ -3145,6 +3146,25 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@iconify/react": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@iconify/react/-/react-4.1.1.tgz", + "integrity": "sha512-jed14EjvKjee8mc0eoscGxlg7mSQRkwQG3iX3cPBCO7UlOjz0DtlvTqxqEcHUJGh+z1VJ31Yhu5B9PxfO0zbdg==", + "dependencies": { + "@iconify/types": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==" + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -22327,9 +22347,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "dev": true, "funding": [ { @@ -33385,18 +33405,18 @@ } }, "node_modules/remarkable": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz", - "integrity": "sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-2.0.1.tgz", + "integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==", "dependencies": { "argparse": "^1.0.10", - "autolinker": "~0.28.0" + "autolinker": "^3.11.0" }, "bin": { "remarkable": "bin/remarkable.js" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 6.0.0" } }, "node_modules/remove-bom-buffer": { @@ -39297,14 +39317,10 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, "node_modules/uglify-js": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", - "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", "devOptional": true, - "dependencies": { - "commander": "~2.19.0", - "source-map": "~0.6.1" - }, "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -39312,12 +39328,6 @@ "node": ">=0.8.0" } }, - "node_modules/uglify-js/node_modules/commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", - "devOptional": true - }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -44479,6 +44489,19 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@iconify/react": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@iconify/react/-/react-4.1.1.tgz", + "integrity": "sha512-jed14EjvKjee8mc0eoscGxlg7mSQRkwQG3iX3cPBCO7UlOjz0DtlvTqxqEcHUJGh+z1VJ31Yhu5B9PxfO0zbdg==", + "requires": { + "@iconify/types": "^2.0.0" + } + }, + "@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==" + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -49857,7 +49880,7 @@ "minimist": "^1.2.5", "neo-async": "^2.6.0", "source-map": "^0.6.1", - "uglify-js": "^3.1.4", + "uglify-js": ">3.15.5", "wordwrap": "^1.0.0" } }, @@ -59468,9 +59491,9 @@ } }, "follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "dev": true }, "for-each": { @@ -60805,7 +60828,7 @@ "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", - "uglify-js": "^3.1.4", + "uglify-js": ">3.15.5", "wordwrap": "^1.0.0" } }, @@ -61244,7 +61267,7 @@ "requires": { "handlebars-utils": "^1.0.2", "highlight.js": "10.4.1", - "remarkable": "^1.7.1" + "remarkable": "2.0.1" } }, "helper-md": { @@ -61255,7 +61278,7 @@ "ent": "^2.2.0", "extend-shallow": "^2.0.1", "fs-exists-sync": "^0.1.0", - "remarkable": "^1.6.2" + "remarkable": "2.0.1" } }, "highlight-es": { @@ -61442,7 +61465,7 @@ "he": "1.2.x", "param-case": "2.1.x", "relateurl": "0.2.x", - "uglify-js": "3.4.x" + "uglify-js": ">3.15.5" }, "dependencies": { "camel-case": { @@ -68120,9 +68143,9 @@ } }, "remarkable": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz", - "integrity": "sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-2.0.1.tgz", + "integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==", "requires": { "argparse": "^1.0.10", "autolinker": "3.14.0" @@ -72776,22 +72799,10 @@ "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, "uglify-js": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", - "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", - "devOptional": true, - "requires": { - "commander": "~2.19.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", - "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", - "devOptional": true - } - } + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "devOptional": true }, "unbox-primitive": { "version": "1.0.2", diff --git a/search-parts/package.json b/search-parts/package.json index 37154fb56..7a68265a4 100644 --- a/search-parts/package.json +++ b/search-parts/package.json @@ -16,6 +16,7 @@ "@fluentui/react": "8.106.4", "@fluentui/react-file-type-icons": "8.10.4", "@fluentui/react-theme-provider": "0.19.16", + "@iconify/react": "^4.1.1", "@microsoft/mgt-react": "^3.1.3", "@microsoft/mgt-sharepoint-provider": "^3.1.3", "@microsoft/sp-adaptive-card-extension-base": "1.18.2", @@ -101,7 +102,9 @@ "@microsoft/mgt-sharepoint-provider": "^3.1.3", "@microsoft/mgt-components": "^3.1.3", "@microsoft/mgt-element": "^3.1.3", - "@microsoft/mgt-spfx": "^3.1.3" + "@microsoft/mgt-spfx": "^3.1.3", + "remarkable" : "2.0.1", + "uglify-js" : ">3.15.5" }, "pnpm": { "overrides": { @@ -121,7 +124,9 @@ "@microsoft/mgt-sharepoint-provider": "^3.1.3", "@microsoft/mgt-components": "^3.1.3", "@microsoft/mgt-element": "^3.1.3", - "@microsoft/mgt-spfx": "^3.1.3" + "@microsoft/mgt-spfx": "^3.1.3", + "remarkable" : "2.0.1", + "uglify-js" : ">3.15.5" } } } \ No newline at end of file diff --git a/search-parts/src/components/AvailableComponents.ts b/search-parts/src/components/AvailableComponents.ts index 672a64284..b7fbe9b5b 100644 --- a/search-parts/src/components/AvailableComponents.ts +++ b/search-parts/src/components/AvailableComponents.ts @@ -22,6 +22,7 @@ import { ImageWebComponent} from './ImageComponent'; import { ItemSelectionWebComponent } from './ItemSelectionComponent'; import { FilterSearchBoxWebComponent } from './filters/FilterSearchBoxComponent'; import { FilterValueOperatorWebComponent } from './filters/FilterValueOperatorComponent'; +import { SpoPathBreadcrumbWebComponent } from './SpoPathBreadcrumbComponent'; import { SortWebComponent } from './SortComponent'; export class AvailableComponents { @@ -122,6 +123,10 @@ export class AvailableComponents { componentName: "pnp-filteroperator", componentClass: FilterValueOperatorWebComponent }, + { + componentName: "pnp-breadcrumb", + componentClass: SpoPathBreadcrumbWebComponent + }, { componentName: 'pnp-sortfield', componentClass: SortWebComponent diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx new file mode 100644 index 000000000..87c12b71b --- /dev/null +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -0,0 +1,216 @@ +import * as React from 'react'; +import { BaseWebComponent } from '@pnp/modern-search-extensibility'; +import * as ReactDOM from 'react-dom'; +import { ITheme, Breadcrumb, IBreadcrumbItem } from '@fluentui/react'; +import { IReadonlyTheme } from '@microsoft/sp-component-base'; + +export interface IBreadcrumbProps { + /** + * Path from which breadcrumb items are formed from. Ideally use the path property of a SharePoint document, list item, folder, etc. + */ + path?: string; + + /** + * The SharePoint site URL from which the entity path originates from. + */ + siteUrl?: string; + + /** + * The SharePoint web URL from which the entity path originates from. + */ + webUrl?: string; + + /** + * Title of the entity for which the breadcrumb path is generated. + */ + entityTitle?: string; + + /** + * File type of the entity for which the breadcrumb path is generated. + */ + entityFileType?: string; + + /** + * Determines whether the site name should be included in the breadcrumb items. + */ + includeSiteName?: boolean; + + /** + * Determines whether the entity name should be included in the breadcrumb items. + */ + includeEntityName?: boolean; + + /** + * Determines whether the breadcrumb items should be clickable links to the path they represent. + */ + breadcrumbItemsAsLinks?: boolean; + + /** + * The maximum number of breadcrumb items to display before coalescing. If not specified, all breadcrumbs will be rendered. + */ + maxDisplayedItems?: number; + + /** + * Index where overflow items will be collapsed. + */ + overflowIndex?: number; + + /** + * Font size of breadcrumb items. + */ + fontSize?: number; + + /** + * The current theme settings. + */ + themeVariant?: IReadonlyTheme; +} + +export interface IBreadcrumbState { } + +// For example list items and images have DispForm.aspx?ID=xxxx in their path. This regex is used to check if the path contains DispForm.aspx?ID=xxxx +const DISP_FORM_REGEX = /DispForm\.aspx\?ID=\d+/; + +export class SpoPathBreadcrumb extends React.Component { + + static defaultProps = { + includeSiteName: true, + includeEntityName: true, + breadcrumbItemsAsLinks: true, + maxDisplayedItems: 3, + overflowIndex: 0, + fontSize: 12 + }; + + public render() { + const { path, siteUrl, webUrl, entityTitle, entityFileType, includeSiteName, includeEntityName, breadcrumbItemsAsLinks, maxDisplayedItems, overflowIndex, fontSize, themeVariant } = this.props; + + const commonStyles = { + fontSize: `${fontSize}px`, + padding: '1px', + selectors: { + // If the entity name is not included in path then reset the formatting of last breadcrumb item. + ...( !includeEntityName ? { '&:last-child': { fontWeight: 'unset !important', color: 'unset !important' } } : {} ) + }, + }; + + const breadcrumbStyles = { + root: { margin: '0' }, + item: { ...commonStyles }, + itemLink: { + ...commonStyles, + selectors: { + '&:hover': { backgroundColor: 'unset', textDecoration: 'underline'}, + ...commonStyles.selectors + }, + }, + }; + + const breadcrumbItems = this.validateEntityProps(path, siteUrl, entityTitle) ? this.getBreadcrumbItems(path, siteUrl, webUrl, entityTitle, entityFileType, includeSiteName, includeEntityName, breadcrumbItemsAsLinks) : undefined; + + return ( + <> + {breadcrumbItems !== undefined && + + } + + ) + } + + private validateEntityProps = (path: string, siteUrl: string, entityTitle: string): boolean => { + return path !== undefined && path !== null + && siteUrl !== undefined && siteUrl !== null + && entityTitle !== undefined && entityTitle !== null; + } + + private getBreadcrumbItems = (path: string, siteUrl: string, webUrl: string, entityTitle: string, entityFileType: string, includeSiteName: boolean, includeEntityName: boolean, breadcrumbItemsAsLinks: boolean): IBreadcrumbItem[] => { + // Example: + // webUrl: https://contoso.sharepoint.com/sites/sitename/subsite + // path: https://contoso.sharepoint.com/sites/sitename/subsite/Shared Documents/Document.docx + + // All entities don't have a webUrl property (e.g. list and doc libs). Therefore webUrl is not part of props validation and undefined/null check is needed here. + if (webUrl === undefined || webUrl === null) webUrl = siteUrl; + + const frags = webUrl.split('/'); + // frags: ["https:", "", "contoso.sharepoint.com", "sites", "sitename", "subsite"] + + const isRootSite = siteUrl.split('/').length === 3; + // Root site only contains parts: ["https:", "", "contoso.sharepoint.com"] + + const basePath = isRootSite ? frags.slice(0, 3).join('/') : frags.slice(0, 4).join('/'); + // basePath: https://contoso.sharepoint.com/sites + // Root site base path: https://contoso.sharepoint.com + + // If includeSiteName is true, then only remove the base path from the original path. In example first items of path are "sitename", "subsite" + // If includeSiteName is false, then remove the whole webUrl from the original path. In example first item of path is "Shared Documents" + const replacePath = includeSiteName ? basePath : webUrl; + const parts = path.replace(replacePath, '').split('/').filter(part => part); + + // If includeEntityName is false, then remove the last part of the path. In example remove Document.doxc. Last part is a title of the entity for which the breadcrumb path is generated + if (!includeEntityName) parts.pop(); + + const breadcrumbItems: IBreadcrumbItem[] = parts.map((part, index) => { + // If the current part is the last part of the path and it contains DispForm.aspx?ID=xxxx, then set the breadcrumb item text as entity title + optionally file type + const itemText = index+1 === parts.length && includeEntityName && DISP_FORM_REGEX.test(part) ? + entityFileType !== undefined && entityFileType !== null ? + `${entityTitle}.${entityFileType}` + : entityTitle + : part; + + const item: IBreadcrumbItem = { + text: itemText, + key: `item${index + 1}` + }; + + // If breadcrumbItemsAsLinks is true, then add the href property to the breadcrumb item + if (breadcrumbItemsAsLinks) { + const relativePath = parts.slice(0, index + 1).join('/'); + + // If includeSiteName is true, then the href is the base path + the current path part because parts contain the site name and possible subsite(s) + // If includeSiteName is false, then the href is the webUrl + the current path part because parts do not contain the site name and possible subsite(s) + item.href = includeSiteName ? `${basePath}/${relativePath}` : `${webUrl}/${relativePath}`; + } + + return item; + }); + + // If entity is located on the root site, then add the root site as first breadcrumb item if includeSiteName is true + if (isRootSite && includeSiteName) { + const item: IBreadcrumbItem = { + text: 'Home', + key: 'home' + }; + + if (breadcrumbItemsAsLinks) item.href = siteUrl; + + breadcrumbItems.unshift(item); + } + + return breadcrumbItems; + } +} + +export class SpoPathBreadcrumbWebComponent extends BaseWebComponent { + + public constructor() { + super(); + } + + public async connectedCallback() { + let props = this.resolveAttributes(); + const spoPathBreadcrumb =
; + ReactDOM.render(spoPathBreadcrumb, this); + } + + protected onDispose(): void { + ReactDOM.unmountComponentAtNode(this); + } +} diff --git a/search-parts/src/components/filters/FilterCheckBoxComponent.tsx b/search-parts/src/components/filters/FilterCheckBoxComponent.tsx index b8f1285c1..ee026861e 100644 --- a/search-parts/src/components/filters/FilterCheckBoxComponent.tsx +++ b/search-parts/src/components/filters/FilterCheckBoxComponent.tsx @@ -101,6 +101,7 @@ export class FilterCheckBoxComponent extends React.Component; } else { renderInput = { + this.properties.title = value; + }, + themeVariant: this._themeVariant, + className: commonStyles.wpTitle + } } as ISearchBoxContainerProps); // Error message @@ -751,7 +761,7 @@ export default class SearchBoxWebPart extends BaseWebPart { return provider.key === providerKey; }); // Can only have one data source instance per key @@ -860,7 +870,7 @@ export default class SearchBoxWebPart extends BaseWebPart { @@ -103,7 +104,7 @@ export default class SearchBoxContainer extends React.Component { + + let renderTitle: JSX.Element = null; + + // WebPart title + renderTitle = ; + let renderErrorMessage: JSX.Element = null; if (this.state.errorMessage) { @@ -167,6 +178,7 @@ export default class SearchBoxContainer extends React.Component {renderErrorMessage} + {renderTitle} {renderSearchBox} );