show description preview with 3-line clamp in party edit

This commit is contained in:
Justin Edmund 2025-12-21 17:38:13 -08:00
parent 5e01e56dec
commit 37ca687a7d

View file

@ -191,23 +191,50 @@
paneStack.pop() paneStack.pop()
} }
function getDescriptionPreview(desc: string | null): string { interface JSONContent {
if (!desc) return '' type?: string
text?: string
content?: JSONContent[]
attrs?: Record<string, unknown>
}
/** Extract first non-empty paragraph from TipTap JSON content */
function getDescriptionPreview(desc: string | null): string | null {
if (!desc) return null
try { try {
const parsed = JSON.parse(desc) const parsed = JSON.parse(desc) as JSONContent
// Extract plain text from TipTap JSON if (parsed.type !== 'doc' || !parsed.content?.length) return null
const extractText = (node: { type?: string; text?: string; content?: unknown[] }): string => {
if (node.text) return node.text // Extract text from an inline node (text or mention)
if (node.content) return node.content.map(extractText).join('') const getNodeText = (node: JSONContent): string => {
if (node.type === 'text') return node.text ?? ''
if (node.type === 'mention') {
const id = node.attrs?.id as { name?: { en?: string }; granblue_en?: string } | undefined
return id?.name?.en ?? id?.granblue_en ?? ''
}
return '' return ''
} }
return extractText(parsed).slice(0, 50) || ''
// Extract text from a block
const getBlockText = (block: JSONContent): string =>
block.content?.map(getNodeText).join('') ?? ''
// Find first non-empty paragraph or heading
for (const node of parsed.content) {
if (node.type !== 'paragraph' && node.type !== 'heading') continue
const text = getBlockText(node).trim()
if (text) return text
}
return null
} catch { } catch {
// Legacy plain text // Legacy plain text - return first non-empty line
return desc.slice(0, 50) return desc.split('\n').map((l) => l.trim()).find(Boolean) ?? null
} }
} }
const descriptionPreview = $derived(getDescriptionPreview(description))
function openDescriptionPane() { function openDescriptionPane() {
paneStack.push({ paneStack.push({
id: 'edit-description', id: 'edit-description',
@ -237,20 +264,17 @@
<YouTubeUrlInput label="Video" bind:value={videoUrl} contained /> <YouTubeUrlInput label="Video" bind:value={videoUrl} contained />
</div> </div>
<DetailsSection title="Content"> <button type="button" class="description-button" onclick={openDescriptionPane}>
<DetailRow label="Description" noHover compact> <div class="description-header">
{#snippet children()} <span class="description-label">Description</span>
<button type="button" class="description-select-button" onclick={openDescriptionPane}> <Icon name="chevron-right" size={16} class="description-chevron" />
{#if description} </div>
<span class="description-preview">{getDescriptionPreview(description)}...</span> {#if descriptionPreview}
{:else} <p class="description-preview">{descriptionPreview}</p>
<span class="placeholder">Add description...</span> {:else}
{/if} <span class="description-placeholder">Add description...</span>
<Icon name="chevron-right" size={16} class="chevron-icon" /> {/if}
</button> </button>
{/snippet}
</DetailRow>
</DetailsSection>
<DetailsSection title="Battle"> <DetailsSection title="Battle">
<DetailRow label="Raid" noHover compact> <DetailRow label="Raid" noHover compact>
@ -325,8 +349,7 @@
padding: 0 $unit-2x; padding: 0 $unit-2x;
} }
.raid-select-button, .raid-select-button {
.description-select-button {
display: flex; display: flex;
align-items: center; align-items: center;
gap: $unit; gap: $unit;
@ -337,8 +360,7 @@
text-align: left; text-align: left;
} }
.raid-name, .raid-name {
.description-preview {
font-size: $font-regular; font-size: $font-regular;
font-weight: $medium; font-weight: $medium;
color: var(--text-secondary); color: var(--text-secondary);
@ -355,6 +377,53 @@
flex-shrink: 0; flex-shrink: 0;
} }
.description-button {
display: flex;
flex-direction: column;
gap: $unit-half;
margin: 0 $unit-2x;
padding: 0;
background: none;
border: none;
cursor: pointer;
text-align: left;
}
.description-header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.description-label {
font-size: $font-name;
font-weight: $medium;
color: var(--text-primary);
}
.description-preview {
margin: 0;
overflow: hidden;
font-size: $font-regular;
color: var(--text-secondary);
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
.description-placeholder {
font-size: $font-regular;
color: var(--text-tertiary);
font-style: italic;
}
.description-chevron {
color: var(--text-secondary);
flex-shrink: 0;
}
// Element-specific colors when raid is selected // Element-specific colors when raid is selected
.raid-select-button { .raid-select-button {
&.wind { &.wind {