HTML/JS Buch-Konfigurator generiert kein DOCX Referenzfehler "docx is not defined"

MikeWelsOOE

Cadet 1st Year
Registriert
Dez. 2024
Beiträge
10
Hallo liebe ComputerBase-Community,

ich arbeite an einem kleinen, clientseitigen Web-Tool und bräuchte eure Unterstützung. Mein Ziel ist es, einen einfachen Buch-Konfigurator zu erstellen, der basierend auf Nutzereingaben (Titel, Autor, Kapitelanzahl, Wörter pro Kapitel) ein DOCX-Dokument generiert und zum Download anbietet.

Ich habe den HTML-Code (inkl. CSS und JavaScript) von einem AI-Modell erstellen lassen, das auf die docx.js- und FileSaver.js-Bibliotheken setzt. Die Idee war, das Ganze komplett im Browser, also ohne Backend und API-Keys, zu realisieren.

Leider stoße ich auf ein hartnäckiges Problem: Beim Klick auf den "Generieren"-Button passiert nichts, und in der Browser-Konsole erhalte ich durchgehend die Fehlermeldung: Uncaught ReferenceError: docx is not defined. Dies deutet darauf hin, dass die docx-Bibliothek nicht korrekt geladen oder zur Verfügung steht, wenn mein Skript versucht, darauf zuzugreifen.

Ich habe bereits versucht:

Trotz dieser Schritte bleibt der Fehler bestehen. Da dies ein "kleines Projekt" sein sollte, wundert es mich, dass ich es mit der AI nicht zum Laufen bekomme. Ich hänge den vollständigen HTML-Code (mit integriertem JS und CSS) an diesen Post an.

Könnte sich jemand von euch den Code bitte ansehen und mir einen Tipp geben, was die Ursache für diesen ReferenceError sein könnte und wie ich das Tool zum Laufen bringe? Für jede Hilfe wäre ich sehr dankbar!

Hier der HTML Code :


<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Buch Konfigurator & DOCX Export</title>

<style>
body {
background-color: #111;
color: #FFA500;
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}

.container {
background-color: #222;
padding: 30px;
border-radius: 10px;
box-shadow: 0 0 15px rgba(255, 165, 0, 0.5);
width: 90%;
max-width: 700px; /* Etwas breiter für mehr Inhalt */
text-align: center;
}

h1 {
color: #FFA500;
text-shadow: 2px 2px 4px #000;
}

label {
display: block;
margin-top: 20px;
margin-bottom: 8px;
font-weight: bold;
color: #FFD700;
text-align: left;
}

input[type="number"],
input[type="text"] {
width: calc(100% - 22px);
padding: 10px;
margin-bottom: 20px;
border: 1px solid #FFA500;
border-radius: 5px;
background-color: #333;
color: #FFA500;
font-size: 1em;
}

input[type="number"]:focus,
input[type="text"]:focus {
outline: none;
border-color: #FFD700;
box-shadow: 0 0 8px rgba(255, 165, 0, 0.7);
}

button {
background-color: #FFA500;
color: #111;
border: none;
padding: 12px 25px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 1.1em;
font-weight: bold;
margin-top: 30px;
cursor: pointer;
border-radius: 5px;
transition: background-color 0.3s ease, transform 0.1s ease;
}

button:hover {
background-color: #FFC14D;
}

button:active {
transform: scale(0.98);
}

.footer-note {
margin-top: 40px;
font-size: 0.9em;
color: #ccc;
}
#status {
margin-top: 20px;
font-size: 0.9em;
color: #FFD700;
min-height: 20px;
}
</style>
</head>
<body>

<div class="container">
<h1>📖 Buch Konfigurator & DOCX Export</h1>

<div>
<label for="buchTitel">Titel des Buches:</label>
<input type="text" id="buchTitel" name="buchTitel" placeholder="z.B. Die Chroniken von Eldoria">
</div>
<div>
<label for="autorName">Autor:</label>
<input type="text" id="autorName" name="autorName" placeholder="Ihr Name oder Pseudonym">
</div>

<div>
<label for="kapitelAnzahl">Wieviele Kapitel möchtest du (max. 120 für Demo)?</label>
<input type="number" id="kapitelAnzahl" name="kapitelAnzahl" min="1" max="120" placeholder="z.B. 10">
</div>

<div>
<label for="woerterProKapitel">Ungefähre Wörter pro Kapitel (für Platzhaltertext):</label>
<input type="number" id="woerterProKapitel" name="woerterProKapitel" min="50" max="2000" placeholder="z.B. 500">
</div>

<button type="button" id="generierenButton">Buch als DOCX generieren</button>
<div id="status"></div>

<div class="footer-note">
<p>Webseiten-Code generiert mit einer KOSTENLOSEN AI. Ohne API KEY. Deutsche Sprache. In Echtzeit.
<br>Die Buchinhaltsgenerierung verwendet Platzhaltertext.
</p>
</div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script src="https://unpkg.com/docx@8.5.0/build/index.js"></script>

