@@ -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