Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/assets/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,9 @@ tbody th a:hover code { text-decoration: underline; }
at once for one a chat sent with the source in the link. What is said to one alone is not shown
to the other; the class is on <html> before the page paints (index.njk). */
.linked .when-unlinked, :root:not(.linked) .when-linked { display: none; }
/* A link whose source could not be read: what is said of the source it carried gives way to what
is said of the link, from when build-page.ts has tried it. */
.unread .when-read, :root:not(.unread) .when-unread { display: none; }

/* A bar at the top, then bands the width of the window, each holding a column of the page's width. */
:root {
Expand Down
36 changes: 34 additions & 2 deletions src/h5p/build/build-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import { formatSource, jsonProblem, place, type Target } from './json-scan'
* a sample that plays (`SAMPLE`), and finds the sample's source in the box, which the first paste
* replaces whole (`prefillSample`). The markup says which words are for whom, and the class
* `linked` on `<html>` shows them, set before the page paints by the script in `index.njk` and
* kept in step with the address here.
* kept in step with the address here. A link whose source cannot be read is said to be damaged,
* with the ways to bring the source (`linkUnread`).
*/

type Opened = Awaited<ReturnType<typeof openPack>>
Expand All @@ -41,6 +42,8 @@ const sourceField = $<HTMLTextAreaElement>('source')
const sourceLabel = $('source-label')
const sourceState = $('source-state')
const sourceFile = $<HTMLInputElement>('source-file')
const linkState = $('link-state')
const openSource = $<HTMLButtonElement>('open-source')
const filesStep = $('files-step')
const fileList = $<HTMLUListElement>('file-list')
const filesInput = $<HTMLInputElement>('files')
Expand Down Expand Up @@ -117,6 +120,28 @@ function setStatus(text: string, state: 'idle' | 'building' | 'ready' | 'error')
status.dataset.state = state
}

/**
* A link whose source cannot be read. A chat writes the link out in its reply, and a compressed
* source survives no wrong character: a link ChatGPT gave held 43 characters of its own in place
* of 37, near its start, and was the command's output everywhere else. The page then said the link
* had carried the source, over an empty box, with the reason under Build, out of sight in a window
* 800px high. So it says the link is damaged where a person who opened it looks. The class `unread`
* on `<html>` shows what the hero says of such a link in place of what it says of a source the link
* carried, for the rest of the visit, since the link carried none whatever comes after. The words
* above the box are the ways to bring the source, and stay until one is here (`checkSource`).
*/
function linkUnread(unread: boolean): void {
document.documentElement.classList.toggle('unread', unread)
showLinkState(unread)
}

/** The words above the box, and with them the step's marker, which site.css draws from their state. */
function showLinkState(show: boolean): void {
linkState.hidden = !show
if (show) linkState.dataset.state = 'error'
else delete linkState.dataset.state
}

