Merge pull request #2075 from joaojmendes/react-documents-links-accordion

New sample react-documents-links-accordion
This commit is contained in:
Hugo Bernier 2021-10-31 12:47:09 -04:00 committed by GitHub
commit aec067b288
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 24155 additions and 0 deletions

View File

@ -0,0 +1,33 @@
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
node_modules
# Build generated files
dist
lib
release
solution
temp
*.sppkg
# Coverage directory used by tools like istanbul
coverage
# OSX
.DS_Store
# Visual Studio files
.ntvs_analysis.dat
.vs
bin
obj
# Resx Generated Code
*.resx.ts
# Styles Generated Code
*.scss.ts

View File

@ -0,0 +1,12 @@
{
"@microsoft/generator-sharepoint": {
"isCreatingSolution": true,
"environment": "spo",
"version": "1.12.1",
"libraryName": "react-menu-accordion",
"libraryId": "f7134ff1-6e97-430c-9a73-5dc4902c75e3",
"packageManager": "npm",
"isDomainIsolated": false,
"componentType": "webpart"
}
}

View File

@ -0,0 +1,85 @@
# Documents Links Accordion
## Summary
This web part allows user create a accordion with documents links grouped by any column of document library.
When the user clicks on the header it dynamically load documents.
![documentsLinksAccordion](./assets/documentsLinksAccordion.gif)
## Screenshots
![documentsLinksAccordion](./assets/documentsLinksAccordion1.png)
## Compatibility
![SPFx 1.12.1](https://img.shields.io/badge/SPFx-1.12.1-green.svg)
![Node.js LTS v14 | LTS v12 | LTS v10](https://img.shields.io/badge/Node.js-LTS%20v14%20%7C%20LTS%20v12%20%7C%20LTS%20v10-green.svg)
![Compatible with SharePoint Online](https://img.shields.io/badge/SharePoint%20Online-Compatible-green.svg)
![Does not work with SharePoint 2019](https://img.shields.io/badge/SharePoint%20Server%202019-Incompatible-red.svg "SharePoint Server 2019 requires SPFx 1.4.1 or lower")
![Does not work with SharePoint 2016 (Feature Pack 2)](https://img.shields.io/badge/SharePoint%20Server%202016%20(Feature%20Pack%202)-Incompatible-red.svg "SharePoint Server 2016 Feature Pack 2 requires SPFx 1.1")
![Local Workbench Incompatible](https://img.shields.io/badge/Local%20Workbench-Incompatible-red.svg)
![Hosted Workbench Compatible](https://img.shields.io/badge/Hosted%20Workbench-Compatible-green.svg)
## Applies to
* [SharePoint Framework](https://docs.microsoft.com/sharepoint/dev/spfx/sharepoint-framework-overview)
* [Office 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
## Web Part Properties
Property |Type|Required| comments
--------------------|----|--------|----------
Web part Title| Text| no|
Select Document Library| dropdown|yes
Select Field to Group By | dropdown|yes
## Solution
The web part Use PnPjs library, Fluent-Ui-react components
Solution|Author(s)
--------|---------
React Documents Links Accordion |[João Mendes](https://github.com/joaojmendes) ([@joaojmendes](https://twitter.com/joaojmendes))
## Version history
Version|Date|Comments
-------|----|--------
1.0.0|October 10, 2021|Initial release
## Minimal Path to Awesome
- Clone this repository
- Move to sample folder
- in the command line run:
- `npm install`
- `gulp build`
- `gulp bundle --ship`
- `gulp package-solution --ship`
- Add to AppCatalog and deploy
## Disclaimer
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
## Help
We do not support samples, but we this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues.
If you encounter any issues while using this sample, [create a new issue](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=bug-report.yml&sample=react-list-items-menu&authors=@joaojmendes%20@Ravikadri&title=react-list-items-menu%20-%20).
For questions regarding this sample, [create a new question](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=question.yml&sample=react-list-items-menu&authors=@joaojmendes%20@Ravikadri&title=react-list-items-menu%20-%20).
Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/sp-dev-fx-webparts/issues/new?assignees=&labels=Needs%3A+Triage+%3Amag%3A%2Ctype%3Abug-suspected&template=suggestion.yml&sample=react-list-items-menu&authors=@joaojmendes%20@Ravikadri&title=react-list-items-menu%20-%20).
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-documents-links-accordion" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

View File

@ -0,0 +1,89 @@
[
{
"name": "pnp-sp-dev-spfx-web-parts-react-documents-links-accordion",
"source": "pnp",
"title": "Documents Links Accordion",
"shortDescription": "This web part allows user create a accordion with documents links grouped by any column of document library.",
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-documents-links-accordion",
"longDescription": [
"This web part allows user create a accordion with documents links grouped by any column of document library.",
"When the user clicks on the header it dynamically load documents."
],
"creationDateTime": "2021-10-10",
"updateDateTime": "2021-10-10",
"products": [
"SharePoint",
"Office"
],
"metadata": [
{
"key": "CLIENT-SIDE-DEV",
"value": "React"
},
{
"key": "SPFX-VERSION",
"value": "1.12.1"
},
{
"key": "SPFX-FULLPAGEAPP",
"value": "true"
},
{
"key": "SPFX-TEAMSTAB",
"value": "true"
},
{
"key": "SPFX-TEAMSPERSONALAPP",
"value": "true"
},
{
"key": "REACT-HOOKS",
"value": "true"
},
{
"key": "PNPCONTROLS",
"value": "PropertyFieldListPicker, PropertyFieldMessage, PropertyFieldSpinner, Placeholder, AccessibleAccordion"
}
],
"thumbnails": [
{
"type": "image",
"order": 100,
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-documents-links-accordion/assets/documentsLinksAccordion.gif",
"alt": "Web part in action"
},
{
"type": "image",
"order": 101,
"url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-documents-links-accordion/assets/documentsLinksAccordion1.png",
"alt": "Screen shot of web part"
}
],
"authors": [
{
"gitHubAccount": "joaojmendes",
"company": "Storm Technology Ltd",
"pictureUrl": "https://github.com/joaojmendes.png",
"name": "Jo\u00E3o Mendes",
"twitter": "joaojmendes"
}
],
"references": [
{
"name": "Build your first SharePoint client-side web part",
"description": "Client-side web parts are client-side components that run in the context of a SharePoint page. Client-side web parts can be deployed to SharePoint environments that support the SharePoint Framework. You can also use modern JavaScript web frameworks, tools, and libraries to build them.",
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/get-started/build-a-hello-world-web-part"
},
{
"name": "Supporting section backgrounds",
"description": "Starting with SharePoint Framework v1.8, web parts can be made aware of any section backgrounds and use these colors to improve the appearance of a web part when hosted in a section with a different background.",
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/web-parts/guidance/supporting-section-backgrounds"
},
{
"name": "Building Microsoft Teams Tabs using SharePoint Framework",
"description": "Starting with SharePoint Framework v1.8, you can build tabs for Microsoft Teams with the SharePoint Framework tooling and use SharePoint as a host for your solutions. As part of the SharePoint Framework v1.10 you can also publish your solution as Microsoft Teams personal app.",
"url": "https://docs.microsoft.com/en-us/sharepoint/dev/spfx/integrate-with-teams-introduction"
}
]
}
]

View File

@ -0,0 +1,20 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"documentslinksaccordion-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/DocumentsLinksAccordion/DocumentsLinksAccordionWebPart.js",
"manifest": "./src/webparts/DocumentsLinksAccordion/DocumentsLinksAccordionWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"DocumentsLinksAccordionWebPartStrings": "lib/webparts/DocumentsLinksAccordion/loc/{locale}.js",
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js",
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
"deployCdnPath": "./release/assets/"
}

View File

@ -0,0 +1,7 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./release/assets/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "react-menu-accordion",
"accessKey": "<!-- ACCESS KEY -->"
}

View File

@ -0,0 +1,21 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "react-documentslinks-accordion",
"id": "f7134ff1-6e97-430c-9a73-5dc4902c75e3",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"isDomainIsolated": false,
"developer": {
"name": "",
"websiteUrl": "",
"privacyUrl": "",
"termsOfUseUrl": "",
"mpnId": ""
}
},
"paths": {
"zippedPackage": "solution/react-documentslinks-accordion.sppkg"
}
}

View File

@ -0,0 +1,10 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://localhost:5432/workbench",
"api": {
"port": 5432,
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
}
}

View File

@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
"cdnBasePath": "<!-- PATH TO CDN -->"
}

View File

@ -0,0 +1,16 @@
'use strict';
const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
var getTasks = build.rig.getTasks;
build.rig.getTasks = function () {
var result = getTasks.call(build.rig);
result.set('serve', result.get('serve-deprecated'));
return result;
};
build.initialize(require('gulp'));

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
{
"name": "react-documentslinks-accordion",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"scripts": {
"build": "gulp bundle",
"clean": "gulp clean",
"test": "gulp test"
},
"dependencies": {
"@microsoft/sp-core-library": "1.12.1",
"@microsoft/sp-lodash-subset": "1.12.1",
"@microsoft/sp-office-ui-fabric-core": "1.12.1",
"@microsoft/sp-property-pane": "1.12.1",
"@microsoft/sp-webpart-base": "1.12.1",
"@pnp/spfx-controls-react": "^3.3.0",
"@pnp/spfx-property-controls": "^3.2.0",
"@uifabric/file-type-icons": "^7.8.1",
"date-fns": "^2.25.0",
"office-ui-fabric-react": "7.156.0",
"react": "16.9.0",
"react-dom": "16.9.0"
},
"devDependencies": {
"@types/react": "16.9.36",
"@types/react-dom": "16.9.8",
"@microsoft/sp-build-web": "1.12.1",
"@microsoft/sp-tslint-rules": "1.12.1",
"@microsoft/sp-module-interfaces": "1.12.1",
"@microsoft/sp-webpart-workbench": "1.12.1",
"@microsoft/rush-stack-compiler-3.7": "0.2.3",
"gulp": "~4.0.2",
"ajv": "~5.2.2",
"@types/webpack-env": "1.13.1"
}
}

View File

@ -0,0 +1,160 @@
import { SPComponentLoader } from "@microsoft/sp-loader";
import { sp } from "@pnp/sp";
import "@pnp/sp/webs";
import "@pnp/sp/regional-settings/web";
import { IRegionalSettingsInfo } from "@pnp/sp/regional-settings";
// get all the web's regional settings
const DEFAULT_PERSONA_IMG_HASH: string = "7ad602295f8386b7615b582d87bcc294";
const DEFAULT_IMAGE_PLACEHOLDER_HASH: string = "4a48f26592f4e1498d7a478a4c48609c";
const MD5_MODULE_ID: string = "8494e7d7-6b99-47b2-a741-59873e42f16f";
const PROFILE_IMAGE_URL: string = "/_layouts/15/userphoto.aspx?size=M&accountname=";
/**
* Gets user photo
* @param userId
* @returns user photo
*/
export const getUserPhoto = async (userId): Promise<string> => {
const personaImgUrl = PROFILE_IMAGE_URL + userId;
console.log(personaImgUrl);
// tslint:disable-next-line: no-use-before-declare
const url: string = await getImageBase64(personaImgUrl);
// tslint:disable-next-line: no-use-before-declare
const newHash = await getMd5HashForUrl(url);
if (newHash !== DEFAULT_PERSONA_IMG_HASH && newHash !== DEFAULT_IMAGE_PLACEHOLDER_HASH) {
return "data:image/png;base64," + url;
} else {
return "undefined";
}
};
/**
* Get MD5Hash for the image url to verify whether user has default image or custom image
* @param url
*/
export const getMd5HashForUrl = async (url: string) => {
return new Promise(async (resolve, reject) => {
// tslint:disable-next-line: no-use-before-declare
const library: any = await loadSPComponentById(MD5_MODULE_ID);
try {
const md5Hash = library.Md5Hash;
if (md5Hash) {
const convertedHash = md5Hash(url);
resolve(convertedHash);
}
} catch (error) {
resolve(url);
}
});
};
/**
* Load SPFx component by id, SPComponentLoader is used to load the SPFx components
* @param componentId - componentId, guid of the component library
*/
export const loadSPComponentById = async (componentId: string) => {
return new Promise((resolve, reject) => {
SPComponentLoader.loadComponentById(componentId)
.then((component: any) => {
resolve(component);
})
.catch((error) => {});
});
};
/**
* Gets image base64
* @param pictureUrl
* @returns image base64
*/
export const getImageBase64 = async (pictureUrl: string): Promise<string> => {
console.log(pictureUrl);
return new Promise((resolve, reject) => {
let image = new Image();
image.addEventListener("load", () => {
let tempCanvas = document.createElement("canvas");
(tempCanvas.width = image.width),
(tempCanvas.height = image.height),
tempCanvas.getContext("2d").drawImage(image, 0, 0);
let base64Str;
try {
base64Str = tempCanvas.toDataURL("image/png");
} catch (e) {
return "";
}
base64Str = base64Str.replace(/^data:image\/png;base64,/, "");
resolve(base64Str);
});
image.src = pictureUrl;
});
};
export const zeroPad = (num, places) => {
var zero = places - num.toString().length + 1;
return Array(+(zero > 0 && zero)).join("0") + num;
};
export const getSiteRegionalSettings = async (): Promise<IRegionalSettingsInfo> => {
try {
const s = await sp.web.regionalSettings();
return s;
} catch (error) {
console.error(error);
Promise.reject(error);
}
};
export const convertTimeTo12H = (
_hour: number
): Promise<{ hourInTimeFormat: string; current12HTimeFormat: string }> => {
console.log("prm", _hour);
return new Promise((resolve, rejected) => {
let hourInTimeFormat: string = "";
let current12HTimeFormat: string = "AM";
if (_hour >= 0 && _hour <= 11) {
if (_hour === 0) {
hourInTimeFormat = "12";
} else {
hourInTimeFormat = _hour.toString();
}
current12HTimeFormat = "AM";
} else {
const _hour12h = _hour - 12;
hourInTimeFormat = _hour12h === 0 ? "12" : _hour12h.toString();
current12HTimeFormat = "PM";
}
resolve({ hourInTimeFormat, current12HTimeFormat });
});
};
export const convertTimeTo24h = (hour: number, current12HTimeFormat: string): Promise<string> => {
return new Promise((resolve, rejected) => {
let hourInTimeFormat: string = "";
if (current12HTimeFormat === "PM") {
if (hour >= 1 && hour <= 11) {
if (hour === 12) {
hourInTimeFormat = "12";
}
const _hour24h = hour + 12;
hourInTimeFormat = _hour24h.toString();
}
} else {
if (hour === 12) {
hourInTimeFormat = "00";
} else {
hourInTimeFormat = hour.toString();
}
}
resolve(hourInTimeFormat);
});
};
/* Check if string is valid date */
export const checkIfValidDate = (str:string):boolean => {
// Regular expression to check if string is valid date
const regexExp = /(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})/gi;
return regexExp.test(str);
};

View File

@ -0,0 +1,16 @@
import * as React from "react";
import { WebPartContext } from "@microsoft/sp-webpart-base";
import { MSGraphClient, AadTokenProvider } from "@microsoft/sp-http";
import { IReadonlyTheme } from '@microsoft/sp-component-base';
import { IRegionalSettingsInfo} from "@pnp/sp/regional-settings";
export interface IAppContextProps {
currentUser:string;
msGraphClient:MSGraphClient;
locale:string;
themeVariant: IReadonlyTheme | undefined;
}
export const AppContext = React.createContext<IAppContextProps>(undefined);

View File

@ -0,0 +1,24 @@
{
"themePrimary": "#6264a7",
"themeLighterAlt": "#f7f7fb",
"themeLighter": "#e1e1f1",
"themeLight": "#c8c9e4",
"themeTertiary": "#989ac9",
"themeSecondary": "#7173b0",
"themeDarkAlt": "#585a95",
"themeDark": "#4a4c7e",
"themeDarker": "#37385d",
"neutralLighterAlt": "#0b0b0b",
"neutralLighter": "#151515",
"neutralLight": "#252525",
"neutralQuaternaryAlt": "#2f2f2f",
"neutralQuaternary": "#373737",
"neutralTertiaryAlt": "#595959",
"neutralTertiary": "#c8c8c8",
"neutralSecondary": "#d0d0d0",
"neutralPrimaryAlt": "#dadada",
"neutralPrimary": "#ffffff",
"neutralDark": "#f4f4f4",
"black": "#f8f8f8",
"white": "#000000"
}

View File

@ -0,0 +1,24 @@
{
"themePrimary": "#6264a7",
"themeLighterAlt": "#f7f7fb",
"themeLighter": "#e1e1f1",
"themeLight": "#c8c9e4",
"themeTertiary": "#989ac9",
"themeSecondary": "#7173b0",
"themeDarkAlt": "#585a95",
"themeDark": "#4a4c7e",
"themeDarker": "#37385d",
"neutralLighterAlt": "#2d2c2c",
"neutralLighter": "#2c2b2b",
"neutralLight": "#2a2929",
"neutralQuaternaryAlt": "#272626",
"neutralQuaternary": "#252525",
"neutralTertiaryAlt": "#242323",
"neutralTertiary": "#c8c8c8",
"neutralSecondary": "#d0d0d0",
"neutralPrimaryAlt": "#dadada",
"neutralPrimary": "#ffffff",
"neutralDark": "#f4f4f4",
"black": "#f8f8f8",
"white": "#2d2c2c"
}

View File

@ -0,0 +1,24 @@
{
"themePrimary": "#6264a7",
"themeLighterAlt": "#f7f7fb",
"themeLighter": "#e1e1f1",
"themeLight": "#c8c9e4",
"themeTertiary": "#989ac9",
"themeSecondary": "#7173b0",
"themeDarkAlt": "#585a95",
"themeDark": "#4a4c7e",
"themeDarker": "#37385d",
"neutralLighterAlt": "#ecebe9",
"neutralLighter": "#e8e7e6",
"neutralLight": "#dedddc",
"neutralQuaternaryAlt": "#cfcecd",
"neutralQuaternary": "#c6c5c4",
"neutralTertiaryAlt": "#bebdbc",
"neutralTertiary": "#b5b4b2",
"neutralSecondary": "#9d9c9a",
"neutralPrimaryAlt": "#868482",
"neutralPrimary": "#252423",
"neutralDark": "#565453",
"black": "#3e3d3b",
"white": "#f3f2f1"
}

View File

@ -0,0 +1,28 @@
import { IDatePickerStrings } from 'office-ui-fabric-react/lib/DatePicker';
export const DayPickerStrings: IDatePickerStrings = {
months: [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
],
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
shortDays: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
goToToday: 'Go to today',
prevMonthAriaLabel: 'Go to previous month',
nextMonthAriaLabel: 'Go to next month',
prevYearAriaLabel: 'Go to previous year',
nextYearAriaLabel: 'Go to next year',
closeButtonAriaLabel: 'Close date picker',
monthPickerHeaderAriaLabel: '{0}, select to change the year',
yearPickerHeaderAriaLabel: '{0}, select to change the month',
};

View File

@ -0,0 +1,16 @@
$default-background: #f3f2f1;
$default-color: #252423;
$default-button-background: #6264a7;
$default-Button-color: #f3f2f1;
// dark theme
$dark-background: #2d2c2c;
$dark-color: #ffffff;
$dark-button-background: #6264a7;
$dark-button-color: #2d2c2c;
// contrast theme
$contrast-background: #000000;
$contrast-color: #ffffff;
$contrast-button-background: #b5c01c;
$contrast-Button-color: #000000;

View File

@ -0,0 +1,74 @@
@import '~office-ui-fabric-react/dist/sass/References.scss';
.DocumentsLinksAccordion {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.row {
@include ms-Grid-row;
@include ms-fontColor-white;
background-color: $ms-color-themeDark;
padding: 20px;
}
.column {
@include ms-Grid-col;
@include ms-lg10;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
}
.title {
@include ms-font-xl;
@include ms-fontColor-white;
}
.subTitle {
@include ms-font-l;
@include ms-fontColor-white;
}
.description {
@include ms-font-l;
@include ms-fontColor-white;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
}
}

View File

@ -0,0 +1,356 @@
import * as React from "react";
import * as strings from "DocumentsLinksAccordionWebPartStrings";
import { filter, findIndex, uniqBy } from "lodash";
import {
Customizer,
INavLink,
INavLinkGroup,
Label,
Link,
mergeStyleSets,
MessageBar,
MessageBarType,
Spinner,
SpinnerSize,
Stack,
FontIcon,
Text,
DetailsList,
DetailsListLayoutMode,
IColumn,
SelectionMode,
IDetailsListProps,
IStackStyles,
IStyle,
IDetailsListStyles,
} from "office-ui-fabric-react";
import { getFileTypeIconProps } from "@uifabric/file-type-icons";
import { useList } from "../../hooks/useList";
import { IDocumentsLinksAccordionState } from "./IDocumentsLinksAccordionState";
import { IDocumentsLinksAccordionProps } from "./IDocumentsLinksAccordionProps";
import {
Accordion,
AccordionItem,
AccordionItemHeading,
AccordionItemButton,
AccordionItemPanel,
} from "@pnp/spfx-controls-react/lib/AccessibleAccordion";
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
const { getGroupItems, getGroupHeaders, getField } = useList();
export const DocumentsLinksAccordion: React.FunctionComponent<IDocumentsLinksAccordionProps> = (
props: IDocumentsLinksAccordionProps
) => {
const [state, setState] = React.useState<IDocumentsLinksAccordionState>({
navLinkGroups: [],
isLoading: false,
hasError: false,
errorMessage: "",
listName: "",
});
const stackItemStyles: Partial<IStackStyles> = React.useMemo(() => {
return {
root: {
padding: 7,
} as IStyle,
};
}, []);
const listViewStyles: Partial<IDetailsListStyles> = React.useMemo(() => {
return {
focusZone: {
width: "auto",
maxHeight: 450,
overflowY: "auto",
overflowX: "hidden",
"&::-webkit-scrollbar-thumb": {
backgroundColor: props.themeVariant?.palette?.neutralLighter,
},
"&::-webkit-scrollbar": {
width: "7.5px",
},
"scrollbar-color": props.themeVariant?.palette?.neutralLighter,
"scrollbar-width": "thin",
},
root: {
"&::-webkit-scrollbar-thumb": {
backgroundColor: props.themeVariant?.palette?.neutralLighter,
},
"&::-webkit-scrollbar": {
height: "7.5px",
},
"scrollbar-color": props.themeVariant?.palette?.neutralLighter,
"scrollbar-width": "thin",
},
};
}, []);
const classComponent = React.useMemo(() => {
return mergeStyleSets({
webPartTitle: {
fontWeight: 600,
overflowX: "hidden",
textOverflow: "Ellipsis",
fontSize: props.themeVariant.fonts.large.fontSize,
marginBottom: 10,
},
});
}, [props.themeVariant]);
const stateRef = React.useRef(state); // Use to access state on eventListenners
const columns: IColumn[] = React.useMemo(() => {
return [
{
key: "documentRenderLink",
name: "Document",
fieldName: "name",
minWidth: 400,
maxWidth: 800,
isResizable: false,
data: "string",
},
];
}, []);
const onRenderRow: IDetailsListProps["onRenderRow"] = (propsItem) => {
if (propsItem.item) {
const item = propsItem.item as INavLink;
return (
<Stack horizontal horizontalAlign="start" tokens={{ childrenGap: 10 }} styles={stackItemStyles}>
<FontIcon iconName={item.iconProps.iconName} />
<Link href={item.url} target="_blank">
<Text variant={"medium"}>{item.name}</Text>
</Link>
</Stack>
);
}
return null;
};
React.useEffect(() => {
(async () => {
if (!props.listId || !props.fieldName) {
return;
}
try {
let _navLinksGroups: INavLinkGroup[] = [];
stateRef.current = {
...stateRef.current,
isLoading: true,
navLinkGroups: _navLinksGroups,
};
setState(stateRef.current);
const _groupHeaders = await getGroupHeaders(props.listId, props.fieldName, props.listBaseTemplate);
const { fieldName } = props;
const _field: any = await getField(props.listId, props.fieldName);
for (const groupHeader of _groupHeaders) {
let _name: any;
switch (_field.fieldType) {
case "TaxonomyFieldType":
_name = groupHeader[fieldName]?.Label ?? "Unassigned";
break;
case "TaxonomyFieldTypeMulti":
_name = groupHeader[fieldName][0]?.Label ?? "Unassigned";
break;
case "User":
if (_name != "Unassigned") {
_name = groupHeader[fieldName][0]?.title;
}
break;
case "Lookup":
_name =
groupHeader[props.fieldName] !== "" &&
groupHeader[props.fieldName] !== undefined &&
groupHeader[props.fieldName][0].lookupValue !== ""
? groupHeader[props.fieldName][0]?.lookupValue
: "Unassigned";
break;
default:
_name =
groupHeader[props.fieldName] !== "" && groupHeader[props.fieldName] !== undefined
? groupHeader[props.fieldName]
: "Unassigned";
break;
}
_navLinksGroups.push({
name: _name,
groupData: _name,
collapseByDefault: true,
links: [],
});
// Ensure the groups name are unique!
_navLinksGroups = uniqBy(_navLinksGroups, "name");
}
stateRef.current = {
...stateRef.current,
hasError: false,
errorMessage: "",
isLoading: false,
listName: _field.fieldScope,
navLinkGroups: _navLinksGroups,
};
setState(stateRef.current);
} catch (error) {
stateRef.current = {
...stateRef.current,
hasError: true,
errorMessage: error.message,
};
setState(stateRef.current);
}
})();
}, [props.listId, props.fieldName]);
// On Header click get Items for the header
const onGroupHeaderClick = React.useCallback(
async (ev: React.MouseEvent<HTMLElement, MouseEvent>) => {
try {
const _groupName = ev.currentTarget.innerText;
const { navLinkGroups } = stateRef.current;
setState(stateRef.current);
const _navGroup = filter(navLinkGroups, { name: _groupName });
if (_navGroup?.length && _navGroup[0]?.links?.length === 0) {
const _navlinks: INavLink[] = [];
const _groupHeaderItems: any[] = await getGroupItems(
props.listId,
props.fieldName,
_groupName,
props.listBaseTemplate
);
if (_groupHeaderItems?.length) {
for (const _groupHeaderItem of _groupHeaderItems) {
if (props.listBaseTemplate === 0) {
// List
_navlinks.push({
name: _groupHeaderItem.Title,
url: `${_groupHeaderItem.FileDirRef}/dispform.aspx?ID=${_groupHeaderItem.Id}`,
iconProps: {
iconName: "TaskManager",
},
key: _groupHeaderItem.Id,
target: "_blank",
isExpanded: false,
});
}
if (props.listBaseTemplate === 1) {
// Document Library
_navlinks.push({
name: _groupHeaderItem.FileLeafRef,
url: _groupHeaderItem.FileRef,
iconProps: {
...getFileTypeIconProps({
extension: _groupHeaderItem.DocIcon,
size: 16,
imageFileType: "svg",
}),
},
key: _groupHeaderItem.title,
target: "_blank",
isExpanded: false,
});
}
}
}
// Update Navigation with Items of Group
_navGroup[0].links = _navlinks;
}
const _index = findIndex(navLinkGroups, { name: _groupName });
_navGroup[0].collapseByDefault = true;
navLinkGroups[_index] = _navGroup[0];
stateRef.current = { ...stateRef.current, navLinkGroups: navLinkGroups };
setState(stateRef.current);
} catch (error) {}
},
[props]
);
// Show Error if Exists
if (state.hasError) {
return (
<>
<MessageBar messageBarType={MessageBarType.error} isMultiline>
{state.errorMessage}
</MessageBar>
</>
);
}
// render component
if (!props.listId || !props.fieldName) {
return (
<Placeholder
iconName="Edit"
iconText={strings.PlaceHolderIconText}
description={strings.PlaceHolderDescription}
buttonLabel={strings.PlaceHolderButtonLable}
onConfigure={props.onConfigure}
/>
);
}
return (
<>
<Customizer settings={{ theme: props.themeVariant }}>
{state.isLoading ? (
<Stack horizontal horizontalAlign="center">
<Spinner size={SpinnerSize.medium}></Spinner>
</Stack>
) : (
<>
<Stack horizontalAlign="space-between" horizontal tokens={{ childrenGap: 10 }} style={{ width: "100%" }}>
<div className={classComponent.webPartTitle}>{props.title}</div>
<Link href={state.listName}>{strings.ViewAllLabel}</Link>
</Stack>
{state.navLinkGroups?.length === 0 ? (
<Label
style={{
fontWeight: 400,
fontSize: props.themeVariant.fonts.small.fontSize,
}}
>
{strings.NodocumentsLabel}
</Label>
) : (
<Accordion allowZeroExpanded allowMultipleExpanded>
{state.navLinkGroups.map((item, i) => {
return (
<AccordionItem>
<AccordionItemHeading
onClick={async (ev) => {
await onGroupHeaderClick(ev);
}}
>
<AccordionItemButton>{item.name}</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel>
<Stack>
<DetailsList
items={item.links}
columns={columns}
styles={listViewStyles}
compact={true}
selectionMode={SelectionMode.none}
setKey="none"
layoutMode={DetailsListLayoutMode.justified}
isHeaderVisible={false}
onRenderRow={onRenderRow}
/>
</Stack>
</AccordionItemPanel>
</AccordionItem>
);
})}
</Accordion>
)}
</>
)}
</Customizer>
</>
);
};

View File

@ -0,0 +1,14 @@
import { IReadonlyTheme } from "@microsoft/sp-component-base";
import { DisplayMode } from "@microsoft/sp-core-library";
export interface IDocumentsLinksAccordionProps {
title: string;
listId:string;
listBaseTemplate:number;
fieldName:string;
locale:string;
themeVariant: IReadonlyTheme | undefined;
onConfigure: () => void;
}

View File

@ -0,0 +1,9 @@
import { Nav, INavStyles, INavLinkGroup } from 'office-ui-fabric-react/lib/Nav';
export interface IDocumentsLinksAccordionState {
navLinkGroups: INavLinkGroup[];
isLoading: boolean;
hasError:boolean;
errorMessage:string;
listName:string;
}

View File

@ -0,0 +1,6 @@
export interface IListItemsMenu {
groupBy:string;
id:string;
title:string;
url:string;
}

View File

@ -0,0 +1,137 @@
import "@pnp/sp/fields";
import "@pnp/sp/items";
import "@pnp/sp/lists";
import "@pnp/sp/webs";
import { sortBy, uniqBy } from "lodash";
import { sp } from "@pnp/sp";
import { IFieldInfo } from "@pnp/sp/fields";
import { IListInfo } from "@pnp/sp/lists";
import * as moment from "moment";
import { format, parse, parseISO } from "date-fns";
import { checkIfValidDate } from "../Utils/Utils";
export const useList = () => {
// Run on useList hook
(async () => {})();
// Get List Columns
const getListColumns = async (listId: string): Promise<IFieldInfo[]> => {
const _listColumnsResults: IFieldInfo[] = await sp.web.lists.getById(listId).fields.filter("Hidden eq false").get();
const _wColumns: IFieldInfo[] = uniqBy(sortBy(_listColumnsResults, "Title"), "Title");
return _wColumns;
};
const getField = async (listId: string, field: string): Promise<any> => {
const _field: IFieldInfo = await sp.web.lists.getById(listId).fields.getByInternalNameOrTitle(field).get();
const fieldType = _field.TypeAsString;
const fieldScope = _field.Scope;
return { fieldType, fieldScope };
};
const getGroupHeaders = async (listId: string, groupByField: string, baseTemplate: number): Promise<any[]> => {
let _viewXml = `<View Scope='Recursive'>
<Query>
<GroupBy Collapse="TRUE">
<FieldRef Name="${groupByField}"/>
</GroupBy>
</Query>
<RowLimit>1000</RowLimit>
</View>`;
const _groupHeadersResults = await sp.web.lists.getById(listId).renderListDataAsStream({ ViewXml: _viewXml });
return uniqBy(_groupHeadersResults.Row, groupByField);
};
const getGroupItems = async (
listId: string,
groupByField: string,
groupFieldValue: string,
baseTemplate: number
): Promise<any[]> => {
const _field: any = await getField(listId, groupByField);
if (checkIfValidDate(groupFieldValue)) {
groupFieldValue = format(new Date(groupFieldValue), "yyyy-MM-dd");
}
switch (_field.fieldType) {
case "DateTime":
groupFieldValue =
groupFieldValue != "Unassigned" ? format(parseISO(groupFieldValue), "yyyy-MM-dd") : "Unassigned";
break;
case "AllDayEvent":
groupFieldValue = groupFieldValue === "No" ? "0" : "1";
break;
default:
break;
}
let _viewXml = `<View Scope='Recursive'>
<Query>
<OrderBy>
<FieldRef Name="${groupByField}" Ascending="FALSE"></FieldRef>
</OrderBy>
</Query>
</View>`;
if (groupFieldValue != "Unassigned") {
_viewXml = `<View Scope='Recursive'>
<Query>
<OrderBy>
<FieldRef Name="${groupByField}" Ascending="FALSE"></FieldRef>
</OrderBy>
<Where>
<Eq>
<FieldRef Name="${groupByField}"></FieldRef>
<Value Type="${_field.fieldType}" IncludeTimeValue="FALSE">${groupFieldValue}</Value>
</Eq>
</Where>
</Query>
</View>`;
}
if (groupFieldValue === "Unassigned") {
_viewXml = `<View Scope='Recursive'>
<Query>
<OrderBy>
<FieldRef Name="${groupByField}" Ascending="FALSE"></FieldRef>
</OrderBy>
<Where>
<IsNull>
<FieldRef Name="${groupByField}"></FieldRef>
</IsNull>
</Where>
</Query>
</View>`;
}
const _groupItemsResults = await sp.web.lists.getById(listId).renderListDataAsStream({ ViewXml: _viewXml });
return _groupItemsResults.Row;
};
// Get Lists
const getLists = async (baseTemplate: number): Promise<IListInfo[]> => {
let _filter: string = "Hidden eq false and ";
if (baseTemplate === 0) {
_filter = _filter + " BaseType ne 1";
} else {
_filter = _filter + " BaseType eq 1";
}
const _lists: IListInfo[] = await sp.web.lists.filter(_filter).get();
return _lists;
};
// Return functions
return {
getListColumns,
getLists,
getGroupItems,
getGroupHeaders,
getField,
};
};

View File

@ -0,0 +1 @@
// A file is required to be in the root of the /src directory by the TypeScript compiler

View File

@ -0,0 +1,31 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "a0215264-a8cd-4952-aecd-4e80719de202",
"alias": "DocumentsLinksAccordionWebPart",
"componentType": "WebPart",
"supportsFullBleed": true,
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportedHosts": ["SharePointWebPart", "SharePointFullPage", "TeamsPersonalApp","TeamsTab"],
"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "SPFx Custom Web Parts" },
"title": { "default": "Documents Links Accordion" },
"description": { "default": "Documents" },
"officeFabricIconFontName": "GroupedList",
"properties": {
"title": "Documents",
"listId": "",
"fieldName": "",
"listBasetemplate": 1
}
}]
}

