feat(playwright): integrate Playwright for end-to-end testing and update .gitignore
- Added Playwright as a development dependency for end-to-end testing. - Updated package.json to include test scripts for Playwright. - Enhanced .gitignore to exclude Playwright test results and cache files. - Improved content extraction logic in various components to handle new content types. Made-with: Cursor
This commit is contained in:
@@ -66,9 +66,15 @@ function isWebsitesLikeResponse(text: string): boolean {
|
||||
|
||||
function extractUrlFromText(text: string): string | undefined {
|
||||
const mdLink = /\[([^\]]*)\]\((https?:\/\/[^)]+)\)/.exec(text)
|
||||
if (mdLink) return mdLink[2]
|
||||
const bare = /(https?:\/\/[^\s)\]\"'<>]+)/.exec(text)
|
||||
return bare ? bare[1] : undefined
|
||||
const raw = mdLink ? mdLink[2] : (/(https?:\/\/[^\s)\]\"'<>]+)/.exec(text)?.[1])
|
||||
if (!raw?.trim()) return undefined
|
||||
try {
|
||||
const u = new URL(raw.trim())
|
||||
if (!/^https?:$/i.test(u.protocol)) return undefined
|
||||
return u.href
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
function extractAuthorFromText(text: string): string | undefined {
|
||||
@@ -90,9 +96,15 @@ function extractAuthorFromText(text: string): string | undefined {
|
||||
|
||||
function extractFirstImageFromText(text: string): string | undefined {
|
||||
const mdImg = /!\[[^\]]*\]\((https?:\/\/[^)]+)\)/.exec(text)
|
||||
if (mdImg) return mdImg[1]
|
||||
const ext = /(https?:\/\/[^\s)\]\"'<>]+\.(?:jpg|jpeg|png|gif|webp)(?:\?[^\s)\]]*)?)/i.exec(text)
|
||||
return ext ? ext[1] : undefined
|
||||
const raw = mdImg ? mdImg[1] : (/(https?:\/\/[^\s)\]\"'<>]+\.(?:jpg|jpeg|png|gif|webp)(?:\?[^\s)\]]*)?)/i.exec(text)?.[1])
|
||||
if (!raw?.trim()) return undefined
|
||||
try {
|
||||
const u = new URL(raw.trim())
|
||||
if (!/^https?:$/i.test(u.protocol)) return undefined
|
||||
return u.href
|
||||
} catch {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const MAGAZINE_CONTENT_MAX = 2000
|
||||
@@ -109,12 +121,19 @@ function addSection(
|
||||
const key = `${t.slice(0, 50)}`
|
||||
if (seen.has(key)) return
|
||||
seen.add(key)
|
||||
const imgMatch = /!\[[^\]]*\]\((https?:\/\/[^)]+)\)/.exec(content)?.[1]
|
||||
const imageUrl = imgMatch ? (() => {
|
||||
try {
|
||||
const u = new URL(imgMatch.trim())
|
||||
return /^https?:$/i.test(u.protocol) ? u.href : undefined
|
||||
} catch { return undefined }
|
||||
})() : undefined
|
||||
sections.push({
|
||||
title: t,
|
||||
content: c,
|
||||
url: extractUrlFromText(content),
|
||||
author: extractAuthorFromText(content),
|
||||
imageUrl: /!\[[^\]]*\]\((https?:\/\/[^)]+)\)/.exec(content)?.[1],
|
||||
imageUrl,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -315,6 +334,23 @@ function looksLikeSong(title: string, artist: string): boolean {
|
||||
}
|
||||
const PODCAST_TAG_RE = /\[\[podcast:(p?\d+)\]\]/gi
|
||||
const PODCAST_EXT_RE = /\[\[podcast_ext:([^|]+)\|([^|]+)(?:\|(\d{4}))?\]\]/gi
|
||||
|
||||
/** Reject obvious non-podcast phrases (documentation, mailing lists, etc.) */
|
||||
function looksLikePodcast(title: string, host: string): boolean {
|
||||
const t = title.toLowerCase()
|
||||
const h = host.toLowerCase()
|
||||
const bad = [
|
||||
'bitcoin mailing list', 'mailing list', 'developer mailing list', 'gnusha.org',
|
||||
'canonical source', 'formal dev', 'github', 'stackexchange', 'reddit', 'twitter',
|
||||
'latest news', 'protocol updates', 'web search', 'training cutoff',
|
||||
'documentation', 'bip discussion', 'bip 110', 'bitcoin bips',
|
||||
]
|
||||
for (const phrase of bad) {
|
||||
if (t.includes(phrase) || h.includes(phrase)) return false
|
||||
}
|
||||
if (t.length > 80 || h.length > 50) return false
|
||||
return true
|
||||
}
|
||||
const MARKDOWN_LINK_RE = /\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g
|
||||
|
||||
const SAFE_URL_SCHEME = /^https?:\/\//i
|
||||
@@ -573,6 +609,7 @@ export function useContentPanel() {
|
||||
while ((match = re.exec(text)) !== null) {
|
||||
const title = match[1].trim()
|
||||
const host = match[2].trim()
|
||||
if (!looksLikePodcast(title, host)) continue
|
||||
const year = match[3] ? parseInt(match[3], 10) : undefined
|
||||
const key = `${title.toLowerCase()}|${host.toLowerCase()}`
|
||||
if (seen.has(key)) continue
|
||||
@@ -620,8 +657,8 @@ export function useContentPanel() {
|
||||
const hasNews = (webResults.length > 0 || mergedWebsites.length > 0) && newsContext
|
||||
const mergedNews = hasNews ? mergeNewsResults(webResults, panelRssArticles.value) : []
|
||||
|
||||
// Fetch RSS from website URLs to surface actual articles in News
|
||||
if (mergedWebsites.length > 0) {
|
||||
// Fetch RSS from website URLs only when news context — avoid surfacing RSS from docs/resource links
|
||||
if (mergedWebsites.length > 0 && newsContext) {
|
||||
const urls = mergedWebsites.map((w) => w.url)
|
||||
fetchRssFromUrls(urls).then((articles) => {
|
||||
if (articles.length === 0) return
|
||||
|
||||
Reference in New Issue
Block a user