mirror of
https://github.com/tomru/pdfer.git
synced 2026-03-02 22:17:18 +01:00
start over with nextjs
This commit is contained in:
@@ -1,15 +0,0 @@
|
|||||||
; EditorConfig file: http://EditorConfig.org
|
|
||||||
; Install the "EditorConfig" plugin into Sublime Text to use
|
|
||||||
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
|
|
||||||
[package.json]
|
|
||||||
indent_size = 2
|
|
||||||
74
.eslintrc
74
.eslintrc
@@ -1,74 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"node": true,
|
|
||||||
"es6": true
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"array-bracket-spacing": [2, "never", {"singleValue": false}],
|
|
||||||
"arrow-spacing": [2, {"before": true, "after": true}],
|
|
||||||
"brace-style": 2,
|
|
||||||
"camelcase": [2, {"properties": "never"}],
|
|
||||||
"comma-style": [2, "last"],
|
|
||||||
"complexity": [2, 10],
|
|
||||||
"constructor-super": 2,
|
|
||||||
"curly": [2, "all"],
|
|
||||||
"eol-last": 2,
|
|
||||||
"eqeqeq": 2,
|
|
||||||
"guard-for-in": 2,
|
|
||||||
"generator-star-spacing": [2, {"before": false, "after": true}],
|
|
||||||
"indent": [2, 4, {"SwitchCase": 1}],
|
|
||||||
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
|
|
||||||
"keyword-spacing": [2, {
|
|
||||||
"overrides": {
|
|
||||||
"catch": {"after": true},
|
|
||||||
"do": {"after": true},
|
|
||||||
"else": {"after": true},
|
|
||||||
"for": {"after": true},
|
|
||||||
"if": {"after": true},
|
|
||||||
"return": {"after": true},
|
|
||||||
"switch": {"after": true},
|
|
||||||
"try": {"after": true},
|
|
||||||
"while": {"after": true}
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
"linebreak-style": [2, "unix"],
|
|
||||||
"max-depth": [2, 3],
|
|
||||||
"max-len": [2, 140],
|
|
||||||
"max-params": [1, 4],
|
|
||||||
"max-statements": [2, 20],
|
|
||||||
"new-cap": 2,
|
|
||||||
"no-eval": 2,
|
|
||||||
"no-bitwise": 2,
|
|
||||||
"no-caller": 2,
|
|
||||||
"no-class-assign": 2,
|
|
||||||
"no-const-assign": 2,
|
|
||||||
"no-dupe-class-members": 2,
|
|
||||||
"no-empty": 2,
|
|
||||||
"no-mixed-spaces-and-tabs": 2,
|
|
||||||
"no-multi-str": 2,
|
|
||||||
"no-new": 2,
|
|
||||||
"no-this-before-super": 2,
|
|
||||||
"no-trailing-spaces": 2,
|
|
||||||
"no-undef": 2,
|
|
||||||
"no-unused-vars": 2,
|
|
||||||
"no-use-before-define": [2, "nofunc"],
|
|
||||||
"no-var": 0,
|
|
||||||
"no-with": 2,
|
|
||||||
"object-shorthand": 0,
|
|
||||||
"prefer-arrow-callback ": 0,
|
|
||||||
"prefer-const": 0,
|
|
||||||
"prefer-spread": 0,
|
|
||||||
"prefer-reflect": 0,
|
|
||||||
"prefer-template": 0,
|
|
||||||
"quotes": [2, "single", "avoid-escape"],
|
|
||||||
"require-yield": 0,
|
|
||||||
"space-before-function-paren": [2, "never"],
|
|
||||||
"space-unary-ops": [2, {"words": false, "nonwords": false}],
|
|
||||||
"space-infix-ops": 2,
|
|
||||||
"strict": [2, "global"],
|
|
||||||
"wrap-iife": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
|||||||
[submodule "server/texmf"]
|
|
||||||
path = server/texmf
|
|
||||||
url = git@github.com:tomru/tex-local-packages.git
|
|
||||||
42
README.md
42
README.md
@@ -1,3 +1,41 @@
|
|||||||
# PDFer
|
# TypeScript Next.js example
|
||||||
|
|
||||||
Write a letter in the browser, let the backend generate a PDF out of it.
|
This is a really simple project that shows the usage of Next.js with TypeScript.
|
||||||
|
|
||||||
|
## Deploy your own
|
||||||
|
|
||||||
|
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
|
||||||
|
|
||||||
|
[](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-typescript&project-name=with-typescript&repository-name=with-typescript)
|
||||||
|
|
||||||
|
## How to use it?
|
||||||
|
|
||||||
|
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx create-next-app --example with-typescript with-typescript-app
|
||||||
|
# or
|
||||||
|
yarn create next-app --example with-typescript with-typescript-app
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
This example shows how to integrate the TypeScript type system into Next.js. Since TypeScript is supported out of the box with Next.js, all we have to do is to install TypeScript.
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install --save-dev typescript
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable TypeScript's features, we install the type declarations for React and Node.
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install --save-dev @types/react @types/react-dom @types/node
|
||||||
|
```
|
||||||
|
|
||||||
|
When we run `next dev` the next time, Next.js will start looking for any `.ts` or `.tsx` files in our project and builds it. It even automatically creates a `tsconfig.json` file for our project with the recommended settings.
|
||||||
|
|
||||||
|
Next.js has built-in TypeScript declarations, so we'll get autocompletion for Next.js' modules straight away.
|
||||||
|
|
||||||
|
A `type-check` script is also added to `package.json`, which runs TypeScript's `tsc` CLI in `noEmit` mode to run type-checking separately. You can then include this, for example, in your `test` scripts.
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
; EditorConfig file: http://EditorConfig.org
|
|
||||||
; Install the "EditorConfig" plugin into Sublime Text to use
|
|
||||||
|
|
||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
16
client/.gitignore
vendored
16
client/.gitignore
vendored
@@ -1,16 +0,0 @@
|
|||||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
|
||||||
|
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
.env
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
FROM nginx:alpine
|
|
||||||
COPY ./build /usr/share/nginx/html
|
|
||||||
COPY ./api.conf /etc/nginx/conf.d/default.conf
|
|
||||||
1356
client/README.md
1356
client/README.md
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html index.htm;
|
|
||||||
}
|
|
||||||
|
|
||||||
# redirect server error pages to the static page /50x.html
|
|
||||||
#
|
|
||||||
error_page 500 502 503 504 /50x.html;
|
|
||||||
location = /50x.html {
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /api {
|
|
||||||
proxy_pass http://backend:5000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"main.css": "static/css/main.7e7cd5ed.css",
|
|
||||||
"main.css.map": "static/css/main.7e7cd5ed.css.map",
|
|
||||||
"main.js": "static/js/main.62dbb9b3.js",
|
|
||||||
"main.js.map": "static/js/main.62dbb9b3.js.map"
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
@@ -1 +0,0 @@
|
|||||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="shortcut icon" href="/favicon.ico"><title>PDFer</title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css"/><link href="/static/css/main.7e7cd5ed.css" rel="stylesheet"></head><body><div id="root"></div><script type="text/javascript" src="/static/js/main.62dbb9b3.js"></script></body></html>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
"use strict";var precacheConfig=[["/index.html","05aaedb2d0a2e6f7d81fddd5c1c6ccfc"],["/static/css/main.7e7cd5ed.css","debc022ef2b5b9c8f5e9810a30c6e2eb"],["/static/js/main.62dbb9b3.js","beb04348cbcf74a0db135f87eb89c64c"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var n=new URL(e);return"/"===n.pathname.slice(-1)&&(n.pathname+=t),n.toString()},cleanResponse=function(e){return e.redirected?("body"in e?Promise.resolve(e.body):e.blob()).then(function(t){return new Response(t,{headers:e.headers,status:e.status,statusText:e.statusText})}):Promise.resolve(e)},createCacheKey=function(e,t,n,r){var a=new URL(e);return r&&a.pathname.match(r)||(a.search+=(a.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(n)),a.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var n=new URL(t).pathname;return e.some(function(e){return n.match(e)})},stripIgnoredUrlParameters=function(e,t){var n=new URL(e);return n.hash="",n.search=n.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(e){return t.every(function(t){return!t.test(e[0])})}).map(function(e){return e.join("=")}).join("&"),n.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],n=e[1],r=new URL(t,self.location),a=createCacheKey(r,hashParamName,n,/\.\w{8}\./);return[r.toString(),a]}));function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(e){return setOfCachedUrls(e).then(function(t){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(n){if(!t.has(n)){var r=new Request(n,{credentials:"same-origin"});return fetch(r).then(function(t){if(!t.ok)throw new Error("Request for "+n+" returned a response with status "+t.status);return cleanResponse(t).then(function(t){return e.put(n,t)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var t=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(e){return e.keys().then(function(n){return Promise.all(n.map(function(n){if(!t.has(n.url))return e.delete(n)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(e){if("GET"===e.request.method){var t,n=stripIgnoredUrlParameters(e.request.url,ignoreUrlParametersMatching),r="index.html";(t=urlsToCacheKeys.has(n))||(n=addDirectoryIndex(n,r),t=urlsToCacheKeys.has(n));var a="/index.html";!t&&"navigate"===e.request.mode&&isPathWhitelisted(["^(?!\\/__).*"],e.request.url)&&(n=new URL(a,self.location).toString(),t=urlsToCacheKeys.has(n)),t&&e.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(n)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(t){return console.warn('Couldn\'t serve response for "%s" from cache: %O',e.request.url,t),fetch(e.request)}))}});
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
.letter-options{width:320px}.letter-options label{display:block;color:#333;font-size:14px;font-weight:700}.letter-options input,.letter-options select,.letter-options textarea{margin-top:3px;margin-bottom:12px;width:100%}.letter-options textarea{resize:vertical}.letter-options textarea.address{height:80px}.letter-options textarea.body{height:340px}.letter-options .Collapsible{background-color:#ddd;border-radius:2px}.letter-options .Collapsible__trigger{padding-left:6px;cursor:pointer;line-height:24px;font-size:14px;font-weight:700}.letter-options .Collapsible__trigger.is-closed:before{content:"> "}.letter-options .Collapsible__trigger.is-open:before{content:"< "}.letter-options .Collapsible{margin-bottom:12px}.letter-options .Collapsible__contentInner{background-color:#fff;padding-top:12px;padding-left:12px}header h1{margin-left:12px}.p-button{margin-top:12px;margin-right:12px;margin-bottom:12px}.home{display:-ms-flexbox;display:flex}.home>div{padding:12px}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,:after,:before{-webkit-box-sizing:inherit;box-sizing:inherit}
|
|
||||||
/*# sourceMappingURL=main.7e7cd5ed.css.map*/
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"sources":["LetterOptions.css","App.css","index.css"],"names":[],"mappings":"AAAA,gBACI,WAAa,CAGjB,sBACI,cACA,WACA,eACA,eAAkB,CAEtB,sEAGI,eACA,mBACA,UAAY,CAGhB,yBACI,eAAiB,CAGrB,iCACI,WAAa,CAGjB,8BACI,YAAc,CAGlB,6BACI,sBACA,iBAAmB,CAGvB,sCACI,iBACA,eACA,iBACA,eACA,eAAkB,CAGtB,uDACI,YAAc,CAGlB,qDACI,YAAc,CAGlB,6BACI,kBAAoB,CAGxB,2CACI,sBACA,iBACA,iBAAmB,CC1DvB,UACI,gBAAkB,CAGtB,UACI,gBACA,kBACA,kBAAoB,CAGxB,MACI,oBACA,YAAc,CAGlB,UACI,YAAc,CChBlB,KACE,8BACQ,qBAAuB,CAGjC,iBACE,2BACQ,kBAAoB","file":"static/css/main.7e7cd5ed.css","sourcesContent":[".letter-options {\n width: 320px;\n}\n\n.letter-options label {\n display: block;\n color: #333;\n font-size: 14px;\n font-weight: bold;\n}\n.letter-options input,\n.letter-options textarea,\n.letter-options select {\n margin-top: 3px;\n margin-bottom: 12px;\n width: 100%;\n}\n\n.letter-options textarea {\n resize: vertical;\n}\n\n.letter-options textarea.address {\n height: 80px;\n}\n\n.letter-options textarea.body {\n height: 340px;\n}\n\n.letter-options .Collapsible {\n background-color: #ddd;\n border-radius: 2px;\n}\n\n.letter-options .Collapsible__trigger {\n padding-left: 6px;\n cursor: pointer;\n line-height: 24px;\n font-size: 14px;\n font-weight: bold;\n}\n\n.letter-options .Collapsible__trigger.is-closed:before {\n content: \"> \";\n}\n\n.letter-options .Collapsible__trigger.is-open:before {\n content: \"< \";\n}\n\n.letter-options .Collapsible {\n margin-bottom: 12px;\n}\n\n.letter-options .Collapsible__contentInner {\n background-color: #fff;\n padding-top: 12px;\n padding-left: 12px;\n}\n\n\n\n\n// WEBPACK FOOTER //\n// ./src/LetterOptions.css","header h1 {\n margin-left: 12px;\n}\n\n.p-button {\n margin-top: 12px;\n margin-right: 12px;\n margin-bottom: 12px;\n}\n\n.home {\n display: -ms-flexbox;\n display: flex;\n}\n\n.home > div {\n padding: 12px;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/App.css","html {\n -webkit-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n*, *:before, *:after {\n -webkit-box-sizing: inherit;\n box-sizing: inherit;\n}\n\n\n\n// WEBPACK FOOTER //\n// ./src/index.css"],"sourceRoot":""}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
12460
client/package-lock.json
generated
12460
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "pdfer-client",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"devDependencies": {
|
|
||||||
"react-scripts": "1.1.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"react": "^15.6.2",
|
|
||||||
"react-collapsible": "^1.5.0",
|
|
||||||
"react-dom": "^15.6.2"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "react-scripts start",
|
|
||||||
"build": "react-scripts build",
|
|
||||||
"test": "react-scripts test --env=jsdom",
|
|
||||||
"eject": "react-scripts eject",
|
|
||||||
"version": "npm run build && git add build"
|
|
||||||
},
|
|
||||||
"proxy": "http://localhost:5000",
|
|
||||||
"homepage": "http://pi"
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
@@ -1,32 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tag above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
|
||||||
-->
|
|
||||||
<title>PDFer</title>
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<!--
|
|
||||||
This HTML file is a template.
|
|
||||||
If you open it directly in the browser, you will see an empty page.
|
|
||||||
|
|
||||||
You can add webfonts, meta tags, or analytics to this file.
|
|
||||||
The build step will place the bundled scripts into the <body> tag.
|
|
||||||
|
|
||||||
To begin the development, run `npm start`.
|
|
||||||
To create a production bundle, use `npm run build`.
|
|
||||||
-->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
|
||||||
<g fill="#61DAFB">
|
|
||||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
|
||||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
|
||||||
<path d="M520.5 78.1z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.6 KiB |
@@ -5,7 +5,7 @@ import Preview from './Preview';
|
|||||||
import LatestList from './LatestList';
|
import LatestList from './LatestList';
|
||||||
import {generatePdf, getLatest, removeLatest} from './apiHelper';
|
import {generatePdf, getLatest, removeLatest} from './apiHelper';
|
||||||
|
|
||||||
import './App.css';
|
//import './App.css';
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -5,7 +5,7 @@ import Input from './Input';
|
|||||||
import TextAreaInput from './TextAreaInput';
|
import TextAreaInput from './TextAreaInput';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
|
|
||||||
import './LetterOptions.css';
|
//import './LetterOptions.css';
|
||||||
|
|
||||||
const EXAMPLE_ADDRESS = 'Max Mustermann\nMusterstr. 73\n12345 Musterstadt';
|
const EXAMPLE_ADDRESS = 'Max Mustermann\nMusterstr. 73\n12345 Musterstadt';
|
||||||
|
|
||||||
19
components/List.tsx
Normal file
19
components/List.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
import ListItem from './ListItem'
|
||||||
|
import { User } from '../interfaces'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
items: User[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const List = ({ items }: Props) => (
|
||||||
|
<ul>
|
||||||
|
{items.map((item) => (
|
||||||
|
<li key={item.id}>
|
||||||
|
<ListItem data={item} />
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default List
|
||||||
16
components/ListDetail.tsx
Normal file
16
components/ListDetail.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import { User } from '../interfaces'
|
||||||
|
|
||||||
|
type ListDetailProps = {
|
||||||
|
item: User
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListDetail = ({ item: user }: ListDetailProps) => (
|
||||||
|
<div>
|
||||||
|
<h1>Detail for {user.name}</h1>
|
||||||
|
<p>ID: {user.id}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ListDetail
|
||||||
18
components/ListItem.tsx
Normal file
18
components/ListItem.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import { User } from '../interfaces'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
data: User
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListItem = ({ data }: Props) => (
|
||||||
|
<Link href="/users/[id]" as={`/users/${data.id}`}>
|
||||||
|
<a>
|
||||||
|
{data.id}: {data.name}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default ListItem
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
version: '2'
|
|
||||||
services:
|
|
||||||
backend:
|
|
||||||
build: ./server
|
|
||||||
ports:
|
|
||||||
- "5000"
|
|
||||||
web:
|
|
||||||
build: ./client
|
|
||||||
links:
|
|
||||||
- backend
|
|
||||||
ports:
|
|
||||||
- "9999:80"
|
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
const fs = require('fs');
|
import { mkdir, writeFile } from 'fs'
|
||||||
const spawn = require('child_process').spawn;
|
import { spawn } from 'child_process'
|
||||||
const uuid = require('uuid');
|
import { v1 as uuidv1 } from 'uuid'
|
||||||
|
|
||||||
const {getDirPath, getDocPath} = require('./utils');
|
import { getDirPath, getDocPath } from './utils';
|
||||||
|
|
||||||
|
|
||||||
function copyToTemp(id, texDocument) {
|
function copyToTemp(id: string, texDocument: string): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const dirPath = getDirPath(id);
|
const dirPath = getDirPath(id);
|
||||||
|
|
||||||
fs.mkdir(dirPath, (err) => {
|
mkdir(dirPath, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const docPath = getDocPath(id);
|
const docPath = getDocPath(id);
|
||||||
fs.writeFile(docPath, texDocument, (err) => {
|
writeFile(docPath, texDocument, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
@@ -26,9 +26,9 @@ function copyToTemp(id, texDocument) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateDoc(id) {
|
function generateDoc(id: string) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const pdflatex = spawn('pdflatex', ['-interaction', 'nonstopmode', getDocPath(id)], {cwd: getDirPath(id)});
|
const pdflatex = spawn('pdflatex', ['-interaction', 'nonstopmode', getDocPath(id)], { cwd: getDirPath(id) });
|
||||||
pdflatex.stderr.on('data', (data) => {
|
pdflatex.stderr.on('data', (data) => {
|
||||||
console.error('onData', data);
|
console.error('onData', data);
|
||||||
});
|
});
|
||||||
@@ -44,8 +44,8 @@ function generateDoc(id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = function(texDocument, callback) {
|
export default function(texDocument: string) {
|
||||||
const id = uuid.v1();
|
const id = uuidv1();
|
||||||
return copyToTemp(id, texDocument)
|
return copyToTemp(id, texDocument)
|
||||||
.then(generateDoc);
|
.then(generateDoc);
|
||||||
};
|
};
|
||||||
14
lib/store.ts
Normal file
14
lib/store.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const storeDir = process.env.JSON_STORE || '/tmp/pdfer-store/';
|
||||||
|
|
||||||
|
import { promisify } from 'util';
|
||||||
|
|
||||||
|
import JsonStore from 'json-fs-store'
|
||||||
|
|
||||||
|
const store = JsonStore(storeDir);
|
||||||
|
|
||||||
|
console.log(`using json-store at ${storeDir}`);
|
||||||
|
|
||||||
|
export const list = promisify(store.list);
|
||||||
|
export const load = promisify(store.load);
|
||||||
|
export const add = promisify(store.add);
|
||||||
|
export const remove = promisify(store.remove);
|
||||||
60
lib/templates.ts
Normal file
60
lib/templates.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
function convertLineBreaks(string) {
|
||||||
|
return string.replace(/\n/g, '\\\\');
|
||||||
|
};
|
||||||
|
|
||||||
|
export function brief(options) {
|
||||||
|
|
||||||
|
const {
|
||||||
|
template = 'brief-fam',
|
||||||
|
subject = '',
|
||||||
|
yourRef = '',
|
||||||
|
yourRefName = 'Ihr Zeichen',
|
||||||
|
yourMail = '',
|
||||||
|
myRef = '',
|
||||||
|
customer = '',
|
||||||
|
invoice = '',
|
||||||
|
date = '\\today',
|
||||||
|
signature = '',
|
||||||
|
specialMail = '',
|
||||||
|
address = 'Max Mustermann\\\\Musterstrasse\\\\12345 Musterstadt',
|
||||||
|
opening = 'Sehr geehrte Damen und Herren',
|
||||||
|
body = '',
|
||||||
|
closing = 'Mit freundlichen Grüßen',
|
||||||
|
ps = '',
|
||||||
|
enclosing = '',
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
return `% brief document
|
||||||
|
\\documentclass{scrlttr2}
|
||||||
|
\\LoadLetterOption{${template}}
|
||||||
|
|
||||||
|
\\setkomavar{subject}{${subject}}
|
||||||
|
|
||||||
|
\\setkomavar{yourref}[${yourRefName}]{${yourRef}}
|
||||||
|
\\setkomavar{yourmail}{${yourMail}}
|
||||||
|
\\setkomavar{myref}{${myRef}}
|
||||||
|
\\setkomavar{customer}{${customer}}
|
||||||
|
\\setkomavar{invoice}{${invoice}}
|
||||||
|
|
||||||
|
\\setkomavar{date}{${date}}
|
||||||
|
|
||||||
|
%\\setkomavar{signature}{${signature}}
|
||||||
|
|
||||||
|
\\setkomavar{specialmail}{${specialMail}}
|
||||||
|
|
||||||
|
\\begin{document}
|
||||||
|
\\begin{letter}{${convertLineBreaks(address)}}
|
||||||
|
|
||||||
|
\\opening{${opening},}
|
||||||
|
|
||||||
|
${convertLineBreaks(body)}
|
||||||
|
|
||||||
|
\\closing{${closing}}
|
||||||
|
|
||||||
|
\\ps{${ps}}
|
||||||
|
|
||||||
|
%\\encl{${enclosing}}
|
||||||
|
|
||||||
|
\\end{letter}
|
||||||
|
\\end{document}`;
|
||||||
|
};
|
||||||
13
lib/utils.ts
Normal file
13
lib/utils.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export function getDirPath(id) {
|
||||||
|
return `/tmp/pdfer-${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDocPath(id) {
|
||||||
|
return path.join(getDirPath(id), 'doc.tex');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPdfPath(id) {
|
||||||
|
return path.join(getDirPath(id), 'doc.pdf');
|
||||||
|
}
|
||||||
2
next-env.d.ts
vendored
Normal file
2
next-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/types/global" />
|
||||||
2605
package-lock.json
generated
Normal file
2605
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
package.json
Normal file
25
package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "pdfer",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"type-check": "tsc"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"json-fs-store": "^1.0.1",
|
||||||
|
"next": "latest",
|
||||||
|
"react": "^16.12.0",
|
||||||
|
"react-collapsible": "^2.8.3",
|
||||||
|
"react-dom": "^16.12.0",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^12.12.21",
|
||||||
|
"@types/react": "^16.9.16",
|
||||||
|
"@types/react-dom": "^16.9.4",
|
||||||
|
"typescript": "4.0"
|
||||||
|
},
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
24
pages/_app.tsx
Normal file
24
pages/_app.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// import App from "next/app";
|
||||||
|
import type { AppProps /*, AppContext */ } from 'next/app'
|
||||||
|
|
||||||
|
import '../styles/index.css'
|
||||||
|
import '../styles/App.css'
|
||||||
|
import '../styles/LetterOptions.css'
|
||||||
|
|
||||||
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
|
return <Component {...pageProps} />
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only uncomment this method if you have blocking data requirements for
|
||||||
|
// every single page in your application. This disables the ability to
|
||||||
|
// perform automatic static optimization, causing every page in your app to
|
||||||
|
// be server-side rendered.
|
||||||
|
//
|
||||||
|
// MyApp.getInitialProps = async (appContext: AppContext) => {
|
||||||
|
// // calls page's `getInitialProps` and fills `appProps.pageProps`
|
||||||
|
// const appProps = await App.getInitialProps(appContext);
|
||||||
|
|
||||||
|
// return { ...appProps }
|
||||||
|
// }
|
||||||
|
|
||||||
|
export default MyApp
|
||||||
45
pages/api/pdf/[id].ts
Normal file
45
pages/api/pdf/[id].ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import {promises} from 'fs'
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
import {remove as storeRemove} from '../../../lib/store'
|
||||||
|
import { getPdfPath } from '../../../lib/utils'
|
||||||
|
|
||||||
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
const { method } = req;
|
||||||
|
|
||||||
|
switch (req.method) {
|
||||||
|
case 'GET':
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
query: { id: idArg },
|
||||||
|
} = req
|
||||||
|
|
||||||
|
const fileContent = await promises.readFile(getPdfPath(idArg))
|
||||||
|
res.setHeader('Content-Type', 'application/pdf')
|
||||||
|
res.status(200).send(fileContent);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
res.status(404).json({ statusCode: 404, message: 'Method Not Allowed' })
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'DELETE':
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
query: { id: idArg },
|
||||||
|
} = req
|
||||||
|
|
||||||
|
storeRemove(idArg)
|
||||||
|
|
||||||
|
res.status(202).end()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
res.status(404).json({ statusCode: 404, message: 'Method Not Allowed' })
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
res.setHeader('Allow', ['GET', 'DELETE'])
|
||||||
|
res.status(405).end(`Method ${method} Not Allowed`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handler;
|
||||||
45
pages/api/pdf/generate/[template].ts
Normal file
45
pages/api/pdf/generate/[template].ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
import { brief as briefTemplate } from '../../../../lib/templates'
|
||||||
|
import renderer from '../../../../lib/renderer'
|
||||||
|
import * as store from '../../../../lib/store'
|
||||||
|
|
||||||
|
const TEMPLATES : { [key: string]: (options: object) => string; } = {
|
||||||
|
brief: briefTemplate
|
||||||
|
};
|
||||||
|
|
||||||
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
res.status(405).json({ statusCode: 405, message: 'Method Not Allowed' })
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
query: { template: templateArg },
|
||||||
|
} = req
|
||||||
|
const options = req.body;
|
||||||
|
const templateName = Array.isArray(templateArg) ? templateArg[0] : templateArg;
|
||||||
|
const template = TEMPLATES[templateName];
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
res.status(404).json({ statusCode: 404, message: 'Template not availabe' })
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const texDoc = template(options)
|
||||||
|
const id = await renderer(texDoc)
|
||||||
|
const storeData = Object.assign({}, options, {
|
||||||
|
id,
|
||||||
|
created: new Date().toISOString()
|
||||||
|
});
|
||||||
|
await store.add(storeData)
|
||||||
|
res.status(200).json({ id: id });
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error:', err, 'for', req.url);
|
||||||
|
res.status(500).json({ error: err.toString() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handler
|
||||||
21
pages/api/pdf/latest.ts
Normal file
21
pages/api/pdf/latest.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
import { list as storeList } from '../../../lib/store'
|
||||||
|
|
||||||
|
|
||||||
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
if (req.method !== 'GET') {
|
||||||
|
res.status(405).json({ statusCode: 405, message: 'Method Not Allowed' })
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const latest = await storeList();
|
||||||
|
res.status(200).json(latest);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error:', err, 'for', req.url);
|
||||||
|
res.status(500).json({ error: err.toString() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handler
|
||||||
10
pages/index.tsx
Normal file
10
pages/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import Layout from '../components/Layout'
|
||||||
|
import App from '../components/App'
|
||||||
|
|
||||||
|
const IndexPage = () => (
|
||||||
|
<Layout title="Home | Next.js + TypeScript Example">
|
||||||
|
<App/>
|
||||||
|
</Layout>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default IndexPage
|
||||||
3
server/.gitignore
vendored
3
server/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
node_modules
|
|
||||||
.tern-project
|
|
||||||
npm-debug.log
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
FROM archlinux
|
|
||||||
MAINTAINER Thomas Ruoff <thomasruoff@gmail.com>
|
|
||||||
|
|
||||||
# basics
|
|
||||||
RUN pacman -Syu --noconfirm sed grep awk
|
|
||||||
|
|
||||||
# texlive
|
|
||||||
RUN pacman -S --noconfirm texlive-core texlive-latexextra
|
|
||||||
|
|
||||||
# TODO: move up later
|
|
||||||
RUN pacman -S --noconfirm tar gzip
|
|
||||||
|
|
||||||
# node
|
|
||||||
RUN pacman -S --noconfirm nodejs npm
|
|
||||||
|
|
||||||
# private tex things
|
|
||||||
ADD ./texmf/tex/latex/* /usr/share/texmf/tex/latex/
|
|
||||||
|
|
||||||
# fix font map file issue
|
|
||||||
RUN updmap
|
|
||||||
|
|
||||||
ADD . /code
|
|
||||||
WORKDIR /code
|
|
||||||
|
|
||||||
RUN npm install --production
|
|
||||||
|
|
||||||
CMD node index.js
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const bodyParser = require('body-parser');
|
|
||||||
const app = express();
|
|
||||||
const templates = require('./templates');
|
|
||||||
const renderer = require('./renderer');
|
|
||||||
const store = require('./store');
|
|
||||||
const { getPdfPath } = require('./utils');
|
|
||||||
|
|
||||||
app.use(bodyParser.json());
|
|
||||||
|
|
||||||
app.options('/api/pdf/generate/:template');
|
|
||||||
app.post('/api/pdf/generate/:template', (req, res) => {
|
|
||||||
const templateName = req.params.template;
|
|
||||||
const options = req.body;
|
|
||||||
|
|
||||||
templates.get(templateName, options)
|
|
||||||
.then(renderer)
|
|
||||||
.then(id => {
|
|
||||||
const storeData = Object.assign({}, options, {
|
|
||||||
id,
|
|
||||||
created: new Date().toISOString()
|
|
||||||
});
|
|
||||||
return store
|
|
||||||
.add(storeData)
|
|
||||||
.then(() => id);
|
|
||||||
})
|
|
||||||
.then(id => {
|
|
||||||
res
|
|
||||||
.status(200)
|
|
||||||
.json({id: id});
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Error:', err, 'for', req.url);
|
|
||||||
res
|
|
||||||
.status(500)
|
|
||||||
.json({error: err.toString()});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/api/pdf/latest', (req, res) => {
|
|
||||||
store
|
|
||||||
.list()
|
|
||||||
.then(results => res
|
|
||||||
.status(200)
|
|
||||||
.json(results)
|
|
||||||
)
|
|
||||||
.catch(err => res
|
|
||||||
.status(500)
|
|
||||||
.json({error: err.toString()})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.delete('/api/pdf/latest/:id', (req, res) => {
|
|
||||||
const { id } = req.params;
|
|
||||||
console.log(`Deleting ${id}...`);
|
|
||||||
store
|
|
||||||
.remove(id)
|
|
||||||
.then(() => res.sendStatus(202))
|
|
||||||
.catch(err => res
|
|
||||||
.status(500)
|
|
||||||
.json({error: err.toString()})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
app.options('/api/pdf/:id');
|
|
||||||
app.get('/api/pdf/:id', (req, res) => {
|
|
||||||
const {id} = req.params;
|
|
||||||
res
|
|
||||||
.status(200)
|
|
||||||
.sendFile(getPdfPath(id));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(express.static('../client/build'));
|
|
||||||
|
|
||||||
app.listen(5000);
|
|
||||||
4306
server/package-lock.json
generated
4306
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "pdfer",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Generate pdfs based on a template",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
|
||||||
"start": "node index.js",
|
|
||||||
"watch": "nodemon index.js"
|
|
||||||
},
|
|
||||||
"author": "Thomas Ruoff <tomru@mail.id0.link>",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"body-parser": "^1.16.1",
|
|
||||||
"express": "^4.14.1",
|
|
||||||
"glob": "^7.1.2",
|
|
||||||
"json-fs-store": "^1.0.1",
|
|
||||||
"lodash.sortby": "^4.7.0",
|
|
||||||
"uuid": "^3.0.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"eslint": "^4.16.0",
|
|
||||||
"nodemon": "^1.14.12"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
const storeDir = process.env.JSON_STORE || '/tmp/pdfer-store/';
|
|
||||||
console.log(`using json-store at ${storeDir}`);
|
|
||||||
|
|
||||||
const { promisify } = require('util');
|
|
||||||
|
|
||||||
const store = require('json-fs-store')(storeDir);
|
|
||||||
const sortBy = require('lodash.sortby');
|
|
||||||
|
|
||||||
const list = promisify(store.list);
|
|
||||||
const load = promisify(store.load);
|
|
||||||
const add = promisify(store.add);
|
|
||||||
const remove = promisify(store.remove);
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
list: () => list()
|
|
||||||
.then(result => sortBy(result, 'created').reverse()),
|
|
||||||
load: id => load(id),
|
|
||||||
add: item => add(item),
|
|
||||||
remove: id => remove(id),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
|
|
||||||
module.exports.get = function(templateName, options) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const modulePath = path.join(__dirname, 'templates', templateName);
|
|
||||||
try {
|
|
||||||
const template = require(modulePath);
|
|
||||||
resolve(template(options));
|
|
||||||
} catch (e) {
|
|
||||||
reject(`${templateName} not found!`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Submodule server/texmf deleted from f5a8fe0ed0
@@ -1,19 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
|
|
||||||
function getDirPath(id) {
|
|
||||||
return `/tmp/pdfer-${id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDocPath(id) {
|
|
||||||
return path.join(getDirPath(id), 'doc.tex');
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPdfPath(id) {
|
|
||||||
return path.join(getDirPath(id), 'doc.pdf');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getDirPath,
|
|
||||||
getDocPath,
|
|
||||||
getPdfPath,
|
|
||||||
};
|
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
function convertLineBreaks(string){
|
function convertLineBreaks(string) {
|
||||||
return string.replace(/\n/g, '\\\\');
|
return string.replace(/\n/g, '\\\\');
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = (options) => {
|
export default (options) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
template = 'brief-fam',
|
template = 'brief-fam',
|
||||||
subject = '',
|
subject = '',
|
||||||
yourRef = '',
|
yourRef = '',
|
||||||
yourRefName = 'Ihr Zeichen',
|
yourRefName = 'Ihr Zeichen',
|
||||||
yourMail = '',
|
yourMail = '',
|
||||||
myRef = '',
|
myRef = '',
|
||||||
customer = '',
|
customer = '',
|
||||||
invoice = '',
|
invoice = '',
|
||||||
date = '\\today',
|
date = '\\today',
|
||||||
signature = '',
|
signature = '',
|
||||||
specialMail = '',
|
specialMail = '',
|
||||||
address = 'Max Mustermann\\\\Musterstrasse\\\\12345 Musterstadt',
|
address = 'Max Mustermann\\\\Musterstrasse\\\\12345 Musterstadt',
|
||||||
opening = 'Sehr geehrte Damen und Herren',
|
opening = 'Sehr geehrte Damen und Herren',
|
||||||
body = '',
|
body = '',
|
||||||
closing = 'Mit freundlichen Grüßen',
|
closing = 'Mit freundlichen Grüßen',
|
||||||
ps = '',
|
ps = '',
|
||||||
enclosing = '',
|
enclosing = '',
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
return `% brief document
|
return `% brief document
|
||||||
\\documentclass{scrlttr2}
|
\\documentclass{scrlttr2}
|
||||||
23
tsconfig.json
Normal file
23
tsconfig.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"lib": ["dom", "es2017"],
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noEmit": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"target": "esnext"
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules"],
|
||||||
|
"include": ["**/*.ts", "**/*.tsx"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user