Skip to content

Commit e3fd707

Browse files
committed
feat: add support to uninstall deprected extensions as the dialog comes up itself
1 parent 62bc331 commit e3fd707

File tree

4 files changed

+85
-15
lines changed

4 files changed

+85
-15
lines changed

src/htmlContent/deprecated-extensions-dialog.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ <h1 class="dialog-title">{{Strings.DEPRECATED_EXTENSIONS_TITLE}}</h1>
1111
<div style="flex: 1;">
1212
<div class="extension-info" style="margin-bottom: 6px;">
1313
<i class="fa fa-exclamation-triangle" style="color: #f5a623; margin-right: 8px;"></i>
14-
<strong>{{id}}</strong>
14+
<strong>{{name}}</strong>
1515
</div>
1616
<div class="extension-alternative" style="margin-left: 24px;">
1717
<a href="{{docUrl}}" target="_blank" rel="noopener">

src/nls/root/strings.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,8 @@ define({
804804
"EXTENSION_VERIFIED_SORT": "Verified",
805805
"EXTENSION_STAR": "Star",
806806
"EXTENSION_STAR_SORT": "Star Rating",
807+
"ERROR_UNINSTALLING_EXTENSION_TITLE": "Error Uninstalling Extension",
808+
"ERROR_UNINSTALLING_EXTENSION_MESSAGE": "Failed to uninstall extension {0}",
807809
// For NOT_FOUND_ERR, see generic strings above
808810
"EXTENSION_MANAGER_TITLE": "Extension Manager",
809811
"EXTENSION_MANAGER_ERROR_LOAD": "Unable to access the extension registry. Please try again later.",
@@ -1171,10 +1173,12 @@ define({
11711173
"DOWNLOAD_ERROR": "Error occurred while downloading.",
11721174
"NETWORK_SLOW_OR_DISCONNECTED": "Network is disconnected or too slow.",
11731175
"RESTART_BUTTON": "Restart",
1176+
"RESTART_APP_BUTTON": "Restart {APP_NAME}",
11741177
"LATER_BUTTON": "Later",
11751178
"DESCRIPTION_AUTO_UPDATE": "Enable/disable {APP_NAME} Auto-update",
11761179
"AUTOUPDATE_ERROR": "Error!",
11771180
"AUTOUPDATE_IN_PROGRESS": "An update is already in progress.",
1181+
"REMOVING": "Removing\u2026",
11781182

11791183
"NUMBER_WITH_PERCENTAGE": "{0}%",
11801184
// Strings for Related Files

src/styles/brackets_patterns_override.less

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2619,3 +2619,7 @@ code {
26192619
margin-top: 2px;
26202620
}
26212621
}
2622+
2623+
.striked {
2624+
text-decoration: line-through;
2625+
}

src/utils/ExtensionLoader.js

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ define(function (require, exports, module) {
5959
PreferencesManager = require("preferences/PreferencesManager"),
6060
Mustache = require("thirdparty/mustache/mustache"),
6161
Strings = require("strings"),
62-
DeprecatedExtensionsTemplate = require("text!htmlContent/deprecated-extensions-dialog.html");
62+
StringUtils = require("utils/StringUtils"),
63+
Metrics = require("utils/Metrics"),
64+
DeprecatedExtensionsTemplate = require("text!htmlContent/deprecated-extensions-dialog.html"),
65+
CommandManager = require("command/CommandManager");
6366

6467
// takedown/dont load extensions that are compromised at app start - start
6568
const EXTENSION_TAKEDOWN_LOCALSTORAGE_KEY = "PH_EXTENSION_TAKEDOWN_LIST";
@@ -82,7 +85,7 @@ define(function (require, exports, module) {
8285
return [];
8386
}
8487

85-
const loadedExtensionIDs = new Set();
88+
const loadedExtensionIDs = new Map();
8689
let takedownExtensionList = new Set(_getTakedownListLS());
8790

8891
const EXTENSION_TAKEDOWN_URL = brackets.config.extensionTakedownURL;
@@ -96,16 +99,16 @@ define(function (require, exports, module) {
9699

97100
if (takedownExtensionList.size < loadedExtensionIDs.size) {
98101
smaller = takedownExtensionList;
99-
larger = loadedExtensionIDs;
102+
larger = Array.from(loadedExtensionIDs.keys());
100103
} else {
101-
smaller = loadedExtensionIDs;
104+
smaller = Array.from(loadedExtensionIDs.keys());
102105
larger = takedownExtensionList;
103106
}
104107

105108
const matches = [];
106109

107110
for (const id of smaller) {
108-
if (larger.has(id)) {
111+
if (larger.has ? larger.has(id) : larger.includes(id)) {
109112
matches.push(id);
110113
}
111114
}
@@ -521,7 +524,11 @@ define(function (require, exports, module) {
521524
}
522525

523526
if(metadata.name) {
524-
loadedExtensionIDs.add(metadata.name);
527+
loadedExtensionIDs.set(metadata.name, {
528+
loadedFromDisc: !!config.nativeDir,
529+
extensionPath: config.nativeDir || config.baseUrl,
530+
extensionName: metadata.title || metadata.name
531+
});
525532
}
526533

527534
// No special handling for themes... Let the promise propagate into the ExtensionManager
@@ -1045,10 +1052,21 @@ define(function (require, exports, module) {
10451052
/**
10461053
* Uninstall a deprecated extension
10471054
* @param {string} extensionID - The ID of the extension to uninstall
1055+
* @return {Promise} A promise that resolves when the extension is uninstalled successfully
10481056
*/
1049-
function uninstallExtension(extensionID) {
1050-
// TODO: Implement uninstall logic
1051-
alert(`Uninstall button clicked for extension: ${extensionID}`);
1057+
async function uninstallExtension(extensionID) {
1058+
const extensionInfo = loadedExtensionIDs.get(extensionID);
1059+
1060+
if (!extensionInfo) {
1061+
throw new Error(`Extension ${extensionID} not found in loaded extensions`);
1062+
}
1063+
1064+
if (!extensionInfo.loadedFromDisc) {
1065+
throw new Error(`Cannot uninstall built-in extension: ${extensionID}`);
1066+
}
1067+
1068+
const extensionDir = FileSystem.getDirectoryForPath(extensionInfo.extensionPath);
1069+
await extensionDir.unlinkAsync();
10521070
}
10531071

10541072
/**
@@ -1057,6 +1075,7 @@ define(function (require, exports, module) {
10571075
*/
10581076
function _checkAndShowDeprecatedExtensionsDialog() {
10591077
// Get deprecated extensions config
1078+
let needsRestart = false;
10601079
const deprecatedExtensionsConfig = DefaultExtensions.deprecatedExtensions;
10611080
if (!deprecatedExtensionsConfig || !deprecatedExtensionsConfig.extensionIDsAndDocs) {
10621081
return;
@@ -1072,10 +1091,12 @@ define(function (require, exports, module) {
10721091

10731092
// Check which deprecated extensions are loaded and not yet shown
10741093
const deprecatedExtensionsFound = [];
1075-
for (const extensionID of loadedExtensionIDs) {
1094+
for (const extensionID of loadedExtensionIDs.keys()) {
10761095
if (deprecatedExtensionIDs[extensionID] && !shownDeprecatedExtensions[extensionID]) {
1096+
const extensionInfo = loadedExtensionIDs.get(extensionID);
10771097
deprecatedExtensionsFound.push({
10781098
id: extensionID,
1099+
name: extensionInfo?.extensionName || extensionID,
10791100
docUrl: deprecatedExtensionIDs[extensionID]
10801101
});
10811102
}
@@ -1093,12 +1114,53 @@ define(function (require, exports, module) {
10931114
};
10941115

10951116
const $template = $(Mustache.render(DeprecatedExtensionsTemplate, templateVars));
1096-
Dialogs.showModalDialogUsingTemplate($template);
1117+
const dialog = Dialogs.showModalDialogUsingTemplate($template, false); // autoDismiss = false
10971118

10981119
// Wire up uninstall button click handlers
1099-
$template.on('click', '.uninstall-extension-btn', function() {
1100-
const extensionID = $(this).data('extension-id');
1101-
uninstallExtension(extensionID);
1120+
$template.on('click', '.uninstall-extension-btn', async function() {
1121+
const $button = $(this);
1122+
const extensionID = $button.data('extension-id');
1123+
const $extensionItem = $button.closest('.deprecated-extension-item');
1124+
1125+
// Disable button during uninstall
1126+
$button.prop('disabled', true);
1127+
$button.text(Strings.REMOVING);
1128+
1129+
try {
1130+
Metrics.countEvent(Metrics.EVENT_TYPE.EXTENSIONS, "removeDep", extensionID);
1131+
await uninstallExtension(extensionID);
1132+
1133+
// Update the OK button to "Restart App"
1134+
const $okButton = $template.find('[data-button-id="ok"]');
1135+
$okButton.text(Strings.RESTART_APP_BUTTON);
1136+
1137+
// Strike through the extension name and disable/strike the uninstall button
1138+
$extensionItem.find('.extension-info strong').addClass('striked');
1139+
$button.remove();
1140+
needsRestart = true;
1141+
} catch (err) {
1142+
Metrics.countEvent(Metrics.EVENT_TYPE.EXTENSIONS, "removeDep", "fail");
1143+
logger.reportError(err, 'Failed to uninstall deprecated extension:' + extensionID);
1144+
1145+
// Show error dialog
1146+
const message = StringUtils.format(Strings.ERROR_UNINSTALLING_EXTENSION_MESSAGE, extensionID);
1147+
Dialogs.showErrorDialog(Strings.ERROR_UNINSTALLING_EXTENSION_TITLE, message);
1148+
1149+
// Re-enable button
1150+
$button.prop('disabled', false);
1151+
$button.text(Strings.REMOVE);
1152+
}
1153+
});
1154+
1155+
// Handle OK button click
1156+
$template.on('click', '[data-button-id="ok"]', function() {
1157+
if (needsRestart) {
1158+
// Reload the app to complete uninstallation
1159+
CommandManager.execute("debug.refreshWindow");
1160+
} else {
1161+
// Just close the dialog
1162+
dialog.close();
1163+
}
11021164
});
11031165

11041166
// Mark each extension as shown

0 commit comments

Comments
 (0)