Skip to content

Commit 6643c0b

Browse files
hhkaosclaude
andcommitted
import: ICS + JSON-LD importers and the editor import flow (M6b)
New connector packages (pure functions, no UI, never invent data — absent field = absent + warning identifying it): - @opentechevents/import-ics: icsToEvents(text). RFC 5545 parser tolerant of real-world exports, DTEND exclusive→inclusive, DURATION, RRULE not expanded (flagged), TENTATIVE/floating times/non-IANA TZIDs left pending + warning, HTML descriptions converted to Markdown. - @opentechevents/import-jsonld: htmlToEvents(html). schema.org Events from ld+json blocks (subtypes, arrays, @graph, ItemList), offsets are not IANA zones (local time kept, timezone pending), attendance/status enums mapped, truncated descriptions and unmodeled properties flagged. Editor (import screens per DESIGN.md "Importar desde fuentes existentes"): - "Import from event page" (URL fetch with always-available paste-HTML fallback for CORS) and "Import .ics" (file/URL), newest first, future events preselected, nothing imported silently. - Import queue: prev/next navigation, per-event discard, edits and position persisted in localStorage per repo, beforeunload warning while unsubmitted, confirmation before replacing form content or a pending queue. - Prefill marks the fields the source did not carry; venue auto-geocoded (Nominatim) into a proposed pin, flagged for review; source.name/url/ retrievedAt provenance (Meetup, Luma, Eventbrite… from the URL). - After "Open GitHub issue": scroll to banner + "check the feed" button (appears once maintainers merge the PR). - Venue + map merged into one block; timezone and license comboboxes (license suggests open, non-viral SPDX ids); feedback footer link prefilled with the editor URL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 77f4a52 commit 6643c0b

38 files changed

Lines changed: 4237 additions & 27 deletions
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
name: verify
3+
description: Build, launch and drive the OTE editor app in a browser to verify changes end-to-end.
4+
---
5+
6+
# Verifying the editor
7+
8+
- Launch: `PORT=8123 pnpm dev` in `apps/editor/` (esbuild watch + static
9+
server over `dist/`). Prints `Editor running at http://localhost:8123/`.
10+
- Open `http://localhost:8123/?repo=octocat/ote-demo` — any `owner/name`
11+
works; a nonexistent repo just logs 404s for config/contents/feed and the
12+
editor degrades to "all fields" with a warning banner. No setup needed.
13+
- Drive with Playwright MCP. Field inputs are addressable as
14+
`[data-key="<FormState key>"]`; field wrappers as `[data-field-id="<id>"]`.
15+
- Same-origin test assets: drop files into `apps/editor/dist/` and fetch
16+
them as `http://localhost:8123/<file>` (avoids CORS in URL-fetch flows).
17+
Remove them afterwards.
18+
- Playwright file uploads only accept paths under the repo root.
19+
- Validation errors stay hidden until the field is touched or "Review &
20+
submit" is pressed once — check `#valid-badge` (`✓ Ready`/`Incomplete`)
21+
for the live state.

apps/editor/index.html

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,50 @@ <h1>OTE event editor</h1>
6060
</label>
6161
</nav>
6262

63+
<!-- Sources to prefill a NEW event from; hidden in edit mode.
64+
The event page (JSON-LD) comes first: it usually carries more
65+
data (attendance mode, status, languages, geo) than an ICS.
66+
More options (cheat sheets…) will land here later. -->
67+
<div id="new-actions">
68+
<button id="jsonld-open" type="button" class="secondary">
69+
Import from event page
70+
</button>
71+
<button id="import-open" type="button" class="secondary">
72+
Import .ics
73+
</button>
74+
</div>
75+
76+
<div id="import-banner" hidden>
77+
<p id="import-banner-text"></p>
78+
<ul id="import-banner-warnings" hidden></ul>
79+
<div id="import-submitted" hidden>
80+
<p>
81+
Issue opened. The event will appear in the published feed once
82+
the repository's maintainers accept the PR (and Pages rebuilds).
83+
</p>
84+
<div class="actions">
85+
<button id="import-check-feed" type="button" class="secondary">
86+
Check the feed
87+
</button>
88+
<span id="import-check-result" class="hint"></span>
89+
</div>
90+
</div>
91+
<div class="actions">
92+
<button id="import-prev" type="button" class="secondary">
93+
← Previous
94+
</button>
95+
<button id="import-next" type="button" class="secondary">
96+
Next →
97+
</button>
98+
<button id="import-discard" type="button" class="secondary">
99+
Discard this event
100+
</button>
101+
<button id="import-dismiss" type="button" class="secondary">
102+
Dismiss all
103+
</button>
104+
</div>
105+
</div>
106+
63107
<form id="event-form" autocomplete="off"></form>
64108

