start over with nextjs

This commit is contained in:
Thomas Ruoff
2021-02-25 00:31:28 +01:00
parent 3a77e7f22b
commit 214d95d3d7
68 changed files with 3047 additions and 18576 deletions

View File

@@ -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

View File

@@ -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
View 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
View File

@@ -1,3 +0,0 @@
[submodule "server/texmf"]
path = server/texmf
url = git@github.com:tomru/tex-local-packages.git

1
.nvmrc
View File

@@ -1 +0,0 @@
6.10.0

View File

@@ -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):
[![Deploy with Vercel](https://vercel.com/button)](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.

3
RUN.md
View File

@@ -1,3 +0,0 @@
# How I am currently runnining this
sytemd runs node server

View File

@@ -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
View File

@@ -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*

View File

@@ -1,3 +0,0 @@
FROM nginx:alpine
COPY ./build /usr/share/nginx/html
COPY ./api.conf /etc/nginx/conf.d/default.conf

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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)}))}});

View File

@@ -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*/

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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() {

View File

@@ -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
View 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
View 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
View 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

View File

@@ -1,12 +0,0 @@
version: '2'
services:
backend:
build: ./server
ports:
- "5000"
web:
build: ./client
links:
- backend
ports:
- "9999:80"

View File

@@ -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
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />

2605
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
package.json Normal file
View 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
View 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
View 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;

View 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
View 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
View 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
View File

@@ -1,3 +0,0 @@
node_modules
.tern-project
npm-debug.log

View File

@@ -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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
}

View File

@@ -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),
}

View File

@@ -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

View File

@@ -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,
};

View File

@@ -1,8 +1,8 @@
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',

23
tsconfig.json Normal file
View 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"]
}