├── .editorconfig ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── .yo-rc.json ├── LICENSE ├── README.md ├── build.cmd ├── config ├── config.json ├── copy-assets.json ├── deploy-azure-storage.json ├── package-solution.json ├── serve.json └── write-manifests.json ├── gulpfile.js ├── images ├── add_app.gif ├── build.png ├── colors.png ├── demo.gif ├── new_item.gif ├── newitem.gif └── package.png ├── package-lock.json ├── package.json ├── sharepoint └── assets │ ├── ClientSideInstance.xml │ ├── elements.xml │ ├── listElements.xml │ └── listSchema_SideNav.xml ├── spfx-versioning.js ├── src ├── extensions │ └── spfxSideNavigation │ │ ├── SpfxSideNavigationApplicationCustomizer.manifest.json │ │ ├── SpfxSideNavigationApplicationCustomizer.ts │ │ ├── components │ │ └── SideNav │ │ │ ├── ISideNavProps.ts │ │ │ ├── ISideNavState.ts │ │ │ ├── SideNav.module.scss │ │ │ ├── SideNav.tsx │ │ │ ├── SideNavNode.tsx │ │ │ ├── model │ │ │ ├── ISPSideNavItem.ts │ │ │ ├── ISideNavItem.ts │ │ │ ├── ISideNavNode.ts │ │ │ ├── ISideNavNodeProps.ts │ │ │ └── ISideNavNodeState.ts │ │ │ ├── provider │ │ │ ├── ISideNavProvider.ts │ │ │ └── SideNavProvider.ts │ │ │ └── site-menu.scss │ │ └── loc │ │ ├── en-us.js │ │ └── myStrings.d.ts └── index.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # we recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | [{package,bower}.json] 24 | indent_style = space 25 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directories 7 | node_modules 8 | 9 | # Build generated files 10 | dist 11 | lib 12 | solution 13 | temp 14 | *.sppkg 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # OSX 20 | .DS_Store 21 | 22 | # Visual Studio files 23 | .ntvs_analysis.dat 24 | .vs 25 | bin 26 | obj 27 | 28 | # Resx Generated Code 29 | *.resx.ts 30 | 31 | # Styles Generated Code 32 | *.scss.ts 33 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "msjsdiag.debugger-for-chrome" 4 | ] 5 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | * Install Chrome Debugger Extension for Visual Studio Code to debug your components with the 4 | * Chrome browser: https://aka.ms/spfx-debugger-extensions 5 | */ 6 | "version": "0.2.0", 7 | "configurations": [{ 8 | "name": "Local workbench", 9 | "type": "chrome", 10 | "request": "launch", 11 | "url": "https://localhost:4321/temp/workbench.html", 12 | "webRoot": "${workspaceRoot}", 13 | "sourceMaps": true, 14 | "sourceMapPathOverrides": { 15 | "webpack:///.././src/*": "${webRoot}/src/*", 16 | "webpack:///../../../src/*": "${webRoot}/src/*", 17 | "webpack:///../../../../src/*": "${webRoot}/src/*", 18 | "webpack:///../../../../../src/*": "${webRoot}/src/*" 19 | }, 20 | "runtimeArgs": [ 21 | "--remote-debugging-port=9222" 22 | ] 23 | }, 24 | { 25 | "name": "Hosted workbench", 26 | "type": "chrome", 27 | "request": "launch", 28 | "url": "https://enter-your-SharePoint-site/_layouts/workbench.aspx", 29 | "webRoot": "${workspaceRoot}", 30 | "sourceMaps": true, 31 | "sourceMapPathOverrides": { 32 | "webpack:///.././src/*": "${webRoot}/src/*", 33 | "webpack:///../../../src/*": "${webRoot}/src/*", 34 | "webpack:///../../../../src/*": "${webRoot}/src/*", 35 | "webpack:///../../../../../src/*": "${webRoot}/src/*" 36 | }, 37 | "runtimeArgs": [ 38 | "--remote-debugging-port=9222", 39 | "-incognito" 40 | ] 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // Configure glob patterns for excluding files and folders in the file explorer. 4 | "files.exclude": { 5 | "**/.git": true, 6 | "**/.DS_Store": true, 7 | "**/bower_components": true, 8 | "**/coverage": true, 9 | "**/lib-amd": true, 10 | "src/**/*.scss.ts": true 11 | }, 12 | "typescript.tsdk": ".\\node_modules\\typescript\\lib" 13 | } -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "@microsoft/generator-sharepoint": { 3 | "isCreatingSolution": true, 4 | "environment": "onprem19", 5 | "version": "1.10.0", 6 | "libraryName": "spfx-side-navigation-v-2", 7 | "libraryId": "3564f9fb-42a8-48ca-ad0c-1c7d6c01eb72", 8 | "packageManager": "npm", 9 | "componentType": "extension", 10 | "extensionType": "ApplicationCustomizer" 11 | } 12 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Thomas Daly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project is no longer actively supported. Feel free to use this as an example but at this point I've moved on to new projects that require most of my time. 2 | 3 | # Introduction 4 | This project is an SPFx application customizer built for Modern SharePoint sites / pages. It will place itself in the header placeholder of your site and pull navigation items from the list contained within the site. It could be a global navigation if you replace provider -- check out this project [https://github.com/tom-daly/spfx-global-navigation] 5 | 6 | Rebuilt fresh on SPFx v1.4 so that it will support: 7 | 8 | Environments 9 | + Office 365 10 | + SharePoint 2019 11 | 12 | Browers 13 | + IE 11 14 | + Chrome 15 | + FireFox 16 | 17 | ## Why does this project exist? 18 | This is just an interesting example of a side navigation that would be useful on Modern Communication sites as they don't have a left navigation. Inspiration taken from the Office 365 Admin Portal Left Navigation. 19 | 20 | ## Modern Page Demonstration 21 | Designed for Modern Sites 22 | ![demo on modern](https://github.com/tom-daly/spfx-side-navigation/blob/master/images/demo.gif) 23 | 24 | # Prerequisites to Build 25 | 1. [SPFx Development Environment](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-development-environment) 26 | 2. [Tenant App Catalog](https://docs.microsoft.com/en-us/sharepoint/dev/spfx/set-up-your-developer-tenant#create-app-catalog-site) 27 | 4. Node 8.x.x or 10.x.x 28 | 29 | # Installation & Deployment 30 | The following steps assume that you've cloned this repository or downloaded the files and successfully installed all the dependencies using 'npm install'. Make sure to use the same version of node to get a successfull build. 31 | 32 | ## Overview 33 | 1. Build the SPFx Application Customizer 34 | 2. Deploy the SPFx Application Customizer 35 | 3. Add the App to your site 36 | 37 | ## Step 1 - Build the Solution 38 | It is recommended to run the 'build.cmd' file from the projects root folder. This file does all the normal SPFx build commands such as build, bundle, package-solution but it will also generate the necessary file needed for support on classic sites. The 'build.cmd' also does a number of other things out of scope for guide. Please refer to the following blogs posts for more information on this file. 39 | 40 | + [Simple Build Script for the SharePoint Framework](https://thomasdaly.net/2018/05/07/simple-build-script-for-the-sharepoint-framework/) 41 | + [SPFx Automatically Generating Revision Numbers](https://thomasdaly.net/2018/08/12/spfx-automatically-generating-revision-numbers/) 42 | + [Update: SPFx Automatically Generating Revision Numbers + Versioning](https://thomasdaly.net/2018/08/21/update-spfx-automatically-generating-revision-numbers-versioning/) 43 | 44 | ![build](https://github.com/tom-daly/spfx-side-navigation/blob/master/images/build.png) 45 | 46 | ### Modern App Build 47 | When the build script completes you will have the app package for modern sites located in './sharepoint/solution/spfx-side-navigation.sppkg' 48 | 49 | ![App Package](https://github.com/tom-daly/spfx-side-navigation/blob/master/images/package.png) 50 | 51 | ## Step 2 - Deploy the Application Customizer 52 | 53 | #### Modern Deployment 54 | Modern site deployment is straightforward. [For more information about this process see official MS Docs](https://docs.microsoft.com/en-us/sharepoint/use-app-catalog) 55 | 56 | 1. Navigate to your tenant App Catalog 57 | 2. Click Apps for SharePoint in the Quick Launch menu 58 | 3. Click and drag the .sppkg file into the tenant App Catalog 59 | 60 | ![deploy app customizer](https://i.imgur.com/il6utDR.gif) 61 | 62 | ## Step 3 - Activate the App 63 | Activation on a Modern site deployment is straightforward. [For more information about this process see official MS Docs](https://docs.microsoft.com/en-us/sharepoint/use-app-catalog) 64 | 65 | 1. Navigate to your Modern site 66 | 2. From the gear icon, click 'Add an App' 67 | 3. In the left menu, click 'From your Organization' 68 | 4. Click 'spfx-side-navigation-client-side-solution' 69 | 70 | ***In a minute or two it will be activated on that modern site*** 71 | 72 | ![update colors](https://github.com/tom-daly/spfx-side-navigation/blob/master/images/add_app.gif) 73 | 74 | # Modifications 75 | 76 | ## Updating the Styles + Changing Colors 77 | The project was written with a global sass stylesheet for Modern sites first and was then extended to support Classic sites. It should be simple enough to update the hex colors and rebuild to match your site's palette. 78 | 79 | + site-menu.scss - Contains all the styles for the menu for both Classic and Modern sites. This file contains sass variables to easily update the colors of the menus to match your environment. All the other sass should not be adjusted as it controls the function of the menu. 80 | 81 | ![update colors](https://github.com/tom-daly/spfx-side-navigation/blob/master/images/colors.png) 82 | 83 | # How To Use 84 | It's super easy to use, once activated just add items to the list. 85 | 86 | ## Side Nav List 87 | In Site Contents you'll see a new list called "Side Nav List" just add new list items. 88 | 89 | The current implementation supports ANY SVG icon as long as its one color and you can provide the list item the SVG code. This way we can recolor with CSS and it will seamless fit into the 90 | navigation. 91 | 92 | ![update colors](https://github.com/tom-daly/spfx-side-navigation/blob/master/images/new_item.gif) 93 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cls 3 | 4 | SET _build=%1 5 | 6 | IF NOT [%_build%]==[] ( 7 | SET _revision=--no-revision 8 | IF "%_build%" == "patch" goto build 9 | IF "%_build%" == "minor" goto build 10 | IF "%_build%" == "major" goto build 11 | goto unrecognized 12 | ) ELSE ( 13 | SET _build=DEV:DEBUG 14 | SET _revision= 15 | goto build 16 | ) 17 | 18 | :build 19 | echo. 20 | echo BUILDING '%_build%' PACKAGE  21 | echo. 22 | IF NOT [%1]==[] goto warning 23 | goto execute 24 | 25 | :warning 26 | echo WARNING: Your code must be checked in prior to buildings a MAJOR/MINOR/PATCH. 27 | echo Verify that you have a clean working tree before continuing. 28 | echo. 29 | 30 | set /p answer=I HAVE VERIFIED MY WORKING TREE IS CLEAN (Y/N)?  31 | 32 | if /i "%answer:~,100%" EQU "Y" goto execute 33 | if /i "%answer:~,100%" EQU "N" goto finish 34 | echo Please type Y for Yes or N for No 35 | goto warning 36 | 37 | :execute 38 | call gulp clean 39 | call gulp build --ship %_revision% 40 | call gulp bundle --ship --no-revision 41 | IF NOT [%1]==[] ( 42 | call npm version %_build% 43 | ) 44 | call gulp package-solution --ship 45 | goto openfolder 46 | 47 | :openfolder 48 | call explorer .\sharepoint\solution\ 49 | goto finish 50 | 51 | :unrecognized 52 | echo. 53 | echo Unrecognized parameters specified. 54 | echo. 55 | echo Specify no parameter for development build. 56 | echo Use 'patch', 'minor', or 'major' for production level builds. 57 | echo. 58 | 59 | :finish 60 | -------------------------------------------------------------------------------- /config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json", 3 | "version": "2.0", 4 | "bundles": { 5 | "spfx-side-navigation-application-customizer": { 6 | "components": [ 7 | { 8 | "entrypoint": "./lib/extensions/spfxSideNavigation/SpfxSideNavigationApplicationCustomizer.js", 9 | "manifest": "./src/extensions/spfxSideNavigation/SpfxSideNavigationApplicationCustomizer.manifest.json" 10 | } 11 | ] 12 | } 13 | }, 14 | "externals": {}, 15 | "localizedResources": { 16 | "SpfxSideNavigationApplicationCustomizerStrings": "lib/extensions/spfxSideNavigation/loc/{locale}.js" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /config/copy-assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json", 3 | "deployCdnPath": "temp/deploy" 4 | } 5 | -------------------------------------------------------------------------------- /config/deploy-azure-storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json", 3 | "workingDir": "./temp/deploy/", 4 | "account": "", 5 | "container": "spfx-side-navigation-v-2", 6 | "accessKey": "" 7 | } -------------------------------------------------------------------------------- /config/package-solution.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json", 3 | "solution": { 4 | "name": "spfx-side-navigation-client-side-solution", 5 | "id": "3564f9fb-42a8-48ca-ad0c-1c7d6c01eb72", 6 | "version": "1.0.0.108", 7 | "includeClientSideAssets": true, 8 | "features": [ 9 | { 10 | "title": "Application Extension - Deployment of custom action.", 11 | "description": "Deploys a custom action with ClientSideComponentId association", 12 | "id": "179819ea-ca50-4225-929e-f640aad32429", 13 | "version": "1.0.0.4", 14 | "assets": { 15 | "elementManifests": [ 16 | "elements.xml", 17 | "listElements.xml" 18 | ], 19 | "elementFiles": [ 20 | "listSchema_SideNav.xml" 21 | ] 22 | } 23 | } 24 | ] 25 | }, 26 | "paths": { 27 | "zippedPackage": "solution/spfx-side-navigation.sppkg" 28 | } 29 | } -------------------------------------------------------------------------------- /config/serve.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json", 3 | "port": 4321, 4 | "https": true, 5 | "serveConfigurations": { 6 | "default": { 7 | "pageUrl": "https://contoso.sharepoint.com/sites/mySite/SitePages/myPage.aspx", 8 | "customActions": { 9 | "b53e7bab-2332-459a-ad60-b9ae2b7d41ec": { 10 | "location": "ClientSideExtension.ApplicationCustomizer", 11 | "properties": { 12 | "testMessage": "Test message" 13 | } 14 | } 15 | } 16 | }, 17 | "spfxSideNavigation": { 18 | "pageUrl": "https://contoso.sharepoint.com/sites/mySite/SitePages/myPage.aspx", 19 | "customActions": { 20 | "b53e7bab-2332-459a-ad60-b9ae2b7d41ec": { 21 | "location": "ClientSideExtension.ApplicationCustomizer", 22 | "properties": { 23 | "testMessage": "Test message" 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /config/write-manifests.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json", 3 | "cdnBasePath": "" 4 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const build = require('@microsoft/sp-build-web'); 4 | build.addSuppression(/filename should end with module.scss$/); 5 | build.addSuppression(/error quotemark: " should be '$/); 6 | build.addSuppression(/error semicolon: Unnecessary semicolon$/); 7 | build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`); 8 | require("./spfx-versioning")(build); 9 | build.initialize(require('gulp')); 10 | -------------------------------------------------------------------------------- /images/add_app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-daly/spfx-side-navigation/4224df7b96c98a4e5494b776befaa386157f07f2/images/add_app.gif -------------------------------------------------------------------------------- /images/build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-daly/spfx-side-navigation/4224df7b96c98a4e5494b776befaa386157f07f2/images/build.png -------------------------------------------------------------------------------- /images/colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-daly/spfx-side-navigation/4224df7b96c98a4e5494b776befaa386157f07f2/images/colors.png -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-daly/spfx-side-navigation/4224df7b96c98a4e5494b776befaa386157f07f2/images/demo.gif -------------------------------------------------------------------------------- /images/new_item.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-daly/spfx-side-navigation/4224df7b96c98a4e5494b776befaa386157f07f2/images/new_item.gif -------------------------------------------------------------------------------- /images/newitem.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-daly/spfx-side-navigation/4224df7b96c98a4e5494b776befaa386157f07f2/images/newitem.gif -------------------------------------------------------------------------------- /images/package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-daly/spfx-side-navigation/4224df7b96c98a4e5494b776befaa386157f07f2/images/package.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spfx-side-navigation-v-2", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "lib/index.js", 6 | "engines": { 7 | "node": ">=0.10.0" 8 | }, 9 | "scripts": { 10 | "build": "gulp bundle", 11 | "clean": "gulp clean", 12 | "test": "gulp test", 13 | "postversion": "gulp version-sync" 14 | }, 15 | "dependencies": { 16 | "@microsoft/sp-core-library": "~1.4.0", 17 | "@microsoft/decorators": "~1.4.0", 18 | "@types/webpack-env": "1.13.1", 19 | "@types/es6-promise": "0.0.33", 20 | "@microsoft/sp-dialog": "~1.4.0", 21 | "@microsoft/sp-application-base": "~1.4.0", 22 | "@pnp/common": "1.3.3", 23 | "@pnp/graph": "1.3.3", 24 | "@pnp/logging": "1.3.3", 25 | "@pnp/odata": "1.3.3", 26 | "@pnp/sp": "1.3.3" 27 | }, 28 | "devDependencies": { 29 | "@microsoft/sp-build-web": "~1.4.1", 30 | "@microsoft/sp-module-interfaces": "~1.4.1", 31 | "@microsoft/sp-webpart-workbench": "~1.4.1", 32 | "gulp": "~3.9.1", 33 | "@types/chai": "3.4.34", 34 | "@types/mocha": "2.2.38", 35 | "ajv": "~5.2.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sharepoint/assets/ClientSideInstance.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /sharepoint/assets/elements.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /sharepoint/assets/listElements.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | -------------------------------------------------------------------------------- /sharepoint/assets/listSchema_SideNav.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 0 26 | 27 | 28 | 29 | 30 | 31 | main.xsl 32 | clienttemplates.js 33 | 30 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /spfx-versioning.js: -------------------------------------------------------------------------------- 1 | module.exports = function(build) { 2 | const gutil = require("gulp-util"); 3 | const fs = require("fs"); 4 | const gulp = require('gulp'); 5 | 6 | var getJson = function(file) { 7 | return JSON.parse(fs.readFileSync(file, "utf8")); 8 | }; 9 | 10 | let bumpRevisionSubTask = build.subTask("bump-revision-subtask", function( 11 | gulp, 12 | buildOptions, 13 | done 14 | ) { 15 | var skipBumpRevision = buildOptions.args["revision"] === false; 16 | if (!skipBumpRevision) { 17 | var pkgSolution = getJson("./config/package-solution.json"); 18 | var oldVersionNumber = String(pkgSolution.solution.version); 19 | gutil.log("Old Version: " + oldVersionNumber); 20 | var oldBuildNumber = parseInt(oldVersionNumber.split(".")[3]); 21 | gutil.log("Old Build Number: " + oldBuildNumber); 22 | var newBuildNumber = oldBuildNumber + 1; 23 | gutil.log("New Build Number: " + newBuildNumber); 24 | var newVersionNumber = 25 | oldVersionNumber.substring( 26 | 0, 27 | String(oldVersionNumber).length - String(oldBuildNumber).length 28 | ) + String(newBuildNumber); 29 | gutil.log("New Version: " + newVersionNumber); 30 | pkgSolution.solution.version = newVersionNumber; 31 | fs.writeFileSync( 32 | "./config/package-solution.json", 33 | JSON.stringify(pkgSolution, null, 4) 34 | ); 35 | } 36 | return gulp 37 | .src("./config/package-solution.json") 38 | .pipe(skipBumpRevision ? gutil.noop() : gulp.dest("./config")); 39 | }); 40 | 41 | let bumpRevisionTask = build.task("bump-revision", bumpRevisionSubTask); 42 | 43 | gulp.task("version-sync", function() { 44 | var pkgConfig = getJson("./package.json"); 45 | var pkgSolution = getJson("./config/package-solution.json"); 46 | gutil.log("Old Version:\t" + pkgSolution.solution.version); 47 | var newVersionNumber = pkgConfig.version.split("-")[0] + ".0"; 48 | pkgSolution.solution.version = newVersionNumber; 49 | gutil.log("New Version:\t" + pkgSolution.solution.version); 50 | fs.writeFileSync( 51 | "./config/package-solution.json", 52 | JSON.stringify(pkgSolution, null, 4) 53 | ); 54 | }); 55 | 56 | build.rig.addPreBuildTask(bumpRevisionTask); 57 | }; 58 | -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/SpfxSideNavigationApplicationCustomizer.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-extension-manifest.schema.json", 3 | 4 | "id": "b53e7bab-2332-459a-ad60-b9ae2b7d41ec", 5 | "alias": "SpfxSideNavigationApplicationCustomizer", 6 | "componentType": "Extension", 7 | "extensionType": "ApplicationCustomizer", 8 | 9 | // The "*" signifies that the version should be taken from the package.json 10 | "version": "*", 11 | "manifestVersion": 2, 12 | 13 | // If true, the component can only be installed on sites where Custom Script is allowed. 14 | // Components that allow authors to embed arbitrary script code should set this to true. 15 | // https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f 16 | "requiresCustomScript": false 17 | } 18 | -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/SpfxSideNavigationApplicationCustomizer.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as React from "react"; 3 | import * as ReactDom from "react-dom"; 4 | import { override } from '@microsoft/decorators'; 5 | import { 6 | BaseApplicationCustomizer, 7 | PlaceholderContent, 8 | PlaceholderName 9 | } from "@microsoft/sp-application-base"; 10 | import { setup as pnpSetup } from "@pnp/common"; 11 | import SideNav from "./components/SideNav/SideNav"; 12 | import { initializeIcons } from 'office-ui-fabric-react/lib/Icons'; 13 | 14 | // import * as strings from 'SpfxSideNavigationApplicationCustomizerStrings'; 15 | 16 | export interface ISpfxSideNavigationApplicationCustomizerProperties {} 17 | 18 | /** A Custom Action which can be run during execution of a Client Side Application */ 19 | export default class SpfxSideNavigationApplicationCustomizer 20 | extends BaseApplicationCustomizer { 21 | 22 | private topPlaceholder: PlaceholderContent | undefined; 23 | 24 | @override 25 | public onInit(): Promise { 26 | initializeIcons(); 27 | return super.onInit().then(_ => { 28 | pnpSetup({ 29 | spfxContext: this.context 30 | }); 31 | this.context.placeholderProvider.changedEvent.add( 32 | this, 33 | this.renderPlaceHolders 34 | ); 35 | }); 36 | } 37 | 38 | private renderPlaceHolders(): void { 39 | if (!this.topPlaceholder) { 40 | this.topPlaceholder = this.context.placeholderProvider.tryCreateContent( 41 | PlaceholderName.Top 42 | ); 43 | 44 | if (!this.topPlaceholder) { 45 | return; 46 | } 47 | 48 | if (this.topPlaceholder.domElement) { 49 | const element: React.ReactElement<{}> = React.createElement( 50 | SideNav, 51 | {} 52 | ); 53 | ReactDom.render(element, this.topPlaceholder.domElement); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/ISideNavProps.ts: -------------------------------------------------------------------------------- 1 | export default interface ISideNavProps {} 2 | -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/ISideNavState.ts: -------------------------------------------------------------------------------- 1 | import ISideNavItem from "./model/ISideNavItem"; 2 | 3 | export default interface ISideNavState { 4 | siteNavItems: ISideNavItem[]; 5 | isOpened: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/SideNav.module.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom-daly/spfx-side-navigation/4224df7b96c98a4e5494b776befaa386157f07f2/src/extensions/spfxSideNavigation/components/SideNav/SideNav.module.scss -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/SideNav.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { IconButton } from "office-ui-fabric-react/lib/Button"; 3 | import SideNavNode from "./SideNavNode"; 4 | import ISideNavItem from "./model/ISideNavItem"; 5 | import ISideNavProvider from "./provider/ISideNavProvider"; 6 | import SideNavProvider from "./provider/SideNavProvider"; 7 | import ISideNavProps from "./ISideNavProps"; 8 | import ISideNavState from "./ISideNavState"; 9 | 10 | export default class SideNav extends React.Component< 11 | ISideNavProps, 12 | ISideNavState 13 | > { 14 | private sideNavProvider: ISideNavProvider; 15 | 16 | constructor(props: ISideNavProps) { 17 | super(props); 18 | this.state = { 19 | siteNavItems: [], 20 | isOpened: false, 21 | }; 22 | window.addEventListener("click", this.handleOutsideClick, true); 23 | } 24 | 25 | public componentWillMount(): void { 26 | this.sideNavProvider = new SideNavProvider(); 27 | } 28 | 29 | public componentDidMount(): void { 30 | this.sideNavProvider 31 | .getSideNav() 32 | .then((result: ISideNavItem[]): void => { 33 | this.setState({ 34 | siteNavItems: result, 35 | }); 36 | }) 37 | .catch((error) => { 38 | console.log(error); 39 | }); 40 | } 41 | 42 | public render(): JSX.Element { 43 | const siteMenuClass: string = this.state.isOpened 44 | ? "site-menu opened" 45 | : "site-menu"; 46 | const toggleIconName: string = this.state.isOpened 47 | ? "DoubleChevronLeft8" 48 | : "DoubleChevronRight8"; 49 | return ( 50 | 76 | ); 77 | } 78 | 79 | private handleOutsideClick = (event: any) => { 80 | if (!this.state.isOpened) { 81 | return; 82 | } // if site nav is already closed, abort 83 | 84 | let foundSideNavPanel: boolean = false; 85 | for (let i: number = 0; i < event.path.length; i++) { 86 | const node: HTMLElement = event.path[i]; 87 | if (!node.className) { 88 | continue; 89 | } // skip if no class name 90 | if (node.className.toLowerCase().indexOf("site-menu-panel") !== -1) { 91 | foundSideNavPanel = true; 92 | break; 93 | } 94 | } 95 | 96 | if (!foundSideNavPanel) { 97 | this.toggleNav(); // if no site menu panel found, close the site menu 98 | } 99 | }; 100 | 101 | private toggleNav = (): void => { 102 | this.setState((state, props) => ({ 103 | isOpened: !state.isOpened, 104 | })); 105 | }; 106 | 107 | private renderSideNavNodes = ( 108 | siteNavItem: ISideNavItem, 109 | index: number 110 | ): JSX.Element => { 111 | return ( 112 | 117 | ); 118 | }; 119 | } 120 | -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/SideNavNode.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import ISideNavNodeProps from "./model/ISideNavNodeProps"; 4 | import ISideNavNodeState from "./model/ISideNavNodeState"; 5 | import ISideNavItem from "./model/ISideNavItem"; 6 | import { IconButton } from "office-ui-fabric-react/lib/Button"; 7 | require("./site-menu.scss"); 8 | 9 | export default class SideNavNode extends React.Component< 10 | ISideNavNodeProps, 11 | ISideNavNodeState 12 | > { 13 | constructor(props: ISideNavNodeProps) { 14 | super(props); 15 | this.state = { 16 | isOpened: false 17 | }; 18 | } 19 | 20 | public render(): JSX.Element { 21 | const nodeClasses: string[] = ["site-nav-node"]; 22 | const url = (window.location.href.split('?') ? window.location.href.split('?')[0] : window.location.href).toLowerCase(); 23 | if(this.props.siteNavItem.url && this.props.siteNavItem.url.toLowerCase() === url) { 24 | nodeClasses.push("active"); 25 | } 26 | if (this.state.isOpened && this.props.navIsOpened) { 27 | nodeClasses.push("opened"); 28 | } 29 | if (this.props.siteNavItem.subNavItems) { 30 | nodeClasses.push("dropdown"); 31 | } 32 | return ( 33 |
34 |
this.nodeClick(e)}> 35 | {(this.props.siteNavItem.svg && ( 36 |
37 |
42 | {this.props.navIsOpened && ( 43 |
44 |
45 | {this.props.siteNavItem.title} 46 |
47 | {(this.props.siteNavItem.subNavItems && 48 | !this.state.isOpened && ( 49 | 56 | )) || 57 | (this.props.siteNavItem.subNavItems && 58 | this.state.isOpened && ( 59 | 66 | ))} 67 |
68 | )} 69 |
70 | )) || 71 | (!this.props.siteNavItem.svg && ( 72 |
73 |
74 |
75 | {this.props.siteNavItem.title} 76 |
77 |
78 |
79 | ))} 80 |
81 | {(this.props.siteNavItem.subNavItems && 82 | !this.props.navIsOpened && ( 83 |
84 | {this.props.siteNavItem.subNavItems.map(this.renderSubNavItems)} 85 |
86 | )) || 87 | (this.props.siteNavItem.subNavItems && 88 | this.props.navIsOpened && 89 | this.state.isOpened && ( 90 |
91 | {this.props.siteNavItem.subNavItems.map(this.renderSubNavItems)} 92 |
93 | ))} 94 |
95 | ); 96 | } 97 | 98 | private check(): void { 99 | const node: Element = ReactDOM.findDOMNode(this.refs.children) as Element; 100 | if (!node) { 101 | return; 102 | } 103 | 104 | const rect: ClientRect = node.getBoundingClientRect(); 105 | const space: number = window.innerHeight - (rect.top + rect.height); 106 | if (space < 0) { 107 | // it's off screen 108 | const heightStyle: string = 109 | "height: " + String(node.clientHeight + space) + "px;"; 110 | const overflowStyle: string = "overflow-y: auto; -webkit-overflow-scrolling: touch;"; 111 | node.setAttribute("style", heightStyle + overflowStyle); 112 | } else { 113 | node.setAttribute("style", "height: auto;"); 114 | } 115 | } 116 | 117 | private nodeClick(e: React.MouseEvent): void { 118 | if (!this.props.siteNavItem) { 119 | return; 120 | } 121 | if (this.props.siteNavItem.url) { 122 | /* if has a url navigate to that address */ 123 | if (this.props.siteNavItem.openInNewWindow) { 124 | window.open(this.props.siteNavItem.url, "_blank"); 125 | } else { 126 | window.location.href = this.props.siteNavItem.url; 127 | } 128 | return; 129 | } 130 | if (!this.props.siteNavItem.url) { 131 | /* if no url then change the state */ 132 | 133 | if (this.props.navIsOpened) { 134 | /* only change state if the navigation is opened */ 135 | this.setState( 136 | { 137 | isOpened: !this.state.isOpened 138 | }, 139 | () => this.check() 140 | ); 141 | } 142 | return; 143 | } 144 | } 145 | 146 | private renderSubNavItems = ( 147 | siteNavItem: ISideNavItem, 148 | index: number 149 | ): JSX.Element => { 150 | return ( 151 | 156 | ); 157 | }; 158 | } 159 | -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/model/ISPSideNavItem.ts: -------------------------------------------------------------------------------- 1 | export default interface ISPSideNavItem { 2 | Title: string; 3 | SideNavIconSvg?: string; 4 | SideNavUrl?: string; 5 | SideNavOpenInNewWindow?: boolean; 6 | SideNavParent?: { 7 | Title: string; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/model/ISideNavItem.ts: -------------------------------------------------------------------------------- 1 | export default interface ISideNavItem { 2 | title: string; 3 | url?: string; 4 | image?: string; 5 | svg?: string; 6 | openInNewWindow?: boolean; 7 | subNavItems?: ISideNavItem[]; 8 | } -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/model/ISideNavNode.ts: -------------------------------------------------------------------------------- 1 | import ISidenavItem from "./ISideNavItem"; 2 | 3 | export default interface ISideNavNode { 4 | key: number; 5 | siteNavItem: ISidenavItem; 6 | navIsOpened: boolean; 7 | } -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/model/ISideNavNodeProps.ts: -------------------------------------------------------------------------------- 1 | import ISideNavNode from "./ISideNavNode"; 2 | 3 | export default interface ISideNavNodeProps extends ISideNavNode { 4 | } 5 | -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/model/ISideNavNodeState.ts: -------------------------------------------------------------------------------- 1 | export default interface ISideNavNodeState { 2 | isOpened: boolean; 3 | } -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/provider/ISideNavProvider.ts: -------------------------------------------------------------------------------- 1 | import ISideNavItem from "../model/ISideNavItem"; 2 | 3 | export default interface ISideNavProvider { 4 | getSideNav(): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/provider/SideNavProvider.ts: -------------------------------------------------------------------------------- 1 | import ISideNavProvider from "./ISideNavProvider"; 2 | import ISideNavItem from "../model/ISideNavItem"; 3 | import ISPSideNavItem from "../model/ISPSideNavItem"; 4 | import { sp } from "@pnp/sp"; 5 | 6 | export default class SideNavProvider implements ISideNavProvider { 7 | public getSideNav(): Promise { 8 | return sp.web.lists 9 | .getByTitle("Side Nav List") 10 | .items.select( 11 | "Title", 12 | "SideNavUrl", 13 | "SideNavIconSvg", 14 | "SideNavOpenInNewWindow", 15 | "SideNavParent/Title" 16 | ) 17 | .expand("SideNavParent") 18 | .orderBy("SideNavOrder") 19 | .usingCaching() 20 | .get() 21 | .then( 22 | (items: ISPSideNavItem[]): ISideNavItem[] => { 23 | const siteNavItems: ISideNavItem[] = []; 24 | items.forEach( 25 | (item: ISPSideNavItem): void => { 26 | if (!item.SideNavParent) { 27 | siteNavItems.push({ 28 | title: item.Title, 29 | svg: item.SideNavIconSvg, 30 | url: item.SideNavUrl, 31 | openInNewWindow: item.SideNavOpenInNewWindow, 32 | subNavItems: this.getSubNavItems(items, item.Title) 33 | }); 34 | } 35 | } 36 | ); 37 | return siteNavItems; 38 | } 39 | ); 40 | } 41 | 42 | private getSubNavItems( 43 | spNavItems: ISPSideNavItem[], 44 | filter: string 45 | ): ISideNavItem[] { 46 | const subNavItems: ISideNavItem[] = []; 47 | spNavItems.forEach( 48 | (item: ISPSideNavItem): void => { 49 | if (item.SideNavParent && item.SideNavParent.Title === filter) { 50 | subNavItems.push({ 51 | title: item.Title, 52 | url: item.SideNavUrl, 53 | openInNewWindow: item.SideNavOpenInNewWindow 54 | }); 55 | } 56 | } 57 | ); 58 | return subNavItems.length > 0 ? subNavItems : undefined; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/components/SideNav/site-menu.scss: -------------------------------------------------------------------------------- 1 | $panelWidth: 48px; 2 | $panelWidthOpened: 260px; 3 | 4 | $white: #fff; 5 | $orange: #e67e22; 6 | 7 | $sideNavBarBgColor: #2c3e50; 8 | $navNodeTextIconColor: $white; 9 | $navNodeTextIconHoverColor: $white; 10 | $navNodeTextIconHoverBgColor: #e74c3c; 11 | 12 | /* break points borrowed from Office UI Fabric */ 13 | $ms-screen-min-sm: 320px !default; 14 | $ms-screen-min-md: 480px !default; 15 | $ms-screen-min-lg: 640px !default; 16 | $ms-screen-max-sm: ($ms-screen-min-md - 1); 17 | $ms-screen-max-md: ($ms-screen-min-lg - 1); 18 | 19 | @mixin Desktop { 20 | @media only screen and (min-width: $ms-screen-min-md) { 21 | @content; 22 | } 23 | } 24 | 25 | #spPageChromeAppDiv, 26 | .sp-App-body { 27 | //margin-left: $panelWidth; 28 | } 29 | 30 | .visible-i { 31 | visibility: visible !important; 32 | } 33 | 34 | .ql-menu { 35 | cursor: pointer; 36 | } 37 | 38 | @mixin SiteNavNode { 39 | color: $navNodeTextIconColor; 40 | font-weight: bold; 41 | font-size: 16px; 42 | } 43 | 44 | .site-nav-node { 45 | position: relative; 46 | background-color: $sideNavBarBgColor; 47 | cursor: pointer; 48 | &.dropdown { 49 | > div:not(.dynamic-children) { 50 | .title { 51 | width: 170px; 52 | margin-right: 30px; 53 | } 54 | button { 55 | border: 0; 56 | background-color: transparent; 57 | position: absolute; 58 | right: 10px; 59 | } 60 | } 61 | } 62 | &.active, 63 | &:hover, 64 | &.opened { 65 | .icon-node { 66 | background-color: $navNodeTextIconHoverBgColor; 67 | .icon svg { 68 | fill: $navNodeTextIconHoverColor; 69 | } 70 | .title, 71 | i { 72 | color: $navNodeTextIconHoverColor; 73 | } 74 | button { 75 | background-color: transparent; 76 | } 77 | } 78 | } 79 | .icon-node { 80 | div { 81 | display: inline-block; 82 | vertical-align: middle; 83 | } 84 | .title { 85 | @include SiteNavNode(); 86 | } 87 | i { 88 | color: $navNodeTextIconColor; 89 | } 90 | .icon { 91 | width: $panelWidth; 92 | height: $panelWidth; 93 | padding: 12px; 94 | -webkit-box-sizing: border-box; 95 | -moz-box-sizing: border-box; 96 | box-sizing: border-box; 97 | svg { 98 | max-width: $panelWidth; 99 | width: 100%; 100 | height: auto; 101 | fill: $navNodeTextIconColor; 102 | } 103 | } 104 | button { 105 | margin: 0; 106 | padding: 0; 107 | height: auto; 108 | width: auto; 109 | button { 110 | margin: 0; 111 | padding: 0; 112 | height: auto; 113 | width: auto; 114 | } 115 | [data-icon-name="ChevronDownSmall"], 116 | [data-icon-name="ChevronUpSmall"] { 117 | position: relative; 118 | top: -2px; 119 | } 120 | } 121 | } 122 | .dynamic-children { 123 | background-color: $orange; 124 | .site-nav-node { 125 | .title-node { 126 | padding: 10px; 127 | color: $navNodeTextIconColor; 128 | } 129 | &.active, 130 | &:hover { 131 | background-color: $navNodeTextIconHoverBgColor; 132 | .title-node { 133 | color: $navNodeTextIconHoverColor; 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | .site-menu-panel { 141 | position: fixed; 142 | left: 0; 143 | top: 0; 144 | height: 100vh; 145 | z-index: 999; 146 | background-color: $sideNavBarBgColor; 147 | margin-top: 48px; 148 | > div:first-child { 149 | background-color: $white; 150 | } 151 | 152 | .site-menu-icon { 153 | width: $panelWidth; 154 | height: $panelWidth; 155 | color: $navNodeTextIconColor; 156 | background-color: $sideNavBarBgColor; 157 | margin: 0 auto; 158 | &:hover, 159 | &.active { 160 | background-color: $navNodeTextIconHoverBgColor; 161 | color: $navNodeTextIconHoverColor; 162 | } 163 | } 164 | 165 | .site-menu { 166 | button { 167 | border: 0; 168 | } 169 | > .site-nav-node { 170 | cursor: pointer; 171 | } 172 | .dynamic-children { 173 | display: none; 174 | } 175 | &:not(.opened) { 176 | width: $panelWidth; 177 | .site-nav-node:hover .dynamic-children.flyouts { 178 | display: block; 179 | position: absolute; 180 | top: 0; 181 | left: $panelWidth; 182 | width: $panelWidthOpened; 183 | } 184 | } 185 | &.opened { 186 | width: $panelWidthOpened; 187 | .menu-toggle { 188 | margin-left: $panelWidthOpened - $panelWidth; 189 | } 190 | .site-nav-node .dynamic-children { 191 | display: block; 192 | .site-nav-node { 193 | .title-node { 194 | padding-left: $panelWidth; 195 | } 196 | } 197 | } 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/loc/en-us.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | return { 3 | "Title": "SpfxSideNavigationApplicationCustomizer" 4 | } 5 | }); -------------------------------------------------------------------------------- /src/extensions/spfxSideNavigation/loc/myStrings.d.ts: -------------------------------------------------------------------------------- 1 | declare interface ISpfxSideNavigationApplicationCustomizerStrings { 2 | Title: string; 3 | } 4 | 5 | declare module 'SpfxSideNavigationApplicationCustomizerStrings' { 6 | const strings: ISpfxSideNavigationApplicationCustomizerStrings; 7 | export = strings; 8 | } 9 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // A file is required to be in the root of the /src directory by the TypeScript compiler 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "forceConsistentCasingInFileNames": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "jsx": "react", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "experimentalDecorators": true, 11 | "skipLibCheck": true, 12 | "typeRoots": [ 13 | "./node_modules/@types", 14 | "./node_modules/@microsoft" 15 | ], 16 | "types": [ 17 | "es6-promise", 18 | "webpack-env" 19 | ], 20 | "lib": [ 21 | "es5", 22 | "dom", 23 | "es2015.collection" 24 | ] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@microsoft/sp-tslint-rules/base-tslint.json", 3 | "rules": { 4 | "class-name": false, 5 | "export-name": false, 6 | "forin": false, 7 | "label-position": false, 8 | "member-access": true, 9 | "no-arg": false, 10 | "no-console": false, 11 | "no-construct": false, 12 | "no-duplicate-variable": true, 13 | "no-eval": false, 14 | "no-function-expression": true, 15 | "no-internal-module": true, 16 | "no-shadowed-variable": true, 17 | "no-switch-case-fall-through": true, 18 | "no-unnecessary-semicolons": true, 19 | "no-unused-expression": true, 20 | "no-use-before-declare": true, 21 | "no-with-statement": true, 22 | "semicolon": true, 23 | "trailing-comma": false, 24 | "typedef": false, 25 | "typedef-whitespace": false, 26 | "use-named-parameter": true, 27 | "variable-name": false, 28 | "whitespace": false 29 | } 30 | } --------------------------------------------------------------------------------