diff --git a/package.json b/package.json index a4a8f8e58..2fab0cf2a 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "rules": { "react/sort-comp": 0, "react/no-danger": 0, + "react/jsx-no-bind": 0, "brace-style": 0, "indent": 0, "lines-around-comment": 0, diff --git a/src/components/controllers/repl-page.jsx b/src/components/controllers/repl-page.jsx index 11854998e..888126769 100644 --- a/src/components/controllers/repl-page.jsx +++ b/src/components/controllers/repl-page.jsx @@ -1,6 +1,6 @@ -import { useRoute } from 'preact-iso'; +import { useLocation, useRoute } from 'preact-iso'; import { Repl } from './repl'; -import { useExample } from './repl/examples'; +import { fetchExample } from './repl/examples'; import { useContent, useResource } from '../../lib/use-resource'; import { useTitle, useDescription } from './utils'; import { useLanguage } from '../../lib/i18n'; @@ -15,7 +15,7 @@ export default function ReplPage() { useTitle(meta.title); useDescription(meta.description); - const [code, slug] = initialCode(query); + const code = useResource(() => getInitialCode(query), [query]); return (
@@ -24,11 +24,8 @@ export default function ReplPage() { height: 100% !important; overflow: hidden !important; } - footer { - display: none !important; - } `} - +
); } @@ -38,42 +35,31 @@ export default function ReplPage() { * * ?code -> ?example -> localStorage -> simple counter example */ -function initialCode(query) { - let code, slug; +async function getInitialCode(query) { + const { route } = useLocation(); + let code; if (query.code) { - try { - code = useResource(() => querySafetyCheck(atob(query.code)), [query.code]); - } catch (e) {} + code = querySafetyCheck(atob(query.code)); } else if (query.example) { - code = useExample([query.example]); - if (code) { - slug = query.example; - history.replaceState( - null, - null, - `/repl?example=${encodeURIComponent(slug)}` - ); + code = await fetchExample(query.example); + if (!code) { + route('/repl', true); } - else history.replaceState(null, null, '/repl'); } if (!code) { if (typeof window !== 'undefined' && localStorage.getItem('preact-www-repl-code')) { code = localStorage.getItem('preact-www-repl-code'); } else { - slug = 'counter'; + const slug = 'counter'; if (typeof window !== 'undefined') { - history.replaceState( - null, - null, - `/repl?example=${encodeURIComponent(slug)}` - ); + route(`/repl?example=${encodeURIComponent(slug)}`, true); } - code = useExample([slug]); + code = await fetchExample(slug); } } - return [code, slug]; + return code; } async function querySafetyCheck(code) { diff --git a/src/components/controllers/repl/examples.js b/src/components/controllers/repl/examples.js index 26a5f0512..387eb8ff7 100644 --- a/src/components/controllers/repl/examples.js +++ b/src/components/controllers/repl/examples.js @@ -1,5 +1,3 @@ -import { useResource } from '../../../lib/use-resource'; - import simpleCounterExample from './examples/simple-counter.txt?url'; import counterWithHtmExample from './examples/counter-with-htm.txt?url'; import todoExample from './examples/todo-list.txt?url'; @@ -69,15 +67,10 @@ export function getExample(slug, list = EXAMPLES) { } /** - * @param {[ slug: string ]} args - * @returns {string | undefined} + * @param {string} slug */ -export function useExample([slug]) { +export async function fetchExample(slug) { const example = getExample(slug); if (!example) return; - return useResource(() => loadExample(example.url), ['example', slug]); -} - -export async function loadExample(exampleUrl) { - return await fetch(exampleUrl).then(r => r.text()); + return await fetch(example.url).then(r => r.text()); } diff --git a/src/components/controllers/repl/index.jsx b/src/components/controllers/repl/index.jsx index 5b91ae557..ac793990c 100644 --- a/src/components/controllers/repl/index.jsx +++ b/src/components/controllers/repl/index.jsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'preact/hooks'; +import { useLocation, useRoute } from 'preact-iso'; import { Splitter } from '../../splitter'; -import { EXAMPLES, getExample, loadExample } from './examples'; +import { EXAMPLES, fetchExample } from './examples'; import { ErrorOverlay } from './error-overlay'; import { useStoredValue } from '../../../lib/localstorage'; import { useResource } from '../../../lib/use-resource'; @@ -13,9 +14,10 @@ import REPL_CSS from './examples.css?raw'; * @param {string} props.code * @param {string} [props.slug] */ -export function Repl({ code, slug }) { +export function Repl({ code }) { + const { route } = useLocation(); + const { query } = useRoute(); const [editorCode, setEditorCode] = useStoredValue('preact-www-repl-code', code); - const [exampleSlug, setExampleSlug] = useState(slug || ''); const [error, setError] = useState(null); const [copied, setCopied] = useState(false); @@ -23,6 +25,9 @@ export function Repl({ code, slug }) { // causes some bad jumping/pop-in. For the moment, this is the best option if (typeof window === 'undefined') return null; + /** + * @type {{ Runner: import('../repl/runner').default, CodeEditor: import('../../code-editor').default }} + */ const { Runner, CodeEditor } = useResource(() => Promise.all([ import('../../code-editor'), import('./runner') @@ -30,38 +35,30 @@ export function Repl({ code, slug }) { const applyExample = (e) => { const slug = e.target.value; - loadExample(getExample(slug).url) + fetchExample(slug) .then(code => { setEditorCode(code); - setExampleSlug(slug); - history.replaceState( - null, - null, - `/repl?example=${encodeURIComponent(slug)}` - ); + route(`/repl?example=${encodeURIComponent(slug)}`, true); }); }; - useEffect(() => { - const example = getExample(exampleSlug); - (async function () { - if (example) { - const code = await loadExample(example.url); - if (location.search && code !== editorCode) { - setExampleSlug(''); - history.replaceState(null, null, '/repl'); - } - } - })(); - }, [editorCode]); + const onEditorInput = (code) => { + setEditorCode(code); + + // Clears the (now outdated) example & code query params + // when a user begins to modify the code + if (location.search) { + route('/repl', true); + } + }; const share = () => { - if (!exampleSlug) { - history.replaceState( - null, - null, - `/repl?code=${encodeURIComponent(btoa(editorCode))}` - ); + // No reason to share semi-sketchy btoa'd code if there's + // a perfectly good example we can use instead + if (!query.example) { + // We use `history.replaceState` here as the code is only relevant on mount. + // There's no need to notify the router of the change. + history.replaceState(null, null, `/repl?code=${encodeURIComponent(btoa(editorCode))}`); } try { @@ -90,13 +87,13 @@ export function Repl({ code, slug }) {