| @@ -121,24 +121,18 @@ |
| 121 | </button> | 121 | </button> |
| 122 | </div> | 122 | </div> |
| 123 | | 123 | |
| 124 | - <form id="annotation-form" hx-post="/library/create" hx-target="#annotations-list" hx-swap="beforeend" | 124 | + <form id="comment-form" hx-post="/library/create" hx-target="#annotations-list" hx-swap="beforeend" |
| 125 | hx-on::after-request="this.reset()" | 125 | hx-on::after-request="this.reset()" |
| 126 | class="bg-spot-surface rounded-xl shadow-spot p-4 mb-4 space-y-3"> | 126 | class="bg-spot-surface rounded-xl shadow-spot p-4 mb-4 space-y-3"> |
| 127 | {{csrfInput .CSRFToken}} | 127 | {{csrfInput .CSRFToken}} |
| 128 | <input type="hidden" name="feed_url" value="{{.Article.FeedURL}}"> | 128 | <input type="hidden" name="feed_url" value="{{.Article.FeedURL}}"> |
| 129 | <input type="hidden" name="article_url" value="{{if .Article.URL.Valid}}{{.Article.URL.String}}{{end}}"> | 129 | <input type="hidden" name="article_url" value="{{if .Article.URL.Valid}}{{.Article.URL.String}}{{end}}"> |
| 130 | - <div id="quote-highlight" class="hidden mb-2"> | 130 | + <textarea name="note" rows="2" placeholder="Add a comment..." |
| 131 | - <blockquote id="quote-preview" class="border-l-2 border-spot-green pl-3 text-sm text-spot-secondary italic"></blockquote> | 131 | + class="w-full bg-spot-hover text-spot-text rounded-lg px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder resize-none"></textarea> |
| 132 | - </div> | | |
| 133 | - <input type="hidden" name="quote" id="quote-input"> | | |
| 134 | - <div> | | |
| 135 | - <textarea name="note" rows="2" placeholder="Add a note..." | | |
| 136 | - class="w-full bg-spot-hover text-spot-text rounded-lg px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder resize-none"></textarea> | | |
| 137 | - </div> | | |
| 138 | <div class="flex gap-2"> | 132 | <div class="flex gap-2"> |
| 139 | <input type="text" name="tags" placeholder="Tags (comma separated)" | 133 | <input type="text" name="tags" placeholder="Tags (comma separated)" |
| 140 | - class="flex-1 bg-spot-hover text-spot-text rounded-pill px-5 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder"> | 134 | + class="flex-1 min-w-0 bg-spot-hover text-spot-text rounded-pill px-5 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder"> |
| 141 | - <button type="submit" class="bg-spot-green text-white rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition">Annotate</button> | 135 | + <button type="submit" class="bg-spot-green text-white rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition shrink-0">Comment</button> |
| 142 | </div> | 136 | </div> |
| 143 | </form> | 137 | </form> |
| 144 | | 138 | |
| @@ -151,6 +145,31 @@ |
| 151 | </div> | 145 | </div> |
| 152 | </section> | 146 | </section> |
| 153 | | 147 | |
| | 148 | + <div id="annotation-popover" class="hidden fixed z-50 w-[22rem] max-w-[calc(100vw-2rem)] bg-spot-surface rounded-xl shadow-spot-heavy border border-spot-divider p-4 space-y-3"> |
| | 149 | + <div class="flex items-center justify-between"> |
| | 150 | + <span class="text-[10px] font-bold uppercase tracking-button text-spot-secondary">Annotate</span> |
| | 151 | + <button type="button" id="annotation-popover-close" aria-label="Close" class="text-spot-secondary hover:text-spot-text transition"> |
| | 152 | + <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg> |
| | 153 | + </button> |
| | 154 | + </div> |
| | 155 | + <form id="annotation-form" hx-post="/library/create" hx-target="#annotations-list" hx-swap="beforeend" class="space-y-3"> |
| | 156 | + {{csrfInput .CSRFToken}} |
| | 157 | + <input type="hidden" name="feed_url" value="{{.Article.FeedURL}}"> |
| | 158 | + <input type="hidden" name="article_url" value="{{if .Article.URL.Valid}}{{.Article.URL.String}}{{end}}"> |
| | 159 | + <div id="quote-highlight" class="hidden mb-1"> |
| | 160 | + <blockquote id="quote-preview" class="border-l-2 border-spot-green pl-3 text-sm text-spot-secondary italic"></blockquote> |
| | 161 | + </div> |
| | 162 | + <input type="hidden" name="quote" id="quote-input"> |
| | 163 | + <textarea name="note" rows="2" placeholder="Add a note..." |
| | 164 | + class="w-full bg-spot-hover text-spot-text rounded-lg px-4 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder resize-none"></textarea> |
| | 165 | + <div class="flex gap-2"> |
| | 166 | + <input type="text" name="tags" placeholder="Tags (comma separated)" |
| | 167 | + class="flex-1 min-w-0 bg-spot-hover text-spot-text rounded-pill px-5 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-spot-green placeholder:text-spot-placeholder"> |
| | 168 | + <button type="submit" class="bg-spot-green text-white rounded-pill px-5 py-2 text-sm font-bold uppercase tracking-button hover:brightness-110 transition shrink-0">Annotate</button> |
| | 169 | + </div> |
| | 170 | + </form> |
| | 171 | + </div> |
| | 172 | + |
| 154 | <div class="flex items-center justify-between mt-8 mb-4"> | 173 | <div class="flex items-center justify-between mt-8 mb-4"> |
| 155 | <a href="javascript:history.back()" class="text-sm text-spot-secondary hover:text-spot-text inline-flex items-center gap-1.5 transition"> | 174 | <a href="javascript:history.back()" class="text-sm text-spot-secondary hover:text-spot-text inline-flex items-center gap-1.5 transition"> |
| 156 | <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5"/></svg> | 175 | <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5"/></svg> |
| @@ -172,8 +191,53 @@ |
| 172 | var quoteInput = document.getElementById('quote-input'); | 191 | var quoteInput = document.getElementById('quote-input'); |
| 173 | var quotePreview = document.getElementById('quote-preview'); | 192 | var quotePreview = document.getElementById('quote-preview'); |
| 174 | var quoteHighlight = document.getElementById('quote-highlight'); | 193 | var quoteHighlight = document.getElementById('quote-highlight'); |
| | 194 | + var popover = document.getElementById('annotation-popover'); |
| | 195 | + var closeBtn = document.getElementById('annotation-popover-close'); |
| | 196 | + var noteField = form.querySelector('textarea[name="note"]'); |
| | 197 | + |
| | 198 | + function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); } |
| | 199 | + |
| | 200 | + function position(rect) { |
| | 201 | + var margin = 8; |
| | 202 | + var popRect = popover.getBoundingClientRect(); |
| | 203 | + var width = popRect.width; |
| | 204 | + var height = popRect.height; |
| | 205 | + |
| | 206 | + var left = clamp(rect.left + (rect.width - width) / 2, margin, document.documentElement.clientWidth - width - margin); |
| | 207 | + |
| | 208 | + var top = rect.bottom + margin; |
| | 209 | + if (document.documentElement.clientHeight - rect.bottom < height + margin && rect.top > height + margin) { |
| | 210 | + top = rect.top - height - margin; |
| | 211 | + } |
| | 212 | + top = Math.max(margin, top); |
| | 213 | + |
| | 214 | + popover.style.left = left + 'px'; |
| | 215 | + popover.style.top = top + 'px'; |
| | 216 | + } |
| | 217 | + |
| | 218 | + function openForSelection(sel, text) { |
| | 219 | + if (text.length > 1000) text = text.substring(0, 1000); |
| | 220 | + quoteInput.value = text; |
| | 221 | + quotePreview.textContent = text; |
| | 222 | + quoteHighlight.classList.remove('hidden'); |
| | 223 | + popover.classList.remove('hidden'); |
| | 224 | + position(sel.getRangeAt(0).getBoundingClientRect()); |
| | 225 | + noteField.focus(); |
| | 226 | + } |
| | 227 | + |
| | 228 | + function close() { |
| | 229 | + popover.classList.add('hidden'); |
| | 230 | + popover.style.top = ''; |
| | 231 | + popover.style.left = ''; |
| | 232 | + form.reset(); |
| | 233 | + quoteInput.value = ''; |
| | 234 | + quoteHighlight.classList.add('hidden'); |
| | 235 | + var sel = window.getSelection(); |
| | 236 | + if (sel) sel.removeAllRanges(); |
| | 237 | + } |
| 175 | | 238 | |
| 176 | document.addEventListener('mouseup', function(e) { | 239 | document.addEventListener('mouseup', function(e) { |
| | 240 | + if (!popover.classList.contains('hidden') && popover.contains(e.target)) return; |
| 177 | var sel = window.getSelection(); | 241 | var sel = window.getSelection(); |
| 178 | var text = sel.toString().trim(); | 242 | var text = sel.toString().trim(); |
| 179 | if (!text) return; | 243 | if (!text) return; |
| @@ -181,12 +245,20 @@ |
| 181 | var body = e.target.closest('.article-body'); | 245 | var body = e.target.closest('.article-body'); |
| 182 | if (!body) return; | 246 | if (!body) return; |
| 183 | | 247 | |
| 184 | - if (text.length > 1000) text = text.substring(0, 1000); | 248 | + openForSelection(sel, text); |
| 185 | - quoteInput.value = text; | 249 | + }); |
| 186 | - quotePreview.textContent = text; | 250 | + |
| 187 | - quoteHighlight.classList.remove('hidden'); | 251 | + closeBtn.addEventListener('click', close); |
| 188 | - form.scrollIntoView({ behavior: 'smooth', block: 'center' }); | 252 | + |
| 189 | - form.querySelector('textarea[name="note"]').focus(); | 253 | + document.addEventListener('mousedown', function(e) { |
| | 254 | + if (popover.classList.contains('hidden')) return; |
| | 255 | + if (popover.contains(e.target)) return; |
| | 256 | + if (e.target.closest('.article-body')) return; |
| | 257 | + close(); |
| | 258 | + }); |
| | 259 | + |
| | 260 | + document.addEventListener('keydown', function(e) { |
| | 261 | + if (e.key === 'Escape' && !popover.classList.contains('hidden')) close(); |
| 190 | }); | 262 | }); |
| 191 | | 263 | |
| 192 | var origReset = form.reset.bind(form); | 264 | var origReset = form.reset.bind(form); |
| @@ -196,6 +268,10 @@ |
| 196 | quoteHighlight.classList.add('hidden'); | 268 | quoteHighlight.classList.add('hidden'); |
| 197 | }; | 269 | }; |
| 198 | | 270 | |
| | 271 | + form.addEventListener('htmx:afterRequest', function(e) { |
| | 272 | + if (e.detail && e.detail.successful) close(); |
| | 273 | + }); |
| | 274 | + |
| 199 | function highlightQuotes() { | 275 | function highlightQuotes() { |
| 200 | var el = document.getElementById('annotation-quotes'); | 276 | var el = document.getElementById('annotation-quotes'); |
| 201 | if (!el) return; | 277 | if (!el) return; |