Merge pull request #2149 from aaclage/Feature/FollowDocumentGraph
updated to use Microsoft Graph follow
This commit is contained in:
commit
78ad673966
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Summary
|
||||
|
||||
This solution has the goal to easily identify/follow user key documents from all Tenant and easily access them in Modern Pages and Microsoft Teams. This solution uses the Out of Box Social feature **"Follow Document WebPart"** with combination of MSGraph queries and extension for Microsoft Teams.
|
||||
This solution has the goal to easily identify/follow user key documents from all Tenant and easily access them in Modern Pages and Microsoft Teams. This solution uses the Out of Box Office Page > Favorites Files Tab with combination of MSGraph queries and extension for Microsoft Teams.
|
||||
|
||||
This is a 2 phase project with associated dependency of solution [Follow-Document](https://github.com/pnp/sp-dev-fx-extensions/tree/main/samples/react-command-follow-document) extension where users are allow to select and manage Followed Document in Libraries to be used in this project.
|
||||
|
||||
|
@ -20,8 +20,9 @@ Available features:
|
|||
- Microsoft Team integration with personal/Tab App that allow user focus on key Documents.
|
||||
|
||||
Usage of following Technologies:
|
||||
- Usage of Social Feature **"Follow" documents** and associated REST "[/_api/social.following/](https://docs.microsoft.com/en-us/sharepoint/dev/general-development/how-to-follow-documents-sites-and-tags-by-using-the-rest-service-in-sharepoint-2)"
|
||||
- Usage of Graph queries using [Graph explorer](https://developer.microsoft.com/en-us/graph/graph-explorer)
|
||||
- Usage of Microsoft Graph API "[Follow drive item](https://docs.microsoft.com/en-us/graph/api/driveitem-follow?view=graph-rest-1.0&tabs=http)"
|
||||
- Usage of Microsoft Graph API "[Unfollow drive item](https://docs.microsoft.com/en-us/graph/api/driveitem-unfollow?view=graph-rest-1.0&tabs=http)"
|
||||
- Usage of Microsoft Graph API "[List followed items](https://docs.microsoft.com/en-us/graph/api/drive-list-following?view=graph-rest-1.0&tabs=http)"- Usage of Graph queries using [Graph explorer](https://developer.microsoft.com/en-us/graph/graph-explorer)
|
||||
- Usage of [adaptive cards](https://adaptivecards.io/)
|
||||
- Microsoft Teams integration with following option [TeamsTab, TeamsPersonalApp]
|
||||
|
||||
|
@ -57,6 +58,8 @@ o365 spo login https://contoso-admin.sharepoint.com
|
|||
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'Files.Read'
|
||||
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'Files.Read.All'
|
||||
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'Sites.Read.All'
|
||||
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'Files.ReadWrite.All'
|
||||
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'Sites.ReadWrite.All'
|
||||
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'Team.ReadBasic.All'
|
||||
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'Channel.ReadBasic.All'
|
||||
o365 spo serviceprincipal grant add --resource 'Microsoft Graph' --scope 'ChannelMessage.Send'
|
||||
|
@ -73,7 +76,7 @@ react-follow-document | [André Lage](https://github.com/aaclage) (http://aaclag
|
|||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|June 22, 2021|Initial release
|
||||
|
||||
2.0|November 25, 2021|Change to use Microsoft Graph Follow
|
||||
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
@ -93,7 +96,7 @@ Description of the extension that expands upon high-level summary above.
|
|||
|
||||
This extension illustrates the following concepts:
|
||||
|
||||
- Change of SharePoint Social Feature **"Follow"** to follow key Documents for users in Modern Sites.
|
||||
- Usage of Office > Favorites to follow key Documents from users in Modern Sites.
|
||||
- Simple UX to manage **Followed** documents and report list followed documents across Tenant and access properties and Preview of Document.
|
||||
- Option to unfollow documents individually.
|
||||
- Integration with other services of Office 365 such us (Preview, Microsoft Team Messages).
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"identify/follow user key documents from all Tenant and easily access them in Modern Pages and Microsoft Teams"
|
||||
],
|
||||
"creationDateTime": "2021-06-21",
|
||||
"updateDateTime": "2021-06-21",
|
||||
"updateDateTime": "2021-11-25",
|
||||
"products": [
|
||||
"SharePoint",
|
||||
"Office"
|
||||
|
|
|
@ -14,25 +14,40 @@
|
|||
"termsOfUseUrl": "",
|
||||
"mpnId": ""
|
||||
},
|
||||
"webApiPermissionRequests": [{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Files.Read"
|
||||
}, {
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Files.Read.All"
|
||||
}, {
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Sites.Read.All"
|
||||
}, {
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Team.ReadBasic.All"
|
||||
}, {
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Channel.ReadBasic.All"
|
||||
}, {
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "ChannelMessage.Send"
|
||||
}]
|
||||
"webApiPermissionRequests": [
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Files.Read"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Files.ReadWrite.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Sites.ReadWrite.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Files.Read.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Sites.Read.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Team.ReadBasic.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "Channel.ReadBasic.All"
|
||||
},
|
||||
{
|
||||
"resource": "Microsoft Graph",
|
||||
"scope": "ChannelMessage.Send"
|
||||
}
|
||||
]
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/follow-document-web-part.sppkg"
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
import {
|
||||
SPHttpClient,
|
||||
SPHttpClientResponse,
|
||||
ISPHttpClientOptions,
|
||||
} from "@microsoft/sp-http";
|
||||
import * as MicrosoftGraph from "@microsoft/microsoft-graph-types";
|
||||
|
||||
export default class Rest {
|
||||
public async isfollowed(
|
||||
spHttpClient: SPHttpClient,
|
||||
fileUrl: string,
|
||||
siteUrl: string
|
||||
): Promise<boolean> {
|
||||
const spOpts: ISPHttpClientOptions = {
|
||||
headers: {
|
||||
Accept: "application/json;odata.metadata=minimal",
|
||||
"Content-type": "application/json;odata=verbose",
|
||||
},
|
||||
body: `{'actor': { 'ActorType':1, 'ContentUri':'${fileUrl}', 'Id':null}}`,
|
||||
};
|
||||
|
||||
const value = spHttpClient
|
||||
.post(
|
||||
`${siteUrl}/_api/social.following/isfollowed`,
|
||||
SPHttpClient.configurations.v1,
|
||||
spOpts
|
||||
)
|
||||
.then((response: SPHttpClientResponse): Promise<{
|
||||
value: boolean;
|
||||
}> => {
|
||||
// Access properties of the response object.
|
||||
console.log(`Status code: ${response.status}`);
|
||||
console.log(`Status text: ${response.statusText}`);
|
||||
|
||||
//response.json() returns a promise so you get access to the json in the resolve callback.
|
||||
return response.json();
|
||||
/* response.json().then((responseJSON: JSON) => {
|
||||
console.log(responseJSON);
|
||||
});*/
|
||||
})
|
||||
.then((item: { value: boolean }) => {
|
||||
return item.value;
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
public async follow(
|
||||
spHttpClient: SPHttpClient,
|
||||
fileUrl: string,
|
||||
siteUrl: string
|
||||
): Promise<boolean> {
|
||||
const spOpts: ISPHttpClientOptions = {
|
||||
headers: {
|
||||
Accept: "application/json;odata.metadata=minimal",
|
||||
"Content-type": "application/json;odata=verbose",
|
||||
},
|
||||
body: `{'actor': { 'ActorType':1, 'ContentUri':'${fileUrl}', 'Id':null}}`,
|
||||
};
|
||||
const value = await spHttpClient
|
||||
.post(
|
||||
`${siteUrl}/_api/social.following/follow`,
|
||||
SPHttpClient.configurations.v1,
|
||||
spOpts
|
||||
)
|
||||
.then((response: SPHttpClientResponse): Promise<number> => {
|
||||
// Access properties of the response object.
|
||||
console.log(`Status code: ${response.status}`);
|
||||
console.log(`Status text: ${response.statusText}`);
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.then((Item: any) => {
|
||||
return Item.value;
|
||||
});
|
||||
if (value === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async stopfollowing(
|
||||
spHttpClient: SPHttpClient,
|
||||
fileUrl: string,
|
||||
siteUrl: string
|
||||
): Promise<boolean> {
|
||||
const spOpts: ISPHttpClientOptions = {
|
||||
headers: {
|
||||
Accept: "application/json;odata.metadata=minimal",
|
||||
"Content-type": "application/json;odata=verbose",
|
||||
},
|
||||
body: `{'actor': { 'ActorType':1, 'ContentUri':'${fileUrl}', 'Id':null}}`,
|
||||
};
|
||||
const value = await spHttpClient
|
||||
.post(
|
||||
`${siteUrl}/_api/social.following/stopfollowing`,
|
||||
SPHttpClient.configurations.v1,
|
||||
spOpts
|
||||
)
|
||||
.then((response: SPHttpClientResponse) => {
|
||||
// Access properties of the response object.
|
||||
console.log(`Status code: ${response.status}`);
|
||||
console.log(`Status text: ${response.statusText}`);
|
||||
return true;
|
||||
});
|
||||
return value;
|
||||
}
|
||||
public async followed(
|
||||
spHttpClient: SPHttpClient,
|
||||
siteUrl: string
|
||||
): Promise<MicrosoftGraph.DriveItem[]> {
|
||||
const spOpts: ISPHttpClientOptions = {
|
||||
headers: {
|
||||
Accept: "application/json;odata.metadata=minimal",
|
||||
"Content-type": "application/json;odata=verbose",
|
||||
},
|
||||
};
|
||||
const values = spHttpClient
|
||||
.post(
|
||||
`${siteUrl}/_api/social.following/my/followed(types=2)`,
|
||||
SPHttpClient.configurations.v1,
|
||||
spOpts
|
||||
)
|
||||
.then((response: SPHttpClientResponse): Promise<
|
||||
MicrosoftGraph.DriveItem[]
|
||||
> => {
|
||||
// Access properties of the response object.
|
||||
console.log(`Status code: ${response.status}`);
|
||||
console.log(`Status text: ${response.statusText}`);
|
||||
|
||||
//response.json() returns a promise so you get access to the json in the resolve callback.
|
||||
return response.json();
|
||||
})
|
||||
.then((Items:any) => {
|
||||
let Values: MicrosoftGraph.DriveItem[] = [];
|
||||
Items.value.forEach((element) => {
|
||||
Values.push({
|
||||
webUrl: decodeURIComponent(element.Uri),
|
||||
name: element.Name,
|
||||
});
|
||||
});
|
||||
return Values;
|
||||
});
|
||||
return values;
|
||||
}
|
||||
}
|
|
@ -4,8 +4,9 @@ import styles from './FollowDocumentWebPart.module.scss';
|
|||
import { IFollowDocumentWebPartProps } from './IFollowDocumentWebPartProps';
|
||||
import { IFollowDocumentWebPartState } from './IFollowDocumentWebPartState';
|
||||
import { FollowDocumentGrid } from '../components/followDocumentGrid/index';
|
||||
import Rest from '../Service/Rest';
|
||||
import Graph from "../Service/GraphService";
|
||||
import { FollowDocument } from "../models/followDocument";
|
||||
import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';
|
||||
|
||||
// Used to render list grid
|
||||
import {
|
||||
|
@ -62,52 +63,228 @@ export default class FollowDocumentWebPart extends React.Component<IFollowDocume
|
|||
visible: true,
|
||||
});
|
||||
}
|
||||
//Load using Graph
|
||||
this.getGraphFollowedDocs();
|
||||
let followDocuments: FollowDocument[] = [];
|
||||
this.getFollowDocuments(followDocuments).then((Items: FollowDocument[]) => {
|
||||
//Order by Date
|
||||
Items = Items.sort((a, b) => {
|
||||
return b.followedDateTime.getTime() - a.followedDateTime.getTime();
|
||||
});
|
||||
let uniq = {};
|
||||
let group: Array<IDropdownOption> = new Array<IDropdownOption>();
|
||||
//Remove duplicated from array
|
||||
let uniqueArray = [];
|
||||
uniqueArray = Items.filter(obj => !uniq[obj.WebUrl] && (uniq[obj.WebUrl] = true));
|
||||
group.push({ key: '0', text: 'All Sites' });
|
||||
uniqueArray.forEach(Item => {
|
||||
group.push({
|
||||
key: Item.WebUrl,
|
||||
text: "Site: " + Item.WebName,
|
||||
});
|
||||
});
|
||||
this.setState({
|
||||
Items: Items,
|
||||
ItemsSearch: Items,
|
||||
ItemsGroup: group,
|
||||
visible: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
/********************************************************************** */
|
||||
private getFollowDocuments = async (followDocuments: FollowDocument[]): Promise<any> => {
|
||||
const graphService: Graph = new Graph();
|
||||
let graphData: any = [];
|
||||
graphData = await graphService.getGraphContent(`https://graph.microsoft.com/v1.0/me/drive/following?$select=id,name,webUrl,parentReference,followed&Top=1000`, this.props.context);
|
||||
if (graphData.value !== undefined) {
|
||||
graphData.value.forEach(data => {
|
||||
|
||||
let followDocument: FollowDocument = {
|
||||
ItemId: data.id,
|
||||
Title: data.name,
|
||||
WebFileUrl: data.webUrl,
|
||||
DriveId: data.parentReference.driveId,
|
||||
followedDateTime: new Date(data.followed.followedDateTime),
|
||||
} as FollowDocument;
|
||||
this.GetIcon(data.name).then(icon => {
|
||||
followDocument.IconUrl = (this.props.context.pageContext.web.absoluteUrl + "/_layouts/15/images/lg_" + icon).replace("lg_iczip.gif", "lg_iczip.png").replace("lg_icmsg.png", "lg_icmsg.gif");
|
||||
});
|
||||
followDocuments.push(followDocument);
|
||||
});
|
||||
followDocuments = await this.getList(followDocuments);
|
||||
}
|
||||
return followDocuments;
|
||||
}
|
||||
|
||||
//get Web Name and Web Url of Document
|
||||
private getSearchWebID = async (graphData: any[], webs: any[]): Promise<any[]> => {
|
||||
private getList = async (followDocuments: FollowDocument[]): Promise<any> => {
|
||||
let items: FollowDocument[] = [];
|
||||
const graphService: Graph = new Graph();
|
||||
const initialized = await graphService.initialize(this.props.context.serviceScope);
|
||||
let queryString: string = "";
|
||||
for (let index = 0; index < webs.length; index++) {
|
||||
if (index === 0) {
|
||||
queryString += "WebId:" + webs[index].replace('{', '').replace('}', '');
|
||||
} else {
|
||||
queryString += " OR WebId:" + webs[index].replace('{', '').replace('}', '') + " ";
|
||||
if (initialized) {
|
||||
let uniq = {};
|
||||
let uniqueArray = [];
|
||||
uniqueArray = followDocuments.filter(obj => !uniq[obj.DriveId] && (uniq[obj.DriveId] = true));
|
||||
const requests = this.getBatchRequest(uniqueArray, "/me/drives/{driveId}/list?select=id,webUrl,parentReference", "GET");
|
||||
for (let index = 0; index < requests.length; index++) {
|
||||
const graphData: any = await graphService.postGraphContent("https://graph.microsoft.com/v1.0/$batch", requests[index]);
|
||||
graphData.responses.forEach((data: any) => {
|
||||
followDocuments.forEach((followDocument: FollowDocument) => {
|
||||
let driveId: string = decodeURI(data.body["@odata.context"].substring(
|
||||
data.body["@odata.context"].indexOf("drives('") + 8,
|
||||
data.body["@odata.context"].lastIndexOf("'")
|
||||
));
|
||||
if (followDocument.DriveId === driveId && (followDocument.ListId === undefined || followDocument.ListId === "")) {
|
||||
followDocument.ListId = data.body.id;
|
||||
followDocument.ItemProperties = data.body.webUrl + "/Forms/dispForm.aspx?ID=";
|
||||
followDocument.SiteId = data.body.parentReference.siteId;
|
||||
items.push(followDocument);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
followDocuments = await this.getDriveItem(items);
|
||||
return followDocuments;
|
||||
|
||||
}
|
||||
|
||||
private getDriveItem = async (followDocuments: FollowDocument[]): Promise<any> => {
|
||||
const graphService: Graph = new Graph();
|
||||
let items: FollowDocument[] = [];
|
||||
const initialized = await graphService.initialize(this.props.context.serviceScope);
|
||||
if (initialized) {
|
||||
const HeaderWeb = {
|
||||
"requests": [
|
||||
{
|
||||
"entityTypes": [
|
||||
"site"
|
||||
],
|
||||
"query": {
|
||||
"queryString": "" + queryString + "",
|
||||
const requests = this.getBatchRequest(followDocuments, "/me/drives/{driveId}/items/{ItemID}?$select=id,content.downloadUrl,ListItem&expand=ListItem(select=id,webUrl),thumbnails(select=large)", "GET");
|
||||
for (let index = 0; index < requests.length; index++) {
|
||||
const graphData: any = await graphService.postGraphContent("https://graph.microsoft.com/v1.0/$batch", requests[index]);
|
||||
graphData.responses.forEach((data: any) => {
|
||||
followDocuments.forEach((followDocument: FollowDocument) => {
|
||||
|
||||
if (followDocument.ItemId === data.body.id && followDocument.Url === undefined) {
|
||||
followDocument.id = data.body.listItem.id;
|
||||
followDocument.Url = data.body.listItem.webUrl;
|
||||
followDocument.Folder = data.body.listItem.webUrl.substring(0, data.body.listItem.webUrl.lastIndexOf("/") + 1);
|
||||
followDocument.ItemProperties = followDocument.ItemProperties + data.body.listItem.id;
|
||||
followDocument.DownloadFile = data.body["@microsoft.graph.downloadUrl"];
|
||||
followDocument.Thumbnail = data.body.thumbnails.length > 0 ? data.body.thumbnails[0].large.url : "";
|
||||
items.push(followDocument);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
//Retrieve webNames
|
||||
const tmpWebs = await graphService.postGraphContent("https://graph.microsoft.com/beta/search/query", HeaderWeb);
|
||||
graphData.forEach(element => {
|
||||
tmpWebs.value[0].hitsContainers[0].hits.forEach(Webelement => {
|
||||
if (element.fields.WebId.replace('{', '').replace('}', '') === Webelement.resource.id.split(/[, ]+/).pop().toUpperCase()) {
|
||||
element.WebName = Webelement.resource.name;
|
||||
element.WebUrl = Webelement.resource.webUrl;
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
return graphData;
|
||||
});
|
||||
});
|
||||
}
|
||||
followDocuments = await this.getWeb(items);
|
||||
return followDocuments;
|
||||
}
|
||||
}
|
||||
|
||||
private onActionTeamsClick = (action: any, ev: React.SyntheticEvent<HTMLElement>): void => {
|
||||
private getWeb = async (followDocuments: FollowDocument[]): Promise<any> => {
|
||||
const graphService: Graph = new Graph();
|
||||
let items: FollowDocument[] = [];
|
||||
const initialized = await graphService.initialize(this.props.context.serviceScope);
|
||||
if (initialized) {
|
||||
let uniq = {};
|
||||
let uniqueArray = [];
|
||||
uniqueArray = followDocuments.filter(obj => !uniq[obj.SiteId] && (uniq[obj.SiteId] = true));
|
||||
const requests = this.getBatchRequest(uniqueArray, "/sites/{SiteId}?$select=id,siteCollection,webUrl,name,displayName", "GET");
|
||||
for (let index = 0; index < requests.length; index++) {
|
||||
const graphData = await graphService.postGraphContent("https://graph.microsoft.com/v1.0/$batch", requests[index]);
|
||||
graphData.responses.forEach((data: any) => {
|
||||
followDocuments.forEach((followDocument: FollowDocument) => {
|
||||
if (followDocument.SiteId === data.body.id && (followDocument.Domain === undefined || followDocument.Domain === "")) {
|
||||
followDocument.Domain = data.body.siteCollection.hostname;
|
||||
followDocument.WebUrl = data.body.webUrl;
|
||||
followDocument.WebName = data.body.displayName;
|
||||
followDocument.documentCardActions = [
|
||||
{
|
||||
iconProps: { iconName: 'TeamsLogo' },
|
||||
onClick: this.onActionTeamsClick.bind(this, followDocument),
|
||||
ariaLabel: 'Send to Teams',
|
||||
},
|
||||
{
|
||||
iconProps: { iconName: 'FabricFolder' },
|
||||
onClick: this.onActionFolderClick.bind(this, followDocument),
|
||||
ariaLabel: 'open Folder',
|
||||
},
|
||||
{
|
||||
iconProps: { iconName: 'FavoriteStarFill' },
|
||||
onClick: this.onActionUnfollowClick.bind(this, followDocument),
|
||||
ariaLabel: 'Unfollow Document',
|
||||
},
|
||||
{
|
||||
iconProps: { iconName: 'Info' },
|
||||
onClick: this.onActionPropertiesClick.bind(this, followDocument),
|
||||
ariaLabel: 'Document info',
|
||||
},
|
||||
{
|
||||
iconProps: { iconName: 'DocumentSearch' },
|
||||
onClick: this.onActionPanelClick.bind(this, followDocument),
|
||||
ariaLabel: 'Preview',
|
||||
},
|
||||
|
||||
];
|
||||
items.push(followDocument);
|
||||
}
|
||||
});
|
||||
});
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public GetIcon = async (name: string): Promise<string> => {
|
||||
var url = `${this.props.context.pageContext.web.absoluteUrl}/_api/web/maptoicon(filename='${name}',%20progid='',%20size=0)`;
|
||||
const value = await this.props.context.spHttpClient.get(url, SPHttpClient.configurations.v1).then((response: SPHttpClientResponse): Promise<{
|
||||
value: string;
|
||||
}> => {
|
||||
return response.json();
|
||||
})
|
||||
.then((item: { value: string }) => {
|
||||
return item.value;
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public getBatchRequest = (followDocuments: FollowDocument[], graphQuery: string, method: string) => {
|
||||
let HeaderDriveItemsId = {
|
||||
"requests": []
|
||||
};
|
||||
let count = 1;
|
||||
let Items = [];
|
||||
followDocuments.forEach((element, index) => {
|
||||
if (count < 21) {
|
||||
HeaderDriveItemsId.requests.push({
|
||||
"url": graphQuery.replace("{driveId}", element.DriveId).replace("{ItemID}", element.ItemId).replace("{SiteId}", element.SiteId),
|
||||
"method": method,
|
||||
"id": count
|
||||
});
|
||||
count++;
|
||||
} else if (count === 21) {
|
||||
Items.push(HeaderDriveItemsId);
|
||||
HeaderDriveItemsId = {
|
||||
"requests": []
|
||||
};
|
||||
count = 1;
|
||||
HeaderDriveItemsId.requests.push({
|
||||
"url": graphQuery.replace("{driveId}", element.DriveId).replace("{ItemID}", element.ItemId).replace("{SiteId}", element.SiteId),
|
||||
"method": method,
|
||||
"id": count
|
||||
});
|
||||
count++;
|
||||
}
|
||||
if (index === followDocuments.length - 1) {
|
||||
Items.push(HeaderDriveItemsId);
|
||||
HeaderDriveItemsId = {
|
||||
"requests": []
|
||||
};
|
||||
count = 1;
|
||||
}
|
||||
});
|
||||
return Items;
|
||||
}
|
||||
|
||||
/************************************************************************************* */
|
||||
|
||||
private onActionTeamsClick = (action: FollowDocument, ev: React.SyntheticEvent<HTMLElement>): void => {
|
||||
|
||||
const dialog: FollowDocumentDialog = new FollowDocumentDialog();
|
||||
dialog.initializedTeams(action, this.props.context, followType.SendTeams);
|
||||
|
@ -115,41 +292,12 @@ export default class FollowDocumentWebPart extends React.Component<IFollowDocume
|
|||
ev.preventDefault();
|
||||
}
|
||||
|
||||
private getSearchListItemID = async (ListId: string): Promise<string> => {
|
||||
const graphService: Graph = new Graph();
|
||||
const initialized = await graphService.initialize(this.props.context.serviceScope);
|
||||
if (initialized) {
|
||||
const HeaderListId = {
|
||||
"requests": [
|
||||
{
|
||||
"entityTypes": [
|
||||
"list"
|
||||
],
|
||||
"query": {
|
||||
"queryString": "ListID:" + ListId + ""
|
||||
},
|
||||
"fields": [
|
||||
"webUrl"
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
const tmpFileID = await graphService.postGraphContent("https://graph.microsoft.com/beta/search/query", HeaderListId);
|
||||
console.log(tmpFileID);
|
||||
return tmpFileID.value[0].hitsContainers[0].hits[0].resource.webUrl.substring(0, tmpFileID.value[0].hitsContainers[0].hits[0].resource.webUrl.lastIndexOf("/"));
|
||||
}
|
||||
}
|
||||
private getListItemID = async (ListID, ItemID) => {
|
||||
const _ListId = await this.getSearchListItemID(ListID);
|
||||
const dialog: FollowDocumentDialog = new FollowDocumentDialog();
|
||||
dialog.initialize(_ListId + "/dispForm.aspx?ID=" + ItemID, followType.ViewPropreties);
|
||||
}
|
||||
|
||||
private _showPanel = (Url: string, Title: string): void => {
|
||||
private _showPanel = (followDocument: FollowDocument): void => {
|
||||
this._renderPanelComponent({
|
||||
FollowDocument: followDocument,
|
||||
context: this.props.context,
|
||||
url: Url,
|
||||
filename: Title,
|
||||
url: followDocument.Url,
|
||||
filename: followDocument.Title,
|
||||
isOpen: true,
|
||||
});
|
||||
}
|
||||
|
@ -159,15 +307,16 @@ export default class FollowDocumentWebPart extends React.Component<IFollowDocume
|
|||
ReactDom.render(element, this._panelPlaceHolder);
|
||||
}
|
||||
|
||||
private onActionPropertiesClick = (action: any, ev: React.SyntheticEvent<HTMLElement>): void => {
|
||||
private onActionPropertiesClick = (action: FollowDocument, ev: React.SyntheticEvent<HTMLElement>): void => {
|
||||
//Get Document Display Form List
|
||||
this.getListItemID(action.fields.ListId.replace('{', '').replace('}', ''), action.fields.ItemId);
|
||||
const dialog: FollowDocumentDialog = new FollowDocumentDialog();
|
||||
dialog.initialize(action.ItemProperties, followType.ViewPropreties);
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
private onActionFolderClick = (action: any, ev: React.SyntheticEvent<HTMLElement>): void => {
|
||||
window.open(action.fields.Url.replace(action.fields.Title, ""), "_blank");
|
||||
private onActionFolderClick = (action: FollowDocument, ev: React.SyntheticEvent<HTMLElement>): void => {
|
||||
window.open(action.Url.replace(action.Title, ""), "_blank");
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
@ -175,159 +324,43 @@ export default class FollowDocumentWebPart extends React.Component<IFollowDocume
|
|||
/**
|
||||
* Unfollow Option
|
||||
*/
|
||||
private onActionUnfollowClick = async (action: any, ev: React.SyntheticEvent<HTMLElement>) => {
|
||||
private onActionUnfollowClick = async (action: FollowDocument, ev: React.SyntheticEvent<HTMLElement>) => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
const dialog: FollowDocumentDialog = new FollowDocumentDialog();
|
||||
dialog._followTypeDialog = followType.Unfollow;
|
||||
dialog._filename = action.fields.Title;
|
||||
dialog._filename = action.Title;
|
||||
dialog.show().then(async () => {
|
||||
if (dialog._followDocumentState) {
|
||||
const restService: Rest = new Rest();
|
||||
const Status = await restService.stopfollowing(
|
||||
this.props.context.spHttpClient,
|
||||
action.fields.Url,
|
||||
this.props.context.pageContext.web.absoluteUrl,
|
||||
);
|
||||
if (Status) {
|
||||
dialog._followDocumentState = false;
|
||||
this.getListItems();
|
||||
const graphService: Graph = new Graph();
|
||||
const initialized = await graphService.initialize(this.props.context.serviceScope);
|
||||
if (initialized) {
|
||||
const graphData: any = await graphService.postGraphContent(`https://graph.microsoft.com/v1.0/drives/${action.DriveId}/items/${action.ItemId}/unfollow`, "");
|
||||
if (graphData === undefined) {
|
||||
dialog._followDocumentState = false;
|
||||
this.getListItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onActionPanelClick = async (action: any, ev: React.SyntheticEvent<HTMLElement>) => {
|
||||
this._showPanel(action.fields.Url, action.fields.Title);
|
||||
private onActionPanelClick = async (action: FollowDocument, ev: React.SyntheticEvent<HTMLElement>) => {
|
||||
this._showPanel(action);
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
private getGraphFollowedDocs = async () => {
|
||||
const GraphService: Graph = new Graph();
|
||||
let DriveItem: any = [];
|
||||
|
||||
if (this.state.siteId === null) {
|
||||
let graphData: any = await GraphService.getGraphContent("https://graph.microsoft.com/v1.0/me/drive/list", this.props.context);
|
||||
this._siteId = graphData.parentReference.siteId;
|
||||
DriveItem = await this.getListID(graphData.parentReference.siteId);
|
||||
} else {
|
||||
if (this.state.listId === null) {
|
||||
DriveItem = await this.getListID(this.state.siteId);
|
||||
} else {
|
||||
DriveItem = await this.getFollowDocuments(this.state.siteId, this.state.listId);
|
||||
|
||||
}
|
||||
}
|
||||
let items = [];
|
||||
DriveItem.forEach(element => {
|
||||
if (element.fields.IconUrl.indexOf("lg_iczip.gif") > -1) {
|
||||
element.fields.IconUrl = element.fields.IconUrl.replace("lg_iczip.gif", "lg_iczip.png");
|
||||
}
|
||||
if (element.fields.IconUrl.indexOf("lg_icmsg.png") > -1) {
|
||||
element.fields.IconUrl = element.fields.IconUrl.replace("lg_icmsg.png", "lg_icmsg.gif");
|
||||
}
|
||||
items.push({
|
||||
thumbnail: element.previewImg,
|
||||
title: element.fields.Title,
|
||||
profileImageSrc: element.fields.IconUrl,
|
||||
url: (element.fields.ServerUrlProgid === undefined ? element.fields.Url : element.fields.ServerUrlProgid.substring(1)),
|
||||
webName: element.WebName,
|
||||
webUrl: element.WebUrl,
|
||||
documentCardActions: [
|
||||
{
|
||||
iconProps: { iconName: 'TeamsLogo' },
|
||||
onClick: this.onActionTeamsClick.bind(this, element),
|
||||
ariaLabel: 'Send to Teams',
|
||||
},
|
||||
{
|
||||
iconProps: { iconName: 'FabricFolder' },
|
||||
onClick: this.onActionFolderClick.bind(this, element),
|
||||
ariaLabel: 'open Folder',
|
||||
},
|
||||
{
|
||||
iconProps: { iconName: 'FavoriteStarFill' },
|
||||
onClick: this.onActionUnfollowClick.bind(this, element),
|
||||
ariaLabel: 'Unfollow Document',
|
||||
},
|
||||
{
|
||||
iconProps: { iconName: 'Info' },
|
||||
onClick: this.onActionPropertiesClick.bind(this, element),
|
||||
ariaLabel: 'Document info',
|
||||
},
|
||||
{
|
||||
iconProps: { iconName: 'DocumentSearch' },
|
||||
onClick: this.onActionPanelClick.bind(this, element),
|
||||
ariaLabel: 'Preview',
|
||||
},
|
||||
|
||||
]
|
||||
});
|
||||
|
||||
});
|
||||
let uniq = {};
|
||||
let group: Array<IDropdownOption> = new Array<IDropdownOption>();
|
||||
//Remove duplicated from array
|
||||
let uniqueArray = [];
|
||||
uniqueArray = items.filter(obj => !uniq[obj.webUrl] && (uniq[obj.webUrl] = true));
|
||||
group.push({ key: '0', text: 'All Sites' });
|
||||
uniqueArray.forEach(element => {
|
||||
group.push({
|
||||
key: element.webUrl,
|
||||
text: "Site: " + element.webName,
|
||||
});
|
||||
});
|
||||
this.setState({
|
||||
Items: items,
|
||||
ItemsSearch: items,
|
||||
ItemsGroup: group,
|
||||
visible: false,
|
||||
siteId: this._siteId,
|
||||
listId: this._listId
|
||||
});
|
||||
|
||||
}
|
||||
private getListID = async (siteId: string): Promise<string> => {
|
||||
const GraphService: Graph = new Graph();
|
||||
let graphData: any = await GraphService.getGraphContent(`https://graph.microsoft.com/v1.0/sites/${siteId}/lists?$select=id&$filter=displayName eq 'Social'`, this.props.context);
|
||||
this._listId = graphData.value[0].id;
|
||||
const DriveItem: string = await this.getFollowDocuments(siteId, graphData.value[0].id);
|
||||
return DriveItem;
|
||||
}
|
||||
|
||||
private getFollowDocuments = async (siteId: string, listId: string): Promise<any> => {
|
||||
const GraphService: Graph = new Graph();
|
||||
let graphData: any = [];
|
||||
graphData = await GraphService.getGraphContent(`https://graph.microsoft.com/v1.0/sites/${siteId}/Lists/${listId}/items?expand=fields(select=ItemId,ListId,SiteId,webId,Title,Url,ServerUrlProgid,IconUrl,File_x0020_Type.progid)&$filter=fields/ItemId gt -1`, this.props.context);
|
||||
graphData.value = graphData.value.sort((a, b) => {
|
||||
return b.id - a.id;
|
||||
});
|
||||
|
||||
//Get Web site Name
|
||||
graphData = await this.getFollowDocumentsWebName(graphData);
|
||||
return graphData;
|
||||
}
|
||||
|
||||
private getFollowDocumentsWebName = async (graphData) => {
|
||||
let _webs = [];
|
||||
graphData.value.forEach(element => {
|
||||
if (_webs.indexOf(element.fields.WebId) === -1) {
|
||||
_webs.push(element.fields.WebId);
|
||||
}
|
||||
});
|
||||
graphData = await this.getSearchWebID(graphData.value, _webs);
|
||||
return graphData;
|
||||
}
|
||||
public render(): React.ReactElement<IFollowDocumentWebPartProps> {
|
||||
//Filter Search Text
|
||||
const checkSearchDrive = (SearchQuery: string) => {
|
||||
let items = [];
|
||||
if (this._selectedGroup === "0") {
|
||||
items = this.state.Items.filter(item => (item.title.toLowerCase().indexOf(SearchQuery.toLowerCase()) > -1));
|
||||
items = this.state.Items.filter(item => (item.Title.toLowerCase().indexOf(SearchQuery.toLowerCase()) > -1));
|
||||
|
||||
} else {
|
||||
items = this.state.Items.filter(item => (item.title.toLowerCase().indexOf(SearchQuery.toLowerCase()) > -1 && item.webUrl.toLowerCase().indexOf(this._selectedGroup.toLowerCase()) > -1));
|
||||
items = this.state.Items.filter(item => (item.Title.toLowerCase().indexOf(SearchQuery.toLowerCase()) > -1 && item.WebUrl.toLowerCase().indexOf(this._selectedGroup.toLowerCase()) > -1));
|
||||
}
|
||||
this.setState({
|
||||
ItemsSearch: items,
|
||||
|
@ -339,7 +372,7 @@ export default class FollowDocumentWebPart extends React.Component<IFollowDocume
|
|||
items = this.state.Items;
|
||||
|
||||
} else {
|
||||
items = this.state.Items.filter(item => (item.webUrl.toLowerCase().indexOf(this._selectedGroup.toLowerCase()) > -1));
|
||||
items = this.state.Items.filter(item => (item.WebUrl.toLowerCase().indexOf(this._selectedGroup.toLowerCase()) > -1));
|
||||
}
|
||||
this.setState({
|
||||
ItemsSearch: items,
|
||||
|
@ -353,7 +386,7 @@ export default class FollowDocumentWebPart extends React.Component<IFollowDocume
|
|||
ItemsSearch: this.state.Items,
|
||||
});
|
||||
} else {
|
||||
const items = this.state.Items.filter(item => item.webUrl.toLowerCase().indexOf(selectedOption.key.toString().toLowerCase()) > -1);
|
||||
const items = this.state.Items.filter(item => item.WebUrl.toLowerCase() === selectedOption.key.toString().toLowerCase());
|
||||
this.setState({
|
||||
ItemsSearch: items,
|
||||
});
|
||||
|
@ -405,31 +438,26 @@ export default class FollowDocumentWebPart extends React.Component<IFollowDocume
|
|||
<div className={styles.grid}>
|
||||
<FollowDocumentGrid
|
||||
items={this.state.ItemsSearch}
|
||||
onRenderGridItem={(item: any, finalSize: ISize, isCompact: boolean) => this._onRenderGridItem(item, finalSize, isCompact)}
|
||||
onRenderGridItem={(item, finalSize: ISize, isCompact: boolean) => this._onRenderGridItem(item, finalSize, isCompact)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
private _onRenderGridItem = (item: any, finalSize: ISize, isCompact: boolean): JSX.Element => {
|
||||
private _onRenderGridItem = (item: FollowDocument, finalSize: ISize, isCompact: boolean): JSX.Element => {
|
||||
|
||||
return <div
|
||||
className={styles.documentTile}
|
||||
data-is-focusable={true}
|
||||
role="listitem"
|
||||
aria-label={item.title}
|
||||
>
|
||||
return <div className={styles.documentTile} data-is-focusable={true} aria-label={item.Title} >
|
||||
<DocumentCard
|
||||
type={isCompact ? DocumentCardType.compact : DocumentCardType.normal}
|
||||
|
||||
>
|
||||
<div style={{ cursor: 'pointer' }} onClick={() => window.open(item.url, '_blank')}>
|
||||
<DocumentCardImage height={100} imageFit={ImageFit.center} imageSrc={item.profileImageSrc} />
|
||||
<div style={{ cursor: 'pointer' }} onClick={() => window.open(item.WebFileUrl, '_blank')}>
|
||||
<DocumentCardImage height={100} imageFit={ImageFit.center} imageSrc={item.IconUrl} />
|
||||
</div>
|
||||
{!isCompact && <DocumentCardLocation location={item.webName} onClick={() => window.open(item.webUrl, '_blank')} />}
|
||||
{!isCompact && <DocumentCardLocation location={item.WebName} onClick={() => window.open(item.WebUrl, '_blank')} />}
|
||||
<DocumentCardDetails>
|
||||
<DocumentCardTitle
|
||||
title={item.title}
|
||||
title={item.Title}
|
||||
shouldTruncate={true}
|
||||
/>
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
|
||||
import { FollowDocument } from "../models/followDocument";
|
||||
export interface IFollowDocumentWebPartState {
|
||||
siteId?: string;
|
||||
listId?: string;
|
||||
Items: any;
|
||||
Items: FollowDocument[];
|
||||
ItemsSearch?: any;
|
||||
ItemsGroup?: IDropdownOption[];
|
||||
previewImgUrl:string;
|
||||
|
|
|
@ -8,14 +8,15 @@ import { BaseDialog, IDialogConfiguration } from '@microsoft/sp-dialog';
|
|||
import { DialogContent, DialogFooter } from 'office-ui-fabric-react/lib/Dialog';
|
||||
import { FollowDocumentProperties } from '../followDocumentProperties/followDocumentProperties';
|
||||
import { FollowDocumentSendMessage } from '../followDocumentSendMessage/followDocumentSendMessage';
|
||||
import { FollowDocument } from '../../models/followDocument';
|
||||
|
||||
export default class FollowDocumentDialog extends BaseDialog {
|
||||
public _followDocumentState: boolean = false;
|
||||
private _webUrl: string;
|
||||
public _filename:string;
|
||||
private _context:WebPartContext;
|
||||
public _filename: string;
|
||||
private _context: WebPartContext;
|
||||
public _followTypeDialog: followType;
|
||||
public _fileInfo: any;
|
||||
public _fileInfo: FollowDocument;
|
||||
public return: (string) => void;
|
||||
|
||||
|
||||
|
@ -24,7 +25,7 @@ export default class FollowDocumentDialog extends BaseDialog {
|
|||
this._followTypeDialog = type;
|
||||
this.show();
|
||||
}
|
||||
public async initializedTeams(fileInfo: any,context:WebPartContext, type: followType) {
|
||||
public async initializedTeams(fileInfo: FollowDocument, context: WebPartContext, type: followType) {
|
||||
this._context = context;
|
||||
this._fileInfo = fileInfo;
|
||||
this._followTypeDialog = type;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
import { FollowDocument } from "../../models/followDocument";
|
||||
export interface IfollowDocumentPreviewProps {
|
||||
isOpen: boolean;
|
||||
url?:string;
|
||||
filename?:string;
|
||||
context: WebPartContext;
|
||||
visible?:boolean;
|
||||
FollowDocument: FollowDocument;
|
||||
|
||||
}
|
|
@ -33,21 +33,10 @@ export class followDocumentPreview extends React.Component<IfollowDocumentPrevie
|
|||
const graphService: Graph = new Graph();
|
||||
const initialized = await graphService.initialize(this.props.context.serviceScope);
|
||||
if (initialized) {
|
||||
const HeaderItem = {
|
||||
"requests": [
|
||||
{
|
||||
"entityTypes": ["driveItem"],
|
||||
"query": {
|
||||
"queryString": "path:\"" + this.props.url.replace(this.props.filename, "") + "\" Filename:\"" + this.props.filename + "\"",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
const tmpFileID = await graphService.postGraphContent("https://graph.microsoft.com/beta/search/query", HeaderItem);
|
||||
let graphData: any = await graphService.postGraphContent(`https://graph.microsoft.com/v1.0/drives/${tmpFileID.value[0].hitsContainers[0].hits[0].resource.parentReference.driveId}/items/${tmpFileID.value[0].hitsContainers[0].hits[0].resource.id}/preview`, {});
|
||||
let graphData: any = await graphService.postGraphContent(`https://graph.microsoft.com/v1.0/drives/${this.props.FollowDocument.DriveId}/items/${this.props.FollowDocument.ItemId}/preview`, {});
|
||||
this.setState({
|
||||
preview: graphData.getUrl,
|
||||
name: tmpFileID.value[0].hitsContainers[0].hits[0].resource.name,
|
||||
name: this.props.FollowDocument.Title,
|
||||
visible: true,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
import { FollowDocument } from '../../models/followDocument';
|
||||
export interface IfollowDocumentSendMessageProps {
|
||||
close: () => void;
|
||||
url: string;
|
||||
context: WebPartContext;
|
||||
fileInfo:any;
|
||||
fileInfo: FollowDocument;
|
||||
}
|
|
@ -9,6 +9,7 @@ import { ITag, } from "office-ui-fabric-react/lib/Pickers";
|
|||
import * as AdaptiveCards from "adaptivecards";
|
||||
import Graph from "../../Service/GraphService";
|
||||
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
|
||||
import { FollowDocument } from '../../models/followDocument';
|
||||
|
||||
export class FollowDocumentSendMessage extends React.Component<IfollowDocumentSendMessageProps, IfollowDocumentSendMessageState> {
|
||||
private card: any;
|
||||
|
@ -47,7 +48,7 @@ export class FollowDocumentSendMessage extends React.Component<IfollowDocumentSe
|
|||
const HeadersendMessage = {
|
||||
"body": {
|
||||
"contentType": "html",
|
||||
"content": this._acContainer.innerHTML + `<a href="${(this.props.fileInfo.fields.ServerUrlProgid === undefined ? this.props.fileInfo.fields.Url : this.props.fileInfo.fields.ServerUrlProgid.substring(1))}">${this.props.fileInfo.fields.Title}</a>`
|
||||
"content": this._acContainer.innerHTML + `<a href="${this.props.fileInfo.WebFileUrl}">${this.props.fileInfo.Title}</a>`
|
||||
}
|
||||
};
|
||||
const getresult = await graphService.postGraphContent(`https://graph.microsoft.com/v1.0/teams/${this.state.selectedTeamId}/channels/${this.state.selectedTeamChannelId}/messages`, HeadersendMessage);
|
||||
|
@ -67,7 +68,7 @@ export class FollowDocumentSendMessage extends React.Component<IfollowDocumentSe
|
|||
return getresult;
|
||||
}
|
||||
|
||||
public showAdaptiveCard(fileInfo: any) {
|
||||
public showAdaptiveCard(fileInfo: FollowDocument) {
|
||||
|
||||
this.card = {
|
||||
"type": "AdaptiveCard",
|
||||
|
@ -86,7 +87,7 @@ export class FollowDocumentSendMessage extends React.Component<IfollowDocumentSe
|
|||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"url": fileInfo.fields.IconUrl,
|
||||
"url": fileInfo.IconUrl,
|
||||
"size": "Small",
|
||||
"spacing": "Medium"
|
||||
}
|
||||
|
@ -99,7 +100,7 @@ export class FollowDocumentSendMessage extends React.Component<IfollowDocumentSe
|
|||
{
|
||||
"type": "TextBlock",
|
||||
"weight": "Bolder",
|
||||
"text": fileInfo.fields.Title,
|
||||
"text": fileInfo.Title,
|
||||
"wrap": true
|
||||
}
|
||||
],
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
export interface FollowDocument {
|
||||
id?: string;
|
||||
Title?: string;
|
||||
WebName?: string;
|
||||
WebUrl?: string;
|
||||
Domain?: string;
|
||||
Folder?: string;
|
||||
ItemProperties?: string;
|
||||
WebFileUrl?: string;
|
||||
DriveId?: string;
|
||||
ListId?: string;
|
||||
SiteId?: string;
|
||||
ItemId?: string;
|
||||
Url?: string;
|
||||
DownloadFile?: string;
|
||||
Thumbnail?: string;
|
||||
IconUrl?: string;
|
||||
documentCardActions?: Array<any>;
|
||||
preview?: string;
|
||||
Description?: string;
|
||||
followedDateTime?: Date;
|
||||
}
|
Loading…
Reference in New Issue