<script>
// Zugriff auf die docx-Bibliothek (wird global durch das Skript-Tag oben verfügbar)
const { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType, PageBreak } = docx;

const buchTitelInput = document.getElementById('buchTitel');
const autorNameInput = document.getElementById('autorName');
const kapitelAnzahlInput = document.getElementById('kapitelAnzahl');
const woerterProKapitelInput = document.getElementById('woerterProKapitel');
const generierenButton = document.getElementById('generierenButton');
const statusDiv = document.getElementById('status');

generierenButton.addEventListener('click', async function() {
const titel = buchTitelInput.value.trim();
const autor = autorNameInput.value.trim() || "Unbekannter Autor";
const anzahlKapitel = parseInt(kapitelAnzahlInput.value);
const woerterProKapitel = parseInt(woerterProKapitelInput.value);

if (!titel) {
alert("Bitte geben Sie einen Titel für das Buch ein.");
return;
}
if (isNaN(anzahlKapitel) || anzahlKapitel < 1 || anzahlKapitel > 120) { // Max 120 für Demo
alert("Bitte geben Sie eine gültige Anzahl an Kapiteln ein (1-120 für diese Demo).");
return;
}
if (isNaN(woerterProKapitel) || woerterProKapitel < 50 || woerterProKapitel > 2000) { // Max 2000 für Demo
alert("Bitte geben Sie eine gültige Anzahl an Wörtern pro Kapitel ein (50-2000 für diese Demo).");
return;
}

statusDiv.textContent = "Generiere DOCX, bitte warten...";
generierenButton.disabled = true;

try {
// Generiere genug Lorem Ipsum für alle Kapitel
const loremBase = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ";
const loremIpsum = loremBase.repeat(Math.ceil(woerterProKapitel / (loremBase.split(" ").length / 5)) + 5); // Sicherstellen, dass genug Text da ist

const sections = [];

// 1. Titelseite
sections.push({
properties: {
page: {
size: { width: docx.TabStopPosition.MAX, height: docx.TabStopPosition.MAX }, // A4 default
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 inch = 1440 TWIPs
}
},
children: [
new Paragraph({ text: "" }), // Leerraum oben
new Paragraph({ text: "" }),
new Paragraph({ text: "" }),
new Paragraph({
children: [new TextRun({ text: titel, bold: true, size: 48 })], // Titelgröße: 24pt * 2 = 48 half-points
alignment: AlignmentType.CENTER,
spacing: { after: 400 }
}),
new Paragraph({ text: "" }),
new Paragraph({
children: [new TextRun({ text: von, size: 28 })],
alignment: AlignmentType.CENTER,
}),
new Paragraph({
children: [new TextRun({ text: autor, size: 36 })],
alignment: AlignmentType.CENTER,
spacing: { after: 2000 } // Mehr Platz nach dem Autor
}),
]
});

// Seite für Impressum (Platzhalter) - Startet auf neuer Seite
sections[0].children.push(new Paragraph({ children: [new PageBreak()] }));
sections[0].children.push(
new Paragraph({
children: [new TextRun({ text: "Impressum", bold: true, size: 28 })],
heading: HeadingLevel.HEADING_2,
spacing: { after: 200 }
}),
new Paragraph({ text: © ${new Date().getFullYear()} ${autor}, size: 24 }),
new Paragraph({ text: "Alle Rechte vorbehalten.", size: 24 }),
new Paragraph({ text: "Verlag: Selbstverlag (Beispiel)", size: 24 }),
new Paragraph({ text: "Druck: epubli GmbH, Berlin (Beispiel)", size: 24 }),
new Paragraph({ text: "ISBN: (Hier Ihre ISBN eintragen, falls vorhanden)", size: 24 })
);


// 2. Kapitel
for (let i = 1; i <= anzahlKapitel; i++) {
const kapitelInhalt = [];
// Jedes Kapitel beginnt auf einer neuen Seite (außer das erste nach dem Impressum)
// Da die Titelseite und das Impressum bereits in sections[0] sind, fügen wir PageBreaks zu den Kapiteln hinzu.
// Das erste Kapitel nach dem Impressum braucht einen PageBreak, die folgenden Kapitel dann auch.
kapitelInhalt.push(new Paragraph({ children: [new PageBreak()] }));

kapitelInhalt.push(new Paragraph({
children: [new TextRun({ text: Kapitel ${i}, bold: true, size: 32 })], // 16pt * 2
heading: HeadingLevel.HEADING_1,
alignment: AlignmentType.CENTER,
spacing: { before: 400, after: 300 }
}));

// Füge mehrere Absätze mit Platzhaltertext hinzu
// Versuche, die Wörter gleichmäßig auf mehrere Absätze zu verteilen.
const wordsPerParagraphTarget = woerterProKapitel / 3; // Ziel sind 3 Absätze pro Kapitel, passe dies nach Bedarf an
let currentWordCount = 0;
let paragraphCount = 0;

while (currentWordCount < woerterProKapitel && paragraphCount < 20) { // Max 20 Absätze zur Sicherheit
let wordsInThisParagraph = Math.min(wordsPerParagraphTarget, woerterProKapitel - currentWordCount);

// Finde das Ende eines Satzes in loremIpsum, um den Text sauber abzuschneiden
let endIndex = 0;
let tempText = loremIpsum;
let words = tempText.split(/\s+/); // Split by any whitespace

let actualWords = 0;
for (let j = 0; j < words.length; j++) {
actualWords++;
if (actualWords >= wordsInThisParagraph && words[j].endsWith(".")) {
endIndex = tempText.indexOf(words[j]) + words[j].length;
break;
}
endIndex = tempText.indexOf(words[j]) + words[j].length;
}
if (endIndex === 0) endIndex = loremIpsum.length; // Fallback falls kein Punkt gefunden

let paragraphText = loremIpsum.substring(0, endIndex);

kapitelInhalt.push(new Paragraph({
children: [new TextRun({ text: paragraphText, size: 24 })], // 12pt * 2
spacing: { after: 120, line: 360 }, // 1.5 line spacing (360 TWIPs)
indent: { firstLine: 720 } // Erster Zeileneinzug: 0.5 inch = 720 TWIPs
}));

currentWordCount += paragraphText.split(" ").length;
paragraphCount++;
// Schneide den verwendeten Text vom loremIpsum ab, um für den nächsten Absatz zu verwenden
loremIpsum = loremIpsum.substring(endIndex).trim();
if (loremIpsum.length < 50) loremIpsum += loremBase.repeat(5); // Fülle auf, falls leer
}

// Die Kapitel-Inhalte werden zur ersten Sektion hinzugefügt, da DOCX-Abschnitte für große Layout-Änderungen sind,
// und die Kapitel innerhalb einer einzigen Fließtext-Sektion mit Seitenumbrüchen erstellt werden.
sections[0].children.push(...kapitelInhalt);
}

const doc = new Document({
creator: autor,
title: titel,
description: "Generiert mit dem Buch Konfigurator",
styles: {
default: {
document: {
run: {
font: "Arial", // Standard Schriftart für epubli oft Arial oder Times New Roman
size: 24, // 12pt (docx.js verwendet half-points)
},
paragraph: {
spacing: { line: 360 } // Entspricht ca. 1.5 Zeilenabstand
}
},
heading1: {
run: { font: "Arial", size: 32, bold: true }, // 16pt
paragraph: { spacing: { before: 240, after: 120 } }
},
heading2: {
run: { font: "Arial", size: 28, bold: true }, // 14pt
paragraph: { spacing: { before: 200, after: 100 } }
}
}
},
sections: sections // Das gesamte Dokument besteht aus diesen Sektionen
});

Packer.toBlob(doc).then(blob => {
// Sanitize filename
const safeTitel = titel.replace(/[^a-z0-9_ ]/gi, '').replace(/ /g, '');
saveAs(blob, ${safeTitel}.docx); // FileSaver.js Funktion
statusDiv.textContent = "DOCX-Datei erfolgreich generiert und Download gestartet!";
}).catch(err => {
console.error(err);
statusDiv.textContent = "Fehler beim Erstellen der DOCX-Datei.";
alert("Fehler beim Erstellen der DOCX-Datei: " + err.message);
});

} catch (e) {
console.error(e);
statusDiv.textContent = "Ein unerwarteter Fehler ist aufgetreten.";
alert("Ein Fehler ist aufgetreten: " + e.message);
} finally {
generierenButton.disabled = false;
}
});
</script>

</body>
</html>

Vielen Dank im Voraus!
 

Anhänge

  • 2025-05-29 14_21_13-Buch Konfigurator & DOCX Export.png
    2025-05-29 14_21_13-Buch Konfigurator & DOCX Export.png
    37,3 KB · Aufrufe: 45
Erstmal würde ich den ganzen HTML-Code in einen passende Code-Block packen. Ist für Helfer deutlich einfach zu lesen, da es dann passend fomratiert ist auch Syntax Highlighting hat. Quellcode als 300 Zeilen Fließtext ohne Einrückungen kann Niemand wirklich überblicken oder nachvollziehen.

HTML:
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Buch Konfigurator & DOCX Export</title>

<style>
body {
background-color: #111;
color: #FFA500;
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}

.container {
background-color: #222;
padding: 30px;
border-radius: 10px;
box-shadow: 0 0 15px rgba(255, 165, 0, 0.5);
width: 90%;
max-width: 700px; /* Etwas breiter für mehr Inhalt */
text-align: center;
}

h1 {
color: #FFA500;
text-shadow: 2px 2px 4px #000;
}

label {
display: block;
margin-top: 20px;
margin-bottom: 8px;
font-weight: bold;
color: #FFD700;
text-align: left;
}

input[type="number"],
input[type="text"] {
width: calc(100% - 22px);
padding: 10px;
margin-bottom: 20px;
border: 1px solid #FFA500;
border-radius: 5px;
background-color: #333;
color: #FFA500;
font-size: 1em;
}

input[type="number"]:focus,
input[type="text"]:focus {
outline: none;
border-color: #FFD700;
box-shadow: 0 0 8px rgba(255, 165, 0, 0.7);
}

button {
background-color: #FFA500;
color: #111;
border: none;
padding: 12px 25px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 1.1em;
font-weight: bold;
margin-top: 30px;
cursor: pointer;
border-radius: 5px;
transition: background-color 0.3s ease, transform 0.1s ease;
}

button:hover {
background-color: #FFC14D;
}

button:active {
transform: scale(0.98);
}

.footer-note {
margin-top: 40px;
font-size: 0.9em;
color: #ccc;
}
#status {
margin-top: 20px;
font-size: 0.9em;
color: #FFD700;
min-height: 20px;
}
</style>
</head>
<body>

<div class="container">
<h1>📖 Buch Konfigurator & DOCX Export</h1>

<div>
<label for="buchTitel">Titel des Buches:</label>
<input type="text" id="buchTitel" name="buchTitel" placeholder="z.B. Die Chroniken von Eldoria">
</div>
<div>
<label for="autorName">Autor:</label>
<input type="text" id="autorName" name="autorName" placeholder="Ihr Name oder Pseudonym">
</div>

<div>
<label for="kapitelAnzahl">Wieviele Kapitel möchtest du (max. 120 für Demo)?</label>
<input type="number" id="kapitelAnzahl" name="kapitelAnzahl" min="1" max="120" placeholder="z.B. 10">
</div>

<div>
<label for="woerterProKapitel">Ungefähre Wörter pro Kapitel (für Platzhaltertext):</label>
<input type="number" id="woerterProKapitel" name="woerterProKapitel" min="50" max="2000" placeholder="z.B. 500">
</div>

<button type="button" id="generierenButton">Buch als DOCX generieren</button>
<div id="status"></div>

<div class="footer-note">
<p>Webseiten-Code generiert mit einer KOSTENLOSEN AI. Ohne API KEY. Deutsche Sprache. In Echtzeit.
<br>Die Buchinhaltsgenerierung verwendet Platzhaltertext.
</p>
</div>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script src="https://unpkg.com/docx@8.5.0/build/index.js"></script>

<script>
// Zugriff auf die docx-Bibliothek (wird global durch das Skript-Tag oben verfügbar)
const { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType, PageBreak } = docx;

const buchTitelInput = document.getElementById('buchTitel');
const autorNameInput = document.getElementById('autorName');
const kapitelAnzahlInput = document.getElementById('kapitelAnzahl');
const woerterProKapitelInput = document.getElementById('woerterProKapitel');
const generierenButton = document.getElementById('generierenButton');
const statusDiv = document.getElementById('status');

