Merge pull request #3 from pnp/master

Pull Request
This commit is contained in:
Ravi Chandra 2021-01-31 23:15:11 -08:00 committed by GitHub
commit 5ccafc4bfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
260 changed files with 5626 additions and 2449 deletions

View File

@ -33,7 +33,7 @@ Whenever you are submitting any changes to the SharePoint repositories, please f
When you are submitting a new sample, it has to follow up below guidelines
* You will need to have a `README.md` file for your contribution, which is based on [the provided template](../samples/README-template.md) under the `samples` folder. Please copy this template to your project and update it accordingly. Your `README.md` must be named exactly `README.md` -- with capital letters -- as this is the information we use to make your sample public.
* You will need to have a screenshot picture of your sample in action in the `README.md` file ("pics or it didn't happen"). The preview image must be located in the `/assets/` folder in the root your you solution.
* You will need to have a screenshot picture of your sample in action in the `README.md` file ("pics or it didn't happen"). The preview image must be located in the `/assets/` folder in the root of your solution.
* The `README` template contains a specific tracking image at the bottom of the file with an `img` tag, where the `src` attribute points to `https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template`. This is a transparent image which is used to track viewership of individual samples in GitHub.
* Update the image `src` attribute according with the repository name and folder information. For example, if your sample is named `react-todo` in the `samples` folder, you should update the `src` attribute to `https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-todo`
* If you find an existing sample which is similar to yours, please extend the existing one rather than submitting a new similar sample

View File

@ -1,7 +1,7 @@
---
name: "\U0001F41E Bug"
about: Report an anomaly or unexpected behavior with a sample from this repository.
title: ''
title: '[Web Part Name]: [Issue Title]'
labels: type:bug
assignees: ''
@ -19,19 +19,41 @@ Thank you for reporting a bug! Use the sections below to submit a bug ONLY if it
DELETE EVERYTHING ABOVE AFTER READING - THANKS!
## Sample (which sample are you having trouble with)
## Sample
> Which sample are you having trouble with?
>
> Issues without a sample name will be closed automatically
>
> _(DELETE THIS PARAGRAPH AFTER READING)_
## Authors
> Because of the way this repository is configured, samples authors do not get a notification when you create an issue. *It makes it less likely for you to get your issue resolved or to get help*. For the section above **@mention any author of the sample**. Authors' GitHub handle can be found on the main sample documentation page, under the "solution" section. Use the `PREVIEW` tab at the top right to preview the rendering before submitting your issue.
> Because of the way this repository is configured, samples authors do not get a notification when you create an issue. *It makes it less likely for you to get your issue resolved or to get help*. For the section above **@mention any author of the sample**. Authors' GitHub handle can be found on the main sample documentation page, under the "solution" section. Use the `PREVIEW` tab at the top right to preview the rendering before submitting your issue.
>
> The author's name can be found in the sample's `README.md` file, under **Authors**.
>
> Issues without an author will be closed automatically
>
> _(DELETE THIS PARAGRAPH AFTER READING)_
## Expected or Desired Behavior
> What should it do?
>
> Issues without an expected behavior will be closed automatically
>
> _(DELETE THIS PARAGRAPH AFTER READING)_
## Observed Behavior
> What does it do?
>
> Issues without an observed behavior will be closed automatically
>
> _(DELETE THIS PARAGRAPH AFTER READING)_
## Steps to Reproduce
@ -41,6 +63,11 @@ Steps to reproduce the behavior:
3. Scroll down to '....'
4. See error
> Issues without an steps to reproduce will be closed automatically
>
> _(DELETE THIS PARAGRAPH AFTER READING)_
# Environment Details (*Development & Target environment*)
- **OS**: [e.g. Windows 10 | MacOS 10.15.x]
@ -50,4 +77,8 @@ Steps to reproduce the behavior:
- **Tooling**: [e.g. VS Code | SPFx v1.10.0 | Visual Studio 2019]
- **Additional details**: *The more context you can provide, the easier it is (and therefore quicker) to help.*
> Issues without environment details will be closed automatically
>
> _(DELETE THIS PARAGRAPH AFTER READING)_
## Additional context

View File

@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Start a discussion
about: 🗨 Start a friendly conversation with the other members of the community about the samples in this repository.
url: https://github.com/pnp/sp-dev-fx-webparts/discussions
- name: SharePoint Developer Documentation
url: https://github.com/SharePoint/sp-dev-docs/issues/new
about: 📚 Issue related to the SharePoint Framework or its documentation? Submit an issue here.

View File

