edouiu3838's picture
Make a Microsoft Word like web app with the possibility to write the document, customise it with colors, font, size and simple format, and save documents or export pdf to computer locally
ca946d2 verified
Raw
History Blame Contribute Delete
14.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WordWizard - Online Document Editor</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<style>
#editor {
min-height: calc(100vh - 120px);
padding: 2rem;
outline: none;
}
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -60px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
@media (max-width: 768px) {
#toolbar {
flex-wrap: wrap;
height: auto;
padding: 0.5rem;
}
.tool-group {
margin-bottom: 0.5rem;
}
}
</style>
</head>
<body class="bg-gray-50">
<div class="flex flex-col h-screen">
<!-- Header -->
<header class="bg-blue-600 text-white shadow-md">
<div class="container mx-auto px-4 py-3 flex items-center justify-between">
<div class="flex items-center space-x-2">
<i data-feather="file-text" class="w-6 h-6"></i>
<h1 class="text-xl font-bold">WordWizard</h1>
</div>
<div class="flex space-x-4">
<button id="saveBtn" class="flex items-center space-x-1 px-3 py-1 bg-blue-700 rounded hover:bg-blue-800 transition">
<i data-feather="save" class="w-4 h-4"></i>
<span>Save</span>
</button>
<button id="exportPdfBtn" class="flex items-center space-x-1 px-3 py-1 bg-green-600 rounded hover:bg-green-700 transition">
<i data-feather="download" class="w-4 h-4"></i>
<span>Export PDF</span>
</button>
</div>
</div>
</header>
<!-- Toolbar -->
<div id="toolbar" class="bg-white border-b border-gray-200 px-4 py-2 flex items-center space-x-4 overflow-x-auto">
<!-- File group -->
<div class="tool-group flex items-center space-x-2">
<button id="newDocBtn" class="tooltip p-2 rounded hover:bg-gray-100">
<i data-feather="file" class="w-5 h-5"></i>
<span class="tooltiptext">New Document</span>
</button>
</div>
<!-- Format group -->
<div class="tool-group flex items-center space-x-2 border-l border-gray-300 pl-4">
<select id="fontFamily" class="border border-gray-300 rounded px-2 py-1 text-sm bg-white">
<option value="Arial">Arial</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Courier New">Courier New</option>
<option value="Georgia">Georgia</option>
<option value="Verdana">Verdana</option>
</select>
<select id="fontSize" class="border border-gray-300 rounded px-2 py-1 text-sm bg-white">
<option value="1">8</option>
<option value="2">10</option>
<option value="3">12</option>
<option value="4">14</option>
<option value="5">18</option>
<option value="6">24</option>
<option value="7">36</option>
</select>
<button id="boldBtn" class="tooltip p-2 rounded hover:bg-gray-100">
<i data-feather="bold" class="w-5 h-5"></i>
<span class="tooltiptext">Bold</span>
</button>
<button id="italicBtn" class="tooltip p-2 rounded hover:bg-gray-100">
<i data-feather="italic" class="w-5 h-5"></i>
<span class="tooltiptext">Italic</span>
</button>
<button id="underlineBtn" class="tooltip p-2 rounded hover:bg-gray-100">
<i data-feather="underline" class="w-5 h-5"></i>
<span class="tooltiptext">Underline</span>
</button>
</div>
<!-- Alignment group -->
<div class="tool-group flex items-center space-x-2 border-l border-gray-300 pl-4">
<button id="alignLeftBtn" class="tooltip p-2 rounded hover:bg-gray-100">
<i data-feather="align-left" class="w-5 h-5"></i>
<span class="tooltiptext">Align Left</span>
</button>
<button id="alignCenterBtn" class="tooltip p-2 rounded hover:bg-gray-100">
<i data-feather="align-center" class="w-5 h-5"></i>
<span class="tooltiptext">Align Center</span>
</button>
<button id="alignRightBtn" class="tooltip p-2 rounded hover:bg-gray-100">
<i data-feather="align-right" class="w-5 h-5"></i>
<span class="tooltiptext">Align Right</span>
</button>
<button id="justifyBtn" class="tooltip p-2 rounded hover:bg-gray-100">
<i data-feather="align-justify" class="w-5 h-5"></i>
<span class="tooltiptext">Justify</span>
</button>
</div>
<!-- Color group -->
<div class="tool-group flex items-center space-x-2 border-l border-gray-300 pl-4">
<div class="relative">
<input type="color" id="textColor" class="w-8 h-8 border border-gray-300 rounded cursor-pointer" value="#000000">
<span class="tooltiptext">Text Color</span>
</div>
<div class="relative">
<input type="color" id="highlightColor" class="w-8 h-8 border border-gray-300 rounded cursor-pointer" value="#ffffff">
<span class="tooltiptext">Highlight Color</span>
</div>
</div>
<!-- List group -->
<div class="tool-group flex items-center space-x-2 border-l border-gray-300 pl-4">
<button id="bulletListBtn" class="tooltip p-2 rounded hover:bg-gray-100">
<i data-feather="list" class="w-5 h-5"></i>
<span class="tooltiptext">Bullet List</span>
</button>
<button id="numberedListBtn" class="tooltip p-2 rounded hover:bg-gray-100">
<i data-feather="list" class="w-5 h-5 rotate-90"></i>
<span class="tooltiptext">Numbered List</span>
</button>
</div>
</div>
<!-- Editor -->
<div id="editor" class="flex-1 bg-white shadow-inner" contenteditable="true">
<p style="font-size: 14pt; line-height: 1.5;">Start typing here...</p>
</div>
<!-- Status bar -->
<footer class="bg-gray-100 border-t border-gray-200 px-4 py-2 text-sm text-gray-600">
<div class="container mx-auto flex justify-between items-center">
<div>Word count: <span id="wordCount">0</span></div>
<div>Characters: <span id="charCount">0</span></div>
</div>
</footer>
</div>
<script>
// Initialize feather icons
feather.replace();
// Document properties
let currentDocument = {
id: Date.now().toString(),
title: 'Untitled Document',
content: ''
};
// Editor functionality
document.addEventListener('DOMContentLoaded', () => {
const editor = document.getElementById('editor');
const wordCount = document.getElementById('wordCount');
const charCount = document.getElementById('charCount');
// Formatting buttons
document.getElementById('boldBtn').addEventListener('click', () => document.execCommand('bold', false, null));
document.getElementById('italicBtn').addEventListener('click', () => document.execCommand('italic', false, null));
document.getElementById('underlineBtn').addEventListener('click', () => document.execCommand('underline', false, null));
// Alignment buttons
document.getElementById('alignLeftBtn').addEventListener('click', () => document.execCommand('justifyLeft', false, null));
document.getElementById('alignCenterBtn').addEventListener('click', () => document.execCommand('justifyCenter', false, null));
document.getElementById('alignRightBtn').addEventListener('click', () => document.execCommand('justifyRight', false, null));
document.getElementById('justifyBtn').addEventListener('click', () => document.execCommand('justifyFull', false, null));
// List buttons
document.getElementById('bulletListBtn').addEventListener('click', () => document.execCommand('insertUnorderedList', false, null));
document.getElementById('numberedListBtn').addEventListener('click', () => document.execCommand('insertOrderedList', false, null));
// Font family
document.getElementById('fontFamily').addEventListener('change', function() {
document.execCommand('fontName', false, this.value);
});
// Font size
document.getElementById('fontSize').addEventListener('change', function() {
const sizes = ['8px', '10px', '12px', '14px', '18px', '24px', '36px'];
document.execCommand('fontSize', false, this.value);
// Workaround for consistent font size
const fontElements = editor.querySelectorAll('font[size="' + this.value + '"]');
fontElements.forEach(el => {
el.removeAttribute('size');
el.style.fontSize = sizes[parseInt(this.value) - 1];
});
});
// Text color
document.getElementById('textColor').addEventListener('input', function() {
document.execCommand('foreColor', false, this.value);
});
// Highlight color
document.getElementById('highlightColor').addEventListener('input', function() {
document.execCommand('hiliteColor', false, this.value);
});
// New document
document.getElementById('newDocBtn').addEventListener('click', () => {
if (confirm('Are you sure you want to create a new document? All unsaved changes will be lost.')) {
editor.innerHTML = '<p style="font-size: 14pt; line-height: 1.5;">Start typing here...</p>';
updateCounts();
}
});
// Save document
document.getElementById('saveBtn').addEventListener('click', () => {
currentDocument.content = editor.innerHTML;
const blob = new Blob([JSON.stringify(currentDocument)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${currentDocument.title}.wordwizard`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
// Show success message
alert('Document saved successfully!');
});
// Export PDF
document.getElementById('exportPdfBtn').addEventListener('click', () => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
html2canvas(editor).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const imgWidth = doc.internal.pageSize.getWidth() - 20;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
doc.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight);
doc.save(`${currentDocument.title || 'document'}.pdf`);
});
});
// Update word and character counts
function updateCounts() {
const text = editor.innerText || '';
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
const chars = text.length;
wordCount.textContent = words;
charCount.textContent = chars;
}
// Listen for changes in the editor
editor.addEventListener('input', updateCounts);
updateCounts();
// Load document if one exists in localStorage
if (localStorage.getItem('lastDocument')) {
try {
const savedDoc = JSON.parse(localStorage.getItem('lastDocument'));
editor.innerHTML = savedDoc.content;
currentDocument = savedDoc;
updateCounts();
} catch (e) {
console.error('Failed to load last document:', e);
}
}
// Auto-save every 30 seconds
setInterval(() => {
currentDocument.content = editor.innerHTML;
localStorage.setItem('lastDocument', JSON.stringify(currentDocument));
}, 30000);
});
</script>
</body>
</html>