65109
<ul id="document-errors" hidden></ul>
@@ -91,6 +135,101 @@ <h2>Review &amp; submit</h2>
91135
</div>
92136
</dialog>
93137

138+
<dialog id="import-dialog">
139+
<h2>Import from iCalendar (.ics)</h2>
140+
<p class="hint">
141+
Upload the file, or fetch it from a URL. You will pick which events
142+
to import; nothing is imported silently.
143+
</p>
144+
<p class="hint">
145+
Any calendar's <code>.ics</code> works — for example a Google
146+
Calendar export. On meetup.com, your group's calendar page
147+
(<code>meetup.com/&lt;group&gt;/events/calendar/</code>) links it
148+
as <code>https://www.meetup.com/&lt;group&gt;/events/ical/</code>.
149+
Luma and guild.host offer one per calendar too. Importing a single
150+
event? Its own page usually carries more data — try “Import from
151+
event page” first.
152+
</p>
153+
<div class="import-sources">
154+
<input
155+
id="import-file"
156+
type="file"
157+
accept=".ics,text/calendar"
158+
aria-label="iCalendar file"
159+
/>
160+
<form id="import-url-form">
161+
<input
162+
id="import-url"
163+
type="url"
164+
placeholder="https://…/calendar.ics"
165+
aria-label="iCalendar URL"
166+
/>
167+
<button type="submit" class="secondary">Fetch</button>
168+
</form>
169+
</div>
170+
<p id="import-status" class="hint" hidden></p>
171+
<div id="import-warnings" hidden></div>
172+
<ul id="import-list" hidden></ul>
173+
<div class="actions">
174+
<button id="import-cancel" type="button" class="secondary">
175+
Cancel
176+
</button>
177+
<button id="import-confirm" type="button" class="primary" disabled>
178+
Import selected
179+
</button>
180+
</div>
181+
</dialog>
182+
183+
<dialog id="jsonld-dialog">
184+
<h2>Import from an event page</h2>
185+
<p class="hint">
186+
Paste the event page's URL (Meetup, Eventbrite, Luma…). The events
187+
are read from the structured data (schema.org JSON-LD) the page
188+
already exposes — usually richer than an .ics export. You will
189+
pick which events to import; nothing is imported silently.
190+
</p>
191+
<form id="jsonld-url-form">
192+
<input
193+
id="jsonld-url"
194+
type="url"
195+
placeholder="https://www.meetup.com/…/events/…/"
196+
aria-label="Event page URL"
197+
/>
198+
<button type="submit" class="secondary">Fetch</button>
199+
</form>
200+
<div id="jsonld-fallback">
201+
<p>
202+
<strong>Or paste the page's HTML.</strong> Most platforms
203+
(meetup.com included) do not allow browser fetches (CORS). Open
204+
the event page, copy its code (right click → “View page source”,
205+
select all, copy) and paste it here — it is parsed locally,
206+
nothing is sent anywhere.
207+
</p>
208+
<textarea
209+
id="jsonld-html"
210+
rows="6"
211+
placeholder="Paste the page's HTML code here…"
212+
aria-label="Event page HTML"
213+
></textarea>
214+
<div class="actions">
215+
<button id="jsonld-parse" type="button" class="secondary">
216+
Read pasted HTML
217+
</button>
218+
</div>
219+
</div>
220+
<p id="jsonld-status" class="hint" hidden></p>
221+
<div id="jsonld-warnings" hidden></div>
222+
<ul id="jsonld-list" hidden></ul>
223+
<div class="actions">
224+
<button id="jsonld-cancel" type="button" class="secondary">
225+
Cancel
226+
</button>
227+
<button id="jsonld-confirm" type="button" class="primary" disabled>
228+
Import selected
229+
</button>
230+
</div>
231+
</dialog>
232+
94233
<section id="fallback" hidden>
95234
<h3>URL too long — copy manually</h3>
96235
<p>
@@ -109,6 +248,10 @@ <h3>URL too long — copy manually</h3>
109248
</section>
110249
</main>
111250

251+
<footer>
252+
<a id="feedback-link" target="_blank" rel="noopener">🐛 Found a bug?</a>
253+
</footer>
254+
112255
<script type="module" src="./main.js"></script>
113256
</body>
114257
</html>

apps/editor/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
},
1414
"dependencies": {
1515
"@opentechevents/build-feed": "workspace:*",
16+
"@opentechevents/import-ics": "workspace:*",
17+
"@opentechevents/import-jsonld": "workspace:*",
1618
"@opentechevents/validate": "workspace:*",
1719
"leaflet": "^1.9.4"
1820
},

0 commit comments

Comments
 (0)