@ -1,7 +1,7 @@
---
name: "\U0001F4A1 Suggestion"
about: Suggest an enhancement to make one of our web part samples even better
title: Let's make SAMPLE even better
title: '[Web Part Name]: [Suggestion Title]'
labels: type:enhancement
assignees: ''
@ -18,13 +18,26 @@ Thank you for your suggestion! Use the sections below to submit a suggestion ONL
DELETE EVERYTHING ABOVE AFTER READING - THANKS!
## Sample (which sample are you talking about)
## Sample
> Which sample are you talking about
>
> Suggestions without a sample name will be closed automatically
>
> _(DELETE THIS PARAGRAPH AFTER READING)_
## Authors
> Because of the way this repository is configured, samples authors do not get a notification when you create an issue. *It makes it less likely for you to get your issue resolved or to get help*. For the section above **@mention any author of the sample**. The authors' GitHub handle can be found on the main sample documentation page, under the "solution" section. Use the `PREVIEW` tab at the top right to preview the rendering before submitting your issue.
>
> The authors can be found in the sample's `README.md` file, under **Authors**
>
> Suggestions without an author will be closed automatically
>
> _(DELETE THIS PARAGRAPH AFTER READING)_
## Suggestion (the more details, the better)
## Suggestion
> The more details, the better
>
> _(DELETE THIS PARAGRAPH AFTER READING)_

View File

@ -15,20 +15,30 @@ Thank you for asking a question! Use the sections below to submit a question ONL
- Include sufficient details and context.
- If you have multiple questions please submit them separately so we can track resolution.
- Screenshots are always helpful (just paste any images right here in the question)
- If you're trying to start a conversation on a topic, consider using [Discussions](https://github.com/pnp/sp-dev-fx-webparts/discussions). We want to close every issue as quickly as possible, but discussions are intended for longer conversations.
DELETE EVERYTHING ABOVE AFTER READING - THANKS!
## Sample (which sample are you talking about)
> Questions without a sample name will be rejected automatically
>
> _(DELETE THIS PARAGRAPH AFTER READING)_
## Authors
> Because of the way this repository is configured, samples authors do not get a notification when you create an issue. *It makes it less likely for you to get your issue resolved or to get help*. For the section above **@mention any author of the sample**. The authors' GitHub handle can be found on the main sample documentation page, under the "solution" section. Use the `PREVIEW` tab at the top right to preview the rendering before submitting your issue.
>
> Questions without an author will be rejected automatically
>
> _(DELETE THIS PARAGRAPH AFTER READING)_
## Question (the more details, the better)
## Question
> The more details, the better
>
> _(DELETE THIS PARAGRAPH AFTER READING)_

View File

