Initial Project
6
.azure-devops/build.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
steps:
|
||||
- task: Npm@1
|
||||
displayName: 'Build'
|
||||
inputs:
|
||||
command: custom
|
||||
customCommand: 'run build'
|
||||
3
.azure-devops/cleanup.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
steps:
|
||||
- task: mspremier.PostBuildCleanup.PostBuildCleanup-task.PostBuildCleanup@3
|
||||
displayName: "Cleanup"
|
||||
5
.azure-devops/devcerts.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
steps:
|
||||
- script: |
|
||||
echo Install Office-AddinDev-Certs at machine level
|
||||
call npx office-addin-dev-certs install --machine
|
||||
displayName: 'Install add-in dev cert'
|
||||
13
.azure-devops/edgewebview.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
steps:
|
||||
- script: |
|
||||
echo Enable EdgeWebView Loopback
|
||||
call npx office-addin-dev-settings appcontainer EdgeWebView --loopback --yes
|
||||
echo Set Edge WebView Registry Settings
|
||||
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"
|
||||
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}"
|
||||
reg add %PATH1% /f /v Moniker /t REG_SZ /d "microsoft.win32webviewhost_cw5n1h2txyewy"
|
||||
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"
|
||||
reg add %PATH2% /f /v DisplayName /t REG_SZ /d "microsoft.win32webviewhost_cw5n1h2txyewy/123"
|
||||
reg add %PATH2% /f /v Moniker /t REG_SZ /d "123"
|
||||
reg add %PATH2% /f /v ParentMoniker /t REG_SZ /d "microsoft.win32webviewhost_cw5n1h2txyewy"
|
||||
displayName: 'Enable Edge WebView'
|
||||
49
.azure-devops/full-pipeline.yml
Normal file
@@ -0,0 +1,49 @@
|
||||
resources:
|
||||
repositories:
|
||||
- repository: OfficePipelineTemplates
|
||||
type: git
|
||||
name: 1ESPipelineTemplates/OfficePipelineTemplates
|
||||
ref: refs/tags/release
|
||||
extends:
|
||||
template: /v1/Office.Unofficial.PipelineTemplate.yml@OfficePipelineTemplates
|
||||
parameters:
|
||||
pool:
|
||||
name: OE-OfficeClientApps
|
||||
sdl:
|
||||
eslint:
|
||||
configuration: required
|
||||
parser: '@typescript-eslint/parser'
|
||||
parserOptions: sourceType:module
|
||||
stages:
|
||||
- stage: stage
|
||||
jobs:
|
||||
- job: Windows_10_Latest
|
||||
steps:
|
||||
- template: /.azure-devops/install.yml@self
|
||||
- template: /.azure-devops/lint.yml@self
|
||||
- template: /.azure-devops/build.yml@self
|
||||
- template: /.azure-devops/devcerts.yml@self
|
||||
- template: /.azure-devops/edgewebview.yml@self
|
||||
- template: /.azure-devops/test.yml@self
|
||||
parameters:
|
||||
webView: "edge-chromium"
|
||||
# - job: WebView_EdgeLegacy
|
||||
# steps:
|
||||
# - template: /.azure-devops/install.yml@self
|
||||
# - template: /.azure-devops/lint.yml@self
|
||||
# - template: /.azure-devops/build.yml@self
|
||||
# - template: /.azure-devops/devcerts.yml@self
|
||||
# - template: /.azure-devops/edgewebview.yml@self
|
||||
# - template: /.azure-devops/test.yml@self
|
||||
# parameters:
|
||||
# webView: "edge-legacy"
|
||||
# - job: WebView_IE
|
||||
# steps:
|
||||
# - template: /.azure-devops/install.yml@self
|
||||
# - template: /.azure-devops/lint.yml@self
|
||||
# - template: /.azure-devops/build.yml@self
|
||||
# - template: /.azure-devops/devcerts.yml@self
|
||||
# - template: /.azure-devops/edgewebview.yml@self
|
||||
# - template: /.azure-devops/test.yml@self
|
||||
# parameters:
|
||||
# webView: "ie"
|
||||
3
.azure-devops/install.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
steps:
|
||||
- task: Npm@1
|
||||
displayName: 'Install'
|
||||
6
.azure-devops/lint.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
steps:
|
||||
- task: Npm@1
|
||||
displayName: 'Lint'
|
||||
inputs:
|
||||
command: custom
|
||||
customCommand: 'run lint'
|
||||
15
.azure-devops/test.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
parameters:
|
||||
- name: webView
|
||||
type: string
|
||||
default: "edge-chromium"
|
||||
steps:
|
||||
- task: CmdLine@2
|
||||
inputs:
|
||||
script: |
|
||||
echo Setting WebView Type: ${{ parameters.webView }}
|
||||
call npx office-addin-dev-settings webview manifest.xml ${{ parameters.webView }}
|
||||
call npx office-addin-dev-settings webview test/end-to-end/test-manifest.xml ${{ parameters.webView }}
|
||||
echo Running Tests
|
||||
npm run test
|
||||
echo Done running tests
|
||||
displayName: 'Run Tests'
|
||||
8
.eslintrc.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"plugins": [
|
||||
"office-addins"
|
||||
],
|
||||
"extends": [
|
||||
"plugin:office-addins/react"
|
||||
]
|
||||
}
|
||||
1
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @OfficeDev/office-platform-devx
|
||||
49
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: needs triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
# Prerequisites
|
||||
|
||||
Please answer the following questions before submitting an issue.
|
||||
**YOU MAY DELETE THE PREREQUISITES SECTION.**
|
||||
- [ ] I am running the latest version of Node and the tools
|
||||
- [ ] I checked the documentation and found no answer
|
||||
- [ ] I checked to make sure that this issue has not already been filed
|
||||
|
||||
|
||||
# Expected behavior
|
||||
|
||||
Please describe the behavior you were expecting
|
||||
|
||||
|
||||
# Current behavior
|
||||
|
||||
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.
|
||||
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
Please provide detailed steps for reproducing the issue.
|
||||
|
||||
1. step 1
|
||||
2. step 2
|
||||
3. you get it...
|
||||
|
||||
|
||||
## Context
|
||||
|
||||
Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions.
|
||||
|
||||
* Operating System:
|
||||
* Node version:
|
||||
* Office version:
|
||||
* Tool version:
|
||||
|
||||
## Failure Logs
|
||||
|
||||
Please include any relevant log snippets, screenshots or code samples here.
|
||||
31
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
Thank you for your pull request! Please provide the following information.
|
||||
|
||||
---
|
||||
|
||||
**Change Description**:
|
||||
|
||||
Describe what the PR is about.
|
||||
|
||||
1. **Do these changes impact any *npm scripts* commands (in package.json)?** (e.g., running 'npm run start')
|
||||
If Yes, briefly describe what is impacted.
|
||||
|
||||
|
||||
2. **Do these changes impact *VS Code debugging* options (launch.json)?**
|
||||
If Yes, briefly describe what is impacted.
|
||||
|
||||
|
||||
3. **Do these changes impact *template output*?** (e.g., add/remove file, update file location, update file contents)
|
||||
If Yes, briefly describe what is impacted.
|
||||
|
||||
|
||||
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)
|
||||
If Yes, briefly describe what is impacted.
|
||||
|
||||
|
||||
If you answered yes to any of these please do the following:
|
||||
> Include 'Rick-Kirkham' in the review
|
||||
> Make sure the README file is correct
|
||||
|
||||
**Validation/testing performed**:
|
||||
|
||||
Describe manual testing done.
|
||||
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
8
.hintrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": [
|
||||
"development"
|
||||
],
|
||||
"hints": {
|
||||
"typescript-config/strict": "off"
|
||||
}
|
||||
}
|
||||
14
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"ms-edgedevtools.vscode-edge-devtools",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"msoffice.microsoft-office-add-in-debugger"
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
127
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug UI Tests",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
|
||||
"args": [
|
||||
"-u",
|
||||
"bdd",
|
||||
"--timeout",
|
||||
"999999",
|
||||
"--colors",
|
||||
"${workspaceFolder}/test/end-to-end",
|
||||
"-r",
|
||||
"ts-node/register",
|
||||
"${workspaceFolder}/test/end-to-end/*.ts"
|
||||
],
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"runtimeArgs": ["--preserve-symlinks"]
|
||||
},
|
||||
{
|
||||
"name": "Debug Unit Tests",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
|
||||
"args": [
|
||||
"-u",
|
||||
"bdd",
|
||||
"--timeout",
|
||||
"999999",
|
||||
"--colors",
|
||||
"${workspaceFolder}/test/unit",
|
||||
"-r",
|
||||
"ts-node/register",
|
||||
"${workspaceFolder}/test/unit/*.test.ts"
|
||||
],
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"runtimeArgs": ["--preserve-symlinks"]
|
||||
},
|
||||
{
|
||||
"name": "Excel Desktop (Edge Chromium)",
|
||||
"type": "msedge",
|
||||
"request": "attach",
|
||||
"port": 9229,
|
||||
"timeout": 600000,
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"preLaunchTask": "Debug: Excel Desktop",
|
||||
"postDebugTask": "Stop Debug"
|
||||
},
|
||||
{
|
||||
"name": "Excel Desktop (Edge Legacy)",
|
||||
"type": "office-addin",
|
||||
"request": "attach",
|
||||
"url": "https://localhost:3000/taskpane.html?_host_Info=Excel$Win32$16.01$en-US$$$$0",
|
||||
"port": 9222,
|
||||
"timeout": 600000,
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"preLaunchTask": "Debug: Excel Desktop",
|
||||
"postDebugTask": "Stop Debug"
|
||||
},
|
||||
{
|
||||
"name": "Outlook Desktop (Edge Chromium)",
|
||||
"type": "msedge",
|
||||
"request": "attach",
|
||||
"port": 9229,
|
||||
"timeout": 600000,
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"preLaunchTask": "Debug: Outlook Desktop",
|
||||
"postDebugTask": "Stop Debug"
|
||||
},
|
||||
{
|
||||
"name": "Outlook Desktop (Edge Legacy)",
|
||||
"type": "office-addin",
|
||||
"request": "attach",
|
||||
"url": "https://localhost:3000/taskpane.html?_host_Info=Outlook$Win32$16.01$en-US$$$$0",
|
||||
"port": 9222,
|
||||
"timeout": 600000,
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"preLaunchTask": "Debug: Outlook Desktop",
|
||||
"postDebugTask": "Stop Debug"
|
||||
},
|
||||
{
|
||||
"name": "PowerPoint Desktop (Edge Chromium)",
|
||||
"type": "msedge",
|
||||
"request": "attach",
|
||||
"port": 9229,
|
||||
"timeout": 600000,
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"preLaunchTask": "Debug: PowerPoint Desktop",
|
||||
"postDebugTask": "Stop Debug"
|
||||
},
|
||||
{
|
||||
"name": "PowerPoint Desktop (Edge Legacy)",
|
||||
"type": "office-addin",
|
||||
"request": "attach",
|
||||
"url": "https://localhost:3000/taskpane.html?_host_Info=PowerPoint$Win32$16.01$en-US$$$$0",
|
||||
"port": 9222,
|
||||
"timeout": 600000,
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"preLaunchTask": "Debug: PowerPoint Desktop",
|
||||
"postDebugTask": "Stop Debug"
|
||||
},
|
||||
{
|
||||
"name": "Word Desktop (Edge Chromium)",
|
||||
"type": "msedge",
|
||||
"request": "attach",
|
||||
"port": 9229,
|
||||
"timeout": 600000,
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"preLaunchTask": "Debug: Word Desktop",
|
||||
"postDebugTask": "Stop Debug"
|
||||
},
|
||||
{
|
||||
"name": "Word Desktop (Edge Legacy)",
|
||||
"type": "office-addin",
|
||||
"request": "attach",
|
||||
"url": "https://localhost:3000/taskpane.html?_host_Info=Word$Win32$16.01$en-US$$$$0",
|
||||
"port": 9222,
|
||||
"timeout": 600000,
|
||||
"webRoot": "${workspaceRoot}",
|
||||
"preLaunchTask": "Debug: Word Desktop",
|
||||
"postDebugTask": "Stop Debug"
|
||||
}
|
||||
]
|
||||
}
|
||||
8
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript"
|
||||
]
|
||||
}
|
||||
|
||||
160
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build (Development)",
|
||||
"type": "npm",
|
||||
"script": "build:dev",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Build (Production)",
|
||||
"type": "npm",
|
||||
"script": "build",
|
||||
"group": "build",
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Debug: Excel Desktop",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": [
|
||||
"run",
|
||||
"start",
|
||||
"--",
|
||||
"desktop",
|
||||
"--app",
|
||||
"excel"
|
||||
],
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"panel": "dedicated"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Debug: Outlook Desktop",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": [
|
||||
"run",
|
||||
"start",
|
||||
"--",
|
||||
"desktop",
|
||||
"--app",
|
||||
"outlook"
|
||||
],
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"panel": "dedicated"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Debug: PowerPoint Desktop",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": [
|
||||
"run",
|
||||
"start",
|
||||
"--",
|
||||
"desktop",
|
||||
"--app",
|
||||
"powerpoint"
|
||||
],
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"panel": "dedicated"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Debug: Word Desktop",
|
||||
"type": "shell",
|
||||
"command": "npm",
|
||||
"args": [
|
||||
"run",
|
||||
"start",
|
||||
"--",
|
||||
"desktop",
|
||||
"--app",
|
||||
"word"
|
||||
],
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"panel": "dedicated"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Dev Server",
|
||||
"type": "npm",
|
||||
"script": "dev-server",
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"panel": "dedicated"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Install",
|
||||
"type": "npm",
|
||||
"script": "install",
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Lint: Check for problems",
|
||||
"type": "npm",
|
||||
"script": "lint",
|
||||
"problemMatcher": [
|
||||
"$eslint-stylish"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Lint: Fix all auto-fixable problems",
|
||||
"type": "npm",
|
||||
"script": "lint:fix",
|
||||
"problemMatcher": [
|
||||
"$eslint-stylish"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Stop Debug",
|
||||
"type": "npm",
|
||||
"script": "stop",
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Watch",
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"panel": "dedicated"
|
||||
},
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
83
CONTRIBUTING.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Contribute to this code sample
|
||||
|
||||
Thank you for your interest in this sample! Your contributions and improvements will help the developer community.
|
||||
|
||||
## Ways to contribute
|
||||
|
||||
There are several ways you can contribute to this sample: providing better code comments, fixing open issues, and adding new features.
|
||||
|
||||
### Provide better code comments
|
||||
|
||||
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.
|
||||
|
||||
|
||||
In general, we want our code comments to follow these guidelines:
|
||||
|
||||
- Any code that has associated documentation displayed in an IDE (such as IntelliSense, or JavaDocs) has code comments.
|
||||
- Classes, methods, parameters, and return values have clear descriptions.
|
||||
- Exceptions and errors are documented.
|
||||
- Remarks exist for anything special or notable about the code.
|
||||
- Sections of code that have complex algorithms have appropriate comments describing what they do.
|
||||
- Code added from Stack Overflow, or any other source, is clearly attributed.
|
||||
|
||||
### Fix open issues
|
||||
|
||||
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.
|
||||
|
||||
### Add a new feature
|
||||
|
||||
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.
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
We have some guidelines to help maintain a healthy repo and code for everyone.
|
||||
|
||||
### The Contribution License Agreement
|
||||
|
||||
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.
|
||||
|
||||
### Code contribution checklist
|
||||
|
||||
Be sure to satisfy all of the requirements in the following list before submitting a pull request:
|
||||
|
||||
- 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).
|
||||
- Test your code.
|
||||
- Test the UI thoroughly to be sure your change hasn't broken anything.
|
||||
- Keep the size of your code change reasonable. If the repository owner cannot review your code change in 4 hours or less, your pull request may not be reviewed and approved quickly.
|
||||
- Avoid unnecessary changes. 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.
|
||||
|
||||
### Submit a pull request to the master branch
|
||||
|
||||
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.
|
||||
|
||||
1. Submit your pull request against the master branch.
|
||||
2. Sign the CLA, if you haven't already done so.
|
||||
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.
|
||||
4. When the repo admins are satisfied, they will accept and merge the pull request.
|
||||
|
||||
Congratulations, you have successfully contributed to the sample!
|
||||
|
||||
## FAQ
|
||||
|
||||
### Where do I get a Contributor's License Agreement?
|
||||
|
||||
If your pull request requires one, you'll automatically be sent a notice that you need to sign the Contributor's License Agreement (CLA).
|
||||
|
||||
As a community member, you must sign the CLA before you can contribute large submissions to this project. You only need complete and submit the CLA document once. Carefully review the document. You may be required to have your employer sign the document.
|
||||
|
||||
### What happens with my contributions?
|
||||
|
||||
When you submit your changes via a pull request, our team will be notified and will review your pull request. You'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.
|
||||
|
||||
### Who approves pull requests?
|
||||
|
||||
The admin of the repository approves pull requests.
|
||||
|
||||
### How soon will I get a response about my change request or issue?
|
||||
|
||||
We typically review pull requests and respond to issues within 10 business days.
|
||||
|
||||
## More resources
|
||||
|
||||
- To learn more about Markdown, see [Daring Fireball](http://daringfireball.net/).
|
||||
- To learn more about using Git and GitHub, check out the [GitHub Help section](http://help.github.com/).
|
||||
BIN
CableConsolidation.zip
Normal file
23
LICENSE
Normal file
@@ -0,0 +1,23 @@
|
||||
Office Add-in TaskPane
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE
|
||||
38
README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Office-Addin-TaskPane-React
|
||||
|
||||
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.
|
||||
|
||||
## TypeScript
|
||||
|
||||
This template is written using [TypeScript](http://www.typescriptlang.org/). For the JavaScript version of this template, go to [Office-Addin-TaskPane-React-JS](https://github.com/OfficeDev/Office-Addin-TaskPane-React-JS).
|
||||
|
||||
## Debugging
|
||||
|
||||
This template supports debugging using any of the following techniques:
|
||||
|
||||
- [Use a browser's developer tools](https://learn.microsoft.com/office/dev/add-ins/testing/debug-add-ins-in-office-online)
|
||||
- [Attach a debugger from the task pane](https://learn.microsoft.com/office/dev/add-ins/testing/attach-debugger-from-task-pane)
|
||||
- [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)
|
||||
|
||||
## Questions and comments
|
||||
|
||||
We'd love to get your feedback about this sample. You can send your feedback to us in the *Issues* section of this repository.
|
||||
|
||||
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].
|
||||
|
||||
## Join the Microsoft 365 Developer Program
|
||||
|
||||
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.
|
||||
|
||||
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-).
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [Office Add-ins documentation](https://learn.microsoft.com/office/dev/add-ins/overview/office-add-ins)
|
||||
- More Office Add-ins samples at [OfficeDev on Github](https://github.com/officedev)
|
||||
|
||||
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.
|
||||
|
||||
## Copyright
|
||||
|
||||
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
|
||||
130
README_Deployment_Guide.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Deployment Guide: Ubuntu Server (Nginx + SSL)
|
||||
|
||||
Diese Anleitung beschreibt, wie du das fertig gebaute Excel Add-in auf deinem privaten Ubuntu-Server unter der Domain `https://kabel.casademm.de` hosten kannst.
|
||||
|
||||
## Voraussetzung
|
||||
1. Ein Linux-Server (Ubuntu) mit Root/Sudo-Zugriff.
|
||||
2. Die Domain `kabel.casademm.de` muss im DNS-Manager deines Domain-Anbieters auf die IP-Adresse (A-Record) dieses Servers zeigen.
|
||||
3. Du hast lokal auf deinem Entwicklungsrechner den Befehl `npm run build` ausgeführt. Dadurch wurde ein Ordner namens `dist` in deinem Projektverzeichnis (`C:\EWSL_Add_in\CableConsolidation\dist`) erstellt.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 1: Nginx installieren
|
||||
Verbinde dich per SSH mit deinem Ubuntu-Server und aktualisiere die Paketquellen, um danach den Nginx Webserver zu installieren:
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install nginx -y
|
||||
```
|
||||
|
||||
Stelle sicher, dass Nginx läuft und beim Systemstart automatisch mitstartet:
|
||||
```bash
|
||||
sudo systemctl enable nginx
|
||||
sudo systemctl start nginx
|
||||
```
|
||||
|
||||
Falls du die `ufw` Firewall nutzt, erlaube den Nginx-Traffic:
|
||||
```bash
|
||||
sudo ufw allow 'Nginx Full'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 2: Dateien auf den Server kopieren
|
||||
|
||||
1. Erstelle auf dem Server ein Verzeichnis für deine Domain:
|
||||
```bash
|
||||
sudo mkdir -p /var/www/kabel.casademm.de/html
|
||||
```
|
||||
|
||||
2. Passe die Rechte an, damit Nginx (und dein User) darauf zugreifen können:
|
||||
```bash
|
||||
sudo chown -R $USER:$USER /var/www/kabel.casademm.de/html
|
||||
sudo chmod -R 755 /var/www/kabel.casademm.de
|
||||
```
|
||||
|
||||
3. Übertrage nun den Inhalt deines lokalen `dist`-Ordners in dieses Verzeichnis auf dem Server. Das kannst du z.B. über ein SFTP-Programm wie WinSCP oder FileZilla machen.
|
||||
* **Quelle:** `C:\EWSL_Add_in\CableConsolidation\dist\*`
|
||||
* **Ziel (Server):** `/var/www/kabel.casademm.de/html/`
|
||||
|
||||
*(Tipp: Vergewissere dich, dass die Datei `taskpane.html` und der `assets`-Ordner direkt im `/html/`-Verzeichnis liegen!)*
|
||||
|
||||
---
|
||||
|
||||
## Schritt 3: Nginx für die Domain konfigurieren
|
||||
|
||||
Erstelle eine neue Server-Block Konfigurationsdatei für Nginx:
|
||||
|
||||
```bash
|
||||
sudo nano /etc/nginx/sites-available/kabel.casademm.de
|
||||
```
|
||||
|
||||
Kopiere folgenden Inhalt hinein (dies lauscht erstmal nur auf Port 80):
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
root /var/www/kabel.casademm.de/html;
|
||||
index taskpane.html index.html index.htm;
|
||||
|
||||
server_name kabel.casademm.de;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
# Erlaubt CORS, was für Web-Add-Ins nützlich ist
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Speichere die Datei (in Nano: `Strg+O`, `Enter`, `Strg+X`).
|
||||
|
||||
Aktiviere die Konfiguration, indem du einen Symlink erstellst:
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/kabel.casademm.de /etc/nginx/sites-enabled/
|
||||
```
|
||||
|
||||
Prüfe, ob Nginx meckert, und starte neu:
|
||||
```bash
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 4: SSL-Zertifikat sichern (WICHTIG!)
|
||||
Microsoft Office weigert sich strikt, Add-ins ohne gültiges HTTPS-Zertifikat zu laden. Wir nutzen Certbot für ein kostenloses Let's Encrypt Zertifikat.
|
||||
|
||||
1. Installiere Certbot:
|
||||
```bash
|
||||
sudo apt install certbot python3-certbot-nginx -y
|
||||
```
|
||||
|
||||
2. Generiere das Zertifikat:
|
||||
```bash
|
||||
sudo certbot --nginx -d kabel.casademm.de
|
||||
```
|
||||
|
||||
Certbot wird dich nach deiner E-Mail-Adresse fragen und dir anbieten, den Traffic automatisch auf HTTPS umzuleiten (wähle Option "2: Redirect").
|
||||
|
||||
Sobald Certbot fertig ist, läuft dein Server sicher unter `https://kabel.casademm.de`.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 5: In Excel einbinden / ausrollen
|
||||
|
||||
Jetzt wo dein Server online ist, benötigst du (und deine Firma) nur noch eine einzige Datei: Die `manifest.xml`.
|
||||
|
||||
In der `manifest.xml` (welche du ebenfalls in deinem Projekt-Root hast) stehen bereits alle Verweise auf `https://kabel.casademm.de`.
|
||||
|
||||
**Wie lade ich es im Office 365 Admin Center hoch?**
|
||||
1. Die IT geht auf `admin.microsoft.com`.
|
||||
2. Gehe zu **Einstellungen > Integrierte Apps**.
|
||||
3. Klicke auf **Benutzerdefinierte Apps hochladen**.
|
||||
4. Lade die finale `manifest.xml` hoch.
|
||||
5. Weise die App den entsprechenden Benutzern (oder allen) zu.
|
||||
6. Sobald Excel von den Mitarbeitern neugestartet wird, erscheint der neue Button im Menüband!
|
||||
|
||||
*(Für lokale Tests kannst du das Add-In in Excel auch einfach über "Meine Add-ins" > "Zusatz-Add-In hochladen" nutzen.)*
|
||||
51
README_User_Guide.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Kurzanleitung: Kabel-Konsolidierungs Add-In
|
||||
|
||||
Willkommen beim Kabel-Konsolidierungs Tool! Mit diesem Add-In kannst du schnell und effizient hunderte von Kabel-Einträgen aus verschiedenen Arbeitsblättern zu einer einzigen "Kabelliste" zusammenfassen.
|
||||
|
||||
## So startest du das Tool
|
||||
|
||||
1. Öffne Excel.
|
||||
2. Wechsle im oberen Menüband (Ribbon) auf den Reiter **Start**.
|
||||
3. Klicke ganz rechts auf den neuen Button **Start Konsolidierung** (mit dem Kabel-Symbol).
|
||||
4. Das Add-In öffnet sich daraufhin am rechten Bildschirmrand.
|
||||
|
||||
---
|
||||
|
||||
## 1. Blätter auswählen & Dateien hochladen
|
||||
|
||||
Im ersten Schritt sagst du dem Add-In, *woher* es die Kabeldaten nehmen soll:
|
||||
|
||||
* **Interne Blätter:** Hier ist eine Liste aller sichtbaren Arbeitsblätter deines aktuell geöffneten Excel-Dokuments. Hake einfach alle Blätter an, die Kabeldaten enthalten.
|
||||
* **Externe Excel-Dateien hinzufügen:** Wenn deine Kollegen dir weitere Excel-Listen (als `.xlsx`, `.xlsm` oder `.csv`) geschickt haben, musst du diese **nicht** manuell in deine aktuelle Mappe kopieren! Klicke einfach auf den Durchsuchen-Button und lade die Dateien direkt hoch. Das Add-In liest sie im Hintergrund aus und fügt auch diese Arbeitsblätter der Checkliste hinzu.
|
||||
|
||||
Klicke auf `Weiter`, wenn du alle gewünschten Blätter markiert hast.
|
||||
|
||||
---
|
||||
|
||||
## 2. Spalten-Mapping (Das Herzstück!)
|
||||
|
||||
Nun liest das Tool automatisch die ersten 50 Zeilen der gewählten Blätter aus und sucht nach den Kopfzeilen (Spaltennamen), die Kabel-Informationen beinhalten.
|
||||
|
||||
Das Add-In ist "schlau" und erkennt auch Abweichungen (es weiß z.B., dass "Kabelnummer" und "Nr." dasselbe meinen wie "K-Nr.").
|
||||
|
||||
* **Ein grünes "OK":** Das Tool hat alle nötigen Spalten für dieses Arbeitsblatt automatisch gefunden. Du musst hier nichts mehr tun.
|
||||
* **Eine rote Warnung "X Lücken":** Für dieses Blatt konnten nicht alle Spalten automatisch gefunden werden.
|
||||
* Klicke auf das Blatt, um die Details auszuklappen.
|
||||
* Stelle sicher, dass unter **"Kopfzeile (Index 0-basiert)"** die korrekte Zeile angegeben ist, in der die Spaltenüberschriften stehen. Wenn deine Überschriften in Zeile 5 stehen, trage hier die "4" ein (da die Zählung bei 0 beginnt).
|
||||
* Weise dann über das Dropdown-Menü manuell zu, welche Spalte im Dokument unserer Zielspalte (z.B. "von Raum") entspricht.
|
||||
* Wenn ein Blatt komplett leer ist oder versehentlich ausgewählt wurde, lass die Spalten auf "--- Nicht gefunden ---". Das Tool überspringt diese Einträge am Ende ganz einfach, anstatt kaputtzugehen.
|
||||
|
||||
---
|
||||
|
||||
## 3. Konsolidierung
|
||||
|
||||
Sobald du zufrieden bist, klicke unten auf den blauen Button **"Konsolidieren"**.
|
||||
|
||||
Das Add-In wird nun:
|
||||
1. Eine komplett neue Tabelle namens **"Kabelliste"** in deiner Arbeitsmappe erzeugen (bzw. eine alte überschreiben).
|
||||
2. Alle Daten aus den internen UND externen Blättern in diese Tabelle schütten.
|
||||
3. Die Tabelle schön und übersichtlich formatieren (inkl. Filter-Buttons).
|
||||
4. Leere Zeilen automatisch überspringen und leere Spalten sauber auffüllen.
|
||||
|
||||
Fertig! Dir wird am Ende rechts angezeigt, wie viele Zeilen erfolgreich aus den Blättern konsolidiert wurden.
|
||||
Du kannst das Add-In nun schließen oder einen neuen Durchlauf starten.
|
||||
41
SECURITY.md
Normal file
@@ -0,0 +1,41 @@
|
||||
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.8 BLOCK -->
|
||||
|
||||
## Security
|
||||
|
||||
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/).
|
||||
|
||||
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.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
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).
|
||||
|
||||
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).
|
||||
|
||||
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).
|
||||
|
||||
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:
|
||||
|
||||
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||
* Full paths of source file(s) related to the manifestation of the issue
|
||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||
* Any special configuration required to reproduce the issue
|
||||
* Step-by-step instructions to reproduce the issue
|
||||
* Proof-of-concept or exploit code (if possible)
|
||||
* Impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
This information will help us triage your report more quickly.
|
||||
|
||||
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.
|
||||
|
||||
## Preferred Languages
|
||||
|
||||
We prefer all communications to be in English.
|
||||
|
||||
## Policy
|
||||
|
||||
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
|
||||
|
||||
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
||||
BIN
assets/icon-128.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
assets/icon-16.png
Normal file
|
After Width: | Height: | Size: 812 B |
BIN
assets/icon-32.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/icon-64.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
assets/icon-80.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
assets/logo-filled.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
13
babel.config.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"esmodules": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@babel/preset-typescript"
|
||||
]
|
||||
}
|
||||
278
convertToSingleHost.js
Normal file
@@ -0,0 +1,278 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* global require, process, console */
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const util = require("util");
|
||||
const childProcess = require("child_process");
|
||||
const hosts = ["excel", "onenote", "outlook", "powerpoint", "project", "word"];
|
||||
|
||||
if (process.argv.length <= 2) {
|
||||
const hostList = hosts.map((host) => `'${host}'`).join(", ");
|
||||
console.log("SYNTAX: convertToSingleHost.js <host> <manifestType> <projectName> <appId>");
|
||||
console.log();
|
||||
console.log(` host (required): Specifies which Office app will host the add-in: ${hostList}`);
|
||||
console.log(` manifestType: Specify the type of manifest to use: 'xml' or 'json'. Defaults to 'xml'`);
|
||||
console.log(` projectName: The name of the project (use quotes when there are spaces in the name). Defaults to 'My Office Add-in'`);
|
||||
console.log(` appId: The id of the project or 'random' to generate one. Defaults to 'random'`);
|
||||
console.log();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const host = process.argv[2];
|
||||
const manifestType = process.argv[3];
|
||||
const projectName = process.argv[4];
|
||||
let appId = process.argv[5];
|
||||
const testPackages = [
|
||||
"@types/mocha",
|
||||
"@types/node",
|
||||
"mocha",
|
||||
"office-addin-mock",
|
||||
"office-addin-test-helpers",
|
||||
"office-addin-test-server",
|
||||
"ts-node",
|
||||
];
|
||||
const readFileAsync = util.promisify(fs.readFile);
|
||||
const unlinkFileAsync = util.promisify(fs.unlink);
|
||||
const writeFileAsync = util.promisify(fs.writeFile);
|
||||
|
||||
async function modifyProjectForSingleHost(host) {
|
||||
if (!host) {
|
||||
throw new Error("The host was not provided.");
|
||||
}
|
||||
if (!hosts.includes(host)) {
|
||||
throw new Error(`'${host}' is not a supported host.`);
|
||||
}
|
||||
|
||||
await convertProjectToSingleHost(host);
|
||||
await updatePackageJsonForSingleHost(host);
|
||||
await updateLaunchJsonFile(host);
|
||||
}
|
||||
|
||||
async function convertProjectToSingleHost(host) {
|
||||
// Copy host-specific manifest over manifest.xml
|
||||
const manifestContent = await readFileAsync(`./manifest.${host}.xml`, "utf8");
|
||||
await writeFileAsync(`./manifest.xml`, manifestContent);
|
||||
|
||||
// Copy host-specific office-document.ts over src/office-document.ts
|
||||
const srcContent = await readFileAsync(`./src/taskpane/${host}.ts`, "utf8");
|
||||
await writeFileAsync(`./src/taskpane/taskpane.ts`, srcContent);
|
||||
|
||||
// Delete all host-specific files
|
||||
hosts.forEach(async function (host) {
|
||||
await unlinkFileAsync(`./manifest.${host}.xml`);
|
||||
await unlinkFileAsync(`./src/taskpane/${host}.ts`);
|
||||
});
|
||||
|
||||
// Delete test folder
|
||||
deleteFolder(path.resolve(`./test`));
|
||||
|
||||
// Delete the .github folder
|
||||
deleteFolder(path.resolve(`./.github`));
|
||||
|
||||
// Delete CI/CD pipeline files
|
||||
deleteFolder(path.resolve(`./.azure-devops`));
|
||||
|
||||
// Delete repo support files
|
||||
await deleteSupportFiles();
|
||||
}
|
||||
|
||||
async function updatePackageJsonForSingleHost(host) {
|
||||
// Update package.json to reflect selected host
|
||||
const packageJson = `./package.json`;
|
||||
const data = await readFileAsync(packageJson, "utf8");
|
||||
let content = JSON.parse(data);
|
||||
|
||||
// Update 'config' section in package.json to use selected host
|
||||
content.config["app_to_debug"] = host;
|
||||
|
||||
// Remove 'engines' section
|
||||
delete content.engines;
|
||||
|
||||
// Remove scripts that are unrelated to the selected host
|
||||
Object.keys(content.scripts).forEach(function (key) {
|
||||
if (key === "convert-to-single-host") {
|
||||
delete content.scripts[key];
|
||||
}
|
||||
});
|
||||
|
||||
// Remove test-related scripts
|
||||
Object.keys(content.scripts).forEach(function (key) {
|
||||
if (key.includes("test")) {
|
||||
delete content.scripts[key];
|
||||
}
|
||||
});
|
||||
|
||||
// Remove test-related packages
|
||||
Object.keys(content.devDependencies).forEach(function (key) {
|
||||
if (testPackages.includes(key)) {
|
||||
delete content.devDependencies[key];
|
||||
}
|
||||
});
|
||||
|
||||
// Write updated JSON to file
|
||||
await writeFileAsync(packageJson, JSON.stringify(content, null, 2));
|
||||
}
|
||||
|
||||
async function updateLaunchJsonFile(host) {
|
||||
// Remove unneeded configuration from launch.json
|
||||
const launchJson = `.vscode/launch.json`;
|
||||
const launchJsonContent = await readFileAsync(launchJson, "utf8");
|
||||
let content = JSON.parse(launchJsonContent);
|
||||
content.configurations = content.configurations.filter(function (config) {
|
||||
return config.name.toLowerCase().startsWith(host);
|
||||
});
|
||||
|
||||
await writeFileAsync(launchJson, JSON.stringify(content, null, 2));
|
||||
}
|
||||
|
||||
function deleteFolder(folder) {
|
||||
try {
|
||||
if (fs.existsSync(folder)) {
|
||||
fs.readdirSync(folder).forEach(function (file) {
|
||||
const curPath = `${folder}/${file}`;
|
||||
|
||||
if (fs.lstatSync(curPath).isDirectory()) {
|
||||
deleteFolder(curPath);
|
||||
} else {
|
||||
fs.unlinkSync(curPath);
|
||||
}
|
||||
});
|
||||
fs.rmdirSync(folder);
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(`Unable to delete folder "${folder}".\n${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSupportFiles() {
|
||||
await unlinkFileAsync("CONTRIBUTING.md");
|
||||
await unlinkFileAsync("LICENSE");
|
||||
await unlinkFileAsync("README.md");
|
||||
await unlinkFileAsync("SECURITY.md");
|
||||
await unlinkFileAsync("./convertToSingleHost.js");
|
||||
await unlinkFileAsync(".npmrc");
|
||||
await unlinkFileAsync("package-lock.json");
|
||||
}
|
||||
|
||||
async function deleteJSONManifestRelatedFiles() {
|
||||
await unlinkFileAsync("assets/color.png");
|
||||
await unlinkFileAsync("assets/outline.png");
|
||||
}
|
||||
|
||||
async function deleteXMLManifestRelatedFiles() {
|
||||
await unlinkFileAsync("manifest.xml");
|
||||
}
|
||||
|
||||
async function updatePackageJsonForXMLManifest() {
|
||||
const packageJson = `./package.json`;
|
||||
const data = await readFileAsync(packageJson, "utf8");
|
||||
let content = JSON.parse(data);
|
||||
|
||||
// Write updated JSON to file
|
||||
await writeFileAsync(packageJson, JSON.stringify(content, null, 2));
|
||||
}
|
||||
|
||||
async function updatePackageJsonForJSONManifest() {
|
||||
const packageJson = `./package.json`;
|
||||
const data = await readFileAsync(packageJson, "utf8");
|
||||
let content = JSON.parse(data);
|
||||
|
||||
// Change manifest file name extension
|
||||
content.scripts.start = "office-addin-debugging start manifest.json";
|
||||
content.scripts.stop = "office-addin-debugging stop manifest.json";
|
||||
content.scripts.validate = "office-addin-manifest validate manifest.json";
|
||||
|
||||
// Write updated JSON to file
|
||||
await writeFileAsync(packageJson, JSON.stringify(content, null, 2));
|
||||
}
|
||||
|
||||
async function updateTasksJsonFileForJSONManifest() {
|
||||
const tasksJson = `.vscode/tasks.json`;
|
||||
const data = await readFileAsync(tasksJson, "utf8");
|
||||
let content = JSON.parse(data);
|
||||
|
||||
content.tasks.forEach(function (task) {
|
||||
if (task.label.startsWith("Build")) {
|
||||
task.dependsOn = ["Install"];
|
||||
}
|
||||
if (task.label === "Debug: Outlook Desktop") {
|
||||
task.script = "start";
|
||||
task.dependsOn = ["Check OS", "Install"];
|
||||
}
|
||||
});
|
||||
|
||||
const checkOSTask = {
|
||||
label: "Check OS",
|
||||
type: "shell",
|
||||
windows: {
|
||||
command: "echo 'Sideloading in Outlook on Windows is supported'",
|
||||
},
|
||||
linux: {
|
||||
command: "echo 'Sideloading on Linux is not supported' && exit 1",
|
||||
},
|
||||
osx: {
|
||||
command: "echo 'Sideloading in Outlook on Mac is not supported' && exit 1",
|
||||
},
|
||||
presentation: {
|
||||
clear: true,
|
||||
panel: "dedicated",
|
||||
},
|
||||
};
|
||||
|
||||
content.tasks.push(checkOSTask);
|
||||
await writeFileAsync(tasksJson, JSON.stringify(content, null, 2));
|
||||
}
|
||||
|
||||
async function updateWebpackConfigForJSONManifest() {
|
||||
const webPack = `webpack.config.js`;
|
||||
const webPackContent = await readFileAsync(webPack, "utf8");
|
||||
const updatedContent = webPackContent.replace(".xml", ".json");
|
||||
await writeFileAsync(webPack, updatedContent);
|
||||
}
|
||||
|
||||
async function modifyProjectForJSONManifest() {
|
||||
await updatePackageJsonForJSONManifest();
|
||||
await updateWebpackConfigForJSONManifest();
|
||||
await updateTasksJsonFileForJSONManifest();
|
||||
await deleteXMLManifestRelatedFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the project so that it only supports a single host.
|
||||
* @param host The host to support.
|
||||
*/
|
||||
modifyProjectForSingleHost(host).catch((err) => {
|
||||
console.error(`Error modifying for single host: ${err instanceof Error ? err.message : err}`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
let manifestPath = "manifest.xml";
|
||||
|
||||
if (host !== "outlook" || manifestType !== "json") {
|
||||
// Remove things that are only relevant to JSON manifest
|
||||
deleteJSONManifestRelatedFiles();
|
||||
updatePackageJsonForXMLManifest();
|
||||
} else {
|
||||
manifestPath = "manifest.json";
|
||||
modifyProjectForJSONManifest().catch((err) => {
|
||||
console.error(`Error modifying for JSON manifest: ${err instanceof Error ? err.message : err}`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
|
||||
if (projectName) {
|
||||
if (!appId) {
|
||||
appId = "random";
|
||||
}
|
||||
|
||||
// Modify the manifest to include the name and id of the project
|
||||
const cmdLine = `npx office-addin-manifest modify ${manifestPath} -g ${appId} -d "${projectName}"`;
|
||||
childProcess.exec(cmdLine, (error, stdout) => {
|
||||
if (error) {
|
||||
Promise.reject(stdout);
|
||||
} else {
|
||||
Promise.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
BIN
dist/assets/icon-128.png
vendored
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
dist/assets/icon-16.png
vendored
Normal file
|
After Width: | Height: | Size: 812 B |
BIN
dist/assets/icon-32.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
dist/assets/icon-64.png
vendored
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
dist/assets/icon-80.png
vendored
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
dist/assets/logo-filled.png
vendored
Normal file
|
After Width: | Height: | Size: 10 KiB |
1
dist/commands.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@@ -1,18 +0,0 @@<!doctype html><html><head><meta charset="UTF-8"/><meta http-equiv="X-UA-Compatible" content="IE=Edge"/><script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script><script defer="defer" src="polyfill.js"></script><script defer="defer" src="commands.js"></script></head><body></body></html>
|
||||
2
dist/commands.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
Office.onReady((function(){})),Office.actions.associate("action",(function(c){c.completed()}));
|
||||
//# sourceMappingURL=commands.js.map
|
||||
1
dist/commands.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"commands.js","mappings":"AAOAA,OAAOC,SAAQ,WACb,IAYFD,OAAOE,QAAQC,UAAU,UALzB,SAAgBC,GAEdA,EAAMC,WACR","sources":["webpack://office-addin-taskpane-react/./src/commands/commands.ts"],"sourcesContent":["/*\n * Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n * See LICENSE in the project root for license information.\n */\n\n/* global Office */\n\nOffice.onReady(() => {\n // If needed, Office.js is ready to be called.\n});\n\n/**\n * Shows a notification when the add-in command is executed.\n * @param event\n */\nfunction action(event: Office.AddinCommands.Event) {\n // Your code here\n event.completed();\n}\n\nOffice.actions.associate(\"action\", action);\n"],"names":["Office","onReady","actions","associate","event","completed"],"sourceRoot":""}
|
||||
85
dist/manifest.xml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<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">
|
||||
<Id>2c37abde-33e4-4624-b95a-a0aed1526f1b</Id>
|
||||
<Version>1.0.0.0</Version>
|
||||
<ProviderName>SAT Elektrotechnik GmbH</ProviderName>
|
||||
<DefaultLocale>de-DE</DefaultLocale>
|
||||
<DisplayName DefaultValue="Kabel-Konsolidierung"/>
|
||||
<Description DefaultValue="Konsolidiert strukturierte Kabeldaten aus mehreren Tabellenblättern in eine Gesamtliste."/>
|
||||
<IconUrl DefaultValue="https://kabel.casademm.de/assets/icon-32.png"/>
|
||||
<HighResolutionIconUrl DefaultValue="https://kabel.casademm.de/assets/icon-64.png"/>
|
||||
<SupportUrl DefaultValue="https://kabel.casademm.de"/>
|
||||
<AppDomains>
|
||||
<AppDomain>https://kabel.casademm.de</AppDomain>
|
||||
</AppDomains>
|
||||
<Hosts>
|
||||
<Host Name="Workbook"/>
|
||||
</Hosts>
|
||||
<DefaultSettings>
|
||||
<SourceLocation DefaultValue="https://kabel.casademm.de/taskpane.html"/>
|
||||
</DefaultSettings>
|
||||
<Permissions>ReadWriteDocument</Permissions>
|
||||
<VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0">
|
||||
<Hosts>
|
||||
<Host xsi:type="Workbook">
|
||||
<DesktopFormFactor>
|
||||
<GetStarted>
|
||||
<Title resid="GetStarted.Title"/>
|
||||
<Description resid="GetStarted.Description"/>
|
||||
<LearnMoreUrl resid="GetStarted.LearnMoreUrl"/>
|
||||
</GetStarted>
|
||||
<FunctionFile resid="Commands.Url"/>
|
||||
<ExtensionPoint xsi:type="PrimaryCommandSurface">
|
||||
<OfficeTab id="TabHome">
|
||||
<Group id="CommandsGroup">
|
||||
<Label resid="CommandsGroup.Label"/>
|
||||
<Icon>
|
||||
<bt:Image size="16" resid="Icon.16x16"/>
|
||||
<bt:Image size="32" resid="Icon.32x32"/>
|
||||
<bt:Image size="80" resid="Icon.80x80"/>
|
||||
</Icon>
|
||||
<Control xsi:type="Button" id="TaskpaneButton">
|
||||
<Label resid="TaskpaneButton.Label"/>
|
||||
<Supertip>
|
||||
<Title resid="TaskpaneButton.Label"/>
|
||||
<Description resid="TaskpaneButton.Tooltip"/>
|
||||
</Supertip>
|
||||
<Icon>
|
||||
<bt:Image size="16" resid="Icon.16x16"/>
|
||||
<bt:Image size="32" resid="Icon.32x32"/>
|
||||
<bt:Image size="80" resid="Icon.80x80"/>
|
||||
</Icon>
|
||||
<Action xsi:type="ShowTaskpane">
|
||||
<TaskpaneId>ButtonId1</TaskpaneId>
|
||||
<SourceLocation resid="Taskpane.Url"/>
|
||||
</Action>
|
||||
</Control>
|
||||
</Group>
|
||||
</OfficeTab>
|
||||
</ExtensionPoint>
|
||||
</DesktopFormFactor>
|
||||
</Host>
|
||||
</Hosts>
|
||||
<Resources>
|
||||
<bt:Images>
|
||||
<bt:Image id="Icon.16x16" DefaultValue="https://kabel.casademm.de/assets/icon-16.png"/>
|
||||
<bt:Image id="Icon.32x32" DefaultValue="https://kabel.casademm.de/assets/icon-32.png"/>
|
||||
<bt:Image id="Icon.80x80" DefaultValue="https://kabel.casademm.de/assets/icon-80.png"/>
|
||||
</bt:Images>
|
||||
<bt:Urls>
|
||||
<bt:Url id="GetStarted.LearnMoreUrl" DefaultValue="https://kabel.casademm.de/help"/>
|
||||
<bt:Url id="Commands.Url" DefaultValue="https://kabel.casademm.de/commands.html"/>
|
||||
<bt:Url id="Taskpane.Url" DefaultValue="https://kabel.casademm.de/taskpane.html"/>
|
||||
</bt:Urls>
|
||||
<bt:ShortStrings>
|
||||
<bt:String id="GetStarted.Title" DefaultValue="Willkommen zum Kabel-Konsolidierungs-Tool!"/>
|
||||
<bt:String id="CommandsGroup.Label" DefaultValue="Funktionen"/>
|
||||
<bt:String id="TaskpaneButton.Label" DefaultValue="Start Konsolidierung"/>
|
||||
</bt:ShortStrings>
|
||||
<bt:LongStrings>
|
||||
<bt:String id="GetStarted.Description" DefaultValue="Das Tool wurde geladen. Klicke im Menüband Start auf 'Start Konsolidierung', um zu beginnen."/>
|
||||
<bt:String id="TaskpaneButton.Tooltip" DefaultValue="Klicke hier, um das Tool zur Kabel-Konsolidierung zu öffnen"/>
|
||||
</bt:LongStrings>
|
||||
</Resources>
|
||||
</VersionOverrides>
|
||||
</OfficeApp>
|
||||
3
dist/polyfill.js
vendored
Normal file
7
dist/polyfill.js.LICENSE.txt
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* @overview es6-promise - a tiny implementation of Promises/A+.
|
||||
* @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
|
||||
* @license Licensed under MIT license
|
||||
* See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE
|
||||
* @version v4.2.8+1e68dce6
|
||||
*/
|
||||
1
dist/polyfill.js.map
vendored
Normal file
3
dist/react.js
vendored
Normal file
37
dist/react.js.LICENSE.txt
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
/*!
|
||||
* @overview es6-promise - a tiny implementation of Promises/A+.
|
||||
* @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
|
||||
* @license Licensed under MIT license
|
||||
* See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE
|
||||
* @version v4.2.8+1e68dce6
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
1
dist/react.js.map
vendored
Normal file
1
dist/taskpane.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<!doctype html><html lang="en" data-framework="typescript"><head><meta charset="UTF-8"/><meta http-equiv="X-UA-Compatible" content="IE=Edge"/><meta name="viewport" content="width=device-width,initial-scale=1"><title>Contoso Task Pane Add-in</title><script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script><script defer="defer" src="polyfill.js"></script><script defer="defer" src="react.js"></script><script defer="defer" src="taskpane.js"></script></head><body style="width:100%;height:100%;margin:0;padding:0"><div id="container"></div><div id="tridentmessage" style="display:none;padding:10">This add-in will not run in your version of Office. Please upgrade either to perpetual Office 2021 (or later) or to a Microsoft 365 account.</div><script>if(-1!==navigator.userAgent.indexOf("Trident")||-1!==navigator.userAgent.indexOf("Edge")){var tridentMessage=document.getElementById("tridentmessage"),normalContainer=document.getElementById("container");tridentMessage.style.display="block",normalContainer.style.display="none"}</script></body></html>
|
||||
3
dist/taskpane.js
vendored
Normal file
37
dist/taskpane.js.LICENSE.txt
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
/*!
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
/*! sheetjs (C) 2013-present SheetJS -- http://sheetjs.com */
|
||||
|
||||
/*! xlsx.js (C) 2013-present SheetJS -- http://sheetjs.com */
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v17.0.2
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
1
dist/taskpane.js.map
vendored
Normal file
180
manifest.json
Normal file
@@ -0,0 +1,180 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/teams/v1.17/MicrosoftTeams.schema.json",
|
||||
"id": "f2b103f1-1ab1-4e1b-8f0b-072aa3d4e19d",
|
||||
"manifestVersion": "1.17",
|
||||
"version": "1.0.0",
|
||||
"name": {
|
||||
"short": "Kabel-Konsolidierung",
|
||||
"full": "Kabel-Konsolidierung Add-in"
|
||||
},
|
||||
"description": {
|
||||
"short": "Kabeldaten konsolidieren",
|
||||
"full": "Macht aus mehreren Tabellenblättern eine Kabelliste"
|
||||
},
|
||||
"developer": {
|
||||
"name": "Toni Martin - SAT Elektrotechnik GmbH",
|
||||
"websiteUrl": "https://kabel.casademm.de",
|
||||
"privacyUrl": "https://kabel.casademm.de/privacy",
|
||||
"termsOfUseUrl": "https://kabel.casademm.de/terms"
|
||||
},
|
||||
"icons": {
|
||||
"outline": "assets/outline.png",
|
||||
"color": "assets/color.png"
|
||||
},
|
||||
"accentColor": "#230201",
|
||||
"localizationInfo": {
|
||||
"defaultLanguageTag": "en-us",
|
||||
"additionalLanguages": []
|
||||
},
|
||||
"authorization": {
|
||||
"permissions": {
|
||||
"resourceSpecific": [
|
||||
{
|
||||
"name": "Mailbox.ReadWrite.User",
|
||||
"type": "Delegated"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"validDomains": [
|
||||
"kabel.casademm.de"
|
||||
],
|
||||
"extensions": [
|
||||
{
|
||||
"requirements": {
|
||||
"scopes": [
|
||||
"mail"
|
||||
],
|
||||
"capabilities": [
|
||||
{
|
||||
"name": "Mailbox",
|
||||
"minVersion": "1.3"
|
||||
}
|
||||
]
|
||||
},
|
||||
"runtimes": [
|
||||
{
|
||||
"requirements": {
|
||||
"capabilities": [
|
||||
{
|
||||
"name": "Mailbox",
|
||||
"minVersion": "1.3"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "TaskPaneRuntime",
|
||||
"type": "general",
|
||||
"code": {
|
||||
"page": "https://kabel.casademm.de/taskpane.html"
|
||||
},
|
||||
"lifetime": "short",
|
||||
"actions": [
|
||||
{
|
||||
"id": "TaskPaneRuntimeShow",
|
||||
"type": "openPage",
|
||||
"pinnable": false,
|
||||
"view": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "CommandsRuntime",
|
||||
"type": "general",
|
||||
"code": {
|
||||
"page": "https://kabel.casademm.de/commands.html",
|
||||
"script": "https://kabel.casademm.de/commands.js"
|
||||
},
|
||||
"lifetime": "short",
|
||||
"actions": [
|
||||
{
|
||||
"id": "action",
|
||||
"type": "executeFunction"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ribbons": [
|
||||
{
|
||||
"contexts": [
|
||||
"mailCompose"
|
||||
],
|
||||
"tabs": [
|
||||
{
|
||||
"builtInTabId": "TabDefault",
|
||||
"groups": [
|
||||
{
|
||||
"id": "msgComposeGroup",
|
||||
"label": "Kabel-Konsolidierung",
|
||||
"icons": [
|
||||
{
|
||||
"size": 16,
|
||||
"url": "https://kabel.casademm.de/assets/icon-16.png"
|
||||
},
|
||||
{
|
||||
"size": 32,
|
||||
"url": "https://kabel.casademm.de/assets/icon-32.png"
|
||||
},
|
||||
{
|
||||
"size": 80,
|
||||
"url": "https://kabel.casademm.de/assets/icon-80.png"
|
||||
}
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"id": "msgComposeOpenPaneButton",
|
||||
"type": "button",
|
||||
"label": "Show Task Pane",
|
||||
"icons": [
|
||||
{
|
||||
"size": 16,
|
||||
"url": "https://kabel.casademm.de/assets/icon-16.png"
|
||||
},
|
||||
{
|
||||
"size": 32,
|
||||
"url": "https://kabel.casademm.de/assets/icon-32.png"
|
||||
},
|
||||
{
|
||||
"size": 80,
|
||||
"url": "https://kabel.casademm.de/assets/icon-80.png"
|
||||
}
|
||||
],
|
||||
"supertip": {
|
||||
"title": "Show Task Pane",
|
||||
"description": "Opens a task pane."
|
||||
},
|
||||
"actionId": "TaskPaneRuntimeShow"
|
||||
},
|
||||
{
|
||||
"id": "ActionButton",
|
||||
"type": "button",
|
||||
"label": "Perform an action",
|
||||
"icons": [
|
||||
{
|
||||
"size": 16,
|
||||
"url": "https://kabel.casademm.de/assets/icon-16.png"
|
||||
},
|
||||
{
|
||||
"size": 32,
|
||||
"url": "https://kabel.casademm.de/assets/icon-32.png"
|
||||
},
|
||||
{
|
||||
"size": 80,
|
||||
"url": "https://kabel.casademm.de/assets/icon-80.png"
|
||||
}
|
||||
],
|
||||
"supertip": {
|
||||
"title": "Perform an action",
|
||||
"description": "Perform an action when clicked."
|
||||
},
|
||||
"actionId": "action"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
85
manifest.xml
Normal file
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<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">
|
||||
<Id>2c37abde-33e4-4624-b95a-a0aed1526f1b</Id>
|
||||
<Version>1.0.0.0</Version>
|
||||
<ProviderName>SAT Elektrotechnik GmbH</ProviderName>
|
||||
<DefaultLocale>de-DE</DefaultLocale>
|
||||
<DisplayName DefaultValue="Kabel-Konsolidierung"/>
|
||||
<Description DefaultValue="Konsolidiert strukturierte Kabeldaten aus mehreren Tabellenblättern in eine Gesamtliste."/>
|
||||
<IconUrl DefaultValue="https://kabel.casademm.de/assets/icon-32.png"/>
|
||||
<HighResolutionIconUrl DefaultValue="https://kabel.casademm.de/assets/icon-64.png"/>
|
||||
<SupportUrl DefaultValue="https://kabel.casademm.de"/>
|
||||
<AppDomains>
|
||||
<AppDomain>https://kabel.casademm.de</AppDomain>
|
||||
</AppDomains>
|
||||
<Hosts>
|
||||
<Host Name="Workbook"/>
|
||||
</Hosts>
|
||||
<DefaultSettings>
|
||||
<SourceLocation DefaultValue="https://kabel.casademm.de/taskpane.html"/>
|
||||
</DefaultSettings>
|
||||
<Permissions>ReadWriteDocument</Permissions>
|
||||
<VersionOverrides xmlns="http://schemas.microsoft.com/office/taskpaneappversionoverrides" xsi:type="VersionOverridesV1_0">
|
||||
<Hosts>
|
||||
<Host xsi:type="Workbook">
|
||||
<DesktopFormFactor>
|
||||
<GetStarted>
|
||||
<Title resid="GetStarted.Title"/>
|
||||
<Description resid="GetStarted.Description"/>
|
||||
<LearnMoreUrl resid="GetStarted.LearnMoreUrl"/>
|
||||
</GetStarted>
|
||||
<FunctionFile resid="Commands.Url"/>
|
||||
<ExtensionPoint xsi:type="PrimaryCommandSurface">
|
||||
<OfficeTab id="TabHome">
|
||||
<Group id="CommandsGroup">
|
||||
<Label resid="CommandsGroup.Label"/>
|
||||
<Icon>
|
||||
<bt:Image size="16" resid="Icon.16x16"/>
|
||||
<bt:Image size="32" resid="Icon.32x32"/>
|
||||
<bt:Image size="80" resid="Icon.80x80"/>
|
||||
</Icon>
|
||||
<Control xsi:type="Button" id="TaskpaneButton">
|
||||
<Label resid="TaskpaneButton.Label"/>
|
||||
<Supertip>
|
||||
<Title resid="TaskpaneButton.Label"/>
|
||||
<Description resid="TaskpaneButton.Tooltip"/>
|
||||
</Supertip>
|
||||
<Icon>
|
||||
<bt:Image size="16" resid="Icon.16x16"/>
|
||||
<bt:Image size="32" resid="Icon.32x32"/>
|
||||
<bt:Image size="80" resid="Icon.80x80"/>
|
||||
</Icon>
|
||||
<Action xsi:type="ShowTaskpane">
|
||||
<TaskpaneId>ButtonId1</TaskpaneId>
|
||||
<SourceLocation resid="Taskpane.Url"/>
|
||||
</Action>
|
||||
</Control>
|
||||
</Group>
|
||||
</OfficeTab>
|
||||
</ExtensionPoint>
|
||||
</DesktopFormFactor>
|
||||
</Host>
|
||||
</Hosts>
|
||||
<Resources>
|
||||
<bt:Images>
|
||||
<bt:Image id="Icon.16x16" DefaultValue="https://kabel.casademm.de/assets/icon-16.png"/>
|
||||
<bt:Image id="Icon.32x32" DefaultValue="https://kabel.casademm.de/assets/icon-32.png"/>
|
||||
<bt:Image id="Icon.80x80" DefaultValue="https://kabel.casademm.de/assets/icon-80.png"/>
|
||||
</bt:Images>
|
||||
<bt:Urls>
|
||||
<bt:Url id="GetStarted.LearnMoreUrl" DefaultValue="https://kabel.casademm.de/help"/>
|
||||
<bt:Url id="Commands.Url" DefaultValue="https://kabel.casademm.de/commands.html"/>
|
||||
<bt:Url id="Taskpane.Url" DefaultValue="https://kabel.casademm.de/taskpane.html"/>
|
||||
</bt:Urls>
|
||||
<bt:ShortStrings>
|
||||
<bt:String id="GetStarted.Title" DefaultValue="Willkommen zum Kabel-Konsolidierungs-Tool!"/>
|
||||
<bt:String id="CommandsGroup.Label" DefaultValue="Funktionen"/>
|
||||
<bt:String id="TaskpaneButton.Label" DefaultValue="Start Konsolidierung"/>
|
||||
</bt:ShortStrings>
|
||||
<bt:LongStrings>
|
||||
<bt:String id="GetStarted.Description" DefaultValue="Das Tool wurde geladen. Klicke im Menüband Start auf 'Start Konsolidierung', um zu beginnen."/>
|
||||
<bt:String id="TaskpaneButton.Tooltip" DefaultValue="Klicke hier, um das Tool zur Kabel-Konsolidierung zu öffnen"/>
|
||||
</bt:LongStrings>
|
||||
</Resources>
|
||||
</VersionOverrides>
|
||||
</OfficeApp>
|
||||
17437
package-lock.json
generated
Normal file
92
package.json
Normal file
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"name": "office-addin-taskpane-react",
|
||||
"version": "0.0.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OfficeDev/Office-Addin-TaskPane-React.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"config": {
|
||||
"app_to_debug": "excel",
|
||||
"app_type_to_debug": "desktop",
|
||||
"dev_server_port": 3000
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16",
|
||||
"npm": ">=7"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --mode production",
|
||||
"build:dev": "webpack --mode development",
|
||||
"convert-to-single-host": "node convertToSingleHost.js",
|
||||
"dev-server": "webpack serve --mode development",
|
||||
"lint": "office-addin-lint check",
|
||||
"lint:fix": "office-addin-lint fix",
|
||||
"prettier": "office-addin-lint prettier",
|
||||
"signin": "office-addin-dev-settings m365-account login",
|
||||
"signout": "office-addin-dev-settings m365-account logout",
|
||||
"start": "office-addin-debugging start manifest.xml",
|
||||
"stop": "office-addin-debugging stop manifest.xml",
|
||||
"test": "npm run test:unit && npm run test:e2e",
|
||||
"test:e2e": "mocha -r ts-node/register test/end-to-end/*.ts",
|
||||
"test:unit": "mocha -r ts-node/register test/unit/*.test.ts",
|
||||
"validate": "office-addin-manifest validate manifest.xml",
|
||||
"watch": "webpack --mode development --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/react-components": "^9.55.1",
|
||||
"@fluentui/react-icons": "^2.0.264",
|
||||
"core-js": "^3.36.0",
|
||||
"es6-promise": "^4.2.8",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.0",
|
||||
"@babel/preset-env": "^7.25.4",
|
||||
"@babel/preset-typescript": "^7.23.3",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/node": "^20.11.25",
|
||||
"@types/office-js": "^1.0.377",
|
||||
"@types/office-runtime": "^1.0.35",
|
||||
"@types/react": "^18.2.64",
|
||||
"@types/react-dom": "^18.2.21",
|
||||
"@types/webpack": "^5.28.5",
|
||||
"acorn": "^8.11.3",
|
||||
"babel-loader": "^9.1.3",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"eslint-plugin-office-addins": "^4.0.3",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-loader": "^5.0.0",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"less": "^4.2.0",
|
||||
"less-loader": "^12.2.0",
|
||||
"mocha": "^11.1.0",
|
||||
"office-addin-cli": "^2.0.3",
|
||||
"office-addin-debugging": "^6.0.3",
|
||||
"office-addin-dev-certs": "^2.0.3",
|
||||
"office-addin-lint": "^3.0.3",
|
||||
"office-addin-manifest": "^2.0.3",
|
||||
"office-addin-mock": "^3.0.3",
|
||||
"office-addin-prettier-config": "^2.0.1",
|
||||
"office-addin-test-helpers": "^2.0.3",
|
||||
"office-addin-test-server": "^2.0.3",
|
||||
"os-browserify": "^0.3.0",
|
||||
"process": "^0.11.10",
|
||||
"source-map-loader": "^5.0.0",
|
||||
"ts-loader": "^9.5.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.2",
|
||||
"webpack": "^5.95.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "5.1.0"
|
||||
},
|
||||
"prettier": "office-addin-prettier-config",
|
||||
"browserslist": [
|
||||
"last 2 versions",
|
||||
"ie 11"
|
||||
]
|
||||
}
|
||||
19
src/commands/commands.html
Normal file
@@ -0,0 +1,19 @@
|
||||
@@ -1,18 +0,0 @@
|
||||
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT License. -->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
|
||||
<!-- Office JavaScript API -->
|
||||
<script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
21
src/commands/commands.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||||
* See LICENSE in the project root for license information.
|
||||
*/
|
||||
|
||||
/* global Office */
|
||||
|
||||
Office.onReady(() => {
|
||||
// If needed, Office.js is ready to be called.
|
||||
});
|
||||
|
||||
/**
|
||||
* Shows a notification when the add-in command is executed.
|
||||
* @param event
|
||||
*/
|
||||
function action(event: Office.AddinCommands.Event) {
|
||||
// Your code here
|
||||
event.completed();
|
||||
}
|
||||
|
||||
Office.actions.associate("action", action);
|
||||
151
src/taskpane/components/App.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import * as React from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { makeStyles } from "@fluentui/react-components";
|
||||
import { SheetInfo, SheetMappingStatus } from "../models";
|
||||
import SheetSelector from "./SheetSelector";
|
||||
import ColumnMapper from "./ColumnMapper";
|
||||
import StatusNotifier from "./StatusNotifier";
|
||||
import { getAvailableSheets, detectHeadersAndColumns, detectHeadersForSingleSheetRow, consolidateData } from "../excelLogic";
|
||||
|
||||
interface AppProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
minHeight: "100vh",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
});
|
||||
|
||||
const App: React.FC<AppProps> = () => {
|
||||
const styles = useStyles();
|
||||
|
||||
type WizardStep = "select_sheets" | "map_columns" | "done";
|
||||
|
||||
const [step, setStep] = useState<WizardStep>("select_sheets");
|
||||
const [sheets, setSheets] = useState<SheetInfo[]>([]);
|
||||
const [selectedSheetIds, setSelectedSheetIds] = useState<string[]>([]);
|
||||
const [sheetMappings, setSheetMappings] = useState<SheetMappingStatus[]>([]);
|
||||
|
||||
const [status, setStatus] = useState<"idle" | "success" | "warning" | "error">("idle");
|
||||
const [statusMessage, setStatusMessage] = useState("");
|
||||
const [isConsolidating, setIsConsolidating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Load internal sheets on mount
|
||||
getAvailableSheets().then(setSheets).catch(err => {
|
||||
setStatus("error");
|
||||
setStatusMessage("Fehler beim Laden der Arbeitsblätter: " + String(err));
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleExternalSheetsLoaded = (newExternalSheets: SheetInfo[]) => {
|
||||
setSheets(prev => [...prev, ...newExternalSheets]);
|
||||
};
|
||||
|
||||
const handleNextToMapping = async () => {
|
||||
const selectedSheets = sheets.filter(s => selectedSheetIds.includes(s.id));
|
||||
if (selectedSheets.length === 0) return;
|
||||
|
||||
try {
|
||||
const mappings = await detectHeadersAndColumns(selectedSheets);
|
||||
setSheetMappings(mappings);
|
||||
setStep("map_columns");
|
||||
setStatus("idle");
|
||||
setStatusMessage("");
|
||||
} catch (err) {
|
||||
setStatus("error");
|
||||
setStatusMessage("Fehler beim Analysieren der Blätter: " + String(err));
|
||||
}
|
||||
};
|
||||
|
||||
const handleHeaderRowChange = async (sheetName: string, newRowIndex: number) => {
|
||||
try {
|
||||
// Wir müssen das richtige SheetInfo Objekt finden (für isExternal check)
|
||||
const sheetInfo = sheets.find(s => s.name === sheetName);
|
||||
if (!sheetInfo) return;
|
||||
|
||||
// Re-detect columns for exactly this row
|
||||
const newMapping = await detectHeadersForSingleSheetRow(sheetInfo, newRowIndex);
|
||||
setSheetMappings(prev => prev.map(m => m.sheetName === sheetName ? newMapping : m));
|
||||
} catch (err) {
|
||||
setStatus("error");
|
||||
setStatusMessage("Fehler beim Neuladen der Zeile " + newRowIndex + " für Blatt " + sheetName);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMappingChange = (sheetName: string, targetCol: string, sourceColIndex: number) => {
|
||||
setSheetMappings(prev => prev.map(sheet => {
|
||||
if (sheet.sheetName !== sheetName) return sheet;
|
||||
|
||||
const newMappings = sheet.mappings.map(m =>
|
||||
m.targetColumn === targetCol ? { ...m, sourceColumnIndex: sourceColIndex } : m
|
||||
);
|
||||
return { ...sheet, mappings: newMappings };
|
||||
}));
|
||||
};
|
||||
|
||||
const handleConsolidate = async () => {
|
||||
setIsConsolidating(true);
|
||||
setStatus("idle");
|
||||
try {
|
||||
const rowsCount = await consolidateData(sheetMappings);
|
||||
setStatus("success");
|
||||
setStatusMessage(`Erfolgreich! Es wurden ${rowsCount} Zeilen aus ${sheetMappings.length} Blättern zusammengefasst.`);
|
||||
setStep("done");
|
||||
} catch (err: any) {
|
||||
setStatus("error");
|
||||
setStatusMessage(err.message || "Fehler bei der Konsolidierung: " + String(err));
|
||||
} finally {
|
||||
setIsConsolidating(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<StatusNotifier status={status} message={statusMessage} />
|
||||
|
||||
{step === "select_sheets" && (
|
||||
<SheetSelector
|
||||
sheets={sheets}
|
||||
selectedSheetIds={selectedSheetIds}
|
||||
onSelectionChange={setSelectedSheetIds}
|
||||
onNext={handleNextToMapping}
|
||||
onExternalSheetsLoaded={handleExternalSheetsLoaded}
|
||||
/>
|
||||
)}
|
||||
|
||||
{step === "map_columns" && (
|
||||
<ColumnMapper
|
||||
sheetMappings={sheetMappings}
|
||||
onHeaderRowChange={handleHeaderRowChange}
|
||||
onMappingChange={handleMappingChange}
|
||||
onBack={() => setStep("select_sheets")}
|
||||
onConsolidate={handleConsolidate}
|
||||
isConsolidating={isConsolidating}
|
||||
/>
|
||||
)}
|
||||
|
||||
{step === "done" && (
|
||||
<div style={{ padding: "10px", textAlign: "center", marginTop: "40px" }}>
|
||||
<h2>Fertig!</h2>
|
||||
<p>Die Daten wurden in die 'Gesamtliste' geschrieben.</p>
|
||||
<button
|
||||
style={{ padding: "8px 16px", marginTop: "10px", cursor: "pointer" }}
|
||||
onClick={() => {
|
||||
setStep("select_sheets");
|
||||
setSelectedSheetIds([]);
|
||||
setStatus("idle");
|
||||
}}
|
||||
>
|
||||
Neuen Durchlauf starten
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
137
src/taskpane/components/ColumnMapper.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
Text,
|
||||
Button,
|
||||
SpinButton,
|
||||
Dropdown,
|
||||
Option,
|
||||
Accordion,
|
||||
AccordionHeader,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Field,
|
||||
Label,
|
||||
Spinner,
|
||||
} from "@fluentui/react-components";
|
||||
import { SheetMappingStatus, TARGET_COLUMNS } from "../models";
|
||||
|
||||
interface ColumnMapperProps {
|
||||
sheetMappings: SheetMappingStatus[];
|
||||
onHeaderRowChange: (sheetName: string, newRowIndex: number) => void;
|
||||
onMappingChange: (sheetName: string, targetCol: string, sourceColIndex: number) => void;
|
||||
onBack: () => void;
|
||||
onConsolidate: () => void;
|
||||
isConsolidating: boolean;
|
||||
}
|
||||
|
||||
const ColumnMapper: React.FC<ColumnMapperProps> = ({
|
||||
sheetMappings,
|
||||
onHeaderRowChange,
|
||||
onMappingChange,
|
||||
onBack,
|
||||
onConsolidate,
|
||||
isConsolidating,
|
||||
}) => {
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "15px", padding: "10px" }}>
|
||||
<Text size={400} weight="semibold">
|
||||
2. Spalten-Mapping prüfen
|
||||
</Text>
|
||||
<Text size={300}>
|
||||
Bitte überprüfe die gefundenen Kopfzeilen und passe fehlende Spalten manuell an.
|
||||
</Text>
|
||||
|
||||
<Accordion multiple collapsible defaultOpenItems={sheetMappings.map((s) => s.sheetName)}>
|
||||
{sheetMappings.map((sheet) => {
|
||||
const missingCount = sheet.mappings.filter((m) => m.sourceColumnIndex === -1).length;
|
||||
|
||||
return (
|
||||
<AccordionItem key={sheet.sheetName} value={sheet.sheetName}>
|
||||
<AccordionHeader>
|
||||
<div style={{ display: "flex", justifyContent: "space-between", width: "100%" }}>
|
||||
<Text weight="semibold">{sheet.sheetName}</Text>
|
||||
{missingCount > 0 ? (
|
||||
<Text style={{ color: "red", paddingRight: "10px" }}>{missingCount} Lücken</Text>
|
||||
) : (
|
||||
<Text style={{ color: "green", paddingRight: "10px" }}>OK</Text>
|
||||
)}
|
||||
</div>
|
||||
</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
|
||||
{missingCount === TARGET_COLUMNS.length && (
|
||||
<div style={{ backgroundColor: "#fdf6f6", padding: "10px", borderRadius: "4px", border: "1px solid #f5b0b0" }}>
|
||||
<Text style={{ color: "red", fontWeight: "semibold" }}>Achtung: Keine passenden Kopfzeilen gefunden.</Text><br />
|
||||
<Text size={200}>Bitte weise die Spalten manuell zu. Wenn du dieses Blatt überspringen willst, lass einfach alles auf "Nicht gefunden".</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Field
|
||||
label="Kopfzeile (Index 0-basiert):"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<SpinButton
|
||||
value={sheet.headerRowIndex}
|
||||
min={0}
|
||||
onChange={(_, data) => {
|
||||
if (data.value !== undefined) {
|
||||
onHeaderRowChange(sheet.sheetName, data.value);
|
||||
}
|
||||
}}
|
||||
style={{ width: "80px" }}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
{TARGET_COLUMNS.map((tc) => {
|
||||
const colName = tc.id;
|
||||
const mappedInfo = sheet.mappings.find((m) => m.targetColumn === colName);
|
||||
const selectedVal = mappedInfo?.sourceColumnIndex !== -1 ? mappedInfo?.sourceColumnIndex.toString() : "-1";
|
||||
|
||||
return (
|
||||
<Field key={colName} label={colName} orientation="horizontal" style={{ justifyContent: "space-between" }}>
|
||||
<Dropdown
|
||||
value={
|
||||
selectedVal === "-1"
|
||||
? "--- Nicht gefunden ---"
|
||||
: sheet.availableColumns.find((c) => c.index.toString() === selectedVal)?.name || "Unbekannt"
|
||||
}
|
||||
selectedOptions={[selectedVal || "-1"]}
|
||||
onOptionSelect={(_, data) => {
|
||||
const newIndex = parseInt(data.optionValue || "-1", 10);
|
||||
onMappingChange(sheet.sheetName, colName, newIndex);
|
||||
}}
|
||||
style={{ minWidth: "150px" }}
|
||||
>
|
||||
<Option value="-1" text="--- Nicht gefunden ---">--- Nicht gefunden ---</Option>
|
||||
{sheet.availableColumns.map((availCol) => (
|
||||
<Option key={availCol.index} value={availCol.index.toString()} text={`${availCol.name} (Spalte ${availCol.index + 1})`}>
|
||||
{availCol.name} (Spalte {availCol.index + 1})
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
</Field>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
);
|
||||
})}
|
||||
</Accordion>
|
||||
|
||||
<div style={{ display: "flex", justifyContent: "space-between", marginTop: "20px" }}>
|
||||
<Button onClick={onBack} disabled={isConsolidating}>Zurück</Button>
|
||||
<Button
|
||||
appearance="primary"
|
||||
onClick={onConsolidate}
|
||||
disabled={isConsolidating || sheetMappings.length === 0}
|
||||
icon={isConsolidating ? <Spinner size="tiny" /> : undefined}
|
||||
>
|
||||
{isConsolidating ? "Konsolidierung läuft..." : "Konsolidieren"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColumnMapper;
|
||||
145
src/taskpane/components/SheetSelector.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import * as React from "react";
|
||||
import { Checkbox, Text, Button, Divider } from "@fluentui/react-components";
|
||||
import { SheetInfo } from "../models";
|
||||
import * as XLSX from "xlsx";
|
||||
|
||||
interface SheetSelectorProps {
|
||||
sheets: SheetInfo[];
|
||||
selectedSheetIds: string[];
|
||||
onSelectionChange: (selectedIds: string[]) => void;
|
||||
onNext: () => void;
|
||||
onExternalSheetsLoaded: (externalSheets: SheetInfo[]) => void;
|
||||
}
|
||||
|
||||
const SheetSelector: React.FC<SheetSelectorProps> = ({
|
||||
sheets,
|
||||
selectedSheetIds,
|
||||
onSelectionChange,
|
||||
onNext,
|
||||
onExternalSheetsLoaded,
|
||||
}) => {
|
||||
const handleCheckboxChange = (sheetId: string, checked: boolean) => {
|
||||
if (checked) {
|
||||
onSelectionChange([...selectedSheetIds, sheetId]);
|
||||
} else {
|
||||
onSelectionChange(selectedSheetIds.filter((id) => id !== sheetId));
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = event.target.files;
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
const newExternalSheets: SheetInfo[] = [];
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
|
||||
// Einlesen der Datei mit SheetJS
|
||||
const workbook = XLSX.read(arrayBuffer, { type: "array" });
|
||||
|
||||
workbook.SheetNames.forEach((sheetName) => {
|
||||
const worksheet = workbook.Sheets[sheetName];
|
||||
// Lese Daten als 2D-Array (header: 1 bedeutet Array of Arrays)
|
||||
// blankrows: false überspringt komplett leere Zeilen beim Einlesen nicht zwingend,
|
||||
// aber sheet_to_json mit header:1 hält die Struktur relativ gut.
|
||||
const data = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "" }) as any[][];
|
||||
|
||||
// Nur hinzufügen, wenn auch Daten drin sind
|
||||
if (data.length > 0) {
|
||||
const uniqueId = `ext_${file.name}_${sheetName}`;
|
||||
newExternalSheets.push({
|
||||
id: uniqueId,
|
||||
name: sheetName,
|
||||
isExternal: true,
|
||||
fileName: file.name,
|
||||
externalData: data
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Neue Blätter an die Hauptkomponente übergeben
|
||||
if (newExternalSheets.length > 0) {
|
||||
onExternalSheetsLoaded(newExternalSheets);
|
||||
// Automatisch die neuen Blätter selektieren
|
||||
onSelectionChange([...selectedSheetIds, ...newExternalSheets.map(s => s.id)]);
|
||||
}
|
||||
};
|
||||
|
||||
// Gruppiere Blätter für die Anzeige
|
||||
const internalSheets = sheets.filter(s => !s.isExternal);
|
||||
const externalSheets = sheets.filter(s => s.isExternal);
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "10px", padding: "10px" }}>
|
||||
<Text size={400} weight="semibold">
|
||||
1. Quell-Blätter auswählen
|
||||
</Text>
|
||||
<Text size={300}>Bitte wähle alle Tabellenblätter aus, die konsolidiert werden sollen:</Text>
|
||||
|
||||
{/* Interne Blätter */}
|
||||
<div style={{ marginTop: "10px" }}>
|
||||
<Text weight="semibold" style={{ marginBottom: "5px", display: "block" }}>
|
||||
Aus dieser Arbeitsmappe:
|
||||
</Text>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "5px" }}>
|
||||
{internalSheets.length === 0 ? (
|
||||
<Text italic>Keine internen Blätter gefunden.</Text>
|
||||
) : (
|
||||
internalSheets.map((s) => (
|
||||
<Checkbox
|
||||
key={s.id}
|
||||
label={s.name}
|
||||
checked={selectedSheetIds.includes(s.id)}
|
||||
onChange={(_, data) => handleCheckboxChange(s.id, !!data.checked)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider style={{ margin: "10px 0" }} />
|
||||
|
||||
{/* Externe Blätter */}
|
||||
<div>
|
||||
<Text weight="semibold" style={{ marginBottom: "5px", display: "block" }}>
|
||||
Aus anderen Dateien hinzufügen:
|
||||
</Text>
|
||||
<input
|
||||
type="file"
|
||||
accept=".xlsx, .xlsm, .xls, .csv"
|
||||
multiple
|
||||
onChange={handleFileUpload}
|
||||
style={{ marginBottom: "10px", display: "block" }}
|
||||
/>
|
||||
|
||||
{externalSheets.length > 0 && (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "5px", marginTop: "10px" }}>
|
||||
{externalSheets.map((s) => (
|
||||
<Checkbox
|
||||
key={s.id}
|
||||
label={`${s.fileName} - ${s.name}`}
|
||||
checked={selectedSheetIds.includes(s.id)}
|
||||
onChange={(_, data) => handleCheckboxChange(s.id, !!data.checked)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: "20px" }}>
|
||||
<Button
|
||||
appearance="primary"
|
||||
disabled={selectedSheetIds.length === 0}
|
||||
onClick={onNext}
|
||||
>
|
||||
Weiter zum Mapping
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SheetSelector;
|
||||
27
src/taskpane/components/StatusNotifier.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import * as React from "react";
|
||||
import { MessageBar, MessageBarBody, MessageBarTitle } from "@fluentui/react-components";
|
||||
|
||||
interface StatusNotifierProps {
|
||||
status: "idle" | "success" | "warning" | "error";
|
||||
title?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
const StatusNotifier: React.FC<StatusNotifierProps> = ({ status, title, message }) => {
|
||||
if (status === "idle" || !message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "10px", padding: "0 10px" }}>
|
||||
<MessageBar intent={status}>
|
||||
<MessageBarBody>
|
||||
{title && <MessageBarTitle>{title}</MessageBarTitle>}
|
||||
{message}
|
||||
</MessageBarBody>
|
||||
</MessageBar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StatusNotifier;
|
||||
255
src/taskpane/excelLogic.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
/* global Excel, console */
|
||||
import { SheetInfo, SheetMappingStatus, TARGET_COLUMNS } from "./models";
|
||||
|
||||
/**
|
||||
* Holt alle sichtbaren Arbeitsblätter des aktuellen Workbooks, außer "Gesamtliste".
|
||||
*/
|
||||
export async function getAvailableSheets(): Promise<SheetInfo[]> {
|
||||
return Excel.run(async (context) => {
|
||||
const sheets = context.workbook.worksheets;
|
||||
sheets.load("items/name, items/visibility");
|
||||
await context.sync();
|
||||
|
||||
return sheets.items
|
||||
.filter((sheet) => sheet.name !== "Gesamtliste" && sheet.visibility === Excel.SheetVisibility.visible)
|
||||
.map((sheet) => ({
|
||||
id: sheet.name,
|
||||
name: sheet.name,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert für ausgewählte Blätter die Kopfzeile und das Mapping-Ergebnis.
|
||||
* Die Funktion durchsucht die ersten 50 Zeilen (inkl. leerer Zeilen),
|
||||
* um die eigentlichen Spaltennamen zu finden.
|
||||
*/
|
||||
export async function detectHeadersAndColumns(sheets: SheetInfo[]): Promise<SheetMappingStatus[]> {
|
||||
return Excel.run(async (context) => {
|
||||
const results: SheetMappingStatus[] = [];
|
||||
|
||||
for (const sheetInfo of sheets) {
|
||||
let values: any[][] = [];
|
||||
|
||||
if (sheetInfo.isExternal && sheetInfo.externalData) {
|
||||
// Bei externen Dateien nehmen wir einfach die ersten 50 Zeilen aus den geparsten Daten
|
||||
values = sheetInfo.externalData.slice(0, 50);
|
||||
} else {
|
||||
// Bei internen Dateien laden wir die Daten über die Excel API
|
||||
const sheet = context.workbook.worksheets.getItem(sheetInfo.name);
|
||||
const range = sheet.getRange("A1:AX50");
|
||||
range.load("values");
|
||||
await context.sync();
|
||||
values = range.values;
|
||||
}
|
||||
|
||||
let bestRowIndex = 0;
|
||||
let maxMatches = -1;
|
||||
|
||||
// Finde die Zeile, die die meisten Übereinstimmungen mit unseren Zielspalten hat
|
||||
for (let r = 0; r < values.length; r++) {
|
||||
const rowData = values[r];
|
||||
if (!rowData) continue; // Safety check in case external sheet has sparse arrays
|
||||
|
||||
let matches = 0;
|
||||
for (let c = 0; c < rowData.length; c++) {
|
||||
const cellStr = String(rowData[c]).trim().toLowerCase();
|
||||
if (TARGET_COLUMNS.some((tc) => tc.aliases.includes(cellStr))) {
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
if (matches > maxMatches) {
|
||||
maxMatches = matches;
|
||||
bestRowIndex = r;
|
||||
}
|
||||
}
|
||||
|
||||
// Sicherstellen, dass values[bestRowIndex] existiert (falls Blatt komplett leer ist)
|
||||
// Wenn maxMatches immer noch -1 ist (oder 0), dann wurde absolut nichts gefunden
|
||||
const headerRow = (maxMatches > 0 && values[bestRowIndex]) ? values[bestRowIndex] : [];
|
||||
const status = buildSheetMappingStatus(sheetInfo, headerRow, maxMatches > 0 ? bestRowIndex : 0);
|
||||
results.push(status);
|
||||
}
|
||||
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Erlaubt das Neuladen für eine spezifische (vom Nutzer geänderte) Kopfzeile.
|
||||
*/
|
||||
export async function detectHeadersForSingleSheetRow(sheetInfo: SheetInfo, rowIndex: number): Promise<SheetMappingStatus> {
|
||||
if (sheetInfo.isExternal && sheetInfo.externalData) {
|
||||
// Externe Datei
|
||||
const headerRow = sheetInfo.externalData[rowIndex] || [];
|
||||
return buildSheetMappingStatus(sheetInfo, headerRow, rowIndex);
|
||||
} else {
|
||||
// Interne Datei
|
||||
return Excel.run(async (context) => {
|
||||
const sheet = context.workbook.worksheets.getItem(sheetInfo.name);
|
||||
// rowIndex ist 0-basiert, Range ist 1-basiert
|
||||
const range = sheet.getRangeByIndexes(rowIndex, 0, 1, 50); // Lese Spalten 0-49 in der gewünschten Zeile
|
||||
range.load("values");
|
||||
await context.sync();
|
||||
|
||||
return buildSheetMappingStatus(sheetInfo, range.values[0], rowIndex);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hilfsfunktion zum Erstellen des State-Objekts für das Mapping.
|
||||
*/
|
||||
function buildSheetMappingStatus(sheetInfo: SheetInfo, headerRow: any[], rowIndex: number): SheetMappingStatus {
|
||||
const availableColumns = headerRow.map((val, idx) => ({
|
||||
name: String(val).trim() || `(Leere Spalte ${idx + 1})`,
|
||||
index: idx,
|
||||
})).filter(col => col.name !== `(Leere Spalte ${col.index + 1})`);
|
||||
|
||||
const mappings = TARGET_COLUMNS.map((tc) => {
|
||||
const foundIdx = headerRow.findIndex((cell) => tc.aliases.includes(String(cell).trim().toLowerCase()));
|
||||
return {
|
||||
targetColumn: tc.id,
|
||||
sourceColumnIndex: foundIdx,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
sheetName: sheetInfo.name, // Für Anzeige
|
||||
headerRowIndex: rowIndex,
|
||||
availableColumns,
|
||||
mappings,
|
||||
isExternal: sheetInfo.isExternal,
|
||||
externalData: sheetInfo.externalData
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt die eigentliche Konsolidierung aus allen Arbeitsblättern durch und schreibt
|
||||
* das Ergebnis in das Blatt "Gesamtliste".
|
||||
*/
|
||||
export async function consolidateData(mappings: SheetMappingStatus[]): Promise<number> {
|
||||
return Excel.run(async (context) => {
|
||||
let rowsConsolidated = 0;
|
||||
const finalData: any[][] = [];
|
||||
|
||||
// Daten auslesen und mergen
|
||||
for (const mapping of mappings) {
|
||||
if (mapping.mappings.length === 0) continue;
|
||||
|
||||
let textValues: any[][] = [];
|
||||
let dataStartRowOffset = 0;
|
||||
const targetIndicesMap = mapping.mappings.map(m => m.sourceColumnIndex);
|
||||
|
||||
if (mapping.isExternal && mapping.externalData) {
|
||||
// Bei externen Dateien
|
||||
textValues = mapping.externalData;
|
||||
dataStartRowOffset = mapping.headerRowIndex + 1;
|
||||
} else {
|
||||
// Bei internen Dateien
|
||||
const sheet = context.workbook.worksheets.getItem(mapping.sheetName);
|
||||
const usedRange = sheet.getUsedRange();
|
||||
usedRange.load(["rowIndex", "rowCount", "columnCount", "text"]);
|
||||
await context.sync();
|
||||
|
||||
const startRowIdx = usedRange.rowIndex;
|
||||
// Offset berechnen: Wie viele Zeilen nach dem Start des UsedRange liegt die erste Datenzeile?
|
||||
dataStartRowOffset = (mapping.headerRowIndex + 1) - startRowIdx;
|
||||
|
||||
// Wenn die Daten unterhalb des UsedRange beginnen oder das Blatt leer ist
|
||||
if (dataStartRowOffset >= usedRange.rowCount || usedRange.rowCount === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
textValues = usedRange.text; // Text returns the exact formatted strings
|
||||
}
|
||||
|
||||
// Schleife ab Zeile nach Header bis Ende
|
||||
for (let r = Math.max(0, dataStartRowOffset); r < textValues.length; r++) {
|
||||
const textRow = textValues[r] || [];
|
||||
|
||||
// Prüfen ob Row komplett leer ist (oder zumindest in den Mapped Columns leer)
|
||||
const isRowEmpty = targetIndicesMap.every(srcColIdx => {
|
||||
if (srcColIdx === -1) return true;
|
||||
const val = textRow[srcColIdx];
|
||||
return val === null || val === undefined || String(val).trim() === "";
|
||||
});
|
||||
|
||||
if (isRowEmpty) continue;
|
||||
|
||||
const consolidatedRow: any[] = [];
|
||||
// Die 6 Standard Target Columns
|
||||
for (const srcColIdx of targetIndicesMap) {
|
||||
if (srcColIdx !== -1 && srcColIdx < textRow.length) {
|
||||
consolidatedRow.push(String(textRow[srcColIdx]));
|
||||
} else {
|
||||
consolidatedRow.push("");
|
||||
}
|
||||
}
|
||||
|
||||
// Zusatzfelder für die Kabelliste: Länge, gezogen am, von (Monteur)
|
||||
consolidatedRow.push("");
|
||||
consolidatedRow.push("");
|
||||
consolidatedRow.push("");
|
||||
|
||||
finalData.push(consolidatedRow);
|
||||
rowsConsolidated++;
|
||||
}
|
||||
}
|
||||
|
||||
// Kabelliste erstellen oder überschreiben
|
||||
let targetSheet: Excel.Worksheet;
|
||||
try {
|
||||
targetSheet = context.workbook.worksheets.getItem("Kabelliste");
|
||||
// Falls sie existiert, zuerst löschen, um sie neu zu erstellen
|
||||
targetSheet.delete();
|
||||
await context.sync();
|
||||
} catch (e) {
|
||||
// Ignorieren (Blatt existiert noch nicht)
|
||||
}
|
||||
|
||||
try {
|
||||
targetSheet = context.workbook.worksheets.add("Kabelliste");
|
||||
|
||||
// Header Zeile schreiben
|
||||
const fullHeaders = [...TARGET_COLUMNS.map(tc => tc.id), "Länge", "gezogen am", "von (Monteur)"];
|
||||
|
||||
// finalData hat fullHeaders.length Spalten
|
||||
const totalRowsCount = finalData.length + 1; // +1 für Header
|
||||
const totalColsCount = fullHeaders.length;
|
||||
|
||||
const targetRange = targetSheet.getRangeByIndexes(0, 0, totalRowsCount, totalColsCount);
|
||||
|
||||
const allValues = [fullHeaders, ...finalData];
|
||||
|
||||
// Formatiere die Zielzellen als Text ("@"), BEVOR die Werte reingeschrieben werden,
|
||||
// damit Excel nicht versucht, Datumsstrings als numerische Datumsformate umzuwandeln.
|
||||
const formatArray: string[][] = [];
|
||||
for (let i = 0; i < totalRowsCount; i++) {
|
||||
formatArray.push(new Array(totalColsCount).fill("@"));
|
||||
}
|
||||
|
||||
targetRange.numberFormat = formatArray;
|
||||
targetRange.values = allValues;
|
||||
|
||||
await context.sync();
|
||||
|
||||
// Als Tabelle formatieren
|
||||
const table = targetSheet.tables.add(targetRange, true /* hasHeaders */);
|
||||
table.name = "KonsolidierteKabel";
|
||||
table.style = "TableStyleLight9";
|
||||
table.showFilterButton = true;
|
||||
|
||||
// Spaltenbreite anpassen (AutoFit)
|
||||
targetRange.format.autofitColumns();
|
||||
await context.sync();
|
||||
|
||||
targetSheet.activate();
|
||||
|
||||
return rowsConsolidated;
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Erstellen der Kabelliste:", error);
|
||||
throw new Error("Fehler beim Erstellen der 'Kabelliste'. Möglicherweise ist die Arbeitsmappe schreibgeschützt.");
|
||||
}
|
||||
});
|
||||
}
|
||||
27
src/taskpane/index.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import * as React from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./components/App";
|
||||
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
|
||||
|
||||
/* global document, Office, module, require, HTMLElement */
|
||||
|
||||
const title = "Contoso Task Pane Add-in";
|
||||
|
||||
const rootElement: HTMLElement | null = document.getElementById("container");
|
||||
const root = rootElement ? createRoot(rootElement) : undefined;
|
||||
|
||||
/* Render application after Office initializes */
|
||||
Office.onReady(() => {
|
||||
root?.render(
|
||||
<FluentProvider theme={webLightTheme}>
|
||||
<App title={title} />
|
||||
</FluentProvider>
|
||||
);
|
||||
});
|
||||
|
||||
if ((module as any).hot) {
|
||||
(module as any).hot.accept("./components/App", () => {
|
||||
const NextApp = require("./components/App").default;
|
||||
root?.render(NextApp);
|
||||
});
|
||||
}
|
||||
36
src/taskpane/models.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export interface SheetInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
isExternal?: boolean; // True if loaded from an external file
|
||||
fileName?: string; // The name of the external file
|
||||
externalData?: any[][]; // The raw parsed data from SheetJS
|
||||
}
|
||||
|
||||
export interface ColumnMappingInfo {
|
||||
targetColumn: string;
|
||||
sourceColumnIndex: number; // -1 if not found
|
||||
}
|
||||
|
||||
export interface SheetMappingStatus {
|
||||
sheetName: string;
|
||||
headerRowIndex: number; // 0-indexed
|
||||
mappings: ColumnMappingInfo[];
|
||||
availableColumns: { name: string; index: number }[];
|
||||
isExternal?: boolean;
|
||||
externalData?: any[][];
|
||||
}
|
||||
|
||||
export interface TargetColumnDef {
|
||||
id: string; // e.g. "K-Nr."
|
||||
aliases: string[]; // e.g. ["k-nr.", "kabelnummer", "nr."]
|
||||
}
|
||||
|
||||
export const TARGET_COLUMNS: TargetColumnDef[] = [
|
||||
{ id: "K-Nr.", aliases: ["k-nr.", "k-nr", "kabelnummer", "kabel", "nummer", "nr.", "nr"] },
|
||||
{ id: "Bezeichnung", aliases: ["bezeichnung", "name", "titel", "beschreibung"] },
|
||||
{ id: "von", aliases: ["von", "start", "quelle", "ursprung"] },
|
||||
{ id: "von Raum", aliases: ["von raum", "raum von", "raum (von)", "startraum"] },
|
||||
{ id: "nach", aliases: ["nach", "ziel", "ende", "destination"] },
|
||||
{ id: "nach Raum", aliases: ["nach raum", "raum nach", "raum (nach)", "zielraum"] },
|
||||
{ id: "Kabeltyp", aliases: ["kabeltyp", "typ", "kabelart", "art", "querschnitt"] }
|
||||
];
|
||||
41
src/taskpane/taskpane.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -->
|
||||
<!-- See LICENSE in the project root for license information -->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en" data-framework="typescript">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Contoso Task Pane Add-in</title>
|
||||
|
||||
<!-- Office JavaScript API -->
|
||||
<script type="text/javascript" src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
|
||||
</head>
|
||||
|
||||
<body style="width: 100%; height: 100%; margin: 0; padding: 0;">
|
||||
<div id="container"></div>
|
||||
|
||||
<!--
|
||||
Fluent UI React v. 9 uses modern JavaScript syntax that is not supported in
|
||||
Trident (Internet Explorer) or EdgeHTML (Edge Legacy), so this add-in won't
|
||||
work in Office versions that use these webviews. The script below makes the
|
||||
following div display when an unsupported webview is in use, and hides the
|
||||
React container div.
|
||||
-->
|
||||
<div id="tridentmessage" style="display: none; padding: 10;">
|
||||
This add-in will not run in your version of Office. Please upgrade either to perpetual Office 2021 (or later)
|
||||
or to a Microsoft 365 account.
|
||||
</div>
|
||||
<script>
|
||||
if ((navigator.userAgent.indexOf("Trident") !== -1) || (navigator.userAgent.indexOf("Edge") !== -1)) {
|
||||
var tridentMessage = document.getElementById("tridentmessage");
|
||||
var normalContainer = document.getElementById("container");
|
||||
tridentMessage.style.display = "block";
|
||||
normalContainer.style.display = "none";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
16
src/taskpane/taskpane.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/* global Excel console */
|
||||
|
||||
export async function insertText(text: string) {
|
||||
// Write text to the top left cell.
|
||||
try {
|
||||
await Excel.run(async (context) => {
|
||||
const sheet = context.workbook.worksheets.getActiveWorksheet();
|
||||
const range = sheet.getRange("A1");
|
||||
range.values = [[text]];
|
||||
range.format.autofitColumns();
|
||||
await context.sync();
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error: " + error);
|
||||
}
|
||||
}
|
||||
0
ts_errors.txt
Normal file
BIN
tsc_output.txt
Normal file
38
tsconfig.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowUnusedLabels": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"jsx": "react",
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitReturns": true,
|
||||
"noUnusedParameters": true,
|
||||
"outDir": "dist",
|
||||
"removeComments": false,
|
||||
"sourceMap": true,
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es7",
|
||||
"dom"
|
||||
],
|
||||
"pretty": true,
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"compileOnSave": false,
|
||||
"buildOnSave": false,
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
},
|
||||
"files": true
|
||||
}
|
||||
}
|
||||
111
webpack.config.js
Normal file
@@ -0,0 +1,111 @@
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
const devCerts = require("office-addin-dev-certs");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const webpack = require("webpack");
|
||||
|
||||
const urlDev = "https://localhost:3037/";
|
||||
const urlProd = "https://kabel.casademm.de/"; // CHANGE THIS TO YOUR PRODUCTION DEPLOYMENT LOCATION
|
||||
|
||||
async function getHttpsOptions() {
|
||||
const httpsOptions = await devCerts.getHttpsServerOptions();
|
||||
return { ca: httpsOptions.ca, key: httpsOptions.key, cert: httpsOptions.cert };
|
||||
}
|
||||
|
||||
module.exports = async (env, options) => {
|
||||
const dev = options.mode === "development";
|
||||
const config = {
|
||||
devtool: "source-map",
|
||||
entry: {
|
||||
polyfill: ["core-js/stable", "regenerator-runtime/runtime"],
|
||||
react: ["react", "react-dom"],
|
||||
taskpane: {
|
||||
import: ["./src/taskpane/index.tsx", "./src/taskpane/taskpane.html"],
|
||||
dependOn: "react",
|
||||
},
|
||||
commands: "./src/commands/commands.ts",
|
||||
},
|
||||
output: {
|
||||
clean: true,
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".tsx", ".html", ".js"],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
use: ["ts-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
exclude: /node_modules/,
|
||||
use: "html-loader",
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|jpeg|ttf|woff|woff2|gif|ico)$/,
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "assets/[name][ext][query]",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "taskpane.html",
|
||||
template: "./src/taskpane/taskpane.html",
|
||||
chunks: ["polyfill", "taskpane", "react"],
|
||||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: "assets/*",
|
||||
to: "assets/[name][ext][query]",
|
||||
},
|
||||
{
|
||||
from: "manifest*.xml",
|
||||
to: "[name]" + "[ext]",
|
||||
transform(content) {
|
||||
if (dev) {
|
||||
return content;
|
||||
} else {
|
||||
return content.toString().replace(new RegExp(urlDev, "g"), urlProd);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "commands.html",
|
||||
template: "./src/commands/commands.html",
|
||||
chunks: ["polyfill", "commands"],
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
Promise: ["es6-promise", "Promise"],
|
||||
}),
|
||||
],
|
||||
devServer: {
|
||||
hot: true,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
server: {
|
||||
type: "https",
|
||||
options: env.WEBPACK_BUILD || options.https !== undefined ? options.https : await getHttpsOptions(),
|
||||
},
|
||||
port: process.env.npm_package_config_dev_server_port || 3000,
|
||||
},
|
||||
};
|
||||
|
||||
return config;
|
||||
};
|
||||