@@ -22,8 +22,9 @@ import { formatSource, jsonProblem, place, type Target } from './json-scan'
2222 * markup is `index.njk` beside this file, and its styles are the site's own, in `site.css`.
2323 *
2424 * Both of its visitors are people. One a chat sent, with the source in the link: it is built at
25- * once. One came with no source and does not know the page: that one is told what it makes, and
26- * shown a sample that plays (`SAMPLE`). The markup says which words are for whom, and the class
25+ * once. One came with no source and does not know the page: that one is told what it makes, shown
26+ * a sample that plays (`SAMPLE`), and finds the sample's source in the box, which the first paste
27+ * replaces whole (`prefillSample`). The markup says which words are for whom, and the class
2728 * `linked` on `<html>` shows them, set before the page paints by the script in `index.njk` and
2829 * kept in step with the address here.
2930 */
@@ -106,6 +107,10 @@ let samplePlays = false
106107/** Whether the sample has been asked to measure itself again, and the wait for it to be still. */
107108let sampleAsked = false
108109let sampleStill : ReturnType < typeof setTimeout > | undefined
110+ /** The sample's source as the page put it in the box, laid out; `null` before it has. */
111+ let sampleText : string | null = null
112+ /** Whether the box holds the sample's source as the page put it there, which a paste then replaces whole. */
113+ const sampleUntouched = ( ) : boolean => sampleText !== null && sourceField . value === sampleText
109114
110115function setStatus ( text : string , state : 'idle' | 'building' | 'ready' | 'error' ) : void {
111116 status . textContent = text
@@ -381,13 +386,17 @@ function setSource(text: string): void {
381386 followLater ( )
382387}
383388
384- // Where the editor has not loaded, a source pasted whole is laid out here, as the editor does it.
389+ // Where the editor has not loaded, a source pasted whole is laid out here, as the editor does it,
390+ // and a paste over the sample as the page put it replaces it whole.
385391sourceField . addEventListener ( 'paste' , ( event ) => {
386392 const text = event . clipboardData ?. getData ( 'text/plain' )
387- const laidOut = text && sourceField . selectionStart === 0 && sourceField . selectionEnd === sourceField . value . length ? formatSource ( text ) : null
388- if ( laidOut === null ) return
393+ if ( ! text ) return
394+ const replaced = sampleUntouched ( )
395+ if ( ! replaced && ( sourceField . selectionStart !== 0 || sourceField . selectionEnd !== sourceField . value . length ) ) return
396+ const insert = formatSource ( text ) ?? ( replaced ? text : null )
397+ if ( insert === null ) return
389398 event . preventDefault ( )
390- sourceField . value = laidOut
399+ sourceField . value = insert
391400 sourceField . setSelectionRange ( 0 , 0 )
392401 sourceChanged ( )
393402} )
@@ -478,13 +487,38 @@ sample.addEventListener('error', (event) => {
478487 noteSample ( `The sample does not play here: ${ message } ` )
479488} )
480489
481- // The sample's source, for a visitor with none who wants to try the steps: it is theirs to build.
490+ async function fetchSampleSource ( ) : Promise < string > {
491+ const response = await fetch ( SAMPLE_SOURCE )
492+ if ( ! response . ok ) throw new Error ( String ( response . status ) )
493+ return response . text ( )
494+ }
495+
496+ /** The sample's source in the box, as the page puts it there: a paste replaces it whole until it is changed. */
497+ function putSample ( text : string ) : void {
498+ setSource ( text )
499+ sampleText = sourceField . value
500+ }
501+
502+ /**
503+ * A visitor who came with no source finds the sample's in the box, to build as it is, to change, or
504+ * to paste theirs over. It does not go over a source that came meanwhile, by a link or by hand, and
505+ * where it cannot be fetched the box stays empty, with its placeholder.
506+ */
507+ async function prefillSample ( ) : Promise < void > {
508+ try {
509+ const text = await fetchSampleSource ( )
510+ if ( linked || sourceField . value . trim ( ) ) return
511+ putSample ( text )
512+ } catch {
513+ // The box stays empty.
514+ }
515+ }
516+
517+ // The sample's source again, for a visitor who changed or replaced it.
482518sampleSource . addEventListener ( 'click' , async ( ) => {
483519 sampleSourceResult . textContent = ''
484520 try {
485- const response = await fetch ( SAMPLE_SOURCE )
486- if ( ! response . ok ) throw new Error ( String ( response . status ) )
487- setSource ( await response . text ( ) )
521+ putSample ( await fetchSampleSource ( ) )
488522 sampleSourceResult . textContent = 'It is in the box above: change it if you like, then Build.'
489523 } catch {
490524 sampleSourceResult . textContent = 'It could not be fetched; load the page again.'
@@ -516,11 +550,13 @@ async function fromLink(): Promise<void> {
516550
517551updateFiles ( )
518552void fromLink ( )
553+ // Once, as the page opens: `fromLink` has said by now whether the address carries a source.
554+ if ( ! linked ) void prefillSample ( )
519555window . addEventListener ( 'hashchange' , ( ) => void fromLink ( ) )
520556
521557// The editor is loaded apart: where it cannot be, the textarea is the source, as it was.
522558import ( './editor' ) . then ( ( { mountEditor } ) => {
523- editor = mountEditor ( sourceField , sourceChanged )
559+ editor = mountEditor ( sourceField , sourceChanged , sampleUntouched )
524560} ) . catch ( ( error : unknown ) => {
525561 console . warn ( 'The source editor did not load; the source stays a plain text field.' , error )
526562} )
0 commit comments