From 58e4f098675d5a4f89b33bc4afed28a221373fb3 Mon Sep 17 00:00:00 2001 From: DipakHalkude <128908555+DipakHalkude@users.noreply.github.com> Date: Tue, 14 Oct 2025 17:21:16 +0530 Subject: [PATCH] Noscript share link (#8193) --- lib/handlers/noscript.ts | 107 ++++++++++++++++++++++++++++++++++++ views/noscript/compiler.pug | 4 +- views/noscript/share.pug | 37 +++++++++++++ 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 views/noscript/share.pug diff --git a/lib/handlers/noscript.ts b/lib/handlers/noscript.ts index 375584037..151c7e55a 100644 --- a/lib/handlers/noscript.ts +++ b/lib/handlers/noscript.ts @@ -63,6 +63,8 @@ export class NoScriptHandler { ), ); }) + .get('/noscript/share', cached, csp, this.handleShareLink.bind(this)) + .post('/noscript/share', express.urlencoded({extended: true}), cached, csp, this.handleShareLink.bind(this)) .get('/noscript/:language', cached, csp, (req, res) => { this.renderNoScriptLayout(undefined, req, res); }); @@ -153,4 +155,109 @@ export class NoScriptHandler { ), ); } + + async handleShareLink(req: express.Request, res: express.Response) { + // Getting form data with proper type checking - handle both GET and POST + const source = + typeof req.body.source === 'string' + ? req.body.source + : typeof req.query.source === 'string' + ? req.query.source + : ''; + const compiler = + typeof req.body.compiler === 'string' + ? req.body.compiler + : typeof req.query.compiler === 'string' + ? req.query.compiler + : ''; + const userArguments = + typeof req.body.userArguments === 'string' + ? req.body.userArguments + : typeof req.query.userArguments === 'string' + ? req.query.userArguments + : ''; + const language = + typeof req.body.lang === 'string' + ? req.body.lang + : typeof req.query.language === 'string' + ? req.query.language + : 'c++'; + + logger.debug('Received data for sharing:', {source, compiler, userArguments, language}); + + // Creating a simple state for sharing + const state = this.createDefaultState(language as LanguageKey); + + if (source) { + const session = state.findOrCreateSession(1); + session.source = source; + session.language = language; + + if (compiler) { + const compilerObj = session.findOrCreateCompiler(1); + compilerObj.id = compiler; + } + + if (userArguments) { + const compilerObj = session.findOrCreateCompiler(1); + compilerObj.options = userArguments; + } + } + + // Generating shareable URL + const shareableUrl = await this.generateShareableUrl(state); + + const httpRoot = (this.renderConfig as any).httpRoot || '/'; + const relativeUrl = shareableUrl.substring(shareableUrl.lastIndexOf('/z/') + 1); + const shortlink = `${req.protocol}://${req.get('host')}${httpRoot}${relativeUrl}`; + + logger.debug('Shareable URL:', shortlink); + + // Rendering the share template + const renderConfig = this.renderConfig( + { + embedded: false, + mobileViewer: isMobileViewer(req), + wantedLanguage: language, + clientstate: state, + shareableUrl: shortlink, + source: source, + }, + req.query, + ); + + // Adding httpRoot to the render config + (renderConfig as any).httpRoot = httpRoot; + + res.render('noscript/share', renderConfig); + } + + async generateShareableUrl(state: ClientState): Promise { + try { + // Creating the stored object like the main handler does + const {config, configHash} = StorageBase.getSafeHash(state); + + // Finding or create the unique subhash + const result = await this.storageHandler.findUniqueSubhash(configHash); + + if (!result.alreadyPresent) { + const storedObject = { + prefix: result.prefix, + uniqueSubHash: result.uniqueSubHash, + fullHash: configHash, + config: config, + }; + + await this.storageHandler.storeItem(storedObject, {} as express.Request); + } + + return `/z/${result.uniqueSubHash}`; + } catch (err) { + logger.error(`Error storing share state: ${err}`); + // Fallback to direct encoding + const stateString = JSON.stringify(state); + const base64State = Buffer.from(stateString).toString('base64url'); + return `/#${base64State}`; + } + } } diff --git a/views/noscript/compiler.pug b/views/noscript/compiler.pug index 00873df52..efbbe1efb 100644 --- a/views/noscript/compiler.pug +++ b/views/noscript/compiler.pug @@ -40,4 +40,6 @@ block content textarea#source(name='source' cols='70' rows='10' placeholder='Type your code here' autocorrect="off" autocapitalize="off" spellcheck="false") =session.source .form-pair-inlined - input(type='submit' value='Compile') + input.btn(type='submit' value='Compile') + | + input.btn(type='submit' value='Get Shareable Link' formaction=`${httpRoot}noscript/share` formmethod='POST') \ No newline at end of file diff --git a/views/noscript/share.pug b/views/noscript/share.pug new file mode 100644 index 000000000..4a367c050 --- /dev/null +++ b/views/noscript/share.pug @@ -0,0 +1,37 @@ +- var noscript = true +doctype html +html(lang="en") + head + title Shareable Link - Compiler Explorer + link(href=require("noscript.css") rel="stylesheet") + link(href=require("vendor.css") rel="stylesheet") + link(href=require("main.css") rel="stylesheet") + body + .container.mt-4 + h1 Shareable Link Generated + + .card + .card-body + h5.card-title Your shareable link: + .input-group.mb-3 + input.form-control(type="text" value=shareableUrl readonly) + button.btn.btn-primary(type="button" onclick="copyToClipboard()") Copy + + if source + .mb-3 + strong Source code preview: + pre.bg-light.p-3(style="white-space: pre-wrap; font-family: monospace;") #{source} + + p.card-text + | This link will open your code in the main Compiler Explorer interface. + + .mt-3 + a.btn.btn-success.me-2(href=shareableUrl target="_blank") Open in Compiler Explorer + + script. + function copyToClipboard() { + const input = document.querySelector('input'); + input.select(); + document.execCommand('copy'); + alert('Link copied to clipboard!'); + } \ No newline at end of file