Add Electron upgrade feature with UI and backend fixes
Introduces an Electron upgrade section in settings, allowing users to check for and upgrade to the latest stable or nightly Electron versions. Implements backend logic to read the installed Electron version directly from package.json, properly handles switching between stable and nightly builds, and improves error handling and UI feedback during upgrade operations. Includes documentation of the upgrade process and bug fixes related to version display and upgrade reliability.
This commit is contained in:
@@ -0,0 +1,94 @@
|
|||||||
|
# Electron Upgrade Feature - Bug Fixes
|
||||||
|
|
||||||
|
## Problem Identified
|
||||||
|
The upgrade feature was downloading and installing new Electron versions successfully, but the app always showed the old version (1.0.0) after restart because:
|
||||||
|
|
||||||
|
1. **Version Source Issue**: The app was reading `app.getVersion()` which gets the version from `package.json` at startup time
|
||||||
|
2. **Package.json Not Re-read**: Even after npm installed a new Electron version, the app didn't re-read the updated `package.json`
|
||||||
|
3. **Runtime Display**: The About tab showed the bundled Electron version (37.x) which is baked into the binary at build time
|
||||||
|
|
||||||
|
## Solutions Implemented
|
||||||
|
|
||||||
|
### 1. **New Helper Function: `getInstalledElectronVersion()`**
|
||||||
|
- Reads `package.json` directly every time it's called (not cached)
|
||||||
|
- Extracts the actual installed Electron version from `devDependencies`
|
||||||
|
- Handles both stable (`electron`) and nightly (`electron-nightly`) packages
|
||||||
|
- Strips version specifiers (^, ~, etc.) to get the clean version number
|
||||||
|
- Falls back to `app.getVersion()` if reading fails
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function getInstalledElectronVersion() {
|
||||||
|
try {
|
||||||
|
const packageJsonPath = path.join(__dirname, 'package.json');
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||||
|
|
||||||
|
const electronDep = packageJson.devDependencies?.electron;
|
||||||
|
const electronNightlyDep = packageJson.devDependencies?.['electron-nightly'];
|
||||||
|
|
||||||
|
if (electronDep) {
|
||||||
|
return electronDep.replace(/^\D+/, '');
|
||||||
|
}
|
||||||
|
if (electronNightlyDep) {
|
||||||
|
return electronNightlyDep.replace(/^\D+/, '');
|
||||||
|
}
|
||||||
|
return app.getVersion();
|
||||||
|
} catch (err) {
|
||||||
|
return app.getVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. **Updated `get-electron-versions` Handler**
|
||||||
|
- Now uses `getInstalledElectronVersion()` instead of `app.getVersion()`
|
||||||
|
- Returns the actual installed version that was modified by npm
|
||||||
|
- Performs fresh version checks each time (no caching)
|
||||||
|
|
||||||
|
### 3. **Improved `upgrade-electron` Handler**
|
||||||
|
- Increased `maxBuffer` to handle large npm output
|
||||||
|
- Added cleanup logic to remove the old Electron variant when switching types
|
||||||
|
- Removes `electron` when upgrading to `nightly`
|
||||||
|
- Removes `electron-nightly` when upgrading to `stable`
|
||||||
|
- Better error logging to debug npm failures
|
||||||
|
- Returns clearer messages about installation status
|
||||||
|
|
||||||
|
### 4. **Enhanced UI/UX in settings.js**
|
||||||
|
- Added more descriptive status text ("Downloading and installing..." instead of just "Upgrading...")
|
||||||
|
- Disables all controls during upgrade to prevent multiple clicks
|
||||||
|
- Reduced restart delay from 2000ms to 1500ms for faster feedback
|
||||||
|
- Better error handling with proper cleanup of disabled states
|
||||||
|
|
||||||
|
## How It Works Now
|
||||||
|
|
||||||
|
1. **User clicks "Check for Updates"**
|
||||||
|
- Queries npm registry for latest version
|
||||||
|
- Uses `getInstalledElectronVersion()` to read current version from `package.json`
|
||||||
|
- Compares versions and shows if update is available
|
||||||
|
|
||||||
|
2. **User clicks "Upgrade Electron"**
|
||||||
|
- Confirms action
|
||||||
|
- Runs `npm install --save-dev electron@latest` (or `electron-nightly@latest`)
|
||||||
|
- npm downloads and installs new version
|
||||||
|
- Handler removes the other Electron variant from `package.json` if needed
|
||||||
|
- Shows success message
|
||||||
|
|
||||||
|
3. **App Restarts**
|
||||||
|
- Uses `app.relaunch()` and `app.quit()`
|
||||||
|
- When app relaunches, it:
|
||||||
|
- Loads new Electron binary from `node_modules`
|
||||||
|
- Runs new Electron version
|
||||||
|
- Settings page shows correct new version on next check
|
||||||
|
|
||||||
|
## Testing Recommendations
|
||||||
|
|
||||||
|
1. Test upgrading from stable to nightly version
|
||||||
|
2. Test upgrading from nightly back to stable
|
||||||
|
3. Verify version display updates after restart
|
||||||
|
4. Check that old variant is removed from `package.json`
|
||||||
|
5. Verify app runs stably with new Electron version
|
||||||
|
|
||||||
|
## Notes for Future Development
|
||||||
|
|
||||||
|
- The About tab displays `process.versions.electron` which is the bundled Chromium version, not the Electron framework version
|
||||||
|
- The Electron version we display in the upgrade section comes from `package.json` which is the actual framework version
|
||||||
|
- When building with electron-builder, the bundled version becomes fixed until next rebuild
|
||||||
|
- For development/testing, the upgrade feature reads live from `package.json`
|
||||||
@@ -666,6 +666,150 @@ ipcMain.handle('open-devtools', (event) => {
|
|||||||
return contents.isDevToolsOpened();
|
return contents.isDevToolsOpened();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Helper function to read package.json version
|
||||||
|
function getInstalledElectronVersion() {
|
||||||
|
try {
|
||||||
|
const packageJsonPath = path.join(__dirname, 'package.json');
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||||
|
|
||||||
|
// Get the version from devDependencies
|
||||||
|
const electronDep = packageJson.devDependencies?.electron;
|
||||||
|
const electronNightlyDep = packageJson.devDependencies?.['electron-nightly'];
|
||||||
|
|
||||||
|
if (electronDep) {
|
||||||
|
return electronDep.replace(/^\D+/, ''); // Remove ^ or ~ or other version specifiers
|
||||||
|
}
|
||||||
|
if (electronNightlyDep) {
|
||||||
|
return electronNightlyDep.replace(/^\D+/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.getVersion();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error reading installed electron version:', err);
|
||||||
|
return app.getVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Electron version management handlers
|
||||||
|
ipcMain.handle('get-electron-versions', async (event, buildType = 'stable') => {
|
||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let url;
|
||||||
|
|
||||||
|
if (buildType === 'nightly') {
|
||||||
|
// Get latest nightly version from npm
|
||||||
|
url = 'https://registry.npmjs.org/electron-nightly/latest';
|
||||||
|
} else {
|
||||||
|
// Get latest stable version from npm
|
||||||
|
url = 'https://registry.npmjs.org/electron/latest';
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = https.get(url, (res) => {
|
||||||
|
let data = '';
|
||||||
|
res.on('data', chunk => data += chunk);
|
||||||
|
res.on('end', () => {
|
||||||
|
try {
|
||||||
|
const packageInfo = JSON.parse(data);
|
||||||
|
// Get the actual installed version from package.json, not app.getVersion()
|
||||||
|
const installedVersion = getInstalledElectronVersion();
|
||||||
|
resolve({
|
||||||
|
available: packageInfo.version,
|
||||||
|
current: installedVersion,
|
||||||
|
buildType: buildType
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to parse version info:', err);
|
||||||
|
resolve({
|
||||||
|
available: null,
|
||||||
|
current: getInstalledElectronVersion(),
|
||||||
|
error: 'Failed to fetch version info'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on('error', (err) => {
|
||||||
|
console.error('Failed to fetch versions:', err);
|
||||||
|
resolve({
|
||||||
|
available: null,
|
||||||
|
current: getInstalledElectronVersion(),
|
||||||
|
error: err.message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request.setTimeout(5000, () => {
|
||||||
|
request.destroy();
|
||||||
|
resolve({
|
||||||
|
available: null,
|
||||||
|
current: getInstalledElectronVersion(),
|
||||||
|
error: 'Version check timed out'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('upgrade-electron', async (event, buildType = 'stable') => {
|
||||||
|
const { execFile } = require('child_process');
|
||||||
|
const packageName = buildType === 'nightly' ? 'electron-nightly' : 'electron';
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// First, remove the other electron package if switching types
|
||||||
|
const otherPackage = buildType === 'nightly' ? 'electron' : 'electron-nightly';
|
||||||
|
|
||||||
|
// Run npm install to upgrade the package
|
||||||
|
const args = ['install', '--save-dev', packageName + '@latest'];
|
||||||
|
|
||||||
|
execFile('npm', args,
|
||||||
|
{ cwd: __dirname, shell: true, maxBuffer: 10 * 1024 * 1024 },
|
||||||
|
(error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
console.error('Upgrade failed:', error);
|
||||||
|
console.error('stderr:', stderr);
|
||||||
|
resolve({
|
||||||
|
success: false,
|
||||||
|
error: error.message,
|
||||||
|
message: 'Failed to upgrade Electron'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('Upgrade output:', stdout);
|
||||||
|
console.log('Upgrade stderr:', stderr);
|
||||||
|
|
||||||
|
// Update package.json to remove the other electron variant if needed
|
||||||
|
try {
|
||||||
|
const packageJsonPath = path.join(__dirname, 'package.json');
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||||
|
|
||||||
|
// Remove electron if we're upgrading to nightly
|
||||||
|
if (buildType === 'nightly' && packageJson.devDependencies?.electron) {
|
||||||
|
delete packageJson.devDependencies.electron;
|
||||||
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
||||||
|
}
|
||||||
|
// Remove electron-nightly if we're upgrading to stable
|
||||||
|
else if (buildType === 'stable' && packageJson.devDependencies?.['electron-nightly']) {
|
||||||
|
delete packageJson.devDependencies['electron-nightly'];
|
||||||
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Could not clean up alternate electron package:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
success: true,
|
||||||
|
message: 'Electron upgrade completed. Restarting application...'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('restart-app', async (event) => {
|
||||||
|
// Quit and relaunch the app
|
||||||
|
app.relaunch();
|
||||||
|
app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
// Open local file dialog -> returns file:// URL (or null if cancelled)
|
// Open local file dialog -> returns file:// URL (or null if cancelled)
|
||||||
ipcMain.handle('show-open-file-dialog', async () => {
|
ipcMain.handle('show-open-file-dialog', async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -245,6 +245,31 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="customization-group">
|
||||||
|
<h3>Electron Upgrade</h3>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||||||
|
<div style="display: flex; gap: 8px; align-items: center;">
|
||||||
|
<label for="electron-version-select" style="min-width: 100px;">Select Build:</label>
|
||||||
|
<select id="electron-version-select" style="flex: 1; padding: 8px; border-radius: 4px; background-color: var(--secondary, #00C6FF); color: var(--bg, #121418); border: none; cursor: pointer;">
|
||||||
|
<option value="stable">Stable</option>
|
||||||
|
<option value="nightly">Nightly</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div id="electron-versions-container" style="display: flex; gap: 8px; flex-wrap: wrap; align-items: center;">
|
||||||
|
<span id="electron-loading" style="color: #888;">Loading available versions...</span>
|
||||||
|
<div id="electron-version-info" style="display: none;">
|
||||||
|
<span id="electron-version-text"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||||
|
<button id="check-electron-versions">Check for Updates</button>
|
||||||
|
<button id="upgrade-electron-btn" style="display: none;" disabled>Upgrade Electron</button>
|
||||||
|
<span id="electron-status-text" style="display: none; color: #888; align-self: center;"></span>
|
||||||
|
</div>
|
||||||
|
<p class="note" style="margin-top: 8px;">Upgrading Electron will require the application to restart.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="customization-group about-actions">
|
<div class="customization-group about-actions">
|
||||||
<button id="copy-about-btn">Copy diagnostics</button>
|
<button id="copy-about-btn">Copy diagnostics</button>
|
||||||
<a id="github-link" href="https://github.com/Bobbybear007/NebulaBrowser" class="github-btn" rel="noopener noreferrer">
|
<a id="github-link" href="https://github.com/Bobbybear007/NebulaBrowser" class="github-btn" rel="noopener noreferrer">
|
||||||
|
|||||||
+183
-1
@@ -262,7 +262,8 @@ async function populateAbout() {
|
|||||||
byId('about-mem').textContent = `${info.totalMemGB} GB`;
|
byId('about-mem').textContent = `${info.totalMemGB} GB`;
|
||||||
|
|
||||||
const copyBtn = document.getElementById('copy-about-btn');
|
const copyBtn = document.getElementById('copy-about-btn');
|
||||||
if (copyBtn) {
|
if (copyBtn && !copyBtn.dataset.listenerAttached) {
|
||||||
|
copyBtn.dataset.listenerAttached = 'true';
|
||||||
copyBtn.addEventListener('click', async () => {
|
copyBtn.addEventListener('click', async () => {
|
||||||
const payload = [
|
const payload = [
|
||||||
`Nebula ${info.appVersion} (${info.isPackaged ? 'packaged' : 'dev'})`,
|
`Nebula ${info.appVersion} (${info.isPackaged ? 'packaged' : 'dev'})`,
|
||||||
@@ -289,8 +290,189 @@ async function populateAbout() {
|
|||||||
// Populate about info after DOM is ready
|
// Populate about info after DOM is ready
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
populateAbout();
|
populateAbout();
|
||||||
|
setupElectronUpgrade();
|
||||||
|
|
||||||
|
// Refresh about info when About tab is clicked
|
||||||
|
const aboutTabBtn = document.getElementById('tab-about');
|
||||||
|
if (aboutTabBtn) {
|
||||||
|
aboutTabBtn.addEventListener('click', () => {
|
||||||
|
// Refresh after a short delay to allow tab transition
|
||||||
|
setTimeout(() => {
|
||||||
|
populateAbout();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Electron upgrade feature setup
|
||||||
|
async function setupElectronUpgrade() {
|
||||||
|
const versionSelect = document.getElementById('electron-version-select');
|
||||||
|
const checkBtn = document.getElementById('check-electron-versions');
|
||||||
|
const upgradeBtn = document.getElementById('upgrade-electron-btn');
|
||||||
|
const loadingSpan = document.getElementById('electron-loading');
|
||||||
|
const versionContainer = document.getElementById('electron-versions-container');
|
||||||
|
const versionInfo = document.getElementById('electron-version-info');
|
||||||
|
const versionText = document.getElementById('electron-version-text');
|
||||||
|
const statusText = document.getElementById('electron-status-text');
|
||||||
|
|
||||||
|
if (!checkBtn || !versionSelect) return;
|
||||||
|
|
||||||
|
let availableVersion = null;
|
||||||
|
let currentVersion = null;
|
||||||
|
|
||||||
|
const checkVersions = async () => {
|
||||||
|
try {
|
||||||
|
if (!ipc) {
|
||||||
|
showStatus('IPC not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkBtn.disabled = true;
|
||||||
|
loadingSpan.style.display = 'block';
|
||||||
|
versionInfo.style.display = 'none';
|
||||||
|
statusText.style.display = 'none';
|
||||||
|
statusText.textContent = '';
|
||||||
|
|
||||||
|
const buildType = versionSelect.value;
|
||||||
|
const result = await ipc.invoke('get-electron-versions', buildType);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
statusText.textContent = `Error: ${result.error}`;
|
||||||
|
statusText.style.display = 'block';
|
||||||
|
showStatus(`Failed to check versions: ${result.error}`);
|
||||||
|
} else {
|
||||||
|
availableVersion = result.available;
|
||||||
|
currentVersion = result.current;
|
||||||
|
|
||||||
|
if (availableVersion) {
|
||||||
|
versionText.textContent = `Available: ${availableVersion} | Current: ${currentVersion}`;
|
||||||
|
versionInfo.style.display = 'block';
|
||||||
|
|
||||||
|
// Enable upgrade button only if there's a newer version
|
||||||
|
const isNewer = compareVersions(availableVersion, currentVersion) > 0;
|
||||||
|
upgradeBtn.disabled = !isNewer;
|
||||||
|
upgradeBtn.style.display = 'block';
|
||||||
|
|
||||||
|
if (isNewer) {
|
||||||
|
statusText.textContent = 'Update available!';
|
||||||
|
statusText.style.color = '#4CAF50';
|
||||||
|
} else {
|
||||||
|
statusText.textContent = 'You are running the latest version.';
|
||||||
|
statusText.style.color = '#888';
|
||||||
|
}
|
||||||
|
statusText.style.display = 'block';
|
||||||
|
showStatus(`Current: ${currentVersion} | Latest ${buildType}: ${availableVersion}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkBtn.disabled = false;
|
||||||
|
loadingSpan.style.display = 'none';
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking versions:', error);
|
||||||
|
statusText.textContent = `Error: ${error.message}`;
|
||||||
|
statusText.style.display = 'block';
|
||||||
|
showStatus('Failed to check versions');
|
||||||
|
checkBtn.disabled = false;
|
||||||
|
loadingSpan.style.display = 'none';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpgrade = async () => {
|
||||||
|
const buildType = versionSelect.value;
|
||||||
|
if (!availableVersion) {
|
||||||
|
showStatus('No version information available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmed = confirm(
|
||||||
|
`Upgrade Electron from ${currentVersion} to ${availableVersion} (${buildType})?\n\nThe application will restart automatically.`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
upgradeBtn.disabled = true;
|
||||||
|
checkBtn.disabled = true;
|
||||||
|
versionSelect.disabled = true;
|
||||||
|
statusText.textContent = 'Downloading and installing...';
|
||||||
|
statusText.style.color = '#FFC107';
|
||||||
|
statusText.style.display = 'block';
|
||||||
|
showStatus('Starting Electron upgrade...');
|
||||||
|
|
||||||
|
const result = await ipc.invoke('upgrade-electron', buildType);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
statusText.textContent = result.message;
|
||||||
|
statusText.style.color = '#4CAF50';
|
||||||
|
showStatus(result.message);
|
||||||
|
|
||||||
|
// Restart the app after a short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
if (ipc) {
|
||||||
|
ipc.invoke('restart-app').catch(err => console.error('Restart failed:', err));
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
statusText.textContent = `Failed: ${result.error}`;
|
||||||
|
statusText.style.color = '#F44336';
|
||||||
|
showStatus(`Upgrade failed: ${result.error}`);
|
||||||
|
upgradeBtn.disabled = false;
|
||||||
|
checkBtn.disabled = false;
|
||||||
|
versionSelect.disabled = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Upgrade error:', error);
|
||||||
|
statusText.textContent = `Error: ${error.message}`;
|
||||||
|
statusText.style.color = '#F44336';
|
||||||
|
statusText.style.display = 'block';
|
||||||
|
showStatus(`Upgrade error: ${error.message}`);
|
||||||
|
upgradeBtn.disabled = false;
|
||||||
|
checkBtn.disabled = false;
|
||||||
|
versionSelect.disabled = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkBtn.addEventListener('click', checkVersions);
|
||||||
|
upgradeBtn.addEventListener('click', handleUpgrade);
|
||||||
|
|
||||||
|
versionSelect.addEventListener('change', () => {
|
||||||
|
// Reset UI when build type changes
|
||||||
|
versionInfo.style.display = 'none';
|
||||||
|
upgradeBtn.style.display = 'none';
|
||||||
|
upgradeBtn.disabled = true;
|
||||||
|
statusText.style.display = 'none';
|
||||||
|
loadingSpan.style.display = 'block';
|
||||||
|
availableVersion = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-refresh about tab and electron versions when this section comes into view
|
||||||
|
const aboutTabBtn = document.getElementById('tab-about');
|
||||||
|
if (aboutTabBtn) {
|
||||||
|
aboutTabBtn.addEventListener('click', () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Refresh about info when About tab is clicked
|
||||||
|
populateAbout();
|
||||||
|
// Also refresh electron versions display
|
||||||
|
checkVersions();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to compare semantic versions
|
||||||
|
function compareVersions(v1, v2) {
|
||||||
|
const parts1 = v1.split('-')[0].split('.').map(x => parseInt(x, 10));
|
||||||
|
const parts2 = v2.split('-')[0].split('.').map(x => parseInt(x, 10));
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
||||||
|
const p1 = parts1[i] || 0;
|
||||||
|
const p2 = parts2[i] || 0;
|
||||||
|
if (p1 > p2) return 1;
|
||||||
|
if (p1 < p2) return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Keep settings open when clicking GitHub by asking host to open externally/new tab
|
// Keep settings open when clicking GitHub by asking host to open externally/new tab
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
const gh = document.getElementById('github-link');
|
const gh = document.getElementById('github-link');
|
||||||
|
|||||||
Reference in New Issue
Block a user