View File

@ -0,0 +1,302 @@
import * as React from "react";
import * as ReactDom from "react-dom";
import {
loadTheme,
MessageBarType,
SpinnerSize
} from "office-ui-fabric-react";
import {
IReadonlyTheme,
ThemeChangedEventArgs,
ThemeProvider
} from "@microsoft/sp-component-base";
import { MSGraphClient } from "@microsoft/sp-http";
import {
IPropertyPaneConfiguration,
IPropertyPaneDropdownOption,
IPropertyPaneField,
IPropertyPaneGroup,
IPropertyPanePage,
PropertyPaneDropdown,
PropertyPaneTextField
} from "@microsoft/sp-property-pane";
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
import { sp } from "@pnp/sp";
import {
PropertyFieldListPicker,
PropertyFieldListPickerOrderBy
} from "@pnp/spfx-property-controls/lib/PropertyFieldListPicker";
import {
PropertyFieldMessage
} from "@pnp/spfx-property-controls/lib/PropertyFieldMessage";
import {
PropertyFieldSpinner
} from "@pnp/spfx-property-controls/lib/PropertyFieldSpinner";
import { useList } from "../../hooks/useList";
export interface IListItemsMenuWebPartProps {
title: string;
listId: string;
fieldName: string;
listBasetemplate: number;
}
const teamsDefaultTheme = require("../../common/TeamsDefaultTheme.json");
const teamsDarkTheme = require("../../common/TeamsDarkTheme.json");
const teamsContrastTheme = require("../../common/TeamsContrastTheme.json");
const { getListColumns, getLists } = useList() ;
import * as strings from 'DocumentsLinksAccordionWebPartStrings';
import { IDocumentsLinksAccordionProps } from "../../components/DocumentsLinksAccordion/IDocumentsLinksAccordionProps";
import {DocumentsLinksAccordion} from "../../components/DocumentsLinksAccordion/DocumentsLinksAccordion";
import { Version } from "@microsoft/sp-core-library";
export interface IDocumentsLinksAccordionWebPartProps {
title: string;
listId: string;
fieldName: string;
listBasetemplate: number;
}
export default class DocumentsLinksAccordionWebPart extends BaseClientSideWebPart<IDocumentsLinksAccordionWebPartProps> {
private columns: IPropertyPaneDropdownOption[] = [];
private lists: IPropertyPaneDropdownOption[] = [];
private _themeProvider: ThemeProvider;
private _themeVariant: IReadonlyTheme | undefined;
private _msgGraphclient: MSGraphClient;
private _hasError: boolean = false;
private _messageError: string = undefined;
protected async onInit(): Promise<void> {
sp.setup({
spfxContext: this.context,
});
this._msgGraphclient = await this.context.msGraphClientFactory.getClient();
this._themeProvider = this.context.serviceScope.consume(
ThemeProvider.serviceKey
);
// If it exists, get the theme variant
this._themeVariant = this._themeProvider.tryGetTheme();
// Register a handler to be notified if the theme variant changes
this._themeProvider.themeChangedEvent.add(
this,
this._handleThemeChangedEvent
);
if (this.context.sdks.microsoftTeams) {
// in teams ?
const context = this.context.sdks.microsoftTeams!.context;
this._applyTheme(context.theme || "default");
this.context.sdks.microsoftTeams.teamsJs.registerOnThemeChangeHandler(
this._applyTheme
);
}
return Promise.resolve();
}
/**
* Update the current theme variant reference and re-render.
*
* @param args The new theme
*/
private _handleThemeChangedEvent(args: ThemeChangedEventArgs): void {
this._themeVariant = args.theme;
this.render();
}
// Apply theme id in Teams
private _applyTheme = (theme: string): void => {
this.context.domElement.setAttribute("data-theme", theme);
document.body.setAttribute("data-theme", theme);
if (theme == "dark") {
loadTheme({
palette: teamsDarkTheme,
});
}
if (theme == "default") {
loadTheme({
palette: teamsDefaultTheme,
});
}
if (theme == "contrast") {
loadTheme({
palette: teamsContrastTheme,
});
}
}
public render(): void {
const element: React.ReactElement<IDocumentsLinksAccordionProps> = React.createElement(
DocumentsLinksAccordion,
{
title: this.properties.title,
listId: this.properties.listId,
fieldName: this.properties.fieldName,
themeVariant: this._themeVariant,
locale: this.context.pageContext.cultureInfo.currentUICultureName,
listBaseTemplate: this.properties.listBasetemplate ?? 1,
onConfigure: () =>{ this.context.propertyPane.open(); },
}
);
ReactDom.render(element, this.domElement);
}
protected onDispose(): void {
ReactDom.unmountComponentAtNode(this.domElement);
}
protected get disableReactivePropertyChanges() {
return true;
}
protected async onPropertyPaneConfigurationStart() {
if (
this.properties.fieldName &&
this.properties.listId &&
this.columns.length === 0
) {
await this.addListColumns(this.properties.listId);
this.context.propertyPane.refresh();
}
if ( this.properties.listId && this.lists.length === 0){
await this.addLists('1');
this.context.propertyPane.refresh();
}
}
protected async onPropertyPaneFieldChanged(
propertyPath: string,
oldValue: any,
newValue: any
) {
if (propertyPath === "listId" && newValue != oldValue) {
this.columns = [];
this.context.propertyPane.refresh();
await this.addListColumns(newValue);
this.context.propertyPane.refresh();
}
}
private async addLists(newValue: any) {
try {
this.lists = [];
const _lists = await getLists(newValue);
for (const _list of _lists) {
this.lists.push({ key: _list.Id, text: _list.Title });
}
} catch (error) {
this._hasError = true;
this._messageError =error.message;
}
}
private async addListColumns(newValue: any) {
try {
this.columns = [];
const _listColumns = await getListColumns(newValue);
for (const _column of _listColumns) {
this.columns.push({ key: _column.InternalName, text: _column.Title });
}
} catch (error) {
this._hasError = true;
this._messageError = error.message;
}
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
const _pages: IPropertyPanePage[] = [
{
header: {
description: strings.PropertyPaneDescription,
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField("title", {
label: strings.DescriptionFieldLabel,
}),
],
},
],
},
];
let groups: IPropertyPaneGroup = _pages[0].groups[0] as IPropertyPaneGroup;
let groupFields: IPropertyPaneField<any>[] = groups.groupFields;
groupFields.push(
PropertyFieldListPicker("listId", {
label: "Select Document Library",
selectedList: this.properties.listId ,
includeHidden: false,
baseTemplate: 101,
orderBy: PropertyFieldListPickerOrderBy.Title,
disabled: false,
onPropertyChange: this.onPropertyPaneFieldChanged.bind(this),
properties: this.properties,
context: this.context,
onGetErrorMessage: null,
deferredValidationTime: 0,
key: "listPickerFieldId",
})
);
if (this.properties.listId) {
groupFields.push(
PropertyFieldSpinner("", {
key: "sp1",
size: SpinnerSize.medium,
isVisible: (this.columns.length || this._hasError) ? false : true,
label: "Loading ...",
})
);
}
// Show Columns
if (this.columns.length > 0) {
groupFields.push(
PropertyPaneDropdown("fieldName", {
label: "Select field to group by documents",
options: this.columns,
selectedKey: this.properties.fieldName,
})
);
}
// Show Error
if (this._hasError) {
groupFields.push(
PropertyFieldMessage("", {
key: "msgError",
messageType: MessageBarType.error,
multiline: true,
text: this._messageError,
isVisible: this._hasError,
})
);
}
const _panelConfiguration: IPropertyPaneConfiguration = { pages: _pages };
return _panelConfiguration;
}
}

