|
26 | 26 | margin-bottom: 20px; |
27 | 27 | } |
28 | 28 |
|
| 29 | +.writing-section { |
| 30 | + margin-bottom: 30px; |
| 31 | + background: white; |
| 32 | + padding: 20px; |
| 33 | + border-radius: 4px; |
| 34 | + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
| 35 | +} |
| 36 | + |
29 | 37 | .writing-area { |
30 | 38 | width: 100%; |
31 | | - min-height: 400px; |
| 39 | + min-height: 200px; |
32 | 40 | padding: 20px; |
33 | 41 | margin-bottom: 15px; |
34 | 42 | border: 1px solid #ccc; |
|
39 | 47 | resize: vertical; |
40 | 48 | } |
41 | 49 |
|
| 50 | +.controls { |
| 51 | + display: flex; |
| 52 | + justify-content: space-between; |
| 53 | + align-items: center; |
| 54 | +} |
| 55 | + |
42 | 56 | .stats { |
43 | 57 | color: #666; |
44 | 58 | font-size: 14px; |
|
49 | 63 | font-style: italic; |
50 | 64 | margin-left: 15px; |
51 | 65 | } |
| 66 | + |
| 67 | +.add-btn { |
| 68 | + background-color: #4CAF50; |
| 69 | + color: white; |
| 70 | + padding: 8px 16px; |
| 71 | + border: none; |
| 72 | + border-radius: 4px; |
| 73 | + font-size: 14px; |
| 74 | + cursor: pointer; |
| 75 | + transition: background-color 0.2s; |
| 76 | +} |
| 77 | + |
| 78 | +.add-btn:hover { |
| 79 | + background-color: #45a049; |
| 80 | +} |
| 81 | + |
| 82 | +.remove-btn { |
| 83 | + background-color: #ff4444; |
| 84 | + color: white; |
| 85 | + padding: 8px 16px; |
| 86 | + border: none; |
| 87 | + border-radius: 4px; |
| 88 | + font-size: 14px; |
| 89 | + cursor: pointer; |
| 90 | + transition: background-color 0.2s; |
| 91 | +} |
| 92 | + |
| 93 | +.remove-btn:hover { |
| 94 | + background-color: #ff6666; |
| 95 | +} |
52 | 96 | </style> |
53 | 97 | </head> |
54 | 98 | <body> |
55 | 99 | <div class="container"> |
56 | 100 | <h1>Word counter</h1> |
57 | | - <textarea class="writing-area" placeholder="Start writing here..."></textarea> |
58 | | - <div class="stats"> |
59 | | - Words: <span id="wordCount">0</span> |
60 | | - <span class="save-status" id="saveStatus"></span> |
61 | | - </div> |
| 101 | + <div id="writing-container"></div> |
| 102 | + <button class="add-btn" id="addSection">Add new section</button> |
62 | 103 | </div> |
63 | 104 |
|
64 | 105 | <script type="module"> |
65 | | -const STORAGE_KEY = 'writing-content' |
66 | | -const textarea = document.querySelector('.writing-area') |
67 | | -const wordCountElement = document.getElementById('wordCount') |
68 | | -const saveStatus = document.getElementById('saveStatus') |
| 106 | +const STORAGE_KEY = 'writing-sections' |
| 107 | +const container = document.getElementById('writing-container') |
| 108 | +const addButton = document.getElementById('addSection') |
| 109 | + |
| 110 | +let saveTimeouts = new Map() |
69 | 111 |
|
70 | | -let saveTimeout |
| 112 | +function generateId() { |
| 113 | + return Date.now().toString(36) + Math.random().toString(36).substr(2) |
| 114 | +} |
| 115 | + |
| 116 | +function createSection(id, content = '') { |
| 117 | + const section = document.createElement('div') |
| 118 | + section.className = 'writing-section' |
| 119 | + section.dataset.id = id |
| 120 | + |
| 121 | + section.innerHTML = ` |
| 122 | + <textarea class="writing-area" placeholder="Start writing here...">${content}</textarea> |
| 123 | + <div class="controls"> |
| 124 | + <div class="stats"> |
| 125 | + Words: <span class="word-count">0</span> |
| 126 | + <span class="save-status"></span> |
| 127 | + </div> |
| 128 | + <button class="remove-btn">Remove</button> |
| 129 | + </div> |
| 130 | + ` |
| 131 | + |
| 132 | + const textarea = section.querySelector('textarea') |
| 133 | + const wordCount = section.querySelector('.word-count') |
| 134 | + const saveStatus = section.querySelector('.save-status') |
| 135 | + const removeBtn = section.querySelector('.remove-btn') |
| 136 | + |
| 137 | + updateWordCount(textarea, wordCount) |
| 138 | + |
| 139 | + textarea.addEventListener('input', () => { |
| 140 | + updateWordCount(textarea, wordCount) |
| 141 | + debouncedSave() |
| 142 | + }) |
| 143 | + |
| 144 | + removeBtn.addEventListener('click', () => { |
| 145 | + section.remove() |
| 146 | + debouncedSave() |
| 147 | + }) |
| 148 | + |
| 149 | + return section |
| 150 | +} |
71 | 151 |
|
72 | 152 | function countWords(text) { |
73 | 153 | return text.trim() ? text.trim().split(/\s+/).length : 0 |
74 | 154 | } |
75 | 155 |
|
76 | | -function updateWordCount() { |
| 156 | +function updateWordCount(textarea, wordCountElement) { |
77 | 157 | const count = countWords(textarea.value) |
78 | 158 | wordCountElement.textContent = count |
79 | 159 | } |
80 | 160 |
|
81 | | -function showSavedStatus() { |
82 | | - saveStatus.textContent = 'Saved' |
83 | | - setTimeout(() => { |
84 | | - saveStatus.textContent = '' |
85 | | - }, 2000) |
86 | | -} |
87 | | - |
88 | 161 | function saveToLocalStorage() { |
89 | | - localStorage.setItem(STORAGE_KEY, textarea.value) |
90 | | - showSavedStatus() |
| 162 | + const sectionsData = Array.from(container.children).map(section => ({ |
| 163 | + id: section.dataset.id, |
| 164 | + content: section.querySelector('textarea').value |
| 165 | + })) |
| 166 | + localStorage.setItem(STORAGE_KEY, JSON.stringify(sectionsData)) |
| 167 | + |
| 168 | + // Update save status for all sections |
| 169 | + document.querySelectorAll('.save-status').forEach(status => { |
| 170 | + status.textContent = 'Saved' |
| 171 | + setTimeout(() => { |
| 172 | + status.textContent = '' |
| 173 | + }, 2000) |
| 174 | + }) |
91 | 175 | } |
92 | 176 |
|
93 | 177 | function debouncedSave() { |
94 | | - clearTimeout(saveTimeout) |
95 | | - saveStatus.textContent = 'Saving...' |
96 | | - saveTimeout = setTimeout(saveToLocalStorage, 1000) |
| 178 | + // Clear any existing save timeout |
| 179 | + if (saveTimeouts.has('save')) { |
| 180 | + clearTimeout(saveTimeouts.get('save')) |
| 181 | + } |
| 182 | + |
| 183 | + // Show 'Saving...' status |
| 184 | + document.querySelectorAll('.save-status').forEach(status => { |
| 185 | + status.textContent = 'Saving...' |
| 186 | + }) |
| 187 | + |
| 188 | + // Set new save timeout |
| 189 | + saveTimeouts.set('save', setTimeout(() => { |
| 190 | + saveTimeouts.delete('save') |
| 191 | + saveToLocalStorage() |
| 192 | + }, 1000)) |
97 | 193 | } |
98 | 194 |
|
99 | 195 | // Load saved content |
100 | 196 | const savedContent = localStorage.getItem(STORAGE_KEY) |
101 | 197 | if (savedContent) { |
102 | | - textarea.value = savedContent |
103 | | - updateWordCount() |
| 198 | + const savedSections = JSON.parse(savedContent) |
| 199 | + savedSections.forEach(section => { |
| 200 | + const newSection = createSection(section.id, section.content) |
| 201 | + container.appendChild(newSection) |
| 202 | + }) |
104 | 203 | } |
105 | 204 |
|
106 | | -// Event listeners |
107 | | -textarea.addEventListener('input', () => { |
108 | | - updateWordCount() |
109 | | - debouncedSave() |
110 | | -}) |
111 | | - |
112 | | -textarea.addEventListener('paste', () => { |
113 | | - setTimeout(updateWordCount, 0) |
| 205 | +// Add new section button handler |
| 206 | +addButton.addEventListener('click', () => { |
| 207 | + const newSection = createSection(generateId()) |
| 208 | + container.appendChild(newSection) |
| 209 | + newSection.querySelector('textarea').focus() |
114 | 210 | }) |
115 | 211 | </script> |
116 | 212 | </body> |
|
0 commit comments