diff --git a/apps/repl/app/routes/edit.ts b/apps/repl/app/routes/edit.ts index 1206bc7e6..c26f197c8 100644 --- a/apps/repl/app/routes/edit.ts +++ b/apps/repl/app/routes/edit.ts @@ -2,6 +2,7 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; import { DEFAULT_SNIPPET } from 'limber/snippets'; +import { formatFrom } from 'limber/utils/messaging'; import type RouterService from '@ember/routing/router-service'; import type Transition from '@ember/routing/transition'; @@ -35,6 +36,22 @@ export default class EditRoute extends Route { let hasFormat = qps.format !== undefined; if (!hasCode) { + /** + * Default starting doc is + * user-configurable. + * (whatever they did last) + */ + let format = localStorage.getItem('format'); + let doc = localStorage.getItem('document'); + + if (format && doc) { + console.info(`Found format and document in localStorage. Using those.`); + transition.abort(); + this.editor.fileURIComponent.set(doc, formatFrom(format)); + + return; + } + console.warn( 'URL contained no document information in the SearchParams. ' + 'Assuming glimdown and using the default sample snippet.' diff --git a/apps/repl/app/utils/editor-text.ts b/apps/repl/app/utils/editor-text.ts index b5a8fcda9..115fe506c 100644 --- a/apps/repl/app/utils/editor-text.ts +++ b/apps/repl/app/utils/editor-text.ts @@ -158,12 +158,22 @@ export class FileURIComponent { return base ?? window.location.toString(); }; - #updateWaiter: unknown; #frame?: number; #qps: URLSearchParams | undefined; + #tokens: unknown[] = []; #updateQPs = async (rawText: string, format: Format) => { + this.#tokens.push(queueWaiter.beginAsync()); + this.#_updateQPs(rawText, format); + }; + + #cleanup = () => { + this.#tokens.forEach((token) => { + queueWaiter.endAsync(token); + }); + }; + + #_updateQPs = async (rawText: string, format: Format) => { if (this.#frame) cancelAnimationFrame(this.#frame); - if (!this.#updateWaiter) this.#updateWaiter = queueWaiter.beginAsync(); let encoded = compressToEncodedURIComponent(rawText); let qps = new URLSearchParams(location.search); @@ -172,6 +182,19 @@ export class FileURIComponent { qps.delete('t'); qps.set('format', formatFrom(format)); + // @ts-expect-error this works + if (this.#qps?.c === qps.get('c') && this.#qps?.format === qps.get('format')) { + // no-op, we should not have gotten here + // it's a mistake to have tried to have update QPs. + // Someone should debug this. + this.#cleanup(); + + return; + } + + localStorage.setItem('format', formatFrom(format)); + localStorage.setItem('document', rawText); + this.#qps = { ...this.#qps, ...qps, @@ -179,8 +202,7 @@ export class FileURIComponent { this.#frame = requestAnimationFrame(async () => { if (isDestroyed(this) || isDestroying(this)) { - queueWaiter.endAsync(this.#updateWaiter); - this.#updateWaiter = null; + this.#cleanup(); return; } @@ -191,15 +213,11 @@ export class FileURIComponent { await new Promise((resolve) => setTimeout(resolve, DEBOUNCE_MS)); if (isDestroyed(this) || isDestroying(this)) { - queueWaiter.endAsync(this.#updateWaiter); - this.#updateWaiter = null; + this.#cleanup(); return; } - queueWaiter.endAsync(this.#updateWaiter); - this.#updateWaiter = null; - // On initial load, if we call #updateQPs, // we may not have a currentURL, because the first transition has yet to complete let base = this.router.currentURL?.split('?')[0]; @@ -207,14 +225,22 @@ export class FileURIComponent { if (macroCondition(isTesting())) { // eslint-disable-next-line @typescript-eslint/no-explicit-any base ??= (this.router as any) /* private API? */?.location?.path; + // @ts-expect-error private api + base = base.split('?')[0]; } else { base ??= window.location.pathname; } + /** + * At some point this added qps + * we don't want them though, so we'll strip them + */ + let next = `${base}?${qps}`; this.router.replaceWith(next); this.#text = rawText; + this.#cleanup(); }); }; } diff --git a/apps/repl/tests/application/-page/index.ts b/apps/repl/tests/application/-page/index.ts index 8fd537807..a682a472e 100644 --- a/apps/repl/tests/application/-page/index.ts +++ b/apps/repl/tests/application/-page/index.ts @@ -22,10 +22,19 @@ export class Page extends PageObject { async expectRedirectToContent( to: string, - { c, t, format }: { t?: string; c?: string; format?: string } = {} + { + c, + t, + format, + checks, + }: { t?: string; c?: string; format?: string; checks?: { aborted?: boolean } } = {} ) { let sawExpectedError = false; + let _checks = { + aborted: checks?.aborted ?? true, + }; + try { await visit(to); } catch (e) { @@ -45,14 +54,19 @@ export class Page extends PageObject { sawExpectedError = true; } - assert(`Expected to see a TransitionAborted error, but it did not occur.`, sawExpectedError); + if (_checks.aborted) { + assert( + `Expected to see a TransitionAborted error, but it did not occur. currentURL: ${currentURL()}`, + sawExpectedError + ); + } // Allow time for transitions to settle await settled(); let url = currentURL(); - assert(`Expected an URL, got ${url}`, url); + assert(`Expected an URL -- via currentURL(), got ${url}`, url); let [, search] = url.split('?'); let query = new URLSearchParams(search); @@ -66,7 +80,7 @@ export class Page extends PageObject { if (c) { let lzString = query.get('c'); - assert(`Missing c query param`, lzString); + assert(`Missing c query param. currentURL: ${url}`, lzString); let value = decompressFromEncodedURIComponent(lzString); diff --git a/apps/repl/tests/application/editor-format-test.gts b/apps/repl/tests/application/editor-format-test.gts index 539237bc1..9e75cd022 100644 --- a/apps/repl/tests/application/editor-format-test.gts +++ b/apps/repl/tests/application/editor-format-test.gts @@ -54,6 +54,22 @@ module('Editor > Format', function (hooks) { assert.true(page.editor.hasText('hi'), 'has passed text as well'); }); + test('after selecting text, it loads again when visiting /', async function (assert) { + await visit(`/edit?format=gjs&t=${defaultText}`); + await page.editor.load(); + + assert.strictEqual(page.editor.format, 'gjs'); + assert.true(page.editor.hasText('hi'), 'has passed text as well'); + + await page.expectRedirectToContent(`application`, { + format: 'gjs', + t: defaultText, + checks: { aborted: false }, + }); + assert.strictEqual(page.editor.format, 'gjs'); + assert.true(page.editor.hasText('hi'), 'has passed text as well'); + }); + test('can start with glimdown, and change to gjs', async function (assert) { await page.expectRedirectToContent(`/edit`, { format: 'glimdown', diff --git a/apps/repl/tests/test-helper.ts b/apps/repl/tests/test-helper.ts index e2db62fb4..25d9e9217 100644 --- a/apps/repl/tests/test-helper.ts +++ b/apps/repl/tests/test-helper.ts @@ -22,6 +22,9 @@ Object.assign(window, { getSettledState, getPendingWaiterState, currentURL, curr setup(QUnit.assert); +QUnit.testStart(() => { + localStorage.clear(); +}); QUnit.testDone(resetOnerror); start();