@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.
- package/README.md +3 -2
- package/{dist-embed-sighting-editor/InterviewMessages_es-gh_1UJfY.js → dist-embed-interview/InterviewMessages_es-D5d2JVA3.js} +1 -1
- package/{dist-embed-sighting-editor/InterviewMessages_fr-B_Tl9ZcL.js → dist-embed-interview/InterviewMessages_fr-CmScqfza.js} +1 -1
- package/{dist-embed-sighting-editor/InterviewMessages_it-ECgJT3FK.js → dist-embed-interview/InterviewMessages_it-2SNw5xMp.js} +1 -1
- package/dist-embed-interview/rr0-interview.mjs +34 -0
- package/dist-embed-scene/rr0-scene.mjs +2 -2
- package/dist-embed-sighting/rr0-sighting.mjs +27 -27
- package/dist-embed-sighting-editor/InterviewEditorElement-CT26rBmp.js +101 -0
- package/dist-embed-sighting-editor/{InterviewMessages_en-C-7vNgM2.js → InterviewMessages_en-cs-wWpmz.js} +1 -1
- package/dist-embed-sighting-editor/InterviewMessages_es-D5d2JVA3.js +1 -0
- package/dist-embed-sighting-editor/InterviewMessages_fr-CmScqfza.js +1 -0
- package/dist-embed-sighting-editor/InterviewMessages_it-2SNw5xMp.js +1 -0
- package/dist-embed-sighting-editor/interviewJson-CfwBPiuG.js +8 -0
- package/dist-embed-sighting-editor/rr0-sighting-editor.mjs +85 -22
- package/package.json +10 -6
- package/dist-embed-sighting-editor/InterviewElement-wiGHrsHB.js +0 -93
- package/dist-embed-sighting-editor/interviewJson-78rX1Ar1.js +0 -7
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rr0/ufoathome",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.123.0",
|
|
5
5
|
"description": "UFO@home — record and replay a UFO sighting's shape, movement and appearance",
|
|
6
6
|
"author": "Jérôme Beau <rr0@rr0.org> (https://rr0.org)",
|
|
7
7
|
"license": "MIT",
|
|
@@ -18,12 +18,14 @@
|
|
|
18
18
|
"./sighting": "./dist-embed-sighting/rr0-sighting.mjs",
|
|
19
19
|
"./editor": "./dist-embed-sighting-editor/rr0-sighting-editor.mjs",
|
|
20
20
|
"./eyewitness": "./dist-embed-sighting/rr0-sighting.mjs",
|
|
21
|
-
"./recorder": "./dist-embed-sighting-editor/rr0-sighting-editor.mjs"
|
|
21
|
+
"./recorder": "./dist-embed-sighting-editor/rr0-sighting-editor.mjs",
|
|
22
|
+
"./interview": "./dist-embed-interview/rr0-interview.mjs"
|
|
22
23
|
},
|
|
23
24
|
"files": [
|
|
24
25
|
"dist-embed-scene",
|
|
25
26
|
"dist-embed-sighting",
|
|
26
|
-
"dist-embed-sighting-editor"
|
|
27
|
+
"dist-embed-sighting-editor",
|
|
28
|
+
"dist-embed-interview"
|
|
27
29
|
],
|
|
28
30
|
"scripts": {
|
|
29
31
|
"predev": "npm run build:schema",
|
|
@@ -32,12 +34,14 @@
|
|
|
32
34
|
"build": "tsc --noEmit && vite build",
|
|
33
35
|
"build:embed-scene": "tsc --noEmit && vite build --config vite.embed-scene.config.ts",
|
|
34
36
|
"build:embed-sighting": "tsc --noEmit && vite build --config vite.embed-sighting.config.ts",
|
|
35
|
-
"build:
|
|
36
|
-
"build:
|
|
37
|
+
"build:embed-interview": "vite build --config vite.embed-interview.config.ts",
|
|
38
|
+
"build:all": "npm run build:schema && npm run build && npm run build:embed-sighting-editor && npm run build:embed-scene && npm run build:embed-sighting && npm run build:embed-interview",
|
|
39
|
+
"build:site": "npm run build:schema && npm run build:embed-sighting-editor && npm run build:embed-scene && npm run build:embed-sighting && npm run build:embed-interview && vite build --config vite.site.config.ts && tsx site/build.ts",
|
|
37
40
|
"site": "tsx site/build.ts",
|
|
38
41
|
"site:watch": "node --watch-path=./site --watch-path=./public/demo-data --import tsx site/build.ts",
|
|
42
|
+
"deploy:site": "npm run build:site && npx netlify deploy --prod --dir dist-site --no-build --site 90e69d49-ac43-433a-9829-3cb861a623ef",
|
|
39
43
|
"site:serve": "vite preview --outDir dist-site --port 5181 --strictPort",
|
|
40
|
-
"prepublishOnly": "npm run build:schema && npm run build:embed-sighting-editor && npm run build:embed-scene && npm run build:embed-sighting && npm test",
|
|
44
|
+
"prepublishOnly": "npm run build:schema && npm run build:embed-sighting-editor && npm run build:embed-scene && npm run build:embed-sighting && npm run build:embed-interview && npm test",
|
|
41
45
|
"preview": "vite preview",
|
|
42
46
|
"test": "vitest run",
|
|
43
47
|
"test:watch": "vitest",
|
|
@@ -1,93 +0,0 @@
|
|
|
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,H as N,s as U,U as B,l as I,I as y,a as k,_ as T,b as M,c as j}from"./rr0-sighting-editor.mjs";import{interviewMessages_en as O}from"./InterviewMessages_en-C-7vNgM2.js";import{InterviewMedia as C}from"./InterviewMedia-CvAxC62V.js";import{InterviewFile as v}from"./interviewJson-78rX1Ar1.js";const R="rr0-interview",P=`
|
|
3
|
-
<style>
|
|
4
|
-
:host { display: block; }
|
|
5
|
-
/* 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. */
|
|
6
|
-
:host { --file-key: #1a5fb4; --file-string: #26662f; --file-literal: #a5321f; --rr0-alert: #c33; }
|
|
7
|
-
@media (prefers-color-scheme: dark) { :host { --file-key: #8cb8ff; --file-string: #8fd39a; --file-literal: #ffa08a; } }
|
|
8
|
-
[hidden] { display: none !important; }
|
|
9
|
-
/* Two groups as the recording's editor has its own (see sightingEditorTemplate): the open tab raised and joined to its panel. */
|
|
10
|
-
.group-tabs { --tab-line: color-mix(in srgb, CanvasText 35%, transparent); --tab-surface: color-mix(in srgb, CanvasText 5%, Canvas);
|
|
11
|
-
--tab-recessed: color-mix(in srgb, CanvasText 11%, Canvas); --tab-accent: #4a9eff;
|
|
12
|
-
display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0; margin: 0; padding: 0; border-bottom: 1px solid var(--tab-line); }
|
|
13
|
-
.group-tab { font: inherit; font-weight: 600; cursor: pointer; color: color-mix(in srgb, CanvasText 70%, transparent); background: var(--tab-recessed);
|
|
14
|
-
border: 1px solid var(--tab-line); border-radius: 8px 8px 0 0; padding: .4em .6em; margin: 0 0 -1px -1px; position: relative; }
|
|
15
|
-
.group-tab:hover { color: CanvasText; background: color-mix(in srgb, CanvasText 16%, Canvas); }
|
|
16
|
-
.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; }
|
|
17
|
-
.group-tab:focus-visible { outline: 2px solid #4af; outline-offset: -2px; }
|
|
18
|
-
.group-panel { border: 1px solid var(--tab-line, color-mix(in srgb, CanvasText 35%, transparent)); border-top: 0; border-radius: 0 0 4px 4px;
|
|
19
|
-
background: color-mix(in srgb, CanvasText 5%, Canvas); padding: .5em .75em; margin: 0 0 .5em; }
|
|
20
|
-
.row { display: flex; align-items: center; gap: .5em; flex-wrap: wrap; margin-bottom: .5em; }
|
|
21
|
-
.group-panel > .row:last-child { margin-bottom: 0; }
|
|
22
|
-
label { display: flex; align-items: center; gap: .4em; }
|
|
23
|
-
label.wide { flex: 1 1 100%; align-items: flex-start; }
|
|
24
|
-
label.wide textarea { flex: 1 1 auto; min-width: 0; min-height: 9em; font: inherit; }
|
|
25
|
-
label.fill { flex: 1 1 100%; }
|
|
26
|
-
label.fill input { flex: 1 1 auto; }
|
|
27
|
-
input[type="text"] { min-width: 0; }
|
|
28
|
-
#by { width: 16em; }
|
|
29
|
-
input[type="url"] { width: 18em; max-width: 100%; }
|
|
30
|
-
/* Addresses and file names are read letter by letter. */
|
|
31
|
-
input[type="url"], #url { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
32
|
-
select { min-width: 0; max-width: 100%; text-overflow: ellipsis; }
|
|
33
|
-
fieldset.witnesses { display: flex; flex-wrap: wrap; align-items: center; gap: .25em .75em; border: 0; margin: 0; padding: 0; min-width: 0; }
|
|
34
|
-
fieldset.witnesses legend { float: left; padding: 0; margin-right: .4em; }
|
|
35
|
-
.exchanges-status { font-size: .85em; opacity: .85; margin: 0 0 .5em; }
|
|
36
|
-
.turn { display: grid; grid-template-columns: 8em 1fr; gap: .1em .6em; padding: .3em .4em; border-left: 3px solid var(--speaker, #888); margin: 0 0 .25em; }
|
|
37
|
-
.turn.flash { background: color-mix(in srgb, #fc3 28%, transparent); }
|
|
38
|
-
.turn .who { font-weight: 600; color: var(--speaker, inherit); overflow-wrap: anywhere; }
|
|
39
|
-
#exchanges { max-height: 32em; overflow-y: auto; }
|
|
40
|
-
/* Tooltips drawn here rather than left to the browser's own, which some hosts (an embedded pane) never show. */
|
|
41
|
-
.turn { position: relative; }
|
|
42
|
-
.turn[data-time]:hover:not(:has(.bar:hover, .bar:focus))::after { content: attr(data-time); position: absolute; left: .5em; top: 2.3em; z-index: 2; padding: 0 .4em; border-radius: 3px;
|
|
43
|
-
font-size: .8em; font-variant-numeric: tabular-nums; background: CanvasText; color: Canvas; pointer-events: none; }
|
|
44
|
-
.bar { position: relative; display: inline-block; width: 3em; height: .9em; vertical-align: -.1em; border-radius: 2px; background: CanvasText; margin: 0 .2em; cursor: help; }
|
|
45
|
-
.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; border-radius: 4px;
|
|
46
|
-
background: CanvasText; color: Canvas; font-size: .85em; line-height: 1.3; white-space: normal; }
|
|
47
|
-
.bar:hover::after, .bar:focus::after { display: block; }
|
|
48
|
-
.turn .said { margin: 0; white-space: pre-wrap; }
|
|
49
|
-
.turn .redacted { margin: 0; }
|
|
50
|
-
.turn.is-redacted .who { color: inherit; opacity: .6; font-weight: 400; }
|
|
51
|
-
.turn .refs { grid-column: 2; display: flex; flex-wrap: wrap; gap: .25em; }
|
|
52
|
-
.turn .ref { font: inherit; font-size: .8em; cursor: pointer; border: 1px solid var(--tab-line); border-radius: 999px; background: transparent; color: inherit; padding: 0 .35em; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
53
|
-
.file-note { font-size: .85em; opacity: .8; margin: .3em 0; }
|
|
54
|
-
.file-editor { min-height: 18em; margin-top: .5em; }
|
|
55
|
-
</style>
|
|
56
|
-
<div id="tabs" class="group-tabs">
|
|
57
|
-
<button class="group-tab" type="button" aria-controls="group-summary" aria-expanded="true"><span id="label-summary-group">Summary</span></button>
|
|
58
|
-
<button class="group-tab" type="button" aria-controls="group-exchanges" aria-expanded="false"><span id="label-exchanges-group">Exchanges</span></button>
|
|
59
|
-
<button class="group-tab" type="button" aria-controls="group-transcript" aria-expanded="false"><span id="label-file-group">File</span></button>
|
|
60
|
-
</div>
|
|
61
|
-
<section class="group-panel" id="group-summary" aria-labelledby="label-summary-group">
|
|
62
|
-
<div class="row">
|
|
63
|
-
<label class="fill"><span id="label-title">Title</span> <input id="title" type="text"/></label>
|
|
64
|
-
</div>
|
|
65
|
-
<div class="row">
|
|
66
|
-
<fieldset id="witness" class="witnesses"><legend id="label-witness">Witness</legend></fieldset>
|
|
67
|
-
<label><span id="label-medium">Medium</span> <select id="medium"></select></label>
|
|
68
|
-
<label><span id="label-time">When</span> <input id="time" type="text" size="16" placeholder="2026-10-13 21:27"/></label>
|
|
69
|
-
<label><span id="label-by">Interviewer</span> <input id="by" type="text" autocomplete="off" spellcheck="false"/></label>
|
|
70
|
-
</div>
|
|
71
|
-
<div class="row">
|
|
72
|
-
<label class="wide"><span id="label-summary">What it established</span> <textarea id="summary"></textarea></label>
|
|
73
|
-
</div>
|
|
74
|
-
</section>
|
|
75
|
-
<section class="group-panel" id="group-exchanges" aria-labelledby="label-exchanges-group" hidden>
|
|
76
|
-
<p id="exchanges-status" class="exchanges-status" role="status"></p>
|
|
77
|
-
<div id="exchanges"></div>
|
|
78
|
-
</section>
|
|
79
|
-
<section class="group-panel" id="group-transcript" aria-labelledby="label-file-group" hidden>
|
|
80
|
-
<div class="row">
|
|
81
|
-
<label class="fill"><span id="label-url">Transcript</span> <input id="url" type="text" autocomplete="off" spellcheck="false" placeholder="interview-….json"/></label>
|
|
82
|
-
</div>
|
|
83
|
-
<div class="row toolbar">
|
|
84
|
-
<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>
|
|
85
|
-
<label><span id="label-import-url">Or load from URL</span> <input id="import-url" type="url" placeholder="https://…/interview.json"/></label>
|
|
86
|
-
<button id="import-url-button" type="button">Load</button>
|
|
87
|
-
<button id="save" type="button">Save</button>
|
|
88
|
-
</div>
|
|
89
|
-
<p id="file-note" class="file-note" hidden role="status"></p>
|
|
90
|
-
<div id="file-editor" class="file-editor"></div>
|
|
91
|
-
</section>
|
|
92
|
-
`;class l extends HTMLElement{shadow=this.attachShadow({mode:"open"});messages=O;said=new S(["en"]);language="en";localeToken=0;current;options=[];group="summary";base;text;fileEditor;fileEditorLoading;shownFor;constructor(){super(),this.shadow.innerHTML=P;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(l.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=U(t,B),s=i==="en"?O:await I(i);e===this.localeToken&&(this.language=i,this.said=new S(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,c)=>{this.shadow.getElementById(o).textContent=c};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,a=(o,c)=>{this.field(o)!==s&&(this.field(o).value=c)};a("title",this.said.read(i?.title)??""),a("time",i?.time??""),a("by",l.byText(i?.by)),a("url",i?.url??""),a("summary",this.said.read(i?.summary)??"");const u=[i?.interviewOf].flat().filter(o=>typeof o=="string"),r=this.shadow.getElementById("witness"),p=[...this.options,...u.filter(o=>!this.options.some(c=>c.id===o)).map(o=>({id:o,label:o}))];r.replaceChildren(r.querySelector("legend"),...p.map(o=>{const c=document.createElement("label"),n=document.createElement("input");return n.type="checkbox",n.value=o.id,n.checked=u.includes(o.id),c.append(n,o.label),c}));const d=this.shadow.getElementById("medium");if(d.replaceChildren(...C.ALL.map(o=>new Option(C.label(o,this.language),o))),i?.medium!==void 0&&!C.ALL.includes(i.medium)&&d.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,d.prepend(o)}d.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(a=>a.trim()).filter(a=>a!=="");s.length===0?delete t.by:t.by=s.map(a=>({people:a}));break}case"witness":{const s=[...this.shadow.querySelectorAll("#witness input:checked")].map(a=>a.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(y,{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;blocks=new Map;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 k.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.messages,i=this.shadow.getElementById("exchanges"),s=this.shadow.getElementById("exchanges-status");this.blocks.clear();const a=Array.isArray(e?.segments)?e.segments:[];if(!e||a.length===0){s.textContent="";const n=document.createElement("p");n.textContent=t.exchangesEmpty,i.replaceChildren(n);return}const u=e.speakers??{},r=Object.keys(u),p=r.filter(n=>!["approved","covered"].includes(e.proofreading?.[n]?.status??""));s.textContent=p.length===0?t.proofreadAll:t.proofreadPending.replace("{names}",p.map(n=>v.speakerName(e,n)).join(", "));const d=n=>{this.dispatchEvent(new CustomEvent(j,{bubbles:!0,composed:!0,detail:{sighting:n.sighting??v.sightingsOf(e)[0],at:n.at}}))},o=[];let c;a.forEach((n,x)=>{const E=n.redacted?JSON.stringify(n.redacted.reason):void 0;if(E!==void 0&&c?.reason===E){this.blocks.set(x,c.block);return}const m=document.createElement("div");m.className="turn",m.dataset.segment=String(x),m.style.setProperty("--speaker",`hsl(${(Math.max(0,r.indexOf(n.speaker))*67+210)%360} 55% 42%)`);const w=document.createElement("div");w.className="who";const A=document.createElement("span");A.textContent=n.redacted?"":v.speakerName(e,n.speaker),n.start!==void 0&&(m.dataset.time=v.clock(n.start,!0)),w.append(A);const F=(h,f)=>{const b=document.createElement(f),g=document.createElement("span");g.className="bar";const L=`${t.redacted} : ${this.said.read(h)??""}`;return g.dataset.tip=L,g.tabIndex=0,g.setAttribute("role","img"),g.setAttribute("aria-label",L),b.append(g),b};if(n.redacted){m.classList.add("is-redacted");const h=F(n.redacted.reason,"p");h.className="redacted",c={block:m,reason:E},m.append(w,h)}else{const h=document.createElement("p");if(h.className="said",n.parts)for(const f of n.parts)"text"in f?h.append(f.text," "):h.append(F(f.redacted.reason,"span")," ");else h.textContent=n.text??"";c=void 0,m.append(w,h)}if(n.refs?.length){const h=document.createElement("div");h.className="refs";for(const f of n.refs){const b=document.createElement("button");b.type="button",b.className="ref",b.textContent=`↗ ${v.clock(f.at,!0)}`,b.addEventListener("click",()=>d(f)),h.append(b)}m.append(h)}this.blocks.set(x,m),o.push(m)}),i.replaceChildren(...o)}async revealSegment(e){this.openGroup("exchanges"),await this.showExchanges();const t=this.blocks.get(e);t&&(t.scrollIntoView?.({block:"center"}),t.classList.add("flash"),window.setTimeout(()=>t.classList.remove("flash"),1600))}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)+`
|
|
93
|
-
`}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 l.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 a of l.SHARED)t[a]===void 0?delete i[a]:i[a]=structuredClone(t[a]);const s=i.speakers;if(typeof s=="object"&&s!==null){const a={...s},u=(r,p,d)=>{d.forEach((o,c)=>{const n=c===0?r:`${r}${c+1}`;a[n]?a[n]={...a[n],[p]:o}:c>0&&(a[n]={role:r,[p]:o})})};u("interviewer","by",t.by??[]),u("witness","of",l.witnessesOf(t.interviewOf)),i.speakers=a}return l.ordered(i)}applyFileToEvent(e,t){const i=this.current;if(!i)return!1;const s=JSON.stringify(l.SHARED.map(r=>i[r])),a=r=>typeof r=="string"||typeof r=="object"&&r!==null&&!Array.isArray(r),u={title:a,summary:a,time:r=>typeof r=="string",medium:r=>typeof r=="string",by:r=>Array.isArray(r),interviewOf:r=>typeof r=="string"||Array.isArray(r)};for(const r of l.SHARED){const p=e[r];p!==void 0&&u[r](p)?i[r]=structuredClone(p):t&&delete i[r]}return JSON.stringify(l.SHARED.map(r=>i[r]))!==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=l.pretty(l.withEventFields(i,e));s!==t&&(this.text=s,this.fileEditor?.replaceText(s,!1),this.dispatchEvent(new CustomEvent(y,{bubbles:!0,composed:!0,detail:{field:"transcript",event:e,text:s}})))}template(e){return l.pretty(l.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([T(()=>import("./SightingFileEditor-C0NC8jbz.js"),__vite__mapDeps([0,1]),import.meta.url),T(()=>import("./interviewSchema-CMRZUnRy.js"),[],import.meta.url),M(this.language)]);return this.fileEditor=new t(this.shadow.getElementById("file-editor"),e,a=>this.onEdited(a),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 k.json(i);this.current===e&&typeof s=="object"&&s!==null&&!Array.isArray(s)?(this.applyFileToEvent(s,!1)&&this.fileChangedEvent(),t=l.pretty(l.withEventFields(s,e))):t=l.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 a=s.id??"1";s.url=`${a.startsWith("interview")?a:`interview-${a}`}.json`,this.field("url").value=s.url,this.dispatchEvent(new CustomEvent(y,{bubbles:!0,composed:!0,detail:{field:"url",event:s}}))}this.dispatchEvent(new CustomEvent(y,{bubbles:!0,composed:!0,detail:{field:"transcript",event:s,text:e}})),this.group==="exchanges"&&this.showExchanges()}fileChangedEvent(){this.render(),this.dispatchEvent(new CustomEvent(y,{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=l.pretty(t?l.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:a}=await T(async()=>{const{InterviewImport:d}=await import("./interviewImport-C-TiARLK.js");return{InterviewImport:d}},[],import.meta.url),u=this.current,r=[u?.interviewOf].flat().filter(d=>typeof d=="string"),p=a.parse(i,{by:(u?.by??[]).map(d=>l.byText([d])).filter(d=>d!==""),witnesses:this.options.filter(d=>r.length===0||r.includes(d.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 k.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 $(){customElements.get(R)||customElements.define(R,l)}export{y as INTERVIEW_CHANGE_EVENT,R as INTERVIEW_ELEMENT_NAME,j as INTERVIEW_SEEK_EVENT,l as InterviewElement,$ as register};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
class i{static isProofread(a){const r=Object.keys(a.speakers);return r.length>0&&r.every(t=>{const e=a.proofreading?.[t]?.status;return e==="approved"||e==="covered"})}static said(a){return a.parts?a.parts.map(r=>"text"in r?r.text:"…").join(" "):a.redacted?void 0:a.text??""}static sightingsOf(a){return[a.interviewOf].flat().filter(r=>typeof r=="string")}static passagesAbout(a,r){const t=i.sightingsOf(a),e=[];return a.segments.forEach((s,n)=>{for(const o of s.refs??[]){if(o.sighting===void 0?!t.includes(r):o.sighting!==r)continue;const c=i.said(s);e.push({index:n,speaker:s.speaker,at:o.at,...c!==void 0?{text:c}:{},...o.range?{range:o.range}:{}})}}),e.sort((s,n)=>s.at-n.at||s.index-n.index)}static clock(a,r=!1){const t=Math.round(a*10),e=Math.floor(t/10),s=c=>String(c).padStart(2,"0"),n=Math.floor(e/3600),o=`${n>0||r?s(Math.floor(e/60)%60):Math.floor(e/60)}:${s(e%60)}`;return`${n>0?`${n}:`:""}${o}${r||t%10!==0?`.${t%10}`:""}`}static speakerName(a,r){const t=a.speakers[r];if(!t)return r;const e=typeof t.title=="string"?t.title:t.title?Object.values(t.title)[0]:void 0;return e||(t.by?"people"in t.by?t.by.people:"org"in t.by?t.by.org:t.by.title??t.role:t.role)}static stamp(a){const r=Math.round(a*1e3),t=(e,s=2)=>String(e).padStart(s,"0");return`${t(Math.floor(r/36e5))}:${t(Math.floor(r/6e4)%60)}:${t(Math.floor(r/1e3)%60)}.${t(r%1e3,3)}`}static toVtt(a){const r=s=>s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"),t=a.segments.filter(s=>s.start!==void 0);return`WEBVTT
|
|
2
|
-
|
|
3
|
-
${t.map((s,n)=>{const o=t[n+1],c=s.end??o?.start??s.start+5,p=o&&o.start<c?Math.max(s.start,o.start):c;return`${i.stamp(s.start)} --> ${i.stamp(p)}
|
|
4
|
-
<v ${r(i.speakerName(a,s.speaker))}>${r(i.said(s)??"[…]")}`}).join(`
|
|
5
|
-
|
|
6
|
-
`)}
|
|
7
|
-
`}}export{i as InterviewFile};
|