show description preview with 3-line clamp in party edit
This commit is contained in:
parent
5e01e56dec
commit
37ca687a7d
1 changed files with 97 additions and 28 deletions
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue