Added enhanced list formatting web part sample (#1175)
* Added enhanced list formatting web part sample * Update README.md * Fixed minor issue with read only render * Added descriptions to monaco editor window * Removed duplicated resources * Fixed markdown lint issues
This commit is contained in:
parent
90e1658e18
commit
0868acab74
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
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
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "stylelint-config-standard",
|
||||
"plugins": [
|
||||
"stylelint-scss"
|
||||
],
|
||||
"rules": {
|
||||
"at-rule-no-unknown": null,
|
||||
"scss/at-rule-no-unknown": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"isCreatingSolution": false,
|
||||
"environment": "spo",
|
||||
"version": "1.10.0",
|
||||
"libraryName": "react-enhanced-list-formatting",
|
||||
"libraryId": "af9a29d7-413a-4763-8ae0-926101bd010a",
|
||||
"packageManager": "npm",
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "library"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
---
|
||||
page_type: sample
|
||||
products:
|
||||
- office-sp
|
||||
languages:
|
||||
- typescript
|
||||
extensions:
|
||||
contentType: samples
|
||||
technologies:
|
||||
- SharePoint Framework
|
||||
platforms:
|
||||
- react
|
||||
createdDate: 5/1/2017 12:00:00 AM
|
||||
---
|
||||
|
||||
# Enhanced List Formatting
|
||||
|
||||
## Summary
|
||||
|
||||
This web part allows you to add custom CSS on a page to enhance list formatting.
|
||||
|
||||
![picture of the web part in action](./assets/EnhancedListFormatting.gif)
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![1.10.0](https://img.shields.io/badge/version-1.10.0-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To use this web part, you must be familiar with SharePoint list formatting and CSS.
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-enhanced-list-formatting | Hugo Bernier ([Tahoe Ninjas](http://tahoeninjas.blog), [@bernierh](https://twitter.com/bernierh))
|
||||
react-enhanced-list-formatting | David Warner II ([@DavidWarnerII](https://twitter.com/davidwarnerii) / [Warner Digital](http://warner.digital))
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|March 17, 2020|Initial release
|
||||
|
||||
## 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.**
|
||||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
* Clone this repository
|
||||
* in the command line run:
|
||||
* `npm install`
|
||||
* `gulp serve`
|
||||
|
||||
## Features
|
||||
|
||||
This web part demonstrates how to use a custom property pane control to allow users to inject custom CSS into the page at runtime.
|
||||
|
||||
> **Important**
|
||||
>
|
||||
> This web part is not intended to be used to override global CSS styles. It should only be used on custom CSS class names.
|
||||
>
|
||||
> At the time that we built this solution, the only codeless way to add custom CSS classes in a SharePoint page is to use the **Format view** option in a list view, then insert the **List** web part on a page.
|
||||
>
|
||||
> If you change any global styles, you may introduce unpredictable issues in your environment. Please remove the web part if you experience any issues.
|
||||
>
|
||||
> Injecting custom CSS is *not* supported by Microsoft or the creators of this sample.
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-enhanced-list-formatting" />
|
||||
|
||||
To use this web part, follow these steps:
|
||||
|
||||
1. Create a custom list view
|
||||
2. From your custom list view, select **Format current view** from the view drop-down.
|
||||
![Format current view](./assets/Viewformatting.png)
|
||||
3. In the **Format view** pane, add the `class` attribute in an element node, as follows:
|
||||
|
||||
```json
|
||||
"attributes": {
|
||||
"class": "yourcustomclassgoeshere"
|
||||
},
|
||||
```
|
||||
|
||||
3. **Preview** and **Save** your custom format.
|
||||
4. Add the list web part to a page and select the custom view you created
|
||||
5. Add the **Enhanced List Formatting** web part (this web part) to the same page where you added the **List** web part.
|
||||
6. After dismissing the disclaimer, use the web part's property pane to add your own CSS styles.
|
||||
7. Save your page and preview it in **View** mode.
|
||||
|
||||
> **TIP**
|
||||
>
|
||||
> Try to use the out-of-the-box custom view format schema by using the `style` attribute wherever possible. Your users may want to use your custom view in areas where the web part will not be available -- for example, within Microsoft Teams.
|
||||
>
|
||||
> Rely on custom CSS styles to *augment* your design, not replace the custom view format.
|
||||
|
||||
### Suitable uses of this web part
|
||||
|
||||
Here are some examples of how you should use this web part responsibly:
|
||||
|
||||
- Add styles to your custom CSS classes that the custom view format schema does not support (e.g.: RGBA values)
|
||||
- Add [pseudo-elements](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements) styles to your custom CSS classes (e.g.: `::first-letter`, `::after`, `::before`)
|
||||
- Add [pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes) styles to your custom CSS classes (e.g.: `:hover`, `:first`, `:nth-child`)
|
||||
- Add [animations](https://developer.mozilla.org/en-US/docs/Web/CSS/animation) to your custom CSS classes
|
||||
|
||||
### Unsuitable uses
|
||||
|
||||
At the risk of repeating ourselves, do not use this web part to do the following style changes:
|
||||
|
||||
- Changing any CSS classes that begin with `ms-`, as they indicate a Microsoft global style.
|
||||
- Changing element styles, unless you use your custom CSS class as a selector to ensure that your styles only apply to your list (e.g.: `div.mycustomclass`, `.mycustomclass > div`)
|
||||
|
||||
### Removing the annoying disclaimer
|
||||
|
||||
The sample has a disclaimer that is inspired by that annoying disclaimer you see on most in-dashboard GPS systems. If you want to remove it, you can do so by following these steps:
|
||||
|
||||
1. Open `EnhancedListFormattingWebPart.manifest.json'
|
||||
2. Find the following section:
|
||||
|
||||
```json
|
||||
"properties": {
|
||||
"description": "Enhanced List Formatting"
|
||||
}
|
||||
```
|
||||
|
||||
3. Add the following JSON:
|
||||
|
||||
```json
|
||||
"acceptedDisclaimer": true
|
||||
```
|
||||
|
||||
4. Your `properties` JSON should now look like this:
|
||||
|
||||
```json
|
||||
"properties": {
|
||||
"description": "Enhanced List Formatting",
|
||||
"acceptedDisclaimer": true
|
||||
}
|
||||
```
|
||||
|
||||
5. Test that your changes work by using `gulp build` and `gulp serve` and re-add a new version of the web part to your page
|
||||
6. Build a production version of the solution using `gulp dist`. See [Building the code](#Building_the_code)
|
||||
|
||||
### Building the code
|
||||
|
||||
```bash
|
||||
git clone the repo
|
||||
npm i
|
||||
npm i -g gulp
|
||||
gulp
|
||||
```
|
||||
|
||||
This package produces the following:
|
||||
|
||||
* lib/* - intermediate-stage commonjs build artifacts
|
||||
* dist/* - the bundled script, along with other resources
|
||||
* deploy/* - all resources which should be uploaded to a CDN.
|
||||
|
||||
### Build options
|
||||
|
||||
* gulp clean - Cleans the solution
|
||||
* gulp test - Runs unit tests
|
||||
* gulp serve - Runs the solution for testing purposes
|
||||
* gulp bundle - Bundles the solution
|
||||
* gulp package-solution - Packages the solution
|
||||
* gulp dev -- Builds a clean instance of the solution for development purposes
|
||||
* gulp dist -- Builds a clean instance of the solution for distribution purposes
|
Binary file not shown.
After Width: | Height: | Size: 9.0 MiB |
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"enhanced-list-formatting-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/enhancedListFormatting/EnhancedListFormattingWebPart.js",
|
||||
"manifest": "./src/webparts/enhancedListFormatting/EnhancedListFormattingWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"EnhancedListFormattingWebPartStrings": "lib/webparts/enhancedListFormatting/loc/{locale}.js",
|
||||
"MonacoControlsLibraryStrings": "lib/controls/loc/{locale}.js",
|
||||
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-enhanced-list-formatting",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"preset": "@voitanos/jest-preset-spfx-react16",
|
||||
"rootDir": "../src"
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "Enhanced List Formatting",
|
||||
"id": "af9a29d7-413a-4763-8ae0-926101bd010a",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"isDomainIsolated": false
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/enhanced-list-formatting.sppkg"
|
||||
}
|
||||
}
|
|
@ -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/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
'use strict';
|
||||
|
||||
// check if gulp dist was called
|
||||
if (process.argv.indexOf('dist') !== -1) {
|
||||
// add ship options to command call
|
||||
process.argv.push('--ship');
|
||||
}
|
||||
|
||||
const path = require('path');
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
const gulpSequence = require('gulp-sequence');
|
||||
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
// Create clean distrubution package
|
||||
gulp.task('dist', gulpSequence('clean', 'bundle', 'package-solution'));
|
||||
// Create clean development package
|
||||
gulp.task('dev', gulpSequence('clean', 'bundle', 'package-solution'));
|
||||
|
||||
|
||||
/**
|
||||
* Webpack Bundle Anlayzer
|
||||
* Reference and gulp task
|
||||
*/
|
||||
const bundleAnalyzer = require('webpack-bundle-analyzer');
|
||||
|
||||
build.configureWebpack.mergeConfig({
|
||||
|
||||
additionalConfiguration: (generatedConfiguration) => {
|
||||
const lastDirName = path.basename(__dirname);
|
||||
const dropPath = path.join(__dirname, 'temp', 'stats');
|
||||
generatedConfiguration.plugins.push(new bundleAnalyzer.BundleAnalyzerPlugin({
|
||||
openAnalyzer: false,
|
||||
analyzerMode: 'static',
|
||||
reportFilename: path.join(dropPath, `${lastDirName}.stats.html`),
|
||||
generateStatsFile: true,
|
||||
statsFilename: path.join(dropPath, `${lastDirName}.stats.json`),
|
||||
logLevel: 'error'
|
||||
}));
|
||||
|
||||
return generatedConfiguration;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* StyleLinter configuration
|
||||
* Reference and custom gulp task
|
||||
*/
|
||||
const stylelint = require('gulp-stylelint');
|
||||
|
||||
/* Stylelinter sub task */
|
||||
let styleLintSubTask = build.subTask('stylelint', (gulp) => {
|
||||
|
||||
return gulp
|
||||
.src('src/**/*.scss')
|
||||
.pipe(stylelint({
|
||||
failAfterError: false,
|
||||
reporters: [{
|
||||
formatter: 'string',
|
||||
console: true
|
||||
}]
|
||||
}));
|
||||
});
|
||||
/* end sub task */
|
||||
|
||||
build.rig.addPreBuildTask(styleLintSubTask);
|
||||
|
||||
/**
|
||||
* Custom Framework Specific gulp tasks
|
||||
*/
|
||||
|
||||
build.initialize(gulp);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"name": "react-enhanced-list-formatting",
|
||||
"version": "0.0.1",
|
||||
"private": false,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "./node_modules/.bin/jest --config ./config/jest.config.json",
|
||||
"test:watch": "./node_modules/.bin/jest --config ./config/jest.config.json --watchAll"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/sp-core-library": "1.10.0",
|
||||
"@microsoft/sp-lodash-subset": "1.10.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
|
||||
"@microsoft/sp-property-pane": "1.10.0",
|
||||
"@microsoft/sp-webpart-base": "1.10.0",
|
||||
"@pnp/spfx-property-controls": "^1.17.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",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.8.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
|
||||
"@microsoft/sp-build-web": "1.10.0",
|
||||
"@microsoft/sp-module-interfaces": "1.10.0",
|
||||
"@microsoft/sp-tslint-rules": "1.10.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.10.0",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"@voitanos/jest-preset-spfx-react16": "^1.3.2",
|
||||
"ajv": "~5.2.2",
|
||||
"gulp": "~3.9.1",
|
||||
"gulp-sequence": "1.0.0",
|
||||
"gulp-stylelint": "^11.0.0",
|
||||
"jest": "^23.6.0",
|
||||
"stylelint": "^13.0.0",
|
||||
"stylelint-config-standard": "^19.0.0",
|
||||
"stylelint-scss": "^3.13.0",
|
||||
"webpack-bundle-analyzer": "^3.6.0"
|
||||
}
|
||||
}
|
8928
samples/react-enhanced-list-formatting/src/controls/MonacoCustomBuild/index.js
vendored
Normal file
8928
samples/react-enhanced-list-formatting/src/controls/MonacoCustomBuild/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,13 @@
|
|||
export interface IMonacoEditorProps {
|
||||
value: string;
|
||||
theme: string;
|
||||
language: string;
|
||||
readOnly?: boolean;
|
||||
showLineNumbers?: boolean;
|
||||
showMiniMap?: boolean;
|
||||
showIndentGuides?: boolean;
|
||||
folding?: boolean;
|
||||
className?: string;
|
||||
onValueChange?: (newValue: string) => void;
|
||||
onDidBlurEditorText?: (value: string)=> void;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
@import "~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss";
|
||||
@import "~office-ui-fabric-react/dist/sass/References.scss";
|
||||
|
||||
.codeEditor {
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
//margin-bottom: 20px;
|
||||
//border:1px solid RGB(194,194,194);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
//border-color: $ms-color-neutralLight;
|
||||
border-color: $ms-color-neutralTertiaryAlt;
|
||||
border-radius: 2px;
|
||||
// border-color: rgb(51, 51, 51);
|
||||
|
||||
&:hover {
|
||||
border-color: $ms-color-neutralPrimary;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
border-color: $ms-color-themePrimary;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { css } from "@uifabric/utilities/lib/css";
|
||||
import styles from './MonacoEditor.module.scss';
|
||||
import { IMonacoEditorProps } from './IMonacoEditorProps';
|
||||
|
||||
const monaco = require('../MonacoCustomBuild') as any;
|
||||
|
||||
|
||||
|
||||
export class MonacoEditor extends React.Component<IMonacoEditorProps, {}> {
|
||||
|
||||
private _container: HTMLElement;
|
||||
private _editor: any;
|
||||
|
||||
public componentDidMount(): void {
|
||||
this.createEditor();
|
||||
}
|
||||
|
||||
private createEditor() {
|
||||
if (this._editor) {
|
||||
this._editor.dispose();
|
||||
}
|
||||
|
||||
//Create the editor
|
||||
this._editor = monaco.editor.create(this._container, {
|
||||
value: this.props.value,
|
||||
scrollBeyondLastLine: false,
|
||||
theme: this.props.theme,
|
||||
language: this.props.language,
|
||||
folding: this.props.folding,
|
||||
renderIndentGuides: this.props.showIndentGuides,
|
||||
readOnly: this.props.readOnly,
|
||||
lineNumbers: this.props.showLineNumbers,
|
||||
lineNumbersMinChars: 4,
|
||||
minimap: {
|
||||
enabled: this.props.showMiniMap
|
||||
}
|
||||
});
|
||||
|
||||
//Subscribe to changes
|
||||
this._editor.onDidChangeModelContent((e:any)=>this.onDidChangeModelContent(e));
|
||||
this._editor.onDidBlurEditorText((e:any)=>this.onDidBlurEditorText(e));
|
||||
this._editor.layout();
|
||||
//KLUDGE: The Monaco editor does not draw if layout is called too early
|
||||
//introduce a slight delay to make sure it is ready
|
||||
setTimeout(() => {
|
||||
this._editor.layout();
|
||||
}, 100);
|
||||
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: IMonacoEditorProps) {
|
||||
if (this.props.value !== prevProps.value) {
|
||||
console.log("Editor value changed", this.props.value);
|
||||
if (this._editor) {
|
||||
this._editor.setValue(this.props.value);
|
||||
}
|
||||
}
|
||||
if (this.props.theme !== prevProps.theme) {
|
||||
console.log("Editor theme changed", this.props.theme);
|
||||
monaco.editor.setTheme(this.props.theme);
|
||||
}
|
||||
if (this.props.showLineNumbers != prevProps.showLineNumbers ||
|
||||
this.props.showMiniMap != prevProps.showMiniMap ||
|
||||
this.props.showIndentGuides != prevProps.showIndentGuides ||
|
||||
this.props.folding != prevProps.folding ) {
|
||||
console.log("Editor various settings changed");
|
||||
this.createEditor();
|
||||
}
|
||||
if (this._editor) {
|
||||
console.log("Calling layout", this.props.theme);
|
||||
this._editor.layout();
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount(): void {
|
||||
if (this._editor) {
|
||||
this._editor.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public render(): React.ReactElement<IMonacoEditorProps> {
|
||||
return (
|
||||
<div ref={(container) => this._container = container!} className={css(styles.codeEditor, this.props.className)} />
|
||||
);
|
||||
}
|
||||
|
||||
private onDidBlurEditorText(e: any): void {
|
||||
if (this.props.onDidBlurEditorText && this._editor) {
|
||||
let curVal: string = this._editor.getValue();
|
||||
if (curVal !== this.props.value) {
|
||||
|
||||
this.props.onDidBlurEditorText(curVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeModelContent(e: any): void {
|
||||
if (this.props.onValueChange && this._editor) {
|
||||
let curVal: string = this._editor.getValue();
|
||||
if (curVal !== this.props.value) {
|
||||
|
||||
this.props.onValueChange(curVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from './MonacoEditor';
|
||||
export * from './IMonacoEditorProps';
|
|
@ -0,0 +1,72 @@
|
|||
@import "~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss";
|
||||
@import "~office-ui-fabric-react/dist/sass/References.scss";
|
||||
|
||||
.codeEditor {
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
//margin-bottom: 20px;
|
||||
//border:1px solid RGB(194,194,194);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
//border-color: $ms-color-neutralLight;
|
||||
border-color: $ms-color-neutralTertiaryAlt;
|
||||
border-radius: 2px;
|
||||
// border-color: rgb(51, 51, 51);
|
||||
|
||||
&:hover {
|
||||
border-color: $ms-color-neutralPrimary;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
border-color: $ms-color-themePrimary;
|
||||
}
|
||||
}
|
||||
|
||||
.actionButtonsContainer {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
background-color: $ms-color-white;
|
||||
-ms-flex-negative: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.actionButtons {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: end;
|
||||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.actionButton {
|
||||
height: 30px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
[dir="ltr"] .actionButton {
|
||||
padding-left: 16px;
|
||||
margin-left: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
[dir="ltr"] .actionButtons {
|
||||
padding-left: 20px;
|
||||
padding-right: 11px;
|
||||
}
|
||||
|
||||
:global {
|
||||
.ms-Panel-content {
|
||||
height: calc(100% - 80px);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
import * as React from 'react';
|
||||
import styles from './EditorPanel.module.scss';
|
||||
|
||||
|
||||
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
|
||||
import { DefaultButton, PrimaryButton, IconButton } from 'office-ui-fabric-react/lib/Button';
|
||||
|
||||
import { Async } from 'office-ui-fabric-react/lib/Utilities';
|
||||
|
||||
import * as strings from 'MonacoControlsLibraryStrings';
|
||||
import { MonacoEditor } from '../MonacoEditor';
|
||||
|
||||
export interface IEditorPanelProps {
|
||||
//defaultValue?: string;
|
||||
deferredValidationTime?: number;
|
||||
disabled?: boolean;
|
||||
editorClassName?: string;
|
||||
editorHeight?: string;
|
||||
errorMessage?: string;
|
||||
initialValue?: string;
|
||||
label?: string;
|
||||
language: string;
|
||||
targetProperty: string;
|
||||
showIndentGuides?: boolean;
|
||||
showLineNumbers?: boolean;
|
||||
showMiniMap?: boolean;
|
||||
folding?: boolean;
|
||||
theme?: string;
|
||||
value?: string;
|
||||
onClose(): void;
|
||||
onSave(value: string): void;
|
||||
}
|
||||
|
||||
export interface IEditorPanelState {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class EditorPanel extends React.Component<IEditorPanelProps, IEditorPanelState> {
|
||||
private _async: Async;
|
||||
//private _editor: AceEditor;
|
||||
private _delayedChange: (value: string) => void;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
constructor(props: IEditorPanelProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: this.props.value,
|
||||
};
|
||||
|
||||
this._async = new Async(this);
|
||||
this._delayedChange = this._async.debounce(this._handleOnChanged, this.props.deferredValidationTime ? this.props.deferredValidationTime : 200);
|
||||
}
|
||||
|
||||
// public componentDidMount(): void {
|
||||
// if (this.props.customMode !== undefined) {
|
||||
// try {
|
||||
// // execute the custom mode function
|
||||
// this.props.customMode();
|
||||
|
||||
// // get a reference to ace
|
||||
// const aceThingy: any = this._editor as any;
|
||||
|
||||
// // get a reference to brace
|
||||
// var ace = require('brace') as any;
|
||||
|
||||
// // set the mode to custom
|
||||
// var editor = ace.edit(aceThingy.editor);
|
||||
// editor.session.setMode(`ace/mode/custom`);
|
||||
// } catch (error) {
|
||||
// console.log("Error with refs", error);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
public render(): React.ReactElement<IEditorPanelProps> {
|
||||
return (
|
||||
<Panel
|
||||
isOpen={true}
|
||||
onDismiss={() => this._handleClose()}
|
||||
type={PanelType.large}
|
||||
headerText={this.props.label}
|
||||
onRenderFooterContent={() => (
|
||||
<div className={styles.actionButtonsContainer}>
|
||||
<div className={styles.actionButtons}>
|
||||
<PrimaryButton
|
||||
onClick={() => this._handleSave()} className={styles.actionButton}>{strings.SaveButtonLabel}</PrimaryButton>
|
||||
<DefaultButton onClick={() => this._handleClose()} className={styles.actionButton}>{strings.CancelButtonLabel}</DefaultButton>
|
||||
</div>
|
||||
</div>
|
||||
)}>
|
||||
<MonacoEditor
|
||||
value={this.props.value}
|
||||
theme={this.props.theme}
|
||||
readOnly={this.props.disabled}
|
||||
language={this.props.language}
|
||||
onValueChange={(editorString: string) => {
|
||||
this._delayedChange(editorString);
|
||||
}}
|
||||
showLineNumbers={this.props.showLineNumbers !== undefined ? this.props.showLineNumbers : false}
|
||||
showMiniMap={this.props.showMiniMap !== undefined ? this.props.showMiniMap : false}
|
||||
showIndentGuides={this.props.showIndentGuides !== undefined ? this.props.showIndentGuides : false}
|
||||
folding={this.props.folding !== undefined ? this.props.folding : false}
|
||||
/>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
||||
private _handleSave = () => {
|
||||
this.props.onSave(this.state.value);
|
||||
}
|
||||
|
||||
private _handleClose = () => {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
/**
|
||||
* On field change event handler
|
||||
*/
|
||||
private _handleOnChanged = (value: string): void => {
|
||||
// Update state
|
||||
this.setState({
|
||||
value
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
import { IPropertyPaneCustomFieldProps } from "@microsoft/sp-property-pane";
|
||||
import { IMonacoEditorProps } from "../MonacoEditor";
|
||||
|
||||
export interface IPropertyFieldMonacoEditorProps {
|
||||
/**
|
||||
* Custom Field will start to validate after users stop typing for `deferredValidationTime` milliseconds.
|
||||
* Default value is 200.
|
||||
*/
|
||||
deferredValidationTime?: number;
|
||||
|
||||
defaultValue?: string;
|
||||
|
||||
/**
|
||||
* Specify if the control needs to be disabled
|
||||
*/
|
||||
disabled?: boolean;
|
||||
|
||||
/**
|
||||
* The CSS class name to apply to the code editor component
|
||||
*/
|
||||
editorClassName?: string;
|
||||
|
||||
/**
|
||||
* The height style to apply to the code editor component
|
||||
*/
|
||||
editorHeight?: string;
|
||||
|
||||
/**
|
||||
* If set, this will be displayed as an error message.
|
||||
*
|
||||
* When onGetErrorMessage returns empty string, if this property has a value set then this will
|
||||
* be displayed as the error message.
|
||||
*
|
||||
* So, make sure to set this only if you want to see an error message dispalyed for the text field.
|
||||
*/
|
||||
errorMessage?: string;
|
||||
|
||||
/**
|
||||
* The initial code to display in the code editor
|
||||
*/
|
||||
initialValue?: string;
|
||||
|
||||
/**
|
||||
* An UNIQUE key indicates the identity of this control
|
||||
*/
|
||||
key: string;
|
||||
|
||||
/**
|
||||
* Property field label displayed on top
|
||||
*/
|
||||
label?: string;
|
||||
|
||||
/**
|
||||
* The language you wish to use with the editor.
|
||||
* */
|
||||
language: string;
|
||||
|
||||
/**
|
||||
* The theme you wish to use.
|
||||
*/
|
||||
theme?: string;
|
||||
|
||||
/**
|
||||
* The code you wish to display in the code editor
|
||||
*/
|
||||
value?: string;
|
||||
|
||||
/**
|
||||
* Indicates whether the editor should be read only
|
||||
*/
|
||||
readOnly?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the editor should show line numbers
|
||||
*/
|
||||
showLineNumbers?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the editor should show a mini-map
|
||||
*/
|
||||
showMiniMap?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the editor should show indent guides
|
||||
*/
|
||||
showIndentGuides?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the editor should allow code folding
|
||||
*/
|
||||
folding?: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether you should show a Full Page Editor button
|
||||
*/
|
||||
showFullScreen?: boolean;
|
||||
|
||||
/**
|
||||
* Defines a onPropertyChange function to raise when the code changes.
|
||||
* Normally this function must be always defined with the 'this.onPropertyChange'
|
||||
* method of the web part object.
|
||||
*/
|
||||
onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void;
|
||||
}
|
||||
|
||||
export interface IPropertyFieldMonacoEditorPropsInternal extends IPropertyPaneCustomFieldProps, IPropertyFieldMonacoEditorProps {
|
||||
targetProperty: string;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import { IPropertyFieldMonacoEditorPropsInternal } from './IPropertyFieldMonacoEditor';
|
||||
|
||||
|
||||
/**
|
||||
* PropertyFieldNumberHost properties interface
|
||||
*/
|
||||
export interface IPropertyFieldMonacoEditorHostProps extends IPropertyFieldMonacoEditorPropsInternal {
|
||||
onChange: (targetProperty?: string, newValue?: any) => void;
|
||||
}
|
||||
|
||||
export interface IPropertyFieldMonacoEditorHostState {
|
||||
//annotations: string[];
|
||||
editorClassName?: string;
|
||||
editorHeight?: string;
|
||||
//errorMessage?: string;
|
||||
value: string;
|
||||
fullScreen: boolean;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
@import "~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss";
|
||||
@import '~office-ui-fabric-react/dist/sass/References.scss';
|
||||
|
||||
.embeddedMonaco {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.fullScreenButton {
|
||||
color: inherit;
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { IPropertyPaneField, PropertyPaneFieldType } from "@microsoft/sp-property-pane";
|
||||
|
||||
import PropertyFieldAceEditorHost from './PropertyFieldMonacoEditorHost';
|
||||
|
||||
import { IPropertyFieldMonacoEditorPropsInternal, IPropertyFieldMonacoEditorProps } from './IPropertyFieldMonacoEditor';
|
||||
|
||||
|
||||
|
||||
class PropertyFieldMonacoEditorBuilder implements IPropertyPaneField<IPropertyFieldMonacoEditorPropsInternal> {
|
||||
public targetProperty: string;
|
||||
public type: PropertyPaneFieldType = PropertyPaneFieldType.Custom;
|
||||
public properties: IPropertyFieldMonacoEditorPropsInternal;
|
||||
public onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void { }
|
||||
private onGetErrorMessage?: (value: string, annotations: string[]) => string | Promise<string>;
|
||||
|
||||
|
||||
//private _onChangeCallback: (targetProperty?: string, newValue?: any) => void;
|
||||
|
||||
public constructor(_targetProperty: string, _properties: IPropertyFieldMonacoEditorPropsInternal) {
|
||||
this.targetProperty = _targetProperty;
|
||||
this.properties = _properties;
|
||||
this.onPropertyChange = _properties.onPropertyChange;
|
||||
this.properties.onRender = this._render.bind(this);
|
||||
this.properties.onDispose = this._dispose.bind(this);
|
||||
}
|
||||
|
||||
private _render(elem: HTMLElement, context?: any, changeCallback?: (targetProperty?: string, newValue?: any) => void): void {
|
||||
|
||||
const props: IPropertyFieldMonacoEditorProps = <IPropertyFieldMonacoEditorProps>this.properties;
|
||||
|
||||
const element = React.createElement(PropertyFieldAceEditorHost, {
|
||||
...props,
|
||||
targetProperty: this.targetProperty,
|
||||
onRender: this._render,
|
||||
onChange: changeCallback,
|
||||
onPropertyChange: this.onPropertyChange,
|
||||
});
|
||||
|
||||
ReactDOM.render(element, elem);
|
||||
|
||||
// if (changeCallback) {
|
||||
// this._onChangeCallback = changeCallback;
|
||||
// }
|
||||
}
|
||||
|
||||
private _dispose(elem: HTMLElement) {
|
||||
ReactDOM.unmountComponentAtNode(elem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this property pane control to allow users to edit in-place code.
|
||||
*
|
||||
* To allow full-page code editing, please use the PnP codeeditor property control.
|
||||
*
|
||||
* @param targetProperty
|
||||
* @param properties
|
||||
*/
|
||||
export function PropertyFieldMonacoEditor(targetProperty: string, properties: IPropertyFieldMonacoEditorProps): IPropertyPaneField<IPropertyFieldMonacoEditorPropsInternal> {
|
||||
return new PropertyFieldMonacoEditorBuilder(targetProperty, {
|
||||
...properties,
|
||||
targetProperty: targetProperty,
|
||||
onRender: null,
|
||||
onDispose: null
|
||||
});
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
import * as strings from 'MonacoControlsLibraryStrings';
|
||||
import * as React from 'react';
|
||||
|
||||
// Custom props and state
|
||||
import { IPropertyFieldMonacoEditorHostProps, IPropertyFieldMonacoEditorHostState } from './IPropertyFieldMonacoEditorHost';
|
||||
|
||||
// Office Fabric
|
||||
import { Label } from 'office-ui-fabric-react/lib/Label';
|
||||
import { IconButton } from 'office-ui-fabric-react/lib/Button';
|
||||
|
||||
// Custom styles
|
||||
import styles from './PropertyFieldMonacoEditor.module.scss';
|
||||
|
||||
// Custom code editor panel
|
||||
import { EditorPanel } from './EditorPanel';
|
||||
import { MonacoEditor } from '../MonacoEditor';
|
||||
|
||||
export default class PropertyFieldNumberHost extends React.Component<IPropertyFieldMonacoEditorHostProps, IPropertyFieldMonacoEditorHostState> {
|
||||
constructor(props: IPropertyFieldMonacoEditorHostProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: this.props.value,
|
||||
fullScreen: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render field
|
||||
*/
|
||||
public render(): JSX.Element {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Label>{this.props.label}</Label>
|
||||
<MonacoEditor
|
||||
value={this.props.defaultValue}
|
||||
theme={this.props.theme}
|
||||
readOnly={this.props.disabled}
|
||||
language={this.props.language}
|
||||
onDidBlurEditorText={(editorString: string)=> {
|
||||
this._handleOnChanged(editorString);
|
||||
}}
|
||||
showLineNumbers={this.props.showLineNumbers || false}
|
||||
showMiniMap={this.props.showMiniMap || false}
|
||||
showIndentGuides={this.props.showIndentGuides || false}
|
||||
folding={this.props.folding || false}
|
||||
className={styles.embeddedMonaco}
|
||||
/>
|
||||
{ this.props.showFullScreen !== false && <IconButton
|
||||
title={strings.ExpandButtonLabel}
|
||||
className={styles.fullScreenButton}
|
||||
iconProps={{ iconName: 'MiniExpand' }}
|
||||
onClick={() => this._handleOpenFullScreen()}
|
||||
/>}
|
||||
|
||||
{this.state.fullScreen &&
|
||||
<EditorPanel
|
||||
label={this.props.label}
|
||||
language={this.props.language}
|
||||
theme={this.props.theme}
|
||||
onSave={(value: string) => this._handleSaveFullScreen(value)}
|
||||
onClose={() => this._handleCloseFullScreen()}
|
||||
value={this.state.value}
|
||||
disabled={this.props.disabled}
|
||||
targetProperty={this.props.targetProperty}
|
||||
showMiniMap={true}
|
||||
showLineNumbers={true}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* On field change event handler
|
||||
*/
|
||||
private _handleOnChanged = (value: string): void => {
|
||||
// Update state
|
||||
this.setState({
|
||||
value
|
||||
});
|
||||
|
||||
|
||||
this.props.onPropertyChange(this.props.targetProperty, this.props.initialValue, value);
|
||||
if (typeof this.props.onChange !== 'undefined' && this.props.onChange !== null) {
|
||||
this.props.onChange(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when user clicks on the expand button
|
||||
*/
|
||||
private _handleOpenFullScreen = () => {
|
||||
this.setState({
|
||||
fullScreen: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets called by the editor pane when it is time to save
|
||||
*/
|
||||
private _handleSaveFullScreen = (newValue: string) => {
|
||||
this.setState({ fullScreen: false });
|
||||
this._handleOnChanged(newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* ets called by the editor pane when it is closed
|
||||
*/
|
||||
private _handleCloseFullScreen = () => {
|
||||
this.setState({ fullScreen: false });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
export * from './IPropertyFieldMonacoEditor';
|
||||
export * from './PropertyFieldMonacoEditor';
|
||||
export * from './IPropertyFieldMonacoEditorHost';
|
||||
export * from './PropertyFieldMonacoEditorHost';
|
|
@ -0,0 +1,7 @@
|
|||
define([], function() {
|
||||
return {
|
||||
ExpandButtonLabel: "Open full-screen editor",
|
||||
SaveButtonLabel: "Save",
|
||||
CancelButtonLabel: "Cancel",
|
||||
}
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
declare interface IMonacoControlsLibraryStrings {
|
||||
ExpandButtonLabel: string;
|
||||
SaveButtonLabel: string;
|
||||
CancelButtonLabel: string;
|
||||
}
|
||||
|
||||
declare module 'MonacoControlsLibraryStrings' {
|
||||
const strings: IMonacoControlsLibraryStrings;
|
||||
export = strings;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "d7608466-3f72-4e48-b3b7-f7cf47a93786",
|
||||
"alias": "EnhancedListFormattingWebPart",
|
||||
"componentType": "WebPart",
|
||||
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
|
||||
"requiresCustomScript": false,
|
||||
"supportedHosts": ["SharePointWebPart"],
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70",
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "Enhanced List Formatting" },
|
||||
"description": { "default": "Adds support for custom CSS styles with list formatting" },
|
||||
"iconImageUrl": "data:image/svg+xml,%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 512 512' style='enable-background:new 0 0 512 512;' xml:space='preserve'%3E %3Cg%3E %3Cpath style='fill:%230F81E0;' d='M370.759,110.345H17.655C7.901,110.345,0,102.444,0,92.69V22.069C0,12.314,7.901,4.414,17.655,4.414 h353.103c9.754,0,17.655,7.901,17.655,17.655V92.69C388.414,102.444,380.513,110.345,370.759,110.345'/%3E %3Cpath style='fill:%23ECBA16;' d='M370.759,242.759H17.655C7.901,242.759,0,234.858,0,225.103v-70.621 c0-9.754,7.901-17.655,17.655-17.655h353.103c9.754,0,17.655,7.901,17.655,17.655v70.621 C388.414,234.858,380.513,242.759,370.759,242.759'/%3E %3Cpath style='fill:%23ED7161;' d='M494.345,375.172H141.241c-9.754,0-17.655-7.901-17.655-17.655v-70.621 c0-9.754,7.901-17.655,17.655-17.655h353.103c9.754,0,17.655,7.901,17.655,17.655v70.621 C512,367.272,504.099,375.172,494.345,375.172'/%3E %3Cg%3E %3Cpath style='fill:%2342B05C;' d='M370.759,507.586H17.655C7.901,507.586,0,499.686,0,489.931V419.31 c0-9.754,7.901-17.655,17.655-17.655h353.103c9.754,0,17.655,7.901,17.655,17.655v70.621 C388.414,499.686,380.513,507.586,370.759,507.586'/%3E %3Cpath style='fill:%2342B05C;' d='M194.207,331.034H61.793c-4.882,0-8.828-3.955-8.828-8.828c0-4.873,3.946-8.828,8.828-8.828 h132.414c4.882,0,8.828,3.955,8.828,8.828C203.034,327.08,199.089,331.034,194.207,331.034'/%3E %3Cpath style='fill:%2342B05C;' d='M194.207,331.034c-2.26,0-4.52-0.865-6.241-2.586l-26.483-26.483 c-3.452-3.452-3.452-9.031,0-12.482s9.031-3.452,12.482,0l26.483,26.483c3.452,3.452,3.452,9.031,0,12.482 C198.727,330.169,196.467,331.034,194.207,331.034'/%3E %3Cpath style='fill:%2342B05C;' d='M167.724,357.517c-2.26,0-4.52-0.865-6.241-2.586c-3.452-3.452-3.452-9.031,0-12.482l26.483-26.483 c3.452-3.452,9.031-3.452,12.482,0c3.452,3.452,3.452,9.031,0,12.482l-26.483,26.483 C172.244,356.652,169.984,357.517,167.724,357.517'/%3E %3C/g%3E %3Cg%3E %3Cpath style='fill:%23FFFFFF;' d='M459.034,331.034H247.172c-4.882,0-8.828-3.955-8.828-8.828c0-4.873,3.946-8.828,8.828-8.828 h211.862c4.882,0,8.828,3.955,8.828,8.828C467.862,327.08,463.916,331.034,459.034,331.034'/%3E %3Cpath style='fill:%23FFFFFF;' d='M335.448,198.621H123.586c-4.882,0-8.828-3.955-8.828-8.828c0-4.873,3.946-8.828,8.828-8.828 h211.862c4.882,0,8.828,3.955,8.828,8.828C344.276,194.666,340.33,198.621,335.448,198.621'/%3E %3Cpath style='fill:%23FFFFFF;' d='M79.448,189.793c0-9.754-7.901-17.655-17.655-17.655s-17.655,7.901-17.655,17.655 c0,9.754,7.901,17.655,17.655,17.655S79.448,199.548,79.448,189.793'/%3E %3Cpath style='fill:%23FFFFFF;' d='M335.448,66.207H123.586c-4.882,0-8.828-3.955-8.828-8.828s3.946-8.828,8.828-8.828h211.862 c4.882,0,8.828,3.955,8.828,8.828S340.33,66.207,335.448,66.207'/%3E %3Cpath style='fill:%23FFFFFF;' d='M79.448,57.379c0-9.754-7.901-17.655-17.655-17.655s-17.655,7.901-17.655,17.655 s7.901,17.655,17.655,17.655S79.448,67.134,79.448,57.379'/%3E %3Cpath style='fill:%23FFFFFF;' d='M335.448,463.448H123.586c-4.882,0-8.828-3.955-8.828-8.828c0-4.873,3.946-8.828,8.828-8.828 h211.862c4.882,0,8.828,3.955,8.828,8.828C344.276,459.493,340.33,463.448,335.448,463.448'/%3E %3Cpath style='fill:%23FFFFFF;' d='M79.448,454.621c0-9.754-7.901-17.655-17.655-17.655s-17.655,7.901-17.655,17.655 c0,9.754,7.901,17.655,17.655,17.655S79.448,464.375,79.448,454.621'/%3E %3C/g%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3C/svg%3E ",
|
||||
"properties": {
|
||||
"description": "Enhanced List Formatting"
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDom from 'react-dom';
|
||||
import { Version } from '@microsoft/sp-core-library';
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
} from '@microsoft/sp-property-pane';
|
||||
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
|
||||
|
||||
import * as strings from 'EnhancedListFormattingWebPartStrings';
|
||||
import EnhancedListFormatting from './components/EnhancedListFormatting';
|
||||
import { IEnhancedListFormattingProps } from './components/IEnhancedListFormattingProps';
|
||||
|
||||
import { PropertyPaneWebPartInformation } from '@pnp/spfx-property-controls/lib/PropertyPaneWebPartInformation';
|
||||
import { PropertyFieldMonacoEditor } from '../../controls/PropertyFieldMonacoEditor';
|
||||
|
||||
|
||||
export interface IEnhancedListFormattingWebPartProps {
|
||||
css: string;
|
||||
acceptedDisclaimer?: boolean;
|
||||
}
|
||||
|
||||
export default class EnhancedListFormattingWebPart extends BaseClientSideWebPart <IEnhancedListFormattingWebPartProps> {
|
||||
|
||||
public render(): void {
|
||||
const element: React.ReactElement<IEnhancedListFormattingProps> = React.createElement(
|
||||
EnhancedListFormatting,
|
||||
{
|
||||
css: this.properties.css,
|
||||
acceptedDisclaimer: this.properties.acceptedDisclaimer,
|
||||
displayMode: this.displayMode,
|
||||
context: this.context,
|
||||
onAcceptDisclaimer: ()=>this._handleAcceptDisclaimer()
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse('1.0');
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyFieldMonacoEditor('css', {
|
||||
label: strings.CSSFieldLabel,
|
||||
key: "cssText",
|
||||
value: this.properties.css,
|
||||
defaultValue: this.properties.css,
|
||||
language: "css",
|
||||
theme: "vs-light",
|
||||
showLineNumbers: false,
|
||||
onPropertyChange: (_propertyPath: string, _oldValue: string, value: string) => this._handleSave(value),
|
||||
}),
|
||||
PropertyPaneWebPartInformation({
|
||||
description: strings.CSSDisclaimer,
|
||||
key: 'cssDisclaimer'
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
private _handleAcceptDisclaimer = () => {
|
||||
this.properties.acceptedDisclaimer = true;
|
||||
this.render();
|
||||
}
|
||||
|
||||
private _handleSave = (value: string) => {
|
||||
this.properties.css = value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
@import "~office-ui-fabric-react/dist/sass/References.scss";
|
||||
|
||||
.enhancedListFormatting {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.disclaimerText {
|
||||
color:$ms-color-black;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import * as React from 'react';
|
||||
import styles from './EnhancedListFormatting.module.scss';
|
||||
import * as strings from 'EnhancedListFormattingWebPartStrings';
|
||||
import { IEnhancedListFormattingProps } from './IEnhancedListFormattingProps';
|
||||
import { MessageBarButton, MessageBar, MessageBarType } from 'office-ui-fabric-react';
|
||||
import { DisplayMode } from '@microsoft/sp-core-library';
|
||||
|
||||
export default class EnhancedListFormatting extends React.Component<IEnhancedListFormattingProps, {}> {
|
||||
public render(): React.ReactElement<IEnhancedListFormattingProps> {
|
||||
const { css, displayMode, acceptedDisclaimer } = this.props;
|
||||
|
||||
// If we accepted the disclaimer, let's work as expected
|
||||
|
||||
// Determine if there is a CSS value
|
||||
const hasCSS: boolean = css !== undefined && css !== "";
|
||||
|
||||
// Create a style element
|
||||
const styleElem: JSX.Element = <style type="text/css">{css}</style>;
|
||||
|
||||
// If we're not in Edit mode, hide this web part
|
||||
if (displayMode !== DisplayMode.Edit) {
|
||||
return styleElem;
|
||||
}
|
||||
|
||||
// if we didn't accept the disclaimer, show a disclaimer and nothing else
|
||||
if (acceptedDisclaimer !== true) {
|
||||
return (<MessageBar
|
||||
onDismiss={()=>this._onAcceptDisclaimer()}
|
||||
dismissButtonAriaLabel={strings.DismissDisclaimerAriaLabel}
|
||||
messageBarType={MessageBarType.warning}
|
||||
actions={
|
||||
<div>
|
||||
<MessageBarButton onClick={()=>this._onAcceptDisclaimer()}>{strings.AcceptDisclaimerButton}</MessageBarButton>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className={styles.disclaimerText} dangerouslySetInnerHTML={{__html: strings.DisclaimerText}}></div>
|
||||
</MessageBar>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.enhancedListFormatting}>
|
||||
{styleElem}
|
||||
<MessageBar
|
||||
messageBarType={hasCSS ? MessageBarType.success : null}
|
||||
isMultiline={false}
|
||||
actions={
|
||||
<div>
|
||||
<MessageBarButton onClick={() => this._onConfigure()}>{hasCSS ? strings.PlaceholderButtonTitleHasStyles : strings.PlaceholderButtonTitleNoStyles}</MessageBarButton>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{hasCSS ? strings.PlaceholderDescriptionHasStyles : strings.PlaceholderDescriptionNoStyles}
|
||||
</MessageBar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private _onAcceptDisclaimer() {
|
||||
this.props.onAcceptDisclaimer();
|
||||
}
|
||||
|
||||
private _onConfigure() {
|
||||
this.props.context.propertyPane.open();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { DisplayMode } from '@microsoft/sp-core-library';
|
||||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
|
||||
export interface IEnhancedListFormattingProps {
|
||||
css: string;
|
||||
acceptedDisclaimer?: boolean;
|
||||
displayMode: DisplayMode;
|
||||
context: WebPartContext;
|
||||
onAcceptDisclaimer: () => void;
|
||||
}
|
30
samples/react-enhanced-list-formatting/src/webparts/enhancedListFormatting/loc/en-us.js
vendored
Normal file
30
samples/react-enhanced-list-formatting/src/webparts/enhancedListFormatting/loc/en-us.js
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
define([], function() {
|
||||
return {
|
||||
DisclaimerText: `<h2 style="font-size: 20px;margin-top:0px;font-weight:400;">Warning!</h2>
|
||||
This web part should ONLY be used to enhance your own custom HTML, such as List Formatting Definitions.<br/><br/>
|
||||
Misuse of this web part can result in:
|
||||
<ul>
|
||||
<li> Loss of styles applied
|
||||
<li> Broken styles
|
||||
<li> User calls asking why SharePoint is "Broken"
|
||||
<li> Tears
|
||||
<li> Nightmares
|
||||
<li> Blue Screens of Death
|
||||
</ul>
|
||||
Changing the CSS on a SharePoint page is unsupported. If you experience issues, please remove this web part.
|
||||
<br/><br/>
|
||||
By dismissing this message or clicking <b>I Accept</b>, you agree that will will use this web part responsibly.
|
||||
`,
|
||||
AcceptDisclaimerButton: "I Accept",
|
||||
DismissDisclaimerAriaLabel: "Close and accept responsibility",
|
||||
CSSDisclaimer: "You should only change the styles for your own custom CSS classes. Avoid changing global styles.<br/><br/>For more information on how to define custom CSS classes using list formatting, visit: <a href=\"https://aka.ms/list-formatting\" target='_blank'>aka.ms/list-formatting</a>.",
|
||||
PlaceholderButtonTitleNoStyles: "Add custom styles",
|
||||
PlaceholderButtonTitleHasStyles: "Change custom styles",
|
||||
PlaceholderDescriptionNoStyles: "Edit this web part to add custom styles to your page.",
|
||||
PlaceholderDescriptionHasStyles: "You are currently applying custom CSS styles to this page",
|
||||
PlaceholderIconText: "Inject Custom CSS",
|
||||
PropertyPaneDescription: `Use this web part to inject custom CSS to be used with list formatting. Don't worry, this web part is only visible when the page is in Edit mode.`,
|
||||
BasicGroupName: "Custom CSS",
|
||||
CSSFieldLabel: "Define your custom CSS"
|
||||
}
|
||||
});
|
19
samples/react-enhanced-list-formatting/src/webparts/enhancedListFormatting/loc/mystrings.d.ts
vendored
Normal file
19
samples/react-enhanced-list-formatting/src/webparts/enhancedListFormatting/loc/mystrings.d.ts
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
declare interface IEnhancedListFormattingWebPartStrings {
|
||||
DisclaimerText: string;
|
||||
AcceptDisclaimerButton: string;
|
||||
DismissDisclaimerAriaLabel: string;
|
||||
CSSDisclaimer: string;
|
||||
PlaceholderButtonTitleNoStyles: string;
|
||||
PlaceholderButtonTitleHasStyles: string;
|
||||
PlaceholderDescriptionNoStyles: string;
|
||||
PlaceholderDescriptionHasStyles: string;
|
||||
PlaceholderIconText: string;
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
CSSFieldLabel: string;
|
||||
}
|
||||
|
||||
declare module 'EnhancedListFormattingWebPartStrings' {
|
||||
const strings: IEnhancedListFormattingWebPartStrings;
|
||||
export = strings;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/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": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"extends": "@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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue