├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── TeamsToDoAppConnector.sln └── TeamsToDoAppConnector ├── App_Start ├── BundleConfig.cs ├── FilterConfig.cs ├── RouteConfig.cs └── WebApiConfig.cs ├── ApplicationInsights.config ├── Content ├── Site.css ├── bootstrap.css └── bootstrap.min.css ├── Controllers ├── AuthenticationController.cs ├── ConnectorController.cs └── TaskController.cs ├── Global.asax ├── Global.asax.cs ├── Models ├── Request.cs ├── Subscription.cs ├── ToDoItem.cs └── WebhookDetails.cs ├── Properties └── AssemblyInfo.cs ├── Repository ├── SubscriptionRepository.cs └── TaskRepository.cs ├── Scripts ├── bootstrap.js ├── bootstrap.min.js ├── jquery-1.10.2.intellisense.js ├── jquery-1.10.2.js ├── jquery-1.10.2.min.js ├── jquery-1.10.2.min.map ├── modernizr-2.6.2.js ├── respond.js └── respond.min.js ├── TeamsAppPackages ├── color_icon.png ├── manifest-CShar.zip ├── manifest.json └── outline_icon.png ├── TeamsToDoAppConnector.csproj ├── Utils ├── AppSettings.cs └── TaskHelper.cs ├── Views ├── Authentication │ ├── SimpleEnd.cshtml │ └── SimpleStart.cshtml ├── Connector │ ├── Error.cshtml │ ├── MainSetup.cshtml │ ├── Setup.cshtml │ └── SetupAuth.cshtml ├── Shared │ ├── Error.cshtml │ └── _Layout.cshtml ├── Task │ ├── Create.cshtml │ ├── Detail.cshtml │ └── Index.cshtml ├── Web.config └── _ViewStart.cshtml ├── Web.Debug.config ├── Web.Release.config ├── Web.config ├── favicon.ico ├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf └── glyphicons-halflings-regular.woff └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 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 | --- 2 | page_type: sample 3 | products: 4 | - office-teams 5 | - office-365 6 | languages: 7 | - csharp 8 | extensions: 9 | contentType: samples 10 | technologies: 11 | - Connectors 12 | createdDate: 10/10/2020 10:21:42 PM 13 | --- 14 | 15 | Note: This repo is archieved and move to this new location: https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/connector-todo-notification/csharp 16 | 17 | # Microsoft Teams Sample Connector in .NET/C# 18 | 19 | This is an MVC sample task management application generated using the [ASP.NET Web Application (.NET Framework)](https://docs.microsoft.com/aspnet/mvc/overview/getting-started/introduction/getting-started#creating-your-first-application) template. The majority of the code is related to either basic MVC configuration or Task management. 20 | 21 | The main connector code is found here: 22 | * ConnectorController.cs - `Setup` & `Save` actions 23 | * TaskController.cs - `Create` & `Update` actions 24 | 25 | This application simulates a real task management system and allows users to create and view tasks. The content is randomly generated to simulate how notification can be sent into Microsoft Teams channel using connector. 26 | 27 | **For more information on developing apps for Microsoft Teams, please review the Microsoft Teams [developer documentation](https://docs.microsoft.com/microsoftteams/platform/overview).** 28 | n 29 | ## Prerequisites 30 | The minimum prerequisites to run this sample are: 31 | * The latest update of Visual Studio. You can download the community version [here](http://www.visualstudio.com) for free. 32 | * An Office 365 account with access to Microsoft Teams, with [sideloading enabled](https://msdn.microsoft.com/en-us/microsoft-teams/setup). 33 | * If you want to run this code locally, use a tunnelling service. These instructions assume you are using [ngrok](https://ngrok.com/). 34 | 35 | ### How to see the connector working in Microsoft Teams 36 | 1) [Upload your custom app in Microsoft Teams](https://docs.microsoft.com/microsoftteams/platform/concepts/apps/apps-upload) using [this manifest file](TeamsToDoAppConnector/TeamsAppPackages/manifest.json). 37 | 2) Configure the [TeamsToDoAppConnector](https://docs.microsoft.com/microsoftteams/platform/concepts/connectors#accessing-office-365-connectors-from-microsoft-teams) connector. 38 | 3) Select either Create or Update on the registration page and click Save. 39 | 4) Once the connector is configured, you will get a notification in channel with link to the Task Manager application. 40 | 5) Go to Task Manager portal and click on Create New and enter the task details and Save. 41 | 6) You will see the MessageCard in the registered Teams channel. 42 | 7) You can try the actionable buttons available on the message card. 43 | 44 | >**Note**: With the above instructions, you can use sample connector which is deployed on Azure. Please follow the instructions below to create your own connector. 45 | 46 | ### [Configure your own connector](https://docs.microsoft.com/microsoftteams/platform/webhooks-and-connectors/how-to/connectors-creating) 47 | The sample shows a simple implementation of a connector registration implementation. It also sends a connector card to the registered connector via a process triggered "externally." 48 | 49 | 1. Open the TeamsToDoAppConnectorAuthentication.sln solution with Visual Studio. 50 | 1. Begin your tunnelling service to get an https endpoint. 51 | 1. Open a new command prompt window. 52 | 1. Change to the directory that contains the ngrok.exe application. 53 | 1. In the command prompt, run the command `ngrok http 3978 --host-header=localhost`. 54 | 1. Ngrok will fill the entire prompt window. Make note of the https:// Forwarding URL. This URL will be your [BASE_URI] referenced below. 55 | 1. Minimize the ngrok Command Prompt window. It is no longer referenced in these instructions, but it must remain running. 56 | 1. Register a new connector in the [Connector Developer Portal](https://outlook.office.com/connectors/home/login/#/new) 57 | 1. Fill in all the basic details such as name, logo, descriptions etc. for the new connector. 58 | 1. For the configuration page, you'll use our sample code's setup endpoint: `https://[BASE_URI]/connector/MainSetup` 59 | 1. For Valid domains, make entery of your domain's https URL, e.g. XXXXXXXX.ngrok.io. 60 | 1. Enable the action on connector card by selecting the Yes radio button and enter the update endpoint: `https://[BASE_URI]/Task/Update` 61 | 1. Click on Save. After the save completes, you will see your connector id. 62 | 1. In the Web.config file, set the `configuration.appSettings.Base_Uri` variable to the ngrok https forwarding url from the above. 63 | 1. In Visual Studio, press the green arrow (Start button) on the main Visual Studio toolbar, or press F5 or Ctrl+F5 to run the program. 64 | 1. Now you can sideload your app package and test your new connector. 65 | 66 | ## Setting up Authentication on Configuration page 67 | 68 | Above steps will let you configure connector without authentication. Please follow these steps to enable authentication in your connector config page. 69 | To be able to use an identity provider, first you have to register your application. 70 | 71 | 1. Using Azure AD 72 | 1. Go to the [Application Registration Portal](https://aka.ms/appregistrations) and sign in with the your account to create an application. 73 | 1. Navigate to **Authentication** under **Manage** and add the following redirect URLs: 74 | 75 | `https:///Authentication/SimpleEnd` 76 | 77 | 1. Additionally, under the **Implicit grant** subsection select **Access tokens** and **ID tokens** 78 | 79 | 1. Setting up Authentication on Configuration page 80 | 81 | 1. Set the ClientAppId in web.config, for example : https://contoso.ngrok.io 82 | 83 | ``` 84 | 85 | ``` 86 | 87 | 1. Update your Microsoft Teams application manifest 88 | 89 | 1. Add your ngrok URL to validDomains. Teams will only show the sign-in popup if its from a whitelisted domain. 90 | 91 | ```json 92 | "validDomains": [ 93 | "<>", 94 | ], 95 | ``` 96 | 97 | App structure 98 | ============= 99 | 100 | ### Routes 101 | 1. `/Connector/MainSetup` renders the connector config UI. 102 | - This is the landing page for connector configuration. The purpose of this view is primarily to allow user to navigate to either Simple setup / Setup with auth page. 103 | 1. `/Connector/Setup` renders the connector config UI. 104 | - This is simple connector configuration page which allows configuration without login. 105 | 1. `/Connector/SetupAuth` renders the connector config UI with auth. 106 | - This is connector configuration page which shows how to restrict configuration to authenticate users. 107 | 1. `/Authenication/SimpleStart` and `/Authenication/SimpleEnd` routes are used to grant the permissions. This experience happens in a separate window. 108 | - The Authenication/SimpleStart view merely creates a valid AAD authorization endpoint and redirects to that AAD consent page. 109 | - Once the user has consented to the permissions, AAD redirects the user back to `Authenication/SimpleEnd`. This view is responsible for returning the results back to the start view by calling the notifySuccess API. 110 | - This workflow is only necessary if you want authorization to use additional Graph APIs. Most apps will find this flow unnecessary if all they want to do is authenticate the user. 111 | - This workflow is the same as our standard [web-based authentication flow](https://docs.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/authentication/auth-tab-aad#navigate-to-the-authorization-page-from-your-popup-page) that we've always had in Teams before we had single sign-on support. It just so happens that it's a great way to request additional permissions from the user, so it's left in this sample as an illustration of what that flow looks like. 112 | 1. `/Tasks` controller. 113 | 1. `/Tasks/Index` renders the tasks list page. 114 | 1. `/Tasks/Create` allows users to create new task, which triggers notification to all the webhooks registered for create event. 115 | 1. `/Tasks/Update` allows users to update new task, which triggers notification to all the webhooks registered for update event. 116 | 117 | ## More Information 118 | For more information about getting started with Teams, please review the following resources: 119 | - Review [Getting Started with Authentications for Tabs](https://docs.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/authentication/auth-tab-aad) 120 | - Review [Getting Started with Teams](https://msdn.microsoft.com/en-us/microsoft-teams/setup) 121 | 122 | 123 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsToDoAppConnector", "TeamsToDoAppConnector\TeamsToDoAppConnector.csproj", "{DB064390-84D7-40E9-9389-F098ECC16AA4}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {DB064390-84D7-40E9-9389-F098ECC16AA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {DB064390-84D7-40E9-9389-F098ECC16AA4}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {DB064390-84D7-40E9-9389-F098ECC16AA4}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {DB064390-84D7-40E9-9389-F098ECC16AA4}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {87849E6D-2D54-4222-AC91-19357ABCD8F9} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/App_Start/BundleConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Optimization; 3 | 4 | namespace TeamsToDoAppConnector 5 | { 6 | public class BundleConfig 7 | { 8 | // For more information on bundling, visit https://go.microsoft.com/fwlink/?LinkId=301862 9 | public static void RegisterBundles(BundleCollection bundles) 10 | { 11 | bundles.Add(new ScriptBundle("~/bundles/jquery").Include( 12 | "~/Scripts/jquery-{version}.js")); 13 | 14 | // Use the development version of Modernizr to develop with and learn from. Then, when you're 15 | // ready for production, use the build tool at https://modernizr.com to pick only the tests you need. 16 | bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( 17 | "~/Scripts/modernizr-*")); 18 | 19 | bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( 20 | "~/Scripts/bootstrap.js", 21 | "~/Scripts/respond.js")); 22 | 23 | bundles.Add(new StyleBundle("~/Content/css").Include( 24 | "~/Content/bootstrap.css", 25 | "~/Content/site.css")); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/App_Start/FilterConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Mvc; 3 | 4 | namespace TeamsToDoAppConnector 5 | { 6 | public class FilterConfig 7 | { 8 | public static void RegisterGlobalFilters(GlobalFilterCollection filters) 9 | { 10 | filters.Add(new HandleErrorAttribute()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/App_Start/RouteConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using System.Web.Routing; 7 | 8 | namespace TeamsToDoAppConnector 9 | { 10 | public class RouteConfig 11 | { 12 | public static void RegisterRoutes(RouteCollection routes) 13 | { 14 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 15 | 16 | routes.MapRoute( 17 | name: "Default", 18 | url: "{controller}/{action}/{id}", 19 | defaults: new { controller = "Task", action = "Index", id = UrlParameter.Optional } 20 | ); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/App_Start/WebApiConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web.Http; 5 | 6 | namespace TeamsToDoAppConnector 7 | { 8 | public static class WebApiConfig 9 | { 10 | public static void Register(HttpConfiguration config) 11 | { 12 | // Web API configuration and services 13 | 14 | // Web API routes 15 | config.MapHttpAttributeRoutes(); 16 | 17 | config.Routes.MapHttpRoute( 18 | name: "DefaultApi", 19 | routeTemplate: "api/{controller}/{id}", 20 | defaults: new { id = RouteParameter.Optional } 21 | ); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/ApplicationInsights.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | search|spider|crawl|Bot|Monitor|AlwaysOn 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 54 | System.Web.Handlers.TransferRequestHandler 55 | Microsoft.VisualStudio.Web.PageInspector.Runtime.Tracing.RequestDataHttpHandler 56 | System.Web.StaticFileHandler 57 | System.Web.Handlers.AssemblyResourceLoader 58 | System.Web.Optimization.BundleHandler 59 | System.Web.Script.Services.ScriptHandlerFactory 60 | System.Web.Handlers.TraceHandler 61 | System.Web.Services.Discovery.DiscoveryRequestHandler 62 | System.Web.HttpDebugHandler 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 5 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Content/Site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Set padding to keep content from hitting the edges */ 7 | .body-content { 8 | padding-left: 15px; 9 | padding-right: 15px; 10 | } 11 | 12 | /* Set width on the form input elements since they're 100% wide by default */ 13 | input, 14 | select, 15 | textarea { 16 | max-width: 280px; 17 | } 18 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Controllers/AuthenticationController.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | 3 | namespace TeamsToDoAppConnector.Controllers 4 | { 5 | /// 6 | /// Represents the controller responsible for start and end page of authentication. 7 | /// 8 | public class AuthenticationController : Controller 9 | { 10 | [HttpGet] 11 | public ActionResult SimpleStart() 12 | { 13 | return View(); 14 | } 15 | 16 | [HttpGet] 17 | public ActionResult SimpleEnd() 18 | { 19 | return View(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Controllers/ConnectorController.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using System.Web.Mvc; 4 | using TeamsToDoAppConnector.Models; 5 | using TeamsToDoAppConnector.Repository; 6 | using TeamsToDoAppConnector.Utils; 7 | 8 | namespace TeamsToDoAppConnector.Controllers 9 | { 10 | /// 11 | /// Represents the controller responsible for setting up the connector. 12 | /// 13 | public class ConnectorController : Controller 14 | { 15 | 16 | /// 17 | /// This is main landing page which allows user to choose between simple config vs auth config page. 18 | /// 19 | /// 20 | [HttpGet] 21 | public ViewResult MainSetup() 22 | { 23 | return View(); 24 | } 25 | 26 | /// 27 | /// This is the landing page which shows simple configuration without auth. Check SetupAuth() for login implementation. 28 | /// 29 | public ViewResult Setup() 30 | { 31 | return View(); 32 | } 33 | 34 | /// 35 | /// This is the landing page shows implementation of login. 36 | /// 37 | [HttpGet] 38 | public ViewResult SetupAuth() 39 | { 40 | return View(); 41 | } 42 | 43 | /// 44 | /// This enpoint is called when we need to save the webhook details. 45 | /// This contains Webhook Url and event type which can be used to push change notifications to the channel. 46 | /// 47 | /// 48 | public async Task Save(WebhookDetails webhookInfo) 49 | { 50 | if (webhookInfo == null || webhookInfo.WebhookUrl == null) 51 | { 52 | return RedirectToAction("Error"); // You could pass error message to Error Action. 53 | } 54 | else 55 | { 56 | var subscription = SubscriptionRepository.Subscriptions.Where(sub => sub.WebHookUri == webhookInfo.WebhookUrl).FirstOrDefault(); 57 | if (subscription == null) 58 | { 59 | Subscription newSubscription = new Subscription 60 | { 61 | WebHookUri = webhookInfo.WebhookUrl, 62 | EventType = webhookInfo.EventType 63 | }; 64 | 65 | // Save the subscription so that it can be used to push data to the registered channels. 66 | SubscriptionRepository.Subscriptions.Add(newSubscription); 67 | } 68 | else 69 | { 70 | // Update existing 71 | subscription.EventType = webhookInfo.EventType; 72 | } 73 | 74 | await TaskHelper.PostWelcomeMessage(webhookInfo.WebhookUrl); 75 | 76 | return View(); 77 | } 78 | } 79 | 80 | // Error page 81 | public ActionResult Error() 82 | { 83 | return View(); 84 | } 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Controllers/TaskController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using System.Web.Mvc; 5 | using TeamsToDoAppConnector.Models; 6 | using TeamsToDoAppConnector.Repository; 7 | using TeamsToDoAppConnector.Utils; 8 | 9 | namespace TeamsToDoAppConnector.Controllers 10 | { 11 | /// 12 | /// Represents the controller which handles tasks create, update. 13 | /// This class also sends push notification to the channels. 14 | /// 15 | public class TaskController : Controller 16 | { 17 | 18 | [Route("task/index")] 19 | [HttpGet] 20 | public ActionResult Index() 21 | { 22 | return View(TaskRepository.Tasks); 23 | } 24 | 25 | [Route("task/create")] 26 | [HttpGet] 27 | public ActionResult Create() 28 | { 29 | return View(); 30 | } 31 | 32 | [Route("task/create")] 33 | [HttpPost] 34 | public async Task Create(TaskItem item) 35 | { 36 | item.Guid = Guid.NewGuid().ToString(); 37 | TaskRepository.Tasks.Add(item); 38 | 39 | // Loop through subscriptions and notify each channel that task is created. 40 | foreach (var sub in SubscriptionRepository.Subscriptions) 41 | { 42 | await TaskHelper.PostTaskNotification(sub.WebHookUri, item, "Created"); 43 | } 44 | 45 | return RedirectToAction("Detail", new { id = item.Guid }); 46 | } 47 | 48 | [Route("task/detail/{id}")] 49 | [HttpGet] 50 | public ActionResult Detail(string id) 51 | { 52 | return View(TaskRepository.Tasks.FirstOrDefault(i => i.Guid == id)); 53 | } 54 | 55 | [Route("task/update")] 56 | [HttpPost] 57 | public async Task Update([System.Web.Http.FromBody]Request request, string id) 58 | { 59 | var task = TaskRepository.Tasks.First(t => t.Guid == id); 60 | task.Title = request.Title; 61 | 62 | string json = TaskHelper.GetConnectorCardJson(task, "Updated"); 63 | 64 | Response.Clear(); 65 | Response.ContentType = "application/json; charset=utf-8"; 66 | Response.Headers.Add("CARD-ACTION-STATUS", "The task is uppdate."); 67 | Response.Headers.Add("CARD-UPDATE-IN-BODY", "true"); 68 | Response.Write(json); 69 | Response.End(); 70 | 71 | // Send Task updated notification to all Subscriptions. 72 | foreach (var sub in SubscriptionRepository.Subscriptions.Where(s => s.EventType == EventType.Update)) 73 | { 74 | await TaskHelper.PostTaskNotification(sub.WebHookUri, task, "Updated"); 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="TeamsToDoAppConnector.WebApiApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Http; 6 | using System.Web.Mvc; 7 | using System.Web.Optimization; 8 | using System.Web.Routing; 9 | 10 | namespace TeamsToDoAppConnector 11 | { 12 | public class WebApiApplication : System.Web.HttpApplication 13 | { 14 | protected void Application_Start() 15 | { 16 | GlobalConfiguration.Configure(WebApiConfig.Register); 17 | FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 18 | RouteConfig.RegisterRoutes(RouteTable.Routes); 19 | BundleConfig.RegisterBundles(BundleTable.Bundles); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Models/Request.cs: -------------------------------------------------------------------------------- 1 | namespace TeamsToDoAppConnector.Models 2 | { 3 | /// 4 | /// Represents the model to capture title information from webrequest body. 5 | /// 6 | public class Request 7 | { 8 | public string Title { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Models/Subscription.cs: -------------------------------------------------------------------------------- 1 | namespace TeamsToDoAppConnector.Models 2 | { 3 | /// 4 | /// Represents the model to store channel subscriptions. 5 | /// 6 | public class Subscription 7 | { 8 | public string WebHookUri { get; set; } 9 | public EventType EventType { get; set; } 10 | } 11 | 12 | /// 13 | /// Represents the event type which user is interested in getting notification for. 14 | /// 15 | public enum EventType 16 | { 17 | Create, 18 | Update 19 | } 20 | } -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Models/ToDoItem.cs: -------------------------------------------------------------------------------- 1 | namespace TeamsToDoAppConnector.Models 2 | { 3 | public class TaskItem 4 | { 5 | public string Title { get; set; } 6 | public string Description { get; set; } 7 | public string Assigned { get; set; } 8 | public string Guid { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Models/WebhookDetails.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Web; 7 | 8 | namespace TeamsToDoAppConnector.Models 9 | { 10 | public class WebhookDetails 11 | { 12 | [JsonProperty("webhookUrl")] 13 | public string WebhookUrl { get; set; } 14 | 15 | [JsonProperty("eventType")] 16 | [JsonConverter(typeof(StringEnumConverter))] 17 | public EventType EventType { get; set; } 18 | 19 | } 20 | } -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("TeamsToDoAppConnector")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("TeamsToDoAppConnector")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("2722d3d4-c695-4064-8df8-90afd6ef911e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Repository/SubscriptionRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using TeamsToDoAppConnector.Models; 3 | 4 | namespace TeamsToDoAppConnector.Repository 5 | { 6 | /// 7 | /// Represents the subscription repository class which stores the temporary data. 8 | /// 9 | public class SubscriptionRepository 10 | { 11 | public static List Subscriptions { get; set; } = new List() ; 12 | } 13 | } -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Repository/TaskRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using TeamsToDoAppConnector.Models; 4 | 5 | namespace TeamsToDoAppConnector.Repository 6 | { 7 | /// 8 | /// Represents the Task repository class which stores the temporary task data. 9 | /// 10 | public class TaskRepository 11 | { 12 | public static List Tasks { get; set; } = new List(); 13 | 14 | static TaskRepository() 15 | { 16 | Tasks.Add(new TaskItem 17 | { 18 | Title = "Get the bills", 19 | Assigned = "Alex", 20 | Description = "Get the travel and accomodation bills", 21 | Guid = Guid.NewGuid().ToString() 22 | }); 23 | 24 | Tasks.Add(new TaskItem 25 | { 26 | Title = "Add a new team member", 27 | Assigned = "John", 28 | Description = "New member Rin joined, please add her in team.", 29 | Guid = Guid.NewGuid().ToString() 30 | }); 31 | 32 | Tasks.Add(new TaskItem 33 | { 34 | Title = "Create new tenant", 35 | Assigned = "Vishal", 36 | Description = "Get new tenant for testing.", 37 | Guid = Guid.NewGuid().ToString() 38 | }); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Scripts/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | 16 | /** 17 | * bootstrap.js v3.0.0 by @fat and @mdo 18 | * Copyright 2013 Twitter Inc. 19 | * http://www.apache.org/licenses/LICENSE-2.0 20 | */ 21 | if(!jQuery)throw new Error("Bootstrap requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]}}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(window.jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d)};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.is("input")?"val":"html",e=c.data();a+="Text",e.resetText||c.data("resetText",c[d]()),c[d](e[a]||this.options[a]),setTimeout(function(){"loadingText"==a?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons"]');if(a.length){var b=this.$element.find("input").prop("checked",!this.$element.hasClass("active")).trigger("change");"radio"===b.prop("type")&&a.find(".active").removeClass("active")}this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(window.jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}this.sliding=!0,f&&this.pause();var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});if(!e.hasClass("active")){if(this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(j),j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)}).emulateTransitionEnd(600)}else{if(this.$element.trigger(j),j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?(this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350),void 0):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(window.jQuery),+function(a){"use strict";function b(){a(d).remove(),a(e).each(function(b){var d=c(a(this));d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown")),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown"))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){if("ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(c).is("body")?a(window):a(c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#\w/.test(e)&&a(e);return f&&f.length&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parents(".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top()),"function"==typeof h&&(h=f.bottom());var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;this.affixed!==i&&(this.unpin&&this.$element.css("top",""),this.affixed=i,this.unpin="bottom"==i?e.top-d:null,this.$element.removeClass(b.RESET).addClass("affix"+(i?"-"+i:"")),"bottom"==i&&this.$element.offset({top:document.body.offsetHeight-h-this.$element.height()}))}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery); -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Scripts/modernizr-2.6.2.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton; http://www.modernizr.com/license/ 15 | * 16 | * Includes matchMedia polyfill; Copyright (c) 2010 Filament Group, Inc; http://opensource.org/licenses/MIT 17 | * 18 | * Includes material adapted from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js; Copyright 2009-2012 by contributors; http://opensource.org/licenses/MIT 19 | * 20 | * Includes material from css-support; Copyright (c) 2005-2012 Diego Perini; https://github.com/dperini/css-support/blob/master/LICENSE 21 | * 22 | * NUGET: END LICENSE TEXT */ 23 | 24 | /*! 25 | * Modernizr v2.6.2 26 | * www.modernizr.com 27 | * 28 | * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton 29 | * Available under the BSD and MIT licenses: www.modernizr.com/license/ 30 | */ 31 | 32 | /* 33 | * Modernizr tests which native CSS3 and HTML5 features are available in 34 | * the current UA and makes the results available to you in two ways: 35 | * as properties on a global Modernizr object, and as classes on the 36 | * element. This information allows you to progressively enhance 37 | * your pages with a granular level of control over the experience. 38 | * 39 | * Modernizr has an optional (not included) conditional resource loader 40 | * called Modernizr.load(), based on Yepnope.js (yepnopejs.com). 41 | * To get a build that includes Modernizr.load(), as well as choosing 42 | * which tests to include, go to www.modernizr.com/download/ 43 | * 44 | * Authors Faruk Ates, Paul Irish, Alex Sexton 45 | * Contributors Ryan Seddon, Ben Alman 46 | */ 47 | 48 | window.Modernizr = (function( window, document, undefined ) { 49 | 50 | var version = '2.6.2', 51 | 52 | Modernizr = {}, 53 | 54 | /*>>cssclasses*/ 55 | // option for enabling the HTML classes to be added 56 | enableClasses = true, 57 | /*>>cssclasses*/ 58 | 59 | docElement = document.documentElement, 60 | 61 | /** 62 | * Create our "modernizr" element that we do most feature tests on. 63 | */ 64 | mod = 'modernizr', 65 | modElem = document.createElement(mod), 66 | mStyle = modElem.style, 67 | 68 | /** 69 | * Create the input element for various Web Forms feature tests. 70 | */ 71 | inputElem /*>>inputelem*/ = document.createElement('input') /*>>inputelem*/ , 72 | 73 | /*>>smile*/ 74 | smile = ':)', 75 | /*>>smile*/ 76 | 77 | toString = {}.toString, 78 | 79 | // TODO :: make the prefixes more granular 80 | /*>>prefixes*/ 81 | // List of property values to set for css tests. See ticket #21 82 | prefixes = ' -webkit- -moz- -o- -ms- '.split(' '), 83 | /*>>prefixes*/ 84 | 85 | /*>>domprefixes*/ 86 | // Following spec is to expose vendor-specific style properties as: 87 | // elem.style.WebkitBorderRadius 88 | // and the following would be incorrect: 89 | // elem.style.webkitBorderRadius 90 | 91 | // Webkit ghosts their properties in lowercase but Opera & Moz do not. 92 | // Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+ 93 | // erik.eae.net/archives/2008/03/10/21.48.10/ 94 | 95 | // More here: github.com/Modernizr/Modernizr/issues/issue/21 96 | omPrefixes = 'Webkit Moz O ms', 97 | 98 | cssomPrefixes = omPrefixes.split(' '), 99 | 100 | domPrefixes = omPrefixes.toLowerCase().split(' '), 101 | /*>>domprefixes*/ 102 | 103 | /*>>ns*/ 104 | ns = {'svg': 'http://www.w3.org/2000/svg'}, 105 | /*>>ns*/ 106 | 107 | tests = {}, 108 | inputs = {}, 109 | attrs = {}, 110 | 111 | classes = [], 112 | 113 | slice = classes.slice, 114 | 115 | featureName, // used in testing loop 116 | 117 | 118 | /*>>teststyles*/ 119 | // Inject element with style element and some CSS rules 120 | injectElementWithStyles = function( rule, callback, nodes, testnames ) { 121 | 122 | var style, ret, node, docOverflow, 123 | div = document.createElement('div'), 124 | // After page load injecting a fake body doesn't work so check if body exists 125 | body = document.body, 126 | // IE6 and 7 won't return offsetWidth or offsetHeight unless it's in the body element, so we fake it. 127 | fakeBody = body || document.createElement('body'); 128 | 129 | if ( parseInt(nodes, 10) ) { 130 | // In order not to give false positives we create a node for each test 131 | // This also allows the method to scale for unspecified uses 132 | while ( nodes-- ) { 133 | node = document.createElement('div'); 134 | node.id = testnames ? testnames[nodes] : mod + (nodes + 1); 135 | div.appendChild(node); 136 | } 137 | } 138 | 139 | // '].join(''); 145 | div.id = mod; 146 | // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody. 147 | // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270 148 | (body ? div : fakeBody).innerHTML += style; 149 | fakeBody.appendChild(div); 150 | if ( !body ) { 151 | //avoid crashing IE8, if background image is used 152 | fakeBody.style.background = ''; 153 | //Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible 154 | fakeBody.style.overflow = 'hidden'; 155 | docOverflow = docElement.style.overflow; 156 | docElement.style.overflow = 'hidden'; 157 | docElement.appendChild(fakeBody); 158 | } 159 | 160 | ret = callback(div, rule); 161 | // If this is done after page load we don't want to remove the body so check if body exists 162 | if ( !body ) { 163 | fakeBody.parentNode.removeChild(fakeBody); 164 | docElement.style.overflow = docOverflow; 165 | } else { 166 | div.parentNode.removeChild(div); 167 | } 168 | 169 | return !!ret; 170 | 171 | }, 172 | /*>>teststyles*/ 173 | 174 | /*>>mq*/ 175 | // adapted from matchMedia polyfill 176 | // by Scott Jehl and Paul Irish 177 | // gist.github.com/786768 178 | testMediaQuery = function( mq ) { 179 | 180 | var matchMedia = window.matchMedia || window.msMatchMedia; 181 | if ( matchMedia ) { 182 | return matchMedia(mq).matches; 183 | } 184 | 185 | var bool; 186 | 187 | injectElementWithStyles('@media ' + mq + ' { #' + mod + ' { position: absolute; } }', function( node ) { 188 | bool = (window.getComputedStyle ? 189 | getComputedStyle(node, null) : 190 | node.currentStyle)['position'] == 'absolute'; 191 | }); 192 | 193 | return bool; 194 | 195 | }, 196 | /*>>mq*/ 197 | 198 | 199 | /*>>hasevent*/ 200 | // 201 | // isEventSupported determines if a given element supports the given event 202 | // kangax.github.com/iseventsupported/ 203 | // 204 | // The following results are known incorrects: 205 | // Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative 206 | // Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333 207 | // ... 208 | isEventSupported = (function() { 209 | 210 | var TAGNAMES = { 211 | 'select': 'input', 'change': 'input', 212 | 'submit': 'form', 'reset': 'form', 213 | 'error': 'img', 'load': 'img', 'abort': 'img' 214 | }; 215 | 216 | function isEventSupported( eventName, element ) { 217 | 218 | element = element || document.createElement(TAGNAMES[eventName] || 'div'); 219 | eventName = 'on' + eventName; 220 | 221 | // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those 222 | var isSupported = eventName in element; 223 | 224 | if ( !isSupported ) { 225 | // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element 226 | if ( !element.setAttribute ) { 227 | element = document.createElement('div'); 228 | } 229 | if ( element.setAttribute && element.removeAttribute ) { 230 | element.setAttribute(eventName, ''); 231 | isSupported = is(element[eventName], 'function'); 232 | 233 | // If property was created, "remove it" (by setting value to `undefined`) 234 | if ( !is(element[eventName], 'undefined') ) { 235 | element[eventName] = undefined; 236 | } 237 | element.removeAttribute(eventName); 238 | } 239 | } 240 | 241 | element = null; 242 | return isSupported; 243 | } 244 | return isEventSupported; 245 | })(), 246 | /*>>hasevent*/ 247 | 248 | // TODO :: Add flag for hasownprop ? didn't last time 249 | 250 | // hasOwnProperty shim by kangax needed for Safari 2.0 support 251 | _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; 252 | 253 | if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { 254 | hasOwnProp = function (object, property) { 255 | return _hasOwnProperty.call(object, property); 256 | }; 257 | } 258 | else { 259 | hasOwnProp = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ 260 | return ((property in object) && is(object.constructor.prototype[property], 'undefined')); 261 | }; 262 | } 263 | 264 | // Adapted from ES5-shim https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js 265 | // es5.github.com/#x15.3.4.5 266 | 267 | if (!Function.prototype.bind) { 268 | Function.prototype.bind = function bind(that) { 269 | 270 | var target = this; 271 | 272 | if (typeof target != "function") { 273 | throw new TypeError(); 274 | } 275 | 276 | var args = slice.call(arguments, 1), 277 | bound = function () { 278 | 279 | if (this instanceof bound) { 280 | 281 | var F = function(){}; 282 | F.prototype = target.prototype; 283 | var self = new F(); 284 | 285 | var result = target.apply( 286 | self, 287 | args.concat(slice.call(arguments)) 288 | ); 289 | if (Object(result) === result) { 290 | return result; 291 | } 292 | return self; 293 | 294 | } else { 295 | 296 | return target.apply( 297 | that, 298 | args.concat(slice.call(arguments)) 299 | ); 300 | 301 | } 302 | 303 | }; 304 | 305 | return bound; 306 | }; 307 | } 308 | 309 | /** 310 | * setCss applies given styles to the Modernizr DOM node. 311 | */ 312 | function setCss( str ) { 313 | mStyle.cssText = str; 314 | } 315 | 316 | /** 317 | * setCssAll extrapolates all vendor-specific css strings. 318 | */ 319 | function setCssAll( str1, str2 ) { 320 | return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); 321 | } 322 | 323 | /** 324 | * is returns a boolean for if typeof obj is exactly type. 325 | */ 326 | function is( obj, type ) { 327 | return typeof obj === type; 328 | } 329 | 330 | /** 331 | * contains returns a boolean for if substr is found within str. 332 | */ 333 | function contains( str, substr ) { 334 | return !!~('' + str).indexOf(substr); 335 | } 336 | 337 | /*>>testprop*/ 338 | 339 | // testProps is a generic CSS / DOM property test. 340 | 341 | // In testing support for a given CSS property, it's legit to test: 342 | // `elem.style[styleName] !== undefined` 343 | // If the property is supported it will return an empty string, 344 | // if unsupported it will return undefined. 345 | 346 | // We'll take advantage of this quick test and skip setting a style 347 | // on our modernizr element, but instead just testing undefined vs 348 | // empty string. 349 | 350 | // Because the testing of the CSS property names (with "-", as 351 | // opposed to the camelCase DOM properties) is non-portable and 352 | // non-standard but works in WebKit and IE (but not Gecko or Opera), 353 | // we explicitly reject properties with dashes so that authors 354 | // developing in WebKit or IE first don't end up with 355 | // browser-specific content by accident. 356 | 357 | function testProps( props, prefixed ) { 358 | for ( var i in props ) { 359 | var prop = props[i]; 360 | if ( !contains(prop, "-") && mStyle[prop] !== undefined ) { 361 | return prefixed == 'pfx' ? prop : true; 362 | } 363 | } 364 | return false; 365 | } 366 | /*>>testprop*/ 367 | 368 | // TODO :: add testDOMProps 369 | /** 370 | * testDOMProps is a generic DOM property test; if a browser supports 371 | * a certain property, it won't return undefined for it. 372 | */ 373 | function testDOMProps( props, obj, elem ) { 374 | for ( var i in props ) { 375 | var item = obj[props[i]]; 376 | if ( item !== undefined) { 377 | 378 | // return the property name as a string 379 | if (elem === false) return props[i]; 380 | 381 | // let's bind a function 382 | if (is(item, 'function')){ 383 | // default to autobind unless override 384 | return item.bind(elem || obj); 385 | } 386 | 387 | // return the unbound function or obj or value 388 | return item; 389 | } 390 | } 391 | return false; 392 | } 393 | 394 | /*>>testallprops*/ 395 | /** 396 | * testPropsAll tests a list of DOM properties we want to check against. 397 | * We specify literally ALL possible (known and/or likely) properties on 398 | * the element including the non-vendor prefixed one, for forward- 399 | * compatibility. 400 | */ 401 | function testPropsAll( prop, prefixed, elem ) { 402 | 403 | var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), 404 | props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); 405 | 406 | // did they call .prefixed('boxSizing') or are we just testing a prop? 407 | if(is(prefixed, "string") || is(prefixed, "undefined")) { 408 | return testProps(props, prefixed); 409 | 410 | // otherwise, they called .prefixed('requestAnimationFrame', window[, elem]) 411 | } else { 412 | props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); 413 | return testDOMProps(props, prefixed, elem); 414 | } 415 | } 416 | /*>>testallprops*/ 417 | 418 | 419 | /** 420 | * Tests 421 | * ----- 422 | */ 423 | 424 | // The *new* flexbox 425 | // dev.w3.org/csswg/css3-flexbox 426 | 427 | tests['flexbox'] = function() { 428 | return testPropsAll('flexWrap'); 429 | }; 430 | 431 | // The *old* flexbox 432 | // www.w3.org/TR/2009/WD-css3-flexbox-20090723/ 433 | 434 | tests['flexboxlegacy'] = function() { 435 | return testPropsAll('boxDirection'); 436 | }; 437 | 438 | // On the S60 and BB Storm, getContext exists, but always returns undefined 439 | // so we actually have to call getContext() to verify 440 | // github.com/Modernizr/Modernizr/issues/issue/97/ 441 | 442 | tests['canvas'] = function() { 443 | var elem = document.createElement('canvas'); 444 | return !!(elem.getContext && elem.getContext('2d')); 445 | }; 446 | 447 | tests['canvastext'] = function() { 448 | return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); 449 | }; 450 | 451 | // webk.it/70117 is tracking a legit WebGL feature detect proposal 452 | 453 | // We do a soft detect which may false positive in order to avoid 454 | // an expensive context creation: bugzil.la/732441 455 | 456 | tests['webgl'] = function() { 457 | return !!window.WebGLRenderingContext; 458 | }; 459 | 460 | /* 461 | * The Modernizr.touch test only indicates if the browser supports 462 | * touch events, which does not necessarily reflect a touchscreen 463 | * device, as evidenced by tablets running Windows 7 or, alas, 464 | * the Palm Pre / WebOS (touch) phones. 465 | * 466 | * Additionally, Chrome (desktop) used to lie about its support on this, 467 | * but that has since been rectified: crbug.com/36415 468 | * 469 | * We also test for Firefox 4 Multitouch Support. 470 | * 471 | * For more info, see: modernizr.github.com/Modernizr/touch.html 472 | */ 473 | 474 | tests['touch'] = function() { 475 | var bool; 476 | 477 | if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { 478 | bool = true; 479 | } else { 480 | injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) { 481 | bool = node.offsetTop === 9; 482 | }); 483 | } 484 | 485 | return bool; 486 | }; 487 | 488 | 489 | // geolocation is often considered a trivial feature detect... 490 | // Turns out, it's quite tricky to get right: 491 | // 492 | // Using !!navigator.geolocation does two things we don't want. It: 493 | // 1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513 494 | // 2. Disables page caching in WebKit: webk.it/43956 495 | // 496 | // Meanwhile, in Firefox < 8, an about:config setting could expose 497 | // a false positive that would throw an exception: bugzil.la/688158 498 | 499 | tests['geolocation'] = function() { 500 | return 'geolocation' in navigator; 501 | }; 502 | 503 | 504 | tests['postmessage'] = function() { 505 | return !!window.postMessage; 506 | }; 507 | 508 | 509 | // Chrome incognito mode used to throw an exception when using openDatabase 510 | // It doesn't anymore. 511 | tests['websqldatabase'] = function() { 512 | return !!window.openDatabase; 513 | }; 514 | 515 | // Vendors had inconsistent prefixing with the experimental Indexed DB: 516 | // - Webkit's implementation is accessible through webkitIndexedDB 517 | // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB 518 | // For speed, we don't test the legacy (and beta-only) indexedDB 519 | tests['indexedDB'] = function() { 520 | return !!testPropsAll("indexedDB", window); 521 | }; 522 | 523 | // documentMode logic from YUI to filter out IE8 Compat Mode 524 | // which false positives. 525 | tests['hashchange'] = function() { 526 | return isEventSupported('hashchange', window) && (document.documentMode === undefined || document.documentMode > 7); 527 | }; 528 | 529 | // Per 1.6: 530 | // This used to be Modernizr.historymanagement but the longer 531 | // name has been deprecated in favor of a shorter and property-matching one. 532 | // The old API is still available in 1.6, but as of 2.0 will throw a warning, 533 | // and in the first release thereafter disappear entirely. 534 | tests['history'] = function() { 535 | return !!(window.history && history.pushState); 536 | }; 537 | 538 | tests['draganddrop'] = function() { 539 | var div = document.createElement('div'); 540 | return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); 541 | }; 542 | 543 | // FF3.6 was EOL'ed on 4/24/12, but the ESR version of FF10 544 | // will be supported until FF19 (2/12/13), at which time, ESR becomes FF17. 545 | // FF10 still uses prefixes, so check for it until then. 546 | // for more ESR info, see: mozilla.org/en-US/firefox/organizations/faq/ 547 | tests['websockets'] = function() { 548 | return 'WebSocket' in window || 'MozWebSocket' in window; 549 | }; 550 | 551 | 552 | // css-tricks.com/rgba-browser-support/ 553 | tests['rgba'] = function() { 554 | // Set an rgba() color and check the returned value 555 | 556 | setCss('background-color:rgba(150,255,150,.5)'); 557 | 558 | return contains(mStyle.backgroundColor, 'rgba'); 559 | }; 560 | 561 | tests['hsla'] = function() { 562 | // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally, 563 | // except IE9 who retains it as hsla 564 | 565 | setCss('background-color:hsla(120,40%,100%,.5)'); 566 | 567 | return contains(mStyle.backgroundColor, 'rgba') || contains(mStyle.backgroundColor, 'hsla'); 568 | }; 569 | 570 | tests['multiplebgs'] = function() { 571 | // Setting multiple images AND a color on the background shorthand property 572 | // and then querying the style.background property value for the number of 573 | // occurrences of "url(" is a reliable method for detecting ACTUAL support for this! 574 | 575 | setCss('background:url(https://),url(https://),red url(https://)'); 576 | 577 | // If the UA supports multiple backgrounds, there should be three occurrences 578 | // of the string "url(" in the return value for elemStyle.background 579 | 580 | return (/(url\s*\(.*?){3}/).test(mStyle.background); 581 | }; 582 | 583 | 584 | 585 | // this will false positive in Opera Mini 586 | // github.com/Modernizr/Modernizr/issues/396 587 | 588 | tests['backgroundsize'] = function() { 589 | return testPropsAll('backgroundSize'); 590 | }; 591 | 592 | tests['borderimage'] = function() { 593 | return testPropsAll('borderImage'); 594 | }; 595 | 596 | 597 | // Super comprehensive table about all the unique implementations of 598 | // border-radius: muddledramblings.com/table-of-css3-border-radius-compliance 599 | 600 | tests['borderradius'] = function() { 601 | return testPropsAll('borderRadius'); 602 | }; 603 | 604 | // WebOS unfortunately false positives on this test. 605 | tests['boxshadow'] = function() { 606 | return testPropsAll('boxShadow'); 607 | }; 608 | 609 | // FF3.0 will false positive on this test 610 | tests['textshadow'] = function() { 611 | return document.createElement('div').style.textShadow === ''; 612 | }; 613 | 614 | 615 | tests['opacity'] = function() { 616 | // Browsers that actually have CSS Opacity implemented have done so 617 | // according to spec, which means their return values are within the 618 | // range of [0.0,1.0] - including the leading zero. 619 | 620 | setCssAll('opacity:.55'); 621 | 622 | // The non-literal . in this regex is intentional: 623 | // German Chrome returns this value as 0,55 624 | // github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632 625 | return (/^0.55$/).test(mStyle.opacity); 626 | }; 627 | 628 | 629 | // Note, Android < 4 will pass this test, but can only animate 630 | // a single property at a time 631 | // daneden.me/2011/12/putting-up-with-androids-bullshit/ 632 | tests['cssanimations'] = function() { 633 | return testPropsAll('animationName'); 634 | }; 635 | 636 | 637 | tests['csscolumns'] = function() { 638 | return testPropsAll('columnCount'); 639 | }; 640 | 641 | 642 | tests['cssgradients'] = function() { 643 | /** 644 | * For CSS Gradients syntax, please see: 645 | * webkit.org/blog/175/introducing-css-gradients/ 646 | * developer.mozilla.org/en/CSS/-moz-linear-gradient 647 | * developer.mozilla.org/en/CSS/-moz-radial-gradient 648 | * dev.w3.org/csswg/css3-images/#gradients- 649 | */ 650 | 651 | var str1 = 'background-image:', 652 | str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', 653 | str3 = 'linear-gradient(left top,#9f9, white);'; 654 | 655 | setCss( 656 | // legacy webkit syntax (FIXME: remove when syntax not in use anymore) 657 | (str1 + '-webkit- '.split(' ').join(str2 + str1) + 658 | // standard syntax // trailing 'background-image:' 659 | prefixes.join(str3 + str1)).slice(0, -str1.length) 660 | ); 661 | 662 | return contains(mStyle.backgroundImage, 'gradient'); 663 | }; 664 | 665 | 666 | tests['cssreflections'] = function() { 667 | return testPropsAll('boxReflect'); 668 | }; 669 | 670 | 671 | tests['csstransforms'] = function() { 672 | return !!testPropsAll('transform'); 673 | }; 674 | 675 | 676 | tests['csstransforms3d'] = function() { 677 | 678 | var ret = !!testPropsAll('perspective'); 679 | 680 | // Webkit's 3D transforms are passed off to the browser's own graphics renderer. 681 | // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in 682 | // some conditions. As a result, Webkit typically recognizes the syntax but 683 | // will sometimes throw a false positive, thus we must do a more thorough check: 684 | if ( ret && 'webkitPerspective' in docElement.style ) { 685 | 686 | // Webkit allows this media query to succeed only if the feature is enabled. 687 | // `@media (transform-3d),(-webkit-transform-3d){ ... }` 688 | injectElementWithStyles('@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}', function( node, rule ) { 689 | ret = node.offsetLeft === 9 && node.offsetHeight === 3; 690 | }); 691 | } 692 | return ret; 693 | }; 694 | 695 | 696 | tests['csstransitions'] = function() { 697 | return testPropsAll('transition'); 698 | }; 699 | 700 | 701 | /*>>fontface*/ 702 | // @font-face detection routine by Diego Perini 703 | // javascript.nwbox.com/CSSSupport/ 704 | 705 | // false positives: 706 | // WebOS github.com/Modernizr/Modernizr/issues/342 707 | // WP7 github.com/Modernizr/Modernizr/issues/538 708 | tests['fontface'] = function() { 709 | var bool; 710 | 711 | injectElementWithStyles('@font-face {font-family:"font";src:url("https://")}', function( node, rule ) { 712 | var style = document.getElementById('smodernizr'), 713 | sheet = style.sheet || style.styleSheet, 714 | cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : ''; 715 | 716 | bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0; 717 | }); 718 | 719 | return bool; 720 | }; 721 | /*>>fontface*/ 722 | 723 | // CSS generated content detection 724 | tests['generatedcontent'] = function() { 725 | var bool; 726 | 727 | injectElementWithStyles(['#',mod,'{font:0/0 a}#',mod,':after{content:"',smile,'";visibility:hidden;font:3px/1 a}'].join(''), function( node ) { 728 | bool = node.offsetHeight >= 3; 729 | }); 730 | 731 | return bool; 732 | }; 733 | 734 | 735 | 736 | // These tests evaluate support of the video/audio elements, as well as 737 | // testing what types of content they support. 738 | // 739 | // We're using the Boolean constructor here, so that we can extend the value 740 | // e.g. Modernizr.video // true 741 | // Modernizr.video.ogg // 'probably' 742 | // 743 | // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 744 | // thx to NielsLeenheer and zcorpan 745 | 746 | // Note: in some older browsers, "no" was a return value instead of empty string. 747 | // It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2 748 | // It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5 749 | 750 | tests['video'] = function() { 751 | var elem = document.createElement('video'), 752 | bool = false; 753 | 754 | // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224 755 | try { 756 | if ( bool = !!elem.canPlayType ) { 757 | bool = new Boolean(bool); 758 | bool.ogg = elem.canPlayType('video/ogg; codecs="theora"') .replace(/^no$/,''); 759 | 760 | // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546 761 | bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"') .replace(/^no$/,''); 762 | 763 | bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,''); 764 | } 765 | 766 | } catch(e) { } 767 | 768 | return bool; 769 | }; 770 | 771 | tests['audio'] = function() { 772 | var elem = document.createElement('audio'), 773 | bool = false; 774 | 775 | try { 776 | if ( bool = !!elem.canPlayType ) { 777 | bool = new Boolean(bool); 778 | bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''); 779 | bool.mp3 = elem.canPlayType('audio/mpeg;') .replace(/^no$/,''); 780 | 781 | // Mimetypes accepted: 782 | // developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements 783 | // bit.ly/iphoneoscodecs 784 | bool.wav = elem.canPlayType('audio/wav; codecs="1"') .replace(/^no$/,''); 785 | bool.m4a = ( elem.canPlayType('audio/x-m4a;') || 786 | elem.canPlayType('audio/aac;')) .replace(/^no$/,''); 787 | } 788 | } catch(e) { } 789 | 790 | return bool; 791 | }; 792 | 793 | 794 | // In FF4, if disabled, window.localStorage should === null. 795 | 796 | // Normally, we could not test that directly and need to do a 797 | // `('localStorage' in window) && ` test first because otherwise Firefox will 798 | // throw bugzil.la/365772 if cookies are disabled 799 | 800 | // Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem 801 | // will throw the exception: 802 | // QUOTA_EXCEEDED_ERRROR DOM Exception 22. 803 | // Peculiarly, getItem and removeItem calls do not throw. 804 | 805 | // Because we are forced to try/catch this, we'll go aggressive. 806 | 807 | // Just FWIW: IE8 Compat mode supports these features completely: 808 | // www.quirksmode.org/dom/html5.html 809 | // But IE8 doesn't support either with local files 810 | 811 | tests['localstorage'] = function() { 812 | try { 813 | localStorage.setItem(mod, mod); 814 | localStorage.removeItem(mod); 815 | return true; 816 | } catch(e) { 817 | return false; 818 | } 819 | }; 820 | 821 | tests['sessionstorage'] = function() { 822 | try { 823 | sessionStorage.setItem(mod, mod); 824 | sessionStorage.removeItem(mod); 825 | return true; 826 | } catch(e) { 827 | return false; 828 | } 829 | }; 830 | 831 | 832 | tests['webworkers'] = function() { 833 | return !!window.Worker; 834 | }; 835 | 836 | 837 | tests['applicationcache'] = function() { 838 | return !!window.applicationCache; 839 | }; 840 | 841 | 842 | // Thanks to Erik Dahlstrom 843 | tests['svg'] = function() { 844 | return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; 845 | }; 846 | 847 | // specifically for SVG inline in HTML, not within XHTML 848 | // test page: paulirish.com/demo/inline-svg 849 | tests['inlinesvg'] = function() { 850 | var div = document.createElement('div'); 851 | div.innerHTML = ''; 852 | return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; 853 | }; 854 | 855 | // SVG SMIL animation 856 | tests['smil'] = function() { 857 | return !!document.createElementNS && /SVGAnimate/.test(toString.call(document.createElementNS(ns.svg, 'animate'))); 858 | }; 859 | 860 | // This test is only for clip paths in SVG proper, not clip paths on HTML content 861 | // demo: srufaculty.sru.edu/david.dailey/svg/newstuff/clipPath4.svg 862 | 863 | // However read the comments to dig into applying SVG clippaths to HTML content here: 864 | // github.com/Modernizr/Modernizr/issues/213#issuecomment-1149491 865 | tests['svgclippaths'] = function() { 866 | return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); 867 | }; 868 | 869 | /*>>webforms*/ 870 | // input features and input types go directly onto the ret object, bypassing the tests loop. 871 | // Hold this guy to execute in a moment. 872 | function webforms() { 873 | /*>>input*/ 874 | // Run through HTML5's new input attributes to see if the UA understands any. 875 | // We're using f which is the element created early on 876 | // Mike Taylr has created a comprehensive resource for testing these attributes 877 | // when applied to all input types: 878 | // miketaylr.com/code/input-type-attr.html 879 | // spec: www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary 880 | 881 | // Only input placeholder is tested while textarea's placeholder is not. 882 | // Currently Safari 4 and Opera 11 have support only for the input placeholder 883 | // Both tests are available in feature-detects/forms-placeholder.js 884 | Modernizr['input'] = (function( props ) { 885 | for ( var i = 0, len = props.length; i < len; i++ ) { 886 | attrs[ props[i] ] = !!(props[i] in inputElem); 887 | } 888 | if (attrs.list){ 889 | // safari false positive's on datalist: webk.it/74252 890 | // see also github.com/Modernizr/Modernizr/issues/146 891 | attrs.list = !!(document.createElement('datalist') && window.HTMLDataListElement); 892 | } 893 | return attrs; 894 | })('autocomplete autofocus list placeholder max min multiple pattern required step'.split(' ')); 895 | /*>>input*/ 896 | 897 | /*>>inputtypes*/ 898 | // Run through HTML5's new input types to see if the UA understands any. 899 | // This is put behind the tests runloop because it doesn't return a 900 | // true/false like all the other tests; instead, it returns an object 901 | // containing each input type with its corresponding true/false value 902 | 903 | // Big thanks to @miketaylr for the html5 forms expertise. miketaylr.com/ 904 | Modernizr['inputtypes'] = (function(props) { 905 | 906 | for ( var i = 0, bool, inputElemType, defaultView, len = props.length; i < len; i++ ) { 907 | 908 | inputElem.setAttribute('type', inputElemType = props[i]); 909 | bool = inputElem.type !== 'text'; 910 | 911 | // We first check to see if the type we give it sticks.. 912 | // If the type does, we feed it a textual value, which shouldn't be valid. 913 | // If the value doesn't stick, we know there's input sanitization which infers a custom UI 914 | if ( bool ) { 915 | 916 | inputElem.value = smile; 917 | inputElem.style.cssText = 'position:absolute;visibility:hidden;'; 918 | 919 | if ( /^range$/.test(inputElemType) && inputElem.style.WebkitAppearance !== undefined ) { 920 | 921 | docElement.appendChild(inputElem); 922 | defaultView = document.defaultView; 923 | 924 | // Safari 2-4 allows the smiley as a value, despite making a slider 925 | bool = defaultView.getComputedStyle && 926 | defaultView.getComputedStyle(inputElem, null).WebkitAppearance !== 'textfield' && 927 | // Mobile android web browser has false positive, so must 928 | // check the height to see if the widget is actually there. 929 | (inputElem.offsetHeight !== 0); 930 | 931 | docElement.removeChild(inputElem); 932 | 933 | } else if ( /^(search|tel)$/.test(inputElemType) ){ 934 | // Spec doesn't define any special parsing or detectable UI 935 | // behaviors so we pass these through as true 936 | 937 | // Interestingly, opera fails the earlier test, so it doesn't 938 | // even make it here. 939 | 940 | } else if ( /^(url|email)$/.test(inputElemType) ) { 941 | // Real url and email support comes with prebaked validation. 942 | bool = inputElem.checkValidity && inputElem.checkValidity() === false; 943 | 944 | } else { 945 | // If the upgraded input compontent rejects the :) text, we got a winner 946 | bool = inputElem.value != smile; 947 | } 948 | } 949 | 950 | inputs[ props[i] ] = !!bool; 951 | } 952 | return inputs; 953 | })('search tel url email datetime date month week time datetime-local number range color'.split(' ')); 954 | /*>>inputtypes*/ 955 | } 956 | /*>>webforms*/ 957 | 958 | 959 | // End of test definitions 960 | // ----------------------- 961 | 962 | 963 | 964 | // Run through all tests and detect their support in the current UA. 965 | // todo: hypothetically we could be doing an array of tests and use a basic loop here. 966 | for ( var feature in tests ) { 967 | if ( hasOwnProp(tests, feature) ) { 968 | // run the test, throw the return value into the Modernizr, 969 | // then based on that boolean, define an appropriate className 970 | // and push it into an array of classes we'll join later. 971 | featureName = feature.toLowerCase(); 972 | Modernizr[featureName] = tests[feature](); 973 | 974 | classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); 975 | } 976 | } 977 | 978 | /*>>webforms*/ 979 | // input tests need to run. 980 | Modernizr.input || webforms(); 981 | /*>>webforms*/ 982 | 983 | 984 | /** 985 | * addTest allows the user to define their own feature tests 986 | * the result will be added onto the Modernizr object, 987 | * as well as an appropriate className set on the html element 988 | * 989 | * @param feature - String naming the feature 990 | * @param test - Function returning true if feature is supported, false if not 991 | */ 992 | Modernizr.addTest = function ( feature, test ) { 993 | if ( typeof feature == 'object' ) { 994 | for ( var key in feature ) { 995 | if ( hasOwnProp( feature, key ) ) { 996 | Modernizr.addTest( key, feature[ key ] ); 997 | } 998 | } 999 | } else { 1000 | 1001 | feature = feature.toLowerCase(); 1002 | 1003 | if ( Modernizr[feature] !== undefined ) { 1004 | // we're going to quit if you're trying to overwrite an existing test 1005 | // if we were to allow it, we'd do this: 1006 | // var re = new RegExp("\\b(no-)?" + feature + "\\b"); 1007 | // docElement.className = docElement.className.replace( re, '' ); 1008 | // but, no rly, stuff 'em. 1009 | return Modernizr; 1010 | } 1011 | 1012 | test = typeof test == 'function' ? test() : test; 1013 | 1014 | if (typeof enableClasses !== "undefined" && enableClasses) { 1015 | docElement.className += ' ' + (test ? '' : 'no-') + feature; 1016 | } 1017 | Modernizr[feature] = test; 1018 | 1019 | } 1020 | 1021 | return Modernizr; // allow chaining. 1022 | }; 1023 | 1024 | 1025 | // Reset modElem.cssText to nothing to reduce memory footprint. 1026 | setCss(''); 1027 | modElem = inputElem = null; 1028 | 1029 | /*>>shiv*/ 1030 | /*! HTML5 Shiv v3.6.1 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed */ 1031 | ;(function(window, document) { 1032 | /*jshint evil:true */ 1033 | /** Preset options */ 1034 | var options = window.html5 || {}; 1035 | 1036 | /** Used to skip problem elements */ 1037 | var reSkip = /^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i; 1038 | 1039 | /** Not all elements can be cloned in IE **/ 1040 | var saveClones = /^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i; 1041 | 1042 | /** Detect whether the browser supports default html5 styles */ 1043 | var supportsHtml5Styles; 1044 | 1045 | /** Name of the expando, to work with multiple documents or to re-shiv one document */ 1046 | var expando = '_html5shiv'; 1047 | 1048 | /** The id for the the documents expando */ 1049 | var expanID = 0; 1050 | 1051 | /** Cached data for each document */ 1052 | var expandoData = {}; 1053 | 1054 | /** Detect whether the browser supports unknown elements */ 1055 | var supportsUnknownElements; 1056 | 1057 | (function() { 1058 | try { 1059 | var a = document.createElement('a'); 1060 | a.innerHTML = ''; 1061 | //if the hidden property is implemented we can assume, that the browser supports basic HTML5 Styles 1062 | supportsHtml5Styles = ('hidden' in a); 1063 | 1064 | supportsUnknownElements = a.childNodes.length == 1 || (function() { 1065 | // assign a false positive if unable to shiv 1066 | (document.createElement)('a'); 1067 | var frag = document.createDocumentFragment(); 1068 | return ( 1069 | typeof frag.cloneNode == 'undefined' || 1070 | typeof frag.createDocumentFragment == 'undefined' || 1071 | typeof frag.createElement == 'undefined' 1072 | ); 1073 | }()); 1074 | } catch(e) { 1075 | supportsHtml5Styles = true; 1076 | supportsUnknownElements = true; 1077 | } 1078 | 1079 | }()); 1080 | 1081 | /*--------------------------------------------------------------------------*/ 1082 | 1083 | /** 1084 | * Creates a style sheet with the given CSS text and adds it to the document. 1085 | * @private 1086 | * @param {Document} ownerDocument The document. 1087 | * @param {String} cssText The CSS text. 1088 | * @returns {StyleSheet} The style element. 1089 | */ 1090 | function addStyleSheet(ownerDocument, cssText) { 1091 | var p = ownerDocument.createElement('p'), 1092 | parent = ownerDocument.getElementsByTagName('head')[0] || ownerDocument.documentElement; 1093 | 1094 | p.innerHTML = 'x'; 1095 | return parent.insertBefore(p.lastChild, parent.firstChild); 1096 | } 1097 | 1098 | /** 1099 | * Returns the value of `html5.elements` as an array. 1100 | * @private 1101 | * @returns {Array} An array of shived element node names. 1102 | */ 1103 | function getElements() { 1104 | var elements = html5.elements; 1105 | return typeof elements == 'string' ? elements.split(' ') : elements; 1106 | } 1107 | 1108 | /** 1109 | * Returns the data associated to the given document 1110 | * @private 1111 | * @param {Document} ownerDocument The document. 1112 | * @returns {Object} An object of data. 1113 | */ 1114 | function getExpandoData(ownerDocument) { 1115 | var data = expandoData[ownerDocument[expando]]; 1116 | if (!data) { 1117 | data = {}; 1118 | expanID++; 1119 | ownerDocument[expando] = expanID; 1120 | expandoData[expanID] = data; 1121 | } 1122 | return data; 1123 | } 1124 | 1125 | /** 1126 | * returns a shived element for the given nodeName and document 1127 | * @memberOf html5 1128 | * @param {String} nodeName name of the element 1129 | * @param {Document} ownerDocument The context document. 1130 | * @returns {Object} The shived element. 1131 | */ 1132 | function createElement(nodeName, ownerDocument, data){ 1133 | if (!ownerDocument) { 1134 | ownerDocument = document; 1135 | } 1136 | if(supportsUnknownElements){ 1137 | return ownerDocument.createElement(nodeName); 1138 | } 1139 | if (!data) { 1140 | data = getExpandoData(ownerDocument); 1141 | } 1142 | var node; 1143 | 1144 | if (data.cache[nodeName]) { 1145 | node = data.cache[nodeName].cloneNode(); 1146 | } else if (saveClones.test(nodeName)) { 1147 | node = (data.cache[nodeName] = data.createElem(nodeName)).cloneNode(); 1148 | } else { 1149 | node = data.createElem(nodeName); 1150 | } 1151 | 1152 | // Avoid adding some elements to fragments in IE < 9 because 1153 | // * Attributes like `name` or `type` cannot be set/changed once an element 1154 | // is inserted into a document/fragment 1155 | // * Link elements with `src` attributes that are inaccessible, as with 1156 | // a 403 response, will cause the tab/window to crash 1157 | // * Script elements appended to fragments will execute when their `src` 1158 | // or `text` property is set 1159 | return node.canHaveChildren && !reSkip.test(nodeName) ? data.frag.appendChild(node) : node; 1160 | } 1161 | 1162 | /** 1163 | * returns a shived DocumentFragment for the given document 1164 | * @memberOf html5 1165 | * @param {Document} ownerDocument The context document. 1166 | * @returns {Object} The shived DocumentFragment. 1167 | */ 1168 | function createDocumentFragment(ownerDocument, data){ 1169 | if (!ownerDocument) { 1170 | ownerDocument = document; 1171 | } 1172 | if(supportsUnknownElements){ 1173 | return ownerDocument.createDocumentFragment(); 1174 | } 1175 | data = data || getExpandoData(ownerDocument); 1176 | var clone = data.frag.cloneNode(), 1177 | i = 0, 1178 | elems = getElements(), 1179 | l = elems.length; 1180 | for(;i>shiv*/ 1319 | 1320 | // Assign private properties to the return object with prefix 1321 | Modernizr._version = version; 1322 | 1323 | // expose these for the plugin API. Look in the source for how to join() them against your input 1324 | /*>>prefixes*/ 1325 | Modernizr._prefixes = prefixes; 1326 | /*>>prefixes*/ 1327 | /*>>domprefixes*/ 1328 | Modernizr._domPrefixes = domPrefixes; 1329 | Modernizr._cssomPrefixes = cssomPrefixes; 1330 | /*>>domprefixes*/ 1331 | 1332 | /*>>mq*/ 1333 | // Modernizr.mq tests a given media query, live against the current state of the window 1334 | // A few important notes: 1335 | // * If a browser does not support media queries at all (eg. oldIE) the mq() will always return false 1336 | // * A max-width or orientation query will be evaluated against the current state, which may change later. 1337 | // * You must specify values. Eg. If you are testing support for the min-width media query use: 1338 | // Modernizr.mq('(min-width:0)') 1339 | // usage: 1340 | // Modernizr.mq('only screen and (max-width:768)') 1341 | Modernizr.mq = testMediaQuery; 1342 | /*>>mq*/ 1343 | 1344 | /*>>hasevent*/ 1345 | // Modernizr.hasEvent() detects support for a given event, with an optional element to test on 1346 | // Modernizr.hasEvent('gesturestart', elem) 1347 | Modernizr.hasEvent = isEventSupported; 1348 | /*>>hasevent*/ 1349 | 1350 | /*>>testprop*/ 1351 | // Modernizr.testProp() investigates whether a given style property is recognized 1352 | // Note that the property names must be provided in the camelCase variant. 1353 | // Modernizr.testProp('pointerEvents') 1354 | Modernizr.testProp = function(prop){ 1355 | return testProps([prop]); 1356 | }; 1357 | /*>>testprop*/ 1358 | 1359 | /*>>testallprops*/ 1360 | // Modernizr.testAllProps() investigates whether a given style property, 1361 | // or any of its vendor-prefixed variants, is recognized 1362 | // Note that the property names must be provided in the camelCase variant. 1363 | // Modernizr.testAllProps('boxSizing') 1364 | Modernizr.testAllProps = testPropsAll; 1365 | /*>>testallprops*/ 1366 | 1367 | 1368 | /*>>teststyles*/ 1369 | // Modernizr.testStyles() allows you to add custom styles to the document and test an element afterwards 1370 | // Modernizr.testStyles('#modernizr { position:absolute }', function(elem, rule){ ... }) 1371 | Modernizr.testStyles = injectElementWithStyles; 1372 | /*>>teststyles*/ 1373 | 1374 | 1375 | /*>>prefixed*/ 1376 | // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input 1377 | // Modernizr.prefixed('boxSizing') // 'MozBoxSizing' 1378 | 1379 | // Properties must be passed as dom-style camelcase, rather than `box-sizing` hypentated style. 1380 | // Return values will also be the camelCase variant, if you need to translate that to hypenated style use: 1381 | // 1382 | // str.replace(/([A-Z])/g, function(str,m1){ return '-' + m1.toLowerCase(); }).replace(/^ms-/,'-ms-'); 1383 | 1384 | // If you're trying to ascertain which transition end event to bind to, you might do something like... 1385 | // 1386 | // var transEndEventNames = { 1387 | // 'WebkitTransition' : 'webkitTransitionEnd', 1388 | // 'MozTransition' : 'transitionend', 1389 | // 'OTransition' : 'oTransitionEnd', 1390 | // 'msTransition' : 'MSTransitionEnd', 1391 | // 'transition' : 'transitionend' 1392 | // }, 1393 | // transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ]; 1394 | 1395 | Modernizr.prefixed = function(prop, obj, elem){ 1396 | if(!obj) { 1397 | return testPropsAll(prop, 'pfx'); 1398 | } else { 1399 | // Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame' 1400 | return testPropsAll(prop, obj, elem); 1401 | } 1402 | }; 1403 | /*>>prefixed*/ 1404 | 1405 | 1406 | /*>>cssclasses*/ 1407 | // Remove "no-js" class from element, if it exists: 1408 | docElement.className = docElement.className.replace(/(^|\s)no-js(\s|$)/, '$1$2') + 1409 | 1410 | // Add the new classes to the element. 1411 | (enableClasses ? ' js ' + classes.join(' ') : ''); 1412 | /*>>cssclasses*/ 1413 | 1414 | return Modernizr; 1415 | 1416 | })(this, this.document); 1417 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Scripts/respond.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 16 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ 17 | window.matchMedia = window.matchMedia || (function(doc, undefined){ 18 | 19 | var bool, 20 | docElem = doc.documentElement, 21 | refNode = docElem.firstElementChild || docElem.firstChild, 22 | // fakeBody required for 23 | fakeBody = doc.createElement('body'), 24 | div = doc.createElement('div'); 25 | 26 | div.id = 'mq-test-1'; 27 | div.style.cssText = "position:absolute;top:-100em"; 28 | fakeBody.style.background = "none"; 29 | fakeBody.appendChild(div); 30 | 31 | return function(q){ 32 | 33 | div.innerHTML = '­'; 34 | 35 | docElem.insertBefore(fakeBody, refNode); 36 | bool = div.offsetWidth == 42; 37 | docElem.removeChild(fakeBody); 38 | 39 | return { matches: bool, media: q }; 40 | }; 41 | 42 | })(document); 43 | 44 | 45 | 46 | 47 | /*! Respond.js v1.2.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ 48 | (function( win ){ 49 | //exposed namespace 50 | win.respond = {}; 51 | 52 | //define update even in native-mq-supporting browsers, to avoid errors 53 | respond.update = function(){}; 54 | 55 | //expose media query support flag for external use 56 | respond.mediaQueriesSupported = win.matchMedia && win.matchMedia( "only all" ).matches; 57 | 58 | //if media queries are supported, exit here 59 | if( respond.mediaQueriesSupported ){ return; } 60 | 61 | //define vars 62 | var doc = win.document, 63 | docElem = doc.documentElement, 64 | mediastyles = [], 65 | rules = [], 66 | appendedEls = [], 67 | parsedSheets = {}, 68 | resizeThrottle = 30, 69 | head = doc.getElementsByTagName( "head" )[0] || docElem, 70 | base = doc.getElementsByTagName( "base" )[0], 71 | links = head.getElementsByTagName( "link" ), 72 | requestQueue = [], 73 | 74 | //loop stylesheets, send text content to translate 75 | ripCSS = function(){ 76 | var sheets = links, 77 | sl = sheets.length, 78 | i = 0, 79 | //vars for loop: 80 | sheet, href, media, isCSS; 81 | 82 | for( ; i < sl; i++ ){ 83 | sheet = sheets[ i ], 84 | href = sheet.href, 85 | media = sheet.media, 86 | isCSS = sheet.rel && sheet.rel.toLowerCase() === "stylesheet"; 87 | 88 | //only links plz and prevent re-parsing 89 | if( !!href && isCSS && !parsedSheets[ href ] ){ 90 | // selectivizr exposes css through the rawCssText expando 91 | if (sheet.styleSheet && sheet.styleSheet.rawCssText) { 92 | translate( sheet.styleSheet.rawCssText, href, media ); 93 | parsedSheets[ href ] = true; 94 | } else { 95 | if( (!/^([a-zA-Z:]*\/\/)/.test( href ) && !base) 96 | || href.replace( RegExp.$1, "" ).split( "/" )[0] === win.location.host ){ 97 | requestQueue.push( { 98 | href: href, 99 | media: media 100 | } ); 101 | } 102 | } 103 | } 104 | } 105 | makeRequests(); 106 | }, 107 | 108 | //recurse through request queue, get css text 109 | makeRequests = function(){ 110 | if( requestQueue.length ){ 111 | var thisRequest = requestQueue.shift(); 112 | 113 | ajax( thisRequest.href, function( styles ){ 114 | translate( styles, thisRequest.href, thisRequest.media ); 115 | parsedSheets[ thisRequest.href ] = true; 116 | makeRequests(); 117 | } ); 118 | } 119 | }, 120 | 121 | //find media blocks in css text, convert to style blocks 122 | translate = function( styles, href, media ){ 123 | var qs = styles.match( /@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi ), 124 | ql = qs && qs.length || 0, 125 | //try to get CSS path 126 | href = href.substring( 0, href.lastIndexOf( "/" )), 127 | repUrls = function( css ){ 128 | return css.replace( /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, "$1" + href + "$2$3" ); 129 | }, 130 | useMedia = !ql && media, 131 | //vars used in loop 132 | i = 0, 133 | j, fullq, thisq, eachq, eql; 134 | 135 | //if path exists, tack on trailing slash 136 | if( href.length ){ href += "/"; } 137 | 138 | //if no internal queries exist, but media attr does, use that 139 | //note: this currently lacks support for situations where a media attr is specified on a link AND 140 | //its associated stylesheet has internal CSS media queries. 141 | //In those cases, the media attribute will currently be ignored. 142 | if( useMedia ){ 143 | ql = 1; 144 | } 145 | 146 | 147 | for( ; i < ql; i++ ){ 148 | j = 0; 149 | 150 | //media attr 151 | if( useMedia ){ 152 | fullq = media; 153 | rules.push( repUrls( styles ) ); 154 | } 155 | //parse for styles 156 | else{ 157 | fullq = qs[ i ].match( /@media *([^\{]+)\{([\S\s]+?)$/ ) && RegExp.$1; 158 | rules.push( RegExp.$2 && repUrls( RegExp.$2 ) ); 159 | } 160 | 161 | eachq = fullq.split( "," ); 162 | eql = eachq.length; 163 | 164 | for( ; j < eql; j++ ){ 165 | thisq = eachq[ j ]; 166 | mediastyles.push( { 167 | media : thisq.split( "(" )[ 0 ].match( /(only\s+)?([a-zA-Z]+)\s?/ ) && RegExp.$2 || "all", 168 | rules : rules.length - 1, 169 | hasquery: thisq.indexOf("(") > -1, 170 | minw : thisq.match( /\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ), 171 | maxw : thisq.match( /\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ) 172 | } ); 173 | } 174 | } 175 | 176 | applyMedia(); 177 | }, 178 | 179 | lastCall, 180 | 181 | resizeDefer, 182 | 183 | // returns the value of 1em in pixels 184 | getEmValue = function() { 185 | var ret, 186 | div = doc.createElement('div'), 187 | body = doc.body, 188 | fakeUsed = false; 189 | 190 | div.style.cssText = "position:absolute;font-size:1em;width:1em"; 191 | 192 | if( !body ){ 193 | body = fakeUsed = doc.createElement( "body" ); 194 | body.style.background = "none"; 195 | } 196 | 197 | body.appendChild( div ); 198 | 199 | docElem.insertBefore( body, docElem.firstChild ); 200 | 201 | ret = div.offsetWidth; 202 | 203 | if( fakeUsed ){ 204 | docElem.removeChild( body ); 205 | } 206 | else { 207 | body.removeChild( div ); 208 | } 209 | 210 | //also update eminpx before returning 211 | ret = eminpx = parseFloat(ret); 212 | 213 | return ret; 214 | }, 215 | 216 | //cached container for 1em value, populated the first time it's needed 217 | eminpx, 218 | 219 | //enable/disable styles 220 | applyMedia = function( fromResize ){ 221 | var name = "clientWidth", 222 | docElemProp = docElem[ name ], 223 | currWidth = doc.compatMode === "CSS1Compat" && docElemProp || doc.body[ name ] || docElemProp, 224 | styleBlocks = {}, 225 | lastLink = links[ links.length-1 ], 226 | now = (new Date()).getTime(); 227 | 228 | //throttle resize calls 229 | if( fromResize && lastCall && now - lastCall < resizeThrottle ){ 230 | clearTimeout( resizeDefer ); 231 | resizeDefer = setTimeout( applyMedia, resizeThrottle ); 232 | return; 233 | } 234 | else { 235 | lastCall = now; 236 | } 237 | 238 | for( var i in mediastyles ){ 239 | var thisstyle = mediastyles[ i ], 240 | min = thisstyle.minw, 241 | max = thisstyle.maxw, 242 | minnull = min === null, 243 | maxnull = max === null, 244 | em = "em"; 245 | 246 | if( !!min ){ 247 | min = parseFloat( min ) * ( min.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); 248 | } 249 | if( !!max ){ 250 | max = parseFloat( max ) * ( max.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); 251 | } 252 | 253 | // if there's no media query at all (the () part), or min or max is not null, and if either is present, they're true 254 | if( !thisstyle.hasquery || ( !minnull || !maxnull ) && ( minnull || currWidth >= min ) && ( maxnull || currWidth <= max ) ){ 255 | if( !styleBlocks[ thisstyle.media ] ){ 256 | styleBlocks[ thisstyle.media ] = []; 257 | } 258 | styleBlocks[ thisstyle.media ].push( rules[ thisstyle.rules ] ); 259 | } 260 | } 261 | 262 | //remove any existing respond style element(s) 263 | for( var i in appendedEls ){ 264 | if( appendedEls[ i ] && appendedEls[ i ].parentNode === head ){ 265 | head.removeChild( appendedEls[ i ] ); 266 | } 267 | } 268 | 269 | //inject active styles, grouped by media type 270 | for( var i in styleBlocks ){ 271 | var ss = doc.createElement( "style" ), 272 | css = styleBlocks[ i ].join( "\n" ); 273 | 274 | ss.type = "text/css"; 275 | ss.media = i; 276 | 277 | //originally, ss was appended to a documentFragment and sheets were appended in bulk. 278 | //this caused crashes in IE in a number of circumstances, such as when the HTML element had a bg image set, so appending beforehand seems best. Thanks to @dvelyk for the initial research on this one! 279 | head.insertBefore( ss, lastLink.nextSibling ); 280 | 281 | if ( ss.styleSheet ){ 282 | ss.styleSheet.cssText = css; 283 | } 284 | else { 285 | ss.appendChild( doc.createTextNode( css ) ); 286 | } 287 | 288 | //push to appendedEls to track for later removal 289 | appendedEls.push( ss ); 290 | } 291 | }, 292 | //tweaked Ajax functions from Quirksmode 293 | ajax = function( url, callback ) { 294 | var req = xmlHttp(); 295 | if (!req){ 296 | return; 297 | } 298 | req.open( "GET", url, true ); 299 | req.onreadystatechange = function () { 300 | if ( req.readyState != 4 || req.status != 200 && req.status != 304 ){ 301 | return; 302 | } 303 | callback( req.responseText ); 304 | } 305 | if ( req.readyState == 4 ){ 306 | return; 307 | } 308 | req.send( null ); 309 | }, 310 | //define ajax obj 311 | xmlHttp = (function() { 312 | var xmlhttpmethod = false; 313 | try { 314 | xmlhttpmethod = new XMLHttpRequest(); 315 | } 316 | catch( e ){ 317 | xmlhttpmethod = new ActiveXObject( "Microsoft.XMLHTTP" ); 318 | } 319 | return function(){ 320 | return xmlhttpmethod; 321 | }; 322 | })(); 323 | 324 | //translate CSS 325 | ripCSS(); 326 | 327 | //expose update for re-running respond later on 328 | respond.update = ripCSS; 329 | 330 | //adjust on resize 331 | function callMedia(){ 332 | applyMedia( true ); 333 | } 334 | if( win.addEventListener ){ 335 | win.addEventListener( "resize", callMedia, false ); 336 | } 337 | else if( win.attachEvent ){ 338 | win.attachEvent( "onresize", callMedia ); 339 | } 340 | })(this); 341 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Scripts/respond.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 16 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ 17 | window.matchMedia=window.matchMedia||(function(e,f){var c,a=e.documentElement,b=a.firstElementChild||a.firstChild,d=e.createElement("body"),g=e.createElement("div");g.id="mq-test-1";g.style.cssText="position:absolute;top:-100em";d.style.background="none";d.appendChild(g);return function(h){g.innerHTML='­';a.insertBefore(d,b);c=g.offsetWidth==42;a.removeChild(d);return{matches:c,media:h}}})(document); 18 | 19 | /*! Respond.js v1.2.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ 20 | (function(e){e.respond={};respond.update=function(){};respond.mediaQueriesSupported=e.matchMedia&&e.matchMedia("only all").matches;if(respond.mediaQueriesSupported){return}var w=e.document,s=w.documentElement,i=[],k=[],q=[],o={},h=30,f=w.getElementsByTagName("head")[0]||s,g=w.getElementsByTagName("base")[0],b=f.getElementsByTagName("link"),d=[],a=function(){var D=b,y=D.length,B=0,A,z,C,x;for(;B-1,minw:F.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:F.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}}j()},l,r,v=function(){var z,A=w.createElement("div"),x=w.body,y=false;A.style.cssText="position:absolute;font-size:1em;width:1em";if(!x){x=y=w.createElement("body");x.style.background="none"}x.appendChild(A);s.insertBefore(x,s.firstChild);z=A.offsetWidth;if(y){s.removeChild(x)}else{x.removeChild(A)}z=p=parseFloat(z);return z},p,j=function(I){var x="clientWidth",B=s[x],H=w.compatMode==="CSS1Compat"&&B||w.body[x]||B,D={},G=b[b.length-1],z=(new Date()).getTime();if(I&&l&&z-l-1?(p||v()):1)}if(!!J){J=parseFloat(J)*(J.indexOf(y)>-1?(p||v()):1)}if(!K.hasquery||(!A||!L)&&(A||H>=C)&&(L||H<=J)){if(!D[K.media]){D[K.media]=[]}D[K.media].push(k[K.rules])}}for(var E in q){if(q[E]&&q[E].parentNode===f){f.removeChild(q[E])}}for(var E in D){var M=w.createElement("style"),F=D[E].join("\n");M.type="text/css";M.media=E;f.insertBefore(M,G.nextSibling);if(M.styleSheet){M.styleSheet.cssText=F}else{M.appendChild(w.createTextNode(F))}q.push(M)}},n=function(x,z){var y=c();if(!y){return}y.open("GET",x,true);y.onreadystatechange=function(){if(y.readyState!=4||y.status!=200&&y.status!=304){return}z(y.responseText)};if(y.readyState==4){return}y.send(null)},c=(function(){var x=false;try{x=new XMLHttpRequest()}catch(y){x=new ActiveXObject("Microsoft.XMLHTTP")}return function(){return x}})();a();respond.update=a;function t(){j(true)}if(e.addEventListener){e.addEventListener("resize",t,false)}else{if(e.attachEvent){e.attachEvent("onresize",t)}}})(this); -------------------------------------------------------------------------------- /TeamsToDoAppConnector/TeamsAppPackages/color_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-sample-connector-csharp/158e4300a497c3225e4c884bdd9e0b27a811bdbb/TeamsToDoAppConnector/TeamsAppPackages/color_icon.png -------------------------------------------------------------------------------- /TeamsToDoAppConnector/TeamsAppPackages/manifest-CShar.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-sample-connector-csharp/158e4300a497c3225e4c884bdd9e0b27a811bdbb/TeamsToDoAppConnector/TeamsAppPackages/manifest-CShar.zip -------------------------------------------------------------------------------- /TeamsToDoAppConnector/TeamsAppPackages/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.7/MicrosoftTeams.schema.json", 3 | "manifestVersion": "1.7", 4 | "version": "1.0.0", 5 | "id": "d61ca023-5aa4-423c-8fd6-1a29d332616e", 6 | "packageName": "com.microsoft.teams.samples.sampleapp", 7 | "developer": { 8 | "name": "Microsoft", 9 | "websiteUrl": "https://www.microsoft.com", 10 | "privacyUrl": "https://www.microsoft.com/privacy", 11 | "termsOfUseUrl": "https://www.microsoft.com/termsofuse" 12 | }, 13 | "name": { 14 | "short": "TeamsToDo Connector in .NET/C#", 15 | "full": "TeamsToDo Connector in .NET/C# - full name" 16 | }, 17 | "description": { 18 | "short": "This is a small sample app we made for you, in .NET/C#!", 19 | "full": "This is a small sample app we made for you, in .NET/C#! This app has connector. The task content is hosted for illustrative purposes only." 20 | }, 21 | "icons": { 22 | "outline": "outline_icon.png", 23 | "color": "color_icon.png" 24 | }, 25 | "accentColor": "#3F487F", 26 | "connectors": [ 27 | { 28 | "connectorId": "d61ca023-5aa4-423c-8fd6-1a29d332616e", 29 | "configurationUrl": "https://teamstodoappconnectorwithinlineconfig.azurewebsites.net/Connector/Setup", 30 | "scopes": [ 31 | "team" 32 | ] 33 | } 34 | ], 35 | "permissions": [ 36 | "identity", 37 | "messageTeamMembers" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/TeamsAppPackages/outline_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-sample-connector-csharp/158e4300a497c3225e4c884bdd9e0b27a811bdbb/TeamsToDoAppConnector/TeamsAppPackages/outline_icon.png -------------------------------------------------------------------------------- /TeamsToDoAppConnector/TeamsToDoAppConnector.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | 10 | 11 | 2.0 12 | {DB064390-84D7-40E9-9389-F098ECC16AA4} 13 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 14 | Library 15 | Properties 16 | TeamsToDoAppConnector 17 | TeamsToDoAppConnector 18 | v4.6.1 19 | false 20 | true 21 | 22 | 3979 23 | enabled 24 | disabled 25 | false 26 | 27 | 28 | 29 | 30 | 31 | true 32 | full 33 | false 34 | bin\ 35 | DEBUG;TRACE 36 | prompt 37 | 4 38 | 39 | 40 | true 41 | pdbonly 42 | true 43 | bin\ 44 | TRACE 45 | prompt 46 | 4 47 | 48 | 49 | 50 | ..\packages\Bogus.20.0.2\lib\net40\Bogus.dll 51 | 52 | 53 | ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.5\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | True 73 | ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 74 | 75 | 76 | ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll 77 | 78 | 79 | 80 | 81 | ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll 82 | 83 | 84 | 85 | 86 | True 87 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll 88 | 89 | 90 | ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll 91 | 92 | 93 | ..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll 94 | 95 | 96 | True 97 | ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll 98 | 99 | 100 | ..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll 101 | 102 | 103 | True 104 | ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll 105 | 106 | 107 | True 108 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll 109 | 110 | 111 | True 112 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll 113 | 114 | 115 | True 116 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll 117 | 118 | 119 | True 120 | ..\packages\WebGrease.1.5.2\lib\WebGrease.dll 121 | 122 | 123 | True 124 | ..\packages\Antlr.3.4.1.9004\lib\Antlr3.Runtime.dll 125 | 126 | 127 | 128 | 129 | ..\packages\Microsoft.ApplicationInsights.2.2.0\lib\net45\Microsoft.ApplicationInsights.dll 130 | 131 | 132 | ..\packages\Microsoft.ApplicationInsights.Agent.Intercept.2.0.6\lib\net45\Microsoft.AI.Agent.Intercept.dll 133 | 134 | 135 | ..\packages\Microsoft.ApplicationInsights.DependencyCollector.2.2.0\lib\net45\Microsoft.AI.DependencyCollector.dll 136 | 137 | 138 | ..\packages\Microsoft.ApplicationInsights.PerfCounterCollector.2.2.0\lib\net45\Microsoft.AI.PerfCounterCollector.dll 139 | 140 | 141 | ..\packages\Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.2.2.0\lib\net45\Microsoft.AI.ServerTelemetryChannel.dll 142 | 143 | 144 | ..\packages\Microsoft.ApplicationInsights.WindowsServer.2.2.0\lib\net45\Microsoft.AI.WindowsServer.dll 145 | 146 | 147 | ..\packages\Microsoft.ApplicationInsights.Web.2.2.0\lib\net45\Microsoft.AI.Web.dll 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | Global.asax 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | PreserveNewest 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | Web.config 193 | 194 | 195 | Web.config 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 10.0 231 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | True 244 | True 245 | 52872 246 | / 247 | http://localhost:3978/ 248 | False 249 | False 250 | 251 | 252 | False 253 | 254 | 255 | 256 | 257 | 258 | 259 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 260 | 261 | 262 | 263 | 264 | 270 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Utils/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | namespace TeamsToDoAppConnector.Utils 4 | { 5 | /// 6 | /// Represents a class to stores settings for easy access. 7 | /// 8 | public static class AppSettings 9 | { 10 | public static string BaseUrl { get; set; } 11 | public static string ClientAppId { get; set; } 12 | 13 | static AppSettings() 14 | { 15 | BaseUrl = ConfigurationManager.AppSettings["BaseUrl"]; 16 | ClientAppId = ConfigurationManager.AppSettings["ClientAppId"]; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Utils/TaskHelper.cs: -------------------------------------------------------------------------------- 1 | using Bogus; 2 | using System; 3 | using System.Net.Http; 4 | using System.Net.Http.Headers; 5 | using System.Threading.Tasks; 6 | using TeamsToDoAppConnector.Models; 7 | 8 | namespace TeamsToDoAppConnector.Utils 9 | { 10 | public static class TaskHelper 11 | { 12 | 13 | public static TaskItem CreateTaskItem() 14 | { 15 | var faker = new Faker(); 16 | return new TaskItem() 17 | { 18 | Title = faker.Commerce.ProductName(), 19 | Description = faker.Lorem.Sentence(), 20 | Assigned = $"{faker.Name.FirstName()} {faker.Name.LastName()}", 21 | Guid = Guid.NewGuid().ToString() 22 | }; 23 | } 24 | 25 | public static async Task PostTaskNotification(string webhook, TaskItem item, string title) 26 | { 27 | string cardJson = GetConnectorCardJson(item, title); 28 | await PostCardAsync(webhook, cardJson); 29 | } 30 | 31 | public static async Task PostWelcomeMessage(string webhookUrl) 32 | { 33 | string cardJson = @"{ 34 | ""@type"": ""MessageCard"", 35 | ""summary"": ""Welcome Message"", 36 | ""sections"": [{ 37 | ""activityTitle"": ""Welcome Message"", 38 | ""text"": ""Teams ToDo connector has been set up. We will send you notification whenever new task is added in [Task Manager Portal]("+AppSettings.BaseUrl+ @" ).""}]}"; 39 | 40 | await PostCardAsync(webhookUrl, cardJson); 41 | } 42 | 43 | private static async Task PostCardAsync(string webhook, string cardJson) 44 | { 45 | //prepare the http POST 46 | HttpClient client = new HttpClient(); 47 | client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 48 | var content = new StringContent(cardJson, System.Text.Encoding.UTF8, "application/json"); 49 | using (var response = await client.PostAsync(webhook, content)) 50 | { 51 | // Check response.IsSuccessStatusCode and take appropriate action if needed. 52 | } 53 | } 54 | 55 | public static string GetConnectorCardJson(TaskItem task, string title) 56 | { 57 | //prepare the json payload 58 | return @" 59 | { 60 | 'summary': 'A task is added.', 61 | 'sections': [ 62 | { 63 | 'activityTitle': 'Task "+ title + @"!', 64 | 'facts': [ 65 | { 66 | 'name': 'Title:', 67 | 'value': '" + task.Title + @"' 68 | }, 69 | { 70 | 'name': 'Description:', 71 | 'value': '" + task.Description + @"' 72 | }, 73 | { 74 | 'name': 'Assigned To:', 75 | 'value': '" + task.Assigned + @"' 76 | } 77 | ] 78 | } 79 | ], 80 | 'potentialAction': [ 81 | { 82 | '@context': 'http://schema.org', 83 | '@type': 'ViewAction', 84 | 'name': 'View Task Details', 85 | 'target': [ 86 | '" + AppSettings.BaseUrl + "/task/detail/" + task.Guid + @"' 87 | ] 88 | }, 89 | { 90 | '@type': 'ActionCard', 91 | 'name': 'Update Title', 92 | 'inputs': [ 93 | { 94 | '@type': 'TextInput', 95 | 'id': 'title', 96 | 'isMultiline': true, 97 | 'title': 'Please enter new title' 98 | } 99 | ], 100 | 'actions': [ 101 | { 102 | '@type': 'HttpPOST', 103 | 'name': 'Update Title', 104 | 'isPrimary': true, 105 | 'target': '" + AppSettings.BaseUrl + "/task/update?id=" + task.Guid + @"', 106 | 'body': '{""Title"":""{{title.value}}""}', 107 | 'bodyContentType': 'application/json' 108 | } 109 | ] 110 | } 111 | ]}"; 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/Authentication/SimpleEnd.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 49 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/Authentication/SimpleStart.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 112 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/Connector/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model System.Web.Mvc.HandleErrorInfo 2 | @{ 3 | Layout = null; 4 | } 5 | 6 | 7 | 8 | 9 | 10 | Error 11 | 12 | 13 |
14 |