@ -2,6 +2,14 @@
> This is how you want the sample to appear in the samples browser.
> When naming your sample, try to give it a friendly name that describes what it does. Avoid using terms like `SharePoint` and `WebPart` -- because that's what all the samples in this repo is all about. Also, don't use `React`, `Angular`, `JavaScript`, etc. in your sample title -- unless that's what the sample is about.
> GOOD 👍:
> Kitten Videos
> BAD 👎:
> react-kittenvideos
> SPFx Kitten Videos Web Part for SharePoint using React
>
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
## Summary
@ -15,20 +23,47 @@ Short summary on functionality and used technologies.
![picture of the web part in action](assets/preview.png)
## Used SharePoint Framework Version
## Compatibility
![SPFx 1.11.0](https://img.shields.io/badge/version-1.11.0-green.svg)
![SPFx 1.11](https://img.shields.io/badge/SPFx-1.11.0-green.svg)
![Node.js LTS 10.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 N/A: Untested with Microsoft Teams](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg "Untested with Microsoft Teams")
![Workbench Local | Hosted](https://img.shields.io/badge/Workbench-Local%20%7C%20Hosted-green.svg)
> Don't worry if you're unsure about the compatibility matrix above. We'll verify it when we approve the PR.
>
> If using an older version of SPFx, update the SPFx compatibility tag accordingly:
> ![SPFx 1.4.1](https://img.shields.io/badge/SPFx-1.4.1-green.svg)
>
> Here's the Node.js compatibility tag for SPFx 1.4.1:
> ![Node.js LTS 6.x | LTS 8.x](https://img.shields.io/badge/Node.js-LTS%206.x%20%7C%20LTS%208.x-green.svg)
>
> If you built this sample specifically for SharePoint 2016, or SharePoint 2019 support, update the SharePoint compatibility tag accordingly:
> ![SharePoint 2019 | Online](https://img.shields.io/badge/SharePoint-2019%20%7C%20Online-yellow.svg)
> ![SharePoint 2016 | 2019 | Online](https://img.shields.io/badge/SharePoint-2016%20%7C%202019%20%7C%20Online-green.svg)
> If you know your web part only works on the hosted workbench, you can use this for the workbench compatibility tag:
> ![Workbench Hosted: Does not work with local workbench](https://img.shields.io/badge/Workbench-Hosted-yellow.svg "Does not work with local workbench")
>
> If you specifically built and tested this web part to work with Teams, use this for the Teams compatibility tag:
> ![Teams Yes: Designed for Microsoft Teams](https://img.shields.io/badge/Teams-Yes-green.svg "Designed for Microsoft Teams")
> And if you know for sure that it is NOT compatible with Teams, use this:
> ![Teams No: Not designed for Microsoft Teams](https://img.shields.io/badge/Teams-No-red.svg "Not designed for Microsoft Teams")
>
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
## 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)
* [Microsoft 365 tenant](https://docs.microsoft.com/sharepoint/dev/spfx/set-up-your-development-environment)
> Update accordingly as needed.
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
## Prerequisites
> Any special pre-requisites?
> Any special pre-requisites? Include any lists, permissions, offerings to the demo gods, or whatever else needs to be done for this web part to work.
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
## Solution
@ -40,8 +75,8 @@ folder name | Author details
Version|Date|Comments
-------|----|--------
1.1|September 2, 2025|Update comment
1.0|August 29, 2025|Initial release
1.1|September 2, 2021|Update comment
1.0|August 29, 2021|Initial release
## Disclaimer
@ -57,6 +92,7 @@ Version|Date|Comments
* `gulp serve`
> Include any additional steps as needed.
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
## Features
@ -67,4 +103,7 @@ This Web Part illustrates the following concepts on top of the SharePoint Framew
* topic 2
* topic 3
> Note that better pictures and documentation will increase the sample usage and the value you are providing for others. Thanks for your submissions in advance! You rock ❤.
> DELETE THIS PARAGRAPH BEFORE SUBMITTING
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/readme-template" />

View File

@ -11,7 +11,7 @@
"privacyUrl": "https://contoso.com/privacy",
"termsOfUseUrl": "https://contoso.com/terms-of-use",
"websiteUrl": "https://contoso.com/my-app",
"mpnId": "000000"
"mpnId": "m365pnp"
}
},
"paths": {

View File

@ -12,7 +12,7 @@
"privacyUrl": "",
"termsOfUseUrl": "",
"websiteUrl": "",
"mpnId": ""
"mpnId": "m365pnp"
}
},
"paths": {

View File

@ -11,7 +11,7 @@
"websiteUrl": "",
"privacyUrl": "",
"termsOfUseUrl": "",
"mpnId": ""
"mpnId": "m365pnp"
}
},
"paths": {

View File

@ -2,7 +2,7 @@
## Summary
- This sample is based on [Erik Benke] and [Mike Zimmerman] (Accordion Section FAQ Builder web part. I has extended it support single FAQs list based on Category and dynamic properties selection.
- This sample is based on [Erik Benke](https://github.com/ejbenke) and [Mike Zimmerman](https://github.com/mikezimm)'s [Accordion Section FAQ Builder web part](https://github.com/pnp/sp-dev-fx-webparts/tree/master/samples/react-accordion-section). It adds extended support for single FAQs list based on Category and dynamic properties selection.
- Adds a collapsible accordion section to an Office 365 SharePoint page or Teams Tab.
- Ideal for displaying FAQs.
- When adding the web part, you'll be prompted to select a list from a property panel dropdown (target list must be created with FAQ type Question and Answer.).

View File

@ -0,0 +1,68 @@
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/pnp/samples/v1.0/metadata-schema.json",
"name": "pnp-sp-dev-spfx-web-parts-react-accordion-dynamic-section",
"version": "1.4.2.0",
"source": "pnp",
"title": "Dynamic Accordion - FAQ Builder",
"shortDescription": "This sample is based on Erik Benke and Mike Zimmerman Accordion Section FAQ Builder web part. It was extended support single FAQs list based on Category and dynamic properties selection.",
"longDescription":
[
"This sample is based on Erik Benke and Mike Zimmerman's Accordion Section FAQ Builder web part. It was extended support single FAQs list based on Category and dynamic properties selection.",
"Adds a collapsible accordion section to an Office 365 SharePoint page or Teams Tab.",
"Ideal for displaying FAQs.",
"When adding the web part, you'll be prompted to select a list from a property panel dropdown (target list must be created with FAQ type Question and Answer.).",
"The web part expects a column called Category of type choice that will be used as the category.",
"The web part will automatically load all the properties in two dropdowns. One for Accordion Title and One for Accordion Content that must be html type.",
"This will generate an accordion with one section for each item in the list.",
"Modifications/deletions/additions to the list items in the target list of an added web part are automatically reflected on the page."
],
"products": [
"Microsoft Teams", "SharePoint", "Office"
],
"categories": [
],
"metadata": [
{
"key": "CLIENT-SIDE-DEV",
"value":"React"
},
{
"key": "SPFX-VERSION",
"value":"1.10"
}
],
"thumbnails": [
{
"type": "image",
"order": 100,
"url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-accordion-dynamic-section/assets/react-accordion-section.gif",
"alt": "Alt text for the image"
},
{
"type": "slideshow",
"order": 102,
"alt": "Alt text for the image",
"slides": [
{ "url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-accordion-dynamic-section/assets/FAQsList.png","order": 10, "alt": "The FAQ list, showing the category column"},
{ "url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-accordion-dynamic-section/assets/AccordionSettings1.png","order": 20, "alt": "The web part before being configured"},
{ "url": "https://github.com/pnp/sp-dev-fx-webparts/raw/master/samples/react-accordion-dynamic-section/assets/AccordionSettings2.png","order": 30, "alt": "An illustration showing the relation between the configuration settings and the web part results"}
]
}
],
"authors": [
{
"gitHubAccount": "ejbenke",
"company": "Clackamas County",
"pictureUrl": "https://avatars.githubusercontent.com/u/5017358?s=460&u=789d537eb1e89ce82b36604fa7ff48cc40fcd5a2&v=4"
},
{
"gitHubAccount": "mikezimm",
"pictureUrl": "https://avatars.githubusercontent.com/u/49648086?s=460&v=4"
},
{
"gitHubAccount": "jyasir",
"company": "DXC Technology",
"pictureUrl": "https://pbs.twimg.com/profile_images/1145701162190082050/pbgFe-BJ_400x400.png"
}
]
}

View File

@ -56,6 +56,7 @@ Solution|Author(s)
SPFx Collapsible Accordion Section|[Erik Benke](https://github.com/ejbenke) ([@erikjbenke](https://twitter.com/erikjbenke))
SPFx Collapsible Accordion Section|[Mike Zimmerman](https://github.com/mikezimm)
SPFx Collapsible Accordion Section|[Ravi Chandra](https://github.com/Ravikadri)
SPFx Collapsible Accordion Section|[Jack Vinitsky](https://github.com/jack-vinitsky)
## Version history
@ -68,7 +69,7 @@ Version|Date|Comments
1.4|July 10, 2020|Upgraded to SPFx 1.10.
1.5|September 1, 2020|Adds ability to click on expanded section headers to collapse accordions
1.6|September 2, 2020|Added Web Part Title, and ability to expand multiple sections
1.7|January 5, 2021|Fixed web part title style to be consistent with first-party web parts
1.7|January 5, 2021|Fixed web part title style to be consistent with first-party web parts; updated other dependencies
## Disclaimer

View File

@ -17,17 +17,17 @@
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
"@microsoft/sp-property-pane": "1.10.0",
"@microsoft/sp-webpart-base": "1.10.0",
"@pnp/common": "^1.3.4",
"@pnp/logging": "^1.3.4",
"@pnp/odata": "^1.3.4",
"@pnp/sp": "^1.3.4",
"@pnp/spfx-controls-react": "^2.3.0",
"@pnp/common": "^1.3.11",
"@pnp/logging": "^1.3.11",
"@pnp/odata": "^1.3.11",
"@pnp/sp": "^1.3.11",
"@pnp/spfx-controls-react": "2.3.0",
"@pnp/spfx-property-controls": "1.16.0",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"office-ui-fabric-react": "6.189.2",
"office-ui-fabric-react": "6.214.0",
"react": "16.8.5",
"react-accessible-accordion": "^3.0.0",
"react-dom": "16.8.5"

View File

@ -11,7 +11,7 @@
"websiteUrl": "",
"privacyUrl": "",
"termsOfUseUrl": "",
"mpnId": ""
"mpnId": "m365pnp"
}
},
"paths": {

View File

@ -17,7 +17,7 @@
"privacyUrl": "https://contoso.com/privacy",
"termsOfUseUrl": "https://contoso.com/terms-of-use",
"websiteUrl": "https://contoso.com/my-app",
"mpnId": "000000"
"mpnId": "m365pnp"
}
},
"paths": {

View File

@ -12,7 +12,7 @@
"privacyUrl": "https://contoso.com/privacy",
"termsOfUseUrl": "https://contoso.com/terms-of-use",
"websiteUrl": "https://contoso.com/my-app",
"mpnId": "000000"
"mpnId": "m365pnp"
}
},
"paths": {

View File

@ -10,7 +10,7 @@
"privacyUrl": "https://contoso.com/privacy",
"termsOfUseUrl": "https://contoso.com/terms-of-use",
"websiteUrl": "https://contoso.com/my-app",
"mpnId": "000000"
"mpnId": "m365pnp"
}
},
"paths": {

View File

@ -2,7 +2,7 @@
## Summary
> **NOTE:** This web part was built with SPFx 1.10.0, making it only compatible with SharePoint Online. If you wish, you can use [an earlier version of this web part](../OnPrem/README.md) which is compatible on-premises versions of SharePoint.
> **NOTE:** This web part was built with SPFx 1.11.0, making it only compatible with SharePoint Online. If you wish, you can use [an earlier version of this web part](../react-content-query-onprem/README.md) which is compatible on-premises versions of SharePoint.
The **Content Query web part** is a modern version of the good old **Content by Query web part** that was introduced in SharePoint 2007. Built for Office 365, this modern version is built using the **SharePoint Framework (SPFx)** and uses the latest *Web Stack* practices.
@ -10,9 +10,17 @@ While the original web part was based on an **XSLT** templating engine, this *Re
![Web Part Preview](assets/toolpart.gif)
## Used SharePoint Framework Version
## Compatibility
![1.11.0](https://img.shields.io/badge/drop-1.11.0-green.svg)
![SPFx 1.11](https://img.shields.io/badge/spfx-1.11.0-green.svg)
![Node.js LTS 10.x](https://img.shields.io/badge/Node.js-LTS%2010.x-green.svg)
![SharePoint Online](https://img.shields.io/badge/SharePoint-Online-red.svg)
![Teams N/A](https://img.shields.io/badge/Teams-N%2FA-lightgrey.svg)
![Workbench Hosted](https://img.shields.io/badge/Workbench-Hosted-yellow.svg)
## Applies to
@ -28,6 +36,7 @@ react-content-query-web part (Online)|Hugo Bernier ([Tahoe Ninjas](http://tahoen
react-content-query-web part (Online)|Paolo Pialorsi ([PiaSys.com](https://piasys.com/), [@PaoloPia](https://twitter.com/PaoloPia?s=20))
react-content-query-web part |Simon-Pierre Plante
react-content-query-web part (Online)|Abderahman Moujahid
## Version history
Version|Date|Comments
@ -48,6 +57,7 @@ Version|Date|Comments
1.0.14|October 30, 2020|Fixed (lookup-)fields with special characters
1.0.15|November 2, 2020|Upgraded to SPFx 1.11; Added support for jsonValue
1.0.16|November 14, 2020|Fixed a bug where the fieldname starts with a special character; Added more special characters
1.1.0|January 5, 2021|Updated dependencies and added MGT support
## Disclaimer
@ -273,16 +283,19 @@ Property | Description
`{{MyField.htmlValue}}` | Renders the HTML value of the field. For example, a *Link* field HTML value would render something like `<a href="...">My Link Field</a>`
`{{MyField.rawValue}}` | Returns the raw value of the field. For example, a *Taxonomy* field raw value would return an object which contains the term `wssId` and its label
`{{MyField.jsonValue}}` | Returns a JSON object value of the field. For example, an *Image* field JSON value would return a JSON object which contains the `serverRelativeUrl` property
`{{MyField.personValue}}` | Returns an object value of a person field. The `personValue` property provides `email`, `displayName` and `image` properties. The `image` property contains `small`, `medium`, and `large` properties, each of which pointing to the profile image URL for the small, medium, and large profile images.
##### Handlebars
```handlebars
{{#each items}}
<div class="item">
<p>MyUserField text value : {{MyUserField.textValue}}</p>
<p>MyUserField html value : {{MyUserField.htmlValue}}</p>
<p>MyUserField raw value : {{MyUserField.rawValue}}</p>
<p>MyField text value : {{MyField.textValue}}</p>
<p>MyField html value : {{MyField.htmlValue}}</p>
<p>MyField raw value : {{MyField.rawValue}}</p>
<p>MyImageField JSON value : {{MyImageField.jsonValue}}</p>
<p>MyPersonField person value : {{MyPersonField.personValue}}</p>
</div>
{{/each}}
```
@ -291,10 +304,11 @@ Property | Description
```html
<div class="item">
<p>MyUserField text value : Simon-Pierre Plante</p>
<p>MyUserField html value : <a href="..." onclick="...">Simon-Pierre Plante</a></p>
<p>MyUserField raw value : 26</p>
<p>MyField text value : Simon-Pierre Plante</p>
<p>MyField html value : <a href="..." onclick="...">Simon-Pierre Plante</a></p>
<p>MyField raw value : 26</p>
<p>MyImageField JSON value: [Object] </p>
<p>MyPersonField person value: [Object] </p>
</div>
...
```
@ -305,6 +319,12 @@ You can use `JSONValue` to parse complex fields -- such as image fields -- and d
<img src="{{MyImageField.jsonValue.serverRelativeUrl}}" />
```
For fields containing person values (e.g.: the `Author`, `Editor` fields and **Person or Group** fields), you can use the `personValue` property to retrieve values such as `email`, `displayName`, and `image.small`, `image.medium`, `image.large`.
```html
<img src="{{MyPersonField.personValue.image.small}}" />
```
### Including your own external scripts and/or block helpers
#### Including basic library files
@ -394,4 +414,61 @@ ReactContentQuery.ExternalScripts.MyCustomBlockHelper = {
}
```
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-web parts/samples/react-content-query-web part/online" />
### Using Microsoft Graph Toolkit
The Content Query Web Part provides support for [Microsoft Graph Toolkit](https://docs.microsoft.com/en-us/graph/toolkit/overview) (MGT) integration to easily query the [Microsoft Graph](https://docs.microsoft.com/en-us/graph/overview) within your handlebars templates.
You can use any of the MGT components without additional steps, although the MGT integration was designed specifically with [Person](https://docs.microsoft.com/en-us/graph/toolkit/components/person), [People](https://docs.microsoft.com/en-us/graph/toolkit/components/people), and [Person card](https://docs.microsoft.com/en-us/graph/toolkit/components/person-card) components.
#### Using MGT with a person field
If you have a SharePoint list containing a user field (like a **Created By** or **Modified By** field) or a person field, you can pass the `email` property from field's the `personValue` to the MGT component's `person-query` attribute.
For example, to use the `mgt-person` component with a person field called `MyPersonField`, you would use the following template:
```handlebars
<mgt-person person-query="{{MyPersonField.personValue.email}}" view="twoLines"></mgt-person>
```
#### Using MGT templates
MGT supports the use of custom templates to modify the content of a components.
According to [MGT documentation](https://docs.microsoft.com/en-us/graph/toolkit/customize-components/templates), the binding syntax to inject dynamic content in a template uses a block delimited by `{{` and `}}` -- which conflicts with the Handlebars binding syntax.
In order to use a custom MGT template in your Handlebars template, use `[[` and `]]` instead of `{{` and `}}` to bind to the MGT context.
For example, you would replace the following Handlebars template:
```handlebars
<mgt-person person-query="{{MyPersonField.personValue.email}}">
<template>
<div data-if="person.image">
<img src="{{ person.image }}" />
</div>
<div data-else>
{{ person.displayName }}
</div>
</template>
</mgt-person>
```
With the following template:
```handlebars
<mgt-person person-query="{{MyPersonField.personValue.email}}">
<template>
<div data-if="person.image">
<img src="[[ person.image ]]" />
</div>
<div data-else>
[[ person.displayName ]]
</div>
</template>
</mgt-person>
```
Note that, in the example above, the `person-query` attribute is still bound using the Handlebars syntax `person-query="{{MyPersonField.personValue.email}}"`, whereas the MGT template uses `[[ person.image ]]` and `[[ person.displayName ]]`.
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-web parts/samples/react-content-query-online" />

View File

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 242 KiB

View File

Before

Width:  |  Height:  |  Size: 498 KiB

After

Width:  |  Height:  |  Size: 498 KiB

View File

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

Before

Width:  |  Height:  |  Size: 716 KiB

After

Width:  |  Height:  |  Size: 716 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 4.6 MiB

After

Width:  |  Height:  |  Size: 4.6 MiB

View File

Before

Width:  |  Height:  |  Size: 9.7 MiB

After

Width:  |  Height:  |  Size: 9.7 MiB

View File

@ -0,0 +1,58 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "React Content Query",
"id": "00406271-0276-406f-9666-512623eb6709",
"version": "1.1.0.0",
"isDomainIsolated": false,
"webApiPermissionRequests": [
{
"resource": "Microsoft Graph",
"scope": "User.Read"
},
{
"resource": "Microsoft Graph",
"scope": "People.Read"
},
{
"resource": "Microsoft Graph",
"scope": "Contacts.Read"
},
{
"resource": "Microsoft Graph",
"scope": "User.ReadBasic.All"
},
{
"resource": "Microsoft Graph",
"scope": "Calendars.Read"
},
{
"resource": "Microsoft Graph",
"scope": "Directory.Read.All"
},
{
"resource": "Microsoft Graph",
"scope": "User.Read.All"
},
{
"resource": "Microsoft Graph",
"scope": "Group.Read.All"
},
{
"resource": "Microsoft Graph",
"scope": "Files.Read.All"
}
],
"includeClientSideAssets": true,
"developer": {
"name": "PnP Community",
"mpnId": "m365pnp",
"privacyUrl": "",
"termsOfUseUrl": "",
"websiteUrl": ""
}
},
"paths": {
"zippedPackage": "solution/react-content-query-webpart.sppkg"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "react-content-query-webpart",
"version": "1.0.16",
"version": "1.1.0",
"private": true,
"main": "lib/index.js",
"engines": {
@ -12,13 +12,14 @@
"test": "gulp test"
},
"dependencies": {
"@microsoft/mgt": "^2.0.1",
"@microsoft/sp-core-library": "1.11.0",
"@microsoft/sp-lodash-subset": "1.11.0",
"@microsoft/sp-office-ui-fabric-core": "1.11.0",
"@microsoft/sp-property-pane": "1.11.0",
"@microsoft/sp-webpart-base": "1.11.0",
"@pnp/spfx-controls-react": "^1.17.0",
"@pnp/spfx-property-controls": "^1.17.0",
"@pnp/spfx-controls-react": "^2.3.0",
"@pnp/spfx-property-controls": "2.2.0",
"@types/handlebars": "4.0.32",
"acorn": ">=5.7.4",
"atob": ">=2.1.0",
@ -33,7 +34,7 @@
"fstream": ">=1.0.12",
"growl": ">=1.10.0",
"handlebars": "^4.0.6",
"handlebars-helpers": "^0.8.2",
"handlebars-helpers": "^0.8.4",
"hoek": ">=4.2.1",
"is-my-json-valid": ">=2.17.2",
"js-yaml": ">=3.13.1",

View File

@ -0,0 +1,9 @@
import { IPersonValue } from "./IPersonValue";
export interface INormalizedResult {
textValue: string;
htmlValue: string;
rawValue: any;
jsonValue: any;
personValue?: IPersonValue;
}

View File

@ -0,0 +1,9 @@
export interface IPersonValue {
email: string;
displayName: string;
picture: {
small: string;
medium: string;
large: string;
};
}

View File

@ -15,6 +15,8 @@ import { ListService, IListTitle } from './ListService';
import { SearchService } from './SearchService';
import { PeoplePickerService } from './PeoplePickerService';
import { TaxonomyService } from './TaxonomyService';
import { INormalizedResult } from '../dataContracts/INormalizedResult';
import { IPersonValue } from '../dataContracts/IPersonValue';
export class ContentQueryService implements IContentQueryService {
@ -75,7 +77,7 @@ export class ContentQueryService implements IContentQueryService {
return new Promise<IContentQueryTemplateContext>((resolve, reject) => {
// Initializes the base template context
let templateContext: IContentQueryTemplateContext = {
const templateContext: IContentQueryTemplateContext = {
pageContext: this.context.pageContext,
webUrl: querySettings.webUrl,
listId: querySettings.listId,
@ -85,11 +87,12 @@ export class ContentQueryService implements IContentQueryService {
callTimeStamp: callTimeStamp
};
// Builds the CAML query based on the webpart settings
let query = CamlQueryHelper.generateCamlQuery(querySettings);
// Builds the CAML query based on the web part settings
const query: string = CamlQueryHelper.generateCamlQuery(querySettings);
//Log.info(this.logSource, Text.format("Generated CAML query {0}...", query), this.context.serviceScope);
// Queries the list with the generated caml query
// Queries the list with the generated CAML query
this.listService.getListItemsByQuery(querySettings.webUrl, querySettings.listId, query)
.then((data: any) => {
// Updates the template context with the normalized query results
@ -257,7 +260,7 @@ export class ContentQueryService implements IContentQueryService {
return new Promise<IDropdownOption[]>((resolve, reject) => {
this.listService.getListTitlesFromWeb(webUrl).then((listTitles: IListTitle[]) => {
let options: IDropdownOption[] = [{ key: "", text: strings.ListTitleFieldPlaceholder }];
let listTitleOptions = listTitles.map((list) => { return { key: list.id, text: list.title }; });
const listTitleOptions = listTitles.map((list) => { return { key: list.id, text: list.title }; });
options = options.concat(listTitleOptions);
this.listTitleOptions = options;
resolve(options);
@ -290,9 +293,9 @@ export class ContentQueryService implements IContentQueryService {
// Otherwise gets the options asynchronously
return new Promise<IDropdownOption[]>((resolve, reject) => {
this.listService.getListFields(webUrl, listId, ['InternalName', 'Title', 'Sortable'], 'Title').then((data: any) => {
let sortableFields: any[] = data.value.filter((field) => { return field.Sortable == true; });
const sortableFields: any[] = data.value.filter((field) => { return field.Sortable == true; });
let options: IDropdownOption[] = [{ key: "", text: strings.queryFilterPanelStrings.queryFilterStrings.fieldSelectLabel }];
let orderByOptions: IDropdownOption[] = sortableFields.map((field) => { return { key: field.InternalName, text: Text.format("{0} \{\{{1}\}\}", field.Title, field.InternalName) }; });
const orderByOptions: IDropdownOption[] = sortableFields.map((field) => { return { key: field.InternalName, text: Text.format("{0} \{\{{1}\}\}", field.Title, field.InternalName) }; });
options = options.concat(orderByOptions);
this.orderByOptions = options;
resolve(options);
@ -480,8 +483,8 @@ export class ContentQueryService implements IContentQueryService {
let selectItemStr = "\n <span><button class='selectItem' data-itemId='{{ID.textValue}}'>Select</button></span>";
let template = Text.format(`<style type="text/css">
.dynamic-template h2 {
font-size: 24px;
font-weight: 300;
font-size: 20px;
font-weight: 600;
color: "[theme:neutralPrimary, default:#323130]";
}
@ -576,31 +579,35 @@ export class ContentQueryService implements IContentQueryService {
* Normalizes the results coming from a CAML query into a userfriendly format for handlebars
* @param results : The results returned by a CAML query executed against a list
**************************************************************************************************/
private normalizeQueryResults(results: any[], viewFields: string[]): any[] {
private normalizeQueryResults(results: any[], viewFields: string[]): INormalizedResult[] {
//Log.verbose(this.logSource, "Normalizing results for the requested handlebars context...", this.context.serviceScope);
let normalizedResults: any[] = [];
for (let result of results) {
const normalizedResults: INormalizedResult[] = results.map((result) => {
let normalizedResult: any = {};
let formattedCharsRegex = /_x00(20|3a|[c-f]{1}[0-9a-f]{1})_/gi;
for (let viewField of viewFields) {
//check if the intenal fieldname begins with a special character (_x00)
let viewFieldOdata = viewField;
let viewFieldOdata: string = viewField;
if (viewField.indexOf("_x00") == 0) {
viewFieldOdata = `OData_${viewField}`;
}
let formattedName = viewFieldOdata.replace(formattedCharsRegex, "_x005f_x00$1_x005f_");
formattedName = formattedName.replace(/_x00$/, "_x005f_x00");
const htmlValue: string = result.FieldValuesAsHtml[formattedName];
normalizedResult[viewField] = {
textValue: result.FieldValuesAsText[formattedName],
htmlValue: result.FieldValuesAsHtml[formattedName],
htmlValue: htmlValue,
rawValue: result[viewField] || result[viewField + 'Id'],
jsonValue: this.jsonParseField(result[viewField] || result[viewField + 'Id'])
jsonValue: this.jsonParseField(result[viewField] || result[viewField + 'Id']),
personValue: this.extractPersonInfo(htmlValue)
};
}
normalizedResults.push(normalizedResult);
}
return normalizedResult;
});
return normalizedResults;
}
@ -621,6 +628,50 @@ export class ContentQueryService implements IContentQueryService {
return value;
}
/**
* Returns user profile information based on a user field
* @param htmlValue : A string representation of the HTML field rendering
* This function does a very rudimentary extraction of user information based on very limited
* HTML parsing. We need to update this in the future to make it more sturdy.
*/
private extractPersonInfo(htmlValue: string): IPersonValue {
try {
const sipIndex = htmlValue.indexOf(`sip='`);
if (sipIndex === -1) {
return null;
}
// Try to extract the user email and name
// Get the email address -- we should use RegExp for this, but I suck at RegExp
const sipValue = htmlValue.substring(sipIndex + 5, htmlValue.indexOf(`'`, sipIndex + 5));
const anchorEnd: number = htmlValue.lastIndexOf('</a>');
const anchorStart: number = htmlValue.substring(0, anchorEnd).lastIndexOf('>');
const name: string = htmlValue.substring(anchorStart + 1, anchorEnd);
// Generate picture URLs
const smallPictureUrl: string = `/_layouts/15/userphoto.aspx?size=S&username=${sipValue}`;
const medPictureUrl: string = `/_layouts/15/userphoto.aspx?size=M&username=${sipValue}`;
const largePictureUrl: string = `/_layouts/15/userphoto.aspx?size=L&username=${sipValue}`;
let result: IPersonValue = {
email: sipValue,
displayName: name,
picture: {
small: smallPictureUrl,
medium: medPictureUrl,
large: largePictureUrl
}
};
return result;
} catch (error) {
return null;
}
}
/**************************************************************************************************
* Returns an error message based on the specified error object

View File

@ -0,0 +1,9 @@
export enum QueryFilterFieldType {
Text = 1,
Number = 2,
Datetime = 3,
User = 4,
Lookup = 5,
Taxonomy = 6,
Url = 7
}

Some files were not shown because too many files have changed in this diff Show More