View File

@ -0,0 +1,12 @@
define([], function() {
return {
PlaceHolderButtonLable: "Configure",
PlaceHolderDescription: "Please configure the web part.",
PlaceHolderIconText: "Configure Documents Links Accordion Web Part",
"PropertyPaneDescription": "Show documents grouped by field",
"BasicGroupName": "Properties",
"DescriptionFieldLabel": "Title",
"NodocumentsLabel":"No documents found in library",
"ViewAllLabel": "View All"
}
});

View File

@ -0,0 +1,15 @@
declare interface IDocumentsLinksAccordionWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
ViewAllLabel:string;
NodocumentsLabel:string;
PlaceHolderButtonLable: string;
PlaceHolderDescription: string;
PlaceHolderIconText: string;
}
declare module 'DocumentsLinksAccordionWebPartStrings' {
const strings: IDocumentsLinksAccordionWebPartStrings;
export = strings;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

View File

@ -0,0 +1,35 @@
{
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.7/includes/tsconfig-web.json",
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"webpack-env"
],
"lib": [
"es5",
"dom",
"es2015.collection",
"es2015.promise"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
]
}

View File

@ -0,0 +1,30 @@
{
"extends": "./node_modules/@microsoft/sp-tslint-rules/base-tslint.json",
"rules": {
"class-name": false,
"export-name": false,
"forin": false,
"label-position": false,
"member-access": true,
"no-arg": false,
"no-console": false,
"no-construct": false,
"no-duplicate-variable": true,
"no-eval": false,
"no-function-expression": true,
"no-internal-module": true,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-unnecessary-semicolons": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-with-statement": true,
"semicolon": true,
"trailing-comma": false,
"typedef": false,
"typedef-whitespace": false,
"use-named-parameter": true,
"variable-name": false,
"whitespace": false
}
}