Compare commits
No commits in common. '3ba8c3340a5dad120c70465b21996f04c7dc9fef' and 'e039a496e487c91ac57dd538f619b3ec21e5655e' have entirely different histories.
3ba8c3340a
...
e039a496e4
13 changed files with 140 additions and 2068 deletions
@ -1,212 +0,0 @@ |
|||||||
import { useQuery } from "@tanstack/react-query"; |
|
||||||
import { useParams, Link, useNavigate } from "react-router-dom"; |
|
||||||
import { fetchTalk } from "../api"; |
|
||||||
|
|
||||||
/** |
|
||||||
* TalkView component displays a single talk with full details. |
|
||||||
*
|
|
||||||
* It shows: |
|
||||||
* - Speaker information with photo |
|
||||||
* - Talk metadata (date, debate type, etc.) |
|
||||||
* - Full text of the speech |
|
||||||
* - Links to related resources |
|
||||||
*/ |
|
||||||
export function TalkView() { |
|
||||||
const { id } = useParams<{ id: string }>(); |
|
||||||
const navigate = useNavigate(); |
|
||||||
|
|
||||||
const { data: talk, isLoading, error } = useQuery({ |
|
||||||
queryKey: ["talk", id], |
|
||||||
queryFn: () => fetchTalk(id!), |
|
||||||
enabled: !!id, |
|
||||||
}); |
|
||||||
|
|
||||||
if (isLoading) { |
|
||||||
return ( |
|
||||||
<div className="talk-view"> |
|
||||||
<div className="panel"> |
|
||||||
<p>Laddar anförande...</p> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
if (error) { |
|
||||||
return ( |
|
||||||
<div className="talk-view"> |
|
||||||
<div className="panel error-banner"> |
|
||||||
Kunde inte ladda anförande: {error.message} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
if (!talk) { |
|
||||||
return ( |
|
||||||
<div className="talk-view"> |
|
||||||
<div className="panel"> |
|
||||||
<p>Anförande hittades inte.</p> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
// Fix image URLs from http to https
|
|
||||||
const imageUrl = talk.person?.bild_url_192?.replace('http://', 'https://'); |
|
||||||
|
|
||||||
const previousTalk = talk.navigation?.previous ?? null; |
|
||||||
const nextTalk = talk.navigation?.next ?? null; |
|
||||||
|
|
||||||
|
|
||||||
// Normalise database IDs so router links stay consistent.
|
|
||||||
const normalizeTalkId = (rawId?: string | null): string | null => { |
|
||||||
if (!rawId) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
// Handle both object with _id property and plain string
|
|
||||||
const idString = typeof rawId === 'object' && rawId !== null && '_id' in rawId
|
|
||||||
? (rawId as any)._id
|
|
||||||
: String(rawId); |
|
||||||
const result = idString.startsWith("talks/") ? idString.slice(6) : idString; |
|
||||||
return result; |
|
||||||
}; |
|
||||||
const previousId = normalizeTalkId(previousTalk); |
|
||||||
const nextId = normalizeTalkId(nextTalk); |
|
||||||
|
|
||||||
// Use browser history when possible so the search page restores prior state.
|
|
||||||
const handleBackClick = () => { |
|
||||||
navigate("/"); |
|
||||||
}; |
|
||||||
|
|
||||||
return ( |
|
||||||
<div className="talk-view"> |
|
||||||
{/* Navigation row: previous, back, next */} |
|
||||||
<div className="talk-view__navRow"> |
|
||||||
{/* Left: Föregående tal */} |
|
||||||
<div className="talk-view__navCell talk-view__navCell--left talk-view__navCell--minwidth"> |
|
||||||
{previousId && previousTalk ? ( |
|
||||||
<Link to={`/talk/${previousId}`} className="secondary-button talk-view__navButton"> |
|
||||||
{/* Use fontSize 1em for arrow so button height matches others */} |
|
||||||
<span aria-hidden="true" style={{ fontSize: "1em", marginRight: "0.3em" }}>←</span> |
|
||||||
Föregående i protokollet |
|
||||||
</Link> |
|
||||||
) : ( |
|
||||||
<span className="secondary-button talk-view__navButton" style={{ visibility: "hidden" }}> </span> |
|
||||||
)} |
|
||||||
</div> |
|
||||||
{/* Center: Tillbaka till sökresultat */} |
|
||||||
<div className="talk-view__navCell talk-view__navCell--center talk-view__navCell--minwidth"> |
|
||||||
<button |
|
||||||
type="button" |
|
||||||
className="secondary-button talk-view__navButton" |
|
||||||
onClick={handleBackClick} |
|
||||||
> |
|
||||||
Tillbaka till sökresultat |
|
||||||
</button> |
|
||||||
</div> |
|
||||||
{/* Right: Nästa tal */} |
|
||||||
<div className="talk-view__navCell talk-view__navCell--right talk-view__navCell--minwidth"> |
|
||||||
{nextId && nextTalk ? ( |
|
||||||
<Link to={`/talk/${nextId}`} className="secondary-button talk-view__navButton"> |
|
||||||
Nästa i protokollet |
|
||||||
{/* Use fontSize 1em for arrow so button height matches others */} |
|
||||||
<span aria-hidden="true" style={{ fontSize: "1em", marginLeft: "0.3em" }}>→</span> |
|
||||||
</Link> |
|
||||||
) : ( |
|
||||||
<span className="secondary-button talk-view__navButton" style={{ visibility: "hidden" }}> </span> |
|
||||||
)} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
{/* Speaker card */} |
|
||||||
<div className="panel talk-view__speaker"> |
|
||||||
{imageUrl && ( |
|
||||||
<img
|
|
||||||
src={imageUrl}
|
|
||||||
alt={talk.talare} |
|
||||||
className="talk-view__speaker-photo" |
|
||||||
/> |
|
||||||
)} |
|
||||||
<div className="talk-view__speaker-info"> |
|
||||||
<div className="talk-view__speaker-row"> |
|
||||||
<h1>{talk.talare}</h1> |
|
||||||
<span className="party-chip" data-party={talk.parti ?? ""}> |
|
||||||
{talk.parti} |
|
||||||
</span> |
|
||||||
</div> |
|
||||||
<div className="talk-view__speaker-meta"> |
|
||||||
{/* Show valkrets if available */} |
|
||||||
{talk.person?.valkrets && ( |
|
||||||
<span className="talk-view__speaker-detail"> |
|
||||||
{talk.person.valkrets} |
|
||||||
</span> |
|
||||||
)} |
|
||||||
{/* Show status if available */} |
|
||||||
{talk.person?.status && ( |
|
||||||
<span className="talk-view__speaker-detail"> |
|
||||||
{talk.person.status} |
|
||||||
</span> |
|
||||||
)} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
{/* Talk metadata */} |
|
||||||
<div className="panel talk-view__metadata"> |
|
||||||
<dl className="talk-view__meta-grid"> |
|
||||||
<dt>Datum</dt> |
|
||||||
<dd>{talk.datum}</dd> |
|
||||||
|
|
||||||
<dt>Debattyp</dt> |
|
||||||
<dd>{talk.kammaraktivitet}</dd> |
|
||||||
|
|
||||||
<dt>Rubrik</dt> |
|
||||||
<dd>{talk.avsnittsrubrik}</dd> |
|
||||||
|
|
||||||
{talk.titel && ( |
|
||||||
<> |
|
||||||
<dt>Protokoll</dt> |
|
||||||
<dd>{talk.titel}</dd> |
|
||||||
</> |
|
||||||
)} |
|
||||||
</dl> |
|
||||||
</div> |
|
||||||
|
|
||||||
{/* Talk text */} |
|
||||||
<div className="panel talk-view__text"> |
|
||||||
<div className="talk-view__content"> |
|
||||||
{talk.anforandetext} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
{/* Links */} |
|
||||||
{(talk.url_session || talk.url_audio) && ( |
|
||||||
<div className="panel talk-view__links"> |
|
||||||
<h3>Länkar</h3> |
|
||||||
<div className="talk-view__link-group"> |
|
||||||
{talk.url_session && ( |
|
||||||
<a
|
|
||||||
href={talk.url_session}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer" |
|
||||||
className="primary" |
|
||||||
> |
|
||||||
Webb-TV → |
|
||||||
</a> |
|
||||||
)} |
|
||||||
{talk.url_audio && ( |
|
||||||
<a
|
|
||||||
href={talk.url_audio}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer" |
|
||||||
className="primary" |
|
||||||
> |
|
||||||
Ljud → |
|
||||||
</a> |
|
||||||
)} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
)} |
|
||||||
</div> |
|
||||||
); |
|
||||||
} |
|
||||||
@ -1,60 +0,0 @@ |
|||||||
from arango_client import arango |
|
||||||
|
|
||||||
|
|
||||||
def clean_talk(text): |
|
||||||
import re |
|
||||||
|
|
||||||
# Remove "STYLEREF Kantrubrik \* MERGEFORMAT" from the text |
|
||||||
if "STYLEREF Kantrubrik * MERGEFORMAT" in text: |
|
||||||
text = text.replace("STYLEREF Kantrubrik * MERGEFORMAT", "") |
|
||||||
# Remove "- " from the text when there are text on both sides, eg. till- sammans (this comes from line breaks in Word) |
|
||||||
text = re.sub(r"(?<=\S)-\s(?=\S)", "", text) |
|
||||||
# Remove linebreaks in the middle of sentences |
|
||||||
text = re.sub(r"(?<=[^\s.!?:;])\n(?=[a-zåäö])", " ", text) |
|
||||||
return text |
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": |
|
||||||
|
|
||||||
people = arango.db.aql.execute( |
|
||||||
"FOR p IN people RETURN {'namn': CONCAT(p.tilltalsnamn, ' ', p.efternamn), '_key': p._key}" |
|
||||||
) |
|
||||||
people_dict = {str(p["_key"]): p["namn"] for p in people} |
|
||||||
|
|
||||||
cursor = arango.db.aql.execute( |
|
||||||
"""FOR t IN talks RETURN {'_id': t._id, 'anforandetext': t.anforandetext, 'avsnittsrubrik': t.avsnittsrubrik, 'parti': t.parti, 'intressent_id': t.intressent_id}""", |
|
||||||
batch_size=100, |
|
||||||
count=True, |
|
||||||
) |
|
||||||
|
|
||||||
cleaned_talks = [] |
|
||||||
n = 0 |
|
||||||
for talk in cursor: |
|
||||||
n += 1 |
|
||||||
talk["anforandetext"] = clean_talk(talk.get("anforandetext", "")) |
|
||||||
talk["avsnittsrubrik"] = clean_talk(talk.get("avsnittsrubrik", "")) |
|
||||||
if talk.get("intressent_id") in people_dict: |
|
||||||
talk["talare"] = people_dict[str(talk.get("intressent_id"))] |
|
||||||
if talk["parti"] == "FP": |
|
||||||
talk["parti"] = "L" |
|
||||||
if talk["parti"] == "KDS": |
|
||||||
talk["parti"] = "KD" |
|
||||||
if talk["parti"] in [ |
|
||||||
"TALMANNEN", |
|
||||||
"FÖRSTE VICE TALMANNEN", |
|
||||||
"ANDRE VICE TALMANNEN", |
|
||||||
"TREDJE VICE TALMANNEN", |
|
||||||
"ÅLDERSPRESIDENTEN", |
|
||||||
"HANS MAJESTÄT KONUNGEN", |
|
||||||
"TJÄNSTGÖRANDE ÅLDERSPRESIDENTEN", |
|
||||||
]: |
|
||||||
# Make first letter uppercase and rest lowercase |
|
||||||
talk["parti"] = talk["parti"].title() |
|
||||||
if talk["parti"] == "": |
|
||||||
talk["parti"] = "-" |
|
||||||
cleaned_talks.append(talk) |
|
||||||
if len(cleaned_talks) >= 100: |
|
||||||
arango.db.collection("talks").update_many(cleaned_talks, silent=True) |
|
||||||
print( |
|
||||||
f"Processed {n} talks", end="\r"), |
|
||||||
cleaned_talks = [] |
|
||||||
Loading…
Reference in new issue