Added the Trending in the sites I follow sample (#22)

This commit is contained in:
Waldek Mastykarz 2016-09-20 16:27:56 +02:00 committed by GitHub
parent ee4e6f4e8f
commit 74be206b38
13 changed files with 394 additions and 2 deletions

View File

@ -22,6 +22,12 @@ Sample SharePoint Framework Client-Side Web Part built using React showing docum
![Working with Web Part in the SharePoint Workbench](./assets/my-recent-documents-preview.png)
### Trending in the sites I follow
Sample SharePoint Framework Client-Side Web Part built using React showing documents trending in the sites followed by the current user.
![Working with Web Part in the SharePoint Workbench](./assets/trending-in-sites-i-follow-preview.png)
## Applies to
* [SharePoint Framework Developer Preview](http://dev.office.com/sharepoint/docs/spfx/sharepoint-framework-overview)
@ -37,6 +43,7 @@ react-officegraph|Waldek Mastykarz (MVP, Rencore, @waldekm)
Version|Date|Comments
-------|----|--------
1.3.0|September 20, 2016|Added the Trending in the sites I follow sample
1.2.0|September 20, 2016|Added the My recent documents sample
1.1.0|September 19, 2016|Added the Working with sample
1.0.0|September 9, 2016|Initial release

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

View File

@ -14,6 +14,11 @@
"entry": "./lib/webparts/myRecentDocuments/MyRecentDocumentsWebPart.js",
"manifest": "./src/webparts/myRecentDocuments/MyRecentDocumentsWebPart.manifest.json",
"outputPath": "./dist/my-recent-documents.bundle.js"
},
{
"entry": "./lib/webparts/trendingInTheSitesIFollow/TrendingInTheSitesIFollowWebPart.js",
"manifest": "./src/webparts/trendingInTheSitesIFollow/TrendingInTheSitesIFollowWebPart.manifest.json",
"outputPath": "./dist/trending-in-the-sites-i-follow.bundle.js"
}
],
"externals": {
@ -28,6 +33,7 @@
"localizedResources": {
"trendingInThisSiteStrings": "webparts/trendingInThisSite/loc/{locale}.js",
"workingWithStrings": "webparts/workingWith/loc/{locale}.js",
"myRecentDocumentsStrings": "webparts/myRecentDocuments/loc/{locale}.js"
"myRecentDocumentsStrings": "webparts/myRecentDocuments/loc/{locale}.js",
"trendingInTheSitesIFollowStrings": "webparts/trendingInTheSitesIFollow/loc/{locale}.js"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "react-officegraph",
"version": "1.2.0",
"version": "1.3.0",
"private": true,
"engines": {
"node": ">=0.10.0"

View File

@ -4,6 +4,8 @@ export interface ITrendingDocument {
id: string;
title: string;
url: string;
webUrl?: string;
webTitle?: string;
previewImageUrl: string;
extension: string;
activity: IActivity;

View File

@ -0,0 +1,4 @@
export interface ITrendingInTheSitesIFollowWebPartProps {
title: string;
numberOfDocuments: number;
}

View File

@ -0,0 +1,16 @@
.trendingInTheSitesIFollow {
.webPartTitle {
margin-bottom: 0.7em;
margin-left: 0.38em;
}
:global .ms-DocumentCard {
float: left;
margin: 0.5em;
}
:global .ms-Spinner {
width: 7em;
margin: 0 auto;
}
}

View File

@ -0,0 +1,20 @@
{
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",
"id": "90a04bb9-fbdf-43d1-bfe1-0491d34941be",
"componentType": "WebPart",
"version": "0.0.1",
"manifestVersion": 2,
"preconfiguredEntries": [{
"groupId": "90a04bb9-fbdf-43d1-bfe1-0491d34941be",
"group": { "default": "Content rollup" },
"title": { "default": "Trending in the sites I follow" },
"description": { "default": "Shows documents trending in the sites followed by the current user" },
"officeFabricIconFontName": "Chart",
"properties": {
"title": "Trending in the sites I follow",
"numberOfDocuments": 5
}
}]
}

View File

@ -0,0 +1,59 @@
import * as React from 'react';
import * as ReactDom from 'react-dom';
import {
BaseClientSideWebPart,
IPropertyPaneSettings,
IWebPartContext,
PropertyPaneTextField,
PropertyPaneSlider
} from '@microsoft/sp-client-preview';
import * as strings from 'trendingInTheSitesIFollowStrings';
import TrendingInTheSitesIFollow, { ITrendingInTheSitesIFollowProps } from './components/TrendingInTheSitesIFollow';
import { ITrendingInTheSitesIFollowWebPartProps } from './ITrendingInTheSitesIFollowWebPartProps';
export default class TrendingInTheSitesIFollowWebPart extends BaseClientSideWebPart<ITrendingInTheSitesIFollowWebPartProps> {
public constructor(context: IWebPartContext) {
super(context);
}
public render(): void {
const element: React.ReactElement<ITrendingInTheSitesIFollowProps> = React.createElement(TrendingInTheSitesIFollow, {
title: this.properties.title,
numberOfDocuments: this.properties.numberOfDocuments,
httpClient: this.context.httpClient,
siteUrl: this.context.pageContext.web.absoluteUrl
});
ReactDom.render(element, this.domElement);
}
protected get propertyPaneSettings(): IPropertyPaneSettings {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.ViewGroupName,
groupFields: [
PropertyPaneTextField('title', {
label: strings.TitleFieldLabel
}),
PropertyPaneSlider('numberOfDocuments', {
label: strings.NumberOfDocumentsFieldLabel,
min: 1,
max: 10,
step: 1
})
]
}
]
}
]
};
}
}

