mirror of
https://github.com/amitwh/markdown-converter.git
synced 2026-08-02 10:00:17 +05:30
1215 lines
33 KiB
Markdown
1215 lines
33 KiB
Markdown
# Modal System Implementation Plan
|
|
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
|
|
**Goal:** Replace existing dialog implementations with a unified modal system providing glassmorphism backdrop, full accessibility, and smooth animations.
|
|
|
|
**Architecture:** Single `ModalManager` class manages all modals with focus trap, keyboard handling, and dynamic backdrop. All 10 dialogs convert to unified HTML structure with new CSS classes.
|
|
|
|
**Tech Stack:** Vanilla JavaScript, CSS custom properties (design tokens), no external dependencies
|
|
|
|
---
|
|
|
|
## Task 1: Create ModalManager Class
|
|
|
|
**Files:**
|
|
- Create: `src/utils/ModalManager.js`
|
|
|
|
**Step 1: Create utils directory and ModalManager class**
|
|
|
|
```javascript
|
|
/**
|
|
* ModalManager - Unified modal system with accessibility support
|
|
* @version 4.0.0
|
|
*/
|
|
export class ModalManager {
|
|
#modal;
|
|
#backdrop;
|
|
#options;
|
|
#lastFocusedElement;
|
|
#focusableElements;
|
|
#eventListeners;
|
|
#isOpen;
|
|
|
|
static #openModals = [];
|
|
|
|
constructor(element, options = {}) {
|
|
this.#modal = typeof element === 'string' ? document.querySelector(element) : element;
|
|
this.#options = {
|
|
closeOnBackdrop: true,
|
|
closeOnEscape: true,
|
|
focusFirst: true,
|
|
onOpen: null,
|
|
onClose: null,
|
|
...options
|
|
};
|
|
this.#isOpen = false;
|
|
this.#eventListeners = [];
|
|
this.#init();
|
|
}
|
|
|
|
#init() {
|
|
// Ensure modal has required attributes
|
|
if (!this.#modal.hasAttribute('role')) {
|
|
this.#modal.setAttribute('role', 'dialog');
|
|
}
|
|
if (!this.#modal.hasAttribute('aria-modal')) {
|
|
this.#modal.setAttribute('aria-modal', 'true');
|
|
}
|
|
|
|
// Find or create backdrop
|
|
this.#backdrop = this.#modal.querySelector('.modal-backdrop');
|
|
|
|
// Setup close triggers
|
|
this.#setupCloseTriggers();
|
|
}
|
|
|
|
#setupCloseTriggers() {
|
|
// Close button
|
|
const closeBtn = this.#modal.querySelector('.modal-close');
|
|
if (closeBtn) {
|
|
const handler = (e) => {
|
|
e.preventDefault();
|
|
this.close();
|
|
};
|
|
closeBtn.addEventListener('click', handler);
|
|
this.#eventListeners.push({ el: closeBtn, type: 'click', handler });
|
|
}
|
|
|
|
// Elements with data-close attribute
|
|
const closeTriggers = this.#modal.querySelectorAll('[data-close]');
|
|
closeTriggers.forEach(el => {
|
|
if (el.classList.contains('modal-backdrop') && !this.#options.closeOnBackdrop) {
|
|
return;
|
|
}
|
|
const handler = (e) => {
|
|
e.preventDefault();
|
|
this.close();
|
|
};
|
|
el.addEventListener('click', handler);
|
|
this.#eventListeners.push({ el, type: 'click', handler });
|
|
});
|
|
}
|
|
|
|
#getFocusableElements() {
|
|
const selector = [
|
|
'button:not([disabled])',
|
|
'input:not([disabled])',
|
|
'select:not([disabled])',
|
|
'textarea:not([disabled])',
|
|
'a[href]',
|
|
'[tabindex]:not([tabindex="-1"])'
|
|
].join(', ');
|
|
|
|
return Array.from(this.#modal.querySelectorAll(selector))
|
|
.filter(el => el.offsetParent !== null && !el.classList.contains('modal-backdrop'));
|
|
}
|
|
|
|
#trapFocus(e) {
|
|
if (e.key !== 'Tab') return;
|
|
|
|
const focusable = this.#getFocusableElements();
|
|
if (focusable.length === 0) return;
|
|
|
|
const firstEl = focusable[0];
|
|
const lastEl = focusable[focusable.length - 1];
|
|
|
|
if (e.shiftKey) {
|
|
if (document.activeElement === firstEl) {
|
|
e.preventDefault();
|
|
lastEl.focus();
|
|
}
|
|
} else {
|
|
if (document.activeElement === lastEl) {
|
|
e.preventDefault();
|
|
firstEl.focus();
|
|
}
|
|
}
|
|
}
|
|
|
|
#handleKeydown(e) {
|
|
if (e.key === 'Escape' && this.#options.closeOnEscape) {
|
|
e.preventDefault();
|
|
this.close();
|
|
}
|
|
this.#trapFocus(e);
|
|
}
|
|
|
|
open() {
|
|
if (this.#isOpen) return;
|
|
|
|
// Store last focused element
|
|
this.#lastFocusedElement = document.activeElement;
|
|
|
|
// Track open modals
|
|
ModalManager.#openModals.push(this);
|
|
|
|
// Show modal
|
|
this.#modal.classList.add('open');
|
|
this.#isOpen = true;
|
|
|
|
// Prevent body scroll
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
// Add keyboard listener
|
|
const keydownHandler = (e) => this.#handleKeydown(e);
|
|
document.addEventListener('keydown', keydownHandler);
|
|
this.#eventListeners.push({ el: document, type: 'keydown', handler: keydownHandler });
|
|
|
|
// Focus first element
|
|
if (this.#options.focusFirst) {
|
|
requestAnimationFrame(() => {
|
|
const focusable = this.#getFocusableElements();
|
|
if (focusable.length > 0) {
|
|
focusable[0].focus();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Callback
|
|
if (this.#options.onOpen) {
|
|
this.#options.onOpen(this);
|
|
}
|
|
|
|
// Dispatch custom event
|
|
this.#modal.dispatchEvent(new CustomEvent('modal:open'));
|
|
}
|
|
|
|
close() {
|
|
if (!this.#isOpen) return;
|
|
|
|
// Remove from open modals
|
|
const index = ModalManager.#openModals.indexOf(this);
|
|
if (index > -1) {
|
|
ModalManager.#openModals.splice(index, 1);
|
|
}
|
|
|
|
// Hide modal
|
|
this.#modal.classList.remove('open');
|
|
this.#isOpen = false;
|
|
|
|
// Restore body scroll if no modals open
|
|
if (ModalManager.#openModals.length === 0) {
|
|
document.body.style.overflow = '';
|
|
}
|
|
|
|
// Remove keyboard listener
|
|
const keydownListener = this.#eventListeners.find(
|
|
l => l.el === document && l.type === 'keydown'
|
|
);
|
|
if (keydownListener) {
|
|
document.removeEventListener('keydown', keydownListener.handler);
|
|
this.#eventListeners = this.#eventListeners.filter(l => l !== keydownListener);
|
|
}
|
|
|
|
// Restore focus
|
|
if (this.#lastFocusedElement && typeof this.#lastFocusedElement.focus === 'function') {
|
|
this.#lastFocusedElement.focus();
|
|
}
|
|
|
|
// Callback
|
|
if (this.#options.onClose) {
|
|
this.#options.onClose(this);
|
|
}
|
|
|
|
// Dispatch custom event
|
|
this.#modal.dispatchEvent(new CustomEvent('modal:close'));
|
|
}
|
|
|
|
isOpen() {
|
|
return this.#isOpen;
|
|
}
|
|
|
|
destroy() {
|
|
// Close if open
|
|
if (this.#isOpen) {
|
|
this.close();
|
|
}
|
|
|
|
// Remove all event listeners
|
|
this.#eventListeners.forEach(({ el, type, handler }) => {
|
|
el.removeEventListener(type, handler);
|
|
});
|
|
this.#eventListeners = [];
|
|
}
|
|
}
|
|
|
|
// Export for use in renderer
|
|
window.ModalManager = ModalManager;
|
|
```
|
|
|
|
**Step 2: Commit ModalManager**
|
|
|
|
```bash
|
|
git add src/utils/ModalManager.js
|
|
git commit -m "feat: add ModalManager class for unified modal system"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 2: Create Modal CSS Styles
|
|
|
|
**Files:**
|
|
- Create: `src/styles/modal.css`
|
|
|
|
**Step 1: Create modal.css with glassmorphism styles**
|
|
|
|
```css
|
|
/**
|
|
* Modal System Styles
|
|
* Unified modal components with glassmorphism backdrop
|
|
* @version 4.0.0
|
|
*/
|
|
|
|
/* ============================================
|
|
* Modal Backdrop - Glassmorphism
|
|
* ============================================ */
|
|
|
|
.modal-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.4);
|
|
backdrop-filter: blur(4px);
|
|
-webkit-backdrop-filter: blur(4px);
|
|
z-index: var(--z-modal, 200);
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* ============================================
|
|
* Modal Container
|
|
* ============================================ */
|
|
|
|
.modal {
|
|
position: fixed;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: calc(var(--z-modal, 200) + 1);
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transition: opacity var(--transition-normal, 200ms cubic-bezier(0.4, 0, 0.2, 1)),
|
|
visibility var(--transition-normal, 200ms cubic-bezier(0.4, 0, 0.2, 1));
|
|
padding: var(--spacing-4, 1rem);
|
|
}
|
|
|
|
.modal.open {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
}
|
|
|
|
/* ============================================
|
|
* Modal Content - With Animation
|
|
* ============================================ */
|
|
|
|
.modal-content {
|
|
background: hsl(var(--background, 0 0% 100%));
|
|
border-radius: var(--radius-lg, 0.5rem);
|
|
box-shadow: var(--shadow-xl, 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1));
|
|
max-width: 90vw;
|
|
max-height: 90vh;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
transform: scale(0.95);
|
|
transition: transform var(--transition-normal, 200ms cubic-bezier(0.4, 0, 0.2, 1));
|
|
}
|
|
|
|
.modal.open .modal-content {
|
|
transform: scale(1);
|
|
}
|
|
|
|
/* ============================================
|
|
* Modal Header
|
|
* ============================================ */
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: var(--spacing-4, 1rem) var(--spacing-6, 1.5rem);
|
|
border-bottom: 1px solid hsl(var(--border, 214.3 31.8% 91.4%));
|
|
background: hsl(var(--muted, 210 40% 96.1%));
|
|
}
|
|
|
|
.modal-header h3 {
|
|
margin: 0;
|
|
font-size: var(--text-lg, 1.125rem);
|
|
font-weight: var(--font-semibold, 600);
|
|
color: hsl(var(--foreground, 222.2 84% 4.9%));
|
|
}
|
|
|
|
/* ============================================
|
|
* Modal Close Button
|
|
* ============================================ */
|
|
|
|
.modal-close {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 32px;
|
|
height: 32px;
|
|
padding: 0;
|
|
border: none;
|
|
border-radius: var(--radius, 0.5rem);
|
|
background: transparent;
|
|
color: hsl(var(--muted-foreground, 215.4 16.3% 46.9%));
|
|
font-size: 24px;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
transition: background-color var(--transition-fast, 150ms),
|
|
color var(--transition-fast, 150ms);
|
|
}
|
|
|
|
.modal-close:hover {
|
|
background: hsl(var(--accent, 210 40% 96.1%));
|
|
color: hsl(var(--foreground, 222.2 84% 4.9%));
|
|
}
|
|
|
|
.modal-close:focus-visible {
|
|
outline: 2px solid hsl(var(--ring, 227 44% 52%));
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* ============================================
|
|
* Modal Body
|
|
* ============================================ */
|
|
|
|
.modal-body {
|
|
padding: var(--spacing-6, 1.5rem);
|
|
overflow-y: auto;
|
|
flex: 1;
|
|
}
|
|
|
|
/* ============================================
|
|
* Modal Footer
|
|
* ============================================ */
|
|
|
|
.modal-footer {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: var(--spacing-3, 0.75rem);
|
|
padding: var(--spacing-4, 1rem) var(--spacing-6, 1.5rem);
|
|
border-top: 1px solid hsl(var(--border, 214.3 31.8% 91.4%));
|
|
background: hsl(var(--muted, 210 40% 96.1%));
|
|
}
|
|
|
|
.modal-footer .btn {
|
|
min-width: 80px;
|
|
}
|
|
|
|
/* ============================================
|
|
* Form Elements within Modal
|
|
* ============================================ */
|
|
|
|
.modal-body .form-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-3, 0.75rem);
|
|
margin-bottom: var(--spacing-3, 0.75rem);
|
|
}
|
|
|
|
.modal-body .form-row label {
|
|
min-width: 100px;
|
|
font-size: var(--text-sm, 0.875rem);
|
|
color: hsl(var(--foreground, 222.2 84% 4.9%));
|
|
}
|
|
|
|
.modal-body .form-row input,
|
|
.modal-body .form-row select {
|
|
flex: 1;
|
|
padding: var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem);
|
|
border: 1px solid hsl(var(--input, 214.3 31.8% 91.4%));
|
|
border-radius: var(--radius, 0.5rem);
|
|
font-size: var(--text-sm, 0.875rem);
|
|
background: hsl(var(--background, 0 0% 100%));
|
|
color: hsl(var(--foreground, 222.2 84% 4.9%));
|
|
}
|
|
|
|
.modal-body .form-row input:focus,
|
|
.modal-body .form-row select:focus {
|
|
outline: 2px solid hsl(var(--ring, 227 44% 52%));
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
.modal-body .export-section {
|
|
margin-bottom: var(--spacing-4, 1rem);
|
|
padding-bottom: var(--spacing-4, 1rem);
|
|
border-bottom: 1px solid hsl(var(--border, 214.3 31.8% 91.4%));
|
|
}
|
|
|
|
.modal-body .export-section:last-child {
|
|
border-bottom: none;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.modal-body .export-section label {
|
|
display: block;
|
|
font-weight: var(--font-medium, 500);
|
|
margin-bottom: var(--spacing-2, 0.5rem);
|
|
color: hsl(var(--foreground, 222.2 84% 4.9%));
|
|
}
|
|
|
|
.modal-body .checkbox-group label {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-2, 0.5rem);
|
|
margin-bottom: var(--spacing-2, 0.5rem);
|
|
font-weight: var(--font-normal, 400);
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* ============================================
|
|
* Size Variants
|
|
* ============================================ */
|
|
|
|
.modal-content.small {
|
|
max-width: 400px;
|
|
}
|
|
|
|
.modal-content.large {
|
|
max-width: 800px;
|
|
}
|
|
|
|
.modal-content.full {
|
|
max-width: 95vw;
|
|
max-height: 95vh;
|
|
}
|
|
|
|
/* ============================================
|
|
* Dark Mode Support
|
|
* ============================================ */
|
|
|
|
.dark .modal-content,
|
|
[data-theme="dark"] .modal-content {
|
|
background: hsl(var(--background));
|
|
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.4), 0 8px 10px -6px rgb(0 0 0 / 0.3);
|
|
}
|
|
|
|
.dark .modal-header,
|
|
.dark .modal-footer,
|
|
[data-theme="dark"] .modal-header,
|
|
[data-theme="dark"] .modal-footer {
|
|
background: hsl(var(--muted));
|
|
}
|
|
|
|
/* ============================================
|
|
* Accessibility - Reduced Motion
|
|
* ============================================ */
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.modal,
|
|
.modal-content {
|
|
transition: none;
|
|
}
|
|
}
|
|
|
|
/* ============================================
|
|
* Legacy Support - Hidden class
|
|
* ============================================ */
|
|
|
|
.modal.hidden {
|
|
display: none;
|
|
}
|
|
```
|
|
|
|
**Step 2: Commit modal.css**
|
|
|
|
```bash
|
|
git add src/styles/modal.css
|
|
git commit -m "feat: add modal CSS with glassmorphism and animations"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 3: Include Modal CSS in HTML
|
|
|
|
**Files:**
|
|
- Modify: `src/index.html` (line ~18)
|
|
|
|
**Step 1: Add modal.css link after tokens.css**
|
|
|
|
Find this line in `src/index.html`:
|
|
```html
|
|
<link rel="stylesheet" href="styles/tokens.css">
|
|
```
|
|
|
|
Add after it:
|
|
```html
|
|
<link rel="stylesheet" href="styles/modal.css">
|
|
```
|
|
|
|
**Step 2: Commit**
|
|
|
|
```bash
|
|
git add src/index.html
|
|
git commit -m "feat: include modal.css in index.html"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 4: Include ModalManager in HTML
|
|
|
|
**Files:**
|
|
- Modify: `src/index.html` (near end of body, before renderer.js)
|
|
|
|
**Step 1: Add script tag for ModalManager**
|
|
|
|
Find the script tags near the end of body and add before renderer.js:
|
|
```html
|
|
<script src="utils/ModalManager.js"></script>
|
|
```
|
|
|
|
**Step 2: Commit**
|
|
|
|
```bash
|
|
git add src/index.html
|
|
git commit -m "feat: include ModalManager script in index.html"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 5: Convert Find Dialog
|
|
|
|
**Files:**
|
|
- Modify: `src/index.html` (lines 160-174)
|
|
- Modify: `src/renderer.js` (find dialog handlers)
|
|
|
|
**Step 1: Update HTML structure**
|
|
|
|
Replace the find-dialog div (lines 160-174) with:
|
|
|
|
```html
|
|
<!-- Find & Replace Dialog -->
|
|
<div id="find-dialog" class="modal" role="dialog" aria-modal="true" aria-labelledby="find-dialog-title">
|
|
<div class="modal-backdrop" data-close></div>
|
|
<div class="modal-content small">
|
|
<div class="modal-header">
|
|
<h3 id="find-dialog-title">Find & Replace</h3>
|
|
<button class="modal-close" id="btn-find-close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="find-controls">
|
|
<input type="text" id="find-input" placeholder="Find..." class="input">
|
|
<input type="text" id="replace-input" placeholder="Replace..." class="input">
|
|
</div>
|
|
<div class="find-buttons" style="display: flex; gap: 8px; margin-top: 8px;">
|
|
<button id="btn-find-prev" class="btn btn-secondary" title="Previous">↑</button>
|
|
<button id="btn-find-next" class="btn btn-secondary" title="Next">↓</button>
|
|
<button id="btn-replace" class="btn btn-secondary" title="Replace">Replace</button>
|
|
<button id="btn-replace-all" class="btn btn-secondary" title="Replace All">All</button>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<span id="find-count" style="flex: 1; color: hsl(var(--muted-foreground));">0 matches</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Step 2: Add CSS for find controls in modal.css**
|
|
|
|
Append to `src/styles/modal.css`:
|
|
|
|
```css
|
|
/* ============================================
|
|
* Find Dialog Specific
|
|
* ============================================ */
|
|
|
|
.find-controls {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--spacing-2, 0.5rem);
|
|
}
|
|
|
|
.find-controls input {
|
|
width: 100%;
|
|
}
|
|
```
|
|
|
|
**Step 3: Update renderer.js to use ModalManager**
|
|
|
|
Find the find dialog show/hide code and replace with ModalManager usage.
|
|
|
|
Add near top of renderer.js (after DOMContentLoaded):
|
|
|
|
```javascript
|
|
// Initialize ModalManager for find dialog
|
|
let findModal = null;
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
findModal = new ModalManager('#find-dialog');
|
|
// ... rest of initialization
|
|
});
|
|
```
|
|
|
|
Replace show/hide calls:
|
|
```javascript
|
|
// Old: document.getElementById('find-dialog').classList.remove('hidden');
|
|
// New:
|
|
findModal.open();
|
|
|
|
// Old: document.getElementById('find-dialog').classList.add('hidden');
|
|
// New:
|
|
findModal.close();
|
|
```
|
|
|
|
**Step 4: Commit**
|
|
|
|
```bash
|
|
git add src/index.html src/renderer.js src/styles/modal.css
|
|
git commit -m "feat: convert find dialog to unified modal system"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 6: Convert Export Dialog
|
|
|
|
**Files:**
|
|
- Modify: `src/index.html` (lines 176-337)
|
|
- Modify: `src/renderer.js` (export dialog handlers)
|
|
|
|
**Step 1: Update HTML structure**
|
|
|
|
Replace the export-dialog div with new modal structure. Keep all the internal content, just wrap in modal classes:
|
|
|
|
```html
|
|
<!-- Export Options Dialog -->
|
|
<div id="export-dialog" class="modal" role="dialog" aria-modal="true" aria-labelledby="export-dialog-title">
|
|
<div class="modal-backdrop" data-close></div>
|
|
<div class="modal-content large">
|
|
<div class="modal-header">
|
|
<h3 id="export-dialog-title">Export Options</h3>
|
|
<button class="modal-close" id="export-dialog-close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- Keep ALL existing content from export-dialog-body here -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="export-cancel" class="btn btn-secondary" data-close>Cancel</button>
|
|
<button id="export-confirm" class="btn btn-primary">Export</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Step 2: Update renderer.js**
|
|
|
|
```javascript
|
|
// Initialize export modal
|
|
let exportModal = null;
|
|
|
|
// In DOMContentLoaded:
|
|
exportModal = new ModalManager('#export-dialog');
|
|
|
|
// Replace showExportDialog function:
|
|
function showExportDialog(format) {
|
|
console.log('showExportDialog called with format:', format);
|
|
const title = document.getElementById('export-dialog-title');
|
|
|
|
title.textContent = `Export as ${format.toUpperCase()}`;
|
|
document.getElementById('export-dialog').setAttribute('data-format', format);
|
|
|
|
initializeExportForm(format);
|
|
exportModal.open();
|
|
}
|
|
|
|
// Replace hideExportDialog function:
|
|
function hideExportDialog() {
|
|
exportModal.close();
|
|
currentExportFormat = null;
|
|
}
|
|
```
|
|
|
|
**Step 3: Remove old event listeners**
|
|
|
|
Remove the manual click and escape key handlers for export-dialog since ModalManager handles them.
|
|
|
|
**Step 4: Commit**
|
|
|
|
```bash
|
|
git add src/index.html src/renderer.js
|
|
git commit -m "feat: convert export dialog to unified modal system"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 7: Convert Print Preview Dialog
|
|
|
|
**Files:**
|
|
- Modify: `src/index.html` (lines 339-420)
|
|
- Modify: `src/renderer.js`
|
|
|
|
**Step 1: Update HTML structure**
|
|
|
|
```html
|
|
<!-- Print Preview Dialog -->
|
|
<div id="print-preview-overlay" class="modal" role="dialog" aria-modal="true" aria-labelledby="print-preview-title">
|
|
<div class="modal-backdrop" data-close></div>
|
|
<div class="modal-content full">
|
|
<div class="modal-header">
|
|
<h3 id="print-preview-title">Print Preview</h3>
|
|
<button class="modal-close" id="print-preview-close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="modal-body print-preview-body">
|
|
<!-- Keep existing print preview content -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Step 2: Update renderer.js**
|
|
|
|
```javascript
|
|
let printPreviewModal = null;
|
|
|
|
// In DOMContentLoaded:
|
|
printPreviewModal = new ModalManager('#print-preview-overlay');
|
|
|
|
function openPrintPreviewDialog() {
|
|
printPreviewModal.open();
|
|
// ... rest of existing logic
|
|
}
|
|
```
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add src/index.html src/renderer.js
|
|
git commit -m "feat: convert print preview dialog to unified modal system"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 8: Convert Table Generator Dialog
|
|
|
|
**Files:**
|
|
- Modify: `src/index.html` (lines 422-471)
|
|
- Modify: `src/renderer.js`
|
|
|
|
**Step 1: Update HTML structure**
|
|
|
|
```html
|
|
<!-- Table Generator Dialog -->
|
|
<div id="table-generator-dialog" class="modal" role="dialog" aria-modal="true" aria-labelledby="table-dialog-title">
|
|
<div class="modal-backdrop" data-close></div>
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 id="table-dialog-title">Table Generator</h3>
|
|
<button class="modal-close" id="table-dialog-close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- Keep existing table generator content -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="table-dialog-cancel" class="btn btn-secondary" data-close>Cancel</button>
|
|
<button id="table-dialog-insert" class="btn btn-primary">Insert Table</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Step 2: Update renderer.js**
|
|
|
|
```javascript
|
|
let tableModal = null;
|
|
|
|
// In DOMContentLoaded:
|
|
tableModal = new ModalManager('#table-generator-dialog');
|
|
```
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add src/index.html src/renderer.js
|
|
git commit -m "feat: convert table generator dialog to unified modal system"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 9: Convert ASCII Art Dialog
|
|
|
|
**Files:**
|
|
- Modify: `src/index.html` (lines 473-582)
|
|
- Modify: `src/renderer.js`
|
|
|
|
**Step 1: Update HTML structure**
|
|
|
|
```html
|
|
<!-- ASCII Art Generator Dialog -->
|
|
<div id="ascii-art-dialog" class="modal" role="dialog" aria-modal="true" aria-labelledby="ascii-dialog-title">
|
|
<div class="modal-backdrop" data-close></div>
|
|
<div class="modal-content large">
|
|
<div class="modal-header">
|
|
<h3 id="ascii-dialog-title">ASCII Art Generator</h3>
|
|
<button class="modal-close" id="ascii-dialog-close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- Keep existing ASCII art content -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="ascii-dialog-cancel" class="btn btn-secondary" data-close>Cancel</button>
|
|
<button id="ascii-dialog-insert" class="btn btn-primary">Insert ASCII Art</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Step 2: Update renderer.js**
|
|
|
|
```javascript
|
|
let asciiModal = null;
|
|
|
|
// In DOMContentLoaded:
|
|
asciiModal = new ModalManager('#ascii-art-dialog');
|
|
```
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add src/index.html src/renderer.js
|
|
git commit -m "feat: convert ASCII art dialog to unified modal system"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 10: Convert Universal Converter Dialog
|
|
|
|
**Files:**
|
|
- Modify: `src/index.html` (lines 584-810)
|
|
- Modify: `src/renderer.js`
|
|
|
|
**Step 1: Update HTML structure**
|
|
|
|
```html
|
|
<!-- Universal File Converter Dialog -->
|
|
<div id="universal-converter-dialog" class="modal" role="dialog" aria-modal="true" aria-labelledby="converter-dialog-title">
|
|
<div class="modal-backdrop" data-close></div>
|
|
<div class="modal-content large">
|
|
<div class="modal-header">
|
|
<h3 id="converter-dialog-title">Universal File Converter</h3>
|
|
<button class="modal-close" id="converter-dialog-close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- Keep existing converter content -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="converter-dialog-cancel" class="btn btn-secondary" data-close>Cancel</button>
|
|
<button id="converter-dialog-convert" class="btn btn-primary">Convert</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Step 2: Update renderer.js**
|
|
|
|
```javascript
|
|
let converterModal = null;
|
|
|
|
// In DOMContentLoaded:
|
|
converterModal = new ModalManager('#universal-converter-dialog');
|
|
```
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add src/index.html src/renderer.js
|
|
git commit -m "feat: convert universal converter dialog to unified modal system"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 11: Convert Batch Dialog
|
|
|
|
**Files:**
|
|
- Modify: `src/index.html` (lines 812-879)
|
|
- Modify: `src/renderer.js`
|
|
|
|
**Step 1: Update HTML structure**
|
|
|
|
```html
|
|
<!-- Batch Conversion Dialog -->
|
|
<div id="batch-dialog" class="modal" role="dialog" aria-modal="true" aria-labelledby="batch-dialog-title">
|
|
<div class="modal-backdrop" data-close></div>
|
|
<div class="modal-content large">
|
|
<div class="modal-header">
|
|
<h3 id="batch-dialog-title">Batch Conversion</h3>
|
|
<button class="modal-close" id="batch-dialog-close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- Keep existing batch content -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="batch-dialog-cancel" class="btn btn-secondary" data-close>Cancel</button>
|
|
<button id="batch-dialog-start" class="btn btn-primary">Start Conversion</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Step 2: Update renderer.js**
|
|
|
|
```javascript
|
|
let batchModal = null;
|
|
|
|
// In DOMContentLoaded:
|
|
batchModal = new ModalManager('#batch-dialog');
|
|
|
|
// Replace showBatchDialog:
|
|
function showBatchDialog() {
|
|
batchModal.open();
|
|
}
|
|
|
|
// Replace hideBatchDialog:
|
|
function hideBatchDialog() {
|
|
batchModal.close();
|
|
}
|
|
```
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add src/index.html src/renderer.js
|
|
git commit -m "feat: convert batch dialog to unified modal system"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 12: Convert PDF Editor Dialog
|
|
|
|
**Files:**
|
|
- Modify: `src/index.html` (lines 881-1274)
|
|
- Modify: `src/renderer.js`
|
|
|
|
**Step 1: Update HTML structure**
|
|
|
|
```html
|
|
<!-- PDF Editor Dialog -->
|
|
<div id="pdf-editor-dialog" class="modal" role="dialog" aria-modal="true" aria-labelledby="pdf-editor-title">
|
|
<div class="modal-backdrop" data-close></div>
|
|
<div class="modal-content full">
|
|
<div class="modal-header">
|
|
<h3 id="pdf-editor-title">PDF Editor</h3>
|
|
<button class="modal-close" id="pdf-editor-dialog-close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="modal-body pdf-editor-body">
|
|
<!-- Keep existing PDF editor content -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="pdf-editor-cancel" class="btn btn-secondary" data-close>Cancel</button>
|
|
<button id="pdf-editor-process" class="btn btn-primary">Process PDF</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Step 2: Update renderer.js**
|
|
|
|
```javascript
|
|
let pdfEditorModal = null;
|
|
|
|
// In DOMContentLoaded:
|
|
pdfEditorModal = new ModalManager('#pdf-editor-dialog');
|
|
```
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add src/index.html src/renderer.js
|
|
git commit -m "feat: convert PDF editor dialog to unified modal system"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 13: Convert Header & Footer Dialog
|
|
|
|
**Files:**
|
|
- Modify: `src/index.html` (lines 1276-1368)
|
|
- Modify: `src/renderer.js`
|
|
|
|
**Step 1: Update HTML structure**
|
|
|
|
```html
|
|
<!-- Header & Footer Configuration Dialog -->
|
|
<div id="header-footer-dialog" class="modal" role="dialog" aria-modal="true" aria-labelledby="header-footer-title">
|
|
<div class="modal-backdrop" data-close></div>
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 id="header-footer-title">Header & Footer Configuration</h3>
|
|
<button class="modal-close" id="header-footer-close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- Keep existing header/footer content -->
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="header-footer-cancel" class="btn btn-secondary" data-close>Cancel</button>
|
|
<button id="header-footer-save" class="btn btn-primary">Save</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Step 2: Update renderer.js**
|
|
|
|
```javascript
|
|
let headerFooterModal = null;
|
|
|
|
// In DOMContentLoaded:
|
|
headerFooterModal = new ModalManager('#header-footer-dialog');
|
|
```
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add src/index.html src/renderer.js
|
|
git commit -m "feat: convert header & footer dialog to unified modal system"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 14: Convert Field Picker Dialog
|
|
|
|
**Files:**
|
|
- Modify: `src/index.html` (lines 1370-1440)
|
|
- Modify: `src/renderer.js`
|
|
|
|
**Step 1: Update HTML structure**
|
|
|
|
```html
|
|
<!-- Dynamic Field Picker Dialog -->
|
|
<div id="field-picker-dialog" class="modal" role="dialog" aria-modal="true" aria-labelledby="field-picker-title">
|
|
<div class="modal-backdrop" data-close></div>
|
|
<div class="modal-content small">
|
|
<div class="modal-header">
|
|
<h3 id="field-picker-title">Insert Field</h3>
|
|
<button class="modal-close" id="field-picker-close" aria-label="Close">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<!-- Keep existing field picker content -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**Step 2: Update renderer.js**
|
|
|
|
```javascript
|
|
let fieldPickerModal = null;
|
|
|
|
// In DOMContentLoaded:
|
|
fieldPickerModal = new ModalManager('#field-picker-dialog');
|
|
```
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add src/index.html src/renderer.js
|
|
git commit -m "feat: convert field picker dialog to unified modal system"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 15: Remove Old Dialog CSS
|
|
|
|
**Files:**
|
|
- Modify: `src/styles.css`
|
|
|
|
**Step 1: Remove old .export-dialog, .batch-dialog, .find-dialog CSS**
|
|
|
|
Find and remove these sections from `src/styles.css`:
|
|
- `.find-dialog` and related selectors (lines ~1244-1328)
|
|
- `.export-dialog` and related selectors (lines ~1332-1540)
|
|
- `.batch-dialog` and related selectors
|
|
|
|
**Step 2: Commit**
|
|
|
|
```bash
|
|
git add src/styles.css
|
|
git commit -m "refactor: remove old dialog CSS in favor of unified modal system"
|
|
```
|
|
|
|
---
|
|
|
|
## Task 16: Test All Modals
|
|
|
|
**Step 1: Run the application**
|
|
|
|
```bash
|
|
npm start
|
|
```
|
|
|
|
**Step 2: Verify each modal**
|
|
|
|
- [ ] Find dialog (Ctrl+F) - opens/closes, focus trap works
|
|
- [ ] Export dialog (Ctrl+E or File > Export) - all options visible
|
|
- [ ] Print preview (Ctrl+P) - renders correctly
|
|
- [ ] Table generator - inserts table
|
|
- [ ] ASCII art - generates art
|
|
- [ ] Universal converter - file selection works
|
|
- [ ] Batch conversion - file list works
|
|
- [ ] PDF editor - opens PDF
|
|
- [ ] Header & footer - saves settings
|
|
- [ ] Field picker - inserts fields
|
|
|
|
**Step 3: Test accessibility**
|
|
|
|
- [ ] Tab cycles within open modal
|
|
- [ ] Escape closes modal
|
|
- [ ] Click backdrop closes modal
|
|
- [ ] Focus returns to trigger element after close
|
|
|
|
---
|
|
|
|
## Task 17: Build Windows Release
|
|
|
|
**Files:**
|
|
- Run: build command
|
|
|
|
**Step 1: Build release**
|
|
|
|
```bash
|
|
npm run build:win
|
|
```
|
|
|
|
**Step 2: Verify artifacts**
|
|
|
|
```bash
|
|
ls -la dist/*.exe
|
|
```
|
|
|
|
Expected output:
|
|
- `dist/MarkdownConverter-Setup-4.0.0.exe`
|
|
- `dist/MarkdownConverter-4.0.0-x64.exe`
|
|
|
|
**Step 3: Final commit with version bump if needed**
|
|
|
|
```bash
|
|
git add -A
|
|
git commit -m "release: v4.0.0 with unified modal system"
|
|
git push origin master
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| Task | Description | Files Modified |
|
|
|------|-------------|----------------|
|
|
| 1 | Create ModalManager class | `src/utils/ModalManager.js` |
|
|
| 2 | Create modal CSS | `src/styles/modal.css` |
|
|
| 3 | Include modal CSS | `src/index.html` |
|
|
| 4 | Include ModalManager script | `src/index.html` |
|
|
| 5 | Convert find dialog | `src/index.html`, `src/renderer.js` |
|
|
| 6 | Convert export dialog | `src/index.html`, `src/renderer.js` |
|
|
| 7 | Convert print preview | `src/index.html`, `src/renderer.js` |
|
|
| 8 | Convert table generator | `src/index.html`, `src/renderer.js` |
|
|
| 9 | Convert ASCII art | `src/index.html`, `src/renderer.js` |
|
|
| 10 | Convert universal converter | `src/index.html`, `src/renderer.js` |
|
|
| 11 | Convert batch dialog | `src/index.html`, `src/renderer.js` |
|
|
| 12 | Convert PDF editor | `src/index.html`, `src/renderer.js` |
|
|
| 13 | Convert header/footer | `src/index.html`, `src/renderer.js` |
|
|
| 14 | Convert field picker | `src/index.html`, `src/renderer.js` |
|
|
| 15 | Remove old CSS | `src/styles.css` |
|
|
| 16 | Test all modals | - |
|
|
| 17 | Build release | - |
|