Add .env support for configurable Name and Slogan in preview cards
NAME and SLOGAN are read from .env via dotenv and injected into the LinkedIn preview card template at startup. Avatar initials are auto- generated from the first letters of NAME. Works identically with npm start and docker compose (via env_file). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2
.env.example
Normal file
2
.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
NAME=Max Mustermann
|
||||
SLOGAN=Senior Developer · Tech Enthusiast
|
||||
47
CLAUDE.md
Normal file
47
CLAUDE.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
A self-hosted LinkedIn Post Formatter that converts text with formatting markers (`**bold**`, `_italic_`) into Unicode Mathematical Alphanumeric Symbols. LinkedIn only accepts plain Unicode text (no HTML/Markdown), so the tool converts text into Unicode variants that *appear* formatted in the LinkedIn feed.
|
||||
|
||||
## Running the Project
|
||||
|
||||
```bash
|
||||
npm start # Start server on port 3000
|
||||
PORT=8080 node server.js # Custom port
|
||||
docker compose up -d # Docker deployment
|
||||
```
|
||||
|
||||
There are no build, test, or lint steps.
|
||||
|
||||
## Architecture
|
||||
|
||||
The project is intentionally minimal:
|
||||
|
||||
- **`server.js`** — 16-line Express server that only serves static files from `public/` and supports a `PORT` env var.
|
||||
- **`public/index.html`** — The entire application: HTML, CSS, and JavaScript all inlined in one file (~289 lines). No frameworks, no bundler, no external dependencies.
|
||||
|
||||
All formatting logic lives in `public/index.html` as vanilla JavaScript:
|
||||
|
||||
- `applyInline(text, variant)` — Processes `**text**` (bold) and `_text_` (italic) markers within a variant pass
|
||||
- `mapAll(text, map)` — Applies Unicode character mapping char-by-char
|
||||
- `liParagraph(text)` — Inserts zero-width spaces (`\u200b`) between double newlines to preserve paragraph breaks when pasting into LinkedIn (which strips trailing blank lines)
|
||||
- Six output variants: Sans (standard), Bold Sans, Italic Sans, Bold Italic Sans, Monospace, Plain
|
||||
|
||||
The Unicode character maps are JavaScript objects mapping A–Z, a–z, 0–9 to their Unicode Mathematical Alphanumeric equivalents (e.g., bold sans, italic sans, monospace).
|
||||
|
||||
## Key Implementation Details
|
||||
|
||||
**Zero-width space trick:** LinkedIn strips trailing blank lines on paste. The app inserts `\u200b` between `\n\n` sequences to preserve paragraph structure.
|
||||
|
||||
**Character limit:** LinkedIn max is 3000 characters. The counter uses color-coded feedback (green → amber → red) with `toLocaleString('de-DE')` formatting.
|
||||
|
||||
**Clipboard fallback:** Uses `navigator.clipboard.writeText()` with a `document.execCommand('copy')` fallback for older browsers.
|
||||
|
||||
**Keyboard shortcuts:** Ctrl/Cmd+B (bold), Ctrl/Cmd+I (italic), Ctrl/Cmd+Enter (generate preview).
|
||||
|
||||
## Language
|
||||
|
||||
The UI and all user-facing text is in **German**. Keep new UI additions in German.
|
||||
@@ -5,3 +5,4 @@ services:
|
||||
ports:
|
||||
- "3000:3000"
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@@ -8,6 +8,7 @@
|
||||
"name": "linkedin-formatter",
|
||||
"version": "1.1.0",
|
||||
"dependencies": {
|
||||
"dotenv": "^17.4.1",
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
},
|
||||
@@ -156,6 +157,18 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz",
|
||||
"integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^17.4.1",
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,8 +189,8 @@ function generate(){
|
||||
<div class="variant-label">${v.label}</div>
|
||||
<div class="li-post">
|
||||
<div class="li-post-header">
|
||||
<div class="li-avatar">JH</div>
|
||||
<div><div class="li-name">Joachim Hummel</div><div class="li-sub">Senior IT Consultant · Automatisierung</div></div>
|
||||
<div class="li-avatar">{{INITIALS}}</div>
|
||||
<div><div class="li-name">{{NAME}}</div><div class="li-sub">{{SLOGAN}}</div></div>
|
||||
</div>
|
||||
<div class="li-text" id="t${i}">${esc(v.text)}</div>
|
||||
<div class="li-more" id="m${i}" onclick="exp(${i})" style="display:none">… mehr anzeigen</div>
|
||||
|
||||
14
server.js
14
server.js
@@ -1,14 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
const NAME = process.env.NAME || 'Name';
|
||||
const SLOGAN = process.env.SLOGAN || '';
|
||||
const INITIALS = NAME.split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase();
|
||||
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
let html = fs.readFileSync(path.join(__dirname, 'public', 'index.html'), 'utf8');
|
||||
html = html
|
||||
.replace('{{INITIALS}}', INITIALS)
|
||||
.replace('{{NAME}}', NAME)
|
||||
.replace('{{SLOGAN}}', SLOGAN);
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
res.send(html);
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
|
||||
Reference in New Issue
Block a user