Error.

15 |

An error occurred while processing your request.

16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/Connector/MainSetup.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = ""; 3 | } 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Configuration Setup:

12 |
13 |
14 |
15 | Configuration Without Authentication 16 |
17 |
18 | Configuration With Authentication 19 |
20 |
21 | 22 |
23 | 24 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/Connector/Setup.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = ""; 3 | } 4 |

Send notifications when tasks are:

5 |
6 |
7 |
8 | Created 9 |
10 |
11 | Updated 12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | 73 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/Connector/SetupAuth.cshtml: -------------------------------------------------------------------------------- 1 |  2 |
3 |
4 | 10 | 11 | 20 | 21 |
22 |

Please Sign In:

23 | 24 | 25 |
26 | 27 | 28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 162 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Error 6 | 7 | 8 |
9 |

Error.

10 |

An error occurred while processing your request.

11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewBag.Title 7 | @Styles.Render("~/Content/css") 8 | @Scripts.Render("~/bundles/modernizr") 9 | 10 | 11 | @**@ 23 |
24 | @RenderBody() 25 | @*
*@ 26 | @*
27 |

© @DateTime.Now.Year - Teams ToDo App

28 |
*@ 29 |
30 | 31 | @Scripts.Render("~/bundles/jquery") 32 | @Scripts.Render("~/bundles/bootstrap") 33 | @RenderSection("scripts", required: false) 34 | 35 | 36 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/Task/Create.cshtml: -------------------------------------------------------------------------------- 1 |  2 | @{ 3 | ViewBag.Title = "Create"; 4 | } 5 | 6 | @model TeamsToDoAppConnector.Models.TaskItem 7 | 8 |