/** Where a problem is in the source, for the button that goes there: its place in the text, or none. */
function target({ path, at }: Problem): Target {
return at === undefined ? { path } : { at }
Expand Down Expand Up @@ -316,6 +341,8 @@ player.addEventListener('error', (event) => {
*/
function checkSource(): void {
const text = sourceField.value
// A source came after a link that could not be read, from a file or a paste: the ways to bring one go.
if (!linkState.hidden && compactSource(text)) showLinkState(false)
const problem = jsonProblem(text)
sourceState.dataset.state = problem ? 'error' : text.trim() ? 'ok' : ''
if (!problem) {
Expand Down Expand Up @@ -406,6 +433,8 @@ sourceFile.addEventListener('change', async () => {
if (!file) return
setSource(await file.text())
})
// The same file field, from the words above the box on a link that could not be read.
openSource.addEventListener('click', () => sourceFile.click())

// The label is for the textarea, which the editor hides: a click on it goes to the editor.
sourceLabel.addEventListener('click', () => editor?.focus())
Expand Down Expand Up @@ -537,9 +566,12 @@ async function fromLink(): Promise<void> {
try {
text = await sourceFromFragment(location.hash)
} catch (error) {
setStatus(error instanceof Error ? error.message : String(error), 'error')
// Why it cannot be read is for whoever looks into it; the person is told what to do.
console.warn(error instanceof Error ? error.message : String(error))
linkUnread(true)
return
}
linkUnread(false)
if (text === null) return
carried = compactSource(text) ?? ''
setSource(text)
Expand Down
29 changes: 25 additions & 4 deletions src/h5p/build/index.njk
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ csp: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' da
(#source=), gets the build at once. One who came with none gets a landing page: what it makes,
a sample that plays, how to make their own. What is said to one alone is marked when-linked or
when-unlinked, and site.css shows it by the class this line puts on <html> before the page
paints; build-page.ts keeps the class in step with the address from then on. -#}
paints; build-page.ts keeps the class in step with the address from then on. A link whose
source cannot be read is a third case, which build-page.ts finds once it has tried: it puts
`unread` on <html>, and what is said of a source the link carried is marked when-read, what is
said in its place when-unread. -#}
<script>if (new URLSearchParams(location.hash.slice(1)).has("source")) document.documentElement.classList.add("linked")</script>
{% include "topbar.njk" %}

Expand All @@ -51,11 +54,20 @@ csp: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' da
<a class="button button-solid button-large" href="#make">Make your own</a>
<a class="button button-large" href="#build-it">Paste your chat's JSON</a>
</div>
<p class="lead when-linked">The link you opened carries an activity an AI chat wrote.</p>
<p class="when-linked">
<p class="lead when-linked when-read">The link you opened carries an activity an AI chat wrote.</p>
<p class="when-linked when-read">
This page builds it in your browser, where you play it and download a
<code>.h5p</code> file that Moodle or any other H5P site takes. Nothing is uploaded.
</p>
{#- A link whose source cannot be read: a chat writes the link out itself, and one wrong
character leaves nothing of a compressed source. The page says so here, first, in place
of saying the link carried an activity, and what to do above the source's box. These
two stay for the visit, so they say what stays true once the source has come another way. -#}
<p class="lead when-unread">The link you opened arrived damaged, so the activity in it cannot be read.</p>
<p class="when-unread">
This page builds an activity in your browser from its source, which
<a href="#source-heading">step 1</a> says how to bring here. Nothing is uploaded.
</p>
</div>
{#- The sample is /h5p/build/sample.h5p, which Vite builds from sample.json beside this file.
build-page.ts gives the player its address only for a visitor who came with no source. -#}
Expand Down Expand Up @@ -157,7 +169,16 @@ csp: "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' da
<section class="step" aria-labelledby="source-heading">
<h3 id="source-heading"><span class="step-marker"><span class="step-number">1</span></span> Source</h3>
<p class="when-unlinked">The source is your activity as the chat wrote it. The sample's is here to start with: paste yours over it, or open it from a file.</p>
<p class="when-linked">The source is your activity as the chat wrote it. The link carried it here.</p>
<p class="when-linked">The source is your activity as the chat wrote it. <span class="when-read">The link carried it here.</span></p>
{#- Shown by build-page.ts for a link whose source cannot be read, until a source is here: above
the box, where a person who opened the link looks, with each way to bring the source. -#}
<p class="status" id="link-state" role="status" hidden>
The source this link carried cannot be read: a chat can get characters wrong when it
writes out a long link. Ask it to give the link again. If it gave you a
<code>.source.json</code> file,
<button type="button" class="link-button" id="open-source">open that file here</button>
and choose Build below. Or ask it for the source as JSON and paste it into the box.
</p>
<div class="field">
<label for="source" id="source-label">The source, as JSON</label>
<textarea id="source" rows="12" spellcheck="false" autocomplete="off" placeholder="The source your chat wrote goes here. It starts with { and ends with }."></textarea>
Expand Down
Loading