@rr0/ufoathome 0.122.0 → 0.123.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,101 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./SightingFileEditor-C0NC8jbz.js","./sightingSchema-kkSSrggd.js"])))=>i.map(i=>d[i]);
2
+ import{S as y,a as w,H as N,s as F,U as O,l as R,I as j,b,_ as k,c as I}from"./rr0-sighting-editor.mjs";import{interviewMessages_en as x}from"./InterviewMessages_en-cs-wWpmz.js";import{InterviewMedia as E}from"./InterviewMedia-CvAxC62V.js";import{InterviewFile as g}from"./interviewJson-CfwBPiuG.js";const C="rr0-interview",U=`
3
+ rr0-interview { display: block; --rr0-interview-line: color-mix(in srgb, currentColor 35%, transparent); }
4
+ rr0-interview [hidden] { display: none !important; }
5
+ rr0-interview .rr0-interview-status { font-size: .85em; opacity: .85; margin: 0 0 .5em; }
6
+ /* Scrolling only where a height is asked for: a box that scrolls keeps clear of the page's floated figures for all its height. */
7
+ rr0-interview .rr0-interview-turns { max-height: var(--rr0-interview-max-height, none); overflow-y: var(--rr0-interview-overflow, visible); }
8
+ rr0-interview .turn { position: relative; padding: .2em .5em; margin: 0 0 .4em; border-left: 3px solid var(--speaker); --speaker: hsl(var(--speaker-hue, 0) 55% 38%); }
9
+ @media (prefers-color-scheme: dark) { rr0-interview .turn { --speaker: hsl(var(--speaker-hue, 0) 65% 70%); } }
10
+ rr0-interview .turn { transition: background-color .15s; }
11
+ rr0-interview .turn:hover { background: color-mix(in srgb, var(--speaker) 7%, transparent); }
12
+ rr0-interview .turn.flash { background: color-mix(in srgb, #fc3 28%, transparent); }
13
+ rr0-interview .turn .who { font-size: .85em; font-weight: 600; color: var(--speaker); overflow-wrap: anywhere; }
14
+ rr0-interview .turn.is-redacted .who, rr0-interview .turn.goes-on .who { display: none; }
15
+ rr0-interview .turn.goes-on { margin-top: -.4em; padding-top: .6em; }
16
+ rr0-interview .turn .said, rr0-interview .turn .redacted { margin: 0; padding: 0; text-indent: 0; white-space: pre-wrap; }
17
+ rr0-interview .turn .said::before, rr0-interview .turn .said::after { content: none; }
18
+ /* Tooltips drawn here rather than left to the browser's own, which some hosts (an embedded pane) never show. */
19
+ rr0-interview .turn[data-time]:hover:not(:has(.bar:hover, .bar:focus, .said a:hover))::after { content: attr(data-time); position: absolute; right: .3em; top: .2em; z-index: 2;
20
+ padding: 0 .4em; border-radius: 3px; font-size: .8em; font-variant-numeric: tabular-nums; background: #222; color: #fff; pointer-events: none; }
21
+ rr0-interview .bar { position: relative; display: inline-block; width: 3em; height: .9em; vertical-align: -.1em; border-radius: 2px; background: currentColor; margin: 0 .2em; cursor: help; }
22
+ rr0-interview .bar::after { content: attr(data-tip); display: none; position: absolute; left: 0; top: 1.4em; z-index: 3; width: max-content; max-width: 22em; padding: .3em .5em;
23
+ border-radius: 4px; background: #222; color: #fff; font-size: .85em; line-height: 1.3; white-space: normal; }
24
+ rr0-interview .bar:hover::after, rr0-interview .bar:focus::after { display: block; }
25
+ rr0-interview .turn .refs { display: flex; flex-wrap: wrap; gap: .25em; }
26
+ rr0-interview .turn .ref { font: inherit; font-size: .8em; cursor: pointer; border: 1px solid var(--rr0-interview-line); border-radius: 999px; background: transparent; color: inherit;
27
+ padding: 0 .35em; font-variant-numeric: tabular-nums; white-space: nowrap; }
28
+ `;class T extends HTMLElement{messages=x;said=new y(["en"]);localeToken=0;loadToken=0;json;base;status=document.createElement("p");turns=document.createElement("div");blocks=new Map;constructor(){super(),this.status.className="rr0-interview-status",this.status.setAttribute("role","status"),this.turns.className="rr0-interview-turns"}static get observedAttributes(){return["src"]}attributeChangedCallback(e,t,i){e==="src"&&i!==t&&this.isConnected&&this.load()}connectedCallback(){T.styleRoot(this.getRootNode()),this.contains(this.turns)||this.replaceChildren(this.status,this.turns),this.loadLocale(),this.hasAttribute("src")&&this.json===void 0&&this.load()}get interview(){return this.json}set interview(e){this.loadToken++,this.json=e,this.draw()}set documentUrl(e){this.base=e}static styleRoot(e){const t=e instanceof ShadowRoot?e:document.head;if(t.querySelector("style[data-rr0-interview]"))return;const i=document.createElement("style");i.dataset.rr0Interview="",i.textContent=U,t.append(i)}async load(){const e=this.getAttribute("src"),t=++this.loadToken;if(!e)return;const i=new URL(e,location.href).href;let s;try{s=await w.json(i)}catch{s=void 0}t===this.loadToken&&(this.base=i,this.json=s,this.draw())}async loadLocale(){const e=++this.localeToken,t=N.preferencesFor(this),i=F(t,O),s=i==="en"?x:await R(i);e===this.localeToken&&(this.said=new y(t),this.messages=s,this.draw())}speakerName(e,t){const i=g.speakerName(e,t),s=e.speakers?.[t]?.role;return i===s&&s in this.messages.roles?this.messages.roles[s]:i}seek(e){const t=this.json,i={sighting:e.sighting??g.sightingsOf(t)[0],at:e.at};this.dispatchEvent(new CustomEvent(j,{bubbles:!0,composed:!0,detail:i}));const s=this.getAttribute("player");if(!s)return;const c=this.getRootNode().getElementById?.(s);typeof c?.seekTo=="function"&&(c.scrollIntoView?.({block:"center",behavior:"smooth"}),c.seekTo(i.at,i.sighting))}gap(e,t){const i=document.createElement(t),s=document.createElement("span");s.className="bar";const r=`${this.messages.redacted} : ${this.said.read(e)??""}`;return s.dataset.tip=r,s.tabIndex=0,s.setAttribute("role","img"),s.setAttribute("aria-label",r),i.append(s),i}saidNodes(e){if(!/[<&]/.test(e))return[document.createTextNode(e)];const t=document.createElement("template");t.innerHTML=e;for(const i of t.content.querySelectorAll("*")){if(["SCRIPT","STYLE","IFRAME","OBJECT","EMBED"].includes(i.tagName)){i.remove();continue}for(const s of[...i.attributes])if(/^on/i.test(s.name)||/^\s*javascript:/i.test(s.value))i.removeAttribute(s.name);else if(["href","src"].includes(s.name))try{s.value=new URL(s.value,this.base??location.href).href}catch{}}return[...t.content.childNodes]}draw(){const e=this.messages,t=this.json;this.blocks.clear();const i=Array.isArray(t?.segments)?t.segments:[];if(!t||i.length===0){this.status.hidden=!0;const a=document.createElement("p");a.textContent=e.exchangesEmpty,this.turns.replaceChildren(a);return}const s=t.speakers??{},r=Object.keys(s);if(t.proofreading){const a=r.filter(o=>!["approved","covered"].includes(t.proofreading?.[o]?.status??""));this.status.textContent=a.length===0?e.proofreadAll:e.proofreadPending.replace("{names}",a.map(o=>this.speakerName(t,o)).join(", ")),this.status.hidden=!1}else this.status.hidden=!0;const c=[];let n,p;i.forEach((a,o)=>{const h=a.redacted?JSON.stringify(a.redacted.reason):void 0;if(h!==void 0&&n?.reason===h){this.blocks.set(o,n.block);return}const l=document.createElement("div");l.className="turn",l.dataset.segment=String(o),l.style.setProperty("--speaker-hue",String((Math.max(0,r.indexOf(a.speaker))*67+210)%360));const v=document.createElement("div");v.className="who";const A=document.createElement("span");if(A.textContent=a.redacted?"":this.speakerName(t,a.speaker),a.start!==void 0&&(l.dataset.time=g.clock(a.start,!0)),!a.redacted&&p===a.speaker&&l.classList.add("goes-on"),p=a.redacted?void 0:a.speaker,v.append(A),a.redacted){l.classList.add("is-redacted");const u=this.gap(a.redacted.reason,"p");u.className="redacted",n={block:l,reason:h},l.append(v,u)}else{const u=document.createElement("p");if(u.className="said",a.parts){let f="";for(const m of a.parts){const L="redacted"in m?"▮":g.plain(m.text);/[\p{L}\p{N}.,;:!?)»”…▮]$/u.test(f)&&/^[\p{L}\p{N}(«“▮]/u.test(L)&&u.append(" "),"redacted"in m?u.append(this.gap(m.redacted.reason,"span")):u.append(...this.saidNodes(m.text)),f=L}}else u.append(...this.saidNodes(a.text??""));n=void 0,l.append(v,u)}if(a.refs?.length){const u=document.createElement("div");u.className="refs";for(const f of a.refs){const m=document.createElement("button");m.type="button",m.className="ref",m.textContent=`↗ ${g.clock(f.at,!0)}`,m.addEventListener("click",()=>this.seek(f)),u.append(m)}l.append(u)}this.blocks.set(o,l),c.push(l)}),this.turns.replaceChildren(...c)}revealSegment(e){const t=this.blocks.get(e);t&&(t.scrollIntoView?.({block:"center"}),t.classList.add("flash"),window.setTimeout(()=>t.classList.remove("flash"),1600))}}function B(){customElements.get(C)||customElements.define(C,T)}const S="rr0-interview-editor",M=`
29
+ <style>
30
+ :host { display: block; }
31
+ /* The colours of the transcript's text editor (see SightingFileEditor): a key, a string, a number or a word, and a mistake. Dark mode redefines them. */
32
+ :host { --file-key: #1a5fb4; --file-string: #26662f; --file-literal: #a5321f; --rr0-alert: #c33; }
33
+ @media (prefers-color-scheme: dark) { :host { --file-key: #8cb8ff; --file-string: #8fd39a; --file-literal: #ffa08a; } }
34
+ [hidden] { display: none !important; }
35
+ /* Two groups as the recording's editor has its own (see sightingEditorTemplate): the open tab raised and joined to its panel. */
36
+ .group-tabs { --tab-line: color-mix(in srgb, CanvasText 35%, transparent); --tab-surface: color-mix(in srgb, CanvasText 5%, Canvas);
37
+ --tab-recessed: color-mix(in srgb, CanvasText 11%, Canvas); --tab-accent: #4a9eff;
38
+ display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0; margin: 0; padding: 0; border-bottom: 1px solid var(--tab-line); }
39
+ .group-tab { font: inherit; font-weight: 600; cursor: pointer; color: color-mix(in srgb, CanvasText 70%, transparent); background: var(--tab-recessed);
40
+ border: 1px solid var(--tab-line); border-radius: 8px 8px 0 0; padding: .4em .6em; margin: 0 0 -1px -1px; position: relative; }
41
+ .group-tab:hover { color: CanvasText; background: color-mix(in srgb, CanvasText 16%, Canvas); }
42
+ .group-tab[aria-expanded="true"] { color: CanvasText; background: var(--tab-surface); border-bottom-color: var(--tab-surface); box-shadow: inset 0 2px 0 var(--tab-accent); z-index: 1; }
43
+ .group-tab:focus-visible { outline: 2px solid #4af; outline-offset: -2px; }
44
+ .group-panel { border: 1px solid var(--tab-line, color-mix(in srgb, CanvasText 35%, transparent)); border-top: 0; border-radius: 0 0 4px 4px;
45
+ background: color-mix(in srgb, CanvasText 5%, Canvas); padding: .5em .75em; margin: 0 0 .5em; }
46
+ .row { display: flex; align-items: center; gap: .5em; flex-wrap: wrap; margin-bottom: .5em; }
47
+ .group-panel > .row:last-child { margin-bottom: 0; }
48
+ label { display: flex; align-items: center; gap: .4em; }
49
+ label.wide { flex: 1 1 100%; align-items: flex-start; }
50
+ label.wide textarea { flex: 1 1 auto; min-width: 0; min-height: 9em; font: inherit; }
51
+ label.fill { flex: 1 1 100%; }
52
+ label.fill input { flex: 1 1 auto; }
53
+ input[type="text"] { min-width: 0; }
54
+ #by { width: 16em; }
55
+ input[type="url"] { width: 18em; max-width: 100%; }
56
+ /* Addresses and file names are read letter by letter. */
57
+ input[type="url"], #url { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
58
+ select { min-width: 0; max-width: 100%; text-overflow: ellipsis; }
59
+ fieldset.witnesses { display: flex; flex-wrap: wrap; align-items: center; gap: .25em .75em; border: 0; margin: 0; padding: 0; min-width: 0; }
60
+ fieldset.witnesses legend { float: left; padding: 0; margin-right: .4em; }
61
+ #exchanges { --rr0-interview-max-height: 32em; --rr0-interview-overflow: auto; }
62
+ .file-note { font-size: .85em; opacity: .8; margin: .3em 0; }
63
+ .file-editor { min-height: 18em; margin-top: .5em; }
64
+ </style>
65
+ <div id="tabs" class="group-tabs">
66
+ <button class="group-tab" type="button" aria-controls="group-summary" aria-expanded="true"><span id="label-summary-group">Summary</span></button>
67
+ <button class="group-tab" type="button" aria-controls="group-exchanges" aria-expanded="false"><span id="label-exchanges-group">Exchanges</span></button>
68
+ <button class="group-tab" type="button" aria-controls="group-transcript" aria-expanded="false"><span id="label-file-group">File</span></button>
69
+ </div>
70
+ <section class="group-panel" id="group-summary" aria-labelledby="label-summary-group">
71
+ <div class="row">
72
+ <label class="fill"><span id="label-title">Title</span> <input id="title" type="text"/></label>
73
+ </div>
74
+ <div class="row">
75
+ <fieldset id="witness" class="witnesses"><legend id="label-witness">Witness</legend></fieldset>
76
+ <label><span id="label-medium">Medium</span> <select id="medium"></select></label>
77
+ <label><span id="label-time">When</span> <input id="time" type="text" size="16" placeholder="2026-10-13 21:27"/></label>
78
+ <label><span id="label-by">Interviewer</span> <input id="by" type="text" autocomplete="off" spellcheck="false"/></label>
79
+ </div>
80
+ <div class="row">
81
+ <label class="wide"><span id="label-summary">What it established</span> <textarea id="summary"></textarea></label>
82
+ </div>
83
+ </section>
84
+ <section class="group-panel" id="group-exchanges" aria-labelledby="label-exchanges-group" hidden>
85
+ <rr0-interview id="exchanges"></rr0-interview>
86
+ </section>
87
+ <section class="group-panel" id="group-transcript" aria-labelledby="label-file-group" hidden>
88
+ <div class="row">
89
+ <label class="fill"><span id="label-url">Transcript</span> <input id="url" type="text" autocomplete="off" spellcheck="false" placeholder="interview-….json"/></label>
90
+ </div>
91
+ <div class="row toolbar">
92
+ <label><span id="label-import-file">Load JSON file</span> <input id="import-file" type="file" accept="application/json,.json,.txt,.md,.vtt,.srt,text/*"/></label>
93
+ <label><span id="label-import-url">Or load from URL</span> <input id="import-url" type="url" placeholder="https://…/interview.json"/></label>
94
+ <button id="import-url-button" type="button">Load</button>
95
+ <button id="save" type="button">Save</button>
96
+ </div>
97
+ <p id="file-note" class="file-note" hidden role="status"></p>
98
+ <div id="file-editor" class="file-editor"></div>
99
+ </section>
100
+ `;class d extends HTMLElement{shadow=this.attachShadow({mode:"open"});messages=x;said=new y(["en"]);language="en";localeToken=0;current;options=[];group="summary";base;text;fileEditor;fileEditorLoading;shownFor;constructor(){super(),this.shadow.innerHTML=M;for(const e of["title","time","by","url","summary","witness","medium"])this.shadow.getElementById(e).addEventListener(e==="witness"||e==="medium"?"change":"input",()=>this.write(e));for(const e of this.shadow.querySelectorAll("#tabs .group-tab"))e.addEventListener("click",()=>this.openGroup(d.GROUPS[e.getAttribute("aria-controls")]??"summary"));this.shadow.getElementById("url").addEventListener("change",()=>{this.shownFor=void 0,this.text===void 0&&this.group==="file"&&this.showTranscript()}),this.shadow.getElementById("import-file").addEventListener("change",()=>void this.importFromFile()),this.shadow.getElementById("import-url-button").addEventListener("click",()=>void this.importFromUrl()),this.shadow.getElementById("save").addEventListener("click",()=>this.save())}static GROUPS={"group-summary":"summary","group-exchanges":"exchanges","group-transcript":"file"};static get observedAttributes(){return["readonly"]}attributeChangedCallback(){this.applyReadonly()}connectedCallback(){this.loadLocale()}get event(){return this.current}set event(e){e!==this.current&&(this.shownFor=void 0),this.current=e,this.render(),this.group==="file"&&this.showTranscript(),this.group==="exchanges"&&this.showExchanges()}set witnesses(e){this.options=e,this.render()}set documentUrl(e){this.base=e}get transcriptText(){return this.text}set transcriptText(e){this.text=e,this.shownFor=void 0}field(e){return this.shadow.getElementById(e)}async loadLocale(){const e=++this.localeToken,t=N.preferencesFor(this),i=F(t,O),s=i==="en"?x:await R(i);e===this.localeToken&&(this.language=i,this.said=new y(t),this.messages=s,this.render())}applyReadonly(){const e=this.hasAttribute("readonly");for(const t of["title","time","by","url","summary"])this.field(t).readOnly=e;for(const t of this.shadow.querySelectorAll("#witness input"))t.disabled=e;this.field("medium").disabled=e;for(const t of["import-file","import-url","import-url-button"])this.field(t).disabled=e}static byText(e){return(e??[]).map(t=>"people"in t?t.people:"org"in t?t.org:t.title??"").filter(t=>t!=="").join(", ")}render(){const e=this.messages,t=(o,h)=>{this.shadow.getElementById(o).textContent=h};t("label-summary-group",e.summaryTab),t("label-file-group",e.fileTab),t("label-exchanges-group",e.exchangesTab),t("label-title",e.title),t("label-witness",e.witness),t("label-medium",e.medium),t("label-time",e.time),t("label-by",e.by),t("label-summary",e.summary),t("label-url",e.transcript),t("label-import-file",e.importFile),t("label-import-url",e.importUrl),t("import-url-button",e.importButton),t("save",e.saveButton),this.field("url").title=e.transcriptHint,this.field("by").title=e.byHint;const i=this.current,s=this.shadow.activeElement,r=(o,h)=>{this.field(o)!==s&&(this.field(o).value=h)};r("title",this.said.read(i?.title)??""),r("time",i?.time??""),r("by",d.byText(i?.by)),r("url",i?.url??""),r("summary",this.said.read(i?.summary)??"");const c=[i?.interviewOf].flat().filter(o=>typeof o=="string"),n=this.shadow.getElementById("witness"),p=[...this.options,...c.filter(o=>!this.options.some(h=>h.id===o)).map(o=>({id:o,label:o}))];n.replaceChildren(n.querySelector("legend"),...p.map(o=>{const h=document.createElement("label"),l=document.createElement("input");return l.type="checkbox",l.value=o.id,l.checked=c.includes(o.id),h.append(l,o.label),h}));const a=this.shadow.getElementById("medium");if(a.replaceChildren(...E.ALL.map(o=>new Option(E.label(o,this.language),o))),i?.medium!==void 0&&!E.ALL.includes(i.medium)&&a.append(new Option(String(i.medium),String(i.medium))),i?.medium===void 0){const o=new Option(e.witnessNone,"");o.disabled=!0,o.hidden=!0,a.prepend(o)}a.value=i?.medium??"",this.applyReadonly()}write(e){const t=this.current;if(!t)return;const i=e==="witness"?"":this.field(e).value;switch(e){case"title":{const s=this.said.write(t.title,i,this.language);s===void 0?delete t.title:t.title=s;break}case"summary":{const s=this.said.write(t.summary,i,this.language);s===void 0?delete t.summary:t.summary=s;break}case"time":i.trim()===""?delete t.time:t.time=i.trim();break;case"url":i.trim()===""?delete t.url:t.url=i.trim();break;case"by":{const s=i.split(",").map(r=>r.trim()).filter(r=>r!=="");s.length===0?delete t.by:t.by=s.map(r=>({people:r}));break}case"witness":{const s=[...this.shadow.querySelectorAll("#witness input:checked")].map(r=>r.value);if(s.length===0){this.render();return}t.interviewOf=s.length===1?s[0]:s;break}case"medium":i===""?delete t.medium:t.medium=i;break}this.dispatchEvent(new CustomEvent(b,{bubbles:!0,composed:!0,detail:{field:e,event:t}})),e==="medium"&&this.render(),e!=="url"&&this.syncFileFromEvent()}openGroup(e){this.group=e;const t={summary:"group-summary",exchanges:"group-exchanges",file:"group-transcript"};for(const i of this.shadow.querySelectorAll("#tabs .group-tab"))i.setAttribute("aria-expanded",String(i.getAttribute("aria-controls")===t[e]));for(const[i,s]of Object.entries(t))this.shadow.getElementById(s).hidden=i!==e;e==="file"&&this.showTranscript(),e==="exchanges"&&this.showExchanges()}exchangesToken=0;async transcriptJson(e){try{const t=this.fileEditor?.text??this.text;if(t!==void 0)return JSON.parse(t);const i=this.transcriptUrl(e);if(i!==void 0)return await w.json(i)}catch{}}async showExchanges(){const e=this.current;if(!e)return;const t=++this.exchangesToken,i=await this.transcriptJson(e);t!==this.exchangesToken||this.current!==e||this.drawExchanges(i)}drawExchanges(e){const t=this.shadow.getElementById("exchanges");t.documentUrl=this.current?this.transcriptUrl(this.current):void 0,t.interview=e}async revealSegment(e){this.openGroup("exchanges"),await this.showExchanges(),this.shadow.getElementById("exchanges").revealSegment(e)}note(e){const t=this.shadow.getElementById("file-note");t.textContent=e??"",t.hidden=e===void 0}transcriptUrl(e){return e.url?new URL(e.url,this.base??location.href).href:void 0}static pretty(e){return JSON.stringify(e,null,2)+`
101
+ `}static SHARED=["title","time","medium","by","interviewOf","summary"];static ORDER=["version","id","language","title","time","medium","by","interviewOf","summary","speakers","transcription","proofreading","anonymization","segments"];static ordered(e){const t={};for(const i of d.ORDER)i in e&&(t[i]=e[i]);for(const i of Object.keys(e))i in t||(t[i]=e[i]);return t}static witnessesOf(e){return[e].flat().filter(t=>typeof t=="string")}static withEventFields(e,t){const i={...e};for(const r of d.SHARED)t[r]===void 0?delete i[r]:i[r]=structuredClone(t[r]);const s=i.speakers;if(typeof s=="object"&&s!==null){const r={...s},c=(n,p,a)=>{a.forEach((o,h)=>{const l=h===0?n:`${n}${h+1}`;r[l]?r[l]={...r[l],[p]:o}:h>0&&(r[l]={role:n,[p]:o})})};c("interviewer","by",t.by??[]),c("witness","of",d.witnessesOf(t.interviewOf)),i.speakers=r}return d.ordered(i)}applyFileToEvent(e,t){const i=this.current;if(!i)return!1;const s=JSON.stringify(d.SHARED.map(n=>i[n])),r=n=>typeof n=="string"||typeof n=="object"&&n!==null&&!Array.isArray(n),c={title:r,summary:r,time:n=>typeof n=="string",medium:n=>typeof n=="string",by:n=>Array.isArray(n),interviewOf:n=>typeof n=="string"||Array.isArray(n)};for(const n of d.SHARED){const p=e[n];p!==void 0&&c[n](p)?i[n]=structuredClone(p):t&&delete i[n]}return JSON.stringify(d.SHARED.map(n=>i[n]))!==s}syncFileFromEvent(){const e=this.current,t=this.fileEditor?.text??this.text;if(!e||t===void 0)return;let i;try{i=JSON.parse(t)}catch{return}if(typeof i!="object"||i===null||Array.isArray(i))return;const s=d.pretty(d.withEventFields(i,e));s!==t&&(this.text=s,this.fileEditor?.replaceText(s,!1),this.dispatchEvent(new CustomEvent(b,{bubbles:!0,composed:!0,detail:{field:"transcript",event:e,text:s}})))}template(e){return d.pretty(d.withEventFields({version:1,language:this.language,speakers:{interviewer:{role:"interviewer"},witness:{role:"witness"}},segments:[]},e))}loadFileEditor(e){return this.fileEditorLoading??=(async()=>{try{const[{SightingFileEditor:t},{default:i},s]=await Promise.all([k(()=>import("./SightingFileEditor-C0NC8jbz.js"),__vite__mapDeps([0,1]),import.meta.url),k(()=>import("./interviewSchema-CMRZUnRy.js"),[],import.meta.url),I(this.language)]);return this.fileEditor=new t(this.shadow.getElementById("file-editor"),e,r=>this.onEdited(r),this.messages.editorLabel,{baseline:e,phrases:s.fileSearchPhrases,schema:i}),this.fileEditor}catch(t){throw this.fileEditorLoading=void 0,t}})(),this.fileEditorLoading}async showTranscript(){const e=this.current;if(!e||this.shownFor===e)return;this.note(this.messages.loading);let t=this.text;if(t===void 0){const i=this.transcriptUrl(e);if(i!==void 0)try{const s=await w.json(i);this.current===e&&typeof s=="object"&&s!==null&&!Array.isArray(s)?(this.applyFileToEvent(s,!1)&&this.fileChangedEvent(),t=d.pretty(d.withEventFields(s,e))):t=d.pretty(s)}catch{this.note(this.messages.loadFailed)}t??=this.template(e)}if(this.current===e)try{const i=await this.loadFileEditor(t);if(this.current!==e)return;i.replaceText(t,!1),this.shownFor=e,this.text!==void 0||this.transcriptUrl(e)===void 0?this.note(void 0):this.shadow.getElementById("file-note").textContent===this.messages.loading&&this.note(void 0)}catch{this.note(this.messages.loadFailed)}}onEdited(e){this.text=e;let t,i=!0;try{t=JSON.parse(e)}catch{i=!1}this.note(i?void 0:this.messages.invalid);const s=this.current;if(i&&s&&typeof t=="object"&&t!==null&&!Array.isArray(t)&&this.applyFileToEvent(t,!0)&&this.fileChangedEvent(),s&&!s.url&&e.trim()!==""){const r=s.id??"1";s.url=`${r.startsWith("interview")?r:`interview-${r}`}.json`,this.field("url").value=s.url,this.dispatchEvent(new CustomEvent(b,{bubbles:!0,composed:!0,detail:{field:"url",event:s}}))}this.dispatchEvent(new CustomEvent(b,{bubbles:!0,composed:!0,detail:{field:"transcript",event:s,text:e}})),this.group==="exchanges"&&this.showExchanges()}fileChangedEvent(){this.render(),this.dispatchEvent(new CustomEvent(b,{bubbles:!0,composed:!0,detail:{field:"file",event:this.current}}))}async take(e){if(typeof e!="object"||e===null||!Array.isArray(e.segments)){this.note(this.messages.notATranscript);return}const t=this.current;t&&this.applyFileToEvent(e,!1)&&this.fileChangedEvent();const i=d.pretty(t?d.withEventFields(e,t):e);await this.loadFileEditor(i).then(s=>s.replaceText(i,!1),()=>{}),this.shownFor=this.current,this.note(void 0),this.onEdited(i)}async importFromFile(){const e=this.field("import-file"),t=e.files?.[0];if(t){try{const i=await t.text();let s;try{s=JSON.parse(i)}catch{s=void 0}if(typeof s!="object"||s===null){const{InterviewImport:r}=await k(async()=>{const{InterviewImport:a}=await import("./interviewImport-C-TiARLK.js");return{InterviewImport:a}},[],import.meta.url),c=this.current,n=[c?.interviewOf].flat().filter(a=>typeof a=="string"),p=r.parse(i,{by:(c?.by??[]).map(a=>d.byText([a])).filter(a=>a!==""),witnesses:this.options.filter(a=>n.length===0||n.includes(a.id))});s=p&&{...p,transcription:{note:`Imported from ${t.name}: who speaks and when are as the file gave them, to be checked.`}}}s===void 0?this.note(this.messages.notATranscript):await this.take(s)}catch{this.note(this.messages.loadFailed)}e.value=""}}async importFromUrl(){const e=this.field("import-url").value.trim();if(e!=="")try{await this.take(await w.json(new URL(e,this.base??location.href).href))}catch{this.note(this.messages.loadFailed)}}save(){const e=this.fileEditor?.text??this.text;if(e===void 0)return;const t=(this.current?.url??"").split("/").pop()||`${this.current?.id??"interview"}.json`,i=URL.createObjectURL(new Blob([e],{type:"application/json"})),s=document.createElement("a");s.href=i,s.download=t,s.click(),URL.revokeObjectURL(i)}}function z(){B(),customElements.get(S)||customElements.define(S,d)}export{b as INTERVIEW_CHANGE_EVENT,S as INTERVIEW_EDITOR_ELEMENT_NAME,j as INTERVIEW_SEEK_EVENT,d as InterviewEditorElement,z as register};
@@ -1 +1 @@
1
- const e={title:"Title",time:"When",by:"Interviewer",medium:"Medium",witness:"Witness",witnessNone:"—",summary:"Description",transcript:"Transcript",transcriptHint:"Address of the anonymised transcript file (optional)",summaryTab:"Summary",fileTab:"File",importFile:"Load a file (JSON, text, WebVTT, SRT)",importUrl:"Or load from URL",importButton:"Load",saveButton:"Save",loading:"Loading the transcript…",invalid:"This text is not valid JSON yet: the interview keeps the last transcript it understood.",notATranscript:"That file is not an interview transcript (it has no segments).",loadFailed:"That transcript could not be read.",editorLabel:"Interview transcript as JSON",byHint:"RR0 people ids, separated by commas",exchangesTab:"Exchanges",exchangesEmpty:"Nothing was said yet.",redacted:"Passage removed",proofreadAll:"Proofread by everyone.",proofreadPending:"Not proofread yet by: {names}"};export{e as interviewMessages_en};
1
+ const e={title:"Title",time:"When",by:"Interviewer",medium:"Medium",witness:"Witness",witnessNone:"—",summary:"Description",transcript:"Transcript",transcriptHint:"Address of the anonymised transcript file (optional)",summaryTab:"Summary",fileTab:"File",importFile:"Load a file (JSON, text, WebVTT, SRT)",importUrl:"Or load from URL",importButton:"Load",saveButton:"Save",loading:"Loading the transcript…",invalid:"This text is not valid JSON yet: the interview keeps the last transcript it understood.",notATranscript:"That file is not an interview transcript (it has no segments).",loadFailed:"That transcript could not be read.",editorLabel:"Interview transcript as JSON",byHint:"RR0 people ids, separated by commas",exchangesTab:"Exchanges",exchangesEmpty:"Nothing was said yet.",redacted:"Passage removed",proofreadAll:"Proofread by everyone.",proofreadPending:"Not proofread yet by: {names}",roles:{interviewer:"Interviewer",interviewee:"Interviewee",witness:"Witness"}};export{e as interviewMessages_en};
@@ -0,0 +1 @@
1
+ const e={title:"Título",time:"Fecha",by:"Entrevistador",medium:"Medio",witness:"Testigo",witnessNone:"—",summary:"Descripción",transcript:"Transcripción",transcriptHint:"Dirección del archivo de transcripción anonimizada (opcional)",summaryTab:"Resumen",fileTab:"Archivo",importFile:"Cargar un archivo (JSON, texto, WebVTT, SRT)",importUrl:"O desde una dirección",importButton:"Cargar",saveButton:"Guardar",loading:"Cargando la transcripción…",invalid:"Este texto aún no es JSON válido: la entrevista conserva la última transcripción que entendió.",notATranscript:"Ese archivo no es una transcripción de entrevista (no tiene turnos de palabra).",loadFailed:"No se pudo leer esa transcripción.",editorLabel:"Transcripción de la entrevista en JSON",byHint:"Identificadores de personas de RR0, separados por comas",exchangesTab:"Intercambios",exchangesEmpty:"Todavía no se ha dicho nada.",redacted:"Pasaje eliminado",proofreadAll:"Revisado por todos.",proofreadPending:"Aún no revisado por: {names}",roles:{interviewer:"Entrevistador",interviewee:"Entrevistado",witness:"Testigo"}};export{e as interviewMessages_es};
@@ -0,0 +1 @@
1
+ const e={title:"Titre",time:"Date",by:"Enquêteur",medium:"Moyen",witness:"Témoin",witnessNone:"—",summary:"Description",transcript:"Transcription",transcriptHint:"Adresse du fichier de transcription anonymisée (facultative)",summaryTab:"Résumé",fileTab:"Fichier",importFile:"Charger un fichier (JSON, texte, WebVTT, SRT)",importUrl:"Ou depuis une adresse",importButton:"Charger",saveButton:"Sauver",loading:"Chargement de la transcription…",invalid:"Ce texte n'est pas encore du JSON valide : l'interview garde la dernière transcription qu'elle a comprise.",notATranscript:"Ce fichier n'est pas une transcription d'interview (il n'a pas de tours de parole).",loadFailed:"Cette transcription n'a pas pu être lue.",editorLabel:"Transcription de l'interview en JSON",byHint:"Identifiants de personnes RR0, séparés par des virgules",exchangesTab:"Échanges",exchangesEmpty:"Rien n'a encore été dit.",redacted:"Passage retiré",proofreadAll:"Relu par tous les participants.",proofreadPending:"Pas encore relu par : {names}",roles:{interviewer:"Intervieweur",interviewee:"Interviewé",witness:"Témoin"}};export{e as interviewMessages_fr};
@@ -0,0 +1 @@
1
+ const i={title:"Titolo",time:"Data",by:"Intervistatore",medium:"Mezzo",witness:"Testimone",witnessNone:"—",summary:"Descrizione",transcript:"Trascrizione",transcriptHint:"Indirizzo del file di trascrizione anonimizzata (facoltativo)",summaryTab:"Riepilogo",fileTab:"File",importFile:"Carica un file (JSON, testo, WebVTT, SRT)",importUrl:"O da un indirizzo",importButton:"Carica",saveButton:"Salva",loading:"Caricamento della trascrizione…",invalid:"Questo testo non è ancora JSON valido: l'intervista conserva l'ultima trascrizione che ha capito.",notATranscript:"Quel file non è una trascrizione di intervista (non ha turni di parola).",loadFailed:"Impossibile leggere quella trascrizione.",editorLabel:"Trascrizione dell'intervista in JSON",byHint:"Identificatori di persone RR0, separati da virgole",exchangesTab:"Scambi",exchangesEmpty:"Non è stato ancora detto nulla.",redacted:"Passaggio rimosso",proofreadAll:"Rivisto da tutti.",proofreadPending:"Non ancora rivisto da: {names}",roles:{interviewer:"Intervistatore",interviewee:"Intervistato",witness:"Testimone"}};export{i as interviewMessages_it};
@@ -0,0 +1,8 @@
1
+ class n{static isProofread(r){const a=Object.keys(r.speakers);return a.length>0&&a.every(t=>{const e=r.proofreading?.[t]?.status;return e==="approved"||e==="covered"})}static said(r){return r.parts?r.parts.map(a=>"redacted"in a?"…":n.plain(a.text)).join(" "):r.redacted?void 0:n.plain(r.text??"")}static ENTITIES={amp:"&",lt:"<",gt:">",quot:'"',apos:"'",nbsp:" "};static plain(r){return/[<&]/.test(r)?r.replace(/<br\s*\/?>/gi,`
2
+ `).replace(/<\/?[a-z][^>]*>/gi,"").replace(/&#x([0-9a-f]+);/gi,(a,t)=>String.fromCodePoint(parseInt(t,16))).replace(/&#(\d+);/g,(a,t)=>String.fromCodePoint(Number(t))).replace(/&(\w+);/g,(a,t)=>n.ENTITIES[t.toLowerCase()]??a):r}static sightingsOf(r){return[r.interviewOf].flat().filter(a=>typeof a=="string")}static passagesAbout(r,a){const t=n.sightingsOf(r),e=[];return r.segments.forEach((s,c)=>{for(const o of s.refs??[]){if(o.sighting===void 0?!t.includes(a):o.sighting!==a)continue;const i=n.said(s);e.push({index:c,speaker:s.speaker,at:o.at,...i!==void 0?{text:i}:{},...o.range?{range:o.range}:{}})}}),e.sort((s,c)=>s.at-c.at||s.index-c.index)}static clock(r,a=!1){const t=Math.round(r*10),e=Math.floor(t/10),s=i=>String(i).padStart(2,"0"),c=Math.floor(e/3600),o=`${c>0||a?s(Math.floor(e/60)%60):Math.floor(e/60)}:${s(e%60)}`;return`${c>0?`${c}:`:""}${o}${a||t%10!==0?`.${t%10}`:""}`}static speakerName(r,a){const t=r.speakers[a];if(!t)return a;const e=typeof t.title=="string"?t.title:t.title?Object.values(t.title)[0]:void 0;return e||t.by&&n.agentName(t.by)||t.role}static agentName(r){if("people"in r){const[a,...t]=r.people.replace(/([a-z])([A-Z])/g,"$1 $2").split(" ");return[...t,a].join(" ")}return"org"in r?r.org:r.title??""}static stamp(r){const a=Math.round(r*1e3),t=(e,s=2)=>String(e).padStart(s,"0");return`${t(Math.floor(a/36e5))}:${t(Math.floor(a/6e4)%60)}:${t(Math.floor(a/1e3)%60)}.${t(a%1e3,3)}`}static toVtt(r){const a=s=>s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),t=r.segments.filter(s=>s.start!==void 0);return`WEBVTT
3
+
4
+ ${t.map((s,c)=>{const o=t[c+1],i=s.end??o?.start??s.start+5,p=o&&o.start<i?Math.max(s.start,o.start):i;return`${n.stamp(s.start)} --> ${n.stamp(p)}
5
+ <v ${a(n.speakerName(r,s.speaker))}>${a(n.said(s)??"[…]")}`}).join(`
6
+
7
+ `)}
8
+ `}}export{n as InterviewFile};