generierenButton.addEventListener('click', async function() {
const titel = buchTitelInput.value.trim();
const autor = autorNameInput.value.trim() || "Unbekannter Autor";
const anzahlKapitel = parseInt(kapitelAnzahlInput.value);
const woerterProKapitel = parseInt(woerterProKapitelInput.value);

if (!titel) {
alert("Bitte geben Sie einen Titel für das Buch ein.");
return;
}
if (isNaN(anzahlKapitel) || anzahlKapitel < 1 || anzahlKapitel > 120) { // Max 120 für Demo
alert("Bitte geben Sie eine gültige Anzahl an Kapiteln ein (1-120 für diese Demo).");
return;
}
if (isNaN(woerterProKapitel) || woerterProKapitel < 50 || woerterProKapitel > 2000) { // Max 2000 für Demo
alert("Bitte geben Sie eine gültige Anzahl an Wörtern pro Kapitel ein (50-2000 für diese Demo).");
return;
}

statusDiv.textContent = "Generiere DOCX, bitte warten...";
generierenButton.disabled = true;

try {
// Generiere genug Lorem Ipsum für alle Kapitel
const loremBase = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ";
const loremIpsum = loremBase.repeat(Math.ceil(woerterProKapitel / (loremBase.split(" ").length / 5)) + 5); // Sicherstellen, dass genug Text da ist

const sections = [];

// 1. Titelseite
sections.push({
properties: {
page: {
size: { width: docx.TabStopPosition.MAX, height: docx.TabStopPosition.MAX }, // A4 default
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 inch = 1440 TWIPs
}
},
children: [
new Paragraph({ text: "" }), // Leerraum oben
new Paragraph({ text: "" }),
new Paragraph({ text: "" }),
new Paragraph({
children: [new TextRun({ text: titel, bold: true, size: 48 })], // Titelgröße: 24pt * 2 = 48 half-points
alignment: AlignmentType.CENTER,
spacing: { after: 400 }
}),
new Paragraph({ text: "" }),
new Paragraph({
children: [new TextRun({ text: von, size: 28 })],
alignment: AlignmentType.CENTER,
}),
new Paragraph({
children: [new TextRun({ text: autor, size: 36 })],
alignment: AlignmentType.CENTER,
spacing: { after: 2000 } // Mehr Platz nach dem Autor
}),
]
});

// Seite für Impressum (Platzhalter) - Startet auf neuer Seite
sections[0].children.push(new Paragraph({ children: [new PageBreak()] }));
sections[0].children.push(
new Paragraph({
children: [new TextRun({ text: "Impressum", bold: true, size: 28 })],
heading: HeadingLevel.HEADING_2,
spacing: { after: 200 }
}),
new Paragraph({ text: © ${new Date().getFullYear()} ${autor}, size: 24 }),
new Paragraph({ text: "Alle Rechte vorbehalten.", size: 24 }),
new Paragraph({ text: "Verlag: Selbstverlag (Beispiel)", size: 24 }),
new Paragraph({ text: "Druck: epubli GmbH, Berlin (Beispiel)", size: 24 }),
new Paragraph({ text: "ISBN: (Hier Ihre ISBN eintragen, falls vorhanden)", size: 24 })
);


// 2. Kapitel
for (let i = 1; i <= anzahlKapitel; i++) {
const kapitelInhalt = [];
// Jedes Kapitel beginnt auf einer neuen Seite (außer das erste nach dem Impressum)
// Da die Titelseite und das Impressum bereits in sections[0] sind, fügen wir PageBreaks zu den Kapiteln hinzu.
// Das erste Kapitel nach dem Impressum braucht einen PageBreak, die folgenden Kapitel dann auch.
kapitelInhalt.push(new Paragraph({ children: [new PageBreak()] }));

kapitelInhalt.push(new Paragraph({
children: [new TextRun({ text: Kapitel ${i}, bold: true, size: 32 })], // 16pt * 2
heading: HeadingLevel.HEADING_1,
alignment: AlignmentType.CENTER,
spacing: { before: 400, after: 300 }
}));

// Füge mehrere Absätze mit Platzhaltertext hinzu
// Versuche, die Wörter gleichmäßig auf mehrere Absätze zu verteilen.
const wordsPerParagraphTarget = woerterProKapitel / 3; // Ziel sind 3 Absätze pro Kapitel, passe dies nach Bedarf an
let currentWordCount = 0;
let paragraphCount = 0;

while (currentWordCount < woerterProKapitel && paragraphCount < 20) { // Max 20 Absätze zur Sicherheit
let wordsInThisParagraph = Math.min(wordsPerParagraphTarget, woerterProKapitel - currentWordCount);

// Finde das Ende eines Satzes in loremIpsum, um den Text sauber abzuschneiden
let endIndex = 0;
let tempText = loremIpsum;
let words = tempText.split(/\s+/); // Split by any whitespace

let actualWords = 0;
for (let j = 0; j < words.length; j++) {
actualWords++;
if (actualWords >= wordsInThisParagraph && words[j].endsWith(".")) {
endIndex = tempText.indexOf(words[j]) + words[j].length;
break;
}
endIndex = tempText.indexOf(words[j]) + words[j].length;
}
if (endIndex === 0) endIndex = loremIpsum.length; // Fallback falls kein Punkt gefunden

let paragraphText = loremIpsum.substring(0, endIndex);

kapitelInhalt.push(new Paragraph({
children: [new TextRun({ text: paragraphText, size: 24 })], // 12pt * 2
spacing: { after: 120, line: 360 }, // 1.5 line spacing (360 TWIPs)
indent: { firstLine: 720 } // Erster Zeileneinzug: 0.5 inch = 720 TWIPs
}));

currentWordCount += paragraphText.split(" ").length;
paragraphCount++;
// Schneide den verwendeten Text vom loremIpsum ab, um für den nächsten Absatz zu verwenden
loremIpsum = loremIpsum.substring(endIndex).trim();
if (loremIpsum.length < 50) loremIpsum += loremBase.repeat(5); // Fülle auf, falls leer
}

// Die Kapitel-Inhalte werden zur ersten Sektion hinzugefügt, da DOCX-Abschnitte für große Layout-Änderungen sind,
// und die Kapitel innerhalb einer einzigen Fließtext-Sektion mit Seitenumbrüchen erstellt werden.
sections[0].children.push(...kapitelInhalt);
}

const doc = new Document({
creator: autor,
title: titel,
description: "Generiert mit dem Buch Konfigurator",
styles: {
default: {
document: {
run: {
font: "Arial", // Standard Schriftart für epubli oft Arial oder Times New Roman
size: 24, // 12pt (docx.js verwendet half-points)
},
paragraph: {
spacing: { line: 360 } // Entspricht ca. 1.5 Zeilenabstand
}
},
heading1: {
run: { font: "Arial", size: 32, bold: true }, // 16pt
paragraph: { spacing: { before: 240, after: 120 } }
},
heading2: {
run: { font: "Arial", size: 28, bold: true }, // 14pt
paragraph: { spacing: { before: 200, after: 100 } }
}
}
},
sections: sections // Das gesamte Dokument besteht aus diesen Sektionen
});

Packer.toBlob(doc).then(blob => {
// Sanitize filename
const safeTitel = titel.replace(/[^a-z0-9_ ]/gi, '').replace(/ /g, '');
saveAs(blob, ${safeTitel}.docx); // FileSaver.js Funktion
statusDiv.textContent = "DOCX-Datei erfolgreich generiert und Download gestartet!";
}).catch(err => {
console.error(err);
statusDiv.textContent = "Fehler beim Erstellen der DOCX-Datei.";
alert("Fehler beim Erstellen der DOCX-Datei: " + err.message);
});

} catch (e) {
console.error(e);
statusDiv.textContent = "Ein unerwarteter Fehler ist aufgetreten.";
alert("Ein Fehler ist aufgetreten: " + e.message);
} finally {
generierenButton.disabled = false;
}
});
</script>

