├── .azure-devops ├── build.yml ├── cleanup.yml ├── devcerts.yml ├── edgewebview.yml ├── full-pipeline.yml ├── install.yml ├── lint.yml └── test.yml ├── .babelrc ├── .eslintrc.json ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ └── bug_report.md └── pull_request_template.md ├── .gitignore ├── .npmrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── assets ├── color.png ├── icon-128.png ├── icon-16.png ├── icon-32.png ├── icon-64.png ├── icon-80.png ├── logo-filled.png └── outline.png ├── babel.config.json ├── convertToSingleHost.js ├── manifest.excel.xml ├── manifest.json ├── manifest.onenote.xml ├── manifest.outlook.xml ├── manifest.powerpoint.xml ├── manifest.project.xml ├── manifest.word.xml ├── manifest.xml ├── package-lock.json ├── package.json ├── src ├── commands │ ├── commands.html │ └── commands.js └── taskpane │ ├── components │ ├── App.jsx │ ├── Header.jsx │ ├── HeroList.jsx │ └── TextInsertion.jsx │ ├── excel.js │ ├── index.jsx │ ├── onenote.js │ ├── outlook.js │ ├── powerpoint.js │ ├── project.js │ ├── taskpane.html │ ├── taskpane.js │ └── word.js ├── test ├── end-to-end │ ├── src │ │ ├── host-tests.ts │ │ ├── test-helpers.ts │ │ ├── test-taskpane.html │ │ └── test.index.tsx │ ├── test-manifest.xml │ ├── ui-test.ts │ └── webpack.config.js └── unit │ ├── excel.test.ts │ ├── powerpoint.test.ts │ └── word.test.ts ├── tsconfig.json └── webpack.config.js /.azure-devops/build.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: Npm@1 3 | displayName: 'Build' 4 | inputs: 5 | command: custom 6 | customCommand: 'run build' -------------------------------------------------------------------------------- /.azure-devops/cleanup.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: mspremier.PostBuildCleanup.PostBuildCleanup-task.PostBuildCleanup@3 3 | displayName: "Cleanup" -------------------------------------------------------------------------------- /.azure-devops/devcerts.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - script: | 3 | echo Install Office-AddinDev-Certs at machine level 4 | call npx office-addin-dev-certs install --machine 5 | displayName: 'Install add-in dev cert' -------------------------------------------------------------------------------- /.azure-devops/edgewebview.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - script: | 3 | echo Enable EdgeWebView Loopback 4 | call npx office-addin-dev-settings appcontainer EdgeWebView --loopback --yes 5 | echo Set Edge WebView Registry Settings 6 | set PATH1="HKEY_CURRENT_USER\SOFTWARE\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Mappings\S-1-15-2-1310292540-1029022339-4008023048-2190398717-53961996-4257829345-603366646" 7 | reg add %PATH1% /f /v DisplayName /t REG_SZ /d "@{Microsoft.Win32WebViewHost_10.0.19041.423_neutral_neutral_cw5n1h2txyewy?ms-resource://Windows.Win32WebViewHost/resources/DisplayName}" 8 | reg add %PATH1% /f /v Moniker /t REG_SZ /d "microsoft.win32webviewhost_cw5n1h2txyewy" 9 | set PATH2="HKEY_CURRENT_USER\SOFTWARE\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppContainer\Mappings\S-1-15-2-1310292540-1029022339-4008023048-2190398717-53961996-4257829345-603366646\Children\S-1-15-2-1310292540-1029022339-4008023048-2190398717-53961996-4257829345-603366646-3829197285-1050560373-949424154-522343454" 10 | reg add %PATH2% /f /v DisplayName /t REG_SZ /d "microsoft.win32webviewhost_cw5n1h2txyewy/123" 11 | reg add %PATH2% /f /v Moniker /t REG_SZ /d "123" 12 | reg add %PATH2% /f /v ParentMoniker /t REG_SZ /d "microsoft.win32webviewhost_cw5n1h2txyewy" 13 | displayName: 'Enable Edge WebView' -------------------------------------------------------------------------------- /.azure-devops/full-pipeline.yml: -------------------------------------------------------------------------------- 1 | resources: 2 | repositories: 3 | - repository: OfficePipelineTemplates 4 | type: git 5 | name: 1ESPipelineTemplates/OfficePipelineTemplates 6 | ref: refs/tags/release 7 | extends: 8 | template: /v1/Office.Official.PipelineTemplate.yml@OfficePipelineTemplates 9 | parameters: 10 | pool: 11 | name: OE-OfficeClientApps 12 | sdl: 13 | eslint: 14 | configuration: required 15 | parser: '@typescript-eslint/parser' 16 | parserOptions: sourceType:module 17 | stages: 18 | - stage: stage 19 | jobs: 20 | - job: Windows_10_Latest 21 | steps: 22 | - template: /.azure-devops/install.yml@self 23 | - template: /.azure-devops/lint.yml@self 24 | - template: /.azure-devops/build.yml@self 25 | - template: /.azure-devops/devcerts.yml@self 26 | - template: /.azure-devops/edgewebview.yml@self 27 | - template: /.azure-devops/test.yml@self 28 | parameters: 29 | webView: "edge-chromium" 30 | # - job: WebView_EdgeLegacy 31 | # steps: 32 | # - template: /.azure-devops/install.yml@self 33 | # - template: /.azure-devops/lint.yml@self 34 | # - template: /.azure-devops/build.yml@self 35 | # - template: /.azure-devops/devcerts.yml@self 36 | # - template: /.azure-devops/edgewebview.yml@self 37 | # - template: /.azure-devops/test.yml@self 38 | # parameters: 39 | # webView: "edge-legacy" 40 | # - job: WebView_IE 41 | # steps: 42 | # - template: /.azure-devops/install.yml@self 43 | # - template: /.azure-devops/lint.yml@self 44 | # - template: /.azure-devops/build.yml@self 45 | # - template: /.azure-devops/devcerts.yml@self 46 | # - template: /.azure-devops/edgewebview.yml@self 47 | # - template: /.azure-devops/test.yml@self 48 | # parameters: 49 | # webView: "ie" 50 | -------------------------------------------------------------------------------- /.azure-devops/install.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: Npm@1 3 | displayName: 'Install' -------------------------------------------------------------------------------- /.azure-devops/lint.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - task: Npm@1 3 | displayName: 'Lint' 4 | inputs: 5 | command: custom 6 | customCommand: 'run lint' -------------------------------------------------------------------------------- /.azure-devops/test.yml: -------------------------------------------------------------------------------- 1 | parameters: 2 | - name: webView 3 | type: string 4 | default: "edge-chromium" 5 | steps: 6 | - task: CmdLine@2 7 | inputs: 8 | script: | 9 | echo Setting WebView Type: ${{ parameters.webView }} 10 | call npx office-addin-dev-settings webview manifest.xml ${{ parameters.webView }} 11 | call npx office-addin-dev-settings webview test/end-to-end/test-manifest.xml ${{ parameters.webView }} 12 | echo Running Tests 13 | npm run test 14 | echo Done running tests 15 | displayName: 'Run Tests' -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | "@babel/plugin-transform-class-properties" 8 | ] 9 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "office-addins" 4 | ], 5 | "extends": [ 6 | "plugin:office-addins/react" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @OfficeDev/office-platform-devx -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: needs triage 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Prerequisites 11 | 12 | Please answer the following questions before submitting an issue. 13 | **YOU MAY DELETE THE PREREQUISITES SECTION.** 14 | - [ ] I am running the latest version of Node and the tools 15 | - [ ] I checked the documentation and found no answer 16 | - [ ] I checked to make sure that this issue has not already been filed 17 | 18 | 19 | # Expected behavior 20 | 21 | Please describe the behavior you were expecting 22 | 23 | 24 | # Current behavior 25 | 26 | Please provide information about the failure. What is the current behavior? If it is not a bug, please submit your idea to the [Microsoft Tech Community Ideas forum](https://techcommunity.microsoft.com/t5/microsoft-365-developer-platform/idb-p/Microsoft365DeveloperPlatform), so that it gets added to our feature roadmap. 27 | 28 | 29 | ## Steps to Reproduce 30 | 31 | Please provide detailed steps for reproducing the issue. 32 | 33 | 1. step 1 34 | 2. step 2 35 | 3. you get it... 36 | 37 | 38 | ## Context 39 | 40 | Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions. 41 | 42 | * Operating System: 43 | * Node version: 44 | * Office version: 45 | * Tool version: 46 | 47 | ## Failure Logs 48 | 49 | Please include any relevant log snippets, screenshots or code samples here. 50 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Thank you for your pull request! Please provide the following information. 2 | 3 | --- 4 | 5 | **Change Description**: 6 | 7 | Describe what the PR is about. 8 | 9 | 1. **Do these changes impact any *npm scripts* commands (in package.json)?** (e.g., running 'npm run start') 10 | If Yes, briefly describe what is impacted. 11 | 12 | 13 | 2. **Do these changes impact *VS Code debugging* options (launch.json)?** 14 | If Yes, briefly describe what is impacted. 15 | 16 | 17 | 3. **Do these changes impact *template output*?** (e.g., add/remove file, update file location, update file contents) 18 | If Yes, briefly describe what is impacted. 19 | 20 | 21 | 4. **Do these changes impact *documentation*?** (e.g., a tutorial on https://docs.microsoft.com/en-us/office/dev/add-ins/overview/office-add-ins) 22 | If Yes, briefly describe what is impacted. 23 | 24 | 25 | If you answered yes to any of these please do the following: 26 | > Include 'Rick-Kirkham' in the review 27 | > Make sure the README file is correct 28 | 29 | **Validation/testing performed**: 30 | 31 | Describe manual testing done. 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build output 2 | dist/ 3 | test/testBuild 4 | 5 | # Node modules folder 6 | node_modules/ 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "ms-edgedevtools.vscode-edge-devtools", 8 | "dbaeumer.vscode-eslint", 9 | "esbenp.prettier-vscode", 10 | "msoffice.microsoft-office-add-in-debugger" 11 | ], 12 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 13 | "unwantedRecommendations": [] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug UI Tests", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 9 | "args": [ 10 | "-u", 11 | "bdd", 12 | "--timeout", 13 | "999999", 14 | "--colors", 15 | "${workspaceFolder}/test/end-to-end", 16 | "-r", 17 | "ts-node/register", 18 | "${workspaceFolder}/test/end-to-end/*.ts" 19 | ], 20 | "internalConsoleOptions": "openOnSessionStart", 21 | "runtimeArgs": ["--preserve-symlinks"] 22 | }, 23 | { 24 | "name": "Debug Unit Tests", 25 | "type": "node", 26 | "request": "launch", 27 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 28 | "args": [ 29 | "-u", 30 | "bdd", 31 | "--timeout", 32 | "999999", 33 | "--colors", 34 | "${workspaceFolder}/test/unit", 35 | "-r", 36 | "ts-node/register", 37 | "${workspaceFolder}/test/unit/*.test.ts" 38 | ], 39 | "internalConsoleOptions": "openOnSessionStart", 40 | "runtimeArgs": ["--preserve-symlinks"] 41 | }, 42 | { 43 | "name": "Excel Desktop (Edge Chromium)", 44 | "type": "msedge", 45 | "request": "attach", 46 | "port": 9229, 47 | "timeout": 600000, 48 | "webRoot": "${workspaceRoot}", 49 | "preLaunchTask": "Debug: Excel Desktop", 50 | "postDebugTask": "Stop Debug" 51 | }, 52 | { 53 | "name": "Excel Desktop (Edge Legacy)", 54 | "type": "office-addin", 55 | "request": "attach", 56 | "url": "https://localhost:3000/taskpane.html?_host_Info=Excel$Win32$16.01$en-US$$$$0", 57 | "port": 9222, 58 | "timeout": 600000, 59 | "webRoot": "${workspaceRoot}", 60 | "preLaunchTask": "Debug: Excel Desktop", 61 | "postDebugTask": "Stop Debug" 62 | }, 63 | { 64 | "name": "Outlook Desktop (Edge Chromium)", 65 | "type": "msedge", 66 | "request": "attach", 67 | "port": 9229, 68 | "timeout": 600000, 69 | "webRoot": "${workspaceRoot}", 70 | "preLaunchTask": "Debug: Outlook Desktop", 71 | "postDebugTask": "Stop Debug" 72 | }, 73 | { 74 | "name": "Outlook Desktop (Edge Legacy)", 75 | "type": "office-addin", 76 | "request": "attach", 77 | "url": "https://localhost:3000/taskpane.html?_host_Info=Outlook$Win32$16.01$en-US$$$$0", 78 | "port": 9222, 79 | "timeout": 600000, 80 | "webRoot": "${workspaceRoot}", 81 | "preLaunchTask": "Debug: Outlook Desktop", 82 | "postDebugTask": "Stop Debug" 83 | }, 84 | { 85 | "name": "PowerPoint Desktop (Edge Chromium)", 86 | "type": "msedge", 87 | "request": "attach", 88 | "port": 9229, 89 | "timeout": 600000, 90 | "webRoot": "${workspaceRoot}", 91 | "preLaunchTask": "Debug: PowerPoint Desktop", 92 | "postDebugTask": "Stop Debug" 93 | }, 94 | { 95 | "name": "PowerPoint Desktop (Edge Legacy)", 96 | "type": "office-addin", 97 | "request": "attach", 98 | "url": "https://localhost:3000/taskpane.html?_host_Info=PowerPoint$Win32$16.01$en-US$$$$0", 99 | "port": 9222, 100 | "timeout": 600000, 101 | "webRoot": "${workspaceRoot}", 102 | "preLaunchTask": "Debug: PowerPoint Desktop", 103 | "postDebugTask": "Stop Debug" 104 | }, 105 | { 106 | "name": "Word Desktop (Edge Chromium)", 107 | "type": "msedge", 108 | "request": "attach", 109 | "port": 9229, 110 | "timeout": 600000, 111 | "webRoot": "${workspaceRoot}", 112 | "preLaunchTask": "Debug: Word Desktop", 113 | "postDebugTask": "Stop Debug" 114 | }, 115 | { 116 | "name": "Word Desktop (Edge Legacy)", 117 | "type": "office-addin", 118 | "request": "attach", 119 | "url": "https://localhost:3000/taskpane.html?_host_Info=Word$Win32$16.01$en-US$$$$0", 120 | "port": 9222, 121 | "timeout": 600000, 122 | "webRoot": "${workspaceRoot}", 123 | "preLaunchTask": "Debug: Word Desktop", 124 | "postDebugTask": "Stop Debug" 125 | } 126 | ] 127 | } 128 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": [ 3 | "javascript", 4 | "javascriptreact", 5 | "typescript" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build (Development)", 6 | "type": "npm", 7 | "script": "build:dev", 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | }, 12 | "presentation": { 13 | "clear": true, 14 | "panel": "shared", 15 | "showReuseMessage": false 16 | } 17 | }, 18 | { 19 | "label": "Build (Production)", 20 | "type": "npm", 21 | "script": "build", 22 | "group": "build", 23 | "presentation": { 24 | "clear": true, 25 | "panel": "shared", 26 | "showReuseMessage": false 27 | } 28 | }, 29 | { 30 | "label": "Debug: Excel Desktop", 31 | "type": "shell", 32 | "command": "npm", 33 | "args": [ 34 | "run", 35 | "start", 36 | "--", 37 | "desktop", 38 | "--app", 39 | "excel" 40 | ], 41 | "presentation": { 42 | "clear": true, 43 | "panel": "dedicated" 44 | }, 45 | "problemMatcher": [] 46 | }, 47 | { 48 | "label": "Debug: Outlook Desktop", 49 | "type": "shell", 50 | "command": "npm", 51 | "args": [ 52 | "run", 53 | "start", 54 | "--", 55 | "desktop", 56 | "--app", 57 | "outlook" 58 | ], 59 | "presentation": { 60 | "clear": true, 61 | "panel": "dedicated" 62 | }, 63 | "problemMatcher": [] 64 | }, 65 | { 66 | "label": "Debug: PowerPoint Desktop", 67 | "type": "shell", 68 | "command": "npm", 69 | "args": [ 70 | "run", 71 | "start", 72 | "--", 73 | "desktop", 74 | "--app", 75 | "powerpoint" 76 | ], 77 | "presentation": { 78 | "clear": true, 79 | "panel": "dedicated" 80 | }, 81 | "problemMatcher": [] 82 | }, 83 | { 84 | "label": "Debug: Word Desktop", 85 | "type": "shell", 86 | "command": "npm", 87 | "args": [ 88 | "run", 89 | "start", 90 | "--", 91 | "desktop", 92 | "--app", 93 | "word" 94 | ], 95 | "presentation": { 96 | "clear": true, 97 | "panel": "dedicated" 98 | }, 99 | "problemMatcher": [] 100 | }, 101 | { 102 | "label": "Dev Server", 103 | "type": "npm", 104 | "script": "dev-server", 105 | "presentation": { 106 | "clear": true, 107 | "panel": "dedicated" 108 | }, 109 | "problemMatcher": [] 110 | }, 111 | { 112 | "label": "Install", 113 | "type": "npm", 114 | "script": "install", 115 | "presentation": { 116 | "clear": true, 117 | "panel": "shared", 118 | "showReuseMessage": false 119 | }, 120 | "problemMatcher": [] 121 | }, 122 | { 123 | "label": "Lint: Check for problems", 124 | "type": "npm", 125 | "script": "lint", 126 | "problemMatcher": [ 127 | "$eslint-stylish" 128 | ] 129 | }, 130 | { 131 | "label": "Lint: Fix all auto-fixable problems", 132 | "type": "npm", 133 | "script": "lint:fix", 134 | "problemMatcher": [ 135 | "$eslint-stylish" 136 | ] 137 | }, 138 | { 139 | "label": "Stop Debug", 140 | "type": "npm", 141 | "script": "stop", 142 | "presentation": { 143 | "clear": true, 144 | "panel": "shared", 145 | "showReuseMessage": false 146 | }, 147 | "problemMatcher": [] 148 | }, 149 | { 150 | "label": "Watch", 151 | "type": "npm", 152 | "script": "watch", 153 | "presentation": { 154 | "clear": true, 155 | "panel": "dedicated" 156 | }, 157 | "problemMatcher": [] 158 | } 159 | ] 160 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute to this code sample 2 | 3 | Thank you for your interest in this sample! Your contributions and improvements will help the developer community. 4 | 5 | ## Ways to contribute 6 | 7 | There are several ways you can contribute to this sample: providing better code comments, fixing open issues, and adding new features. 8 | 9 | ### Provide better code comments 10 | 11 | Code comments make code samples even better by helping developers learn to use the code correctly in their own applications. If you spot a class, method, or section of code that you think could use better descriptions, then create a pull request with your code comments. 12 | 13 | 14 | In general, we want our code comments to follow these guidelines: 15 | 16 | - Any code that has associated documentation displayed in an IDE (such as IntelliSense, or JavaDocs) has code comments. 17 | - Classes, methods, parameters, and return values have clear descriptions. 18 | - Exceptions and errors are documented. 19 | - Remarks exist for anything special or notable about the code. 20 | - Sections of code that have complex algorithms have appropriate comments describing what they do. 21 | - Code added from Stack Overflow, or any other source, is clearly attributed. 22 | 23 | ### Fix open issues 24 | 25 | Sometimes we get a lot of issues, and it can be hard to keep up. If you have a solution to an open issue that hasn't been addressed, fix the issue and then submit a pull request. 26 | 27 | ### Add a new feature 28 | 29 | New features are great! Be sure to check with the repository admin first to be sure the feature fits the intent of the sample. Start by opening an issue in the repository that proposes and describes the feature. The repository admin will respond and may ask for more information. If the admin agrees to the new feature, create the feature and submit a pull request. 30 | 31 | ## Contribution guidelines 32 | 33 | We have some guidelines to help maintain a healthy repo and code for everyone. 34 | 35 | ### The Contribution License Agreement 36 | 37 | For most contributions, we ask you to sign a Contribution License Agreement (CLA). This will happen when you submit a pull request. Microsoft will send a link to the CLA to sign via email. Once you sign the CLA, your pull request can proceed. Read the CLA carefully, because you may need to have your employer sign it. 38 | 39 | ### Code contribution checklist 40 | 41 | Be sure to satisfy all of the requirements in the following list before submitting a pull request: 42 | 43 | - Follow the code style that is appropriate for the platform and language in this repo. For example, Android code follows the style conventions found in the [Code Style for Contributors guide](https://source.android.com/source/code-style.html). 44 | - Test your code. 45 | - Test the UI thoroughly to be sure your change hasn't broken anything. 46 | - 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. 47 | - Avoid unnecessary changes. The reviewer will check 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. 48 | 49 | ### Submit a pull request to the master branch 50 | 51 | When you're finished with your work and are ready to have it merged into the master repository, follow these steps. Note: pull requests are typically reviewed within 10 business days. If your pull request is accepted you will be credited for your submission. 52 | 53 | 1. Submit your pull request against the master branch. 54 | 2. Sign the CLA, if you haven't already done so. 55 | 3. One of the repo admins will process your pull request, including performing a code review. If there are questions, discussions, or change requests in the pull request, be sure to respond. 56 | 4. When the repo admins are satisfied, they will accept and merge the pull request. 57 | 58 | Congratulations, you have successfully contributed to the sample! 59 | 60 | ## FAQ 61 | 62 | ### Where do I get a Contributor's License Agreement? 63 | 64 | If your pull request requires one, you'll automatically be sent a notice that you need to sign the Contributor's License Agreement (CLA). 65 | 66 | 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. 67 | 68 | ### What happens with my contributions? 69 | 70 | When you submit your changes via a pull request, our team will be notified and will review your pull request. You'll receive notifications about your pull request from GitHub; you may also be notified by someone from our team if we need more information. We reserve the right to edit your submission for legal, style, clarity, or other issues. 71 | 72 | ### Who approves pull requests? 73 | 74 | The admin of the repository approves pull requests. 75 | 76 | ### How soon will I get a response about my change request or issue? 77 | 78 | We typically review pull requests and respond to issues within 10 business days. 79 | 80 | ## More resources 81 | 82 | - To learn more about Markdown, see [Daring Fireball](http://daringfireball.net/). 83 | - To learn more about using Git and GitHub, check out the [GitHub Help section](http://help.github.com/). 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Office Add-in TaskPane 2 | 3 | MIT License 4 | 5 | Copyright (c) Microsoft Corporation. All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Office-Addin-TaskPane-React-JS 2 | 3 | This repository contains the source code used by the [Yo Office generator](https://github.com/OfficeDev/generator-office) when you create a new Office Add-in that appears in the task pane. You can also use this repository as a sample to base your own project from if you choose not to use the generator. 4 | 5 | ## JavaScript 6 | 7 | This template is written using JavaScript. For the [TypeScript](http://www.typescriptlang.org/) version of this template, go to [Office-Addin-TaskPane-React](https://github.com/OfficeDev/Office-Addin-TaskPane-React). 8 | 9 | ## Debugging 10 | 11 | This template supports debugging using any of the following techniques: 12 | 13 | - [Use a browser's developer tools](https://learn.microsoft.com/office/dev/add-ins/testing/debug-add-ins-in-office-online) 14 | - [Attach a debugger from the task pane](https://learn.microsoft.com/office/dev/add-ins/testing/attach-debugger-from-task-pane) 15 | - [Use F12 developer tools on Windows 10](https://learn.microsoft.com/office/dev/add-ins/testing/debug-add-ins-using-f12-developer-tools-on-windows-10) 16 | 17 | ## Questions and comments 18 | 19 | We'd love to get your feedback about this sample. You can send your feedback to us in the *Issues* section of this repository. 20 | 21 | Questions about Office Add-ins development in general should be posted to [Microsoft Q&A](https://learn.microsoft.com/answers/questions/185087/questions-about-office-add-ins.html). If your question is about the Office JavaScript APIs, make sure it's tagged with [office-js-dev]. 22 | 23 | ## Join the Microsoft 365 Developer Program 24 | 25 | Join the [Microsoft 365 Developer Program](https://aka.ms/m365devprogram) to get resources and information to help you build solutions for the Microsoft 365 platform, including recommendations tailored to your areas of interest. 26 | 27 | You might also qualify for a free developer subscription that's renewable for 90 days and comes configured with sample data; for details, see the [FAQ](https://learn.microsoft.com/office/developer-program/microsoft-365-developer-program-faq#who-qualifies-for-a-microsoft-365-e5-developer-subscription-). 28 | 29 | ## Additional resources 30 | 31 | - [Office Add-ins documentation](https://learn.microsoft.com/office/dev/add-ins/overview/office-add-ins) 32 | - More Office Add-ins samples at [OfficeDev on Github](https://github.com/officedev) 33 | 34 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 35 | 36 | ## Copyright 37 | 38 | Copyright (c) 2021 Microsoft Corporation. All rights reserved. 39 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /assets/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Office-Addin-TaskPane-React-JS/94a09b2ed4b4ecda6852f1f7b76378385389ec9b/assets/color.png -------------------------------------------------------------------------------- /assets/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Office-Addin-TaskPane-React-JS/94a09b2ed4b4ecda6852f1f7b76378385389ec9b/assets/icon-128.png -------------------------------------------------------------------------------- /assets/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Office-Addin-TaskPane-React-JS/94a09b2ed4b4ecda6852f1f7b76378385389ec9b/assets/icon-16.png -------------------------------------------------------------------------------- /assets/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Office-Addin-TaskPane-React-JS/94a09b2ed4b4ecda6852f1f7b76378385389ec9b/assets/icon-32.png -------------------------------------------------------------------------------- /assets/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Office-Addin-TaskPane-React-JS/94a09b2ed4b4ecda6852f1f7b76378385389ec9b/assets/icon-64.png -------------------------------------------------------------------------------- /assets/icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Office-Addin-TaskPane-React-JS/94a09b2ed4b4ecda6852f1f7b76378385389ec9b/assets/icon-80.png -------------------------------------------------------------------------------- /assets/logo-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Office-Addin-TaskPane-React-JS/94a09b2ed4b4ecda6852f1f7b76378385389ec9b/assets/logo-filled.png -------------------------------------------------------------------------------- /assets/outline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficeDev/Office-Addin-TaskPane-React-JS/94a09b2ed4b4ecda6852f1f7b76378385389ec9b/assets/outline.png -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "esmodules": false 8 | } 9 | } 10 | ], 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /convertToSingleHost.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* global require, process, console */ 3 | 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | const util = require("util"); 7 | const childProcess = require("child_process"); 8 | const hosts = ["excel", "onenote", "outlook", "powerpoint", "project", "word"]; 9 | 10 | if (process.argv.length <= 2) { 11 | const hostList = hosts.map((host) => `'${host}'`).join(", "); 12 | console.log("SYNTAX: convertToSingleHost.js "); 13 | console.log(); 14 | console.log(` host (required): Specifies which Office app will host the add-in: ${hostList}`); 15 | console.log(` manifestType: Specify the type of manifest to use: 'xml' or 'json'. Defaults to 'xml'`); 16 | console.log(` projectName: The name of the project (use quotes when there are spaces in the name). Defaults to 'My Office Add-in'`); 17 | console.log(` appId: The id of the project or 'random' to generate one. Defaults to 'random'`); 18 | console.log(); 19 | process.exit(1); 20 | } 21 | 22 | const host = process.argv[2]; 23 | const manifestType = process.argv[3]; 24 | const projectName = process.argv[4]; 25 | let appId = process.argv[5]; 26 | const testPackages = [ 27 | "@types/mocha", 28 | "@types/node", 29 | "mocha", 30 | "office-addin-mock", 31 | "office-addin-test-helpers", 32 | "office-addin-test-server", 33 | "ts-node", 34 | ]; 35 | const readFileAsync = util.promisify(fs.readFile); 36 | const unlinkFileAsync = util.promisify(fs.unlink); 37 | const writeFileAsync = util.promisify(fs.writeFile); 38 | 39 | async function modifyProjectForSingleHost(host) { 40 | if (!host) { 41 | throw new Error("The host was not provided."); 42 | } 43 | if (!hosts.includes(host)) { 44 | throw new Error(`'${host}' is not a supported host.`); 45 | } 46 | await convertProjectToSingleHost(host); 47 | await updatePackageJsonForSingleHost(host); 48 | await updateLaunchJsonFile(host); 49 | } 50 | 51 | async function convertProjectToSingleHost(host) { 52 | // Copy host-specific manifest over manifest.xml 53 | const manifestContent = await readFileAsync(`./manifest.${host}.xml`, "utf8"); 54 | await writeFileAsync(`./manifest.xml`, manifestContent); 55 | 56 | // Copy host-specific office-document.js over src/office-document.js 57 | const hostName = getHostName(host); 58 | const srcContent = await readFileAsync(`./src/taskpane/${hostName}.js`, "utf8"); 59 | await writeFileAsync(`./src/taskpane/taskpane.js`, srcContent); 60 | // Delete all host-specific files 61 | hosts.forEach(async function (host) { 62 | await unlinkFileAsync(`./manifest.${host}.xml`); 63 | await unlinkFileAsync(`./src/taskpane/${getHostName(host)}.js`); 64 | }); 65 | 66 | // Delete test folder 67 | deleteFolder(path.resolve(`./test`)); 68 | 69 | // Delete the .github folder 70 | deleteFolder(path.resolve(`./.github`)); 71 | 72 | // Delete CI/CD pipeline files 73 | deleteFolder(path.resolve(`./.azure-devops`)); 74 | 75 | // Delete repo support files 76 | await deleteSupportFiles(); 77 | } 78 | 79 | async function updatePackageJsonForSingleHost(host) { 80 | // Update package.json to reflect selected host 81 | const packageJson = `./package.json`; 82 | const data = await readFileAsync(packageJson, "utf8"); 83 | let content = JSON.parse(data); 84 | 85 | // Update 'config' section in package.json to use selected host 86 | content.config["app_to_debug"] = host; 87 | 88 | // Remove 'engines' section 89 | delete content.engines; 90 | 91 | // Remove scripts that are unrelated to the selected host 92 | Object.keys(content.scripts).forEach(function (key) { 93 | if (key === "convert-to-single-host") { 94 | delete content.scripts[key]; 95 | } 96 | }); 97 | 98 | // Remove test-related scripts 99 | Object.keys(content.scripts).forEach(function (key) { 100 | if (key.includes("test")) { 101 | delete content.scripts[key]; 102 | } 103 | }); 104 | 105 | // Remove test-related packages 106 | Object.keys(content.devDependencies).forEach(function (key) { 107 | if (testPackages.includes(key)) { 108 | delete content.devDependencies[key]; 109 | } 110 | }); 111 | 112 | // Write updated JSON to file 113 | await writeFileAsync(packageJson, JSON.stringify(content, null, 2)); 114 | } 115 | 116 | async function updateLaunchJsonFile(host) { 117 | // Remove unneeded configuration from launch.json 118 | const launchJson = `.vscode/launch.json`; 119 | const launchJsonContent = await readFileAsync(launchJson, "utf8"); 120 | let content = JSON.parse(launchJsonContent); 121 | content.configurations = content.configurations.filter(function (config) { 122 | return config.name.startsWith(getHostName(host)); 123 | }); 124 | await writeFileAsync(launchJson, JSON.stringify(content, null, 2)); 125 | } 126 | 127 | function getHostName(host) { 128 | switch (host) { 129 | case "excel": 130 | return "Excel"; 131 | case "onenote": 132 | return "OneNote"; 133 | case "outlook": 134 | return "Outlook"; 135 | case "powerpoint": 136 | return "PowerPoint"; 137 | case "project": 138 | return "Project"; 139 | case "word": 140 | return "Word"; 141 | default: 142 | throw new Error(`'${host}' is not a supported host.`); 143 | } 144 | } 145 | 146 | function deleteFolder(folder) { 147 | try { 148 | if (fs.existsSync(folder)) { 149 | fs.readdirSync(folder).forEach(function (file) { 150 | const curPath = `${folder}/${file}`; 151 | 152 | if (fs.lstatSync(curPath).isDirectory()) { 153 | deleteFolder(curPath); 154 | } else { 155 | fs.unlinkSync(curPath); 156 | } 157 | }); 158 | fs.rmdirSync(folder); 159 | } 160 | } catch (err) { 161 | throw new Error(`Unable to delete folder "${folder}".\n${err}`); 162 | } 163 | } 164 | 165 | async function deleteSupportFiles() { 166 | await unlinkFileAsync("CONTRIBUTING.md"); 167 | await unlinkFileAsync("LICENSE"); 168 | await unlinkFileAsync("README.md"); 169 | await unlinkFileAsync("SECURITY.md"); 170 | await unlinkFileAsync("./convertToSingleHost.js"); 171 | await unlinkFileAsync(".npmrc"); 172 | await unlinkFileAsync("package-lock.json"); 173 | } 174 | 175 | async function deleteJSONManifestRelatedFiles() { 176 | await unlinkFileAsync("manifest.json"); 177 | await unlinkFileAsync("assets/color.png"); 178 | await unlinkFileAsync("assets/outline.png"); 179 | } 180 | 181 | async function deleteXMLManifestRelatedFiles() { 182 | await unlinkFileAsync("manifest.xml"); 183 | } 184 | 185 | async function updatePackageJsonForXMLManifest() { 186 | const packageJson = `./package.json`; 187 | const data = await readFileAsync(packageJson, "utf8"); 188 | let content = JSON.parse(data); 189 | 190 | // Write updated JSON to file 191 | await writeFileAsync(packageJson, JSON.stringify(content, null, 2)); 192 | } 193 | 194 | async function updatePackageJsonForJSONManifest() { 195 | const packageJson = `./package.json`; 196 | const data = await readFileAsync(packageJson, "utf8"); 197 | let content = JSON.parse(data); 198 | 199 | // Change manifest file name extension 200 | content.scripts.start = "office-addin-debugging start manifest.json"; 201 | content.scripts.stop = "office-addin-debugging stop manifest.json"; 202 | content.scripts.validate = "office-addin-manifest validate manifest.json"; 203 | 204 | // Write updated JSON to file 205 | await writeFileAsync(packageJson, JSON.stringify(content, null, 2)); 206 | } 207 | 208 | async function updateTasksJsonFileForJSONManifest() { 209 | const tasksJson = `.vscode/tasks.json`; 210 | const data = await readFileAsync(tasksJson, "utf8"); 211 | let content = JSON.parse(data); 212 | 213 | content.tasks.forEach(function (task) { 214 | if (task.label.startsWith("Build")) { 215 | task.dependsOn = ["Install"]; 216 | } 217 | if (task.label === "Debug: Outlook Desktop") { 218 | task.script = "start"; 219 | task.dependsOn = ["Check OS", "Install"]; 220 | } 221 | }); 222 | 223 | const checkOSTask = { 224 | label: "Check OS", 225 | type: "shell", 226 | windows: { 227 | command: "echo 'Sideloading in Outlook on Windows is supported'", 228 | }, 229 | linux: { 230 | command: "echo 'Sideloading on Linux is not supported' && exit 1", 231 | }, 232 | osx: { 233 | command: "echo 'Sideloading in Outlook on Mac is not supported' && exit 1", 234 | }, 235 | presentation: { 236 | clear: true, 237 | panel: "dedicated", 238 | }, 239 | }; 240 | 241 | content.tasks.push(checkOSTask); 242 | await writeFileAsync(tasksJson, JSON.stringify(content, null, 2)); 243 | } 244 | 245 | async function updateWebpackConfigForJSONManifest() { 246 | const webPack = `webpack.config.js`; 247 | const webPackContent = await readFileAsync(webPack, "utf8"); 248 | const updatedContent = webPackContent.replace(".xml", ".json"); 249 | await writeFileAsync(webPack, updatedContent); 250 | } 251 | 252 | async function modifyProjectForJSONManifest() { 253 | await updatePackageJsonForJSONManifest(); 254 | await updateWebpackConfigForJSONManifest(); 255 | await updateTasksJsonFileForJSONManifest(); 256 | await deleteXMLManifestRelatedFiles(); 257 | } 258 | 259 | /** 260 | * Modify the project so that it only supports a single host. 261 | * @param host The host to support. 262 | */ 263 | modifyProjectForSingleHost(host).catch((err) => { 264 | console.error(`Error modifying for single host: ${err instanceof Error ? err.message : err}`); 265 | process.exitCode = 1; 266 | }); 267 | 268 | let manifestPath = "manifest.xml"; 269 | 270 | if (host !== "outlook" || manifestType !== "json") { 271 | // Remove things that are only relevant to JSON manifest 272 | deleteJSONManifestRelatedFiles(); 273 | updatePackageJsonForXMLManifest(); 274 | } else { 275 | manifestPath = "manifest.json"; 276 | modifyProjectForJSONManifest().catch((err) => { 277 | console.error(`Error modifying for JSON manifest: ${err instanceof Error ? err.message : err}`); 278 | process.exitCode = 1; 279 | }); 280 | } 281 | 282 | if (projectName) { 283 | if (!appId) { 284 | appId = "random"; 285 | } 286 | 287 | // Modify the manifest to include the name and id of the project 288 | const cmdLine = `npx office-addin-manifest modify ${manifestPath} -g ${appId} -d "${projectName}"`; 289 | childProcess.exec(cmdLine, (error, stdout) => { 290 | if (error) { 291 | Promise.reject(stdout); 292 | } else { 293 | Promise.resolve(); 294 | } 295 | }); 296 | } 297 | -------------------------------------------------------------------------------- /manifest.excel.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 05c2e1c9-3e1d-406e-9a91-e9ac64854143 7 | 1.0.0.0 8 | Contoso 9 | en-US 10 | 11 | 12 | 13 | 14 | 15 | 16 | https://www.contoso.com 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ReadWriteDocument 25 | 26 | 27 | 28 | 29 | 30 | 31 | <Description resid="GetStarted.Description"/> 32 | <LearnMoreUrl resid="GetStarted.LearnMoreUrl"/> 33 | </GetStarted> 34 | <FunctionFile resid="Commands.Url" /> 35 | <ExtensionPoint xsi:type="PrimaryCommandSurface"> 36 | <OfficeTab id="TabHome"> 37 | <Group id="CommandsGroup"> 38 | <Label resid="CommandsGroup.Label" /> 39 | <Icon> 40 | <bt:Image size="16" resid="Icon.16x16" /> 41 | <bt:Image size="32" resid="Icon.32x32" /> 42 | <bt:Image size="80" resid="Icon.80x80" /> 43 | </Icon> 44 | <Control xsi:type="Button" id="TaskpaneButton"> 45 | <Label resid="TaskpaneButton.Label" /> 46 | <Supertip> 47 | <Title resid="TaskpaneButton.Label" /> 48 | <Description resid="TaskpaneButton.Tooltip" /> 49 | </Supertip> 50 | <Icon> 51 | <bt:Image size="16" resid="Icon.16x16" /> 52 | <bt:Image size="32" resid="Icon.32x32" /> 53 | <bt:Image size="80" resid="Icon.80x80" /> 54 | </Icon> 55 | <Action xsi:type="ShowTaskpane"> 56 | <TaskpaneId>ButtonId1</TaskpaneId> 57 | <SourceLocation resid="Taskpane.Url" /> 58 | </Action> 59 | </Control> 60 | </Group> 61 | </OfficeTab> 62 | </ExtensionPoint> 63 | </DesktopFormFactor> 64 | </Host> 65 | </Hosts> 66 | <Resources> 67 | <bt:Images> 68 | <bt:Image id="Icon.16x16" DefaultValue="https://localhost:3000/assets/icon-16.png"/> 69 | <bt:Image id="Icon.32x32" DefaultValue="https://localhost:3000/assets/icon-32.png"/> 70 | <bt:Image id="Icon.80x80" DefaultValue="https://localhost:3000/assets/icon-80.png"/> 71 | </bt:Images> 72 | <bt:Urls> 73 | <bt:Url id="GetStarted.LearnMoreUrl" DefaultValue="https://go.microsoft.com/fwlink/?LinkId=276812" /> 74 | <bt:Url id="Commands.Url" DefaultValue="https://localhost:3000/commands.html" /> 75 | <bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3000/taskpane.html" /> 76 | </bt:Urls> 77 | <bt:ShortStrings> 78 | <bt:String id="GetStarted.Title" DefaultValue="Get started with your sample add-in!" /> 79 | <bt:String id="CommandsGroup.Label" DefaultValue="Commands Group" /> 80 | <bt:String id="TaskpaneButton.Label" DefaultValue="Show Task Pane" /> 81 | </bt:ShortStrings> 82 | <bt:LongStrings> 83 | <bt:String id="GetStarted.Description" DefaultValue="Your sample add-in loaded successfully. Go to the HOME tab and click the 'Show Task Pane' button to get started." /> 84 | <bt:String id="TaskpaneButton.Tooltip" DefaultValue="Click to Show a Taskpane" /> 85 | </bt:LongStrings> 86 | </Resources> 87 | </VersionOverrides> 88 | </OfficeApp> -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.17/MicrosoftTeams.schema.json", 3 | "id": "f2b103f1-1ab1-4e1b-8f0b-072aa3d4e19d", 4 | "manifestVersion": "1.17", 5 | "version": "1.0.0", 6 | "name": { 7 | "short": "Contoso Task Pane Add-in", 8 | "full": "Contoso Task Pane Add-in" 9 | }, 10 | "description": { 11 | "short": "A template to get started.", 12 | "full": "This is the template to get started." 13 | }, 14 | "developer": { 15 | "name": "Contoso", 16 | "websiteUrl": "https://www.contoso.com", 17 | "privacyUrl": "https://www.contoso.com/privacy", 18 | "termsOfUseUrl": "https://www.contoso.com/servicesagreement" 19 | }, 20 | "icons": { 21 | "outline": "assets/outline.png", 22 | "color": "assets/color.png" 23 | }, 24 | "accentColor": "#230201", 25 | "localizationInfo": { 26 | "defaultLanguageTag": "en-us", 27 | "additionalLanguages": [] 28 | }, 29 | "authorization": { 30 | "permissions": { 31 | "resourceSpecific": [ 32 | { 33 | "name": "Mailbox.ReadWrite.User", 34 | "type": "Delegated" 35 | } 36 | ] 37 | } 38 | }, 39 | "validDomains": ["contoso.com"], 40 | "extensions": [ 41 | { 42 | "requirements": { 43 | "scopes": ["mail"], 44 | "capabilities": [ 45 | { "name": "Mailbox", "minVersion": "1.3" } 46 | ] 47 | }, 48 | "runtimes": [ 49 | { 50 | "requirements": { 51 | "capabilities": [ 52 | { "name": "Mailbox", "minVersion": "1.3" } 53 | ] 54 | }, 55 | "id": "TaskPaneRuntime", 56 | "type": "general", 57 | "code": { 58 | "page": "https://localhost:3000/taskpane.html" 59 | }, 60 | "lifetime": "short", 61 | "actions": [ 62 | { 63 | "id": "TaskPaneRuntimeShow", 64 | "type":"openPage", 65 | "pinnable": false, 66 | "view": "dashboard" 67 | } 68 | ] 69 | }, 70 | { 71 | "id": "CommandsRuntime", 72 | "type": "general", 73 | "code": { 74 | "page": "https://localhost:3000/commands.html", 75 | "script": "https://localhost:3000/commands.js" 76 | }, 77 | "lifetime": "short", 78 | "actions": [ 79 | { 80 | "id": "action", 81 | "type": "executeFunction" 82 | } 83 | ] 84 | } 85 | ], 86 | "ribbons": [ 87 | { 88 | "contexts": [ 89 | "mailCompose" 90 | ], 91 | "tabs": [ 92 | { 93 | "builtInTabId": "TabDefault", 94 | "groups": [ 95 | { 96 | "id": "msgComposeGroup", 97 | "label": "Contoso Add-in", 98 | "icons": [ 99 | { "size": 16, "url": "https://localhost:3000/assets/icon-16.png" }, 100 | { "size": 32, "url": "https://localhost:3000/assets/icon-32.png" }, 101 | { "size": 80, "url": "https://localhost:3000/assets/icon-80.png" } 102 | ], 103 | "controls": [ 104 | { 105 | "id": "msgComposeOpenPaneButton", 106 | "type": "button", 107 | "label": "Show Task Pane", 108 | "icons": [ 109 | { "size": 16, "url": "https://localhost:3000/assets/icon-16.png" }, 110 | { "size": 32, "url": "https://localhost:3000/assets/icon-32.png" }, 111 | { "size": 80, "url": "https://localhost:3000/assets/icon-80.png" } 112 | ], 113 | "supertip": { 114 | "title": "Show Task Pane", 115 | "description": "Opens a task pane." 116 | }, 117 | "actionId": "TaskPaneRuntimeShow" 118 | }, 119 | { 120 | "id": "ActionButton", 121 | "type": "button", 122 | "label": "Perform an action", 123 | "icons": [ 124 | { "size": 16, "url": "https://localhost:3000/assets/icon-16.png" }, 125 | { "size": 32, "url": "https://localhost:3000/assets/icon-32.png" }, 126 | { "size": 80, "url": "https://localhost:3000/assets/icon-80.png" } 127 | ], 128 | "supertip": { 129 | "title": "Perform an action", 130 | "description": "Perform an action when clicked." 131 | }, 132 | "actionId": "action" 133 | } 134 | ] 135 | } 136 | ] 137 | } 138 | ] 139 | } 140 | ] 141 | } 142 | ] 143 | } 144 | -------------------------------------------------------------------------------- /manifest.onenote.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 2 | <OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" 3 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 | xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" 5 | xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="TaskPaneApp"> 6 | <Id>05c2e1c9-3e1d-406e-9a91-e9ac64854143</Id> 7 | <Version>1.0.0.0</Version> 8 | <ProviderName>Contoso</ProviderName> 9 | <DefaultLocale>en-US</DefaultLocale> 10 | <DisplayName DefaultValue="Contoso Task Pane Add-in"/> 11 | <Description DefaultValue="A template to get started."/> 12 | <IconUrl DefaultValue="https://localhost:3000/assets/icon-32.png"/> 13 | <HighResolutionIconUrl DefaultValue="https://localhost:3000/assets/icon-64.png"/> 14 | <SupportUrl DefaultValue="https://www.contoso.com/help"/> 15 | <AppDomains> 16 | <AppDomain>https://www.contoso.com</AppDomain> 17 | </AppDomains> 18 | <Hosts> 19 | <Host Name="Notebook"/> 20 | </Hosts> 21 | <DefaultSettings> 22 | <SourceLocation DefaultValue="https://localhost:3000/taskpane.html"/> 23 | </DefaultSettings> 24 | <Permissions>ReadWriteDocument</Permissions> 25 | <VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0"> 26 | <Hosts> 27 | <Host xsi:type="Notebook"> 28 | <DesktopFormFactor> 29 | <GetStarted> 30 | <Title resid="GetStarted.Title"/> 31 | <Description resid="GetStarted.Description"/> 32 | <LearnMoreUrl resid="GetStarted.LearnMoreUrl"/> 33 | </GetStarted> 34 | <FunctionFile resid="Commands.Url" /> 35 | <ExtensionPoint xsi:type="PrimaryCommandSurface"> 36 | <OfficeTab id="TabHome"> 37 | <Group id="CommandsGroup"> 38 | <Label resid="CommandsGroup.Label" /> 39 | <Icon> 40 | <bt:Image size="16" resid="Icon.16x16" /> 41 | <bt:Image size="32" resid="Icon.32x32" /> 42 | <bt:Image size="80" resid="Icon.80x80" /> 43 | </Icon> 44 | <Control xsi:type="Button" id="TaskpaneButton"> 45 | <Label resid="TaskpaneButton.Label" /> 46 | <Supertip> 47 | <Title resid="TaskpaneButton.Label" /> 48 | <Description resid="TaskpaneButton.Tooltip" /> 49 | </Supertip> 50 | <Icon> 51 | <bt:Image size="16" resid="Icon.16x16" /> 52 | <bt:Image size="32" resid="Icon.32x32" /> 53 | <bt:Image size="80" resid="Icon.80x80" /> 54 | </Icon> 55 | <Action xsi:type="ShowTaskpane"> 56 | <TaskpaneId>ButtonId1</TaskpaneId> 57 | <SourceLocation resid="Taskpane.Url" /> 58 | </Action> 59 | </Control> 60 | </Group> 61 | </OfficeTab> 62 | </ExtensionPoint> 63 | </DesktopFormFactor> 64 | </Host> 65 | </Hosts> 66 | <Resources> 67 | <bt:Images> 68 | <bt:Image id="Icon.16x16" DefaultValue="https://localhost:3000/assets/icon-16.png"/> 69 | <bt:Image id="Icon.32x32" DefaultValue="https://localhost:3000/assets/icon-32.png"/> 70 | <bt:Image id="Icon.80x80" DefaultValue="https://localhost:3000/assets/icon-80.png"/> 71 | </bt:Images> 72 | <bt:Urls> 73 | <bt:Url id="GetStarted.LearnMoreUrl" DefaultValue="https://go.microsoft.com/fwlink/?LinkId=276812" /> 74 | <bt:Url id="Commands.Url" DefaultValue="https://localhost:3000/commands.html" /> 75 | <bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3000/taskpane.html" /> 76 | </bt:Urls> 77 | <bt:ShortStrings> 78 | <bt:String id="GetStarted.Title" DefaultValue="Get started with your sample add-in!" /> 79 | <bt:String id="CommandsGroup.Label" DefaultValue="Commands Group" /> 80 | <bt:String id="TaskpaneButton.Label" DefaultValue="Show Task Pane" /> 81 | </bt:ShortStrings> 82 | <bt:LongStrings> 83 | <bt:String id="GetStarted.Description" DefaultValue="Your sample add-in loaded successfully. Go to the HOME tab and click the 'Show Task Pane' button to get started." /> 84 | <bt:String id="TaskpaneButton.Tooltip" DefaultValue="Click to Show a Taskpane" /> 85 | </bt:LongStrings> 86 | </Resources> 87 | </VersionOverrides> 88 | </OfficeApp> -------------------------------------------------------------------------------- /manifest.outlook.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 2 | <OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" 3 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 | xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" 5 | xmlns:mailappor="http://schemas.microsoft.com/office/mailappversionoverrides/1.0" xsi:type="MailApp"> 6 | <Id>05c2e1c9-3e1d-406e-9a91-e9ac64854143</Id> 7 | <Version>1.0.0.0</Version> 8 | <ProviderName>Contoso</ProviderName> 9 | <DefaultLocale>en-US</DefaultLocale> 10 | <DisplayName DefaultValue="Contoso Task Pane Add-in"/> 11 | <Description DefaultValue="A template to get started."/> 12 | <IconUrl DefaultValue="https://localhost:3000/assets/icon-64.png"/> 13 | <HighResolutionIconUrl DefaultValue="https://localhost:3000/assets/icon-128.png"/> 14 | <SupportUrl DefaultValue="https://www.contoso.com/help"/> 15 | <AppDomains> 16 | <AppDomain>https://www.contoso.com</AppDomain> 17 | </AppDomains> 18 | <Hosts> 19 | <Host Name="Mailbox" /> 20 | </Hosts> 21 | <!-- The <Requirements> element overridden by any <Requirements> element inside a <VersionOverrides> element. --> 22 | <Requirements> 23 | <Sets> 24 | <Set Name="Mailbox" MinVersion="1.1" /> 25 | </Sets> 26 | </Requirements> 27 | <!-- The <FormSettins> element is required for validation, but is ignored when there is a <VersionOverrides> element. --> 28 | <FormSettings> 29 | <Form xsi:type="ItemRead"> 30 | <DesktopSettings> 31 | <SourceLocation DefaultValue="https://localhost:3000/taskpane.html"/> 32 | <RequestedHeight>250</RequestedHeight> 33 | </DesktopSettings> 34 | </Form> 35 | </FormSettings> 36 | <Permissions>ReadWriteItem</Permissions> 37 | <!-- The <Rule> element is required for validation, but is ignored when there is a <VersionOverrides> element. --> 38 | <Rule xsi:type="RuleCollection" Mode="Or"> 39 | <Rule xsi:type="ItemIs" ItemType="Message" FormType="Read" /> 40 | </Rule> 41 | <VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides" xsi:type="VersionOverridesV1_0"> 42 | <VersionOverrides xmlns="http://schemas.microsoft.com/office/mailappversionoverrides/1.1" xsi:type="VersionOverridesV1_1"> 43 | <Requirements> 44 | <bt:Sets DefaultMinVersion="1.3"> 45 | <bt:Set Name="Mailbox" /> 46 | </bt:Sets> 47 | </Requirements> 48 | <Hosts> 49 | <Host xsi:type="MailHost"> 50 | <DesktopFormFactor> 51 | <FunctionFile resid="Commands.Url" /> 52 | <ExtensionPoint xsi:type="MessageComposeCommandSurface"> 53 | <OfficeTab id="TabDefault"> 54 | <Group id="msgComposeGroup"> 55 | <Label resid="GroupLabel" /> 56 | <Control xsi:type="Button" id="msgComposeOpenPaneButton"> 57 | <Label resid="TaskpaneButton.Label" /> 58 | <Supertip> 59 | <Title resid="TaskpaneButton.Label" /> 60 | <Description resid="TaskpaneButton.Tooltip" /> 61 | </Supertip> 62 | <Icon> 63 | <bt:Image size="16" resid="Icon.16x16" /> 64 | <bt:Image size="32" resid="Icon.32x32" /> 65 | <bt:Image size="80" resid="Icon.80x80" /> 66 | </Icon> 67 | <Action xsi:type="ShowTaskpane"> 68 | <SourceLocation resid="Taskpane.Url" /> 69 | </Action> 70 | </Control> 71 | <Control xsi:type="Button" id="ActionButton"> 72 | <Label resid="ActionButton.Label"/> 73 | <Supertip> 74 | <Title resid="ActionButton.Label"/> 75 | <Description resid="ActionButton.Tooltip"/> 76 | </Supertip> 77 | <Icon> 78 | <bt:Image size="16" resid="Icon.16x16"/> 79 | <bt:Image size="32" resid="Icon.32x32"/> 80 | <bt:Image size="80" resid="Icon.80x80"/> 81 | </Icon> 82 | <Action xsi:type="ExecuteFunction"> 83 | <FunctionName>action</FunctionName> 84 | </Action> 85 | </Control> 86 | </Group> 87 | </OfficeTab> 88 | </ExtensionPoint> 89 | </DesktopFormFactor> 90 | </Host> 91 | </Hosts> 92 | <Resources> 93 | <bt:Images> 94 | <bt:Image id="Icon.16x16" DefaultValue="https://localhost:3000/assets/icon-16.png"/> 95 | <bt:Image id="Icon.32x32" DefaultValue="https://localhost:3000/assets/icon-32.png"/> 96 | <bt:Image id="Icon.80x80" DefaultValue="https://localhost:3000/assets/icon-80.png"/> 97 | </bt:Images> 98 | <bt:Urls> 99 | <bt:Url id="Commands.Url" DefaultValue="https://localhost:3000/commands.html" /> 100 | <bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3000/taskpane.html" /> 101 | </bt:Urls> 102 | <bt:ShortStrings> 103 | <bt:String id="GroupLabel" DefaultValue="Contoso Add-in"/> 104 | <bt:String id="TaskpaneButton.Label" DefaultValue="Show Task Pane"/> 105 | <bt:String id="ActionButton.Label" DefaultValue="Perform an action"/> 106 | </bt:ShortStrings> 107 | <bt:LongStrings> 108 | <bt:String id="TaskpaneButton.Tooltip" DefaultValue="Opens a Task Pane that enables users to insert text."/> 109 | <bt:String id="ActionButton.Tooltip" DefaultValue="Perform an action when clicked."/> 110 | </bt:LongStrings> 111 | </Resources> 112 | </VersionOverrides> 113 | </VersionOverrides> 114 | </OfficeApp> -------------------------------------------------------------------------------- /manifest.powerpoint.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 2 | <OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" 3 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 | xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" 5 | xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="TaskPaneApp"> 6 | <Id>05c2e1c9-3e1d-406e-9a91-e9ac64854143</Id> 7 | <Version>1.0.0.0</Version> 8 | <ProviderName>Contoso</ProviderName> 9 | <DefaultLocale>en-US</DefaultLocale> 10 | <DisplayName DefaultValue="Contoso Task Pane Add-in"/> 11 | <Description DefaultValue="A template to get started."/> 12 | <IconUrl DefaultValue="https://localhost:3000/assets/icon-32.png"/> 13 | <HighResolutionIconUrl DefaultValue="https://localhost:3000/assets/icon-64.png"/> 14 | <SupportUrl DefaultValue="https://www.contoso.com/help"/> 15 | <AppDomains> 16 | <AppDomain>https://www.contoso.com</AppDomain> 17 | </AppDomains> 18 | <Hosts> 19 | <Host Name="Presentation"/> 20 | </Hosts> 21 | <DefaultSettings> 22 | <SourceLocation DefaultValue="https://localhost:3000/taskpane.html"/> 23 | </DefaultSettings> 24 | <Permissions>ReadWriteDocument</Permissions> 25 | <VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0"> 26 | <Hosts> 27 | <Host xsi:type="Presentation"> 28 | <DesktopFormFactor> 29 | <GetStarted> 30 | <Title resid="GetStarted.Title"/> 31 | <Description resid="GetStarted.Description"/> 32 | <LearnMoreUrl resid="GetStarted.LearnMoreUrl"/> 33 | </GetStarted> 34 | <FunctionFile resid="Commands.Url" /> 35 | <ExtensionPoint xsi:type="PrimaryCommandSurface"> 36 | <OfficeTab id="TabHome"> 37 | <Group id="CommandsGroup"> 38 | <Label resid="CommandsGroup.Label" /> 39 | <Icon> 40 | <bt:Image size="16" resid="Icon.16x16" /> 41 | <bt:Image size="32" resid="Icon.32x32" /> 42 | <bt:Image size="80" resid="Icon.80x80" /> 43 | </Icon> 44 | <Control xsi:type="Button" id="TaskpaneButton"> 45 | <Label resid="TaskpaneButton.Label" /> 46 | <Supertip> 47 | <Title resid="TaskpaneButton.Label" /> 48 | <Description resid="TaskpaneButton.Tooltip" /> 49 | </Supertip> 50 | <Icon> 51 | <bt:Image size="16" resid="Icon.16x16" /> 52 | <bt:Image size="32" resid="Icon.32x32" /> 53 | <bt:Image size="80" resid="Icon.80x80" /> 54 | </Icon> 55 | <Action xsi:type="ShowTaskpane"> 56 | <TaskpaneId>ButtonId1</TaskpaneId> 57 | <SourceLocation resid="Taskpane.Url" /> 58 | </Action> 59 | </Control> 60 | </Group> 61 | </OfficeTab> 62 | </ExtensionPoint> 63 | </DesktopFormFactor> 64 | </Host> 65 | </Hosts> 66 | <Resources> 67 | <bt:Images> 68 | <bt:Image id="Icon.16x16" DefaultValue="https://localhost:3000/assets/icon-16.png"/> 69 | <bt:Image id="Icon.32x32" DefaultValue="https://localhost:3000/assets/icon-32.png"/> 70 | <bt:Image id="Icon.80x80" DefaultValue="https://localhost:3000/assets/icon-80.png"/> 71 | </bt:Images> 72 | <bt:Urls> 73 | <bt:Url id="GetStarted.LearnMoreUrl" DefaultValue="https://go.microsoft.com/fwlink/?LinkId=276812" /> 74 | <bt:Url id="Commands.Url" DefaultValue="https://localhost:3000/commands.html" /> 75 | <bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3000/taskpane.html" /> 76 | </bt:Urls> 77 | <bt:ShortStrings> 78 | <bt:String id="GetStarted.Title" DefaultValue="Get started with your sample add-in!" /> 79 | <bt:String id="CommandsGroup.Label" DefaultValue="Commands Group" /> 80 | <bt:String id="TaskpaneButton.Label" DefaultValue="Show Task Pane" /> 81 | </bt:ShortStrings> 82 | <bt:LongStrings> 83 | <bt:String id="GetStarted.Description" DefaultValue="Your sample add-in loaded successfully. Go to the HOME tab and click the 'Show Task Pane' button to get started." /> 84 | <bt:String id="TaskpaneButton.Tooltip" DefaultValue="Click to Show a Taskpane" /> 85 | </bt:LongStrings> 86 | </Resources> 87 | </VersionOverrides> 88 | </OfficeApp> -------------------------------------------------------------------------------- /manifest.project.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 2 | <OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" 3 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 | xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" 5 | xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="TaskPaneApp"> 6 | <Id>05c2e1c9-3e1d-406e-9a91-e9ac64854143</Id> 7 | <Version>1.0.0.0</Version> 8 | <ProviderName>Contoso</ProviderName> 9 | <DefaultLocale>en-US</DefaultLocale> 10 | <DisplayName DefaultValue="Contoso Task Pane Add-in"/> 11 | <Description DefaultValue="A template to get started."/> 12 | <IconUrl DefaultValue="https://localhost:3000/assets/icon-32.png"/> 13 | <HighResolutionIconUrl DefaultValue="https://localhost:3000/assets/icon-64.png"/> 14 | <SupportUrl DefaultValue="https://www.contoso.com/help"/> 15 | <AppDomains> 16 | <AppDomain>https://www.contoso.com</AppDomain> 17 | </AppDomains> 18 | <Hosts> 19 | <Host Name="Project"/> 20 | </Hosts> 21 | <DefaultSettings> 22 | <SourceLocation DefaultValue="https://localhost:3000/taskpane.html"/> 23 | </DefaultSettings> 24 | <Permissions>ReadWriteDocument</Permissions> 25 | </OfficeApp> -------------------------------------------------------------------------------- /manifest.word.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 2 | <OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="TaskPaneApp"> 3 | <Id>9bb7a975-b568-4b2d-9683-39fd2900118f</Id> 4 | <Version>1.0.0.0</Version> 5 | <ProviderName>Contoso</ProviderName> 6 | <DefaultLocale>en-US</DefaultLocale> 7 | <DisplayName DefaultValue="Contoso Task Pane Add-in"/> 8 | <Description DefaultValue="A template to get started."/> 9 | <IconUrl DefaultValue="https://localhost:3000/assets/icon-32.png"/> 10 | <HighResolutionIconUrl DefaultValue="https://localhost:3000/assets/icon-64.png"/> 11 | <SupportUrl DefaultValue="https://www.contoso.com/help"/> 12 | <AppDomains> 13 | <AppDomain>https://www.contoso.com</AppDomain> 14 | </AppDomains> 15 | <Hosts> 16 | <Host Name="Document"/> 17 | </Hosts> 18 | <DefaultSettings> 19 | <SourceLocation DefaultValue="https://localhost:3000/taskpane.html"/> 20 | </DefaultSettings> 21 | <Permissions>ReadWriteDocument</Permissions> 22 | <VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0"> 23 | <Hosts> 24 | <Host xsi:type="Document"> 25 | <DesktopFormFactor> 26 | <GetStarted> 27 | <Title resid="GetStarted.Title"/> 28 | <Description resid="GetStarted.Description"/> 29 | <LearnMoreUrl resid="GetStarted.LearnMoreUrl"/> 30 | </GetStarted> 31 | <FunctionFile resid="Commands.Url" /> 32 | <ExtensionPoint xsi:type="PrimaryCommandSurface"> 33 | <OfficeTab id="TabHome"> 34 | <Group id="CommandsGroup"> 35 | <Label resid="CommandsGroup.Label"/> 36 | <Icon> 37 | <bt:Image size="16" resid="Icon.16x16"/> 38 | <bt:Image size="32" resid="Icon.32x32"/> 39 | <bt:Image size="80" resid="Icon.80x80"/> 40 | </Icon> 41 | <Control xsi:type="Button" id="TaskpaneButton"> 42 | <Label resid="TaskpaneButton.Label"/> 43 | <Supertip> 44 | <Title resid="TaskpaneButton.Label"/> 45 | <Description resid="TaskpaneButton.Tooltip"/> 46 | </Supertip> 47 | <Icon> 48 | <bt:Image size="16" resid="Icon.16x16"/> 49 | <bt:Image size="32" resid="Icon.32x32"/> 50 | <bt:Image size="80" resid="Icon.80x80"/> 51 | </Icon> 52 | <Action xsi:type="ShowTaskpane"> 53 | <TaskpaneId>ButtonId1</TaskpaneId> 54 | <SourceLocation resid="Taskpane.Url"/> 55 | </Action> 56 | </Control> 57 | </Group> 58 | </OfficeTab> 59 | </ExtensionPoint> 60 | </DesktopFormFactor> 61 | </Host> 62 | </Hosts> 63 | <Resources> 64 | <bt:Images> 65 | <bt:Image id="Icon.16x16" DefaultValue="https://localhost:3000/assets/icon-16.png"/> 66 | <bt:Image id="Icon.32x32" DefaultValue="https://localhost:3000/assets/icon-32.png"/> 67 | <bt:Image id="Icon.80x80" DefaultValue="https://localhost:3000/assets/icon-80.png"/> 68 | </bt:Images> 69 | <bt:Urls> 70 | <bt:Url id="GetStarted.LearnMoreUrl" DefaultValue="https://go.microsoft.com/fwlink/?LinkId=276812"/> 71 | <bt:Url id="Commands.Url" DefaultValue="https://localhost:3000/commands.html" /> 72 | <bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3000/taskpane.html"/> 73 | </bt:Urls> 74 | <bt:ShortStrings> 75 | <bt:String id="GetStarted.Title" DefaultValue="Get started with your sample add-in!" /> 76 | <bt:String id="CommandsGroup.Label" DefaultValue="Commands Group" /> 77 | <bt:String id="TaskpaneButton.Label" DefaultValue="Show Task Pane" /> 78 | </bt:ShortStrings> 79 | <bt:LongStrings> 80 | <bt:String id="GetStarted.Description" DefaultValue="Your sample add-in loaded successfully. Go to the HOME tab and click the 'Show Task Pane' button to get started." /> 81 | <bt:String id="TaskpaneButton.Tooltip" DefaultValue="Click to Show a Task Pane" /> 82 | </bt:LongStrings> 83 | </Resources> 84 | </VersionOverrides> 85 | </OfficeApp> -------------------------------------------------------------------------------- /manifest.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 2 | <OfficeApp xmlns="http://schemas.microsoft.com/office/appforoffice/1.1" 3 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 | xmlns:bt="http://schemas.microsoft.com/office/officeappbasictypes/1.0" 5 | xmlns:ov="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="TaskPaneApp"> 6 | <Id>05c2e1c9-3e1d-406e-9a91-e9ac64854143</Id> 7 | <Version>1.0.0.0</Version> 8 | <ProviderName>Contoso</ProviderName> 9 | <DefaultLocale>en-US</DefaultLocale> 10 | <DisplayName DefaultValue="Contoso Task Pane Add-in"/> 11 | <Description DefaultValue="A template to get started."/> 12 | <IconUrl DefaultValue="https://localhost:3000/assets/icon-32.png"/> 13 | <HighResolutionIconUrl DefaultValue="https://localhost:3000/assets/icon-64.png"/> 14 | <SupportUrl DefaultValue="https://www.contoso.com/help"/> 15 | <AppDomains> 16 | <AppDomain>https://www.contoso.com</AppDomain> 17 | </AppDomains> 18 | <Hosts> 19 | <Host Name="Document"/> 20 | <Host Name="Notebook"/> 21 | <Host Name="Presentation"/> 22 | <Host Name="Workbook"/> 23 | </Hosts> 24 | <DefaultSettings> 25 | <SourceLocation DefaultValue="https://localhost:3000/taskpane.html"/> 26 | </DefaultSettings> 27 | <Permissions>ReadWriteDocument</Permissions> 28 | <VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0"> 29 | <Hosts> 30 | <Host xsi:type="Document"> 31 | <DesktopFormFactor> 32 | <GetStarted> 33 | <Title resid="GetStarted.Title"/> 34 | <Description resid="GetStarted.Description"/> 35 | <LearnMoreUrl resid="GetStarted.LearnMoreUrl"/> 36 | </GetStarted> 37 | <FunctionFile resid="Commands.Url" /> 38 | <ExtensionPoint xsi:type="PrimaryCommandSurface"> 39 | <OfficeTab id="TabHome"> 40 | <Group id="CommandsGroup"> 41 | <Label resid="CommandsGroup.Label"/> 42 | <Icon> 43 | <bt:Image size="16" resid="Icon.16x16"/> 44 | <bt:Image size="32" resid="Icon.32x32"/> 45 | <bt:Image size="80" resid="Icon.80x80"/> 46 | </Icon> 47 | <Control xsi:type="Button" id="TaskpaneButton"> 48 | <Label resid="TaskpaneButton.Label"/> 49 | <Supertip> 50 | <Title resid="TaskpaneButton.Label"/> 51 | <Description resid="TaskpaneButton.Tooltip"/> 52 | </Supertip> 53 | <Icon> 54 | <bt:Image size="16" resid="Icon.16x16"/> 55 | <bt:Image size="32" resid="Icon.32x32"/> 56 | <bt:Image size="80" resid="Icon.80x80"/> 57 | </Icon> 58 | <Action xsi:type="ShowTaskpane"> 59 | <TaskpaneId>ButtonId1</TaskpaneId> 60 | <SourceLocation resid="Taskpane.Url"/> 61 | </Action> 62 | </Control> 63 | </Group> 64 | </OfficeTab> 65 | </ExtensionPoint> 66 | </DesktopFormFactor> 67 | </Host> 68 | <Host xsi:type="Notebook"> 69 | <DesktopFormFactor> 70 | <GetStarted> 71 | <Title resid="GetStarted.Title"/> 72 | <Description resid="GetStarted.Description"/> 73 | <LearnMoreUrl resid="GetStarted.LearnMoreUrl"/> 74 | </GetStarted> 75 | <FunctionFile resid="Commands.Url" /> 76 | <ExtensionPoint xsi:type="PrimaryCommandSurface"> 77 | <OfficeTab id="TabHome"> 78 | <Group id="CommandsGroup"> 79 | <Label resid="CommandsGroup.Label" /> 80 | <Icon> 81 | <bt:Image size="16" resid="Icon.16x16" /> 82 | <bt:Image size="32" resid="Icon.32x32" /> 83 | <bt:Image size="80" resid="Icon.80x80" /> 84 | </Icon> 85 | <Control xsi:type="Button" id="TaskpaneButton"> 86 | <Label resid="TaskpaneButton.Label" /> 87 | <Supertip> 88 | <Title resid="TaskpaneButton.Label" /> 89 | <Description resid="TaskpaneButton.Tooltip" /> 90 | </Supertip> 91 | <Icon> 92 | <bt:Image size="16" resid="Icon.16x16" /> 93 | <bt:Image size="32" resid="Icon.32x32" /> 94 | <bt:Image size="80" resid="Icon.80x80" /> 95 | </Icon> 96 | <Action xsi:type="ShowTaskpane"> 97 | <TaskpaneId>ButtonId1</TaskpaneId> 98 | <SourceLocation resid="Taskpane.Url" /> 99 | </Action> 100 | </Control> 101 | </Group> 102 | </OfficeTab> 103 | </ExtensionPoint> 104 | </DesktopFormFactor> 105 | </Host> 106 | <Host xsi:type="Presentation"> 107 | <DesktopFormFactor> 108 | <GetStarted> 109 | <Title resid="GetStarted.Title"/> 110 | <Description resid="GetStarted.Description"/> 111 | <LearnMoreUrl resid="GetStarted.LearnMoreUrl"/> 112 | </GetStarted> 113 | <FunctionFile resid="Commands.Url" /> 114 | <ExtensionPoint xsi:type="PrimaryCommandSurface"> 115 | <OfficeTab id="TabHome"> 116 | <Group id="CommandsGroup"> 117 | <Label resid="CommandsGroup.Label" /> 118 | <Icon> 119 | <bt:Image size="16" resid="Icon.16x16" /> 120 | <bt:Image size="32" resid="Icon.32x32" /> 121 | <bt:Image size="80" resid="Icon.80x80" /> 122 | </Icon> 123 | <Control xsi:type="Button" id="TaskpaneButton"> 124 | <Label resid="TaskpaneButton.Label" /> 125 | <Supertip> 126 | <Title resid="TaskpaneButton.Label" /> 127 | <Description resid="TaskpaneButton.Tooltip" /> 128 | </Supertip> 129 | <Icon> 130 | <bt:Image size="16" resid="Icon.16x16" /> 131 | <bt:Image size="32" resid="Icon.32x32" /> 132 | <bt:Image size="80" resid="Icon.80x80" /> 133 | </Icon> 134 | <Action xsi:type="ShowTaskpane"> 135 | <TaskpaneId>ButtonId1</TaskpaneId> 136 | <SourceLocation resid="Taskpane.Url" /> 137 | </Action> 138 | </Control> 139 | </Group> 140 | </OfficeTab> 141 | </ExtensionPoint> 142 | </DesktopFormFactor> 143 | </Host> 144 | <Host xsi:type="Workbook"> 145 | <DesktopFormFactor> 146 | <GetStarted> 147 | <Title resid="GetStarted.Title"/> 148 | <Description resid="GetStarted.Description"/> 149 | <LearnMoreUrl resid="GetStarted.LearnMoreUrl"/> 150 | </GetStarted> 151 | <FunctionFile resid="Commands.Url" /> 152 | <ExtensionPoint xsi:type="PrimaryCommandSurface"> 153 | <OfficeTab id="TabHome"> 154 | <Group id="CommandsGroup"> 155 | <Label resid="CommandsGroup.Label" /> 156 | <Icon> 157 | <bt:Image size="16" resid="Icon.16x16" /> 158 | <bt:Image size="32" resid="Icon.32x32" /> 159 | <bt:Image size="80" resid="Icon.80x80" /> 160 | </Icon> 161 | <Control xsi:type="Button" id="TaskpaneButton"> 162 | <Label resid="TaskpaneButton.Label" /> 163 | <Supertip> 164 | <Title resid="TaskpaneButton.Label" /> 165 | <Description resid="TaskpaneButton.Tooltip" /> 166 | </Supertip> 167 | <Icon> 168 | <bt:Image size="16" resid="Icon.16x16" /> 169 | <bt:Image size="32" resid="Icon.32x32" /> 170 | <bt:Image size="80" resid="Icon.80x80" /> 171 | </Icon> 172 | <Action xsi:type="ShowTaskpane"> 173 | <TaskpaneId>ButtonId1</TaskpaneId> 174 | <SourceLocation resid="Taskpane.Url" /> 175 | </Action> 176 | </Control> 177 | </Group> 178 | </OfficeTab> 179 | </ExtensionPoint> 180 | </DesktopFormFactor> 181 | </Host> 182 | </Hosts> 183 | <Resources> 184 | <bt:Images> 185 | <bt:Image id="Icon.16x16" DefaultValue="https://localhost:3000/assets/icon-16.png"/> 186 | <bt:Image id="Icon.32x32" DefaultValue="https://localhost:3000/assets/icon-32.png"/> 187 | <bt:Image id="Icon.80x80" DefaultValue="https://localhost:3000/assets/icon-80.png"/> 188 | </bt:Images> 189 | <bt:Urls> 190 | <bt:Url id="GetStarted.LearnMoreUrl" DefaultValue="https://go.microsoft.com/fwlink/?LinkId=276812" /> 191 | <bt:Url id="Commands.Url" DefaultValue="https://localhost:3000/commands.html" /> 192 | <bt:Url id="Taskpane.Url" DefaultValue="https://localhost:3000/taskpane.html" /> 193 | </bt:Urls> 194 | <bt:ShortStrings> 195 | <bt:String id="GetStarted.Title" DefaultValue="Get started with your sample add-in!" /> 196 | <bt:String id="CommandsGroup.Label" DefaultValue="Commands Group" /> 197 | <bt:String id="TaskpaneButton.Label" DefaultValue="Show Task Pane" /> 198 | </bt:ShortStrings> 199 | <bt:LongStrings> 200 | <bt:String id="GetStarted.Description" DefaultValue="Your sample add-in loaded successfully. Go to the HOME tab and click the 'Show Task Pane' button to get started." /> 201 | <bt:String id="TaskpaneButton.Tooltip" DefaultValue="Click to Show a Taskpane" /> 202 | </bt:LongStrings> 203 | </Resources> 204 | </VersionOverrides> 205 | </OfficeApp> 206 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "office-addin-taskpane-react-js", 3 | "version": "0.0.1", 4 | "repository": { 5 | "type": "git", 6 | "url": "https://github.com/OfficeDev/Office-Addin-TaskPane-React-JS.git" 7 | }, 8 | "license": "MIT", 9 | "config": { 10 | "app_to_debug": "excel", 11 | "app_type_to_debug": "desktop", 12 | "dev_server_port": 3000 13 | }, 14 | "engines": { 15 | "node": ">=16 <21", 16 | "npm": ">=7 <11" 17 | }, 18 | "scripts": { 19 | "build": "webpack --mode production", 20 | "build:dev": "webpack --mode development", 21 | "convert-to-single-host": "node convertToSingleHost.js", 22 | "dev-server": "webpack serve --mode development", 23 | "lint": "office-addin-lint check", 24 | "lint:fix": "office-addin-lint fix", 25 | "prettier": "office-addin-lint prettier", 26 | "signin": "office-addin-dev-settings m365-account login", 27 | "signout": "office-addin-dev-settings m365-account logout", 28 | "start": "office-addin-debugging start manifest.xml", 29 | "stop": "office-addin-debugging stop manifest.xml", 30 | "test": "npm run test:unit && npm run test:e2e", 31 | "test:e2e": "mocha -r ts-node/register test/end-to-end/*.ts", 32 | "test:unit": "mocha -r ts-node/register test/unit/*.test.ts", 33 | "validate": "office-addin-manifest validate manifest.xml", 34 | "watch": "webpack --mode development --watch" 35 | }, 36 | "dependencies": { 37 | "@fluentui/react-components": "^9.55.1", 38 | "@fluentui/react-icons": "^2.0.264", 39 | "core-js": "^3.36.0", 40 | "es6-promise": "^4.2.8", 41 | "react": "^18.2.0", 42 | "react-dom": "^18.2.0", 43 | "regenerator-runtime": "^0.14.1" 44 | }, 45 | "devDependencies": { 46 | "@babel/core": "^7.24.0", 47 | "@babel/plugin-transform-class-properties": "^7.3.0", 48 | "@babel/preset-env": "^7.25.4", 49 | "@babel/preset-typescript": "^7.24.1", 50 | "@babel/preset-react": "^7.24.1", 51 | "@types/es6-collections": "^0.5.36", 52 | "@types/mocha": "^10.0.6", 53 | "@types/node": "^20.11.25", 54 | "@types/office-js": "^1.0.377", 55 | "@types/office-runtime": "^1.0.35", 56 | "@types/react": "^18.2.64", 57 | "@types/react-dom": "^18.2.21", 58 | "@types/webpack": "^5.28.5", 59 | "acorn": "^8.11.3", 60 | "babel-loader": "^9.1.3", 61 | "babel-polyfill": "^6.26.0", 62 | "copy-webpack-plugin": "^12.0.2", 63 | "eslint-plugin-office-addins": "^4.0.3", 64 | "eslint-plugin-react": "^7.28.0", 65 | "file-loader": "^6.2.0", 66 | "find-process": "^1.4.4", 67 | "html-loader": "^5.0.0", 68 | "html-webpack-plugin": "^5.6.0", 69 | "less": "^4.2.0", 70 | "less-loader": "^12.2.0", 71 | "mocha": "^11.1.0", 72 | "office-addin-cli": "^2.0.3", 73 | "office-addin-debugging": "^6.0.3", 74 | "office-addin-dev-certs": "^2.0.3", 75 | "office-addin-lint": "^3.0.3", 76 | "office-addin-manifest": "^2.0.3", 77 | "office-addin-mock": "^3.0.3", 78 | "office-addin-prettier-config": "^2.0.1", 79 | "office-addin-test-helpers": "^2.0.3", 80 | "office-addin-test-server": "^2.0.3", 81 | "os-browserify": "^0.3.0", 82 | "process": "^0.11.10", 83 | "source-map-loader": "^5.0.0", 84 | "ts-loader": "^9.5.1", 85 | "ts-node": "^10.9.2", 86 | "typescript": "^5.4.2", 87 | "webpack": "^5.95.0", 88 | "webpack-cli": "^5.1.4", 89 | "webpack-dev-server": "5.1.0" 90 | }, 91 | "prettier": "office-addin-prettier-config", 92 | "browserslist": [ 93 | "last 2 versions", 94 | "ie 11" 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /src/commands/commands.html: -------------------------------------------------------------------------------- 1 | @@ -1,18 +0,0 @@ 2 | <!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. --> 3 | 4 | <!DOCTYPE html> 5 | <html> 6 | 7 | <head> 8 | <meta charset="UTF-8" /> 9 | <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> 10 | 11 | <!-- Office JavaScript API --> 12 | <script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script> 13 | </head> 14 | 15 | <body> 16 | 17 | </body> 18 | 19 | </html> -------------------------------------------------------------------------------- /src/commands/commands.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. 3 | * See LICENSE in the project root for license information. 4 | */ 5 | 6 | /* global Office */ 7 | 8 | Office.onReady(() => { 9 | // If needed, Office.js is ready to be called. 10 | }); 11 | 12 | /** 13 | * Shows a notification when the add-in command is executed. 14 | * @param event {Office.AddinCommands.Event} 15 | */ 16 | function action(event) { 17 | const message = { 18 | type: Office.MailboxEnums.ItemNotificationMessageType.InformationalMessage, 19 | message: "Performed action.", 20 | icon: "Icon.80x80", 21 | persistent: true, 22 | }; 23 | 24 | // Show a notification message. 25 | Office.context.mailbox.item?.notificationMessages.replaceAsync( 26 | "ActionPerformanceNotification", 27 | message 28 | ); 29 | 30 | // Be sure to indicate when the add-in command function is complete. 31 | event.completed(); 32 | } 33 | 34 | // Register the function with Office. 35 | Office.actions.associate("action", action); 36 | -------------------------------------------------------------------------------- /src/taskpane/components/App.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import PropTypes from "prop-types"; 3 | import Header from "./Header"; 4 | import HeroList from "./HeroList"; 5 | import TextInsertion from "./TextInsertion"; 6 | import { makeStyles } from "@fluentui/react-components"; 7 | import { Ribbon24Regular, LockOpen24Regular, DesignIdeas24Regular } from "@fluentui/react-icons"; 8 | import { insertText } from "../taskpane"; 9 | 10 | const useStyles = makeStyles({ 11 | root: { 12 | minHeight: "100vh", 13 | }, 14 | }); 15 | 16 | const App = (props) => { 17 | const { title } = props; 18 | const styles = useStyles(); 19 | // The list items are static and won't change at runtime, 20 | // so this should be an ordinary const, not a part of state. 21 | const listItems = [ 22 | { 23 | icon: <Ribbon24Regular />, 24 | primaryText: "Achieve more with Office integration", 25 | }, 26 | { 27 | icon: <LockOpen24Regular />, 28 | primaryText: "Unlock features and functionality", 29 | }, 30 | { 31 | icon: <DesignIdeas24Regular />, 32 | primaryText: "Create and visualize like a pro", 33 | }, 34 | ]; 35 | 36 | return ( 37 | <div className={styles.root}> 38 | <Header logo="assets/logo-filled.png" title={title} message="Welcome" /> 39 | <HeroList message="Discover what this add-in can do for you today!" items={listItems} /> 40 | <TextInsertion insertText={insertText} /> 41 | </div> 42 | ); 43 | }; 44 | 45 | App.propTypes = { 46 | title: PropTypes.string, 47 | }; 48 | 49 | export default App; 50 | -------------------------------------------------------------------------------- /src/taskpane/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Image, tokens, makeStyles } from "@fluentui/react-components"; 4 | 5 | const useStyles = makeStyles({ 6 | welcome__header: { 7 | display: "flex", 8 | flexDirection: "column", 9 | alignItems: "center", 10 | paddingBottom: "30px", 11 | paddingTop: "100px", 12 | backgroundColor: tokens.colorNeutralBackground3, 13 | }, 14 | message: { 15 | fontSize: tokens.fontSizeHero900, 16 | fontWeight: tokens.fontWeightRegular, 17 | fontColor: tokens.colorNeutralBackgroundStatic, 18 | }, 19 | }); 20 | 21 | const Header = (props) => { 22 | const { title, logo, message } = props; 23 | const styles = useStyles(); 24 | 25 | return ( 26 | <section className={styles.welcome__header}> 27 | <Image width="90" height="90" src={logo} alt={title} /> 28 | <h1 className={styles.message}>{message}</h1> 29 | </section> 30 | ); 31 | }; 32 | 33 | Header.propTypes = { 34 | title: PropTypes.string, 35 | logo: PropTypes.string, 36 | message: PropTypes.string, 37 | }; 38 | 39 | export default Header; 40 | -------------------------------------------------------------------------------- /src/taskpane/components/HeroList.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { tokens, makeStyles } from "@fluentui/react-components"; 4 | 5 | const useStyles = makeStyles({ 6 | list: { 7 | marginTop: "20px", 8 | }, 9 | listItem: { 10 | paddingBottom: "20px", 11 | display: "flex", 12 | }, 13 | icon: { 14 | marginRight: "10px", 15 | }, 16 | itemText: { 17 | fontSize: tokens.fontSizeBase300, 18 | fontColor: tokens.colorNeutralBackgroundStatic, 19 | }, 20 | welcome__main: { 21 | width: "100%", 22 | display: "flex", 23 | flexDirection: "column", 24 | alignItems: "center", 25 | }, 26 | message: { 27 | fontSize: tokens.fontSizeBase500, 28 | fontColor: tokens.colorNeutralBackgroundStatic, 29 | fontWeight: tokens.fontWeightRegular, 30 | paddingLeft: "10px", 31 | paddingRight: "10px", 32 | }, 33 | }); 34 | 35 | const HeroList = (props) => { 36 | const { items, message } = props; 37 | const styles = useStyles(); 38 | 39 | const listItems = items.map((item, index) => ( 40 | <li className={styles.listItem} key={index}> 41 | <i className={styles.icon}>{item.icon}</i> 42 | <span className={styles.itemText}>{item.primaryText}</span> 43 | </li> 44 | )); 45 | return ( 46 | <div className={styles.welcome__main}> 47 | <h2 className={styles.message}>{message}</h2> 48 | <ul className={styles.list}>{listItems}</ul> 49 | </div> 50 | ); 51 | }; 52 | 53 | HeroList.propTypes = { 54 | items: PropTypes.arrayOf( 55 | PropTypes.shape({ 56 | icon: PropTypes.element.isRequired, 57 | primaryText: PropTypes.string.isRequired, 58 | }) 59 | ).isRequired, 60 | message: PropTypes.string.isRequired, 61 | }; 62 | 63 | export default HeroList; 64 | -------------------------------------------------------------------------------- /src/taskpane/components/TextInsertion.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { useState } from "react"; 3 | import { Button, Field, Textarea, tokens, makeStyles } from "@fluentui/react-components"; 4 | import PropTypes from "prop-types"; 5 | 6 | const useStyles = makeStyles({ 7 | instructions: { 8 | fontWeight: tokens.fontWeightSemibold, 9 | marginTop: "20px", 10 | marginBottom: "10px", 11 | }, 12 | textPromptAndInsertion: { 13 | display: "flex", 14 | flexDirection: "column", 15 | alignItems: "center", 16 | }, 17 | textAreaField: { 18 | marginLeft: "20px", 19 | marginTop: "30px", 20 | marginBottom: "20px", 21 | marginRight: "20px", 22 | maxWidth: "50%", 23 | }, 24 | }); 25 | 26 | const TextInsertion = (props) => { 27 | const [text, setText] = useState("Some text."); 28 | 29 | const handleTextInsertion = async () => { 30 | await props.insertText(text); 31 | }; 32 | 33 | const handleTextChange = async (event) => { 34 | setText(event.target.value); 35 | }; 36 | 37 | const styles = useStyles(); 38 | 39 | return ( 40 | <div className={styles.textPromptAndInsertion}> 41 | <Field className={styles.textAreaField} size="large" label="Enter text to be inserted into the document."> 42 | <Textarea size="large" value={text} onChange={handleTextChange} /> 43 | </Field> 44 | <Field className={styles.instructions}>Click the button to insert text.</Field> 45 | <Button appearance="primary" disabled={false} size="large" onClick={handleTextInsertion}> 46 | Insert text 47 | </Button> 48 | </div> 49 | ); 50 | }; 51 | 52 | TextInsertion.propTypes = { 53 | insertText: PropTypes.func.isRequired, 54 | }; 55 | 56 | export default TextInsertion; 57 | -------------------------------------------------------------------------------- /src/taskpane/excel.js: -------------------------------------------------------------------------------- 1 | /* global Excel console */ 2 | 3 | export async function insertText(text) { 4 | // Write text to the top left cell. 5 | try { 6 | await Excel.run(async (context) => { 7 | const sheet = context.workbook.worksheets.getActiveWorksheet(); 8 | const range = sheet.getRange("A1"); 9 | range.values = [[text]]; 10 | range.format.autofitColumns(); 11 | await context.sync(); 12 | }); 13 | } catch (error) { 14 | console.log("Error: " + error); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/taskpane/index.jsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import App from "./components/App"; 4 | import { FluentProvider, webLightTheme } from "@fluentui/react-components"; 5 | 6 | /* global document, Office, module, require */ 7 | 8 | const title = "Contoso Task Pane Add-in"; 9 | 10 | const rootElement = document.getElementById("container"); 11 | const root = rootElement ? createRoot(rootElement) : undefined; 12 | 13 | /* Render application after Office initializes */ 14 | Office.onReady(() => { 15 | root?.render( 16 | <FluentProvider theme={webLightTheme}> 17 | <App title={title} /> 18 | </FluentProvider> 19 | ); 20 | }); 21 | 22 | if (module.hot) { 23 | module.hot.accept("./components/App", () => { 24 | const NextApp = require("./components/App").default; 25 | root?.render(NextApp); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/taskpane/onenote.js: -------------------------------------------------------------------------------- 1 | /* global OneNote console */ 2 | 3 | export async function insertText(text) { 4 | // Write text to the title. 5 | try { 6 | await OneNote.run(async (context) => { 7 | const page = context.application.getActivePage(); 8 | page.title = text; 9 | await context.sync(); 10 | }); 11 | } catch (error) { 12 | console.log("Error: " + error); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/taskpane/outlook.js: -------------------------------------------------------------------------------- 1 | /* global Office console */ 2 | 3 | export async function insertText(text) { 4 | // Write text to the cursor point in the compose surface. 5 | try { 6 | Office.context.mailbox.item?.body.setSelectedDataAsync( 7 | text, 8 | { coercionType: Office.CoercionType.Text }, 9 | (asyncResult) => { 10 | if (asyncResult.status === Office.AsyncResultStatus.Failed) { 11 | throw asyncResult.error.message; 12 | } 13 | } 14 | ); 15 | } catch (error) { 16 | console.log("Error: " + error); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/taskpane/powerpoint.js: -------------------------------------------------------------------------------- 1 | /* global PowerPoint console */ 2 | 3 | export async function insertText(text) { 4 | try { 5 | await PowerPoint.run(async (context) => { 6 | const slide = context.presentation.getSelectedSlides().getItemAt(0); 7 | const textBox = slide.shapes.addTextBox(text); 8 | textBox.fill.setSolidColor("white"); 9 | textBox.lineFormat.color = "black"; 10 | textBox.lineFormat.weight = 1; 11 | textBox.lineFormat.dashStyle = PowerPoint.ShapeLineDashStyle.solid; 12 | await context.sync(); 13 | }); 14 | } catch (error) { 15 | console.log("Error: " + error); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/taskpane/project.js: -------------------------------------------------------------------------------- 1 | /* global Office console */ 2 | 3 | export async function insertText(text) { 4 | // Write text to the task notes field. 5 | try { 6 | // Get the GUID of the selected task 7 | Office.context.document.getSelectedTaskAsync((result) => { 8 | let taskGuid; 9 | if (result.status === Office.AsyncResultStatus.Succeeded) { 10 | taskGuid = result.value; 11 | 12 | // Set the specified fields for the selected task. 13 | const targetFields = [Office.ProjectTaskFields.Name, Office.ProjectTaskFields.Notes]; 14 | const fieldValues = ["New task name", text]; 15 | 16 | // Set the field value. If the call is successful, set the next field. 17 | for (let index = 0; index < targetFields.length; index++) { 18 | Office.context.document.setTaskFieldAsync( 19 | taskGuid, 20 | targetFields[index], 21 | fieldValues[index], 22 | (result) => { 23 | if (result.status === Office.AsyncResultStatus.Succeeded) { 24 | index++; 25 | } else { 26 | console.log(result.error); 27 | } 28 | } 29 | ); 30 | } 31 | } else { 32 | console.log(result.error); 33 | } 34 | }); 35 | } catch (error) { 36 | console.error(error); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/taskpane/taskpane.html: -------------------------------------------------------------------------------- 1 | <!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. --> 2 | <!-- See LICENSE in the project root for license information --> 3 | 4 | <!doctype html> 5 | <html lang="en" data-framework="javascript"> 6 | 7 | <head> 8 | <meta charset="UTF-8" /> 9 | <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> 10 | <meta name="viewport" content="width=device-width, initial-scale=1"> 11 | <title>Contoso Task Pane Add-in 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 27 | 31 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/taskpane/taskpane.js: -------------------------------------------------------------------------------- 1 | import { insertText as insertTextInExcel } from "./excel"; 2 | import { insertText as insertTextInOneNote } from "./onenote"; 3 | import { insertText as insertTextInOutlook } from "./outlook"; 4 | import { insertText as insertTextInPowerPoint } from "./powerpoint"; 5 | import { insertText as insertTextInProject } from "./project"; 6 | import { insertText as insertTextInWord } from "./word"; 7 | 8 | /* global Office */ 9 | 10 | export async function insertText(text) { 11 | Office.onReady(async (info) => { 12 | switch (info.host) { 13 | case Office.HostType.Excel: 14 | await insertTextInExcel(text); 15 | break; 16 | case Office.HostType.OneNote: 17 | await insertTextInOneNote(text); 18 | break; 19 | case Office.HostType.Outlook: 20 | await insertTextInOutlook(text); 21 | break; 22 | case Office.HostType.Project: 23 | await insertTextInProject(text); 24 | break; 25 | case Office.HostType.PowerPoint: 26 | await insertTextInPowerPoint(text); 27 | break; 28 | case Office.HostType.Word: 29 | await insertTextInWord(text); 30 | break; 31 | default: { 32 | throw new Error("Don't know how to insert text when running in ${info.host}."); 33 | } 34 | } 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /src/taskpane/word.js: -------------------------------------------------------------------------------- 1 | /* global Word console */ 2 | 3 | export async function insertText(text) { 4 | // Write text to the document. 5 | try { 6 | await Word.run(async (context) => { 7 | let body = context.document.body; 8 | body.insertParagraph(text, Word.InsertLocation.end); 9 | await context.sync(); 10 | }); 11 | } catch (error) { 12 | console.log("Error: " + error); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/end-to-end/src/host-tests.ts: -------------------------------------------------------------------------------- 1 | import { insertText as insertExcelText } from "../../../src/taskpane/excel"; 2 | import { insertText as insertPowerPointText } from "../../../src/taskpane/powerpoint"; 3 | import { insertText as insertWordText } from "../../../src/taskpane/word"; 4 | import * as testHelpers from "./test-helpers"; 5 | import { sendTestResults } from "office-addin-test-helpers"; 6 | 7 | /* global Excel PowerPoint Word */ 8 | 9 | let testValues: any = []; 10 | 11 | export const testExcelEnd2End = async (testServerPort: number): Promise => { 12 | try { 13 | // Execute taskpane code 14 | await insertExcelText("Hello Excel End2End Test"); 15 | await testHelpers.sleep(2000); 16 | 17 | // Get output of executed taskpane code 18 | await Excel.run(async (context) => { 19 | const range = context.workbook.getSelectedRange(); 20 | range.load("values"); 21 | await context.sync(); 22 | await testHelpers.sleep(2000); 23 | 24 | // send test results 25 | testHelpers.addTestResult(testValues, "output-message", range.values[0][0], "Hello Excel End2End Test"); 26 | await sendTestResults(testValues, testServerPort); 27 | testValues.pop(); 28 | await testHelpers.closeWorkbook(); 29 | Promise.resolve(); 30 | }); 31 | } catch (error) { 32 | testHelpers.addTestResult(testValues, "output-message", getErrorMessage(error), ""); 33 | await sendTestResults(testValues, testServerPort); 34 | testValues.pop(); 35 | Promise.reject(); 36 | } 37 | }; 38 | 39 | export const testPowerPointEnd2End = async (testServerPort: number): Promise => { 40 | try { 41 | const textToInsert = "Hello PowerPoint End2End Test"; 42 | 43 | // Execute taskpane code 44 | await insertPowerPointText(textToInsert); 45 | await testHelpers.sleep(2000); 46 | 47 | // Get output of executed taskpane code 48 | PowerPoint.run(async (context: PowerPoint.RequestContext) => { 49 | // get text from inserted text shape 50 | const slide = context.presentation.getSelectedSlides().getItemAt(0); 51 | // eslint-disable-next-line office-addins/load-object-before-read, office-addins/call-sync-before-read 52 | const shapes = slide.shapes; 53 | slide.shapes.load(["textFrame/textRange/text"]); 54 | await context.sync(); 55 | const shape = shapes.items[shapes.items.length - 1]; 56 | const text = shape.textFrame.textRange.text; 57 | 58 | // send test results 59 | testHelpers.addTestResult(testValues, "output-message", text, textToInsert); 60 | await sendTestResults(testValues, testServerPort); 61 | testValues.pop(); 62 | Promise.resolve(); 63 | }); 64 | } catch (error) { 65 | testHelpers.addTestResult(testValues, "output-message", getErrorMessage(error), ""); 66 | await sendTestResults(testValues, testServerPort); 67 | testValues.pop(); 68 | Promise.reject(); 69 | } 70 | }; 71 | 72 | export const testWordEnd2End = async (testServerPort: number): Promise => { 73 | try { 74 | // Execute taskpane code 75 | await insertWordText("Hello Word End2End Test"); 76 | await testHelpers.sleep(2000); 77 | 78 | // Get output of executed taskpane code 79 | Word.run(async (context) => { 80 | var firstParagraph = context.document.body.paragraphs.getFirst(); 81 | firstParagraph.load("text"); 82 | await context.sync(); 83 | await testHelpers.sleep(2000); 84 | 85 | // send test results 86 | testHelpers.addTestResult(testValues, "output-message", firstParagraph.text, "Hello Word End2End Test"); 87 | await sendTestResults(testValues, testServerPort); 88 | testValues.pop(); 89 | Promise.resolve(); 90 | }); 91 | } catch (error) { 92 | testHelpers.addTestResult(testValues, "output-message", getErrorMessage(error), ""); 93 | await sendTestResults(testValues, testServerPort); 94 | testValues.pop(); 95 | Promise.reject(); 96 | } 97 | }; 98 | 99 | const getErrorMessage = (error: any): string => { 100 | if (error instanceof Error) { 101 | if ("stack" in error) { 102 | return error.stack; 103 | } else { 104 | return `${error.name}: ${error.message}`; 105 | } 106 | } else { 107 | return error; 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /test/end-to-end/src/test-helpers.ts: -------------------------------------------------------------------------------- 1 | import * as childProcess from "child_process"; 2 | 3 | /* global Excel, process, Promise, setTimeout */ 4 | 5 | export async function closeDesktopApplication(application: string): Promise { 6 | let processName: string = ""; 7 | switch (application.toLowerCase()) { 8 | case "excel": 9 | processName = "Excel"; 10 | break; 11 | case "powerpoint": 12 | processName = process.platform === "win32" ? "Powerpnt" : "PowerPoint"; 13 | break; 14 | case "onenote": 15 | processName = "Onenote"; 16 | break; 17 | case "outlook": 18 | processName = "Outlook"; 19 | break; 20 | case "project": 21 | processName = "Project"; 22 | break; 23 | case "word": 24 | processName = process.platform === "win32" ? "Winword" : "Word"; 25 | break; 26 | default: 27 | throw new Error(`${application} is not a valid Office desktop application.`); 28 | } 29 | 30 | await sleep(3000); // wait for host to settle 31 | try { 32 | let cmdLine: string; 33 | if (process.platform == "win32") { 34 | cmdLine = `tskill ${processName}`; 35 | } else { 36 | cmdLine = `pkill ${processName}`; 37 | } 38 | 39 | return await executeCommandLine(cmdLine); 40 | } catch (err) { 41 | throw new Error(`Unable to kill ${application} process. ${err}`); 42 | } 43 | } 44 | 45 | export async function closeWorkbook(): Promise { 46 | await sleep(1000); 47 | await Excel.run(async (context) => context.workbook.close(Excel.CloseBehavior.skipSave)); 48 | } 49 | 50 | export function addTestResult(testValues: any[], resultName: string, resultValue: any, expectedValue: any) { 51 | var data = {}; 52 | data["expectedValue"] = expectedValue; 53 | data["resultName"] = resultName; 54 | data["resultValue"] = resultValue; 55 | testValues.push(data); 56 | } 57 | 58 | export async function sleep(ms: number): Promise { 59 | return new Promise((resolve) => setTimeout(resolve, ms)); 60 | } 61 | 62 | async function executeCommandLine(cmdLine: string): Promise { 63 | return new Promise((resolve, reject) => { 64 | childProcess.exec(cmdLine, (error) => { 65 | if (error) { 66 | reject(false); 67 | } else { 68 | resolve(true); 69 | } 70 | }); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /test/end-to-end/src/test-taskpane.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Contoso Task Pane Add-in 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 27 | 31 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /test/end-to-end/src/test.index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import App from "../../../src/taskpane/components/App"; 4 | import { FluentProvider, webLightTheme } from "@fluentui/react-components"; 5 | import { pingTestServer } from "office-addin-test-helpers"; 6 | import { testExcelEnd2End, testPowerPointEnd2End, testWordEnd2End } from "./host-tests"; 7 | 8 | /* global document, Office, module, require */ 9 | 10 | const port: number = 4201; 11 | 12 | const title = "Contoso Task Pane Add-in"; 13 | 14 | const rootElement: HTMLElement = document.getElementById("container"); 15 | const root = createRoot(rootElement); 16 | 17 | /* Render application after Office initializes */ 18 | Office.onReady(async (info) => { 19 | const testServerResponse: { status?: number } = await pingTestServer(port); 20 | if (testServerResponse?.status === 200) { 21 | //render(App); 22 | root.render( 23 | 24 | 25 | 26 | ); 27 | 28 | switch (info.host) { 29 | case Office.HostType.Excel: { 30 | return testExcelEnd2End(port); 31 | } 32 | case Office.HostType.PowerPoint: { 33 | return testPowerPointEnd2End(port); 34 | } 35 | case Office.HostType.Word: { 36 | return testWordEnd2End(port); 37 | } 38 | } 39 | } 40 | }); 41 | 42 | if ((module as any).hot) { 43 | (module as any).hot.accept("../../../src/taskpane/components/App", () => { 44 | const NextApp = require("../../../src/taskpane/components/App").default; 45 | root.render(NextApp); 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /test/end-to-end/test-manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | da968be6-628b-4f14-ba3c-3e614effa9bd 7 | 1.0.0.0 8 | Contoso 9 | en-US 10 | 11 | 12 | 13 | 14 | 15 | 16 | https://www.contoso.com 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ReadWriteDocument 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 | -------------------------------------------------------------------------------- /test/end-to-end/ui-test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import { parseNumber } from "office-addin-cli"; 3 | import { AppType, startDebugging, stopDebugging } from "office-addin-debugging"; 4 | import { toOfficeApp } from "office-addin-manifest"; 5 | import * as officeAddinTestHelpers from "office-addin-test-helpers"; 6 | import * as officeAddinTestServer from "office-addin-test-server"; 7 | import * as path from "path"; 8 | import * as testHelpers from "./src/test-helpers"; 9 | 10 | /* global process, describe, before, it, after */ 11 | 12 | const hosts = ["Excel", "PowerPoint", "Word"]; 13 | const manifestPath = path.resolve(`${process.cwd()}/test/end-to-end/test-manifest.xml`); 14 | const testServerPort: number = 4201; 15 | 16 | hosts.forEach(function (host) { 17 | const testServer = new officeAddinTestServer.TestServer(testServerPort); 18 | let testValues: any = []; 19 | 20 | describe(`Test ${host} Task Pane Project`, function () { 21 | before(`Setup test environment and sideload ${host}`, async function () { 22 | this.timeout(0); 23 | // Start test server and ping to ensure it's started 24 | const testServerStarted = await testServer.startTestServer(true /* mochaTest */); 25 | const serverResponse = await officeAddinTestHelpers.pingTestServer(testServerPort); 26 | assert.strictEqual(testServerStarted, true); 27 | assert.strictEqual(serverResponse["status"], 200); 28 | 29 | // Call startDebugging to start dev-server and sideload 30 | const devServerCmd = `npm run dev-server -- --config ./test/end-to-end/webpack.config.js `; 31 | const devServerPort = parseNumber(process.env.npm_package_config_dev_server_port || 3000); 32 | await startDebugging(manifestPath, { 33 | app: toOfficeApp(host), 34 | appType: AppType.Desktop, 35 | devServerCommandLine: devServerCmd, 36 | devServerPort: devServerPort, 37 | enableDebugging: false, 38 | }); 39 | }), 40 | describe(`Get test results for ${host} taskpane project`, function () { 41 | it("Validate expected result count", async function () { 42 | this.timeout(0); 43 | testValues = await testServer.getTestResults(); 44 | assert.strictEqual(testValues.length > 0, true); 45 | }); 46 | it("Validate expected result name", async function () { 47 | assert.strictEqual(testValues[0].resultName, "output-message"); 48 | }); 49 | it("Validate expected result", async function () { 50 | assert.strictEqual(testValues[0].resultValue, testValues[0].expectedValue); 51 | }); 52 | }); 53 | after(`Teardown test environment and shutdown ${host}`, async function () { 54 | this.timeout(0); 55 | // Stop the test server 56 | const stopTestServer = await testServer.stopTestServer(); 57 | assert.strictEqual(stopTestServer, true); 58 | 59 | const applicationClosed = await testHelpers.closeDesktopApplication(host); 60 | assert.strictEqual(applicationClosed, true); 61 | }); 62 | }); 63 | }); 64 | 65 | after(`Unregister the add-in`, async function () { 66 | return stopDebugging(manifestPath); 67 | }); 68 | -------------------------------------------------------------------------------- /test/end-to-end/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | 3 | const devCerts = require("office-addin-dev-certs"); 4 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 5 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 6 | const path = require("path"); 7 | const webpack = require("webpack"); 8 | 9 | async function getHttpsOptions() { 10 | const httpsOptions = await devCerts.getHttpsServerOptions(); 11 | return { ca: httpsOptions.ca, key: httpsOptions.key, cert: httpsOptions.cert }; 12 | } 13 | 14 | module.exports = async (env, options) => { 15 | const config = { 16 | devtool: "source-map", 17 | entry: { 18 | polyfill: ["core-js/stable", "regenerator-runtime/runtime"], 19 | vendor: ["react", "react-dom", "core-js", "@fluentui/react-components", "@fluentui/react-icons"], 20 | taskpane: [path.resolve(__dirname, "./src/test.index.tsx"), path.resolve(__dirname, "./src/test-taskpane.html")], 21 | }, 22 | output: { 23 | path: path.resolve(__dirname, "testBuild"), 24 | clean: true, 25 | }, 26 | resolve: { 27 | extensions: [".ts", ".tsx", ".html", ".js", ".jsx"], 28 | fallback: { 29 | child_process: false, 30 | fs: false, 31 | os: require.resolve("os-browserify/browser"), 32 | }, 33 | }, 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.jsx?$/, 38 | use: [ 39 | { 40 | loader: "babel-loader", 41 | options: { 42 | presets: ["@babel/preset-env"], 43 | }, 44 | }, 45 | ], 46 | exclude: /node_modules/, 47 | }, 48 | 49 | { 50 | test: /\.ts$/, 51 | exclude: /node_modules/, 52 | use: { 53 | loader: "babel-loader", 54 | options: { 55 | presets: ["@babel/preset-typescript"], 56 | }, 57 | }, 58 | }, 59 | { 60 | test: /\.tsx?$/, 61 | use: ["ts-loader"], 62 | exclude: /node_modules/, 63 | }, 64 | { 65 | test: /\.html$/, 66 | exclude: /node_modules/, 67 | use: "html-loader", 68 | }, 69 | { 70 | test: /\.(png|jpg|jpeg|ttf|woff|woff2|gif|ico)$/, 71 | type: "asset/resource", 72 | generator: { 73 | filename: "assets/[name][ext][query]", 74 | }, 75 | }, 76 | ], 77 | }, 78 | plugins: [ 79 | new webpack.ProvidePlugin({ 80 | Promise: ["es6-promise", "Promise"], 81 | process: "process/browser", 82 | }), 83 | new HtmlWebpackPlugin({ 84 | filename: "taskpane.html", 85 | template: path.resolve(__dirname, "./src/test-taskpane.html"), 86 | chunks: ["polyfill", "vendor", "taskpane"], 87 | }), 88 | new CopyWebpackPlugin({ 89 | patterns: [ 90 | { 91 | from: "assets/*", 92 | to: "assets/[name][ext][query]", 93 | }, 94 | ], 95 | }), 96 | ], 97 | devServer: { 98 | static: { 99 | directory: "testBuild", 100 | }, 101 | headers: { 102 | "Access-Control-Allow-Origin": "*", 103 | }, 104 | server: { 105 | type: "https", 106 | options: env.WEBPACK_BUILD || options.https !== undefined ? options.https : await getHttpsOptions(), 107 | }, 108 | port: process.env.npm_package_config_dev_server_port || 3000, 109 | }, 110 | }; 111 | 112 | return config; 113 | }; 114 | -------------------------------------------------------------------------------- /test/unit/excel.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { OfficeMockObject } from "office-addin-mock"; 4 | import { insertText } from "../../src/taskpane/excel"; 5 | 6 | /* global describe, global, it */ 7 | 8 | const ExcelMockData = { 9 | context: { 10 | workbook: { 11 | worksheets: { 12 | range: { 13 | values: [[" "]], 14 | format: { 15 | autofitColumns: function () {}, 16 | }, 17 | }, 18 | getRange: function () { 19 | return this.range; 20 | }, 21 | getActiveWorksheet: function () { 22 | return this; 23 | }, 24 | }, 25 | }, 26 | }, 27 | run: async function (callback) { 28 | await callback(this.context); 29 | }, 30 | }; 31 | 32 | describe("Excel", function () { 33 | it("Inserts text", async function () { 34 | const excelMock: OfficeMockObject = new OfficeMockObject(ExcelMockData); // Mocking the host specific namespace 35 | global.Excel = excelMock as any; 36 | 37 | await insertText("Hello Excel"); 38 | 39 | excelMock.context.workbook.worksheets.range.load("values"); 40 | await excelMock.context.sync(); 41 | 42 | assert.strictEqual(excelMock.context.workbook.worksheets.range.values[0][0], "Hello Excel"); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/unit/powerpoint.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { OfficeMockObject } from "office-addin-mock"; 4 | import { insertText } from "../../src/taskpane/powerpoint"; 5 | 6 | /* global describe, global, it */ 7 | 8 | const shapes = []; 9 | const selectedSlide = { 10 | shapes: { 11 | addTextBox: function (text) { 12 | const shape = { text }; 13 | shapes.push(shape); 14 | }, 15 | items: shapes, 16 | }, 17 | }; 18 | const PowerPointMockData = { 19 | context: { 20 | presentation: { 21 | getSelectedSlides: function () { 22 | return { 23 | getItemAt: function () { 24 | return selectedSlide; 25 | }, 26 | }; 27 | }, 28 | }, 29 | slides: { 30 | items: [selectedSlide], 31 | }, 32 | }, 33 | onReady: async function () {}, 34 | run: async function (callback) { 35 | await callback(this.context); 36 | }, 37 | }; 38 | 39 | describe(`PowerPoint`, function () { 40 | it("Inserts text", async function () { 41 | const officeMock = new OfficeMockObject(PowerPointMockData); 42 | global.PowerPoint = officeMock as any; 43 | 44 | await insertText("Hello PowerPoint"); 45 | 46 | assert.strictEqual(shapes[0].text, "Hello PowerPoint"); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/unit/word.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from "assert"; 2 | import "mocha"; 3 | import { OfficeMockObject } from "office-addin-mock"; 4 | import { insertText } from "../../src/taskpane/word"; 5 | 6 | /* global describe, global, it, Word */ 7 | 8 | const WordMockData = { 9 | context: { 10 | document: { 11 | body: { 12 | paragraph: { 13 | text: "", 14 | }, 15 | insertParagraph: function (paragraphText: string, insertLocation: Word.InsertLocation): Word.Paragraph { 16 | this.paragraph.text = paragraphText; 17 | this.paragraph.insertLocation = insertLocation; 18 | return this.paragraph; 19 | }, 20 | }, 21 | }, 22 | }, 23 | InsertLocation: { 24 | end: "End", 25 | }, 26 | run: async function (callback) { 27 | await callback(this.context); 28 | }, 29 | }; 30 | 31 | describe("Word", function () { 32 | it("Inserts text", async function () { 33 | const wordMock: OfficeMockObject = new OfficeMockObject(WordMockData); // Mocking the host specific namespace 34 | global.Word = wordMock as any; 35 | 36 | await insertText("Hello Word"); 37 | 38 | wordMock.context.document.body.paragraph.load("text"); 39 | await wordMock.context.sync(); 40 | 41 | assert.strictEqual(wordMock.context.document.body.paragraph.text, "Hello Word"); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowUnusedLabels": false, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "jsx": "react", 10 | "module": "ES2020", 11 | "moduleResolution": "node", 12 | "noImplicitReturns": true, 13 | "noUnusedParameters": true, 14 | "outDir": "dist", 15 | "removeComments": false, 16 | "sourceMap": true, 17 | "target": "es5", 18 | "lib": [ 19 | "es7", 20 | "dom" 21 | ], 22 | "pretty": true, 23 | "typeRoots": [ 24 | "node_modules/@types" 25 | ] 26 | }, 27 | "exclude": [ 28 | "node_modules" 29 | ], 30 | "compileOnSave": false, 31 | "buildOnSave": false, 32 | "ts-node": { 33 | "compilerOptions": { 34 | "module": "commonjs" 35 | }, 36 | "files": true 37 | } 38 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | 3 | const devCerts = require("office-addin-dev-certs"); 4 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 5 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 6 | const webpack = require("webpack"); 7 | 8 | const urlDev = "https://localhost:3000/"; 9 | const urlProd = "https://www.contoso.com/"; // CHANGE THIS TO YOUR PRODUCTION DEPLOYMENT LOCATION 10 | 11 | async function getHttpsOptions() { 12 | const httpsOptions = await devCerts.getHttpsServerOptions(); 13 | return { ca: httpsOptions.ca, key: httpsOptions.key, cert: httpsOptions.cert }; 14 | } 15 | 16 | module.exports = async (env, options) => { 17 | const dev = options.mode === "development"; 18 | const config = { 19 | devtool: "source-map", 20 | entry: { 21 | polyfill: ["core-js/stable", "regenerator-runtime/runtime"], 22 | react: ["react", "react-dom"], 23 | taskpane: { 24 | import: ["./src/taskpane/index.jsx", "./src/taskpane/taskpane.html"], 25 | dependOn: "react", 26 | }, 27 | commands: "./src/commands/commands.js", 28 | }, 29 | output: { 30 | clean: true, 31 | }, 32 | resolve: { 33 | extensions: [".js", ".jsx", ".html"], 34 | }, 35 | module: { 36 | rules: [ 37 | { 38 | test: /\.jsx?$/, 39 | use: { 40 | loader: "babel-loader", 41 | }, 42 | exclude: /node_modules/, 43 | }, 44 | { 45 | test: /\.html$/, 46 | exclude: /node_modules/, 47 | use: "html-loader", 48 | }, 49 | { 50 | test: /\.(png|jpg|jpeg|ttf|woff|woff2|gif|ico)$/, 51 | type: "asset/resource", 52 | generator: { 53 | filename: "assets/[name][ext][query]", 54 | }, 55 | }, 56 | ], 57 | }, 58 | plugins: [ 59 | new HtmlWebpackPlugin({ 60 | filename: "taskpane.html", 61 | template: "./src/taskpane/taskpane.html", 62 | chunks: ["polyfill", "taskpane", "react"], 63 | }), 64 | new CopyWebpackPlugin({ 65 | patterns: [ 66 | { 67 | from: "assets/*", 68 | to: "assets/[name][ext][query]", 69 | }, 70 | { 71 | from: "manifest*.xml", 72 | to: "[name]" + "[ext]", 73 | transform(content) { 74 | if (dev) { 75 | return content; 76 | } else { 77 | return content.toString().replace(new RegExp(urlDev, "g"), urlProd); 78 | } 79 | }, 80 | }, 81 | ], 82 | }), 83 | new HtmlWebpackPlugin({ 84 | filename: "commands.html", 85 | template: "./src/commands/commands.html", 86 | chunks: ["polyfill", "commands"], 87 | }), 88 | new webpack.ProvidePlugin({ 89 | Promise: ["es6-promise", "Promise"], 90 | }), 91 | ], 92 | devServer: { 93 | hot: true, 94 | headers: { 95 | "Access-Control-Allow-Origin": "*", 96 | }, 97 | server: { 98 | type: "https", 99 | options: env.WEBPACK_BUILD || options.https !== undefined ? options.https : await getHttpsOptions(), 100 | }, 101 | port: process.env.npm_package_config_dev_server_port || 3000, 102 | }, 103 | }; 104 | 105 | return config; 106 | }; 107 | --------------------------------------------------------------------------------