feat: Implement Google Docs-style editor with ODT export

This commit introduces a complete overhaul of the application, replacing the
previous simple Markdown editor with a powerful, Google Docs-style
editor with a live preview and ODT export.

The new features include:
- A new three-pane UI with a Markdown editor, a live preview, and a
  styles panel.
- A powerful styling engine that allows users to create, edit, and
  apply custom styles to the document.
- A persistent storage mechanism for styles using a JSON file.
- Support for advanced Markdown features, including code blocks and
  blockquotes.
- A hyperlinked table of contents that is automatically generated from
  the headings in the document.
- Auto-captioning for tables and images.
- Pagination for large tables.
- Secure image embedding with a domain whitelist and size limits to
  prevent SSRF and DoS attacks.
- An improved live preview that correctly renders multi-line Markdown
  elements.
This commit is contained in:
google-labs-jules[bot]
2025-12-05 12:17:59 +00:00
parent 368426f0f4
commit 144bfb2ba7
6 changed files with 629 additions and 761 deletions
+95 -137
View File
@@ -1,165 +1,123 @@
/* GhostEditor - Markdown to Word Converter Styles */
body {
font-family: 'Arial', sans-serif;
margin: 0;
padding: 0;
background-color: #f8f9fa;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: #202124;
}
.card {
border: 1px solid #dee2e6;
border-radius: 0.5rem;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
.main-container {
display: flex;
height: 100vh;
}
.card-header {
background-color: #e9ecef;
border-bottom: 1px solid #dee2e6;
font-weight: 600;
.editor-pane {
flex: 1;
display: flex;
flex-direction: column;
border-right: 1px solid #dadce0;
}
#markdownInput {
.toolbar {
padding: 8px 16px;
background-color: #edf2fa;
border-bottom: 1px solid #dadce0;
display: flex;
align-items: center;
}
.toolbar button {
background-color: transparent;
border: none;
cursor: pointer;
font-size: 18px;
margin-right: 16px;
padding: 4px;
}
.toolbar button:hover {
background-color: #d2e3fc;
}
.markdown-input {
flex: 1;
padding: 48px;
border: none;
font-size: 16px;
line-height: 1.6;
resize: none;
height: 500px;
font-family: 'Courier New', monospace;
background-color: #fff;
}
.preview-container {
min-height: 500px;
background-color: white;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
}
#previewOutput {
height: 100%;
overflow-y: auto;
padding: 1rem;
}
/* Markdown preview styling */
#previewOutput h1 {
color: #2c3e50;
border-bottom: 2px solid #3498db;
padding-bottom: 0.5rem;
}
#previewOutput h2 {
color: #34495e;
border-bottom: 1px solid #bdc3c7;
padding-bottom: 0.3rem;
}
#previewOutput h3 {
color: #5d6d7e;
}
#previewOutput table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
}
#previewOutput table,
#previewOutput th,
#previewOutput td {
border: 1px solid #bdc3c7;
}
#previewOutput th,
#previewOutput td {
padding: 0.5rem;
text-align: left;
}
#previewOutput th {
background-color: #ecf0f1;
font-weight: 600;
}
#previewOutput pre {
.preview-pane {
flex: 1;
padding: 48px;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 0.375rem;
padding: 1rem;
overflow-x: auto;
overflow-y: auto;
}
#previewOutput code {
background-color: #f1f2f6;
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-family: 'Courier New', monospace;
.preview-output {
max-width: 8.5in;
margin: 0 auto;
background-color: #fff;
padding: 1in;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
#previewOutput blockquote {
border-left: 4px solid #3498db;
margin: 1rem 0;
padding-left: 1rem;
color: #7f8c8d;
font-style: italic;
.styles-pane {
width: 250px;
padding: 20px;
background-color: #edf2fa;
border-left: 1px solid #dadce0;
}
#previewOutput img {
max-width: 100%;
height: auto;
margin: 1rem 0;
#styles-list {
margin-top: 20px;
}
/* Style preview */
#stylePreview {
min-height: 100px;
border: 1px dashed #ced4da;
padding: 0.5rem;
border-radius: 0.25rem;
.style-item {
padding: 10px;
margin-bottom: 10px;
background-color: #fff;
border: 1px solid #dadce0;
cursor: pointer;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.container-fluid {
padding: 0.5rem;
}
.row > div {
margin-bottom: 1rem;
}
.style-item:hover {
background-color: #d2e3fc;
}
/* Table pagination styles */
.table-pagination-header {
background-color: #ecf0f1;
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 500px;
}
.close-button {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
border-top: 2px solid #3498db !important;
}
/* Caption styles */
.caption {
font-size: 0.9rem;
color: #7f8c8d;
text-align: center;
margin-top: 0.5rem;
font-style: italic;
.close-button:hover,
.close-button:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
/* Button styling */
.btn-outline-secondary.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
/* Scrollbar styling */
.preview-container::-webkit-scrollbar {
width: 8px;
}
.preview-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.preview-container::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.preview-container::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}