Quite a few customers have asked me for a solution for automatic linking of related articles on their modern SharePoint intranet. I’ve drummed together a solution using PnP Modern Search and Term store. This solution requires that each news article (or page) is tagged with a single metadata column.
- The template in Modern PnP Search:
<content id="data-content">
<style>
.template--card {
flex-grow: 1;
flex-shrink: 1;
min-width: 206px; /* Min width of the Office UI Fabric document card */
flex-basis: {{@root.properties.layoutProperties.columnSizePercentage}}%;
margin: 0px -10px 0px -8px;
}
.template--header{
display:none;
}
.ms-DocumentCardPreview{
max-height:76px;
border-radius: 5px;
border:1px solid rgb(93, 141, 169);
}
.ms-Image{
max-height:75px;
margin: -1px 0 0 0;
}
.ms-DocumentCard--compact {
height:75px!important;
box-shadow: none;
border-radius: 5px;
}
.ms-DocumentCardTitle {
font-size: 14px;
font-weight: 400;
}
</style>
<div class="template">
<div class="template--header">
{{#if @root.properties.showResultsCount}}
<div class="template--resultCount">
<label class="ms-fontWeight-semibold">{{getCountMessage @root.data.totalItemsCount @root.inputQueryText}}</label>
</div>
{{/if}}
<div class="template--sort">
<pnp-sortfield
data-fields="{{JSONstringify @root.properties.dataSourceProperties.sortList}}"
data-default-selected-field="{{sort.selectedSortFieldName}}"
data-default-direction="{{sort.selectedSortDirection}}"
data-theme-variant="{{JSONstringify @root.theme}}">
</pnp-sortfield>
</div>
</div>
<div class="template--cardContainer">
{{#each data.items as |item|}}
{{#> resultTypes item=item}}
<pnp-documentcard class="template--card"
data-ui-test-id="resultCard"
data-item="{{JSONstringify item}}"
data-enable-preview="false"
data-show-file-icon="false"
data-is-compact="true"
data-context="{{JSONstringify @root}}"
data-instance-id="{{@root.instanceId}}"
data-theme-variant="{{JSONstringify @root.theme}}"
data-allow-item-selection="{{@root.properties.itemSelectionProps.allowItemSelection}}"
data-item-key="{{@root.paging.currentPageNumber}}{{@index}}"
data-selected-keys="{{JSONstringify @root.selectedKeys}}"
data-index="{{@index}}"
data-title="{{slot item @root.slots.Title}}"
data-preview-image="{{slot item @root.slots.PreviewImageUrl}}"
data-preview-url="{{slot item @root.slots.PreviewUrl}}"
data-href="{{slot item @root.slots.Path}}"
>
</pnp-documentcard>
{{/resultTypes}}
{{/each}}
<!-- Generate fake items to correctly align cards on rows using flexbox -->
{{#times @root.properties.layoutProperties.preferedCardNumberPerRow}}
<pnp-documentcard
class="template--card"
data-ui-test-id="fakeCard"
data-theme-variant="{{JSONstringify @root.theme}}"
style="visibility:hidden;height: 0;padding: 0">
</pnp-documentcard>
{{/times}}
</div>
{{#if @root.properties.paging.showPaging}}
{{#gt @root.data.totalItemsCount @root.properties.paging.itemsCountPerPage}}
<pnp-pagination
data-total-items="{{@root.data.totalItemsCount}}"
data-hide-first-last-pages="{{@root.properties.paging.hideFirstLastPages}}"
data-hide-disabled="{{@root.properties.paging.hideDisabled}}"
data-hide-navigation="{{@root.properties.paging.hideNavigation}}"
data-range="{{@root.properties.paging.pagingRange}}"
data-items-count-per-page="{{@root.properties.paging.itemsCountPerPage}}"
data-current-page-number="{{@root.paging.currentPageNumber}}"
data-theme-variant="{{JSONstringify @root.theme}}"
>
</pnp-pagination>
{{/gt}}
{{/if}}
</div>
</content>
<content id="placeholder-content">
<style>
/* Assign the correct size % ratio according to the number of wanted cards on a single row. We use flexbox instead of static grid to deal with SharePoint canvas sections*/
.template--card {
flex-grow: 1;
flex-shrink: 1;
min-width: 206px; /* Min width of the Office UI Fabric document card */
flex-basis: {{@root.properties.layoutProperties.columnSizePercentage}}%;
}
</style>
<div class="placeholder">
{{!-- #if showResultsCount --}}
<div class="template--resultCount">
<span class="placeholder--shimmer placeholder--line" style="width: 20%"></span>
</div>
{{!-- /if --}}
<div class="template--cardContainer">
{{#times @root.properties.paging.itemsCountPerPage}}
<pnp-documentcardshimmers class="template--card"
data-ui-test-id="fakeCard"
data-theme-variant="{{JSONstringify @root.theme}}"
data-is-compact="true">
</pnp-documentcardshimmers>
{{/times}}
<!-- Generate fake items to correctly align cards on rows using flexbox -->
{{#times @root.properties.layoutProperties.preferedCardNumberPerRow}}
<pnp-documentcard class="template--card"
data-ui-test-id="fakeCard"
data-theme-variant="{{JSONstringify @root.theme}}"
style="visibility:hidden;height: 0;padding: 0">
</pnp-documentcard>
{{/times}}
</div>
</div>
</content>
2. The search query:
DepartmentId:{{Hub.HubSiteId}} AND
PromotedState:2 AND
{|owstaxidCategories:{Page.Categories.TermID}}
-Path:{Page}
3. Create a Term set for page categories and connect this to a column (multi choice) on the SitePages library on your news site(s). If you need to use this on several sites, consider creating an Azure Automation for this or a site template. This example uses a site column, called ‘Categories’.
4. Add PnP Modern Search with the template and query on your news templates. Remember to limit the results and sort by firstpublisheddate. I’d also like to recommend to sync templates on all sites that hosts news stories on your intranet. Logic app or Power Automate to the rescue…
5. Each news post will now display other posts that are relevant.
Leave a Reply