feat(calendar): pill badge links for persons and companies in event details
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Match NordaGPT chat visual style — person names show as green pill badges, company names as orange pill badges. Enriches event description with auto-linked companies (not just persons). Speaker name also uses pill badges. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
81c839ab5a
commit
4f44c59a80
@ -138,20 +138,32 @@ def _enrich_event_description(db, html):
|
||||
paragraphs = html.split('\n\n')
|
||||
html = ''.join(f'<p>{p.replace(chr(10), "<br>")}</p>' for p in paragraphs if p.strip())
|
||||
|
||||
# Build replacement maps
|
||||
# Build replacement maps — persons
|
||||
members = db.query(User.name, User.person_id).filter(
|
||||
User.person_id.isnot(None),
|
||||
User.name.isnot(None),
|
||||
).all()
|
||||
# Sort longest first to avoid partial matches
|
||||
members = sorted(members, key=lambda m: len(m.name), reverse=True)
|
||||
|
||||
member_map = {}
|
||||
person_map = {}
|
||||
for m in members:
|
||||
member_map[m.name] = flask_url_for('person_detail', person_id=m.person_id)
|
||||
person_map[m.name] = flask_url_for('person_detail', person_id=m.person_id)
|
||||
|
||||
# Build replacement maps — companies
|
||||
from database import Company
|
||||
companies = db.query(Company.name, Company.slug).filter(
|
||||
Company.slug.isnot(None),
|
||||
Company.name.isnot(None),
|
||||
Company.status == 'active',
|
||||
).all()
|
||||
companies = sorted(companies, key=lambda c: len(c.name), reverse=True)
|
||||
|
||||
company_map = {}
|
||||
for c in companies:
|
||||
company_map[c.name] = flask_url_for('company_detail', slug=c.slug)
|
||||
|
||||
def enrich_text_node(text):
|
||||
"""Apply member linking and URL linkification to a plain text fragment."""
|
||||
"""Apply person/company linking and URL linkification to a plain text fragment."""
|
||||
# 1. Auto-link URLs
|
||||
url_pattern = r'(https?://[^\s<>"\']+|(?<!\w)www\.[^\s<>"\']+|(?<!\w)nordabiznes\.pl[^\s<>"\']*)'
|
||||
def url_replacer(m):
|
||||
@ -160,10 +172,16 @@ def _enrich_event_description(db, html):
|
||||
return f'<a href="{href}" target="_blank" style="color:var(--primary);font-weight:500;">{url}</a>'
|
||||
text = re.sub(url_pattern, url_replacer, text)
|
||||
|
||||
# 2. Link member names (whole word match)
|
||||
for name, url in member_map.items():
|
||||
# 2. Link person names (pill badge — green)
|
||||
for name, url in person_map.items():
|
||||
pattern = r'\b' + re.escape(name) + r'\b'
|
||||
link = f'<a href="{url}" style="color:var(--primary);font-weight:600;text-decoration:none;border-bottom:1px dashed var(--primary);" title="Zobacz profil">{name}</a>'
|
||||
link = f'<a href="{url}" class="person-link" title="Zobacz profil">{name}</a>'
|
||||
text = re.sub(pattern, link, text)
|
||||
|
||||
# 3. Link company names (pill badge — orange)
|
||||
for name, url in company_map.items():
|
||||
pattern = r'\b' + re.escape(name) + r'\b'
|
||||
link = f'<a href="{url}" class="company-link" title="Zobacz firmę">{name}</a>'
|
||||
text = re.sub(pattern, link, text)
|
||||
|
||||
return text
|
||||
@ -229,9 +247,10 @@ def event(event_id):
|
||||
EventAttendee.user_id == current_user.id
|
||||
).first()
|
||||
|
||||
# Find speaker's person_id by matching name
|
||||
# Find speaker's person_id or company by matching name
|
||||
speaker_person_id = None
|
||||
from database import User
|
||||
speaker_company_slug = None
|
||||
from database import User, Company
|
||||
if event.speaker_name:
|
||||
speaker_user = db.query(User).filter(
|
||||
User.name == event.speaker_name,
|
||||
@ -239,8 +258,16 @@ def event(event_id):
|
||||
).first()
|
||||
if speaker_user:
|
||||
speaker_person_id = speaker_user.person_id
|
||||
else:
|
||||
# Try matching as company name
|
||||
speaker_company = db.query(Company).filter(
|
||||
Company.name == event.speaker_name,
|
||||
Company.status == 'active',
|
||||
).first()
|
||||
if speaker_company:
|
||||
speaker_company_slug = speaker_company.slug
|
||||
|
||||
# Enrich description: linkify member names and URLs
|
||||
# Enrich description: linkify member names, companies and URLs
|
||||
enriched_description = event.description or ''
|
||||
if enriched_description:
|
||||
enriched_description = _enrich_event_description(db, enriched_description)
|
||||
@ -249,6 +276,7 @@ def event(event_id):
|
||||
event=event,
|
||||
user_attending=user_attending,
|
||||
speaker_person_id=speaker_person_id,
|
||||
speaker_company_slug=speaker_company_slug,
|
||||
enriched_description=enriched_description,
|
||||
)
|
||||
finally:
|
||||
|
||||
@ -238,6 +238,40 @@
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* Pill badge links — spójne z NordaGPT chat */
|
||||
.person-link, .company-link {
|
||||
display: inline-block;
|
||||
padding: 2px 10px;
|
||||
margin: 1px 2px;
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.95em;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.person-link {
|
||||
background: #ecfdf5;
|
||||
color: #047857;
|
||||
}
|
||||
.person-link:hover {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(4, 120, 87, 0.2);
|
||||
}
|
||||
|
||||
.company-link {
|
||||
background: #fff7ed;
|
||||
color: #c2410c;
|
||||
}
|
||||
.company-link:hover {
|
||||
background: #ffedd5;
|
||||
color: #9a3412;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(194, 65, 12, 0.2);
|
||||
}
|
||||
|
||||
.speaker-link {
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
@ -422,7 +456,9 @@
|
||||
<div class="info-label">Prelegent</div>
|
||||
<div class="info-value">
|
||||
{% if speaker_person_id %}
|
||||
<a href="{{ url_for('person_detail', person_id=speaker_person_id) }}" class="speaker-link">{{ event.speaker_name }}</a>
|
||||
<a href="{{ url_for('person_detail', person_id=speaker_person_id) }}" class="person-link">{{ event.speaker_name }}</a>
|
||||
{% elif speaker_company_slug %}
|
||||
<a href="{{ url_for('company_detail', slug=speaker_company_slug) }}" class="company-link">{{ event.speaker_name }}</a>
|
||||
{% else %}
|
||||
{{ event.speaker_name }}
|
||||
{% endif %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user