commit new sample react-documents-links-accordion

This commit is contained in:
João Mendes 2021-10-10 22:33:40 +01:00
parent 1911f8a5c8
commit d7c31a3938
37 changed files with 24158 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.11.0-green.svg)
![Node.js LTS 12.x](https://img.shields.io/badge/Node.js-LTS%2010.x-green.svg)
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-yellow.svg)
![Teams Yes: Designed for Microsoft Teams](https://img.shields.io/badge/Teams-Yes-green.svg "Designed for Microsoft Teams")
![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
## 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)
## WebPart Properties
Property |Type|Required| comments
--------------------|----|--------|----------
WebPart 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.**
## Support
We do not support samples, but we do use GitHub to track issues and constantly want to improve these samples.
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,92 @@
[
{
"name": "pnp-sp-dev-spfx-web-parts-react-list-items-menu",
"source": "pnp",
"title": "List Items Menu",
"shortDescription": "Allows user create a navigation menu , grouped by any column of document library. When the user clicks on the header it dynamically load documents.",
"url": "https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-list-items-menu",
"longDescription": [
"Allows user create a navigation menu , grouped by any column of document library. When the user clicks on the header it dynamically load documents."
],
"creationDateTime": "2021-02-18",
"updateDateTime": "2021-02-18",
"products": [
"SharePoint",
"Office"
],
"metadata": [
{
"key": "CLIENT-SIDE-DEV",
"value": "React"
},
{
"key": "SPFX-VERSION",
"value": "1.11.0"
},
{
"key": "SPFX-SUPPORTSTHEMEVARIANTS",
"value": "true"
},
{
"key": "SPFX-TEAMSTAB",
"value": "true"
},
{
"key": "SPFX-TEAMSPERSONALAPP",
"value": "true"
}
],
"thumbnails": [
{
"type": "image",
"order": 100,
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/main/samples/react-list-items-menu/assets/ListMenuDocs.gif",
"alt": "List Items Menu"
},
{
"type": "image",
"order": 101,
"url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-list-items-menu/assets/reactListItems1.JPG?raw=true",
"alt": "List Items Menu"
},
{
"type": "image",
"order": 102,
"url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-list-items-menu/assets/reactListItems2.JPG?raw=true",
"alt": "List Items Menu"
},
{
"type": "image",
"order": 103,
"url": "https://github.com/pnp/sp-dev-fx-webparts/blob/main/samples/react-list-items-menu/assets/reactListItems3.JPG?raw=true",
"alt": "List Items Menu"
}
],
"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
}
}