From 7df74230744e8ab3c1ebde7b86c05462838d2957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Vogel?= <104342347+cedricvogel-leo@users.noreply.github.com> Date: Fri, 6 Nov 2026 19:06:45 +0100 Subject: [PATCH] IDE links carry the block's features= (P34 C) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit «IDE-Link kopieren» and «In IDE öffnen» put the block's effective features= list into the IDE link's payload ("features"), so the IDE tab starts with the same parts; a block without features= writes none. check:ready covers both. Co-Authored-By: Claude Opus 5.5 --- README.md | 12 ++++++++++-- scripts/check-ready.mjs | 18 ++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 12de9d7..6b49ec7 100644 --- a/README.md +++ b/README.md @@ -424,7 +424,10 @@ window.__glpythonMount = (args, send) => { ...codeFiles.map(({ path, source }) => [path, { path, label: path, kind: /\.py$/i.test(path) ? "python" : "text", code: source }]), ]) const heading = frame?.closest(".lia-slide__content")?.querySelector("h1, h2, h3, h4, h5, h6")?.textContent?.trim() - const json = JSON.stringify({ v: libraries.length > 0 ? 2 : 1, name: heading || document.title || strings.ideProject, mainFile: program.mainFile, files }) + // The block's own features= (what it shows) set the IDE's parts too; a + // block without features= leaves the IDE as it is. + const features = featuresOption ? requestedFeatures.filter((name) => featureNames.includes(name)) : null + const json = JSON.stringify({ v: libraries.length > 0 ? 2 : 1, name: heading || document.title || strings.ideProject, mainFile: program.mainFile, ...(features ? { features } : {}), files }) const [param, encoded] = fflate ? ["p", base64Url(fflate.deflateSync(new TextEncoder().encode(json), { level: 9 }))] : ["project", lzString.compressToEncodedURIComponent(json)] @@ -1139,7 +1142,12 @@ libraries by their URL - the IDE fetches them, so a block with a library keeps a short link (an Etappe 4 hand-in measured 1,980 characters in the IDE's old form and 1,185 in the compact one). Libraries are read-only in the IDE (a lock, no editing, renaming or deleting), -as they are here. A link longer than 2,083 characters - OneNote, for one, cuts +as they are here. A block with `features=` passes them on: the IDE then shows +the same parts (▶, Tests, Visualisieren, the console's input line, the → +chain export, coverage, `listtree`, «Baum»; `ide` and `reset` mean nothing +there), as the starting state of that IDE tab - its ⋮ «Optionen» can switch +each part, e.g. for a teacher looking at a hand-in. A block without +`features=` passes nothing, and the IDE shows what it always does. A link longer than 2,083 characters - OneNote, for one, cuts links there - is still copied, with a note to choose «In IDE öffnen» and download the project there for a hand-in. A link longer than GitHub Pages accepts (8192 characters of path and query, a diff --git a/scripts/check-ready.mjs b/scripts/check-ready.mjs index f04562c..25f917e 100644 --- a/scripts/check-ready.mjs +++ b/scripts/check-ready.mjs @@ -32,13 +32,15 @@ const blocks = [ { id: 'empty', mode: 'visual', editors: [{ text: '', fill: null }] }, { id: 'files', mode: 'eval', editors: [{ text: '', fill: 'start' }, { text: 'import datei1\nz = 3', fill: 'start' }] }, { id: 'click', mode: 'eval', editors: [{ text: 'w = 4', fill: 1000 }] }, + // A block with features= (LiaScript splits the argument at its commas). + { id: 'feat', mode: 'eval', options: ['features=run', 'repl', 'visualize', 'tests', 'ide', 'coverage', 'listtree', 'tree'], editors: [{ text: 'v = 5', fill: 'start' }] }, ] const escape = (text) => text.replace(/&/g, '&').replace(/"/g, '"').replace(/ glpython ready check -${blocks.map((block) => `
+${blocks.map((block) => `
${block.editors.map((editor) => ``).join('')}
${template.replaceAll('@0', block.id).replaceAll('@1', block.mode)}
`).join('\n')} @@ -79,7 +81,8 @@ window.addEventListener('message', (event) => { ${onload} for (const block of document.querySelectorAll('.lia-slide__content')) { const id = block.dataset.block - window.__glpythonMount([id, block.querySelector('.glpython-embed').className.split('--')[1], '@0', '@1', '@2', '@3', '@4', '@5', '@6', '@7'], { lia: () => {} }) + const options = JSON.parse(block.dataset.options) + window.__glpythonMount([id, block.querySelector('.glpython-embed').className.split('--')[1], ...['@0', '@1', '@2', '@3', '@4', '@5', '@6', '@7'].map((placeholder, index) => options[index] ?? placeholder)], { lia: () => {} }) } ` @@ -198,8 +201,19 @@ try { if (compact !== null) assert.match(compact, /^[A-Za-z0-9_-]+$/, 'base64url without padding') const payload = JSON.parse(compact !== null ? inflateRawSync(Buffer.from(compact, 'base64url')).toString('utf8') : lz.decompressFromEncodedURIComponent(old)) assert.equal(payload.mainFile, 'datei2.py') + assert.equal(payload.features, undefined, 'a block without features= writes none') assert.deepEqual(Object.values(payload.files).map((file) => [file.path, file.code]), [['datei1.py', ''], ['datei2.py', 'import datei1\nz = 3']]) }) + if (fflateLoads) { + await check('«IDE-Link kopieren» carries the block\'s features=', async () => { + await linkTab.waitForFunction(() => document.querySelector('#glpython-ide-feat')?.disabled === false, null, { timeout: 10000 }) + await linkTab.locator('#glpython-ide-feat').click() + await linkTab.waitForTimeout(300) + const link = new URL(await linkTab.evaluate(() => navigator.clipboard.readText())) + const payload = JSON.parse(inflateRawSync(Buffer.from(link.searchParams.get('p'), 'base64url')).toString('utf8')) + assert.deepEqual(payload.features, ['run', 'repl', 'visualize', 'tests', 'ide', 'coverage', 'listtree', 'tree']) + }) + } await linkContext.close() } } finally {