From f87db1221dfad5ff106125c38c5dec411242c50c Mon Sep 17 00:00:00 2001 From: Bhargav Krishna Date: Fri, 6 Jan 2017 18:29:03 -0800 Subject: [PATCH] Base TodoMVC App using React --- .gitignore | 4 + LICENSE | 24 ++++ README.md | 146 ++++++++++++++++++++++ config/webpack.common.js | 56 +++++++++ config/webpack.dev.js | 24 ++++ config/webpack.prod.js | 35 ++++++ contributing.md | 191 ++++++++++++++++++++++++++++ index.html | 22 ++++ package.json | 47 +++++++ src/app.css | 60 +++++++++ src/app.tsx | 16 +++ src/core/constants.ts | 5 + src/core/index.ts | 4 + src/core/interfaces.ts | 57 +++++++++ src/core/todoModel.ts | 106 ++++++++++++++++ src/core/utils.ts | 45 +++++++ src/footer.tsx | 57 +++++++++ src/services/index.ts | 1 + src/services/outlook.tasks.ts | 229 ++++++++++++++++++++++++++++++++++ src/todoApp.tsx | 161 ++++++++++++++++++++++++ src/todoItem.tsx | 102 +++++++++++++++ src/vendor.ts | 12 ++ tsconfig.json | 20 +++ typings.json | 11 ++ webpack.config.js | 1 + 25 files changed, 1436 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 config/webpack.common.js create mode 100644 config/webpack.dev.js create mode 100644 config/webpack.prod.js create mode 100644 contributing.md create mode 100644 index.html create mode 100644 package.json create mode 100644 src/app.css create mode 100644 src/app.tsx create mode 100644 src/core/constants.ts create mode 100644 src/core/index.ts create mode 100644 src/core/interfaces.ts create mode 100644 src/core/todoModel.ts create mode 100644 src/core/utils.ts create mode 100644 src/footer.tsx create mode 100644 src/services/index.ts create mode 100644 src/services/outlook.tasks.ts create mode 100644 src/todoApp.tsx create mode 100644 src/todoItem.tsx create mode 100644 src/vendor.ts create mode 100644 tsconfig.json create mode 100644 typings.json create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d9ad07b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/** +typings/** +dist/** +.vscode/** \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..35d4afe --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +microsoft-teams-sample-todo + +Copyright (c) Microsoft Corporation. All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1639f72 --- /dev/null +++ b/README.md @@ -0,0 +1,146 @@ +# Microsoft Teams 'Todo List' sample tab app + +This is an example [tab app for Microsoft Teams](https://aka.ms/microsoftteamstabsplatform). The point of this sample to illustrate how simple it is to convert an existing web app into a Microsoft Teams tab app. The existing web app, [**TodoMVC for React**](https://github.com/tastejs/todomvc/tree/gh-pages/examples/typescript-react), provides a basic task manager which integrates with your personal Outlook Tasks. With only a few minor modifications, this web view can be added to a channel as a tab app. Take a look at the [code diff between the 'before' and 'after' branches](https://github.com/OfficeDev/microsoft-teams-sample-todo/compare/85ac809a2b52b528e8323a0a14e419afca21da12...9a1224eb276fa15a76f9e4882c4abe5ae8b68a99) to see what changes were made. + +> **Note:** This is not a realistic example of a team collaboration app. The tasks shown belong to the user's individual account and not to a shared team account. + +**For more information on developing experiences for Microsoft Teams, please review the Microsoft Teams [developer documentation](https://msdn.microsoft.com/en-us/microsoft-teams/index).** + +## Prerequisites + +1. An [Office 365 account with access to Microsoft Teams](https://msdn.microsoft.com/en-us/microsoft-teams/setup). +2. This sample is built using [Node.js](https://nodejs.org). Download and install the recommended version if you don't already have it. + +## Run the app + +### Host the tab apps "configuration page" and "content page" + +To enable the app in Dev mode: +1. Clone the repo. +2. Open a command line in the repo subdirectory. +3. Run `npm install` (included as part of Node.js) from the command line. +4. Run `npm start` to start the `webpack-dev-server` to enable the dev app to function. +5. Alternatively, run `npm run build` to generate a deployable build, which you can host in your own environment. Note: you will need to modify the config.url in the manifest to point to your hosting location. [More information](#registering-an-application-to-authenticate-with-microsoft) + +### Add the tab to Microsoft Teams + +1. Download the [tab app dev package](https://github.com/OfficeDev/microsoft-teams-sample-todo/raw/before-final/package/todo.dev.zip) zip file for this sample. +2. Create a new team for testing, if necessary. Click **Create team** at the bottom of the left-hand panel. +3. Select the team from the left-hand panel, select **... (more options)** and then select **View Team**. +4. Select the **Developer (Preview)** tab, and then select **Upload**. +5. Navigate to the downloaded zip file from step 1 above and select it. +6. Go to any channel in the team. Click the '+' to the right of the existing tabs. +7. Select your tab from the gallery that appears. +8. Accept the consent prompt. +9. If needed, sign in using your Office 365 work/school account. Note that the code will try to do silent authentication if possible. +10. Validate authentication information. +11. Hit Save to add the tab to channel. + +> **Note:** To re-upload an updated package, with the same `id`, click the 'Replace' icon at the end of the tab's table row. Don't click 'Upload' again: Microsoft Teams will say the tab already exists. + +> It is advisable to have multiple configs, one per environment. The names of the zip files can be anything such as `todo.dev.zip`, `todo.prod.zip` etc. but the zip must contain a `manifest.json` with a unique `id`. See [Creating a manifest for your tab](https://msdn.microsoft.com/en-us/microsoft-teams/createpackage). + +## Code walk through + +While the `master` branch shows the latest state of the sample, take a look at the following [code diff](https://github.com/OfficeDev/microsoft-teams-sample-todo/compare/85ac809a2b52b528e8323a0a14e419afca21da12...9a1224eb276fa15a76f9e4882c4abe5ae8b68a99) between: + +* [`before`](https://github.com/OfficeDev/microsoft-teams-sample-todo/commit/85ac809a2b52b528e8323a0a14e419afca21da12): the initial app + +* [`after`](https://github.com/OfficeDev/microsoft-teams-sample-todo/commit/9a1224eb276fa15a76f9e4882c4abe5ae8b68a99): the app after integration with Microsoft Teams. + +Going through this step by step: + +1. We have added a new `config.html` and `config.tsx` page which is responsible for the the application to allow the user to manipulate any settings, perform single signon Authentication etc. during the first launch. This is required so that the team administrator can configure the application/settings. See [Create the configuration page](https://msdn.microsoft.com/en-us/microsoft-teams/createconfigpage). + +2. We have added the same `config.html` file to our `webpack.common.js` configuration so that it can inject the right bundles during runtime. + +3. We add a reference to `MicrosoftTeams.js` in our index.html and added `MicrosoftTeams.d.ts` for Typescript intellisense. + +4. We have added a `manifest.dev.json`, `manifest.prod.json` and two logos for size *44x44* and *88x88*. Remember to rename these as `manifest.json` in your zip files that you upload to Microsoft Teams. + +5. We have added some styles to our `app.css`. + +6. Finally we have modified the `authentication` in outlook.tasks.ts to depend instead on 'useMicrosoftTeamsAuth', a new feature from the beta version of OfficeHelpers referenced in this sample. + +### Invoking the Authentication dialog + +When the user adds the tab, the configuration page is presented (config.html). In this case, the code authenticates the user if possible. + +Authentication leverages a new Teams-specific function in the latest (>0.4.0) version of the [office-js-helpers](https://github.com/OfficeDev/office-js-helpers) library. This helper function will attempt to silently authenticate, but if it cannot, it will call the Microsoft Teams specific auth dialog for you. For more information on the full Authentication process in Microsoft Teams, please review [Authenticating a user](https://msdn.microsoft.com/en-us/microsoft-teams/auth) in the Microsoft Teams [developer documentation](https://msdn.microsoft.com/en-us/microsoft-teams/index). + +In outlook.tasks.ts: +```typescript + return this.authenticator.authenticate('Microsoft') + .then(token => this._token = token) + .catch(error => { + Utilities.log(error); + throw new Error('Failed to login using your Microsoft Account'); + }); +``` + +### Handling the 'Save' event + +After successful sign-in, the user will Save the tab into the channel. The following code enables the Save button, and sets the SaveHandler, which will store what content to display in the tab (in this case, just the project's index.html). + +In config.tsx: +```typescript + initialize({ groupId, upn}) { + this.setState({ groupId, upn }); + console.log(this.state); + /** Enable the Save button */ + microsoftTeams.settings.setValidityState(true); + /** Register the save handler */ + microsoftTeams.settings.registerOnSaveHandler(saveEvent => { + /** Store Tab content settings */ + microsoftTeams.settings.setSettings({ + contentUrl: `${location.origin}/index.html`, + suggestedDisplayName: "My Tasks", + websiteUrl: `${location.origin}/index.html` + }); + saveEvent.notifySuccess(); + }); + } +``` + +## Technology used + + +It uses the following stack: + +1. [`React by Facebook`](https://facebook.github.io/react/) as the UI Framework. +2. [`TypeScript`](https://www.typescriptlang.org/) as the transpiler. +4. [`TodoMVC`](http://todomvc.com/examples/typescript-react/#/) base for TodoMVC functionality. +5. [`Webpack`](https://webpack.github.io/) as the build tool. + +## Additional Resources + +### Registering an application to authenticate with Microsoft +Note that this will not be necessary if you use the local Dev option above, but if you choose to host this tab in your own environment, you must register the application in order to authenticate. + +1. Go to the [Microsoft Application Registration Portal](https://apps.dev.microsoft.com). +2. Sign in with your Office 365 work/school account. Don't use your personal Microsoft account. +2. Add a new app. +2. Take note of your new `Application ID`. +2. Click on `Add Platform` and choose `Web`. +3. Check `Allow Implicit Flow` and configure the redirect URL to be `https:///config.html`. + +For more information on hosting your own tab pages, see the [Microsoft Teams 'Get Started' sample README](https://github.com/OfficeDev/microsoft-teams-sample-get-started#host-tab-pages-over-https). + +>**Note:** By defult, your organization should allow you to create new apps. But if it doesn't, you can use a one-year trial subscription of Office 365 Developer at no charge. [Here's how](https://msdn.microsoft.com/en-us/microsoft-teams/setup). + + +## Credits + +This project is based on the TodoMVC Typescript - React template located [here](https://github.com/tastejs/todomvc/tree/gh-pages/examples/typescript-react). + +## Contributing + +Please read [Contributing](contributing.md) for details on our code of conduct, and the process for submitting pull requests to us. + +## Versioning + +We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/officedev/microsoft-teams-sample-todo/tags). + +## License + +This project is licensed under the MIT License - see the [License](LICENSE) file for details \ No newline at end of file diff --git a/config/webpack.common.js b/config/webpack.common.js new file mode 100644 index 0000000..77d83fa --- /dev/null +++ b/config/webpack.common.js @@ -0,0 +1,56 @@ +var path = require('path'); +var webpack = require('webpack'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); + +module.exports = { + entry: { + 'app': './src/app.tsx', + 'vendor': './src/vendor.ts' + }, + + // Enable sourcemaps for debugging webpack's output. + devtool: 'source-map', + + resolve: { + // Add '.ts' and '.tsx' as resolvable extensions. + extensions: ['', '.css', '.ts', '.tsx', '.js', '.jsx'] + }, + + module: { + loaders: [ + // All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'. + { + test: /\.tsx?$/, + loader: 'ts-loader', + exclude: /node_modules/ + }, + + // All files with a '.css' extension will be handled by 'extract-text-plugin and css-loader'. + { + test: /\.css$/, + loader: ExtractTextPlugin.extract('css') + } + ], + + preLoaders: [ + // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. + { test: /\.js$/, loader: 'source-map-loader' } + ] + }, + + plugins: [ + new HtmlWebpackPlugin({ + title: 'React • TodoMVC', + filename: 'index.html', + template: path.resolve('index.html'), + chunks: ['app', 'vendor'] + }), + + new ExtractTextPlugin('[name].css'), + + new webpack.ProvidePlugin({ + Router: 'director' + }) + ] +}; \ No newline at end of file diff --git a/config/webpack.dev.js b/config/webpack.dev.js new file mode 100644 index 0000000..cc7052a --- /dev/null +++ b/config/webpack.dev.js @@ -0,0 +1,24 @@ +var path = require('path'); +var webpack = require('webpack'); +var webpackMerge = require('webpack-merge'); +var commonConfig = require('./webpack.common.js'); + +module.exports = webpackMerge(commonConfig, { + output: { + path: path.resolve('dist'), + filename: '[name].[hash].js', + chunkFilename: '[id].chunk.js', + sourceMapFilename: '[name].[hash].map' + }, + + devServer: { + https: true, + historyApiFallback: true, + stats: 'minimal', + watchOptions: { + aggregateTimeout: 300, + poll: 1000 + }, + outputPath: path.resolve('dist') + } +}); \ No newline at end of file diff --git a/config/webpack.prod.js b/config/webpack.prod.js new file mode 100644 index 0000000..6c23368 --- /dev/null +++ b/config/webpack.prod.js @@ -0,0 +1,35 @@ +var path = require('path'); +var webpack = require('webpack'); +var webpackMerge = require('webpack-merge'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); +var commonConfig = require('./webpack.common.js'); + +const ENV = process.env.NODE_ENV = process.env.ENV = 'production'; + +module.exports = webpackMerge(commonConfig, { + devtool: 'source-map', + + externals: { + 'react': 'React', + 'react-dom': 'ReactDOM' + }, + + output: { + path: path.resolve('dist'), + filename: '[name].js', + chunkFilename: '[id].chunk.js', + sourceMapFilename: '[name].map' + }, + + plugins: [ + new webpack.NoErrorsPlugin(), + new webpack.optimize.DedupePlugin(), + new webpack.optimize.UglifyJsPlugin(), + new ExtractTextPlugin('[name].css'), + new webpack.DefinePlugin({ + 'process.env': { + 'ENV': JSON.stringify(ENV) + } + }) + ] +}); \ No newline at end of file diff --git a/contributing.md b/contributing.md new file mode 100644 index 0000000..bc3e1d9 --- /dev/null +++ b/contributing.md @@ -0,0 +1,191 @@ +# Contribute to this helper + +Thank you for your interest in our sample + +* [Ways to contribute](#ways-to-contribute) +* [To contribute using Git](#to-contribute-using-git) +* [Contribute code](#contribute-code) +* [FAQ](#faq) +* [More resources](#more-resources) + +## Ways to contribute + +Here are some ways you can contribute to this sample: + +* Add better comments to the sample code. +* Fix issues opened in GitHub against this sample. +* Add a new feature to the sample. + +We want your contributions. Help the developer community by improving this sample. +Submit code comment contributions where you want a better explanation of what's going on. + +Another great way to improve the sample in this repository is to take on some of the open issues filed against the repository. You may have a solution to an bug in the sample code that hasn't been addressed. Fix the issue and then create a pull request following our [Contribute code](#contribute-code) guidance. + +If you want to add a new feature to the sample, be sure you have the agreement of the repository owner before writing the code. Start by opening an issue in the repository. Use the new issue to propose the feature. The repository owner will respond and will usually ask you for more information. When the owner agrees to take the new feature, code it and submit a pull request. + +## To contribute using Git +For most contributions, you'll be asked to sign a Contribution License Agreement (CLA). For those contributions that need it, The Office 365 organization on GitHub will send a link to the CLA that we want you to sign via email. +By signing the CLA, you acknowledge the rights of the GitHub community to use any code that you submit. The intellectual property represented by the code contribution is licensed for use by Microsoft open source projects. + +If Office 365 emails a CLA to you, you need to sign it before you can contribute large submissions to a project. You only need to complete and submit it once. +Read the CLA carefully. You may need to have your employer sign it. + +Signing the CLA does not grant you rights to commit to the main repository, but it does mean that the Office Developer and Office Developer Content Publishing teams will be able to review and approve your contributions. You will be credited for your submissions. + +Pull requests are typically reviewed within 10 business days. + +## Use GitHub, Git, and this repository + +**Note:** Most of the information in this section can be found in [GitHub Help] articles. If you're familiar with Git and GitHub, skip to the **Contribute code** section for the specifics of the code contributions for this repository. + +### To set up your fork of the repository + +1. Set up a GitHub account so you can contribute to this project. If you haven't done this, go to [GitHub](https://github.com/join) and do it now. +2. Install Git on your computer. Follow the steps in the [Setting up Git Tutorial] [Set Up Git]. +3. Create your own fork of this repository. To do this, at the top of the page, choose the **Fork** button. +4. Copy your fork to your computer. To do this, open Git Bash. At the command prompt enter: + + git clone https://github.com//.git + + Next, create a reference to the root repository by entering these commands: + + cd + git remote add upstream https://github.com/OfficeDev/.git + git fetch upstream + +Congratulations! You've now set up your repository. You won't need to repeat these steps again. + +## Contribute code + +To make the contribution process as seamless as possible, follow these steps. + +### To contribute code + +1. Create a new branch. +2. Add new code or modify existing code. +3. Submit a pull request to the main repository. +4. Await notification of acceptance and merge. +5. Delete the branch. + + +### To create a new branch + +1. Open Git Bash. +2. At the Git Bash command prompt, type `git pull upstream master:`. This creates a new branch locally that is copied from the latest OfficeDev master branch. +3. At the Git Bash command prompt, type `git push origin `. This alerts GitHub to the new branch. You should now see the new branch in your fork of the repository on GitHub. +4. At the Git Bash command prompt, type `git checkout ` to switch to your new branch. + +### Add new code or modify existing code + +Navigate to the repository on your computer. On a Windows PC, the repository files are in `C:\Users\\`. + +Use the IDE of your choice to modify and build the sample. Once you have completed your change, commented your code, and test, check the code +into the remote branch on GitHub. + +#### Code contribution checklist +Be sure to satisfy all of the requirements in the following list before submitting a pull request: + + +- Follow the code style found in the cloned repository code. Our Android code follows the style conventions found in the [Code Style for Contributors](https://source.android.com/source/code-style.html) guide. +- Code must be tested. +- Test the sample UI thoroughly to be sure nothing has been broken by your change. +- Keep the size of your code change reasonable. If the repository owner cannot review your code change in 4 hours or less, your pull request may not be reviewed and approved quickly. +- Avoid unnecessary changes to cloned or forked code. The reviewer will use a tool to find the differences between your code and the original code. Whitespace changes are called out along with your code. Be sure your changes will help improve the content. + +### Push your code to the remote GitHub branch +The files in `C:\Users\\` are a working copy of the new branch that you created in your local repository. Changing anything in this folder doesn't affect the local repository until you commit a change. To commit a change to the local repository, type the following commands in GitBash: + + git add . + git commit -v -a -m "" + +The `add` command adds your changes to a staging area in preparation for committing them to the repository. The period after the `add` command specifies that you want to stage all of the files that you added or modified, checking subfolders recursively. (If you don't want to commit all of the changes, you can add specific files. You can also undo a commit. For help, type `git add -help` or `git status`.) + +The `commit` command applies the staged changes to the repository. The switch `-m` means you are providing the commit comment in the command line. The -v and -a switches can be omitted. The -v switch is for verbose output from the command, and -a does what you already did with the add command. + +You can commit multiple times while you are doing your work, or you can commit once when you're done. + +### Submit a pull request to the master repository + +When you're finished with your work and are ready to have it merged into the master repository, follow these steps. + +#### To submit a pull request to the master repository + +1. In the Git Bash command prompt, type `git push origin `. In your local repository, `origin` refers to your GitHub repository that you cloned the local repository from. This command pushes the current state of your new branch, including all commits made in the previous steps, to your GitHub fork. +2. On the GitHub site, navigate in your fork to the new branch. +3. Choose the **Pull Request** button at the top of the page. +4. Verify the Base branch is `OfficeDev/@master` and the Head branch is `/@`. +5. Choose the **Update Commit Range** button. +6. Add a title to your pull request, and describe all the changes you're making. +7. Submit the pull request. + +One of the site administrators will process your pull request. Your pull request will surface on the `OfficeDev/` site under Issues. When the pull request is accepted, the issue will be resolved. + +### Repository owner code review +The owner of the repository will review your pull request to be sure that all requirements are met. If the reviewer +finds any issues, she will communicate with you and ask you to address them and then submit a new pull request. If your pull +request is accepted, then the repository owner will tell you that your pull request is to be merged. + +### Create a new branch after merge + +After a branch is successfully merged (that is, your pull request is accepted), don't continue working in that local branch. This can lead to merge conflicts if you submit another pull request. To do another update, create a new local branch from the successfully merged upstream branch, and then delete your initial local branch. + +For example, if your local branch X was successfully merged into the OfficeDev/O365-Android-Microsoft-Graph-Connect master branch and you want to make additional updates to the code that was merged. Create a new local branch, X2, from the OfficeDev/O365-Android-Microsoft-Graph-Connect branch. To do this, open GitBash and execute the following commands: + + cd + git pull upstream master:X2 + git push origin X2 + +You now have local copies (in a new local branch) of the work that you submitted in branch X. The X2 branch also contains all the work other developers have merged, so if your work depends on others' work (for example, a base class), it is available in the new branch. You can verify that your previous work (and others' work) is in the branch by checking out the new branch... + + git checkout X2 + +...and verifying the code. (The `checkout` command updates the files in `C:\Users\\O365-Android-Microsoft-Graph-Connect` to the current state of the X2 branch.) Once you check out the new branch, you can make updates to the code and commit them as usual. However, to avoid working in the merged branch (X) by mistake, it's best to delete it (see the following **Delete a branch** section). + +### Delete a branch + +Once your changes are successfully merged into the main repository, delete the branch you used because you no longer need it. Any additional work should be done in a new branch. + +#### To delete a branch + +1. In the Git Bash command prompt, type `git checkout master`. This ensures that you aren't in the branch to be deleted (which isn't allowed). +2. Next, at the command prompt, type `git branch -d `. This deletes the branch on your computer only if it has been successfully merged to the upstream repository. (You can override this behavior with the `-D` flag, but first be sure you want to do this.) +3. Finally, type `git push origin :` at the command prompt (a space before the colon and no space after it). This will delete the branch on your github fork. + +Congratulations, you have successfully contributed to the sample app! + + +## FAQ + +### How do I get a GitHub account? + +Fill out the form at [Join GitHub](https://github.com/join) to open a free GitHub account. + +### Where do I get a Contributor's License Agreement? + +You will automatically be sent a notice that you need to sign the Contributor's License Agreement (CLA) if your pull request requires one. + +As a community member, **you must sign the CLA before you can contribute large submissions to this project**. You only need complete and submit the CLA document once. Carefully review the document. You may be required to have your employer sign the document. + +### What happens with my contributions? + +When you submit your changes, via a pull request, our team will be notified and will review your pull request. You will receive notifications about your pull request from GitHub; you may also be notified by someone from our team if we need more information. If your pull request is approved, we'll update the documentation on GitHub and on MSDN. We reserve the right to edit your submission for legal, style, clarity, or other issues. + +### Who approves pull requests? + +The owner of the sample repository approves pull requests. + +### How soon will I get a response about my change request? + +Pull requests are typically reviewed within 10 business days. + + +## More resources + +* To learn more about Markdown, go to the Git creator's site [Daring Fireball]. +* To learn more about using Git and GitHub, check out the [GitHub Help section] [GitHub Help]. + +[GitHub Home]: http://github.com +[GitHub Help]: http://help.github.com/ +[Set Up Git]: http://help.github.com/win-set-up-git/ +[Markdown Home]: http://daringfireball.net/projects/markdown/ +[Daring Fireball]: http://daringfireball.net/ \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..3f653b8 --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + + + React • TodoMVC + + + +
+ + + + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..d0797d3 --- /dev/null +++ b/package.json @@ -0,0 +1,47 @@ +{ + "name": "microsoft-teams-sample-todo", + "version": "1.0.0", + "author": { + "name": "Microsoft" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/OfficeDev/microsoft-teams-sample-todo.git" + }, + "bugs": { + "url": "https://github.com/OfficeDev/microsoft-teams-sample-todo/issues" + }, + "homepage": "https://github.com/OfficeDev/microsoft-teams-sample-todo#readme", + "private": true, + "scripts": { + "build": "rimraf dist && webpack --config config/webpack.prod.js --colors --display-modules --progress", + "start": "webpack-dev-server --port 3000 --config config/webpack.dev.js --colors --display-modules --progress --open --inline", + "postinstall": "typings install" + }, + "dependencies": { + "classnames": "^2.2.5", + "director": "^1.2.8", + "react": "^15.3.2", + "react-dom": "^15.3.2", + "todomvc-app-css": "^2.0.6", + "todomvc-common": "^1.0.2", + "office-ui-fabric-core": "^4.1.0", + "office-ui-fabric-js": "^1.1.0", + "core-js": "^2.4.1", + "@microsoft/office-js-helpers": "^0.4.1", + "whatwg-fetch": "^1.0.0" + }, + "devDependencies": { + "css-loader": "^0.25.0", + "extract-text-webpack-plugin": "^1.0.1", + "html-webpack-plugin": "^2.22.0", + "rimraf": "^2.5.4", + "source-map-loader": "^0.1.5", + "ts-loader": "^0.9.5", + "typescript": "^2.0.3", + "typings": "^1.4.0", + "webpack": "^1.13.2", + "webpack-dev-server": "^1.16.2", + "webpack-merge": "^0.15.0" + } +} \ No newline at end of file diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..c85272d --- /dev/null +++ b/src/app.css @@ -0,0 +1,60 @@ +body { + background-color: #004578; +} + +.todoapp h1 { + color: #c7e0f4; +} + +.info { + text-shadow: none; + color: #c7e0f4; +} + +.ms-font-xl { + font-weight: 400; +} + +.todo-list li .toggle:after, +.todo-list li .toggle:checked::after, +input[type=checkbox].toggle-all::before { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-family: 'FabricMDL2Icons'; + font-style: normal; + font-weight: normal; + speak: none; + content: '\E73A'; + padding: 10px 20px 10px 20px; + font-size: 19pt; +} + +.todo-list li .toggle:after { + content: '\E739'; + padding: 10px 20px 10px 12px; + color: rgba(0, 0, 0, 0.1); +} + +.todo-list li .toggle:checked::after { + padding: 10px 20px 10px 12px; + color: #737373; +} + +/* Remove the safari hack on ToDoMVC */ +@media screen and (-webkit-min-device-pixel-ratio: 0) { + .toggle-all { + -webkit-transform: rotate(90deg); + transform: none; + -webkit-appearance: none; + appearance: none; + } +} + +button.clear-completed { + color: #e81123; +} + +.filters li a.selected { + border-color: #2b88d8; +} \ No newline at end of file diff --git a/src/app.tsx b/src/app.tsx new file mode 100644 index 0000000..61a3eee --- /dev/null +++ b/src/app.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { TodoApp } from './todoApp'; +import { TodoModel } from './core'; + +var model = new TodoModel('react-todos'); + +function render() { + ReactDOM.render( + , + document.getElementsByClassName('todoapp')[0] + ); +} + +model.subscribe(render); +render(); \ No newline at end of file diff --git a/src/core/constants.ts b/src/core/constants.ts new file mode 100644 index 0000000..403227b --- /dev/null +++ b/src/core/constants.ts @@ -0,0 +1,5 @@ +export const ALL_TODOS = 'all'; +export const ACTIVE_TODOS = 'active'; +export const COMPLETED_TODOS = 'completed'; +export const ENTER_KEY = 13; +export const ESCAPE_KEY = 27; \ No newline at end of file diff --git a/src/core/index.ts b/src/core/index.ts new file mode 100644 index 0000000..140992c --- /dev/null +++ b/src/core/index.ts @@ -0,0 +1,4 @@ +export * from './constants'; +export * from './interfaces'; +export * from './todoModel'; +export * from './utils'; \ No newline at end of file diff --git a/src/core/interfaces.ts b/src/core/interfaces.ts new file mode 100644 index 0000000..2de8a1c --- /dev/null +++ b/src/core/interfaces.ts @@ -0,0 +1,57 @@ +export interface ITodo { + id: string, + title: string, + completed: boolean, + importance?: string; +} + +export interface IProfile { + displayName: string, + mail: string, + thumbnail: string; +} + +export interface ITodoItemProps { + key: string, + todo: ITodo; + editing?: boolean; + onSave: (val: any) => void; + onDestroy: () => void; + onEdit: () => void; + onCancel: (event: any) => void; + onToggle: () => void; +} + +export interface ITodoItemState { + editText: string +} + +export interface ITodoFooterProps { + completedCount: number; + onClearCompleted: any; + nowShowing: string; + count: number; +} + +export interface ITodoModel { + key: any; + todos: Array; + onChanges: Array; + subscribe(onChange); + inform(); + addTodo(title: string); + toggleAll(checked); + toggle(todoToToggle); + destroy(todo); + save(todoToSave, text); + clearCompleted(); +} + +export interface IAppProps { + model: ITodoModel; +} + +export interface IAppState { + editing?: string; + nowShowing?: string +} \ No newline at end of file diff --git a/src/core/todoModel.ts b/src/core/todoModel.ts new file mode 100644 index 0000000..c7a6779 --- /dev/null +++ b/src/core/todoModel.ts @@ -0,0 +1,106 @@ +import { Utils } from './utils'; +import { ITodo, ITodoModel } from './interfaces'; +import { OutlookTasks } from '../services'; + +// Generic 'model' object. You can use whatever +// framework you want. For this application it +// may not even be worth separating this logic +// out, but we do this to demonstrate one way to +// separate out parts of your application. +export class TodoModel implements ITodoModel { + + public key: string; + public todos: Array; + public onChanges: Array; + private _outlookTasks: OutlookTasks; + + constructor(key) { + this.key = key; + this._outlookTasks = new OutlookTasks(); + this.todos = []; + this._outlookTasks.get().then(tasks => { + this.todos = tasks; + this.inform(); + }) + this.onChanges = []; + } + + public subscribe(onChange) { + this.onChanges.push(onChange); + } + + public inform() { + this.onChanges.forEach(function (cb) { cb(); }); + } + + public addTodo(title: string) { + let todo: ITodo = { + id: Utils.uuid(), + title: title, + completed: false + }; + + this._outlookTasks.create(todo).then(todo => { + this.todos = this.todos.concat(todo); + this.inform(); + }); + } + + public toggleAll(checked: Boolean) { + // Note: It's usually better to use immutable data structures since they're + // easier to reason about and React works very well with them. That's why + // we use map(), filter() and reduce() everywhere instead of mutating the + // array or todo items themselves. + this.todos = this.todos.map((todo: ITodo) => { + var update = Utils.extend({}, todo, { completed: checked }); + this._outlookTasks.update(update); + return update; + }); + + this.inform(); + } + + public toggle(todoToToggle: ITodo) { + this.todos = this.todos.map((todo: ITodo) => { + return todo !== todoToToggle ? + todo : (() => { + var update = Utils.extend({}, todo, { completed: !todo.completed }); + this._outlookTasks.update(update); + return update; + })(); + }); + + this.inform(); + } + + public destroy(todo: ITodo) { + this._outlookTasks.delete(todo).then(result => { + if (result) { + this.todos = this.todos.filter(function (candidate) { + return candidate !== todo; + }); + } + + this.inform(); + }); + } + + public save(todoToSave: ITodo, text: string) { + this.todos = this.todos.map(function (todo) { + return todo !== todoToSave ? todo : Utils.extend({}, todo, { title: text }); + }); + + this.inform(); + } + + public clearCompleted() { + this.todos = this.todos.filter(todo => { + if (todo.completed) { + this._outlookTasks.delete(todo); + } + return !todo.completed; + }); + + this.inform(); + } +} \ No newline at end of file diff --git a/src/core/utils.ts b/src/core/utils.ts new file mode 100644 index 0000000..802c8e3 --- /dev/null +++ b/src/core/utils.ts @@ -0,0 +1,45 @@ +export class Utils { + + public static uuid(): string { + /*jshint bitwise:false */ + var i, random; + var uuid = ''; + + for (i = 0; i < 32; i++) { + random = Math.random() * 16 | 0; + if (i === 8 || i === 12 || i === 16 || i === 20) { + uuid += '-'; + } + uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)) + .toString(16); + } + + return uuid; + } + + public static pluralize(count: number, word: string) { + return count === 1 ? word : word + 's'; + } + + public static store(namespace: string, data?: any) { + if (data) { + return localStorage.setItem(namespace, JSON.stringify(data)); + } + + var store = localStorage.getItem(namespace); + return (store && JSON.parse(store)) || []; + } + + public static extend(...objs: any[]): any { + var newObj = {}; + for (var i = 0; i < objs.length; i++) { + var obj = objs[i]; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + newObj[key] = obj[key]; + } + } + } + return newObj; + } +} \ No newline at end of file diff --git a/src/footer.tsx b/src/footer.tsx new file mode 100644 index 0000000..0fe7f08 --- /dev/null +++ b/src/footer.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import * as classNames from 'classnames'; +import { ALL_TODOS, ACTIVE_TODOS, COMPLETED_TODOS, ITodoFooterProps, Utils } from './core'; + +export class TodoFooter extends React.Component { + + public render() { + var activeTodoWord = Utils.pluralize(this.props.count, 'item'); + var clearButton = null; + + if (this.props.completedCount > 0) { + clearButton = ( + + ); + } + + const nowShowing = this.props.nowShowing; + return ( + + ); + } +} \ No newline at end of file diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..6a8af20 --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1 @@ +export * from './outlook.tasks'; \ No newline at end of file diff --git a/src/services/outlook.tasks.ts b/src/services/outlook.tasks.ts new file mode 100644 index 0000000..9a3cef5 --- /dev/null +++ b/src/services/outlook.tasks.ts @@ -0,0 +1,229 @@ +import { ITodo, IProfile } from '../core'; + +/** + * We are using OfficeHelpers library as it allows us to complete authentication + * with relative ease and also provides other useful Utilities. + * + * Note: We have included a beta version of OfficeHelpers that has support for + * MicrosoftTeams and the API signatures might change when OfficeHelpers for + * Microsoft Teams releases. + */ +import { Authenticator, IToken, Utilities } from '@microsoft/office-js-helpers'; + +interface ITodoService { + authenticator: Authenticator; + login: () => Promise; + get: () => Promise; + create: (todo: ITodo) => Promise; + delete: (todo: ITodo) => Promise; + markAsComplete: (todo: ITodo) => Promise; +} + +export class OutlookTasks implements ITodoService { + private _token: IToken; + private _baseUrl: string = 'https://outlook.office.com/api/v2.0'; + private _login: boolean; + + authenticator: Authenticator; + + /** + * Note: This is a demo clientID and can be removed/deactivated + * at any point of time. Please modify this to use your own + * clientId. + */ + clientId = 'bd9464a2-8b29-4165-a3c8-4d4dfd64b59a'; + + constructor() { + + /** + * OfficeHelpers provides a authentication utility that does Implicit OAuth + * in simple steps and supports Microsoft, AzureAD, Google, Facebook out of the box. + * + * Note: We have included a beta version of OfficeHelpers that has support for + * MicrosoftTeams and the API signatures might change when OfficeHelpers for + * Microsoft Teams releases. + * + * If you wish to achieve the same authentication call without OfficeHelpers, + * then you would need to call the following Microsoft Teams API manually and + * handle token mangement yourself. + * + * microsoftTeams.authentication.authenticate({ + * url: , + * width: , + * height: , + * successCallback: , + * failureCallback: + * }); + * + * Inside the dialog you can call + * microsoftTeams.authentication.notifySuccess() + * OR + * microsoftTeams.authentication.notifyFailure() + * accordingly. + */ + this.authenticator = new Authenticator(); + this.authenticator.endpoints.registerMicrosoftAuth(this.clientId, { + baseUrl: 'https://login.microsoftonline.com/organizations/oauth2/v2.0', + scope: 'https://outlook.office.com/tasks.readwrite' + }); + + + this._token = this.authenticator.tokens.get('Microsoft'); + if (this._token == null) { + this.login(); + } + } + + async login(): Promise { + /** + * Read the comments above to achieve Authentication without depending on + * the OfficeHelpers library. + */ + try { + this._token = await this.authenticator.authenticate('Microsoft', false); + return this._token; + } + catch (error) { + Utilities.log(error); + throw new Error('Failed to login using your Microsoft Account'); + }; + } + + logout() { + this.authenticator.tokens.clear(); + } + + async get(): Promise { + try { + let res = await fetch(`${this._baseUrl}/me/tasks`, + { + headers: { + 'Authorization': `Bearer ${this._token.access_token}` + } + }); + + res = await res.json(); + let tasks = this._checkForErrors(res); + return tasks.value.map(this._convertTask); + } + catch (error) { + Utilities.log(error); + throw new Error('Failed to get your todos'); + }; + } + + async create(todo: ITodo): Promise { + try { + let res = await fetch(`${this._baseUrl}/me/tasks`, + { + method: "POST", + body: JSON.stringify({ + Subject: todo.title, + Status: todo.completed ? 'Completed' : 'NotStarted', + Importance: todo.importance == null ? 'normal' : todo.importance + }), + headers: { + 'Authorization': `Bearer ${this._token.access_token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + res = await res.json(); + let task = this._checkForErrors(res); + return this._convertTask(task); + } + catch (error) { + Utilities.log(error); + throw new Error('Failed to create your todo'); + }; + } + + async update(todo: ITodo): Promise { + try { + let res = await fetch(`${this._baseUrl}/me/tasks('${todo.id}')`, + { + method: "PATCH", + body: JSON.stringify({ + Subject: todo.title, + Status: todo.completed ? 'Completed' : 'NotStarted', + Importance: todo.importance == null ? 'normal' : todo.importance + }), + headers: { + 'Authorization': `Bearer ${this._token.access_token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + res = await res.json(); + let task = this._checkForErrors(res); + return this._convertTask(task); + } + catch (error) { + Utilities.log(error); + throw new Error('Failed to create your todo'); + }; + } + + async delete(todo: ITodo): Promise { + try { + let res = await fetch(`${this._baseUrl}/me/tasks('${todo.id}')`, + { + method: "DELETE", + headers: { + 'Authorization': `Bearer ${this._token.access_token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + if (res.status == 204) { + return this._checkForErrors(res); + } + } + catch (error) { + Utilities.log(error); + throw new Error('Failed to delete your todo'); + }; + } + + async markAsComplete(todo: ITodo): Promise { + try { + let res = await fetch(`${this._baseUrl}/me/tasks('${todo.id}')/complete`, + { + method: "POST", + headers: { + 'Authorization': `Bearer ${this._token.access_token}`, + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + }); + + res = await res.json(); + let task = this._checkForErrors(res); + return this._convertTask(task); + } + catch (error) { + Utilities.log(error); + throw new Error('Failed to mark your todo as complete'); + } + } + + private _convertTask(task: any): ITodo { + return { + id: task.Id, + title: task.Subject, + completed: task.Status === 'Completed', + importance: task.Importance && task.Importance.toLowerCase() + } as ITodo; + } + + private _checkForErrors = response => { + if (response.error) { + throw new Error(response.error.message) + } + + return response; + } +} \ No newline at end of file diff --git a/src/todoApp.tsx b/src/todoApp.tsx new file mode 100644 index 0000000..6630242 --- /dev/null +++ b/src/todoApp.tsx @@ -0,0 +1,161 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { TodoFooter } from "./footer"; +import { TodoItem } from "./todoItem"; +import { ITodo, IAppProps, IAppState, TodoModel, ALL_TODOS, ACTIVE_TODOS, COMPLETED_TODOS, ENTER_KEY } from './core'; + +declare var Router; + +export class TodoApp extends React.Component { + + public state: IAppState; + + constructor(props: IAppProps) { + super(props); + this.state = { + nowShowing: ALL_TODOS, + editing: null + }; + } + + public componentDidMount() { + var setState = this.setState; + var router = Router.Router({ + '/': setState.bind(this, { nowShowing: ALL_TODOS }), + '/active': setState.bind(this, { nowShowing: ACTIVE_TODOS }), + '/completed': setState.bind(this, { nowShowing: COMPLETED_TODOS }) + }); + router.init('/'); + } + + public handleNewTodoKeyDown(event: React.KeyboardEvent) { + if (event.keyCode !== ENTER_KEY) { + return; + } + + event.preventDefault(); + + var val = ReactDOM.findDOMNode(this.refs["newField"]).value.trim(); + + if (val) { + this.props.model.addTodo(val); + ReactDOM.findDOMNode(this.refs["newField"]).value = ''; + } + } + + public toggleAll(event: React.FormEvent) { + var target: any = event.target; + var checked = target.checked; + this.props.model.toggleAll(checked); + } + + public toggle(todoToToggle: ITodo) { + this.props.model.toggle(todoToToggle); + } + + public destroy(todo: ITodo) { + this.props.model.destroy(todo); + } + + public edit(todo: ITodo) { + this.setState({ editing: todo.id }); + } + + public save(todoToSave: ITodo, text: String) { + this.props.model.save(todoToSave, text); + this.setState({ editing: null }); + } + + public cancel() { + this.setState({ editing: null }); + } + + public clearCompleted() { + this.props.model.clearCompleted(); + } + + public render() { + var footer; + var main; + const todos = this.props.model.todos; + + var shownTodos = todos.filter((todo) => { + switch (this.state.nowShowing) { + case ACTIVE_TODOS: + return !todo.completed; + case COMPLETED_TODOS: + return todo.completed; + default: + return true; + } + }); + + var todoItems = shownTodos.map((todo) => { + return ( + this.cancel()} + /> + ); + }); + + // Note: It's usually better to use immutable data structures since they're + // easier to reason about and React works very well with them. That's why + // we use map(), filter() and reduce() everywhere instead of mutating the + // array or todo items themselves. + var activeTodoCount = todos.reduce(function (accum, todo) { + return todo.completed ? accum : accum + 1; + }, 0); + + var completedCount = todos.length - activeTodoCount; + + if (activeTodoCount || completedCount) { + footer = + this.clearCompleted()} + />; + } + + if (todos.length) { + main = ( +
+ this.toggleAll(e)} + checked={activeTodoCount === 0} + /> +
    + {todoItems} +
+
+ ); + } + + return ( +
+
+

todos

+ this.handleNewTodoKeyDown(e)} + autoFocus={true} + /> +
+ {main} + {footer} +
+ ); + } +} \ No newline at end of file diff --git a/src/todoItem.tsx b/src/todoItem.tsx new file mode 100644 index 0000000..fd54752 --- /dev/null +++ b/src/todoItem.tsx @@ -0,0 +1,102 @@ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import * as classNames from 'classnames'; +import { ITodoItemProps, ITodoItemState, Utils, ENTER_KEY, ESCAPE_KEY } from './core'; + +export class TodoItem extends React.Component { + + public state: ITodoItemState; + + constructor(props: ITodoItemProps) { + super(props); + this.state = { editText: this.props.todo.title }; + } + + public handleSubmit(event: React.FormEvent) { + var val = this.state.editText.trim(); + if (val) { + this.props.onSave(val); + this.setState({ editText: val }); + } else { + this.props.onDestroy(); + } + } + + public handleEdit() { + this.props.onEdit(); + this.setState({ editText: this.props.todo.title }); + } + + public handleKeyDown(event: React.KeyboardEvent) { + if (event.keyCode === ESCAPE_KEY) { + this.setState({ editText: this.props.todo.title }); + this.props.onCancel(event); + } else if (event.keyCode === ENTER_KEY) { + this.handleSubmit(event); + } + } + + public handleChange(event: React.FormEvent) { + var input: any = event.target; + this.setState({ editText: input.value }); + } + + /** + * This is a completely optional performance enhancement that you can + * implement on any React component. If you were to delete this method + * the app would still work correctly (and still be very performant!), we + * just use it as an example of how little code it takes to get an order + * of magnitude performance improvement. + */ + public shouldComponentUpdate(nextProps: ITodoItemProps, nextState: ITodoItemState) { + return ( + nextProps.todo !== this.props.todo || + nextProps.editing !== this.props.editing || + nextState.editText !== this.state.editText + ); + } + + /** + * Safely manipulate the DOM after updating the state when invoking + * `this.props.onEdit()` in the `handleEdit` method above. + * For more info refer to notes at https://facebook.github.io/react/docs/component-api.html#setstate + * and https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate + */ + public componentDidUpdate(prevProps: ITodoItemProps) { + if (!prevProps.editing && this.props.editing) { + var node = ReactDOM.findDOMNode(this.refs["editField"]); + node.focus(); + node.setSelectionRange(node.value.length, node.value.length); + } + } + + public render() { + return ( +
  • +
    + + +
    + this.handleSubmit(e)} + onChange={e => this.handleChange(e)} + onKeyDown={e => this.handleKeyDown(e)} + /> +
  • + ); + } +} \ No newline at end of file diff --git a/src/vendor.ts b/src/vendor.ts new file mode 100644 index 0000000..8f79141 --- /dev/null +++ b/src/vendor.ts @@ -0,0 +1,12 @@ +import 'react'; +import 'react-dom'; +import 'core-js/es6'; +import 'whatwg-fetch'; + +import 'todomvc-common/base.css'; +import 'todomvc-app-css/index.css'; +import 'todomvc-common/base.js'; + +import 'office-ui-fabric-core/dist/css/fabric.min.css'; +import 'office-ui-fabric-js/dist/css/fabric.components.min.css'; +import './app.css'; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d92607b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "sourceMap": true, + "moduleResolution": "node", + "jsx": "react", + "declaration": false, + "noImplicitAny": false, + "rootDir": "./src", + "outDir": "./dist", + "lib": [ + "dom", + "es2015" + ] + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/typings.json b/typings.json new file mode 100644 index 0000000..54d5955 --- /dev/null +++ b/typings.json @@ -0,0 +1,11 @@ +{ + "globalDependencies": { + "classnames": "registry:dt/classnames#0.0.0+20160316155526", + "jquery": "registry:dt/jquery#1.10.0+20170104155652", + "office-js": "registry:dt/office-js#0.0.0+20161213215240", + "react": "registry:dt/react#0.14.0+20161008064207", + "react-dom": "registry:dt/react-dom#0.14.0+20160412154040", + "whatwg-fetch": "registry:dt/whatwg-fetch#0.0.0+20161004182750", + "whatwg-streams": "registry:dt/whatwg-streams#0.0.0+20160829180742" + } +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..614db52 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1 @@ +require('config/webpack.common.js'); \ No newline at end of file