View File

@ -0,0 +1,252 @@
import * as React from 'react';
import {
css,
DocumentCard,
DocumentCardLocation,
DocumentCardPreview,
DocumentCardTitle,
DocumentCardActivity,
Spinner
} from 'office-ui-fabric-react';
import styles from '../TrendingInTheSitesIFollow.module.scss';
import { ITrendingInTheSitesIFollowWebPartProps } from '../ITrendingInTheSitesIFollowWebPartProps';
import { HttpClient } from '@microsoft/sp-client-base';
import { ITrendingDocument } from '../../ITrendingDocument';
import { IActorInformation } from '../../IActorInformation';
import { SearchUtils, ISearchQueryResponse, IRow, ICell, IEdge } from '../../SearchUtils';
import { Utils } from '../../Utils';
export interface ITrendingInTheSitesIFollowProps extends ITrendingInTheSitesIFollowWebPartProps {
httpClient: HttpClient;
siteUrl: string;
}
export interface ITrendingInTheSitesIFollowState {
trendingDocuments: ITrendingDocument[];
loading: boolean;
error: string;
}
interface ISiteInfo {
Uri: string;
}
export default class TrendingInTheSitesIFollow extends React.Component<ITrendingInTheSitesIFollowProps, ITrendingInTheSitesIFollowState> {
constructor(props: ITrendingInTheSitesIFollowProps, state: ITrendingInTheSitesIFollowState) {
super(props);
this.state = {
trendingDocuments: [] as ITrendingDocument[],
loading: true,
error: null
};
}
public componentDidMount(): void {
this.loadDocuments(this.props.siteUrl, this.props.numberOfDocuments);
}
public componentDidUpdate(prevProps: ITrendingInTheSitesIFollowProps, prevState: ITrendingInTheSitesIFollowState, prevContext: any): void {
if (this.props.numberOfDocuments !== prevProps.numberOfDocuments ||
this.props.siteUrl !== prevProps.siteUrl && (
this.props.numberOfDocuments && this.props.siteUrl
)) {
this.loadDocuments(this.props.siteUrl, this.props.numberOfDocuments);
}
}
public render(): JSX.Element {
const loading: JSX.Element = this.state.loading ? <div style={{ margin: '0 auto' }}><Spinner label={'Loading...'} /></div> : <div/>;
const error: JSX.Element = this.state.error ? <div><strong>Error: </strong> {this.state.error}</div> : <div/>;
const documents: JSX.Element[] = this.state.trendingDocuments.map((doc: ITrendingDocument, i: number) => {
const iconUrl: string = `https://spoprod-a.akamaihd.net/files/odsp-next-prod_ship-2016-08-15_20160815.002/odsp-media/images/filetypes/32/${doc.extension}.png`;
return (
<DocumentCard onClickHref={doc.url} key={doc.id}>
<DocumentCardPreview
previewImages={[
{
previewImageSrc: doc.previewImageUrl,
iconSrc: iconUrl,
width: 318,
height: 196,
accentColor: '#ce4b1f'
}
]}
/>
<DocumentCardTitle title={doc.title}/>
<DocumentCardLocation location={doc.webTitle} locationHref={doc.webUrl} />
<DocumentCardActivity
activity={`${doc.activity.name} ${doc.activity.date}`}
people={
[
{ name: doc.activity.actorName, profileImageSrc: doc.activity.actorPhotoUrl }
]
}
/>
</DocumentCard>
);
});
return (
<div className={styles.trendingInTheSitesIFollow}>
<div className={css('ms-font-xl', styles.webPartTitle) }>{this.props.title}</div>
{loading}
{error}
{documents}
<div style={{ clear: 'both' }}/>
</div>
);
}
private loadDocuments(siteUrl: string, numberOfDocuments: number): void {
this.setState({
loading: true,
error: undefined,
trendingDocuments: []
});
const trendingDocuments: ITrendingDocument[] = [];
this.getSitesIFollow(siteUrl)
.then((sitesIFollow: string[]): Promise<ITrendingDocument[]> => {
return this.getTrendingDocuments(sitesIFollow, siteUrl, numberOfDocuments);
})
.then((trendingDocuments: ITrendingDocument[]): void => {
this.setState({
loading: false,
error: undefined,
trendingDocuments: trendingDocuments
});
}, (error: any): void => {
this.setState({
loading: false,
error: error,
trendingDocuments: []
});
});
return;
}
private getSitesIFollow(siteUrl: string): Promise<string[]> {
return new Promise((resolve: (sitesIFollow: string[]) => void, reject: (error: any) => void): void => {
this.props.httpClient.get(`${siteUrl}/_api/social.following/my/followed(types=4)`, {
headers: {
'Accept': 'application/json;odata=nometadata',
'odata-version': ''
}
})
.then((response: Response): Promise<{ value: ISiteInfo[] }> => {
return response.json();
})
.then((sitesIFollowInfo: { value: ISiteInfo[] }): void => {
const sitesIFollow: string[] = [];
for (let i: number = 0; i < sitesIFollowInfo.value.length; i++) {
sitesIFollow.push(sitesIFollowInfo.value[i].Uri);
}
resolve(sitesIFollow);
}, (error: any): void => {
reject(error);
});
});
}
private getTrendingDocuments(sitesIFollow: string[], siteUrl: string, numberOfDocuments: number): Promise<ITrendingDocument[]> {
return new Promise((resolve: (trendingDocuments: ITrendingDocument[]) => void, reject: (error: any) => void): void => {
if (sitesIFollow.length === 0) {
return resolve([]);
}
let query: string = '(';
for (let i: number = 0; i < sitesIFollow.length; i++) {
if (query.length > 1) {
query += ' OR ';
}
query += `Path:"${sitesIFollow[i]}"`;
}
query += ') AND (IsDocument:1)';
const postData: string = JSON.stringify({
'request': {
'__metadata': {
'type': 'Microsoft.Office.Server.Search.REST.SearchRequest'
},
'Querytext': query,
'SelectProperties': {
'results': ['Author', 'AuthorOwsUser', 'DocId', 'DocumentPreviewMetadata', 'Edges', 'EditorOwsUser', 'FileExtension', 'FileType', 'HitHighlightedProperties', 'HitHighlightedSummary', 'LastModifiedTime', 'LikeCountLifetime', 'ListID', 'ListItemID', 'OriginalPath', 'Path', 'Rank', 'SPWebUrl', 'SecondaryFileExtension', 'ServerRedirectedURL', 'SiteTitle', 'Title', 'ViewCountLifetime', 'siteID', 'uniqueID', 'webID']
},
'ClientType': 'TrendingInTheSitesIFollow',
'BypassResultTypes': 'true',
'RowLimit': numberOfDocuments,
'StartRow': '0',
'RankingModelId': '0c77ded8-c3ef-466d-929d-905670ea1d72',
'Properties': {
'results': [{
'Name': 'IncludeExternalContent',
'Value': {
'BoolVal': 'True',
'QueryPropertyValueTypeIndex': 3
}
}, {
'Name': 'GraphQuery',
'Value': {
'StrVal': 'actor(ME,action:1021)',
'QueryPropertyValueTypeIndex': 1
}
}]
}
}
});
this.props.httpClient.post(`${siteUrl}/_api/search/postquery`, {
headers: {
'Accept': 'application/json;odata=nometadata',
'Content-type': 'application/json;odata=verbose',
'odata-version': ''
},
body: postData
})
.then((response: Response): Promise<ISearchQueryResponse> => {
return response.json();
})
.then((response: ISearchQueryResponse): void => {
if (!response ||
!response.PrimaryQueryResult ||
!response.PrimaryQueryResult.RelevantResults ||
response.PrimaryQueryResult.RelevantResults.RowCount === 0) {
resolve([]);
return;
}
const trendingDocuments: ITrendingDocument[] = [];
for (let i: number = 0; i < response.PrimaryQueryResult.RelevantResults.Table.Rows.length; i++) {
const row: IRow = response.PrimaryQueryResult.RelevantResults.Table.Rows[i];
const cells: ICell[] = row.Cells;
const editorInfo: string[] = SearchUtils.getValueFromResults('EditorOwsUser', cells).split('|');
const modifiedDate: Date = new Date(SearchUtils.getValueFromResults('LastModifiedTime', cells).replace('.0000000', ''));
const dateString: string = (modifiedDate.getMonth() + 1) + '/' + modifiedDate.getDate() + '/' + modifiedDate.getFullYear();
trendingDocuments.push({
id: SearchUtils.getValueFromResults('DocId', cells),
url: SearchUtils.getValueFromResults('ServerRedirectedURL', cells),
webUrl: SearchUtils.getValueFromResults('SPWebUrl', cells),
webTitle: SearchUtils.getValueFromResults('SiteTitle', cells),
title: SearchUtils.getValueFromResults('Title', cells),
previewImageUrl: SearchUtils.getPreviewImageUrl(cells, siteUrl),
extension: SearchUtils.getValueFromResults('FileType', cells),
activity: {
actorId: -1,
actorName: Utils.trim(editorInfo[1]),
actorPhotoUrl: Utils.getUserPhotoUrl(Utils.trim(editorInfo[0]), siteUrl),
date: dateString,
name: 'Modified'
}
});
}
resolve(trendingDocuments);
}, (error: any): void => {
reject(error);
});
});
}
}

View File

@ -0,0 +1,8 @@
define([], function() {
return {
"PropertyPaneDescription": "Manage the settings of this Web Part",
"ViewGroupName": "View",
"NumberOfDocumentsFieldLabel": "Number of documents to show",
"TitleFieldLabel": "Web Part Title (displayed in the body)"
}
});

View File

@ -0,0 +1,11 @@
declare interface ITrendingInTheSitesIFollowStrings {
PropertyPaneDescription: string;
ViewGroupName: string;
NumberOfDocumentsFieldLabel: string;
TitleFieldLabel: string;
}
declare module 'trendingInTheSitesIFollowStrings' {
const strings: ITrendingInTheSitesIFollowStrings;
export = strings;
}

View File

@ -0,0 +1,7 @@
import * as assert from 'assert';
describe('TrendingInTheSitesIFollowWebPart', () => {
it('should do something', () => {
assert.ok(true);
});
});