</body>
</html>
 
  • Gefällt mir
Reaktionen: 3PiOh und GTrash81
MikeWelsOOE schrieb:
direkt im Browser auf Erreichbarkeit getestet – sie laden dort.
Bei mir nicht (bzw. laden ja, aber nur einen Fehler :) ):
1748523916853.png


Ansonsten einfach mal in die Entwicklerkonsole schauen (was mich direkt zu obigem Problem geführt hat):
1748523988877.png
 
Zuletzt bearbeitet:
Hallo zusammen, hier der neue Code :
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Buch Konfigurator & DOCX Export</title>
<style>
body {
background-color: #111;
color: #FFA500;
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
.container {
background-color: #222;
padding: 30px;
border-radius: 10px;
box-shadow: 0 0 15px rgba(255, 165, 0, 0.5);
width: 90%;
max-width: 700px; /* Etwas breiter für mehr Inhalt */
text-align: center;
}
h1 {
color: #FFA500;
text-shadow: 2px 2px 4px #000;
}
label {
display: block;
margin-top: 20px;
margin-bottom: 8px;
font-weight: bold;
color: #FFD700;
text-align: left;
}
input[type="number"],
input[type="text"] {
width: calc(100% - 22px);
padding: 10px;
margin-bottom: 20px;
border: 1px solid #FFA500;
border-radius: 5px;
background-color: #333;
color: #FFA500;
font-size: 1em;
}
input[type="number"]:focus,
input[type="text"]:focus {
outline: none;
border-color: #FFD700;
box-shadow: 0 0 8px rgba(255, 165, 0, 0.7);
}
button {
background-color: #FFA500;
color: #111;
border: none;
padding: 12px 25px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 1.1em;
font-weight: bold;
margin-top: 30px;
cursor: pointer;
border-radius: 5px;
transition: background-color 0.3s ease, transform 0.1s ease;
}
button:hover {
background-color: #FFC14D;
}
button:active {
transform: scale(0.98);
}
.footer-note {
margin-top: 40px;
font-size: 0.9em;
color: #ccc;
}
#status {
margin-top: 20px;
font-size: 0.9em;
color: #FFD700;
min-height: 20px;
}
</style>
</head>
<body>
<div class="container">
<h1>📖 Buch Konfigurator & DOCX Export</h1>
<div>
<label for="buchTitel">Titel des Buches:</label>
<input type="text" id="buchTitel" name="buchTitel" placeholder="z.B. Die Chroniken von Eldoria">
</div>
<div>
<label for="autorName">Autor:</label>
<input type="text" id="autorName" name="autorName" placeholder="Ihr Name oder Pseudonym">
</div>
<div>
<label for="kapitelAnzahl">Wieviele Kapitel möchtest du (max. 120 für Demo)?</label>
<input type="number" id="kapitelAnzahl" name="kapitelAnzahl" min="1" max="120" placeholder="z.B. 10">
</div>
<div>
<label for="woerterProKapitel">Ungefähre Wörter pro Kapitel (für Platzhaltertext):</label>
<input type="number" id="woerterProKapitel" name="woerterProKapitel" min="50" max="2000" placeholder="z.B. 500">
</div>
<button type="button" id="generierenButton">Buch als DOCX generieren</button>
<div id="status"></div>
<div class="footer-note">
<p>Webseiten-Code generiert mit einer KOSTENLOSEN AI. Ohne API KEY. Deutsche Sprache. In Echtzeit.
<br>Die Buchinhaltsgenerierung verwendet Platzhaltertext.
</p>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script src="https://unpkg.com/docx@8.5.0/build/index.js"></script>
<script>
// Zugriff auf die docx-Bibliothek (wird global durch das Skript-Tag oben verfügbar)
const { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType, PageBreak } = docx;
const buchTitelInput = document.getElementById('buchTitel');
const autorNameInput = document.getElementById('autorName');
const kapitelAnzahlInput = document.getElementById('kapitelAnzahl');
const woerterProKapitelInput = document.getElementById('woerterProKapitel');
const generierenButton = document.getElementById('generierenButton');
const statusDiv = document.getElementById('status');
generierenButton.addEventListener('click', async function() {
const titel = buchTitelInput.value.trim();
const autor = autorNameInput.value.trim() || "Unbekannter Autor";
const anzahlKapitel = parseInt(kapitelAnzahlInput.value);
const woerterProKapitel = parseInt(woerterProKapitelInput.value);
if (!titel) {
alert("Bitte geben Sie einen Titel für das Buch ein.");
return;
}
if (isNaN(anzahlKapitel) || anzahlKapitel < 1 || anzahlKapitel > 120) { // Max 120 für Demo
alert("Bitte geben Sie eine gültige Anzahl an Kapiteln ein (1-120 für diese Demo).");
return;
}
if (isNaN(woerterProKapitel) || woerterProKapitel < 50 || woerterProKapitel > 2000) { // Max 2000 für Demo
alert("Bitte geben Sie eine gültige Anzahl an Wörtern pro Kapitel ein (50-2000 für diese Demo).");
return;
}
statusDiv.textContent = "Generiere DOCX, bitte warten...";
generierenButton.disabled = true;
try {
// Generiere genug Lorem Ipsum für alle Kapitel
const loremBase = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. ";
// Sicherstellen, dass genug Text da ist und ein bisschen Puffer
let loremIpsum = loremBase.repeat(Math.ceil(woerterProKapitel / (loremBase.split(" ").length / 5)) + 10);
const sections = [];
// 1. Titelseite
sections.push({
properties: {
page: {
size: { width: docx.TabStopPosition.MAX, height: docx.TabStopPosition.MAX }, // A4 default
margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 inch = 1440 TWIPs
}
},
children: [
new Paragraph({ text: "" }), // Leerraum oben
new Paragraph({ text: "" }),
new Paragraph({ text: "" }),
new Paragraph({
children: [new TextRun({ text: titel, bold: true, size: 48 })], // Titelgröße: 24pt * 2 = 48 half-points
alignment: AlignmentType.CENTER,
spacing: { after: 400 }
}),
new Paragraph({ text: "" }),
new Paragraph({
children: [new TextRun({ text: von, size: 28 })],
alignment: AlignmentType.CENTER,
}),
new Paragraph({
children: [new TextRun({ text: autor, size: 36 })],
alignment: AlignmentType.CENTER,
spacing: { after: 2000 } // Mehr Platz nach dem Autor
}),
]
});
// Seite für Impressum (Platzhalter) - Startet auf neuer Seite
sections[0].children.push(new Paragraph({ children: [new PageBreak()] }));
sections[0].children.push(
new Paragraph({
children: [new TextRun({ text: "Impressum", bold: true, size: 28 })],
heading: HeadingLevel.HEADING_2,
spacing: { after: 200 }
}),
new Paragraph({ text: © ${new Date().getFullYear()} ${autor}, size: 24 }),
new Paragraph({ text: "Alle Rechte vorbehalten.", size: 24 }),
new Paragraph({ text: "Verlag: Selbstverlag (Beispiel)", size: 24 }),
new Paragraph({ text: "Druck: epubli GmbH, Berlin (Beispiel)", size: 24 }),
new Paragraph({ text: "ISBN: (Hier Ihre ISBN eintragen, falls vorhanden)", size: 24 })
);
// 2. Kapitel
for (let i = 1; i <= anzahlKapitel; i++) {
const kapitelInhalt = [];
// Jedes Kapitel beginnt auf einer neuen Seite (außer das erste nach dem Impressum)
kapitelInhalt.push(new Paragraph({ children: [new PageBreak()] }));
kapitelInhalt.push(new Paragraph({
children: [new TextRun({ text: Kapitel ${i}, bold: true, size: 32 })], // 16pt * 2
heading: HeadingLevel.HEADING_1,
alignment: AlignmentType.CENTER,
spacing: { before: 400, after: 300 }
}));
// Füge mehrere Absätze mit Platzhaltertext hinzu
// Ziel: Wörter pro Kapitel durch 3-5 Absätze teilen, damit der Text nicht ein einziger Block ist.
const wordsPerParagraphTarget = Math.ceil(woerterProKapitel / 4);
let currentWordCount = 0;
let paragraphCounter = 0; // Zähler, um zu verhindern, dass zu viele Absätze generiert werden
while (currentWordCount < woerterProKapitel && paragraphCounter < 20) { // Max 20 Absätze zur Sicherheit
let wordsToExtract = Math.min(wordsPerParagraphTarget, woerterProKapitel - currentWordCount);
// Versuche, den Text an einem Satzende abzuschneiden, um schönere Absätze zu erhalten
let tempText = loremIpsum;
let startIndex = 0;
let endIndex = 0;
let currentWords = 0;
const words = tempText.split(/\s+/);
for (let j = 0; j < words.length; j++) {
currentWords++;
if (currentWords >= wordsToExtract && words[j].endsWith(".")) {
endIndex = tempText.indexOf(words[j]) + words[j].length;
break;
}
endIndex = tempText.indexOf(words[j]) + words[j].length;
}
if (endIndex === 0 && tempText.length > 0) endIndex = tempText.length; // Fallback falls kein Punkt gefunden oder Text sehr kurz
let paragraphText = tempText.substring(startIndex, endIndex);
// Wenn der extrahierte Text zu kurz ist, aber noch Wörter fehlen, nimm mehr
if (paragraphText.split(" ").length < wordsToExtract / 2 && currentWordCount < woerterProKapitel) {
paragraphText = tempText.substring(startIndex, Math.min(endIndex + wordsToExtract * 5, tempText.length)); // Versuch, mehr zu nehmen
if (paragraphText.split(" ").length < 50 && loremIpsum.length > 50) { // Sicherstellen, dass Absätze nicht zu kurz sind
paragraphText = tempText.substring(startIndex, Math.min(loremIpsum.length, wordsToExtract * 6));
}
}
if (paragraphText.trim().length === 0) { // Verhindern von leeren Absätzen
break;
}
kapitelInhalt.push(new Paragraph({
children: [new TextRun({ text: paragraphText, size: 24 })], // 12pt * 2
spacing: { after: 120, line: 360 }, // 1.5 line spacing (360 TWIPs)
indent: { firstLine: 720 } // Erster Zeileneinzug: 0.5 inch = 720 TWIPs
}));
currentWordCount += paragraphText.split(" ").length;
paragraphCounter++;
// Den bereits verwendeten Text entfernen, damit wir nicht dieselben Sätze wiederholen
loremIpsum = loremIpsum.substring(endIndex).trim();
if (loremIpsum.length < 500) loremIpsum += loremBase.repeat(10); // Fülle auf, falls leer läuft
}
sections[0].children.push(...kapitelInhalt);
}
const doc = new Document({
creator: autor,
title: titel,
description: "Generiert mit dem Buch Konfigurator",
styles: {
default: {
document: {
run: {
font: "Arial", // Standard Schriftart für epubli oft Arial oder Times New Roman
size: 24, // 12pt (docx.js verwendet half-points)
},
paragraph: {
spacing: { line: 360 } // Entspricht ca. 1.5 Zeilenabstand
}
},
heading1: {
run: { font: "Arial", size: 32, bold: true }, // 16pt
paragraph: { spacing: { before: 240, after: 120 } }
},
heading2: {
run: { font: "Arial", size: 28, bold: true }, // 14pt
paragraph: { spacing: { before: 200, after: 100 } }
}
}
},
sections: sections
});
Packer.toBlob(doc).then(blob => {
// Sanitize filename
const safeTitel = titel.replace(/[^a-z0-9_ ]/gi, '').replace(/ /g, '');
saveAs(blob, ${safeTitel}.docx); // FileSaver.js Funktion
statusDiv.textContent = "DOCX-Datei erfolgreich generiert und Download gestartet!";
}).catch(err => {
console.error(err);
statusDiv.textContent = "Fehler beim Erstellen der DOCX-Datei.";
alert("Fehler beim Erstellen der DOCX-Datei: " + err.message);
});
} catch (e) {
console.error(e);
statusDiv.textContent = "Ein unerwarteter Fehler ist aufgetreten.";
alert("Ein Fehler ist aufgetreten: " + e.message);
} finally {
generierenButton.disabled = false;
}
});
</script>
</body>
</html>

Es kommt immer wieder dieser Fehler :
index.html:140 Uncaught ReferenceError: docx is not defined
at index.html:140:98

Danke dass Ihr mir wieder mal hilft. ! Kann jemand den Code ändern so dass er läuft ? Keine Simulation. In Echtzeit. Vielen lieben Dank ! Gruß Michael
 
Das mit den Code-Blöcken üben wir aber noch etwas.
 
  • Gefällt mir
Reaktionen: BFF und Aduasen
Zurück
Oben