Create New Task

9 | @using (Html.BeginForm("create", "task", FormMethod.Post, new { enctype = "multipart/form-data" })) 10 | { 11 |
12 |
13 | 14 | @Html.EditorFor(model => model.Title, new { htmlAttributes = new { @class = "form-control" } }) 15 | @Html.ValidationMessageFor(model => model.Title, "", new { @class = "text-danger" }) 16 |
17 |
18 |
19 |
20 | 21 | @Html.EditorFor(model => model.Description, new { htmlAttributes = new { @class = "form-control" } }) 22 | @Html.ValidationMessageFor(model => model.Description, "", new { @class = "text-danger" }) 23 |
24 |
25 |
26 |
27 | 28 | @Html.EditorFor(model => model.Assigned, new { htmlAttributes = new { @class = "form-control" } }) 29 | @Html.ValidationMessageFor(model => model.Assigned, "", new { @class = "text-danger" }) 30 |
31 |
32 |
33 | 34 | @Html.ActionLink("Cancel", "My", new object() { }, new { @class = "btn btn-default" }) 35 |
36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/Task/Detail.cshtml: -------------------------------------------------------------------------------- 1 |  2 | @{ 3 | ViewBag.Title = "Detail"; 4 | } 5 | @model TeamsToDoAppConnector.Models.TaskItem 6 | 7 |

Task Details

8 | 9 |
10 |
11 |
12 |
13 | @Html.DisplayNameFor(model => model.Title) 14 |
15 | 16 |
17 | @Html.DisplayFor(model => model.Title) 18 |
19 | 20 |
21 | @Html.DisplayNameFor(model => model.Description) 22 |
23 | 24 |
25 | @Html.DisplayFor(model => model.Description) 26 |
27 | 28 |
29 | @Html.DisplayNameFor(model => model.Assigned) 30 |
31 | 32 |
33 | @Html.DisplayFor(model => model.Assigned) 34 |
35 |
36 |
37 |

38 | @Html.ActionLink("Back to List", "Index") 39 |

40 | 41 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/Task/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IList 2 | @{ 3 | ViewBag.Title = "Index"; 4 | } 5 | 6 |

Tasks

7 | 8 |

9 | @Html.ActionLink("Create New", "Create") 10 |

11 | 12 | 13 | 14 | 17 | 20 | 23 | 24 | 25 | 26 | @foreach (var item in Model) 27 | { 28 | 29 | 32 | 35 | 38 | 41 | 42 | } 43 | 44 |
15 | Task Name 16 | 18 | Description 19 | 21 | Assigned To 22 |
30 | @Html.DisplayFor(modelItem => item.Title) 31 | 33 | @Html.DisplayFor(modelItem => item.Description) 34 | 36 | @Html.DisplayFor(modelItem => item.Assigned) 37 | 39 | @Html.ActionLink("Details", "Detail", new { id = item.Guid }) 40 |
45 | 46 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Web.Debug.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Web.Release.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 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 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /TeamsToDoAppConnector/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-sample-connector-csharp/158e4300a497c3225e4c884bdd9e0b27a811bdbb/TeamsToDoAppConnector/favicon.ico -------------------------------------------------------------------------------- /TeamsToDoAppConnector/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-sample-connector-csharp/158e4300a497c3225e4c884bdd9e0b27a811bdbb/TeamsToDoAppConnector/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /TeamsToDoAppConnector/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-sample-connector-csharp/158e4300a497c3225e4c884bdd9e0b27a811bdbb/TeamsToDoAppConnector/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /TeamsToDoAppConnector/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/microsoft-teams-sample-connector-csharp/158e4300a497c3225e4c884bdd9e0b27a811bdbb/TeamsToDoAppConnector/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /TeamsToDoAppConnector/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | --------------------------------------------------------------------------------