From 30789674226ec4b03ffb0a5e0ef74553690463e9 Mon Sep 17 00:00:00 2001 From: lauribohm Date: Mon, 15 Jan 2024 21:26:08 +0200 Subject: [PATCH 01/19] add breadcrumb component --- .../src/components/AvailableComponents.ts | 5 + .../FilePathBreadcrumbComponent.tsx | 158 ++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 search-parts/src/components/FilePathBreadcrumbComponent.tsx diff --git a/search-parts/src/components/AvailableComponents.ts b/search-parts/src/components/AvailableComponents.ts index 672a64284..29d8e4359 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 { FilePathBreadcrumbWebComponent } from './FilePathBreadcrumbComponent'; import { SortWebComponent } from './SortComponent'; export class AvailableComponents { @@ -122,6 +123,10 @@ export class AvailableComponents { componentName: "pnp-filteroperator", componentClass: FilterValueOperatorWebComponent }, + { + componentName: "pnp-breadcrumb", + componentClass: FilePathBreadcrumbWebComponent + }, { componentName: 'pnp-sortfield', componentClass: SortWebComponent diff --git a/search-parts/src/components/FilePathBreadcrumbComponent.tsx b/search-parts/src/components/FilePathBreadcrumbComponent.tsx new file mode 100644 index 000000000..cf9a8e648 --- /dev/null +++ b/search-parts/src/components/FilePathBreadcrumbComponent.tsx @@ -0,0 +1,158 @@ +import * as React from 'react'; +import { BaseWebComponent } from '@pnp/modern-search-extensibility'; +import * as ReactDOM from 'react-dom'; +import { ITheme } from '@fluentui/react'; +import { Breadcrumb, IBreadcrumbItem } from '@fluentui/react'; +import { IReadonlyTheme } from '@microsoft/sp-component-base'; + +export interface IBreadcrumbProps { + /** + * Path from which breadcrumbs are formed from. This should ideally be the OriginalPath property of a SharePoint document, list item, folder, etc. + */ + path?: string; + + /** + * Determines whether the site name should be included in the breadcrumbs. + */ + includeSiteName?: boolean; + + /** + * Determines whether the item name should be included in the breadcrumbs. + */ + includeItemName?: boolean; + + /** + * Determines whether the breadcrumbs should be clickable links to the path they represent. + */ + breadcrumbItemsAsLinks?: boolean; + + /** + * The maximum number of breadcrumbs 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 for breadcrumbs. + */ + fontSize?: number; + + /** + * The current theme settings. + */ + themeVariant?: IReadonlyTheme; +} + +export interface IBreadcrumbState { } + +const SITE_REGEX = /https:\/\/\w+\.sharepoint\.com\/sites\//; + +export class FilePathBreadcrumb extends React.Component { + + static defaultProps = { + includeSiteName: true, + includeItemName: true, + breadcrumbItemsAsLinks: true, + maxDisplayedItems: 3, + overflowIndex: 0, + fontSize: 12 + }; + + public render() { + const { includeSiteName, includeItemName, breadcrumbItemsAsLinks, maxDisplayedItems, overflowIndex, fontSize, path, themeVariant } = this.props; + + const breadcrumbStyles = { + root: { + margin: '0', + }, + item: { + fontSize: `${fontSize}px`, + padding: '1px', + }, + itemLink: { + fontSize: `${fontSize}px`, + padding: '1px', + selectors: { + ':hover': { + backgroundColor: 'unset' + }, + }, + }, + }; + + return ( + <> + {path !== undefined && this.validateFilePath(path) && + + } + + ) + } + + // Validate that item path is SharePoint item path and not for example personal OneDrive item path. + // For example: + // SharePoint: https://m365xXYZ.sharepoint.com/sites/dev/SomeFolder/SomeFile.docx + // OneDrive: https://m365xXYZ-my.sharepoint.com/personal/admin_m365xXYZ_onmicrosoft_com/Documents + private validateFilePath = (path: string): boolean => { + return SITE_REGEX.test(path); + } + + private getBreadcrumbItems = (path: string, includeSiteName: boolean, includeItemName: boolean, breadcrumbItemsAsLinks: boolean): IBreadcrumbItem[] => { + const frags = path.split('/'); + const index = frags.indexOf('sites'); + const basePath = frags.slice(0, index + 1).join('/'); + + const breadcrumbNodes = this.getBreadcrumbNodes(frags, index, includeSiteName, includeItemName); + + const breadcrumbItems = breadcrumbNodes.map((frag, index) => { + const item: IBreadcrumbItem = { + text: frag, + key: `item${index + 1}`, + isCurrentItem: false + }; + + if (breadcrumbItemsAsLinks) { + item.href = basePath + '/' + breadcrumbNodes.slice(0, index + 1).join('/'); + } + + return item; + }); + + return breadcrumbItems; + } + + private getBreadcrumbNodes = (frags: string[], index: number, includeSiteName: boolean, includeItemName: boolean) => { + const breadcrumbNodes = includeSiteName ? frags.slice(index + 1) : frags.slice(index + 2); + + return includeItemName ? breadcrumbNodes : breadcrumbNodes.slice(0, breadcrumbNodes.length - 1); + } +} + +export class FilePathBreadcrumbWebComponent extends BaseWebComponent { + + public constructor() { + super(); + } + + public async connectedCallback() { + let props = this.resolveAttributes(); + const filePathBreadcrumb =
; + ReactDOM.render(filePathBreadcrumb, this); + } + + protected onDispose(): void { + ReactDOM.unmountComponentAtNode(this); + } +} \ No newline at end of file From 55133684421952f1b818ea1b1b1ba65463717d79 Mon Sep 17 00:00:00 2001 From: lauribohm Date: Mon, 15 Jan 2024 22:07:26 +0200 Subject: [PATCH 02/19] rename classes and props --- .../src/components/AvailableComponents.ts | 4 +-- ...ent.tsx => SpoPathBreadcrumbComponent.tsx} | 36 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) rename search-parts/src/components/{FilePathBreadcrumbComponent.tsx => SpoPathBreadcrumbComponent.tsx} (71%) diff --git a/search-parts/src/components/AvailableComponents.ts b/search-parts/src/components/AvailableComponents.ts index 29d8e4359..b7fbe9b5b 100644 --- a/search-parts/src/components/AvailableComponents.ts +++ b/search-parts/src/components/AvailableComponents.ts @@ -22,7 +22,7 @@ import { ImageWebComponent} from './ImageComponent'; import { ItemSelectionWebComponent } from './ItemSelectionComponent'; import { FilterSearchBoxWebComponent } from './filters/FilterSearchBoxComponent'; import { FilterValueOperatorWebComponent } from './filters/FilterValueOperatorComponent'; -import { FilePathBreadcrumbWebComponent } from './FilePathBreadcrumbComponent'; +import { SpoPathBreadcrumbWebComponent } from './SpoPathBreadcrumbComponent'; import { SortWebComponent } from './SortComponent'; export class AvailableComponents { @@ -125,7 +125,7 @@ export class AvailableComponents { }, { componentName: "pnp-breadcrumb", - componentClass: FilePathBreadcrumbWebComponent + componentClass: SpoPathBreadcrumbWebComponent }, { componentName: 'pnp-sortfield', diff --git a/search-parts/src/components/FilePathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx similarity index 71% rename from search-parts/src/components/FilePathBreadcrumbComponent.tsx rename to search-parts/src/components/SpoPathBreadcrumbComponent.tsx index cf9a8e648..cff43153a 100644 --- a/search-parts/src/components/FilePathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -7,27 +7,27 @@ import { IReadonlyTheme } from '@microsoft/sp-component-base'; export interface IBreadcrumbProps { /** - * Path from which breadcrumbs are formed from. This should ideally be the OriginalPath property of a SharePoint document, list item, folder, etc. + * Path from which breadcrumb items are formed from. Ideally use the OriginalPath property of a SharePoint document, list item, folder, etc. */ path?: string; /** - * Determines whether the site name should be included in the breadcrumbs. + * Determines whether the site name should be included in the breadcrumb items. */ includeSiteName?: boolean; /** - * Determines whether the item name should be included in the breadcrumbs. + * Determines whether the entity name should be included in the breadcrumb items. */ - includeItemName?: boolean; + includeEntityName?: boolean; /** - * Determines whether the breadcrumbs should be clickable links to the path they represent. + * Determines whether the breadcrumb items should be clickable links to the path they represent. */ breadcrumbItemsAsLinks?: boolean; /** - * The maximum number of breadcrumbs to display before coalescing. If not specified, all breadcrumbs will be rendered. + * The maximum number of breadcrumb items to display before coalescing. If not specified, all breadcrumbs will be rendered. */ maxDisplayedItems?: number; @@ -37,7 +37,7 @@ export interface IBreadcrumbProps { overflowIndex?: number; /** - * Font size for breadcrumbs. + * Font size of breadcrumb items. */ fontSize?: number; @@ -51,7 +51,7 @@ export interface IBreadcrumbState { } const SITE_REGEX = /https:\/\/\w+\.sharepoint\.com\/sites\//; -export class FilePathBreadcrumb extends React.Component { +export class SpoPathBreadcrumb extends React.Component { static defaultProps = { includeSiteName: true, @@ -63,7 +63,7 @@ export class FilePathBreadcrumb extends React.Component {path !== undefined && this.validateFilePath(path) && { + private getBreadcrumbItems = (path: string, includeSiteName: boolean, includeEntityName: boolean, breadcrumbItemsAsLinks: boolean): IBreadcrumbItem[] => { const frags = path.split('/'); const index = frags.indexOf('sites'); const basePath = frags.slice(0, index + 1).join('/'); - const breadcrumbNodes = this.getBreadcrumbNodes(frags, index, includeSiteName, includeItemName); + const breadcrumbNodes = this.getBreadcrumbNodes(frags, index, includeSiteName, includeEntityName); const breadcrumbItems = breadcrumbNodes.map((frag, index) => { const item: IBreadcrumbItem = { @@ -133,14 +133,14 @@ export class FilePathBreadcrumb extends React.Component { + private getBreadcrumbNodes = (frags: string[], index: number, includeSiteName: boolean, includeEntityName: boolean) => { const breadcrumbNodes = includeSiteName ? frags.slice(index + 1) : frags.slice(index + 2); - return includeItemName ? breadcrumbNodes : breadcrumbNodes.slice(0, breadcrumbNodes.length - 1); + return includeEntityName ? breadcrumbNodes : breadcrumbNodes.slice(0, breadcrumbNodes.length - 1); } } -export class FilePathBreadcrumbWebComponent extends BaseWebComponent { +export class SpoPathBreadcrumbWebComponent extends BaseWebComponent { public constructor() { super(); @@ -148,8 +148,8 @@ export class FilePathBreadcrumbWebComponent extends BaseWebComponent { public async connectedCallback() { let props = this.resolveAttributes(); - const filePathBreadcrumb =
; - ReactDOM.render(filePathBreadcrumb, this); + const spoPathBreadcrumb =
; + ReactDOM.render(spoPathBreadcrumb, this); } protected onDispose(): void { From 893c730308c98a914ac45e2847963646173a0596 Mon Sep 17 00:00:00 2001 From: lauribohm <46068076+lauribohm@users.noreply.github.com> Date: Mon, 15 Jan 2024 22:27:14 +0200 Subject: [PATCH 03/19] remove duplicate import --- search-parts/src/components/SpoPathBreadcrumbComponent.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index cff43153a..29cc2883a 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -1,8 +1,7 @@ import * as React from 'react'; import { BaseWebComponent } from '@pnp/modern-search-extensibility'; import * as ReactDOM from 'react-dom'; -import { ITheme } from '@fluentui/react'; -import { Breadcrumb, IBreadcrumbItem } from '@fluentui/react'; +import { ITheme, Breadcrumb, IBreadcrumbItem } from '@fluentui/react'; import { IReadonlyTheme } from '@microsoft/sp-component-base'; export interface IBreadcrumbProps { @@ -155,4 +154,4 @@ export class SpoPathBreadcrumbWebComponent extends BaseWebComponent { protected onDispose(): void { ReactDOM.unmountComponentAtNode(this); } -} \ No newline at end of file +} From 252dba01475cf4a087509e22c4f3c51eaac61c19 Mon Sep 17 00:00:00 2001 From: lauribohm Date: Tue, 16 Jan 2024 14:38:56 +0200 Subject: [PATCH 04/19] chnages to regex and breadcrumb creation --- .../components/SpoPathBreadcrumbComponent.tsx | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index 29cc2883a..56a21ffa0 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -8,7 +8,12 @@ export interface IBreadcrumbProps { /** * Path from which breadcrumb items are formed from. Ideally use the OriginalPath property of a SharePoint document, list item, folder, etc. */ - path?: string; + originalPath?: string; + + /** + * The SharePoint site URL from which the path originates from. Used for creating links without need to struggle with all possible domain name variations. + */ + spWebUrl?: string; /** * Determines whether the site name should be included in the breadcrumb items. @@ -48,21 +53,26 @@ export interface IBreadcrumbProps { export interface IBreadcrumbState { } -const SITE_REGEX = /https:\/\/\w+\.sharepoint\.com\/sites\//; +// This regular expression is used to validate SharePoint paths. The path must include sharepoint.com/sites/ or sharepoint.com/teams/. +// This method will not recognize OneDrive paths, as they are not compatible with this component. +// OneDrive paths are excluded because they are not user-friendly and thus, inappropriate for breadcrumb navigation. +// The usage of domain extensions such as *.mcas.ms, *.mcas-gov.us, or *.mcas-gov.ms does not impact this validation as they are not part of the OriginalPath property. +// It is recommended to use the OriginalPath property with this validation. +const SPO_REGEX = /https:\/\/.*\.sharepoint\.com\/(sites|teams)\//; export class SpoPathBreadcrumb extends React.Component { static defaultProps = { includeSiteName: true, - includeItemName: true, - breadcrumbItemsAsLinks: true, + includeEntityName: true, + breadcrumbItemsAsLinks: false, maxDisplayedItems: 3, overflowIndex: 0, fontSize: 12 }; public render() { - const { includeSiteName, includeEntityName, breadcrumbItemsAsLinks, maxDisplayedItems, overflowIndex, fontSize, path, themeVariant } = this.props; + const { originalPath, spWebUrl, includeSiteName, includeEntityName, breadcrumbItemsAsLinks, maxDisplayedItems, overflowIndex, fontSize, themeVariant } = this.props; const breadcrumbStyles = { root: { @@ -85,9 +95,9 @@ export class SpoPathBreadcrumb extends React.Component - {path !== undefined && this.validateFilePath(path) && + {originalPath !== undefined && spWebUrl !== undefined && this.validateFilePath(originalPath) && { - return SITE_REGEX.test(path); + return SPO_REGEX.test(path); } - private getBreadcrumbItems = (path: string, includeSiteName: boolean, includeEntityName: boolean, breadcrumbItemsAsLinks: boolean): IBreadcrumbItem[] => { - const frags = path.split('/'); - const index = frags.indexOf('sites'); - const basePath = frags.slice(0, index + 1).join('/'); - - const breadcrumbNodes = this.getBreadcrumbNodes(frags, index, includeSiteName, includeEntityName); - - const breadcrumbItems = breadcrumbNodes.map((frag, index) => { + private getBreadcrumbItems = (spWebUrl: string, originalPath: string, includeSiteName: boolean, includeEntityName: boolean, breadcrumbItemsAsLinks: boolean): IBreadcrumbItem[] => { + const frags = spWebUrl.split('/'); + const basePath = frags.slice(0, 4).join('/'); + + const parts = originalPath.replace(basePath, '').split('/').filter(part => part); + if (!includeSiteName) parts.shift(); + if (!includeEntityName) parts.pop(); + + const breadcrumbItems: IBreadcrumbItem[] = parts.map((part, index) => { const item: IBreadcrumbItem = { - text: frag, - key: `item${index + 1}`, - isCurrentItem: false + text: part, + key: `item${index + 1}` }; - if (breadcrumbItemsAsLinks) { - item.href = basePath + '/' + breadcrumbNodes.slice(0, index + 1).join('/'); - } - + if (breadcrumbItemsAsLinks) item.href = `${basePath}/${parts.slice(0, index + 1).join('/')}` + return item; - }); - + }); + return breadcrumbItems; } - - private getBreadcrumbNodes = (frags: string[], index: number, includeSiteName: boolean, includeEntityName: boolean) => { - const breadcrumbNodes = includeSiteName ? frags.slice(index + 1) : frags.slice(index + 2); - - return includeEntityName ? breadcrumbNodes : breadcrumbNodes.slice(0, breadcrumbNodes.length - 1); - } } export class SpoPathBreadcrumbWebComponent extends BaseWebComponent { From 6461f325acb2db4bae93d7ff40c9c9bf64ba4cdd Mon Sep 17 00:00:00 2001 From: lauribohm Date: Tue, 16 Jan 2024 15:19:10 +0200 Subject: [PATCH 05/19] add conditional styling for breadcrumb last item --- .../components/SpoPathBreadcrumbComponent.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index 56a21ffa0..004515887 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -74,21 +74,25 @@ export class SpoPathBreadcrumb extends React.Component Date: Tue, 16 Jan 2024 15:19:36 +0200 Subject: [PATCH 06/19] change prop default value --- search-parts/src/components/SpoPathBreadcrumbComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index 004515887..f9e84f015 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -65,7 +65,7 @@ export class SpoPathBreadcrumb extends React.Component Date: Tue, 16 Jan 2024 17:44:55 +0200 Subject: [PATCH 07/19] added documentation --- .../web_components/breadcrumb_component.png | Bin 0 -> 12845 bytes docs/extensibility/web_components_list.md | 41 +++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 docs/assets/extensibility/web_components/breadcrumb_component.png 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 0000000000000000000000000000000000000000..f1dd1b828061e328fb6845ce81c5c8f550fcc980 GIT binary patch literal 12845 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 literal 0 HcmV?d00001 diff --git a/docs/extensibility/web_components_list.md b/docs/extensibility/web_components_list.md index c831c6e19..e778b4d51 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,42 @@ 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 path is only rendered for items in SharePoint. + +!["Breadcrumb component"](../assets/extensibility/web_components/breadcrumb_component.png){: .center} + +- **Usage**: + +Get started with: +```html + +``` +Use all properties: +```html + +``` +|Parameter|Description| +|--|--| +|data-original-path|Used for creating the breadcrumb path. Component is designed to receive `OriginalPath` property. Property is required for rendering the breadcrumb path. `String`| +|data-sp-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-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 From ecc040102e68cde2742b63ebaeebc2753fdb1584 Mon Sep 17 00:00:00 2001 From: lauribohm Date: Tue, 16 Jan 2024 23:55:29 +0200 Subject: [PATCH 08/19] removed regex check --- .../components/SpoPathBreadcrumbComponent.tsx | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index f9e84f015..e29f7a63d 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -11,7 +11,7 @@ export interface IBreadcrumbProps { originalPath?: string; /** - * The SharePoint site URL from which the path originates from. Used for creating links without need to struggle with all possible domain name variations. + * The SharePoint site URL from which the entity path originates from. */ spWebUrl?: string; @@ -53,13 +53,6 @@ export interface IBreadcrumbProps { export interface IBreadcrumbState { } -// This regular expression is used to validate SharePoint paths. The path must include sharepoint.com/sites/ or sharepoint.com/teams/. -// This method will not recognize OneDrive paths, as they are not compatible with this component. -// OneDrive paths are excluded because they are not user-friendly and thus, inappropriate for breadcrumb navigation. -// The usage of domain extensions such as *.mcas.ms, *.mcas-gov.us, or *.mcas-gov.ms does not impact this validation as they are not part of the OriginalPath property. -// It is recommended to use the OriginalPath property with this validation. -const SPO_REGEX = /https:\/\/.*\.sharepoint\.com\/(sites|teams)\//; - export class SpoPathBreadcrumb extends React.Component { static defaultProps = { @@ -84,9 +77,7 @@ export class SpoPathBreadcrumb extends React.Component - {originalPath !== undefined && spWebUrl !== undefined && this.validateFilePath(originalPath) && + {this.validateFilePath(originalPath, spWebUrl) && { - return SPO_REGEX.test(path); + private validateFilePath = (originalPath: string, spWebUrl: string): boolean => { + return originalPath !== undefined && originalPath !== null && spWebUrl !== undefined && spWebUrl !== null; } private getBreadcrumbItems = (spWebUrl: string, originalPath: string, includeSiteName: boolean, includeEntityName: boolean, breadcrumbItemsAsLinks: boolean): IBreadcrumbItem[] => { From 37e7db6be120a2746ca3c88b8d81a17ed8adaa27 Mon Sep 17 00:00:00 2001 From: lauribohm Date: Wed, 17 Jan 2024 10:25:44 +0200 Subject: [PATCH 09/19] modifications in creating the breadcrumb --- .../components/SpoPathBreadcrumbComponent.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index e29f7a63d..ed58c754e 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -110,11 +110,20 @@ export class SpoPathBreadcrumb extends React.Component { + // spWebUrl: https://contoso.sharepoint.com/sites/sitename/subsite + // originalPath: https://contoso.sharepoint.com/sites/sitename/subsite/Shared Documents/Document.docx + const frags = spWebUrl.split('/'); + // frags: ["https:", "", "contoso.sharepoint.com", "sites", "sitename", "subsite"] + const basePath = frags.slice(0, 4).join('/'); - - const parts = originalPath.replace(basePath, '').split('/').filter(part => part); - if (!includeSiteName) parts.shift(); + // basePath: https://contoso.sharepoint.com/sites + + // If includeSiteName is true, then only remove the base path from the original path so first items of path are "sitename", "subsite" + // If includeSiteName is false, then remove the whole spWebUrl from the original path so first item of path is "Shared Documents" + const parts = includeSiteName ? originalPath.replace(basePath, '').split('/').filter(part => part) : originalPath.replace(spWebUrl, '').split('/').filter(part => part); + + // If includeEntityName is false, then remove the last part of the path e.g. Document.doxc. Last part is the entity for which the breadcrumb is generated. if (!includeEntityName) parts.pop(); const breadcrumbItems: IBreadcrumbItem[] = parts.map((part, index) => { @@ -123,7 +132,10 @@ export class SpoPathBreadcrumb extends React.Component Date: Wed, 17 Jan 2024 11:11:01 +0200 Subject: [PATCH 10/19] handle overflow index out of range --- .../src/components/SpoPathBreadcrumbComponent.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index ed58c754e..d71da010f 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -88,13 +88,15 @@ export class SpoPathBreadcrumb extends React.Component - {this.validateFilePath(originalPath, spWebUrl) && + {items !== undefined && part) : originalPath.replace(spWebUrl, '').split('/').filter(part => part); - + // If includeEntityName is false, then remove the last part of the path e.g. Document.doxc. Last part is the entity for which the breadcrumb is generated. if (!includeEntityName) parts.pop(); From d3db91b69fb694117fb823eacce3fbb3b679d3f4 Mon Sep 17 00:00:00 2001 From: lauribohm Date: Wed, 17 Jan 2024 11:12:49 +0200 Subject: [PATCH 11/19] clean code --- .../components/SpoPathBreadcrumbComponent.tsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index d71da010f..7273e60be 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -88,15 +88,15 @@ export class SpoPathBreadcrumb extends React.Component - {items !== undefined && + {breadcrumbItems !== undefined && part) : originalPath.replace(spWebUrl, '').split('/').filter(part => part); + const replacePath = includeSiteName ? basePath : spWebUrl; + const parts = originalPath.replace(replacePath, '').split('/').filter(part => part); // If includeEntityName is false, then remove the last part of the path e.g. Document.doxc. Last part is the entity for which the breadcrumb is generated. if (!includeEntityName) parts.pop(); @@ -135,9 +136,13 @@ export class SpoPathBreadcrumb extends React.Component Date: Wed, 17 Jan 2024 15:31:14 +0200 Subject: [PATCH 12/19] add root site handling and new property for that --- .../components/SpoPathBreadcrumbComponent.tsx | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index 7273e60be..6a8acc720 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -13,6 +13,11 @@ export interface IBreadcrumbProps { /** * The SharePoint site URL from which the entity path originates from. */ + spSiteUrl?: string; + + /** + * The SharePoint web URL from which the entity path originates from. + */ spWebUrl?: string; /** @@ -65,7 +70,11 @@ export class SpoPathBreadcrumb extends React.Component @@ -107,25 +116,36 @@ export class SpoPathBreadcrumb extends React.Component { - return originalPath !== undefined && originalPath !== null && spWebUrl !== undefined && spWebUrl !== null; + private validateFilePath = (originalPath: string, spSiteUrl: string, spWebUrl: string): boolean => { + return originalPath !== undefined && originalPath !== null + && spSiteUrl !== undefined && spSiteUrl !== null + && spWebUrl !== undefined && spWebUrl !== null; } - private getBreadcrumbItems = (spWebUrl: string, originalPath: string, includeSiteName: boolean, includeEntityName: boolean, breadcrumbItemsAsLinks: boolean): IBreadcrumbItem[] => { + private getBreadcrumbItems = (originalPath: string, spSiteUrl: string, spWebUrl: string, includeSiteName: boolean, includeEntityName: boolean, breadcrumbItemsAsLinks: boolean): IBreadcrumbItem[] => { + // TODO: remove extensive commenting once tested and working + + // Example: // spWebUrl: https://contoso.sharepoint.com/sites/sitename/subsite // originalPath: https://contoso.sharepoint.com/sites/sitename/subsite/Shared Documents/Document.docx const frags = spWebUrl.split('/'); // frags: ["https:", "", "contoso.sharepoint.com", "sites", "sitename", "subsite"] - - const basePath = frags.slice(0, 4).join('/'); + + const isRootSite = spSiteUrl.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 so first items of path are "sitename", "subsite" // If includeSiteName is false, then remove the whole spWebUrl from the original path so first item of path is "Shared Documents" const replacePath = includeSiteName ? basePath : spWebUrl; const parts = originalPath.replace(replacePath, '').split('/').filter(part => part); + console.log('parts', parts); + // If includeEntityName is false, then remove the last part of the path e.g. Document.doxc. Last part is the entity for which the breadcrumb is generated. if (!includeEntityName) parts.pop(); @@ -145,7 +165,10 @@ export class SpoPathBreadcrumb extends React.Component Date: Wed, 17 Jan 2024 15:31:23 +0200 Subject: [PATCH 13/19] update documentation --- docs/extensibility/web_components_list.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/extensibility/web_components_list.md b/docs/extensibility/web_components_list.md index e778b4d51..213140c56 100644 --- a/docs/extensibility/web_components_list.md +++ b/docs/extensibility/web_components_list.md @@ -230,6 +230,7 @@ Get started with: ```html ``` @@ -237,6 +238,7 @@ Use all properties: ```html Date: Wed, 17 Jan 2024 15:53:05 +0200 Subject: [PATCH 14/19] clean code --- search-parts/src/components/SpoPathBreadcrumbComponent.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index 6a8acc720..3175af08f 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -124,7 +124,7 @@ export class SpoPathBreadcrumb extends React.Component { // TODO: remove extensive commenting once tested and working - + // Example: // spWebUrl: https://contoso.sharepoint.com/sites/sitename/subsite // originalPath: https://contoso.sharepoint.com/sites/sitename/subsite/Shared Documents/Document.docx @@ -144,8 +144,6 @@ export class SpoPathBreadcrumb extends React.Component part); - console.log('parts', parts); - // If includeEntityName is false, then remove the last part of the path e.g. Document.doxc. Last part is the entity for which the breadcrumb is generated. if (!includeEntityName) parts.pop(); @@ -168,7 +166,7 @@ export class SpoPathBreadcrumb extends React.Component Date: Wed, 17 Jan 2024 15:56:23 +0200 Subject: [PATCH 15/19] remove logging --- search-parts/src/components/SpoPathBreadcrumbComponent.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index 3175af08f..284bf40f8 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -72,10 +72,6 @@ export class SpoPathBreadcrumb extends React.Component Date: Thu, 25 Jan 2024 09:32:17 +0200 Subject: [PATCH 16/19] rename props --- .../components/SpoPathBreadcrumbComponent.tsx | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index 284bf40f8..217783186 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -6,19 +6,19 @@ import { IReadonlyTheme } from '@microsoft/sp-component-base'; export interface IBreadcrumbProps { /** - * Path from which breadcrumb items are formed from. Ideally use the OriginalPath property of a SharePoint document, list item, folder, etc. + * Path from which breadcrumb items are formed from. Ideally use the path property of a SharePoint document, list item, folder, etc. */ - originalPath?: string; + path?: string; /** * The SharePoint site URL from which the entity path originates from. */ - spSiteUrl?: string; + siteUrl?: string; /** * The SharePoint web URL from which the entity path originates from. */ - spWebUrl?: string; + webUrl?: string; /** * Determines whether the site name should be included in the breadcrumb items. @@ -70,7 +70,7 @@ export class SpoPathBreadcrumb extends React.Component @@ -112,35 +112,33 @@ export class SpoPathBreadcrumb extends React.Component { - return originalPath !== undefined && originalPath !== null - && spSiteUrl !== undefined && spSiteUrl !== null - && spWebUrl !== undefined && spWebUrl !== null; + private validateEntityPath = (path: string, siteUrl: string, webUrl: string): boolean => { + return path !== undefined && path !== null + && siteUrl !== undefined && siteUrl !== null + && webUrl !== undefined && webUrl !== null; } - private getBreadcrumbItems = (originalPath: string, spSiteUrl: string, spWebUrl: string, includeSiteName: boolean, includeEntityName: boolean, breadcrumbItemsAsLinks: boolean): IBreadcrumbItem[] => { - // TODO: remove extensive commenting once tested and working - + private getBreadcrumbItems = (path: string, siteUrl: string, webUrl: string, includeSiteName: boolean, includeEntityName: boolean, breadcrumbItemsAsLinks: boolean): IBreadcrumbItem[] => { // Example: - // spWebUrl: https://contoso.sharepoint.com/sites/sitename/subsite - // originalPath: https://contoso.sharepoint.com/sites/sitename/subsite/Shared Documents/Document.docx + // webUrl: https://contoso.sharepoint.com/sites/sitename/subsite + // path: https://contoso.sharepoint.com/sites/sitename/subsite/Shared Documents/Document.docx - const frags = spWebUrl.split('/'); + const frags = webUrl.split('/'); // frags: ["https:", "", "contoso.sharepoint.com", "sites", "sitename", "subsite"] - const isRootSite = spSiteUrl.split('/').length === 3; + 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 so first items of path are "sitename", "subsite" - // If includeSiteName is false, then remove the whole spWebUrl from the original path so first item of path is "Shared Documents" - const replacePath = includeSiteName ? basePath : spWebUrl; - const parts = originalPath.replace(replacePath, '').split('/').filter(part => part); + // 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 e.g. Document.doxc. Last part is the entity for which the breadcrumb is generated. + // 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) => { @@ -154,15 +152,15 @@ export class SpoPathBreadcrumb extends React.Component Date: Thu, 25 Jan 2024 10:45:06 +0200 Subject: [PATCH 17/19] handle DispForms and fix root site link creation --- .../components/SpoPathBreadcrumbComponent.tsx | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index 217783186..b46facaca 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -20,6 +20,16 @@ export interface IBreadcrumbProps { */ 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. */ @@ -58,6 +68,9 @@ export interface IBreadcrumbProps { 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 = { @@ -70,7 +83,7 @@ export class SpoPathBreadcrumb extends React.Component @@ -112,13 +125,14 @@ export class SpoPathBreadcrumb extends React.Component { + private validateEntityProps = (path: string, siteUrl: string, webUrl: string, entityTitle: string): boolean => { return path !== undefined && path !== null && siteUrl !== undefined && siteUrl !== null - && webUrl !== undefined && webUrl !== null; + && webUrl !== undefined && webUrl !== null + && entityTitle !== undefined && entityTitle !== null; } - private getBreadcrumbItems = (path: string, siteUrl: string, webUrl: string, includeSiteName: boolean, includeEntityName: boolean, breadcrumbItemsAsLinks: boolean): IBreadcrumbItem[] => { + 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 @@ -142,8 +156,15 @@ export class SpoPathBreadcrumb extends React.Component { + // 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: part, + text: itemText, key: `item${index + 1}` }; @@ -160,7 +181,16 @@ export class SpoPathBreadcrumb extends React.Component Date: Thu, 25 Jan 2024 10:45:26 +0200 Subject: [PATCH 18/19] update documentation --- docs/extensibility/web_components_list.md | 32 ++++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/docs/extensibility/web_components_list.md b/docs/extensibility/web_components_list.md index 213140c56..9b502d815 100644 --- a/docs/extensibility/web_components_list.md +++ b/docs/extensibility/web_components_list.md @@ -220,7 +220,7 @@ Here are the list of all **reusable** web components you can use to customize yo |**hideOnError**|Hide image on error ## `` -- **Description**: Render a breadcrumb path of a SharePoint entity (file, item, folder, document library etc.). Breadcrumb path is only rendered for items in SharePoint. +- **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} @@ -229,30 +229,36 @@ Here are the list of all **reusable** web components you can use to customize yo Get started with: ```html ``` Use all properties: ```html ``` |Parameter|Description| |--|--| -|data-original-path|Used for creating the breadcrumb path. Component is designed to receive `OriginalPath` property. Property is required for rendering the breadcrumb path. `String`| -|data-sp-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-sp-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-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`| From 71d62d0321940182cd4328ab13d27b81f7535622 Mon Sep 17 00:00:00 2001 From: lauribohm Date: Thu, 25 Jan 2024 16:26:33 +0200 Subject: [PATCH 19/19] remove webUrl from required props --- .../src/components/SpoPathBreadcrumbComponent.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx index b46facaca..87c12b71b 100644 --- a/search-parts/src/components/SpoPathBreadcrumbComponent.tsx +++ b/search-parts/src/components/SpoPathBreadcrumbComponent.tsx @@ -106,7 +106,7 @@ export class SpoPathBreadcrumb extends React.Component @@ -125,10 +125,9 @@ export class SpoPathBreadcrumb extends React.Component { + private validateEntityProps = (path: string, siteUrl: string, entityTitle: string): boolean => { return path !== undefined && path !== null && siteUrl !== undefined && siteUrl !== null - && webUrl !== undefined && webUrl !== null && entityTitle !== undefined && entityTitle !== null; } @@ -137,6 +136,9 @@ export class SpoPathBreadcrumb extends React.Component