first commit
This commit is contained in:
3
.env.example
Normal file
3
.env.example
Normal file
@ -0,0 +1,3 @@
|
||||
KINTONE_BASE_URL="https://sample.kintone.com"
|
||||
KINTONE_USERNAME="user01"
|
||||
KINTONE_PASSWORD="XXXXXXXXXXXXXXXX"
|
167
.gitignore
vendored
Normal file
167
.gitignore
vendored
Normal file
@ -0,0 +1,167 @@
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,node
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,node
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
### Node Patch ###
|
||||
# Serverless Webpack directories
|
||||
.webpack/
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
# SvelteKit build / generate output
|
||||
.svelte-kit
|
||||
|
||||
### VisualStudioCode ###
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
### VisualStudioCode Patch ###
|
||||
# Ignore all local history of files
|
||||
.history
|
||||
.ionide
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,node
|
||||
|
||||
# customize
|
||||
private.ppk
|
||||
plugin/js/
|
8
.prettierrc.json
Normal file
8
.prettierrc.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"trailingComma": "all"
|
||||
}
|
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["esbenp.prettier-vscode"]
|
||||
}
|
37
.vscode/settings.json
vendored
Normal file
37
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[html]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[css]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
// Extensions - HTML
|
||||
"html.format.wrapLineLength": 0,
|
||||
// Extentions - Prettier
|
||||
// - see: .prettierrc.json
|
||||
// Extentions - Typescript
|
||||
"javascript.updateImportsOnFileMove.enabled": "always",
|
||||
"typescript.updateImportsOnFileMove.enabled": "always"
|
||||
}
|
45
declaration.d.ts
vendored
Normal file
45
declaration.d.ts
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
// CSS modules
|
||||
type CSSModuleClasses = { readonly [key: string]: string };
|
||||
|
||||
declare module '*.module.css' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.scss' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.sass' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.less' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.styl' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.stylus' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.pcss' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
declare module '*.module.sss' {
|
||||
const classes: CSSModuleClasses;
|
||||
export default classes;
|
||||
}
|
||||
|
||||
// CSS
|
||||
declare module '*.css' {}
|
||||
declare module '*.scss' {}
|
||||
declare module '*.sass' {}
|
||||
declare module '*.less' {}
|
||||
declare module '*.styl' {}
|
||||
declare module '*.stylus' {}
|
||||
declare module '*.pcss' {}
|
||||
declare module '*.sss' {}
|
4
eslint.config.mjs
Normal file
4
eslint.config.mjs
Normal file
@ -0,0 +1,4 @@
|
||||
import presetsPrettier from "@cybozu/eslint-config/flat/presets/react-typescript-prettier.js";
|
||||
|
||||
/** @type {import("eslint").Linter.Config[]} */
|
||||
export default [...presetsPrettier];
|
11665
package-lock.json
generated
Normal file
11665
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
59
package.json
Normal file
59
package.json
Normal file
@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "kintone-plugin-docx",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"prepare": "node scripts/prepare-private-key.js",
|
||||
"start": "node scripts/npm-start.js",
|
||||
"develop": "npm run build -- --watch",
|
||||
"build": "npm run prepare && cross-env NODE_ENV=development webpack",
|
||||
"build:prod": "npm run prepare && cross-env NODE_ENV=production webpack",
|
||||
"dts-gen": "kintone-dts-gen",
|
||||
"lint": "eslint src --ext .js,.jsx,.ts,.tsx",
|
||||
"upload": "env-cmd --silent kintone-plugin-uploader dist/plugin.zip --watch --waiting-dialog-ms 3000"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kintone/rest-api-client": "^5.7.3",
|
||||
"angular-expressions": "^1.4.3",
|
||||
"clsx": "^2.1.1",
|
||||
"core-js": "^3.42.0",
|
||||
"docxtemplater": "^3.63.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"kintone-pretty-fields": "^0.10.5",
|
||||
"moize": "^6.1.6",
|
||||
"pizzip": "^3.2.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"tiny-invariant": "^1.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.1",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/preset-typescript": "^7.27.1",
|
||||
"@cybozu/eslint-config": "^24.0.0",
|
||||
"@kintone/dts-gen": "^8.1.2",
|
||||
"@kintone/plugin-uploader": "^9.1.5",
|
||||
"@kintone/webpack-plugin-kintone-plugin": "^8.0.11",
|
||||
"@shin-chan/kypes": "^0.0.7",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/react": "^19.1.5",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"ajv": "^8.17.1",
|
||||
"babel-loader": "^10.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^7.1.2",
|
||||
"env-cmd": "^10.1.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"prettier": "^3.5.3",
|
||||
"sass": "^1.89.0",
|
||||
"sass-loader": "^16.0.5",
|
||||
"style-loader": "^4.0.0",
|
||||
"typescript": "^5.8.3",
|
||||
"webpack": "^5.99.9",
|
||||
"webpack-cli": "^6.0.1"
|
||||
}
|
||||
}
|
1
plugin/html/config.html
Normal file
1
plugin/html/config.html
Normal file
@ -0,0 +1 @@
|
||||
<div id="plugin-config-root"></div>
|
BIN
plugin/image/icon.png
Normal file
BIN
plugin/image/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
26
plugin/manifest.json
Normal file
26
plugin/manifest.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/kintone/js-sdk/%40kintone/plugin-manifest-validator%4010.2.0/packages/plugin-manifest-validator/manifest-schema.json",
|
||||
"manifest_version": 1,
|
||||
"version": "1.0.0",
|
||||
"type": "APP",
|
||||
"desktop": {
|
||||
"js": ["js/desktop.js"]
|
||||
},
|
||||
"mobile": {
|
||||
"js": ["js/desktop.js"]
|
||||
},
|
||||
"icon": "image/icon.png",
|
||||
"config": {
|
||||
"html": "html/config.html",
|
||||
"js": ["js/config.js"],
|
||||
"required_params": ["template"]
|
||||
},
|
||||
"name": {
|
||||
"en": "Word output plugin",
|
||||
"ja": "Word出力プラグイン"
|
||||
},
|
||||
"description": {
|
||||
"en": "Data can be formatted and downloaded using WORD template files.",
|
||||
"ja": "WORDテンプレートファイルを使用して、データを整形してダウンロードできます。"
|
||||
}
|
||||
}
|
16
scripts/npm-start.js
Normal file
16
scripts/npm-start.js
Normal file
@ -0,0 +1,16 @@
|
||||
/* eslint-env node */
|
||||
'use strict';
|
||||
|
||||
const runAll = require('npm-run-all');
|
||||
|
||||
runAll(['develop', 'upload'], {
|
||||
parallel: true,
|
||||
stdout: process.stdout,
|
||||
stdin: process.stdin,
|
||||
}).catch(({ results }) => {
|
||||
results
|
||||
.filter(({ code }) => code)
|
||||
.forEach(({ name }) => {
|
||||
console.log(`"npm run ${name}" was failed`);
|
||||
});
|
||||
});
|
15
scripts/prepare-private-key.js
Normal file
15
scripts/prepare-private-key.js
Normal file
@ -0,0 +1,15 @@
|
||||
/* eslint-env node */
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const RSA = require('node-rsa');
|
||||
|
||||
const key = new RSA({ b: 1024 });
|
||||
const privateKey = key.exportKey('pkcs1-private');
|
||||
const privateKeyFile = './private.ppk';
|
||||
|
||||
if (!fs.existsSync(privateKeyFile)) {
|
||||
fs.writeFile(privateKeyFile, privateKey, (err) => {
|
||||
err && console.error(err);
|
||||
});
|
||||
}
|
18
src/common/ErrorFallback.tsx
Normal file
18
src/common/ErrorFallback.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
import KintonePluginAlert from './ui/KintonePluginAlert';
|
||||
|
||||
interface Props {
|
||||
error: Error;
|
||||
}
|
||||
|
||||
const ErrorFallback: React.FC<Props> = (props) => {
|
||||
const { error } = props;
|
||||
return (
|
||||
<KintonePluginAlert>
|
||||
<p>Something went wrong:</p>
|
||||
<pre>{error.message}</pre>
|
||||
</KintonePluginAlert>
|
||||
);
|
||||
};
|
||||
export default ErrorFallback;
|
196
src/common/Loading.css
Normal file
196
src/common/Loading.css
Normal file
@ -0,0 +1,196 @@
|
||||
.loading {
|
||||
margin: 100px auto;
|
||||
font-size: 25px;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
text-indent: -9999em;
|
||||
-webkit-animation: loadingKeyframes 1.1s infinite ease;
|
||||
animation: loadingKeyframes 1.1s infinite ease;
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
@-webkit-keyframes loadingKeyframes {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em #000000,
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2),
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.5),
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
12.5% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.7),
|
||||
1.8em -1.8em 0 0em #000000,
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2),
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
25% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.5),
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.7),
|
||||
2.5em 0em 0 0em #000000,
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2),
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
37.5% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.5),
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.7),
|
||||
1.75em 1.75em 0 0em #000000,
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
50% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.5),
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.7),
|
||||
0em 2.5em 0 0em #000000,
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
62.5% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.5),
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.7),
|
||||
-1.8em 1.8em 0 0em #000000,
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
75% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2),
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.5),
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.7),
|
||||
-2.6em 0em 0 0em #000000,
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
87.5% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2),
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.5),
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.7),
|
||||
-1.8em -1.8em 0 0em #000000;
|
||||
}
|
||||
}
|
||||
@keyframes loadingKeyframes {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em #000000,
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2),
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.5),
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
12.5% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.7),
|
||||
1.8em -1.8em 0 0em #000000,
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2),
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
25% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.5),
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.7),
|
||||
2.5em 0em 0 0em #000000,
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2),
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
37.5% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.5),
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.7),
|
||||
1.75em 1.75em 0 0em #000000,
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
50% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.5),
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.7),
|
||||
0em 2.5em 0 0em #000000,
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
62.5% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.5),
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.7),
|
||||
-1.8em 1.8em 0 0em #000000,
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
75% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2),
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.5),
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.7),
|
||||
-2.6em 0em 0 0em #000000,
|
||||
-1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
87.5% {
|
||||
box-shadow:
|
||||
0em -2.6em 0em 0em rgba(0, 0, 0, 0.2),
|
||||
1.8em -1.8em 0 0em rgba(0, 0, 0, 0.2),
|
||||
2.5em 0em 0 0em rgba(0, 0, 0, 0.2),
|
||||
1.75em 1.75em 0 0em rgba(0, 0, 0, 0.2),
|
||||
0em 2.5em 0 0em rgba(0, 0, 0, 0.2),
|
||||
-1.8em 1.8em 0 0em rgba(0, 0, 0, 0.5),
|
||||
-2.6em 0em 0 0em rgba(0, 0, 0, 0.7),
|
||||
-1.8em -1.8em 0 0em #000000;
|
||||
}
|
||||
}
|
8
src/common/Loading.tsx
Normal file
8
src/common/Loading.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
import './Loading.css';
|
||||
|
||||
const Loading: React.FC = () => {
|
||||
return <div className="loading">Loading...</div>;
|
||||
};
|
||||
export default Loading;
|
4
src/common/global.ts
Normal file
4
src/common/global.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import invariant from 'tiny-invariant';
|
||||
|
||||
export const PLUGIN_ID = kintone.$PLUGIN_ID;
|
||||
invariant(PLUGIN_ID, 'The PLUGIN_ID is not available. Please ensure you are on a Kintone plugin page.');
|
669
src/common/ui/51-modern-default.css
Normal file
669
src/common/ui/51-modern-default.css
Normal file
File diff suppressed because one or more lines are too long
18
src/common/ui/KintonePluginAlert.tsx
Normal file
18
src/common/ui/KintonePluginAlert.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface KintonePluginAlertProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const KintonePluginAlert: React.FC<KintonePluginAlertProps> = (props) => {
|
||||
const { className, children } = props;
|
||||
return (
|
||||
<div className={clsx('kintoneplugin-alert', className)} role="alert">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default KintonePluginAlert;
|
26
src/common/ui/KintonePluginButton.tsx
Normal file
26
src/common/ui/KintonePluginButton.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface KintonePluginButtonProps {
|
||||
className?: string;
|
||||
variant: 'normal' | 'disabled' | 'dialog-ok' | 'dialog-cancel';
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const KintonePluginButton: React.FC<KintonePluginButtonProps> = (props) => {
|
||||
const { className, variant, type, onClick, children } = props;
|
||||
return (
|
||||
<button
|
||||
className={clsx(`kintoneplugin-button-${variant}`, className)}
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
disabled={variant === 'disabled'}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
export default KintonePluginButton;
|
14
src/common/ui/KintonePluginDesc.tsx
Normal file
14
src/common/ui/KintonePluginDesc.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface KintonePluginDescProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const KintonePluginDesc: React.FC<KintonePluginDescProps> = (props) => {
|
||||
const { className, children } = props;
|
||||
return <div className={clsx('kintoneplugin-desc', className)}>{children}</div>;
|
||||
};
|
||||
export default KintonePluginDesc;
|
14
src/common/ui/KintonePluginLabel.tsx
Normal file
14
src/common/ui/KintonePluginLabel.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface KintonePluginLabelProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const KintonePluginLabel: React.FC<KintonePluginLabelProps> = (props) => {
|
||||
const { className, children } = props;
|
||||
return <div className={clsx('kintoneplugin-label', className)}>{children}</div>;
|
||||
};
|
||||
export default KintonePluginLabel;
|
14
src/common/ui/KintonePluginRequire.tsx
Normal file
14
src/common/ui/KintonePluginRequire.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface KintonePluginRequire {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const KintonePluginRequire: React.FC<KintonePluginRequire> = (props) => {
|
||||
const { className, children } = props;
|
||||
return <span className={clsx('kintoneplugin-require', className)}>{children}</span>;
|
||||
};
|
||||
export default KintonePluginRequire;
|
14
src/common/ui/KintonePluginRow.tsx
Normal file
14
src/common/ui/KintonePluginRow.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface KintonePluginRowProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const KintonePluginRow: React.FC<KintonePluginRowProps> = (props) => {
|
||||
const { className, children } = props;
|
||||
return <div className={clsx('kintoneplugin-row', className)}>{children}</div>;
|
||||
};
|
||||
export default KintonePluginRow;
|
38
src/common/ui/KintonePluginSelect.tsx
Normal file
38
src/common/ui/KintonePluginSelect.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
export interface KintonePluginSelectOptionData {
|
||||
key: string;
|
||||
value: string;
|
||||
label: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface KintonePluginSelectProps {
|
||||
className?: string;
|
||||
defaultValue?: string;
|
||||
onChange?: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
options: KintonePluginSelectOptionData[];
|
||||
}
|
||||
|
||||
const KintonePluginSelect: React.FC<KintonePluginSelectProps> = (props) => {
|
||||
const { className, defaultValue, onChange, options } = props;
|
||||
if (!options || options.length === 0) {
|
||||
return null; // Return null if no options are provided
|
||||
}
|
||||
return (
|
||||
<div className={clsx('kintoneplugin-select-outer', className)}>
|
||||
<div className="kintoneplugin-select">
|
||||
<select defaultValue={defaultValue} onChange={onChange}>
|
||||
{options.map((option) => (
|
||||
<option key={option.key} value={option.value} disabled={option.disabled}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default KintonePluginSelect;
|
14
src/common/ui/KintonePluginTitle.tsx
Normal file
14
src/common/ui/KintonePluginTitle.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
interface KintonePluginTitleProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const KintonePluginTitle: React.FC<KintonePluginTitleProps> = (props) => {
|
||||
const { className, children } = props;
|
||||
return <div className={clsx('kintoneplugin-title', className)}>{children}</div>;
|
||||
};
|
||||
export default KintonePluginTitle;
|
17
src/config/ConfigApp.tsx
Normal file
17
src/config/ConfigApp.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import ErrorFallback from '../common/ErrorFallback';
|
||||
import Loading from '../common/Loading';
|
||||
import Settings from './Settings';
|
||||
|
||||
const ConfigApp: React.FC = () => {
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<React.Suspense fallback={<Loading />}>
|
||||
<Settings />
|
||||
</React.Suspense>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
export default ConfigApp;
|
3
src/config/Settings.module.css
Normal file
3
src/config/Settings.module.css
Normal file
@ -0,0 +1,3 @@
|
||||
.buttons > *:not(:last-child) {
|
||||
margin-right: 0.5em;
|
||||
}
|
87
src/config/Settings.tsx
Normal file
87
src/config/Settings.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
|
||||
import { KintoneRestAPIClient } from '@kintone/rest-api-client';
|
||||
import { kintonePrettyFields } from 'kintone-pretty-fields';
|
||||
import moize from 'moize';
|
||||
import invariant from 'tiny-invariant';
|
||||
import { PLUGIN_ID } from '../common/global';
|
||||
import KintonePluginAlert from '../common/ui/KintonePluginAlert';
|
||||
import KintonePluginButton from '../common/ui/KintonePluginButton';
|
||||
import KintonePluginDesc from '../common/ui/KintonePluginDesc';
|
||||
import KintonePluginLabel from '../common/ui/KintonePluginLabel';
|
||||
import KintonePluginRequire from '../common/ui/KintonePluginRequire';
|
||||
import KintonePluginRow from '../common/ui/KintonePluginRow';
|
||||
import KintonePluginSelect, { KintonePluginSelectOptionData } from '../common/ui/KintonePluginSelect';
|
||||
import KintonePluginTitle from '../common/ui/KintonePluginTitle';
|
||||
|
||||
import styles from './Settings.module.css';
|
||||
|
||||
const cachedFields = moize.promise(async (appId: number) => {
|
||||
const client = new KintoneRestAPIClient();
|
||||
const fields = await kintonePrettyFields.getFields({ client, app: appId, lang: 'en', preview: false });
|
||||
return fields;
|
||||
});
|
||||
|
||||
const Settings: React.FC = () => {
|
||||
const config = kintone.plugin.app.getConfig(PLUGIN_ID);
|
||||
const appId = kintone.app.getId();
|
||||
invariant(appId, 'The app ID is not available. Please ensure you are on a Kintone app page.');
|
||||
const { fields } = React.use(cachedFields(appId));
|
||||
const fileFields = fields.filter(kintonePrettyFields.isFile);
|
||||
const options: KintonePluginSelectOptionData[] = [
|
||||
{ key: '-', value: '', label: 'Select a File field', disabled: true }, // Default option
|
||||
...fileFields.map((field) => ({
|
||||
key: field.code,
|
||||
value: field.code,
|
||||
label: field.label,
|
||||
})),
|
||||
];
|
||||
|
||||
const [template, setTemplate] = React.useState<string>(config.template ?? '');
|
||||
|
||||
const handleOnSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
kintone.plugin.app.setConfig({ template }, () => {
|
||||
alert('The plug-in settings have been saved. Please update the app!');
|
||||
window.location.href = `../../flow?app=${appId}`;
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnChangeTemplate = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setTemplate(e.target.value);
|
||||
};
|
||||
|
||||
const handleOnClickCancel = () => {
|
||||
window.location.href = `../../${appId}/plugin/`;
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="settings">
|
||||
<KintonePluginLabel>Settings for the Kintone Word output plugin</KintonePluginLabel>
|
||||
<form onSubmit={handleOnSubmit}>
|
||||
<KintonePluginRow>
|
||||
<KintonePluginTitle>
|
||||
Template<KintonePluginRequire>*</KintonePluginRequire>
|
||||
</KintonePluginTitle>
|
||||
<KintonePluginDesc>Select a file field that contains the WORD template file.</KintonePluginDesc>
|
||||
{fileFields.length === 0 ? (
|
||||
<KintonePluginAlert>
|
||||
No file fields found in the app. Please add a file field to use this plugin.
|
||||
</KintonePluginAlert>
|
||||
) : (
|
||||
<KintonePluginSelect defaultValue={template} onChange={handleOnChangeTemplate} options={options} />
|
||||
)}
|
||||
</KintonePluginRow>
|
||||
<KintonePluginRow className={styles.buttons}>
|
||||
<KintonePluginButton variant="dialog-cancel" type="button" onClick={handleOnClickCancel}>
|
||||
Cancel
|
||||
</KintonePluginButton>
|
||||
<KintonePluginButton variant="dialog-ok" type="submit">
|
||||
Save
|
||||
</KintonePluginButton>
|
||||
</KintonePluginRow>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
export default Settings;
|
16
src/config/index.tsx
Normal file
16
src/config/index.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import invariant from 'tiny-invariant';
|
||||
import ConfigApp from './ConfigApp';
|
||||
|
||||
import '../common/ui/51-modern-default.css';
|
||||
|
||||
const root = document.getElementById('plugin-config-root');
|
||||
invariant(root, 'The plugin configuration root element "plugin-config-root" is not found.');
|
||||
|
||||
ReactDOM.createRoot(root).render(
|
||||
<React.StrictMode>
|
||||
<ConfigApp />
|
||||
</React.StrictMode>,
|
||||
);
|
24
src/desktop/DesktopApp.tsx
Normal file
24
src/desktop/DesktopApp.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import ErrorFallback from '../common/ErrorFallback';
|
||||
import Loading from '../common/Loading';
|
||||
import MenuPanel from './MenuPanel';
|
||||
|
||||
import '@shin-chan/kypes';
|
||||
|
||||
interface DesktopAppProps {
|
||||
event: kintone.events.AppRecordDetailShowEvent | kintone.events.MobileAppRecordDetailShowEvent;
|
||||
}
|
||||
|
||||
const DesktopApp: React.FC<DesktopAppProps> = (props) => {
|
||||
const { event } = props;
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||
<React.Suspense fallback={<Loading />}>
|
||||
<MenuPanel event={event} />
|
||||
</React.Suspense>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
export default DesktopApp;
|
128
src/desktop/MenuPanel.tsx
Normal file
128
src/desktop/MenuPanel.tsx
Normal file
@ -0,0 +1,128 @@
|
||||
import React from 'react';
|
||||
|
||||
import { KintoneRestAPIClient } from '@kintone/rest-api-client';
|
||||
import { KintoneRecord } from '@shin-chan/kypes/types/page';
|
||||
import Docxtemplater from 'docxtemplater';
|
||||
import expressionParser from 'docxtemplater/expressions';
|
||||
import { saveAs } from 'file-saver';
|
||||
import PizZip from 'pizzip';
|
||||
import { PLUGIN_ID } from '../common/global';
|
||||
import KintonePluginAlert from '../common/ui/KintonePluginAlert';
|
||||
import KintonePluginButton from '../common/ui/KintonePluginButton';
|
||||
|
||||
import '@shin-chan/kypes';
|
||||
|
||||
interface TemplateData {
|
||||
[key: string]: TemplateData | TemplateData[] | string | string[];
|
||||
}
|
||||
|
||||
const DOCX_CONTENTTYPE = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
|
||||
|
||||
interface MenuPanelProps {
|
||||
event: kintone.events.AppRecordDetailShowEvent | kintone.events.MobileAppRecordDetailShowEvent;
|
||||
}
|
||||
|
||||
const record2data = (record: Partial<KintoneRecord>): TemplateData => {
|
||||
const data: TemplateData = {};
|
||||
for (const key in record) {
|
||||
if (Object.prototype.hasOwnProperty.call(record, key)) {
|
||||
const field = record[key];
|
||||
if (field == null) continue;
|
||||
const { type, value } = field;
|
||||
if (value == null) continue;
|
||||
if (type === 'CREATOR' || type === 'MODIFIER') {
|
||||
data[key] = {
|
||||
name: value.name,
|
||||
code: value.code,
|
||||
};
|
||||
} else if (type === 'CATEGORY' || type === 'CHECK_BOX' || type === 'MULTI_SELECT') {
|
||||
data[key] = value.map((v) => v);
|
||||
} else if (
|
||||
type === 'STATUS_ASSIGNEE' ||
|
||||
type === 'USER_SELECT' ||
|
||||
type === 'GROUP_SELECT' ||
|
||||
type === 'ORGANIZATION_SELECT'
|
||||
) {
|
||||
data[key] = value.map((v) => {
|
||||
return { name: v.name, code: v.code };
|
||||
});
|
||||
} else if (type === 'SUBTABLE') {
|
||||
data[key] = value.map((subRecord) => record2data(subRecord.value));
|
||||
} else {
|
||||
data[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const MenuPanel: React.FC<MenuPanelProps> = (props) => {
|
||||
const { event } = props;
|
||||
const config = kintone.plugin.app.getConfig(PLUGIN_ID);
|
||||
const template: string = config.template ?? '';
|
||||
if (template === '') {
|
||||
return (
|
||||
<KintonePluginAlert>
|
||||
WORD output plugin: Template field is not set. Please configure the plugin.
|
||||
</KintonePluginAlert>
|
||||
);
|
||||
}
|
||||
const record = event.record[template];
|
||||
if (record == null) {
|
||||
return <KintonePluginAlert>WORD output plugin: Template field is not available in this app.</KintonePluginAlert>;
|
||||
}
|
||||
if (record.type !== 'FILE') {
|
||||
return <KintonePluginAlert>WORD output plugin: The template field must be a file field.</KintonePluginAlert>;
|
||||
}
|
||||
if (record.value.length === 0) {
|
||||
return <KintonePluginAlert>WORD output plugin: The template field does not contain any files.</KintonePluginAlert>;
|
||||
}
|
||||
if (record.value.length > 1) {
|
||||
return (
|
||||
<KintonePluginAlert>
|
||||
WORD output plugin: The template field contains multiple files. Only the first file will be used.
|
||||
</KintonePluginAlert>
|
||||
);
|
||||
}
|
||||
const { fileKey, contentType } = record.value[0];
|
||||
if (contentType !== DOCX_CONTENTTYPE) {
|
||||
return (
|
||||
<KintonePluginAlert>
|
||||
WORD output plugin: The template file must be a DOCX file. The current file type is {contentType}.
|
||||
</KintonePluginAlert>
|
||||
);
|
||||
}
|
||||
const handleOnClickOutputButton = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
const appId = event.type === 'app.record.detail.show' ? kintone.app.getId() : kintone.mobile.app.getId();
|
||||
if (!appId) {
|
||||
alert('The app ID is not available. Please ensure you are on a Kintone app page.');
|
||||
return;
|
||||
}
|
||||
const client = new KintoneRestAPIClient();
|
||||
client.file
|
||||
.downloadFile({ fileKey })
|
||||
.then((content) => {
|
||||
const zip = new PizZip(content);
|
||||
const doc = new Docxtemplater(zip, {
|
||||
paragraphLoop: true,
|
||||
linebreaks: true,
|
||||
parser: expressionParser,
|
||||
});
|
||||
doc.render(record2data(event.record));
|
||||
const out = doc.getZip().generate({ type: 'blob', mimeType: DOCX_CONTENTTYPE });
|
||||
saveAs(out, 'output.docx');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error downloading file:', error);
|
||||
alert('Failed to download the WORD template file.');
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<KintonePluginButton variant="normal" onClick={handleOnClickOutputButton}>
|
||||
WORD出力
|
||||
</KintonePluginButton>
|
||||
);
|
||||
};
|
||||
export default MenuPanel;
|
34
src/desktop/index.tsx
Normal file
34
src/desktop/index.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import invariant from 'tiny-invariant';
|
||||
import DesktopApp from './DesktopApp';
|
||||
|
||||
import '@shin-chan/kypes';
|
||||
|
||||
import '../common/ui/51-modern-default.css';
|
||||
|
||||
kintone.events.on(
|
||||
['app.record.detail.show', 'mobile.app.record.detail.show'],
|
||||
async (event: kintone.events.AppRecordDetailShowEvent | kintone.events.MobileAppRecordDetailShowEvent) => {
|
||||
const spaceElement =
|
||||
event.type === 'app.record.detail.show'
|
||||
? kintone.app.record.getHeaderMenuSpaceElement()
|
||||
: kintone.mobile.app.getHeaderSpaceElement();
|
||||
invariant(
|
||||
spaceElement,
|
||||
'The header menu space element is not available. Please ensure you are on a Kintone app record detail page.',
|
||||
);
|
||||
|
||||
const root = document.createElement('div');
|
||||
spaceElement.appendChild(root);
|
||||
|
||||
ReactDOM.createRoot(root).render(
|
||||
<React.StrictMode>
|
||||
<DesktopApp event={event} />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
|
||||
return event;
|
||||
},
|
||||
);
|
74
tsconfig.json
Normal file
74
tsconfig.json
Normal file
@ -0,0 +1,74 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
||||
"module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
"lib": ["dom", "es2020"], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./plugin", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"./declaration.d.ts",
|
||||
"./node_modules/@kintone/dts-gen/kintone.d.ts"
|
||||
]
|
||||
}
|
71
webpack.config.js
Normal file
71
webpack.config.js
Normal file
@ -0,0 +1,71 @@
|
||||
/* eslint-env node */
|
||||
const path = require('path');
|
||||
const KintonePlugin = require('@kintone/webpack-plugin-kintone-plugin');
|
||||
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
module.exports = {
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
devtool: isProduction ? false : 'inline-cheap-module-source-map',
|
||||
entry: { config: './src/config/index.tsx', desktop: './src/desktop/index.tsx' },
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'plugin', 'js'),
|
||||
filename: '[name].js',
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.[t|j]sx?$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
options: {
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
useBuiltIns: 'usage',
|
||||
corejs: 3,
|
||||
modules: false,
|
||||
},
|
||||
],
|
||||
'@babel/preset-typescript',
|
||||
'@babel/preset-react',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /[^\.module]\.css$/i,
|
||||
use: ['style-loader', 'css-loader', 'postcss-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.module\.css$/i,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
sourceMap: !isProduction,
|
||||
modules: {
|
||||
localIdentName: isProduction ? '[hash:base64]' : '[name]__[local]--[hash:base64:5]',
|
||||
namedExport: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
'postcss-loader',
|
||||
'sass-loader',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new KintonePlugin({
|
||||
manifestJSONPath: './plugin/manifest.json',
|
||||
privateKeyPath: './private.ppk',
|
||||
pluginZipPath: './dist/plugin.zip',
|
||||
}),
|
||||
],
|
||||
};
|
Reference in New Issue
Block a user