feat(ui): deleted-annotation history on the book page
Replace the Notes & Highlights 'coming soon' stub with a real modal: active counts plus a 'Recently deleted' section listing every tombstoned highlight, note, and bookmark (type badge, deletion time in the user's timezone, text preview), each with Restore and Delete-permanently actions. Restore returns the annotation to every synced device; Delete permanently is confirmed before purging. The list is server-rendered from MediaDetail.DeletedAnnotations — no fetch on open. Alpine handlers in book-detail.ts call the new restore/purge endpoints and reload on success. style.css picks up the line-clamp utilities used by the text previews.
This commit is contained in:
1 parent
1f5c5a28bd
commit
f70579b4fc
6 files changed
+763
-421
No files matched your search
@@ -425,7 +425,7 @@ templ BookDetail(user User, book handlers.MediaDetail, errorMessage string) {
|
||||
}
|
||||
</div>
|
||||
@ProgressSyncModal(user, book)
|
||||
@NotesHighlightsModal(book)
|
||||
@NotesHighlightsModal(user, book)
|
||||
@MetadataEditorModal(book)
|
||||
@ErrorToast(errorMessage)
|
||||
</body>
|
||||
|
||||
@@ -128,25 +128,106 @@ templ ProgressSyncModal(user User, book handlers.MediaDetail) {
|
||||
</div>
|
||||
}
|
||||
|
||||
templ NotesHighlightsModal(book handlers.MediaDetail) {
|
||||
templ NotesHighlightsModal(user User, book handlers.MediaDetail) {
|
||||
<div
|
||||
id="notes-modal"
|
||||
class="hidden fixed inset-0 z-50 flex items-center justify-center p-4"
|
||||
class="hidden fixed inset-0 z-50 flex items-center justify-center overflow-y-auto p-4"
|
||||
style="background-color: var(--surface-overlay);"
|
||||
>
|
||||
<div
|
||||
class="card w-full max-w-2xl p-8 text-center"
|
||||
class="card w-full max-w-2xl my-8 flex flex-col max-h-[90vh]"
|
||||
style="box-shadow: var(--shadow-pop);"
|
||||
>
|
||||
<span class="inline-grid place-items-center h-14 w-14 rounded-2xl mb-4" style="background-color: var(--accent-muted); color: var(--accent);">
|
||||
@Icon("edit", "h-7 w-7")
|
||||
</span>
|
||||
<h2 class="text-2xl font-bold mb-2" style="color: var(--text-primary)">Notes & Highlights</h2>
|
||||
<p class="mb-2" style="color: var(--text-secondary);">
|
||||
This book has <strong>{ book.NotesCount }</strong> notes and <strong>{ book.HighlightsCount }</strong> highlights.
|
||||
</p>
|
||||
<p class="mb-6" style="color: var(--text-secondary);">Feature coming soon!</p>
|
||||
<div>
|
||||
<div class="flex justify-between items-center p-6 border-b flex-shrink-0" style="border-color: var(--border);">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="grid place-items-center h-10 w-10 rounded-xl shrink-0" style="background-color: var(--accent-muted); color: var(--accent);">
|
||||
@Icon("edit", "h-5 w-5")
|
||||
</span>
|
||||
<div>
|
||||
<h2 class="text-xl font-bold" style="color: var(--text-primary)">Notes & Highlights</h2>
|
||||
<p class="text-sm" style="color: var(--text-secondary);">{ book.Title }</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@click="hideNotesModal()"
|
||||
class="icon-btn"
|
||||
aria-label="Close"
|
||||
>
|
||||
@Icon("close", "h-5 w-5")
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-6 overflow-y-auto flex-1 min-h-0">
|
||||
<div class="flex items-center justify-center gap-6 mb-6">
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold" style="color: var(--accent);">{ book.HighlightsCount }</div>
|
||||
<div class="text-xs uppercase tracking-wide" style="color: var(--text-secondary);">Highlights</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold" style="color: var(--accent);">{ book.NotesCount }</div>
|
||||
<div class="text-xs uppercase tracking-wide" style="color: var(--text-secondary);">Notes</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-3xl font-bold" style="color: var(--text-secondary);">{ len(book.DeletedAnnotations) }</div>
|
||||
<div class="text-xs uppercase tracking-wide" style="color: var(--text-secondary);">Deleted</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if len(book.DeletedAnnotations) == 0 {
|
||||
<div class="text-center py-6">
|
||||
<p style="color: var(--text-secondary);">No deleted annotations. Highlights, notes, and bookmarks removed on the web or on a synced device appear here for recovery.</p>
|
||||
</div>
|
||||
} else {
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<h3 class="text-sm font-semibold uppercase tracking-wide" style="color: var(--text-secondary);">Recently deleted</h3>
|
||||
<span class="badge" style="background-color: var(--accent-muted); color: var(--accent);">{ len(book.DeletedAnnotations) }</span>
|
||||
</div>
|
||||
<p class="text-xs mb-4" style="color: var(--text-secondary);">
|
||||
Deleted entries are kept for the sync retention window before being removed automatically. Restore returns them to every synced device; Delete permanently removes them immediately.
|
||||
</p>
|
||||
<div class="space-y-3">
|
||||
for _, ann := range book.DeletedAnnotations {
|
||||
<div class="card p-4" data-deleted-annotation={ ann.ID }>
|
||||
<div class="flex justify-between items-start gap-3 mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="badge" style="background-color: var(--accent-muted); color: var(--accent);">{ ann.AnnotationType }</span>
|
||||
<span class="text-xs" style="color: var(--text-secondary);">
|
||||
{ FormatInTimezone(ann.DeletedAt, user.Timezone) }
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<button
|
||||
@click={"restoreDeletedAnnotation('" + ann.AnnotationType + "', '" + ann.ID + "')"}
|
||||
class="btn btn-secondary text-xs px-3 py-1.5"
|
||||
>
|
||||
@Icon("refresh", "h-3.5 w-3.5")
|
||||
Restore
|
||||
</button>
|
||||
<button
|
||||
@click={"purgeDeletedAnnotation('" + ann.AnnotationType + "', '" + ann.ID + "')"}
|
||||
class="btn btn-ghost text-xs px-3 py-1.5"
|
||||
style="color: var(--status-error);"
|
||||
>
|
||||
@Icon("close", "h-3.5 w-3.5")
|
||||
Delete permanently
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
if ann.DisplayText != "" {
|
||||
<p class="text-sm line-clamp-3" style="color: var(--text-primary);">{ ann.DisplayText }</p>
|
||||
} else {
|
||||
<p class="text-sm italic" style="color: var(--text-secondary);">(no text)</p>
|
||||
}
|
||||
if ann.SecondaryText != "" {
|
||||
<p class="text-xs mt-1 line-clamp-2" style="color: var(--text-secondary);">{ ann.SecondaryText }</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end p-6 border-t flex-shrink-0" style="border-color: var(--border);">
|
||||
<button
|
||||
@click="hideNotesModal()"
|
||||
class="btn btn-primary"
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1336,7 +1336,7 @@ func BookDetail(user User, book handlers.MediaDetail, errorMessage string) templ
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = NotesHighlightsModal(book).Render(ctx, templ_7745c5c3_Buffer)
|
||||
templ_7745c5c3_Err = NotesHighlightsModal(user, book).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
@@ -116,6 +116,8 @@ interface MetadataEditorState {
|
||||
clearRating(): Promise<void>;
|
||||
toggleRead(read: boolean): Promise<void>;
|
||||
resolveConflict(conflictId: string, winner: string): Promise<void>;
|
||||
restoreDeletedAnnotation(annotationType: string, annotationId: string): Promise<void>;
|
||||
purgeDeletedAnnotation(annotationType: string, annotationId: string): Promise<void>;
|
||||
}
|
||||
|
||||
Alpine.data("bookDetail", () => {
|
||||
@@ -194,6 +196,65 @@ Alpine.data("bookDetail", () => {
|
||||
}
|
||||
},
|
||||
|
||||
async restoreDeletedAnnotation(annotationType: string, annotationId: string) {
|
||||
const mediaId = getMediaId();
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`/api/media-items/${mediaId}/annotations/${annotationId}/restore`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: getAuthHeader(),
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ annotation_type: annotationType }),
|
||||
},
|
||||
);
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json().catch(() => ({}));
|
||||
throw new Error(err.error || "Failed to restore annotation");
|
||||
}
|
||||
showToast("Annotation restored", "success");
|
||||
setTimeout(() => window.location.reload(), 500);
|
||||
} catch (e) {
|
||||
showToast(
|
||||
e instanceof Error ? e.message : "Failed to restore annotation",
|
||||
"error",
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
async purgeDeletedAnnotation(annotationType: string, annotationId: string) {
|
||||
if (
|
||||
!confirm(
|
||||
"Permanently delete this annotation? This cannot be undone and it will not reappear on any device.",
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const mediaId = getMediaId();
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`/api/media-items/${mediaId}/annotations/${annotationId}?annotation_type=${encodeURIComponent(annotationType)}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: { Authorization: getAuthHeader() },
|
||||
},
|
||||
);
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json().catch(() => ({}));
|
||||
throw new Error(err.error || "Failed to delete annotation");
|
||||
}
|
||||
showToast("Annotation permanently deleted", "success");
|
||||
setTimeout(() => window.location.reload(), 500);
|
||||
} catch (e) {
|
||||
showToast(
|
||||
e instanceof Error ? e.message : "Failed to delete annotation",
|
||||
"error",
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
init() {
|
||||
const ratingAttr = document.body.getAttribute("data-rating");
|
||||
this.userRating = ratingAttr ? parseInt(ratingAttr, 10) || 0 : 0;
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }
|
||||
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Noto Sans SC,Noto Sans TC,Noto Sans JP,Noto Sans KR,PingFang SC,Microsoft YaHei,Hiragino Sans,Apple SD Gothic Neo,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}input:where(:not([type])),input:where([type=date]),input:where([type=datetime-local]),input:where([type=email]),input:where([type=month]),input:where([type=number]),input:where([type=password]),input:where([type=search]),input:where([type=tel]),input:where([type=text]),input:where([type=time]),input:where([type=url]),input:where([type=week]),select,select:where([multiple]),textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}input:where(:not([type])):focus,input:where([type=date]):focus,input:where([type=datetime-local]):focus,input:where([type=email]):focus,input:where([type=month]):focus,input:where([type=number]):focus,input:where([type=password]):focus,input:where([type=search]):focus,input:where([type=tel]):focus,input:where([type=text]):focus,input:where([type=time]):focus,input:where([type=url]):focus,input:where([type=week]):focus,select:focus,select:where([multiple]):focus,textarea:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath Line truncated
|
||||
/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Noto Sans SC,Noto Sans TC,Noto Sans JP,Noto Sans KR,PingFang SC,Microsoft YaHei,Hiragino Sans,Apple SD Gothic Neo,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}input:where(:not([type])),input:where([type=date]),input:where([type=datetime-local]),input:where([type=email]),input:where([type=month]),input:where([type=number]),input:where([type=password]),input:where([type=search]),input:where([type=tel]),input:where([type=text]),input:where([type=time]),input:where([type=url]),input:where([type=week]),select,select:where([multiple]),textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}input:where(:not([type])):focus,input:where([type=date]):focus,input:where([type=datetime-local]):focus,input:where([type=email]):focus,input:where([type=month]):focus,input:where([type=number]):focus,input:where([type=password]):focus,input:where([type=search]):focus,input:where([type=tel]):focus,input:where([type=text]):focus,input:where([type=time]):focus,input:where([type=url]):focus,input:where([type=week]):focus,select:focus,select:where([multiple]):focus,textarea:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath Line truncated
|
||||
Reference in new issue
Block a user