mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 07:04:04 -05:00
Fix embedded iframe links not updating with code state (#8166)
## Summary Fixes the issue where the "Edit on Compiler Explorer" link in embedded iframes doesn't update with the current code state, staying as "/" instead of including the serialized state. ## Changes - Refactored `Sharing` class into a base class `SharingBase` that handles state tracking and embedded link updates - `Sharing` class now extends `SharingBase` and adds the full sharing UI features - Created `initialiseSharing()` function that instantiates the appropriate class based on embedded mode - Updated `main.ts` to use the new initialization function - Updated `history.ts` to use `SharingBase.filterComponentState()` instead of `Sharing.filterComponentState()` ## Testing - TypeScript compilation passes (`npm run ts-check`) - Linting passes (`npm run lint`) - Tests pass (`npm run test-min`) - Manual browser testing confirms embedded links now update correctly Closes #8097 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
|
||||
import _ from 'underscore';
|
||||
import {localStorage} from './local.js';
|
||||
import {Sharing} from './sharing.js';
|
||||
import {SharingBase} from './sharing.js';
|
||||
|
||||
const maxHistoryEntries = 30;
|
||||
export type HistorySource = {dt: number; source: string};
|
||||
@@ -100,7 +100,7 @@ export function trackHistory(layout: any) {
|
||||
let lastState: string | null = null;
|
||||
const debouncedPush = _.debounce(push, 500);
|
||||
layout.on('stateChanged', () => {
|
||||
const stringifiedConfig = JSON.stringify(Sharing.filterComponentState(layout.toConfig()));
|
||||
const stringifiedConfig = JSON.stringify(SharingBase.filterComponentState(layout.toConfig()));
|
||||
if (stringifiedConfig !== lastState) {
|
||||
lastState = stringifiedConfig;
|
||||
debouncedPush(stringifiedConfig);
|
||||
|
||||
@@ -64,7 +64,7 @@ import {Presentation} from './presentation.js';
|
||||
import {Printerinator} from './print-view.js';
|
||||
import {setupRealDark, takeUsersOutOfRealDark} from './real-dark.js';
|
||||
import {Settings, SiteSettings} from './settings.js';
|
||||
import {Sharing} from './sharing.js';
|
||||
import {initialiseSharing} from './sharing.js';
|
||||
import {Themer} from './themes.js';
|
||||
import * as url from './url.js';
|
||||
import {formatISODate, updateAndCalcTopBarHeight} from './utils.js';
|
||||
@@ -779,7 +779,7 @@ function start() {
|
||||
|
||||
History.trackHistory(layout);
|
||||
setupSiteTemplateWidgetButton(layout);
|
||||
if (!options.embedded) new Sharing(layout);
|
||||
initialiseSharing(layout, !!options.embedded);
|
||||
new Printerinator(hub, themer);
|
||||
|
||||
hub.layout.eventHub.emit('settingsChange', settings); // Ensure everyone knows the settings
|
||||
|
||||
@@ -85,10 +85,61 @@ const shareServices = {
|
||||
},
|
||||
};
|
||||
|
||||
export class Sharing {
|
||||
private layout: GoldenLayout;
|
||||
private lastState: any;
|
||||
// Base class that handles state tracking and embedded link updates
|
||||
export class SharingBase {
|
||||
protected layout: GoldenLayout;
|
||||
protected lastState: string | null = null;
|
||||
|
||||
constructor(layout: GoldenLayout) {
|
||||
this.layout = layout;
|
||||
this.initCallbacks();
|
||||
}
|
||||
|
||||
protected initCallbacks(): void {
|
||||
this.layout.on('stateChanged', this.onStateChanged.bind(this));
|
||||
}
|
||||
|
||||
protected onStateChanged(): void {
|
||||
const config = SharingBase.filterComponentState(this.layout.toConfig());
|
||||
this.ensureUrlIsNotOutdated(config);
|
||||
|
||||
// Update embedded links if present (works in both modes)
|
||||
if (options.embedded) {
|
||||
const strippedToLast = window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/') + 1);
|
||||
$('a.link').prop('href', strippedToLast + '#' + url.serialiseState(config));
|
||||
}
|
||||
}
|
||||
|
||||
protected ensureUrlIsNotOutdated(config: any): void {
|
||||
const stringifiedConfig = JSON.stringify(config);
|
||||
if (stringifiedConfig !== this.lastState) {
|
||||
if (this.lastState != null && window.location.pathname !== window.httpRoot) {
|
||||
window.history.replaceState(null, '', window.httpRoot);
|
||||
}
|
||||
this.lastState = stringifiedConfig;
|
||||
}
|
||||
}
|
||||
|
||||
public static filterComponentState(config: any): any {
|
||||
function filterComponentStateImpl(component: any) {
|
||||
if (component.content) {
|
||||
for (let i = 0; i < component.content.length; i++) {
|
||||
filterComponentStateImpl(component.content[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (component.componentState) {
|
||||
delete component.componentState.selection;
|
||||
}
|
||||
}
|
||||
|
||||
config = cloneDeep(config);
|
||||
filterComponentStateImpl(config);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
export class Sharing extends SharingBase {
|
||||
private readonly share: JQuery;
|
||||
private readonly shareTooltipTarget: JQuery;
|
||||
private readonly shareShort: JQuery;
|
||||
@@ -100,9 +151,8 @@ export class Sharing {
|
||||
private clippyButton: ClipboardJS | null;
|
||||
private readonly shareLinkDialog: HTMLElement;
|
||||
|
||||
constructor(layout: any) {
|
||||
this.layout = layout;
|
||||
this.lastState = null;
|
||||
constructor(layout: GoldenLayout) {
|
||||
super(layout);
|
||||
this.shareLinkDialog = unwrap(document.getElementById('sharelinkdialog'), 'Share modal element not found');
|
||||
|
||||
this.share = $('#share');
|
||||
@@ -119,17 +169,16 @@ export class Sharing {
|
||||
this.clippyButton = null;
|
||||
|
||||
this.initButtons();
|
||||
this.initCallbacks();
|
||||
this.initFullCallbacks();
|
||||
}
|
||||
|
||||
private initCallbacks(): void {
|
||||
private initFullCallbacks(): void {
|
||||
this.layout.eventHub.on('displaySharingPopover', () => {
|
||||
this.openShareModalForType(LinkType.Short);
|
||||
});
|
||||
this.layout.eventHub.on('copyShortLinkToClip', () => {
|
||||
this.copyLinkTypeToClipboard(LinkType.Short);
|
||||
});
|
||||
this.layout.on('stateChanged', this.onStateChanged.bind(this));
|
||||
|
||||
this.shareLinkDialog.addEventListener('show.bs.modal', this.onOpenModalPane.bind(this));
|
||||
this.shareLinkDialog.addEventListener('hidden.bs.modal', this.onCloseModalPane.bind(this));
|
||||
@@ -152,25 +201,6 @@ export class Sharing {
|
||||
});
|
||||
}
|
||||
|
||||
private onStateChanged(): void {
|
||||
const config = Sharing.filterComponentState(this.layout.toConfig());
|
||||
this.ensureUrlIsNotOutdated(config);
|
||||
if (options.embedded) {
|
||||
const strippedToLast = window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/') + 1);
|
||||
$('a.link').prop('href', strippedToLast + '#' + url.serialiseState(config));
|
||||
}
|
||||
}
|
||||
|
||||
private ensureUrlIsNotOutdated(config: any): void {
|
||||
const stringifiedConfig = JSON.stringify(config);
|
||||
if (stringifiedConfig !== this.lastState) {
|
||||
if (this.lastState != null && window.location.pathname !== window.httpRoot) {
|
||||
window.history.replaceState(null, '', window.httpRoot);
|
||||
}
|
||||
this.lastState = stringifiedConfig;
|
||||
}
|
||||
}
|
||||
|
||||
private static bindToLinkType(bind: string): LinkType {
|
||||
switch (bind) {
|
||||
case 'Full':
|
||||
@@ -466,28 +496,6 @@ export class Sharing {
|
||||
return (navigator.clipboard as Clipboard | undefined) !== undefined;
|
||||
}
|
||||
|
||||
public static filterComponentState(config: any, keysToRemove: [string] = ['selection']): any {
|
||||
function filterComponentStateImpl(component: any) {
|
||||
if (component.content) {
|
||||
for (let i = 0; i < component.content.length; i++) {
|
||||
filterComponentStateImpl(component.content[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (component.componentState) {
|
||||
Object.keys(component.componentState)
|
||||
.filter(e => keysToRemove.includes(e))
|
||||
.forEach(key => {
|
||||
delete component.componentState[key];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
config = cloneDeep(config);
|
||||
filterComponentStateImpl(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
private static updateShares(container: JQuery, url: string): void {
|
||||
const baseTemplate = $('#share-item');
|
||||
_.each(shareServices, (service, serviceName) => {
|
||||
@@ -508,3 +516,14 @@ export class Sharing {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize sharing functionality based on whether we're in embedded mode or not
|
||||
export function initialiseSharing(layout: GoldenLayout, isEmbedded: boolean): void {
|
||||
if (isEmbedded) {
|
||||
// Just create the base class for state tracking and link updates
|
||||
new SharingBase(layout);
|
||||
} else {
|
||||
// Create full Sharing with all features
|
||||
new Sharing(layout);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user