diff --git a/samples/react-script-editor/README.md b/samples/react-script-editor/README.md index 52266e3b1..863c6dc66 100644 --- a/samples/react-script-editor/README.md +++ b/samples/react-script-editor/README.md @@ -48,7 +48,7 @@ If all you want is to add markup on the page, you can do that as well. Adding th ``` ## Used SharePoint Framework Version -![drop](https://img.shields.io/badge/drop-1.3.0-green.svg) +![drop](https://img.shields.io/badge/drop-1.4.0-green.svg) ## Applies to @@ -68,6 +68,7 @@ Version|Date|Comments 1.0|March 10th, 2017|Initial release 1.0.0.1|August 8th, 2017|Updated SPFx version and CSS loading 1.0.0.2|October 4th, 2017|Updated SPFx version, bundle Office UI Fabric and CSS in webpart +1.0.0.3|January 10th, 2018|Updated SPFx version, added remove padding property and refactoring ## 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.** diff --git a/samples/react-script-editor/config/package-solution.json b/samples/react-script-editor/config/package-solution.json index a5558798a..8de8dee3c 100644 --- a/samples/react-script-editor/config/package-solution.json +++ b/samples/react-script-editor/config/package-solution.json @@ -2,7 +2,9 @@ "solution": { "name": "Modern Script Editor web part by Puzzlepart", "id": "1425175f-3ed8-44d2-8fc4-dd1497191294", - "version": "1.0.0.2" + "version": "1.0.0.3", + "includeClientSideAssets": true, + "skipFeatureDeployment": false }, "paths": { "zippedPackage": "solution/pzl-script-editor.sppkg" diff --git a/samples/react-script-editor/config/tslint.json b/samples/react-script-editor/config/tslint.json index 3c085daaf..573df5093 100644 --- a/samples/react-script-editor/config/tslint.json +++ b/samples/react-script-editor/config/tslint.json @@ -29,7 +29,6 @@ "no-switch-case-fall-through": true, "no-unnecessary-semicolons": true, "no-unused-expression": true, - "no-unused-imports": true, "no-use-before-declare": true, "no-with-statement": true, "semicolon": true, diff --git a/samples/react-script-editor/package.json b/samples/react-script-editor/package.json index 95bba0bc8..e03ddd35b 100644 --- a/samples/react-script-editor/package.json +++ b/samples/react-script-editor/package.json @@ -6,25 +6,23 @@ "node": ">=0.10.0" }, "dependencies": { - "@microsoft/sp-core-library": "~1.3.0", - "@microsoft/sp-webpart-base": "~1.3.0", - "@types/react": "15.0.38", - "@types/react-addons-shallow-compare": "0.14.17", - "@types/react-addons-test-utils": "0.14.15", - "@types/react-addons-update": "0.14.14", - "@types/react-dom": "0.14.18", + "@microsoft/sp-core-library": "~1.4.0", + "@microsoft/sp-webpart-base": "~1.4.0", + "@types/react": "15.6.6", + "@types/react-dom": "15.5.6", "@types/webpack-env": ">=1.12.1 <1.14.0", "office-ui-fabric-react": "^4.40.1", - "react": "15.4.2", - "react-dom": "15.4.2" + "react": "15.6.2", + "react-dom": "15.6.2" }, "devDependencies": { - "@microsoft/sp-build-web": "~1.3.0", - "@microsoft/sp-module-interfaces": "~1.3.0", - "@microsoft/sp-webpart-workbench": "~1.3.0", + "@microsoft/sp-build-web": "~1.4.0", + "@microsoft/sp-module-interfaces": "~1.4.0", + "@microsoft/sp-webpart-workbench": "~1.4.0", "gulp": "~3.9.1", "@types/chai": ">=3.4.34 <3.6.0", - "@types/mocha": ">=2.2.33 <2.6.0" + "@types/mocha": ">=2.2.33 <2.6.0", + "ajv": "~5.2.2" }, "scripts": { "build": "gulp bundle", diff --git a/samples/react-script-editor/src/webparts/scriptEditor/IScriptEditorWebPartProps.ts b/samples/react-script-editor/src/webparts/scriptEditor/IScriptEditorWebPartProps.ts index 962a6f508..80b462b8a 100644 --- a/samples/react-script-editor/src/webparts/scriptEditor/IScriptEditorWebPartProps.ts +++ b/samples/react-script-editor/src/webparts/scriptEditor/IScriptEditorWebPartProps.ts @@ -1,3 +1,4 @@ export interface IScriptEditorWebPartProps { script: string; + removePadding: boolean; } diff --git a/samples/react-script-editor/src/webparts/scriptEditor/ScriptEditorWebPart.manifest.json b/samples/react-script-editor/src/webparts/scriptEditor/ScriptEditorWebPart.manifest.json index 87cd22b5a..1935a65c0 100644 --- a/samples/react-script-editor/src/webparts/scriptEditor/ScriptEditorWebPart.manifest.json +++ b/samples/react-script-editor/src/webparts/scriptEditor/ScriptEditorWebPart.manifest.json @@ -1,25 +1,28 @@ { - "$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json", - "id": "3a328f0a-99c4-4b28-95ab-fe0847f657a3", - "alias": "ScriptEditorWebPart", - "componentType": "WebPart", - "version": "*", // The "*" signifies that the version should be taken from the package.json - "manifestVersion": 2, - - /** - * This property should only be set to true if it is certain that the webpart does not - * allow arbitrary scripts to be called - */ - "safeWithCustomScriptDisabled": false, - - "preconfiguredEntries": [{ - "groupId": "3a328f0a-99c4-4b28-95ab-fe0847f657a3", - "group": { "default": "Puzzlepart" }, - "title": { "default": "Modern Script Editor" }, - "description": { "default": "Add arbitrary script to a page" }, - "officeFabricIconFontName": "JS", - "properties": { - "script": "" - } - }] -} + "$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json", + "id": "3a328f0a-99c4-4b28-95ab-fe0847f657a3", + "alias": "ScriptEditorWebPart", + "componentType": "WebPart", + "version": "*", + "manifestVersion": 2, + "requiresCustomScript": true, + "preconfiguredEntries": [ + { + "groupId": "3a328f0a-99c4-4b28-95ab-fe0847f657a3", + "group": { + "default": "Puzzlepart" + }, + "title": { + "default": "Modern Script Editor" + }, + "description": { + "default": "Add arbitrary script to a page" + }, + "officeFabricIconFontName": "JS", + "properties": { + "script": "", + "removePadding": false + } + } + ] +} \ No newline at end of file diff --git a/samples/react-script-editor/src/webparts/scriptEditor/ScriptEditorWebPart.ts b/samples/react-script-editor/src/webparts/scriptEditor/ScriptEditorWebPart.ts index 2c1723d53..659624ff0 100644 --- a/samples/react-script-editor/src/webparts/scriptEditor/ScriptEditorWebPart.ts +++ b/samples/react-script-editor/src/webparts/scriptEditor/ScriptEditorWebPart.ts @@ -3,156 +3,167 @@ import * as ReactDom from 'react-dom'; import { Version, DisplayMode } from '@microsoft/sp-core-library'; import { SPComponentLoader } from '@microsoft/sp-loader'; import { - BaseClientSideWebPart, - IPropertyPaneConfiguration, - PropertyPaneCustomField + BaseClientSideWebPart, + IPropertyPaneConfiguration, + PropertyPaneCustomField } from '@microsoft/sp-webpart-base'; import ScriptEditor from './components/ScriptEditor'; import { IScriptEditorProps } from './components/IScriptEditorProps'; import { IScriptEditorWebPartProps } from './IScriptEditorWebPartProps'; +import { PropertyPaneToggle } from '@microsoft/sp-webpart-base/lib/propertyPane/propertyPaneFields/propertyPaneToggle/PropertyPaneToggle'; export default class ScriptEditorWebPart extends BaseClientSideWebPart { - public save: (script: string) => void = (script: string) => { - this.properties.script = script; - this.render(); - } - - public render(): void { - const element: React.ReactElement = React.createElement( - ScriptEditor, - { - script: this.properties.script, - save: this.save - } - ); - - if (this.displayMode == DisplayMode.Read) { - this.domElement.innerHTML = this.properties.script; - this.executeScript(this.domElement); - } else { - ReactDom.render(element, this.domElement); + public save: (script: string) => void = (script: string) => { + this.properties.script = script; + this.render(); } - } - protected get dataVersion(): Version { - return Version.parse('1.0'); - } + public render(): void { + const element: React.ReactElement = React.createElement( + ScriptEditor, + { + script: this.properties.script, + save: this.save + } + ); - protected renderLogo(domElement: HTMLElement) { - domElement.innerHTML = ` + if (this.displayMode == DisplayMode.Read) { + if (this.properties.removePadding) { + this.domElement.parentElement.parentElement.parentElement.style.paddingTop = "0"; + this.domElement.parentElement.parentElement.parentElement.style.paddingBottom = "0"; + } else { + this.domElement.parentElement.parentElement.parentElement.style.paddingTop = ""; + this.domElement.parentElement.parentElement.parentElement.style.paddingBottom = ""; + } + this.domElement.innerHTML = this.properties.script; + this.executeScript(this.domElement); + } else { + ReactDom.render(element, this.domElement); + } + } + + protected get dataVersion(): Version { + return Version.parse('1.0'); + } + + protected renderLogo(domElement: HTMLElement) { + domElement.innerHTML = `
Author: Mikael Svenson
-
+
`; - } + } - protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { - return { - pages: [ - { - header: { - description: 'No settings to change for this web part.' - }, - groups: [ - { - groupFields: [ - PropertyPaneCustomField({ - onRender: this.renderLogo, - key: "logo" - }) - ] + protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { + return { + pages: [ + { + groups: [ + { + groupFields: [ + PropertyPaneToggle("removePadding", { + label: "Remove top/bottom padding of web part container", + checked: this.properties.removePadding, + onText: "Remove padding", + offText: "Keep padding" + }), + PropertyPaneCustomField({ + onRender: this.renderLogo, + key: "logo" + }) + ] + } + ] + } + ] + }; + } + + + private evalScript(elem) { + const data = (elem.text || elem.textContent || elem.innerHTML || ""); + const headTag = document.getElementsByTagName("head")[0] || document.documentElement; + const scriptTag = document.createElement("script"); + + scriptTag.type = "text/javascript"; + if (elem.src && elem.src.length > 0) { + return; + } + if (elem.onload && elem.onload.length > 0) { + scriptTag.onload = elem.onload; + } + + try { + // doesn't work on ie... + scriptTag.appendChild(document.createTextNode(data)); + } catch (e) { + // IE has funky script nodes + scriptTag.text = data; + } + + headTag.insertBefore(scriptTag, headTag.firstChild); + headTag.removeChild(scriptTag); + } + + private nodeName(elem, name) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + } + + + // Finds and executes scripts in a newly added element's body. + // Needed since innerHTML does not run scripts. + // + // Argument element is an element in the dom. + private executeScript(element: HTMLElement) { + // Define global name to tack scripts on in case script to be loaded is not AMD/UMD + (window).ScriptGlobal = {}; + + // main section of function + const scripts = []; + const children_nodes = element.childNodes; + + for (var i = 0; children_nodes[i]; i++) { + const child: any = children_nodes[i]; + if (this.nodeName(child, "script") && + (!child.type || child.type.toLowerCase() === "text/javascript")) { + scripts.push(child); } - ] } - ] - }; - } - - private evalScript(elem) { - const data = (elem.text || elem.textContent || elem.innerHTML || ""); - const headTag = document.getElementsByTagName("head")[0] || document.documentElement; - const scriptTag = document.createElement("script"); - - scriptTag.type = "text/javascript"; - if (elem.src && elem.src.length > 0) { - return; - } - if (elem.onload && elem.onload.length > 0) { - scriptTag.onload = elem.onload; - } - - try { - // doesn't work on ie... - scriptTag.appendChild(document.createTextNode(data)); - } catch (e) { - // IE has funky script nodes - scriptTag.text = data; - } - - headTag.insertBefore(scriptTag, headTag.firstChild); - headTag.removeChild(scriptTag); - } - - private nodeName(elem, name) { - return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); - } - - - // Finds and executes scripts in a newly added element's body. - // Needed since innerHTML does not run scripts. - // - // Argument element is an element in the dom. - private executeScript(element: HTMLElement) { - // Define global name to tack scripts on in case script to be loaded is not AMD/UMD - (window).ScriptGlobal = {}; - - // main section of function - const scripts = []; - const children_nodes = element.childNodes; - - for (var i = 0; children_nodes[i]; i++) { - const child: any = children_nodes[i]; - if (this.nodeName(child, "script") && - (!child.type || child.type.toLowerCase() === "text/javascript")) { - scripts.push(child); - } - } - - const urls = []; - const onLoads = []; - for (var j = 0; scripts[j]; j++) { - const scriptTag = scripts[j]; - if (scriptTag.src && scriptTag.src.length > 0) { - urls.push(scriptTag.src); - } - if (scriptTag.onload && scriptTag.onload.length > 0) { - onLoads.push(scriptTag.onload); - } - } - - // Execute promises in sequentially - https://hackernoon.com/functional-javascript-resolving-promises-sequentially-7aac18c4431e - // Use "ScriptGlobal" as the global namein case script is AMD/UMD - const allFuncs = urls.map(url => () => SPComponentLoader.loadScript(url, { globalExportsName: "ScriptGlobal" })); - - const promiseSerial = funcs => - funcs.reduce((promise, func) => - promise.then(result => func().then(Array.prototype.concat.bind(result))), - Promise.resolve([])); - - // execute Promises in serial - promiseSerial(allFuncs) - .then(() => { - // execute any onload people have added - for (j = 0; onLoads[j]; j++) { - onLoads[j](); + const urls = []; + const onLoads = []; + for (var j = 0; scripts[j]; j++) { + const scriptTag = scripts[j]; + if (scriptTag.src && scriptTag.src.length > 0) { + urls.push(scriptTag.src); + } + if (scriptTag.onload && scriptTag.onload.length > 0) { + onLoads.push(scriptTag.onload); + } } - // execute script blocks - for (j = 0; scripts[j]; j++) { - const scriptTag = scripts[j]; - if (scriptTag.parentNode) { scriptTag.parentNode.removeChild(scriptTag); } - this.evalScript(scripts[j]); - } - }).catch(console.error); - } + + // Execute promises in sequentially - https://hackernoon.com/functional-javascript-resolving-promises-sequentially-7aac18c4431e + // Use "ScriptGlobal" as the global namein case script is AMD/UMD + const allFuncs = urls.map(url => () => SPComponentLoader.loadScript(url, { globalExportsName: "ScriptGlobal" })); + + const promiseSerial = funcs => + funcs.reduce((promise, func) => + promise.then(result => func().then(Array.prototype.concat.bind(result))), + Promise.resolve([])); + + // execute Promises in serial + promiseSerial(allFuncs) + .then(() => { + // execute any onload people have added + for (j = 0; onLoads[j]; j++) { + onLoads[j](); + } + // execute script blocks + for (j = 0; scripts[j]; j++) { + const scriptTag = scripts[j]; + if (scriptTag.parentNode) { scriptTag.parentNode.removeChild(scriptTag); } + this.evalScript(scripts[j]); + } + }).catch(console.error); + } } \ No newline at end of file diff --git a/samples/react-script-editor/src/webparts/scriptEditor/components/ScriptEditor.module.scss b/samples/react-script-editor/src/webparts/scriptEditor/components/ScriptEditor.module.scss index 771ab3e72..deadb3761 100644 --- a/samples/react-script-editor/src/webparts/scriptEditor/components/ScriptEditor.module.scss +++ b/samples/react-script-editor/src/webparts/scriptEditor/components/ScriptEditor.module.scss @@ -1,4 +1,4 @@ -//@import url(https://publiccdn.sharepointonline.com/techmikael.sharepoint.com/11510075fe4212d19d3e6d07c91981263dd697bf111cb1e5f0efb15de0ec08b382cde399/5.0.1/office-ui-fabric.min.css); +@import '~office-ui-fabric-react/dist/sass/References.scss'; .scriptEditor { .container { @@ -10,45 +10,4 @@ .row { padding: 20px; } - - .listItem { - max-width: 715px; - margin: 5px auto 5px auto; - box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1); - } - - .button { - // Our button - text-decoration: none; - height: 32px; - - // Primary Button - min-width: 80px; - background-color: #0078d7; - border-color: #0078d7; - color: #ffffff; - - // 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: 14px; - font-weight: 400; - border-width: 0; - text-align: center; - cursor: pointer; - display: inline-block; - padding: 0 16px; - - .label { - font-weight: 600; - font-size: 14px; - height: 32px; - line-height: 32px; - margin: 0 4px; - vertical-align: top; - display: inline-block; - } - } } \ No newline at end of file diff --git a/samples/react-script-editor/src/webparts/scriptEditor/components/ScriptEditor.tsx b/samples/react-script-editor/src/webparts/scriptEditor/components/ScriptEditor.tsx index 254957f53..5fee23cd2 100644 --- a/samples/react-script-editor/src/webparts/scriptEditor/components/ScriptEditor.tsx +++ b/samples/react-script-editor/src/webparts/scriptEditor/components/ScriptEditor.tsx @@ -8,72 +8,78 @@ import { loadStyles } from '@microsoft/load-themed-styles'; require('./overrides.css'); export default class ScriptEditor extends React.Component { - constructor() { - super(); - const uiFabricCSS: string = ` + constructor() { + super(); + + this._showDialog = this._showDialog.bind(this); + this._closeDialog = this._closeDialog.bind(this); + this._cancelDialog = this._cancelDialog.bind(this); + this._onScriptEditorTextChanged = this._onScriptEditorTextChanged.bind(this); + + const uiFabricCSS: string = ` .pzl-bgColor-themeDark, .pzl-bgColor-themeDark--hover:hover { background-color: "[theme:themeDark, default:#005a9e]"; } `; - loadStyles(uiFabricCSS); - this.state = { - showDialog: false - }; - } + loadStyles(uiFabricCSS); + this.state = { + showDialog: false + }; + } - public componentDidMount(): void { - this.setState({ script: this.props.script, loaded: this.props.script }); - } - private _showDialog() { - this.setState({ showDialog: true }); - } + public componentDidMount(): void { + this.setState({ script: this.props.script, loaded: this.props.script }); + } + private _showDialog() { + this.setState({ showDialog: true }); + } - private _closeDialog() { - this.setState({ showDialog: false }); - this.props.save(this.state.script); - } + private _closeDialog() { + this.setState({ showDialog: false }); + this.props.save(this.state.script); + } - private _cancelDialog() { - this.setState({ showDialog: false }); - this.state.script = this.state.loaded; - } + private _cancelDialog() { + this.props.save(this.state.loaded); + this.setState({ showDialog: false, script: this.state.loaded }); + } - private _onScriptEditorTextChanged(text: string) { - this.setState({ script: text }); - } + private _onScriptEditorTextChanged(text: string) { + this.setState({ script: text }); + } - public render(): React.ReactElement { - const viewMode = ; + public render(): React.ReactElement { + const viewMode = ; - return ( -
-
-
-
-
- The Modern Script Editor web part! -

- Edit snippet -
-
-
-
- - - - Save - Cancel - - {viewMode} - -
); - } + return ( +
+
+
+
+
+ The Modern Script Editor web part! +

+ Edit snippet +
+
+
+
+ + + + Save + Cancel + + {viewMode} + +
); + } } \ No newline at end of file diff --git a/samples/react-script-editor/tsconfig.json b/samples/react-script-editor/tsconfig.json index 5fa39c930..aafd78af1 100644 --- a/samples/react-script-editor/tsconfig.json +++ b/samples/react-script-editor/tsconfig.json @@ -8,8 +8,12 @@ "sourceMap": true, "types": [ "es6-promise", - "es6-collections", "webpack-env" + ], + "lib": [ + "es5", + "dom", + "es2015.collection" ] } }