wip: magazine tile layout, film descriptions, dev script fixes
- MagazineGrid: New Yorker tile layout with wide/half/dark/banner tiles - Extract AI film descriptions into synopsis field - Fix section extraction to handle ### headers - Strip emojis from magazine titles and content - FilmDetail: conditional synopsis with "Why watch" header - FilmCard: synopsis preview for external films - Consolidate dev server to single script - Fix dev.sh for macOS bash 3.2 compatibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
c82b4ef54f
commit
2f27cb86c4
@@ -115,8 +115,8 @@ function addSection(
|
||||
content: string,
|
||||
seen: Set<string>,
|
||||
): void {
|
||||
const t = title.trim().replace(/\s+/g, ' ').slice(0, 150)
|
||||
const c = content.trim().replace(/\n{2,}/g, '\n\n').slice(0, MAGAZINE_CONTENT_MAX)
|
||||
const t = title.trim().replace(/[\p{Emoji_Presentation}\p{Extended_Pictographic}]\s*/gu, '').replace(/^#+\s*/, '').replace(/\s+/g, ' ').slice(0, 150)
|
||||
const c = content.trim().replace(/[\p{Emoji_Presentation}\p{Extended_Pictographic}]\s*/gu, '').replace(/\n{2,}/g, '\n\n').slice(0, MAGAZINE_CONTENT_MAX)
|
||||
if (t.length < 2 || c.length < 15) return
|
||||
const key = `${t.slice(0, 50)}`
|
||||
if (seen.has(key)) return
|
||||
@@ -143,11 +143,11 @@ function extractMagazineSections(text: string): MagazineSection[] {
|
||||
const seen = new Set<string>()
|
||||
const blockContents = new Set<string>() // avoid duplicate bullets already in ## blocks
|
||||
|
||||
// 1. ## Heading blocks: full content until next ## or **Section**
|
||||
const headingRe = /^##\s*[^\n]*?(?:⚡|🔥|📌|✨)?\s*(.+?)\n\n([\s\S]+?)(?=\n##\s|\n\*\*[^*]+\*\*[🟠🔴🟢]?|\n\nFor deeper|\n\nThis is being|\z)/gim
|
||||
// 1. ## or ### Heading blocks: full content until next heading or **Section**
|
||||
const headingRe = /^#{2,3}\s*[^\n]*?(?:⚡|🔥|📌|✨|📺|⭐|🔄|🎬|🎭|🎵|🎧|📖|💡|🔍|🌍|💭|🧠|🔬|📊|🏆|📝)?\s*(.+?)\n\n([\s\S]+?)(?=\n#{2,3}\s|\n\*\*[^*]+\*\*[🟠🔴🟢]?|\n\nFor deeper|\n\nThis is being|\n\n---|\n\n\*\*TL;DR|\z)/gim
|
||||
let m: RegExpExecArray | null
|
||||
while ((m = headingRe.exec(text)) !== null) {
|
||||
const title = m[1].trim().replace(/^[⚡🔥📌✨]\s*/, '').split(':')[0].trim()
|
||||
const title = m[1].trim().replace(/[\p{Emoji_Presentation}\p{Extended_Pictographic}]\s*/gu, '').split(':')[0].trim()
|
||||
const content = m[2].trim()
|
||||
if (content.length > 20) {
|
||||
blockContents.add(content)
|
||||
@@ -156,15 +156,15 @@ function extractMagazineSections(text: string): MagazineSection[] {
|
||||
}
|
||||
|
||||
// 2. **Pro/ Anti camp** blocks with their bullets
|
||||
const campRe = /\*\*([^*]+(?:camp| side| view)[^*]*)\*\*[🟠🔴🟢]?\s*\n([\s\S]+?)(?=\n\*\*[^*]+(?:camp| side)[^*]*\*\*|\n##\s|\n\nThis is|\n\nFor deeper|\z)/gim
|
||||
const campRe = /\*\*([^*]+(?:camp| side| view)[^*]*)\*\*[🟠🔴🟢]?\s*\n([\s\S]+?)(?=\n\*\*[^*]+(?:camp| side)[^*]*\*\*|\n#{2,3}\s|\n\nThis is|\n\nFor deeper|\z)/gim
|
||||
while ((m = campRe.exec(text)) !== null) {
|
||||
const title = m[1].trim()
|
||||
const content = m[2].trim()
|
||||
if (content.length > 15) addSection(sections, title, content, seen)
|
||||
}
|
||||
|
||||
// 3. Bullets: - **Title**: Content (skip if already in a ## block)
|
||||
const bulletRe = /^\s*[-•]\s*\*\*([^*]+)\*\*\s*[:\u2014\u2013–]\s*(.+?)(?=\n\s*[-•]\s*\*\*|\n\s*[-•]\s+\w|\n\n##\s|\n\*\*[^*]+\*\*[🟠🔴]?|\z)/gms
|
||||
// 3. Bullets: - **Title**: Content (skip if already in a ## or ### block)
|
||||
const bulletRe = /^\s*[-•]\s*\*\*([^*]+)\*\*\s*[:\u2014\u2013–]\s*(.+?)(?=\n\s*[-•]\s*\*\*|\n\s*[-•]\s+\w|\n\n#{2,3}\s|\n\*\*[^*]+\*\*[🟠🔴]?|\z)/gms
|
||||
while ((m = bulletRe.exec(text)) !== null) {
|
||||
const raw = m[2].trim().replace(/\n+/g, ' ')
|
||||
if (raw.length < 15) continue
|
||||
@@ -173,14 +173,14 @@ function extractMagazineSections(text: string): MagazineSection[] {
|
||||
}
|
||||
|
||||
// 4. - **Name** (Role) description — attributed quotes
|
||||
const attrRe = /^\s*[-•]\s*\*\*([^*]+)\*\*\s*\(([^)]+)\)\s+([^-\n].+?)(?=\n\s*[-•]|\n\*\*|\n##\s|\z)/gms
|
||||
const attrRe = /^\s*[-•]\s*\*\*([^*]+)\*\*\s*\(([^)]+)\)\s+([^-\n].+?)(?=\n\s*[-•]|\n\*\*|\n#{2,3}\s|\z)/gms
|
||||
while ((m = attrRe.exec(text)) !== null) {
|
||||
const content = m[3].trim().replace(/\n+/g, ' ')
|
||||
if (content.length > 20) addSection(sections, `${m[1].trim()} (${m[2].trim()})`, content, seen)
|
||||
}
|
||||
|
||||
// 5. Intro paragraph before first ## or ---
|
||||
const introMatch = /^([\s\S]+?)(?=\n##\s|\n---\s*\n|\n-\s+\*\*[^*]+\*\*\s*[:\u2014])/m.exec(text)
|
||||
// 5. Intro paragraph before first ## / ### or ---
|
||||
const introMatch = /^([\s\S]+?)(?=\n#{2,3}\s|\n---\s*\n|\n-\s+\*\*[^*]+\*\*\s*[:\u2014])/m.exec(text)
|
||||
if (introMatch) {
|
||||
const intro = introMatch[1].trim().replace(/^[#*_\s-]+/gm, '').trim()
|
||||
if (intro.length > 60 && !seen.has('Summary')) {
|
||||
@@ -415,6 +415,32 @@ export function useContentPanel() {
|
||||
.filter((f): f is Film => !!f)
|
||||
}
|
||||
|
||||
function extractDescriptionForTag(text: string, matchIndex: number, matchLength: number): string {
|
||||
const prevNewline = text.lastIndexOf('\n', matchIndex - 1)
|
||||
const lineStart = prevNewline === -1 ? 0 : prevNewline + 1
|
||||
const nextNewline = text.indexOf('\n', matchIndex + matchLength)
|
||||
const lineEnd = nextNewline === -1 ? text.length : nextNewline
|
||||
|
||||
let line = text.slice(lineStart, lineEnd)
|
||||
// Remove the tag itself
|
||||
line = line.replace(text.slice(matchIndex, matchIndex + matchLength), '')
|
||||
// Remove other content tags on the same line
|
||||
line = line.replace(/\[\[(?:film|song|podcast)(?:_ext)?:[^\]]*\]\]/g, '')
|
||||
// Remove leading bullet/list markers
|
||||
line = line.replace(/^\s*[-*•]\s*/, '').replace(/^\s*\d+\.\s*/, '')
|
||||
// Remove **Bold Title** followed by separator
|
||||
line = line.replace(/\*\*[^*]+\*\*\s*[-–—:]\s*/, '').replace(/\*\*[^*]+\*\*\s*/, '')
|
||||
// Remove stray markdown bold/italic
|
||||
line = line.replace(/\*\*/g, '').replace(/\*/g, '')
|
||||
// Remove parenthetical (Year) duplicating tag data
|
||||
line = line.replace(/\(\d{4}\)\s*/g, '')
|
||||
// Clean separators at edges
|
||||
line = line.replace(/^[\s\-–—:,]+/, '').replace(/[\s\-–—:,]+$/, '')
|
||||
|
||||
const result = line.trim().slice(0, 300)
|
||||
return result.length >= 10 ? result : ''
|
||||
}
|
||||
|
||||
function extractExternalFilms(text: string): Film[] {
|
||||
const films: Film[] = []
|
||||
const seen = new Set<string>()
|
||||
@@ -432,7 +458,7 @@ export function useContentPanel() {
|
||||
title,
|
||||
year,
|
||||
posterUrl: generatePosterFallback(title, year),
|
||||
synopsis: '',
|
||||
synopsis: extractDescriptionForTag(text, match.index, match[0].length),
|
||||
genres: [],
|
||||
rating: 0,
|
||||
runtime: 0,
|
||||
|
||||
Reference in New Issue
Block a user